@dxos/plugin-space 0.7.2 → 0.7.3-main.2dd075e

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 (95) 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 +1056 -674
  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 +1160 -789
  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 +1056 -674
  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/SpaceSettingsPanel.d.ts.map +1 -1
  44. package/dist/types/src/components/SyncStatus/InlineSyncStatus.d.ts +6 -0
  45. package/dist/types/src/components/SyncStatus/InlineSyncStatus.d.ts.map +1 -0
  46. package/dist/types/src/components/SyncStatus/Space.d.ts +8 -3
  47. package/dist/types/src/components/SyncStatus/Space.d.ts.map +1 -1
  48. package/dist/types/src/components/SyncStatus/SyncStatus.d.ts +3 -2
  49. package/dist/types/src/components/SyncStatus/SyncStatus.d.ts.map +1 -1
  50. package/dist/types/src/components/SyncStatus/SyncStatus.stories.d.ts +1 -2
  51. package/dist/types/src/components/SyncStatus/SyncStatus.stories.d.ts.map +1 -1
  52. package/dist/types/src/components/SyncStatus/SyncStatusDetail.stories.d.ts +8 -0
  53. package/dist/types/src/components/SyncStatus/SyncStatusDetail.stories.d.ts.map +1 -0
  54. package/dist/types/src/components/SyncStatus/index.d.ts +1 -0
  55. package/dist/types/src/components/SyncStatus/index.d.ts.map +1 -1
  56. package/dist/types/src/components/SyncStatus/sync-state.d.ts +5 -1
  57. package/dist/types/src/components/SyncStatus/sync-state.d.ts.map +1 -1
  58. package/dist/types/src/components/index.d.ts +1 -0
  59. package/dist/types/src/components/index.d.ts.map +1 -1
  60. package/dist/types/src/meta.d.ts +5 -0
  61. package/dist/types/src/meta.d.ts.map +1 -1
  62. package/dist/types/src/translations.d.ts +224 -0
  63. package/dist/types/src/translations.d.ts.map +1 -1
  64. package/dist/types/src/types/types.d.ts +14 -14
  65. package/dist/types/src/types/types.d.ts.map +1 -1
  66. package/dist/types/src/util.d.ts +3 -13
  67. package/dist/types/src/util.d.ts.map +1 -1
  68. package/package.json +38 -35
  69. package/src/SpacePlugin.tsx +169 -75
  70. package/src/components/AwaitingObject.tsx +2 -2
  71. package/src/components/CreateDialog/CreateObjectDialog.stories.tsx +83 -0
  72. package/src/components/CreateDialog/CreateObjectDialog.tsx +97 -0
  73. package/src/components/CreateDialog/CreateObjectPanel.tsx +169 -0
  74. package/src/components/CreateDialog/CreateSpaceDialog.tsx +57 -0
  75. package/src/components/CreateDialog/index.ts +6 -0
  76. package/src/components/PopoverRenameObject.tsx +1 -1
  77. package/src/components/SpacePluginSettings.tsx +3 -32
  78. package/src/components/SpaceSettings/SpaceSettingsDialog.tsx +1 -1
  79. package/src/components/SpaceSettings/SpaceSettingsPanel.tsx +2 -6
  80. package/src/components/SyncStatus/InlineSyncStatus.tsx +45 -0
  81. package/src/components/SyncStatus/Space.tsx +30 -6
  82. package/src/components/SyncStatus/SyncStatus.stories.tsx +3 -32
  83. package/src/components/SyncStatus/SyncStatus.tsx +32 -14
  84. package/src/components/SyncStatus/SyncStatusDetail.stories.tsx +83 -0
  85. package/src/components/SyncStatus/index.ts +1 -0
  86. package/src/components/SyncStatus/sync-state.ts +24 -0
  87. package/src/components/index.ts +1 -0
  88. package/src/meta.ts +6 -0
  89. package/src/translations.ts +15 -0
  90. package/src/types/types.ts +20 -16
  91. package/src/util.tsx +51 -141
  92. package/dist/lib/browser/chunk-DJE2HYFV.mjs.map +0 -7
  93. package/dist/lib/node/chunk-FYWGZYJB.cjs.map +0 -7
  94. package/dist/lib/node/chunk-JFDDZI4Y.cjs.map +0 -7
  95. 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;
@@ -611,18 +686,18 @@ export const SpacePlugin = ({
611
686
  filter: (node): node is Node<null> => node.id === SPACES,
612
687
  actions: () => [
613
688
  {
614
- id: SpaceAction.CREATE,
689
+ id: SpaceAction.OPEN_CREATE_SPACE,
615
690
  data: async () => {
616
691
  await dispatch({
617
692
  plugin: SPACE_PLUGIN,
618
- action: SpaceAction.CREATE,
693
+ action: SpaceAction.OPEN_CREATE_SPACE,
619
694
  });
620
695
  },
621
696
  properties: {
622
697
  label: ['create space label', { ns: SPACE_PLUGIN }],
623
698
  icon: 'ph--plus--regular',
624
- disposition: 'item',
625
699
  testId: 'spacePlugin.createSpace',
700
+ disposition: 'item',
626
701
  className: 'border-t border-separator',
627
702
  },
628
703
  },
@@ -637,8 +712,8 @@ export const SpacePlugin = ({
637
712
  properties: {
638
713
  label: ['join space label', { ns: SPACE_PLUGIN }],
639
714
  icon: 'ph--sign-in--regular',
640
- disposition: 'item',
641
715
  testId: 'spacePlugin.joinSpace',
716
+ disposition: 'item',
642
717
  },
643
718
  },
644
719
  ],
@@ -720,16 +795,10 @@ export const SpacePlugin = ({
720
795
  },
721
796
  }),
722
797
 
723
- // Create space actions and action groups.
798
+ // Create space actions.
724
799
  createExtension({
725
800
  id: `${SPACE_PLUGIN}/actions`,
726
801
  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
802
  actions: ({ node }) => {
734
803
  const space = node.data;
735
804
  return constructSpaceActions({
@@ -818,7 +887,8 @@ export const SpacePlugin = ({
818
887
  void space.db
819
888
  .query({ id: objectId })
820
889
  .first()
821
- .then((o) => (store.value = o));
890
+ .then((o) => (store.value = o))
891
+ .catch((err) => log.catch(err, { objectId }));
822
892
  }
823
893
  }, id);
824
894
  const object = store.value;
@@ -838,12 +908,6 @@ export const SpacePlugin = ({
838
908
  createExtension({
839
909
  id: `${SPACE_PLUGIN}/object-actions`,
840
910
  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
911
  actions: ({ node }) => constructObjectActions({ node, dispatch }),
848
912
  }),
849
913
 
@@ -889,18 +953,7 @@ export const SpacePlugin = ({
889
953
  };
890
954
  }
891
955
 
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
- );
956
+ const [object] = memoizeQuery(space, { id: objectId });
904
957
  if (!object || !subjectId) {
905
958
  return;
906
959
  }
@@ -1002,12 +1055,34 @@ export const SpacePlugin = ({
1002
1055
  return { data: true };
1003
1056
  }
1004
1057
 
1058
+ case SpaceAction.OPEN_CREATE_SPACE: {
1059
+ return {
1060
+ data: true,
1061
+ intents: [
1062
+ [
1063
+ {
1064
+ action: LayoutAction.SET_LAYOUT,
1065
+ data: {
1066
+ element: 'dialog',
1067
+ component: 'dxos.org/plugin/space/CreateSpaceDialog',
1068
+ dialogBlockAlign: 'start',
1069
+ subject: intent.data,
1070
+ },
1071
+ },
1072
+ ],
1073
+ ],
1074
+ };
1075
+ }
1076
+
1005
1077
  case SpaceAction.CREATE: {
1006
- if (!client) {
1078
+ if (!client || !S.is(SpaceForm)(intent.data)) {
1007
1079
  return;
1008
1080
  }
1009
1081
 
1010
- const space = await client.spaces.create(intent.data as PropertiesTypeProps);
1082
+ const space = await client.spaces.create({ name: intent.data.name });
1083
+ if (intent.data.edgeReplication) {
1084
+ await space.internal.setEdgeReplicationPreference(EdgeReplicationSetting.ENABLED);
1085
+ }
1011
1086
  await space.waitUntilReady();
1012
1087
  const collection = create(CollectionType, { objects: [], views: {} });
1013
1088
  space.properties[CollectionType.typename] = collection;
@@ -1023,16 +1098,6 @@ export const SpacePlugin = ({
1023
1098
  activeParts: { main: [space.id] },
1024
1099
  },
1025
1100
  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
1101
  [
1037
1102
  {
1038
1103
  action: ObservabilityAction.SEND_EVENT,
@@ -1258,6 +1323,25 @@ export const SpacePlugin = ({
1258
1323
  break;
1259
1324
  }
1260
1325
 
1326
+ case SpaceAction.OPEN_CREATE_OBJECT: {
1327
+ return {
1328
+ data: true,
1329
+ intents: [
1330
+ [
1331
+ {
1332
+ action: LayoutAction.SET_LAYOUT,
1333
+ data: {
1334
+ element: 'dialog',
1335
+ component: 'dxos.org/plugin/space/CreateObjectDialog',
1336
+ dialogBlockAlign: 'start',
1337
+ subject: intent.data,
1338
+ },
1339
+ },
1340
+ ],
1341
+ ],
1342
+ };
1343
+ }
1344
+
1261
1345
  case SpaceAction.ADD_OBJECT: {
1262
1346
  const object = intent.data?.object ?? intent.data?.result;
1263
1347
  if (!isReactiveObject(object)) {
@@ -1491,6 +1575,16 @@ export const SpacePlugin = ({
1491
1575
  settings.values.showHidden = intent.data?.state ?? !settings.values.showHidden;
1492
1576
  return { data: true };
1493
1577
  }
1578
+
1579
+ case CollectionAction.CREATE: {
1580
+ const collection = create(CollectionType, {
1581
+ name: intent.data?.name,
1582
+ objects: [],
1583
+ views: {},
1584
+ });
1585
+
1586
+ return { data: collection };
1587
+ }
1494
1588
  }
1495
1589
  },
1496
1590
  },
@@ -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) {
@@ -0,0 +1,83 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import { type Meta, type StoryObj } from '@storybook/react';
8
+ import React, { useEffect } from 'react';
9
+
10
+ import { create, Filter, useQuery, useSpace } from '@dxos/react-client/echo';
11
+ import { withClientProvider } from '@dxos/react-client/testing';
12
+ import { Dialog } from '@dxos/react-ui';
13
+ import { osTranslations } from '@dxos/shell/react';
14
+ import { withLayout, withTheme } from '@dxos/storybook-utils';
15
+
16
+ import { CreateObjectDialog, type CreateObjectDialogProps } from './CreateObjectDialog';
17
+ import translations from '../../translations';
18
+ import { CollectionType } from '../../types';
19
+
20
+ const Story = (args: CreateObjectDialogProps) => {
21
+ return (
22
+ <Dialog.Root open>
23
+ <Dialog.Overlay blockAlign='start'>
24
+ <CreateObjectDialog {...args} />
25
+ </Dialog.Overlay>
26
+ </Dialog.Root>
27
+ );
28
+ };
29
+
30
+ // TODO(wittjosiah): Story should be for CreateObjectPanel.
31
+ const meta: Meta<typeof CreateObjectDialog> = {
32
+ title: 'plugins/plugin-space/CreateObjectDialog',
33
+ component: CreateObjectDialog,
34
+ render: Story,
35
+ decorators: [
36
+ withClientProvider({ createIdentity: true, createSpace: true, types: [CollectionType] }),
37
+ withTheme,
38
+ withLayout({ tooltips: true }),
39
+ ],
40
+ parameters: { translations: [...translations, osTranslations] },
41
+ args: {
42
+ schemas: [CollectionType],
43
+ },
44
+ };
45
+
46
+ export default meta;
47
+
48
+ export const Default: StoryObj<typeof CreateObjectDialog> = {};
49
+
50
+ export const Typename: StoryObj<typeof CreateObjectDialog> = {
51
+ args: { typename: CollectionType.typename },
52
+ };
53
+
54
+ export const TargetSpace: StoryObj<typeof CreateObjectDialog> = {
55
+ render: (args) => {
56
+ const space = useSpace();
57
+
58
+ if (!space) {
59
+ return <></>;
60
+ }
61
+
62
+ return <Story {...args} target={space} />;
63
+ },
64
+ };
65
+
66
+ export const TargetCollection: StoryObj<typeof CreateObjectDialog> = {
67
+ render: (args) => {
68
+ const space = useSpace();
69
+ const [collection] = useQuery(space, Filter.schema(CollectionType));
70
+
71
+ useEffect(() => {
72
+ if (space) {
73
+ space.db.add(create(CollectionType, { name: 'My Collection', objects: [], views: {} }));
74
+ }
75
+ }, [space]);
76
+
77
+ if (!collection) {
78
+ return <></>;
79
+ }
80
+
81
+ return <Story {...args} target={collection} />;
82
+ },
83
+ };