Skip to content

Commit 6258d4d

Browse files
committed
Merge branch 'next' of github.com:devforth/adminforth into next
2 parents b52eead + 710445f commit 6258d4d

File tree

24 files changed

+500
-98
lines changed

24 files changed

+500
-98
lines changed

adminforth/commands/createCustomComponent/configUpdater.js

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,27 @@ async function findResourceFilePath(resourceId) {
4747

4848
if (n.TSAsExpression.check(declaration) && n.ObjectExpression.check(declaration.expression)) {
4949
objectExpressionNode = declaration.expression;
50-
}
51-
else if (n.ObjectExpression.check(declaration)) {
50+
} else if (n.ObjectExpression.check(declaration)) {
5251
objectExpressionNode = declaration;
52+
} else if (n.Identifier.check(declaration)) {
53+
const varName = declaration.name;
54+
55+
recast.visit(ast, {
56+
visitVariableDeclaration(path) {
57+
for (const decl of path.node.declarations) {
58+
if (
59+
n.VariableDeclarator.check(decl) &&
60+
n.Identifier.check(decl.id) &&
61+
decl.id.name === varName &&
62+
n.ObjectExpression.check(decl.init)
63+
) {
64+
objectExpressionNode = decl.init;
65+
return false;
66+
}
67+
}
68+
this.traverse(path);
69+
}
70+
});
5371
}
5472

5573
if (objectExpressionNode) {
@@ -118,6 +136,25 @@ export async function updateResourceConfig(resourceId, columnName, fieldType, co
118136
objectExpressionNode = declaration.expression;
119137
} else if (n.ObjectExpression.check(declaration)) {
120138
objectExpressionNode = declaration;
139+
} else if (n.Identifier.check(declaration)) {
140+
const varName = declaration.name;
141+
142+
recast.visit(ast, {
143+
visitVariableDeclaration(path) {
144+
for (const decl of path.node.declarations) {
145+
if (
146+
n.VariableDeclarator.check(decl) &&
147+
n.Identifier.check(decl.id) &&
148+
decl.id.name === varName &&
149+
n.ObjectExpression.check(decl.init)
150+
) {
151+
objectExpressionNode = decl.init;
152+
return false;
153+
}
154+
}
155+
this.traverse(path);
156+
}
157+
});
121158
}
122159

123160
if (!objectExpressionNode) {
@@ -446,9 +483,28 @@ export async function updateCrudInjectionConfig(resourceId, crudType, injectionP
446483
let objectExpressionNode = null;
447484

448485
if (n.TSAsExpression.check(declaration) && n.ObjectExpression.check(declaration.expression)) {
449-
objectExpressionNode = declaration.expression;
486+
objectExpressionNode = declaration.expression;
450487
} else if (n.ObjectExpression.check(declaration)) {
451-
objectExpressionNode = declaration;
488+
objectExpressionNode = declaration;
489+
} else if (n.Identifier.check(declaration)) {
490+
const varName = declaration.name;
491+
492+
recast.visit(ast, {
493+
visitVariableDeclaration(path) {
494+
for (const decl of path.node.declarations) {
495+
if (
496+
n.VariableDeclarator.check(decl) &&
497+
n.Identifier.check(decl.id) &&
498+
decl.id.name === varName &&
499+
n.ObjectExpression.check(decl.init)
500+
) {
501+
objectExpressionNode = decl.init;
502+
return false;
503+
}
504+
}
505+
this.traverse(path);
506+
}
507+
});
452508
}
453509

454510
if (!objectExpressionNode) {

adminforth/commands/createCustomComponent/main.js

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,26 @@ async function handleFieldComponentCreation(config, resources) {
5858

5959
console.log(chalk.grey(`Selected ❯ 🔤 Custom fields ❯ ${fieldType}`));
6060

61-
const resourceId = await select({
62-
message: 'Select resource for which you want to change component:',
63-
choices: [
64-
...resources.map(r => ({ name: `${r.label} ${chalk.grey(`${r.resourceId}`)}`, value: r.resourceId })),
65-
new Separator(),
66-
{ name: '🔙 BACK', value: '__BACK__' },
67-
]
61+
const resourceId = await search({
62+
message: 'Select resource for which you want to change component:',
63+
source: async (input) => {
64+
const searchTerm = input ? input.toLowerCase() : '';
65+
66+
const filtered = resources.filter(r => {
67+
const label = r.label || '';
68+
const id = r.resourceId || '';
69+
return label.toLowerCase().includes(searchTerm) || id.toLowerCase().includes(searchTerm);
70+
});
71+
72+
return [
73+
...filtered.map(r => ({
74+
name: `${r.label} ${chalk.grey(`${r.resourceId}`)}`,
75+
value: r.resourceId,
76+
})),
77+
new Separator(),
78+
{ name: '🔙 BACK', value: '__BACK__' },
79+
];
80+
}
6881
});
6982
if (resourceId === '__BACK__') return handleFieldComponentCreation(config, resources); // Pass config back
7083

@@ -144,14 +157,28 @@ async function handleCrudPageInjectionCreation(config, resources) {
144157

145158
console.log(chalk.grey(`Selected ❯ 📄 CRUD Page Injection ❯ ${crudType}`));
146159

147-
const resourceId = await select({
160+
const resourceId = await search({
148161
message: 'Select resource for which you want to inject the component:',
149-
choices: [
150-
...resources.map(r => ({ name: `${r.label} ${chalk.grey(`${r.resourceId}`)}`, value: r.resourceId })),
151-
new Separator(),
152-
{ name: '🔙 BACK', value: '__BACK__' },
153-
],
162+
source: async (input) => {
163+
const searchTerm = input ? input.toLowerCase() : '';
164+
165+
const filtered = resources.filter(r => {
166+
const label = r.label || '';
167+
const id = r.resourceId || '';
168+
return label.toLowerCase().includes(searchTerm) || id.toLowerCase().includes(searchTerm);
169+
});
170+
171+
return [
172+
...filtered.map(r => ({
173+
name: `${r.label} ${chalk.grey(`${r.resourceId}`)}`,
174+
value: r.resourceId,
175+
})),
176+
new Separator(),
177+
{ name: '🔙 BACK', value: '__BACK__' },
178+
];
179+
}
154180
});
181+
155182
if (resourceId === '__BACK__') return handleCrudPageInjectionCreation(config, resources);
156183

157184
const selectedResource = resources.find(r => r.resourceId === resourceId);

adminforth/dataConnectors/clickhouse.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,10 @@ class ClickhouseConnector extends AdminForthBaseConnector implements IAdminForth
207207
const column = resource.dataSourceColumns.find((col) => col.name == field);
208208
let placeholder = `{f$?:${column._underlineType}}`;
209209
let operator = this.OperatorsMap[filter.operator];
210+
if ((filter.operator == AdminForthFilterOperators.LIKE || filter.operator == AdminForthFilterOperators.ILIKE) && column._underlineType == 'UUID') {
211+
placeholder = '{f$?:String}';
212+
field = `toString(${field})`;
213+
}
210214
if (filter.operator == AdminForthFilterOperators.IN || filter.operator == AdminForthFilterOperators.NIN) {
211215
placeholder = `(${filter.value.map((_, j) => `{p$?:${column._underlineType}}`).join(', ')})`;
212216
} else if (filter.operator == AdminForthFilterOperators.EQ && filter.value === null) {

adminforth/documentation/blog/2024-10-01-ai-blog/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -736,12 +736,12 @@ $grColor5: #695BE9;
736736
-moz-osx-font-smoothing: grayscale;
737737
// gradient with color spots
738738
animation: gradient 15s ease infinite;
739-
min-height: 100vh;
739+
min-height: 100dvh;
740740
}
741741
body {
742742
margin: 0;
743743
padding: 0;
744-
max-height: 100vh;
744+
max-height: 100dvh;
745745
overflow: overlay;
746746
background-image: radial-gradient(
747747
circle farthest-corner at top left, $grColor1 0%, rgba(225, 243, 97,0) 50%),

adminforth/documentation/docs/tutorial/03-Customization/13-standardPagesTuning.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ export default {
138138

139139
### Virtual scroll
140140

141-
Set `options.listVirtualScrollEnabled` to true to enable virtual scrolling in the table
141+
Set `options.listVirtualScrollEnabled` to true to enable virtual scrolling in the table. The default value is false. Enable this option if you need to display a large number of records on a single page.
142142

143143
```typescript title="./resources/apartments.ts"
144144
export default {
@@ -151,6 +151,20 @@ export default {
151151
}
152152
]
153153
```
154+
Additionally, you can configure `options.listBufferSize` to specify the number of rows to buffer for virtual scrolling. The default value is 30 rows.
155+
156+
```typescript title="./resources/apartments.ts"
157+
export default {
158+
resourceId: 'aparts',
159+
options: {
160+
...
161+
listVirtualScrollEnabled: true,
162+
//diff-add
163+
listBufferSize: 20,
164+
}
165+
}
166+
]
167+
```
154168

155169
### Custom row click action
156170

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

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,22 @@ Spinner component is used to display a loading state for a component.
861861
</div>
862862
</div>
863863

864+
## Country Flag
865+
866+
The Country Flag component displays national flags using ISO 3166-1 alpha-2 country codes.
867+
868+
<div class="split-screen" >
869+
<div>
870+
```html
871+
<CountryFlag countryCode="ua" />
872+
```
873+
</div>
874+
<div>
875+
![Country Flag](image-92.png)
876+
</div>
877+
</div>
878+
879+
864880
## Bar Chart
865881

866882
Under the hood AdminForth uses MIT-licensed [ApexCharts](https://apexcharts.com/). It has very rich variety of options, you can pass
@@ -996,6 +1012,7 @@ any of native settings to `options` prop. Here we will only show some basics.
9961012
}
9971013
//diff-add
9981014
}
1015+
//diff-add
9991016
}
10001017
}"
10011018
/>
@@ -1453,7 +1470,7 @@ import { PieChart } from '@/afcl'
14531470
//diff-add
14541471
offset: -10, // Moves labels closer to or further from the slices
14551472
//diff-add
1456-
minAngleToShowLabel: 10, // Ensures that small slices dont show labels
1473+
minAngleToShowLabel: 10, // Ensures that small slices don't show labels
14571474
//diff-add
14581475
},
14591476
//diff-add
@@ -1685,7 +1702,3 @@ import { MixedChart } from '@/afcl'
16851702
</div>
16861703
</div>
16871704

1688-
1689-
1690-
1691-
Loading

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

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,113 @@ To get completion suggestions for the text in the editor, you can use the `compl
144144
```
145145

146146
![alt text](gptDemo.gif)
147+
148+
### Imges in Rich editor
149+
150+
First, you need to create resource for images:
151+
```prisma title="schema.prisma"
152+
model description_image {
153+
id String @id
154+
created_at DateTime
155+
resource_id String
156+
record_id String
157+
image_path String
158+
}
159+
```
160+
161+
```bash
162+
npm run makemigration -- --name add_description_image
163+
```
164+
165+
```bash
166+
npm i @adminforth/upload --save
167+
npm i @adminforth/storage-adapter-local --save
168+
```
169+
170+
```typescript title="./resources/description_image.ts"
171+
import AdminForthStorageAdapterLocalFilesystem from "../../adapters/adminforth-storage-adapter-local";
172+
import { AdminForthResourceInput } from "../../adminforth";
173+
import UploadPlugin from "../../plugins/adminforth-upload";
174+
import { v1 as uuid } from "uuid";
175+
176+
export default {
177+
dataSource: "maindb",
178+
table: "description_image",
179+
resourceId: "description_images",
180+
label: "Description images",
181+
columns: [
182+
{
183+
name: "id",
184+
primaryKey: true,
185+
required: false,
186+
fillOnCreate: ({ initialRecord }: any) => uuid(),
187+
showIn: {
188+
create: false,
189+
},
190+
},
191+
{
192+
name: "created_at",
193+
required: false,
194+
fillOnCreate: ({ initialRecord }: any) => new Date().toISOString(),
195+
showIn: {
196+
create: false,
197+
},
198+
},
199+
{ name: "resource_id", required: false },
200+
{ name: "record_id", required: false },
201+
{ name: "image_path", required: false },
202+
],
203+
plugins: [
204+
new UploadPlugin({
205+
pathColumnName: "image_path",
206+
207+
// rich editor plugin supports only 'public-read' ACL images for SEO purposes (instead of presigned URLs which change every time)
208+
storageAdapter: new AdminForthStorageAdapterLocalFilesystem({
209+
fileSystemFolder: "./db/uploads/description_images", // folder where files will be stored on disk
210+
adminServeBaseUrl: "static/source", // the adapter not only stores files, but also serves them for HTTP requests
211+
mode: "public", // public if all files should be accessible from the web, private only if could be accesed by temporary presigned links
212+
signingSecret: process.env.ADMINFORTH_SECRET, // secret used to generate presigned URLs
213+
}),
214+
215+
allowedFileExtensions: [
216+
"jpg",
217+
"jpeg",
218+
"png",
219+
"gif",
220+
"webm",
221+
"exe",
222+
"webp",
223+
],
224+
maxFileSize: 1024 * 1024 * 20, // 5MB
225+
226+
227+
filePath: ({ originalFilename, originalExtension, contentType }) =>
228+
`description_images/${new Date().getFullYear()}/${uuid()}/${originalFilename}.${originalExtension}`,
229+
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+
},
236+
}),
237+
],
238+
} as AdminForthResourceInput;
239+
```
240+
241+
Next, add attachments to RichEditor plugin:
242+
243+
```typescript title="./resources/apartments.ts"
244+
import RichEditorPlugin from '@adminforth/rich-editor';
245+
246+
// ... existing resource configuration ...
247+
248+
new RichEditorPlugin({
249+
htmlFieldName: 'description',
250+
attachments: {
251+
attachmentResource: "description_images",
252+
attachmentFieldName: "image_path",
253+
attachmentRecordIdFieldName: "record_id",
254+
attachmentResourceIdFieldName: "resource_id",
255+
}
256+
})

0 commit comments

Comments
 (0)