@dxos/react-ui-form 0.8.4-main.406dc2a → 0.8.4-main.52d7546f51
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 +2228 -2254
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +2228 -2254
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +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/{ControlSection/ControlSection.stories.d.ts → Form/fields/SelectOptionField.stories.d.ts} +12 -14
- 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/Settings/Settings.d.ts +56 -0
- package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
- package/dist/types/src/components/{Form/custom/SelectOptionsInput.stories.d.ts → Settings/Settings.stories.d.ts} +10 -8
- package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -0
- package/dist/types/src/components/Settings/index.d.ts +2 -0
- package/dist/types/src/components/Settings/index.d.ts.map +1 -0
- 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/index.d.ts +1 -1
- package/dist/types/src/components/index.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/FieldEditor/FieldEditor.stories.tsx +43 -22
- 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 +151 -248
- package/src/components/Form/Form.tsx +329 -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 +127 -0
- package/src/components/Form/README.md +3 -0
- package/src/components/Form/fields/ArrayField.tsx +154 -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 +189 -0
- package/src/components/Form/fields/SelectField.tsx +65 -0
- package/src/components/Form/{custom/SelectOptionsInput.stories.tsx → fields/SelectOptionField.stories.tsx} +18 -15
- 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 +21 -14
- package/src/components/ObjectPicker/ObjectPicker.tsx +76 -62
- package/src/components/Settings/Settings.stories.tsx +56 -0
- package/src/components/Settings/Settings.tsx +234 -0
- package/src/components/{ControlSection → Settings}/index.ts +1 -1
- package/src/components/ViewEditor/ViewEditor.stories.tsx +43 -41
- package/src/components/ViewEditor/ViewEditor.test.tsx +5 -5
- package/src/components/ViewEditor/ViewEditor.tsx +245 -192
- package/src/components/index.ts +1 -1
- 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/ControlSection/ControlSection.d.ts +0 -23
- package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +0 -1
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +0 -1
- package/dist/types/src/components/ControlSection/index.d.ts +0 -2
- package/dist/types/src/components/ControlSection/index.d.ts.map +0 -1
- 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/ControlSection/ControlSection.stories.tsx +0 -38
- package/src/components/ControlSection/ControlSection.tsx +0 -153
- 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,19 +70,32 @@ 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
|
-
const [expandedField, setExpandedField] = useState<FieldType['id']>();
|
|
80
99
|
|
|
81
100
|
const queueTarget = Match.value(view.query.ast).pipe(
|
|
82
101
|
Match.when({ type: 'options' }, ({ options }) => {
|
|
@@ -96,9 +115,8 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
96
115
|
: QueryAST.Query.annotations({ title: 'Query' }),
|
|
97
116
|
});
|
|
98
117
|
|
|
99
|
-
if (mode === '
|
|
118
|
+
if (mode === 'tag') {
|
|
100
119
|
return Schema.Struct({
|
|
101
|
-
name: Schema.optional(Schema.String.annotations({ title: 'Name' })),
|
|
102
120
|
...base.fields,
|
|
103
121
|
target: Schema.optional(Schema.String.annotations({ title: 'Target Queue' })),
|
|
104
122
|
}).pipe(Schema.mutable);
|
|
@@ -111,219 +129,254 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
111
129
|
// TODO(burdon): Settings should have domain name owned by user.
|
|
112
130
|
const viewValues = useMemo(
|
|
113
131
|
() => ({
|
|
114
|
-
name: view.name,
|
|
115
132
|
query: mode === 'schema' ? getTypenameFromQuery(view.query.ast) : view.query.ast,
|
|
116
133
|
target: queueTarget,
|
|
117
134
|
}),
|
|
118
|
-
[
|
|
135
|
+
[mode, view.query.ast, queueTarget],
|
|
119
136
|
);
|
|
120
137
|
|
|
121
|
-
const
|
|
122
|
-
(
|
|
123
|
-
|
|
124
|
-
},
|
|
125
|
-
[readonly],
|
|
138
|
+
const fieldMap = useMemo<FormFieldMap | undefined>(
|
|
139
|
+
() => (mode === 'tag' ? customFields({ types, tags }) : undefined),
|
|
140
|
+
[mode, types, tags],
|
|
126
141
|
);
|
|
127
142
|
|
|
128
|
-
// TODO(burdon): Check if mutable; variant of useCallback that return undefined if readonly?
|
|
129
|
-
|
|
130
|
-
const handleAdd = useCallback(() => {
|
|
131
|
-
invariant(!readonly);
|
|
132
|
-
const field = projectionModel.createFieldProjection();
|
|
133
|
-
setExpandedField(field.id);
|
|
134
|
-
}, [schema, projectionModel, readonly]);
|
|
135
|
-
|
|
136
143
|
const handleUpdate = useCallback(
|
|
137
144
|
(values: any) => {
|
|
138
|
-
invariant(!readonly);
|
|
139
145
|
requestAnimationFrame(() => {
|
|
140
|
-
if ('name' in values && view.name !== values.name) {
|
|
141
|
-
view.name = values.name;
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
146
|
const query = mode === 'schema' ? Query.select(Filter.typename(values.query)).ast : values.query;
|
|
146
147
|
onQueryChanged?.(query, values.target);
|
|
147
148
|
});
|
|
148
149
|
},
|
|
149
|
-
[onQueryChanged,
|
|
150
|
+
[onQueryChanged, view, queueTarget, mode],
|
|
150
151
|
);
|
|
151
152
|
|
|
152
153
|
const handleDelete = useCallback(
|
|
153
154
|
(fieldId: string) => {
|
|
154
155
|
invariant(!readonly);
|
|
155
|
-
if (fieldId === expandedField) {
|
|
156
|
-
setExpandedField(undefined);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
156
|
onDelete?.(fieldId);
|
|
160
157
|
},
|
|
161
|
-
[
|
|
162
|
-
);
|
|
163
|
-
|
|
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],
|
|
158
|
+
[onDelete, readonly],
|
|
174
159
|
);
|
|
175
160
|
|
|
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
161
|
return (
|
|
197
162
|
<div role='none' className={mx(classNames)}>
|
|
198
|
-
{
|
|
199
|
-
|
|
163
|
+
{/* If readonly is set, then the callout is not needed. */}
|
|
164
|
+
{schemaReadonly && !readonly && (
|
|
165
|
+
<Callout.Root valence='info' classNames='mlb-cardSpacingBlock'>
|
|
200
166
|
<Callout.Title>{t('system schema description')}</Callout.Title>
|
|
201
167
|
</Callout.Root>
|
|
202
168
|
)}
|
|
203
169
|
|
|
204
170
|
{/* 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}
|
|
171
|
+
<Form.Root schema={viewSchema} values={viewValues} fieldMap={fieldMap} autoSave onSave={handleUpdate}>
|
|
172
|
+
<Form.FieldSet />
|
|
173
|
+
|
|
174
|
+
<FormFieldLabel label={t('fields label')} asChild />
|
|
175
|
+
<FieldList
|
|
176
|
+
schema={schema}
|
|
177
|
+
view={view}
|
|
178
|
+
registry={registry}
|
|
224
179
|
readonly={readonly}
|
|
225
|
-
|
|
226
|
-
{
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
<div role='list' className={listGrid}>
|
|
230
|
-
{fields?.map((field) => {
|
|
231
|
-
const hidden = field.visible === false;
|
|
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'}>
|
|
301
|
-
<IconButton
|
|
302
|
-
icon='ph--plus--regular'
|
|
303
|
-
label={t('button add property')}
|
|
304
|
-
onClick={readonly ? undefined : handleAdd}
|
|
305
|
-
// TODO(burdon): Show field limit in ux (not tooltip).
|
|
306
|
-
disabled={view.projection.fields.length >= VIEW_FIELD_LIMIT}
|
|
307
|
-
classNames='is-full'
|
|
308
|
-
/>
|
|
309
|
-
</div>
|
|
310
|
-
)}
|
|
180
|
+
showHeading={showHeading}
|
|
181
|
+
onDelete={handleDelete}
|
|
182
|
+
/>
|
|
183
|
+
</Form.Root>
|
|
311
184
|
</div>
|
|
312
185
|
);
|
|
313
186
|
},
|
|
314
187
|
);
|
|
315
188
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
189
|
+
type FieldListProps = Pick<ViewEditorProps, 'schema' | 'view' | 'registry' | 'readonly' | 'showHeading' | 'onDelete'>;
|
|
190
|
+
|
|
191
|
+
const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDelete }: FieldListProps) => {
|
|
192
|
+
const atomRegistry = useContext(RegistryContext);
|
|
193
|
+
const schemaReadonly = !isMutable(schema);
|
|
194
|
+
const { t } = useTranslation(translationKey);
|
|
195
|
+
|
|
196
|
+
// Subscribe to view changes for reactivity.
|
|
197
|
+
const [viewSnapshot] = useObject(view);
|
|
198
|
+
|
|
199
|
+
const projectionModel = useMemo(() => {
|
|
200
|
+
// Use reactive and mutable version of json schema when schema is mutable.
|
|
201
|
+
const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
|
|
202
|
+
|
|
203
|
+
// Always use createEchoChangeCallback since the view is ECHO-backed.
|
|
204
|
+
// Pass schema only when mutable to allow schema mutations.
|
|
205
|
+
const change = createEchoChangeCallback(view, schema instanceof EchoSchema ? schema : undefined);
|
|
206
|
+
|
|
207
|
+
const model = new ProjectionModel({
|
|
208
|
+
registry: atomRegistry,
|
|
209
|
+
view,
|
|
210
|
+
baseSchema: jsonSchema,
|
|
211
|
+
change,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
return model;
|
|
215
|
+
}, [atomRegistry, schema, view]);
|
|
216
|
+
|
|
217
|
+
const [expandedField, setExpandedField] = useState<FieldType['id']>();
|
|
218
|
+
|
|
219
|
+
const handleAdd = useCallback(() => {
|
|
220
|
+
invariant(!readonly);
|
|
221
|
+
const field = projectionModel.createFieldProjection();
|
|
222
|
+
setExpandedField(field.id);
|
|
223
|
+
}, [projectionModel, readonly]);
|
|
224
|
+
|
|
225
|
+
const handleToggleField = useCallback(
|
|
226
|
+
(field: FieldType) => {
|
|
227
|
+
setExpandedField((prevExpandedFieldId) => (prevExpandedFieldId === field.id ? undefined : field.id));
|
|
228
|
+
},
|
|
229
|
+
[readonly],
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
const handleDelete = useCallback(
|
|
233
|
+
(fieldId: string) => {
|
|
234
|
+
invariant(!readonly);
|
|
235
|
+
if (fieldId === expandedField) {
|
|
236
|
+
setExpandedField(undefined);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
onDelete?.(fieldId);
|
|
240
|
+
},
|
|
241
|
+
[expandedField, onDelete, readonly],
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
const handleMove = useCallback(
|
|
245
|
+
(fromIndex: number, toIndex: number) => {
|
|
246
|
+
invariant(!readonly);
|
|
247
|
+
Obj.change(view, (v) => {
|
|
248
|
+
// NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
|
|
249
|
+
const fields = [...v.projection.fields];
|
|
250
|
+
const [moved] = fields.splice(fromIndex, 1);
|
|
251
|
+
fields.splice(toIndex, 0, moved);
|
|
252
|
+
v.projection.fields = fields;
|
|
253
|
+
});
|
|
254
|
+
},
|
|
255
|
+
[view, readonly],
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const handleClose = useCallback(() => setExpandedField(undefined), []);
|
|
259
|
+
|
|
260
|
+
const handleHide = useCallback(
|
|
261
|
+
(fieldId: string) => {
|
|
262
|
+
setExpandedField(undefined);
|
|
263
|
+
projectionModel.hideFieldProjection(fieldId);
|
|
264
|
+
},
|
|
265
|
+
[projectionModel],
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
const handleShow = useCallback(
|
|
269
|
+
(property: string) => {
|
|
270
|
+
setExpandedField(undefined);
|
|
271
|
+
projectionModel.showFieldProjection(property as JsonProp);
|
|
272
|
+
},
|
|
273
|
+
[projectionModel],
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
if (!viewSnapshot) {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
<List.Root<FieldType>
|
|
282
|
+
items={viewSnapshot.projection.fields as FieldType[]}
|
|
283
|
+
isItem={Schema.is(FieldSchema)}
|
|
284
|
+
getId={(field) => field.id}
|
|
285
|
+
onMove={readonly ? undefined : handleMove}
|
|
286
|
+
readonly={readonly}
|
|
287
|
+
>
|
|
288
|
+
{({ items: fields }) => (
|
|
289
|
+
<>
|
|
290
|
+
{showHeading && <h3 className='text-sm'>{t('field path label')}</h3>}
|
|
291
|
+
<div role='list' className={listGrid}>
|
|
292
|
+
{fields?.map((field) => {
|
|
293
|
+
const hidden = field.visible === false;
|
|
294
|
+
return (
|
|
295
|
+
<List.Item<FieldType>
|
|
296
|
+
key={field.id}
|
|
297
|
+
item={field}
|
|
298
|
+
classNames={listItemGrid}
|
|
299
|
+
aria-expanded={expandedField === field.id}
|
|
300
|
+
>
|
|
301
|
+
<div role='none' className={mx(subtleHover, listItemGrid, 'rounded-sm cursor-pointer min-bs-10')}>
|
|
302
|
+
<List.ItemDragHandle disabled={readonly || schemaReadonly} />
|
|
303
|
+
<List.ItemTitle classNames={hidden && 'text-subdued'} onClick={() => handleToggleField(field)}>
|
|
304
|
+
{field.path}
|
|
305
|
+
</List.ItemTitle>
|
|
306
|
+
<List.ItemButton
|
|
307
|
+
label={t(hidden ? 'show field label' : 'hide field label')}
|
|
308
|
+
data-testid={hidden ? 'show-field-button' : 'hide-field-button'}
|
|
309
|
+
icon={hidden ? 'ph--eye-closed--regular' : 'ph--eye--regular'}
|
|
310
|
+
autoHide={false}
|
|
311
|
+
disabled={readonly || (!hidden && projectionModel.getFields().length <= 1)}
|
|
312
|
+
onClick={() => (hidden ? handleShow(field.path) : handleHide(field.id))}
|
|
313
|
+
/>
|
|
314
|
+
{!readonly && (
|
|
315
|
+
<>
|
|
316
|
+
<List.ItemDeleteButton
|
|
317
|
+
label={t('delete field label')}
|
|
318
|
+
autoHide={false}
|
|
319
|
+
disabled={readonly || schemaReadonly || viewSnapshot.projection.fields.length <= 1}
|
|
320
|
+
onClick={() => handleDelete(field.id)}
|
|
321
|
+
data-testid='field.delete'
|
|
322
|
+
/>
|
|
323
|
+
<IconButton
|
|
324
|
+
iconOnly
|
|
325
|
+
variant='ghost'
|
|
326
|
+
label={t('toggle expand label', { ns: osTranslations })}
|
|
327
|
+
icon={expandedField === field.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'}
|
|
328
|
+
onClick={() => handleToggleField(field)}
|
|
329
|
+
data-testid='field.toggle'
|
|
330
|
+
/>
|
|
331
|
+
</>
|
|
332
|
+
)}
|
|
333
|
+
</div>
|
|
334
|
+
{expandedField === field.id && !readonly && (
|
|
335
|
+
<div role='none' className='col-span-5 mbs-1 mbe-1 border border-separator rounded-md'>
|
|
336
|
+
<FieldEditor
|
|
337
|
+
readonly={readonly || schemaReadonly}
|
|
338
|
+
registry={registry}
|
|
339
|
+
projection={projectionModel}
|
|
340
|
+
field={field}
|
|
341
|
+
onSave={handleClose}
|
|
342
|
+
/>
|
|
343
|
+
</div>
|
|
344
|
+
)}
|
|
345
|
+
</List.Item>
|
|
346
|
+
);
|
|
347
|
+
})}
|
|
348
|
+
</div>
|
|
349
|
+
{!readonly && !expandedField && (
|
|
350
|
+
<div role='none' className='mlb-cardSpacingBlock'>
|
|
351
|
+
<IconButton
|
|
352
|
+
icon='ph--plus--regular'
|
|
353
|
+
label={t('add property button label')}
|
|
354
|
+
onClick={handleAdd}
|
|
355
|
+
disabled={viewSnapshot.projection.fields.length >= VIEW_FIELD_LIMIT}
|
|
356
|
+
classNames='is-full'
|
|
357
|
+
/>
|
|
358
|
+
</div>
|
|
359
|
+
)}
|
|
360
|
+
</>
|
|
361
|
+
)}
|
|
362
|
+
</List.Root>
|
|
363
|
+
);
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const customFields = ({
|
|
367
|
+
types,
|
|
368
|
+
tags,
|
|
369
|
+
}: Pick<ViewEditorProps, 'types' | 'tags'>): Record<string, FormFieldComponent> => ({
|
|
370
|
+
query: ({ type, readonly, label, getValue, onValueChange }: FormFieldComponentProps) => {
|
|
371
|
+
const handleChange = useCallback<NonNullable<QueryFormProps['onChange']>>(
|
|
372
|
+
(query) => onValueChange(type, query.ast),
|
|
373
|
+
[onValueChange],
|
|
321
374
|
);
|
|
322
375
|
|
|
323
376
|
return (
|
|
324
377
|
<Input.Root>
|
|
325
|
-
<
|
|
326
|
-
<QueryForm initialQuery={
|
|
378
|
+
<FormFieldLabel readonly={readonly} label={label} />
|
|
379
|
+
<QueryForm initialQuery={getValue()} types={types} tags={tags} onChange={handleChange} />
|
|
327
380
|
</Input.Root>
|
|
328
381
|
);
|
|
329
382
|
},
|
package/src/components/index.ts
CHANGED
|
@@ -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