@dxos/react-ui-form 0.8.4-main.c4373fc → 0.8.4-main.c85a9c8dae
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 +2289 -2269
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +2289 -2269
- 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 +15 -10
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +10 -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 +16 -16
- 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 -107
- 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 +40 -37
- package/src/components/ObjectPicker/ObjectPicker.tsx +84 -70
- 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 +69 -49
- package/src/components/ViewEditor/ViewEditor.test.tsx +8 -6
- package/src/components/ViewEditor/ViewEditor.tsx +302 -271
- 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 -21
- 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 -28
- 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 -269
- 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 -295
- package/src/hooks/useQueryRefOptions.ts +0 -59
- package/src/hooks/useRefQueryLookupHandler.ts +0 -39
- package/src/util/schema.ts +0 -12
|
@@ -2,57 +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,
|
|
10
|
+
import React, { forwardRef, useCallback, useContext, useImperativeHandle, useMemo, useState } from 'react';
|
|
10
11
|
|
|
11
|
-
import {
|
|
12
|
-
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';
|
|
13
15
|
import { invariant } from '@dxos/invariant';
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
Input,
|
|
18
|
-
type ThemedClassName,
|
|
19
|
-
useDensityContext,
|
|
20
|
-
useElevationContext,
|
|
21
|
-
useThemeContext,
|
|
22
|
-
useTranslation,
|
|
23
|
-
} from '@dxos/react-ui';
|
|
24
|
-
import { Editor, createBasicExtensions, createThemeExtensions } from '@dxos/react-ui-editor';
|
|
16
|
+
import { useObject, useQuery } from '@dxos/react-client/echo';
|
|
17
|
+
import { IconButton, Input, Message, type ThemedClassName, useTranslation } from '@dxos/react-ui';
|
|
18
|
+
import { QueryForm, type QueryFormProps } from '@dxos/react-ui-components';
|
|
25
19
|
import { List } from '@dxos/react-ui-list';
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import { inputTextLabel, mx, subtleHover } from '@dxos/react-ui-theme';
|
|
29
|
-
import {
|
|
30
|
-
type DataType,
|
|
31
|
-
FieldSchema,
|
|
32
|
-
type FieldType,
|
|
33
|
-
ProjectionModel,
|
|
34
|
-
VIEW_FIELD_LIMIT,
|
|
35
|
-
getTypenameFromQuery,
|
|
36
|
-
} from '@dxos/schema';
|
|
20
|
+
import { ProjectionModel, VIEW_FIELD_LIMIT, createEchoChangeCallback, getTypenameFromQuery } from '@dxos/schema';
|
|
21
|
+
import { mx, osTranslations, subtleHover } from '@dxos/ui-theme';
|
|
37
22
|
|
|
38
23
|
import { translationKey } from '../../translations';
|
|
39
24
|
import { FieldEditor } from '../FieldEditor';
|
|
40
|
-
import {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
25
|
+
import {
|
|
26
|
+
Form,
|
|
27
|
+
type FormFieldComponent,
|
|
28
|
+
type FormFieldComponentProps,
|
|
29
|
+
FormFieldLabel,
|
|
30
|
+
type FormFieldMap,
|
|
31
|
+
type FormRootProps,
|
|
32
|
+
} from '../Form';
|
|
44
33
|
|
|
45
34
|
export type ViewEditorProps = ThemedClassName<
|
|
46
35
|
{
|
|
47
36
|
schema: Schema.Schema.AnyNoContext;
|
|
48
|
-
view:
|
|
49
|
-
mode?: 'schema' | '
|
|
50
|
-
registry?: SchemaRegistry;
|
|
51
|
-
readonly?: boolean;
|
|
37
|
+
view: View.View;
|
|
38
|
+
mode?: 'schema' | 'tag';
|
|
39
|
+
registry?: SchemaRegistry.SchemaRegistry;
|
|
52
40
|
showHeading?: boolean;
|
|
53
|
-
onQueryChanged?: (query:
|
|
41
|
+
onQueryChanged?: (query: QueryAST.Query, target?: string) => void;
|
|
54
42
|
onDelete?: (fieldId: string) => void;
|
|
55
|
-
} & Pick<
|
|
43
|
+
} & Pick<QueryFormProps, 'types' | 'tags'> &
|
|
44
|
+
Pick<FormRootProps<any>, 'readonly' | 'db'>
|
|
56
45
|
>;
|
|
57
46
|
|
|
58
47
|
/**
|
|
@@ -66,313 +55,355 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
66
55
|
view,
|
|
67
56
|
mode = 'schema',
|
|
68
57
|
registry,
|
|
58
|
+
db,
|
|
69
59
|
readonly,
|
|
70
60
|
showHeading = false,
|
|
61
|
+
types,
|
|
62
|
+
tags,
|
|
71
63
|
onQueryChanged,
|
|
72
64
|
onDelete,
|
|
73
|
-
outerSpacing = true,
|
|
74
65
|
},
|
|
75
66
|
forwardedRef,
|
|
76
67
|
) => {
|
|
68
|
+
const atomRegistry = useContext(RegistryContext);
|
|
77
69
|
const schemaReadonly = !isMutable(schema);
|
|
78
70
|
const { t } = useTranslation(translationKey);
|
|
71
|
+
|
|
79
72
|
const projectionModel = useMemo(() => {
|
|
80
73
|
// Use reactive and mutable version of json schema when schema is mutable.
|
|
81
74
|
const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
|
|
82
|
-
|
|
83
|
-
|
|
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
|
+
|
|
84
90
|
useImperativeHandle(forwardedRef, () => projectionModel, [projectionModel]);
|
|
85
|
-
const [expandedField, setExpandedField] = useState<FieldType['id']>();
|
|
86
|
-
|
|
87
|
-
const serializedQuery = Match.value(mode).pipe(
|
|
88
|
-
Match.when('schema', () => getTypenameFromQuery(view.query.ast)),
|
|
89
|
-
Match.when('query', () => {
|
|
90
|
-
if (view.query.string) {
|
|
91
|
-
return view.query.string;
|
|
92
|
-
} else {
|
|
93
|
-
return 'Serializing query AST is not currently supported.';
|
|
94
|
-
}
|
|
95
|
-
}),
|
|
96
|
-
Match.exhaustive,
|
|
97
|
-
);
|
|
98
91
|
|
|
99
92
|
const queueTarget = Match.value(view.query.ast).pipe(
|
|
100
|
-
Match.when({ type: '
|
|
101
|
-
|
|
93
|
+
Match.when({ type: 'from' }, ({ from }) => {
|
|
94
|
+
if (from._tag !== 'scope') {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
return Option.fromNullable(from.scope.queues).pipe(
|
|
102
98
|
Option.flatMap((queues) => Array.head(queues)),
|
|
99
|
+
Option.map(String),
|
|
103
100
|
Option.getOrUndefined,
|
|
104
101
|
);
|
|
105
102
|
}),
|
|
106
103
|
Match.orElse(() => undefined),
|
|
107
104
|
);
|
|
108
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
|
+
|
|
109
116
|
const viewSchema = useMemo(() => {
|
|
110
117
|
const base = Schema.Struct({
|
|
111
118
|
query:
|
|
112
119
|
mode === 'schema'
|
|
113
120
|
? Format.URL.annotations({ title: 'Record type' })
|
|
114
|
-
:
|
|
121
|
+
: QueryAST.Query.annotations({ title: 'Query' }),
|
|
115
122
|
});
|
|
116
123
|
|
|
117
|
-
if (mode === '
|
|
124
|
+
if (mode === 'tag') {
|
|
118
125
|
return Schema.Struct({
|
|
119
|
-
name: Schema.optional(Schema.String.annotations({ title: 'Name' })),
|
|
120
126
|
...base.fields,
|
|
121
|
-
target: Schema.optional(
|
|
127
|
+
target: Schema.optional(Ref.Ref(Feed.Feed).annotations({ title: 'Target Feed' })),
|
|
122
128
|
}).pipe(Schema.mutable);
|
|
123
129
|
}
|
|
124
130
|
|
|
125
131
|
return base.pipe(Schema.mutable);
|
|
126
132
|
}, [mode]);
|
|
133
|
+
|
|
127
134
|
// TODO(burdon): Need to warn user of possible consequences of editing.
|
|
128
135
|
// TODO(burdon): Settings should have domain name owned by user.
|
|
129
136
|
const viewValues = useMemo(
|
|
130
|
-
() => ({
|
|
131
|
-
|
|
137
|
+
() => ({
|
|
138
|
+
query: mode === 'schema' ? getTypenameFromQuery(view.query.ast) : view.query.ast,
|
|
139
|
+
target: targetRef,
|
|
140
|
+
}),
|
|
141
|
+
[mode, view.query.ast, targetRef],
|
|
132
142
|
);
|
|
133
143
|
|
|
134
|
-
const
|
|
135
|
-
(
|
|
136
|
-
|
|
137
|
-
},
|
|
138
|
-
[readonly],
|
|
144
|
+
const fieldMap = useMemo<FormFieldMap | undefined>(
|
|
145
|
+
() => (mode === 'tag' ? customFields({ types, tags }) : undefined),
|
|
146
|
+
[mode, types, tags],
|
|
139
147
|
);
|
|
140
148
|
|
|
141
|
-
// TODO(burdon): Check if mutable; variant of useCallback that return undefined if readonly?
|
|
142
|
-
|
|
143
|
-
const handleAdd = useCallback(() => {
|
|
144
|
-
invariant(!readonly);
|
|
145
|
-
const field = projectionModel.createFieldProjection();
|
|
146
|
-
setExpandedField(field.id);
|
|
147
|
-
}, [schema, projectionModel, readonly]);
|
|
148
|
-
|
|
149
149
|
const handleUpdate = useCallback(
|
|
150
|
-
(values:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
150
|
+
(values: any) => {
|
|
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();
|
|
155
159
|
}
|
|
160
|
+
}
|
|
156
161
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
});
|
|
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);
|
|
165
168
|
},
|
|
166
|
-
[
|
|
169
|
+
[onQueryChanged, mode, feeds],
|
|
167
170
|
);
|
|
168
171
|
|
|
169
172
|
const handleDelete = useCallback(
|
|
170
173
|
(fieldId: string) => {
|
|
171
174
|
invariant(!readonly);
|
|
172
|
-
if (fieldId === expandedField) {
|
|
173
|
-
setExpandedField(undefined);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
175
|
onDelete?.(fieldId);
|
|
177
176
|
},
|
|
178
|
-
[
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
const handleMove = useCallback(
|
|
182
|
-
(fromIndex: number, toIndex: number) => {
|
|
183
|
-
invariant(!readonly);
|
|
184
|
-
// NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
|
|
185
|
-
const fields = [...view.projection.fields];
|
|
186
|
-
const [moved] = fields.splice(fromIndex, 1);
|
|
187
|
-
fields.splice(toIndex, 0, moved);
|
|
188
|
-
view.projection.fields = fields;
|
|
189
|
-
},
|
|
190
|
-
[view.projection.fields, readonly],
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
const handleClose = useCallback(() => setExpandedField(undefined), []);
|
|
194
|
-
|
|
195
|
-
const handleHide = useCallback(
|
|
196
|
-
(fieldId: string) => {
|
|
197
|
-
setExpandedField(undefined);
|
|
198
|
-
projectionModel.hideFieldProjection(fieldId);
|
|
199
|
-
},
|
|
200
|
-
[projectionModel],
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
const handleShow = useCallback(
|
|
204
|
-
(property: string) => {
|
|
205
|
-
setExpandedField(undefined);
|
|
206
|
-
projectionModel.showFieldProjection(property as JsonProp);
|
|
207
|
-
},
|
|
208
|
-
[projectionModel],
|
|
177
|
+
[onDelete, readonly],
|
|
209
178
|
);
|
|
210
179
|
|
|
211
180
|
return (
|
|
212
181
|
<div role='none' className={mx(classNames)}>
|
|
213
|
-
{
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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>
|
|
217
187
|
)}
|
|
218
188
|
|
|
219
189
|
{/* TODO(burdon): Is the form read-only or just the schema? */}
|
|
220
|
-
{
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
Custom={mode === 'query' ? customFields : undefined}
|
|
229
|
-
/>
|
|
230
|
-
|
|
231
|
-
<div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
|
|
232
|
-
<h2 className={mx(inputTextLabel)}>{t('fields label')}</h2>
|
|
233
|
-
|
|
234
|
-
<List.Root<FieldType>
|
|
235
|
-
items={view.projection.fields}
|
|
236
|
-
isItem={Schema.is(FieldSchema)}
|
|
237
|
-
getId={(field) => field.id}
|
|
238
|
-
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}
|
|
239
198
|
readonly={readonly}
|
|
240
|
-
|
|
241
|
-
{
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
<div role='list' className={listGrid}>
|
|
245
|
-
{fields?.map((field) => {
|
|
246
|
-
const hidden = field.visible === false;
|
|
247
|
-
return (
|
|
248
|
-
<List.Item<FieldType>
|
|
249
|
-
key={field.id}
|
|
250
|
-
item={field}
|
|
251
|
-
classNames={listItemGrid}
|
|
252
|
-
aria-expanded={expandedField === field.id}
|
|
253
|
-
>
|
|
254
|
-
<div
|
|
255
|
-
role='none'
|
|
256
|
-
className={mx(subtleHover, listItemGrid, 'rounded-sm cursor-pointer min-bs-10')}
|
|
257
|
-
>
|
|
258
|
-
<List.ItemDragHandle disabled={readonly} />
|
|
259
|
-
<List.ItemTitle
|
|
260
|
-
classNames={hidden && 'text-subdued'}
|
|
261
|
-
onClick={() => handleToggleField(field)}
|
|
262
|
-
>
|
|
263
|
-
{field.path}
|
|
264
|
-
</List.ItemTitle>
|
|
265
|
-
<List.ItemButton
|
|
266
|
-
label={t(hidden ? 'show field label' : 'hide field label')}
|
|
267
|
-
data-testid={hidden ? 'show-field-button' : 'hide-field-button'}
|
|
268
|
-
icon={hidden ? 'ph--eye-closed--regular' : 'ph--eye--regular'}
|
|
269
|
-
autoHide={false}
|
|
270
|
-
disabled={readonly || (!hidden && projectionModel.fields.length <= 1)}
|
|
271
|
-
onClick={() => (hidden ? handleShow(field.path) : handleHide(field.id))}
|
|
272
|
-
/>
|
|
273
|
-
{mode === 'schema' && (
|
|
274
|
-
<>
|
|
275
|
-
<List.ItemDeleteButton
|
|
276
|
-
label={t('delete field label')}
|
|
277
|
-
autoHide={false}
|
|
278
|
-
disabled={readonly || schemaReadonly || view.projection.fields.length <= 1}
|
|
279
|
-
onClick={() => handleDelete(field.id)}
|
|
280
|
-
data-testid='field.delete'
|
|
281
|
-
/>
|
|
282
|
-
<IconButton
|
|
283
|
-
iconOnly
|
|
284
|
-
variant='ghost'
|
|
285
|
-
label={t('toggle expand label', { ns: 'os' })}
|
|
286
|
-
icon={
|
|
287
|
-
expandedField === field.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'
|
|
288
|
-
}
|
|
289
|
-
onClick={() => handleToggleField(field)}
|
|
290
|
-
/>
|
|
291
|
-
</>
|
|
292
|
-
)}
|
|
293
|
-
</div>
|
|
294
|
-
{expandedField === field.id && mode === 'schema' && (
|
|
295
|
-
<div role='none' className='col-span-5 mbs-1 mbe-1 border border-separator rounded-md'>
|
|
296
|
-
<FieldEditor
|
|
297
|
-
readonly={readonly || schemaReadonly ? 'disabled-input' : false}
|
|
298
|
-
projection={projectionModel}
|
|
299
|
-
field={field}
|
|
300
|
-
registry={registry}
|
|
301
|
-
onSave={handleClose}
|
|
302
|
-
/>
|
|
303
|
-
</div>
|
|
304
|
-
)}
|
|
305
|
-
</List.Item>
|
|
306
|
-
);
|
|
307
|
-
})}
|
|
308
|
-
</div>
|
|
309
|
-
</>
|
|
310
|
-
)}
|
|
311
|
-
</List.Root>
|
|
312
|
-
</div>
|
|
313
|
-
|
|
314
|
-
{!readonly && !expandedField && mode === 'schema' && (
|
|
315
|
-
<div role='none' className={outerSpacing ? cardSpacing : 'mlb-cardSpacingBlock'}>
|
|
316
|
-
<IconButton
|
|
317
|
-
icon='ph--plus--regular'
|
|
318
|
-
label={t('button add property')}
|
|
319
|
-
onClick={readonly ? undefined : handleAdd}
|
|
320
|
-
// TODO(burdon): Show field limit in ux (not tooltip).
|
|
321
|
-
disabled={view.projection.fields.length >= VIEW_FIELD_LIMIT}
|
|
322
|
-
classNames='is-full'
|
|
323
|
-
/>
|
|
324
|
-
</div>
|
|
325
|
-
)}
|
|
199
|
+
showHeading={showHeading}
|
|
200
|
+
onDelete={handleDelete}
|
|
201
|
+
/>
|
|
202
|
+
</Form.Root>
|
|
326
203
|
</div>
|
|
327
204
|
);
|
|
328
205
|
},
|
|
329
206
|
);
|
|
330
207
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
//
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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],
|
|
348
400
|
);
|
|
349
401
|
|
|
350
|
-
// TODO(wittjosiah): This is probably not the right way to do these styles.
|
|
351
402
|
return (
|
|
352
403
|
<Input.Root>
|
|
353
|
-
<
|
|
354
|
-
<
|
|
355
|
-
classNames={mx(
|
|
356
|
-
inputTheme.input({ density, elevation }),
|
|
357
|
-
'flex items-center',
|
|
358
|
-
'focus-within:bg-focusSurface focus-within:border-separator focus-within:hover:bg-focusSurface',
|
|
359
|
-
)}
|
|
360
|
-
extensions={extensions}
|
|
361
|
-
value={props.getValue()}
|
|
362
|
-
onChange={handleChange}
|
|
363
|
-
/>
|
|
364
|
-
{/* TODO(wittjosiah): Support query editor.
|
|
365
|
-
<QueryEditor
|
|
366
|
-
classNames={mx(
|
|
367
|
-
inputTheme.input({ density, elevation }),
|
|
368
|
-
'flex items-center',
|
|
369
|
-
'focus-within:bg-focusSurface focus-within:border-separator focus-within:hover:bg-focusSurface',
|
|
370
|
-
)}
|
|
371
|
-
space={space}
|
|
372
|
-
value={props.getValue()}
|
|
373
|
-
onChange={handleChange}
|
|
374
|
-
/> */}
|
|
404
|
+
<FormFieldLabel readonly={readonly} label={label} />
|
|
405
|
+
<QueryForm initialQuery={getValue()} types={types} tags={tags} onChange={handleChange} />
|
|
375
406
|
</Input.Root>
|
|
376
407
|
);
|
|
377
408
|
},
|
|
378
|
-
};
|
|
409
|
+
});
|