@dxos/react-ui-form 0.8.4-main.70d3990 → 0.8.4-main.74a063c4e0
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/README.md +0 -1
- package/dist/lib/browser/index.mjs +2319 -2347
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +2319 -2347
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +8 -7
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +66 -60
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +87 -27
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +111 -150
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField.d.ts +43 -0
- package/dist/types/src/components/Form/FormField.d.ts.map +1 -0
- package/dist/types/src/components/Form/FormFieldComponent.d.ts +77 -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 +18 -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 +16 -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 +23 -10
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +62 -59
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
- package/dist/types/src/components/Settings/Settings.d.ts +39 -0
- package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
- package/dist/types/src/components/Settings/Settings.stories.d.ts +81 -0
- package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -0
- package/dist/types/src/components/Settings/index.d.ts +2 -0
- package/dist/types/src/components/Settings/index.d.ts.map +1 -0
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +9 -11
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +66 -62
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +0 -2
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -3
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useFormHandler.d.ts +82 -0
- package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -0
- package/dist/types/src/hooks/useKeyHandler.d.ts +7 -0
- package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +63 -60
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +1 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/properties.d.ts +7 -0
- package/dist/types/src/util/properties.d.ts.map +1 -0
- package/dist/types/src/util/refs.d.ts +2 -2
- package/dist/types/src/util/refs.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +51 -48
- package/src/components/FieldEditor/FieldEditor.stories.tsx +43 -25
- package/src/components/FieldEditor/FieldEditor.test.tsx +0 -1
- package/src/components/FieldEditor/FieldEditor.tsx +73 -77
- package/src/components/Form/AUDIT.md +17 -0
- package/src/components/Form/Form.stories.tsx +147 -249
- package/src/components/Form/Form.tsx +324 -110
- package/src/components/Form/FormField.tsx +304 -0
- package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
- package/src/components/Form/FormFieldComponent.tsx +171 -0
- package/src/components/Form/FormFieldSet.tsx +128 -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 +198 -0
- package/src/components/Form/fields/SelectField.tsx +65 -0
- package/src/components/Form/{custom/SelectOptionsInput.stories.tsx → fields/SelectOptionField.stories.tsx} +19 -17
- package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +29 -24
- 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 +18 -16
- package/src/components/ObjectPicker/ObjectPicker.tsx +103 -78
- package/src/components/Settings/Settings.stories.tsx +55 -0
- package/src/components/Settings/Settings.tsx +129 -0
- package/src/components/{ControlSection → Settings}/index.ts +1 -1
- package/src/components/ViewEditor/ViewEditor.stories.tsx +44 -39
- package/src/components/ViewEditor/ViewEditor.test.tsx +5 -5
- package/src/components/ViewEditor/ViewEditor.tsx +295 -197
- package/src/components/index.ts +1 -1
- package/src/components/testing.tsx +23 -9
- package/src/hooks/index.ts +2 -3
- package/src/hooks/useFormHandler.ts +380 -0
- package/src/hooks/useKeyHandler.tsx +37 -0
- package/src/translations.ts +66 -65
- package/src/util/index.ts +1 -1
- package/src/util/properties.ts +18 -0
- package/src/util/refs.ts +13 -15
- package/dist/types/src/components/ControlSection/ControlSection.d.ts +0 -23
- package/dist/types/src/components/ControlSection/ControlSection.d.ts.map +0 -1
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts +0 -84
- package/dist/types/src/components/ControlSection/ControlSection.stories.d.ts.map +0 -1
- package/dist/types/src/components/ControlSection/index.d.ts +0 -2
- package/dist/types/src/components/ControlSection/index.d.ts.map +0 -1
- package/dist/types/src/components/Form/ArrayField.d.ts +0 -16
- package/dist/types/src/components/Form/ArrayField.d.ts.map +0 -1
- package/dist/types/src/components/Form/Defaults.d.ts +0 -14
- package/dist/types/src/components/Form/Defaults.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormActions.d.ts +0 -9
- package/dist/types/src/components/Form/FormActions.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormContent.d.ts +0 -63
- package/dist/types/src/components/Form/FormContent.d.ts.map +0 -1
- package/dist/types/src/components/Form/FormContext.d.ts +0 -23
- package/dist/types/src/components/Form/FormContext.d.ts.map +0 -1
- package/dist/types/src/components/Form/Input.d.ts +0 -29
- package/dist/types/src/components/Form/Input.d.ts.map +0 -1
- package/dist/types/src/components/Form/RefField.d.ts +0 -22
- package/dist/types/src/components/Form/RefField.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts +0 -4
- package/dist/types/src/components/Form/custom/GeoPointInput.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts +0 -4
- package/dist/types/src/components/Form/custom/SelectOptionsInput.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts +0 -79
- package/dist/types/src/components/Form/custom/SelectOptionsInput.stories.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/TupleInput.d.ts +0 -6
- package/dist/types/src/components/Form/custom/TupleInput.d.ts.map +0 -1
- package/dist/types/src/components/Form/custom/index.d.ts +0 -4
- package/dist/types/src/components/Form/custom/index.d.ts.map +0 -1
- package/dist/types/src/components/Form/factory.d.ts +0 -8
- package/dist/types/src/components/Form/factory.d.ts.map +0 -1
- package/dist/types/src/hooks/useForm.d.ts +0 -67
- package/dist/types/src/hooks/useForm.d.ts.map +0 -1
- package/dist/types/src/hooks/useQueryRefOptions.d.ts +0 -29
- package/dist/types/src/hooks/useQueryRefOptions.d.ts.map +0 -1
- package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts +0 -8
- package/dist/types/src/hooks/useRefQueryLookupHandler.d.ts.map +0 -1
- package/dist/types/src/util/schema.d.ts +0 -3
- package/dist/types/src/util/schema.d.ts.map +0 -1
- package/src/components/ControlSection/ControlSection.stories.tsx +0 -38
- package/src/components/ControlSection/ControlSection.tsx +0 -153
- package/src/components/Form/ArrayField.tsx +0 -127
- package/src/components/Form/Defaults.tsx +0 -205
- package/src/components/Form/FormActions.tsx +0 -59
- package/src/components/Form/FormContent.tsx +0 -362
- package/src/components/Form/FormContext.tsx +0 -110
- package/src/components/Form/Input.tsx +0 -54
- package/src/components/Form/RefField.tsx +0 -283
- package/src/components/Form/custom/GeoPointInput.tsx +0 -80
- package/src/components/Form/custom/index.ts +0 -7
- package/src/components/Form/factory.tsx +0 -34
- package/src/hooks/useForm.ts +0 -296
- package/src/hooks/useQueryRefOptions.ts +0 -62
- package/src/hooks/useRefQueryLookupHandler.ts +0 -39
- package/src/util/schema.ts +0 -12
|
@@ -2,49 +2,52 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { RegistryContext } from '@effect-atom/atom-react';
|
|
5
6
|
import * as Array from 'effect/Array';
|
|
6
7
|
import * as Match from 'effect/Match';
|
|
7
8
|
import * as Option from 'effect/Option';
|
|
8
9
|
import * as Schema from 'effect/Schema';
|
|
9
|
-
import React, { forwardRef, useCallback, useImperativeHandle, useMemo, useState } from 'react';
|
|
10
|
+
import React, { forwardRef, useCallback, useContext, useImperativeHandle, useMemo, useState } from 'react';
|
|
10
11
|
|
|
11
|
-
import { QueryAST } from '@dxos/echo';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
12
|
+
import { Entity, Feed, Filter, Format, Obj, Query, QueryAST, Ref, type SchemaRegistry } from '@dxos/echo';
|
|
13
|
+
import { View } from '@dxos/echo';
|
|
14
|
+
import { EchoSchema, type JsonProp, isMutable, toJsonSchema } from '@dxos/echo/internal';
|
|
14
15
|
import { invariant } from '@dxos/invariant';
|
|
15
|
-
import {
|
|
16
|
+
import { useObject, useQuery } from '@dxos/react-client/echo';
|
|
17
|
+
import { IconButton, Input, Message, type ThemedClassName, useTranslation } from '@dxos/react-ui';
|
|
16
18
|
import { QueryForm, type QueryFormProps } from '@dxos/react-ui-components';
|
|
17
19
|
import { List } from '@dxos/react-ui-list';
|
|
18
|
-
import { cardSpacing } from '@dxos/react-ui-stack';
|
|
19
|
-
import { inputTextLabel, mx, subtleHover } from '@dxos/react-ui-theme';
|
|
20
20
|
import {
|
|
21
|
-
|
|
22
|
-
type FieldType,
|
|
21
|
+
ParentLabelAnnotation,
|
|
23
22
|
ProjectionModel,
|
|
24
23
|
VIEW_FIELD_LIMIT,
|
|
25
|
-
|
|
24
|
+
createEchoChangeCallback,
|
|
26
25
|
getTypenameFromQuery,
|
|
27
26
|
} from '@dxos/schema';
|
|
27
|
+
import { mx, osTranslations, subtleHover } from '@dxos/ui-theme';
|
|
28
28
|
|
|
29
29
|
import { translationKey } from '../../translations';
|
|
30
30
|
import { FieldEditor } from '../FieldEditor';
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
import {
|
|
32
|
+
Form,
|
|
33
|
+
type FormFieldComponent,
|
|
34
|
+
type FormFieldComponentProps,
|
|
35
|
+
FormFieldLabel,
|
|
36
|
+
type FormFieldMap,
|
|
37
|
+
type FormRootProps,
|
|
38
|
+
} from '../Form';
|
|
35
39
|
|
|
36
40
|
export type ViewEditorProps = ThemedClassName<
|
|
37
41
|
{
|
|
38
42
|
schema: Schema.Schema.AnyNoContext;
|
|
39
43
|
view: View.View;
|
|
40
44
|
mode?: 'schema' | 'tag';
|
|
41
|
-
registry?: SchemaRegistry;
|
|
42
|
-
readonly?: boolean;
|
|
45
|
+
registry?: SchemaRegistry.SchemaRegistry;
|
|
43
46
|
showHeading?: boolean;
|
|
44
47
|
onQueryChanged?: (query: QueryAST.Query, target?: string) => void;
|
|
45
48
|
onDelete?: (fieldId: string) => void;
|
|
46
|
-
} & Pick<
|
|
47
|
-
Pick<
|
|
49
|
+
} & Pick<QueryFormProps, 'types' | 'tags'> &
|
|
50
|
+
Pick<FormRootProps<any>, 'readonly' | 'db'>
|
|
48
51
|
>;
|
|
49
52
|
|
|
50
53
|
/**
|
|
@@ -58,36 +61,64 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
58
61
|
view,
|
|
59
62
|
mode = 'schema',
|
|
60
63
|
registry,
|
|
64
|
+
db,
|
|
61
65
|
readonly,
|
|
62
66
|
showHeading = false,
|
|
63
67
|
types,
|
|
64
68
|
tags,
|
|
65
69
|
onQueryChanged,
|
|
66
70
|
onDelete,
|
|
67
|
-
outerSpacing = true,
|
|
68
71
|
},
|
|
69
72
|
forwardedRef,
|
|
70
73
|
) => {
|
|
74
|
+
const atomRegistry = useContext(RegistryContext);
|
|
71
75
|
const schemaReadonly = !isMutable(schema);
|
|
72
76
|
const { t } = useTranslation(translationKey);
|
|
77
|
+
|
|
73
78
|
const projectionModel = useMemo(() => {
|
|
74
79
|
// Use reactive and mutable version of json schema when schema is mutable.
|
|
75
80
|
const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
|
|
76
|
-
|
|
77
|
-
|
|
81
|
+
|
|
82
|
+
// Always use createEchoChangeCallback since the view is ECHO-backed.
|
|
83
|
+
// Pass schema only when mutable to allow schema mutations.
|
|
84
|
+
const change = createEchoChangeCallback(view, schema instanceof EchoSchema ? schema : undefined);
|
|
85
|
+
|
|
86
|
+
const model = new ProjectionModel({
|
|
87
|
+
registry: atomRegistry,
|
|
88
|
+
view,
|
|
89
|
+
baseSchema: jsonSchema,
|
|
90
|
+
change,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return model;
|
|
94
|
+
}, [atomRegistry, schema, view]);
|
|
95
|
+
|
|
78
96
|
useImperativeHandle(forwardedRef, () => projectionModel, [projectionModel]);
|
|
79
|
-
const [expandedField, setExpandedField] = useState<FieldType['id']>();
|
|
80
97
|
|
|
81
98
|
const queueTarget = Match.value(view.query.ast).pipe(
|
|
82
|
-
Match.when({ type: '
|
|
83
|
-
|
|
99
|
+
Match.when({ type: 'from' }, ({ from }) => {
|
|
100
|
+
if (from._tag !== 'scope') {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
return Option.fromNullable(from.scope.queues).pipe(
|
|
84
104
|
Option.flatMap((queues) => Array.head(queues)),
|
|
105
|
+
Option.map(String),
|
|
85
106
|
Option.getOrUndefined,
|
|
86
107
|
);
|
|
87
108
|
}),
|
|
88
109
|
Match.orElse(() => undefined),
|
|
89
110
|
);
|
|
90
111
|
|
|
112
|
+
const feeds = useQuery(db, Filter.type(Feed.Feed));
|
|
113
|
+
|
|
114
|
+
const targetRef = useMemo(() => {
|
|
115
|
+
if (!queueTarget) {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
const feed = feeds.find((feed) => Feed.getQueueDxn(feed)?.toString() === queueTarget);
|
|
119
|
+
return feed ? Ref.fromDXN(Entity.getDXN(feed)) : undefined;
|
|
120
|
+
}, [queueTarget, feeds]);
|
|
121
|
+
|
|
91
122
|
const viewSchema = useMemo(() => {
|
|
92
123
|
const base = Schema.Struct({
|
|
93
124
|
query:
|
|
@@ -99,7 +130,12 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
99
130
|
if (mode === 'tag') {
|
|
100
131
|
return Schema.Struct({
|
|
101
132
|
...base.fields,
|
|
102
|
-
|
|
133
|
+
// TODO(wittjosiah): Replace Type.Feed with Dataset.Dataset when Ref.Ref supports unions.
|
|
134
|
+
target: Ref.Ref(Feed.Feed).pipe(
|
|
135
|
+
Schema.annotations({ title: 'Target Feed' }),
|
|
136
|
+
ParentLabelAnnotation.set(true),
|
|
137
|
+
Schema.optional,
|
|
138
|
+
),
|
|
103
139
|
}).pipe(Schema.mutable);
|
|
104
140
|
}
|
|
105
141
|
|
|
@@ -111,211 +147,273 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
111
147
|
const viewValues = useMemo(
|
|
112
148
|
() => ({
|
|
113
149
|
query: mode === 'schema' ? getTypenameFromQuery(view.query.ast) : view.query.ast,
|
|
114
|
-
target:
|
|
150
|
+
target: targetRef,
|
|
115
151
|
}),
|
|
116
|
-
[mode, view.query.ast,
|
|
152
|
+
[mode, view.query.ast, targetRef],
|
|
117
153
|
);
|
|
118
154
|
|
|
119
|
-
const
|
|
120
|
-
(
|
|
121
|
-
|
|
122
|
-
},
|
|
123
|
-
[readonly],
|
|
155
|
+
const fieldMap = useMemo<FormFieldMap | undefined>(
|
|
156
|
+
() => (mode === 'tag' ? customFields({ types, tags }) : undefined),
|
|
157
|
+
[mode, types, tags],
|
|
124
158
|
);
|
|
125
159
|
|
|
126
|
-
// TODO(burdon): Check if mutable; variant of useCallback that return undefined if readonly?
|
|
127
|
-
|
|
128
|
-
const handleAdd = useCallback(() => {
|
|
129
|
-
invariant(!readonly);
|
|
130
|
-
const field = projectionModel.createFieldProjection();
|
|
131
|
-
setExpandedField(field.id);
|
|
132
|
-
}, [schema, projectionModel, readonly]);
|
|
133
|
-
|
|
134
160
|
const handleUpdate = useCallback(
|
|
135
161
|
(values: any) => {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
onQueryChanged?.(query, values.target);
|
|
139
|
-
});
|
|
140
|
-
},
|
|
141
|
-
[onQueryChanged, view, queueTarget, mode],
|
|
142
|
-
);
|
|
162
|
+
const targetValue = values.target;
|
|
163
|
+
let queueDxn: string | undefined;
|
|
143
164
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
165
|
+
if (Ref.isRef(targetValue)) {
|
|
166
|
+
const feedDxn = targetValue.dxn.toString();
|
|
167
|
+
const feed = feeds.find((feed) => Obj.getDXN(feed).toString() === feedDxn);
|
|
168
|
+
if (feed) {
|
|
169
|
+
queueDxn = Feed.getQueueDxn(feed)?.toString();
|
|
170
|
+
}
|
|
149
171
|
}
|
|
150
172
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
(fromIndex: number, toIndex: number) => {
|
|
158
|
-
invariant(!readonly);
|
|
159
|
-
// NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
|
|
160
|
-
const fields = [...view.projection.fields];
|
|
161
|
-
const [moved] = fields.splice(fromIndex, 1);
|
|
162
|
-
fields.splice(toIndex, 0, moved);
|
|
163
|
-
view.projection.fields = fields;
|
|
173
|
+
// TODO(wittjosiah): Deep-clone the AST to plain JS or ECHO proxy arrays become objects with numeric keys.
|
|
174
|
+
const query =
|
|
175
|
+
mode === 'schema'
|
|
176
|
+
? Query.select(Filter.typename(values.query)).ast
|
|
177
|
+
: JSON.parse(JSON.stringify(values.query));
|
|
178
|
+
onQueryChanged?.(query, queueDxn);
|
|
164
179
|
},
|
|
165
|
-
[
|
|
180
|
+
[onQueryChanged, mode, feeds],
|
|
166
181
|
);
|
|
167
182
|
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
const handleHide = useCallback(
|
|
183
|
+
const handleDelete = useCallback(
|
|
171
184
|
(fieldId: string) => {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
},
|
|
175
|
-
[projectionModel],
|
|
176
|
-
);
|
|
177
|
-
|
|
178
|
-
const handleShow = useCallback(
|
|
179
|
-
(property: string) => {
|
|
180
|
-
setExpandedField(undefined);
|
|
181
|
-
projectionModel.showFieldProjection(property as JsonProp);
|
|
185
|
+
invariant(!readonly);
|
|
186
|
+
onDelete?.(fieldId);
|
|
182
187
|
},
|
|
183
|
-
[
|
|
188
|
+
[onDelete, readonly],
|
|
184
189
|
);
|
|
185
190
|
|
|
186
|
-
const custom = useMemo(() => (mode === 'tag' ? customFields({ types, tags }) : undefined), [types, tags, mode]);
|
|
187
|
-
|
|
188
191
|
return (
|
|
189
192
|
<div role='none' className={mx(classNames)}>
|
|
190
|
-
{/* If
|
|
193
|
+
{/* If readonly is set, then the callout is not needed. */}
|
|
191
194
|
{schemaReadonly && !readonly && (
|
|
192
|
-
<
|
|
193
|
-
<
|
|
194
|
-
</
|
|
195
|
+
<Message.Root valence='info' classNames='my-form-padding'>
|
|
196
|
+
<Message.Title>{t('system-schema.description')}</Message.Title>
|
|
197
|
+
</Message.Root>
|
|
195
198
|
)}
|
|
196
199
|
|
|
197
200
|
{/* TODO(burdon): Is the form read-only or just the schema? */}
|
|
198
|
-
{
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
/>
|
|
207
|
-
|
|
208
|
-
<div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
|
|
209
|
-
<h2 className={mx(inputTextLabel)}>{t('fields label')}</h2>
|
|
210
|
-
|
|
211
|
-
<List.Root<FieldType>
|
|
212
|
-
items={view.projection.fields}
|
|
213
|
-
isItem={Schema.is(FieldSchema)}
|
|
214
|
-
getId={(field) => field.id}
|
|
215
|
-
onMove={readonly ? undefined : handleMove}
|
|
201
|
+
<Form.Root schema={viewSchema} values={viewValues} fieldMap={fieldMap} db={db} onValuesChanged={handleUpdate}>
|
|
202
|
+
<Form.FieldSet />
|
|
203
|
+
|
|
204
|
+
<FormFieldLabel label={t('fields.label')} asChild />
|
|
205
|
+
<FieldList
|
|
206
|
+
schema={schema}
|
|
207
|
+
view={view}
|
|
208
|
+
registry={registry}
|
|
216
209
|
readonly={readonly}
|
|
217
|
-
|
|
218
|
-
{
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
<div role='list' className={listGrid}>
|
|
222
|
-
{fields?.map((field) => {
|
|
223
|
-
const hidden = field.visible === false;
|
|
224
|
-
return (
|
|
225
|
-
<List.Item<FieldType>
|
|
226
|
-
key={field.id}
|
|
227
|
-
item={field}
|
|
228
|
-
classNames={listItemGrid}
|
|
229
|
-
aria-expanded={expandedField === field.id}
|
|
230
|
-
>
|
|
231
|
-
<div
|
|
232
|
-
role='none'
|
|
233
|
-
className={mx(subtleHover, listItemGrid, 'rounded-sm cursor-pointer min-bs-10')}
|
|
234
|
-
>
|
|
235
|
-
<List.ItemDragHandle disabled={readonly || schemaReadonly} />
|
|
236
|
-
<List.ItemTitle
|
|
237
|
-
classNames={hidden && 'text-subdued'}
|
|
238
|
-
onClick={() => handleToggleField(field)}
|
|
239
|
-
>
|
|
240
|
-
{field.path}
|
|
241
|
-
</List.ItemTitle>
|
|
242
|
-
<List.ItemButton
|
|
243
|
-
label={t(hidden ? 'show field label' : 'hide field label')}
|
|
244
|
-
data-testid={hidden ? 'show-field-button' : 'hide-field-button'}
|
|
245
|
-
icon={hidden ? 'ph--eye-closed--regular' : 'ph--eye--regular'}
|
|
246
|
-
autoHide={false}
|
|
247
|
-
disabled={readonly || (!hidden && projectionModel.fields.length <= 1)}
|
|
248
|
-
onClick={() => (hidden ? handleShow(field.path) : handleHide(field.id))}
|
|
249
|
-
/>
|
|
250
|
-
{!readonly && (
|
|
251
|
-
<>
|
|
252
|
-
<List.ItemDeleteButton
|
|
253
|
-
label={t('delete field label')}
|
|
254
|
-
autoHide={false}
|
|
255
|
-
disabled={readonly || schemaReadonly || view.projection.fields.length <= 1}
|
|
256
|
-
onClick={() => handleDelete(field.id)}
|
|
257
|
-
data-testid='field.delete'
|
|
258
|
-
/>
|
|
259
|
-
<IconButton
|
|
260
|
-
iconOnly
|
|
261
|
-
variant='ghost'
|
|
262
|
-
label={t('toggle expand label', { ns: 'os' })}
|
|
263
|
-
icon={
|
|
264
|
-
expandedField === field.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'
|
|
265
|
-
}
|
|
266
|
-
onClick={() => handleToggleField(field)}
|
|
267
|
-
/>
|
|
268
|
-
</>
|
|
269
|
-
)}
|
|
270
|
-
</div>
|
|
271
|
-
{expandedField === field.id && !readonly && (
|
|
272
|
-
<div role='none' className='col-span-5 mbs-1 mbe-1 border border-separator rounded-md'>
|
|
273
|
-
<FieldEditor
|
|
274
|
-
readonly={readonly || schemaReadonly ? 'disabled-input' : false}
|
|
275
|
-
projection={projectionModel}
|
|
276
|
-
field={field}
|
|
277
|
-
registry={registry}
|
|
278
|
-
onSave={handleClose}
|
|
279
|
-
/>
|
|
280
|
-
</div>
|
|
281
|
-
)}
|
|
282
|
-
</List.Item>
|
|
283
|
-
);
|
|
284
|
-
})}
|
|
285
|
-
</div>
|
|
286
|
-
</>
|
|
287
|
-
)}
|
|
288
|
-
</List.Root>
|
|
289
|
-
</div>
|
|
290
|
-
|
|
291
|
-
{!readonly && !expandedField && (
|
|
292
|
-
<div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
|
|
293
|
-
<IconButton
|
|
294
|
-
icon='ph--plus--regular'
|
|
295
|
-
label={t('add property button label')}
|
|
296
|
-
onClick={readonly ? undefined : handleAdd}
|
|
297
|
-
// TODO(burdon): Show field limit in ux (not tooltip).
|
|
298
|
-
disabled={view.projection.fields.length >= VIEW_FIELD_LIMIT}
|
|
299
|
-
classNames='is-full'
|
|
300
|
-
/>
|
|
301
|
-
</div>
|
|
302
|
-
)}
|
|
210
|
+
showHeading={showHeading}
|
|
211
|
+
onDelete={handleDelete}
|
|
212
|
+
/>
|
|
213
|
+
</Form.Root>
|
|
303
214
|
</div>
|
|
304
215
|
);
|
|
305
216
|
},
|
|
306
217
|
);
|
|
307
218
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
219
|
+
type FieldListProps = Pick<ViewEditorProps, 'schema' | 'view' | 'registry' | 'readonly' | 'showHeading' | 'onDelete'>;
|
|
220
|
+
|
|
221
|
+
const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDelete }: FieldListProps) => {
|
|
222
|
+
const atomRegistry = useContext(RegistryContext);
|
|
223
|
+
const schemaReadonly = !isMutable(schema);
|
|
224
|
+
const { t } = useTranslation(translationKey);
|
|
225
|
+
|
|
226
|
+
// Subscribe to view changes for reactivity.
|
|
227
|
+
const [viewSnapshot] = useObject(view);
|
|
228
|
+
|
|
229
|
+
const projectionModel = useMemo(() => {
|
|
230
|
+
// Use reactive and mutable version of json schema when schema is mutable.
|
|
231
|
+
const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
|
|
232
|
+
|
|
233
|
+
// Always use createEchoChangeCallback since the view is ECHO-backed.
|
|
234
|
+
// Pass schema only when mutable to allow schema mutations.
|
|
235
|
+
const change = createEchoChangeCallback(view, schema instanceof EchoSchema ? schema : undefined);
|
|
236
|
+
|
|
237
|
+
const model = new ProjectionModel({
|
|
238
|
+
registry: atomRegistry,
|
|
239
|
+
view,
|
|
240
|
+
baseSchema: jsonSchema,
|
|
241
|
+
change,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
return model;
|
|
245
|
+
}, [atomRegistry, schema, view]);
|
|
246
|
+
|
|
247
|
+
const [expandedField, setExpandedField] = useState<View.FieldType['id']>();
|
|
248
|
+
|
|
249
|
+
const handleAdd = useCallback(() => {
|
|
250
|
+
invariant(!readonly);
|
|
251
|
+
const field = projectionModel.createFieldProjection();
|
|
252
|
+
setExpandedField(field.id);
|
|
253
|
+
}, [projectionModel, readonly]);
|
|
254
|
+
|
|
255
|
+
const handleToggleField = useCallback(
|
|
256
|
+
(field: View.FieldType) => {
|
|
257
|
+
setExpandedField((prevExpandedFieldId) => (prevExpandedFieldId === field.id ? undefined : field.id));
|
|
258
|
+
},
|
|
259
|
+
[readonly],
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
const handleDelete = useCallback(
|
|
263
|
+
(fieldId: string) => {
|
|
264
|
+
invariant(!readonly);
|
|
265
|
+
if (fieldId === expandedField) {
|
|
266
|
+
setExpandedField(undefined);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
onDelete?.(fieldId);
|
|
270
|
+
},
|
|
271
|
+
[expandedField, onDelete, readonly],
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
const handleMove = useCallback(
|
|
275
|
+
(fromIndex: number, toIndex: number) => {
|
|
276
|
+
invariant(!readonly);
|
|
277
|
+
Obj.change(view, (obj) => {
|
|
278
|
+
// NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
|
|
279
|
+
const fields = [...obj.projection.fields];
|
|
280
|
+
const [moved] = fields.splice(fromIndex, 1);
|
|
281
|
+
fields.splice(toIndex, 0, moved);
|
|
282
|
+
obj.projection.fields = fields;
|
|
283
|
+
});
|
|
284
|
+
},
|
|
285
|
+
[view, readonly],
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
const handleClose = useCallback(() => setExpandedField(undefined), []);
|
|
289
|
+
|
|
290
|
+
const handleHide = useCallback(
|
|
291
|
+
(fieldId: string) => {
|
|
292
|
+
setExpandedField(undefined);
|
|
293
|
+
projectionModel.hideFieldProjection(fieldId);
|
|
294
|
+
},
|
|
295
|
+
[projectionModel],
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
const handleShow = useCallback(
|
|
299
|
+
(property: string) => {
|
|
300
|
+
setExpandedField(undefined);
|
|
301
|
+
projectionModel.showFieldProjection(property as JsonProp);
|
|
302
|
+
},
|
|
303
|
+
[projectionModel],
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
if (!viewSnapshot) {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return (
|
|
311
|
+
<List.Root<View.FieldType>
|
|
312
|
+
items={viewSnapshot.projection.fields as View.FieldType[]}
|
|
313
|
+
isItem={Schema.is(View.FieldSchema)}
|
|
314
|
+
getId={(field) => field.id}
|
|
315
|
+
onMove={readonly ? undefined : handleMove}
|
|
316
|
+
readonly={readonly}
|
|
317
|
+
>
|
|
318
|
+
{({ items: fields }) => (
|
|
319
|
+
<>
|
|
320
|
+
{showHeading && <h3 className='text-sm'>{t('field-path.label')}</h3>}
|
|
321
|
+
<div role='list' className='grid grid-cols-[min-content_1fr_min-content_min-content_min-content]'>
|
|
322
|
+
{fields?.map((field) => {
|
|
323
|
+
const hidden = field.visible === false;
|
|
324
|
+
return (
|
|
325
|
+
<List.Item<View.FieldType>
|
|
326
|
+
key={field.id}
|
|
327
|
+
item={field}
|
|
328
|
+
classNames={'grid grid-cols-subgrid col-span-5'}
|
|
329
|
+
aria-expanded={expandedField === field.id}
|
|
330
|
+
>
|
|
331
|
+
<div
|
|
332
|
+
role='none'
|
|
333
|
+
className={mx(
|
|
334
|
+
subtleHover,
|
|
335
|
+
'grid grid-cols-subgrid col-span-5',
|
|
336
|
+
'rounded-xs cursor-pointer min-h-10',
|
|
337
|
+
)}
|
|
338
|
+
>
|
|
339
|
+
<List.ItemDragHandle disabled={readonly || schemaReadonly} />
|
|
340
|
+
<List.ItemTitle classNames={hidden && 'text-subdued'} onClick={() => handleToggleField(field)}>
|
|
341
|
+
{field.path}
|
|
342
|
+
</List.ItemTitle>
|
|
343
|
+
<List.ItemIconButton
|
|
344
|
+
label={t(hidden ? 'show field label' : 'hide field label')}
|
|
345
|
+
data-testid={hidden ? 'show-field-button' : 'hide-field-button'}
|
|
346
|
+
icon={hidden ? 'ph--eye-closed--regular' : 'ph--eye--regular'}
|
|
347
|
+
autoHide={false}
|
|
348
|
+
disabled={readonly || (!hidden && projectionModel.getFields().length <= 1)}
|
|
349
|
+
onClick={() => (hidden ? handleShow(field.path) : handleHide(field.id))}
|
|
350
|
+
/>
|
|
351
|
+
{!readonly && (
|
|
352
|
+
<>
|
|
353
|
+
<List.ItemDeleteButton
|
|
354
|
+
label={t('delete-field.label')}
|
|
355
|
+
autoHide={false}
|
|
356
|
+
disabled={readonly || schemaReadonly || viewSnapshot.projection.fields.length <= 1}
|
|
357
|
+
onClick={() => handleDelete(field.id)}
|
|
358
|
+
data-testid='field.delete'
|
|
359
|
+
/>
|
|
360
|
+
<List.ItemIconButton
|
|
361
|
+
iconOnly
|
|
362
|
+
variant='ghost'
|
|
363
|
+
label={t('toggle-expand.label', { ns: osTranslations })}
|
|
364
|
+
icon={expandedField === field.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'}
|
|
365
|
+
onClick={() => handleToggleField(field)}
|
|
366
|
+
data-testid='field.toggle'
|
|
367
|
+
/>
|
|
368
|
+
</>
|
|
369
|
+
)}
|
|
370
|
+
</div>
|
|
371
|
+
{expandedField === field.id && !readonly && (
|
|
372
|
+
<div role='none' className='col-span-5 mt-1 mb-1 border border-separator rounded-md'>
|
|
373
|
+
<FieldEditor
|
|
374
|
+
readonly={readonly || schemaReadonly}
|
|
375
|
+
registry={registry}
|
|
376
|
+
projection={projectionModel}
|
|
377
|
+
field={field}
|
|
378
|
+
onSave={handleClose}
|
|
379
|
+
/>
|
|
380
|
+
</div>
|
|
381
|
+
)}
|
|
382
|
+
</List.Item>
|
|
383
|
+
);
|
|
384
|
+
})}
|
|
385
|
+
</div>
|
|
386
|
+
{!readonly && !expandedField && (
|
|
387
|
+
<div role='none' className='my-form-padding'>
|
|
388
|
+
<IconButton
|
|
389
|
+
icon='ph--plus--regular'
|
|
390
|
+
label={t('add-property-button.label')}
|
|
391
|
+
onClick={handleAdd}
|
|
392
|
+
disabled={viewSnapshot.projection.fields.length >= VIEW_FIELD_LIMIT}
|
|
393
|
+
classNames='w-full'
|
|
394
|
+
/>
|
|
395
|
+
</div>
|
|
396
|
+
)}
|
|
397
|
+
</>
|
|
398
|
+
)}
|
|
399
|
+
</List.Root>
|
|
400
|
+
);
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
const customFields = ({
|
|
404
|
+
types,
|
|
405
|
+
tags,
|
|
406
|
+
}: Pick<ViewEditorProps, 'types' | 'tags'>): Record<string, FormFieldComponent> => ({
|
|
407
|
+
query: ({ type, readonly, label, getValue, onValueChange }: FormFieldComponentProps) => {
|
|
408
|
+
const handleChange = useCallback<NonNullable<QueryFormProps['onChange']>>(
|
|
409
|
+
(query) => onValueChange(type, query.ast),
|
|
410
|
+
[onValueChange],
|
|
313
411
|
);
|
|
314
412
|
|
|
315
413
|
return (
|
|
316
414
|
<Input.Root>
|
|
317
|
-
<
|
|
318
|
-
<QueryForm initialQuery={
|
|
415
|
+
<FormFieldLabel readonly={readonly} label={label} />
|
|
416
|
+
<QueryForm initialQuery={getValue()} types={types} tags={tags} onChange={handleChange} />
|
|
319
417
|
</Input.Root>
|
|
320
418
|
);
|
|
321
419
|
},
|
package/src/components/index.ts
CHANGED
|
@@ -4,26 +4,40 @@
|
|
|
4
4
|
|
|
5
5
|
import React, { type PropsWithChildren } from 'react';
|
|
6
6
|
|
|
7
|
-
import { type ThemedClassName } from '@dxos/react-ui';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { mx, textBlockWidth } from '@dxos/react-ui-theme';
|
|
7
|
+
import { Column, type ThemedClassName } from '@dxos/react-ui';
|
|
8
|
+
import { Json } from '@dxos/react-ui-syntax-highlighter';
|
|
9
|
+
import { mx } from '@dxos/ui-theme';
|
|
11
10
|
|
|
12
11
|
type TestLayoutProps = ThemedClassName<PropsWithChildren<{ json?: any }>>;
|
|
13
12
|
|
|
14
13
|
export const TestLayout = ({ classNames, children, json }: TestLayoutProps) => {
|
|
15
14
|
return (
|
|
16
|
-
<div className='
|
|
17
|
-
<
|
|
18
|
-
|
|
15
|
+
<div className='h-full w-full grid grid-cols-[1fr_1fr] p-4 gap-4 overflow-hidden'>
|
|
16
|
+
<TestPanel classNames={['dx-container', classNames]}>
|
|
17
|
+
<Column.Root classNames='dx-container' gutter='sm'>
|
|
18
|
+
{children}
|
|
19
|
+
</Column.Root>
|
|
20
|
+
</TestPanel>
|
|
21
|
+
<TestPanel classNames={'overflow-hidden'}>
|
|
22
|
+
<Json.Root data={json}>
|
|
23
|
+
<Json.Content classNames='h-full'>
|
|
24
|
+
<Json.Filter />
|
|
25
|
+
<Json.Data testId='debug' classNames='text-xs' />
|
|
26
|
+
</Json.Content>
|
|
27
|
+
</Json.Root>
|
|
28
|
+
</TestPanel>
|
|
19
29
|
</div>
|
|
20
30
|
);
|
|
21
31
|
};
|
|
22
32
|
|
|
23
33
|
type TestPanelProps = ThemedClassName<PropsWithChildren>;
|
|
24
34
|
|
|
25
|
-
|
|
26
|
-
return
|
|
35
|
+
const TestPanel = ({ classNames, children }: TestPanelProps) => {
|
|
36
|
+
return (
|
|
37
|
+
<div role='none' className={mx(['dx-document h-full bg-modal-surface rounded-md', classNames])}>
|
|
38
|
+
{children}
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
27
41
|
};
|
|
28
42
|
|
|
29
43
|
// Symbol for accessing debug objects in tests.
|
package/src/hooks/index.ts
CHANGED