@dxos/react-ui-form 0.8.4-main.72ec0f3 → 0.8.4-main.7996785055
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 +2306 -2293
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +2306 -2293
- 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 +14 -6
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +84 -27
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +60 -83
- 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/{ControlSection/ControlSection.stories.d.ts → Form/fields/SelectOptionField.stories.d.ts} +13 -13
- 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 +10 -5
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
- package/dist/types/src/components/Settings/Settings.d.ts +58 -0
- package/dist/types/src/components/Settings/Settings.d.ts.map +1 -0
- package/dist/types/src/components/{Form/custom/SelectOptionsInput.stories.d.ts → Settings/Settings.stories.d.ts} +11 -7
- 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 +11 -13
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +15 -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 +0 -2
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -3
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useFormHandler.d.ts +82 -0
- package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -0
- package/dist/types/src/hooks/useKeyHandler.d.ts +6 -0
- package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +11 -6
- 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 -2
- package/dist/types/src/util/refs.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +50 -47
- package/src/components/FieldEditor/FieldEditor.stories.tsx +43 -24
- package/src/components/FieldEditor/FieldEditor.tsx +74 -78
- package/src/components/Form/AUDIT.md +17 -0
- package/src/components/Form/Form.stories.tsx +149 -250
- package/src/components/Form/Form.tsx +314 -111
- package/src/components/Form/FormField.tsx +305 -0
- package/src/components/Form/FormFieldComponent.stories.tsx +65 -0
- package/src/components/Form/FormFieldComponent.tsx +171 -0
- package/src/components/Form/FormFieldSet.tsx +129 -0
- package/src/components/Form/README.md +3 -0
- package/src/components/Form/fields/ArrayField.tsx +149 -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 +199 -0
- package/src/components/Form/fields/SelectField.tsx +65 -0
- package/src/components/Form/{custom/SelectOptionsInput.stories.tsx → fields/SelectOptionField.stories.tsx} +19 -16
- package/src/components/Form/{custom/SelectOptionsInput.tsx → fields/SelectOptionField.tsx} +25 -19
- 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 +15 -12
- package/src/components/ObjectPicker/ObjectPicker.tsx +103 -78
- package/src/components/Settings/Settings.stories.tsx +56 -0
- package/src/components/Settings/Settings.tsx +235 -0
- package/src/components/{ControlSection → Settings}/index.ts +1 -1
- package/src/components/ViewEditor/ViewEditor.stories.tsx +47 -44
- package/src/components/ViewEditor/ViewEditor.test.tsx +5 -4
- package/src/components/ViewEditor/ViewEditor.tsx +297 -210
- package/src/components/index.ts +1 -1
- package/src/components/testing.tsx +17 -8
- 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 +13 -9
- package/src/util/field.ts +25 -25
- package/src/util/formatting.ts +22 -22
- package/src/util/index.ts +1 -1
- package/src/util/properties.ts +18 -0
- package/src/util/refs.ts +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.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.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 -125
- package/src/components/Form/Defaults.tsx +0 -205
- package/src/components/Form/FormActions.tsx +0 -54
- 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 -31
- 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
|
-
mode?: 'schema' | '
|
|
41
|
-
registry?: SchemaRegistry;
|
|
42
|
-
readonly?: boolean;
|
|
44
|
+
mode?: 'schema' | 'tag';
|
|
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:
|
|
@@ -96,14 +127,15 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
96
127
|
: QueryAST.Query.annotations({ title: 'Query' }),
|
|
97
128
|
});
|
|
98
129
|
|
|
99
|
-
if (mode === '
|
|
100
|
-
const name = Schema.Struct({
|
|
101
|
-
name: Schema.optional(Schema.String.annotations({ title: 'Name' })),
|
|
102
|
-
});
|
|
103
|
-
|
|
130
|
+
if (mode === 'tag') {
|
|
104
131
|
return Schema.Struct({
|
|
105
|
-
...
|
|
106
|
-
|
|
132
|
+
...base.fields,
|
|
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
|
+
),
|
|
107
139
|
}).pipe(Schema.mutable);
|
|
108
140
|
}
|
|
109
141
|
|
|
@@ -114,219 +146,274 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
114
146
|
// TODO(burdon): Settings should have domain name owned by user.
|
|
115
147
|
const viewValues = useMemo(
|
|
116
148
|
() => ({
|
|
117
|
-
name: view.name,
|
|
118
149
|
query: mode === 'schema' ? getTypenameFromQuery(view.query.ast) : view.query.ast,
|
|
119
|
-
target:
|
|
150
|
+
target: targetRef,
|
|
120
151
|
}),
|
|
121
|
-
[
|
|
152
|
+
[mode, view.query.ast, targetRef],
|
|
122
153
|
);
|
|
123
154
|
|
|
124
|
-
const
|
|
125
|
-
(
|
|
126
|
-
|
|
127
|
-
},
|
|
128
|
-
[readonly],
|
|
155
|
+
const fieldMap = useMemo<FormFieldMap | undefined>(
|
|
156
|
+
() => (mode === 'tag' ? customFields({ types, tags }) : undefined),
|
|
157
|
+
[mode, types, tags],
|
|
129
158
|
);
|
|
130
159
|
|
|
131
|
-
// TODO(burdon): Check if mutable; variant of useCallback that return undefined if readonly?
|
|
132
|
-
|
|
133
|
-
const handleAdd = useCallback(() => {
|
|
134
|
-
invariant(!readonly);
|
|
135
|
-
const field = projectionModel.createFieldProjection();
|
|
136
|
-
setExpandedField(field.id);
|
|
137
|
-
}, [schema, projectionModel, readonly]);
|
|
138
|
-
|
|
139
160
|
const handleUpdate = useCallback(
|
|
140
161
|
(values: any) => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
162
|
+
const targetValue = values.target;
|
|
163
|
+
let queueDxn: string | undefined;
|
|
164
|
+
|
|
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();
|
|
146
170
|
}
|
|
171
|
+
}
|
|
147
172
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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);
|
|
151
179
|
},
|
|
152
|
-
[onQueryChanged,
|
|
180
|
+
[onQueryChanged, mode, feeds],
|
|
153
181
|
);
|
|
154
182
|
|
|
155
183
|
const handleDelete = useCallback(
|
|
156
184
|
(fieldId: string) => {
|
|
157
185
|
invariant(!readonly);
|
|
158
|
-
if (fieldId === expandedField) {
|
|
159
|
-
setExpandedField(undefined);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
186
|
onDelete?.(fieldId);
|
|
163
187
|
},
|
|
164
|
-
[
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
const handleMove = useCallback(
|
|
168
|
-
(fromIndex: number, toIndex: number) => {
|
|
169
|
-
invariant(!readonly);
|
|
170
|
-
// NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
|
|
171
|
-
const fields = [...view.projection.fields];
|
|
172
|
-
const [moved] = fields.splice(fromIndex, 1);
|
|
173
|
-
fields.splice(toIndex, 0, moved);
|
|
174
|
-
view.projection.fields = fields;
|
|
175
|
-
},
|
|
176
|
-
[view.projection.fields, readonly],
|
|
188
|
+
[onDelete, readonly],
|
|
177
189
|
);
|
|
178
190
|
|
|
179
|
-
const handleClose = useCallback(() => setExpandedField(undefined), []);
|
|
180
|
-
|
|
181
|
-
const handleHide = useCallback(
|
|
182
|
-
(fieldId: string) => {
|
|
183
|
-
setExpandedField(undefined);
|
|
184
|
-
projectionModel.hideFieldProjection(fieldId);
|
|
185
|
-
},
|
|
186
|
-
[projectionModel],
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
const handleShow = useCallback(
|
|
190
|
-
(property: string) => {
|
|
191
|
-
setExpandedField(undefined);
|
|
192
|
-
projectionModel.showFieldProjection(property as JsonProp);
|
|
193
|
-
},
|
|
194
|
-
[projectionModel],
|
|
195
|
-
);
|
|
196
|
-
|
|
197
|
-
const custom = useMemo(() => (mode === 'query' ? customFields({ types, tags }) : undefined), [types, tags, mode]);
|
|
198
|
-
|
|
199
191
|
return (
|
|
200
192
|
<div role='none' className={mx(classNames)}>
|
|
201
|
-
{
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
193
|
+
{/* If readonly is set, then the callout is not needed. */}
|
|
194
|
+
{schemaReadonly && !readonly && (
|
|
195
|
+
<Message.Root valence='info' classNames='my-form-padding'>
|
|
196
|
+
<Message.Title>{t('system schema description')}</Message.Title>
|
|
197
|
+
</Message.Root>
|
|
205
198
|
)}
|
|
206
199
|
|
|
207
200
|
{/* TODO(burdon): Is the form read-only or just the schema? */}
|
|
208
|
-
{
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
Custom={custom}
|
|
217
|
-
/>
|
|
218
|
-
|
|
219
|
-
<div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
|
|
220
|
-
<h2 className={mx(inputTextLabel)}>{t('fields label')}</h2>
|
|
221
|
-
|
|
222
|
-
<List.Root<FieldType>
|
|
223
|
-
items={view.projection.fields}
|
|
224
|
-
isItem={Schema.is(FieldSchema)}
|
|
225
|
-
getId={(field) => field.id}
|
|
226
|
-
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}
|
|
227
209
|
readonly={readonly}
|
|
228
|
-
|
|
229
|
-
{
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
<div role='list' className={listGrid}>
|
|
233
|
-
{fields?.map((field) => {
|
|
234
|
-
const hidden = field.visible === false;
|
|
235
|
-
return (
|
|
236
|
-
<List.Item<FieldType>
|
|
237
|
-
key={field.id}
|
|
238
|
-
item={field}
|
|
239
|
-
classNames={listItemGrid}
|
|
240
|
-
aria-expanded={expandedField === field.id}
|
|
241
|
-
>
|
|
242
|
-
<div
|
|
243
|
-
role='none'
|
|
244
|
-
className={mx(subtleHover, listItemGrid, 'rounded-sm cursor-pointer min-bs-10')}
|
|
245
|
-
>
|
|
246
|
-
<List.ItemDragHandle disabled={readonly} />
|
|
247
|
-
<List.ItemTitle
|
|
248
|
-
classNames={hidden && 'text-subdued'}
|
|
249
|
-
onClick={() => handleToggleField(field)}
|
|
250
|
-
>
|
|
251
|
-
{field.path}
|
|
252
|
-
</List.ItemTitle>
|
|
253
|
-
<List.ItemButton
|
|
254
|
-
label={t(hidden ? 'show field label' : 'hide field label')}
|
|
255
|
-
data-testid={hidden ? 'show-field-button' : 'hide-field-button'}
|
|
256
|
-
icon={hidden ? 'ph--eye-closed--regular' : 'ph--eye--regular'}
|
|
257
|
-
autoHide={false}
|
|
258
|
-
disabled={readonly || (!hidden && projectionModel.fields.length <= 1)}
|
|
259
|
-
onClick={() => (hidden ? handleShow(field.path) : handleHide(field.id))}
|
|
260
|
-
/>
|
|
261
|
-
{mode === 'schema' && (
|
|
262
|
-
<>
|
|
263
|
-
<List.ItemDeleteButton
|
|
264
|
-
label={t('delete field label')}
|
|
265
|
-
autoHide={false}
|
|
266
|
-
disabled={readonly || schemaReadonly || view.projection.fields.length <= 1}
|
|
267
|
-
onClick={() => handleDelete(field.id)}
|
|
268
|
-
data-testid='field.delete'
|
|
269
|
-
/>
|
|
270
|
-
<IconButton
|
|
271
|
-
iconOnly
|
|
272
|
-
variant='ghost'
|
|
273
|
-
label={t('toggle expand label', { ns: 'os' })}
|
|
274
|
-
icon={
|
|
275
|
-
expandedField === field.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'
|
|
276
|
-
}
|
|
277
|
-
onClick={() => handleToggleField(field)}
|
|
278
|
-
/>
|
|
279
|
-
</>
|
|
280
|
-
)}
|
|
281
|
-
</div>
|
|
282
|
-
{expandedField === field.id && mode === 'schema' && (
|
|
283
|
-
<div role='none' className='col-span-5 mbs-1 mbe-1 border border-separator rounded-md'>
|
|
284
|
-
<FieldEditor
|
|
285
|
-
readonly={readonly || schemaReadonly ? 'disabled-input' : false}
|
|
286
|
-
projection={projectionModel}
|
|
287
|
-
field={field}
|
|
288
|
-
registry={registry}
|
|
289
|
-
onSave={handleClose}
|
|
290
|
-
/>
|
|
291
|
-
</div>
|
|
292
|
-
)}
|
|
293
|
-
</List.Item>
|
|
294
|
-
);
|
|
295
|
-
})}
|
|
296
|
-
</div>
|
|
297
|
-
</>
|
|
298
|
-
)}
|
|
299
|
-
</List.Root>
|
|
300
|
-
</div>
|
|
301
|
-
|
|
302
|
-
{!readonly && !expandedField && mode === 'schema' && (
|
|
303
|
-
<div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
|
|
304
|
-
<IconButton
|
|
305
|
-
icon='ph--plus--regular'
|
|
306
|
-
label={t('add property button')}
|
|
307
|
-
onClick={readonly ? undefined : handleAdd}
|
|
308
|
-
// TODO(burdon): Show field limit in ux (not tooltip).
|
|
309
|
-
disabled={view.projection.fields.length >= VIEW_FIELD_LIMIT}
|
|
310
|
-
classNames='is-full'
|
|
311
|
-
/>
|
|
312
|
-
</div>
|
|
313
|
-
)}
|
|
210
|
+
showHeading={showHeading}
|
|
211
|
+
onDelete={handleDelete}
|
|
212
|
+
/>
|
|
213
|
+
</Form.Root>
|
|
314
214
|
</div>
|
|
315
215
|
);
|
|
316
216
|
},
|
|
317
217
|
);
|
|
318
218
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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],
|
|
324
411
|
);
|
|
325
412
|
|
|
326
413
|
return (
|
|
327
414
|
<Input.Root>
|
|
328
|
-
<
|
|
329
|
-
<QueryForm initialQuery={
|
|
415
|
+
<FormFieldLabel readonly={readonly} label={label} />
|
|
416
|
+
<QueryForm initialQuery={getValue()} types={types} tags={tags} onChange={handleChange} />
|
|
330
417
|
</Input.Root>
|
|
331
418
|
);
|
|
332
419
|
},
|
package/src/components/index.ts
CHANGED
|
@@ -4,26 +4,35 @@
|
|
|
4
4
|
|
|
5
5
|
import React, { type PropsWithChildren } from 'react';
|
|
6
6
|
|
|
7
|
-
import { type ThemedClassName } from '@dxos/react-ui';
|
|
8
|
-
import { Card } from '@dxos/react-ui-stack';
|
|
7
|
+
import { Column, type ThemedClassName } from '@dxos/react-ui';
|
|
9
8
|
import { JsonFilter } from '@dxos/react-ui-syntax-highlighter';
|
|
10
|
-
import { mx
|
|
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
|
+
<JsonFilter testId='debug' data={json} classNames='h-full text-xs' />
|
|
23
|
+
</TestPanel>
|
|
19
24
|
</div>
|
|
20
25
|
);
|
|
21
26
|
};
|
|
22
27
|
|
|
23
28
|
type TestPanelProps = ThemedClassName<PropsWithChildren>;
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
return
|
|
30
|
+
const TestPanel = ({ classNames, children }: TestPanelProps) => {
|
|
31
|
+
return (
|
|
32
|
+
<div role='none' className={mx(['dx-document h-full bg-modal-surface rounded-md', classNames])}>
|
|
33
|
+
{children}
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
27
36
|
};
|
|
28
37
|
|
|
29
38
|
// Symbol for accessing debug objects in tests.
|