@dxos/plugin-space 0.6.8-main.046e6cf

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.
Files changed (109) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +15 -0
  3. package/dist/lib/browser/chunk-DTVUOG2C.mjs +95 -0
  4. package/dist/lib/browser/chunk-DTVUOG2C.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-LZEGRS7H.mjs +35 -0
  6. package/dist/lib/browser/chunk-LZEGRS7H.mjs.map +7 -0
  7. package/dist/lib/browser/index.mjs +2660 -0
  8. package/dist/lib/browser/index.mjs.map +7 -0
  9. package/dist/lib/browser/meta.json +1 -0
  10. package/dist/lib/browser/meta.mjs +13 -0
  11. package/dist/lib/browser/meta.mjs.map +7 -0
  12. package/dist/lib/browser/types/index.mjs +21 -0
  13. package/dist/lib/browser/types/index.mjs.map +7 -0
  14. package/dist/lib/node/chunk-6CNYF6YU.cjs +60 -0
  15. package/dist/lib/node/chunk-6CNYF6YU.cjs.map +7 -0
  16. package/dist/lib/node/chunk-CVZPI2P3.cjs +120 -0
  17. package/dist/lib/node/chunk-CVZPI2P3.cjs.map +7 -0
  18. package/dist/lib/node/index.cjs +2682 -0
  19. package/dist/lib/node/index.cjs.map +7 -0
  20. package/dist/lib/node/meta.cjs +34 -0
  21. package/dist/lib/node/meta.cjs.map +7 -0
  22. package/dist/lib/node/meta.json +1 -0
  23. package/dist/lib/node/types/index.cjs +43 -0
  24. package/dist/lib/node/types/index.cjs.map +7 -0
  25. package/dist/types/src/SpacePlugin.d.ts +27 -0
  26. package/dist/types/src/SpacePlugin.d.ts.map +1 -0
  27. package/dist/types/src/components/AwaitingObject.d.ts +5 -0
  28. package/dist/types/src/components/AwaitingObject.d.ts.map +1 -0
  29. package/dist/types/src/components/CollectionMain.d.ts +6 -0
  30. package/dist/types/src/components/CollectionMain.d.ts.map +1 -0
  31. package/dist/types/src/components/CollectionSection.d.ts +6 -0
  32. package/dist/types/src/components/CollectionSection.d.ts.map +1 -0
  33. package/dist/types/src/components/EmptySpace.d.ts +3 -0
  34. package/dist/types/src/components/EmptySpace.d.ts.map +1 -0
  35. package/dist/types/src/components/EmptyTree.d.ts +3 -0
  36. package/dist/types/src/components/EmptyTree.d.ts.map +1 -0
  37. package/dist/types/src/components/MenuFooter.d.ts +6 -0
  38. package/dist/types/src/components/MenuFooter.d.ts.map +1 -0
  39. package/dist/types/src/components/MissingObject.d.ts +5 -0
  40. package/dist/types/src/components/MissingObject.d.ts.map +1 -0
  41. package/dist/types/src/components/PersistenceStatus.d.ts +6 -0
  42. package/dist/types/src/components/PersistenceStatus.d.ts.map +1 -0
  43. package/dist/types/src/components/PopoverRenameObject.d.ts +6 -0
  44. package/dist/types/src/components/PopoverRenameObject.d.ts.map +1 -0
  45. package/dist/types/src/components/PopoverRenameSpace.d.ts +6 -0
  46. package/dist/types/src/components/PopoverRenameSpace.d.ts.map +1 -0
  47. package/dist/types/src/components/ShareSpaceButton.d.ts +8 -0
  48. package/dist/types/src/components/ShareSpaceButton.d.ts.map +1 -0
  49. package/dist/types/src/components/ShareSpaceButton.stories.d.ts +98 -0
  50. package/dist/types/src/components/ShareSpaceButton.stories.d.ts.map +1 -0
  51. package/dist/types/src/components/SpaceMain/SpaceMain.d.ts +10 -0
  52. package/dist/types/src/components/SpaceMain/SpaceMain.d.ts.map +1 -0
  53. package/dist/types/src/components/SpaceMain/SpaceMembersSection.d.ts +6 -0
  54. package/dist/types/src/components/SpaceMain/SpaceMembersSection.d.ts.map +1 -0
  55. package/dist/types/src/components/SpaceMain/index.d.ts +2 -0
  56. package/dist/types/src/components/SpaceMain/index.d.ts.map +1 -0
  57. package/dist/types/src/components/SpacePresence.d.ts +34 -0
  58. package/dist/types/src/components/SpacePresence.d.ts.map +1 -0
  59. package/dist/types/src/components/SpacePresence.stories.d.ts +97 -0
  60. package/dist/types/src/components/SpacePresence.stories.d.ts.map +1 -0
  61. package/dist/types/src/components/SpaceSettings.d.ts +6 -0
  62. package/dist/types/src/components/SpaceSettings.d.ts.map +1 -0
  63. package/dist/types/src/components/index.d.ts +15 -0
  64. package/dist/types/src/components/index.d.ts.map +1 -0
  65. package/dist/types/src/index.d.ts +9 -0
  66. package/dist/types/src/index.d.ts.map +1 -0
  67. package/dist/types/src/meta.d.ts +26 -0
  68. package/dist/types/src/meta.d.ts.map +1 -0
  69. package/dist/types/src/translations.d.ts +83 -0
  70. package/dist/types/src/translations.d.ts.map +1 -0
  71. package/dist/types/src/types/collection.d.ts +18 -0
  72. package/dist/types/src/types/collection.d.ts.map +1 -0
  73. package/dist/types/src/types/index.d.ts +4 -0
  74. package/dist/types/src/types/index.d.ts.map +1 -0
  75. package/dist/types/src/types/thread.d.ts +225 -0
  76. package/dist/types/src/types/thread.d.ts.map +1 -0
  77. package/dist/types/src/types/types.d.ts +54 -0
  78. package/dist/types/src/types/types.d.ts.map +1 -0
  79. package/dist/types/src/util.d.ts +85 -0
  80. package/dist/types/src/util.d.ts.map +1 -0
  81. package/package.json +101 -0
  82. package/src/SpacePlugin.tsx +1234 -0
  83. package/src/components/AwaitingObject.tsx +118 -0
  84. package/src/components/CollectionMain.tsx +33 -0
  85. package/src/components/CollectionSection.tsx +20 -0
  86. package/src/components/EmptySpace.tsx +25 -0
  87. package/src/components/EmptyTree.tsx +25 -0
  88. package/src/components/MenuFooter.tsx +33 -0
  89. package/src/components/MissingObject.tsx +54 -0
  90. package/src/components/PersistenceStatus.tsx +87 -0
  91. package/src/components/PopoverRenameObject.tsx +54 -0
  92. package/src/components/PopoverRenameSpace.tsx +44 -0
  93. package/src/components/ShareSpaceButton.stories.tsx +23 -0
  94. package/src/components/ShareSpaceButton.tsx +27 -0
  95. package/src/components/SpaceMain/SpaceMain.tsx +81 -0
  96. package/src/components/SpaceMain/SpaceMembersSection.tsx +205 -0
  97. package/src/components/SpaceMain/index.ts +5 -0
  98. package/src/components/SpacePresence.stories.tsx +102 -0
  99. package/src/components/SpacePresence.tsx +244 -0
  100. package/src/components/SpaceSettings.tsx +34 -0
  101. package/src/components/index.ts +18 -0
  102. package/src/index.ts +15 -0
  103. package/src/meta.ts +31 -0
  104. package/src/translations.ts +92 -0
  105. package/src/types/collection.ts +16 -0
  106. package/src/types/index.ts +7 -0
  107. package/src/types/thread.ts +68 -0
  108. package/src/types/types.ts +81 -0
  109. package/src/util.tsx +642 -0
package/src/util.tsx ADDED
@@ -0,0 +1,642 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import {
6
+ CardsThree,
7
+ Database,
8
+ PencilSimpleLine,
9
+ Planet,
10
+ Plus,
11
+ Trash,
12
+ Users,
13
+ X,
14
+ ClockCounterClockwise,
15
+ type IconProps,
16
+ LockSimpleOpen,
17
+ LockSimple,
18
+ Placeholder,
19
+ Link,
20
+ } from '@phosphor-icons/react';
21
+ import React from 'react';
22
+
23
+ import { type MetadataResolver, NavigationAction, type IntentDispatcher } from '@dxos/app-framework';
24
+ import {
25
+ type EchoReactiveObject,
26
+ create,
27
+ isReactiveObject,
28
+ getTypename,
29
+ type Expando,
30
+ getSchema,
31
+ getEchoObjectAnnotation,
32
+ EXPANDO_TYPENAME,
33
+ } from '@dxos/echo-schema';
34
+ import { invariant } from '@dxos/invariant';
35
+ import { Migrations } from '@dxos/migrations';
36
+ import {
37
+ ACTION_TYPE,
38
+ ACTION_GROUP_TYPE,
39
+ actionGroupSymbol,
40
+ type ActionData,
41
+ type Graph,
42
+ type Node,
43
+ type InvokeParams,
44
+ type NodeArg,
45
+ getGraph,
46
+ cleanup,
47
+ memoize,
48
+ } from '@dxos/plugin-graph';
49
+ import {
50
+ SpaceState,
51
+ fullyQualifiedId,
52
+ getSpace,
53
+ isEchoObject,
54
+ isSpace,
55
+ type Echo,
56
+ type FilterSource,
57
+ type Query,
58
+ type QueryOptions,
59
+ type Space,
60
+ } from '@dxos/react-client/echo';
61
+
62
+ import { SpaceAction, SPACE_PLUGIN } from './meta';
63
+ import { CollectionType } from './types';
64
+
65
+ export const SPACES = `${SPACE_PLUGIN}-spaces`;
66
+ export const SPACE_TYPE = 'dxos.org/type/Space';
67
+ export const COMPOSER_SPACE_LOCK = 'dxos.org/plugin/space/lock';
68
+ // TODO(wittjosiah): Remove.
69
+ export const SHARED = 'shared-spaces';
70
+
71
+ const EMPTY_ARRAY: never[] = [];
72
+
73
+ /**
74
+ *
75
+ * @param spaceOrEcho
76
+ * @param filter
77
+ * @param options
78
+ * @returns
79
+ */
80
+ export const memoizeQuery = <T extends EchoReactiveObject<any>>(
81
+ spaceOrEcho?: Space | Echo,
82
+ filter?: FilterSource<T>,
83
+ options?: QueryOptions,
84
+ ): T[] => {
85
+ const key = isSpace(spaceOrEcho) ? spaceOrEcho.id : undefined;
86
+ const query = memoize(
87
+ () =>
88
+ isSpace(spaceOrEcho)
89
+ ? spaceOrEcho.db.query(filter, options)
90
+ : (spaceOrEcho?.query(filter, options) as Query<T> | undefined),
91
+ key,
92
+ );
93
+ const unsubscribe = memoize(() => query?.subscribe(), key);
94
+ cleanup(() => unsubscribe?.());
95
+
96
+ return query?.objects ?? EMPTY_ARRAY;
97
+ };
98
+
99
+ export const getSpaceDisplayName = (
100
+ space: Space,
101
+ { personal, namesCache = {} }: { personal?: boolean; namesCache?: Record<string, string> } = {},
102
+ ): string | [string, { ns: string }] => {
103
+ return space.state.get() === SpaceState.SPACE_READY && (space.properties.name?.length ?? 0) > 0
104
+ ? space.properties.name
105
+ : namesCache[space.id]
106
+ ? namesCache[space.id]
107
+ : personal
108
+ ? ['personal space label', { ns: SPACE_PLUGIN }]
109
+ : ['unnamed space label', { ns: SPACE_PLUGIN }];
110
+ };
111
+
112
+ const getCollectionGraphNodePartials = ({
113
+ collection,
114
+ space,
115
+ resolve,
116
+ }: {
117
+ collection: CollectionType;
118
+ space: Space;
119
+ resolve: MetadataResolver;
120
+ }) => {
121
+ return {
122
+ acceptPersistenceClass: new Set(['echo']),
123
+ acceptPersistenceKey: new Set([space.id]),
124
+ role: 'branch',
125
+ onRearrangeChildren: (nextOrder: unknown[]) => {
126
+ // Change on disk.
127
+ collection.objects = nextOrder.filter(isEchoObject);
128
+ },
129
+ onTransferStart: (child: Node<EchoReactiveObject<any>>, index?: number) => {
130
+ // TODO(wittjosiah): Support transfer between spaces.
131
+ // const childSpace = getSpace(child.data);
132
+ // if (space && childSpace && !childSpace.key.equals(space.key)) {
133
+ // // Create clone of child and add to destination space.
134
+ // const newObject = clone(child.data, {
135
+ // // TODO(wittjosiah): This needs to be generalized and not hardcoded here.
136
+ // additional: [
137
+ // child.data.content,
138
+ // ...(child.data.objects ?? []),
139
+ // ...(child.data.objects ?? []).map((object: TypedObject) => object.content),
140
+ // ],
141
+ // });
142
+ // space.db.add(newObject);
143
+ // collection.objects.push(newObject);
144
+ // } else {
145
+
146
+ // Add child to destination collection.
147
+ if (!collection.objects.includes(child.data)) {
148
+ if (typeof index !== 'undefined') {
149
+ collection.objects.splice(index, 0, child.data);
150
+ } else {
151
+ collection.objects.push(child.data);
152
+ }
153
+ }
154
+
155
+ // }
156
+ },
157
+ onTransferEnd: (child: Node<EchoReactiveObject<any>>, destination: Node) => {
158
+ // Remove child from origin collection.
159
+ const index = collection.objects.indexOf(child.data);
160
+ if (index > -1) {
161
+ collection.objects.splice(index, 1);
162
+ }
163
+
164
+ // TODO(wittjosiah): Support transfer between spaces.
165
+ // const childSpace = getSpace(child.data);
166
+ // const destinationSpace =
167
+ // destination.data instanceof SpaceProxy ? destination.data : getSpace(destination.data);
168
+ // if (destinationSpace && childSpace && !childSpace.key.equals(destinationSpace.key)) {
169
+ // // Mark child as deleted in origin space.
170
+ // childSpace.db.remove(child.data);
171
+ // }
172
+ },
173
+ onCopy: async (child: Node<EchoReactiveObject<any>>, index?: number) => {
174
+ // Create clone of child and add to destination space.
175
+ const newObject = await cloneObject(child.data, resolve);
176
+ space.db.add(newObject);
177
+ if (typeof index !== 'undefined') {
178
+ collection.objects.splice(index, 0, newObject);
179
+ } else {
180
+ collection.objects.push(newObject);
181
+ }
182
+ },
183
+ };
184
+ };
185
+
186
+ const checkPendingMigration = (space: Space) => {
187
+ return (
188
+ space.state.get() === SpaceState.SPACE_REQUIRES_MIGRATION ||
189
+ (space.state.get() === SpaceState.SPACE_READY &&
190
+ !!Migrations.versionProperty &&
191
+ space.properties[Migrations.versionProperty] !== Migrations.targetVersion)
192
+ );
193
+ };
194
+
195
+ export const constructSpaceNode = ({
196
+ space,
197
+ personal,
198
+ namesCache,
199
+ resolve,
200
+ }: {
201
+ space: Space;
202
+ personal?: boolean;
203
+ namesCache?: Record<string, string>;
204
+ resolve: MetadataResolver;
205
+ }) => {
206
+ const hasPendingMigration = checkPendingMigration(space);
207
+ const collection = space.state.get() === SpaceState.SPACE_READY && space.properties[CollectionType.typename];
208
+ const partials =
209
+ space.state.get() === SpaceState.SPACE_READY && collection instanceof CollectionType
210
+ ? getCollectionGraphNodePartials({ collection, space, resolve })
211
+ : {};
212
+
213
+ return {
214
+ id: space.id,
215
+ type: SPACE_TYPE,
216
+ data: space,
217
+ properties: {
218
+ ...partials,
219
+ label: getSpaceDisplayName(space, { personal, namesCache }),
220
+ description: space.state.get() === SpaceState.SPACE_READY && space.properties.description,
221
+ icon: (props: IconProps) => <Planet {...props} />,
222
+ iconSymbol: 'ph--planet--regular',
223
+ disabled: space.state.get() !== SpaceState.SPACE_READY || hasPendingMigration,
224
+ testId: 'spacePlugin.space',
225
+ },
226
+ };
227
+ };
228
+
229
+ export const constructSpaceActionGroups = ({ space, dispatch }: { space: Space; dispatch: IntentDispatcher }) => {
230
+ const state = space.state.get();
231
+ const hasPendingMigration = checkPendingMigration(space);
232
+ const getId = (id: string) => `${id}/${space.id}`;
233
+
234
+ if (state !== SpaceState.SPACE_READY || hasPendingMigration) {
235
+ return [];
236
+ }
237
+
238
+ const collection = space.properties[CollectionType.typename];
239
+ const actions: NodeArg<typeof actionGroupSymbol>[] = [
240
+ {
241
+ id: getId(SpaceAction.ADD_OBJECT),
242
+ type: ACTION_GROUP_TYPE,
243
+ data: actionGroupSymbol,
244
+ properties: {
245
+ label: ['create object in space label', { ns: SPACE_PLUGIN }],
246
+ icon: (props: IconProps) => <Plus {...props} />,
247
+ iconSymbol: 'ph--plus--regular',
248
+ disposition: 'toolbar',
249
+ // TODO(wittjosiah): This is currently a navtree feature. Address this with cmd+k integration.
250
+ // mainAreaDisposition: 'in-flow',
251
+ menuType: 'searchList',
252
+ testId: 'spacePlugin.createObject',
253
+ },
254
+ nodes: [
255
+ {
256
+ id: getId(SpaceAction.ADD_OBJECT.replace('object', 'collection')),
257
+ type: ACTION_TYPE,
258
+ data: () =>
259
+ dispatch([
260
+ {
261
+ plugin: SPACE_PLUGIN,
262
+ action: SpaceAction.ADD_OBJECT,
263
+ data: { target: collection, object: create(CollectionType, { objects: [], views: {} }) },
264
+ },
265
+ {
266
+ action: NavigationAction.OPEN,
267
+ },
268
+ ]),
269
+ properties: {
270
+ label: ['create collection label', { ns: SPACE_PLUGIN }],
271
+ icon: (props: IconProps) => <CardsThree {...props} />,
272
+ iconSymbol: 'ph--cards-three--regular',
273
+ testId: 'spacePlugin.createCollection',
274
+ },
275
+ },
276
+ ],
277
+ },
278
+ ];
279
+
280
+ return actions;
281
+ };
282
+
283
+ export const constructSpaceActions = ({
284
+ space,
285
+ dispatch,
286
+ personal,
287
+ migrating,
288
+ }: {
289
+ space: Space;
290
+ dispatch: IntentDispatcher;
291
+ personal?: boolean;
292
+ migrating?: boolean;
293
+ }) => {
294
+ const state = space.state.get();
295
+ const hasPendingMigration = checkPendingMigration(space);
296
+ const getId = (id: string) => `${id}/${space.id}`;
297
+ const actions: NodeArg<ActionData>[] = [];
298
+
299
+ if (hasPendingMigration) {
300
+ actions.push({
301
+ id: getId(SpaceAction.MIGRATE),
302
+ type: ACTION_GROUP_TYPE,
303
+ data: async () => {
304
+ await dispatch({ plugin: SPACE_PLUGIN, action: SpaceAction.MIGRATE, data: { space } });
305
+ },
306
+ properties: {
307
+ label: ['migrate space label', { ns: SPACE_PLUGIN }],
308
+ icon: (props: IconProps) => <Database {...props} />,
309
+ iconSymbol: 'ph--database--regular',
310
+ disposition: 'toolbar',
311
+ mainAreaDisposition: 'in-flow',
312
+ disabled: migrating || Migrations.running(space),
313
+ },
314
+ });
315
+ }
316
+
317
+ if (state === SpaceState.SPACE_READY && !hasPendingMigration) {
318
+ const locked = space.properties[COMPOSER_SPACE_LOCK];
319
+ actions.push(
320
+ {
321
+ id: getId(SpaceAction.SHARE),
322
+ type: ACTION_TYPE,
323
+ data: async () => {
324
+ if (locked) {
325
+ return;
326
+ }
327
+ await dispatch({ plugin: SPACE_PLUGIN, action: SpaceAction.SHARE, data: { spaceId: space.id } });
328
+ },
329
+ properties: {
330
+ label: ['share space label', { ns: SPACE_PLUGIN }],
331
+ icon: (props: IconProps) => <Users {...props} />,
332
+ iconSymbol: 'ph--users--regular',
333
+ disabled: locked,
334
+ keyBinding: {
335
+ macos: 'meta+.',
336
+ windows: 'alt+.',
337
+ },
338
+ mainAreaDisposition: 'absent',
339
+ },
340
+ },
341
+ {
342
+ id: locked ? getId(SpaceAction.UNLOCK) : getId(SpaceAction.LOCK),
343
+ type: ACTION_TYPE,
344
+ data: async () => {
345
+ await dispatch({
346
+ plugin: SPACE_PLUGIN,
347
+ action: locked ? SpaceAction.UNLOCK : SpaceAction.LOCK,
348
+ data: { space },
349
+ });
350
+ },
351
+ properties: {
352
+ label: [locked ? 'unlock space label' : 'lock space label', { ns: SPACE_PLUGIN }],
353
+ icon: locked
354
+ ? (props: IconProps) => <LockSimpleOpen {...props} />
355
+ : (props: IconProps) => <LockSimple {...props} />,
356
+ iconSymbol: locked ? 'ph--lock-simple-open--regular' : 'ph--lock-simple--regular',
357
+ },
358
+ },
359
+ {
360
+ id: getId(SpaceAction.RENAME),
361
+ type: ACTION_TYPE,
362
+ data: async (params: InvokeParams) => {
363
+ await dispatch({ plugin: SPACE_PLUGIN, action: SpaceAction.RENAME, data: { space, ...params } });
364
+ },
365
+ properties: {
366
+ label: ['rename space label', { ns: SPACE_PLUGIN }],
367
+ icon: (props: IconProps) => <PencilSimpleLine {...props} />,
368
+ iconSymbol: 'ph--pencil-simple-line--regular',
369
+ keyBinding: {
370
+ macos: 'shift+F6',
371
+ windows: 'shift+F6',
372
+ },
373
+ mainAreaDisposition: 'absent',
374
+ },
375
+ },
376
+ );
377
+ }
378
+
379
+ if (state !== SpaceState.SPACE_INACTIVE && !hasPendingMigration) {
380
+ actions.push({
381
+ id: getId(SpaceAction.CLOSE),
382
+ type: ACTION_TYPE,
383
+ data: async () => {
384
+ await dispatch({ plugin: SPACE_PLUGIN, action: SpaceAction.CLOSE, data: { space } });
385
+ },
386
+ properties: {
387
+ label: ['close space label', { ns: SPACE_PLUGIN }],
388
+ icon: (props: IconProps) => <X {...props} />,
389
+ iconSymbol: 'ph--x--regular',
390
+ mainAreaDisposition: 'menu',
391
+ disabled: personal,
392
+ },
393
+ });
394
+ }
395
+
396
+ if (state === SpaceState.SPACE_INACTIVE) {
397
+ actions.push({
398
+ id: getId(SpaceAction.OPEN),
399
+ type: ACTION_TYPE,
400
+ data: async () => {
401
+ await dispatch({ plugin: SPACE_PLUGIN, action: SpaceAction.OPEN, data: { space } });
402
+ },
403
+ properties: {
404
+ label: ['open space label', { ns: SPACE_PLUGIN }],
405
+ icon: (props: IconProps) => <ClockCounterClockwise {...props} />,
406
+ iconSymbol: 'ph--clock-counter-clockwise--regular',
407
+ disposition: 'toolbar',
408
+ mainAreaDisposition: 'in-flow',
409
+ },
410
+ });
411
+ }
412
+
413
+ return actions;
414
+ };
415
+
416
+ export const createObjectNode = ({
417
+ object,
418
+ space,
419
+ resolve,
420
+ }: {
421
+ object: EchoReactiveObject<any>;
422
+ space: Space;
423
+ resolve: MetadataResolver;
424
+ }) => {
425
+ const type = getTypename(object);
426
+ if (!type) {
427
+ return undefined;
428
+ }
429
+
430
+ const metadata = resolve(type);
431
+ if (Object.keys(metadata).length === 0) {
432
+ return undefined;
433
+ }
434
+
435
+ const partials =
436
+ object instanceof CollectionType
437
+ ? getCollectionGraphNodePartials({ collection: object, space, resolve })
438
+ : metadata.graphProps;
439
+
440
+ return {
441
+ id: fullyQualifiedId(object),
442
+ type,
443
+ data: object,
444
+ properties: {
445
+ ...partials,
446
+ label: metadata.label?.(object) ||
447
+ object.name ||
448
+ metadata.placeholder || ['unnamed object label', { ns: SPACE_PLUGIN }],
449
+ icon: metadata.icon ?? (() => <Placeholder />),
450
+ iconSymbol: metadata.iconSymbol ?? 'ph--placeholder--regular',
451
+ testId: 'spacePlugin.object',
452
+ persistenceClass: 'echo',
453
+ persistenceKey: space?.id,
454
+ },
455
+ };
456
+ };
457
+
458
+ export const constructObjectActionGroups = ({
459
+ object,
460
+ dispatch,
461
+ }: {
462
+ object: EchoReactiveObject<any>;
463
+ dispatch: IntentDispatcher;
464
+ }) => {
465
+ if (!(object instanceof CollectionType)) {
466
+ return [];
467
+ }
468
+
469
+ const collection = object;
470
+ const getId = (id: string) => `${id}/${fullyQualifiedId(object)}`;
471
+ const actions: NodeArg<typeof actionGroupSymbol>[] = [
472
+ {
473
+ id: getId(SpaceAction.ADD_OBJECT),
474
+ type: ACTION_GROUP_TYPE,
475
+ data: actionGroupSymbol,
476
+ properties: {
477
+ label: ['create object in collection label', { ns: SPACE_PLUGIN }],
478
+ icon: (props: IconProps) => <Plus {...props} />,
479
+ iconSymbol: 'ph--plus--regular',
480
+ disposition: 'toolbar',
481
+ // TODO(wittjosiah): This is currently a navtree feature. Address this with cmd+k integration.
482
+ // mainAreaDisposition: 'in-flow',
483
+ menuType: 'searchList',
484
+ testId: 'spacePlugin.createObject',
485
+ },
486
+ nodes: [
487
+ {
488
+ id: getId(SpaceAction.ADD_OBJECT.replace('object', 'collection')),
489
+ type: ACTION_TYPE,
490
+ data: () =>
491
+ dispatch([
492
+ {
493
+ plugin: SPACE_PLUGIN,
494
+ action: SpaceAction.ADD_OBJECT,
495
+ data: { target: collection, object: create(CollectionType, { objects: [], views: {} }) },
496
+ },
497
+ {
498
+ action: NavigationAction.OPEN,
499
+ },
500
+ ]),
501
+ properties: {
502
+ label: ['create collection label', { ns: SPACE_PLUGIN }],
503
+ icon: (props: IconProps) => <CardsThree {...props} />,
504
+ iconSymbol: 'ph--cards-three--regular',
505
+ testId: 'spacePlugin.createCollection',
506
+ },
507
+ },
508
+ ],
509
+ },
510
+ ];
511
+
512
+ return actions;
513
+ };
514
+
515
+ export const constructObjectActions = ({
516
+ node,
517
+ dispatch,
518
+ }: {
519
+ node: Node<EchoReactiveObject<any>>;
520
+ dispatch: IntentDispatcher;
521
+ }) => {
522
+ const object = node.data;
523
+ const getId = (id: string) => `${id}/${fullyQualifiedId(object)}`;
524
+ const actions: NodeArg<ActionData>[] = [
525
+ {
526
+ id: getId(SpaceAction.RENAME_OBJECT),
527
+ type: ACTION_TYPE,
528
+ data: async (params: InvokeParams) => {
529
+ await dispatch({
530
+ action: SpaceAction.RENAME_OBJECT,
531
+ data: { object, ...params },
532
+ });
533
+ },
534
+ properties: {
535
+ label: [
536
+ object instanceof CollectionType ? 'rename collection label' : 'rename object label',
537
+ { ns: SPACE_PLUGIN },
538
+ ],
539
+ icon: (props: IconProps) => <PencilSimpleLine {...props} />,
540
+ iconSymbol: 'ph--pencil-simple-line--regular',
541
+ // TODO(wittjosiah): Doesn't work.
542
+ // keyBinding: 'shift+F6',
543
+ testId: 'spacePlugin.renameObject',
544
+ },
545
+ },
546
+ {
547
+ id: getId(SpaceAction.REMOVE_OBJECT),
548
+ type: ACTION_TYPE,
549
+ data: async () => {
550
+ const graph = getGraph(node);
551
+ const collection = graph
552
+ .nodes(node, { relation: 'inbound' })
553
+ .find(({ data }) => data instanceof CollectionType)?.data;
554
+ await dispatch([
555
+ {
556
+ action: SpaceAction.REMOVE_OBJECT,
557
+ data: { object, collection },
558
+ },
559
+ ]);
560
+ },
561
+ properties: {
562
+ label: [
563
+ object instanceof CollectionType ? 'delete collection label' : 'delete object label',
564
+ { ns: SPACE_PLUGIN },
565
+ ],
566
+ icon: (props: IconProps) => <Trash {...props} />,
567
+ iconSymbol: 'ph--trash--regular',
568
+ keyBinding: object instanceof CollectionType ? undefined : 'shift+meta+Backspace',
569
+ testId: 'spacePlugin.deleteObject',
570
+ },
571
+ },
572
+ {
573
+ id: getId('copy-link'),
574
+ type: ACTION_TYPE,
575
+ data: async () => {
576
+ const url = `${window.location.origin}/${fullyQualifiedId(object)}`;
577
+ await navigator.clipboard.writeText(url);
578
+ },
579
+ properties: {
580
+ label: ['copy link label', { ns: SPACE_PLUGIN }],
581
+ icon: (props: IconProps) => <Link {...props} />,
582
+ iconSymbol: 'ph--link--regular',
583
+ testId: 'spacePlugin.copyLink',
584
+ },
585
+ },
586
+ ];
587
+
588
+ return actions;
589
+ };
590
+
591
+ /**
592
+ * @deprecated
593
+ */
594
+ export const getActiveSpace = (graph: Graph, active?: string) => {
595
+ if (!active) {
596
+ return;
597
+ }
598
+
599
+ const node = graph.findNode(active);
600
+ if (!node || !isReactiveObject(node.data)) {
601
+ return;
602
+ }
603
+
604
+ return getSpace(node.data);
605
+ };
606
+
607
+ /**
608
+ * @deprecated This is a temporary solution.
609
+ */
610
+ export const getNestedObjects = async (
611
+ object: EchoReactiveObject<any>,
612
+ resolve: MetadataResolver,
613
+ ): Promise<EchoReactiveObject<any>[]> => {
614
+ const type = getTypename(object);
615
+ if (!type) {
616
+ return [];
617
+ }
618
+
619
+ const metadata = resolve(type);
620
+ const loadReferences = metadata.loadReferences;
621
+ if (typeof loadReferences !== 'function') {
622
+ return [];
623
+ }
624
+
625
+ const objects: EchoReactiveObject<any>[] = await loadReferences(object);
626
+ const nested = await Promise.all(objects.map((object) => getNestedObjects(object, resolve)));
627
+ return [...objects, ...nested.flat()];
628
+ };
629
+
630
+ /**
631
+ * @deprecated Workaround for ECHO not supporting clone.
632
+ */
633
+ // TODO(burdon): Remove.
634
+ export const cloneObject = async (object: Expando, resolve: MetadataResolver): Promise<Expando> => {
635
+ const schema = getSchema(object);
636
+ const typename = schema ? getEchoObjectAnnotation(schema)?.typename ?? EXPANDO_TYPENAME : EXPANDO_TYPENAME;
637
+ const metadata = resolve(typename);
638
+ const serializer = metadata.serializer;
639
+ invariant(serializer, `No serializer for type: ${typename}`);
640
+ const content = await serializer.serialize({ object });
641
+ return serializer.deserialize({ content, newId: true });
642
+ };