@dxos/react-ui-form 0.8.4-main.ae835ea → 0.8.4-main.bc674ce
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/lib/browser/index.mjs +2182 -2231
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +2182 -2231
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/ControlSection/ControlSection.d.ts +2 -2
- package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +1 -1
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +11 -8
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +6 -5
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +13 -7
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form-old.stories.d.ts +131 -0
- package/dist/types/src/components/Form/Form-old.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/Form.d.ts +81 -26
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +59 -84
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField.d.ts +37 -0
- package/dist/types/src/components/Form/FormField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldComponent.d.ts +78 -0
- package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts +9 -0
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldSet.d.ts +19 -0
- package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/ArrayField.d.ts +18 -0
- package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/BooleanField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/BooleanField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/GeoPointField.d.ts +5 -0
- package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/NumberField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/NumberField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/RefField.d.ts +14 -0
- package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/SelectField.d.ts +10 -0
- package/dist/types/src/components/Form/fields/SelectField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts +5 -0
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -0
- package/dist/types/src/components/Form/{custom/SelectOptionsInput.stories.d.ts → fields/SelectOptionField.stories.d.ts} +10 -7
- package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/TextAreaField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/TextAreaField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/TextField.d.ts +4 -0
- package/dist/types/src/components/Form/fields/TextField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/TupleField.d.ts +6 -0
- package/dist/types/src/components/Form/fields/TupleField.d.ts.map +1 -0
- package/dist/types/src/components/Form/fields/index.d.ts +12 -0
- package/dist/types/src/components/Form/fields/index.d.ts.map +1 -0
- package/dist/types/src/components/Form/index.d.ts +2 -4
- package/dist/types/src/components/Form/index.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts +14 -9
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +9 -6
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +11 -13
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +12 -9
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +0 -2
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -3
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useFormHandler.d.ts +82 -0
- package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -0
- package/dist/types/src/hooks/useKeyHandler.d.ts +6 -0
- package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +10 -7
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/util/echo.d.ts +6 -0
- package/dist/types/src/util/echo.d.ts.map +1 -0
- package/dist/types/src/util/field.d.ts +3 -3
- package/dist/types/src/util/field.d.ts.map +1 -1
- package/dist/types/src/util/formatting.d.ts +2 -2
- package/dist/types/src/util/formatting.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +2 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/properties.d.ts +7 -0
- package/dist/types/src/util/properties.d.ts.map +1 -0
- package/dist/types/src/util/refs.d.ts +1 -2
- package/dist/types/src/util/refs.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +49 -43
- package/src/components/ControlSection/ControlSection.stories.tsx +1 -1
- package/src/components/ControlSection/ControlSection.tsx +8 -8
- package/src/components/FieldEditor/FieldEditor.stories.tsx +42 -21
- package/src/components/FieldEditor/FieldEditor.tsx +73 -76
- package/src/components/Form/Form-old.stories.tsx +314 -0
- package/src/components/Form/Form.stories.tsx +148 -248
- package/src/components/Form/Form.tsx +320 -108
- package/src/components/Form/FormField.tsx +286 -0
- package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
- package/src/components/Form/FormFieldComponent.tsx +174 -0
- package/src/components/Form/FormFieldSet.tsx +125 -0
- package/src/components/Form/README.md +3 -0
- package/src/components/Form/fields/ArrayField.tsx +151 -0
- package/src/components/Form/fields/BooleanField.tsx +22 -0
- package/src/components/Form/fields/GeoPointField.tsx +120 -0
- package/src/components/Form/fields/MarkdownField.tsx +78 -0
- package/src/components/Form/fields/NumberField.tsx +39 -0
- package/src/components/Form/fields/RefField.tsx +181 -0
- package/src/components/Form/fields/SelectField.tsx +65 -0
- package/src/components/Form/{custom/SelectOptionsInput.stories.tsx → fields/SelectOptionField.stories.tsx} +17 -14
- package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +21 -15
- package/src/components/Form/fields/TextAreaField.tsx +38 -0
- package/src/components/Form/fields/TextField.tsx +38 -0
- package/src/components/Form/{custom/TupleInput.tsx → fields/TupleField.tsx} +15 -8
- package/src/components/Form/fields/index.ts +15 -0
- package/src/components/Form/index.ts +3 -4
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +20 -13
- package/src/components/ObjectPicker/ObjectPicker.tsx +76 -62
- package/src/components/ViewEditor/ViewEditor.stories.tsx +42 -40
- package/src/components/ViewEditor/ViewEditor.test.tsx +8 -4
- package/src/components/ViewEditor/ViewEditor.tsx +233 -172
- package/src/components/testing.tsx +9 -7
- package/src/hooks/index.ts +2 -3
- package/src/hooks/useFormHandler.ts +362 -0
- package/src/hooks/useKeyHandler.tsx +37 -0
- package/src/translations.ts +12 -10
- package/src/util/echo.ts +25 -0
- package/src/util/field.ts +25 -25
- package/src/util/formatting.ts +22 -22
- package/src/util/index.ts +2 -1
- package/src/util/properties.ts +18 -0
- package/src/util/refs.ts +11 -17
- package/dist/types/src/components/Form/ArrayField.d.ts +0 -16
- package/dist/types/src/components/Form/ArrayField.d.ts.map +0 -1
- package/dist/types/src/components/Form/Defaults.d.ts +0 -14
- package/dist/types/src/components/Form/Defaults.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormActions.d.ts +0 -9
- package/dist/types/src/components/Form/FormActions.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormContent.d.ts +0 -63
- package/dist/types/src/components/Form/FormContent.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormContext.d.ts +0 -23
- package/dist/types/src/components/Form/FormContext.d.ts.map +0 -1
- package/dist/types/src/components/Form/Input.d.ts +0 -29
- package/dist/types/src/components/Form/Input.d.ts.map +0 -1
- package/dist/types/src/components/Form/RefField.d.ts +0 -22
- package/dist/types/src/components/Form/RefField.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +0 -4
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +0 -4
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/TupleInput.d.ts +0 -6
- package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/index.d.ts +0 -4
- package/dist/types/src/components/Form/custom/index.d.ts.map +0 -1
- package/dist/types/src/components/Form/factory.d.ts +0 -8
- package/dist/types/src/components/Form/factory.d.ts.map +0 -1
- package/dist/types/src/hooks/useForm.d.ts +0 -67
- package/dist/types/src/hooks/useForm.d.ts.map +0 -1
- package/dist/types/src/hooks/useQueryRefOptions.d.ts +0 -29
- package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +0 -1
- package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts +0 -8
- package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +0 -1
- package/dist/types/src/util/schema.d.ts +0 -3
- package/dist/types/src/util/schema.d.ts.map +0 -1
- package/src/components/Form/ArrayField.tsx +0 -125
- package/src/components/Form/Defaults.tsx +0 -205
- package/src/components/Form/FormActions.tsx +0 -54
- package/src/components/Form/FormContent.tsx +0 -322
- package/src/components/Form/FormContext.tsx +0 -110
- package/src/components/Form/Input.tsx +0 -54
- package/src/components/Form/RefField.tsx +0 -283
- package/src/components/Form/custom/GeoPointInput.tsx +0 -80
- package/src/components/Form/custom/index.ts +0 -7
- package/src/components/Form/factory.tsx +0 -31
- package/src/hooks/useForm.ts +0 -296
- package/src/hooks/useQueryRefOptions.ts +0 -62
- package/src/hooks/useRefQueryLookupHandler.ts +0 -39
- package/src/util/schema.ts +0 -12
|
@@ -2,33 +2,40 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { RegistryContext } from '@effect-atom/atom-react';
|
|
5
6
|
import * as Array from 'effect/Array';
|
|
6
7
|
import * as Match from 'effect/Match';
|
|
7
8
|
import * as Option from 'effect/Option';
|
|
8
9
|
import * as Schema from 'effect/Schema';
|
|
9
|
-
import React, { forwardRef, useCallback, useImperativeHandle, useMemo, useState } from 'react';
|
|
10
|
+
import React, { forwardRef, useCallback, useContext, useImperativeHandle, useMemo, useState } from 'react';
|
|
10
11
|
|
|
11
|
-
import { QueryAST } from '@dxos/echo';
|
|
12
|
-
import { EchoSchema,
|
|
13
|
-
import { Filter, Query, type SchemaRegistry } from '@dxos/echo-db';
|
|
12
|
+
import { Filter, Format, Obj, Query, QueryAST, type SchemaRegistry } from '@dxos/echo';
|
|
13
|
+
import { EchoSchema, type JsonProp, isMutable, toJsonSchema } from '@dxos/echo/internal';
|
|
14
14
|
import { invariant } from '@dxos/invariant';
|
|
15
|
+
import { useObject } from '@dxos/react-client/echo';
|
|
15
16
|
import { Callout, IconButton, Input, type ThemedClassName, useTranslation } from '@dxos/react-ui';
|
|
16
17
|
import { QueryForm, type QueryFormProps } from '@dxos/react-ui-components';
|
|
17
18
|
import { List } from '@dxos/react-ui-list';
|
|
18
|
-
import { cardSpacing } from '@dxos/react-ui-stack';
|
|
19
|
-
import { inputTextLabel, mx, subtleHover } from '@dxos/react-ui-theme';
|
|
20
19
|
import {
|
|
21
|
-
type DataType,
|
|
22
20
|
FieldSchema,
|
|
23
21
|
type FieldType,
|
|
24
22
|
ProjectionModel,
|
|
25
23
|
VIEW_FIELD_LIMIT,
|
|
24
|
+
type View,
|
|
25
|
+
createEchoChangeCallback,
|
|
26
26
|
getTypenameFromQuery,
|
|
27
27
|
} from '@dxos/schema';
|
|
28
|
+
import { mx, osTranslations, subtleHover } from '@dxos/ui-theme';
|
|
28
29
|
|
|
29
30
|
import { translationKey } from '../../translations';
|
|
30
31
|
import { FieldEditor } from '../FieldEditor';
|
|
31
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
Form,
|
|
34
|
+
type FormFieldComponent,
|
|
35
|
+
type FormFieldComponentProps,
|
|
36
|
+
FormFieldLabel,
|
|
37
|
+
type FormFieldMap,
|
|
38
|
+
} from '../Form';
|
|
32
39
|
|
|
33
40
|
const listGrid = 'grid grid-cols-[min-content_1fr_min-content_min-content_min-content]';
|
|
34
41
|
const listItemGrid = 'grid grid-cols-subgrid col-span-5';
|
|
@@ -36,15 +43,14 @@ const listItemGrid = 'grid grid-cols-subgrid col-span-5';
|
|
|
36
43
|
export type ViewEditorProps = ThemedClassName<
|
|
37
44
|
{
|
|
38
45
|
schema: Schema.Schema.AnyNoContext;
|
|
39
|
-
view:
|
|
40
|
-
mode?: 'schema' | '
|
|
41
|
-
registry?: SchemaRegistry;
|
|
46
|
+
view: View.View;
|
|
47
|
+
mode?: 'schema' | 'tag';
|
|
48
|
+
registry?: SchemaRegistry.SchemaRegistry;
|
|
42
49
|
readonly?: boolean;
|
|
43
50
|
showHeading?: boolean;
|
|
44
51
|
onQueryChanged?: (query: QueryAST.Query, target?: string) => void;
|
|
45
52
|
onDelete?: (fieldId: string) => void;
|
|
46
|
-
} & Pick<
|
|
47
|
-
Pick<QueryFormProps, 'types' | 'tags'>
|
|
53
|
+
} & Pick<QueryFormProps, 'types' | 'tags'>
|
|
48
54
|
>;
|
|
49
55
|
|
|
50
56
|
/**
|
|
@@ -64,17 +70,31 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
64
70
|
tags,
|
|
65
71
|
onQueryChanged,
|
|
66
72
|
onDelete,
|
|
67
|
-
outerSpacing = true,
|
|
68
73
|
},
|
|
69
74
|
forwardedRef,
|
|
70
75
|
) => {
|
|
76
|
+
const atomRegistry = useContext(RegistryContext);
|
|
71
77
|
const schemaReadonly = !isMutable(schema);
|
|
72
78
|
const { t } = useTranslation(translationKey);
|
|
79
|
+
|
|
73
80
|
const projectionModel = useMemo(() => {
|
|
74
81
|
// Use reactive and mutable version of json schema when schema is mutable.
|
|
75
82
|
const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
|
|
84
|
+
// Always use createEchoChangeCallback since the view is ECHO-backed.
|
|
85
|
+
// Pass schema only when mutable to allow schema mutations.
|
|
86
|
+
const change = createEchoChangeCallback(view, schema instanceof EchoSchema ? schema : undefined);
|
|
87
|
+
|
|
88
|
+
const model = new ProjectionModel({
|
|
89
|
+
registry: atomRegistry,
|
|
90
|
+
view,
|
|
91
|
+
baseSchema: jsonSchema,
|
|
92
|
+
change,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return model;
|
|
96
|
+
}, [atomRegistry, schema, view]);
|
|
97
|
+
|
|
78
98
|
useImperativeHandle(forwardedRef, () => projectionModel, [projectionModel]);
|
|
79
99
|
const [expandedField, setExpandedField] = useState<FieldType['id']>();
|
|
80
100
|
|
|
@@ -96,9 +116,8 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
96
116
|
: QueryAST.Query.annotations({ title: 'Query' }),
|
|
97
117
|
});
|
|
98
118
|
|
|
99
|
-
if (mode === '
|
|
119
|
+
if (mode === 'tag') {
|
|
100
120
|
return Schema.Struct({
|
|
101
|
-
name: Schema.optional(Schema.String.annotations({ title: 'Name' })),
|
|
102
121
|
...base.fields,
|
|
103
122
|
target: Schema.optional(Schema.String.annotations({ title: 'Target Queue' })),
|
|
104
123
|
}).pipe(Schema.mutable);
|
|
@@ -111,42 +130,32 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
111
130
|
// TODO(burdon): Settings should have domain name owned by user.
|
|
112
131
|
const viewValues = useMemo(
|
|
113
132
|
() => ({
|
|
114
|
-
name: view.name,
|
|
115
133
|
query: mode === 'schema' ? getTypenameFromQuery(view.query.ast) : view.query.ast,
|
|
116
134
|
target: queueTarget,
|
|
117
135
|
}),
|
|
118
|
-
[
|
|
136
|
+
[mode, view.query.ast, queueTarget],
|
|
119
137
|
);
|
|
120
138
|
|
|
121
|
-
const
|
|
122
|
-
(
|
|
123
|
-
|
|
124
|
-
},
|
|
125
|
-
[readonly],
|
|
139
|
+
const fieldMap = useMemo<FormFieldMap | undefined>(
|
|
140
|
+
() => (mode === 'tag' ? customFields({ types, tags }) : undefined),
|
|
141
|
+
[mode, types, tags],
|
|
126
142
|
);
|
|
127
143
|
|
|
128
|
-
// TODO(burdon): Check if mutable
|
|
129
|
-
|
|
144
|
+
// TODO(burdon): Check if mutable.
|
|
130
145
|
const handleAdd = useCallback(() => {
|
|
131
146
|
invariant(!readonly);
|
|
132
147
|
const field = projectionModel.createFieldProjection();
|
|
133
148
|
setExpandedField(field.id);
|
|
134
|
-
}, [
|
|
149
|
+
}, [projectionModel, readonly]);
|
|
135
150
|
|
|
136
151
|
const handleUpdate = useCallback(
|
|
137
152
|
(values: any) => {
|
|
138
|
-
invariant(!readonly);
|
|
139
153
|
requestAnimationFrame(() => {
|
|
140
|
-
if ('name' in values && view.name !== values.name) {
|
|
141
|
-
view.name = values.name;
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
154
|
const query = mode === 'schema' ? Query.select(Filter.typename(values.query)).ast : values.query;
|
|
146
155
|
onQueryChanged?.(query, values.target);
|
|
147
156
|
});
|
|
148
157
|
},
|
|
149
|
-
[onQueryChanged,
|
|
158
|
+
[onQueryChanged, view, queueTarget, mode],
|
|
150
159
|
);
|
|
151
160
|
|
|
152
161
|
const handleDelete = useCallback(
|
|
@@ -161,146 +170,35 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
161
170
|
[expandedField, onDelete, readonly],
|
|
162
171
|
);
|
|
163
172
|
|
|
164
|
-
const handleMove = useCallback(
|
|
165
|
-
(fromIndex: number, toIndex: number) => {
|
|
166
|
-
invariant(!readonly);
|
|
167
|
-
// NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
|
|
168
|
-
const fields = [...view.projection.fields];
|
|
169
|
-
const [moved] = fields.splice(fromIndex, 1);
|
|
170
|
-
fields.splice(toIndex, 0, moved);
|
|
171
|
-
view.projection.fields = fields;
|
|
172
|
-
},
|
|
173
|
-
[view.projection.fields, readonly],
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
const handleClose = useCallback(() => setExpandedField(undefined), []);
|
|
177
|
-
|
|
178
|
-
const handleHide = useCallback(
|
|
179
|
-
(fieldId: string) => {
|
|
180
|
-
setExpandedField(undefined);
|
|
181
|
-
projectionModel.hideFieldProjection(fieldId);
|
|
182
|
-
},
|
|
183
|
-
[projectionModel],
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
const handleShow = useCallback(
|
|
187
|
-
(property: string) => {
|
|
188
|
-
setExpandedField(undefined);
|
|
189
|
-
projectionModel.showFieldProjection(property as JsonProp);
|
|
190
|
-
},
|
|
191
|
-
[projectionModel],
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
const custom = useMemo(() => (mode === 'query' ? customFields({ types, tags }) : undefined), [types, tags, mode]);
|
|
195
|
-
|
|
196
173
|
return (
|
|
197
174
|
<div role='none' className={mx(classNames)}>
|
|
198
|
-
{
|
|
199
|
-
|
|
175
|
+
{/* If readonly is set, then the callout is not needed. */}
|
|
176
|
+
{schemaReadonly && !readonly && (
|
|
177
|
+
<Callout.Root valence='info' classNames='mlb-cardSpacingBlock'>
|
|
200
178
|
<Callout.Title>{t('system schema description')}</Callout.Title>
|
|
201
179
|
</Callout.Root>
|
|
202
180
|
)}
|
|
203
181
|
|
|
204
182
|
{/* TODO(burdon): Is the form read-only or just the schema? */}
|
|
205
|
-
{
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
Custom={custom}
|
|
214
|
-
/>
|
|
215
|
-
|
|
216
|
-
<div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
|
|
217
|
-
<h2 className={mx(inputTextLabel)}>{t('fields label')}</h2>
|
|
218
|
-
|
|
219
|
-
<List.Root<FieldType>
|
|
220
|
-
items={view.projection.fields}
|
|
221
|
-
isItem={Schema.is(FieldSchema)}
|
|
222
|
-
getId={(field) => field.id}
|
|
223
|
-
onMove={readonly ? undefined : handleMove}
|
|
183
|
+
<Form.Root schema={viewSchema} values={viewValues} fieldMap={fieldMap} autoSave onSave={handleUpdate}>
|
|
184
|
+
<Form.FieldSet />
|
|
185
|
+
|
|
186
|
+
<FormFieldLabel label={t('fields label')} asChild />
|
|
187
|
+
<FieldList
|
|
188
|
+
schema={schema}
|
|
189
|
+
view={view}
|
|
190
|
+
registry={registry}
|
|
224
191
|
readonly={readonly}
|
|
225
|
-
|
|
226
|
-
{
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
return (
|
|
233
|
-
<List.Item<FieldType>
|
|
234
|
-
key={field.id}
|
|
235
|
-
item={field}
|
|
236
|
-
classNames={listItemGrid}
|
|
237
|
-
aria-expanded={expandedField === field.id}
|
|
238
|
-
>
|
|
239
|
-
<div
|
|
240
|
-
role='none'
|
|
241
|
-
className={mx(subtleHover, listItemGrid, 'rounded-sm cursor-pointer min-bs-10')}
|
|
242
|
-
>
|
|
243
|
-
<List.ItemDragHandle disabled={readonly} />
|
|
244
|
-
<List.ItemTitle
|
|
245
|
-
classNames={hidden && 'text-subdued'}
|
|
246
|
-
onClick={() => handleToggleField(field)}
|
|
247
|
-
>
|
|
248
|
-
{field.path}
|
|
249
|
-
</List.ItemTitle>
|
|
250
|
-
<List.ItemButton
|
|
251
|
-
label={t(hidden ? 'show field label' : 'hide field label')}
|
|
252
|
-
data-testid={hidden ? 'show-field-button' : 'hide-field-button'}
|
|
253
|
-
icon={hidden ? 'ph--eye-closed--regular' : 'ph--eye--regular'}
|
|
254
|
-
autoHide={false}
|
|
255
|
-
disabled={readonly || (!hidden && projectionModel.fields.length <= 1)}
|
|
256
|
-
onClick={() => (hidden ? handleShow(field.path) : handleHide(field.id))}
|
|
257
|
-
/>
|
|
258
|
-
{mode === 'schema' && (
|
|
259
|
-
<>
|
|
260
|
-
<List.ItemDeleteButton
|
|
261
|
-
label={t('delete field label')}
|
|
262
|
-
autoHide={false}
|
|
263
|
-
disabled={readonly || schemaReadonly || view.projection.fields.length <= 1}
|
|
264
|
-
onClick={() => handleDelete(field.id)}
|
|
265
|
-
data-testid='field.delete'
|
|
266
|
-
/>
|
|
267
|
-
<IconButton
|
|
268
|
-
iconOnly
|
|
269
|
-
variant='ghost'
|
|
270
|
-
label={t('toggle expand label', { ns: 'os' })}
|
|
271
|
-
icon={
|
|
272
|
-
expandedField === field.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'
|
|
273
|
-
}
|
|
274
|
-
onClick={() => handleToggleField(field)}
|
|
275
|
-
/>
|
|
276
|
-
</>
|
|
277
|
-
)}
|
|
278
|
-
</div>
|
|
279
|
-
{expandedField === field.id && mode === 'schema' && (
|
|
280
|
-
<div role='none' className='col-span-5 mbs-1 mbe-1 border border-separator rounded-md'>
|
|
281
|
-
<FieldEditor
|
|
282
|
-
readonly={readonly || schemaReadonly ? 'disabled-input' : false}
|
|
283
|
-
projection={projectionModel}
|
|
284
|
-
field={field}
|
|
285
|
-
registry={registry}
|
|
286
|
-
onSave={handleClose}
|
|
287
|
-
/>
|
|
288
|
-
</div>
|
|
289
|
-
)}
|
|
290
|
-
</List.Item>
|
|
291
|
-
);
|
|
292
|
-
})}
|
|
293
|
-
</div>
|
|
294
|
-
</>
|
|
295
|
-
)}
|
|
296
|
-
</List.Root>
|
|
297
|
-
</div>
|
|
298
|
-
|
|
299
|
-
{!readonly && !expandedField && mode === 'schema' && (
|
|
300
|
-
<div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
|
|
192
|
+
showHeading={showHeading}
|
|
193
|
+
onDelete={handleDelete}
|
|
194
|
+
/>
|
|
195
|
+
</Form.Root>
|
|
196
|
+
|
|
197
|
+
{!readonly && !expandedField && (
|
|
198
|
+
<div role='none' className='mlb-cardSpacingBlock'>
|
|
301
199
|
<IconButton
|
|
302
200
|
icon='ph--plus--regular'
|
|
303
|
-
label={t('
|
|
201
|
+
label={t('add property button label')}
|
|
304
202
|
onClick={readonly ? undefined : handleAdd}
|
|
305
203
|
// TODO(burdon): Show field limit in ux (not tooltip).
|
|
306
204
|
disabled={view.projection.fields.length >= VIEW_FIELD_LIMIT}
|
|
@@ -313,17 +211,180 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
313
211
|
},
|
|
314
212
|
);
|
|
315
213
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
214
|
+
type FieldListProps = Pick<ViewEditorProps, 'schema' | 'view' | 'registry' | 'readonly' | 'showHeading' | 'onDelete'>;
|
|
215
|
+
|
|
216
|
+
const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDelete }: FieldListProps) => {
|
|
217
|
+
const atomRegistry = useContext(RegistryContext);
|
|
218
|
+
const schemaReadonly = !isMutable(schema);
|
|
219
|
+
const { t } = useTranslation(translationKey);
|
|
220
|
+
|
|
221
|
+
// Subscribe to view changes for reactivity.
|
|
222
|
+
const [viewSnapshot] = useObject(view);
|
|
223
|
+
|
|
224
|
+
const projectionModel = useMemo(() => {
|
|
225
|
+
// Use reactive and mutable version of json schema when schema is mutable.
|
|
226
|
+
const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
|
|
227
|
+
|
|
228
|
+
// Always use createEchoChangeCallback since the view is ECHO-backed.
|
|
229
|
+
// Pass schema only when mutable to allow schema mutations.
|
|
230
|
+
const change = createEchoChangeCallback(view, schema instanceof EchoSchema ? schema : undefined);
|
|
231
|
+
|
|
232
|
+
const model = new ProjectionModel({
|
|
233
|
+
registry: atomRegistry,
|
|
234
|
+
view,
|
|
235
|
+
baseSchema: jsonSchema,
|
|
236
|
+
change,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
return model;
|
|
240
|
+
}, [atomRegistry, schema, view]);
|
|
241
|
+
|
|
242
|
+
const [expandedField, setExpandedField] = useState<FieldType['id']>();
|
|
243
|
+
|
|
244
|
+
const handleToggleField = useCallback(
|
|
245
|
+
(field: FieldType) => {
|
|
246
|
+
setExpandedField((prevExpandedFieldId) => (prevExpandedFieldId === field.id ? undefined : field.id));
|
|
247
|
+
},
|
|
248
|
+
[readonly],
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
const handleDelete = useCallback(
|
|
252
|
+
(fieldId: string) => {
|
|
253
|
+
invariant(!readonly);
|
|
254
|
+
if (fieldId === expandedField) {
|
|
255
|
+
setExpandedField(undefined);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
onDelete?.(fieldId);
|
|
259
|
+
},
|
|
260
|
+
[expandedField, onDelete, readonly],
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
const handleMove = useCallback(
|
|
264
|
+
(fromIndex: number, toIndex: number) => {
|
|
265
|
+
invariant(!readonly);
|
|
266
|
+
Obj.change(view, (v) => {
|
|
267
|
+
// NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
|
|
268
|
+
const fields = [...v.projection.fields];
|
|
269
|
+
const [moved] = fields.splice(fromIndex, 1);
|
|
270
|
+
fields.splice(toIndex, 0, moved);
|
|
271
|
+
v.projection.fields = fields;
|
|
272
|
+
});
|
|
273
|
+
},
|
|
274
|
+
[view, readonly],
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
const handleClose = useCallback(() => setExpandedField(undefined), []);
|
|
278
|
+
|
|
279
|
+
const handleHide = useCallback(
|
|
280
|
+
(fieldId: string) => {
|
|
281
|
+
setExpandedField(undefined);
|
|
282
|
+
projectionModel.hideFieldProjection(fieldId);
|
|
283
|
+
},
|
|
284
|
+
[projectionModel],
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
const handleShow = useCallback(
|
|
288
|
+
(property: string) => {
|
|
289
|
+
setExpandedField(undefined);
|
|
290
|
+
projectionModel.showFieldProjection(property as JsonProp);
|
|
291
|
+
},
|
|
292
|
+
[projectionModel],
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
if (!viewSnapshot) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return (
|
|
300
|
+
<List.Root<FieldType>
|
|
301
|
+
items={viewSnapshot.projection.fields as FieldType[]}
|
|
302
|
+
isItem={Schema.is(FieldSchema)}
|
|
303
|
+
getId={(field) => field.id}
|
|
304
|
+
onMove={readonly ? undefined : handleMove}
|
|
305
|
+
readonly={readonly}
|
|
306
|
+
>
|
|
307
|
+
{({ items: fields }) => (
|
|
308
|
+
<>
|
|
309
|
+
{showHeading && <h3 className='text-sm'>{t('field path label')}</h3>}
|
|
310
|
+
<div role='list' className={listGrid}>
|
|
311
|
+
{fields?.map((field) => {
|
|
312
|
+
const hidden = field.visible === false;
|
|
313
|
+
return (
|
|
314
|
+
<List.Item<FieldType>
|
|
315
|
+
key={field.id}
|
|
316
|
+
item={field}
|
|
317
|
+
classNames={listItemGrid}
|
|
318
|
+
aria-expanded={expandedField === field.id}
|
|
319
|
+
>
|
|
320
|
+
<div role='none' className={mx(subtleHover, listItemGrid, 'rounded-sm cursor-pointer min-bs-10')}>
|
|
321
|
+
<List.ItemDragHandle disabled={readonly || schemaReadonly} />
|
|
322
|
+
<List.ItemTitle classNames={hidden && 'text-subdued'} onClick={() => handleToggleField(field)}>
|
|
323
|
+
{field.path}
|
|
324
|
+
</List.ItemTitle>
|
|
325
|
+
<List.ItemButton
|
|
326
|
+
label={t(hidden ? 'show field label' : 'hide field label')}
|
|
327
|
+
data-testid={hidden ? 'show-field-button' : 'hide-field-button'}
|
|
328
|
+
icon={hidden ? 'ph--eye-closed--regular' : 'ph--eye--regular'}
|
|
329
|
+
autoHide={false}
|
|
330
|
+
disabled={readonly || (!hidden && projectionModel.getFields().length <= 1)}
|
|
331
|
+
onClick={() => (hidden ? handleShow(field.path) : handleHide(field.id))}
|
|
332
|
+
/>
|
|
333
|
+
{!readonly && (
|
|
334
|
+
<>
|
|
335
|
+
<List.ItemDeleteButton
|
|
336
|
+
label={t('delete field label')}
|
|
337
|
+
autoHide={false}
|
|
338
|
+
disabled={readonly || schemaReadonly || viewSnapshot.projection.fields.length <= 1}
|
|
339
|
+
onClick={() => handleDelete(field.id)}
|
|
340
|
+
data-testid='field.delete'
|
|
341
|
+
/>
|
|
342
|
+
<IconButton
|
|
343
|
+
iconOnly
|
|
344
|
+
variant='ghost'
|
|
345
|
+
label={t('toggle expand label', { ns: osTranslations })}
|
|
346
|
+
icon={expandedField === field.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'}
|
|
347
|
+
onClick={() => handleToggleField(field)}
|
|
348
|
+
data-testid='field.toggle'
|
|
349
|
+
/>
|
|
350
|
+
</>
|
|
351
|
+
)}
|
|
352
|
+
</div>
|
|
353
|
+
{expandedField === field.id && !readonly && (
|
|
354
|
+
<div role='none' className='col-span-5 mbs-1 mbe-1 border border-separator rounded-md'>
|
|
355
|
+
<FieldEditor
|
|
356
|
+
readonly={readonly || schemaReadonly}
|
|
357
|
+
registry={registry}
|
|
358
|
+
projection={projectionModel}
|
|
359
|
+
field={field}
|
|
360
|
+
onSave={handleClose}
|
|
361
|
+
/>
|
|
362
|
+
</div>
|
|
363
|
+
)}
|
|
364
|
+
</List.Item>
|
|
365
|
+
);
|
|
366
|
+
})}
|
|
367
|
+
</div>
|
|
368
|
+
</>
|
|
369
|
+
)}
|
|
370
|
+
</List.Root>
|
|
371
|
+
);
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
const customFields = ({
|
|
375
|
+
types,
|
|
376
|
+
tags,
|
|
377
|
+
}: Pick<ViewEditorProps, 'types' | 'tags'>): Record<string, FormFieldComponent> => ({
|
|
378
|
+
query: ({ type, readonly, label, getValue, onValueChange }: FormFieldComponentProps) => {
|
|
379
|
+
const handleChange = useCallback<NonNullable<QueryFormProps['onChange']>>(
|
|
380
|
+
(query) => onValueChange(type, query.ast),
|
|
381
|
+
[onValueChange],
|
|
321
382
|
);
|
|
322
383
|
|
|
323
384
|
return (
|
|
324
385
|
<Input.Root>
|
|
325
|
-
<
|
|
326
|
-
<QueryForm initialQuery={
|
|
386
|
+
<FormFieldLabel readonly={readonly} label={label} />
|
|
387
|
+
<QueryForm initialQuery={getValue()} types={types} tags={tags} onChange={handleChange} />
|
|
327
388
|
</Input.Root>
|
|
328
389
|
);
|
|
329
390
|
},
|
|
@@ -5,25 +5,27 @@
|
|
|
5
5
|
import React, { type PropsWithChildren } from 'react';
|
|
6
6
|
|
|
7
7
|
import { type ThemedClassName } from '@dxos/react-ui';
|
|
8
|
-
import { Card } from '@dxos/react-ui-
|
|
8
|
+
import { Card } from '@dxos/react-ui-mosaic';
|
|
9
9
|
import { JsonFilter } from '@dxos/react-ui-syntax-highlighter';
|
|
10
|
-
import {
|
|
10
|
+
import { textBlockWidth } from '@dxos/ui-theme';
|
|
11
11
|
|
|
12
12
|
type TestLayoutProps = ThemedClassName<PropsWithChildren<{ json?: any }>>;
|
|
13
13
|
|
|
14
14
|
export const TestLayout = ({ classNames, children, json }: TestLayoutProps) => {
|
|
15
15
|
return (
|
|
16
|
-
<div className='is-full bs-full grid grid-cols-[1fr_1fr] gap-
|
|
17
|
-
<
|
|
18
|
-
<
|
|
16
|
+
<div className='is-full bs-full grid grid-cols-[1fr_1fr] p-4 gap-4 overflow-hidden'>
|
|
17
|
+
<TestPanel classNames={['flex flex-col bs-full overflow-y-auto', classNames]}>{children}</TestPanel>
|
|
18
|
+
<TestPanel classNames={'overflow-hidden'}>
|
|
19
|
+
<JsonFilter testId='debug' data={json} classNames='bs-full text-xs' />
|
|
20
|
+
</TestPanel>
|
|
19
21
|
</div>
|
|
20
22
|
);
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
type TestPanelProps = ThemedClassName<PropsWithChildren>;
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
return <Card.
|
|
27
|
+
const TestPanel = ({ classNames, children }: TestPanelProps) => {
|
|
28
|
+
return <Card.Root classNames={['bs-full overflow-y-auto', textBlockWidth, classNames]}>{children}</Card.Root>;
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
// Symbol for accessing debug objects in tests.
|
package/src/hooks/index.ts
CHANGED