Skip to content

Commit 9305344

Browse files
authored
Merge branch 'next' into rememberMeDuration
2 parents 48067fd + 2847309 commit 9305344

File tree

43 files changed

+2099
-1751
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2099
-1751
lines changed

adminforth/commands/createResource/templates/resource.ts.hbs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ export default {
1414
components: {
1515
list: "@/renderers/CompactUUID.vue",
1616
}{{/if}},
17+
showIn: {
18+
all:true,
19+
}
1720
}{{#unless @last}},{{/unless}}
1821
{{/each}}
1922
],

adminforth/dataConnectors/postgres.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,8 @@ class PostgresConnector extends AdminForthBaseConnector implements IAdminForthDa
400400
}
401401
const primaryKey = this.getPrimaryKey(resource);
402402
const q = `INSERT INTO "${tableName}" (${columns.join(', ')}) VALUES (${placeholders}) RETURNING "${primaryKey}"`;
403+
// console.log('\n🔵 [PG INSERT]:', q);
404+
// console.log('📦 [VALUES]:', JSON.stringify(values, null, 2));
403405
if (process.env.HEAVY_DEBUG_QUERY) {
404406
console.log('🪲📜 PG Q:', q, 'values:', values);
405407
}

adminforth/dataConnectors/sqlite.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,12 @@ class SQLiteConnector extends AdminForthBaseConnector implements IAdminForthData
143143
} else {
144144
return value;
145145
}
146+
}
147+
else if (field.isArray?.enabled) {
148+
if (value === null || value === undefined) {
149+
return null;
150+
}
151+
return JSON.stringify(value);
146152
} else if (field.type == AdminForthDataTypes.BOOLEAN) {
147153
return value === null ? null : (value ? 1 : 0);
148154
} else if (field.type == AdminForthDataTypes.JSON) {
@@ -322,7 +328,14 @@ class SQLiteConnector extends AdminForthBaseConnector implements IAdminForthData
322328
const columns = Object.keys(record);
323329
const placeholders = columns.map(() => '?').join(', ');
324330
const values = columns.map((colName) => record[colName]);
325-
const q = this.client.prepare(`INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`);
331+
// const q = this.client.prepare(`INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`);
332+
const sql = `INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`;
333+
//console.log('\n🟢 [SQLITE INSERT]:', sql);
334+
//console.log('📦 [VALUES]:', JSON.stringify(values, null, 2));
335+
const q = this.client.prepare(sql);
336+
if (process.env.HEAVY_DEBUG_QUERY) {
337+
console.log('🪲📜 SQL Q:', q, 'values:', values);
338+
}
326339
const ret = await q.run(values);
327340
return ret.lastInsertRowid;
328341
}

adminforth/documentation/docs/tutorial/03-Customization/08-pageInjections.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,79 @@ cd custom
396396
npm i @iconify-prerendered/vue-mdi
397397
```
398398
399+
## List table row replace injection
400+
401+
`tableRowReplace` lets you fully control how each list table row is rendered. Instead of the default table `<tr></tr>` markup, AdminForth will mount your Vue component per record and use its returned DOM to display the row. Use this when you need custom row layouts, extra controls, or conditional styling that goes beyond column-level customization.
402+
403+
Supported forms:
404+
- Single component: `pageInjections.list.tableRowReplace = '@@/MyRowRenderer.vue'`
405+
- Object form with meta: `pageInjections.list.tableRowReplace = { file: '@@/MyRowRenderer.vue', meta: { /* optional */ } }`
406+
- If an array is provided, the first element is used.
407+
408+
Example configuration:
409+
410+
```ts title="/resources/apartments.ts"
411+
{
412+
resourceId: 'aparts',
413+
...
414+
options: {
415+
pageInjections: {
416+
list: {
417+
tableRowReplace: {
418+
file: '@@/ApartRowRenderer.vue',
419+
meta: {
420+
// You can pass any meta your component may read
421+
}
422+
}
423+
}
424+
}
425+
}
426+
}
427+
```
428+
429+
Minimal component example (decorate default row with a border):
430+
431+
```vue title="/custom/ApartRowRenderer.vue"
432+
<template>
433+
<tr class="border border-gray-200 dark:border-gray-700 rounded-sm">
434+
<slot />
435+
</tr>
436+
437+
</template>
438+
439+
<script setup lang="ts">
440+
import { computed } from 'vue';
441+
const props = defineProps<{
442+
record: any
443+
resource: any
444+
meta: any
445+
adminUser: any
446+
}>();
447+
</script>
448+
```
449+
450+
Component contract:
451+
- Inputs
452+
- `record`: the current record object
453+
- `resource`: the resource config object
454+
- `meta`: the meta object passed in the injection config
455+
- Slots
456+
- Default slot: the table’s standard row content (cells) will be projected here. Your component can wrap or style it.
457+
- Output
458+
- Render a full `<tr></tr>` fragment. For example, to replace the standard set of cells with a single full‑width cell, render:
459+
460+
```vue
461+
<tr>
462+
<td :colspan="columnsCount">
463+
<slot />
464+
</td>
465+
</tr>
466+
```
467+
468+
Notes and tips:
469+
- Requirements:
470+
- Required `<tr></tr>` structure around `<slot />`
471+
399472
## List table beforeActionButtons
400473
401474
`beforeActionButtons` allows injecting one or more compact components into the header bar of the list page, directly to the left of the default action buttons (`Create`, `Filter`, bulk actions, three‑dots menu). Use it for small inputs (quick search, toggle, status chip) rather than large panels.

adminforth/documentation/docs/tutorial/03-Customization/10-menuConfiguration.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Menu Configuration
1+
# Menu & Header
22

33

44
## Icons
@@ -253,3 +253,26 @@ import adminforth from '@/adminforth';
253253
adminforth.menu.refreshMenuBadges()
254254
```
255255

256+
## Avatars
257+
258+
If you want your user to have custom avatar you can use avatarUrl:
259+
260+
```ts title='./index.ts'
261+
262+
auth: {
263+
264+
...
265+
266+
avatarUrl: async (adminUser)=> {
267+
return `https://${process.env.STORAGE_PROVIDER_PATH}/${adminUser.dbUser.avatar_path}`
268+
},
269+
270+
...
271+
272+
}
273+
274+
```
275+
276+
This syntax can be use to get unique avatar for each user of hardcode avatar, but it makes more sense to use it with [upload plugin](https://adminforth.dev/docs/tutorial/Plugins/upload/#using-plugin-for-uploading-avatar)
277+
278+

adminforth/documentation/docs/tutorial/03-Customization/15-afcl.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,26 @@ function onBeforeClose() {
759759
}
760760
```
761761

762+
### Ask for extra confirmation before close
763+
764+
If you want dialog to ask user for conformation when he tryes to close dialog, you can add:
765+
766+
```ts
767+
<Dialog
768+
ref="confirmDialog"
769+
class="w-96"
770+
//diff-add
771+
:closable="false"
772+
//diff-add
773+
:askForCloseConfirmation="true"
774+
//diff-add
775+
closeConfirmationText='Are you sure you want to close this dialog?',
776+
>
777+
<div class="space-y-4">
778+
The dialog content goes here.
779+
</div>
780+
</Dialog>
781+
```
762782

763783
## Dropzone
764784

adminforth/documentation/docs/tutorial/07-Plugins/04-RichEditor.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -221,18 +221,13 @@ export default {
221221
"exe",
222222
"webp",
223223
],
224-
maxFileSize: 1024 * 1024 * 20, // 5MB
224+
maxFileSize: 1024 * 1024 * 20, // 20MB
225225

226226

227227
filePath: ({ originalFilename, originalExtension, contentType }) =>
228228
`description_images/${new Date().getFullYear()}/${uuid()}/${originalFilename}.${originalExtension}`,
229229

230-
preview: {
231-
// Used to display preview (if it is image) in list and show views instead of just path
232-
// previewUrl: ({s3Path}) => `https://tmpbucket-adminforth.s3.eu-central-1.amazonaws.com/${s3Path}`,
233-
// show image preview instead of path in list view
234-
// showInList: false,
235-
},
230+
236231
}),
237232
],
238233
} as AdminForthResourceInput;

adminforth/documentation/docs/tutorial/07-Plugins/05-upload.md

Lines changed: 154 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ Leave all settings unchanged (ACL Disabled, Block all public access - checked)
3131
],
3232
"AllowedMethods": [
3333
"HEAD",
34-
"PUT"
34+
"PUT",
35+
"GET"
3536
],
3637
"AllowedOrigins": [
3738
"http://localhost:3500"
@@ -507,4 +508,155 @@ You can set the maximum width for the preview image in the `./resources/apartmen
507508
...
508509

509510
});
510-
```
511+
```
512+
513+
## Using plugin for uploading avatar
514+
515+
Let's say, that you want to use upload plugin for uploading avatar for each user.
516+
To do this add avatar column to the user resource:
517+
518+
519+
```ts title="./schema.prisma"
520+
model adminuser {
521+
id String @id
522+
email String @unique
523+
password_hash String
524+
role String
525+
created_at DateTime
526+
//diff-add
527+
avatar ?String
528+
}
529+
```
530+
531+
Then make migration:
532+
533+
```bash
534+
npm run makemigration -- --name add-user-avatar-field ; npm run migrate:local
535+
```
536+
537+
Add this column to the users resource:
538+
539+
```ts title="./resources/adminuser.ts"
540+
541+
...
542+
543+
columns: [
544+
545+
...
546+
547+
//diff-add
548+
{
549+
//diff-add
550+
name: "avatar",
551+
//diff-add
552+
type: AdminForthDataTypes.STRING,
553+
//diff-add
554+
showIn: ["show", "edit", "create" ],
555+
//diff-add
556+
},
557+
558+
...
559+
560+
]
561+
562+
...
563+
564+
plugins: [
565+
566+
...
567+
568+
//diff-add
569+
new UploadPlugin({
570+
//diff-add
571+
pathColumnName: "avatar",
572+
//diff-add
573+
storageAdapter: new AdminForthAdapterS3Storage({
574+
//diff-add
575+
bucket: process.env.AWS_BUCKET_NAME,
576+
//diff-add
577+
region: process.env.AWS_REGION,
578+
//diff-add
579+
accessKeyId: process.env.AWS_ACCESS_KEY_ID as string,
580+
//diff-add
581+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string,
582+
//diff-add
583+
}),
584+
//diff-add
585+
allowedFileExtensions: [
586+
//diff-add
587+
"jpg",
588+
//diff-add
589+
"jpeg",
590+
//diff-add
591+
"png",
592+
//diff-add
593+
"gif",
594+
//diff-add
595+
"webm",
596+
//diff-add
597+
"exe",
598+
//diff-add
599+
"webp",
600+
//diff-add
601+
],
602+
//diff-add
603+
maxFileSize: 1024 * 1024 * 20, // 20MB
604+
//diff-add
605+
filePath: ({ originalFilename, originalExtension, contentType, record }) => {
606+
//diff-add
607+
return `aparts/${new Date().getFullYear()}/${originalFilename}.${originalExtension}`
608+
//diff-add
609+
},
610+
//diff-add
611+
preview: {
612+
//diff-add
613+
maxWidth: "200px",
614+
//diff-add
615+
},
616+
//diff-add
617+
}),
618+
619+
...
620+
621+
]
622+
623+
```
624+
625+
And finally add this callback:
626+
627+
```ts title="./index.ts"
628+
629+
auth: {
630+
631+
...
632+
//diff-add
633+
avatarUrl: async (adminUser)=>{
634+
//diff-add
635+
const plugin = admin.getPluginsByClassName('UploadPlugin').find(p => p.pluginOptions.pathColumnName === 'avatar') as any;
636+
//diff-add
637+
if (!plugin) {
638+
//diff-add
639+
throw new Error('Upload plugin for avatar not found');
640+
//diff-add
641+
}
642+
//diff-add
643+
if (adminUser.dbUser.avatar === null || adminUser.dbUser.avatar === undefined || adminUser.dbUser.avatar === '') {
644+
//diff-add
645+
return '';
646+
//diff-add
647+
}
648+
//diff-add
649+
const imageUrl = await plugin.getFileDownloadUrl(adminUser.dbUser.avatar || '', 3600);
650+
//diff-add
651+
return imageUrl;
652+
//diff-add
653+
},
654+
655+
656+
...
657+
658+
}
659+
660+
```
661+
662+
And now you can easily update avatar for each user

0 commit comments

Comments
 (0)