@dxos/react-ui-form 0.8.4-main.1da679c → 0.8.4-main.21d9917
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 +2188 -2119
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +2188 -2119
- 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 +14 -10
- 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 +16 -9
- 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 +63 -87
- 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} +13 -9
- 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 +46 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +85 -0
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -0
- package/dist/types/src/components/ObjectPicker/index.d.ts +2 -0
- package/dist/types/src/components/ObjectPicker/index.d.ts.map +1 -0
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +21 -10
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +17 -13
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -0
- 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 +12 -8
- package/dist/types/src/translations.d.ts.map +1 -1
- 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 +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 +2 -3
- package/dist/types/src/util/refs.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +50 -44
- package/src/components/ControlSection/ControlSection.stories.tsx +4 -5
- package/src/components/ControlSection/ControlSection.tsx +8 -8
- package/src/components/FieldEditor/FieldEditor.stories.tsx +46 -24
- package/src/components/FieldEditor/FieldEditor.tsx +77 -77
- package/src/components/Form/Form-old.stories.tsx +314 -0
- package/src/components/Form/Form.stories.tsx +151 -251
- package/src/components/Form/Form.tsx +319 -100
- 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} +21 -19
- package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +22 -16
- 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 +214 -0
- package/src/components/ObjectPicker/ObjectPicker.tsx +175 -0
- package/src/components/ObjectPicker/index.ts +5 -0
- package/src/components/ViewEditor/ViewEditor.stories.tsx +112 -68
- package/src/components/ViewEditor/ViewEditor.test.tsx +12 -7
- package/src/components/ViewEditor/ViewEditor.tsx +283 -184
- package/src/components/index.ts +1 -0
- 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 +14 -11
- package/src/util/field.ts +25 -25
- package/src/util/formatting.ts +22 -22
- package/src/util/index.ts +1 -1
- package/src/util/properties.ts +18 -0
- package/src/util/refs.ts +12 -18
- 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 -18
- 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 -23
- 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 -124
- package/src/components/Form/Defaults.tsx +0 -205
- package/src/components/Form/FormActions.tsx +0 -54
- package/src/components/Form/FormContent.tsx +0 -302
- package/src/components/Form/FormContext.tsx +0 -110
- package/src/components/Form/Input.tsx +0 -54
- package/src/components/Form/RefField.tsx +0 -299
- 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 -293
- package/src/hooks/useQueryRefOptions.ts +0 -53
- package/src/hooks/useRefQueryLookupHandler.ts +0 -39
- package/src/util/schema.ts +0 -14
|
@@ -2,30 +2,40 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
5
|
+
import { RegistryContext } from '@effect-atom/atom-react';
|
|
6
|
+
import * as Array from 'effect/Array';
|
|
7
|
+
import * as Match from 'effect/Match';
|
|
8
|
+
import * as Option from 'effect/Option';
|
|
9
|
+
import * as Schema from 'effect/Schema';
|
|
10
|
+
import React, { forwardRef, useCallback, useContext, useImperativeHandle, useMemo, useState } from 'react';
|
|
7
11
|
|
|
8
|
-
import { type SchemaRegistry } from '@dxos/echo
|
|
9
|
-
import { EchoSchema,
|
|
12
|
+
import { Filter, Format, Obj, Query, QueryAST, type SchemaRegistry } from '@dxos/echo';
|
|
13
|
+
import { EchoSchema, type JsonProp, isMutable, toJsonSchema } from '@dxos/echo/internal';
|
|
10
14
|
import { invariant } from '@dxos/invariant';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { QueryEditor, QuerySerializer, createExpression } from '@dxos/react-ui-components';
|
|
15
|
+
import { Callout, IconButton, Input, type ThemedClassName, useTranslation } from '@dxos/react-ui';
|
|
16
|
+
import { QueryForm, type QueryFormProps } from '@dxos/react-ui-components';
|
|
14
17
|
import { List } from '@dxos/react-ui-list';
|
|
15
|
-
import { cardSpacing } from '@dxos/react-ui-stack';
|
|
16
|
-
import { inputTextLabel, mx, subtleHover } from '@dxos/react-ui-theme';
|
|
17
18
|
import {
|
|
18
|
-
type DataType,
|
|
19
19
|
FieldSchema,
|
|
20
20
|
type FieldType,
|
|
21
21
|
ProjectionModel,
|
|
22
22
|
VIEW_FIELD_LIMIT,
|
|
23
|
-
|
|
23
|
+
type View,
|
|
24
|
+
createDirectChangeCallback,
|
|
25
|
+
createEchoChangeCallback,
|
|
26
|
+
getTypenameFromQuery,
|
|
24
27
|
} from '@dxos/schema';
|
|
28
|
+
import { mx, osTranslations, subtleHover } from '@dxos/ui-theme';
|
|
25
29
|
|
|
26
30
|
import { translationKey } from '../../translations';
|
|
27
31
|
import { FieldEditor } from '../FieldEditor';
|
|
28
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
Form,
|
|
34
|
+
type FormFieldComponent,
|
|
35
|
+
type FormFieldComponentProps,
|
|
36
|
+
FormFieldLabel,
|
|
37
|
+
type FormFieldMap,
|
|
38
|
+
} from '../Form';
|
|
29
39
|
|
|
30
40
|
const listGrid = 'grid grid-cols-[min-content_1fr_min-content_min-content_min-content]';
|
|
31
41
|
const listItemGrid = 'grid grid-cols-subgrid col-span-5';
|
|
@@ -33,65 +43,204 @@ const listItemGrid = 'grid grid-cols-subgrid col-span-5';
|
|
|
33
43
|
export type ViewEditorProps = ThemedClassName<
|
|
34
44
|
{
|
|
35
45
|
schema: Schema.Schema.AnyNoContext;
|
|
36
|
-
view:
|
|
37
|
-
|
|
38
|
-
registry?: SchemaRegistry;
|
|
46
|
+
view: View.View;
|
|
47
|
+
mode?: 'schema' | 'tag';
|
|
48
|
+
registry?: SchemaRegistry.SchemaRegistry;
|
|
39
49
|
readonly?: boolean;
|
|
40
50
|
showHeading?: boolean;
|
|
41
|
-
onQueryChanged?: (query: string) => void;
|
|
51
|
+
onQueryChanged?: (query: QueryAST.Query, target?: string) => void;
|
|
42
52
|
onDelete?: (fieldId: string) => void;
|
|
43
|
-
} & Pick<
|
|
53
|
+
} & Pick<QueryFormProps, 'types' | 'tags'>
|
|
44
54
|
>;
|
|
45
55
|
|
|
46
56
|
/**
|
|
47
57
|
* Schema-based object form.
|
|
48
58
|
*/
|
|
49
|
-
export const ViewEditor = (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
60
|
+
(
|
|
61
|
+
{
|
|
62
|
+
classNames,
|
|
63
|
+
schema,
|
|
64
|
+
view,
|
|
65
|
+
mode = 'schema',
|
|
66
|
+
registry,
|
|
67
|
+
readonly,
|
|
68
|
+
showHeading = false,
|
|
69
|
+
types,
|
|
70
|
+
tags,
|
|
71
|
+
onQueryChanged,
|
|
72
|
+
onDelete,
|
|
73
|
+
},
|
|
74
|
+
forwardedRef,
|
|
75
|
+
) => {
|
|
76
|
+
const atomRegistry = useContext(RegistryContext);
|
|
77
|
+
const schemaReadonly = !isMutable(schema);
|
|
78
|
+
const { t } = useTranslation(translationKey);
|
|
79
|
+
|
|
80
|
+
const projectionModel = useMemo(() => {
|
|
81
|
+
// Use reactive and mutable version of json schema when schema is mutable.
|
|
82
|
+
const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
|
|
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
|
+
|
|
100
|
+
useImperativeHandle(forwardedRef, () => projectionModel, [projectionModel]);
|
|
101
|
+
const [expandedField, setExpandedField] = useState<FieldType['id']>();
|
|
102
|
+
|
|
103
|
+
const queueTarget = Match.value(view.query.ast).pipe(
|
|
104
|
+
Match.when({ type: 'options' }, ({ options }) => {
|
|
105
|
+
return Option.fromNullable(options.queues).pipe(
|
|
106
|
+
Option.flatMap((queues) => Array.head(queues)),
|
|
107
|
+
Option.getOrUndefined,
|
|
108
|
+
);
|
|
109
|
+
}),
|
|
110
|
+
Match.orElse(() => undefined),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const viewSchema = useMemo(() => {
|
|
114
|
+
const base = Schema.Struct({
|
|
115
|
+
query:
|
|
116
|
+
mode === 'schema'
|
|
117
|
+
? Format.URL.annotations({ title: 'Record type' })
|
|
118
|
+
: QueryAST.Query.annotations({ title: 'Query' }),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
if (mode === 'tag') {
|
|
122
|
+
return Schema.Struct({
|
|
123
|
+
...base.fields,
|
|
124
|
+
target: Schema.optional(Schema.String.annotations({ title: 'Target Queue' })),
|
|
125
|
+
}).pipe(Schema.mutable);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return base.pipe(Schema.mutable);
|
|
129
|
+
}, [mode]);
|
|
130
|
+
|
|
131
|
+
// TODO(burdon): Need to warn user of possible consequences of editing.
|
|
132
|
+
// TODO(burdon): Settings should have domain name owned by user.
|
|
133
|
+
const viewValues = useMemo(
|
|
134
|
+
() => ({
|
|
135
|
+
query: mode === 'schema' ? getTypenameFromQuery(view.query.ast) : view.query.ast,
|
|
136
|
+
target: queueTarget,
|
|
137
|
+
}),
|
|
138
|
+
[mode, view.query.ast, queueTarget],
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const fieldMap = useMemo<FormFieldMap | undefined>(
|
|
142
|
+
() => (mode === 'tag' ? customFields({ types, tags }) : undefined),
|
|
143
|
+
[mode, types, tags],
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// TODO(burdon): Check if mutable.
|
|
147
|
+
const handleAdd = useCallback(() => {
|
|
148
|
+
invariant(!readonly);
|
|
149
|
+
const field = projectionModel.createFieldProjection();
|
|
150
|
+
setExpandedField(field.id);
|
|
151
|
+
}, [projectionModel, readonly]);
|
|
152
|
+
|
|
153
|
+
const handleUpdate = useCallback(
|
|
154
|
+
(values: any) => {
|
|
155
|
+
requestAnimationFrame(() => {
|
|
156
|
+
const query = mode === 'schema' ? Query.select(Filter.typename(values.query)).ast : values.query;
|
|
157
|
+
onQueryChanged?.(query, values.target);
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
[onQueryChanged, view, queueTarget, mode],
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const handleDelete = useCallback(
|
|
164
|
+
(fieldId: string) => {
|
|
165
|
+
invariant(!readonly);
|
|
166
|
+
if (fieldId === expandedField) {
|
|
167
|
+
setExpandedField(undefined);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
onDelete?.(fieldId);
|
|
171
|
+
},
|
|
172
|
+
[expandedField, onDelete, readonly],
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<div role='none' className={mx(classNames)}>
|
|
177
|
+
{/* If readonly is set, then the callout is not needed. */}
|
|
178
|
+
{schemaReadonly && !readonly && (
|
|
179
|
+
<Callout.Root valence='info' classNames='mlb-cardSpacingBlock'>
|
|
180
|
+
<Callout.Title>{t('system schema description')}</Callout.Title>
|
|
181
|
+
</Callout.Root>
|
|
182
|
+
)}
|
|
183
|
+
|
|
184
|
+
{/* TODO(burdon): Is the form read-only or just the schema? */}
|
|
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}
|
|
193
|
+
readonly={readonly}
|
|
194
|
+
showHeading={showHeading}
|
|
195
|
+
onDelete={handleDelete}
|
|
196
|
+
/>
|
|
197
|
+
</Form.Root>
|
|
198
|
+
|
|
199
|
+
{!readonly && !expandedField && (
|
|
200
|
+
<div role='none' className='mlb-cardSpacingBlock'>
|
|
201
|
+
<IconButton
|
|
202
|
+
icon='ph--plus--regular'
|
|
203
|
+
label={t('add property button label')}
|
|
204
|
+
onClick={readonly ? undefined : handleAdd}
|
|
205
|
+
// TODO(burdon): Show field limit in ux (not tooltip).
|
|
206
|
+
disabled={view.projection.fields.length >= VIEW_FIELD_LIMIT}
|
|
207
|
+
classNames='is-full'
|
|
208
|
+
/>
|
|
209
|
+
</div>
|
|
210
|
+
)}
|
|
211
|
+
</div>
|
|
212
|
+
);
|
|
213
|
+
},
|
|
214
|
+
);
|
|
215
|
+
|
|
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);
|
|
62
220
|
const schemaReadonly = !isMutable(schema);
|
|
63
221
|
const { t } = useTranslation(translationKey);
|
|
222
|
+
|
|
64
223
|
const projectionModel = useMemo(() => {
|
|
65
224
|
// Use reactive and mutable version of json schema when schema is mutable.
|
|
66
225
|
const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
|
|
67
|
-
return new ProjectionModel(jsonSchema, view.projection);
|
|
68
|
-
}, [schema, view.projection]);
|
|
69
|
-
const [expandedField, setExpandedField] = useState<FieldType['id']>();
|
|
70
226
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
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);
|
|
77
232
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
233
|
+
const model = new ProjectionModel({
|
|
234
|
+
registry: atomRegistry,
|
|
235
|
+
view,
|
|
236
|
+
baseSchema: jsonSchema,
|
|
237
|
+
change,
|
|
238
|
+
});
|
|
83
239
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
? Format.URL.annotations({ title: 'Record type' })
|
|
89
|
-
: Schema.String.annotations({ title: 'Query' }),
|
|
90
|
-
}).pipe(Schema.mutable);
|
|
91
|
-
}, [kind]);
|
|
92
|
-
// TODO(burdon): Need to warn user of possible consequences of editing.
|
|
93
|
-
// TODO(burdon): Settings should have domain name owned by user.
|
|
94
|
-
const viewValues = useMemo(() => ({ query: serializedQuery }), [serializedQuery]);
|
|
240
|
+
return model;
|
|
241
|
+
}, [atomRegistry, schema, view]);
|
|
242
|
+
|
|
243
|
+
const [expandedField, setExpandedField] = useState<FieldType['id']>();
|
|
95
244
|
|
|
96
245
|
const handleToggleField = useCallback(
|
|
97
246
|
(field: FieldType) => {
|
|
@@ -100,26 +249,6 @@ export const ViewEditor = ({
|
|
|
100
249
|
[readonly],
|
|
101
250
|
);
|
|
102
251
|
|
|
103
|
-
// TODO(burdon): Check if mutable; variant of useCallback that return undefined if readonly?
|
|
104
|
-
|
|
105
|
-
const handleAdd = useCallback(() => {
|
|
106
|
-
invariant(!readonly);
|
|
107
|
-
const field = projectionModel.createFieldProjection();
|
|
108
|
-
setExpandedField(field.id);
|
|
109
|
-
}, [schema, projectionModel, readonly]);
|
|
110
|
-
|
|
111
|
-
const handleUpdate = useCallback(
|
|
112
|
-
({ query }: Schema.Schema.Type<typeof viewSchema>) => {
|
|
113
|
-
invariant(!readonly);
|
|
114
|
-
requestAnimationFrame(() => {
|
|
115
|
-
if (serializedQuery !== query && !readonly) {
|
|
116
|
-
onQueryChanged?.(query);
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
},
|
|
120
|
-
[serializedQuery, onQueryChanged, readonly],
|
|
121
|
-
);
|
|
122
|
-
|
|
123
252
|
const handleDelete = useCallback(
|
|
124
253
|
(fieldId: string) => {
|
|
125
254
|
invariant(!readonly);
|
|
@@ -139,7 +268,9 @@ export const ViewEditor = ({
|
|
|
139
268
|
const fields = [...view.projection.fields];
|
|
140
269
|
const [moved] = fields.splice(fromIndex, 1);
|
|
141
270
|
fields.splice(toIndex, 0, moved);
|
|
142
|
-
view
|
|
271
|
+
Obj.change(view, (v) => {
|
|
272
|
+
v.projection.fields = fields;
|
|
273
|
+
});
|
|
143
274
|
},
|
|
144
275
|
[view.projection.fields, readonly],
|
|
145
276
|
);
|
|
@@ -162,81 +293,42 @@ export const ViewEditor = ({
|
|
|
162
293
|
[projectionModel],
|
|
163
294
|
);
|
|
164
295
|
|
|
165
|
-
const custom: Partial<Record<string, InputComponent>> = useMemo(
|
|
166
|
-
() =>
|
|
167
|
-
kind === 'advanced'
|
|
168
|
-
? {
|
|
169
|
-
['query' satisfies keyof Schema.Schema.Type<typeof viewSchema>]: (props: InputProps) => {
|
|
170
|
-
const handleChange = useCallback(
|
|
171
|
-
(text: string) => props.onValueChange('string', text),
|
|
172
|
-
[props.onValueChange],
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
// TODO(wittjosiah): Add label & input styles.
|
|
176
|
-
return <QueryEditor space={space} initialValue={props.getValue()} onChange={handleChange} />;
|
|
177
|
-
},
|
|
178
|
-
}
|
|
179
|
-
: {},
|
|
180
|
-
[],
|
|
181
|
-
);
|
|
182
|
-
|
|
183
296
|
return (
|
|
184
|
-
<
|
|
185
|
-
{
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
{
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
return (
|
|
220
|
-
<List.Item<FieldType>
|
|
221
|
-
key={field.id}
|
|
222
|
-
item={field}
|
|
223
|
-
classNames={listItemGrid}
|
|
224
|
-
aria-expanded={expandedField === field.id}
|
|
225
|
-
>
|
|
226
|
-
<div role='none' className={mx(subtleHover, listItemGrid, 'rounded-sm cursor-pointer min-bs-10')}>
|
|
227
|
-
<List.ItemDragHandle disabled={readonly} />
|
|
228
|
-
<List.ItemTitle classNames={hidden && 'text-subdued'} onClick={() => handleToggleField(field)}>
|
|
229
|
-
{field.path}
|
|
230
|
-
</List.ItemTitle>
|
|
231
|
-
<List.ItemButton
|
|
232
|
-
label={t(hidden ? 'show field label' : 'hide field label')}
|
|
233
|
-
data-testid={hidden ? 'show-field-button' : 'hide-field-button'}
|
|
234
|
-
icon={hidden ? 'ph--eye-closed--regular' : 'ph--eye--regular'}
|
|
235
|
-
autoHide={false}
|
|
236
|
-
disabled={readonly || (!hidden && projectionModel.fields.length <= 1)}
|
|
237
|
-
onClick={() => (hidden ? handleShow(field.path) : handleHide(field.id))}
|
|
238
|
-
/>
|
|
239
|
-
{/* TODO(burdon): Remove unless implement undo. */}
|
|
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
|
+
<>
|
|
240
332
|
<List.ItemDeleteButton
|
|
241
333
|
label={t('delete field label')}
|
|
242
334
|
autoHide={false}
|
|
@@ -247,43 +339,50 @@ export const ViewEditor = ({
|
|
|
247
339
|
<IconButton
|
|
248
340
|
iconOnly
|
|
249
341
|
variant='ghost'
|
|
250
|
-
label={t('toggle expand label', { ns:
|
|
342
|
+
label={t('toggle expand label', { ns: osTranslations })}
|
|
251
343
|
icon={expandedField === field.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'}
|
|
252
344
|
onClick={() => handleToggleField(field)}
|
|
345
|
+
data-testid='field.toggle'
|
|
253
346
|
/>
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
</
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
</div>
|
|
274
|
-
|
|
275
|
-
{!readonly && !expandedField && (
|
|
276
|
-
<div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
|
|
277
|
-
<IconButton
|
|
278
|
-
icon='ph--plus--regular'
|
|
279
|
-
label={t('button add property')}
|
|
280
|
-
onClick={readonly ? undefined : handleAdd}
|
|
281
|
-
// TODO(burdon): Show field limit in ux (not tooltip).
|
|
282
|
-
disabled={view.projection.fields.length >= VIEW_FIELD_LIMIT}
|
|
283
|
-
classNames='is-full'
|
|
284
|
-
/>
|
|
285
|
-
</div>
|
|
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
|
+
</>
|
|
286
366
|
)}
|
|
287
|
-
</
|
|
367
|
+
</List.Root>
|
|
288
368
|
);
|
|
289
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],
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
return (
|
|
382
|
+
<Input.Root>
|
|
383
|
+
<FormFieldLabel readonly={readonly} label={label} />
|
|
384
|
+
<QueryForm initialQuery={getValue()} types={types} tags={tags} onChange={handleChange} />
|
|
385
|
+
</Input.Root>
|
|
386
|
+
);
|
|
387
|
+
},
|
|
388
|
+
});
|
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='
|
|
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