@dxos/plugin-space 0.6.13 → 0.6.14-main.2b6a0f3

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 (94) hide show
  1. package/dist/lib/browser/{chunk-DTVUOG2C.mjs → chunk-47SVNCZM.mjs} +24 -5
  2. package/dist/lib/browser/chunk-47SVNCZM.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-LZEGRS7H.mjs → chunk-AVLRQF6L.mjs} +1 -1
  4. package/dist/lib/browser/chunk-AVLRQF6L.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +609 -250
  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-CTYDNFGG.cjs} +30 -9
  11. package/dist/lib/node/chunk-CTYDNFGG.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 +827 -471
  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-PLPMYTLC.mjs +116 -0
  22. package/dist/lib/node-esm/chunk-PLPMYTLC.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 +3000 -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.stories.d.ts +4 -92
  43. package/dist/types/src/components/SpacePresence.stories.d.ts.map +1 -1
  44. package/dist/types/src/components/SpaceSettings.d.ts.map +1 -1
  45. package/dist/types/src/components/SyncStatus/SyncStatus.d.ts +13 -0
  46. package/dist/types/src/components/SyncStatus/SyncStatus.d.ts.map +1 -0
  47. package/dist/types/src/components/SyncStatus/SyncStatus.stories.d.ts +24 -0
  48. package/dist/types/src/components/SyncStatus/SyncStatus.stories.d.ts.map +1 -0
  49. package/dist/types/src/components/SyncStatus/index.d.ts +2 -0
  50. package/dist/types/src/components/SyncStatus/index.d.ts.map +1 -0
  51. package/dist/types/src/components/SyncStatus/types.d.ts +14 -0
  52. package/dist/types/src/components/SyncStatus/types.d.ts.map +1 -0
  53. package/dist/types/src/components/index.d.ts +3 -2
  54. package/dist/types/src/components/index.d.ts.map +1 -1
  55. package/dist/types/src/meta.d.ts.map +1 -1
  56. package/dist/types/src/translations.d.ts +6 -0
  57. package/dist/types/src/translations.d.ts.map +1 -1
  58. package/dist/types/src/types/thread.d.ts +15 -1
  59. package/dist/types/src/types/thread.d.ts.map +1 -1
  60. package/dist/types/src/types/types.d.ts +18 -1
  61. package/dist/types/src/types/types.d.ts.map +1 -1
  62. package/dist/types/src/util.d.ts +3 -6
  63. package/dist/types/src/util.d.ts.map +1 -1
  64. package/package.json +45 -36
  65. package/src/SpacePlugin.tsx +173 -90
  66. package/src/components/DefaultObjectSettings.tsx +32 -0
  67. package/src/components/MenuFooter.tsx +2 -2
  68. package/src/components/SaveStatus.tsx +95 -0
  69. package/src/components/ShareSpaceButton.stories.tsx +11 -7
  70. package/src/components/SpaceMain/SpaceMain.tsx +1 -22
  71. package/src/components/SpacePresence.stories.tsx +11 -9
  72. package/src/components/SpacePresence.tsx +1 -1
  73. package/src/components/SpaceSettings.tsx +32 -3
  74. package/src/components/SyncStatus/SyncStatus.stories.tsx +65 -0
  75. package/src/components/SyncStatus/SyncStatus.tsx +188 -0
  76. package/src/components/SyncStatus/index.ts +5 -0
  77. package/src/components/SyncStatus/types.ts +77 -0
  78. package/src/components/index.ts +3 -2
  79. package/src/meta.ts +3 -1
  80. package/src/translations.ts +6 -0
  81. package/src/types/collection.ts +1 -1
  82. package/src/types/thread.ts +12 -2
  83. package/src/types/types.ts +25 -1
  84. package/src/util.tsx +20 -55
  85. package/dist/lib/browser/chunk-DTVUOG2C.mjs.map +0 -7
  86. package/dist/lib/browser/chunk-LZEGRS7H.mjs.map +0 -7
  87. package/dist/lib/node/chunk-6CNYF6YU.cjs.map +0 -7
  88. package/dist/lib/node/chunk-CVZPI2P3.cjs.map +0 -7
  89. package/dist/types/src/components/EmptySpace.d.ts +0 -3
  90. package/dist/types/src/components/EmptySpace.d.ts.map +0 -1
  91. package/dist/types/src/components/EmptyTree.d.ts +0 -3
  92. package/dist/types/src/components/EmptyTree.d.ts.map +0 -1
  93. package/src/components/EmptySpace.tsx +0 -25
  94. package/src/components/EmptyTree.tsx +0 -25
@@ -2,10 +2,11 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import React from 'react';
6
-
7
5
  import '@dxos-theme';
8
6
 
7
+ import { type Meta } from '@storybook/react';
8
+ import React from 'react';
9
+
9
10
  import { PublicKey } from '@dxos/keys';
10
11
  import { HaloSpaceMember, SpaceMember } from '@dxos/react-client/echo';
11
12
  import { withLayout, withTheme } from '@dxos/storybook-utils';
@@ -13,13 +14,6 @@ import { withLayout, withTheme } from '@dxos/storybook-utils';
13
14
  import { FullPresence, type MemberPresenceProps, SmallPresence, type Member } from './SpacePresence';
14
15
  import translations from '../translations';
15
16
 
16
- export default {
17
- title: 'plugin-space/SpacePresence',
18
- decorators: [withTheme, withLayout({ tooltips: true })],
19
- parameters: { translations },
20
- actions: { argTypesRegex: '^on.*' },
21
- };
22
-
23
17
  const nViewers = (n: number, currentlyAttended = true): Member[] =>
24
18
  Array.from({ length: n }, () => ({
25
19
  role: HaloSpaceMember.Role.ADMIN,
@@ -95,3 +89,11 @@ export const Small = (props: MemberPresenceProps) => {
95
89
  </div>
96
90
  );
97
91
  };
92
+
93
+ const meta: Meta = {
94
+ title: 'plugins/plugin-space/SpacePresence',
95
+ decorators: [withTheme, withLayout({ tooltips: true })],
96
+ parameters: { translations },
97
+ };
98
+
99
+ export default meta;
@@ -110,7 +110,7 @@ export type MemberPresenceProps = ThemedClassName<{
110
110
 
111
111
  export const FullPresence = (props: MemberPresenceProps) => {
112
112
  const { size = 9, onMemberClick } = props;
113
- const members = useDefaultValue(props.members, []);
113
+ const members = useDefaultValue(props.members, () => []);
114
114
 
115
115
  if (members.length === 0) {
116
116
  return null;
@@ -4,16 +4,17 @@
4
4
 
5
5
  import React from 'react';
6
6
 
7
- import { useIntentDispatcher } from '@dxos/app-framework';
7
+ import { useIntentDispatcher, useResolvePlugins } from '@dxos/app-framework';
8
8
  import { SettingsValue } from '@dxos/plugin-settings';
9
- import { Input, useTranslation } from '@dxos/react-ui';
9
+ import { Input, Select, toLocalizedString, useTranslation } from '@dxos/react-ui';
10
10
 
11
11
  import { SpaceAction, SPACE_PLUGIN } from '../meta';
12
- import { type SpaceSettingsProps } from '../types';
12
+ import { parseSpaceInitPlugin, type SpaceSettingsProps } from '../types';
13
13
 
14
14
  export const SpaceSettings = ({ settings }: { settings: SpaceSettingsProps }) => {
15
15
  const { t } = useTranslation(SPACE_PLUGIN);
16
16
  const dispatch = useIntentDispatcher();
17
+ const plugins = useResolvePlugins(parseSpaceInitPlugin);
17
18
 
18
19
  return (
19
20
  <>
@@ -29,6 +30,34 @@ export const SpaceSettings = ({ settings }: { settings: SpaceSettingsProps }) =>
29
30
  }
30
31
  />
31
32
  </SettingsValue>
33
+
34
+ <SettingsValue label={t('default on space create label')}>
35
+ <Select.Root
36
+ value={settings.onSpaceCreate}
37
+ onValueChange={(value) => {
38
+ settings.onSpaceCreate = value;
39
+ }}
40
+ >
41
+ <Select.TriggerButton />
42
+ <Select.Portal>
43
+ <Select.Content>
44
+ <Select.Viewport>
45
+ {plugins.map(
46
+ ({
47
+ provides: {
48
+ space: { onSpaceCreate },
49
+ },
50
+ }) => (
51
+ <Select.Option key={onSpaceCreate.action} value={onSpaceCreate.action}>
52
+ {toLocalizedString(onSpaceCreate.label, t)}
53
+ </Select.Option>
54
+ ),
55
+ )}
56
+ </Select.Viewport>
57
+ </Select.Content>
58
+ </Select.Portal>
59
+ </Select.Root>
60
+ </SettingsValue>
32
61
  </>
33
62
  );
34
63
  };
@@ -0,0 +1,65 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import { type Meta } from '@storybook/react';
8
+ import React from 'react';
9
+
10
+ import { SpaceId } from '@dxos/keys';
11
+ import { withTheme, withLayout } from '@dxos/storybook-utils';
12
+
13
+ import { SyncStatusDetail, SyncStatusIndicator } from './SyncStatus';
14
+ import { getSyncSummary, type SpaceSyncStateMap } from './types';
15
+ import translations from '../../translations';
16
+
17
+ const DefaultStory = (props: any) => {
18
+ return (
19
+ <div className='flex flex-col-reverse p-4 '>
20
+ <SyncStatusIndicator {...props} />
21
+ </div>
22
+ );
23
+ };
24
+
25
+ const random = ({ min, max }: { min: number; max: number }) => min + Math.floor(Math.random() * (max - min));
26
+
27
+ const state: SpaceSyncStateMap = Array.from({ length: 5 }).reduce<SpaceSyncStateMap>((map) => {
28
+ const total = random({ min: 10, max: 500 });
29
+ const haveLocal = random({ min: 0, max: total });
30
+ const haveRemote = random({ min: 0, max: total });
31
+ map[SpaceId.random()] = {
32
+ localDocumentCount: haveLocal,
33
+ remoteDocumentCount: haveRemote,
34
+ missingOnLocal: total - haveLocal,
35
+ missingOnRemote: total - haveRemote,
36
+ differentDocuments: 0,
37
+ };
38
+
39
+ return map;
40
+ }, {});
41
+
42
+ export const Default = {
43
+ args: {
44
+ state,
45
+ },
46
+ };
47
+
48
+ export const Detail = {
49
+ render: SyncStatusDetail,
50
+ args: {
51
+ state,
52
+ summary: getSyncSummary(state),
53
+ classNames: 'm-2 w-[200px] border border-separator rounded-md',
54
+ },
55
+ };
56
+
57
+ const meta: Meta = {
58
+ title: 'plugins/plugin-space/SyncStatusIndicator',
59
+ component: SyncStatusIndicator,
60
+ render: DefaultStory,
61
+ decorators: [withTheme, withLayout({ fullscreen: true })],
62
+ parameters: { translations },
63
+ };
64
+
65
+ export default meta;
@@ -0,0 +1,188 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React, { type HTMLAttributes, useEffect, useState } from 'react';
6
+
7
+ import { StatusBar } from '@dxos/plugin-status-bar';
8
+ import { Icon, Popover, useTranslation } from '@dxos/react-ui';
9
+ import { type ThemedClassName } from '@dxos/react-ui';
10
+ import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
11
+ import { mx } from '@dxos/react-ui-theme';
12
+
13
+ import { type Progress, type PeerSyncState, type SpaceSyncStateMap, getSyncSummary, useSyncState } from './types';
14
+ import { SPACE_PLUGIN } from '../../meta';
15
+
16
+ const SYNC_STALLED_TIMEOUT = 5_000;
17
+
18
+ const styles = {
19
+ barBg: 'bg-neutral-50 dark:bg-green-900 text-black',
20
+ barFg: 'bg-neutral-100 bg-green-500',
21
+ barHover: 'dark:hover:bg-green-500',
22
+ };
23
+
24
+ export const SyncStatus = () => {
25
+ const state = useSyncState();
26
+ return <SyncStatusIndicator state={state} />;
27
+ };
28
+
29
+ export const SyncStatusIndicator = ({ state }: { state: SpaceSyncStateMap }) => {
30
+ const summary = getSyncSummary(state);
31
+ const offline = false;
32
+
33
+ const needsToUpload = summary.differentDocuments > 0 || summary.missingOnRemote > 0;
34
+ const needsToDownload = summary.differentDocuments > 0 || summary.missingOnLocal > 0;
35
+ const [classNames, setClassNames] = useState<string>();
36
+ useEffect(() => {
37
+ setClassNames(undefined);
38
+ if (!needsToUpload && !needsToDownload) {
39
+ return;
40
+ }
41
+
42
+ const t = setTimeout(() => {
43
+ setClassNames('text-orange-500');
44
+ }, SYNC_STALLED_TIMEOUT);
45
+ return () => clearTimeout(t);
46
+ }, [needsToUpload, needsToDownload]);
47
+
48
+ return (
49
+ <StatusBar.Item>
50
+ <Popover.Root>
51
+ <Popover.Trigger>
52
+ <Icon
53
+ icon={
54
+ offline
55
+ ? 'ph--cloud-x--regular'
56
+ : needsToUpload
57
+ ? 'ph--cloud-arrow-up--regular'
58
+ : needsToDownload
59
+ ? 'ph--cloud-arrow-down--regular'
60
+ : 'ph--cloud-check--regular'
61
+ }
62
+ size={4}
63
+ classNames={classNames}
64
+ />
65
+ </Popover.Trigger>
66
+ <Popover.Content>
67
+ <SyncStatusDetail state={state} summary={summary} debug={false} />
68
+ </Popover.Content>
69
+ </Popover.Root>
70
+ </StatusBar.Item>
71
+ );
72
+ };
73
+
74
+ export const SyncStatusDetail = ({
75
+ classNames,
76
+ state,
77
+ summary,
78
+ debug,
79
+ }: ThemedClassName<{
80
+ state: SpaceSyncStateMap;
81
+ summary: PeerSyncState;
82
+ debug?: boolean;
83
+ }>) => {
84
+ const { t } = useTranslation(SPACE_PLUGIN);
85
+ const entries = Object.entries(state).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
86
+
87
+ // TODO(burdon): Normalize to max document count?
88
+ return (
89
+ <div className={mx('flex flex-col text-xs min-w-[16rem]', classNames)}>
90
+ <h1 className='p-2'>{t('sync status title')}</h1>
91
+ <div className='flex flex-col gap-[2px] my-[2px]'>
92
+ {entries.map(([spaceId, state]) => (
93
+ <SpaceRow key={spaceId} spaceId={spaceId} state={state} />
94
+ ))}
95
+ </div>
96
+ {debug && <SyntaxHighlighter language='json'>{JSON.stringify(summary, null, 2)}</SyntaxHighlighter>}
97
+ </div>
98
+ );
99
+ };
100
+
101
+ const useActive = (count: number) => {
102
+ const [current, setCurrent] = useState(count);
103
+ const [active, setActive] = useState(false);
104
+ useEffect(() => {
105
+ let t: NodeJS.Timeout | undefined;
106
+ if (count !== current) {
107
+ setActive(true);
108
+ setCurrent(count);
109
+ t && clearTimeout(t);
110
+ t = setTimeout(() => {
111
+ setActive(false);
112
+ }, SYNC_STALLED_TIMEOUT);
113
+ }
114
+
115
+ return () => {
116
+ setActive(false);
117
+ clearTimeout(t);
118
+ };
119
+ }, [count, current]);
120
+ return active;
121
+ };
122
+
123
+ const SpaceRow = ({
124
+ spaceId,
125
+ state: { localDocumentCount, remoteDocumentCount, missingOnLocal, missingOnRemote },
126
+ }: {
127
+ spaceId: string;
128
+ state: PeerSyncState;
129
+ }) => {
130
+ const downActive = useActive(localDocumentCount);
131
+ const upActive = useActive(remoteDocumentCount);
132
+
133
+ return (
134
+ <div
135
+ className={mx('flex items-center mx-[2px] gap-[2px] cursor-pointer', styles.barHover)}
136
+ title={spaceId}
137
+ onClick={() => {
138
+ void navigator.clipboard.writeText(spaceId);
139
+ }}
140
+ >
141
+ <Icon
142
+ icon='ph--arrow-fat-line-left--regular'
143
+ size={3}
144
+ classNames={mx(downActive && 'animate-[pulse_1s_infinite]')}
145
+ />
146
+ <Candle
147
+ up={{ count: remoteDocumentCount, total: remoteDocumentCount + missingOnRemote }}
148
+ down={{ count: localDocumentCount, total: localDocumentCount + missingOnLocal }}
149
+ title={spaceId}
150
+ />
151
+ <Icon
152
+ icon='ph--arrow-fat-line-right--regular'
153
+ size={3}
154
+ classNames={mx(upActive && 'animate-[pulse_1s_step-start_infinite]')}
155
+ />
156
+ </div>
157
+ );
158
+ };
159
+
160
+ type CandleProps = ThemedClassName<Pick<HTMLAttributes<HTMLDivElement>, 'title'>> & { up: Progress; down: Progress };
161
+
162
+ const Candle = ({ classNames, up, down }: CandleProps) => {
163
+ return (
164
+ <div className={mx('grid grid-cols-[1fr_2rem_1fr] w-full h-3', classNames)}>
165
+ <Bar classNames='justify-end' {...up} />
166
+ <div className='relative'>
167
+ <div className={mx('absolute inset-0 flex items-center justify-center text-xs', styles.barBg)}>{up.total}</div>
168
+ </div>
169
+ <Bar {...down} />
170
+ </div>
171
+ );
172
+ };
173
+
174
+ const Bar = ({ classNames, count, total }: ThemedClassName<Progress>) => {
175
+ let p = (count / total) * 100;
176
+ if (count < total) {
177
+ p = Math.min(p, 95);
178
+ }
179
+
180
+ return (
181
+ <div className={mx('relative flex w-full', styles.barBg, classNames)}>
182
+ <div className={mx('shrink-0', styles.barFg)} style={{ width: `${p}%` }}></div>
183
+ {count !== total && (
184
+ <div className='absolute top-0 bottom-0 flex items-center mx-0.5 text-black text-xs'>{count}</div>
185
+ )}
186
+ </div>
187
+ );
188
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './SyncStatus';
@@ -0,0 +1,77 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { useEffect, useState } from 'react';
6
+
7
+ import { type Space, type SpaceId, type SpaceSyncState } from '@dxos/client/echo';
8
+ import { Context } from '@dxos/context';
9
+ import { EdgeService } from '@dxos/protocols';
10
+ import { useClient } from '@dxos/react-client';
11
+
12
+ export type Progress = { count: number; total: number };
13
+
14
+ export type PeerSyncState = Omit<SpaceSyncState.PeerState, 'peerId'>;
15
+
16
+ export type SpaceSyncStateMap = Record<SpaceId, PeerSyncState>;
17
+
18
+ export const createEmptyEdgeSyncState = (): PeerSyncState => ({
19
+ missingOnLocal: 0,
20
+ missingOnRemote: 0,
21
+ localDocumentCount: 0,
22
+ remoteDocumentCount: 0,
23
+ differentDocuments: 0,
24
+ });
25
+
26
+ export const getSyncSummary = (syncMap: SpaceSyncStateMap): PeerSyncState => {
27
+ return Object.entries(syncMap).reduce<PeerSyncState>((summary, [_spaceId, peerState]) => {
28
+ summary.missingOnLocal += peerState.missingOnLocal;
29
+ summary.missingOnRemote += peerState.missingOnRemote;
30
+ summary.localDocumentCount += peerState.localDocumentCount;
31
+ summary.remoteDocumentCount += peerState.remoteDocumentCount;
32
+ summary.differentDocuments += peerState.differentDocuments;
33
+ return summary;
34
+ }, createEmptyEdgeSyncState());
35
+ };
36
+
37
+ const isEdgePeerId = (peerId: string, spaceId: SpaceId) =>
38
+ peerId.startsWith(`${EdgeService.AUTOMERGE_REPLICATOR}:${spaceId}`);
39
+
40
+ /**
41
+ * Hook Subscribes to sync state for each space.
42
+ */
43
+ export const useSyncState = (): SpaceSyncStateMap => {
44
+ const client = useClient();
45
+ const [spaceState, setSpaceState] = useState<SpaceSyncStateMap>({});
46
+
47
+ useEffect(() => {
48
+ const ctx = new Context();
49
+ const createSubscriptions = (spaces: Space[]) => {
50
+ for (const space of spaces) {
51
+ if (spaceState[space.id]) {
52
+ continue;
53
+ }
54
+
55
+ ctx.onDispose(
56
+ space.crud.subscribeToSyncState(ctx, ({ peers = [] }) => {
57
+ const syncState = peers.find((state) => isEdgePeerId(state.peerId, space.id));
58
+ if (syncState) {
59
+ setSpaceState((spaceState) => ({ ...spaceState, [space.id]: syncState }));
60
+ }
61
+ }),
62
+ );
63
+ }
64
+ };
65
+
66
+ createSubscriptions(client.spaces.get());
67
+ client.spaces.subscribe((spaces) => {
68
+ createSubscriptions(spaces);
69
+ });
70
+
71
+ return () => {
72
+ void ctx.dispose();
73
+ };
74
+ }, [client]);
75
+
76
+ return spaceState;
77
+ };
@@ -5,8 +5,7 @@
5
5
  export * from './AwaitingObject';
6
6
  export * from './CollectionMain';
7
7
  export * from './CollectionSection';
8
- export * from './EmptySpace';
9
- export * from './EmptyTree';
8
+ export * from './DefaultObjectSettings';
10
9
  export * from './MenuFooter';
11
10
  export * from './MissingObject';
12
11
  export * from './PersistenceStatus';
@@ -16,3 +15,5 @@ export * from './ShareSpaceButton';
16
15
  export * from './SpaceMain';
17
16
  export * from './SpacePresence';
18
17
  export * from './SpaceSettings';
18
+ export * from './SaveStatus';
19
+ export * from './SyncStatus';
package/src/meta.ts CHANGED
@@ -2,6 +2,8 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ import { type PluginMeta } from '@dxos/app-framework';
6
+
5
7
  export const SPACE_PLUGIN = 'dxos.org/plugin/space';
6
8
  export const SPACE_PLUGIN_SHORT_ID = 'space';
7
9
 
@@ -9,7 +11,7 @@ export default {
9
11
  id: SPACE_PLUGIN,
10
12
  shortId: SPACE_PLUGIN_SHORT_ID,
11
13
  name: 'Spaces',
12
- };
14
+ } satisfies PluginMeta;
13
15
 
14
16
  const SPACE_ACTION = `${SPACE_PLUGIN}/action`;
15
17
  export enum SpaceAction {
@@ -86,6 +86,12 @@ export default [
86
86
  'remove deleted objects label': 'Cleanup',
87
87
  'remove deleted objects alt': 'Permanently remove deleted objects to free up space.',
88
88
  'copy link label': 'Copy link',
89
+ 'default on space create label': 'On space create',
90
+ 'sync status title': 'Sync status',
91
+ 'dismiss label': 'Dismiss',
92
+ 'join success label': 'Successfully joined space',
93
+ 'name label': 'Name',
94
+ 'name placeholder': 'Name',
89
95
  },
90
96
  },
91
97
  },
@@ -12,5 +12,5 @@ export class CollectionType extends TypedObject({ typename: 'dxos.org/type/Colle
12
12
  // This also leaves open a future where this key could be changed to allow for multiple stack views per section.
13
13
  // TODO(wittjosiah): Any way to make this more type safe?
14
14
  // TODO(wittjosiah): Should the views be separate objects or just be schemas for view data in this record?
15
- views: S.mutable(S.Record(S.String, ref(Expando))),
15
+ views: S.mutable(S.Record({ key: S.String, value: ref(Expando) })),
16
16
  }) {}
@@ -34,17 +34,26 @@ export const ActorSchema = S.mutable(
34
34
 
35
35
  export type ActorType = S.Schema.Type<typeof ActorSchema>;
36
36
 
37
+ export enum MessageState {
38
+ NONE = 0,
39
+ ARCHIVED = 1,
40
+ DELETED = 2,
41
+ SPAM = 3,
42
+ }
43
+
37
44
  export class MessageType extends TypedObject({ typename: 'dxos.org/type/Message', version: '0.1.0' })({
38
45
  /** ISO date string when the message was sent. */
39
46
  timestamp: S.String,
47
+ /** Message state. */
48
+ state: S.optional(S.Enums(MessageState)),
40
49
  /** Identity of the message sender. */
41
50
  sender: ActorSchema,
42
51
  /** Text content of the message. */
43
52
  text: S.String,
44
- /** Non-text content sent with a message (e.g. files, polls, etc.) */
53
+ /** Non-text content sent with a message (e.g., files, polls, etc.) */
45
54
  parts: S.optional(S.mutable(S.Array(ref(Expando)))),
46
55
  /** Custom properties for specific message types (e.g. email subject or cc fields). */
47
- properties: S.optional(S.mutable(S.Record(S.String, S.Any))),
56
+ properties: S.optional(S.mutable(S.Record({ key: S.String, value: S.Any }))),
48
57
  // TODO(wittjosiah): Add read status:
49
58
  // - Read receipts need to be per space member.
50
59
  // - Read receipts don't need to be added to schema until they being implemented.
@@ -57,6 +66,7 @@ export const ThreadStatus = S.Union(S.Literal('staged'), S.Literal('active'), S.
57
66
 
58
67
  export class ThreadType extends TypedObject({ typename: 'dxos.org/type/Thread', version: '0.1.0' })({
59
68
  name: S.optional(S.String),
69
+ /** AM cursor-range: 'from:to'. */
60
70
  anchor: S.optional(S.String),
61
71
  status: S.optional(ThreadStatus),
62
72
  messages: S.mutable(S.Array(ref(MessageType))),
@@ -10,10 +10,12 @@ import type {
10
10
  SettingsProvides,
11
11
  SurfaceProvides,
12
12
  TranslationsProvides,
13
+ Plugin,
13
14
  } from '@dxos/app-framework';
14
15
  import { type Expando } from '@dxos/echo-schema';
15
16
  import { type SchemaProvides } from '@dxos/plugin-client';
16
17
  import { type PublicKey } from '@dxos/react-client';
18
+ import { type Label } from '@dxos/react-ui';
17
19
  import { type ComplexMap } from '@dxos/util';
18
20
 
19
21
  export const SPACE_DIRECTORY_HANDLE = 'dxos.org/plugin/space/directory';
@@ -53,7 +55,29 @@ export type PluginState = {
53
55
  sdkMigrationRunning: Record<string, boolean>;
54
56
  };
55
57
 
56
- export type SpaceSettingsProps = { showHidden?: boolean };
58
+ export type SpaceSettingsProps = {
59
+ /**
60
+ * Show closed spaces.
61
+ */
62
+ showHidden?: boolean;
63
+
64
+ /**
65
+ * Action to perform when a space is created.
66
+ */
67
+ onSpaceCreate?: string;
68
+ };
69
+
70
+ export type SpaceInitProvides = {
71
+ space: {
72
+ onSpaceCreate: {
73
+ label: Label;
74
+ action: string;
75
+ };
76
+ };
77
+ };
78
+
79
+ export const parseSpaceInitPlugin = (plugin: Plugin) =>
80
+ typeof (plugin.provides as any).space?.onSpaceCreate === 'object' ? (plugin as Plugin<SpaceInitProvides>) : undefined;
57
81
 
58
82
  export type SpacePluginProvides = SurfaceProvides &
59
83
  IntentResolverProvides &