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