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