@dxos/plugin-space 0.6.14-staging.54a8bab → 0.6.14-staging.7b35391

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 (84) hide show
  1. package/dist/lib/browser/{chunk-AVLRQF6L.mjs → chunk-DJE2HYFV.mjs} +3 -2
  2. package/dist/lib/browser/{chunk-AVLRQF6L.mjs.map → chunk-DJE2HYFV.mjs.map} +2 -2
  3. package/dist/lib/browser/{chunk-WZAM3FNP.mjs → chunk-OWZKSWMX.mjs} +1 -1
  4. package/dist/lib/browser/{chunk-WZAM3FNP.mjs.map → chunk-OWZKSWMX.mjs.map} +2 -2
  5. package/dist/lib/browser/index.mjs +586 -576
  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 +1 -1
  9. package/dist/lib/browser/types/index.mjs +1 -1
  10. package/dist/lib/node/{chunk-HTAM5LQD.cjs → chunk-FYWGZYJB.cjs} +4 -4
  11. package/dist/lib/node/{chunk-HTAM5LQD.cjs.map → chunk-FYWGZYJB.cjs.map} +2 -2
  12. package/dist/lib/node/{chunk-P4XUXM7Y.cjs → chunk-JFDDZI4Y.cjs} +6 -5
  13. package/dist/lib/node/{chunk-P4XUXM7Y.cjs.map → chunk-JFDDZI4Y.cjs.map} +2 -2
  14. package/dist/lib/node/index.cjs +816 -805
  15. package/dist/lib/node/index.cjs.map +4 -4
  16. package/dist/lib/node/meta.cjs +5 -5
  17. package/dist/lib/node/meta.cjs.map +1 -1
  18. package/dist/lib/node/meta.json +1 -1
  19. package/dist/lib/node/types/index.cjs +11 -11
  20. package/dist/lib/node/types/index.cjs.map +1 -1
  21. package/dist/lib/node-esm/{chunk-YPQGKWHJ.mjs → chunk-DVUZ7A7G.mjs} +3 -2
  22. package/dist/lib/node-esm/{chunk-YPQGKWHJ.mjs.map → chunk-DVUZ7A7G.mjs.map} +2 -2
  23. package/dist/lib/node-esm/{chunk-TRJKV4PK.mjs → chunk-MCEAI4CV.mjs} +1 -1
  24. package/dist/lib/node-esm/{chunk-TRJKV4PK.mjs.map → chunk-MCEAI4CV.mjs.map} +2 -2
  25. package/dist/lib/node-esm/index.mjs +586 -576
  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 +1 -1
  29. package/dist/lib/node-esm/types/index.mjs +1 -1
  30. package/dist/types/src/SpacePlugin.d.ts +9 -1
  31. package/dist/types/src/SpacePlugin.d.ts.map +1 -1
  32. package/dist/types/src/components/JoinDialog.d.ts +7 -0
  33. package/dist/types/src/components/JoinDialog.d.ts.map +1 -0
  34. package/dist/types/src/components/ShareSpaceButton.d.ts +3 -2
  35. package/dist/types/src/components/ShareSpaceButton.d.ts.map +1 -1
  36. package/dist/types/src/components/{SpaceSettings.d.ts → SpacePluginSettings.d.ts} +2 -2
  37. package/dist/types/src/components/SpacePluginSettings.d.ts.map +1 -0
  38. package/dist/types/src/components/SpaceSettings/SpaceSettingsDialog.d.ts +10 -0
  39. package/dist/types/src/components/SpaceSettings/SpaceSettingsDialog.d.ts.map +1 -0
  40. package/dist/types/src/components/SpaceSettings/SpaceSettingsDialog.stories.d.ts +7 -0
  41. package/dist/types/src/components/SpaceSettings/SpaceSettingsDialog.stories.d.ts.map +1 -0
  42. package/dist/types/src/components/SpaceSettings/SpaceSettingsPanel.d.ts.map +1 -0
  43. package/dist/types/src/components/SpaceSettings/SpaceSettingsPanel.stories.d.ts +7 -0
  44. package/dist/types/src/components/SpaceSettings/SpaceSettingsPanel.stories.d.ts.map +1 -0
  45. package/dist/types/src/components/SpaceSettings/index.d.ts +3 -0
  46. package/dist/types/src/components/SpaceSettings/index.d.ts.map +1 -0
  47. package/dist/types/src/components/index.d.ts +2 -2
  48. package/dist/types/src/components/index.d.ts.map +1 -1
  49. package/dist/types/src/meta.d.ts +3 -2
  50. package/dist/types/src/meta.d.ts.map +1 -1
  51. package/dist/types/src/translations.d.ts +4 -0
  52. package/dist/types/src/translations.d.ts.map +1 -1
  53. package/dist/types/src/types/types.d.ts +5 -0
  54. package/dist/types/src/types/types.d.ts.map +1 -1
  55. package/dist/types/src/util.d.ts +8 -4
  56. package/dist/types/src/util.d.ts.map +1 -1
  57. package/package.json +35 -34
  58. package/src/SpacePlugin.tsx +225 -146
  59. package/src/components/AwaitingObject.tsx +1 -1
  60. package/src/components/JoinDialog.tsx +100 -0
  61. package/src/components/ShareSpaceButton.tsx +10 -6
  62. package/src/components/{SpaceSettings.tsx → SpacePluginSettings.tsx} +1 -1
  63. package/src/components/SpaceSettings/SpaceSettingsDialog.stories.tsx +44 -0
  64. package/src/components/SpaceSettings/SpaceSettingsDialog.tsx +103 -0
  65. package/src/components/SpaceSettings/SpaceSettingsPanel.stories.tsx +32 -0
  66. package/src/components/{SpaceSettingsPanel.tsx → SpaceSettings/SpaceSettingsPanel.tsx} +15 -20
  67. package/src/components/SpaceSettings/index.ts +6 -0
  68. package/src/components/index.ts +2 -2
  69. package/src/meta.ts +2 -1
  70. package/src/translations.ts +4 -0
  71. package/src/types/types.ts +6 -0
  72. package/src/util.tsx +51 -23
  73. package/dist/types/src/components/SpaceMain/SpaceMain.d.ts +0 -10
  74. package/dist/types/src/components/SpaceMain/SpaceMain.d.ts.map +0 -1
  75. package/dist/types/src/components/SpaceMain/SpaceMembersSection.d.ts +0 -6
  76. package/dist/types/src/components/SpaceMain/SpaceMembersSection.d.ts.map +0 -1
  77. package/dist/types/src/components/SpaceMain/index.d.ts +0 -2
  78. package/dist/types/src/components/SpaceMain/index.d.ts.map +0 -1
  79. package/dist/types/src/components/SpaceSettings.d.ts.map +0 -1
  80. package/dist/types/src/components/SpaceSettingsPanel.d.ts.map +0 -1
  81. package/src/components/SpaceMain/SpaceMain.tsx +0 -60
  82. package/src/components/SpaceMain/SpaceMembersSection.tsx +0 -205
  83. package/src/components/SpaceMain/index.ts +0 -5
  84. /package/dist/types/src/components/{SpaceSettingsPanel.d.ts → SpaceSettings/SpaceSettingsPanel.d.ts} +0 -0
@@ -16,6 +16,7 @@ import {
16
16
  type Plugin,
17
17
  type PluginDefinition,
18
18
  Surface,
19
+ findPlugin,
19
20
  firstIdInPart,
20
21
  openIds,
21
22
  parseGraphPlugin,
@@ -28,6 +29,7 @@ import {
28
29
  import { EventSubscriptions, type Trigger, type UnsubscribeCallback } from '@dxos/async';
29
30
  import { type HasId, isReactiveObject } from '@dxos/echo-schema';
30
31
  import { scheduledEffect } from '@dxos/echo-signals/core';
32
+ import { invariant } from '@dxos/invariant';
31
33
  import { LocalStorageStore } from '@dxos/local-storage';
32
34
  import { log } from '@dxos/log';
33
35
  import { Migrations } from '@dxos/migrations';
@@ -53,8 +55,7 @@ import {
53
55
  parseId,
54
56
  FQ_ID_LENGTH,
55
57
  } from '@dxos/react-client/echo';
56
- import { Dialog } from '@dxos/react-ui';
57
- import { ClipboardProvider, InvitationManager, type InvitationManagerProps, osTranslations } from '@dxos/shell/react';
58
+ import { type JoinPanelProps, osTranslations } from '@dxos/shell/react';
58
59
  import { ComplexMap, nonNullable, reduceGroupBy } from '@dxos/util';
59
60
 
60
61
  import {
@@ -62,6 +63,7 @@ import {
62
63
  CollectionMain,
63
64
  CollectionSection,
64
65
  DefaultObjectSettings,
66
+ JoinDialog,
65
67
  MenuFooter,
66
68
  PopoverRenameObject,
67
69
  PopoverRenameSpace,
@@ -69,9 +71,11 @@ import {
69
71
  SmallPresence,
70
72
  SmallPresenceLive,
71
73
  SpacePresence,
72
- SpaceSettings,
74
+ SpacePluginSettings,
73
75
  SpaceSettingsPanel,
74
76
  SyncStatus,
77
+ SpaceSettingsDialog,
78
+ type SpaceSettingsDialogProps,
75
79
  } from './components';
76
80
  import meta, { SPACE_PLUGIN, SpaceAction } from './meta';
77
81
  import translations from './translations';
@@ -93,6 +97,7 @@ import {
93
97
  } from './util';
94
98
 
95
99
  const ACTIVE_NODE_BROADCAST_INTERVAL = 30_000;
100
+ const WAIT_FOR_OBJECT_TIMEOUT = 1000;
96
101
  const SPACE_MAX_OBJECTS = 500;
97
102
  // https://stackoverflow.com/a/19016910
98
103
  const DIRECTORY_TYPE = 'text/directory';
@@ -101,6 +106,16 @@ export const parseSpacePlugin = (plugin?: Plugin) =>
101
106
  Array.isArray((plugin?.provides as any).space?.enabled) ? (plugin as Plugin<SpacePluginProvides>) : undefined;
102
107
 
103
108
  export type SpacePluginOptions = {
109
+ /**
110
+ * Base URL for the invitation link.
111
+ */
112
+ invitationUrl?: string;
113
+
114
+ /**
115
+ * Query parameter for the invitation code.
116
+ */
117
+ invitationParam?: string;
118
+
104
119
  /**
105
120
  * Fired when first run logic should be executed.
106
121
  *
@@ -120,6 +135,8 @@ export type SpacePluginOptions = {
120
135
  };
121
136
 
122
137
  export const SpacePlugin = ({
138
+ invitationUrl = window.location.origin,
139
+ invitationParam = 'spaceInvitationCode',
123
140
  firstRun,
124
141
  onFirstRun,
125
142
  }: SpacePluginOptions = {}): PluginDefinition<SpacePluginProvides> => {
@@ -133,6 +150,7 @@ export const SpacePlugin = ({
133
150
  // TODO(wittjosiah): Stop using (Complex)Map inside reactive object.
134
151
  viewersByIdentity: new ComplexMap(PublicKey.hash),
135
152
  sdkMigrationRunning: {},
153
+ navigableCollections: false,
136
154
  });
137
155
  const subscriptions = new EventSubscriptions();
138
156
  const spaceSubscriptions = new EventSubscriptions();
@@ -145,6 +163,12 @@ export const SpacePlugin = ({
145
163
  let navigationPlugin: Plugin<LocationProvides> | undefined;
146
164
  let attentionPlugin: Plugin<AttentionPluginProvides> | undefined;
147
165
 
166
+ const createSpaceInvitationUrl = (invitationCode: string) => {
167
+ const baseUrl = new URL(invitationUrl);
168
+ baseUrl.searchParams.set(invitationParam, invitationCode);
169
+ return baseUrl.toString();
170
+ };
171
+
148
172
  const onSpaceReady = async () => {
149
173
  if (!clientPlugin || !intentPlugin || !graphPlugin || !navigationPlugin || !layoutPlugin || !attentionPlugin) {
150
174
  return;
@@ -186,7 +210,18 @@ export const SpacePlugin = ({
186
210
 
187
211
  const node = graph.findNode(soloPart.id);
188
212
  if (!node && soloPart.id.length === FQ_ID_LENGTH) {
189
- void dispatch({ plugin: SPACE_PLUGIN, action: SpaceAction.WAIT_FOR_OBJECT, data: { id: soloPart.id } });
213
+ const timeout = setTimeout(async () => {
214
+ const node = graph.findNode(soloPart.id);
215
+ if (!node) {
216
+ await dispatch({
217
+ plugin: SPACE_PLUGIN,
218
+ action: SpaceAction.WAIT_FOR_OBJECT,
219
+ data: { id: soloPart.id },
220
+ });
221
+ }
222
+ }, WAIT_FOR_OBJECT_TIMEOUT);
223
+
224
+ return () => clearTimeout(timeout);
190
225
  }
191
226
  },
192
227
  ),
@@ -315,6 +350,12 @@ export const SpacePlugin = ({
315
350
  settings.prop({ key: 'showHidden', type: LocalStorageStore.bool({ allowUndefined: true }) });
316
351
  state.prop({ key: 'spaceNames', type: LocalStorageStore.json<Record<string, string>>() });
317
352
 
353
+ // TODO(wittjosiah): Hardcoded due to circular dependency.
354
+ // Should be based on a provides interface.
355
+ if (findPlugin(plugins, 'dxos.org/plugin/stack')) {
356
+ state.values.navigableCollections = true;
357
+ }
358
+
318
359
  graphPlugin = resolvePlugin(plugins, parseGraphPlugin);
319
360
  layoutPlugin = resolvePlugin(plugins, parseLayoutPlugin);
320
361
  navigationPlugin = resolvePlugin(plugins, parseNavigationPlugin);
@@ -412,14 +453,15 @@ export const SpacePlugin = ({
412
453
  { node: <DefaultObjectSettings object={data.subject} />, disposition: 'fallback' }
413
454
  ) : null;
414
455
  case 'dialog':
415
- if (data.component === 'dxos.org/plugin/space/InvitationManagerDialog') {
456
+ if (data.component === 'dxos.org/plugin/space/SpaceSettingsDialog') {
416
457
  return (
417
- <Dialog.Content>
418
- <ClipboardProvider>
419
- <InvitationManager active {...(data.subject as InvitationManagerProps)} />
420
- </ClipboardProvider>
421
- </Dialog.Content>
458
+ <SpaceSettingsDialog
459
+ {...(data.subject as SpaceSettingsDialogProps)}
460
+ createInvitationUrl={createSpaceInvitationUrl}
461
+ />
422
462
  );
463
+ } else if (data.component === 'dxos.org/plugin/space/JoinDialog') {
464
+ return <JoinDialog {...(data.subject as JoinPanelProps)} />;
423
465
  }
424
466
  return null;
425
467
  case 'popover': {
@@ -462,7 +504,7 @@ export const SpacePlugin = ({
462
504
  node: (
463
505
  <>
464
506
  <SpacePresence object={object} />
465
- {space.properties[COMPOSER_SPACE_LOCK] ? null : <ShareSpaceButton spaceId={space.id} />}
507
+ {space.properties[COMPOSER_SPACE_LOCK] ? null : <ShareSpaceButton space={space} />}
466
508
  </>
467
509
  ),
468
510
  disposition: 'hoist',
@@ -472,7 +514,7 @@ export const SpacePlugin = ({
472
514
  case 'section':
473
515
  return data.object instanceof CollectionType ? <CollectionSection collection={data.object} /> : null;
474
516
  case 'settings':
475
- return data.plugin === meta.id ? <SpaceSettings settings={settings.values} /> : null;
517
+ return data.plugin === meta.id ? <SpacePluginSettings settings={settings.values} /> : null;
476
518
  case 'menu-footer':
477
519
  if (isEchoObject(data.object)) {
478
520
  return <MenuFooter object={data.object} />;
@@ -533,6 +575,7 @@ export const SpacePlugin = ({
533
575
  label: ['spaces label', { ns: SPACE_PLUGIN }],
534
576
  testId: 'spacePlugin.spaces',
535
577
  role: 'branch',
578
+ disabled: true,
536
579
  childrenPersistenceClass: 'echo',
537
580
  onRearrangeChildren: async (nextOrder: Space[]) => {
538
581
  // NOTE: This is needed to ensure order is updated by next animation frame.
@@ -567,43 +610,32 @@ export const SpacePlugin = ({
567
610
  {
568
611
  id: SpaceAction.CREATE,
569
612
  data: async () => {
570
- await dispatch([
571
- {
572
- plugin: SPACE_PLUGIN,
573
- action: SpaceAction.CREATE,
574
- },
575
- {
576
- action: NavigationAction.OPEN,
577
- },
578
- ]);
613
+ await dispatch({
614
+ plugin: SPACE_PLUGIN,
615
+ action: SpaceAction.CREATE,
616
+ });
579
617
  },
580
618
  properties: {
581
619
  label: ['create space label', { ns: SPACE_PLUGIN }],
582
620
  icon: 'ph--plus--regular',
583
621
  disposition: 'item',
584
622
  testId: 'spacePlugin.createSpace',
585
- className: 'pbs-4',
623
+ className: 'border-t border-separator',
586
624
  },
587
625
  },
588
626
  {
589
627
  id: SpaceAction.JOIN,
590
628
  data: async () => {
591
- await dispatch([
592
- {
593
- plugin: SPACE_PLUGIN,
594
- action: SpaceAction.JOIN,
595
- },
596
- {
597
- action: NavigationAction.OPEN,
598
- },
599
- ]);
629
+ await dispatch({
630
+ plugin: SPACE_PLUGIN,
631
+ action: SpaceAction.JOIN,
632
+ });
600
633
  },
601
634
  properties: {
602
635
  label: ['join space label', { ns: SPACE_PLUGIN }],
603
636
  icon: 'ph--sign-in--regular',
604
637
  disposition: 'item',
605
638
  testId: 'spacePlugin.joinSpace',
606
- className: 'pbe-4',
607
639
  },
608
640
  },
609
641
  ],
@@ -622,26 +654,30 @@ export const SpacePlugin = ({
622
654
  return;
623
655
  }
624
656
 
625
- const [spacesOrder] = memoizeQuery(client.spaces.default, Filter.schema(Expando, { key: SHARED }));
626
- const order: string[] = spacesOrder?.order ?? [];
627
- const orderMap = new Map(order.map((id, index) => [id, index]));
628
- return [
629
- ...spaces
630
- .filter((space) => orderMap.has(space.id))
631
- .sort((a, b) => orderMap.get(a.id)! - orderMap.get(b.id)!),
632
- ...spaces.filter((space) => !orderMap.has(space.id)),
633
- ]
634
- .filter((space) =>
635
- settings.values.showHidden ? true : space.state.get() !== SpaceState.SPACE_INACTIVE,
636
- )
637
- .map((space) =>
638
- constructSpaceNode({
639
- space,
640
- personal: space === client.spaces.default,
641
- namesCache: state.values.spaceNames,
642
- resolve,
643
- }),
644
- );
657
+ // TODO(wittjosiah): During client reset, accessing default space throws.
658
+ try {
659
+ const [spacesOrder] = memoizeQuery(client.spaces.default, Filter.schema(Expando, { key: SHARED }));
660
+ const order: string[] = spacesOrder?.order ?? [];
661
+ const orderMap = new Map(order.map((id, index) => [id, index]));
662
+ return [
663
+ ...spaces
664
+ .filter((space) => orderMap.has(space.id))
665
+ .sort((a, b) => orderMap.get(a.id)! - orderMap.get(b.id)!),
666
+ ...spaces.filter((space) => !orderMap.has(space.id)),
667
+ ]
668
+ .filter((space) =>
669
+ settings.values.showHidden ? true : space.state.get() !== SpaceState.SPACE_INACTIVE,
670
+ )
671
+ .map((space) =>
672
+ constructSpaceNode({
673
+ space,
674
+ navigable: state.values.navigableCollections,
675
+ personal: space === client.spaces.default,
676
+ namesCache: state.values.spaceNames,
677
+ resolve,
678
+ }),
679
+ );
680
+ } catch {}
645
681
  },
646
682
  }),
647
683
 
@@ -655,12 +691,12 @@ export const SpacePlugin = ({
655
691
  return;
656
692
  }
657
693
 
658
- const state = toSignal(
694
+ const spaceState = toSignal(
659
695
  (onChange) => space.state.subscribe(() => onChange()).unsubscribe,
660
696
  () => space.state.get(),
661
697
  space.id,
662
698
  );
663
- if (state !== SpaceState.SPACE_READY) {
699
+ if (spaceState !== SpaceState.SPACE_READY) {
664
700
  return;
665
701
  }
666
702
 
@@ -675,7 +711,7 @@ export const SpacePlugin = ({
675
711
  return;
676
712
  }
677
713
 
678
- return createObjectNode({ object, space, resolve });
714
+ return createObjectNode({ object, space, resolve, navigable: state.values.navigableCollections });
679
715
  },
680
716
  }),
681
717
 
@@ -683,7 +719,12 @@ export const SpacePlugin = ({
683
719
  createExtension({
684
720
  id: `${SPACE_PLUGIN}/actions`,
685
721
  filter: (node): node is Node<Space> => isSpace(node.data),
686
- actionGroups: ({ node }) => constructSpaceActionGroups({ space: node.data, dispatch }),
722
+ actionGroups: ({ node }) =>
723
+ constructSpaceActionGroups({
724
+ space: node.data,
725
+ dispatch,
726
+ navigable: state.values.navigableCollections,
727
+ }),
687
728
  actions: ({ node }) => {
688
729
  const space = node.data;
689
730
  return constructSpaceActions({
@@ -701,12 +742,12 @@ export const SpacePlugin = ({
701
742
  filter: (node): node is Node<Space> => isSpace(node.data),
702
743
  connector: ({ node }) => {
703
744
  const space = node.data;
704
- const state = toSignal(
745
+ const spaceState = toSignal(
705
746
  (onChange) => space.state.subscribe(() => onChange()).unsubscribe,
706
747
  () => space.state.get(),
707
748
  space.id,
708
749
  );
709
- if (state !== SpaceState.SPACE_READY) {
750
+ if (spaceState !== SpaceState.SPACE_READY) {
710
751
  return;
711
752
  }
712
753
 
@@ -717,7 +758,9 @@ export const SpacePlugin = ({
717
758
 
718
759
  return collection.objects
719
760
  .filter(nonNullable)
720
- .map((object) => createObjectNode({ object, space, resolve }))
761
+ .map((object) =>
762
+ createObjectNode({ object, space, resolve, navigable: state.values.navigableCollections }),
763
+ )
721
764
  .filter(nonNullable);
722
765
  },
723
766
  }),
@@ -726,7 +769,12 @@ export const SpacePlugin = ({
726
769
  createExtension({
727
770
  id: `${SPACE_PLUGIN}/object-actions`,
728
771
  filter: (node): node is Node<EchoReactiveObject<any>> => isEchoObject(node.data),
729
- actionGroups: ({ node }) => constructObjectActionGroups({ object: node.data, dispatch }),
772
+ actionGroups: ({ node }) =>
773
+ constructObjectActionGroups({
774
+ object: node.data,
775
+ dispatch,
776
+ navigable: state.values.navigableCollections,
777
+ }),
730
778
  actions: ({ node }) => constructObjectActions({ node, dispatch }),
731
779
  }),
732
780
 
@@ -743,7 +791,9 @@ export const SpacePlugin = ({
743
791
 
744
792
  return collection.objects
745
793
  .filter(nonNullable)
746
- .map((object) => createObjectNode({ object, space, resolve }))
794
+ .map((object) =>
795
+ createObjectNode({ object, space, resolve, navigable: state.values.navigableCollections }),
796
+ )
747
797
  .filter(nonNullable);
748
798
  },
749
799
  }),
@@ -923,6 +973,7 @@ export const SpacePlugin = ({
923
973
  [
924
974
  { action: settings.values.onSpaceCreate, data: { space } },
925
975
  { action: SpaceAction.ADD_OBJECT, data: { target: space } },
976
+ { action: NavigationAction.OPEN },
926
977
  { action: NavigationAction.EXPOSE },
927
978
  ],
928
979
  ]
@@ -943,69 +994,60 @@ export const SpacePlugin = ({
943
994
  }
944
995
 
945
996
  case SpaceAction.JOIN: {
946
- if (client) {
947
- const { space } = await client.shell.joinSpace({ invitationCode: intent.data?.invitationCode });
948
- if (space) {
949
- return {
950
- data: {
951
- space,
952
- id: space.id,
953
- activeParts: { main: [space.id] },
997
+ return {
998
+ data: true,
999
+ intents: [
1000
+ [
1001
+ {
1002
+ action: LayoutAction.SET_LAYOUT,
1003
+ data: {
1004
+ element: 'dialog',
1005
+ component: 'dxos.org/plugin/space/JoinDialog',
1006
+ dialogBlockAlign: 'start',
1007
+ subject: {
1008
+ initialInvitationCode: intent.data?.invitationCode,
1009
+ } satisfies Partial<JoinPanelProps>,
1010
+ },
954
1011
  },
955
- intents: [
956
- [
957
- {
958
- action: LayoutAction.SET_LAYOUT,
959
- data: {
960
- element: 'toast',
961
- subject: {
962
- id: `${SPACE_PLUGIN}/join-success`,
963
- duration: 10_000,
964
- title: translations[0]['en-US'][SPACE_PLUGIN]['join success label'],
965
- closeLabel: translations[0]['en-US'][SPACE_PLUGIN]['dismiss label'],
966
- },
967
- },
968
- },
969
- ],
970
- [
971
- {
972
- action: ObservabilityAction.SEND_EVENT,
973
- data: {
974
- name: 'space.join',
975
- properties: {
976
- spaceId: space.id,
977
- },
978
- },
979
- },
980
- ],
981
- ],
982
- };
983
- }
984
- }
1012
+ ],
1013
+ ],
1014
+ };
985
1015
  break;
986
1016
  }
987
1017
 
988
1018
  case SpaceAction.SHARE: {
989
- const spaceId = intent.data?.spaceId;
990
- if (clientPlugin && typeof spaceId === 'string') {
991
- if (!navigationPlugin?.provides.location.active) {
992
- return;
993
- }
994
- const target = firstIdInPart(navigationPlugin?.provides.location.active, 'main');
995
- const result = await clientPlugin.provides.client.shell.shareSpace({ spaceId, target });
1019
+ const space = intent.data?.space;
1020
+ if (isSpace(space) && !space.properties[COMPOSER_SPACE_LOCK]) {
1021
+ const active = navigationPlugin?.provides.location.active;
1022
+ const mode = layoutPlugin?.provides.layout.layoutMode;
1023
+ const target = active ? firstIdInPart(active, mode === 'solo' ? 'solo' : 'main') : undefined;
1024
+
996
1025
  return {
997
- data: result,
1026
+ data: true,
998
1027
  intents: [
1028
+ [
1029
+ {
1030
+ action: LayoutAction.SET_LAYOUT,
1031
+ data: {
1032
+ element: 'dialog',
1033
+ component: 'dxos.org/plugin/space/SpaceSettingsDialog',
1034
+ dialogBlockAlign: 'start',
1035
+ subject: {
1036
+ space,
1037
+ target,
1038
+ initialTab: 'members',
1039
+ createInvitationUrl: createSpaceInvitationUrl,
1040
+ } satisfies Partial<SpaceSettingsDialogProps>,
1041
+ },
1042
+ },
1043
+ ],
999
1044
  [
1000
1045
  {
1001
1046
  action: ObservabilityAction.SEND_EVENT,
1002
1047
  data: {
1003
1048
  name: 'space.share',
1004
1049
  properties: {
1005
- spaceId,
1006
- members: result.members?.length,
1007
- error: result.error?.message,
1008
- cancelled: result.cancelled,
1050
+ space: space.id,
1009
1051
  },
1010
1052
  },
1011
1053
  },
@@ -1086,6 +1128,33 @@ export const SpacePlugin = ({
1086
1128
  break;
1087
1129
  }
1088
1130
 
1131
+ case SpaceAction.OPEN_SETTINGS: {
1132
+ const space = intent.data?.space;
1133
+ if (isSpace(space)) {
1134
+ return {
1135
+ data: true,
1136
+ intents: [
1137
+ [
1138
+ {
1139
+ action: LayoutAction.SET_LAYOUT,
1140
+ data: {
1141
+ element: 'dialog',
1142
+ component: 'dxos.org/plugin/space/SpaceSettingsDialog',
1143
+ dialogBlockAlign: 'start',
1144
+ subject: {
1145
+ space,
1146
+ initialTab: 'settings',
1147
+ createInvitationUrl: createSpaceInvitationUrl,
1148
+ } satisfies Partial<SpaceSettingsDialogProps>,
1149
+ },
1150
+ },
1151
+ ],
1152
+ ],
1153
+ };
1154
+ }
1155
+ break;
1156
+ }
1157
+
1089
1158
  case SpaceAction.OPEN: {
1090
1159
  const space = intent.data?.space;
1091
1160
  if (isSpace(space)) {
@@ -1217,10 +1286,13 @@ export const SpacePlugin = ({
1217
1286
  };
1218
1287
  }
1219
1288
 
1220
- case SpaceAction.REMOVE_OBJECT: {
1221
- const object = intent.data?.object ?? intent.data?.result;
1222
- const space = getSpace(object);
1223
- if (!(isEchoObject(object) && space)) {
1289
+ case SpaceAction.REMOVE_OBJECTS: {
1290
+ const objects = intent.data?.objects ?? intent.data?.result;
1291
+ invariant(Array.isArray(objects));
1292
+
1293
+ // All objects must be a member of the same space.
1294
+ const space = getSpace(objects[0]);
1295
+ if (!space || !objects.every((obj) => isEchoObject(obj) && getSpace(obj) === space)) {
1224
1296
  return;
1225
1297
  }
1226
1298
 
@@ -1229,23 +1301,22 @@ export const SpacePlugin = ({
1229
1301
  const openObjectIds = new Set<string>(openIds(activeParts ?? {}));
1230
1302
 
1231
1303
  if (!intent.undo && resolve) {
1232
- // Capture the current state for undo.
1233
1304
  const parentCollection = intent.data?.collection ?? space.properties[CollectionType.typename];
1234
- const nestedObjects = await getNestedObjects(object, resolve);
1305
+ const nestedObjectsList = await Promise.all(objects.map((obj) => getNestedObjects(obj, resolve)));
1306
+
1235
1307
  const deletionData = {
1236
- object,
1308
+ objects,
1237
1309
  parentCollection,
1238
- index:
1239
- parentCollection instanceof CollectionType
1240
- ? parentCollection.objects.indexOf(object as Expando)
1241
- : -1,
1242
- nestedObjects,
1243
- wasActive: [object, ...nestedObjects]
1310
+ indices: objects.map((obj) =>
1311
+ parentCollection instanceof CollectionType ? parentCollection.objects.indexOf(obj as Expando) : -1,
1312
+ ),
1313
+ nestedObjectsList,
1314
+ wasActive: objects
1315
+ .flatMap((obj, i) => [obj, ...nestedObjectsList[i]])
1244
1316
  .map((obj) => fullyQualifiedId(obj))
1245
1317
  .filter((id) => openObjectIds.has(id)),
1246
1318
  };
1247
1319
 
1248
- // If the item is active, navigate to "nowhere" to avoid navigating to a removed item.
1249
1320
  if (deletionData.wasActive.length > 0) {
1250
1321
  await intentPlugin?.provides.intent.dispatch({
1251
1322
  action: NavigationAction.CLOSE,
@@ -1258,48 +1329,56 @@ export const SpacePlugin = ({
1258
1329
  });
1259
1330
  }
1260
1331
 
1261
- if (parentCollection instanceof CollectionType) {
1262
- // TODO(Zan): Is there a nicer way to do this without casting to Expando?
1263
- const index = parentCollection.objects.indexOf(object as Expando);
1264
- if (index !== -1) {
1265
- parentCollection.objects.splice(index, 1);
1266
- }
1332
+ if (deletionData.parentCollection instanceof CollectionType) {
1333
+ [...deletionData.indices]
1334
+ .sort((a, b) => b - a)
1335
+ .forEach((index: number) => {
1336
+ if (index !== -1) {
1337
+ deletionData.parentCollection.objects.splice(index, 1);
1338
+ }
1339
+ });
1267
1340
  }
1268
1341
 
1269
- // If the object is a collection, also delete its nested objects.
1270
- deletionData.nestedObjects.forEach((obj) => {
1342
+ deletionData.nestedObjectsList.flat().forEach((obj) => {
1271
1343
  space.db.remove(obj);
1272
1344
  });
1273
- space.db.remove(object);
1345
+ objects.forEach((obj) => space.db.remove(obj));
1274
1346
 
1275
- const undoMessageKey =
1276
- object instanceof CollectionType ? 'collection deleted label' : 'object deleted label';
1347
+ const undoMessageKey = objects.some((obj) => obj instanceof CollectionType)
1348
+ ? 'collection deleted label'
1349
+ : objects.length > 1
1350
+ ? 'objects deleted label'
1351
+ : 'object deleted label';
1277
1352
 
1278
1353
  return {
1279
1354
  data: true,
1280
1355
  undoable: {
1281
- // Consider using a translation key here.
1356
+ // TODO(ZaymonFC): Pluralize if more than one object.
1282
1357
  message: translations[0]['en-US'][SPACE_PLUGIN][undoMessageKey],
1283
1358
  data: deletionData,
1284
1359
  },
1285
1360
  };
1286
1361
  } else {
1287
1362
  const undoData = intent.data;
1288
- if (undoData && isEchoObject(undoData.object) && undoData.parentCollection instanceof CollectionType) {
1363
+ if (
1364
+ undoData?.objects?.length &&
1365
+ undoData.objects.every(isEchoObject) &&
1366
+ undoData.parentCollection instanceof CollectionType
1367
+ ) {
1289
1368
  // Restore the object to the space.
1290
- const restoredObject = space.db.add(undoData.object);
1369
+ const restoredObjects = undoData.objects.map((obj: Expando) => space.db.add(obj));
1291
1370
 
1292
- // Restore nested objects if the object was a collection.
1293
- undoData.nestedObjects.forEach((obj: Expando) => {
1371
+ // Restore nested objects to the space.
1372
+ undoData.nestedObjectsList.flat().forEach((obj: Expando) => {
1294
1373
  space.db.add(obj);
1295
1374
  });
1296
1375
 
1297
- // Restore the object to its original position in the collection.
1298
- if (undoData.index !== -1) {
1299
- undoData.parentCollection.objects.splice(undoData.index, 0, restoredObject as Expando);
1300
- }
1376
+ undoData.indices.forEach((index: number, i: number) => {
1377
+ if (index !== -1) {
1378
+ undoData.parentCollection.objects.splice(index, 0, restoredObjects[i] as Expando);
1379
+ }
1380
+ });
1301
1381
 
1302
- // Restore active state if it was active before removal.
1303
1382
  if (undoData.wasActive.length > 0) {
1304
1383
  await intentPlugin?.provides.intent.dispatch({
1305
1384
  action: NavigationAction.OPEN,
@@ -43,7 +43,7 @@ export const AwaitingObject = ({ id }: { id: string }) => {
43
43
  if (objects.findIndex((object) => fullyQualifiedId(object) === id) > -1) {
44
44
  setFound(true);
45
45
 
46
- if (navigationPlugin?.provides.location.active === id) {
46
+ if (navigationPlugin?.provides.location.active.solo?.[0].id === id) {
47
47
  setOpen(false);
48
48
  }
49
49
  }