@dxos/plugin-space 0.7.5-main.9d26e3a → 0.7.5-main.b19bfc8

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 (258) hide show
  1. package/dist/lib/browser/app-graph-builder-MGK5HWPZ.mjs +288 -0
  2. package/dist/lib/browser/app-graph-builder-MGK5HWPZ.mjs.map +7 -0
  3. package/dist/lib/browser/app-graph-serializer-FOWFLYGU.mjs +80 -0
  4. package/dist/lib/browser/app-graph-serializer-FOWFLYGU.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-23RVI5FZ.mjs +539 -0
  6. package/dist/lib/browser/chunk-23RVI5FZ.mjs.map +7 -0
  7. package/dist/lib/browser/chunk-NU7WDVGN.mjs +23 -0
  8. package/dist/lib/browser/chunk-NU7WDVGN.mjs.map +7 -0
  9. package/dist/lib/browser/chunk-PQXZCNAU.mjs +13 -0
  10. package/dist/lib/browser/chunk-PQXZCNAU.mjs.map +7 -0
  11. package/dist/lib/browser/chunk-S6B7627U.mjs +1809 -0
  12. package/dist/lib/browser/chunk-S6B7627U.mjs.map +7 -0
  13. package/dist/lib/browser/chunk-UDWHTKB5.mjs +136 -0
  14. package/dist/lib/browser/chunk-UDWHTKB5.mjs.map +7 -0
  15. package/dist/lib/browser/{chunk-54VE4GTA.mjs → chunk-ULA2UQJ4.mjs} +25 -15
  16. package/dist/lib/browser/chunk-ULA2UQJ4.mjs.map +7 -0
  17. package/dist/lib/browser/identity-created-FYGS6TBH.mjs +28 -0
  18. package/dist/lib/browser/identity-created-FYGS6TBH.mjs.map +7 -0
  19. package/dist/lib/browser/index.mjs +219 -3480
  20. package/dist/lib/browser/index.mjs.map +4 -4
  21. package/dist/lib/browser/intent-resolver-QVR4MSJV.mjs +555 -0
  22. package/dist/lib/browser/intent-resolver-QVR4MSJV.mjs.map +7 -0
  23. package/dist/lib/browser/meta.json +1 -1
  24. package/dist/lib/browser/react-root-IP2ZB245.mjs +28 -0
  25. package/dist/lib/browser/react-root-IP2ZB245.mjs.map +7 -0
  26. package/dist/lib/browser/react-surface-BT3EHB6V.mjs +225 -0
  27. package/dist/lib/browser/react-surface-BT3EHB6V.mjs.map +7 -0
  28. package/dist/lib/browser/schema-5W3DSY2E.mjs +24 -0
  29. package/dist/lib/browser/schema-5W3DSY2E.mjs.map +7 -0
  30. package/dist/lib/browser/settings-PHPCXX33.mjs +24 -0
  31. package/dist/lib/browser/settings-PHPCXX33.mjs.map +7 -0
  32. package/dist/lib/browser/spaces-ready-K47RR7N2.mjs +199 -0
  33. package/dist/lib/browser/spaces-ready-K47RR7N2.mjs.map +7 -0
  34. package/dist/lib/browser/state-INJ63O57.mjs +47 -0
  35. package/dist/lib/browser/state-INJ63O57.mjs.map +7 -0
  36. package/dist/lib/browser/types/index.mjs +6 -4
  37. package/dist/lib/node/app-graph-builder-ZIUBXRPA.cjs +291 -0
  38. package/dist/lib/node/app-graph-builder-ZIUBXRPA.cjs.map +7 -0
  39. package/dist/lib/node/app-graph-serializer-VQOGHKXL.cjs +88 -0
  40. package/dist/lib/node/app-graph-serializer-VQOGHKXL.cjs.map +7 -0
  41. package/dist/lib/node/chunk-N2FS7PRA.cjs +1807 -0
  42. package/dist/lib/node/chunk-N2FS7PRA.cjs.map +7 -0
  43. package/dist/lib/node/chunk-OVGKWJOC.cjs +567 -0
  44. package/dist/lib/node/chunk-OVGKWJOC.cjs.map +7 -0
  45. package/dist/lib/node/chunk-U6DYXAR3.cjs +153 -0
  46. package/dist/lib/node/chunk-U6DYXAR3.cjs.map +7 -0
  47. package/dist/lib/node/{chunk-YF2AQ7KP.cjs → chunk-WAJKBO3J.cjs} +31 -20
  48. package/dist/lib/node/chunk-WAJKBO3J.cjs.map +7 -0
  49. package/dist/lib/node/{chunk-46S3JOES.cjs → chunk-WZR6OAN3.cjs} +9 -12
  50. package/dist/lib/node/chunk-WZR6OAN3.cjs.map +7 -0
  51. package/dist/lib/node/chunk-YZKNRFHU.cjs +43 -0
  52. package/dist/lib/node/chunk-YZKNRFHU.cjs.map +7 -0
  53. package/dist/lib/node/identity-created-AXI64BLE.cjs +44 -0
  54. package/dist/lib/node/identity-created-AXI64BLE.cjs.map +7 -0
  55. package/dist/lib/node/index.cjs +203 -3468
  56. package/dist/lib/node/index.cjs.map +4 -4
  57. package/dist/lib/node/intent-resolver-MLENGECT.cjs +553 -0
  58. package/dist/lib/node/intent-resolver-MLENGECT.cjs.map +7 -0
  59. package/dist/lib/node/meta.json +1 -1
  60. package/dist/lib/node/react-root-3OX5Z5CX.cjs +50 -0
  61. package/dist/lib/node/react-root-3OX5Z5CX.cjs.map +7 -0
  62. package/dist/lib/node/react-surface-5NYCMXSM.cjs +221 -0
  63. package/dist/lib/node/react-surface-5NYCMXSM.cjs.map +7 -0
  64. package/dist/lib/node/schema-YN7WVFRX.cjs +40 -0
  65. package/dist/lib/node/schema-YN7WVFRX.cjs.map +7 -0
  66. package/dist/lib/node/{meta.cjs → settings-5QYFWNH7.cjs} +19 -13
  67. package/dist/lib/node/settings-5QYFWNH7.cjs.map +7 -0
  68. package/dist/lib/node/spaces-ready-FQNAKR7G.cjs +210 -0
  69. package/dist/lib/node/spaces-ready-FQNAKR7G.cjs.map +7 -0
  70. package/dist/lib/node/state-57UE3DYE.cjs +61 -0
  71. package/dist/lib/node/state-57UE3DYE.cjs.map +7 -0
  72. package/dist/lib/node/types/index.cjs +19 -17
  73. package/dist/lib/node/types/index.cjs.map +2 -2
  74. package/dist/lib/node-esm/app-graph-builder-TERVM2SL.mjs +289 -0
  75. package/dist/lib/node-esm/app-graph-builder-TERVM2SL.mjs.map +7 -0
  76. package/dist/lib/node-esm/app-graph-serializer-GZRSWHEN.mjs +81 -0
  77. package/dist/lib/node-esm/app-graph-serializer-GZRSWHEN.mjs.map +7 -0
  78. package/dist/lib/node-esm/chunk-2TQ2AJEZ.mjs +137 -0
  79. package/dist/lib/node-esm/chunk-2TQ2AJEZ.mjs.map +7 -0
  80. package/dist/lib/node-esm/chunk-6RSVVEPS.mjs +24 -0
  81. package/dist/lib/node-esm/chunk-6RSVVEPS.mjs.map +7 -0
  82. package/dist/lib/node-esm/chunk-DIKRH2IK.mjs +1810 -0
  83. package/dist/lib/node-esm/chunk-DIKRH2IK.mjs.map +7 -0
  84. package/dist/lib/node-esm/{chunk-2MNFEB23.mjs → chunk-ICCM4YRJ.mjs} +5 -7
  85. package/dist/lib/node-esm/chunk-ICCM4YRJ.mjs.map +7 -0
  86. package/dist/lib/node-esm/chunk-PGH5L7MV.mjs +540 -0
  87. package/dist/lib/node-esm/chunk-PGH5L7MV.mjs.map +7 -0
  88. package/dist/lib/node-esm/{chunk-CDZETPO7.mjs → chunk-TRNZQEEN.mjs} +25 -15
  89. package/dist/lib/node-esm/chunk-TRNZQEEN.mjs.map +7 -0
  90. package/dist/lib/node-esm/identity-created-3CGEXNPO.mjs +29 -0
  91. package/dist/lib/node-esm/identity-created-3CGEXNPO.mjs.map +7 -0
  92. package/dist/lib/node-esm/index.mjs +219 -3480
  93. package/dist/lib/node-esm/index.mjs.map +4 -4
  94. package/dist/lib/node-esm/intent-resolver-BLW4RM6X.mjs +556 -0
  95. package/dist/lib/node-esm/intent-resolver-BLW4RM6X.mjs.map +7 -0
  96. package/dist/lib/node-esm/meta.json +1 -1
  97. package/dist/lib/node-esm/react-root-7XXGP56B.mjs +29 -0
  98. package/dist/lib/node-esm/react-root-7XXGP56B.mjs.map +7 -0
  99. package/dist/lib/node-esm/react-surface-R2ECJSFB.mjs +226 -0
  100. package/dist/lib/node-esm/react-surface-R2ECJSFB.mjs.map +7 -0
  101. package/dist/lib/node-esm/schema-LOR2EVGY.mjs +25 -0
  102. package/dist/lib/node-esm/schema-LOR2EVGY.mjs.map +7 -0
  103. package/dist/lib/node-esm/settings-H6MXTEQM.mjs +25 -0
  104. package/dist/lib/node-esm/settings-H6MXTEQM.mjs.map +7 -0
  105. package/dist/lib/node-esm/spaces-ready-HKAQG5SA.mjs +200 -0
  106. package/dist/lib/node-esm/spaces-ready-HKAQG5SA.mjs.map +7 -0
  107. package/dist/lib/node-esm/state-VYA6OFHD.mjs +48 -0
  108. package/dist/lib/node-esm/state-VYA6OFHD.mjs.map +7 -0
  109. package/dist/lib/node-esm/types/index.mjs +6 -4
  110. package/dist/types/src/SpacePlugin.d.ts +3 -22
  111. package/dist/types/src/SpacePlugin.d.ts.map +1 -1
  112. package/dist/types/src/capabilities/app-graph-builder.d.ts +181 -0
  113. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
  114. package/dist/types/src/capabilities/app-graph-serializer.d.ts +4 -0
  115. package/dist/types/src/capabilities/app-graph-serializer.d.ts.map +1 -0
  116. package/dist/types/src/capabilities/capabilities.d.ts +22 -0
  117. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -0
  118. package/dist/types/src/capabilities/identity-created.d.ts +4 -0
  119. package/dist/types/src/capabilities/identity-created.d.ts.map +1 -0
  120. package/dist/types/src/capabilities/index.d.ts +197 -0
  121. package/dist/types/src/capabilities/index.d.ts.map +1 -0
  122. package/dist/types/src/capabilities/intent-resolver.d.ts +9 -0
  123. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -0
  124. package/dist/types/src/capabilities/react-root.d.ts +7 -0
  125. package/dist/types/src/capabilities/react-root.d.ts.map +1 -0
  126. package/dist/types/src/capabilities/react-surface.d.ts +7 -0
  127. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  128. package/dist/types/src/capabilities/schema.d.ts +4 -0
  129. package/dist/types/src/capabilities/schema.d.ts.map +1 -0
  130. package/dist/types/src/capabilities/settings.d.ts +4 -0
  131. package/dist/types/src/capabilities/settings.d.ts.map +1 -0
  132. package/dist/types/src/capabilities/spaces-ready.d.ts +4 -0
  133. package/dist/types/src/capabilities/spaces-ready.d.ts.map +1 -0
  134. package/dist/types/src/capabilities/state.d.ts +5 -0
  135. package/dist/types/src/capabilities/state.d.ts.map +1 -0
  136. package/dist/types/src/components/AdvancedObjectSettings/AdvancedObjectSettings.d.ts +6 -0
  137. package/dist/types/src/components/AdvancedObjectSettings/AdvancedObjectSettings.d.ts.map +1 -0
  138. package/dist/types/src/components/AdvancedObjectSettings/ForeignKeys.d.ts +7 -0
  139. package/dist/types/src/components/AdvancedObjectSettings/ForeignKeys.d.ts.map +1 -0
  140. package/dist/types/src/components/AdvancedObjectSettings/index.d.ts +2 -0
  141. package/dist/types/src/components/AdvancedObjectSettings/index.d.ts.map +1 -0
  142. package/dist/types/src/components/AwaitingObject.d.ts +1 -2
  143. package/dist/types/src/components/AwaitingObject.d.ts.map +1 -1
  144. package/dist/types/src/components/BaseObjectSettings.d.ts +6 -0
  145. package/dist/types/src/components/BaseObjectSettings.d.ts.map +1 -0
  146. package/dist/types/src/components/CollectionMain.d.ts +1 -2
  147. package/dist/types/src/components/CollectionMain.d.ts.map +1 -1
  148. package/dist/types/src/components/CollectionSection.d.ts +1 -2
  149. package/dist/types/src/components/CollectionSection.d.ts.map +1 -1
  150. package/dist/types/src/components/CreateDialog/CreateObjectDialog.d.ts +2 -5
  151. package/dist/types/src/components/CreateDialog/CreateObjectDialog.d.ts.map +1 -1
  152. package/dist/types/src/components/CreateDialog/CreateObjectDialog.stories.d.ts.map +1 -1
  153. package/dist/types/src/components/CreateDialog/CreateObjectPanel.d.ts +9 -11
  154. package/dist/types/src/components/CreateDialog/CreateObjectPanel.d.ts.map +1 -1
  155. package/dist/types/src/components/CreateDialog/CreateSpaceDialog.d.ts +1 -2
  156. package/dist/types/src/components/CreateDialog/CreateSpaceDialog.d.ts.map +1 -1
  157. package/dist/types/src/components/JoinDialog.d.ts +1 -2
  158. package/dist/types/src/components/JoinDialog.d.ts.map +1 -1
  159. package/dist/types/src/components/MenuFooter.d.ts +1 -2
  160. package/dist/types/src/components/MenuFooter.d.ts.map +1 -1
  161. package/dist/types/src/components/PersistenceStatus.d.ts +1 -2
  162. package/dist/types/src/components/PersistenceStatus.d.ts.map +1 -1
  163. package/dist/types/src/components/PopoverAddSpace.d.ts +3 -0
  164. package/dist/types/src/components/PopoverAddSpace.d.ts.map +1 -0
  165. package/dist/types/src/components/PopoverRenameObject.d.ts +1 -2
  166. package/dist/types/src/components/PopoverRenameObject.d.ts.map +1 -1
  167. package/dist/types/src/components/PopoverRenameSpace.d.ts +1 -2
  168. package/dist/types/src/components/PopoverRenameSpace.d.ts.map +1 -1
  169. package/dist/types/src/components/ShareSpaceButton.d.ts +2 -3
  170. package/dist/types/src/components/ShareSpaceButton.d.ts.map +1 -1
  171. package/dist/types/src/components/SpacePluginSettings.d.ts +1 -2
  172. package/dist/types/src/components/SpacePluginSettings.d.ts.map +1 -1
  173. package/dist/types/src/components/SpacePresence.d.ts +4 -5
  174. package/dist/types/src/components/SpacePresence.d.ts.map +1 -1
  175. package/dist/types/src/components/SpacePresence.stories.d.ts +2 -3
  176. package/dist/types/src/components/SpacePresence.stories.d.ts.map +1 -1
  177. package/dist/types/src/components/SpaceSettings/SpaceSettingsDialog.d.ts +1 -2
  178. package/dist/types/src/components/SpaceSettings/SpaceSettingsDialog.d.ts.map +1 -1
  179. package/dist/types/src/components/SpaceSettings/SpaceSettingsPanel.d.ts +3 -5
  180. package/dist/types/src/components/SpaceSettings/SpaceSettingsPanel.d.ts.map +1 -1
  181. package/dist/types/src/components/SpaceSettings/SpaceSettingsPanel.stories.d.ts.map +1 -1
  182. package/dist/types/src/components/SyncStatus/InlineSyncStatus.d.ts +1 -2
  183. package/dist/types/src/components/SyncStatus/InlineSyncStatus.d.ts.map +1 -1
  184. package/dist/types/src/components/SyncStatus/Space.d.ts +2 -3
  185. package/dist/types/src/components/SyncStatus/Space.d.ts.map +1 -1
  186. package/dist/types/src/components/SyncStatus/SyncStatus.d.ts +3 -4
  187. package/dist/types/src/components/SyncStatus/SyncStatus.d.ts.map +1 -1
  188. package/dist/types/src/components/index.d.ts +3 -1
  189. package/dist/types/src/components/index.d.ts.map +1 -1
  190. package/dist/types/src/events.d.ts +9 -0
  191. package/dist/types/src/events.d.ts.map +1 -0
  192. package/dist/types/src/index.d.ts +3 -5
  193. package/dist/types/src/index.d.ts.map +1 -1
  194. package/dist/types/src/meta.d.ts +2 -4
  195. package/dist/types/src/meta.d.ts.map +1 -1
  196. package/dist/types/src/translations.d.ts +21 -0
  197. package/dist/types/src/translations.d.ts.map +1 -1
  198. package/dist/types/src/types/collection.d.ts +2 -2
  199. package/dist/types/src/types/thread.d.ts +34 -34
  200. package/dist/types/src/types/types.d.ts +41 -37
  201. package/dist/types/src/types/types.d.ts.map +1 -1
  202. package/dist/types/src/util.d.ts +10 -8
  203. package/dist/types/src/util.d.ts.map +1 -1
  204. package/dist/types/tsconfig.tsbuildinfo +1 -1
  205. package/package.json +41 -47
  206. package/src/SpacePlugin.tsx +141 -1410
  207. package/src/capabilities/app-graph-builder.ts +319 -0
  208. package/src/capabilities/app-graph-serializer.ts +73 -0
  209. package/src/capabilities/capabilities.ts +28 -0
  210. package/src/capabilities/identity-created.ts +26 -0
  211. package/src/capabilities/index.ts +18 -0
  212. package/src/capabilities/intent-resolver.ts +536 -0
  213. package/src/capabilities/react-root.tsx +20 -0
  214. package/src/capabilities/react-surface.tsx +210 -0
  215. package/src/capabilities/schema.ts +27 -0
  216. package/src/capabilities/settings.ts +17 -0
  217. package/src/capabilities/spaces-ready.ts +230 -0
  218. package/src/capabilities/state.ts +45 -0
  219. package/src/components/AdvancedObjectSettings/AdvancedObjectSettings.tsx +72 -0
  220. package/src/components/AdvancedObjectSettings/ForeignKeys.tsx +51 -0
  221. package/src/components/AdvancedObjectSettings/index.ts +5 -0
  222. package/src/components/AwaitingObject.tsx +5 -11
  223. package/src/components/{DefaultObjectSettings.tsx → BaseObjectSettings.tsx} +9 -2
  224. package/src/components/CreateDialog/CreateObjectDialog.stories.tsx +1 -3
  225. package/src/components/CreateDialog/CreateObjectDialog.tsx +43 -35
  226. package/src/components/CreateDialog/CreateObjectPanel.tsx +139 -105
  227. package/src/components/CreateDialog/CreateSpaceDialog.tsx +18 -9
  228. package/src/components/JoinDialog.tsx +27 -19
  229. package/src/components/PopoverAddSpace.tsx +46 -0
  230. package/src/components/SpacePluginSettings.tsx +5 -10
  231. package/src/components/SpacePresence.stories.tsx +2 -2
  232. package/src/components/SpacePresence.tsx +7 -7
  233. package/src/components/SpaceSettings/SpaceSettingsDialog.tsx +16 -1
  234. package/src/components/SpaceSettings/SpaceSettingsPanel.stories.tsx +5 -1
  235. package/src/components/SpaceSettings/SpaceSettingsPanel.tsx +30 -8
  236. package/src/components/SyncStatus/InlineSyncStatus.tsx +2 -2
  237. package/src/components/SyncStatus/SyncStatus.tsx +2 -1
  238. package/src/components/index.ts +3 -1
  239. package/src/events.ts +18 -0
  240. package/src/index.ts +3 -7
  241. package/src/meta.ts +2 -3
  242. package/src/translations.ts +7 -0
  243. package/src/types/types.ts +32 -51
  244. package/src/util.tsx +45 -29
  245. package/dist/lib/browser/chunk-54VE4GTA.mjs.map +0 -7
  246. package/dist/lib/browser/chunk-73BCBSLP.mjs +0 -15
  247. package/dist/lib/browser/chunk-73BCBSLP.mjs.map +0 -7
  248. package/dist/lib/browser/meta.mjs +0 -11
  249. package/dist/lib/browser/meta.mjs.map +0 -7
  250. package/dist/lib/node/chunk-46S3JOES.cjs.map +0 -7
  251. package/dist/lib/node/chunk-YF2AQ7KP.cjs.map +0 -7
  252. package/dist/lib/node/meta.cjs.map +0 -7
  253. package/dist/lib/node-esm/chunk-2MNFEB23.mjs.map +0 -7
  254. package/dist/lib/node-esm/chunk-CDZETPO7.mjs.map +0 -7
  255. package/dist/lib/node-esm/meta.mjs +0 -12
  256. package/dist/lib/node-esm/meta.mjs.map +0 -7
  257. package/dist/types/src/components/DefaultObjectSettings.d.ts +0 -7
  258. package/dist/types/src/components/DefaultObjectSettings.d.ts.map +0 -1
@@ -1,132 +1,42 @@
1
1
  //
2
- // Copyright 2024 DXOS.org
2
+ // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { signal } from '@preact/signals-core';
6
- import React from 'react';
7
-
8
5
  import {
9
- type GraphProvides,
10
- type IntentPluginProvides,
11
- LayoutAction,
12
- type LayoutProvides,
13
- type LocationProvides,
14
- NavigationAction,
15
- type Plugin,
16
- type PluginDefinition,
17
- type PromiseIntentDispatcher,
18
- Surface,
6
+ allOf,
7
+ Capabilities,
8
+ contributes,
19
9
  createIntent,
20
- createResolver,
21
- createSurface,
22
- filterPlugins,
23
- findPlugin,
24
- firstIdInPart,
25
- openIds,
26
- parseGraphPlugin,
27
- parseIntentPlugin,
28
- parseLayoutPlugin,
29
- parseMetadataResolverPlugin,
30
- parseNavigationPlugin,
31
- resolvePlugin,
10
+ defineModule,
11
+ definePlugin,
12
+ Events,
13
+ oneOf,
32
14
  } from '@dxos/app-framework';
33
- import { EventSubscriptions, type Trigger, type UnsubscribeCallback } from '@dxos/async';
34
- import { type HasId, type TypedObject } from '@dxos/echo-schema';
35
- import { scheduledEffect } from '@dxos/echo-signals/core';
36
- import { invariant } from '@dxos/invariant';
37
- import { type ReactiveObject, RefArray, create, isDeleted, isReactiveObject, makeRef } from '@dxos/live-object';
38
- import { LocalStorageStore } from '@dxos/local-storage';
39
- import { log } from '@dxos/log';
40
- import { Migrations } from '@dxos/migrations';
41
- import { type AttentionPluginProvides, parseAttentionPlugin } from '@dxos/plugin-attention';
42
- import { type ClientPluginProvides, parseClientPlugin } from '@dxos/plugin-client/types';
43
- import { type Node, createExtension, memoize, toSignal } from '@dxos/plugin-graph';
44
- import { ObservabilityAction } from '@dxos/plugin-observability/types';
45
- import { EdgeReplicationSetting } from '@dxos/protocols/proto/dxos/echo/metadata';
46
- import { type Client, PublicKey } from '@dxos/react-client';
47
- import {
48
- Expando,
49
- FQ_ID_LENGTH,
50
- Filter,
51
- OBJECT_ID_LENGTH,
52
- QueryOptions,
53
- type ReactiveEchoObject,
54
- SPACE_ID_LENGTH,
55
- type Space,
56
- SpaceState,
57
- fullyQualifiedId,
58
- getSpace,
59
- getTypename,
60
- isEchoObject,
61
- isSpace,
62
- parseFullyQualifiedId,
63
- parseId,
64
- } from '@dxos/react-client/echo';
65
- import { type JoinPanelProps, osTranslations } from '@dxos/shell/react';
66
- import { ComplexMap, nonNullable, reduceGroupBy } from '@dxos/util';
15
+ import { S } from '@dxos/echo-schema';
16
+ import { RefArray } from '@dxos/live-object';
17
+ import { AttentionEvents } from '@dxos/plugin-attention';
18
+ import { ClientEvents } from '@dxos/plugin-client';
19
+ import { DeckCapabilities, DeckEvents } from '@dxos/plugin-deck';
20
+ import { isEchoObject, getSpace } from '@dxos/react-client/echo';
21
+ import { osTranslations } from '@dxos/shell/react';
67
22
 
68
23
  import {
69
- AwaitingObject,
70
- CREATE_OBJECT_DIALOG,
71
- CREATE_SPACE_DIALOG,
72
- CollectionMain,
73
- CollectionSection,
74
- CreateObjectDialog,
75
- type CreateObjectDialogProps,
76
- CreateSpaceDialog,
77
- DefaultObjectSettings,
78
- InlineSyncStatus,
79
- JOIN_DIALOG,
80
- JoinDialog,
81
- type JoinDialogProps,
82
- MenuFooter,
83
- POPOVER_RENAME_OBJECT,
84
- POPOVER_RENAME_SPACE,
85
- PopoverRenameObject,
86
- PopoverRenameSpace,
87
- SPACE_SETTINGS_DIALOG,
88
- ShareSpaceButton,
89
- SmallPresenceLive,
90
- SpacePluginSettings,
91
- SpacePresence,
92
- SpaceSettingsDialog,
93
- type SpaceSettingsDialogProps,
94
- SpaceSettingsPanel,
95
- SyncStatus,
96
- } from './components';
97
- import meta, { SPACE_PLUGIN } from './meta';
24
+ AppGraphBuilder,
25
+ AppGraphSerializer,
26
+ IdentityCreated,
27
+ IntentResolver,
28
+ ReactRoot,
29
+ ReactSurface,
30
+ Schema,
31
+ SpaceCapabilities,
32
+ SpaceSettings,
33
+ SpacesReady,
34
+ SpaceState,
35
+ } from './capabilities';
36
+ import { SpaceEvents } from './events';
37
+ import { meta, SPACE_PLUGIN } from './meta';
98
38
  import translations from './translations';
99
- import {
100
- CollectionAction,
101
- CollectionType,
102
- type PluginState,
103
- SpaceAction,
104
- type SpacePluginProvides,
105
- type SpaceSettingsProps,
106
- parseSchemaPlugin,
107
- } from './types';
108
- import {
109
- COMPOSER_SPACE_LOCK,
110
- SHARED,
111
- SPACES,
112
- SPACE_TYPE,
113
- cloneObject,
114
- constructObjectActions,
115
- constructSpaceActions,
116
- constructSpaceNode,
117
- createObjectNode,
118
- getNestedObjects,
119
- memoizeQuery,
120
- } from './util';
121
-
122
- const ACTIVE_NODE_BROADCAST_INTERVAL = 30_000;
123
- const WAIT_FOR_OBJECT_TIMEOUT = 1000;
124
- const SPACE_MAX_OBJECTS = 500;
125
- // https://stackoverflow.com/a/19016910
126
- const DIRECTORY_TYPE = 'text/directory';
127
-
128
- export const parseSpacePlugin = (plugin?: Plugin) =>
129
- Array.isArray((plugin?.provides as any).space?.enabled) ? (plugin as Plugin<SpacePluginProvides>) : undefined;
39
+ import { CollectionAction, CollectionType, defineObjectForm } from './types';
130
40
 
131
41
  export type SpacePluginOptions = {
132
42
  /**
@@ -140,1311 +50,132 @@ export type SpacePluginOptions = {
140
50
  invitationParam?: string;
141
51
 
142
52
  /**
143
- * Fired when first run logic should be executed.
144
- *
145
- * This trigger is invoked once the HALO identity is created but must only be run in one instance of the application.
146
- * As such it cannot depend directly on the HALO identity event.
147
- */
148
- firstRun?: Trigger<void>;
149
-
150
- /**
151
- * Root collection structure is created on application first run if it does not yet exist.
152
- * This callback is invoked immediately following the creation of the root collection structure.
153
- *
154
- * @param params.client DXOS Client
155
- * @param params.dispatch Function to dispatch intents
53
+ * Whether to send observability events.
156
54
  */
157
- onFirstRun?: (params: { client: Client; dispatch: PromiseIntentDispatcher }) => Promise<void>;
55
+ observability?: boolean;
158
56
  };
159
57
 
160
58
  export const SpacePlugin = ({
161
59
  invitationUrl = window.location.origin,
162
60
  invitationParam = 'spaceInvitationCode',
163
- firstRun,
164
- onFirstRun,
165
- }: SpacePluginOptions = {}): PluginDefinition<SpacePluginProvides> => {
166
- const settings = new LocalStorageStore<SpaceSettingsProps>(SPACE_PLUGIN, {});
167
- const state = new LocalStorageStore<PluginState>(SPACE_PLUGIN, {
168
- awaiting: undefined,
169
- spaceNames: {},
170
- viewersByObject: {},
171
- // TODO(wittjosiah): Stop using (Complex)Map inside reactive object.
172
- viewersByIdentity: new ComplexMap(PublicKey.hash),
173
- sdkMigrationRunning: {},
174
- navigableCollections: false,
175
- enabledEdgeReplication: false,
176
- });
177
- const subscriptions = new EventSubscriptions();
178
- const spaceSubscriptions = new EventSubscriptions();
179
- const graphSubscriptions = new Map<string, UnsubscribeCallback>();
180
- const schemas: TypedObject[] = [];
181
-
182
- let clientPlugin: Plugin<ClientPluginProvides> | undefined;
183
- let graphPlugin: Plugin<GraphProvides> | undefined;
184
- let intentPlugin: Plugin<IntentPluginProvides> | undefined;
185
- let layoutPlugin: Plugin<LayoutProvides> | undefined;
186
- let navigationPlugin: Plugin<LocationProvides> | undefined;
187
- let attentionPlugin: Plugin<AttentionPluginProvides> | undefined;
188
-
189
- const createSpaceInvitationUrl = (invitationCode: string) => {
61
+ observability = true,
62
+ }: SpacePluginOptions = {}) => {
63
+ const createInvitationUrl = (invitationCode: string) => {
190
64
  const baseUrl = new URL(invitationUrl);
191
65
  baseUrl.searchParams.set(invitationParam, invitationCode);
192
66
  return baseUrl.toString();
193
67
  };
194
68
 
195
- const onSpaceReady = async () => {
196
- if (!clientPlugin || !intentPlugin || !graphPlugin || !navigationPlugin || !layoutPlugin || !attentionPlugin) {
197
- return;
198
- }
199
-
200
- const client = clientPlugin.provides.client;
201
- const dispatch = intentPlugin.provides.intent.dispatch;
202
- const graph = graphPlugin.provides.graph;
203
- const location = navigationPlugin.provides.location;
204
- const layout = layoutPlugin.provides.layout;
205
- const attention = attentionPlugin.provides.attention;
206
- const defaultSpace = client.spaces.default;
207
-
208
- // Initialize space sharing lock in default space.
209
- if (typeof defaultSpace.properties[COMPOSER_SPACE_LOCK] !== 'boolean') {
210
- defaultSpace.properties[COMPOSER_SPACE_LOCK] = true;
211
- }
212
-
213
- const {
214
- objects: [spacesOrder],
215
- } = await defaultSpace.db.query(Filter.schema(Expando, { key: SHARED })).run();
216
- if (!spacesOrder) {
217
- // TODO(wittjosiah): Cannot be a Folder because Spaces are not TypedObjects so can't be saved in the database.
218
- // Instead, we store order as an array of space ids.
219
- defaultSpace.db.add(create({ key: SHARED, order: [] }));
220
- }
221
-
222
- // Await missing objects.
223
- subscriptions.add(
224
- scheduledEffect(
225
- () => ({
226
- layoutMode: layout.layoutMode,
227
- soloPart: location.active.solo?.[0],
228
- }),
229
- ({ layoutMode, soloPart }) => {
230
- if (layoutMode !== 'solo' || !soloPart) {
231
- return;
232
- }
233
-
234
- const node = graph.findNode(soloPart.id);
235
- if (!node && soloPart.id.length === FQ_ID_LENGTH) {
236
- const timeout = setTimeout(async () => {
237
- const node = graph.findNode(soloPart.id);
238
- if (!node) {
239
- await dispatch(createIntent(SpaceAction.WaitForObject, { id: soloPart.id }));
240
- }
241
- }, WAIT_FOR_OBJECT_TIMEOUT);
242
-
243
- return () => clearTimeout(timeout);
244
- }
245
- },
246
- ),
247
- );
248
-
249
- // Cache space names.
250
- subscriptions.add(
251
- client.spaces.subscribe(async (spaces) => {
252
- // TODO(wittjosiah): Remove. This is a hack to be able to migrate the default space properties.
253
- if (defaultSpace.state.get() === SpaceState.SPACE_REQUIRES_MIGRATION) {
254
- await defaultSpace.internal.migrate();
255
- }
256
-
257
- spaces
258
- .filter((space) => space.state.get() === SpaceState.SPACE_READY)
259
- .forEach((space) => {
260
- subscriptions.add(
261
- scheduledEffect(
262
- () => ({ name: space.properties.name }),
263
- ({ name }) => (state.values.spaceNames[space.id] = name),
264
- ),
265
- );
266
- });
267
- }).unsubscribe,
268
- );
269
-
270
- // Broadcast active node to other peers in the space.
271
- subscriptions.add(
272
- scheduledEffect(
273
- () => ({
274
- open: openIds(location.active, layout.layoutMode === 'solo' ? ['solo'] : ['main']),
275
- closed: [...location.closed],
276
- }),
277
- ({ open, closed }) => {
278
- const send = () => {
279
- const spaces = client.spaces.get();
280
- const identity = client.halo.identity.get();
281
- if (identity && location.active) {
282
- // Group parts by space for efficient messaging.
283
- const idsBySpace = reduceGroupBy(open, (id) => {
284
- try {
285
- const [spaceId] = parseFullyQualifiedId(id);
286
- return spaceId;
287
- } catch {
288
- return null;
289
- }
290
- });
291
-
292
- const removedBySpace = reduceGroupBy(closed, (id) => {
293
- try {
294
- const [spaceId] = parseFullyQualifiedId(id);
295
- return spaceId;
296
- } catch {
297
- return null;
298
- }
299
- });
300
-
301
- // NOTE: Ensure all spaces are included so that we send the correct `removed` object arrays.
302
- for (const space of spaces) {
303
- if (!idsBySpace.has(space.id)) {
304
- idsBySpace.set(space.id, []);
305
- }
306
- }
307
-
308
- for (const [spaceId, added] of idsBySpace) {
309
- const removed = removedBySpace.get(spaceId) ?? [];
310
- const space = spaces.find((space) => space.id === spaceId);
311
- if (!space) {
312
- continue;
313
- }
314
-
315
- void space
316
- .postMessage('viewing', {
317
- identityKey: identity.identityKey.toHex(),
318
- attended: attention.attended ? [...attention.attended] : [],
319
- added,
320
- removed,
321
- })
322
- // TODO(burdon): This seems defensive; why would this fail? Backoff interval.
323
- .catch((err) => {
324
- log.warn('Failed to broadcast active node for presence.', { err: err.message });
325
- });
326
- }
327
- }
328
- };
329
-
330
- send();
331
- // Send at interval to allow peers to expire entries if they become disconnected.
332
- const interval = setInterval(() => send(), ACTIVE_NODE_BROADCAST_INTERVAL);
333
- return () => clearInterval(interval);
334
- },
335
- ),
336
- );
337
-
338
- // Listen for active nodes from other peers in the space.
339
- subscriptions.add(
340
- client.spaces.subscribe((spaces) => {
341
- spaceSubscriptions.clear();
342
- spaces.forEach((space) => {
343
- spaceSubscriptions.add(
344
- space.listen('viewing', (message) => {
345
- const { added, removed, attended } = message.payload;
346
-
347
- const identityKey = PublicKey.safeFrom(message.payload.identityKey);
348
- const currentIdentity = client.halo.identity.get();
349
- if (
350
- identityKey &&
351
- !currentIdentity?.identityKey.equals(identityKey) &&
352
- Array.isArray(added) &&
353
- Array.isArray(removed)
354
- ) {
355
- added.forEach((id) => {
356
- if (typeof id === 'string') {
357
- if (!(id in state.values.viewersByObject)) {
358
- state.values.viewersByObject[id] = new ComplexMap(PublicKey.hash);
359
- }
360
- state.values.viewersByObject[id]!.set(identityKey, {
361
- lastSeen: Date.now(),
362
- currentlyAttended: new Set(attended).has(id),
363
- });
364
- if (!state.values.viewersByIdentity.has(identityKey)) {
365
- state.values.viewersByIdentity.set(identityKey, new Set());
366
- }
367
- state.values.viewersByIdentity.get(identityKey)!.add(id);
368
- }
369
- });
370
-
371
- removed.forEach((id) => {
372
- if (typeof id === 'string') {
373
- state.values.viewersByObject[id]?.delete(identityKey);
374
- state.values.viewersByIdentity.get(identityKey)?.delete(id);
375
- // It’s okay for these to be empty sets/maps, reduces churn.
376
- }
377
- });
378
- }
379
- }),
380
- );
381
- });
382
- }).unsubscribe,
383
- );
384
- };
385
-
386
- const setEdgeReplicationDefault = async (client: Client) => {
387
- try {
388
- await Promise.all(
389
- client.spaces.get().map((space) => space.internal.setEdgeReplicationPreference(EdgeReplicationSetting.ENABLED)),
390
- );
391
- state.values.enabledEdgeReplication = true;
392
- } catch (err) {
393
- log.catch(err);
394
- }
395
- };
396
-
397
- return {
398
- meta,
399
- ready: async ({ plugins }) => {
400
- settings.prop({ key: 'showHidden', type: LocalStorageStore.bool({ allowUndefined: true }) });
401
- state
402
- .prop({ key: 'spaceNames', type: LocalStorageStore.json<Record<string, string>>() })
403
- .prop({ key: 'enabledEdgeReplication', type: LocalStorageStore.bool() });
404
-
405
- // TODO(wittjosiah): Hardcoded due to circular dependency.
406
- // Should be based on a provides interface.
407
- if (findPlugin(plugins, 'dxos.org/plugin/stack')) {
408
- state.values.navigableCollections = true;
409
- }
410
-
411
- graphPlugin = resolvePlugin(plugins, parseGraphPlugin);
412
- layoutPlugin = resolvePlugin(plugins, parseLayoutPlugin);
413
- navigationPlugin = resolvePlugin(plugins, parseNavigationPlugin);
414
- attentionPlugin = resolvePlugin(plugins, parseAttentionPlugin);
415
- clientPlugin = resolvePlugin(plugins, parseClientPlugin);
416
- intentPlugin = resolvePlugin(plugins, parseIntentPlugin);
417
- if (!clientPlugin || !intentPlugin) {
418
- return;
419
- }
420
-
421
- const client = clientPlugin.provides.client;
422
- const dispatch = intentPlugin.provides.intent.dispatchPromise;
423
-
424
- schemas.push(
425
- ...filterPlugins(plugins, parseSchemaPlugin)
426
- .map((plugin) => plugin.provides.echo.schema)
427
- .filter(nonNullable)
428
- .reduce((acc, schema) => {
429
- return [...acc, ...schema];
430
- }),
431
- );
432
- client.addTypes(schemas);
433
- filterPlugins(plugins, parseSchemaPlugin).forEach((plugin) => {
434
- if (plugin.provides.echo.system) {
435
- client.addTypes(plugin.provides.echo.system);
436
- }
437
- });
438
-
439
- const handleFirstRun = async () => {
440
- const defaultSpace = client.spaces.default;
441
-
442
- // Create root collection structure.
443
- defaultSpace.properties[CollectionType.typename] = makeRef(create(CollectionType, { objects: [], views: {} }));
444
- if (Migrations.versionProperty) {
445
- defaultSpace.properties[Migrations.versionProperty] = Migrations.targetVersion;
446
- }
447
- await onFirstRun?.({ client, dispatch });
448
- };
449
-
450
- subscriptions.add(
451
- client.spaces.isReady.subscribe(async (ready) => {
452
- if (ready) {
453
- await clientPlugin?.provides.client.spaces.default.waitUntilReady();
454
- if (firstRun) {
455
- void firstRun?.wait().then(handleFirstRun);
456
- } else {
457
- await handleFirstRun();
458
- }
459
-
460
- await onSpaceReady();
461
- await setEdgeReplicationDefault(client);
462
- }
463
- }).unsubscribe,
464
- );
465
- },
466
- unload: async () => {
467
- settings.close();
468
- spaceSubscriptions.clear();
469
- subscriptions.clear();
470
- graphSubscriptions.forEach((cb) => cb());
471
- graphSubscriptions.clear();
472
- },
473
- provides: {
474
- space: state.values,
475
- settings: settings.values,
476
- translations: [...translations, osTranslations],
477
- complementary: {
478
- panels: [
479
- { id: 'settings', label: ['open settings panel label', { ns: SPACE_PLUGIN }], icon: 'ph--gear--regular' },
480
- ],
481
- },
482
- root: () => (state.values.awaiting ? <AwaitingObject id={state.values.awaiting} /> : null),
483
- metadata: {
484
- records: {
485
- [CollectionType.typename]: {
486
- createObject: (props: { name?: string }) => createIntent(CollectionAction.Create, props),
69
+ return definePlugin(meta, [
70
+ defineModule({
71
+ id: `${meta.id}/module/state`,
72
+ // TODO(wittjosiah): Does not integrate with settings store.
73
+ // Should this be a different event?
74
+ // Should settings store be renamed to be more generic?
75
+ activatesOn: oneOf(Events.SetupSettings, Events.SetupAppGraph),
76
+ activatesAfter: [SpaceEvents.StateReady],
77
+ activate: SpaceState,
78
+ }),
79
+ defineModule({
80
+ id: `${meta.id}/module/settings`,
81
+ activatesOn: Events.SetupSettings,
82
+ activate: SpaceSettings,
83
+ }),
84
+ defineModule({
85
+ id: `${meta.id}/module/translations`,
86
+ activatesOn: Events.SetupTranslations,
87
+ activate: () => contributes(Capabilities.Translations, [...translations, osTranslations]),
88
+ }),
89
+ defineModule({
90
+ id: `${meta.id}/module/metadata`,
91
+ activatesOn: Events.SetupMetadata,
92
+ activate: () =>
93
+ contributes(Capabilities.Metadata, {
94
+ id: CollectionType.typename,
95
+ metadata: {
487
96
  placeholder: ['unnamed collection label', { ns: SPACE_PLUGIN }],
488
97
  icon: 'ph--cards-three--regular',
489
98
  // TODO(wittjosiah): Move out of metadata.
490
99
  loadReferences: async (collection: CollectionType) =>
491
100
  await RefArray.loadAll([...collection.objects, ...Object.values(collection.views)]),
492
101
  },
493
- },
494
- },
495
- echo: {
496
- schema: [CollectionType],
497
- },
498
- surface: {
499
- definitions: ({ plugins }) => {
500
- const resolve = resolvePlugin(plugins, parseMetadataResolverPlugin)?.provides.metadata.resolver;
501
- const attention = resolvePlugin(plugins, parseAttentionPlugin)?.provides.attention;
502
-
503
- invariant(resolve, 'Metadata plugin not found.');
504
- invariant(attention, 'Attention plugin not found.');
505
-
506
- return [
507
- createSurface({
508
- id: `${SPACE_PLUGIN}/article`,
509
- role: 'article',
510
- filter: (data): data is { subject: Space } =>
511
- // TODO(wittjosiah): Need to avoid shotgun parsing space state everywhere.
512
- isSpace(data.subject) && data.subject.state.get() === SpaceState.SPACE_READY,
513
- component: ({ data, role, ...rest }) => (
514
- <Surface
515
- data={{ id: data.subject.id, subject: data.subject.properties[CollectionType.typename]?.target }}
516
- role={role}
517
- {...rest}
518
- />
519
- ),
520
- }),
521
- createSurface({
522
- id: `${SPACE_PLUGIN}/collection-fallback`,
523
- role: 'article',
524
- disposition: 'fallback',
525
- filter: (data): data is { subject: CollectionType } => data.subject instanceof CollectionType,
526
- component: ({ data }) => <CollectionMain collection={data.subject} />,
527
- }),
528
- createSurface({
529
- id: `${SPACE_PLUGIN}/settings-panel`,
530
- // TODO(burdon): Add role name syntax to minimal plugin docs.
531
- role: 'complementary--settings',
532
- filter: (data): data is { subject: Space } => isSpace(data.subject),
533
- component: ({ data }) => <SpaceSettingsPanel space={data.subject} />,
534
- }),
535
- createSurface({
536
- id: `${SPACE_PLUGIN}/object-settings-panel-fallback`,
537
- role: 'complementary--settings',
538
- disposition: 'fallback',
539
- filter: (data): data is { subject: ReactiveEchoObject<any> } => isEchoObject(data.subject),
540
- component: ({ data }) => <DefaultObjectSettings object={data.subject} />,
541
- }),
542
- createSurface({
543
- id: SPACE_SETTINGS_DIALOG,
544
- role: 'dialog',
545
- filter: (data): data is { subject: SpaceSettingsDialogProps } => data.component === SPACE_SETTINGS_DIALOG,
546
- component: ({ data }) => (
547
- <SpaceSettingsDialog {...data.subject} createInvitationUrl={createSpaceInvitationUrl} />
548
- ),
549
- }),
550
- createSurface({
551
- id: JOIN_DIALOG,
552
- role: 'dialog',
553
- filter: (data): data is { subject: JoinPanelProps } => data.component === JOIN_DIALOG,
554
- component: ({ data }) => <JoinDialog {...data.subject} />,
555
- }),
556
- createSurface({
557
- id: CREATE_SPACE_DIALOG,
558
- role: 'dialog',
559
- filter: (data): data is any => data.component === CREATE_SPACE_DIALOG,
560
- component: () => <CreateSpaceDialog />,
561
- }),
562
- createSurface({
563
- id: CREATE_OBJECT_DIALOG,
564
- role: 'dialog',
565
- filter: (data): data is { subject: Partial<CreateObjectDialogProps> } =>
566
- data.component === CREATE_OBJECT_DIALOG,
567
- component: ({ data }) => <CreateObjectDialog schemas={schemas} resolve={resolve} {...data.subject} />,
568
- }),
569
- createSurface({
570
- id: POPOVER_RENAME_SPACE,
571
- role: 'popover',
572
- filter: (data): data is { subject: Space } =>
573
- data.component === POPOVER_RENAME_SPACE && isSpace(data.subject),
574
- component: ({ data }) => <PopoverRenameSpace space={data.subject} />,
575
- }),
576
- createSurface({
577
- id: POPOVER_RENAME_OBJECT,
578
- role: 'popover',
579
- filter: (data): data is { subject: ReactiveEchoObject<any> } =>
580
- data.component === POPOVER_RENAME_OBJECT && isReactiveObject(data.subject),
581
- component: ({ data }) => <PopoverRenameObject object={data.subject} />,
582
- }),
583
- createSurface({
584
- id: `${SPACE_PLUGIN}/navtree-presence`,
585
- role: 'navtree-item-end',
586
- filter: (data): data is { id: string; subject: ReactiveEchoObject<any>; open?: boolean } =>
587
- typeof data.id === 'string' && isEchoObject(data.subject),
588
- component: ({ data }) => (
589
- <SmallPresenceLive id={data.id} open={data.open} viewers={state.values.viewersByObject[data.id]} />
590
- ),
591
- }),
592
- createSurface({
593
- // TODO(wittjosiah): Attention glyph for non-echo items should be handled elsewhere.
594
- id: `${SPACE_PLUGIN}/navtree-presence-fallback`,
595
- role: 'navtree-item-end',
596
- disposition: 'fallback',
597
- filter: (data): data is { id: string; open?: boolean } => typeof data.id === 'string',
598
- component: ({ data }) => <SmallPresenceLive id={data.id} open={data.open} />,
599
- }),
600
- createSurface({
601
- id: `${SPACE_PLUGIN}/navtree-sync-status`,
602
- role: 'navtree-item-end',
603
- filter: (data): data is { subject: Space; open?: boolean } => isSpace(data.subject),
604
- component: ({ data }) => <InlineSyncStatus space={data.subject} open={data.open} />,
605
- }),
606
- createSurface({
607
- id: `${SPACE_PLUGIN}/navbar-presence`,
608
- role: 'navbar-end',
609
- disposition: 'hoist',
610
- filter: (data): data is { subject: Space | ReactiveEchoObject<any> } =>
611
- isSpace(data.subject) || isEchoObject(data.subject),
612
- component: ({ data }) => {
613
- const space = isSpace(data.subject) ? data.subject : getSpace(data.subject);
614
- const object = isSpace(data.subject)
615
- ? data.subject.state.get() === SpaceState.SPACE_READY
616
- ? (space?.properties[CollectionType.typename]?.target as CollectionType)
617
- : undefined
618
- : data.subject;
619
-
620
- return space && object ? (
621
- <>
622
- <SpacePresence object={object} />
623
- {space.properties[COMPOSER_SPACE_LOCK] ? null : <ShareSpaceButton space={space} />}
624
- </>
625
- ) : null;
626
- },
627
- }),
628
- createSurface({
629
- id: `${SPACE_PLUGIN}/collection-section`,
630
- role: 'section',
631
- filter: (data): data is { subject: CollectionType } => data.subject instanceof CollectionType,
632
- component: ({ data }) => <CollectionSection collection={data.subject} />,
633
- }),
634
- createSurface({
635
- id: `${SPACE_PLUGIN}/settings`,
636
- role: 'settings',
637
- filter: (data): data is any => data.subject === SPACE_PLUGIN,
638
- component: () => <SpacePluginSettings settings={settings.values} />,
639
- }),
640
- createSurface({
641
- id: `${SPACE_PLUGIN}/menu-footer`,
642
- role: 'menu-footer',
643
- filter: (data): data is { subject: ReactiveEchoObject<any> } => isEchoObject(data.subject),
644
- component: ({ data }) => <MenuFooter object={data.subject} />,
645
- }),
646
- createSurface({
647
- id: `${SPACE_PLUGIN}/status`,
648
- role: 'status',
649
- component: () => <SyncStatus />,
650
- }),
651
- ];
652
- },
653
- },
654
- graph: {
655
- builder: (plugins) => {
656
- const clientPlugin = resolvePlugin(plugins, parseClientPlugin);
657
- const metadataPlugin = resolvePlugin(plugins, parseMetadataResolverPlugin);
658
- const graphPlugin = resolvePlugin(plugins, parseGraphPlugin);
659
-
660
- const client = clientPlugin?.provides.client;
661
- const dispatch = intentPlugin?.provides.intent.dispatchPromise;
662
- const resolve = metadataPlugin?.provides.metadata.resolver;
663
- const graph = graphPlugin?.provides.graph;
664
- if (!client || !dispatch || !resolve || !graph) {
665
- return [];
666
- }
667
-
668
- const spacesNode = {
669
- id: SPACES,
670
- type: SPACES,
671
- cacheable: ['label', 'role'],
672
- properties: {
673
- label: ['spaces label', { ns: SPACE_PLUGIN }],
674
- testId: 'spacePlugin.spaces',
675
- role: 'branch',
676
- disabled: true,
677
- childrenPersistenceClass: 'echo',
678
- onRearrangeChildren: async (nextOrder: Space[]) => {
679
- // NOTE: This is needed to ensure order is updated by next animation frame.
680
- // TODO(wittjosiah): Is there a better way to do this?
681
- // If not, graph should be passed as an argument to the extension.
682
- graph._sortEdges(
683
- SPACES,
684
- 'outbound',
685
- nextOrder.map(({ id }) => id),
686
- );
687
-
688
- const {
689
- objects: [spacesOrder],
690
- } = await client.spaces.default.db.query(Filter.schema(Expando, { key: SHARED })).run();
691
- if (spacesOrder) {
692
- spacesOrder.order = nextOrder.map(({ id }) => id);
693
- } else {
694
- log.warn('spaces order object not found');
695
- }
696
- },
697
- },
698
- };
699
-
700
- return [
701
- // Primary actions.
702
- createExtension({
703
- id: `${SPACE_PLUGIN}/primary-actions`,
704
- filter: (node): node is Node<null> => node.id === 'root',
705
- actions: () => [
706
- {
707
- id: SpaceAction.OpenCreateSpace._tag,
708
- data: async () => {
709
- await dispatch(createIntent(SpaceAction.OpenCreateSpace));
710
- },
711
- properties: {
712
- label: ['create space label', { ns: SPACE_PLUGIN }],
713
- icon: 'ph--plus--regular',
714
- testId: 'spacePlugin.createSpace',
715
- disposition: 'item',
716
- },
717
- },
718
- {
719
- id: SpaceAction.Join._tag,
720
- data: async () => {
721
- await dispatch(createIntent(SpaceAction.Join));
722
- },
723
- properties: {
724
- label: ['join space label', { ns: SPACE_PLUGIN }],
725
- icon: 'ph--sign-in--regular',
726
- testId: 'spacePlugin.joinSpace',
727
- disposition: 'item',
728
- className: 'border-b border-separator',
729
- },
730
- },
731
- ],
732
- }),
733
-
734
- // Create spaces group node.
735
- createExtension({
736
- id: `${SPACE_PLUGIN}/root`,
737
- filter: (node): node is Node<null> => node.id === 'root',
738
- connector: () => [spacesNode],
739
- resolver: ({ id }) => (id === SPACES ? spacesNode : undefined),
740
- }),
741
-
742
- // Create space nodes.
743
- createExtension({
744
- id: SPACES,
745
- filter: (node): node is Node<null> => node.id === SPACES,
746
- connector: () => {
747
- const spaces = toSignal(
748
- (onChange) => client.spaces.subscribe(() => onChange()).unsubscribe,
749
- () => client.spaces.get(),
750
- );
751
-
752
- const isReady = toSignal(
753
- (onChange) => client.spaces.isReady.subscribe(() => onChange()).unsubscribe,
754
- () => client.spaces.isReady.get(),
755
- );
756
-
757
- if (!spaces || !isReady) {
758
- return;
759
- }
760
-
761
- // TODO(wittjosiah): During client reset, accessing default space throws.
762
- try {
763
- const [spacesOrder] = memoizeQuery(client.spaces.default, Filter.schema(Expando, { key: SHARED }));
764
- const order: string[] = spacesOrder?.order ?? [];
765
- const orderMap = new Map(order.map((id, index) => [id, index]));
766
- return [
767
- ...spaces
768
- .filter((space) => orderMap.has(space.id))
769
- .sort((a, b) => orderMap.get(a.id)! - orderMap.get(b.id)!),
770
- ...spaces.filter((space) => !orderMap.has(space.id)),
771
- ]
772
- .filter((space) =>
773
- settings.values.showHidden ? true : space.state.get() !== SpaceState.SPACE_INACTIVE,
774
- )
775
- .map((space) =>
776
- constructSpaceNode({
777
- space,
778
- navigable: state.values.navigableCollections,
779
- personal: space === client.spaces.default,
780
- namesCache: state.values.spaceNames,
781
- resolve,
782
- }),
783
- );
784
- } catch {}
785
- },
786
- resolver: ({ id }) => {
787
- if (id.length !== SPACE_ID_LENGTH) {
788
- return;
789
- }
790
-
791
- const spaces = toSignal(
792
- (onChange) => client.spaces.subscribe(() => onChange()).unsubscribe,
793
- () => client.spaces.get(),
794
- );
795
-
796
- const isReady = toSignal(
797
- (onChange) => client.spaces.isReady.subscribe(() => onChange()).unsubscribe,
798
- () => client.spaces.isReady.get(),
799
- );
800
-
801
- if (!spaces || !isReady) {
802
- return;
803
- }
804
-
805
- const space = spaces.find((space) => space.id === id);
806
- if (!space) {
807
- return;
808
- }
809
-
810
- if (space.state.get() === SpaceState.SPACE_INACTIVE) {
811
- return false;
812
- } else {
813
- return constructSpaceNode({
814
- space,
815
- navigable: state.values.navigableCollections,
816
- personal: space === client.spaces.default,
817
- namesCache: state.values.spaceNames,
818
- resolve,
819
- });
820
- }
821
- },
822
- }),
823
-
824
- // Create space actions.
825
- createExtension({
826
- id: `${SPACE_PLUGIN}/actions`,
827
- filter: (node): node is Node<Space> => isSpace(node.data),
828
- actions: ({ node }) => {
829
- const space = node.data;
830
- return constructSpaceActions({
831
- space,
832
- dispatch,
833
- personal: space === client.spaces.default,
834
- migrating: state.values.sdkMigrationRunning[space.id],
835
- });
836
- },
837
- }),
838
-
839
- // Create nodes for objects in the root collection of a space.
840
- createExtension({
841
- id: `${SPACE_PLUGIN}/root-collection`,
842
- filter: (node): node is Node<Space> => isSpace(node.data),
843
- connector: ({ node }) => {
844
- const space = node.data;
845
- const spaceState = toSignal(
846
- (onChange) => space.state.subscribe(() => onChange()).unsubscribe,
847
- () => space.state.get(),
848
- space.id,
849
- );
850
- if (spaceState !== SpaceState.SPACE_READY) {
851
- return;
852
- }
853
-
854
- const collection = space.properties[CollectionType.typename]?.target as CollectionType | undefined;
855
- if (!collection) {
856
- return;
857
- }
858
-
859
- return collection.objects
860
- .map((object) => object.target)
861
- .filter(nonNullable)
862
- .map((object) =>
863
- createObjectNode({ object, space, resolve, navigable: state.values.navigableCollections }),
864
- )
865
- .filter(nonNullable);
866
- },
867
- }),
868
-
869
- // Create nodes for objects in a collection or by its fully qualified id.
870
- createExtension({
871
- id: `${SPACE_PLUGIN}/objects`,
872
- filter: (node): node is Node<CollectionType> => node.data instanceof CollectionType,
873
- connector: ({ node }) => {
874
- const collection = node.data;
875
- const space = getSpace(collection);
876
- if (!space) {
877
- return;
878
- }
879
-
880
- return collection.objects
881
- .map((object) => object.target)
882
- .filter(nonNullable)
883
- .map((object) =>
884
- createObjectNode({ object, space, resolve, navigable: state.values.navigableCollections }),
885
- )
886
- .filter(nonNullable);
887
- },
888
- resolver: ({ id }) => {
889
- if (id.length !== FQ_ID_LENGTH) {
890
- return;
891
- }
892
-
893
- const [spaceId, objectId] = id.split(':');
894
- if (spaceId.length !== SPACE_ID_LENGTH && objectId.length !== OBJECT_ID_LENGTH) {
895
- return;
896
- }
897
-
898
- const space = client.spaces.get().find((space) => space.id === spaceId);
899
- if (!space) {
900
- return;
901
- }
902
-
903
- const spaceState = toSignal(
904
- (onChange) => space.state.subscribe(() => onChange()).unsubscribe,
905
- () => space.state.get(),
906
- space.id,
907
- );
908
- if (spaceState !== SpaceState.SPACE_READY) {
909
- return;
910
- }
911
-
912
- const store = memoize(() => signal(space.db.getObjectById(objectId)), id);
913
- memoize(() => {
914
- if (!store.value) {
915
- void space.db
916
- .query({ id: objectId }, { deleted: QueryOptions.ShowDeletedOption.SHOW_DELETED })
917
- .first()
918
- .then((o) => (store.value = o))
919
- .catch((err) => log.catch(err, { objectId }));
920
- }
921
- }, id);
922
- const object = store.value;
923
- if (!object) {
924
- return;
925
- }
926
-
927
- if (isDeleted(object)) {
928
- return false;
929
- } else {
930
- return createObjectNode({ object, space, resolve, navigable: state.values.navigableCollections });
931
- }
932
- },
933
- }),
934
-
935
- // Create collection actions and action groups.
936
- createExtension({
937
- id: `${SPACE_PLUGIN}/object-actions`,
938
- filter: (node): node is Node<ReactiveEchoObject<any>> => isEchoObject(node.data),
939
- actions: ({ node }) => constructObjectActions({ node, dispatch }),
940
- }),
941
-
942
- // Create nodes for object settings.
943
- createExtension({
944
- id: `${SPACE_PLUGIN}/settings-for-subject`,
945
- resolver: ({ id }) => {
946
- // TODO(Zan): Find util (or make one).
947
- if (!id.endsWith('~settings')) {
948
- return;
949
- }
950
-
951
- const type = 'orphan-settings-for-subject';
952
- const icon = 'ph--gear--regular';
953
-
954
- const [subjectId] = id.split('~');
955
- const { spaceId, objectId } = parseId(subjectId);
956
- const spaces = toSignal(
957
- (onChange) => client.spaces.subscribe(() => onChange()).unsubscribe,
958
- () => client.spaces.get(),
959
- );
960
- const space = spaces?.find(
961
- (space) => space.id === spaceId && space.state.get() === SpaceState.SPACE_READY,
962
- );
963
- if (!objectId) {
964
- const label = space
965
- ? space.properties.name || ['unnamed space label', { ns: SPACE_PLUGIN }]
966
- : ['unnamed object settings label', { ns: SPACE_PLUGIN }];
967
-
968
- // TODO(wittjosiah): Support comments for arbitrary subjects.
969
- // This is to ensure that the comments panel is not stuck on an old object.
970
- return {
971
- id,
972
- type,
973
- data: null,
974
- properties: {
975
- icon,
976
- label,
977
- showResolvedThreads: false,
978
- object: null,
979
- space,
980
- },
981
- };
982
- }
983
-
984
- const [object] = memoizeQuery(space, { id: objectId });
985
- if (!object || !subjectId) {
986
- return;
987
- }
988
-
989
- const meta = resolve(getTypename(object) ?? '');
990
- const label = meta.label?.(object) ||
991
- object.name ||
992
- meta.placeholder || ['unnamed object settings label', { ns: SPACE_PLUGIN }];
993
-
994
- return {
995
- id,
996
- type,
997
- data: null,
998
- properties: {
999
- icon,
1000
- label,
1001
- object,
1002
- },
1003
- };
1004
- },
1005
- }),
1006
- ];
1007
- },
1008
- serializer: (plugins) => {
1009
- const dispatch = resolvePlugin(plugins, parseIntentPlugin)?.provides.intent.dispatchPromise;
1010
- if (!dispatch) {
1011
- return [];
1012
- }
1013
-
1014
- return [
1015
- {
1016
- inputType: SPACES,
1017
- outputType: DIRECTORY_TYPE,
1018
- serialize: (node) => ({
1019
- name: translations[0]['en-US'][SPACE_PLUGIN]['spaces label'],
1020
- data: translations[0]['en-US'][SPACE_PLUGIN]['spaces label'],
1021
- type: DIRECTORY_TYPE,
1022
- }),
1023
- deserialize: () => {
1024
- // No-op.
1025
- },
1026
- },
1027
- {
1028
- inputType: SPACE_TYPE,
1029
- outputType: DIRECTORY_TYPE,
1030
- serialize: (node) => ({
1031
- name: node.data.properties.name ?? translations[0]['en-US'][SPACE_PLUGIN]['unnamed space label'],
1032
- data: node.data.properties.name ?? translations[0]['en-US'][SPACE_PLUGIN]['unnamed space label'],
1033
- type: DIRECTORY_TYPE,
1034
- }),
1035
- deserialize: async (data) => {
1036
- const result = await dispatch(
1037
- createIntent(SpaceAction.Create, { name: data.name, edgeReplication: true }),
1038
- );
1039
- return result.data?.space;
1040
- },
1041
- },
1042
- {
1043
- inputType: CollectionType.typename,
1044
- outputType: DIRECTORY_TYPE,
1045
- serialize: (node) => ({
1046
- name: node.data.name ?? translations[0]['en-US'][SPACE_PLUGIN]['unnamed collection label'],
1047
- data: node.data.name ?? translations[0]['en-US'][SPACE_PLUGIN]['unnamed collection label'],
1048
- type: DIRECTORY_TYPE,
1049
- }),
1050
- deserialize: async (data, ancestors) => {
1051
- const space = ancestors.find(isSpace);
1052
- const collection =
1053
- ancestors.findLast((ancestor) => ancestor instanceof CollectionType) ??
1054
- space?.properties[CollectionType.typename]?.target;
1055
- if (!space || !collection) {
1056
- return;
1057
- }
1058
-
1059
- const result = await dispatch(
1060
- createIntent(SpaceAction.AddObject, {
1061
- target: collection,
1062
- object: create(CollectionType, { name: data.name, objects: [], views: {} }),
1063
- }),
1064
- );
1065
-
1066
- return result.data?.object;
1067
- },
1068
- },
1069
- ];
1070
- },
1071
- },
1072
- intent: {
1073
- resolvers: ({ plugins, dispatchPromise: dispatch }) => {
1074
- const activeParts = resolvePlugin(plugins, parseNavigationPlugin)?.provides.location.active;
1075
- const client = resolvePlugin(plugins, parseClientPlugin)?.provides.client;
1076
- const resolve = resolvePlugin(plugins, parseMetadataResolverPlugin)?.provides.metadata.resolver;
1077
-
1078
- invariant(activeParts, 'Active parts not available.');
1079
- invariant(client, 'Client not available.');
1080
- invariant(resolve, 'Metadata resolver not available.');
1081
-
1082
- return [
1083
- createResolver(SpaceAction.OpenCreateSpace, () => ({
1084
- intents: [
1085
- createIntent(LayoutAction.SetLayout, {
1086
- element: 'dialog',
1087
- component: CREATE_SPACE_DIALOG,
1088
- dialogBlockAlign: 'start',
1089
- }),
1090
- ],
1091
- })),
1092
- createResolver(SpaceAction.Create, async ({ name, edgeReplication }) => {
1093
- const space = await client.spaces.create({ name });
1094
- if (edgeReplication) {
1095
- await space.internal.setEdgeReplicationPreference(EdgeReplicationSetting.ENABLED);
1096
- }
1097
- await space.waitUntilReady();
1098
- const collection = create(CollectionType, { objects: [], views: {} });
1099
- space.properties[CollectionType.typename] = makeRef(collection);
1100
-
1101
- if (Migrations.versionProperty) {
1102
- space.properties[Migrations.versionProperty] = Migrations.targetVersion;
1103
- }
1104
-
1105
- return {
1106
- data: {
1107
- space,
1108
- id: space.id,
1109
- activeParts: { main: [space.id] },
1110
- },
1111
- intents: [
1112
- createIntent(ObservabilityAction.SendEvent, {
1113
- name: 'space.create',
1114
- properties: {
1115
- spaceId: space.id,
1116
- },
1117
- }),
1118
- ],
1119
- };
1120
- }),
1121
- createResolver(SpaceAction.Join, ({ invitationCode }) => ({
1122
- intents: [
1123
- createIntent(LayoutAction.SetLayout, {
1124
- element: 'dialog',
1125
- component: JOIN_DIALOG,
1126
- dialogBlockAlign: 'start',
1127
- subject: {
1128
- initialInvitationCode: invitationCode,
1129
- } satisfies Partial<JoinDialogProps>,
1130
- }),
1131
- ],
1132
- })),
1133
- createResolver(
1134
- SpaceAction.Share,
1135
- ({ space }) => {
1136
- const active = navigationPlugin?.provides.location.active;
1137
- const mode = layoutPlugin?.provides.layout.layoutMode;
1138
- const current = active ? firstIdInPart(active, mode === 'solo' ? 'solo' : 'main') : undefined;
1139
- const target = current?.startsWith(space.id) ? current : undefined;
1140
-
1141
- return {
1142
- intents: [
1143
- createIntent(LayoutAction.SetLayout, {
1144
- element: 'dialog',
1145
- component: SPACE_SETTINGS_DIALOG,
1146
- dialogBlockAlign: 'start',
1147
- subject: {
1148
- space,
1149
- target,
1150
- initialTab: 'members',
1151
- createInvitationUrl: createSpaceInvitationUrl,
1152
- } satisfies Partial<SpaceSettingsDialogProps>,
1153
- }),
1154
- createIntent(ObservabilityAction.SendEvent, {
1155
- name: 'space.share',
1156
- properties: {
1157
- space: space.id,
1158
- },
1159
- }),
1160
- ],
1161
- };
1162
- },
1163
- { filter: (data): data is { space: Space } => !data.space.properties[COMPOSER_SPACE_LOCK] },
1164
- ),
1165
- createResolver(SpaceAction.Lock, ({ space }) => {
1166
- space.properties[COMPOSER_SPACE_LOCK] = true;
1167
- return {
1168
- intents: [
1169
- createIntent(ObservabilityAction.SendEvent, {
1170
- name: 'space.lock',
1171
- properties: {
1172
- spaceId: space.id,
1173
- },
1174
- }),
1175
- ],
1176
- };
1177
- }),
1178
- createResolver(SpaceAction.Unlock, ({ space }) => {
1179
- space.properties[COMPOSER_SPACE_LOCK] = false;
1180
- return {
1181
- intents: [
1182
- createIntent(ObservabilityAction.SendEvent, {
1183
- name: 'space.unlock',
1184
- properties: {
1185
- spaceId: space.id,
1186
- },
1187
- }),
1188
- ],
1189
- };
1190
- }),
1191
- createResolver(SpaceAction.Rename, ({ caller, space }) => {
1192
- return {
1193
- intents: [
1194
- createIntent(LayoutAction.SetLayout, {
1195
- element: 'popover',
1196
- anchorId: `dxos.org/ui/${caller}/${space.id}`,
1197
- component: POPOVER_RENAME_SPACE,
1198
- subject: space,
1199
- }),
1200
- ],
1201
- };
1202
- }),
1203
- createResolver(SpaceAction.OpenSettings, ({ space }) => {
1204
- return {
1205
- intents: [
1206
- createIntent(LayoutAction.SetLayout, {
1207
- element: 'dialog',
1208
- component: SPACE_SETTINGS_DIALOG,
1209
- dialogBlockAlign: 'start',
1210
- subject: {
1211
- space,
1212
- initialTab: 'settings',
1213
- createInvitationUrl: createSpaceInvitationUrl,
1214
- } satisfies Partial<SpaceSettingsDialogProps>,
1215
- }),
1216
- ],
1217
- };
1218
- }),
1219
- createResolver(SpaceAction.Open, async ({ space }) => {
1220
- await space.open();
1221
- }),
1222
- createResolver(SpaceAction.Close, async ({ space }) => {
1223
- await space.close();
1224
- }),
1225
- createResolver(SpaceAction.Migrate, async ({ space, version }) => {
1226
- if (space.state.get() === SpaceState.SPACE_REQUIRES_MIGRATION) {
1227
- state.values.sdkMigrationRunning[space.id] = true;
1228
- await space.internal.migrate();
1229
- state.values.sdkMigrationRunning[space.id] = false;
1230
- }
1231
- const result = await Migrations.migrate(space, version);
1232
- return {
1233
- data: result,
1234
- intents: [
1235
- createIntent(ObservabilityAction.SendEvent, {
1236
- name: 'space.migrate',
1237
- properties: {
1238
- spaceId: space.id,
1239
- version,
1240
- },
1241
- }),
1242
- ],
1243
- };
1244
- }),
1245
- createResolver(SpaceAction.OpenCreateObject, ({ target, navigable = true }) => {
1246
- return {
1247
- intents: [
1248
- createIntent(LayoutAction.SetLayout, {
1249
- element: 'dialog',
1250
- component: CREATE_OBJECT_DIALOG,
1251
- dialogBlockAlign: 'start',
1252
- subject: {
1253
- target,
1254
- shouldNavigate: navigable
1255
- ? (object: ReactiveObject<any>) =>
1256
- !(object instanceof CollectionType) || state.values.navigableCollections
1257
- : () => false,
1258
- } satisfies Partial<CreateObjectDialogProps>,
1259
- }),
1260
- ],
1261
- };
1262
- }),
1263
- createResolver(SpaceAction.AddObject, async ({ target, object }) => {
1264
- const space = isSpace(target) ? target : getSpace(target);
1265
- invariant(space, 'Space not found.');
1266
-
1267
- if (space.db.coreDatabase.getAllObjectIds().length >= SPACE_MAX_OBJECTS) {
1268
- void dispatch(
1269
- createIntent(LayoutAction.SetLayout, {
1270
- element: 'toast',
1271
- subject: {
1272
- id: `${SPACE_PLUGIN}/space-limit`,
1273
- title: ['space limit label', { ns: SPACE_PLUGIN }],
1274
- description: ['space limit description', { ns: SPACE_PLUGIN }],
1275
- duration: 5_000,
1276
- icon: 'ph--warning--regular',
1277
- actionLabel: ['remove deleted objects label', { ns: SPACE_PLUGIN }],
1278
- actionAlt: ['remove deleted objects alt', { ns: SPACE_PLUGIN }],
1279
- closeLabel: ['close label', { ns: 'os' }],
1280
- onAction: () => space.db.coreDatabase.unlinkDeletedObjects(),
1281
- },
1282
- }),
1283
- );
1284
- void dispatch(
1285
- createIntent(ObservabilityAction.SendEvent, {
1286
- name: 'space.limit',
1287
- properties: {
1288
- spaceId: space.id,
1289
- },
1290
- }),
1291
- );
1292
-
1293
- throw new Error('Space limit reached.');
1294
- }
1295
-
1296
- if (target instanceof CollectionType) {
1297
- target.objects.push(makeRef(object as HasId));
1298
- } else if (isSpace(target)) {
1299
- const collection = space.properties[CollectionType.typename]?.target;
1300
- if (collection instanceof CollectionType) {
1301
- collection.objects.push(makeRef(object as HasId));
1302
- } else {
1303
- // TODO(wittjosiah): Can't add non-echo objects by including in a collection because of types.
1304
- const collection = create(CollectionType, { objects: [makeRef(object as HasId)], views: {} });
1305
- space.properties[CollectionType.typename] = makeRef(collection);
1306
- }
1307
- }
1308
-
1309
- return {
1310
- data: {
1311
- id: fullyQualifiedId(object),
1312
- object: object as HasId,
1313
- activeParts: { main: [fullyQualifiedId(object)] },
1314
- },
1315
- intents: [
1316
- createIntent(ObservabilityAction.SendEvent, {
1317
- name: 'space.object.add',
1318
- properties: {
1319
- spaceId: space.id,
1320
- objectId: object.id,
1321
- typename: getTypename(object),
1322
- },
1323
- }),
1324
- ],
1325
- };
1326
- }),
1327
- createResolver(SpaceAction.RemoveObjects, async ({ objects, target, deletionData }, undo) => {
1328
- // All objects must be a member of the same space.
1329
- const space = getSpace(objects[0]);
1330
- invariant(space && objects.every((obj) => isEchoObject(obj) && getSpace(obj) === space));
1331
- const openObjectIds = new Set<string>(openIds(activeParts ?? {}));
1332
-
1333
- if (!undo) {
1334
- const parentCollection: CollectionType = target ?? space.properties[CollectionType.typename]?.target;
1335
- const nestedObjectsList = await Promise.all(objects.map((obj) => getNestedObjects(obj, resolve)));
1336
-
1337
- const deletionData = {
1338
- objects,
1339
- parentCollection,
1340
- indices: objects.map((obj) =>
1341
- parentCollection instanceof CollectionType
1342
- ? parentCollection.objects.findIndex((object) => object.target === obj)
1343
- : -1,
1344
- ),
1345
- nestedObjectsList,
1346
- wasActive: objects
1347
- .flatMap((obj, i) => [obj, ...nestedObjectsList[i]])
1348
- .map((obj) => fullyQualifiedId(obj))
1349
- .filter((id) => openObjectIds.has(id)),
1350
- } satisfies SpaceAction.DeletionData;
1351
-
1352
- if (deletionData.parentCollection instanceof CollectionType) {
1353
- [...deletionData.indices]
1354
- .sort((a, b) => b - a)
1355
- .forEach((index: number) => {
1356
- if (index !== -1) {
1357
- deletionData.parentCollection.objects.splice(index, 1);
1358
- }
1359
- });
1360
- }
1361
-
1362
- deletionData.nestedObjectsList.flat().forEach((obj) => {
1363
- space.db.remove(obj);
1364
- });
1365
- objects.forEach((obj) => space.db.remove(obj));
1366
-
1367
- const undoMessageKey = objects.some((obj) => obj instanceof CollectionType)
1368
- ? 'collection deleted label'
1369
- : objects.length > 1
1370
- ? 'objects deleted label'
1371
- : 'object deleted label';
1372
-
1373
- return {
1374
- undoable: {
1375
- // TODO(ZaymonFC): Pluralize if more than one object.
1376
- message: [undoMessageKey, { ns: SPACE_PLUGIN }],
1377
- data: { deletionData },
1378
- },
1379
- intents:
1380
- deletionData.wasActive.length > 0
1381
- ? [createIntent(NavigationAction.Close, { activeParts: { main: deletionData.wasActive } })]
1382
- : undefined,
1383
- };
1384
- } else {
1385
- if (
1386
- deletionData?.objects?.length &&
1387
- deletionData.objects.every(isEchoObject) &&
1388
- deletionData.parentCollection instanceof CollectionType
1389
- ) {
1390
- // Restore the object to the space.
1391
- const restoredObjects = deletionData.objects.map((obj: Expando) => space.db.add(obj));
1392
-
1393
- // Restore nested objects to the space.
1394
- deletionData.nestedObjectsList.flat().forEach((obj: Expando) => {
1395
- space.db.add(obj);
1396
- });
1397
-
1398
- deletionData.indices.forEach((index: number, i: number) => {
1399
- if (index !== -1) {
1400
- deletionData.parentCollection.objects.splice(index, 0, makeRef(restoredObjects[i] as Expando));
1401
- }
1402
- });
1403
-
1404
- return {
1405
- intents:
1406
- deletionData.wasActive.length > 0
1407
- ? [
1408
- createIntent(NavigationAction.Open, {
1409
- activeParts: { main: deletionData.wasActive as string[] },
1410
- }),
1411
- ]
1412
- : undefined,
1413
- };
1414
- }
1415
- }
1416
- }),
1417
- createResolver(SpaceAction.RenameObject, async ({ object, caller }) => ({
1418
- intents: [
1419
- createIntent(LayoutAction.SetLayout, {
1420
- element: 'popover',
1421
- anchorId: `dxos.org/ui/${caller}/${fullyQualifiedId(object)}`,
1422
- component: POPOVER_RENAME_OBJECT,
1423
- subject: object,
1424
- }),
1425
- ],
1426
- })),
1427
- createResolver(SpaceAction.DuplicateObject, async ({ object, target }) => {
1428
- const space = isSpace(target) ? target : getSpace(target);
1429
- invariant(space, 'Space not found.');
1430
-
1431
- const newObject = await cloneObject(object, resolve, space);
1432
- return {
1433
- intents: [createIntent(SpaceAction.AddObject, { object: newObject, target })],
1434
- };
1435
- }),
1436
- createResolver(SpaceAction.WaitForObject, async ({ id }) => {
1437
- state.values.awaiting = id;
1438
- }),
1439
- createResolver(SpaceAction.ToggleHidden, async ({ state }) => {
1440
- settings.values.showHidden = state;
1441
- }),
1442
- createResolver(CollectionAction.Create, async ({ name }) => ({
1443
- data: { object: create(CollectionType, { name, objects: [], views: {} }) },
1444
- })),
1445
- ];
1446
- },
1447
- },
1448
- },
1449
- };
102
+ }),
103
+ }),
104
+ defineModule({
105
+ id: `${meta.id}/module/object-form`,
106
+ activatesOn: ClientEvents.SetupSchema,
107
+ activate: () =>
108
+ contributes(
109
+ SpaceCapabilities.ObjectForm,
110
+ defineObjectForm({
111
+ objectSchema: CollectionType,
112
+ formSchema: S.Struct({ name: S.optional(S.String) }),
113
+ getIntent: (props) => createIntent(CollectionAction.Create, props),
114
+ }),
115
+ ),
116
+ }),
117
+ defineModule({
118
+ id: `${meta.id}/module/complementary-panel`,
119
+ activatesOn: DeckEvents.SetupComplementaryPanels,
120
+ activate: () =>
121
+ contributes(DeckCapabilities.ComplementaryPanel, {
122
+ id: 'settings',
123
+ label: ['settings panel label', { ns: SPACE_PLUGIN }],
124
+ icon: 'ph--sliders--regular',
125
+ filter: (node) => isEchoObject(node.data) && !!getSpace(node.data),
126
+ }),
127
+ }),
128
+ defineModule({
129
+ id: `${meta.id}/module/schema`,
130
+ activatesOn: ClientEvents.ClientReady,
131
+ activatesBefore: [ClientEvents.SetupSchema],
132
+ activate: Schema,
133
+ }),
134
+ defineModule({
135
+ id: `${meta.id}/module/react-root`,
136
+ activatesOn: Events.Startup,
137
+ activate: ReactRoot,
138
+ }),
139
+ defineModule({
140
+ id: `${meta.id}/module/react-surface`,
141
+ activatesOn: Events.SetupReactSurface,
142
+ // TODO(wittjosiah): Should occur before the settings dialog is loaded when surfaces activation is more granular.
143
+ activatesBefore: [SpaceEvents.SetupSettingsPanel],
144
+ activate: () => ReactSurface({ createInvitationUrl }),
145
+ }),
146
+ defineModule({
147
+ id: `${meta.id}/module/intent-resolver`,
148
+ activatesOn: Events.SetupIntentResolver,
149
+ activate: (context) => IntentResolver({ createInvitationUrl, context, observability }),
150
+ }),
151
+ defineModule({
152
+ id: `${meta.id}/module/app-graph-builder`,
153
+ activatesOn: Events.SetupAppGraph,
154
+ activate: AppGraphBuilder,
155
+ }),
156
+ // TODO(wittjosiah): This could probably be deferred.
157
+ defineModule({
158
+ id: `${meta.id}/module/app-graph-serializer`,
159
+ activatesOn: Events.AppGraphReady,
160
+ activate: AppGraphSerializer,
161
+ }),
162
+ defineModule({
163
+ id: `${meta.id}/module/identity-created`,
164
+ activatesOn: ClientEvents.IdentityCreated,
165
+ activatesAfter: [SpaceEvents.DefaultSpaceReady],
166
+ activate: IdentityCreated,
167
+ }),
168
+ defineModule({
169
+ id: `${meta.id}/module/spaces-ready`,
170
+ activatesOn: allOf(
171
+ Events.DispatcherReady,
172
+ Events.LayoutReady,
173
+ Events.AppGraphReady,
174
+ AttentionEvents.AttentionReady,
175
+ SpaceEvents.StateReady,
176
+ ClientEvents.SpacesReady,
177
+ ),
178
+ activate: SpacesReady,
179
+ }),
180
+ ]);
1450
181
  };