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