@meeovi/directus-client 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/createClient.d.ts +3 -2
- package/dist/client/createClient.js +1 -1
- package/dist/generators/form-engine.d.ts +11 -13
- package/dist/generators/form-engine.js +20 -61
- package/dist/generators/widget-registry.d.ts +9 -17
- package/dist/generators/widget-registry.js +50 -3
- package/package.json +1 -1
- package/src/client/createClient.ts +5 -4
- package/src/generators/form-engine.ts +122 -11
- package/tsconfig.json +3 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readItem, readItems, createItem, updateItem, deleteItem, uploadFiles, readSingleton, readFieldsByCollection, type DirectusClient } from '@directus/sdk';
|
|
2
2
|
export interface MeeoviDirectusClient<Schema> {
|
|
3
|
-
client:
|
|
3
|
+
client: DirectusClient<Schema>;
|
|
4
|
+
request: any;
|
|
4
5
|
readItem: typeof readItem;
|
|
5
6
|
readItems: typeof readItems;
|
|
6
7
|
createItem: typeof createItem;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// src/client/createClient.ts
|
|
2
1
|
import { createDirectus, rest, authentication, readItem, readItems, createItem, updateItem, deleteItem, uploadFiles, readSingleton, readFieldsByCollection } from '@directus/sdk';
|
|
3
2
|
export function createMeeoviDirectusClient(url) {
|
|
4
3
|
const client = createDirectus(url)
|
|
@@ -6,6 +5,7 @@ export function createMeeoviDirectusClient(url) {
|
|
|
6
5
|
.with(authentication());
|
|
7
6
|
return {
|
|
8
7
|
client,
|
|
8
|
+
request: client.request,
|
|
9
9
|
readItem,
|
|
10
10
|
readItems,
|
|
11
11
|
createItem,
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import type { DirectusField } from '../schema/types';
|
|
2
|
-
export interface
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
export interface GeneratedFormField {
|
|
3
|
+
key: string;
|
|
4
|
+
widget: string;
|
|
5
|
+
type: string;
|
|
6
|
+
options?: Record<string, any>;
|
|
7
|
+
fields?: GeneratedFormField[];
|
|
8
|
+
isRepeatable?: boolean;
|
|
9
|
+
isFile?: boolean;
|
|
10
|
+
isRelational?: boolean;
|
|
6
11
|
}
|
|
7
|
-
export declare function
|
|
8
|
-
|
|
9
|
-
submit: () => Promise<{
|
|
10
|
-
error: string | null;
|
|
11
|
-
success: string | null;
|
|
12
|
-
}>;
|
|
13
|
-
readonly error: string | null;
|
|
14
|
-
readonly success: string | null;
|
|
15
|
-
};
|
|
12
|
+
export declare function generateFormField(field: DirectusField): GeneratedFormField;
|
|
13
|
+
export declare function generateFormSchema(fields: DirectusField[]): GeneratedFormField[];
|
|
@@ -1,63 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const validation = meta.validation;
|
|
13
|
-
if (validation._and) {
|
|
14
|
-
for (const rule of validation._and) {
|
|
15
|
-
const fieldName = Object.keys(rule)[0];
|
|
16
|
-
if (!fieldName)
|
|
17
|
-
continue;
|
|
18
|
-
const ruleDef = rule[fieldName];
|
|
19
|
-
if (ruleDef?._regex) {
|
|
20
|
-
const regex = new RegExp(ruleDef._regex);
|
|
21
|
-
const value = String(form[field.field] ?? '');
|
|
22
|
-
if (!regex.test(value)) {
|
|
23
|
-
error = meta.validation_message || `${field.field} failed validation`;
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
error = `Validation error for ${field.field}`;
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return true;
|
|
36
|
-
};
|
|
37
|
-
const submit = async () => {
|
|
38
|
-
if (!validate())
|
|
39
|
-
return { error, success };
|
|
40
|
-
const result = await directusClient.request(directusClient.createItem(collectionName, form));
|
|
41
|
-
if (result?.error) {
|
|
42
|
-
error = result.error.message;
|
|
43
|
-
return { error, success };
|
|
44
|
-
}
|
|
45
|
-
success = `${collectionName} created successfully`;
|
|
46
|
-
if (opts?.clearOnSuccess) {
|
|
47
|
-
for (const key of Object.keys(form))
|
|
48
|
-
delete form[key];
|
|
49
|
-
}
|
|
50
|
-
opts?.onSuccess?.();
|
|
51
|
-
return { error, success };
|
|
52
|
-
};
|
|
53
|
-
return {
|
|
54
|
-
form,
|
|
55
|
-
submit,
|
|
56
|
-
get error() {
|
|
57
|
-
return error;
|
|
58
|
-
},
|
|
59
|
-
get success() {
|
|
60
|
-
return success;
|
|
61
|
-
}
|
|
1
|
+
import { widgetRegistry } from './widget-registry';
|
|
2
|
+
export function generateFormField(field) {
|
|
3
|
+
const widget = widgetRegistry[field.interface || 'input'];
|
|
4
|
+
const base = {
|
|
5
|
+
key: field.field,
|
|
6
|
+
widget: widget.component,
|
|
7
|
+
type: field.type,
|
|
8
|
+
options: field.options || {},
|
|
9
|
+
isRepeatable: widget.isRepeatable,
|
|
10
|
+
isFile: widget.isFile,
|
|
11
|
+
isRelational: widget.isRelational
|
|
62
12
|
};
|
|
13
|
+
if ((field.interface === 'repeater' || field.interface === 'group') && field.options?.fields) {
|
|
14
|
+
base.fields = field.options.fields.map((sub) => generateFormField(sub));
|
|
15
|
+
}
|
|
16
|
+
return base;
|
|
17
|
+
}
|
|
18
|
+
export function generateFormSchema(fields) {
|
|
19
|
+
return fields
|
|
20
|
+
.filter(f => f.interface !== 'presentation' && f.interface !== 'divider')
|
|
21
|
+
.map(generateFormField);
|
|
63
22
|
}
|
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
};
|
|
11
|
-
date: {
|
|
12
|
-
component: string;
|
|
13
|
-
};
|
|
14
|
-
json: {
|
|
15
|
-
component: string;
|
|
16
|
-
};
|
|
17
|
-
};
|
|
1
|
+
export interface WidgetDefinition {
|
|
2
|
+
component: string;
|
|
3
|
+
props?: Record<string, any>;
|
|
4
|
+
isRepeatable?: boolean;
|
|
5
|
+
isRelational?: boolean;
|
|
6
|
+
isFile?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare const widgetRegistry: Record<string, WidgetDefinition>;
|
|
9
|
+
export declare function extendWidgetRegistryFromDirectus(client: any): Promise<void>;
|
|
@@ -1,7 +1,54 @@
|
|
|
1
|
+
import { readItems } from "@directus/sdk";
|
|
1
2
|
export const widgetRegistry = {
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
// Basic inputs
|
|
4
|
+
input: { component: 'TextInput' },
|
|
5
|
+
textarea: { component: 'TextareaInput' },
|
|
6
|
+
boolean: { component: 'ToggleInput' },
|
|
7
|
+
slider: { component: 'SliderInput' },
|
|
8
|
+
color: { component: 'ColorPicker' },
|
|
9
|
+
rating: { component: 'RatingInput' },
|
|
10
|
+
// Selects
|
|
11
|
+
'select-dropdown': { component: 'SelectInput' },
|
|
12
|
+
'select-multiple-dropdown': { component: 'MultiSelectInput' },
|
|
13
|
+
tags: { component: 'TagInput' },
|
|
4
14
|
checkbox: { component: 'CheckboxInput' },
|
|
15
|
+
radio: { component: 'RadioInput' },
|
|
16
|
+
// Date/time
|
|
17
|
+
datetime: { component: 'DateTimeInput' },
|
|
5
18
|
date: { component: 'DateInput' },
|
|
6
|
-
|
|
19
|
+
time: { component: 'TimeInput' },
|
|
20
|
+
// Files
|
|
21
|
+
file: { component: 'FileInput', isFile: true },
|
|
22
|
+
files: { component: 'FilesInput', isFile: true },
|
|
23
|
+
image: { component: 'ImageInput', isFile: true },
|
|
24
|
+
images: { component: 'ImagesInput', isFile: true },
|
|
25
|
+
// Complex
|
|
26
|
+
repeater: { component: 'RepeaterInput', isRepeatable: true },
|
|
27
|
+
group: { component: 'GroupInput' },
|
|
28
|
+
json: { component: 'JsonEditor' },
|
|
29
|
+
code: { component: 'CodeEditor' },
|
|
30
|
+
wysiwyg: { component: 'WysiwygEditor' },
|
|
31
|
+
markdown: { component: 'MarkdownEditor' },
|
|
32
|
+
// Directus-specific
|
|
33
|
+
icon: { component: 'IconPicker' },
|
|
34
|
+
user: { component: 'UserSelect' },
|
|
35
|
+
role: { component: 'RoleSelect' },
|
|
36
|
+
translation: { component: 'TranslationInput' },
|
|
37
|
+
// Presentation (ignored in forms)
|
|
38
|
+
presentation: { component: 'PresentationBlock' },
|
|
39
|
+
divider: { component: 'DividerBlock' }
|
|
7
40
|
};
|
|
41
|
+
export async function extendWidgetRegistryFromDirectus(client) {
|
|
42
|
+
const extensions = await client.request(readItems('directus_extensions'));
|
|
43
|
+
for (const ext of extensions) {
|
|
44
|
+
if (ext.type !== 'interface')
|
|
45
|
+
continue;
|
|
46
|
+
const name = ext.name;
|
|
47
|
+
if (!widgetRegistry[name]) {
|
|
48
|
+
widgetRegistry[name] = {
|
|
49
|
+
component: 'CustomInterfaceRenderer',
|
|
50
|
+
props: { interfaceName: name }
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meeovi/directus-client",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Directus Client Library with auto generating forms and tables, Directus SDK and Types, and vue/react components for Directus Visual Editing.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// src/client/createClient.ts
|
|
2
|
-
|
|
3
1
|
import {
|
|
4
2
|
createDirectus,
|
|
5
3
|
rest,
|
|
@@ -11,11 +9,13 @@ import {
|
|
|
11
9
|
deleteItem,
|
|
12
10
|
uploadFiles,
|
|
13
11
|
readSingleton,
|
|
14
|
-
readFieldsByCollection
|
|
12
|
+
readFieldsByCollection,
|
|
13
|
+
type DirectusClient
|
|
15
14
|
} from '@directus/sdk';
|
|
16
15
|
|
|
17
16
|
export interface MeeoviDirectusClient<Schema> {
|
|
18
|
-
client:
|
|
17
|
+
client: DirectusClient<Schema>;
|
|
18
|
+
request: any;
|
|
19
19
|
readItem: typeof readItem;
|
|
20
20
|
readItems: typeof readItems;
|
|
21
21
|
createItem: typeof createItem;
|
|
@@ -33,6 +33,7 @@ export function createMeeoviDirectusClient<Schema>(url: string): MeeoviDirectusC
|
|
|
33
33
|
|
|
34
34
|
return {
|
|
35
35
|
client,
|
|
36
|
+
request: client.request,
|
|
36
37
|
readItem,
|
|
37
38
|
readItems,
|
|
38
39
|
createItem,
|
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import type { DirectusField } from '../schema/types';
|
|
2
2
|
import { widgetRegistry } from './widget-registry';
|
|
3
3
|
|
|
4
|
+
export interface FormEngineOptions {
|
|
5
|
+
clearOnSuccess?: boolean;
|
|
6
|
+
onSuccess?: () => void;
|
|
7
|
+
onError?: (msg: string) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
4
10
|
export interface GeneratedFormField {
|
|
5
11
|
key: string;
|
|
6
12
|
widget: string;
|
|
7
13
|
type: string;
|
|
8
14
|
options?: Record<string, any>;
|
|
9
|
-
fields?: GeneratedFormField[];
|
|
15
|
+
fields?: GeneratedFormField[];
|
|
10
16
|
isRepeatable?: boolean;
|
|
11
17
|
isFile?: boolean;
|
|
12
18
|
isRelational?: boolean;
|
|
13
19
|
}
|
|
14
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Convert a Directus field into a UI-ready form field schema.
|
|
23
|
+
*/
|
|
15
24
|
export function generateFormField(field: DirectusField): GeneratedFormField {
|
|
16
25
|
const widget = widgetRegistry[field.interface || 'input'];
|
|
17
26
|
|
|
@@ -25,15 +34,11 @@ export function generateFormField(field: DirectusField): GeneratedFormField {
|
|
|
25
34
|
isRelational: widget.isRelational
|
|
26
35
|
};
|
|
27
36
|
|
|
28
|
-
//
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Handle group
|
|
36
|
-
if (field.interface === 'group' && field.options?.fields) {
|
|
37
|
+
// Repeater or group fields contain nested fields
|
|
38
|
+
if (
|
|
39
|
+
(field.interface === 'repeater' || field.interface === 'group') &&
|
|
40
|
+
Array.isArray(field.options?.fields)
|
|
41
|
+
) {
|
|
37
42
|
base.fields = field.options.fields.map((sub: any) =>
|
|
38
43
|
generateFormField(sub)
|
|
39
44
|
);
|
|
@@ -42,8 +47,114 @@ export function generateFormField(field: DirectusField): GeneratedFormField {
|
|
|
42
47
|
return base;
|
|
43
48
|
}
|
|
44
49
|
|
|
45
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Convert an entire collection's fields into a form schema.
|
|
52
|
+
*/
|
|
53
|
+
export function generateFormSchema(fields: DirectusField[]): GeneratedFormField[] {
|
|
46
54
|
return fields
|
|
47
55
|
.filter(f => f.interface !== 'presentation' && f.interface !== 'divider')
|
|
48
56
|
.map(generateFormField);
|
|
49
57
|
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Framework-agnostic form engine for submitting Directus items.
|
|
61
|
+
*/
|
|
62
|
+
export function createFormEngine(
|
|
63
|
+
collectionName: string,
|
|
64
|
+
fields: DirectusField[],
|
|
65
|
+
directusClient: any,
|
|
66
|
+
opts?: FormEngineOptions
|
|
67
|
+
) {
|
|
68
|
+
const form: Record<string, any> = {};
|
|
69
|
+
let error: string | null = null;
|
|
70
|
+
let success: string | null = null;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Validate a single field using Directus validation metadata.
|
|
74
|
+
*/
|
|
75
|
+
const validateField = (field: DirectusField): string | null => {
|
|
76
|
+
if (!field.validation) return null;
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const validation = field.validation;
|
|
80
|
+
|
|
81
|
+
if (validation._and) {
|
|
82
|
+
for (const rule of validation._and) {
|
|
83
|
+
const fieldName = Object.keys(rule)[0];
|
|
84
|
+
if (!fieldName) continue;
|
|
85
|
+
|
|
86
|
+
const ruleDef = (rule as any)[fieldName];
|
|
87
|
+
|
|
88
|
+
if (ruleDef?._regex) {
|
|
89
|
+
const regex = new RegExp(ruleDef._regex);
|
|
90
|
+
const value = String(form[field.field] ?? '');
|
|
91
|
+
|
|
92
|
+
if (!regex.test(value)) {
|
|
93
|
+
return field.validation_message || `${field.field} failed validation`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
return `Validation error for ${field.field}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return null;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Validate all fields before submission.
|
|
107
|
+
*/
|
|
108
|
+
const validate = () => {
|
|
109
|
+
error = null;
|
|
110
|
+
|
|
111
|
+
for (const field of fields) {
|
|
112
|
+
const result = validateField(field);
|
|
113
|
+
if (result) {
|
|
114
|
+
error = result;
|
|
115
|
+
opts?.onError?.(result);
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return true;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Submit the form to Directus.
|
|
125
|
+
*/
|
|
126
|
+
const submit = async () => {
|
|
127
|
+
if (!validate()) return { error, success };
|
|
128
|
+
|
|
129
|
+
const result = await directusClient.request(
|
|
130
|
+
directusClient.createItem(collectionName, form)
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
if (result?.error) {
|
|
134
|
+
error = result.error.message;
|
|
135
|
+
opts?.onError?.(error as any);
|
|
136
|
+
return { error, success };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
success = `${collectionName} created successfully`;
|
|
140
|
+
|
|
141
|
+
if (opts?.clearOnSuccess) {
|
|
142
|
+
for (const key of Object.keys(form)) delete form[key];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
opts?.onSuccess?.();
|
|
146
|
+
|
|
147
|
+
return { error, success };
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
form,
|
|
152
|
+
submit,
|
|
153
|
+
get error() {
|
|
154
|
+
return error;
|
|
155
|
+
},
|
|
156
|
+
get success() {
|
|
157
|
+
return success;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
}
|