Skip to content

Commit b63353b

Browse files
committed
1 parent b88e29c commit b63353b

File tree

8 files changed

+249
-73
lines changed

8 files changed

+249
-73
lines changed

src/features/instance/applications/components/AddSchemaForm/index.tsx

Lines changed: 201 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -7,85 +7,178 @@ import { FormLabel } from '@/components/ui/form/FormLabel';
77
import { FormMessage } from '@/components/ui/form/FormMessage';
88
import { Input } from '@/components/ui/input';
99
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
10-
import { Plus, TrashIcon } from 'lucide-react';
10+
import { useEditorFileContent } from '@/features/instance/applications/context/editorFileContent';
11+
import { useEditorView } from '@/features/instance/applications/hooks/useEditorView';
12+
import { fieldNameSchema } from '@/integrations/api/instance/database/fieldNameSchema';
13+
import { FieldType } from '@/integrations/api/instance/database/fieldType';
14+
import { tableNameSchema } from '@/integrations/api/instance/database/tableNameSchema';
15+
import { useSetWatchedValue } from '@/lib/events/watcher';
16+
import { zodResolver } from '@hookform/resolvers/zod';
17+
import { Ban, Plus, PlusIcon, TrashIcon } from 'lucide-react';
18+
import { useCallback } from 'react';
1119
import { useFieldArray, useForm } from 'react-hook-form';
20+
import { z } from 'zod';
21+
22+
const addSchema = z.object({
23+
tableName: tableNameSchema,
24+
enableRest: z.boolean(),
25+
sealed: z.boolean(),
26+
replicate: z.boolean(),
27+
tableFields: z.array(
28+
z.object({
29+
fieldName: fieldNameSchema,
30+
fieldType: z.enum(FieldType),
31+
primaryKey: z.boolean(),
32+
nullable: z.boolean(),
33+
indexed: z.boolean(),
34+
array: z.boolean(),
35+
}),
36+
).min(1, { error: 'Please add at least one field.' }),
37+
});
1238

1339
export function AddSchemaForm() {
40+
const closeModal = useSetWatchedValue('ShowAddSchemaModal', false);
41+
const { openedEntry, openedEntryContents } = useEditorView();
42+
const { setContent } = useEditorFileContent(!!openedEntry && !openedEntry.package && openedEntry.path);
43+
1444
const methods = useForm({
45+
resolver: zodResolver(addSchema),
1546
defaultValues: {
1647
tableName: '',
17-
isRestEndpoint: 'false',
18-
noAdditionalProperties: 'false',
19-
tableFields: [{ fieldName: '', fieldType: 'id', isPrimaryKey: 'true', isNullable: 'false', isIndexed: 'false' }],
48+
enableRest: true,
49+
sealed: true,
50+
replicate: true,
51+
tableFields: [
52+
{
53+
fieldName: 'id',
54+
fieldType: FieldType.ID,
55+
primaryKey: true,
56+
nullable: true,
57+
indexed: false,
58+
array: false,
59+
},
60+
],
2061
},
2162
});
22-
const { control } = methods;
63+
const { control, handleSubmit, formState } = methods;
2364

2465
const { fields, append, remove } = useFieldArray({
2566
name: 'tableFields',
2667
control,
2768
});
69+
70+
const addFieldClicked = useCallback(() => {
71+
append({
72+
fieldName: '',
73+
fieldType: FieldType.String,
74+
primaryKey: false,
75+
nullable: false,
76+
indexed: false,
77+
array: false,
78+
});
79+
}, [append]);
80+
81+
const submitForm = useCallback(async (formData: z.infer<typeof addSchema>) => {
82+
setContent(existingContent =>
83+
`${existingContent || openedEntryContents || ''}
84+
type ${formData.tableName} @table${formData.replicate ? '' : '(replicate: false) '}${
85+
formData.enableRest ? ' @export' : ''
86+
}${formData.sealed ? ' @sealed' : ''} {${
87+
formData.tableFields.map(tableField => `
88+
${tableField.fieldName}: ${tableField.array ? '[' : ''}${tableField.fieldType}${tableField.array ? ']' : ''}${
89+
tableField.nullable ? '' : '!'
90+
}${tableField.primaryKey ? ' @primaryKey' : ''}${tableField.indexed ? ' @indexed' : ''}`).join('')
91+
}
92+
}
93+
`
94+
);
95+
closeModal();
96+
}, [setContent, closeModal, openedEntryContents]);
97+
2898
return (
2999
<Form {...methods}>
30-
<form>
100+
<form onSubmit={handleSubmit(submitForm)}>
31101
<FormField
32102
control={control}
33103
name="tableName"
34104
render={({ field }) => (
35105
<FormItem className="my-4">
36106
<FormLabel>Table Name</FormLabel>
37107
<FormControl>
38-
<Input type="text" className="my-1" placeholder="Enter Table Name" {...field} />
108+
<Input type="text" className="my-1" {...field} />
39109
</FormControl>
40110
<FormMessage />
41111
</FormItem>
42112
)}
43113
/>
44114
<FormField
45115
control={control}
46-
name="isRestEndpoint"
116+
name="enableRest"
47117
render={({ field }) => (
48118
<FormItem className="flex">
49119
<FormControl>
50-
<Input type="checkbox" className="w-5" {...field} />
120+
<Input
121+
type="checkbox"
122+
className="w-5"
123+
checked={field.value}
124+
onChange={(e) => field.onChange(e.target.checked)}
125+
/>
51126
</FormControl>
52-
<FormLabel className="flex-1 py-2.5">Make available as a REST endpoint</FormLabel>
127+
<FormLabel className="flex-1 py-2.5">
128+
Make available as a REST endpoint (add{' '}
129+
<code className="text-muted-foreground italic ml-1">@export</code>)
130+
</FormLabel>
53131
<FormMessage />
54132
</FormItem>
55133
)}
56134
/>
57135
<FormField
58136
control={control}
59-
name="noAdditionalProperties"
137+
name="replicate"
60138
render={({ field }) => (
61139
<FormItem className="flex">
62140
<FormControl>
63-
<Input type="checkbox" className="w-5" {...field} />
141+
<Input
142+
type="checkbox"
143+
className="w-5"
144+
checked={field.value}
145+
onChange={(e) => field.onChange(e.target.checked)}
146+
/>
64147
</FormControl>
65-
<FormLabel className="flex-1 py-2.5">Do not allow additional properties</FormLabel>
148+
<FormLabel className="flex-1 py-2.5">
149+
Replicate this table (add
150+
<code className="text-muted-foreground italic ml-1">@table(replicate: {String(field.value)})</code>{' '}
151+
to your table)
152+
</FormLabel>
66153
<FormMessage />
67154
</FormItem>
68155
)}
69156
/>
70-
<hr className="my-6" />
71-
<div className="flex items-center justify-between mb-4">
72-
<h3 className="text-lg">Table Fields</h3>
73-
<Button
74-
variant="positiveOutline"
75-
type="button"
76-
className="rounded-full"
77-
onClick={() =>
78-
append({
79-
fieldName: '',
80-
fieldType: 'string',
81-
isPrimaryKey: 'false',
82-
isNullable: 'false',
83-
isIndexed: 'false',
84-
})}
85-
>
86-
<Plus /> Add Field
87-
</Button>
88-
</div>
157+
<FormField
158+
control={control}
159+
name="sealed"
160+
render={({ field }) => (
161+
<FormItem className="flex">
162+
<FormControl>
163+
<Input
164+
type="checkbox"
165+
className="w-5"
166+
checked={field.value}
167+
onChange={(e) => field.onChange(e.target.checked)}
168+
/>
169+
</FormControl>
170+
<FormLabel className="flex-1 py-2.5">
171+
Sealed (add
172+
<code className="text-muted-foreground italic ml-1">@sealed</code> to ignore unspecified properties)
173+
</FormLabel>
174+
<FormMessage />
175+
</FormItem>
176+
)}
177+
/>
178+
<hr className="my-6 border-gray-600" />
179+
180+
<h3 className="text-lg">Table Fields</h3>
181+
89182
{fields.map((field, index) => (
90183
<div key={field.id} className="p-4 my-2 border rounded-lg">
91184
<div className="grid grid-cols-2 gap-4">
@@ -96,7 +189,7 @@ export function AddSchemaForm() {
96189
<FormItem>
97190
<FormLabel>Field Name</FormLabel>
98191
<FormControl>
99-
<Input type="text" placeholder="id" {...field} />
192+
<Input type="text" {...field} />
100193
</FormControl>
101194
<FormMessage />
102195
</FormItem>
@@ -111,16 +204,20 @@ export function AddSchemaForm() {
111204
<FormControl>
112205
<Select {...field} onValueChange={field.onChange} defaultValue={field.value}>
113206
<SelectTrigger className="w-full">
114-
<SelectValue placeholder="Select the Field Type" />
207+
<SelectValue />
115208
</SelectTrigger>
116209
<SelectContent>
117-
<SelectItem value="id">ID</SelectItem>
118-
<SelectItem value="string">String</SelectItem>
119-
<SelectItem value="number">Number</SelectItem>
120-
<SelectItem value="boolean">Boolean</SelectItem>
121-
<SelectItem value="date">Date</SelectItem>
122-
<SelectItem value="array">Array</SelectItem>
123-
<SelectItem value="object">Object</SelectItem>
210+
<SelectItem value="Any">Any</SelectItem>
211+
<SelectItem value="BigInt">BigInt</SelectItem>
212+
<SelectItem value="Blob">Blob</SelectItem>
213+
<SelectItem value="Boolean">Boolean</SelectItem>
214+
<SelectItem value="Bytes">Bytes</SelectItem>
215+
<SelectItem value="Date">Date</SelectItem>
216+
<SelectItem value="Float">Float</SelectItem>
217+
<SelectItem value="ID">ID</SelectItem>
218+
<SelectItem value="Int">Int</SelectItem>
219+
<SelectItem value="Long">Long</SelectItem>
220+
<SelectItem value="String">String</SelectItem>
124221
</SelectContent>
125222
</Select>
126223
</FormControl>
@@ -133,11 +230,16 @@ export function AddSchemaForm() {
133230
<div className="flex gap-x-2 md:gap-x-4">
134231
<FormField
135232
control={control}
136-
name={`tableFields.${index}.isIndexed`}
233+
name={`tableFields.${index}.indexed`}
137234
render={({ field }) => (
138235
<FormItem className="inline-flex">
139236
<FormControl>
140-
<Input type="checkbox" className="w-5" {...field} />
237+
<Input
238+
type="checkbox"
239+
className="w-5"
240+
checked={field.value}
241+
onChange={(e) => field.onChange(e.target.checked)}
242+
/>
141243
</FormControl>
142244
<FormLabel className="flex-1 py-2.5">Indexed</FormLabel>
143245
<FormMessage />
@@ -146,11 +248,16 @@ export function AddSchemaForm() {
146248
/>
147249
<FormField
148250
control={control}
149-
name={`tableFields.${index}.isPrimaryKey`}
251+
name={`tableFields.${index}.primaryKey`}
150252
render={({ field }) => (
151253
<FormItem className="inline-flex">
152254
<FormControl>
153-
<Input type="checkbox" className="w-5" {...field} />
255+
<Input
256+
type="checkbox"
257+
className="w-5"
258+
checked={field.value}
259+
onChange={(e) => field.onChange(e.target.checked)}
260+
/>
154261
</FormControl>
155262
<FormLabel className="flex-1 py-2.5">Primary Key</FormLabel>
156263
<FormMessage />
@@ -159,24 +266,70 @@ export function AddSchemaForm() {
159266
/>
160267
<FormField
161268
control={control}
162-
name={`tableFields.${index}.isNullable`}
269+
name={`tableFields.${index}.nullable`}
163270
render={({ field }) => (
164271
<FormItem className="inline-flex">
165272
<FormControl>
166-
<Input type="checkbox" className="w-5" {...field} />
273+
<Input
274+
type="checkbox"
275+
className="w-5"
276+
checked={field.value}
277+
onChange={(e) => field.onChange(e.target.checked)}
278+
/>
167279
</FormControl>
168280
<FormLabel className="flex-1 py-2.5">Nullable</FormLabel>
169281
<FormMessage />
170282
</FormItem>
171283
)}
172284
/>
285+
<FormField
286+
control={control}
287+
name={`tableFields.${index}.array`}
288+
render={({ field }) => (
289+
<FormItem className="inline-flex">
290+
<FormControl>
291+
<Input
292+
type="checkbox"
293+
className="w-5"
294+
checked={field.value}
295+
onChange={(e) => field.onChange(e.target.checked)}
296+
/>
297+
</FormControl>
298+
<FormLabel className="flex-1 py-2.5">Array</FormLabel>
299+
<FormMessage />
300+
</FormItem>
301+
)}
302+
/>
173303
</div>
174-
<Button type="button" variant="destructiveOutline" className="rounded-full" onClick={() => remove(index)}>
304+
<Button type="button" variant="destructiveGhost" className="rounded-full" onClick={() => remove(index)}>
175305
<TrashIcon /> Remove Field
176306
</Button>
177307
</div>
178308
</div>
179309
))}
310+
311+
<Button
312+
variant="ghost"
313+
type="button"
314+
className="w-full"
315+
onClick={addFieldClicked}
316+
>
317+
<Plus /> Add Another Field
318+
</Button>
319+
320+
<div className="flex w-full gap-4">
321+
<Button variant="ghost" className="w-full rounded-full" onClick={closeModal}>
322+
<Ban /> Cancel
323+
</Button>
324+
<Button
325+
variant="positive"
326+
type="submit"
327+
className="w-full rounded-full"
328+
disabled={!formState.isValid}
329+
>
330+
<PlusIcon /> Add Schema
331+
</Button>
332+
</div>
180333
</form>
181334
</Form>
182335
);

src/features/instance/applications/context/EditorViewContext.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ export type EditorViewContextValue = {
1515

1616
restrictPackageModification: boolean;
1717
openedEntryContents: string | undefined;
18-
setOpenedEntryContents: (contents: string | undefined) => void;
18+
setOpenedEntryContents: (
19+
contents: (string | undefined) | ((existingContents: string | undefined) => string | undefined),
20+
) => void;
1921

2022
focusedItem: TreeItemIndex | undefined;
2123
setFocusedItem: (

src/features/instance/applications/modals/AddSchemaModal.tsx

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { Button } from '@/components/ui/button';
21
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
32
import { AddSchemaForm } from '@/features/instance/applications/components/AddSchemaForm';
43
import { attemptToRestoreFocus } from '@/lib/attemptToRestoreFocus';
54
import { setWatchedValue, useWatchedValue } from '@/lib/events/watcher';
6-
import { Ban, PlusIcon } from 'lucide-react';
75
import { useCallback } from 'react';
86

97
export function AddSchemaModal() {
@@ -26,15 +24,6 @@ export function AddSchemaModal() {
2624
</DialogHeader>
2725

2826
<AddSchemaForm />
29-
30-
<div className="flex w-full gap-4">
31-
<Button variant="ghost" className="w-full rounded-full" onClick={closeModal}>
32-
<Ban /> Cancel
33-
</Button>
34-
<Button variant="positive" type="button" className="w-full rounded-full">
35-
<PlusIcon /> Add Schema
36-
</Button>
37-
</div>
3827
</DialogContent>
3928
</Dialog>
4029
);

0 commit comments

Comments
 (0)