@dxos/react-ui-form 0.8.4-main.84f28bd → 0.8.4-main.c1de068
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/dist/lib/browser/index.mjs +29 -35
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +29 -35
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts +3 -4
- package/dist/types/src/components/FieldEditor/FieldEditor.d.ts.map +1 -1
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts +2 -2
- package/dist/types/src/components/FieldEditor/FieldEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts +2 -2
- package/dist/types/src/components/ViewEditor/ViewEditor.d.ts.map +1 -1
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts +3 -3
- package/dist/types/src/components/ViewEditor/ViewEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/deprecated/Form.d.ts +3 -3
- package/dist/types/src/components/deprecated/Form.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +29 -29
- package/src/components/FieldEditor/FieldEditor.stories.tsx +12 -10
- package/src/components/FieldEditor/FieldEditor.test.tsx +11 -11
- package/src/components/FieldEditor/FieldEditor.tsx +5 -15
- package/src/components/Form/ArrayField.tsx +1 -1
- package/src/components/Form/custom/SelectOptionsInput.tsx +1 -1
- package/src/components/ViewEditor/ViewEditor.stories.tsx +14 -8
- package/src/components/ViewEditor/ViewEditor.test.tsx +14 -12
- package/src/components/ViewEditor/ViewEditor.tsx +17 -32
- package/src/components/deprecated/Form.stories.tsx +3 -3
- package/src/components/deprecated/Form.tsx +4 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-form",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.c1de068",
|
|
4
4
|
"description": "Data components.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -29,48 +29,48 @@
|
|
|
29
29
|
"@radix-ui/react-context": "1.1.1",
|
|
30
30
|
"date-fns": "^3.3.1",
|
|
31
31
|
"jsonpath": "^1.1.1",
|
|
32
|
-
"@dxos/async": "0.8.4-main.
|
|
33
|
-
"@dxos/debug": "0.8.4-main.
|
|
34
|
-
"@dxos/echo": "0.8.4-main.
|
|
35
|
-
"@dxos/echo-db": "0.8.4-main.
|
|
36
|
-
"@dxos/echo-schema": "0.8.4-main.
|
|
37
|
-
"@dxos/effect": "0.8.4-main.
|
|
38
|
-
"@dxos/invariant": "0.8.4-main.
|
|
39
|
-
"@dxos/keys": "0.8.4-main.
|
|
40
|
-
"@dxos/
|
|
41
|
-
"@dxos/
|
|
42
|
-
"@dxos/log": "0.8.4-main.
|
|
43
|
-
"@dxos/react-client": "0.8.4-main.
|
|
44
|
-
"@dxos/react-ui-list": "0.8.4-main.
|
|
45
|
-
"@dxos/react-ui-pickers": "0.8.4-main.
|
|
46
|
-
"@dxos/react-ui-
|
|
47
|
-
"@dxos/react-ui-
|
|
48
|
-
"@dxos/react-ui-types": "0.8.4-main.
|
|
49
|
-
"@dxos/schema": "0.8.4-main.
|
|
50
|
-
"@dxos/util": "0.8.4-main.
|
|
32
|
+
"@dxos/async": "0.8.4-main.c1de068",
|
|
33
|
+
"@dxos/debug": "0.8.4-main.c1de068",
|
|
34
|
+
"@dxos/echo": "0.8.4-main.c1de068",
|
|
35
|
+
"@dxos/echo-db": "0.8.4-main.c1de068",
|
|
36
|
+
"@dxos/echo-schema": "0.8.4-main.c1de068",
|
|
37
|
+
"@dxos/effect": "0.8.4-main.c1de068",
|
|
38
|
+
"@dxos/invariant": "0.8.4-main.c1de068",
|
|
39
|
+
"@dxos/keys": "0.8.4-main.c1de068",
|
|
40
|
+
"@dxos/live-object": "0.8.4-main.c1de068",
|
|
41
|
+
"@dxos/lit-ui": "0.8.4-main.c1de068",
|
|
42
|
+
"@dxos/log": "0.8.4-main.c1de068",
|
|
43
|
+
"@dxos/react-client": "0.8.4-main.c1de068",
|
|
44
|
+
"@dxos/react-ui-list": "0.8.4-main.c1de068",
|
|
45
|
+
"@dxos/react-ui-pickers": "0.8.4-main.c1de068",
|
|
46
|
+
"@dxos/react-ui-tag-picker": "0.8.4-main.c1de068",
|
|
47
|
+
"@dxos/react-ui-stack": "0.8.4-main.c1de068",
|
|
48
|
+
"@dxos/react-ui-types": "0.8.4-main.c1de068",
|
|
49
|
+
"@dxos/schema": "0.8.4-main.c1de068",
|
|
50
|
+
"@dxos/util": "0.8.4-main.c1de068"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/jsonpath": "^0.2.4",
|
|
54
54
|
"@types/react": "~18.2.0",
|
|
55
55
|
"@types/react-dom": "~18.2.0",
|
|
56
|
-
"effect": "3.
|
|
56
|
+
"effect": "3.17.0",
|
|
57
57
|
"fast-check": "^3.19.0",
|
|
58
58
|
"react": "~18.2.0",
|
|
59
59
|
"react-dom": "~18.2.0",
|
|
60
60
|
"vite": "5.4.7",
|
|
61
|
-
"@dxos/echo-signals": "0.8.4-main.
|
|
62
|
-
"@dxos/
|
|
63
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
64
|
-
"@dxos/
|
|
65
|
-
"@dxos/react-ui-theme": "0.8.4-main.
|
|
66
|
-
"@dxos/storybook-utils": "0.8.4-main.
|
|
61
|
+
"@dxos/echo-signals": "0.8.4-main.c1de068",
|
|
62
|
+
"@dxos/react-ui": "0.8.4-main.c1de068",
|
|
63
|
+
"@dxos/react-ui-syntax-highlighter": "0.8.4-main.c1de068",
|
|
64
|
+
"@dxos/random": "0.8.4-main.c1de068",
|
|
65
|
+
"@dxos/react-ui-theme": "0.8.4-main.c1de068",
|
|
66
|
+
"@dxos/storybook-utils": "0.8.4-main.c1de068"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
69
|
"effect": "^3.13.3",
|
|
70
70
|
"react": "~18.2.0",
|
|
71
71
|
"react-dom": "~18.2.0",
|
|
72
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
73
|
-
"@dxos/react-ui-theme": "0.8.4-main.
|
|
72
|
+
"@dxos/react-ui": "0.8.4-main.c1de068",
|
|
73
|
+
"@dxos/react-ui-theme": "0.8.4-main.c1de068"
|
|
74
74
|
},
|
|
75
75
|
"publishConfig": {
|
|
76
76
|
"access": "public"
|
|
@@ -9,7 +9,7 @@ import React, { useMemo, useEffect } from 'react';
|
|
|
9
9
|
|
|
10
10
|
import { createEchoSchema } from '@dxos/live-object/testing';
|
|
11
11
|
import { log } from '@dxos/log';
|
|
12
|
-
import {
|
|
12
|
+
import { ProjectionModel } from '@dxos/schema';
|
|
13
13
|
import { TestSchema, testView } from '@dxos/schema/testing';
|
|
14
14
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
15
15
|
|
|
@@ -20,13 +20,12 @@ import { TestLayout, TestPanel, FIELD_EDITOR_DEBUG_SYMBOL } from '../testing';
|
|
|
20
20
|
// Type definition for debug objects exposed to tests.
|
|
21
21
|
export type FieldEditorDebugObjects = {
|
|
22
22
|
props: FieldEditorProps;
|
|
23
|
-
projection:
|
|
23
|
+
projection: ProjectionModel;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
type StoryProps = FieldEditorProps;
|
|
27
27
|
|
|
28
28
|
const DefaultStory = (props: FieldEditorProps) => {
|
|
29
|
-
const projection = useMemo(() => new ViewProjection(createEchoSchema(TestSchema).jsonSchema, testView), []);
|
|
30
29
|
const handleComplete: FieldEditorProps['onSave'] = () => {
|
|
31
30
|
log.info('onClose', { props });
|
|
32
31
|
};
|
|
@@ -34,20 +33,23 @@ const DefaultStory = (props: FieldEditorProps) => {
|
|
|
34
33
|
// Expose objects on window for test access.
|
|
35
34
|
useEffect(() => {
|
|
36
35
|
if (typeof window !== 'undefined') {
|
|
37
|
-
(window as any)[FIELD_EDITOR_DEBUG_SYMBOL] = {
|
|
36
|
+
(window as any)[FIELD_EDITOR_DEBUG_SYMBOL] = {
|
|
37
|
+
props,
|
|
38
|
+
projection: props.projection,
|
|
39
|
+
} satisfies FieldEditorDebugObjects;
|
|
38
40
|
}
|
|
39
|
-
}, [props, projection]);
|
|
41
|
+
}, [props, props.projection]);
|
|
40
42
|
|
|
41
43
|
// NOTE(ZaymonFC): This looks awkward but it resolves an infinite parsing issue with sb.
|
|
42
44
|
const json = useMemo(
|
|
43
|
-
() => JSON.parse(JSON.stringify({ props, projection })),
|
|
44
|
-
[JSON.stringify(props), JSON.stringify(projection)],
|
|
45
|
+
() => JSON.parse(JSON.stringify({ props, projection: props.projection })),
|
|
46
|
+
[JSON.stringify(props), JSON.stringify(props.projection)],
|
|
45
47
|
);
|
|
46
48
|
|
|
47
49
|
return (
|
|
48
50
|
<TestLayout json={json}>
|
|
49
51
|
<TestPanel>
|
|
50
|
-
<FieldEditor {...props}
|
|
52
|
+
<FieldEditor {...props} onSave={handleComplete} />
|
|
51
53
|
</TestPanel>
|
|
52
54
|
</TestLayout>
|
|
53
55
|
);
|
|
@@ -69,8 +71,8 @@ type Story = StoryObj<StoryProps>;
|
|
|
69
71
|
|
|
70
72
|
export const Default: Story = {
|
|
71
73
|
args: {
|
|
72
|
-
|
|
73
|
-
field: testView.fields[0],
|
|
74
|
+
projection: new ProjectionModel(createEchoSchema(TestSchema).jsonSchema, testView.projection),
|
|
75
|
+
field: testView.projection.fields[0],
|
|
74
76
|
},
|
|
75
77
|
parameters: { controls: { disabled: true } },
|
|
76
78
|
};
|
|
@@ -6,7 +6,7 @@ import { composeStories } from '@storybook/react';
|
|
|
6
6
|
import { screen, cleanup, fireEvent, act } from '@testing-library/react';
|
|
7
7
|
import { afterEach, describe, expect, test } from 'vitest';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { ProjectionModel } from '@dxos/schema';
|
|
10
10
|
|
|
11
11
|
import * as stories from './FieldEditor.stories';
|
|
12
12
|
import { type FieldEditorDebugObjects } from './FieldEditor.stories';
|
|
@@ -14,14 +14,6 @@ import { FIELD_EDITOR_DEBUG_SYMBOL } from '../testing';
|
|
|
14
14
|
|
|
15
15
|
const { Default } = composeStories(stories);
|
|
16
16
|
|
|
17
|
-
const getFieldEditorDebugObjects = (): FieldEditorDebugObjects => {
|
|
18
|
-
const debugObjects = (window as any)[FIELD_EDITOR_DEBUG_SYMBOL] as FieldEditorDebugObjects;
|
|
19
|
-
expect(debugObjects).toBeDefined();
|
|
20
|
-
expect(debugObjects.props).toBeInstanceOf(Object); // Props object
|
|
21
|
-
expect(debugObjects.projection).toBeInstanceOf(ViewProjection);
|
|
22
|
-
return debugObjects;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
17
|
describe('FieldEditor', () => {
|
|
26
18
|
afterEach(() => {
|
|
27
19
|
cleanup();
|
|
@@ -33,7 +25,7 @@ describe('FieldEditor', () => {
|
|
|
33
25
|
expect(screen.getByText('Type format')).toBeInTheDocument();
|
|
34
26
|
expect(screen.getByText('String')).toBeInTheDocument();
|
|
35
27
|
|
|
36
|
-
// Verify the initial field shows as String type
|
|
28
|
+
// Verify the initial field shows as String type.
|
|
37
29
|
expect(screen.getByText('String')).toBeInTheDocument();
|
|
38
30
|
|
|
39
31
|
fireEvent.click(screen.getByRole('combobox'));
|
|
@@ -49,9 +41,17 @@ describe('FieldEditor', () => {
|
|
|
49
41
|
// Access the live objects directly from window using symbol.
|
|
50
42
|
const debugObjects = getFieldEditorDebugObjects();
|
|
51
43
|
|
|
52
|
-
const name = debugObjects.projection.
|
|
44
|
+
const name = debugObjects.projection.baseSchema.properties!.name;
|
|
53
45
|
expect(name.type).toBe('number');
|
|
54
46
|
expect(name.format).toBe('number');
|
|
55
47
|
expect(name.description).toBe('Full name.');
|
|
56
48
|
});
|
|
57
49
|
});
|
|
50
|
+
|
|
51
|
+
const getFieldEditorDebugObjects = (): FieldEditorDebugObjects => {
|
|
52
|
+
const debugObjects = (window as any)[FIELD_EDITOR_DEBUG_SYMBOL] as FieldEditorDebugObjects;
|
|
53
|
+
expect(debugObjects).toBeDefined();
|
|
54
|
+
expect(debugObjects.props).toBeInstanceOf(Object); // Props object
|
|
55
|
+
expect(debugObjects.projection).toBeInstanceOf(ProjectionModel);
|
|
56
|
+
return debugObjects;
|
|
57
|
+
};
|
|
@@ -14,8 +14,7 @@ import {
|
|
|
14
14
|
sortProperties,
|
|
15
15
|
type FieldType,
|
|
16
16
|
type PropertyType,
|
|
17
|
-
type
|
|
18
|
-
type ViewProjection,
|
|
17
|
+
type ProjectionModel,
|
|
19
18
|
type SchemaProperty,
|
|
20
19
|
} from '@dxos/schema';
|
|
21
20
|
|
|
@@ -23,8 +22,7 @@ import { translationKey } from '../../translations';
|
|
|
23
22
|
import { Form, type FormProps, type InputComponent, SelectInput, SelectOptionInput } from '../Form';
|
|
24
23
|
|
|
25
24
|
export type FieldEditorProps = {
|
|
26
|
-
|
|
27
|
-
projection: ViewProjection;
|
|
25
|
+
projection: ProjectionModel;
|
|
28
26
|
field: FieldType;
|
|
29
27
|
registry?: SchemaRegistry;
|
|
30
28
|
onSave: () => void;
|
|
@@ -34,15 +32,7 @@ export type FieldEditorProps = {
|
|
|
34
32
|
/**
|
|
35
33
|
* Displays a Form representing the metadata for a given `Field` and `View`.
|
|
36
34
|
*/
|
|
37
|
-
export const FieldEditor = ({
|
|
38
|
-
view,
|
|
39
|
-
projection,
|
|
40
|
-
field,
|
|
41
|
-
registry,
|
|
42
|
-
onSave,
|
|
43
|
-
onCancel,
|
|
44
|
-
outerSpacing,
|
|
45
|
-
}: FieldEditorProps) => {
|
|
35
|
+
export const FieldEditor = ({ projection, field, registry, onSave, onCancel, outerSpacing }: FieldEditorProps) => {
|
|
46
36
|
const { t } = useTranslation(translationKey);
|
|
47
37
|
const [props, setProps] = useState<PropertyType>(projection.getFieldProjection(field.id).props);
|
|
48
38
|
useEffect(() => setProps(projection.getFieldProjection(field.id).props), [field, projection]);
|
|
@@ -112,7 +102,7 @@ export const FieldEditor = ({
|
|
|
112
102
|
|
|
113
103
|
const handleValidate = useCallback<NonNullable<FormProps<PropertyType>['onValidate']>>(
|
|
114
104
|
({ property }) => {
|
|
115
|
-
if (property &&
|
|
105
|
+
if (property && projection.fields.find((f) => f.path === property && f.path !== field.path)) {
|
|
116
106
|
return [
|
|
117
107
|
{
|
|
118
108
|
path: 'property',
|
|
@@ -121,7 +111,7 @@ export const FieldEditor = ({
|
|
|
121
111
|
];
|
|
122
112
|
}
|
|
123
113
|
},
|
|
124
|
-
[
|
|
114
|
+
[projection.fields, field],
|
|
125
115
|
);
|
|
126
116
|
|
|
127
117
|
const handleSave = useCallback<NonNullable<FormProps<PropertyType>['onSave']>>(
|
|
@@ -113,7 +113,7 @@ export const ArrayField = ({ property, readonly, path, inputProps, Custom, looku
|
|
|
113
113
|
</div>
|
|
114
114
|
{!readonly && (
|
|
115
115
|
<IconButton
|
|
116
|
-
classNames='is-full mlb-
|
|
116
|
+
classNames='is-full mlb-cardSpacingBlock flex'
|
|
117
117
|
icon='ph--plus--regular'
|
|
118
118
|
label={t('add field')}
|
|
119
119
|
onClick={handleAdd}
|
|
@@ -123,7 +123,7 @@ export const SelectOptionInput = ({ type, label, disabled, getStatus, getValue,
|
|
|
123
123
|
<List.Item
|
|
124
124
|
role='button'
|
|
125
125
|
item={item}
|
|
126
|
-
classNames={
|
|
126
|
+
classNames={['p-1 plb-2', 'flex flex-col', 'cursor-pointer', 'hover:bg-hoverOverlay']}
|
|
127
127
|
aria-expanded={selected === item.id}
|
|
128
128
|
>
|
|
129
129
|
<div className='flex items-center'>
|
|
@@ -8,11 +8,12 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
|
8
8
|
import { Schema } from 'effect';
|
|
9
9
|
import React, { useCallback, useMemo, useState, useEffect } from 'react';
|
|
10
10
|
|
|
11
|
+
import { Obj, Type } from '@dxos/echo';
|
|
11
12
|
import { Format, type EchoSchema, toJsonSchema, TypedObject } from '@dxos/echo-schema';
|
|
12
13
|
import { Filter, useQuery, useSpace } from '@dxos/react-client/echo';
|
|
13
14
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
14
15
|
import { useAsyncEffect } from '@dxos/react-ui';
|
|
15
|
-
import {
|
|
16
|
+
import { DataType, ProjectionModel, createView } from '@dxos/schema';
|
|
16
17
|
import { withTheme, withLayout } from '@dxos/storybook-utils';
|
|
17
18
|
|
|
18
19
|
import { ViewEditor, type ViewEditorProps } from './ViewEditor';
|
|
@@ -22,8 +23,8 @@ import { TestLayout, TestPanel, VIEW_EDITOR_DEBUG_SYMBOL } from '../testing';
|
|
|
22
23
|
// Type definition for debug objects exposed to tests.
|
|
23
24
|
export type ViewEditorDebugObjects = {
|
|
24
25
|
schema: EchoSchema;
|
|
25
|
-
view:
|
|
26
|
-
projection:
|
|
26
|
+
view: DataType.View;
|
|
27
|
+
projection: ProjectionModel;
|
|
27
28
|
};
|
|
28
29
|
|
|
29
30
|
type StoryProps = Pick<ViewEditorProps, 'readonly'>;
|
|
@@ -31,8 +32,8 @@ type StoryProps = Pick<ViewEditorProps, 'readonly'>;
|
|
|
31
32
|
const DefaultStory = (props: StoryProps) => {
|
|
32
33
|
const space = useSpace();
|
|
33
34
|
const [schema, setSchema] = useState<EchoSchema>();
|
|
34
|
-
const [view, setView] = useState<
|
|
35
|
-
const [projection, setProjection] = useState<
|
|
35
|
+
const [view, setView] = useState<DataType.View>();
|
|
36
|
+
const [projection, setProjection] = useState<ProjectionModel>();
|
|
36
37
|
useAsyncEffect(async () => {
|
|
37
38
|
if (space) {
|
|
38
39
|
class TestSchema extends TypedObject({ typename: 'example.com/type/Test', version: '0.1.0' })({
|
|
@@ -42,8 +43,13 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
42
43
|
}) {}
|
|
43
44
|
|
|
44
45
|
const [schema] = await space.db.schemaRegistry.register([TestSchema]);
|
|
45
|
-
const view = createView({
|
|
46
|
-
|
|
46
|
+
const view = createView({
|
|
47
|
+
name: 'Test',
|
|
48
|
+
typename: schema.typename,
|
|
49
|
+
jsonSchema: toJsonSchema(TestSchema),
|
|
50
|
+
presentation: Obj.make(Type.Expando, {}),
|
|
51
|
+
});
|
|
52
|
+
const projection = new ProjectionModel(schema.jsonSchema, view.projection);
|
|
47
53
|
|
|
48
54
|
setSchema(schema);
|
|
49
55
|
setView(view);
|
|
@@ -51,7 +57,7 @@ const DefaultStory = (props: StoryProps) => {
|
|
|
51
57
|
}
|
|
52
58
|
}, [space]);
|
|
53
59
|
|
|
54
|
-
const views = useQuery(space, Filter.type(
|
|
60
|
+
const views = useQuery(space, Filter.type(DataType.View));
|
|
55
61
|
const currentTypename = useMemo(() => view?.query?.typename, [view]);
|
|
56
62
|
const updateViewTypename = useCallback(
|
|
57
63
|
(newTypename: string) => {
|
|
@@ -6,8 +6,8 @@ import { composeStories } from '@storybook/react';
|
|
|
6
6
|
import { screen, cleanup, fireEvent, waitFor } from '@testing-library/react';
|
|
7
7
|
import { afterEach, describe, expect, test } from 'vitest';
|
|
8
8
|
|
|
9
|
-
import { EchoSchema } from '@dxos/echo-schema';
|
|
10
|
-
import {
|
|
9
|
+
import { EchoSchema, isInstanceOf } from '@dxos/echo-schema';
|
|
10
|
+
import { DataType, ProjectionModel } from '@dxos/schema';
|
|
11
11
|
|
|
12
12
|
import * as stories from './ViewEditor.stories';
|
|
13
13
|
import { type ViewEditorDebugObjects } from './ViewEditor.stories';
|
|
@@ -19,8 +19,8 @@ const getViewEditorDebugObjects = (): ViewEditorDebugObjects => {
|
|
|
19
19
|
const debugObjects = (window as any)[VIEW_EDITOR_DEBUG_SYMBOL] as ViewEditorDebugObjects;
|
|
20
20
|
expect(debugObjects).toBeDefined();
|
|
21
21
|
expect(debugObjects.schema).toBeInstanceOf(EchoSchema);
|
|
22
|
-
expect(debugObjects.view).
|
|
23
|
-
expect(debugObjects.projection).toBeInstanceOf(
|
|
22
|
+
expect(isInstanceOf(DataType.View, debugObjects.view)).toBeTruthy();
|
|
23
|
+
expect(debugObjects.projection).toBeInstanceOf(ProjectionModel);
|
|
24
24
|
return debugObjects;
|
|
25
25
|
};
|
|
26
26
|
|
|
@@ -41,7 +41,7 @@ describe('ViewEditor', () => {
|
|
|
41
41
|
test('renders view editor', async () => {
|
|
42
42
|
await Default.run();
|
|
43
43
|
await waitForViewEditor();
|
|
44
|
-
expect(screen.getByText('
|
|
44
|
+
expect(screen.getByText('Typename')).toBeInTheDocument();
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
test('change property name', async () => {
|
|
@@ -73,7 +73,7 @@ describe('ViewEditor', () => {
|
|
|
73
73
|
expect(schemaProperties!.name).toBeUndefined();
|
|
74
74
|
|
|
75
75
|
// Check view contains new_property field.
|
|
76
|
-
const newPropertyField = debugObjects.view.fields.find((field: any) => field.path === 'new_property');
|
|
76
|
+
const newPropertyField = debugObjects.view.projection.fields.find((field: any) => field.path === 'new_property');
|
|
77
77
|
expect(newPropertyField).toBeDefined();
|
|
78
78
|
expect(newPropertyField!.path).toBe('new_property');
|
|
79
79
|
|
|
@@ -120,7 +120,9 @@ describe('ViewEditor', () => {
|
|
|
120
120
|
expect(schemaProperties!.added_property).toBeDefined();
|
|
121
121
|
|
|
122
122
|
// Check view contains added_property field.
|
|
123
|
-
const addedPropertyField = debugObjects.view.fields.find(
|
|
123
|
+
const addedPropertyField = debugObjects.view.projection.fields.find(
|
|
124
|
+
(field: any) => field.path === 'added_property',
|
|
125
|
+
);
|
|
124
126
|
expect(addedPropertyField).toBeDefined();
|
|
125
127
|
expect(addedPropertyField!.path).toBe('added_property');
|
|
126
128
|
|
|
@@ -161,7 +163,7 @@ describe('ViewEditor', () => {
|
|
|
161
163
|
expect(schemaProperties!.name).toBeUndefined();
|
|
162
164
|
|
|
163
165
|
// Check view no longer contains the name field.
|
|
164
|
-
const nameField = debugObjects.view.fields.find((field: any) => field.path === 'name');
|
|
166
|
+
const nameField = debugObjects.view.projection.fields.find((field: any) => field.path === 'name');
|
|
165
167
|
expect(nameField).toBeUndefined();
|
|
166
168
|
|
|
167
169
|
// Check projection no longer contains the name property.
|
|
@@ -187,18 +189,18 @@ describe('ViewEditor', () => {
|
|
|
187
189
|
const debugObjects = getViewEditorDebugObjects();
|
|
188
190
|
|
|
189
191
|
// Check that hiddenFields is non-empty.
|
|
190
|
-
expect(debugObjects.view.hiddenFields).toBeDefined();
|
|
191
|
-
expect(debugObjects.view.hiddenFields!.length).toBeGreaterThan(0);
|
|
192
|
+
expect(debugObjects.view.projection.hiddenFields).toBeDefined();
|
|
193
|
+
expect(debugObjects.view.projection.hiddenFields!.length).toBeGreaterThan(0);
|
|
192
194
|
|
|
193
195
|
// Click the show button
|
|
194
196
|
fireEvent.click(screen.getByTestId('show-field-button'));
|
|
195
197
|
|
|
196
198
|
// Wait for the data to update and check that hiddenFields is empty.
|
|
197
199
|
await waitFor(() => {
|
|
198
|
-
expect(debugObjects.view.hiddenFields!.length).toBe(0);
|
|
200
|
+
expect(debugObjects.view.projection.hiddenFields!.length).toBe(0);
|
|
199
201
|
});
|
|
200
202
|
|
|
201
203
|
// Also verify that the field is back in the visible fields list.
|
|
202
|
-
expect(debugObjects.view.fields.length).toBeGreaterThan(0);
|
|
204
|
+
expect(debugObjects.view.projection.fields.length).toBeGreaterThan(0);
|
|
203
205
|
});
|
|
204
206
|
});
|
|
@@ -12,7 +12,7 @@ import { Callout, IconButton, type ThemedClassName, useTranslation } from '@dxos
|
|
|
12
12
|
import { List } from '@dxos/react-ui-list';
|
|
13
13
|
import { cardSpacing } from '@dxos/react-ui-stack';
|
|
14
14
|
import { ghostHover, inputTextLabel, mx } from '@dxos/react-ui-theme';
|
|
15
|
-
import { FieldSchema, type FieldType, type
|
|
15
|
+
import { FieldSchema, type FieldType, VIEW_FIELD_LIMIT, type DataType, ProjectionModel } from '@dxos/schema';
|
|
16
16
|
|
|
17
17
|
import { translationKey } from '../../translations';
|
|
18
18
|
import { FieldEditor } from '../FieldEditor';
|
|
@@ -21,9 +21,6 @@ import { Form, type FormProps } from '../Form';
|
|
|
21
21
|
const grid = 'grid grid-cols-[32px_1fr_32px_32px] min-bs-[2.5rem]';
|
|
22
22
|
|
|
23
23
|
const ViewMetaSchema = Schema.Struct({
|
|
24
|
-
name: Schema.String.annotations({
|
|
25
|
-
title: 'View',
|
|
26
|
-
}),
|
|
27
24
|
typename: Format.URL.annotations({
|
|
28
25
|
title: 'Typename',
|
|
29
26
|
}),
|
|
@@ -34,7 +31,7 @@ type ViewMetaType = Schema.Schema.Type<typeof ViewMetaSchema>;
|
|
|
34
31
|
export type ViewEditorProps = ThemedClassName<
|
|
35
32
|
{
|
|
36
33
|
schema: Schema.Schema.AnyNoContext;
|
|
37
|
-
view:
|
|
34
|
+
view: DataType.View;
|
|
38
35
|
registry?: SchemaRegistry;
|
|
39
36
|
readonly?: boolean;
|
|
40
37
|
showHeading?: boolean;
|
|
@@ -61,20 +58,19 @@ export const ViewEditor = ({
|
|
|
61
58
|
const projection = useMemo(() => {
|
|
62
59
|
// Use reactive and mutable version of json schema when schema is mutable.
|
|
63
60
|
const jsonSchema = schema instanceof EchoSchema ? schema.jsonSchema : toJsonSchema(schema);
|
|
64
|
-
return new
|
|
65
|
-
}, [schema, view]);
|
|
61
|
+
return new ProjectionModel(jsonSchema, view.projection);
|
|
62
|
+
}, [schema, view.projection]);
|
|
66
63
|
const [field, setField] = useState<FieldType>();
|
|
67
64
|
const readonly = _readonly || !isMutable(schema);
|
|
68
65
|
|
|
69
66
|
// TODO(burdon): Should be reactive.
|
|
70
67
|
const viewValues = useMemo(() => {
|
|
71
68
|
return {
|
|
72
|
-
name: view.name,
|
|
73
69
|
// TODO(burdon): Need to warn user of possible consequences of editing.
|
|
74
70
|
// TODO(burdon): Settings should have domain name owned by user.
|
|
75
71
|
typename: view.query.typename,
|
|
76
72
|
};
|
|
77
|
-
}, [view]);
|
|
73
|
+
}, [view.query.typename]);
|
|
78
74
|
|
|
79
75
|
const handleSelect = useCallback(
|
|
80
76
|
(field: FieldType) => {
|
|
@@ -92,22 +88,18 @@ export const ViewEditor = ({
|
|
|
92
88
|
invariant(!readonly);
|
|
93
89
|
const field = projection.createFieldProjection();
|
|
94
90
|
setField(field);
|
|
95
|
-
}, [schema,
|
|
91
|
+
}, [schema, projection, readonly]);
|
|
96
92
|
|
|
97
93
|
const handleUpdate = useCallback(
|
|
98
|
-
({
|
|
94
|
+
({ typename }: ViewMetaType) => {
|
|
99
95
|
invariant(!readonly);
|
|
100
96
|
requestAnimationFrame(() => {
|
|
101
|
-
if (view.name !== name) {
|
|
102
|
-
view.name = name;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
97
|
if (view.query.typename !== typename && !readonly) {
|
|
106
98
|
onTypenameChanged?.(typename);
|
|
107
99
|
}
|
|
108
100
|
});
|
|
109
101
|
},
|
|
110
|
-
[
|
|
102
|
+
[view.query.typename, onTypenameChanged, readonly],
|
|
111
103
|
);
|
|
112
104
|
|
|
113
105
|
const handleDelete = useCallback(
|
|
@@ -119,19 +111,19 @@ export const ViewEditor = ({
|
|
|
119
111
|
|
|
120
112
|
onDelete?.(fieldId);
|
|
121
113
|
},
|
|
122
|
-
[
|
|
114
|
+
[field, onDelete, readonly],
|
|
123
115
|
);
|
|
124
116
|
|
|
125
117
|
const handleMove = useCallback(
|
|
126
118
|
(fromIndex: number, toIndex: number) => {
|
|
127
119
|
invariant(!readonly);
|
|
128
120
|
// NOTE(ZaymonFC): Using arrayMove here causes a race condition with the kanban model.
|
|
129
|
-
const fields = [...view.fields];
|
|
121
|
+
const fields = [...view.projection.fields];
|
|
130
122
|
const [moved] = fields.splice(fromIndex, 1);
|
|
131
123
|
fields.splice(toIndex, 0, moved);
|
|
132
|
-
view.fields = fields;
|
|
124
|
+
view.projection.fields = fields;
|
|
133
125
|
},
|
|
134
|
-
[
|
|
126
|
+
[view.projection.fields, readonly],
|
|
135
127
|
);
|
|
136
128
|
|
|
137
129
|
const handleClose = useCallback(() => setField(undefined), []);
|
|
@@ -179,7 +171,7 @@ export const ViewEditor = ({
|
|
|
179
171
|
<label className={mx(inputTextLabel)}>{t('fields label')}</label>
|
|
180
172
|
|
|
181
173
|
<List.Root<FieldType>
|
|
182
|
-
items={view.fields}
|
|
174
|
+
items={view.projection.fields}
|
|
183
175
|
isItem={Schema.is(FieldSchema)}
|
|
184
176
|
getId={(field) => field.id}
|
|
185
177
|
readonly={readonly}
|
|
@@ -207,13 +199,13 @@ export const ViewEditor = ({
|
|
|
207
199
|
data-testid='hide-field-button'
|
|
208
200
|
icon='ph--eye-slash--regular'
|
|
209
201
|
// TDOO(burdon): Is this the correct test?
|
|
210
|
-
disabled={view.fields.length <= 1}
|
|
202
|
+
disabled={view.projection.fields.length <= 1}
|
|
211
203
|
onClick={() => handleHide(field.id)}
|
|
212
204
|
/>
|
|
213
205
|
{/* TODO(burdon): Remove unless implement undo. */}
|
|
214
206
|
{!readonly && (
|
|
215
207
|
<List.ItemDeleteButton
|
|
216
|
-
disabled={view.fields.length <= 1}
|
|
208
|
+
disabled={view.projection.fields.length <= 1}
|
|
217
209
|
onClick={() => handleDelete(field.id)}
|
|
218
210
|
/>
|
|
219
211
|
)}
|
|
@@ -258,14 +250,7 @@ export const ViewEditor = ({
|
|
|
258
250
|
</div>
|
|
259
251
|
|
|
260
252
|
{field && (
|
|
261
|
-
<FieldEditor
|
|
262
|
-
key={field.id}
|
|
263
|
-
view={view}
|
|
264
|
-
projection={projection}
|
|
265
|
-
field={field}
|
|
266
|
-
registry={registry}
|
|
267
|
-
onSave={handleClose}
|
|
268
|
-
/>
|
|
253
|
+
<FieldEditor key={field.id} projection={projection} field={field} registry={registry} onSave={handleClose} />
|
|
269
254
|
)}
|
|
270
255
|
|
|
271
256
|
{!readonly && !field && (
|
|
@@ -275,7 +260,7 @@ export const ViewEditor = ({
|
|
|
275
260
|
label={t('button add property')}
|
|
276
261
|
onClick={readonly ? undefined : handleAdd}
|
|
277
262
|
// TODO(burdon): Show field limit in ux (not tooltip).
|
|
278
|
-
disabled={view.fields.length >= VIEW_FIELD_LIMIT}
|
|
263
|
+
disabled={view.projection.fields.length >= VIEW_FIELD_LIMIT}
|
|
279
264
|
classNames='flex is-full'
|
|
280
265
|
/>
|
|
281
266
|
</div>
|
|
@@ -40,14 +40,14 @@ type Story = StoryObj<DeprecatedFormProps<TestType>>;
|
|
|
40
40
|
export const Default: Story = {
|
|
41
41
|
args: {
|
|
42
42
|
object: live(testData),
|
|
43
|
-
|
|
43
|
+
projection: live(testView).projection,
|
|
44
44
|
schema: TestSchema,
|
|
45
45
|
},
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
export const Empty: Story = {
|
|
49
49
|
args: {
|
|
50
|
-
|
|
50
|
+
projection: live(testView).projection,
|
|
51
51
|
schema: TestSchema,
|
|
52
52
|
},
|
|
53
53
|
};
|
|
@@ -55,7 +55,7 @@ export const Empty: Story = {
|
|
|
55
55
|
export const Readonly: Story = {
|
|
56
56
|
args: {
|
|
57
57
|
object: live(testData),
|
|
58
|
-
|
|
58
|
+
projection: live(testView).projection,
|
|
59
59
|
schema: TestSchema,
|
|
60
60
|
readonly: true,
|
|
61
61
|
},
|
|
@@ -8,10 +8,10 @@ import React, { useState } from 'react';
|
|
|
8
8
|
import { findProperty } from '@dxos/effect';
|
|
9
9
|
import { Input, type ThemedClassName, type TextInputProps as NativeTextInputProps } from '@dxos/react-ui';
|
|
10
10
|
import { mx } from '@dxos/react-ui-theme';
|
|
11
|
-
import {
|
|
11
|
+
import { getFieldValue, setFieldValue, type Projection } from '@dxos/schema';
|
|
12
12
|
|
|
13
13
|
export type DeprecatedFormProps<T extends {} = {}> = ThemedClassName<{
|
|
14
|
-
|
|
14
|
+
projection: Projection;
|
|
15
15
|
object: T;
|
|
16
16
|
schema?: Schema.Schema<T>;
|
|
17
17
|
readonly?: boolean;
|
|
@@ -22,14 +22,14 @@ export type DeprecatedFormProps<T extends {} = {}> = ThemedClassName<{
|
|
|
22
22
|
*/
|
|
23
23
|
export const DeprecatedForm = <T extends {} = {}>({
|
|
24
24
|
classNames,
|
|
25
|
-
|
|
25
|
+
projection,
|
|
26
26
|
object,
|
|
27
27
|
schema,
|
|
28
28
|
readonly,
|
|
29
29
|
}: DeprecatedFormProps<T>) => {
|
|
30
30
|
return (
|
|
31
31
|
<div role='none' className={mx('flex flex-col w-full gap-2 p-2', classNames)}>
|
|
32
|
-
{
|
|
32
|
+
{projection.fields.map((field) => {
|
|
33
33
|
const prop = schema && findProperty(schema, field.path);
|
|
34
34
|
const title = (prop && pipe(SchemaAST.getTitleAnnotation(prop), Option.getOrUndefined)) ?? '';
|
|
35
35
|
const description = (prop && pipe(SchemaAST.getDescriptionAnnotation(prop), Option.getOrUndefined)) ?? title;
|