@dxos/react-ui-form 0.8.4-main.3eb6e50203 → 0.8.4-main.3fbcb4aa9b
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 +1134 -682
- 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 +1134 -682
- 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 +3 -3
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +63 -61
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.d.ts +44 -16
- package/dist/types/src/components/Form/Form.d.ts.map +1 -1
- package/dist/types/src/components/Form/Form.stories.d.ts +75 -73
- package/dist/types/src/components/Form/Form.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormField.d.ts +11 -5
- package/dist/types/src/components/Form/FormField.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldComponent.d.ts +8 -7
- package/dist/types/src/components/Form/FormFieldComponent.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts +2 -2
- package/dist/types/src/components/Form/FormFieldComponent.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/FormFieldSet.d.ts +6 -10
- package/dist/types/src/components/Form/FormFieldSet.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/ArrayField.d.ts +4 -4
- package/dist/types/src/components/Form/fields/ArrayField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/BooleanField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/GeoPointField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts +8 -1
- package/dist/types/src/components/Form/fields/MarkdownField.d.ts.map +1 -1
- 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.map +1 -1
- package/dist/types/src/components/Form/fields/RefField.d.ts +15 -7
- package/dist/types/src/components/Form/fields/RefField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/SelectField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/SelectOptionField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts +63 -61
- package/dist/types/src/components/Form/fields/SelectOptionField.stories.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/TextAreaField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/TextField.d.ts +1 -1
- package/dist/types/src/components/Form/fields/TextField.d.ts.map +1 -1
- package/dist/types/src/components/Form/fields/TupleField.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 +26 -4
- package/dist/types/src/components/ObjectPicker/ObjectPicker.d.ts.map +1 -1
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts +63 -61
- package/dist/types/src/components/ObjectPicker/ObjectPicker.stories.d.ts.map +1 -1
- 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 +28 -33
- package/dist/types/src/components/Settings/Settings.d.ts.map +1 -1
- package/dist/types/src/components/Settings/Settings.stories.d.ts +64 -61
- package/dist/types/src/components/Settings/Settings.stories.d.ts.map +1 -1
- 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 +2 -0
- package/dist/types/src/components/Settings/index.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +6 -6
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +67 -64
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +2 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +4 -4
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/useFormHandler.d.ts.map +1 -1
- package/dist/types/src/hooks/useKeyHandler.d.ts +2 -1
- package/dist/types/src/hooks/useKeyHandler.d.ts.map +1 -1
- 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 +63 -61
- 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.map +1 -1
- 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.map +1 -1
- package/dist/types/src/util/refs.d.ts +1 -0
- package/dist/types/src/util/refs.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +42 -42
- package/src/components/FieldEditor/FieldEditor.stories.tsx +6 -8
- package/src/components/FieldEditor/FieldEditor.test.tsx +0 -1
- package/src/components/FieldEditor/FieldEditor.tsx +6 -6
- package/src/components/Form/AUDIT.md +17 -0
- package/src/components/Form/Form.stories.tsx +29 -44
- package/src/components/Form/Form.tsx +101 -54
- package/src/components/Form/FormField.tsx +39 -19
- package/src/components/Form/FormFieldComponent.stories.tsx +4 -4
- package/src/components/Form/FormFieldComponent.tsx +11 -11
- package/src/components/Form/FormFieldSet.tsx +121 -104
- package/src/components/Form/fields/ArrayField.tsx +10 -9
- package/src/components/Form/fields/GeoPointField.tsx +8 -7
- package/src/components/Form/fields/MarkdownField.stories.tsx +206 -0
- package/src/components/Form/fields/MarkdownField.tsx +135 -45
- package/src/components/Form/fields/RefField.tsx +85 -45
- package/src/components/Form/fields/SelectField.tsx +1 -1
- package/src/components/Form/fields/SelectOptionField.stories.tsx +10 -8
- package/src/components/Form/fields/SelectOptionField.tsx +13 -13
- package/src/components/Form/fields/TextField.tsx +2 -2
- package/src/components/Form/fields/TupleField.tsx +1 -1
- package/src/components/ObjectForm/ObjectForm.tsx +114 -0
- package/src/components/ObjectForm/index.ts +5 -0
- package/src/components/ObjectPicker/ObjectPicker.stories.tsx +16 -18
- package/src/components/ObjectPicker/ObjectPicker.tsx +92 -65
- package/src/components/ObjectProperties/ObjectProperties.stories.tsx +362 -0
- package/src/components/ObjectProperties/ObjectProperties.test.tsx +101 -0
- package/src/components/ObjectProperties/ObjectProperties.tsx +141 -0
- package/src/components/ObjectProperties/index.ts +5 -0
- package/src/components/Settings/AUDIT.md +52 -0
- package/src/components/Settings/Settings.stories.tsx +69 -21
- package/src/components/Settings/Settings.tsx +33 -187
- 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 +2 -0
- package/src/components/ViewEditor/ViewEditor.stories.tsx +21 -19
- package/src/components/ViewEditor/ViewEditor.test.tsx +2 -2
- package/src/components/ViewEditor/ViewEditor.tsx +87 -50
- package/src/components/index.ts +2 -0
- package/src/components/testing.tsx +23 -12
- package/src/hooks/useFormHandler.ts +42 -24
- package/src/hooks/useKeyHandler.tsx +7 -7
- package/src/index.ts +0 -4
- package/src/translations.ts +66 -63
- package/src/util/field-type.ts +60 -0
- package/src/util/index.ts +1 -1
- package/src/util/refs.ts +7 -3
- package/dist/types/src/components/Form/Form-old.stories.d.ts +0 -131
- package/dist/types/src/components/Form/Form-old.stories.d.ts.map +0 -1
- package/dist/types/src/util/echo.d.ts +0 -6
- package/dist/types/src/util/echo.d.ts.map +0 -1
- package/src/components/Form/Form-old.stories.tsx +0 -314
- package/src/util/echo.ts +0 -25
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { Select } from '@dxos/react-ui';
|
|
8
|
+
|
|
9
|
+
import { type SettingsFieldProps } from './SettingsFieldSet';
|
|
10
|
+
|
|
11
|
+
export type SelectFieldOption = {
|
|
12
|
+
value: string;
|
|
13
|
+
label?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type CreateSelectFieldOptions = {
|
|
17
|
+
/** Options to display. Strings are used as both value and label. */
|
|
18
|
+
options: ReadonlyArray<string | SelectFieldOption>;
|
|
19
|
+
/** Label for the sentinel option that maps to `undefined`. Defaults to `'Default'`. Pass `null` to omit. */
|
|
20
|
+
defaultLabel?: string | null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Factory for a `SettingsFieldSet` `fieldMap` entry that renders a Select control
|
|
25
|
+
* over a fixed list of options, with an optional sentinel for `undefined`.
|
|
26
|
+
*/
|
|
27
|
+
export const createSelectField = ({
|
|
28
|
+
options,
|
|
29
|
+
defaultLabel = 'Default',
|
|
30
|
+
}: CreateSelectFieldOptions): React.FC<SettingsFieldProps<string | undefined>> => {
|
|
31
|
+
const normalized = options.map((option) => (typeof option === 'string' ? { value: option, label: option } : option));
|
|
32
|
+
const hasDefault = defaultLabel !== null;
|
|
33
|
+
// Per-instance sentinel avoids collisions with real option values; empty string when the
|
|
34
|
+
// sentinel is omitted so `undefined` maps to a value Select.Root treats as "no selection".
|
|
35
|
+
const sentinel = hasDefault ? `__default__${Math.random().toString(36).slice(2)}` : '';
|
|
36
|
+
|
|
37
|
+
return ({ value, onChange, readonly }) => (
|
|
38
|
+
<Select.Root
|
|
39
|
+
disabled={readonly}
|
|
40
|
+
value={value ?? sentinel}
|
|
41
|
+
onValueChange={(next) => onChange(hasDefault && next === sentinel ? undefined : next)}
|
|
42
|
+
>
|
|
43
|
+
<Select.TriggerButton disabled={readonly} />
|
|
44
|
+
<Select.Portal>
|
|
45
|
+
<Select.Content>
|
|
46
|
+
<Select.Viewport>
|
|
47
|
+
{hasDefault && <Select.Option value={sentinel}>{defaultLabel}</Select.Option>}
|
|
48
|
+
{normalized.map((option) => (
|
|
49
|
+
<Select.Option key={option.value} value={option.value}>
|
|
50
|
+
{option.label ?? option.value}
|
|
51
|
+
</Select.Option>
|
|
52
|
+
))}
|
|
53
|
+
</Select.Viewport>
|
|
54
|
+
<Select.Arrow />
|
|
55
|
+
</Select.Content>
|
|
56
|
+
</Select.Portal>
|
|
57
|
+
</Select.Root>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
@@ -6,18 +6,19 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
|
6
6
|
import * as Schema from 'effect/Schema';
|
|
7
7
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
8
8
|
|
|
9
|
-
import { DXN, Filter, Obj, Query, type QueryAST, Tag, Type } from '@dxos/echo';
|
|
9
|
+
import { DXN, Filter, JsonSchema, Obj, Query, type QueryAST, Tag, Type } from '@dxos/echo';
|
|
10
|
+
import { type View } from '@dxos/echo';
|
|
10
11
|
import { type EchoSchema, Format, type Mutable } from '@dxos/echo/internal';
|
|
11
12
|
import { useQuery } from '@dxos/react-client/echo';
|
|
12
13
|
import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
|
|
13
14
|
import { useAsyncEffect } from '@dxos/react-ui';
|
|
14
|
-
import { withTheme } from '@dxos/react-ui/testing';
|
|
15
|
-
import { type ProjectionModel,
|
|
15
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
16
|
+
import { type ProjectionModel, ViewModel, getTypenameFromQuery } from '@dxos/schema';
|
|
16
17
|
import { Employer, Organization, Person, Pipeline } from '@dxos/types';
|
|
17
18
|
|
|
18
|
-
import { translations } from '
|
|
19
|
-
import { TestLayout, VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
|
|
19
|
+
import { translations } from '#translations';
|
|
20
20
|
|
|
21
|
+
import { TestLayout, VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
|
|
21
22
|
import { ViewEditor, type ViewEditorProps } from './ViewEditor';
|
|
22
23
|
|
|
23
24
|
const types = [
|
|
@@ -35,9 +36,9 @@ export type ViewEditorDebugObjects = {
|
|
|
35
36
|
projection: ProjectionModel;
|
|
36
37
|
};
|
|
37
38
|
|
|
38
|
-
type
|
|
39
|
+
type DefaultStoryProps = Pick<ViewEditorProps, 'readonly' | 'mode'>;
|
|
39
40
|
|
|
40
|
-
const DefaultStory = (props:
|
|
41
|
+
const DefaultStory = (props: DefaultStoryProps) => {
|
|
41
42
|
const { space } = useClientStory();
|
|
42
43
|
const [schema, setSchema] = useState<EchoSchema>();
|
|
43
44
|
const [view, setView] = useState<View.View>();
|
|
@@ -53,7 +54,7 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
53
54
|
salary: Format.Currency(),
|
|
54
55
|
}).pipe(
|
|
55
56
|
Type.object({
|
|
56
|
-
typename: 'example.
|
|
57
|
+
typename: 'com.example.type.test',
|
|
57
58
|
version: '0.1.0',
|
|
58
59
|
}),
|
|
59
60
|
);
|
|
@@ -64,16 +65,16 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
64
65
|
completed: Schema.Boolean,
|
|
65
66
|
}).pipe(
|
|
66
67
|
Type.object({
|
|
67
|
-
typename: 'example.
|
|
68
|
+
typename: 'com.example.type.alternate',
|
|
68
69
|
version: '0.1.0',
|
|
69
70
|
}),
|
|
70
71
|
);
|
|
71
72
|
|
|
72
73
|
const [testSchema] = await space.db.schemaRegistry.register([TestSchema, AlternateSchema]);
|
|
73
|
-
const view =
|
|
74
|
+
const view = ViewModel.make({
|
|
74
75
|
name: 'Test',
|
|
75
76
|
query: Query.select(Filter.type(TestSchema)),
|
|
76
|
-
jsonSchema:
|
|
77
|
+
jsonSchema: JsonSchema.toJsonSchema(TestSchema),
|
|
77
78
|
});
|
|
78
79
|
|
|
79
80
|
setSchema(testSchema);
|
|
@@ -89,9 +90,9 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
89
90
|
|
|
90
91
|
if (props.mode === 'tag') {
|
|
91
92
|
const queue = target && DXN.tryParse(target) ? target : undefined;
|
|
92
|
-
const query = queue ? Query.fromAst(newQuery).
|
|
93
|
-
Obj.
|
|
94
|
-
|
|
93
|
+
const query = queue ? Query.fromAst(newQuery).from({ queues: [queue] }) : Query.fromAst(newQuery);
|
|
94
|
+
Obj.update(view, (view) => {
|
|
95
|
+
view.query.ast = query.ast as Mutable<typeof query.ast>;
|
|
95
96
|
});
|
|
96
97
|
|
|
97
98
|
const typename = getTypenameFromQuery(query.ast);
|
|
@@ -100,17 +101,17 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
100
101
|
return;
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
const newView =
|
|
104
|
+
const newView = ViewModel.make({
|
|
104
105
|
query,
|
|
105
106
|
jsonSchema: newSchema.jsonSchema,
|
|
106
107
|
});
|
|
107
|
-
Obj.
|
|
108
|
-
|
|
108
|
+
Obj.update(view, (view) => {
|
|
109
|
+
view.projection = Obj.getSnapshot(newView).projection as Mutable<typeof view.projection>;
|
|
109
110
|
});
|
|
110
111
|
setSchema(() => newSchema);
|
|
111
112
|
} else {
|
|
112
|
-
Obj.
|
|
113
|
-
|
|
113
|
+
Obj.update(view, (view) => {
|
|
114
|
+
view.query.ast = newQuery as Mutable<typeof newQuery>;
|
|
114
115
|
});
|
|
115
116
|
schema.updateTypename(getTypenameFromQuery(newQuery));
|
|
116
117
|
}
|
|
@@ -176,6 +177,7 @@ const meta = {
|
|
|
176
177
|
},
|
|
177
178
|
}),
|
|
178
179
|
withTheme(),
|
|
180
|
+
withLayout({ layout: 'fullscreen' }),
|
|
179
181
|
],
|
|
180
182
|
parameters: {
|
|
181
183
|
layout: 'fullscreen',
|
|
@@ -6,11 +6,11 @@ import { composeStories } from '@storybook/react';
|
|
|
6
6
|
import { cleanup, fireEvent, screen, waitFor } from '@testing-library/react';
|
|
7
7
|
import { afterEach, describe, expect, test } from 'vitest';
|
|
8
8
|
|
|
9
|
+
import { View } from '@dxos/echo';
|
|
9
10
|
import { EchoSchema, isInstanceOf } from '@dxos/echo/internal';
|
|
10
|
-
import { ProjectionModel
|
|
11
|
+
import { ProjectionModel } from '@dxos/schema';
|
|
11
12
|
|
|
12
13
|
import { VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
|
|
13
|
-
|
|
14
14
|
import * as stories from './ViewEditor.stories';
|
|
15
15
|
import { type ViewEditorDebugObjects } from './ViewEditor.stories';
|
|
16
16
|
|
|
@@ -9,25 +9,25 @@ import * as Option from 'effect/Option';
|
|
|
9
9
|
import * as Schema from 'effect/Schema';
|
|
10
10
|
import React, { forwardRef, useCallback, useContext, useImperativeHandle, useMemo, useState } from 'react';
|
|
11
11
|
|
|
12
|
-
import { Filter, Format, Obj, Query, QueryAST, type SchemaRegistry } from '@dxos/echo';
|
|
12
|
+
import { Entity, Feed, Filter, Format, Obj, Query, QueryAST, Ref, type SchemaRegistry } from '@dxos/echo';
|
|
13
|
+
import { View } from '@dxos/echo';
|
|
13
14
|
import { EchoSchema, type JsonProp, isMutable, toJsonSchema } from '@dxos/echo/internal';
|
|
14
15
|
import { invariant } from '@dxos/invariant';
|
|
15
|
-
import { useObject } from '@dxos/react-client/echo';
|
|
16
|
-
import {
|
|
16
|
+
import { useObject, useQuery } from '@dxos/react-client/echo';
|
|
17
|
+
import { IconButton, Input, Message, type ThemedClassName, useTranslation } from '@dxos/react-ui';
|
|
17
18
|
import { QueryForm, type QueryFormProps } from '@dxos/react-ui-components';
|
|
18
19
|
import { List } from '@dxos/react-ui-list';
|
|
19
20
|
import {
|
|
20
|
-
|
|
21
|
-
type FieldType,
|
|
21
|
+
ParentLabelAnnotation,
|
|
22
22
|
ProjectionModel,
|
|
23
23
|
VIEW_FIELD_LIMIT,
|
|
24
|
-
type View,
|
|
25
24
|
createEchoChangeCallback,
|
|
26
25
|
getTypenameFromQuery,
|
|
27
26
|
} from '@dxos/schema';
|
|
28
27
|
import { mx, osTranslations, subtleHover } from '@dxos/ui-theme';
|
|
29
28
|
|
|
30
|
-
import { translationKey } from '
|
|
29
|
+
import { translationKey } from '#translations';
|
|
30
|
+
|
|
31
31
|
import { FieldEditor } from '../FieldEditor';
|
|
32
32
|
import {
|
|
33
33
|
Form,
|
|
@@ -35,22 +35,20 @@ import {
|
|
|
35
35
|
type FormFieldComponentProps,
|
|
36
36
|
FormFieldLabel,
|
|
37
37
|
type FormFieldMap,
|
|
38
|
+
type FormRootProps,
|
|
38
39
|
} from '../Form';
|
|
39
40
|
|
|
40
|
-
const listGrid = 'grid grid-cols-[min-content_1fr_min-content_min-content_min-content]';
|
|
41
|
-
const listItemGrid = 'grid grid-cols-subgrid col-span-5';
|
|
42
|
-
|
|
43
41
|
export type ViewEditorProps = ThemedClassName<
|
|
44
42
|
{
|
|
45
43
|
schema: Schema.Schema.AnyNoContext;
|
|
46
44
|
view: View.View;
|
|
47
45
|
mode?: 'schema' | 'tag';
|
|
48
46
|
registry?: SchemaRegistry.SchemaRegistry;
|
|
49
|
-
readonly?: boolean;
|
|
50
47
|
showHeading?: boolean;
|
|
51
48
|
onQueryChanged?: (query: QueryAST.Query, target?: string) => void;
|
|
52
49
|
onDelete?: (fieldId: string) => void;
|
|
53
|
-
} & Pick<QueryFormProps, 'types' | 'tags'>
|
|
50
|
+
} & Pick<QueryFormProps, 'types' | 'tags'> &
|
|
51
|
+
Pick<FormRootProps<any>, 'readonly' | 'db'>
|
|
54
52
|
>;
|
|
55
53
|
|
|
56
54
|
/**
|
|
@@ -64,6 +62,7 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
64
62
|
view,
|
|
65
63
|
mode = 'schema',
|
|
66
64
|
registry,
|
|
65
|
+
db,
|
|
67
66
|
readonly,
|
|
68
67
|
showHeading = false,
|
|
69
68
|
types,
|
|
@@ -98,15 +97,29 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
98
97
|
useImperativeHandle(forwardedRef, () => projectionModel, [projectionModel]);
|
|
99
98
|
|
|
100
99
|
const queueTarget = Match.value(view.query.ast).pipe(
|
|
101
|
-
Match.when({ type: '
|
|
102
|
-
|
|
100
|
+
Match.when({ type: 'from' }, ({ from }) => {
|
|
101
|
+
if (from._tag !== 'scope') {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
return Option.fromNullable(from.scope.queues).pipe(
|
|
103
105
|
Option.flatMap((queues) => Array.head(queues)),
|
|
106
|
+
Option.map(String),
|
|
104
107
|
Option.getOrUndefined,
|
|
105
108
|
);
|
|
106
109
|
}),
|
|
107
110
|
Match.orElse(() => undefined),
|
|
108
111
|
);
|
|
109
112
|
|
|
113
|
+
const feeds = useQuery(db, Filter.type(Feed.Feed));
|
|
114
|
+
|
|
115
|
+
const targetRef = useMemo(() => {
|
|
116
|
+
if (!queueTarget) {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
const feed = feeds.find((feed) => Feed.getQueueDxn(feed)?.toString() === queueTarget);
|
|
120
|
+
return feed ? Ref.fromDXN(Entity.getDXN(feed)) : undefined;
|
|
121
|
+
}, [queueTarget, feeds]);
|
|
122
|
+
|
|
110
123
|
const viewSchema = useMemo(() => {
|
|
111
124
|
const base = Schema.Struct({
|
|
112
125
|
query:
|
|
@@ -118,7 +131,12 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
118
131
|
if (mode === 'tag') {
|
|
119
132
|
return Schema.Struct({
|
|
120
133
|
...base.fields,
|
|
121
|
-
|
|
134
|
+
// TODO(wittjosiah): Replace Type.Feed with Dataset.Dataset when Ref.Ref supports unions.
|
|
135
|
+
target: Ref.Ref(Feed.Feed).pipe(
|
|
136
|
+
Schema.annotations({ title: 'Target Feed' }),
|
|
137
|
+
ParentLabelAnnotation.set(true),
|
|
138
|
+
Schema.optional,
|
|
139
|
+
),
|
|
122
140
|
}).pipe(Schema.mutable);
|
|
123
141
|
}
|
|
124
142
|
|
|
@@ -130,9 +148,9 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
130
148
|
const viewValues = useMemo(
|
|
131
149
|
() => ({
|
|
132
150
|
query: mode === 'schema' ? getTypenameFromQuery(view.query.ast) : view.query.ast,
|
|
133
|
-
target:
|
|
151
|
+
target: targetRef,
|
|
134
152
|
}),
|
|
135
|
-
[mode, view.query.ast,
|
|
153
|
+
[mode, view.query.ast, targetRef],
|
|
136
154
|
);
|
|
137
155
|
|
|
138
156
|
const fieldMap = useMemo<FormFieldMap | undefined>(
|
|
@@ -142,12 +160,25 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
142
160
|
|
|
143
161
|
const handleUpdate = useCallback(
|
|
144
162
|
(values: any) => {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
163
|
+
const targetValue = values.target;
|
|
164
|
+
let queueDxn: string | undefined;
|
|
165
|
+
|
|
166
|
+
if (Ref.isRef(targetValue)) {
|
|
167
|
+
const feedDxn = targetValue.dxn.toString();
|
|
168
|
+
const feed = feeds.find((feed) => Obj.getDXN(feed).toString() === feedDxn);
|
|
169
|
+
if (feed) {
|
|
170
|
+
queueDxn = Feed.getQueueDxn(feed)?.toString();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// TODO(wittjosiah): Deep-clone the AST to plain JS or ECHO proxy arrays become objects with numeric keys.
|
|
175
|
+
const query =
|
|
176
|
+
mode === 'schema'
|
|
177
|
+
? Query.select(Filter.typename(values.query)).ast
|
|
178
|
+
: JSON.parse(JSON.stringify(values.query));
|
|
179
|
+
onQueryChanged?.(query, queueDxn);
|
|
149
180
|
},
|
|
150
|
-
[onQueryChanged,
|
|
181
|
+
[onQueryChanged, mode, feeds],
|
|
151
182
|
);
|
|
152
183
|
|
|
153
184
|
const handleDelete = useCallback(
|
|
@@ -159,19 +190,19 @@ export const ViewEditor = forwardRef<ProjectionModel, ViewEditorProps>(
|
|
|
159
190
|
);
|
|
160
191
|
|
|
161
192
|
return (
|
|
162
|
-
<div
|
|
193
|
+
<div className={mx(classNames)}>
|
|
163
194
|
{/* If readonly is set, then the callout is not needed. */}
|
|
164
195
|
{schemaReadonly && !readonly && (
|
|
165
|
-
<
|
|
166
|
-
<
|
|
167
|
-
</
|
|
196
|
+
<Message.Root valence='info' classNames='my-form-padding'>
|
|
197
|
+
<Message.Title>{t('system-schema.description')}</Message.Title>
|
|
198
|
+
</Message.Root>
|
|
168
199
|
)}
|
|
169
200
|
|
|
170
201
|
{/* TODO(burdon): Is the form read-only or just the schema? */}
|
|
171
|
-
<Form.Root schema={viewSchema} values={viewValues} fieldMap={fieldMap}
|
|
202
|
+
<Form.Root schema={viewSchema} values={viewValues} fieldMap={fieldMap} db={db} onValuesChanged={handleUpdate}>
|
|
172
203
|
<Form.FieldSet />
|
|
173
204
|
|
|
174
|
-
<FormFieldLabel label={t('fields
|
|
205
|
+
<FormFieldLabel label={t('fields.label')} asChild />
|
|
175
206
|
<FieldList
|
|
176
207
|
schema={schema}
|
|
177
208
|
view={view}
|
|
@@ -214,7 +245,7 @@ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDe
|
|
|
214
245
|
return model;
|
|
215
246
|
}, [atomRegistry, schema, view]);
|
|
216
247
|
|
|
217
|
-
const [expandedField, setExpandedField] = useState<FieldType['id']>();
|
|
248
|
+
const [expandedField, setExpandedField] = useState<View.FieldType['id']>();
|
|
218
249
|
|
|
219
250
|
const handleAdd = useCallback(() => {
|
|
220
251
|
invariant(!readonly);
|
|
@@ -223,7 +254,7 @@ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDe
|
|
|
223
254
|
}, [projectionModel, readonly]);
|
|
224
255
|
|
|
225
256
|
const handleToggleField = useCallback(
|
|
226
|
-
(field: FieldType) => {
|
|
257
|
+
(field: View.FieldType) => {
|
|
227
258
|
setExpandedField((prevExpandedFieldId) => (prevExpandedFieldId === field.id ? undefined : field.id));
|
|
228
259
|
},
|
|
229
260
|
[readonly],
|
|
@@ -244,12 +275,12 @@ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDe
|
|
|
244
275
|
const handleMove = useCallback(
|
|
245
276
|
(fromIndex: number, toIndex: number) => {
|
|
246
277
|
invariant(!readonly);
|
|
247
|
-
Obj.
|
|
278
|
+
Obj.update(view, (view) => {
|
|
248
279
|
// NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
|
|
249
|
-
const fields = [...
|
|
280
|
+
const fields = [...view.projection.fields];
|
|
250
281
|
const [moved] = fields.splice(fromIndex, 1);
|
|
251
282
|
fields.splice(toIndex, 0, moved);
|
|
252
|
-
|
|
283
|
+
view.projection.fields = fields;
|
|
253
284
|
});
|
|
254
285
|
},
|
|
255
286
|
[view, readonly],
|
|
@@ -278,33 +309,39 @@ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDe
|
|
|
278
309
|
}
|
|
279
310
|
|
|
280
311
|
return (
|
|
281
|
-
<List.Root<FieldType>
|
|
282
|
-
items={viewSnapshot.projection.fields as FieldType[]}
|
|
283
|
-
isItem={Schema.is(FieldSchema)}
|
|
312
|
+
<List.Root<View.FieldType>
|
|
313
|
+
items={viewSnapshot.projection.fields as View.FieldType[]}
|
|
314
|
+
isItem={Schema.is(View.FieldSchema)}
|
|
284
315
|
getId={(field) => field.id}
|
|
285
316
|
onMove={readonly ? undefined : handleMove}
|
|
286
317
|
readonly={readonly}
|
|
287
318
|
>
|
|
288
319
|
{({ items: fields }) => (
|
|
289
320
|
<>
|
|
290
|
-
{showHeading && <h3 className='text-sm'>{t('field
|
|
291
|
-
<div role='list' className=
|
|
321
|
+
{showHeading && <h3 className='text-sm'>{t('field-path.label')}</h3>}
|
|
322
|
+
<div role='list' className='grid grid-cols-[min-content_1fr_min-content_min-content_min-content]'>
|
|
292
323
|
{fields?.map((field) => {
|
|
293
324
|
const hidden = field.visible === false;
|
|
294
325
|
return (
|
|
295
|
-
<List.Item<FieldType>
|
|
326
|
+
<List.Item<View.FieldType>
|
|
296
327
|
key={field.id}
|
|
297
328
|
item={field}
|
|
298
|
-
classNames={
|
|
329
|
+
classNames={'grid grid-cols-subgrid col-span-5'}
|
|
299
330
|
aria-expanded={expandedField === field.id}
|
|
300
331
|
>
|
|
301
|
-
<div
|
|
332
|
+
<div
|
|
333
|
+
className={mx(
|
|
334
|
+
subtleHover,
|
|
335
|
+
'grid grid-cols-subgrid col-span-5',
|
|
336
|
+
'rounded-xs cursor-pointer min-h-10',
|
|
337
|
+
)}
|
|
338
|
+
>
|
|
302
339
|
<List.ItemDragHandle disabled={readonly || schemaReadonly} />
|
|
303
340
|
<List.ItemTitle classNames={hidden && 'text-subdued'} onClick={() => handleToggleField(field)}>
|
|
304
341
|
{field.path}
|
|
305
342
|
</List.ItemTitle>
|
|
306
|
-
<List.
|
|
307
|
-
label={t(hidden ? 'show
|
|
343
|
+
<List.ItemIconButton
|
|
344
|
+
label={t(hidden ? 'show-field.label' : 'hide-field.label')}
|
|
308
345
|
data-testid={hidden ? 'show-field-button' : 'hide-field-button'}
|
|
309
346
|
icon={hidden ? 'ph--eye-closed--regular' : 'ph--eye--regular'}
|
|
310
347
|
autoHide={false}
|
|
@@ -314,16 +351,16 @@ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDe
|
|
|
314
351
|
{!readonly && (
|
|
315
352
|
<>
|
|
316
353
|
<List.ItemDeleteButton
|
|
317
|
-
label={t('delete
|
|
354
|
+
label={t('delete-field.label')}
|
|
318
355
|
autoHide={false}
|
|
319
356
|
disabled={readonly || schemaReadonly || viewSnapshot.projection.fields.length <= 1}
|
|
320
357
|
onClick={() => handleDelete(field.id)}
|
|
321
358
|
data-testid='field.delete'
|
|
322
359
|
/>
|
|
323
|
-
<
|
|
360
|
+
<List.ItemIconButton
|
|
324
361
|
iconOnly
|
|
325
362
|
variant='ghost'
|
|
326
|
-
label={t('toggle
|
|
363
|
+
label={t('toggle-expand.label', { ns: osTranslations })}
|
|
327
364
|
icon={expandedField === field.id ? 'ph--caret-down--regular' : 'ph--caret-right--regular'}
|
|
328
365
|
onClick={() => handleToggleField(field)}
|
|
329
366
|
data-testid='field.toggle'
|
|
@@ -332,7 +369,7 @@ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDe
|
|
|
332
369
|
)}
|
|
333
370
|
</div>
|
|
334
371
|
{expandedField === field.id && !readonly && (
|
|
335
|
-
<div
|
|
372
|
+
<div className='col-span-full mt-1 mb-1 border border-separator rounded-md'>
|
|
336
373
|
<FieldEditor
|
|
337
374
|
readonly={readonly || schemaReadonly}
|
|
338
375
|
registry={registry}
|
|
@@ -347,13 +384,13 @@ const FieldList = ({ schema, view, registry, readonly, showHeading = false, onDe
|
|
|
347
384
|
})}
|
|
348
385
|
</div>
|
|
349
386
|
{!readonly && !expandedField && (
|
|
350
|
-
<div
|
|
387
|
+
<div className='my-form-padding'>
|
|
351
388
|
<IconButton
|
|
352
389
|
icon='ph--plus--regular'
|
|
353
|
-
label={t('add
|
|
390
|
+
label={t('add-property-button.label')}
|
|
354
391
|
onClick={handleAdd}
|
|
355
392
|
disabled={viewSnapshot.projection.fields.length >= VIEW_FIELD_LIMIT}
|
|
356
|
-
classNames='
|
|
393
|
+
classNames='w-full'
|
|
357
394
|
/>
|
|
358
395
|
</div>
|
|
359
396
|
)}
|
package/src/components/index.ts
CHANGED
|
@@ -5,18 +5,24 @@
|
|
|
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 { textBlockWidth } from '@dxos/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
|
-
<TestPanel
|
|
18
|
-
<TestPanel
|
|
19
|
-
<
|
|
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>
|
|
20
26
|
</TestPanel>
|
|
21
27
|
</div>
|
|
22
28
|
);
|
|
@@ -24,10 +30,15 @@ export const TestLayout = ({ classNames, children, json }: TestLayoutProps) => {
|
|
|
24
30
|
|
|
25
31
|
type TestPanelProps = ThemedClassName<PropsWithChildren>;
|
|
26
32
|
|
|
27
|
-
const TestPanel = ({
|
|
28
|
-
return
|
|
29
|
-
}
|
|
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
|
+
});
|
|
30
40
|
|
|
31
41
|
// Symbol for accessing debug objects in tests.
|
|
32
42
|
export const VIEW_EDITOR_DEBUG_SYMBOL = Symbol('viewEditorDebug');
|
|
33
43
|
export const FIELD_EDITOR_DEBUG_SYMBOL = Symbol('fieldEditorDebug');
|
|
44
|
+
export const OBJECT_PROPERTIES_DEBUG_SYMBOL = Symbol('objectPropertiesDebug');
|