@dxos/plugin-space 0.7.2 → 0.7.3-staging.0905f03

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 (99) hide show
  1. package/dist/lib/browser/{chunk-DJE2HYFV.mjs → chunk-FTKV32QZ.mjs} +9 -2
  2. package/dist/lib/browser/chunk-FTKV32QZ.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-OWZKSWMX.mjs → chunk-MWKXNS5S.mjs} +13 -3
  4. package/dist/lib/browser/{chunk-OWZKSWMX.mjs.map → chunk-MWKXNS5S.mjs.map} +3 -3
  5. package/dist/lib/browser/index.mjs +1167 -786
  6. package/dist/lib/browser/index.mjs.map +4 -4
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/meta.mjs +3 -1
  9. package/dist/lib/browser/types/index.mjs +5 -3
  10. package/dist/lib/node/{chunk-FYWGZYJB.cjs → chunk-6SNOZF7Y.cjs} +18 -7
  11. package/dist/lib/node/chunk-6SNOZF7Y.cjs.map +7 -0
  12. package/dist/lib/node/{chunk-JFDDZI4Y.cjs → chunk-QNVEU2UD.cjs} +12 -4
  13. package/dist/lib/node/chunk-QNVEU2UD.cjs.map +7 -0
  14. package/dist/lib/node/index.cjs +1210 -839
  15. package/dist/lib/node/index.cjs.map +4 -4
  16. package/dist/lib/node/meta.cjs +7 -5
  17. package/dist/lib/node/meta.cjs.map +2 -2
  18. package/dist/lib/node/meta.json +1 -1
  19. package/dist/lib/node/types/index.cjs +14 -12
  20. package/dist/lib/node/types/index.cjs.map +2 -2
  21. package/dist/lib/node-esm/{chunk-MCEAI4CV.mjs → chunk-OHEAWSCA.mjs} +13 -3
  22. package/dist/lib/node-esm/{chunk-MCEAI4CV.mjs.map → chunk-OHEAWSCA.mjs.map} +3 -3
  23. package/dist/lib/node-esm/{chunk-DVUZ7A7G.mjs → chunk-UMV7XREB.mjs} +9 -2
  24. package/dist/lib/node-esm/chunk-UMV7XREB.mjs.map +7 -0
  25. package/dist/lib/node-esm/index.mjs +1167 -786
  26. package/dist/lib/node-esm/index.mjs.map +4 -4
  27. package/dist/lib/node-esm/meta.json +1 -1
  28. package/dist/lib/node-esm/meta.mjs +3 -1
  29. package/dist/lib/node-esm/types/index.mjs +5 -3
  30. package/dist/types/src/SpacePlugin.d.ts.map +1 -1
  31. package/dist/types/src/components/CreateDialog/CreateObjectDialog.d.ts +9 -0
  32. package/dist/types/src/components/CreateDialog/CreateObjectDialog.d.ts.map +1 -0
  33. package/dist/types/src/components/CreateDialog/CreateObjectDialog.stories.d.ts +10 -0
  34. package/dist/types/src/components/CreateDialog/CreateObjectDialog.stories.d.ts.map +1 -0
  35. package/dist/types/src/components/CreateDialog/CreateObjectPanel.d.ts +22 -0
  36. package/dist/types/src/components/CreateDialog/CreateObjectPanel.d.ts.map +1 -0
  37. package/dist/types/src/components/CreateDialog/CreateSpaceDialog.d.ts +3 -0
  38. package/dist/types/src/components/CreateDialog/CreateSpaceDialog.d.ts.map +1 -0
  39. package/dist/types/src/components/CreateDialog/index.d.ts +3 -0
  40. package/dist/types/src/components/CreateDialog/index.d.ts.map +1 -0
  41. package/dist/types/src/components/PopoverRenameObject.d.ts +1 -1
  42. package/dist/types/src/components/SpacePluginSettings.d.ts.map +1 -1
  43. package/dist/types/src/components/SpaceSettings/SpaceSettingsDialog.d.ts.map +1 -1
  44. package/dist/types/src/components/SpaceSettings/SpaceSettingsPanel.d.ts.map +1 -1
  45. package/dist/types/src/components/SyncStatus/InlineSyncStatus.d.ts +7 -0
  46. package/dist/types/src/components/SyncStatus/InlineSyncStatus.d.ts.map +1 -0
  47. package/dist/types/src/components/SyncStatus/InlineSyncStatus.stories.d.ts +6 -0
  48. package/dist/types/src/components/SyncStatus/InlineSyncStatus.stories.d.ts.map +1 -0
  49. package/dist/types/src/components/SyncStatus/Space.d.ts +8 -3
  50. package/dist/types/src/components/SyncStatus/Space.d.ts.map +1 -1
  51. package/dist/types/src/components/SyncStatus/SyncStatus.d.ts +3 -2
  52. package/dist/types/src/components/SyncStatus/SyncStatus.d.ts.map +1 -1
  53. package/dist/types/src/components/SyncStatus/SyncStatus.stories.d.ts +4 -4
  54. package/dist/types/src/components/SyncStatus/SyncStatus.stories.d.ts.map +1 -1
  55. package/dist/types/src/components/SyncStatus/SyncStatusDetail.stories.d.ts +9 -0
  56. package/dist/types/src/components/SyncStatus/SyncStatusDetail.stories.d.ts.map +1 -0
  57. package/dist/types/src/components/SyncStatus/index.d.ts +1 -0
  58. package/dist/types/src/components/SyncStatus/index.d.ts.map +1 -1
  59. package/dist/types/src/components/SyncStatus/sync-state.d.ts +5 -1
  60. package/dist/types/src/components/SyncStatus/sync-state.d.ts.map +1 -1
  61. package/dist/types/src/components/index.d.ts +1 -0
  62. package/dist/types/src/components/index.d.ts.map +1 -1
  63. package/dist/types/src/meta.d.ts +5 -0
  64. package/dist/types/src/meta.d.ts.map +1 -1
  65. package/dist/types/src/translations.d.ts +224 -0
  66. package/dist/types/src/translations.d.ts.map +1 -1
  67. package/dist/types/src/types/types.d.ts +14 -14
  68. package/dist/types/src/types/types.d.ts.map +1 -1
  69. package/dist/types/src/util.d.ts +3 -13
  70. package/dist/types/src/util.d.ts.map +1 -1
  71. package/package.json +38 -35
  72. package/src/SpacePlugin.tsx +203 -128
  73. package/src/components/AwaitingObject.tsx +2 -2
  74. package/src/components/CreateDialog/CreateObjectDialog.stories.tsx +83 -0
  75. package/src/components/CreateDialog/CreateObjectDialog.tsx +98 -0
  76. package/src/components/CreateDialog/CreateObjectPanel.tsx +169 -0
  77. package/src/components/CreateDialog/CreateSpaceDialog.tsx +57 -0
  78. package/src/components/CreateDialog/index.ts +6 -0
  79. package/src/components/PopoverRenameObject.tsx +1 -1
  80. package/src/components/SpacePluginSettings.tsx +3 -32
  81. package/src/components/SpaceSettings/SpaceSettingsDialog.tsx +5 -5
  82. package/src/components/SpaceSettings/SpaceSettingsPanel.tsx +2 -6
  83. package/src/components/SyncStatus/InlineSyncStatus.stories.tsx +57 -0
  84. package/src/components/SyncStatus/InlineSyncStatus.tsx +61 -0
  85. package/src/components/SyncStatus/Space.tsx +30 -6
  86. package/src/components/SyncStatus/SyncStatus.stories.tsx +15 -42
  87. package/src/components/SyncStatus/SyncStatus.tsx +32 -14
  88. package/src/components/SyncStatus/SyncStatusDetail.stories.tsx +85 -0
  89. package/src/components/SyncStatus/index.ts +1 -0
  90. package/src/components/SyncStatus/sync-state.ts +24 -0
  91. package/src/components/index.ts +1 -0
  92. package/src/meta.ts +6 -0
  93. package/src/translations.ts +15 -0
  94. package/src/types/types.ts +20 -16
  95. package/src/util.tsx +51 -141
  96. package/dist/lib/browser/chunk-DJE2HYFV.mjs.map +0 -7
  97. package/dist/lib/node/chunk-FYWGZYJB.cjs.map +0 -7
  98. package/dist/lib/node/chunk-JFDDZI4Y.cjs.map +0 -7
  99. package/dist/lib/node-esm/chunk-DVUZ7A7G.mjs.map +0 -7
@@ -12,10 +12,12 @@ import {
12
12
  LayoutAction,
13
13
  type LayoutProvides,
14
14
  type LocationProvides,
15
+ type MetadataResolverProvides,
15
16
  NavigationAction,
16
17
  type Plugin,
17
18
  type PluginDefinition,
18
19
  Surface,
20
+ filterPlugins,
19
21
  findPlugin,
20
22
  firstIdInPart,
21
23
  openIds,
@@ -27,9 +29,10 @@ import {
27
29
  resolvePlugin,
28
30
  } from '@dxos/app-framework';
29
31
  import { EventSubscriptions, type Trigger, type UnsubscribeCallback } from '@dxos/async';
30
- import { type HasId, isDeleted, isReactiveObject } from '@dxos/echo-schema';
32
+ import { S, type AbstractTypedObject, type HasId } from '@dxos/echo-schema';
31
33
  import { scheduledEffect } from '@dxos/echo-signals/core';
32
34
  import { invariant } from '@dxos/invariant';
35
+ import { create, isDeleted, isReactiveObject } from '@dxos/live-object';
33
36
  import { LocalStorageStore } from '@dxos/local-storage';
34
37
  import { log } from '@dxos/log';
35
38
  import { Migrations } from '@dxos/migrations';
@@ -37,25 +40,25 @@ import { type AttentionPluginProvides, parseAttentionPlugin } from '@dxos/plugin
37
40
  import { type ClientPluginProvides, parseClientPlugin } from '@dxos/plugin-client';
38
41
  import { type Node, createExtension, memoize, toSignal } from '@dxos/plugin-graph';
39
42
  import { ObservabilityAction } from '@dxos/plugin-observability/meta';
43
+ import { EdgeReplicationSetting } from '@dxos/protocols/proto/dxos/echo/metadata';
40
44
  import { type Client, PublicKey } from '@dxos/react-client';
41
45
  import {
42
- type ReactiveEchoObject,
43
46
  Expando,
47
+ FQ_ID_LENGTH,
44
48
  Filter,
45
- type PropertiesTypeProps,
49
+ OBJECT_ID_LENGTH,
50
+ type ReactiveEchoObject,
51
+ SPACE_ID_LENGTH,
46
52
  type Space,
47
53
  SpaceState,
48
- create,
49
54
  fullyQualifiedId,
50
55
  getSpace,
51
56
  getTypename,
52
57
  isEchoObject,
53
58
  isSpace,
54
59
  loadObjectReferences,
60
+ parseFullyQualifiedId,
55
61
  parseId,
56
- FQ_ID_LENGTH,
57
- SPACE_ID_LENGTH,
58
- OBJECT_ID_LENGTH,
59
62
  } from '@dxos/react-client/echo';
60
63
  import { type JoinPanelProps, osTranslations } from '@dxos/shell/react';
61
64
  import { ComplexMap, nonNullable, reduceGroupBy } from '@dxos/util';
@@ -64,33 +67,42 @@ import {
64
67
  AwaitingObject,
65
68
  CollectionMain,
66
69
  CollectionSection,
70
+ CreateObjectDialog,
71
+ type CreateObjectDialogProps,
72
+ CreateSpaceDialog,
67
73
  DefaultObjectSettings,
68
74
  JoinDialog,
75
+ InlineSyncStatus,
69
76
  MenuFooter,
70
77
  PopoverRenameObject,
71
78
  PopoverRenameSpace,
72
79
  ShareSpaceButton,
73
80
  SmallPresence,
74
81
  SmallPresenceLive,
75
- SpacePresence,
76
82
  SpacePluginSettings,
83
+ SpacePresence,
84
+ SpaceSettingsDialog,
77
85
  SpaceSettingsPanel,
78
86
  SyncStatus,
79
- SpaceSettingsDialog,
80
87
  type SpaceSettingsDialogProps,
81
88
  } from './components';
82
- import meta, { SPACE_PLUGIN, SpaceAction } from './meta';
89
+ import meta, { CollectionAction, SPACE_PLUGIN, SpaceAction } from './meta';
83
90
  import translations from './translations';
84
- import { CollectionType, type PluginState, type SpacePluginProvides, type SpaceSettingsProps } from './types';
91
+ import {
92
+ CollectionType,
93
+ parseSchemaPlugin,
94
+ SpaceForm,
95
+ type PluginState,
96
+ type SpacePluginProvides,
97
+ type SpaceSettingsProps,
98
+ } from './types';
85
99
  import {
86
100
  COMPOSER_SPACE_LOCK,
87
101
  SHARED,
88
102
  SPACES,
89
103
  SPACE_TYPE,
90
104
  cloneObject,
91
- constructObjectActionGroups,
92
105
  constructObjectActions,
93
- constructSpaceActionGroups,
94
106
  constructSpaceActions,
95
107
  constructSpaceNode,
96
108
  createObjectNode,
@@ -142,9 +154,7 @@ export const SpacePlugin = ({
142
154
  firstRun,
143
155
  onFirstRun,
144
156
  }: SpacePluginOptions = {}): PluginDefinition<SpacePluginProvides> => {
145
- const settings = new LocalStorageStore<SpaceSettingsProps>(SPACE_PLUGIN, {
146
- onSpaceCreate: 'dxos.org/plugin/markdown/action/create',
147
- });
157
+ const settings = new LocalStorageStore<SpaceSettingsProps>(SPACE_PLUGIN, {});
148
158
  const state = new LocalStorageStore<PluginState>(SPACE_PLUGIN, {
149
159
  awaiting: undefined,
150
160
  spaceNames: {},
@@ -153,10 +163,12 @@ export const SpacePlugin = ({
153
163
  viewersByIdentity: new ComplexMap(PublicKey.hash),
154
164
  sdkMigrationRunning: {},
155
165
  navigableCollections: false,
166
+ enabledEdgeReplication: false,
156
167
  });
157
168
  const subscriptions = new EventSubscriptions();
158
169
  const spaceSubscriptions = new EventSubscriptions();
159
170
  const graphSubscriptions = new Map<string, UnsubscribeCallback>();
171
+ const schemas: AbstractTypedObject[] = [];
160
172
 
161
173
  let clientPlugin: Plugin<ClientPluginProvides> | undefined;
162
174
  let graphPlugin: Plugin<GraphProvides> | undefined;
@@ -164,6 +176,7 @@ export const SpacePlugin = ({
164
176
  let layoutPlugin: Plugin<LayoutProvides> | undefined;
165
177
  let navigationPlugin: Plugin<LocationProvides> | undefined;
166
178
  let attentionPlugin: Plugin<AttentionPluginProvides> | undefined;
179
+ let metadataPlugin: Plugin<MetadataResolverProvides> | undefined;
167
180
 
168
181
  const createSpaceInvitationUrl = (invitationCode: string) => {
169
182
  const baseUrl = new URL(invitationUrl);
@@ -254,18 +267,31 @@ export const SpacePlugin = ({
254
267
  subscriptions.add(
255
268
  scheduledEffect(
256
269
  () => ({
257
- ids: openIds(location.active),
258
- removed: location.closed ? [location.closed].flat() : [],
270
+ open: openIds(location.active, layout.layoutMode === 'solo' ? ['solo'] : ['main']),
271
+ closed: [...location.closed],
259
272
  }),
260
- ({ ids, removed }) => {
273
+ ({ open, closed }) => {
261
274
  const send = () => {
262
275
  const spaces = client.spaces.get();
263
276
  const identity = client.halo.identity.get();
264
277
  if (identity && location.active) {
265
278
  // Group parts by space for efficient messaging.
266
- const idsBySpace = reduceGroupBy(ids, (id) => {
267
- const [spaceId] = id.split(':'); // TODO(burdon): Factor out.
268
- return spaceId;
279
+ const idsBySpace = reduceGroupBy(open, (id) => {
280
+ try {
281
+ const [spaceId] = parseFullyQualifiedId(id);
282
+ return spaceId;
283
+ } catch {
284
+ return null;
285
+ }
286
+ });
287
+
288
+ const removedBySpace = reduceGroupBy(closed, (id) => {
289
+ try {
290
+ const [spaceId] = parseFullyQualifiedId(id);
291
+ return spaceId;
292
+ } catch {
293
+ return null;
294
+ }
269
295
  });
270
296
 
271
297
  // NOTE: Ensure all spaces are included so that we send the correct `removed` object arrays.
@@ -275,7 +301,8 @@ export const SpacePlugin = ({
275
301
  }
276
302
  }
277
303
 
278
- for (const [spaceId, ids] of idsBySpace) {
304
+ for (const [spaceId, added] of idsBySpace) {
305
+ const removed = removedBySpace.get(spaceId) ?? [];
279
306
  const space = spaces.find((space) => space.id === spaceId);
280
307
  if (!space) {
281
308
  continue;
@@ -285,9 +312,8 @@ export const SpacePlugin = ({
285
312
  .postMessage('viewing', {
286
313
  identityKey: identity.identityKey.toHex(),
287
314
  attended: attention.attended ? [...attention.attended] : [],
288
- added: ids,
289
- // TODO(Zan): When we re-open a part, we should remove it from the removed list in the navigation plugin.
290
- removed: removed.filter((id) => !ids.includes(id)),
315
+ added,
316
+ removed,
291
317
  })
292
318
  // TODO(burdon): This seems defensive; why would this fail? Backoff interval.
293
319
  .catch((err) => {
@@ -298,6 +324,7 @@ export const SpacePlugin = ({
298
324
  };
299
325
 
300
326
  send();
327
+ // Send at interval to allow peers to expire entries if they become disconnected.
301
328
  const interval = setInterval(() => send(), ACTIVE_NODE_BROADCAST_INTERVAL);
302
329
  return () => clearInterval(interval);
303
330
  },
@@ -314,7 +341,13 @@ export const SpacePlugin = ({
314
341
  const { added, removed, attended } = message.payload;
315
342
 
316
343
  const identityKey = PublicKey.safeFrom(message.payload.identityKey);
317
- if (identityKey && Array.isArray(added) && Array.isArray(removed)) {
344
+ const currentIdentity = client.halo.identity.get();
345
+ if (
346
+ identityKey &&
347
+ !currentIdentity?.identityKey.equals(identityKey) &&
348
+ Array.isArray(added) &&
349
+ Array.isArray(removed)
350
+ ) {
318
351
  added.forEach((id) => {
319
352
  if (typeof id === 'string') {
320
353
  if (!(id in state.values.viewersByObject)) {
@@ -346,11 +379,24 @@ export const SpacePlugin = ({
346
379
  );
347
380
  };
348
381
 
382
+ const setEdgeReplicationDefault = async (client: Client) => {
383
+ try {
384
+ await Promise.all(
385
+ client.spaces.get().map((space) => space.internal.setEdgeReplicationPreference(EdgeReplicationSetting.ENABLED)),
386
+ );
387
+ state.values.enabledEdgeReplication = true;
388
+ } catch (err) {
389
+ log.catch(err);
390
+ }
391
+ };
392
+
349
393
  return {
350
394
  meta,
351
395
  ready: async (plugins) => {
352
396
  settings.prop({ key: 'showHidden', type: LocalStorageStore.bool({ allowUndefined: true }) });
353
- state.prop({ key: 'spaceNames', type: LocalStorageStore.json<Record<string, string>>() });
397
+ state
398
+ .prop({ key: 'spaceNames', type: LocalStorageStore.json<Record<string, string>>() })
399
+ .prop({ key: 'enabledEdgeReplication', type: LocalStorageStore.bool() });
354
400
 
355
401
  // TODO(wittjosiah): Hardcoded due to circular dependency.
356
402
  // Should be based on a provides interface.
@@ -360,6 +406,7 @@ export const SpacePlugin = ({
360
406
 
361
407
  graphPlugin = resolvePlugin(plugins, parseGraphPlugin);
362
408
  layoutPlugin = resolvePlugin(plugins, parseLayoutPlugin);
409
+ metadataPlugin = resolvePlugin(plugins, parseMetadataResolverPlugin);
363
410
  navigationPlugin = resolvePlugin(plugins, parseNavigationPlugin);
364
411
  attentionPlugin = resolvePlugin(plugins, parseAttentionPlugin);
365
412
  clientPlugin = resolvePlugin(plugins, parseClientPlugin);
@@ -371,6 +418,21 @@ export const SpacePlugin = ({
371
418
  const client = clientPlugin.provides.client;
372
419
  const dispatch = intentPlugin.provides.intent.dispatch;
373
420
 
421
+ schemas.push(
422
+ ...filterPlugins(plugins, parseSchemaPlugin)
423
+ .map((plugin) => plugin.provides.echo.schema)
424
+ .filter(nonNullable)
425
+ .reduce((acc, schema) => {
426
+ return [...acc, ...schema];
427
+ }),
428
+ );
429
+ client.addTypes(schemas);
430
+ filterPlugins(plugins, parseSchemaPlugin).forEach((plugin) => {
431
+ if (plugin.provides.echo.system) {
432
+ client.addTypes(plugin.provides.echo.system);
433
+ }
434
+ });
435
+
374
436
  const handleFirstRun = async () => {
375
437
  const defaultSpace = client.spaces.default;
376
438
 
@@ -393,6 +455,7 @@ export const SpacePlugin = ({
393
455
  }
394
456
 
395
457
  await onSpaceReady();
458
+ await setEdgeReplicationDefault(client);
396
459
  }
397
460
  }).unsubscribe,
398
461
  );
@@ -417,6 +480,7 @@ export const SpacePlugin = ({
417
480
  metadata: {
418
481
  records: {
419
482
  [CollectionType.typename]: {
483
+ createObject: CollectionAction.CREATE,
420
484
  placeholder: ['unnamed collection label', { ns: SPACE_PLUGIN }],
421
485
  icon: 'ph--cards-three--regular',
422
486
  // TODO(wittjosiah): Move out of metadata.
@@ -448,6 +512,7 @@ export const SpacePlugin = ({
448
512
  disposition: 'fallback',
449
513
  }
450
514
  ) : null;
515
+ // TODO(burdon): Add role name syntax to minimal plugin docs.
451
516
  case 'complementary--settings':
452
517
  return isSpace(data.subject) ? (
453
518
  <SpaceSettingsPanel space={data.subject} />
@@ -464,6 +529,17 @@ export const SpacePlugin = ({
464
529
  );
465
530
  } else if (data.component === 'dxos.org/plugin/space/JoinDialog') {
466
531
  return <JoinDialog {...(data.subject as JoinPanelProps)} />;
532
+ } else if (data.component === 'dxos.org/plugin/space/CreateSpaceDialog') {
533
+ return <CreateSpaceDialog />;
534
+ } else if (data.component === 'dxos.org/plugin/space/CreateObjectDialog') {
535
+ return (
536
+ <CreateObjectDialog
537
+ {...(data.subject as CreateObjectDialogProps)}
538
+ schemas={schemas}
539
+ navigableCollections={state.values.navigableCollections}
540
+ resolve={metadataPlugin?.provides.metadata.resolver}
541
+ />
542
+ );
467
543
  }
468
544
  return null;
469
545
  case 'popover': {
@@ -475,20 +551,19 @@ export const SpacePlugin = ({
475
551
  }
476
552
  return null;
477
553
  }
478
- // TODO(burdon): Add role name syntax to minimal plugin docs.
479
- case 'presence--glyph': {
554
+ case 'navtree-item-end': {
480
555
  return isReactiveObject(data.object) ? (
481
556
  <SmallPresenceLive
482
557
  id={data.id as string}
483
558
  viewers={state.values.viewersByObject[fullyQualifiedId(data.object)]}
484
559
  />
560
+ ) : isSpace(data.object) ? (
561
+ <InlineSyncStatus space={data.object} />
485
562
  ) : (
563
+ // TODO(wittjosiah): Attention glyph for non-echo items should be handled elsewhere.
486
564
  <SmallPresence id={data.id as string} count={0} />
487
565
  );
488
566
  }
489
- case 'navbar-start': {
490
- return null;
491
- }
492
567
  case 'navbar-end': {
493
568
  if (!isEchoObject(data.object) && !isSpace(data.object)) {
494
569
  return null;
@@ -545,64 +620,45 @@ export const SpacePlugin = ({
545
620
  return [];
546
621
  }
547
622
 
623
+ const spacesNode = {
624
+ id: SPACES,
625
+ type: SPACES,
626
+ cacheable: ['label', 'role'],
627
+ properties: {
628
+ label: ['spaces label', { ns: SPACE_PLUGIN }],
629
+ testId: 'spacePlugin.spaces',
630
+ role: 'branch',
631
+ disabled: true,
632
+ childrenPersistenceClass: 'echo',
633
+ onRearrangeChildren: async (nextOrder: Space[]) => {
634
+ // NOTE: This is needed to ensure order is updated by next animation frame.
635
+ // TODO(wittjosiah): Is there a better way to do this?
636
+ // If not, graph should be passed as an argument to the extension.
637
+ graph._sortEdges(
638
+ SPACES,
639
+ 'outbound',
640
+ nextOrder.map(({ id }) => id),
641
+ );
642
+
643
+ const {
644
+ objects: [spacesOrder],
645
+ } = await client.spaces.default.db.query(Filter.schema(Expando, { key: SHARED })).run();
646
+ if (spacesOrder) {
647
+ spacesOrder.order = nextOrder.map(({ id }) => id);
648
+ } else {
649
+ log.warn('spaces order object not found');
650
+ }
651
+ },
652
+ },
653
+ };
654
+
548
655
  return [
549
656
  // Create spaces group node.
550
657
  createExtension({
551
658
  id: `${SPACE_PLUGIN}/root`,
552
659
  filter: (node): node is Node<null> => node.id === 'root',
553
- connector: () => {
554
- const isReady = toSignal(
555
- (onChange) => {
556
- let defaultSpaceUnsubscribe: UnsubscribeCallback | undefined;
557
- // No need to unsubscribe because this observable completes when spaces are ready.
558
- client.spaces.isReady.subscribe((ready) => {
559
- if (ready) {
560
- defaultSpaceUnsubscribe = client.spaces.default.state.subscribe(() => onChange()).unsubscribe;
561
- }
562
- });
563
-
564
- return () => defaultSpaceUnsubscribe?.();
565
- },
566
- () => client.spaces.isReady.get() && client.spaces.default.state.get() === SpaceState.SPACE_READY,
567
- );
568
- if (!isReady) {
569
- return [];
570
- }
571
-
572
- return [
573
- {
574
- id: SPACES,
575
- type: SPACES,
576
- cacheable: ['label', 'role'],
577
- properties: {
578
- label: ['spaces label', { ns: SPACE_PLUGIN }],
579
- testId: 'spacePlugin.spaces',
580
- role: 'branch',
581
- disabled: true,
582
- childrenPersistenceClass: 'echo',
583
- onRearrangeChildren: async (nextOrder: Space[]) => {
584
- // NOTE: This is needed to ensure order is updated by next animation frame.
585
- // TODO(wittjosiah): Is there a better way to do this?
586
- // If not, graph should be passed as an argument to the extension.
587
- graph._sortEdges(
588
- SPACES,
589
- 'outbound',
590
- nextOrder.map(({ id }) => id),
591
- );
592
-
593
- const {
594
- objects: [spacesOrder],
595
- } = await client.spaces.default.db.query(Filter.schema(Expando, { key: SHARED })).run();
596
- if (spacesOrder) {
597
- spacesOrder.order = nextOrder.map(({ id }) => id);
598
- } else {
599
- log.warn('spaces order object not found');
600
- }
601
- },
602
- },
603
- },
604
- ];
605
- },
660
+ connector: () => [spacesNode],
661
+ resolver: ({ id }) => (id === SPACES ? spacesNode : undefined),
606
662
  }),
607
663
 
608
664
  // Create space nodes.
@@ -611,18 +667,18 @@ export const SpacePlugin = ({
611
667
  filter: (node): node is Node<null> => node.id === SPACES,
612
668
  actions: () => [
613
669
  {
614
- id: SpaceAction.CREATE,
670
+ id: SpaceAction.OPEN_CREATE_SPACE,
615
671
  data: async () => {
616
672
  await dispatch({
617
673
  plugin: SPACE_PLUGIN,
618
- action: SpaceAction.CREATE,
674
+ action: SpaceAction.OPEN_CREATE_SPACE,
619
675
  });
620
676
  },
621
677
  properties: {
622
678
  label: ['create space label', { ns: SPACE_PLUGIN }],
623
679
  icon: 'ph--plus--regular',
624
- disposition: 'item',
625
680
  testId: 'spacePlugin.createSpace',
681
+ disposition: 'item',
626
682
  className: 'border-t border-separator',
627
683
  },
628
684
  },
@@ -637,8 +693,8 @@ export const SpacePlugin = ({
637
693
  properties: {
638
694
  label: ['join space label', { ns: SPACE_PLUGIN }],
639
695
  icon: 'ph--sign-in--regular',
640
- disposition: 'item',
641
696
  testId: 'spacePlugin.joinSpace',
697
+ disposition: 'item',
642
698
  },
643
699
  },
644
700
  ],
@@ -720,16 +776,10 @@ export const SpacePlugin = ({
720
776
  },
721
777
  }),
722
778
 
723
- // Create space actions and action groups.
779
+ // Create space actions.
724
780
  createExtension({
725
781
  id: `${SPACE_PLUGIN}/actions`,
726
782
  filter: (node): node is Node<Space> => isSpace(node.data),
727
- actionGroups: ({ node }) =>
728
- constructSpaceActionGroups({
729
- space: node.data,
730
- dispatch,
731
- navigable: state.values.navigableCollections,
732
- }),
733
783
  actions: ({ node }) => {
734
784
  const space = node.data;
735
785
  return constructSpaceActions({
@@ -818,7 +868,8 @@ export const SpacePlugin = ({
818
868
  void space.db
819
869
  .query({ id: objectId })
820
870
  .first()
821
- .then((o) => (store.value = o));
871
+ .then((o) => (store.value = o))
872
+ .catch((err) => log.catch(err, { objectId }));
822
873
  }
823
874
  }, id);
824
875
  const object = store.value;
@@ -838,12 +889,6 @@ export const SpacePlugin = ({
838
889
  createExtension({
839
890
  id: `${SPACE_PLUGIN}/object-actions`,
840
891
  filter: (node): node is Node<ReactiveEchoObject<any>> => isEchoObject(node.data),
841
- actionGroups: ({ node }) =>
842
- constructObjectActionGroups({
843
- object: node.data,
844
- dispatch,
845
- navigable: state.values.navigableCollections,
846
- }),
847
892
  actions: ({ node }) => constructObjectActions({ node, dispatch }),
848
893
  }),
849
894
 
@@ -889,18 +934,7 @@ export const SpacePlugin = ({
889
934
  };
890
935
  }
891
936
 
892
- const object = toSignal(
893
- (onChange) => {
894
- const timeout = setTimeout(async () => {
895
- await space?.db.query({ id: objectId }).first();
896
- onChange();
897
- });
898
-
899
- return () => clearTimeout(timeout);
900
- },
901
- () => space?.db.getObjectById(objectId),
902
- subjectId,
903
- );
937
+ const [object] = memoizeQuery(space, { id: objectId });
904
938
  if (!object || !subjectId) {
905
939
  return;
906
940
  }
@@ -1002,12 +1036,34 @@ export const SpacePlugin = ({
1002
1036
  return { data: true };
1003
1037
  }
1004
1038
 
1039
+ case SpaceAction.OPEN_CREATE_SPACE: {
1040
+ return {
1041
+ data: true,
1042
+ intents: [
1043
+ [
1044
+ {
1045
+ action: LayoutAction.SET_LAYOUT,
1046
+ data: {
1047
+ element: 'dialog',
1048
+ component: 'dxos.org/plugin/space/CreateSpaceDialog',
1049
+ dialogBlockAlign: 'start',
1050
+ subject: intent.data,
1051
+ },
1052
+ },
1053
+ ],
1054
+ ],
1055
+ };
1056
+ }
1057
+
1005
1058
  case SpaceAction.CREATE: {
1006
- if (!client) {
1059
+ if (!client || !S.is(SpaceForm)(intent.data)) {
1007
1060
  return;
1008
1061
  }
1009
1062
 
1010
- const space = await client.spaces.create(intent.data as PropertiesTypeProps);
1063
+ const space = await client.spaces.create({ name: intent.data.name });
1064
+ if (intent.data.edgeReplication) {
1065
+ await space.internal.setEdgeReplicationPreference(EdgeReplicationSetting.ENABLED);
1066
+ }
1011
1067
  await space.waitUntilReady();
1012
1068
  const collection = create(CollectionType, { objects: [], views: {} });
1013
1069
  space.properties[CollectionType.typename] = collection;
@@ -1023,16 +1079,6 @@ export const SpacePlugin = ({
1023
1079
  activeParts: { main: [space.id] },
1024
1080
  },
1025
1081
  intents: [
1026
- ...(settings.values.onSpaceCreate
1027
- ? [
1028
- [
1029
- { action: settings.values.onSpaceCreate, data: { space } },
1030
- { action: SpaceAction.ADD_OBJECT, data: { target: space } },
1031
- { action: NavigationAction.OPEN },
1032
- { action: NavigationAction.EXPOSE },
1033
- ],
1034
- ]
1035
- : []),
1036
1082
  [
1037
1083
  {
1038
1084
  action: ObservabilityAction.SEND_EVENT,
@@ -1258,6 +1304,25 @@ export const SpacePlugin = ({
1258
1304
  break;
1259
1305
  }
1260
1306
 
1307
+ case SpaceAction.OPEN_CREATE_OBJECT: {
1308
+ return {
1309
+ data: true,
1310
+ intents: [
1311
+ [
1312
+ {
1313
+ action: LayoutAction.SET_LAYOUT,
1314
+ data: {
1315
+ element: 'dialog',
1316
+ component: 'dxos.org/plugin/space/CreateObjectDialog',
1317
+ dialogBlockAlign: 'start',
1318
+ subject: intent.data,
1319
+ },
1320
+ },
1321
+ ],
1322
+ ],
1323
+ };
1324
+ }
1325
+
1261
1326
  case SpaceAction.ADD_OBJECT: {
1262
1327
  const object = intent.data?.object ?? intent.data?.result;
1263
1328
  if (!isReactiveObject(object)) {
@@ -1491,6 +1556,16 @@ export const SpacePlugin = ({
1491
1556
  settings.values.showHidden = intent.data?.state ?? !settings.values.showHidden;
1492
1557
  return { data: true };
1493
1558
  }
1559
+
1560
+ case CollectionAction.CREATE: {
1561
+ const collection = create(CollectionType, {
1562
+ name: intent.data?.name,
1563
+ objects: [],
1564
+ views: {},
1565
+ });
1566
+
1567
+ return { data: collection };
1568
+ }
1494
1569
  }
1495
1570
  },
1496
1571
  },
@@ -7,7 +7,7 @@ import React, { useEffect, useState } from 'react';
7
7
 
8
8
  import { parseIntentPlugin, useResolvePlugin, parseNavigationPlugin, NavigationAction } from '@dxos/app-framework';
9
9
  import { useClient } from '@dxos/react-client';
10
- import { fullyQualifiedId, useQuery } from '@dxos/react-client/echo';
10
+ import { Filter, fullyQualifiedId, useQuery } from '@dxos/react-client/echo';
11
11
  import { Button, Toast, useTranslation } from '@dxos/react-ui';
12
12
  import { getSize, mx } from '@dxos/react-ui-theme';
13
13
 
@@ -25,7 +25,7 @@ export const AwaitingObject = ({ id }: { id: string }) => {
25
25
  const navigationPlugin = useResolvePlugin(parseNavigationPlugin);
26
26
 
27
27
  const client = useClient();
28
- const objects = useQuery(client.spaces);
28
+ const objects = useQuery(client.spaces, Filter.all());
29
29
 
30
30
  useEffect(() => {
31
31
  if (!id) {