@dxos/react-ui-form 0.8.4-main.c4373fc → 0.8.4-main.d05673bc65
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 +2297 -2268
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +2297 -2268
- 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 +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} +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 +194 -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 +71 -51
- package/src/components/ViewEditor/ViewEditor.test.tsx +8 -6
- package/src/components/ViewEditor/ViewEditor.tsx +308 -266
- 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,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,
|
|
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 { cardSpacing } from '@dxos/react-ui-stack';
|
|
27
|
-
import { inputTheme } from '@dxos/react-ui-theme';
|
|
28
|
-
import { inputTextLabel, mx, subtleHover } from '@dxos/react-ui-theme';
|
|
29
20
|
import {
|
|
30
|
-
|
|
31
|
-
FieldSchema,
|
|
32
|
-
type FieldType,
|
|
21
|
+
ParentLabelAnnotation,
|
|
33
22
|
ProjectionModel,
|
|
34
23
|
VIEW_FIELD_LIMIT,
|
|
24
|
+
createEchoChangeCallback,
|
|
35
25
|
getTypenameFromQuery,
|
|
36
26
|
} from '@dxos/schema';
|
|
27
|
+
import { mx, osTranslations, subtleHover } from '@dxos/ui-theme';
|
|
37
28
|
|
|
38
29
|
import { translationKey } from '../../translations';
|
|
39
30
|
import { FieldEditor } from '../FieldEditor';
|
|
40
|
-
import {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
31
|
+
import {
|
|
32
|
+
Form,
|
|
33
|
+
type FormFieldComponent,
|
|
34
|
+
type FormFieldComponentProps,
|
|
35
|
+
FormFieldLabel,
|
|
36
|
+
type FormFieldMap,
|
|
37
|
+
type FormRootProps,
|
|
38
|
+
} from '../Form';
|
|
44
39
|
|
|
45
40
|
export type ViewEditorProps = ThemedClassName<
|
|
46
41
|
{
|
|
47
42
|
schema: Schema.Schema.AnyNoContext;
|
|
48
|
-
view:
|
|
49
|
-
mode?: 'schema' | '
|
|
50
|
-
registry?: SchemaRegistry;
|
|
51
|
-
readonly?: boolean;
|
|
43
|
+
view: View.View;
|
|
44
|
+
mode?: 'schema' | 'tag';
|
|
45
|
+
registry?: SchemaRegistry.SchemaRegistry;
|
|
52
46
|
showHeading?: boolean;
|
|
53
|
-
onQueryChanged?: (query:
|
|
47
|
+
onQueryChanged?: (query: QueryAST.Query, target?: string) => void;
|
|
54
48
|
onDelete?: (fieldId: string) => void;
|
|
55
|
-
} & Pick<
|
|
49
|
+
} & Pick<QueryFormProps, 'types' | 'tags'> &
|
|
50
|
+
Pick<FormRootProps<any>, 'readonly' | 'db'>
|
|
56
51
|
>;
|
|
57
52
|
|
|
58
53
|
/**
|
|
@@ -66,313 +61,360 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
66
61
|
view,
|
|
67
62
|
mode = 'schema',
|
|
68
63
|
registry,
|
|
64
|
+
db,
|
|
69
65
|
readonly,
|
|
70
66
|
showHeading = false,
|
|
67
|
+
types,
|
|
68
|
+
tags,
|
|
71
69
|
onQueryChanged,
|
|
72
70
|
onDelete,
|
|
73
|
-
outerSpacing = true,
|
|
74
71
|
},
|
|
75
72
|
forwardedRef,
|
|
76
73
|
) => {
|
|
74
|
+
const atomRegistry = useContext(RegistryContext);
|
|
77
75
|
const schemaReadonly = !isMutable(schema);
|
|
78
76
|
const { t } = useTranslation(translationKey);
|
|
77
|
+
|
|
79
78
|
const projectionModel = useMemo(() => {
|
|
80
79
|
// Use reactive and mutable version of json schema when schema is mutable.
|
|
81
80
|
const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
|
|
82
|
-
|
|
83
|
-
|
|
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
|
+
|
|
84
96
|
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
97
|
|
|
99
98
|
const queueTarget = Match.value(view.query.ast).pipe(
|
|
100
|
-
Match.when({ type: '
|
|
101
|
-
|
|
99
|
+
Match.when({ type: 'from' }, ({ from }) => {
|
|
100
|
+
if (from._tag !== 'scope') {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
return Option.fromNullable(from.scope.queues).pipe(
|
|
102
104
|
Option.flatMap((queues) => Array.head(queues)),
|
|
105
|
+
Option.map(String),
|
|
103
106
|
Option.getOrUndefined,
|
|
104
107
|
);
|
|
105
108
|
}),
|
|
106
109
|
Match.orElse(() => undefined),
|
|
107
110
|
);
|
|
108
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
|
+
|
|
109
122
|
const viewSchema = useMemo(() => {
|
|
110
123
|
const base = Schema.Struct({
|
|
111
124
|
query:
|
|
112
125
|
mode === 'schema'
|
|
113
126
|
? Format.URL.annotations({ title: 'Record type' })
|
|
114
|
-
:
|
|
127
|
+
: QueryAST.Query.annotations({ title: 'Query' }),
|
|
115
128
|
});
|
|
116
129
|
|
|
117
|
-
if (mode === '
|
|
130
|
+
if (mode === 'tag') {
|
|
118
131
|
return Schema.Struct({
|
|
119
|
-
name: Schema.optional(Schema.String.annotations({ title: 'Name' })),
|
|
120
132
|
...base.fields,
|
|
121
|
-
|
|
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
|
+
),
|
|
122
139
|
}).pipe(Schema.mutable);
|
|
123
140
|
}
|
|
124
141
|
|
|
125
142
|
return base.pipe(Schema.mutable);
|
|
126
143
|
}, [mode]);
|
|
144
|
+
|
|
127
145
|
// TODO(burdon): Need to warn user of possible consequences of editing.
|
|
128
146
|
// TODO(burdon): Settings should have domain name owned by user.
|
|
129
147
|
const viewValues = useMemo(
|
|
130
|
-
() => ({
|
|
131
|
-
|
|
148
|
+
() => ({
|
|
149
|
+
query: mode === 'schema' ? getTypenameFromQuery(view.query.ast) : view.query.ast,
|
|
150
|
+
target: targetRef,
|
|
151
|
+
}),
|
|
152
|
+
[mode, view.query.ast, targetRef],
|
|
132
153
|
);
|
|
133
154
|
|
|
134
|
-
const
|
|
135
|
-
(
|
|
136
|
-
|
|
137
|
-
},
|
|
138
|
-
[readonly],
|
|
155
|
+
const fieldMap = useMemo<FormFieldMap | undefined>(
|
|
156
|
+
() => (mode === 'tag' ? customFields({ types, tags }) : undefined),
|
|
157
|
+
[mode, types, tags],
|
|
139
158
|
);
|
|
140
159
|
|
|
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
160
|
const handleUpdate = useCallback(
|
|
150
|
-
(values:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
161
|
+
(values: any) => {
|
|
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();
|
|
155
170
|
}
|
|
171
|
+
}
|
|
156
172
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
});
|
|
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);
|
|
165
179
|
},
|
|
166
|
-
[
|
|
180
|
+
[onQueryChanged, mode, feeds],
|
|
167
181
|
);
|
|
168
182
|
|
|
169
183
|
const handleDelete = useCallback(
|
|
170
184
|
(fieldId: string) => {
|
|
171
185
|
invariant(!readonly);
|
|
172
|
-
if (fieldId === expandedField) {
|
|
173
|
-
setExpandedField(undefined);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
186
|
onDelete?.(fieldId);
|
|
177
187
|
},
|
|
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],
|
|
188
|
+
[onDelete, readonly],
|
|
209
189
|
);
|
|
210
190
|
|
|
211
191
|
return (
|
|
212
192
|
<div role='none' className={mx(classNames)}>
|
|
213
|
-
{
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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>
|
|
217
198
|
)}
|
|
218
199
|
|
|
219
200
|
{/* 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}
|
|
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}
|
|
239
209
|
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
|
-
)}
|
|
210
|
+
showHeading={showHeading}
|
|
211
|
+
onDelete={handleDelete}
|
|
212
|
+
/>
|
|
213
|
+
</Form.Root>
|
|
326
214
|
</div>
|
|
327
215
|
);
|
|
328
216
|
},
|
|
329
217
|
);
|
|
330
218
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
//
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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, (v) => {
|
|
278
|
+
// NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
|
|
279
|
+
const fields = [...v.projection.fields];
|
|
280
|
+
const [moved] = fields.splice(fromIndex, 1);
|
|
281
|
+
fields.splice(toIndex, 0, moved);
|
|
282
|
+
v.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],
|
|
348
411
|
);
|
|
349
412
|
|
|
350
|
-
// TODO(wittjosiah): This is probably not the right way to do these styles.
|
|
351
413
|
return (
|
|
352
414
|
<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
|
-
/> */}
|
|
415
|
+
<FormFieldLabel readonly={readonly} label={label} />
|
|
416
|
+
<QueryForm initialQuery={getValue()} types={types} tags={tags} onChange={handleChange} />
|
|
375
417
|
</Input.Root>
|
|
376
418
|
);
|
|
377
419
|
},
|
|
378
|
-
};
|
|
420
|
+
});
|