@dxos/react-ui-table 0.8.4-main.72ec0f3 → 0.8.4-main.74a063c4e0
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/chunk-BF54SSNL.mjs +70 -0
- package/dist/lib/browser/chunk-BF54SSNL.mjs.map +7 -0
- package/dist/lib/browser/{chunk-GS2UNGJK.mjs → chunk-NTWHM2PX.mjs} +656 -680
- package/dist/lib/browser/chunk-NTWHM2PX.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1166 -1011
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +32 -44
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/browser/types/index.mjs +3 -3
- package/dist/lib/node-esm/chunk-SIRDIPHZ.mjs +71 -0
- package/dist/lib/node-esm/chunk-SIRDIPHZ.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-TRR3C5NX.mjs → chunk-Z3TLFG6Z.mjs} +656 -679
- package/dist/lib/node-esm/chunk-Z3TLFG6Z.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1166 -1011
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/testing/index.mjs +32 -44
- package/dist/lib/node-esm/testing/index.mjs.map +3 -3
- package/dist/lib/node-esm/types/index.mjs +3 -3
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts +1 -2
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts +2 -2
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +7 -7
- package/dist/types/src/components/Table/DynamicTable.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts +21 -37
- package/dist/types/src/components/Table/DynamicTable.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/Relations.stories.d.ts +82 -80
- package/dist/types/src/components/Table/Relations.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/RowActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.d.ts +28 -21
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +90 -26
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/TableContent.d.ts +21 -0
- package/dist/types/src/components/Table/TableContent.d.ts.map +1 -0
- package/dist/types/src/components/Table/TableToolbar.d.ts +25 -0
- package/dist/types/src/components/Table/TableToolbar.d.ts.map +1 -0
- package/dist/types/src/components/TableCellEditor/CellValidationMessage.d.ts +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +10 -12
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +5 -7
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts +24 -26
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +0 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +22 -15
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -1
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useAddRow.d.ts +5 -6
- package/dist/types/src/hooks/useAddRow.d.ts.map +1 -1
- package/dist/types/src/hooks/useProjectionModel.d.ts +6 -0
- package/dist/types/src/hooks/useProjectionModel.d.ts.map +1 -0
- package/dist/types/src/hooks/useTableModel.d.ts +9 -9
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts +5 -1
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts +17 -8
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts +104 -24
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts +3 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/playwright/TableManager.d.ts +2 -1
- package/dist/types/src/playwright/TableManager.d.ts.map +1 -1
- package/dist/types/src/testing/useTestTableModel.d.ts +18 -15
- package/dist/types/src/testing/useTestTableModel.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +21 -23
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/Table.d.ts +26 -0
- package/dist/types/src/types/Table.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +1 -1
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.d.ts +14 -11
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.test.d.ts +2 -0
- package/dist/types/src/util/dynamic-table.test.d.ts.map +1 -0
- package/dist/types/src/util/index.d.ts +2 -0
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/query-ast.d.ts +7 -0
- package/dist/types/src/util/query-ast.d.ts.map +1 -0
- package/dist/types/src/util/sort.d.ts +12 -0
- package/dist/types/src/util/sort.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +55 -56
- package/src/components/Table/ColumnActionsMenu.tsx +15 -11
- package/src/components/Table/ColumnSettings.tsx +8 -9
- package/src/components/Table/DynamicTable.stories.tsx +27 -28
- package/src/components/Table/DynamicTable.tsx +32 -32
- package/src/components/Table/Relations.stories.tsx +61 -58
- package/src/components/Table/RowActionsMenu.tsx +3 -2
- package/src/components/Table/Table.stories.tsx +118 -102
- package/src/components/Table/Table.tsx +43 -407
- package/src/components/Table/TableContent.tsx +421 -0
- package/src/components/{TableToolbar → Table}/TableToolbar.tsx +43 -44
- package/src/components/TableCellEditor/CellValidationMessage.tsx +5 -5
- package/src/components/TableCellEditor/FormCellEditor.tsx +118 -135
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +20 -17
- package/src/components/TableCellEditor/TableCellEditor.tsx +37 -33
- package/src/components/index.ts +0 -1
- package/src/components/testing.ts +43 -17
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useAddRow.ts +8 -10
- package/src/hooks/useProjectionModel.ts +39 -0
- package/src/hooks/useTableModel.ts +36 -34
- package/src/model/modal-controller.ts +23 -12
- package/src/model/selection-model.ts +75 -44
- package/src/model/table-model.test.ts +82 -55
- package/src/model/table-model.ts +430 -167
- package/src/model/table-presentation.test.ts +59 -50
- package/src/model/table-presentation.ts +43 -38
- package/src/playwright/TableManager.ts +13 -6
- package/src/playwright/playwright.config.ts +1 -1
- package/src/playwright/smoke.spec.ts +52 -8
- package/src/testing/useTestTableModel.ts +48 -41
- package/src/translations.ts +20 -22
- package/src/types/Table.ts +79 -0
- package/src/types/index.ts +1 -1
- package/src/util/dynamic-table.test.ts +70 -0
- package/src/util/dynamic-table.ts +40 -26
- package/src/util/index.ts +2 -0
- package/src/util/query-ast.ts +22 -0
- package/src/util/sort.ts +90 -0
- package/src/util/table-buttons.ts +1 -1
- package/src/util/table-controls.ts +2 -2
- package/dist/lib/browser/chunk-2PFAPQL4.mjs +0 -66
- package/dist/lib/browser/chunk-2PFAPQL4.mjs.map +0 -7
- package/dist/lib/browser/chunk-GS2UNGJK.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-6POOFFYF.mjs +0 -67
- package/dist/lib/node-esm/chunk-6POOFFYF.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-TRR3C5NX.mjs.map +0 -7
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +0 -21
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +0 -1
- package/dist/types/src/components/TableToolbar/index.d.ts +0 -2
- package/dist/types/src/components/TableToolbar/index.d.ts.map +0 -1
- package/dist/types/src/model/table-sorting.d.ts +0 -52
- package/dist/types/src/model/table-sorting.d.ts.map +0 -1
- package/dist/types/src/types/schema.d.ts +0 -28
- package/dist/types/src/types/schema.d.ts.map +0 -1
- package/src/components/TableToolbar/index.ts +0 -5
- package/src/model/table-sorting.ts +0 -225
- package/src/types/schema.ts +0 -73
|
@@ -2,44 +2,43 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { RegistryContext } from '@effect-atom/atom-react';
|
|
5
6
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import { expect, userEvent, within } from 'storybook/test';
|
|
7
|
+
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
|
8
|
+
import { expect, userEvent, waitFor, within } from 'storybook/test';
|
|
9
9
|
|
|
10
10
|
import { Obj, Type } from '@dxos/echo';
|
|
11
|
-
import {
|
|
11
|
+
import { View } from '@dxos/echo';
|
|
12
|
+
import { invariant } from '@dxos/invariant';
|
|
12
13
|
import { type DxGrid } from '@dxos/lit-grid';
|
|
13
|
-
import '@dxos/
|
|
14
|
-
import {
|
|
15
|
-
import { useClient } from '@dxos/react-client';
|
|
16
|
-
import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
|
|
14
|
+
import { random } from '@dxos/random';
|
|
15
|
+
import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
|
|
17
16
|
import { useAsyncEffect } from '@dxos/react-ui';
|
|
18
|
-
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
19
17
|
import { translations as formTranslations } from '@dxos/react-ui-form';
|
|
20
|
-
import {
|
|
18
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
19
|
+
import { ViewModel } from '@dxos/schema';
|
|
21
20
|
import { type ValueGenerator, createAsyncGenerator } from '@dxos/schema/testing';
|
|
21
|
+
import { withRegistry } from '@dxos/storybook-utils';
|
|
22
22
|
import { Organization, Person } from '@dxos/types';
|
|
23
|
+
import '@dxos/lit-ui/dx-tag-picker.pcss';
|
|
23
24
|
|
|
24
|
-
import { useTableModel } from '../../hooks';
|
|
25
|
+
import { useProjectionModel, useTableModel } from '../../hooks';
|
|
25
26
|
import { type TableFeatures, TablePresentation, type TableRow } from '../../model';
|
|
26
27
|
import { translations } from '../../translations';
|
|
27
28
|
import { Table } from '../../types';
|
|
28
|
-
|
|
29
29
|
import { Table as TableComponent } from './Table';
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
const generator: ValueGenerator =
|
|
31
|
+
random.seed(1);
|
|
32
|
+
const generator: ValueGenerator = random as any;
|
|
33
33
|
|
|
34
34
|
// TODO(burdon): Many-to-many relations.
|
|
35
35
|
// TODO(burdon): Mutable and immutable views.
|
|
36
36
|
// TODO(burdon): Reconcile schemas types and utils (see API PR).
|
|
37
37
|
// TODO(burdon): Base type for T (with id); see ECHO API PR?
|
|
38
|
-
const useTestModel = <S extends Type.
|
|
39
|
-
const
|
|
40
|
-
const { space } =
|
|
41
|
-
const [
|
|
42
|
-
const [jsonSchema, setJsonSchema] = useState<JsonSchemaType>();
|
|
38
|
+
const useTestModel = <S extends Type.AnyObj>(schema: S, count: number) => {
|
|
39
|
+
const registry = useContext(RegistryContext);
|
|
40
|
+
const { space } = useClientStory();
|
|
41
|
+
const [object, setObject] = useState<Table.Table>();
|
|
43
42
|
|
|
44
43
|
const features = useMemo<TableFeatures>(
|
|
45
44
|
() => ({ schemaEditable: false, dataEditable: true, selection: { enabled: false } }),
|
|
@@ -51,18 +50,14 @@ const useTestModel = <S extends Type.Obj.Any>(schema: S, count: number) => {
|
|
|
51
50
|
return;
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
const {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
setJsonSchema(jsonSchema);
|
|
60
|
-
setView(view);
|
|
61
|
-
space.db.add(view);
|
|
62
|
-
await space.db.schemaRegistry.register([effectSchema]);
|
|
63
|
-
}, [client, space, schema]);
|
|
53
|
+
const { view, jsonSchema } = await ViewModel.makeFromDatabase({ db: space.db, typename: Type.getTypename(schema) });
|
|
54
|
+
const object = Table.make({ view, jsonSchema });
|
|
55
|
+
setObject(object);
|
|
56
|
+
space.db.add(object);
|
|
57
|
+
}, [space, schema]);
|
|
64
58
|
|
|
65
|
-
const
|
|
59
|
+
const projection = useProjectionModel(schema, object, registry);
|
|
60
|
+
const model = useTableModel<TableRow>({ object, projection, db: space?.db, features });
|
|
66
61
|
|
|
67
62
|
useEffect(() => {
|
|
68
63
|
if (!model || !space) {
|
|
@@ -80,45 +75,44 @@ const useTestModel = <S extends Type.Obj.Any>(schema: S, count: number) => {
|
|
|
80
75
|
return;
|
|
81
76
|
}
|
|
82
77
|
|
|
83
|
-
return new TablePresentation(model);
|
|
84
|
-
}, [model]);
|
|
78
|
+
return new TablePresentation(registry, model);
|
|
79
|
+
}, [registry, model]);
|
|
85
80
|
|
|
86
81
|
return { model, presentation };
|
|
87
82
|
};
|
|
88
83
|
|
|
89
84
|
const DefaultStory = () => {
|
|
90
|
-
const client = useClient();
|
|
91
85
|
const { model: orgModel, presentation: orgPresentation } = useTestModel(Organization.Organization, 50);
|
|
92
86
|
const { model: contactModel, presentation: contactPresentation } = useTestModel(Person.Person, 50);
|
|
93
|
-
const { space } =
|
|
87
|
+
const { space } = useClientStory();
|
|
94
88
|
|
|
95
89
|
const handleCreate = useCallback(
|
|
96
|
-
(schema:
|
|
97
|
-
|
|
90
|
+
(schema: Type.AnyEntity, values: any) => {
|
|
91
|
+
invariant(Type.isObjectSchema(schema));
|
|
92
|
+
invariant(space);
|
|
93
|
+
return space.db.add(Obj.make(schema, values));
|
|
98
94
|
},
|
|
99
95
|
[space],
|
|
100
96
|
);
|
|
101
97
|
|
|
102
98
|
return (
|
|
103
|
-
<div className='
|
|
99
|
+
<div className='dx-expander grid grid-cols-2 divide-x divide-separator'>
|
|
104
100
|
<TableComponent.Root>
|
|
105
|
-
<TableComponent.
|
|
101
|
+
<TableComponent.Content
|
|
106
102
|
model={orgModel}
|
|
107
103
|
schema={Organization.Organization}
|
|
108
104
|
presentation={orgPresentation}
|
|
109
105
|
onCreate={handleCreate}
|
|
110
|
-
client={client}
|
|
111
106
|
ignoreAttention
|
|
112
107
|
testId='relations-0'
|
|
113
108
|
/>
|
|
114
109
|
</TableComponent.Root>
|
|
115
110
|
<TableComponent.Root>
|
|
116
|
-
<TableComponent.
|
|
111
|
+
<TableComponent.Content
|
|
117
112
|
model={contactModel}
|
|
118
113
|
schema={Person.Person}
|
|
119
114
|
presentation={contactPresentation}
|
|
120
115
|
onCreate={handleCreate}
|
|
121
|
-
client={client}
|
|
122
116
|
ignoreAttention
|
|
123
117
|
testId='relations-1'
|
|
124
118
|
/>
|
|
@@ -131,8 +125,9 @@ const meta = {
|
|
|
131
125
|
title: 'ui/react-ui-table/Relations',
|
|
132
126
|
render: DefaultStory,
|
|
133
127
|
decorators: [
|
|
134
|
-
withTheme,
|
|
135
|
-
|
|
128
|
+
withTheme(),
|
|
129
|
+
withRegistry,
|
|
130
|
+
// TODO(thure): Shouldn't `layout: 'fullscreen'` below make this unnecessary?
|
|
136
131
|
withLayout({ classNames: 'fixed inset-0' }),
|
|
137
132
|
withClientProvider({
|
|
138
133
|
types: [View.View, Organization.Organization, Person.Person, Table.Table],
|
|
@@ -152,7 +147,7 @@ export default meta;
|
|
|
152
147
|
type Story = StoryObj<typeof meta>;
|
|
153
148
|
|
|
154
149
|
export const Default: Story = {
|
|
155
|
-
play: async ({ canvasElement }
|
|
150
|
+
play: async ({ canvasElement }) => {
|
|
156
151
|
const canvas = within(canvasElement);
|
|
157
152
|
const body = within(document.body);
|
|
158
153
|
|
|
@@ -200,8 +195,15 @@ export const Default: Story = {
|
|
|
200
195
|
const saveButton = await body.findByTestId('save-button');
|
|
201
196
|
await userEvent.click(saveButton);
|
|
202
197
|
|
|
203
|
-
// Verify the relation was set (cell should now contain the org name)
|
|
204
|
-
|
|
198
|
+
// Verify the relation was set (cell should now contain the org name).
|
|
199
|
+
// Wait for the grid (Lit web component) to repaint after the reactive data update.
|
|
200
|
+
await waitFor(
|
|
201
|
+
async () => {
|
|
202
|
+
const updatedCell = within(secondGrid).getByTestId('grid.4.0');
|
|
203
|
+
await expect(updatedCell).toHaveTextContent(orgName.substring(0, 4));
|
|
204
|
+
},
|
|
205
|
+
{ timeout: 5000 },
|
|
206
|
+
);
|
|
205
207
|
|
|
206
208
|
// Test object creation (new relations) - equivalent to "new relations work as expected" test
|
|
207
209
|
// Find a different cell to test object creation (second row, relations column)
|
|
@@ -220,23 +222,24 @@ export const Default: Story = {
|
|
|
220
222
|
const newSearchField = await body.findByPlaceholderText('Search…');
|
|
221
223
|
await userEvent.click(newSearchField);
|
|
222
224
|
|
|
223
|
-
// Type a new object name (this will create a new object)
|
|
225
|
+
// Type a new object name (this will create a new object).
|
|
224
226
|
const newOrgName = 'Salieri LLC';
|
|
225
227
|
await userEvent.type(newSearchField, newOrgName);
|
|
226
228
|
|
|
227
|
-
//
|
|
228
|
-
const
|
|
229
|
-
await
|
|
229
|
+
// Click the create option directly to open the create form.
|
|
230
|
+
const createOptionLabel = await body.findByText(/Create new object/, undefined, { timeout: 2000 });
|
|
231
|
+
await userEvent.click(createOptionLabel.closest('[role="option"]') as HTMLElement);
|
|
230
232
|
|
|
231
|
-
//
|
|
232
|
-
await
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const createReferencedObjectForm = await body.findByTestId('create-referenced-object-form');
|
|
233
|
+
// Look for and click save button.
|
|
234
|
+
const createReferencedObjectForm = await body.findByTestId('create-referenced-object-form', undefined, {
|
|
235
|
+
timeout: 2000,
|
|
236
|
+
});
|
|
236
237
|
const saveObjectButton = await within(createReferencedObjectForm).findByTestId('save-button');
|
|
238
|
+
await expect(saveObjectButton).not.toBeDisabled();
|
|
237
239
|
await userEvent.click(saveObjectButton);
|
|
238
240
|
|
|
239
|
-
// Verify the
|
|
240
|
-
await
|
|
241
|
+
// Verify the relation was set by checking for the accessory link anchor in the cell.
|
|
242
|
+
const updatedNewCell = await within(secondGrid).findByTestId('grid.4.1', undefined, { timeout: 5000 });
|
|
243
|
+
await expect(updatedNewCell.querySelector('dx-anchor')).toBeTruthy();
|
|
241
244
|
},
|
|
242
|
-
}
|
|
245
|
+
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { useAtomValue } from '@effect-atom/atom-react';
|
|
5
6
|
import React from 'react';
|
|
6
7
|
|
|
7
8
|
import { DropdownMenu, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
@@ -13,8 +14,8 @@ type RowActionsMenuProps = { model: TableModel; modals: ModalController };
|
|
|
13
14
|
|
|
14
15
|
export const RowActionsMenu = ({ model, modals }: RowActionsMenuProps) => {
|
|
15
16
|
const { t } = useTranslation(translationKey);
|
|
16
|
-
const hasSelection = model.selection.hasSelection
|
|
17
|
-
const state = modals.state
|
|
17
|
+
const hasSelection = model.selection.hasSelection;
|
|
18
|
+
const state = useAtomValue(modals.state);
|
|
18
19
|
if (state?.type !== 'row') {
|
|
19
20
|
return null;
|
|
20
21
|
}
|
|
@@ -6,41 +6,33 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
|
6
6
|
import * as Schema from 'effect/Schema';
|
|
7
7
|
import React, { useCallback } from 'react';
|
|
8
8
|
|
|
9
|
-
import { Obj, type QueryAST, Type } from '@dxos/echo';
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
FormatAnnotation,
|
|
13
|
-
FormatEnum,
|
|
14
|
-
GeneratorAnnotation,
|
|
15
|
-
LabelAnnotation,
|
|
16
|
-
PropertyMetaAnnotationId,
|
|
17
|
-
} from '@dxos/echo/internal';
|
|
18
|
-
import { live } from '@dxos/echo/internal';
|
|
9
|
+
import { Annotation, type Database, Format, Obj, type QueryAST, Ref, Type } from '@dxos/echo';
|
|
10
|
+
import { View } from '@dxos/echo';
|
|
11
|
+
import { type Mutable, PropertyMetaAnnotationId } from '@dxos/echo/internal';
|
|
19
12
|
import { invariant } from '@dxos/invariant';
|
|
20
|
-
import {
|
|
13
|
+
import { random } from '@dxos/random';
|
|
21
14
|
import { PublicKey } from '@dxos/react-client';
|
|
22
|
-
import { type Space } from '@dxos/react-client/echo';
|
|
23
15
|
import { withClientProvider } from '@dxos/react-client/testing';
|
|
24
|
-
import {
|
|
25
|
-
import { ViewEditor } from '@dxos/react-ui-form';
|
|
16
|
+
import { Panel, ScrollArea } from '@dxos/react-ui';
|
|
17
|
+
import { ViewEditor, translations as formTranslations } from '@dxos/react-ui-form';
|
|
26
18
|
import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
19
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
20
|
+
import { ViewModel, getSchemaFromPropertyDefinitions, getTypenameFromQuery } from '@dxos/schema';
|
|
21
|
+
import { TestSchema, createObjectFactory } from '@dxos/schema/testing';
|
|
22
|
+
import { withRegistry } from '@dxos/storybook-utils';
|
|
29
23
|
|
|
30
24
|
import { useTestTableModel } from '../../testing';
|
|
31
25
|
import { translations } from '../../translations';
|
|
32
26
|
import { Table } from '../../types';
|
|
33
|
-
import { TableToolbar } from '../TableToolbar';
|
|
34
|
-
|
|
35
27
|
import { Table as TableComponent } from './Table';
|
|
36
28
|
|
|
37
|
-
const
|
|
29
|
+
const Example = Schema.Struct({
|
|
38
30
|
// TODO(wittjosiah): Should be title. Currently name to work with default label.
|
|
39
31
|
name: Schema.optional(Schema.String).annotations({ title: 'Title' }),
|
|
40
32
|
urgent: Schema.optional(Schema.Boolean).annotations({ title: 'Urgent' }),
|
|
41
33
|
status: Schema.optional(
|
|
42
34
|
Schema.Literal('todo', 'in-progress', 'done')
|
|
43
|
-
.pipe(FormatAnnotation.set(
|
|
35
|
+
.pipe(Format.FormatAnnotation.set(Format.TypeFormat.SingleSelect))
|
|
44
36
|
.annotations({
|
|
45
37
|
title: 'Status',
|
|
46
38
|
[PropertyMetaAnnotationId]: {
|
|
@@ -55,24 +47,24 @@ const TestSchema = Schema.Struct({
|
|
|
55
47
|
}),
|
|
56
48
|
),
|
|
57
49
|
description: Schema.optional(Schema.String).annotations({ title: 'Description' }),
|
|
58
|
-
parent: Schema.optional(Schema.suspend(():
|
|
50
|
+
parent: Schema.optional(Schema.suspend((): Ref.RefSchema<Example> => Ref.Ref(Example))).annotations({
|
|
59
51
|
title: 'Parent',
|
|
60
52
|
}),
|
|
61
53
|
}).pipe(
|
|
62
|
-
Type.
|
|
63
|
-
LabelAnnotation.set(['name']),
|
|
54
|
+
Type.object({ typename: `com.example.type.${PublicKey.random().truncate()}`, version: '0.1.0' }),
|
|
55
|
+
Annotation.LabelAnnotation.set(['name']),
|
|
64
56
|
);
|
|
65
|
-
interface
|
|
57
|
+
interface Example extends Schema.Schema.Type<typeof Example> {}
|
|
66
58
|
|
|
67
59
|
const StoryViewEditor = ({
|
|
68
60
|
view,
|
|
69
61
|
schema,
|
|
70
|
-
|
|
62
|
+
db,
|
|
71
63
|
handleDeleteColumn,
|
|
72
64
|
}: {
|
|
73
65
|
view?: View.View;
|
|
74
66
|
schema?: Schema.Schema.AnyNoContext;
|
|
75
|
-
|
|
67
|
+
db?: Database.Database;
|
|
76
68
|
handleDeleteColumn: (fieldId: string) => void;
|
|
77
69
|
}) => {
|
|
78
70
|
const handleQueryChanged = useCallback(
|
|
@@ -81,7 +73,9 @@ const StoryViewEditor = ({
|
|
|
81
73
|
invariant(Type.isMutable(schema));
|
|
82
74
|
schema.updateTypename(getTypenameFromQuery(newQuery));
|
|
83
75
|
invariant(view);
|
|
84
|
-
view
|
|
76
|
+
Obj.change(view, (obj) => {
|
|
77
|
+
obj.query.ast = newQuery as Mutable<typeof newQuery>;
|
|
78
|
+
});
|
|
85
79
|
},
|
|
86
80
|
[schema, view],
|
|
87
81
|
);
|
|
@@ -92,7 +86,7 @@ const StoryViewEditor = ({
|
|
|
92
86
|
|
|
93
87
|
return (
|
|
94
88
|
<ViewEditor
|
|
95
|
-
registry={
|
|
89
|
+
registry={db?.schemaRegistry}
|
|
96
90
|
schema={schema}
|
|
97
91
|
view={view}
|
|
98
92
|
onQueryChanged={handleQueryChanged}
|
|
@@ -106,81 +100,94 @@ const StoryViewEditor = ({
|
|
|
106
100
|
//
|
|
107
101
|
|
|
108
102
|
const DefaultStory = () => {
|
|
109
|
-
const {
|
|
110
|
-
|
|
111
|
-
schema,
|
|
112
|
-
view,
|
|
113
|
-
tableRef,
|
|
114
|
-
model,
|
|
115
|
-
presentation,
|
|
116
|
-
client,
|
|
117
|
-
handleInsertRow,
|
|
118
|
-
handleSaveView,
|
|
119
|
-
handleDeleteColumn,
|
|
120
|
-
} = useTestTableModel();
|
|
103
|
+
const { db, schema, table, tableRef, model, presentation, handleInsertRow, handleSaveView, handleDeleteColumn } =
|
|
104
|
+
useTestTableModel();
|
|
121
105
|
|
|
122
|
-
|
|
106
|
+
const handleRowClick = useCallback(
|
|
107
|
+
(row: any) => {
|
|
108
|
+
if (model?.getDraftRowCount() === 0 && ['frozenRowsEnd', 'fixedEndStart', 'fixedEndEnd'].includes(row?.plane)) {
|
|
109
|
+
handleInsertRow();
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
[model, handleInsertRow],
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (!schema || !table?.view.target) {
|
|
123
116
|
return <div />;
|
|
124
117
|
}
|
|
125
118
|
|
|
126
119
|
return (
|
|
127
120
|
<div className='grow grid grid-cols-[1fr_350px]'>
|
|
128
|
-
<
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
121
|
+
<TableComponent.Root ref={tableRef}>
|
|
122
|
+
<Panel.Root>
|
|
123
|
+
<Panel.Toolbar asChild>
|
|
124
|
+
<TableComponent.Toolbar
|
|
125
|
+
classNames='border-b border-subdued-separator'
|
|
126
|
+
onAdd={handleInsertRow}
|
|
127
|
+
onSave={handleSaveView}
|
|
128
|
+
/>
|
|
129
|
+
</Panel.Toolbar>
|
|
130
|
+
<Panel.Content asChild>
|
|
131
|
+
<TableComponent.Content
|
|
132
|
+
schema={schema}
|
|
133
|
+
model={model}
|
|
134
|
+
presentation={presentation}
|
|
135
|
+
onRowClick={handleRowClick}
|
|
136
|
+
ignoreAttention
|
|
137
|
+
/>
|
|
138
|
+
</Panel.Content>
|
|
139
|
+
</Panel.Root>
|
|
140
|
+
</TableComponent.Root>
|
|
141
|
+
<ScrollArea.Root orientation='vertical' classNames='border-l border-separator'>
|
|
142
|
+
<ScrollArea.Viewport>
|
|
143
|
+
<StoryViewEditor view={table.view.target} schema={schema} db={db} handleDeleteColumn={handleDeleteColumn} />
|
|
144
|
+
<SyntaxHighlighter language='json' className='text-xs'>
|
|
145
|
+
{JSON.stringify({ view: table.view.target, schema }, null, 2)}
|
|
146
|
+
</SyntaxHighlighter>
|
|
147
|
+
</ScrollArea.Viewport>
|
|
148
|
+
</ScrollArea.Root>
|
|
147
149
|
</div>
|
|
148
150
|
);
|
|
149
151
|
};
|
|
150
152
|
|
|
151
|
-
type
|
|
153
|
+
type DefaultStoryProps = { rows?: number };
|
|
152
154
|
|
|
153
155
|
//
|
|
154
156
|
// Story definitions.
|
|
155
157
|
//
|
|
156
158
|
|
|
157
|
-
// TODO(burdon): Need
|
|
159
|
+
// TODO(burdon): Need simpler story.
|
|
158
160
|
|
|
159
161
|
const meta = {
|
|
160
162
|
title: 'ui/react-ui-table/Table',
|
|
161
163
|
render: DefaultStory,
|
|
162
164
|
decorators: [
|
|
163
|
-
withTheme,
|
|
165
|
+
withTheme(),
|
|
166
|
+
withLayout({ layout: 'fullscreen' }),
|
|
167
|
+
withRegistry,
|
|
164
168
|
withClientProvider({
|
|
165
169
|
types: [View.View, Table.Table],
|
|
166
170
|
createIdentity: true,
|
|
167
171
|
createSpace: true,
|
|
168
|
-
onCreateSpace: async ({
|
|
169
|
-
const [schema] = await space.db.schemaRegistry.register([
|
|
170
|
-
const { view } = await
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
172
|
+
onCreateSpace: async ({ space }) => {
|
|
173
|
+
const [schema] = await space.db.schemaRegistry.register([Example]);
|
|
174
|
+
const { view, jsonSchema } = await ViewModel.makeFromDatabase({ db: space.db, typename: schema.typename });
|
|
175
|
+
const table = Table.make({ view, jsonSchema });
|
|
176
|
+
Obj.change(view, (obj) => {
|
|
177
|
+
obj.projection.fields = [
|
|
178
|
+
obj.projection.fields.find((field) => field.path === 'name')!,
|
|
179
|
+
...obj.projection.fields.filter((field) => field.path !== 'name'),
|
|
180
|
+
];
|
|
181
|
+
});
|
|
175
182
|
|
|
176
|
-
space.db.add(
|
|
183
|
+
space.db.add(table);
|
|
177
184
|
|
|
178
185
|
Array.from({ length: 10 }).map(() => {
|
|
179
186
|
return space.db.add(
|
|
180
187
|
Obj.make(schema, {
|
|
181
|
-
name:
|
|
182
|
-
status:
|
|
183
|
-
description:
|
|
188
|
+
name: random.lorem.sentence(),
|
|
189
|
+
status: random.helpers.arrayElement(['todo', 'in-progress', 'done'] as const),
|
|
190
|
+
description: random.lorem.paragraph(),
|
|
184
191
|
}),
|
|
185
192
|
);
|
|
186
193
|
});
|
|
@@ -192,7 +199,7 @@ const meta = {
|
|
|
192
199
|
controls: {
|
|
193
200
|
disable: true,
|
|
194
201
|
},
|
|
195
|
-
translations,
|
|
202
|
+
translations: [...translations, ...formTranslations],
|
|
196
203
|
},
|
|
197
204
|
} satisfies Meta<typeof TableComponent>;
|
|
198
205
|
|
|
@@ -209,26 +216,30 @@ export const StaticSchema: StoryObj = {
|
|
|
209
216
|
types: [View.View, Table.Table],
|
|
210
217
|
createIdentity: true,
|
|
211
218
|
createSpace: true,
|
|
212
|
-
onCreateSpace: async ({
|
|
213
|
-
const { view } = await
|
|
214
|
-
|
|
219
|
+
onCreateSpace: async ({ space }) => {
|
|
220
|
+
const { view, jsonSchema } = await ViewModel.makeFromDatabase({
|
|
221
|
+
db: space.db,
|
|
222
|
+
typename: TestSchema.Person.typename,
|
|
223
|
+
});
|
|
224
|
+
const table = Table.make({ view, jsonSchema });
|
|
225
|
+
space.db.add(table);
|
|
215
226
|
|
|
216
|
-
const factory = createObjectFactory(space.db,
|
|
227
|
+
const factory = createObjectFactory(space.db, random as any);
|
|
217
228
|
await factory([
|
|
218
|
-
{ type:
|
|
219
|
-
// { type:
|
|
229
|
+
{ type: TestSchema.Person, count: 10 },
|
|
230
|
+
// { type: TestSchema.Organization, count: 1 },
|
|
220
231
|
]);
|
|
221
232
|
},
|
|
222
233
|
}),
|
|
223
234
|
],
|
|
224
235
|
parameters: {
|
|
225
236
|
layout: 'fullscreen',
|
|
226
|
-
translations,
|
|
237
|
+
translations: [...translations, ...formTranslations],
|
|
227
238
|
},
|
|
228
239
|
};
|
|
229
240
|
|
|
230
241
|
const ContactWithArrayOfEmails = Schema.Struct({
|
|
231
|
-
name: Schema.String.pipe(GeneratorAnnotation.set('person.fullName')),
|
|
242
|
+
name: Schema.String.pipe(Annotation.GeneratorAnnotation.set('person.fullName')),
|
|
232
243
|
emails: Schema.optional(
|
|
233
244
|
Schema.Array(
|
|
234
245
|
Schema.Struct({
|
|
@@ -238,8 +249,8 @@ const ContactWithArrayOfEmails = Schema.Struct({
|
|
|
238
249
|
),
|
|
239
250
|
),
|
|
240
251
|
}).pipe(
|
|
241
|
-
|
|
242
|
-
typename: 'dxos.
|
|
252
|
+
Type.object({
|
|
253
|
+
typename: 'org.dxos.type.contactWithArrayOfEmails',
|
|
243
254
|
version: '0.1.0',
|
|
244
255
|
}),
|
|
245
256
|
);
|
|
@@ -248,17 +259,21 @@ export const ArrayOfObjects: StoryObj = {
|
|
|
248
259
|
render: DefaultStory,
|
|
249
260
|
decorators: [
|
|
250
261
|
withClientProvider({
|
|
251
|
-
types: [View.View, Table.Table,
|
|
262
|
+
types: [View.View, Table.Table, TestSchema.Person, TestSchema.Organization, ContactWithArrayOfEmails],
|
|
252
263
|
createIdentity: true,
|
|
253
264
|
createSpace: true,
|
|
254
|
-
onCreateSpace: async ({
|
|
255
|
-
const { view } = await
|
|
256
|
-
|
|
265
|
+
onCreateSpace: async ({ space }) => {
|
|
266
|
+
const { view, jsonSchema } = await ViewModel.makeFromDatabase({
|
|
267
|
+
db: space.db,
|
|
268
|
+
typename: ContactWithArrayOfEmails.typename,
|
|
269
|
+
});
|
|
270
|
+
const table = Table.make({ view, jsonSchema });
|
|
271
|
+
space.db.add(table);
|
|
257
272
|
|
|
258
|
-
const factory = createObjectFactory(space.db,
|
|
273
|
+
const factory = createObjectFactory(space.db, random as any);
|
|
259
274
|
await factory([
|
|
260
|
-
// { type:
|
|
261
|
-
// { type:
|
|
275
|
+
// { type: TestSchema.Person, count: 10 },
|
|
276
|
+
// { type: TestSchema.Organization, count: 1 },
|
|
262
277
|
{ type: ContactWithArrayOfEmails, count: 10 },
|
|
263
278
|
]);
|
|
264
279
|
},
|
|
@@ -270,7 +285,7 @@ export const ArrayOfObjects: StoryObj = {
|
|
|
270
285
|
},
|
|
271
286
|
};
|
|
272
287
|
|
|
273
|
-
export const Tags: Meta<
|
|
288
|
+
export const Tags: Meta<DefaultStoryProps> = {
|
|
274
289
|
title: 'ui/react-ui-table/Table',
|
|
275
290
|
render: DefaultStory,
|
|
276
291
|
decorators: [
|
|
@@ -278,9 +293,9 @@ export const Tags: Meta<StoryProps> = {
|
|
|
278
293
|
types: [View.View, Table.Table],
|
|
279
294
|
createIdentity: true,
|
|
280
295
|
createSpace: true,
|
|
281
|
-
onCreateSpace: async ({
|
|
296
|
+
onCreateSpace: async ({ space }) => {
|
|
282
297
|
// Configure schema.
|
|
283
|
-
const typename = 'example.
|
|
298
|
+
const typename = 'com.example.type.single-select';
|
|
284
299
|
const selectOptions = [
|
|
285
300
|
{ id: 'one', title: 'One', color: 'emerald' },
|
|
286
301
|
{ id: 'two', title: 'Two', color: 'blue' },
|
|
@@ -294,27 +309,28 @@ export const Tags: Meta<StoryProps> = {
|
|
|
294
309
|
const schema = getSchemaFromPropertyDefinitions(typename, [
|
|
295
310
|
{
|
|
296
311
|
name: 'single',
|
|
297
|
-
format:
|
|
312
|
+
format: Format.TypeFormat.SingleSelect,
|
|
298
313
|
config: { options: selectOptions },
|
|
299
314
|
},
|
|
300
315
|
{
|
|
301
316
|
name: 'multiple',
|
|
302
|
-
format:
|
|
317
|
+
format: Format.TypeFormat.MultiSelect,
|
|
303
318
|
config: { options: selectOptions },
|
|
304
319
|
},
|
|
305
320
|
]);
|
|
306
321
|
const [storedSchema] = await space.db.schemaRegistry.register([schema]);
|
|
307
322
|
|
|
308
323
|
// Initialize table.
|
|
309
|
-
const { view } = await
|
|
310
|
-
|
|
324
|
+
const { view, jsonSchema } = await ViewModel.makeFromDatabase({ db: space.db, typename });
|
|
325
|
+
const table = Table.make({ view, jsonSchema });
|
|
326
|
+
space.db.add(table);
|
|
311
327
|
|
|
312
328
|
// Populate.
|
|
313
329
|
Array.from({ length: 10 }).map(() => {
|
|
314
330
|
return space.db.add(
|
|
315
|
-
|
|
316
|
-
single:
|
|
317
|
-
multiple:
|
|
331
|
+
Obj.make(storedSchema, {
|
|
332
|
+
single: random.helpers.arrayElement([...selectOptionIds, undefined]),
|
|
333
|
+
multiple: random.helpers.randomSubset(selectOptionIds),
|
|
318
334
|
}),
|
|
319
335
|
);
|
|
320
336
|
});
|