@dxos/react-ui-table 0.8.2-staging.7ac8446 → 0.8.2
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-U6LL2AVU.mjs +26 -0
- package/dist/lib/browser/chunk-U6LL2AVU.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +1115 -905
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/types/index.mjs +1 -1
- package/dist/lib/node/{chunk-TGG6OZ54.cjs → chunk-CXDGXPM6.cjs} +12 -12
- package/dist/lib/node/chunk-CXDGXPM6.cjs.map +7 -0
- package/dist/lib/node/index.cjs +1113 -906
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/types/index.cjs +3 -3
- package/dist/lib/node/types/index.cjs.map +1 -1
- package/dist/lib/node-esm/{chunk-KKLP54L3.mjs → chunk-EFSM52KQ.mjs} +10 -10
- package/dist/lib/node-esm/chunk-EFSM52KQ.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1115 -905
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/types/index.mjs +1 -1
- package/dist/types/src/components/Table/ColumnActionsMenu.d.ts.map +1 -1
- package/dist/types/src/components/Table/ColumnSettings.d.ts.map +1 -1
- package/dist/types/src/components/Table/CreateRefPanel.d.ts.map +1 -1
- package/dist/types/src/components/Table/DynamicTable.d.ts +3 -4
- package/dist/types/src/components/Table/DynamicTable.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 +2 -0
- package/dist/types/src/components/Table/Table.d.ts.map +1 -1
- package/dist/types/src/components/Table/Table.stories.d.ts +1 -0
- package/dist/types/src/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/types/src/components/Table/relations.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts +13 -0
- package/dist/types/src/components/TableCellEditor/FormCellEditor.d.ts.map +1 -0
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts +3 -0
- package/dist/types/src/components/TableCellEditor/TableCellEditor.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/TableCellEditor.stories.d.ts.map +1 -1
- package/dist/types/src/components/TableCellEditor/extension.d.ts.map +1 -1
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts +6 -3
- package/dist/types/src/components/TableToolbar/TableToolbar.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +40 -40
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/hooks/useTableModel.d.ts +2 -2
- package/dist/types/src/hooks/useTableModel.d.ts.map +1 -1
- package/dist/types/src/model/modal-controller.d.ts +3 -3
- package/dist/types/src/model/modal-controller.d.ts.map +1 -1
- package/dist/types/src/model/selection-model.d.ts.map +1 -1
- package/dist/types/src/model/table-model.d.ts.map +1 -1
- package/dist/types/src/model/table-presentation.d.ts.map +1 -1
- package/dist/types/src/types/table.d.ts +46 -45
- package/dist/types/src/types/table.d.ts.map +1 -1
- package/dist/types/src/util/dynamic-table.d.ts +4 -3
- package/dist/types/src/util/dynamic-table.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +1 -0
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/schema.d.ts +12 -0
- package/dist/types/src/util/schema.d.ts.map +1 -0
- package/dist/types/src/util/signals.d.ts.map +1 -1
- package/dist/types/src/util/table-buttons.d.ts +0 -14
- package/dist/types/src/util/table-buttons.d.ts.map +1 -1
- package/dist/types/src/util/table-controls.d.ts.map +1 -1
- package/dist/types/src/util/table.d.ts +2 -2
- package/dist/types/src/util/table.d.ts.map +1 -1
- package/dist/types/src/util/tag.d.ts +11 -0
- package/dist/types/src/util/tag.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +41 -34
- package/src/components/Table/CreateRefPanel.tsx +5 -5
- package/src/components/Table/DynamicTable.stories.tsx +4 -4
- package/src/components/Table/DynamicTable.tsx +4 -5
- package/src/components/Table/Table.stories.tsx +84 -50
- package/src/components/Table/Table.tsx +25 -28
- package/src/components/Table/relations.stories.tsx +38 -32
- package/src/components/TableCellEditor/FormCellEditor.tsx +94 -0
- package/src/components/TableCellEditor/TableCellEditor.stories.tsx +13 -11
- package/src/components/TableCellEditor/TableCellEditor.tsx +45 -4
- package/src/components/TableToolbar/TableToolbar.tsx +65 -35
- package/src/components/testing.ts +10 -9
- package/src/hooks/useTableModel.ts +6 -4
- package/src/model/modal-controller.ts +3 -3
- package/src/model/selection-model.ts +1 -1
- package/src/model/table-model.test.ts +7 -6
- package/src/model/table-model.ts +20 -3
- package/src/model/table-presentation.test.ts +7 -6
- package/src/model/table-presentation.ts +54 -10
- package/src/types/table.ts +16 -11
- package/src/util/dynamic-table.ts +9 -6
- package/src/util/index.ts +1 -0
- package/src/util/schema.ts +36 -0
- package/src/util/table-buttons.ts +1 -24
- package/src/util/table.ts +13 -16
- package/src/util/tag.ts +33 -0
- package/dist/lib/browser/chunk-YWNRUF4J.mjs +0 -26
- package/dist/lib/browser/chunk-YWNRUF4J.mjs.map +0 -7
- package/dist/lib/node/chunk-TGG6OZ54.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-KKLP54L3.mjs.map +0 -7
- package/dist/types/src/components/Table/RefPanel.d.ts +0 -9
- package/dist/types/src/components/Table/RefPanel.d.ts.map +0 -1
- package/src/components/Table/RefPanel.tsx +0 -52
|
@@ -5,14 +5,22 @@
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
7
|
import { type StoryObj, type Meta } from '@storybook/react';
|
|
8
|
+
import { Schema } from 'effect';
|
|
8
9
|
import React, { useCallback, useMemo, useRef } from 'react';
|
|
9
10
|
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
assertEchoSchema,
|
|
13
|
+
FormatEnum,
|
|
14
|
+
isMutable,
|
|
15
|
+
toJsonSchema,
|
|
16
|
+
EchoObject,
|
|
17
|
+
GeneratorAnnotation,
|
|
18
|
+
} from '@dxos/echo-schema';
|
|
11
19
|
import { invariant } from '@dxos/invariant';
|
|
12
20
|
import { useGlobalFilteredObjects } from '@dxos/plugin-search';
|
|
13
21
|
import { faker } from '@dxos/random';
|
|
14
22
|
import { useClient } from '@dxos/react-client';
|
|
15
|
-
import { Filter, useQuery, useSchema,
|
|
23
|
+
import { Filter, useQuery, useSchema, live } from '@dxos/react-client/echo';
|
|
16
24
|
import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
|
|
17
25
|
import { ViewEditor } from '@dxos/react-ui-form';
|
|
18
26
|
import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
|
|
@@ -28,8 +36,7 @@ import { TableType } from '../../types';
|
|
|
28
36
|
import { initializeTable } from '../../util';
|
|
29
37
|
import { TableToolbar } from '../TableToolbar';
|
|
30
38
|
|
|
31
|
-
// NOTE(ZaymonFC):
|
|
32
|
-
faker.seed(0);
|
|
39
|
+
faker.seed(0); // NOTE(ZaymonFC): Required for smoke tests.
|
|
33
40
|
|
|
34
41
|
/**
|
|
35
42
|
* Custom hook to create and manage a test table model for storybook demonstrations.
|
|
@@ -39,14 +46,14 @@ const useTestTableModel = () => {
|
|
|
39
46
|
const client = useClient();
|
|
40
47
|
const { space } = useClientProvider();
|
|
41
48
|
|
|
42
|
-
const filter = useMemo(() => Filter.
|
|
49
|
+
const filter = useMemo(() => Filter.type(TableType), []);
|
|
43
50
|
const tables = useQuery(space, filter);
|
|
44
51
|
const table = useMemo(() => tables.at(0), [tables]);
|
|
45
52
|
const schema = useSchema(client, space, table?.view?.target?.query.typename);
|
|
46
53
|
|
|
47
54
|
const projection = useMemo(() => {
|
|
48
55
|
if (schema && table?.view?.target) {
|
|
49
|
-
return new ViewProjection(schema
|
|
56
|
+
return new ViewProjection(toJsonSchema(schema), table.view.target);
|
|
50
57
|
}
|
|
51
58
|
}, [schema, table?.view?.target]);
|
|
52
59
|
|
|
@@ -54,12 +61,12 @@ const useTestTableModel = () => {
|
|
|
54
61
|
() => ({
|
|
55
62
|
selection: { enabled: true, mode: 'multiple' as const },
|
|
56
63
|
dataEditable: true,
|
|
57
|
-
schemaEditable:
|
|
64
|
+
schemaEditable: schema && isMutable(schema),
|
|
58
65
|
}),
|
|
59
66
|
[schema],
|
|
60
67
|
);
|
|
61
68
|
|
|
62
|
-
const objects = useQuery(space, schema ? Filter.
|
|
69
|
+
const objects = useQuery(space, schema ? Filter.type(schema) : Filter.nothing());
|
|
63
70
|
const filteredObjects = useGlobalFilteredObjects(objects);
|
|
64
71
|
|
|
65
72
|
const tableRef = useRef<TableController>(null);
|
|
@@ -73,7 +80,7 @@ const useTestTableModel = () => {
|
|
|
73
80
|
|
|
74
81
|
const handleInsertRow = useCallback(() => {
|
|
75
82
|
if (space && schema) {
|
|
76
|
-
space.db.add(
|
|
83
|
+
space.db.add(live(schema, {}));
|
|
77
84
|
}
|
|
78
85
|
}, [space, schema]);
|
|
79
86
|
|
|
@@ -107,32 +114,16 @@ const useTestTableModel = () => {
|
|
|
107
114
|
onRowOrderChange: handleRowOrderChange,
|
|
108
115
|
});
|
|
109
116
|
|
|
117
|
+
const handleSaveView = useCallback(() => {
|
|
118
|
+
model?.saveView();
|
|
119
|
+
}, [model]);
|
|
120
|
+
|
|
110
121
|
const presentation = useMemo(() => {
|
|
111
122
|
if (model) {
|
|
112
123
|
return new TablePresentation(model);
|
|
113
124
|
}
|
|
114
125
|
}, [model]);
|
|
115
126
|
|
|
116
|
-
const handleToolbarAction = useCallback(
|
|
117
|
-
(action: { type: string }) => {
|
|
118
|
-
switch (action.type) {
|
|
119
|
-
case 'on-thread-create': {
|
|
120
|
-
console.log('Thread creation triggered');
|
|
121
|
-
break;
|
|
122
|
-
}
|
|
123
|
-
case 'add-row': {
|
|
124
|
-
handleInsertRow();
|
|
125
|
-
break;
|
|
126
|
-
}
|
|
127
|
-
case 'save-view': {
|
|
128
|
-
model?.saveView();
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
},
|
|
133
|
-
[table, model],
|
|
134
|
-
);
|
|
135
|
-
|
|
136
127
|
return {
|
|
137
128
|
schema,
|
|
138
129
|
table,
|
|
@@ -140,7 +131,8 @@ const useTestTableModel = () => {
|
|
|
140
131
|
model,
|
|
141
132
|
presentation,
|
|
142
133
|
space,
|
|
143
|
-
|
|
134
|
+
handleInsertRow,
|
|
135
|
+
handleSaveView,
|
|
144
136
|
handleDeleteRows,
|
|
145
137
|
handleDeleteColumn,
|
|
146
138
|
};
|
|
@@ -151,11 +143,10 @@ const StoryViewEditor = () => {
|
|
|
151
143
|
|
|
152
144
|
const handleTypenameChanged = useCallback(
|
|
153
145
|
(typename: string) => {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
146
|
+
invariant(schema);
|
|
147
|
+
invariant(table?.view?.target);
|
|
148
|
+
assertEchoSchema(schema).updateTypename(typename);
|
|
149
|
+
table.view.target.query.typename = typename;
|
|
159
150
|
},
|
|
160
151
|
[schema, table?.view?.target],
|
|
161
152
|
);
|
|
@@ -180,7 +171,7 @@ const StoryViewEditor = () => {
|
|
|
180
171
|
//
|
|
181
172
|
|
|
182
173
|
const DefaultStory = () => {
|
|
183
|
-
const { schema, table, tableRef, model, presentation,
|
|
174
|
+
const { schema, table, tableRef, model, presentation, handleInsertRow, handleSaveView } = useTestTableModel();
|
|
184
175
|
|
|
185
176
|
if (!schema || !table) {
|
|
186
177
|
return <div />;
|
|
@@ -189,9 +180,9 @@ const DefaultStory = () => {
|
|
|
189
180
|
return (
|
|
190
181
|
<div className='grow grid grid-cols-[1fr_350px]'>
|
|
191
182
|
<div className='grid grid-rows-[min-content_1fr] min-bs-0 overflow-hidden'>
|
|
192
|
-
<TableToolbar classNames='border-be border-separator'
|
|
183
|
+
<TableToolbar classNames='border-be border-separator' onAdd={handleInsertRow} onSave={handleSaveView} />
|
|
193
184
|
<Table.Root>
|
|
194
|
-
<Table.Main ref={tableRef} model={model} presentation={presentation} ignoreAttention />
|
|
185
|
+
<Table.Main ref={tableRef} model={model} presentation={presentation} schema={schema} ignoreAttention />
|
|
195
186
|
</Table.Root>
|
|
196
187
|
</div>
|
|
197
188
|
<div className='flex flex-col h-full border-l border-separator overflow-y-auto'>
|
|
@@ -222,19 +213,19 @@ const meta: Meta<StoryProps> = {
|
|
|
222
213
|
createIdentity: true,
|
|
223
214
|
createSpace: true,
|
|
224
215
|
onSpaceCreated: async ({ client, space }) => {
|
|
225
|
-
const table = space.db.add(
|
|
216
|
+
const table = space.db.add(live(TableType, {}));
|
|
226
217
|
const schema = await initializeTable({ client, space, table, initialRow: false });
|
|
227
218
|
Array.from({ length: 10 }).map(() => {
|
|
228
219
|
return space.db.add(
|
|
229
|
-
|
|
220
|
+
live(schema, {
|
|
230
221
|
name: faker.person.fullName(),
|
|
231
222
|
}),
|
|
232
223
|
);
|
|
233
224
|
});
|
|
234
225
|
},
|
|
235
226
|
}),
|
|
227
|
+
withLayout({ fullscreen: true }),
|
|
236
228
|
withTheme,
|
|
237
|
-
withLayout({ fullscreen: true, tooltips: true }),
|
|
238
229
|
],
|
|
239
230
|
};
|
|
240
231
|
|
|
@@ -247,22 +238,65 @@ export const StaticSchema: StoryObj = {
|
|
|
247
238
|
parameters: { translations },
|
|
248
239
|
decorators: [
|
|
249
240
|
withClientProvider({
|
|
250
|
-
types: [TableType, ViewType, Testing.Contact, Testing.
|
|
241
|
+
types: [TableType, ViewType, Testing.Contact, Testing.Organization],
|
|
242
|
+
createIdentity: true,
|
|
243
|
+
createSpace: true,
|
|
244
|
+
onSpaceCreated: async ({ client, space }) => {
|
|
245
|
+
const table = space.db.add(live(TableType, {}));
|
|
246
|
+
await initializeTable({ client, space, table, typename: Testing.Organization.typename });
|
|
247
|
+
|
|
248
|
+
const factory = createObjectFactory(space.db, faker as any);
|
|
249
|
+
await factory([
|
|
250
|
+
// { type: Testing.Contact, count: 10 },
|
|
251
|
+
// { type: Testing.Organization, count: 1 },
|
|
252
|
+
{ type: ContactWithArrayOfEmails, count: 10 },
|
|
253
|
+
]);
|
|
254
|
+
},
|
|
255
|
+
}),
|
|
256
|
+
withLayout({ fullscreen: true }),
|
|
257
|
+
withTheme,
|
|
258
|
+
],
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const ContactWithArrayOfEmails = Schema.Struct({
|
|
262
|
+
name: Schema.String.pipe(GeneratorAnnotation.set('person.fullName')),
|
|
263
|
+
emails: Schema.optional(
|
|
264
|
+
Schema.Array(
|
|
265
|
+
Schema.Struct({
|
|
266
|
+
value: Schema.String,
|
|
267
|
+
label: Schema.String.pipe(Schema.optional),
|
|
268
|
+
}),
|
|
269
|
+
),
|
|
270
|
+
),
|
|
271
|
+
}).pipe(
|
|
272
|
+
EchoObject({
|
|
273
|
+
typename: 'dxos.org/type/ContactWithArrayOfEmails',
|
|
274
|
+
version: '0.1.0',
|
|
275
|
+
}),
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
export const ArrayOfObjects: StoryObj = {
|
|
279
|
+
render: DefaultStory,
|
|
280
|
+
parameters: { translations },
|
|
281
|
+
decorators: [
|
|
282
|
+
withClientProvider({
|
|
283
|
+
types: [TableType, ViewType, Testing.Contact, Testing.Organization, ContactWithArrayOfEmails],
|
|
251
284
|
createIdentity: true,
|
|
252
285
|
createSpace: true,
|
|
253
286
|
onSpaceCreated: async ({ client, space }) => {
|
|
254
|
-
const table = space.db.add(
|
|
255
|
-
await initializeTable({ client, space, table, typename:
|
|
287
|
+
const table = space.db.add(live(TableType, {}));
|
|
288
|
+
await initializeTable({ client, space, table, typename: ContactWithArrayOfEmails.typename });
|
|
256
289
|
|
|
257
290
|
const factory = createObjectFactory(space.db, faker as any);
|
|
258
291
|
await factory([
|
|
259
|
-
{ type: Testing.Contact, count: 10 },
|
|
260
|
-
{ type: Testing.
|
|
292
|
+
// { type: Testing.Contact, count: 10 },
|
|
293
|
+
// { type: Testing.Organization, count: 1 },
|
|
294
|
+
{ type: ContactWithArrayOfEmails, count: 10 },
|
|
261
295
|
]);
|
|
262
296
|
},
|
|
263
297
|
}),
|
|
298
|
+
withLayout({ fullscreen: true }),
|
|
264
299
|
withTheme,
|
|
265
|
-
withLayout({ fullscreen: true, tooltips: true }),
|
|
266
300
|
],
|
|
267
301
|
};
|
|
268
302
|
|
|
@@ -303,13 +337,13 @@ export const Tags: Meta<StoryProps> = {
|
|
|
303
337
|
const [storedSchema] = await space.db.schemaRegistry.register([schema]);
|
|
304
338
|
|
|
305
339
|
// Initialize table.
|
|
306
|
-
const table = space.db.add(
|
|
340
|
+
const table = space.db.add(live(TableType, {}));
|
|
307
341
|
await initializeTable({ client, space, table, initialRow: false, typename });
|
|
308
342
|
|
|
309
343
|
// Populate.
|
|
310
344
|
Array.from({ length: 10 }).map(() => {
|
|
311
345
|
return space.db.add(
|
|
312
|
-
|
|
346
|
+
live(storedSchema, {
|
|
313
347
|
single: faker.helpers.arrayElement([...selectOptionIds, undefined]),
|
|
314
348
|
multiple: faker.helpers.randomSubset(selectOptionIds),
|
|
315
349
|
}),
|
|
@@ -317,7 +351,7 @@ export const Tags: Meta<StoryProps> = {
|
|
|
317
351
|
});
|
|
318
352
|
},
|
|
319
353
|
}),
|
|
354
|
+
withLayout({ fullscreen: true }),
|
|
320
355
|
withTheme,
|
|
321
|
-
withLayout({ fullscreen: true, tooltips: true }),
|
|
322
356
|
],
|
|
323
357
|
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { type Schema } from 'effect/Schema';
|
|
5
6
|
import React, {
|
|
6
7
|
type MouseEvent,
|
|
7
8
|
type PropsWithChildren,
|
|
@@ -19,26 +20,28 @@ import { invariant } from '@dxos/invariant';
|
|
|
19
20
|
import { Filter } from '@dxos/react-client/echo';
|
|
20
21
|
import { useTranslation } from '@dxos/react-ui';
|
|
21
22
|
import { useAttention } from '@dxos/react-ui-attention';
|
|
22
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
closestCell,
|
|
25
|
+
type DxGridElement,
|
|
26
|
+
type DxGridPosition,
|
|
27
|
+
type GridContentProps,
|
|
28
|
+
Grid,
|
|
29
|
+
type DxGridPlane,
|
|
30
|
+
type DxGridPlaneRange,
|
|
31
|
+
gridSeparatorInlineEnd,
|
|
32
|
+
gridSeparatorBlockEnd,
|
|
33
|
+
} from '@dxos/react-ui-grid';
|
|
23
34
|
import { mx } from '@dxos/react-ui-theme';
|
|
24
35
|
import { isNotFalsy, safeParseInt } from '@dxos/util';
|
|
25
36
|
|
|
26
37
|
import { ColumnActionsMenu } from './ColumnActionsMenu';
|
|
27
38
|
import { ColumnSettings } from './ColumnSettings';
|
|
28
39
|
import { CreateRefPanel } from './CreateRefPanel';
|
|
29
|
-
import { RefPanel } from './RefPanel';
|
|
30
40
|
import { RowActionsMenu } from './RowActionsMenu';
|
|
31
41
|
import { ModalController, type TableModel, type TablePresentation } from '../../model';
|
|
32
42
|
import { translationKey } from '../../translations';
|
|
33
43
|
import { tableButtons, tableControls } from '../../util';
|
|
34
|
-
import { createOption,
|
|
35
|
-
|
|
36
|
-
// NOTE(Zan): These fragments add border to inline-end and block-end of the grid using pseudo-elements.
|
|
37
|
-
// These are offset by 1px to avoid double borders in planks.
|
|
38
|
-
const inlineEndLine =
|
|
39
|
-
'[&>.dx-grid]:relative [&>.dx-grid]:after:absolute [&>.dx-grid]:after:inset-block-0 [&>.dx-grid]:after:-inline-end-px [&>.dx-grid]:after:is-px [&>.dx-grid]:after:bg-separator';
|
|
40
|
-
const blockEndLine =
|
|
41
|
-
'[&>.dx-grid]:before:absolute [&>.dx-grid]:before:inset-inline-0 [&>.dx-grid]:before:-block-end-px [&>.dx-grid]:before:bs-px [&>.dx-grid]:before:bg-separator';
|
|
44
|
+
import { createOption, TableValueEditor, type TableCellEditorProps } from '../TableCellEditor';
|
|
42
45
|
|
|
43
46
|
//
|
|
44
47
|
// Table.Root
|
|
@@ -73,13 +76,14 @@ export type TableController = {
|
|
|
73
76
|
export type TableMainProps = {
|
|
74
77
|
model?: TableModel;
|
|
75
78
|
presentation?: TablePresentation;
|
|
79
|
+
schema?: Schema.AnyNoContext;
|
|
76
80
|
// TODO(burdon): Rename since attention isn't a useful concept here? Standardize across other components. Pass property into useAttention.
|
|
77
81
|
ignoreAttention?: boolean;
|
|
78
82
|
onRowClick?: (row: any) => void;
|
|
79
83
|
};
|
|
80
84
|
|
|
81
85
|
const TableMain = forwardRef<TableController, TableMainProps>(
|
|
82
|
-
({ model, presentation, ignoreAttention, onRowClick }, forwardedRef) => {
|
|
86
|
+
({ model, presentation, ignoreAttention, schema, onRowClick }, forwardedRef) => {
|
|
83
87
|
const [dxGrid, setDxGrid] = useState<DxGridElement | null>(null);
|
|
84
88
|
const { hasAttention } = useAttention(model?.id ?? 'table');
|
|
85
89
|
const { t } = useTranslation(translationKey);
|
|
@@ -98,20 +102,17 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
98
102
|
};
|
|
99
103
|
}, [model]);
|
|
100
104
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// );
|
|
105
|
+
const getCells = useCallback<NonNullable<GridContentProps['getCells']>>(
|
|
106
|
+
(range: DxGridPlaneRange, plane: DxGridPlane) => presentation?.getCells(range, plane) ?? {},
|
|
107
|
+
[presentation],
|
|
108
|
+
);
|
|
106
109
|
|
|
107
110
|
useEffect(() => {
|
|
108
111
|
if (!presentation || !dxGrid) {
|
|
109
112
|
return;
|
|
110
113
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
dxGrid.getCells = (range, plane) => presentation.getCells(range, plane);
|
|
114
|
-
}, [presentation, dxGrid]);
|
|
114
|
+
dxGrid.getCells = getCells;
|
|
115
|
+
}, [presentation, dxGrid, getCells]);
|
|
115
116
|
|
|
116
117
|
/**
|
|
117
118
|
* Provides an external controller that can be called to repaint the table.
|
|
@@ -181,10 +182,6 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
181
182
|
modals.showColumnCreator();
|
|
182
183
|
break;
|
|
183
184
|
}
|
|
184
|
-
case 'referencedCell': {
|
|
185
|
-
modals.showReferencePanel(data.targetId, data.schemaId);
|
|
186
|
-
break;
|
|
187
|
-
}
|
|
188
185
|
case 'sort': {
|
|
189
186
|
model?.sorting?.toggleSort(data.fieldId);
|
|
190
187
|
break;
|
|
@@ -312,7 +309,7 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
312
309
|
invariant(space);
|
|
313
310
|
const schema = space.db.schemaRegistry.getSchema(props.referenceSchema);
|
|
314
311
|
if (schema) {
|
|
315
|
-
const { objects } = await space.db.query(Filter.
|
|
312
|
+
const { objects } = await space.db.query(Filter.type(schema)).run();
|
|
316
313
|
const options = objects
|
|
317
314
|
.map((obj) => {
|
|
318
315
|
const value = getValue(obj, field.referencePath!);
|
|
@@ -348,15 +345,16 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
348
345
|
|
|
349
346
|
return (
|
|
350
347
|
<Grid.Root id={model.id ?? 'table-grid'}>
|
|
351
|
-
<
|
|
348
|
+
<TableValueEditor
|
|
352
349
|
model={model}
|
|
353
350
|
modals={modals}
|
|
351
|
+
schema={schema}
|
|
354
352
|
onEnter={handleEnter}
|
|
355
353
|
onFocus={handleFocus}
|
|
356
354
|
onQuery={handleQuery}
|
|
357
355
|
/>
|
|
358
356
|
<Grid.Content
|
|
359
|
-
className={mx('[--dx-grid-base:var(--
|
|
357
|
+
className={mx('[--dx-grid-base:var(--baseSurface)]', gridSeparatorInlineEnd, gridSeparatorBlockEnd)}
|
|
360
358
|
frozen={frozen}
|
|
361
359
|
// getCells={getCells}
|
|
362
360
|
columns={model.columnMeta.value}
|
|
@@ -373,7 +371,6 @@ const TableMain = forwardRef<TableController, TableMainProps>(
|
|
|
373
371
|
<ColumnActionsMenu model={model} modals={modals} />
|
|
374
372
|
<ColumnSettings model={model} modals={modals} onNewColumn={handleNewColumn} />
|
|
375
373
|
<CreateRefPanel model={model} modals={modals} />
|
|
376
|
-
<RefPanel model={model} modals={modals} />
|
|
377
374
|
</Grid.Root>
|
|
378
375
|
);
|
|
379
376
|
},
|
|
@@ -5,20 +5,23 @@
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
7
|
import { type StoryObj, type Meta } from '@storybook/react';
|
|
8
|
+
import { type Schema } from 'effect';
|
|
9
|
+
import { SchemaAST } from 'effect';
|
|
8
10
|
import React, { useEffect, useMemo } from 'react';
|
|
9
11
|
|
|
10
|
-
import {
|
|
12
|
+
import { type BaseObject, getSchemaTypename, type HasId, toJsonSchema } from '@dxos/echo-schema';
|
|
11
13
|
import { getAnnotation } from '@dxos/effect';
|
|
14
|
+
import { invariant } from '@dxos/invariant';
|
|
12
15
|
import { faker } from '@dxos/random';
|
|
13
|
-
import {
|
|
14
|
-
import { withClientProvider } from '@dxos/react-client/testing';
|
|
15
|
-
import { createView, ViewProjection, ViewType } from '@dxos/schema';
|
|
16
|
-
import {
|
|
16
|
+
import { live, makeRef } from '@dxos/react-client/echo';
|
|
17
|
+
import { useClientProvider, withClientProvider } from '@dxos/react-client/testing';
|
|
18
|
+
import { DataType, createView, ViewProjection, ViewType } from '@dxos/schema';
|
|
19
|
+
import { createAsyncGenerator, type ValueGenerator } from '@dxos/schema/testing';
|
|
17
20
|
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
18
21
|
|
|
19
22
|
import { Table } from './Table';
|
|
20
23
|
import { useTableModel } from '../../hooks';
|
|
21
|
-
import { TablePresentation } from '../../model';
|
|
24
|
+
import { type TableFeatures, TablePresentation, type TableRow } from '../../model';
|
|
22
25
|
import translations from '../../translations';
|
|
23
26
|
import { TableType } from '../../types';
|
|
24
27
|
|
|
@@ -29,13 +32,16 @@ const generator: ValueGenerator = faker as any;
|
|
|
29
32
|
// TODO(burdon): Mutable and immutable views.
|
|
30
33
|
// TODO(burdon): Reconcile schemas types and utils (see API PR).
|
|
31
34
|
// TODO(burdon): Base type for T (with id); see ECHO API PR?
|
|
32
|
-
const useTestModel = <T extends BaseObject & HasId>(schema:
|
|
35
|
+
const useTestModel = <T extends BaseObject & HasId>(schema: Schema.Schema<T>, count: number) => {
|
|
36
|
+
const { space } = useClientProvider();
|
|
37
|
+
|
|
38
|
+
const jsonSchema = useMemo(() => toJsonSchema(schema), [schema]);
|
|
33
39
|
const table = useMemo(() => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const name = getAnnotation<string>(
|
|
37
|
-
const view = createView({ name, typename, jsonSchema
|
|
38
|
-
return
|
|
40
|
+
const typename = getSchemaTypename(schema);
|
|
41
|
+
invariant(typename);
|
|
42
|
+
const name = getAnnotation<string>(SchemaAST.TitleAnnotationId)(schema.ast) ?? typename;
|
|
43
|
+
const view = createView({ name, typename, jsonSchema });
|
|
44
|
+
return live(TableType, { view: makeRef(view) });
|
|
39
45
|
}, [schema]);
|
|
40
46
|
|
|
41
47
|
const projection = useMemo(() => {
|
|
@@ -44,19 +50,25 @@ const useTestModel = <T extends BaseObject & HasId>(schema: BaseSchema<T>, count
|
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
// TODO(burdon): Just pass in view? Reuse same jsonSchema instance? View determines if mutable, etc.
|
|
47
|
-
return new ViewProjection(
|
|
53
|
+
return new ViewProjection(jsonSchema, table.view.target);
|
|
48
54
|
}, [schema, table]);
|
|
49
55
|
|
|
50
|
-
const
|
|
56
|
+
const features = useMemo<TableFeatures>(
|
|
57
|
+
() => ({ schemaEditable: false, dataEditable: true, selection: { enabled: false } }),
|
|
58
|
+
[],
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const model = useTableModel<TableRow>({ table, projection, rows: [], features });
|
|
51
62
|
useEffect(() => {
|
|
52
|
-
if (!model) {
|
|
63
|
+
if (!model || !space) {
|
|
53
64
|
return;
|
|
54
65
|
}
|
|
55
66
|
|
|
56
|
-
const objectGenerator =
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
67
|
+
const objectGenerator = createAsyncGenerator(generator, schema, { db: space?.db, force: true });
|
|
68
|
+
void objectGenerator.createObjects(count).then((objects) => {
|
|
69
|
+
model.setRows(objects);
|
|
70
|
+
});
|
|
71
|
+
}, [model, space]);
|
|
60
72
|
|
|
61
73
|
const presentation = useMemo(() => {
|
|
62
74
|
if (!model) {
|
|
@@ -70,22 +82,16 @@ const useTestModel = <T extends BaseObject & HasId>(schema: BaseSchema<T>, count
|
|
|
70
82
|
};
|
|
71
83
|
|
|
72
84
|
const DefaultStory = () => {
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
const { model: orgModel, presentation: orgPresentation } = useTestModel<Testing.Org>(orgSchema, 50);
|
|
76
|
-
|
|
77
|
-
// TODO(burdon): Generate links with references.
|
|
78
|
-
const contactSchema = useMemo(() => new ImmutableSchema(Testing.Contact), []);
|
|
79
|
-
const { model: contactModel, presentation: contactPresentation } = useTestModel<Testing.Contact>(contactSchema, 50);
|
|
85
|
+
const { model: orgModel, presentation: orgPresentation } = useTestModel(DataType.Organization, 50);
|
|
86
|
+
const { model: contactModel, presentation: contactPresentation } = useTestModel(DataType.Person, 50);
|
|
80
87
|
|
|
81
|
-
// TODO(burdon): Scrolling isn't working.
|
|
82
88
|
return (
|
|
83
|
-
<div className='
|
|
89
|
+
<div className='is-full bs-full grid grid-cols-2 divide-x divide-separator'>
|
|
84
90
|
<Table.Root>
|
|
85
|
-
<Table.Main model={orgModel} presentation={orgPresentation} />
|
|
91
|
+
<Table.Main model={orgModel} presentation={orgPresentation} ignoreAttention />
|
|
86
92
|
</Table.Root>
|
|
87
93
|
<Table.Root>
|
|
88
|
-
<Table.Main model={contactModel} presentation={contactPresentation} />
|
|
94
|
+
<Table.Main model={contactModel} presentation={contactPresentation} ignoreAttention />
|
|
89
95
|
</Table.Root>
|
|
90
96
|
</div>
|
|
91
97
|
);
|
|
@@ -97,12 +103,12 @@ const meta: Meta<typeof DefaultStory> = {
|
|
|
97
103
|
parameters: { translations },
|
|
98
104
|
decorators: [
|
|
99
105
|
withClientProvider({
|
|
100
|
-
types: [TableType, ViewType,
|
|
106
|
+
types: [TableType, ViewType, DataType.Organization, DataType.Person],
|
|
101
107
|
createIdentity: true,
|
|
102
108
|
createSpace: true,
|
|
103
109
|
}),
|
|
104
110
|
withTheme,
|
|
105
|
-
withLayout({ fullscreen: true
|
|
111
|
+
withLayout({ fullscreen: true }),
|
|
106
112
|
],
|
|
107
113
|
};
|
|
108
114
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Schema } from 'effect';
|
|
6
|
+
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
7
|
+
|
|
8
|
+
import { getSnapshot } from '@dxos/echo-schema';
|
|
9
|
+
import { invariant } from '@dxos/invariant';
|
|
10
|
+
import { Popover } from '@dxos/react-ui';
|
|
11
|
+
import { Form } from '@dxos/react-ui-form';
|
|
12
|
+
import { parseCellIndex, useGridContext } from '@dxos/react-ui-grid';
|
|
13
|
+
import { type FieldProjection } from '@dxos/schema';
|
|
14
|
+
import { getDeep, setDeep } from '@dxos/util';
|
|
15
|
+
|
|
16
|
+
import { type TableModel } from '../../model';
|
|
17
|
+
import { narrowSchema } from '../../util';
|
|
18
|
+
|
|
19
|
+
type FormCellEditorProps = {
|
|
20
|
+
fieldProjection: FieldProjection;
|
|
21
|
+
model?: TableModel;
|
|
22
|
+
schema?: Schema.Schema.AnyNoContext;
|
|
23
|
+
__gridScope: any;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const FormCellEditor = ({ fieldProjection, model, schema, __gridScope }: FormCellEditorProps) => {
|
|
27
|
+
const { editing, setEditing } = useGridContext('ArrayEditor', __gridScope);
|
|
28
|
+
const cellRef = useRef<HTMLButtonElement | null>(null);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (editing && editing.cellElement) {
|
|
32
|
+
cellRef.current = editing.cellElement as HTMLButtonElement;
|
|
33
|
+
}
|
|
34
|
+
}, [editing?.cellElement]);
|
|
35
|
+
|
|
36
|
+
const narrowedSchema = useMemo(() => {
|
|
37
|
+
if (!schema) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
return narrowSchema(schema, [fieldProjection.field.path]);
|
|
41
|
+
}, [JSON.stringify(schema), fieldProjection.field.path]); // TODO(burdon): Avoid stringify.
|
|
42
|
+
|
|
43
|
+
const originalRow = useMemo(() => {
|
|
44
|
+
if (model && editing) {
|
|
45
|
+
const cell = parseCellIndex(editing.index);
|
|
46
|
+
const row = model.getRowAt(cell.row);
|
|
47
|
+
invariant(row);
|
|
48
|
+
|
|
49
|
+
return row;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return undefined;
|
|
53
|
+
}, [model, editing]);
|
|
54
|
+
|
|
55
|
+
const formValues = useMemo(() => {
|
|
56
|
+
if (originalRow) {
|
|
57
|
+
// NOTE(ZaymonFC): Important to get a snapshot to eject from the live object.
|
|
58
|
+
return getSnapshot(originalRow);
|
|
59
|
+
}
|
|
60
|
+
}, [originalRow]);
|
|
61
|
+
|
|
62
|
+
const handleSave = useCallback(
|
|
63
|
+
(values: any) => {
|
|
64
|
+
const path = fieldProjection.field.path;
|
|
65
|
+
const value = getDeep(values, [path]);
|
|
66
|
+
setDeep(originalRow, [path], value);
|
|
67
|
+
setEditing(null);
|
|
68
|
+
},
|
|
69
|
+
[fieldProjection.field.path, originalRow],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const handleOpenChange = useCallback(
|
|
73
|
+
(nextOpen: boolean) => {
|
|
74
|
+
if (nextOpen === false) {
|
|
75
|
+
setEditing(null);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
[setEditing],
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (!editing) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<Popover.Root open={editing !== null} onOpenChange={handleOpenChange}>
|
|
87
|
+
<Popover.VirtualTrigger virtualRef={cellRef} />
|
|
88
|
+
<Popover.Content tabIndex={-1} classNames='popover-card-width'>
|
|
89
|
+
<Popover.Arrow />
|
|
90
|
+
<Form values={formValues} schema={narrowedSchema as any} onSave={handleSave} />
|
|
91
|
+
</Popover.Content>
|
|
92
|
+
</Popover.Root>
|
|
93
|
+
);
|
|
94
|
+
};
|