@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.
- package/dist/lib/browser/{chunk-DTVUOG2C.mjs → chunk-47SVNCZM.mjs} +24 -5
- package/dist/lib/browser/chunk-47SVNCZM.mjs.map +7 -0
- package/dist/lib/browser/{chunk-LZEGRS7H.mjs → chunk-AVLRQF6L.mjs} +1 -1
- package/dist/lib/browser/chunk-AVLRQF6L.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +609 -250
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/meta.mjs +1 -1
- package/dist/lib/browser/types/index.mjs +7 -3
- package/dist/lib/node/{chunk-CVZPI2P3.cjs → chunk-CTYDNFGG.cjs} +30 -9
- package/dist/lib/node/chunk-CTYDNFGG.cjs.map +7 -0
- package/dist/lib/node/{chunk-6CNYF6YU.cjs → chunk-P4XUXM7Y.cjs} +4 -4
- package/dist/lib/node/chunk-P4XUXM7Y.cjs.map +7 -0
- package/dist/lib/node/index.cjs +827 -471
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.cjs +5 -5
- package/dist/lib/node/meta.cjs.map +1 -1
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/types/index.cjs +14 -10
- package/dist/lib/node/types/index.cjs.map +2 -2
- package/dist/lib/node-esm/chunk-PLPMYTLC.mjs +116 -0
- package/dist/lib/node-esm/chunk-PLPMYTLC.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-YPQGKWHJ.mjs +37 -0
- package/dist/lib/node-esm/chunk-YPQGKWHJ.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +3000 -0
- package/dist/lib/node-esm/index.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -0
- package/dist/lib/node-esm/meta.mjs +14 -0
- package/dist/lib/node-esm/meta.mjs.map +7 -0
- package/dist/lib/node-esm/types/index.mjs +26 -0
- package/dist/lib/node-esm/types/index.mjs.map +7 -0
- package/dist/types/src/SpacePlugin.d.ts.map +1 -1
- package/dist/types/src/components/DefaultObjectSettings.d.ts +7 -0
- package/dist/types/src/components/DefaultObjectSettings.d.ts.map +1 -0
- package/dist/types/src/components/MenuFooter.d.ts +1 -1
- package/dist/types/src/components/MenuFooter.d.ts.map +1 -1
- package/dist/types/src/components/SaveStatus.d.ts +3 -0
- package/dist/types/src/components/SaveStatus.d.ts.map +1 -0
- package/dist/types/src/components/ShareSpaceButton.stories.d.ts +3 -91
- package/dist/types/src/components/ShareSpaceButton.stories.d.ts.map +1 -1
- package/dist/types/src/components/SpaceMain/SpaceMain.d.ts.map +1 -1
- package/dist/types/src/components/SpacePresence.stories.d.ts +4 -92
- package/dist/types/src/components/SpacePresence.stories.d.ts.map +1 -1
- package/dist/types/src/components/SpaceSettings.d.ts.map +1 -1
- package/dist/types/src/components/SyncStatus/SyncStatus.d.ts +13 -0
- package/dist/types/src/components/SyncStatus/SyncStatus.d.ts.map +1 -0
- package/dist/types/src/components/SyncStatus/SyncStatus.stories.d.ts +24 -0
- package/dist/types/src/components/SyncStatus/SyncStatus.stories.d.ts.map +1 -0
- package/dist/types/src/components/SyncStatus/index.d.ts +2 -0
- package/dist/types/src/components/SyncStatus/index.d.ts.map +1 -0
- package/dist/types/src/components/SyncStatus/types.d.ts +14 -0
- package/dist/types/src/components/SyncStatus/types.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +3 -2
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +6 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/thread.d.ts +15 -1
- package/dist/types/src/types/thread.d.ts.map +1 -1
- package/dist/types/src/types/types.d.ts +18 -1
- package/dist/types/src/types/types.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +3 -6
- package/dist/types/src/util.d.ts.map +1 -1
- package/package.json +45 -36
- package/src/SpacePlugin.tsx +173 -90
- package/src/components/DefaultObjectSettings.tsx +32 -0
- package/src/components/MenuFooter.tsx +2 -2
- package/src/components/SaveStatus.tsx +95 -0
- package/src/components/ShareSpaceButton.stories.tsx +11 -7
- package/src/components/SpaceMain/SpaceMain.tsx +1 -22
- package/src/components/SpacePresence.stories.tsx +11 -9
- package/src/components/SpacePresence.tsx +1 -1
- package/src/components/SpaceSettings.tsx +32 -3
- package/src/components/SyncStatus/SyncStatus.stories.tsx +65 -0
- package/src/components/SyncStatus/SyncStatus.tsx +188 -0
- package/src/components/SyncStatus/index.ts +5 -0
- package/src/components/SyncStatus/types.ts +77 -0
- package/src/components/index.ts +3 -2
- package/src/meta.ts +3 -1
- package/src/translations.ts +6 -0
- package/src/types/collection.ts +1 -1
- package/src/types/thread.ts +12 -2
- package/src/types/types.ts +25 -1
- package/src/util.tsx +20 -55
- package/dist/lib/browser/chunk-DTVUOG2C.mjs.map +0 -7
- package/dist/lib/browser/chunk-LZEGRS7H.mjs.map +0 -7
- package/dist/lib/node/chunk-6CNYF6YU.cjs.map +0 -7
- package/dist/lib/node/chunk-CVZPI2P3.cjs.map +0 -7
- package/dist/types/src/components/EmptySpace.d.ts +0 -3
- package/dist/types/src/components/EmptySpace.d.ts.map +0 -1
- package/dist/types/src/components/EmptyTree.d.ts +0 -3
- package/dist/types/src/components/EmptyTree.d.ts.map +0 -1
- package/src/components/EmptySpace.tsx +0 -25
- package/src/components/EmptyTree.tsx +0 -25
package/src/SpacePlugin.tsx
CHANGED
|
@@ -2,74 +2,77 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import { effect, signal } from '@preact/signals-core';
|
|
5
|
+
import { signal } from '@preact/signals-core';
|
|
7
6
|
import React from 'react';
|
|
8
7
|
|
|
9
8
|
import {
|
|
10
9
|
type IntentDispatcher,
|
|
11
10
|
type IntentPluginProvides,
|
|
12
11
|
LayoutAction,
|
|
13
|
-
Surface,
|
|
14
12
|
type LocationProvides,
|
|
15
13
|
NavigationAction,
|
|
16
14
|
type Plugin,
|
|
17
15
|
type PluginDefinition,
|
|
18
|
-
|
|
16
|
+
Surface,
|
|
19
17
|
firstIdInPart,
|
|
18
|
+
openIds,
|
|
19
|
+
parseGraphPlugin,
|
|
20
20
|
parseIntentPlugin,
|
|
21
|
-
parseNavigationPlugin,
|
|
22
21
|
parseMetadataResolverPlugin,
|
|
22
|
+
parseNavigationPlugin,
|
|
23
23
|
resolvePlugin,
|
|
24
|
-
parseGraphPlugin,
|
|
25
24
|
} from '@dxos/app-framework';
|
|
26
25
|
import { EventSubscriptions, type Trigger, type UnsubscribeCallback } from '@dxos/async';
|
|
27
|
-
import { type
|
|
26
|
+
import { type HasId, isReactiveObject } from '@dxos/echo-schema';
|
|
27
|
+
import { scheduledEffect } from '@dxos/echo-signals/core';
|
|
28
28
|
import { LocalStorageStore } from '@dxos/local-storage';
|
|
29
29
|
import { log } from '@dxos/log';
|
|
30
30
|
import { Migrations } from '@dxos/migrations';
|
|
31
31
|
import { type AttentionPluginProvides, parseAttentionPlugin } from '@dxos/plugin-attention';
|
|
32
32
|
import { type ClientPluginProvides, parseClientPlugin } from '@dxos/plugin-client';
|
|
33
|
-
import {
|
|
33
|
+
import { type Node, createExtension, memoize, toSignal } from '@dxos/plugin-graph';
|
|
34
34
|
import { ObservabilityAction } from '@dxos/plugin-observability/meta';
|
|
35
35
|
import { type Client, PublicKey } from '@dxos/react-client';
|
|
36
36
|
import {
|
|
37
|
+
Expando,
|
|
38
|
+
type EchoReactiveObject,
|
|
39
|
+
Filter,
|
|
37
40
|
type PropertiesTypeProps,
|
|
38
41
|
type Space,
|
|
42
|
+
SpaceState,
|
|
39
43
|
create,
|
|
40
|
-
Expando,
|
|
41
|
-
Filter,
|
|
42
44
|
fullyQualifiedId,
|
|
43
45
|
getSpace,
|
|
44
46
|
getTypename,
|
|
45
47
|
isEchoObject,
|
|
46
48
|
isSpace,
|
|
47
49
|
loadObjectReferences,
|
|
48
|
-
|
|
50
|
+
parseFullyQualifiedId,
|
|
49
51
|
} from '@dxos/react-client/echo';
|
|
50
52
|
import { Dialog } from '@dxos/react-ui';
|
|
51
|
-
import { InvitationManager, type InvitationManagerProps, osTranslations
|
|
53
|
+
import { ClipboardProvider, InvitationManager, type InvitationManagerProps, osTranslations } from '@dxos/shell/react';
|
|
52
54
|
import { ComplexMap, nonNullable, reduceGroupBy } from '@dxos/util';
|
|
53
55
|
|
|
54
56
|
import {
|
|
55
57
|
AwaitingObject,
|
|
56
58
|
CollectionMain,
|
|
57
59
|
CollectionSection,
|
|
58
|
-
|
|
59
|
-
EmptyTree,
|
|
60
|
+
DefaultObjectSettings,
|
|
60
61
|
MenuFooter,
|
|
61
62
|
MissingObject,
|
|
62
63
|
PopoverRenameObject,
|
|
63
64
|
PopoverRenameSpace,
|
|
65
|
+
SaveStatus,
|
|
64
66
|
ShareSpaceButton,
|
|
65
67
|
SmallPresence,
|
|
66
68
|
SmallPresenceLive,
|
|
67
69
|
SpacePresence,
|
|
68
70
|
SpaceSettings,
|
|
71
|
+
SyncStatus,
|
|
69
72
|
} from './components';
|
|
70
73
|
import meta, { SPACE_PLUGIN, SpaceAction } from './meta';
|
|
71
74
|
import translations from './translations';
|
|
72
|
-
import { CollectionType, type
|
|
75
|
+
import { CollectionType, type PluginState, type SpacePluginProvides, type SpaceSettingsProps } from './types';
|
|
73
76
|
import {
|
|
74
77
|
COMPOSER_SPACE_LOCK,
|
|
75
78
|
SHARED,
|
|
@@ -118,7 +121,9 @@ export const SpacePlugin = ({
|
|
|
118
121
|
firstRun,
|
|
119
122
|
onFirstRun,
|
|
120
123
|
}: SpacePluginOptions = {}): PluginDefinition<SpacePluginProvides> => {
|
|
121
|
-
const settings = new LocalStorageStore<SpaceSettingsProps>(SPACE_PLUGIN
|
|
124
|
+
const settings = new LocalStorageStore<SpaceSettingsProps>(SPACE_PLUGIN, {
|
|
125
|
+
onSpaceCreate: 'dxos.org/plugin/markdown/action/create',
|
|
126
|
+
});
|
|
122
127
|
const state = new LocalStorageStore<PluginState>(SPACE_PLUGIN, {
|
|
123
128
|
awaiting: undefined,
|
|
124
129
|
spaceNames: {},
|
|
@@ -171,9 +176,10 @@ export const SpacePlugin = ({
|
|
|
171
176
|
.filter((space) => space.state.get() === SpaceState.SPACE_READY)
|
|
172
177
|
.forEach((space) => {
|
|
173
178
|
subscriptions.add(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
179
|
+
scheduledEffect(
|
|
180
|
+
() => ({ name: space.properties.name }),
|
|
181
|
+
({ name }) => (state.values.spaceNames[space.id] = name),
|
|
182
|
+
),
|
|
177
183
|
);
|
|
178
184
|
});
|
|
179
185
|
}).unsubscribe,
|
|
@@ -181,54 +187,56 @@ export const SpacePlugin = ({
|
|
|
181
187
|
|
|
182
188
|
// Broadcast active node to other peers in the space.
|
|
183
189
|
subscriptions.add(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
190
|
+
scheduledEffect(
|
|
191
|
+
() => ({
|
|
192
|
+
ids: openIds(location.active),
|
|
193
|
+
removed: location.closed ? [location.closed].flat() : [],
|
|
194
|
+
}),
|
|
195
|
+
({ ids, removed }) => {
|
|
196
|
+
const send = () => {
|
|
197
|
+
const spaces = client.spaces.get();
|
|
198
|
+
const identity = client.halo.identity.get();
|
|
199
|
+
if (identity && location.active) {
|
|
200
|
+
// Group parts by space for efficient messaging.
|
|
201
|
+
const idsBySpace = reduceGroupBy(ids, (id) => {
|
|
202
|
+
const [spaceId] = id.split(':'); // TODO(burdon): Factor out.
|
|
203
|
+
return spaceId;
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// NOTE: Ensure all spaces are included so that we send the correct `removed` object arrays.
|
|
207
|
+
for (const space of spaces) {
|
|
208
|
+
if (!idsBySpace.has(space.id)) {
|
|
209
|
+
idsBySpace.set(space.id, []);
|
|
210
|
+
}
|
|
201
211
|
}
|
|
202
|
-
}
|
|
203
212
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
213
|
+
for (const [spaceId, ids] of idsBySpace) {
|
|
214
|
+
const space = spaces.find((space) => space.id === spaceId);
|
|
215
|
+
if (!space) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
209
218
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
});
|
|
219
|
+
void space
|
|
220
|
+
.postMessage('viewing', {
|
|
221
|
+
identityKey: identity.identityKey.toHex(),
|
|
222
|
+
attended: attention.attended ? [...attention.attended] : [],
|
|
223
|
+
added: ids,
|
|
224
|
+
// TODO(Zan): When we re-open a part, we should remove it from the removed list in the navigation plugin.
|
|
225
|
+
removed: removed.filter((id) => !ids.includes(id)),
|
|
226
|
+
})
|
|
227
|
+
// TODO(burdon): This seems defensive; why would this fail? Backoff interval.
|
|
228
|
+
.catch((err) => {
|
|
229
|
+
log.warn('Failed to broadcast active node for presence.', { err: err.message });
|
|
230
|
+
});
|
|
231
|
+
}
|
|
224
232
|
}
|
|
225
|
-
}
|
|
226
|
-
};
|
|
233
|
+
};
|
|
227
234
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
235
|
+
send();
|
|
236
|
+
const interval = setInterval(() => send(), ACTIVE_NODE_BROADCAST_INTERVAL);
|
|
237
|
+
return () => clearInterval(interval);
|
|
238
|
+
},
|
|
239
|
+
),
|
|
232
240
|
);
|
|
233
241
|
|
|
234
242
|
// Listen for active nodes from other peers in the space.
|
|
@@ -340,8 +348,7 @@ export const SpacePlugin = ({
|
|
|
340
348
|
records: {
|
|
341
349
|
[CollectionType.typename]: {
|
|
342
350
|
placeholder: ['unnamed collection label', { ns: SPACE_PLUGIN }],
|
|
343
|
-
icon:
|
|
344
|
-
iconSymbol: 'ph--cards-three--regular',
|
|
351
|
+
icon: 'ph--cards-three--regular',
|
|
345
352
|
// TODO(wittjosiah): Move out of metadata.
|
|
346
353
|
loadReferences: (collection: CollectionType) =>
|
|
347
354
|
loadObjectReferences(collection, (collection) => [
|
|
@@ -362,22 +369,18 @@ export const SpacePlugin = ({
|
|
|
362
369
|
case 'main':
|
|
363
370
|
// TODO(wittjosiah): Need to avoid shotgun parsing space state everywhere.
|
|
364
371
|
return isSpace(primary) && primary.state.get() === SpaceState.SPACE_READY ? (
|
|
365
|
-
<Surface
|
|
372
|
+
<Surface
|
|
373
|
+
data={{ active: primary.properties[CollectionType.typename], id: primary.id }}
|
|
374
|
+
role={role}
|
|
375
|
+
{...rest}
|
|
376
|
+
/>
|
|
366
377
|
) : primary instanceof CollectionType ? (
|
|
367
378
|
{ node: <CollectionMain collection={primary} />, disposition: 'fallback' }
|
|
368
379
|
) : typeof primary === 'string' && primary.length === OBJECT_ID_LENGTH ? (
|
|
369
380
|
<MissingObject id={primary} />
|
|
370
381
|
) : null;
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
switch (true) {
|
|
374
|
-
case data.plugin === SPACE_PLUGIN:
|
|
375
|
-
return <EmptyTree />;
|
|
376
|
-
case isGraphNode(data.activeNode) && isSpace(data.activeNode.data):
|
|
377
|
-
return <EmptySpace />;
|
|
378
|
-
default:
|
|
379
|
-
return null;
|
|
380
|
-
}
|
|
382
|
+
case 'complementary--settings':
|
|
383
|
+
return isEchoObject(data.subject) ? <DefaultObjectSettings object={data.subject} /> : null;
|
|
381
384
|
case 'dialog':
|
|
382
385
|
if (data.component === 'dxos.org/plugin/space/InvitationManagerDialog') {
|
|
383
386
|
return (
|
|
@@ -398,6 +401,7 @@ export const SpacePlugin = ({
|
|
|
398
401
|
return <PopoverRenameObject object={data.subject} />;
|
|
399
402
|
}
|
|
400
403
|
return null;
|
|
404
|
+
// TODO(burdon): Add role name syntax to minimal plugin docs.
|
|
401
405
|
case 'presence--glyph': {
|
|
402
406
|
return isReactiveObject(data.object) ? (
|
|
403
407
|
<SmallPresenceLive viewers={state.values.viewersByObject[fullyQualifiedId(data.object)]} />
|
|
@@ -441,6 +445,14 @@ export const SpacePlugin = ({
|
|
|
441
445
|
} else {
|
|
442
446
|
return <MenuFooter object={data.object} />;
|
|
443
447
|
}
|
|
448
|
+
case 'status': {
|
|
449
|
+
return (
|
|
450
|
+
<>
|
|
451
|
+
<SyncStatus />
|
|
452
|
+
<SaveStatus />
|
|
453
|
+
</>
|
|
454
|
+
);
|
|
455
|
+
}
|
|
444
456
|
default:
|
|
445
457
|
return null;
|
|
446
458
|
}
|
|
@@ -540,8 +552,7 @@ export const SpacePlugin = ({
|
|
|
540
552
|
},
|
|
541
553
|
properties: {
|
|
542
554
|
label: ['create space label', { ns: SPACE_PLUGIN }],
|
|
543
|
-
icon:
|
|
544
|
-
iconSymbol: 'ph--plus--regular',
|
|
555
|
+
icon: 'ph--plus--regular',
|
|
545
556
|
disposition: 'toolbar',
|
|
546
557
|
testId: 'spacePlugin.createSpace',
|
|
547
558
|
},
|
|
@@ -561,8 +572,7 @@ export const SpacePlugin = ({
|
|
|
561
572
|
},
|
|
562
573
|
properties: {
|
|
563
574
|
label: ['join space label', { ns: SPACE_PLUGIN }],
|
|
564
|
-
icon:
|
|
565
|
-
iconSymbol: 'ph--sign-in--regular',
|
|
575
|
+
icon: 'ph--sign-in--regular',
|
|
566
576
|
testId: 'spacePlugin.joinSpace',
|
|
567
577
|
},
|
|
568
578
|
},
|
|
@@ -707,6 +717,52 @@ export const SpacePlugin = ({
|
|
|
707
717
|
.filter(nonNullable);
|
|
708
718
|
},
|
|
709
719
|
}),
|
|
720
|
+
|
|
721
|
+
// Create nodes for object settings.
|
|
722
|
+
createExtension({
|
|
723
|
+
id: `${SPACE_PLUGIN}/settings-for-subject`,
|
|
724
|
+
resolver: ({ id }) => {
|
|
725
|
+
// TODO(Zan): Find util (or make one).
|
|
726
|
+
if (!id.endsWith('~settings')) {
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const [subjectId] = id.split('~');
|
|
731
|
+
const [spaceId, objectId] = parseFullyQualifiedId(subjectId);
|
|
732
|
+
const space = client.spaces.get().find((space) => space.id === spaceId);
|
|
733
|
+
const object = toSignal(
|
|
734
|
+
(onChange) => {
|
|
735
|
+
const timeout = setTimeout(async () => {
|
|
736
|
+
await space?.db.loadObjectById(objectId);
|
|
737
|
+
onChange();
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
return () => clearTimeout(timeout);
|
|
741
|
+
},
|
|
742
|
+
() => space?.db.getObjectById(objectId),
|
|
743
|
+
subjectId,
|
|
744
|
+
);
|
|
745
|
+
if (!object || !subjectId) {
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
const meta = resolve(getTypename(object) ?? '');
|
|
750
|
+
const label = meta.label?.(object) ||
|
|
751
|
+
object.name ||
|
|
752
|
+
meta.placeholder || ['unnamed object settings label', { ns: SPACE_PLUGIN }];
|
|
753
|
+
|
|
754
|
+
return {
|
|
755
|
+
id,
|
|
756
|
+
type: 'orphan-settings-for-subject',
|
|
757
|
+
data: null,
|
|
758
|
+
properties: {
|
|
759
|
+
icon: 'ph--gear--regular',
|
|
760
|
+
label,
|
|
761
|
+
object,
|
|
762
|
+
},
|
|
763
|
+
};
|
|
764
|
+
},
|
|
765
|
+
}),
|
|
710
766
|
];
|
|
711
767
|
},
|
|
712
768
|
serializer: (plugins) => {
|
|
@@ -802,9 +858,21 @@ export const SpacePlugin = ({
|
|
|
802
858
|
}
|
|
803
859
|
|
|
804
860
|
return {
|
|
805
|
-
data: {
|
|
806
|
-
|
|
861
|
+
data: {
|
|
862
|
+
space,
|
|
863
|
+
id: space.id,
|
|
864
|
+
activeParts: { main: [space.id] },
|
|
865
|
+
},
|
|
807
866
|
intents: [
|
|
867
|
+
...(settings.values.onSpaceCreate
|
|
868
|
+
? [
|
|
869
|
+
[
|
|
870
|
+
{ action: settings.values.onSpaceCreate, data: { space } },
|
|
871
|
+
{ action: SpaceAction.ADD_OBJECT, data: { target: space } },
|
|
872
|
+
{ action: NavigationAction.EXPOSE },
|
|
873
|
+
],
|
|
874
|
+
]
|
|
875
|
+
: []),
|
|
808
876
|
[
|
|
809
877
|
{
|
|
810
878
|
action: ObservabilityAction.SEND_EVENT,
|
|
@@ -825,9 +893,26 @@ export const SpacePlugin = ({
|
|
|
825
893
|
const { space } = await client.shell.joinSpace({ invitationCode: intent.data?.invitationCode });
|
|
826
894
|
if (space) {
|
|
827
895
|
return {
|
|
828
|
-
data: {
|
|
829
|
-
|
|
896
|
+
data: {
|
|
897
|
+
space,
|
|
898
|
+
id: space.id,
|
|
899
|
+
activeParts: { main: [space.id] },
|
|
900
|
+
},
|
|
830
901
|
intents: [
|
|
902
|
+
[
|
|
903
|
+
{
|
|
904
|
+
action: LayoutAction.SET_LAYOUT,
|
|
905
|
+
data: {
|
|
906
|
+
element: 'toast',
|
|
907
|
+
subject: {
|
|
908
|
+
id: `${SPACE_PLUGIN}/join-success`,
|
|
909
|
+
duration: 10_000,
|
|
910
|
+
title: translations[0]['en-US'][SPACE_PLUGIN]['join success label'],
|
|
911
|
+
closeLabel: translations[0]['en-US'][SPACE_PLUGIN]['dismiss label'],
|
|
912
|
+
},
|
|
913
|
+
},
|
|
914
|
+
},
|
|
915
|
+
],
|
|
831
916
|
[
|
|
832
917
|
{
|
|
833
918
|
action: ObservabilityAction.SEND_EVENT,
|
|
@@ -1020,8 +1105,7 @@ export const SpacePlugin = ({
|
|
|
1020
1105
|
title: translations[0]['en-US'][SPACE_PLUGIN]['space limit label'],
|
|
1021
1106
|
description: translations[0]['en-US'][SPACE_PLUGIN]['space limit description'],
|
|
1022
1107
|
duration: 5_000,
|
|
1023
|
-
icon:
|
|
1024
|
-
iconSymbol: 'ph--warning--regular',
|
|
1108
|
+
icon: 'ph--warning--regular',
|
|
1025
1109
|
actionLabel: translations[0]['en-US'][SPACE_PLUGIN]['remove deleted objects label'],
|
|
1026
1110
|
actionAlt: translations[0]['en-US'][SPACE_PLUGIN]['remove deleted objects alt'],
|
|
1027
1111
|
// TODO(wittjosiah): Use OS namespace.
|
|
@@ -1047,20 +1131,20 @@ export const SpacePlugin = ({
|
|
|
1047
1131
|
}
|
|
1048
1132
|
|
|
1049
1133
|
if (intent.data?.target instanceof CollectionType) {
|
|
1050
|
-
intent.data?.target.objects.push(object as
|
|
1134
|
+
intent.data?.target.objects.push(object as HasId);
|
|
1051
1135
|
} else if (isSpace(intent.data?.target)) {
|
|
1052
1136
|
const collection = space.properties[CollectionType.typename];
|
|
1053
1137
|
if (collection instanceof CollectionType) {
|
|
1054
|
-
collection.objects.push(object as
|
|
1138
|
+
collection.objects.push(object as HasId);
|
|
1055
1139
|
} else {
|
|
1056
1140
|
// TODO(wittjosiah): Can't add non-echo objects by including in a collection because of types.
|
|
1057
|
-
const collection = create(CollectionType, { objects: [object as
|
|
1141
|
+
const collection = create(CollectionType, { objects: [object as HasId], views: {} });
|
|
1058
1142
|
space.properties[CollectionType.typename] = collection;
|
|
1059
1143
|
}
|
|
1060
1144
|
}
|
|
1061
1145
|
|
|
1062
1146
|
return {
|
|
1063
|
-
data: { id: object
|
|
1147
|
+
data: { id: fullyQualifiedId(object), object, activeParts: { main: [fullyQualifiedId(object)] } },
|
|
1064
1148
|
intents: [
|
|
1065
1149
|
[
|
|
1066
1150
|
{
|
|
@@ -1115,7 +1199,6 @@ export const SpacePlugin = ({
|
|
|
1115
1199
|
activeParts: {
|
|
1116
1200
|
main: deletionData.wasActive,
|
|
1117
1201
|
sidebar: deletionData.wasActive,
|
|
1118
|
-
complementary: deletionData.wasActive,
|
|
1119
1202
|
},
|
|
1120
1203
|
},
|
|
1121
1204
|
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { type EchoReactiveObject } from '@dxos/client/echo';
|
|
8
|
+
import { Input, useTranslation } from '@dxos/react-ui';
|
|
9
|
+
|
|
10
|
+
import { SPACE_PLUGIN } from '../meta';
|
|
11
|
+
|
|
12
|
+
export type DefaultObjectSettingsProps = {
|
|
13
|
+
object: EchoReactiveObject<any>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const DefaultObjectSettings = ({ object }: DefaultObjectSettingsProps) => {
|
|
17
|
+
const { t } = useTranslation(SPACE_PLUGIN);
|
|
18
|
+
return (
|
|
19
|
+
<div role='form' className='flex flex-col w-full p-2 gap-1'>
|
|
20
|
+
<Input.Root>
|
|
21
|
+
<Input.Label>{t('name label')}</Input.Label>
|
|
22
|
+
<Input.TextInput
|
|
23
|
+
placeholder={t('name placeholder')}
|
|
24
|
+
value={object.name}
|
|
25
|
+
onChange={(event) => {
|
|
26
|
+
object.name = event.target.value;
|
|
27
|
+
}}
|
|
28
|
+
/>
|
|
29
|
+
</Input.Root>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
//
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
|
+
|
|
4
5
|
import { Planet } from '@phosphor-icons/react';
|
|
5
6
|
import React from 'react';
|
|
6
7
|
|
|
7
|
-
import { getSpace } from '@dxos/client/echo';
|
|
8
|
-
import type { EchoReactiveObject } from '@dxos/echo-schema';
|
|
8
|
+
import { type EchoReactiveObject, getSpace } from '@dxos/client/echo';
|
|
9
9
|
import { useClient } from '@dxos/react-client';
|
|
10
10
|
import { DropdownMenu, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
11
11
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useEffect, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type UnsubscribeCallback } from '@dxos/async';
|
|
8
|
+
import { type Client } from '@dxos/client';
|
|
9
|
+
import { type Space, type SpaceId } from '@dxos/client/echo';
|
|
10
|
+
import { Context } from '@dxos/context';
|
|
11
|
+
import { StatusBar } from '@dxos/plugin-status-bar';
|
|
12
|
+
import { useClient } from '@dxos/react-client';
|
|
13
|
+
import { Icon, useTranslation } from '@dxos/react-ui';
|
|
14
|
+
|
|
15
|
+
import { SPACE_PLUGIN } from '../meta';
|
|
16
|
+
|
|
17
|
+
export const SaveStatus = () => {
|
|
18
|
+
const { t } = useTranslation(SPACE_PLUGIN);
|
|
19
|
+
const client = useClient();
|
|
20
|
+
const [state, setState] = useState<'saved' | 'saving'>('saved');
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
return createClientSaveTracker(client, (state) => {
|
|
23
|
+
setState(state);
|
|
24
|
+
});
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<StatusBar.Item title={state === 'saving' ? t('saving label') : t('saved label')}>
|
|
29
|
+
<Icon icon={state === 'saving' ? 'ph--arrows-clockwise--regular' : 'ph--check-circle--regular'} size={4} />
|
|
30
|
+
</StatusBar.Item>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const createClientSaveTracker = (client: Client, cb: (state: 'saved' | 'saving') => void) => {
|
|
35
|
+
const unsubscribeCallbacks: Record<SpaceId, UnsubscribeCallback> = {};
|
|
36
|
+
const state: Record<SpaceId, 'saved' | 'saving'> = {};
|
|
37
|
+
|
|
38
|
+
const install = (spaces: Space[]) => {
|
|
39
|
+
for (const space of spaces) {
|
|
40
|
+
if (state[space.id]) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
state[space.id] = 'saved';
|
|
45
|
+
unsubscribeCallbacks[space.id] = createSpaceSaveTracker(space, (s) => {
|
|
46
|
+
state[space.id] = s;
|
|
47
|
+
cb(Object.values(state).some((s) => s === 'saving') ? 'saving' : 'saved');
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
client.spaces.subscribe((spaces) => {
|
|
52
|
+
install(spaces);
|
|
53
|
+
});
|
|
54
|
+
install(client.spaces.get());
|
|
55
|
+
|
|
56
|
+
return () => {
|
|
57
|
+
for (const unsubscribe of Object.values(unsubscribeCallbacks)) {
|
|
58
|
+
unsubscribe();
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const createSpaceSaveTracker = (space: Space, cb: (state: 'saved' | 'saving') => void): UnsubscribeCallback => {
|
|
64
|
+
const ctx = new Context();
|
|
65
|
+
|
|
66
|
+
void space.waitUntilReady().then(() => {
|
|
67
|
+
if (ctx.disposed) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let hasUnsavedChanges = false;
|
|
72
|
+
let lastFlushPromise: Promise<void> | undefined;
|
|
73
|
+
space.crud.saveStateChanged.on(ctx, ({ unsavedDocuments }) => {
|
|
74
|
+
hasUnsavedChanges = unsavedDocuments.length > 0;
|
|
75
|
+
});
|
|
76
|
+
space.crud.saveStateChanged.debounce(500).on(ctx, () => {
|
|
77
|
+
if (hasUnsavedChanges) {
|
|
78
|
+
lastFlushPromise = undefined;
|
|
79
|
+
cb('saving');
|
|
80
|
+
} else {
|
|
81
|
+
const flushPromise = space.crud.flush();
|
|
82
|
+
lastFlushPromise = flushPromise;
|
|
83
|
+
void flushPromise.then(() => {
|
|
84
|
+
if (lastFlushPromise === flushPromise) {
|
|
85
|
+
cb('saved');
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return () => {
|
|
93
|
+
void ctx.dispose();
|
|
94
|
+
};
|
|
95
|
+
};
|
|
@@ -4,20 +4,24 @@
|
|
|
4
4
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
|
+
import { type Meta } from '@storybook/react';
|
|
8
|
+
|
|
7
9
|
import { withTheme } from '@dxos/storybook-utils';
|
|
8
10
|
|
|
9
11
|
import { ShareSpaceButtonImpl } from './ShareSpaceButton';
|
|
10
12
|
import translations from '../translations';
|
|
11
13
|
|
|
12
|
-
export default {
|
|
13
|
-
title: 'plugin-space/ShareSpaceButton',
|
|
14
|
-
component: ShareSpaceButtonImpl,
|
|
15
|
-
decorators: [withTheme],
|
|
16
|
-
parameters: { translations },
|
|
17
|
-
};
|
|
18
|
-
|
|
19
14
|
export const Default = {
|
|
20
15
|
args: {
|
|
21
16
|
onClick: () => console.log('clicked'),
|
|
22
17
|
},
|
|
23
18
|
};
|
|
19
|
+
|
|
20
|
+
const meta: Meta = {
|
|
21
|
+
title: 'plugins/plugin-space/ShareSpaceButton',
|
|
22
|
+
component: ShareSpaceButtonImpl,
|
|
23
|
+
decorators: [withTheme],
|
|
24
|
+
parameters: { translations },
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default meta;
|
|
@@ -6,35 +6,14 @@ import { Command } from '@phosphor-icons/react';
|
|
|
6
6
|
import React from 'react';
|
|
7
7
|
|
|
8
8
|
import { Surface } from '@dxos/app-framework';
|
|
9
|
-
import { type Action } from '@dxos/plugin-graph';
|
|
10
9
|
import { SpaceState, type Space } from '@dxos/react-client/echo';
|
|
11
|
-
import {
|
|
10
|
+
import { Main, useTranslation } from '@dxos/react-ui';
|
|
12
11
|
import { getSize, mx, topbarBlockPaddingStart } from '@dxos/react-ui-theme';
|
|
13
12
|
import { ClipboardProvider } from '@dxos/shell/react';
|
|
14
13
|
|
|
15
14
|
import { SpaceMembersSection } from './SpaceMembersSection';
|
|
16
15
|
import { SPACE_PLUGIN } from '../../meta';
|
|
17
16
|
|
|
18
|
-
const _InFlowSpaceActions = ({ actionsMap }: { actionsMap: Record<string, Action> }) => {
|
|
19
|
-
const { t } = useTranslation(SPACE_PLUGIN);
|
|
20
|
-
return (
|
|
21
|
-
<section className='mbe-4 col-start-2 col-end-4 md:col-end-7 grid gap-2 auto-rows-min grid-cols-[repeat(auto-fill,minmax(8rem,1fr))]'>
|
|
22
|
-
{Object.entries(actionsMap)
|
|
23
|
-
.filter(([_, { properties }]) => properties?.mainAreaDisposition === 'in-flow')
|
|
24
|
-
.map(([actionId, { data: invoke, properties }]) => {
|
|
25
|
-
const Icon = properties?.icon;
|
|
26
|
-
const label = properties?.label;
|
|
27
|
-
return (
|
|
28
|
-
<Button key={actionId} classNames='block text-center plb-2 font-normal'>
|
|
29
|
-
{Icon && <Icon size={5} className='mli-auto' />}
|
|
30
|
-
<p>{toLocalizedString(label, t)}</p>
|
|
31
|
-
</Button>
|
|
32
|
-
);
|
|
33
|
-
})}
|
|
34
|
-
</section>
|
|
35
|
-
);
|
|
36
|
-
};
|
|
37
|
-
|
|
38
17
|
const KeyShortcuts = () => {
|
|
39
18
|
const { t } = useTranslation(SPACE_PLUGIN);
|
|
40
19
|
return (
|