@dxos/plugin-space 0.6.13 → 0.6.14-main.69511f5

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-LZEGRS7H.mjs → chunk-AVLRQF6L.mjs} +1 -1
  2. package/dist/lib/browser/chunk-AVLRQF6L.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-DTVUOG2C.mjs → chunk-FOI7DAUV.mjs} +24 -5
  4. package/dist/lib/browser/chunk-FOI7DAUV.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +757 -298
  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 +7 -3
  10. package/dist/lib/node/{chunk-CVZPI2P3.cjs → chunk-OTDRTHT4.cjs} +30 -9
  11. package/dist/lib/node/chunk-OTDRTHT4.cjs.map +7 -0
  12. package/dist/lib/node/{chunk-6CNYF6YU.cjs → chunk-P4XUXM7Y.cjs} +4 -4
  13. package/dist/lib/node/chunk-P4XUXM7Y.cjs.map +7 -0
  14. package/dist/lib/node/index.cjs +953 -498
  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 +14 -10
  20. package/dist/lib/node/types/index.cjs.map +2 -2
  21. package/dist/lib/node-esm/chunk-FYDGMPSC.mjs +116 -0
  22. package/dist/lib/node-esm/chunk-FYDGMPSC.mjs.map +7 -0
  23. package/dist/lib/node-esm/chunk-YPQGKWHJ.mjs +37 -0
  24. package/dist/lib/node-esm/chunk-YPQGKWHJ.mjs.map +7 -0
  25. package/dist/lib/node-esm/index.mjs +3100 -0
  26. package/dist/lib/node-esm/index.mjs.map +7 -0
  27. package/dist/lib/node-esm/meta.json +1 -0
  28. package/dist/lib/node-esm/meta.mjs +14 -0
  29. package/dist/lib/node-esm/meta.mjs.map +7 -0
  30. package/dist/lib/node-esm/types/index.mjs +26 -0
  31. package/dist/lib/node-esm/types/index.mjs.map +7 -0
  32. package/dist/types/src/SpacePlugin.d.ts.map +1 -1
  33. package/dist/types/src/components/DefaultObjectSettings.d.ts +7 -0
  34. package/dist/types/src/components/DefaultObjectSettings.d.ts.map +1 -0
  35. package/dist/types/src/components/MenuFooter.d.ts +1 -1
  36. package/dist/types/src/components/MenuFooter.d.ts.map +1 -1
  37. package/dist/types/src/components/SaveStatus.d.ts +3 -0
  38. package/dist/types/src/components/SaveStatus.d.ts.map +1 -0
  39. package/dist/types/src/components/ShareSpaceButton.stories.d.ts +3 -91
  40. package/dist/types/src/components/ShareSpaceButton.stories.d.ts.map +1 -1
  41. package/dist/types/src/components/SpaceMain/SpaceMain.d.ts.map +1 -1
  42. package/dist/types/src/components/SpacePresence.d.ts +4 -2
  43. package/dist/types/src/components/SpacePresence.d.ts.map +1 -1
  44. package/dist/types/src/components/SpacePresence.stories.d.ts +4 -92
  45. package/dist/types/src/components/SpacePresence.stories.d.ts.map +1 -1
  46. package/dist/types/src/components/SpaceSettings.d.ts.map +1 -1
  47. package/dist/types/src/components/SpaceSettingsPanel.d.ts +7 -0
  48. package/dist/types/src/components/SpaceSettingsPanel.d.ts.map +1 -0
  49. package/dist/types/src/components/SyncStatus/SyncStatus.d.ts +13 -0
  50. package/dist/types/src/components/SyncStatus/SyncStatus.d.ts.map +1 -0
  51. package/dist/types/src/components/SyncStatus/SyncStatus.stories.d.ts +24 -0
  52. package/dist/types/src/components/SyncStatus/SyncStatus.stories.d.ts.map +1 -0
  53. package/dist/types/src/components/SyncStatus/index.d.ts +2 -0
  54. package/dist/types/src/components/SyncStatus/index.d.ts.map +1 -0
  55. package/dist/types/src/components/SyncStatus/types.d.ts +14 -0
  56. package/dist/types/src/components/SyncStatus/types.d.ts.map +1 -0
  57. package/dist/types/src/components/index.d.ts +4 -2
  58. package/dist/types/src/components/index.d.ts.map +1 -1
  59. package/dist/types/src/meta.d.ts.map +1 -1
  60. package/dist/types/src/translations.d.ts +8 -0
  61. package/dist/types/src/translations.d.ts.map +1 -1
  62. package/dist/types/src/types/thread.d.ts +15 -1
  63. package/dist/types/src/types/thread.d.ts.map +1 -1
  64. package/dist/types/src/types/types.d.ts +21 -1
  65. package/dist/types/src/types/types.d.ts.map +1 -1
  66. package/dist/types/src/util.d.ts +4 -7
  67. package/dist/types/src/util.d.ts.map +1 -1
  68. package/package.json +45 -45
  69. package/src/SpacePlugin.tsx +229 -116
  70. package/src/components/DefaultObjectSettings.tsx +33 -0
  71. package/src/components/MenuFooter.tsx +2 -2
  72. package/src/components/SaveStatus.tsx +95 -0
  73. package/src/components/ShareSpaceButton.stories.tsx +11 -7
  74. package/src/components/SpaceMain/SpaceMain.tsx +1 -22
  75. package/src/components/SpacePresence.stories.tsx +11 -9
  76. package/src/components/SpacePresence.tsx +34 -23
  77. package/src/components/SpaceSettings.tsx +35 -6
  78. package/src/components/SpaceSettingsPanel.tsx +59 -0
  79. package/src/components/SyncStatus/SyncStatus.stories.tsx +65 -0
  80. package/src/components/SyncStatus/SyncStatus.tsx +188 -0
  81. package/src/components/SyncStatus/index.ts +5 -0
  82. package/src/components/SyncStatus/types.ts +77 -0
  83. package/src/components/index.ts +4 -2
  84. package/src/meta.ts +3 -1
  85. package/src/translations.ts +10 -2
  86. package/src/types/collection.ts +1 -1
  87. package/src/types/thread.ts +12 -2
  88. package/src/types/types.ts +28 -2
  89. package/src/util.tsx +23 -58
  90. package/dist/lib/browser/chunk-DTVUOG2C.mjs.map +0 -7
  91. package/dist/lib/browser/chunk-LZEGRS7H.mjs.map +0 -7
  92. package/dist/lib/node/chunk-6CNYF6YU.cjs.map +0 -7
  93. package/dist/lib/node/chunk-CVZPI2P3.cjs.map +0 -7
  94. package/dist/types/src/components/EmptySpace.d.ts +0 -3
  95. package/dist/types/src/components/EmptySpace.d.ts.map +0 -1
  96. package/dist/types/src/components/EmptyTree.d.ts +0 -3
  97. package/dist/types/src/components/EmptyTree.d.ts.map +0 -1
  98. package/src/components/EmptySpace.tsx +0 -25
  99. package/src/components/EmptyTree.tsx +0 -25
@@ -2,74 +2,78 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { type IconProps, Plus, SignIn, CardsThree, Warning } from '@phosphor-icons/react';
6
- import { effect, signal } from '@preact/signals-core';
5
+ import { signal } from '@preact/signals-core';
7
6
  import React from 'react';
8
7
 
9
8
  import {
10
9
  type IntentDispatcher,
11
10
  type IntentPluginProvides,
12
11
  LayoutAction,
13
- Surface,
14
12
  type LocationProvides,
15
13
  NavigationAction,
16
14
  type Plugin,
17
15
  type PluginDefinition,
18
- openIds,
16
+ Surface,
19
17
  firstIdInPart,
18
+ openIds,
19
+ parseGraphPlugin,
20
20
  parseIntentPlugin,
21
- parseNavigationPlugin,
22
21
  parseMetadataResolverPlugin,
22
+ parseNavigationPlugin,
23
23
  resolvePlugin,
24
- parseGraphPlugin,
25
24
  } from '@dxos/app-framework';
26
25
  import { EventSubscriptions, type Trigger, type UnsubscribeCallback } from '@dxos/async';
27
- import { type Identifiable, isReactiveObject, type EchoReactiveObject } from '@dxos/echo-schema';
26
+ import { type HasId, isReactiveObject } from '@dxos/echo-schema';
27
+ import { scheduledEffect } from '@dxos/echo-signals/core';
28
28
  import { LocalStorageStore } from '@dxos/local-storage';
29
29
  import { log } from '@dxos/log';
30
30
  import { Migrations } from '@dxos/migrations';
31
31
  import { type AttentionPluginProvides, parseAttentionPlugin } from '@dxos/plugin-attention';
32
32
  import { type ClientPluginProvides, parseClientPlugin } from '@dxos/plugin-client';
33
- import { createExtension, isGraphNode, memoize, type Node, toSignal } from '@dxos/plugin-graph';
33
+ import { type Node, createExtension, memoize, toSignal } from '@dxos/plugin-graph';
34
34
  import { ObservabilityAction } from '@dxos/plugin-observability/meta';
35
35
  import { type Client, PublicKey } from '@dxos/react-client';
36
36
  import {
37
+ type EchoReactiveObject,
38
+ Expando,
39
+ Filter,
37
40
  type PropertiesTypeProps,
38
41
  type Space,
42
+ SpaceState,
39
43
  create,
40
- Expando,
41
- Filter,
42
44
  fullyQualifiedId,
43
45
  getSpace,
44
46
  getTypename,
45
47
  isEchoObject,
46
48
  isSpace,
47
49
  loadObjectReferences,
48
- SpaceState,
50
+ parseId,
49
51
  } from '@dxos/react-client/echo';
50
52
  import { Dialog } from '@dxos/react-ui';
51
- import { InvitationManager, type InvitationManagerProps, osTranslations, ClipboardProvider } from '@dxos/shell/react';
53
+ import { ClipboardProvider, InvitationManager, type InvitationManagerProps, osTranslations } from '@dxos/shell/react';
52
54
  import { ComplexMap, nonNullable, reduceGroupBy } from '@dxos/util';
53
55
 
54
56
  import {
55
57
  AwaitingObject,
56
58
  CollectionMain,
57
59
  CollectionSection,
58
- EmptySpace,
59
- EmptyTree,
60
+ DefaultObjectSettings,
60
61
  MenuFooter,
61
62
  MissingObject,
62
63
  PopoverRenameObject,
63
64
  PopoverRenameSpace,
65
+ SaveStatus,
64
66
  ShareSpaceButton,
65
67
  SmallPresence,
66
68
  SmallPresenceLive,
67
69
  SpacePresence,
68
70
  SpaceSettings,
71
+ SpaceSettingsPanel,
72
+ SyncStatus,
69
73
  } from './components';
70
74
  import meta, { SPACE_PLUGIN, SpaceAction } from './meta';
71
75
  import translations from './translations';
72
- import { CollectionType, type SpacePluginProvides, type SpaceSettingsProps, type PluginState } from './types';
76
+ import { CollectionType, type PluginState, type SpacePluginProvides, type SpaceSettingsProps } from './types';
73
77
  import {
74
78
  COMPOSER_SPACE_LOCK,
75
79
  SHARED,
@@ -118,11 +122,14 @@ export const SpacePlugin = ({
118
122
  firstRun,
119
123
  onFirstRun,
120
124
  }: SpacePluginOptions = {}): PluginDefinition<SpacePluginProvides> => {
121
- const settings = new LocalStorageStore<SpaceSettingsProps>(SPACE_PLUGIN);
125
+ const settings = new LocalStorageStore<SpaceSettingsProps>(SPACE_PLUGIN, {
126
+ onSpaceCreate: 'dxos.org/plugin/markdown/action/create',
127
+ });
122
128
  const state = new LocalStorageStore<PluginState>(SPACE_PLUGIN, {
123
129
  awaiting: undefined,
124
130
  spaceNames: {},
125
131
  viewersByObject: {},
132
+ // TODO(wittjosiah): Stop using (Complex)Map inside reactive object.
126
133
  viewersByIdentity: new ComplexMap(PublicKey.hash),
127
134
  sdkMigrationRunning: {},
128
135
  });
@@ -171,9 +178,10 @@ export const SpacePlugin = ({
171
178
  .filter((space) => space.state.get() === SpaceState.SPACE_READY)
172
179
  .forEach((space) => {
173
180
  subscriptions.add(
174
- effect(() => {
175
- state.values.spaceNames[space.id] = space.properties.name;
176
- }),
181
+ scheduledEffect(
182
+ () => ({ name: space.properties.name }),
183
+ ({ name }) => (state.values.spaceNames[space.id] = name),
184
+ ),
177
185
  );
178
186
  });
179
187
  }).unsubscribe,
@@ -181,54 +189,56 @@ export const SpacePlugin = ({
181
189
 
182
190
  // Broadcast active node to other peers in the space.
183
191
  subscriptions.add(
184
- effect(() => {
185
- const send = () => {
186
- const spaces = client.spaces.get();
187
- const identity = client.halo.identity.get();
188
- if (identity && location.active) {
189
- const ids = openIds(location.active);
190
-
191
- // Group parts by space for efficient messaging.
192
- const idsBySpace = reduceGroupBy(ids, (id) => {
193
- const [spaceId] = id.split(':'); // TODO(burdon): Factor out.
194
- return spaceId;
195
- });
196
-
197
- // NOTE: Ensure all spaces are included so that we send the correct `removed` object arrays.
198
- for (const space of spaces) {
199
- if (!idsBySpace.has(space.id)) {
200
- idsBySpace.set(space.id, []);
192
+ scheduledEffect(
193
+ () => ({
194
+ ids: openIds(location.active),
195
+ removed: location.closed ? [location.closed].flat() : [],
196
+ }),
197
+ ({ ids, removed }) => {
198
+ const send = () => {
199
+ const spaces = client.spaces.get();
200
+ const identity = client.halo.identity.get();
201
+ if (identity && location.active) {
202
+ // Group parts by space for efficient messaging.
203
+ const idsBySpace = reduceGroupBy(ids, (id) => {
204
+ const [spaceId] = id.split(':'); // TODO(burdon): Factor out.
205
+ return spaceId;
206
+ });
207
+
208
+ // NOTE: Ensure all spaces are included so that we send the correct `removed` object arrays.
209
+ for (const space of spaces) {
210
+ if (!idsBySpace.has(space.id)) {
211
+ idsBySpace.set(space.id, []);
212
+ }
201
213
  }
202
- }
203
214
 
204
- for (const [spaceId, ids] of idsBySpace) {
205
- const space = spaces.find((space) => space.id === spaceId);
206
- if (!space) {
207
- continue;
208
- }
215
+ for (const [spaceId, ids] of idsBySpace) {
216
+ const space = spaces.find((space) => space.id === spaceId);
217
+ if (!space) {
218
+ continue;
219
+ }
209
220
 
210
- const removed = location.closed ? [location.closed].flat() : [];
211
-
212
- void space
213
- .postMessage('viewing', {
214
- identityKey: identity.identityKey.toHex(),
215
- attended: attention.attended ? [...attention.attended] : [],
216
- added: ids,
217
- // TODO(Zan): When we re-open a part, we should remove it from the removed list in the navigation plugin.
218
- removed: removed.filter((id) => !ids.includes(id)),
219
- })
220
- // TODO(burdon): This seems defensive; why would this fail? Backoff interval.
221
- .catch((err) => {
222
- log.warn('Failed to broadcast active node for presence.', { err: err.message });
223
- });
221
+ void space
222
+ .postMessage('viewing', {
223
+ identityKey: identity.identityKey.toHex(),
224
+ attended: attention.attended ? [...attention.attended] : [],
225
+ added: ids,
226
+ // TODO(Zan): When we re-open a part, we should remove it from the removed list in the navigation plugin.
227
+ removed: removed.filter((id) => !ids.includes(id)),
228
+ })
229
+ // TODO(burdon): This seems defensive; why would this fail? Backoff interval.
230
+ .catch((err) => {
231
+ log.warn('Failed to broadcast active node for presence.', { err: err.message });
232
+ });
233
+ }
224
234
  }
225
- }
226
- };
235
+ };
227
236
 
228
- send();
229
- const interval = setInterval(() => send(), ACTIVE_NODE_BROADCAST_INTERVAL);
230
- return () => clearInterval(interval);
231
- }),
237
+ send();
238
+ const interval = setInterval(() => send(), ACTIVE_NODE_BROADCAST_INTERVAL);
239
+ return () => clearInterval(interval);
240
+ },
241
+ ),
232
242
  );
233
243
 
234
244
  // Listen for active nodes from other peers in the space.
@@ -276,17 +286,8 @@ export const SpacePlugin = ({
276
286
  return {
277
287
  meta,
278
288
  ready: async (plugins) => {
279
- settings.prop({
280
- key: 'showHidden',
281
- storageKey: 'show-hidden',
282
- type: LocalStorageStore.bool({ allowUndefined: true }),
283
- });
284
-
285
- state.prop({
286
- key: 'spaceNames',
287
- storageKey: 'space-names',
288
- type: LocalStorageStore.json<Record<string, string>>(),
289
- });
289
+ settings.prop({ key: 'showHidden', type: LocalStorageStore.bool({ allowUndefined: true }) });
290
+ state.prop({ key: 'spaceNames', type: LocalStorageStore.json<Record<string, string>>() });
290
291
 
291
292
  navigationPlugin = resolvePlugin(plugins, parseNavigationPlugin);
292
293
  attentionPlugin = resolvePlugin(plugins, parseAttentionPlugin);
@@ -340,8 +341,7 @@ export const SpacePlugin = ({
340
341
  records: {
341
342
  [CollectionType.typename]: {
342
343
  placeholder: ['unnamed collection label', { ns: SPACE_PLUGIN }],
343
- icon: (props: IconProps) => <CardsThree {...props} />,
344
- iconSymbol: 'ph--cards-three--regular',
344
+ icon: 'ph--cards-three--regular',
345
345
  // TODO(wittjosiah): Move out of metadata.
346
346
  loadReferences: (collection: CollectionType) =>
347
347
  loadObjectReferences(collection, (collection) => [
@@ -362,22 +362,25 @@ export const SpacePlugin = ({
362
362
  case 'main':
363
363
  // TODO(wittjosiah): Need to avoid shotgun parsing space state everywhere.
364
364
  return isSpace(primary) && primary.state.get() === SpaceState.SPACE_READY ? (
365
- <Surface data={{ active: primary.properties[CollectionType.typename] }} role={role} {...rest} />
365
+ <Surface
366
+ data={{ active: primary.properties[CollectionType.typename], id: primary.id }}
367
+ role={role}
368
+ {...rest}
369
+ />
366
370
  ) : primary instanceof CollectionType ? (
367
- { node: <CollectionMain collection={primary} />, disposition: 'fallback' }
371
+ {
372
+ node: <CollectionMain collection={primary} />,
373
+ disposition: 'fallback',
374
+ }
368
375
  ) : typeof primary === 'string' && primary.length === OBJECT_ID_LENGTH ? (
369
376
  <MissingObject id={primary} />
370
377
  ) : null;
371
- // TODO(burdon): Add role name syntax to minimal plugin docs.
372
- case 'tree--empty':
373
- switch (true) {
374
- case data.plugin === SPACE_PLUGIN:
375
- return <EmptyTree />;
376
- case isGraphNode(data.activeNode) && isSpace(data.activeNode.data):
377
- return <EmptySpace />;
378
- default:
379
- return null;
380
- }
378
+ case 'complementary--settings':
379
+ return isSpace(data.subject) ? (
380
+ <SpaceSettingsPanel space={data.subject} />
381
+ ) : isEchoObject(data.subject) ? (
382
+ { node: <DefaultObjectSettings object={data.subject} />, disposition: 'fallback' }
383
+ ) : null;
381
384
  case 'dialog':
382
385
  if (data.component === 'dxos.org/plugin/space/InvitationManagerDialog') {
383
386
  return (
@@ -387,10 +390,9 @@ export const SpacePlugin = ({
387
390
  </ClipboardProvider>
388
391
  </Dialog.Content>
389
392
  );
390
- } else {
391
- return null;
392
393
  }
393
- case 'popover':
394
+ return null;
395
+ case 'popover': {
394
396
  if (data.component === 'dxos.org/plugin/space/RenameSpacePopover' && isSpace(data.subject)) {
395
397
  return <PopoverRenameSpace space={data.subject} />;
396
398
  }
@@ -398,11 +400,16 @@ export const SpacePlugin = ({
398
400
  return <PopoverRenameObject object={data.subject} />;
399
401
  }
400
402
  return null;
403
+ }
404
+ // TODO(burdon): Add role name syntax to minimal plugin docs.
401
405
  case 'presence--glyph': {
402
406
  return isReactiveObject(data.object) ? (
403
- <SmallPresenceLive viewers={state.values.viewersByObject[fullyQualifiedId(data.object)]} />
407
+ <SmallPresenceLive
408
+ id={data.id as string}
409
+ viewers={state.values.viewersByObject[fullyQualifiedId(data.object)]}
410
+ />
404
411
  ) : (
405
- <SmallPresence count={0} />
412
+ <SmallPresence id={data.id as string} count={0} />
406
413
  );
407
414
  }
408
415
  case 'navbar-start': {
@@ -419,6 +426,7 @@ export const SpacePlugin = ({
419
426
  ? (space?.properties[CollectionType.typename] as CollectionType)
420
427
  : undefined
421
428
  : data.object;
429
+
422
430
  return space && object
423
431
  ? {
424
432
  node: (
@@ -436,11 +444,19 @@ export const SpacePlugin = ({
436
444
  case 'settings':
437
445
  return data.plugin === meta.id ? <SpaceSettings settings={settings.values} /> : null;
438
446
  case 'menu-footer':
439
- if (!isEchoObject(data.object)) {
440
- return null;
441
- } else {
447
+ if (isEchoObject(data.object)) {
442
448
  return <MenuFooter object={data.object} />;
449
+ } else {
450
+ return null;
443
451
  }
452
+ case 'status': {
453
+ return (
454
+ <>
455
+ <SyncStatus />
456
+ <SaveStatus />
457
+ </>
458
+ );
459
+ }
444
460
  default:
445
461
  return null;
446
462
  }
@@ -456,8 +472,7 @@ export const SpacePlugin = ({
456
472
  const dispatch = intentPlugin?.provides.intent.dispatch;
457
473
  const resolve = metadataPlugin?.provides.metadata.resolver;
458
474
  const graph = graphPlugin?.provides.graph;
459
-
460
- if (!graph || !dispatch || !resolve || !client) {
475
+ if (!client || !dispatch || !resolve || !graph) {
461
476
  return [];
462
477
  }
463
478
 
@@ -491,7 +506,6 @@ export const SpacePlugin = ({
491
506
  type: SPACES,
492
507
  properties: {
493
508
  label: ['spaces label', { ns: SPACE_PLUGIN }],
494
- palette: 'teal',
495
509
  testId: 'spacePlugin.spaces',
496
510
  role: 'branch',
497
511
  childrenPersistenceClass: 'echo',
@@ -540,10 +554,10 @@ export const SpacePlugin = ({
540
554
  },
541
555
  properties: {
542
556
  label: ['create space label', { ns: SPACE_PLUGIN }],
543
- icon: (props: IconProps) => <Plus {...props} />,
544
- iconSymbol: 'ph--plus--regular',
545
- disposition: 'toolbar',
557
+ icon: 'ph--plus--regular',
558
+ disposition: 'item',
546
559
  testId: 'spacePlugin.createSpace',
560
+ className: 'pbs-4',
547
561
  },
548
562
  },
549
563
  {
@@ -561,9 +575,10 @@ export const SpacePlugin = ({
561
575
  },
562
576
  properties: {
563
577
  label: ['join space label', { ns: SPACE_PLUGIN }],
564
- icon: (props: IconProps) => <SignIn {...props} />,
565
- iconSymbol: 'ph--sign-in--regular',
578
+ icon: 'ph--sign-in--regular',
579
+ disposition: 'item',
566
580
  testId: 'spacePlugin.joinSpace',
581
+ className: 'pbe-4',
567
582
  },
568
583
  },
569
584
  ],
@@ -707,6 +722,76 @@ export const SpacePlugin = ({
707
722
  .filter(nonNullable);
708
723
  },
709
724
  }),
725
+
726
+ // Create nodes for object settings.
727
+ createExtension({
728
+ id: `${SPACE_PLUGIN}/settings-for-subject`,
729
+ resolver: ({ id }) => {
730
+ // TODO(Zan): Find util (or make one).
731
+ if (!id.endsWith('~settings')) {
732
+ return;
733
+ }
734
+
735
+ const type = 'orphan-settings-for-subject';
736
+ const icon = 'ph--gear--regular';
737
+
738
+ const [subjectId] = id.split('~');
739
+ const { spaceId, objectId } = parseId(subjectId);
740
+ const space = client.spaces.get().find((space) => space.id === spaceId);
741
+ if (!objectId) {
742
+ const label = space
743
+ ? space.properties.name || ['unnamed space label', { ns: SPACE_PLUGIN }]
744
+ : ['unnamed object settings label', { ns: SPACE_PLUGIN }];
745
+
746
+ // TODO(wittjosiah): Support comments for arbitrary subjects.
747
+ // This is to ensure that the comments panel is not stuck on an old object.
748
+ return {
749
+ id,
750
+ type,
751
+ data: null,
752
+ properties: {
753
+ icon,
754
+ label,
755
+ showResolvedThreads: false,
756
+ object: null,
757
+ space,
758
+ },
759
+ };
760
+ }
761
+
762
+ const object = toSignal(
763
+ (onChange) => {
764
+ const timeout = setTimeout(async () => {
765
+ await space?.db.loadObjectById(objectId);
766
+ onChange();
767
+ });
768
+
769
+ return () => clearTimeout(timeout);
770
+ },
771
+ () => space?.db.getObjectById(objectId),
772
+ subjectId,
773
+ );
774
+ if (!object || !subjectId) {
775
+ return;
776
+ }
777
+
778
+ const meta = resolve(getTypename(object) ?? '');
779
+ const label = meta.label?.(object) ||
780
+ object.name ||
781
+ meta.placeholder || ['unnamed object settings label', { ns: SPACE_PLUGIN }];
782
+
783
+ return {
784
+ id,
785
+ type,
786
+ data: null,
787
+ properties: {
788
+ icon,
789
+ label,
790
+ object,
791
+ },
792
+ };
793
+ },
794
+ }),
710
795
  ];
711
796
  },
712
797
  serializer: (plugins) => {
@@ -802,9 +887,21 @@ export const SpacePlugin = ({
802
887
  }
803
888
 
804
889
  return {
805
- data: { space, id: space.id, activeParts: { main: [space.id] } },
806
-
890
+ data: {
891
+ space,
892
+ id: space.id,
893
+ activeParts: { main: [space.id] },
894
+ },
807
895
  intents: [
896
+ ...(settings.values.onSpaceCreate
897
+ ? [
898
+ [
899
+ { action: settings.values.onSpaceCreate, data: { space } },
900
+ { action: SpaceAction.ADD_OBJECT, data: { target: space } },
901
+ { action: NavigationAction.EXPOSE },
902
+ ],
903
+ ]
904
+ : []),
808
905
  [
809
906
  {
810
907
  action: ObservabilityAction.SEND_EVENT,
@@ -825,9 +922,26 @@ export const SpacePlugin = ({
825
922
  const { space } = await client.shell.joinSpace({ invitationCode: intent.data?.invitationCode });
826
923
  if (space) {
827
924
  return {
828
- data: { space, id: space.id, activeParts: { main: [space.id] } },
829
-
925
+ data: {
926
+ space,
927
+ id: space.id,
928
+ activeParts: { main: [space.id] },
929
+ },
830
930
  intents: [
931
+ [
932
+ {
933
+ action: LayoutAction.SET_LAYOUT,
934
+ data: {
935
+ element: 'toast',
936
+ subject: {
937
+ id: `${SPACE_PLUGIN}/join-success`,
938
+ duration: 10_000,
939
+ title: translations[0]['en-US'][SPACE_PLUGIN]['join success label'],
940
+ closeLabel: translations[0]['en-US'][SPACE_PLUGIN]['dismiss label'],
941
+ },
942
+ },
943
+ },
944
+ ],
831
945
  [
832
946
  {
833
947
  action: ObservabilityAction.SEND_EVENT,
@@ -1020,8 +1134,7 @@ export const SpacePlugin = ({
1020
1134
  title: translations[0]['en-US'][SPACE_PLUGIN]['space limit label'],
1021
1135
  description: translations[0]['en-US'][SPACE_PLUGIN]['space limit description'],
1022
1136
  duration: 5_000,
1023
- icon: (props: IconProps) => <Warning {...props} />,
1024
- iconSymbol: 'ph--warning--regular',
1137
+ icon: 'ph--warning--regular',
1025
1138
  actionLabel: translations[0]['en-US'][SPACE_PLUGIN]['remove deleted objects label'],
1026
1139
  actionAlt: translations[0]['en-US'][SPACE_PLUGIN]['remove deleted objects alt'],
1027
1140
  // TODO(wittjosiah): Use OS namespace.
@@ -1047,20 +1160,20 @@ export const SpacePlugin = ({
1047
1160
  }
1048
1161
 
1049
1162
  if (intent.data?.target instanceof CollectionType) {
1050
- intent.data?.target.objects.push(object as Identifiable);
1163
+ intent.data?.target.objects.push(object as HasId);
1051
1164
  } else if (isSpace(intent.data?.target)) {
1052
1165
  const collection = space.properties[CollectionType.typename];
1053
1166
  if (collection instanceof CollectionType) {
1054
- collection.objects.push(object as Identifiable);
1167
+ collection.objects.push(object as HasId);
1055
1168
  } else {
1056
1169
  // TODO(wittjosiah): Can't add non-echo objects by including in a collection because of types.
1057
- const collection = create(CollectionType, { objects: [object as Identifiable], views: {} });
1170
+ const collection = create(CollectionType, { objects: [object as HasId], views: {} });
1058
1171
  space.properties[CollectionType.typename] = collection;
1059
1172
  }
1060
1173
  }
1061
1174
 
1062
1175
  return {
1063
- data: { id: object.id, object, activeParts: { main: [fullyQualifiedId(object)] } },
1176
+ data: { id: fullyQualifiedId(object), object, activeParts: { main: [fullyQualifiedId(object)] } },
1064
1177
  intents: [
1065
1178
  [
1066
1179
  {
@@ -1115,7 +1228,6 @@ export const SpacePlugin = ({
1115
1228
  activeParts: {
1116
1229
  main: deletionData.wasActive,
1117
1230
  sidebar: deletionData.wasActive,
1118
- complementary: deletionData.wasActive,
1119
1231
  },
1120
1232
  },
1121
1233
  });
@@ -1203,11 +1315,12 @@ export const SpacePlugin = ({
1203
1315
  case SpaceAction.DUPLICATE_OBJECT: {
1204
1316
  const originalObject = intent.data?.object ?? intent.data?.result;
1205
1317
  const resolve = resolvePlugin(plugins, parseMetadataResolverPlugin)?.provides.metadata.resolver;
1206
- if (!isEchoObject(originalObject) || !resolve) {
1318
+ const space = isSpace(intent.data?.target) ? intent.data?.target : getSpace(intent.data?.target);
1319
+ if (!isEchoObject(originalObject) || !resolve || !space) {
1207
1320
  return;
1208
1321
  }
1209
1322
 
1210
- const newObject = await cloneObject(originalObject, resolve);
1323
+ const newObject = await cloneObject(originalObject, resolve, space);
1211
1324
  return {
1212
1325
  intents: [
1213
1326
  [{ action: SpaceAction.ADD_OBJECT, data: { object: newObject, target: intent.data?.target } }],
@@ -0,0 +1,33 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { type EchoReactiveObject } from '@dxos/react-client/echo';
8
+ import { Input, useTranslation } from '@dxos/react-ui';
9
+
10
+ import { SPACE_PLUGIN } from '../meta';
11
+
12
+ export type DefaultObjectSettingsProps = {
13
+ object: EchoReactiveObject<any>;
14
+ };
15
+
16
+ export const DefaultObjectSettings = ({ object }: DefaultObjectSettingsProps) => {
17
+ const { t } = useTranslation(SPACE_PLUGIN);
18
+ // TODO(burdon): Standardize forms.
19
+ return (
20
+ <div role='form' className='flex flex-col w-full p-2 gap-1'>
21
+ <Input.Root>
22
+ <Input.Label>{t('name label')}</Input.Label>
23
+ <Input.TextInput
24
+ placeholder={t('name placeholder')}
25
+ value={object.name ?? ''}
26
+ onChange={(event) => {
27
+ object.name = event.target.value;
28
+ }}
29
+ />
30
+ </Input.Root>
31
+ </div>
32
+ );
33
+ };
@@ -1,11 +1,11 @@
1
1
  //
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
+
4
5
  import { Planet } from '@phosphor-icons/react';
5
6
  import React from 'react';
6
7
 
7
- import { getSpace } from '@dxos/client/echo';
8
- import type { EchoReactiveObject } from '@dxos/echo-schema';
8
+ import { type EchoReactiveObject, getSpace } from '@dxos/client/echo';
9
9
  import { useClient } from '@dxos/react-client';
10
10
  import { DropdownMenu, toLocalizedString, useTranslation } from '@dxos/react-ui';
11
11