@dxos/react-ui-form 0.8.4-main.7ace549 → 0.8.4-main.937b3ca
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 +2191 -2283
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +2191 -2283
- 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 +7 -4
- 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 +9 -3
- 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 -27
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +53 -92
- 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} +6 -3
- 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 +5 -2
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +6 -8
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +5 -2
- 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 +6 -3
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +1 -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 -44
- package/src/components/ControlSection/ControlSection.tsx +8 -8
- package/src/components/FieldEditor/FieldEditor.stories.tsx +42 -21
- package/src/components/FieldEditor/FieldEditor.tsx +72 -75
- package/src/components/Form/Form-old.stories.tsx +314 -0
- package/src/components/Form/Form.stories.tsx +146 -246
- package/src/components/Form/Form.tsx +319 -110
- 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} +20 -14
- 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 +15 -8
- package/src/components/ObjectPicker/ObjectPicker.tsx +76 -62
- package/src/components/ViewEditor/ViewEditor.stories.tsx +29 -25
- package/src/components/ViewEditor/ViewEditor.test.tsx +6 -2
- package/src/components/ViewEditor/ViewEditor.tsx +220 -154
- package/src/components/testing.tsx +9 -7
- package/src/hooks/index.ts +2 -3
- package/src/hooks/useFormHandler.ts +356 -0
- package/src/hooks/useKeyHandler.tsx +37 -0
- package/src/translations.ts +8 -6
- package/src/util/index.ts +1 -1
- package/src/util/properties.ts +18 -0
- package/src/util/refs.ts +7 -13
- 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 -127
- package/src/components/Form/Defaults.tsx +0 -205
- package/src/components/Form/FormActions.tsx +0 -59
- package/src/components/Form/FormContent.tsx +0 -362
- 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 -34
- 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
|
@@ -7,16 +7,16 @@ import * as Schema from 'effect/Schema';
|
|
|
7
7
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
8
8
|
|
|
9
9
|
import { DXN, Filter, Obj, Query, type QueryAST, Tag, Type } from '@dxos/echo';
|
|
10
|
-
import { type EchoSchema, Format } from '@dxos/echo/internal';
|
|
10
|
+
import { type EchoSchema, Format, type Mutable } from '@dxos/echo/internal';
|
|
11
11
|
import { useQuery } from '@dxos/react-client/echo';
|
|
12
|
-
import {
|
|
12
|
+
import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
|
|
13
13
|
import { useAsyncEffect } from '@dxos/react-ui';
|
|
14
14
|
import { withTheme } from '@dxos/react-ui/testing';
|
|
15
15
|
import { type ProjectionModel, View, getTypenameFromQuery } from '@dxos/schema';
|
|
16
16
|
import { Employer, Organization, Person, Project } from '@dxos/types';
|
|
17
17
|
|
|
18
18
|
import { translations } from '../../translations';
|
|
19
|
-
import { TestLayout,
|
|
19
|
+
import { TestLayout, VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
|
|
20
20
|
|
|
21
21
|
import { ViewEditor, type ViewEditorProps } from './ViewEditor';
|
|
22
22
|
|
|
@@ -24,7 +24,7 @@ const types = [
|
|
|
24
24
|
// TODO(burdon): Get label from annotation.
|
|
25
25
|
{ value: Organization.Organization.typename, label: 'Organization' },
|
|
26
26
|
{ value: Person.Person.typename, label: 'Person' },
|
|
27
|
-
{ value:
|
|
27
|
+
{ value: Project.Project.typename, label: 'Project' },
|
|
28
28
|
{ value: Employer.Employer.typename, label: 'Employer' },
|
|
29
29
|
];
|
|
30
30
|
|
|
@@ -38,12 +38,12 @@ export type ViewEditorDebugObjects = {
|
|
|
38
38
|
type StoryProps = Pick<ViewEditorProps, 'readonly' | 'mode'>;
|
|
39
39
|
|
|
40
40
|
const DefaultStory = (props: StoryProps) => {
|
|
41
|
-
const { space } =
|
|
41
|
+
const { space } = useClientStory();
|
|
42
42
|
const [schema, setSchema] = useState<EchoSchema>();
|
|
43
43
|
const [view, setView] = useState<View.View>();
|
|
44
44
|
const projectionRef = useRef<ProjectionModel>(null);
|
|
45
45
|
|
|
46
|
-
const tags = useQuery(space, Filter.type(Tag.Tag));
|
|
46
|
+
const tags = useQuery(space?.db, Filter.type(Tag.Tag));
|
|
47
47
|
|
|
48
48
|
useAsyncEffect(async () => {
|
|
49
49
|
if (space) {
|
|
@@ -52,7 +52,7 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
52
52
|
email: Format.Email,
|
|
53
53
|
salary: Format.Currency(),
|
|
54
54
|
}).pipe(
|
|
55
|
-
Type.
|
|
55
|
+
Type.object({
|
|
56
56
|
typename: 'example.com/type/Test',
|
|
57
57
|
version: '0.1.0',
|
|
58
58
|
}),
|
|
@@ -63,7 +63,7 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
63
63
|
description: Schema.String,
|
|
64
64
|
completed: Schema.Boolean,
|
|
65
65
|
}).pipe(
|
|
66
|
-
Type.
|
|
66
|
+
Type.object({
|
|
67
67
|
typename: 'example.com/type/Alternate',
|
|
68
68
|
version: '0.1.0',
|
|
69
69
|
}),
|
|
@@ -90,7 +90,9 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
90
90
|
if (props.mode === 'tag') {
|
|
91
91
|
const queue = target && DXN.tryParse(target) ? target : undefined;
|
|
92
92
|
const query = queue ? Query.fromAst(newQuery).options({ queues: [queue] }) : Query.fromAst(newQuery);
|
|
93
|
-
view
|
|
93
|
+
Obj.change(view, (v) => {
|
|
94
|
+
v.query.ast = query.ast as Mutable<typeof query.ast>;
|
|
95
|
+
});
|
|
94
96
|
|
|
95
97
|
const typename = getTypenameFromQuery(query.ast);
|
|
96
98
|
const [newSchema] = await space.db.schemaRegistry.query({ typename }).run();
|
|
@@ -102,10 +104,14 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
102
104
|
query,
|
|
103
105
|
jsonSchema: newSchema.jsonSchema,
|
|
104
106
|
});
|
|
105
|
-
|
|
107
|
+
Obj.change(view, (v) => {
|
|
108
|
+
v.projection = Obj.getSnapshot(newView).projection as Mutable<typeof v.projection>;
|
|
109
|
+
});
|
|
106
110
|
setSchema(() => newSchema);
|
|
107
111
|
} else {
|
|
108
|
-
view
|
|
112
|
+
Obj.change(view, (v) => {
|
|
113
|
+
v.query.ast = newQuery as Mutable<typeof newQuery>;
|
|
114
|
+
});
|
|
109
115
|
schema.updateTypename(getTypenameFromQuery(newQuery));
|
|
110
116
|
}
|
|
111
117
|
},
|
|
@@ -140,20 +146,18 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
140
146
|
|
|
141
147
|
return (
|
|
142
148
|
<TestLayout json={json}>
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
/>
|
|
156
|
-
</TestPanel>
|
|
149
|
+
<ViewEditor
|
|
150
|
+
ref={projectionRef}
|
|
151
|
+
schema={schema}
|
|
152
|
+
view={view}
|
|
153
|
+
registry={space?.db.schemaRegistry}
|
|
154
|
+
mode={props.mode}
|
|
155
|
+
readonly={props.readonly}
|
|
156
|
+
types={types}
|
|
157
|
+
tags={tags}
|
|
158
|
+
onQueryChanged={updateViewQuery}
|
|
159
|
+
onDelete={handleDelete}
|
|
160
|
+
/>
|
|
157
161
|
</TestLayout>
|
|
158
162
|
);
|
|
159
163
|
};
|
|
@@ -54,7 +54,7 @@ describe('ViewEditor', () => {
|
|
|
54
54
|
const nameField = screen.getByText('name');
|
|
55
55
|
fireEvent.click(nameField);
|
|
56
56
|
|
|
57
|
-
const fieldInput = screen.getByPlaceholderText('
|
|
57
|
+
const fieldInput = screen.getByPlaceholderText('Property name');
|
|
58
58
|
fireEvent.change(fieldInput, { target: { value: 'new_property' } });
|
|
59
59
|
|
|
60
60
|
fireEvent.click(screen.getByTestId('save-button'));
|
|
@@ -93,8 +93,12 @@ describe('ViewEditor', () => {
|
|
|
93
93
|
const addButton = screen.getByText('Add property');
|
|
94
94
|
fireEvent.click(addButton);
|
|
95
95
|
|
|
96
|
+
// Open the last field.
|
|
97
|
+
const buttons = screen.getAllByTestId('field.toggle');
|
|
98
|
+
fireEvent.click(buttons.at(-1)!);
|
|
99
|
+
|
|
96
100
|
// Fill out the property field.
|
|
97
|
-
const fieldInput = screen.getByPlaceholderText('
|
|
101
|
+
const fieldInput = screen.getByPlaceholderText('Property name');
|
|
98
102
|
fireEvent.change(fieldInput, { target: { value: 'added_property' } });
|
|
99
103
|
|
|
100
104
|
// Click the format combo box and select the first option.
|
|
@@ -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
15
|
import { Callout, IconButton, Input, type ThemedClassName, useTranslation } from '@dxos/react-ui';
|
|
16
16
|
import { QueryForm, type QueryFormProps } from '@dxos/react-ui-components';
|
|
17
17
|
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
18
|
import {
|
|
21
19
|
FieldSchema,
|
|
22
20
|
type FieldType,
|
|
23
21
|
ProjectionModel,
|
|
24
22
|
VIEW_FIELD_LIMIT,
|
|
25
23
|
type View,
|
|
24
|
+
createDirectChangeCallback,
|
|
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';
|
|
@@ -38,13 +45,12 @@ export type ViewEditorProps = ThemedClassName<
|
|
|
38
45
|
schema: Schema.Schema.AnyNoContext;
|
|
39
46
|
view: View.View;
|
|
40
47
|
mode?: 'schema' | 'tag';
|
|
41
|
-
registry?: SchemaRegistry;
|
|
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,33 @@ 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
|
+
// Use createEchoChangeCallback for mutable schemas (EchoSchema), otherwise use direct mutation.
|
|
85
|
+
const change =
|
|
86
|
+
schema instanceof EchoSchema
|
|
87
|
+
? createEchoChangeCallback(view, schema)
|
|
88
|
+
: createDirectChangeCallback(view.projection, jsonSchema);
|
|
89
|
+
|
|
90
|
+
const model = new ProjectionModel({
|
|
91
|
+
registry: atomRegistry,
|
|
92
|
+
view,
|
|
93
|
+
baseSchema: jsonSchema,
|
|
94
|
+
change,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return model;
|
|
98
|
+
}, [atomRegistry, schema, view]);
|
|
99
|
+
|
|
78
100
|
useImperativeHandle(forwardedRef, () => projectionModel, [projectionModel]);
|
|
79
101
|
const [expandedField, setExpandedField] = useState<FieldType['id']>();
|
|
80
102
|
|
|
@@ -116,20 +138,17 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
116
138
|
[mode, view.query.ast, queueTarget],
|
|
117
139
|
);
|
|
118
140
|
|
|
119
|
-
const
|
|
120
|
-
(
|
|
121
|
-
|
|
122
|
-
},
|
|
123
|
-
[readonly],
|
|
141
|
+
const fieldMap = useMemo<FormFieldMap | undefined>(
|
|
142
|
+
() => (mode === 'tag' ? customFields({ types, tags }) : undefined),
|
|
143
|
+
[mode, types, tags],
|
|
124
144
|
);
|
|
125
145
|
|
|
126
|
-
// TODO(burdon): Check if mutable
|
|
127
|
-
|
|
146
|
+
// TODO(burdon): Check if mutable.
|
|
128
147
|
const handleAdd = useCallback(() => {
|
|
129
148
|
invariant(!readonly);
|
|
130
149
|
const field = projectionModel.createFieldProjection();
|
|
131
150
|
setExpandedField(field.id);
|
|
132
|
-
}, [
|
|
151
|
+
}, [projectionModel, readonly]);
|
|
133
152
|
|
|
134
153
|
const handleUpdate = useCallback(
|
|
135
154
|
(values: any) => {
|
|
@@ -153,143 +172,32 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
153
172
|
[expandedField, onDelete, readonly],
|
|
154
173
|
);
|
|
155
174
|
|
|
156
|
-
const handleMove = useCallback(
|
|
157
|
-
(fromIndex: number, toIndex: number) => {
|
|
158
|
-
invariant(!readonly);
|
|
159
|
-
// NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
|
|
160
|
-
const fields = [...view.projection.fields];
|
|
161
|
-
const [moved] = fields.splice(fromIndex, 1);
|
|
162
|
-
fields.splice(toIndex, 0, moved);
|
|
163
|
-
view.projection.fields = fields;
|
|
164
|
-
},
|
|
165
|
-
[view.projection.fields, readonly],
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
const handleClose = useCallback(() => setExpandedField(undefined), []);
|
|
169
|
-
|
|
170
|
-
const handleHide = useCallback(
|
|
171
|
-
(fieldId: string) => {
|
|
172
|
-
setExpandedField(undefined);
|
|
173
|
-
projectionModel.hideFieldProjection(fieldId);
|
|
174
|
-
},
|
|
175
|
-
[projectionModel],
|
|
176
|
-
);
|
|
177
|
-
|
|
178
|
-
const handleShow = useCallback(
|
|
179
|
-
(property: string) => {
|
|
180
|
-
setExpandedField(undefined);
|
|
181
|
-
projectionModel.showFieldProjection(property as JsonProp);
|
|
182
|
-
},
|
|
183
|
-
[projectionModel],
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
const custom = useMemo(() => (mode === 'tag' ? customFields({ types, tags }) : undefined), [types, tags, mode]);
|
|
187
|
-
|
|
188
175
|
return (
|
|
189
176
|
<div role='none' className={mx(classNames)}>
|
|
190
|
-
{/* If
|
|
177
|
+
{/* If readonly is set, then the callout is not needed. */}
|
|
191
178
|
{schemaReadonly && !readonly && (
|
|
192
|
-
<Callout.Root valence='info' classNames=
|
|
179
|
+
<Callout.Root valence='info' classNames='mlb-cardSpacingBlock'>
|
|
193
180
|
<Callout.Title>{t('system schema description')}</Callout.Title>
|
|
194
181
|
</Callout.Root>
|
|
195
182
|
)}
|
|
196
183
|
|
|
197
184
|
{/* TODO(burdon): Is the form read-only or just the schema? */}
|
|
198
|
-
{
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
/>
|
|
207
|
-
|
|
208
|
-
<div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
|
|
209
|
-
<h2 className={mx(inputTextLabel)}>{t('fields label')}</h2>
|
|
210
|
-
|
|
211
|
-
<List.Root<FieldType>
|
|
212
|
-
items={view.projection.fields}
|
|
213
|
-
isItem={Schema.is(FieldSchema)}
|
|
214
|
-
getId={(field) => field.id}
|
|
215
|
-
onMove={readonly ? undefined : handleMove}
|
|
185
|
+
<Form.Root schema={viewSchema} values={viewValues} fieldMap={fieldMap} autoSave onSave={handleUpdate}>
|
|
186
|
+
<Form.FieldSet />
|
|
187
|
+
|
|
188
|
+
<FormFieldLabel label={t('fields label')} asChild />
|
|
189
|
+
<FieldList
|
|
190
|
+
schema={schema}
|
|
191
|
+
view={view}
|
|
192
|
+
registry={registry}
|
|
216
193
|
readonly={readonly}
|
|
217
|
-
|
|
218
|
-
{
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
<div role='list' className={listGrid}>
|
|
222
|
-
{fields?.map((field) => {
|
|
223
|
-
const hidden = field.visible === false;
|
|
224
|
-
return (
|
|
225
|
-
<List.Item<FieldType>
|
|
226
|
-
key={field.id}
|
|
227
|
-
item={field}
|
|
228
|
-
classNames={listItemGrid}
|
|
229
|
-
aria-expanded={expandedField === field.id}
|
|
230
|
-
>
|
|
231
|
-
<div
|
|
232
|
-
role='none'
|
|
233
|
-
className={mx(subtleHover, listItemGrid, 'rounded-sm cursor-pointer min-bs-10')}
|
|
234
|
-
>
|
|
235
|
-
<List.ItemDragHandle disabled={readonly || schemaReadonly} />
|
|
236
|
-
<List.ItemTitle
|
|
237
|
-
classNames={hidden && 'text-subdued'}
|
|
238
|
-
onClick={() => handleToggleField(field)}
|
|
239
|
-
>
|
|
240
|
-
{field.path}
|
|
241
|
-
</List.ItemTitle>
|
|
242
|
-
<List.ItemButton
|
|
243
|
-
label={t(hidden ? 'show field label' : 'hide field label')}
|
|
244
|
-
data-testid={hidden ? 'show-field-button' : 'hide-field-button'}
|
|
245
|
-
icon={hidden ? 'ph--eye-closed--regular' : 'ph--eye--regular'}
|
|
246
|
-
autoHide={false}
|
|
247
|
-
disabled={readonly || (!hidden && projectionModel.fields.length <= 1)}
|
|
248
|
-
onClick={() => (hidden ? handleShow(field.path) : handleHide(field.id))}
|
|
249
|
-
/>
|
|
250
|
-
{!readonly && (
|
|
251
|
-
<>
|
|
252
|
-
<List.ItemDeleteButton
|
|
253
|
-
label={t('delete field label')}
|
|
254
|
-
autoHide={false}
|
|
255
|
-
disabled={readonly || schemaReadonly || view.projection.fields.length <= 1}
|
|
256
|
-
onClick={() => handleDelete(field.id)}
|
|
257
|
-
data-testid='field.delete'
|
|
258
|
-
/>
|
|
259
|
-
<IconButton
|
|
260
|
-
iconOnly
|
|
261
|
-
variant='ghost'
|
|
262
|
-
label={t('toggle expand label', { ns: 'os' })}
|
|
263
|
-
icon={
|
|
264
|
-
expandedField === field.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'
|
|
265
|
-
}
|
|
266
|
-
onClick={() => handleToggleField(field)}
|
|
267
|
-
/>
|
|
268
|
-
</>
|
|
269
|
-
)}
|
|
270
|
-
</div>
|
|
271
|
-
{expandedField === field.id && !readonly && (
|
|
272
|
-
<div role='none' className='col-span-5 mbs-1 mbe-1 border border-separator rounded-md'>
|
|
273
|
-
<FieldEditor
|
|
274
|
-
readonly={readonly || schemaReadonly ? 'disabled-input' : false}
|
|
275
|
-
projection={projectionModel}
|
|
276
|
-
field={field}
|
|
277
|
-
registry={registry}
|
|
278
|
-
onSave={handleClose}
|
|
279
|
-
/>
|
|
280
|
-
</div>
|
|
281
|
-
)}
|
|
282
|
-
</List.Item>
|
|
283
|
-
);
|
|
284
|
-
})}
|
|
285
|
-
</div>
|
|
286
|
-
</>
|
|
287
|
-
)}
|
|
288
|
-
</List.Root>
|
|
289
|
-
</div>
|
|
194
|
+
showHeading={showHeading}
|
|
195
|
+
onDelete={handleDelete}
|
|
196
|
+
/>
|
|
197
|
+
</Form.Root>
|
|
290
198
|
|
|
291
199
|
{!readonly && !expandedField && (
|
|
292
|
-
<div role='none' className=
|
|
200
|
+
<div role='none' className='mlb-cardSpacingBlock'>
|
|
293
201
|
<IconButton
|
|
294
202
|
icon='ph--plus--regular'
|
|
295
203
|
label={t('add property button label')}
|
|
@@ -305,17 +213,175 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
305
213
|
},
|
|
306
214
|
);
|
|
307
215
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
216
|
+
type FieldListProps = Pick<ViewEditorProps, 'schema' | 'view' | 'registry' | 'readonly' | 'showHeading' | 'onDelete'>;
|
|
217
|
+
|
|
218
|
+
const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDelete }: FieldListProps) => {
|
|
219
|
+
const atomRegistry = useContext(RegistryContext);
|
|
220
|
+
const schemaReadonly = !isMutable(schema);
|
|
221
|
+
const { t } = useTranslation(translationKey);
|
|
222
|
+
|
|
223
|
+
const projectionModel = useMemo(() => {
|
|
224
|
+
// Use reactive and mutable version of json schema when schema is mutable.
|
|
225
|
+
const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
|
|
226
|
+
|
|
227
|
+
// Use createEchoChangeCallback for mutable schemas (EchoSchema), otherwise use direct mutation.
|
|
228
|
+
const change =
|
|
229
|
+
schema instanceof EchoSchema
|
|
230
|
+
? createEchoChangeCallback(view, schema)
|
|
231
|
+
: createDirectChangeCallback(view.projection, jsonSchema);
|
|
232
|
+
|
|
233
|
+
const model = new ProjectionModel({
|
|
234
|
+
registry: atomRegistry,
|
|
235
|
+
view,
|
|
236
|
+
baseSchema: jsonSchema,
|
|
237
|
+
change,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
return model;
|
|
241
|
+
}, [atomRegistry, schema, view]);
|
|
242
|
+
|
|
243
|
+
const [expandedField, setExpandedField] = useState<FieldType['id']>();
|
|
244
|
+
|
|
245
|
+
const handleToggleField = useCallback(
|
|
246
|
+
(field: FieldType) => {
|
|
247
|
+
setExpandedField((prevExpandedFieldId) => (prevExpandedFieldId === field.id ? undefined : field.id));
|
|
248
|
+
},
|
|
249
|
+
[readonly],
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
const handleDelete = useCallback(
|
|
253
|
+
(fieldId: string) => {
|
|
254
|
+
invariant(!readonly);
|
|
255
|
+
if (fieldId === expandedField) {
|
|
256
|
+
setExpandedField(undefined);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
onDelete?.(fieldId);
|
|
260
|
+
},
|
|
261
|
+
[expandedField, onDelete, readonly],
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
const handleMove = useCallback(
|
|
265
|
+
(fromIndex: number, toIndex: number) => {
|
|
266
|
+
invariant(!readonly);
|
|
267
|
+
// NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
|
|
268
|
+
const fields = [...view.projection.fields];
|
|
269
|
+
const [moved] = fields.splice(fromIndex, 1);
|
|
270
|
+
fields.splice(toIndex, 0, moved);
|
|
271
|
+
Obj.change(view, (v) => {
|
|
272
|
+
v.projection.fields = fields;
|
|
273
|
+
});
|
|
274
|
+
},
|
|
275
|
+
[view.projection.fields, readonly],
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
const handleClose = useCallback(() => setExpandedField(undefined), []);
|
|
279
|
+
|
|
280
|
+
const handleHide = useCallback(
|
|
281
|
+
(fieldId: string) => {
|
|
282
|
+
setExpandedField(undefined);
|
|
283
|
+
projectionModel.hideFieldProjection(fieldId);
|
|
284
|
+
},
|
|
285
|
+
[projectionModel],
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
const handleShow = useCallback(
|
|
289
|
+
(property: string) => {
|
|
290
|
+
setExpandedField(undefined);
|
|
291
|
+
projectionModel.showFieldProjection(property as JsonProp);
|
|
292
|
+
},
|
|
293
|
+
[projectionModel],
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
return (
|
|
297
|
+
<List.Root<FieldType>
|
|
298
|
+
items={view.projection.fields as FieldType[]}
|
|
299
|
+
isItem={Schema.is(FieldSchema)}
|
|
300
|
+
getId={(field) => field.id}
|
|
301
|
+
onMove={readonly ? undefined : handleMove}
|
|
302
|
+
readonly={readonly}
|
|
303
|
+
>
|
|
304
|
+
{({ items: fields }) => (
|
|
305
|
+
<>
|
|
306
|
+
{showHeading && <h3 className='text-sm'>{t('field path label')}</h3>}
|
|
307
|
+
<div role='list' className={listGrid}>
|
|
308
|
+
{fields?.map((field) => {
|
|
309
|
+
const hidden = field.visible === false;
|
|
310
|
+
return (
|
|
311
|
+
<List.Item<FieldType>
|
|
312
|
+
key={field.id}
|
|
313
|
+
item={field}
|
|
314
|
+
classNames={listItemGrid}
|
|
315
|
+
aria-expanded={expandedField === field.id}
|
|
316
|
+
>
|
|
317
|
+
<div role='none' className={mx(subtleHover, listItemGrid, 'rounded-sm cursor-pointer min-bs-10')}>
|
|
318
|
+
<List.ItemDragHandle disabled={readonly || schemaReadonly} />
|
|
319
|
+
<List.ItemTitle classNames={hidden && 'text-subdued'} onClick={() => handleToggleField(field)}>
|
|
320
|
+
{field.path}
|
|
321
|
+
</List.ItemTitle>
|
|
322
|
+
<List.ItemButton
|
|
323
|
+
label={t(hidden ? 'show field label' : 'hide field label')}
|
|
324
|
+
data-testid={hidden ? 'show-field-button' : 'hide-field-button'}
|
|
325
|
+
icon={hidden ? 'ph--eye-closed--regular' : 'ph--eye--regular'}
|
|
326
|
+
autoHide={false}
|
|
327
|
+
disabled={readonly || (!hidden && projectionModel.getFields().length <= 1)}
|
|
328
|
+
onClick={() => (hidden ? handleShow(field.path) : handleHide(field.id))}
|
|
329
|
+
/>
|
|
330
|
+
{!readonly && (
|
|
331
|
+
<>
|
|
332
|
+
<List.ItemDeleteButton
|
|
333
|
+
label={t('delete field label')}
|
|
334
|
+
autoHide={false}
|
|
335
|
+
disabled={readonly || schemaReadonly || view.projection.fields.length <= 1}
|
|
336
|
+
onClick={() => handleDelete(field.id)}
|
|
337
|
+
data-testid='field.delete'
|
|
338
|
+
/>
|
|
339
|
+
<IconButton
|
|
340
|
+
iconOnly
|
|
341
|
+
variant='ghost'
|
|
342
|
+
label={t('toggle expand label', { ns: osTranslations })}
|
|
343
|
+
icon={expandedField === field.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'}
|
|
344
|
+
onClick={() => handleToggleField(field)}
|
|
345
|
+
data-testid='field.toggle'
|
|
346
|
+
/>
|
|
347
|
+
</>
|
|
348
|
+
)}
|
|
349
|
+
</div>
|
|
350
|
+
{expandedField === field.id && !readonly && (
|
|
351
|
+
<div role='none' className='col-span-5 mbs-1 mbe-1 border border-separator rounded-md'>
|
|
352
|
+
<FieldEditor
|
|
353
|
+
readonly={readonly || schemaReadonly}
|
|
354
|
+
registry={registry}
|
|
355
|
+
projection={projectionModel}
|
|
356
|
+
field={field}
|
|
357
|
+
onSave={handleClose}
|
|
358
|
+
/>
|
|
359
|
+
</div>
|
|
360
|
+
)}
|
|
361
|
+
</List.Item>
|
|
362
|
+
);
|
|
363
|
+
})}
|
|
364
|
+
</div>
|
|
365
|
+
</>
|
|
366
|
+
)}
|
|
367
|
+
</List.Root>
|
|
368
|
+
);
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
const customFields = ({
|
|
372
|
+
types,
|
|
373
|
+
tags,
|
|
374
|
+
}: Pick<ViewEditorProps, 'types' | 'tags'>): Record<string, FormFieldComponent> => ({
|
|
375
|
+
query: ({ type, readonly, label, getValue, onValueChange }: FormFieldComponentProps) => {
|
|
376
|
+
const handleChange = useCallback<NonNullable<QueryFormProps['onChange']>>(
|
|
377
|
+
(query) => onValueChange(type, query.ast),
|
|
378
|
+
[onValueChange],
|
|
313
379
|
);
|
|
314
380
|
|
|
315
381
|
return (
|
|
316
382
|
<Input.Root>
|
|
317
|
-
<
|
|
318
|
-
<QueryForm initialQuery={
|
|
383
|
+
<FormFieldLabel readonly={readonly} label={label} />
|
|
384
|
+
<QueryForm initialQuery={getValue()} types={types} tags={tags} onChange={handleChange} />
|
|
319
385
|
</Input.Root>
|
|
320
386
|
);
|
|
321
387
|
},
|
|
@@ -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