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