@dxos/plugin-space 0.6.14-staging.e15392e → 0.7.0
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-AVLRQF6L.mjs → chunk-DJE2HYFV.mjs} +3 -2
- package/dist/lib/browser/{chunk-AVLRQF6L.mjs.map → chunk-DJE2HYFV.mjs.map} +2 -2
- package/dist/lib/browser/{chunk-FOI7DAUV.mjs → chunk-OWZKSWMX.mjs} +1 -1
- package/dist/lib/browser/{chunk-FOI7DAUV.mjs.map → chunk-OWZKSWMX.mjs.map} +2 -2
- package/dist/lib/browser/index.mjs +817 -762
- 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 +1 -1
- package/dist/lib/node/{chunk-OTDRTHT4.cjs → chunk-FYWGZYJB.cjs} +4 -4
- package/dist/lib/node/{chunk-OTDRTHT4.cjs.map → chunk-FYWGZYJB.cjs.map} +2 -2
- package/dist/lib/node/{chunk-P4XUXM7Y.cjs → chunk-JFDDZI4Y.cjs} +6 -5
- package/dist/lib/node/{chunk-P4XUXM7Y.cjs.map → chunk-JFDDZI4Y.cjs.map} +2 -2
- package/dist/lib/node/index.cjs +1009 -957
- 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 +11 -11
- package/dist/lib/node/types/index.cjs.map +1 -1
- package/dist/lib/node-esm/{chunk-YPQGKWHJ.mjs → chunk-DVUZ7A7G.mjs} +3 -2
- package/dist/lib/node-esm/{chunk-YPQGKWHJ.mjs.map → chunk-DVUZ7A7G.mjs.map} +2 -2
- package/dist/lib/node-esm/{chunk-FYDGMPSC.mjs → chunk-MCEAI4CV.mjs} +1 -1
- package/dist/lib/node-esm/{chunk-FYDGMPSC.mjs.map → chunk-MCEAI4CV.mjs.map} +2 -2
- package/dist/lib/node-esm/index.mjs +817 -762
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/meta.mjs +1 -1
- package/dist/lib/node-esm/types/index.mjs +1 -1
- package/dist/types/src/SpacePlugin.d.ts +9 -1
- package/dist/types/src/SpacePlugin.d.ts.map +1 -1
- package/dist/types/src/components/JoinDialog.d.ts +7 -0
- package/dist/types/src/components/JoinDialog.d.ts.map +1 -0
- package/dist/types/src/components/ShareSpaceButton.d.ts +3 -2
- package/dist/types/src/components/ShareSpaceButton.d.ts.map +1 -1
- package/dist/types/src/components/{SpaceSettings.d.ts → SpacePluginSettings.d.ts} +2 -2
- package/dist/types/src/components/SpacePluginSettings.d.ts.map +1 -0
- package/dist/types/src/components/SpaceSettings/SpaceSettingsDialog.d.ts +10 -0
- package/dist/types/src/components/SpaceSettings/SpaceSettingsDialog.d.ts.map +1 -0
- package/dist/types/src/components/SpaceSettings/SpaceSettingsDialog.stories.d.ts +7 -0
- package/dist/types/src/components/SpaceSettings/SpaceSettingsDialog.stories.d.ts.map +1 -0
- package/dist/types/src/components/SpaceSettings/SpaceSettingsPanel.d.ts.map +1 -0
- package/dist/types/src/components/SpaceSettings/SpaceSettingsPanel.stories.d.ts +7 -0
- package/dist/types/src/components/SpaceSettings/SpaceSettingsPanel.stories.d.ts.map +1 -0
- package/dist/types/src/components/SpaceSettings/index.d.ts +3 -0
- package/dist/types/src/components/SpaceSettings/index.d.ts.map +1 -0
- package/dist/types/src/components/SyncStatus/Space.d.ts +8 -0
- package/dist/types/src/components/SyncStatus/Space.d.ts.map +1 -0
- package/dist/types/src/components/SyncStatus/SyncStatus.d.ts +3 -2
- package/dist/types/src/components/SyncStatus/SyncStatus.d.ts.map +1 -1
- package/dist/types/src/components/SyncStatus/SyncStatus.stories.d.ts +5 -20
- package/dist/types/src/components/SyncStatus/SyncStatus.stories.d.ts.map +1 -1
- package/dist/types/src/components/SyncStatus/save-tracker.d.ts +3 -0
- package/dist/types/src/components/SyncStatus/save-tracker.d.ts.map +1 -0
- package/dist/types/src/components/SyncStatus/status.d.ts +9 -0
- package/dist/types/src/components/SyncStatus/status.d.ts.map +1 -0
- package/dist/types/src/components/SyncStatus/{types.d.ts → sync-state.d.ts} +1 -1
- package/dist/types/src/components/SyncStatus/sync-state.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +2 -4
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +3 -2
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +10 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/types.d.ts +7 -1
- package/dist/types/src/types/types.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +8 -4
- package/dist/types/src/util.d.ts.map +1 -1
- package/package.json +35 -33
- package/src/SpacePlugin.tsx +279 -175
- package/src/components/AwaitingObject.tsx +1 -1
- package/src/components/JoinDialog.tsx +100 -0
- package/src/components/ShareSpaceButton.tsx +10 -6
- package/src/components/{SpaceSettings.tsx → SpacePluginSettings.tsx} +6 -6
- package/src/components/SpaceSettings/SpaceSettingsDialog.stories.tsx +44 -0
- package/src/components/SpaceSettings/SpaceSettingsDialog.tsx +103 -0
- package/src/components/SpaceSettings/SpaceSettingsPanel.stories.tsx +32 -0
- package/src/components/{SpaceSettingsPanel.tsx → SpaceSettings/SpaceSettingsPanel.tsx} +25 -20
- package/src/components/SpaceSettings/index.ts +6 -0
- package/src/components/SyncStatus/Space.tsx +109 -0
- package/src/components/SyncStatus/SyncStatus.stories.tsx +13 -4
- package/src/components/SyncStatus/SyncStatus.tsx +43 -129
- package/src/components/{SaveStatus.tsx → SyncStatus/save-tracker.ts} +1 -25
- package/src/components/SyncStatus/status.ts +44 -0
- package/src/components/index.ts +2 -4
- package/src/meta.ts +2 -1
- package/src/translations.ts +10 -0
- package/src/types/types.ts +9 -1
- package/src/util.tsx +51 -23
- package/dist/types/src/components/MissingObject.d.ts +0 -5
- package/dist/types/src/components/MissingObject.d.ts.map +0 -1
- package/dist/types/src/components/SaveStatus.d.ts +0 -3
- package/dist/types/src/components/SaveStatus.d.ts.map +0 -1
- package/dist/types/src/components/SpaceMain/SpaceMain.d.ts +0 -10
- package/dist/types/src/components/SpaceMain/SpaceMain.d.ts.map +0 -1
- package/dist/types/src/components/SpaceMain/SpaceMembersSection.d.ts +0 -6
- package/dist/types/src/components/SpaceMain/SpaceMembersSection.d.ts.map +0 -1
- package/dist/types/src/components/SpaceMain/index.d.ts +0 -2
- package/dist/types/src/components/SpaceMain/index.d.ts.map +0 -1
- package/dist/types/src/components/SpaceSettings.d.ts.map +0 -1
- package/dist/types/src/components/SpaceSettingsPanel.d.ts.map +0 -1
- package/dist/types/src/components/SyncStatus/types.d.ts.map +0 -1
- package/src/components/MissingObject.tsx +0 -54
- package/src/components/SpaceMain/SpaceMain.tsx +0 -60
- package/src/components/SpaceMain/SpaceMembersSection.tsx +0 -205
- package/src/components/SpaceMain/index.ts +0 -5
- /package/dist/types/src/components/{SpaceSettingsPanel.d.ts → SpaceSettings/SpaceSettingsPanel.d.ts} +0 -0
- /package/src/components/SyncStatus/{types.ts → sync-state.ts} +0 -0
|
@@ -43,7 +43,7 @@ export const AwaitingObject = ({ id }: { id: string }) => {
|
|
|
43
43
|
if (objects.findIndex((object) => fullyQualifiedId(object) === id) > -1) {
|
|
44
44
|
setFound(true);
|
|
45
45
|
|
|
46
|
-
if (navigationPlugin?.provides.location.active === id) {
|
|
46
|
+
if (navigationPlugin?.provides.location.active.solo?.[0].id === id) {
|
|
47
47
|
setOpen(false);
|
|
48
48
|
}
|
|
49
49
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback } from 'react';
|
|
6
|
+
|
|
7
|
+
import { LayoutAction, NavigationAction, useIntentDispatcher } from '@dxos/app-framework';
|
|
8
|
+
import { useGraph } from '@dxos/plugin-graph';
|
|
9
|
+
import { ObservabilityAction } from '@dxos/plugin-observability/meta';
|
|
10
|
+
import { useSpaces } from '@dxos/react-client/echo';
|
|
11
|
+
import { type InvitationResult } from '@dxos/react-client/invitations';
|
|
12
|
+
import { Dialog, useTranslation } from '@dxos/react-ui';
|
|
13
|
+
import { JoinPanel, type JoinPanelProps } from '@dxos/shell/react';
|
|
14
|
+
|
|
15
|
+
import { SPACE_PLUGIN } from '../meta';
|
|
16
|
+
|
|
17
|
+
export type JoinDialogProps = JoinPanelProps & {
|
|
18
|
+
navigableCollections?: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const JoinDialog = ({ navigableCollections, ...props }: JoinDialogProps) => {
|
|
22
|
+
const { t } = useTranslation(SPACE_PLUGIN);
|
|
23
|
+
const dispatch = useIntentDispatcher();
|
|
24
|
+
const spaces = useSpaces();
|
|
25
|
+
const { graph } = useGraph();
|
|
26
|
+
|
|
27
|
+
const handleDone = useCallback(
|
|
28
|
+
async (result: InvitationResult | null) => {
|
|
29
|
+
if (result?.spaceKey) {
|
|
30
|
+
await Promise.all([
|
|
31
|
+
dispatch({
|
|
32
|
+
action: LayoutAction.SET_LAYOUT,
|
|
33
|
+
data: {
|
|
34
|
+
element: 'toast',
|
|
35
|
+
subject: {
|
|
36
|
+
id: `${SPACE_PLUGIN}/join-success`,
|
|
37
|
+
duration: 5_000,
|
|
38
|
+
title: t('join success label'),
|
|
39
|
+
closeLabel: t('dismiss label'),
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
43
|
+
dispatch({
|
|
44
|
+
action: LayoutAction.SET_LAYOUT,
|
|
45
|
+
data: {
|
|
46
|
+
element: 'dialog',
|
|
47
|
+
state: false,
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
]);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const space = spaces.find(({ key }) => result?.spaceKey?.equals(key));
|
|
54
|
+
const target = result?.target || (navigableCollections ? space?.id : undefined);
|
|
55
|
+
if (target) {
|
|
56
|
+
// Wait for up to 1 second before navigating to the target node.
|
|
57
|
+
// If the target has not yet replicated, this will trigger a loading toast.
|
|
58
|
+
await graph.waitForPath({ target }).catch(() => {});
|
|
59
|
+
await Promise.all([
|
|
60
|
+
dispatch({
|
|
61
|
+
action: NavigationAction.OPEN,
|
|
62
|
+
data: {
|
|
63
|
+
activeParts: { main: [target] },
|
|
64
|
+
},
|
|
65
|
+
}),
|
|
66
|
+
dispatch({
|
|
67
|
+
action: NavigationAction.EXPOSE,
|
|
68
|
+
data: {
|
|
69
|
+
id: target,
|
|
70
|
+
},
|
|
71
|
+
}),
|
|
72
|
+
]);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (space) {
|
|
76
|
+
await dispatch({
|
|
77
|
+
action: ObservabilityAction.SEND_EVENT,
|
|
78
|
+
data: {
|
|
79
|
+
name: 'space.join',
|
|
80
|
+
properties: {
|
|
81
|
+
spaceId: space.id,
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
[dispatch, spaces],
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<Dialog.Content>
|
|
92
|
+
<JoinPanel
|
|
93
|
+
{...props}
|
|
94
|
+
exitActionParent={<Dialog.Close asChild />}
|
|
95
|
+
doneActionParent={<Dialog.Close asChild />}
|
|
96
|
+
onDone={handleDone}
|
|
97
|
+
/>
|
|
98
|
+
</Dialog.Content>
|
|
99
|
+
);
|
|
100
|
+
};
|
|
@@ -5,14 +5,15 @@
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
7
|
import { useIntentDispatcher } from '@dxos/app-framework';
|
|
8
|
-
import {
|
|
8
|
+
import { type Space } from '@dxos/react-client/echo';
|
|
9
|
+
import { IconButton, useTranslation } from '@dxos/react-ui';
|
|
9
10
|
|
|
10
11
|
import { SPACE_PLUGIN, SpaceAction } from '../meta';
|
|
11
12
|
|
|
12
|
-
export const ShareSpaceButton = ({
|
|
13
|
+
export const ShareSpaceButton = ({ space }: { space: Space }) => {
|
|
13
14
|
const dispatch = useIntentDispatcher();
|
|
14
15
|
|
|
15
|
-
return <ShareSpaceButtonImpl onClick={() => dispatch({ action: SpaceAction.SHARE, data: {
|
|
16
|
+
return <ShareSpaceButtonImpl onClick={() => dispatch({ action: SpaceAction.SHARE, data: { space } })} />;
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
// TODO(wittjosiah): Better way to name pure/impure components?
|
|
@@ -20,8 +21,11 @@ export const ShareSpaceButtonImpl = ({ onClick }: { onClick: () => void }) => {
|
|
|
20
21
|
const { t } = useTranslation(SPACE_PLUGIN);
|
|
21
22
|
|
|
22
23
|
return (
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
<IconButton
|
|
25
|
+
data-testid='spacePlugin.shareSpaceButton'
|
|
26
|
+
icon='ph--users--regular'
|
|
27
|
+
label={t('share space label')}
|
|
28
|
+
onClick={onClick}
|
|
29
|
+
/>
|
|
26
30
|
);
|
|
27
31
|
};
|
|
@@ -6,19 +6,19 @@ import React from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { useIntentDispatcher, useResolvePlugins } from '@dxos/app-framework';
|
|
8
8
|
import { Input, Select, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
9
|
-
import {
|
|
9
|
+
import { DeprecatedFormInput } from '@dxos/react-ui-data';
|
|
10
10
|
|
|
11
11
|
import { SpaceAction, SPACE_PLUGIN } from '../meta';
|
|
12
12
|
import { parseSpaceInitPlugin, type SpaceSettingsProps } from '../types';
|
|
13
13
|
|
|
14
|
-
export const
|
|
14
|
+
export const SpacePluginSettings = ({ settings }: { settings: SpaceSettingsProps }) => {
|
|
15
15
|
const { t } = useTranslation(SPACE_PLUGIN);
|
|
16
16
|
const dispatch = useIntentDispatcher();
|
|
17
17
|
const plugins = useResolvePlugins(parseSpaceInitPlugin);
|
|
18
18
|
|
|
19
19
|
return (
|
|
20
20
|
<>
|
|
21
|
-
<
|
|
21
|
+
<DeprecatedFormInput label={t('show hidden spaces label')}>
|
|
22
22
|
<Input.Switch
|
|
23
23
|
checked={settings.showHidden}
|
|
24
24
|
onCheckedChange={(checked) =>
|
|
@@ -29,9 +29,9 @@ export const SpaceSettings = ({ settings }: { settings: SpaceSettingsProps }) =>
|
|
|
29
29
|
})
|
|
30
30
|
}
|
|
31
31
|
/>
|
|
32
|
-
</
|
|
32
|
+
</DeprecatedFormInput>
|
|
33
33
|
|
|
34
|
-
<
|
|
34
|
+
<DeprecatedFormInput label={t('default on space create label')}>
|
|
35
35
|
<Select.Root
|
|
36
36
|
value={settings.onSpaceCreate}
|
|
37
37
|
onValueChange={(value) => {
|
|
@@ -57,7 +57,7 @@ export const SpaceSettings = ({ settings }: { settings: SpaceSettingsProps }) =>
|
|
|
57
57
|
</Select.Content>
|
|
58
58
|
</Select.Portal>
|
|
59
59
|
</Select.Root>
|
|
60
|
-
</
|
|
60
|
+
</DeprecatedFormInput>
|
|
61
61
|
</>
|
|
62
62
|
);
|
|
63
63
|
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import '@dxos-theme';
|
|
6
|
+
|
|
7
|
+
import { type Meta, type StoryObj } from '@storybook/react';
|
|
8
|
+
import React from 'react';
|
|
9
|
+
|
|
10
|
+
import { useStoryClientData, withClientProvider } from '@dxos/react-client/testing';
|
|
11
|
+
import { Dialog } from '@dxos/react-ui';
|
|
12
|
+
import { osTranslations } from '@dxos/shell/react';
|
|
13
|
+
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
14
|
+
|
|
15
|
+
import { SpaceSettingsDialog, type SpaceSettingsDialogProps } from './SpaceSettingsDialog';
|
|
16
|
+
import translations from '../../translations';
|
|
17
|
+
|
|
18
|
+
const Story = (args: Partial<SpaceSettingsDialogProps>) => {
|
|
19
|
+
const { space } = useStoryClientData();
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Dialog.Root open>
|
|
23
|
+
<Dialog.Overlay blockAlign='start'>
|
|
24
|
+
<SpaceSettingsDialog {...args} space={space!} />
|
|
25
|
+
</Dialog.Overlay>
|
|
26
|
+
</Dialog.Root>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const meta: Meta = {
|
|
31
|
+
title: 'plugins/plugin-space/SpaceSettingsDialog',
|
|
32
|
+
component: SpaceSettingsDialog,
|
|
33
|
+
render: Story,
|
|
34
|
+
decorators: [
|
|
35
|
+
withClientProvider({ createIdentity: true, createSpace: true }),
|
|
36
|
+
withTheme,
|
|
37
|
+
withLayout({ tooltips: true }),
|
|
38
|
+
],
|
|
39
|
+
parameters: { translations: [...translations, osTranslations] },
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default meta;
|
|
43
|
+
|
|
44
|
+
export const Default: StoryObj<typeof SpaceSettingsDialog> = {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { useClient } from '@dxos/react-client';
|
|
8
|
+
import { Button, Dialog, Icon, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
9
|
+
import { Tabs, type TabsRootProps, type TabsActivePart } from '@dxos/react-ui-tabs';
|
|
10
|
+
import { ClipboardProvider, SpacePanel, type SpacePanelProps } from '@dxos/shell/react';
|
|
11
|
+
|
|
12
|
+
import { SpaceSettingsPanel, type SpaceSettingsPanelProps } from './SpaceSettingsPanel';
|
|
13
|
+
import { SPACE_PLUGIN } from '../../meta';
|
|
14
|
+
import { COMPOSER_SPACE_LOCK, getSpaceDisplayName } from '../../util';
|
|
15
|
+
|
|
16
|
+
export type SpaceSettingsTab = 'members' | 'settings';
|
|
17
|
+
|
|
18
|
+
export type SpaceSettingsDialogProps = {
|
|
19
|
+
initialTab?: SpaceSettingsTab;
|
|
20
|
+
namesCache?: Record<string, string>;
|
|
21
|
+
} & SpaceSettingsPanelProps &
|
|
22
|
+
Pick<SpacePanelProps, 'createInvitationUrl' | 'target'>;
|
|
23
|
+
|
|
24
|
+
export const SpaceSettingsDialog = ({
|
|
25
|
+
space,
|
|
26
|
+
target,
|
|
27
|
+
createInvitationUrl,
|
|
28
|
+
initialTab = 'members',
|
|
29
|
+
namesCache,
|
|
30
|
+
}: SpaceSettingsDialogProps) => {
|
|
31
|
+
const { t } = useTranslation(SPACE_PLUGIN);
|
|
32
|
+
const client = useClient();
|
|
33
|
+
const [tabsActivePart, setTabsActivePart] = useState<TabsActivePart>('list');
|
|
34
|
+
const [selected, setSelected] = useState<SpaceSettingsTab>(initialTab);
|
|
35
|
+
const locked = space.properties[COMPOSER_SPACE_LOCK];
|
|
36
|
+
const name = getSpaceDisplayName(space, { personal: client.spaces.default === space, namesCache });
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
// TODO(wittjosiah): The tablist dialog pattern is copied from @dxos/plugin-manager.
|
|
40
|
+
// Consider factoring it out to the tabs package.
|
|
41
|
+
<Dialog.Content classNames='p-0 bs-content min-bs-[15rem] max-bs-full md:max-is-[40rem] overflow-hidden'>
|
|
42
|
+
<div role='none' className='flex justify-between pbs-3 pis-2 pie-3 @md:pbs-4 @md:pis-4 @md:pie-5'>
|
|
43
|
+
<Dialog.Title
|
|
44
|
+
onClick={() => setTabsActivePart('list')}
|
|
45
|
+
aria-description={t('click to return to tablist description')}
|
|
46
|
+
classNames='flex cursor-pointer items-center group/title'
|
|
47
|
+
>
|
|
48
|
+
<Icon
|
|
49
|
+
icon='ph--caret-left--regular'
|
|
50
|
+
size={4}
|
|
51
|
+
classNames={['@md:hidden', tabsActivePart === 'list' && 'invisible']}
|
|
52
|
+
/>
|
|
53
|
+
<span
|
|
54
|
+
className={
|
|
55
|
+
tabsActivePart !== 'list'
|
|
56
|
+
? 'group-hover/title:underline @md:group-hover/title:no-underline underline-offset-4 decoration-1'
|
|
57
|
+
: ''
|
|
58
|
+
}
|
|
59
|
+
>
|
|
60
|
+
{toLocalizedString(name, t)}
|
|
61
|
+
</span>
|
|
62
|
+
</Dialog.Title>
|
|
63
|
+
<Dialog.Close asChild>
|
|
64
|
+
<Button density='fine' variant='ghost' autoFocus>
|
|
65
|
+
<Icon icon='ph--x--regular' size={3} />
|
|
66
|
+
</Button>
|
|
67
|
+
</Dialog.Close>
|
|
68
|
+
</div>
|
|
69
|
+
<Tabs.Root
|
|
70
|
+
orientation='vertical'
|
|
71
|
+
value={selected}
|
|
72
|
+
onValueChange={setSelected as TabsRootProps['onValueChange']}
|
|
73
|
+
activePart={tabsActivePart}
|
|
74
|
+
onActivePartChange={setTabsActivePart}
|
|
75
|
+
classNames='flex flex-col flex-1 mbs-2'
|
|
76
|
+
>
|
|
77
|
+
<Tabs.Viewport classNames='flex-1 min-bs-0'>
|
|
78
|
+
<div role='none' className='overflow-y-auto pli-3 @md:pis-2 @md:pie-0 mbe-4 border-r border-separator'>
|
|
79
|
+
<Tabs.Tablist classNames='flex flex-col max-bs-none min-is-[200px] gap-4 overflow-y-auto'>
|
|
80
|
+
<div role='none' className='flex flex-col ml-1'>
|
|
81
|
+
<Tabs.Tab value='settings'>{t('settings tab label')}</Tabs.Tab>
|
|
82
|
+
<Tabs.Tab value='members' disabled={locked}>
|
|
83
|
+
{t('members tab label')}
|
|
84
|
+
</Tabs.Tab>
|
|
85
|
+
</div>
|
|
86
|
+
</Tabs.Tablist>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<Tabs.Tabpanel value='settings' classNames='pli-3 @md:pli-5 max-bs-dvh overflow-y-auto'>
|
|
90
|
+
<SpaceSettingsPanel space={space} />
|
|
91
|
+
</Tabs.Tabpanel>
|
|
92
|
+
|
|
93
|
+
{/* TODO(wittjosiah): Weird focus ring when tabpanel is focused. */}
|
|
94
|
+
<Tabs.Tabpanel value='members' classNames='pli-3 @md:pli-5 max-bs-dvh overflow-y-auto'>
|
|
95
|
+
<ClipboardProvider>
|
|
96
|
+
<SpacePanel space={space} hideHeading target={target} createInvitationUrl={createInvitationUrl} />
|
|
97
|
+
</ClipboardProvider>
|
|
98
|
+
</Tabs.Tabpanel>
|
|
99
|
+
</Tabs.Viewport>
|
|
100
|
+
</Tabs.Root>
|
|
101
|
+
</Dialog.Content>
|
|
102
|
+
);
|
|
103
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import '@dxos-theme';
|
|
6
|
+
|
|
7
|
+
import { type Meta, type StoryObj } from '@storybook/react';
|
|
8
|
+
import React from 'react';
|
|
9
|
+
|
|
10
|
+
import { useStoryClientData, withClientProvider } from '@dxos/react-client/testing';
|
|
11
|
+
import { withTheme } from '@dxos/storybook-utils';
|
|
12
|
+
|
|
13
|
+
import { SpaceSettingsPanel, type SpaceSettingsPanelProps } from './SpaceSettingsPanel';
|
|
14
|
+
import translations from '../../translations';
|
|
15
|
+
|
|
16
|
+
const Story = (args: Partial<SpaceSettingsPanelProps>) => {
|
|
17
|
+
const { space } = useStoryClientData();
|
|
18
|
+
|
|
19
|
+
return <SpaceSettingsPanel {...args} space={space!} />;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const meta: Meta = {
|
|
23
|
+
title: 'plugins/plugin-space/SpaceSettingsPanel',
|
|
24
|
+
component: SpaceSettingsPanel,
|
|
25
|
+
render: Story,
|
|
26
|
+
decorators: [withClientProvider({ createIdentity: true, createSpace: true }), withTheme],
|
|
27
|
+
parameters: { translations },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default meta;
|
|
31
|
+
|
|
32
|
+
export const Default: StoryObj<typeof SpaceSettingsPanel> = {};
|
|
@@ -6,10 +6,13 @@ import React, { useCallback, useState } from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { log } from '@dxos/log';
|
|
8
8
|
import { EdgeReplicationSetting } from '@dxos/protocols/proto/dxos/echo/metadata';
|
|
9
|
+
import { useClient } from '@dxos/react-client';
|
|
9
10
|
import { type Space } from '@dxos/react-client/echo';
|
|
11
|
+
import { DeviceType, useDevices } from '@dxos/react-client/halo';
|
|
10
12
|
import { Input, useTranslation } from '@dxos/react-ui';
|
|
13
|
+
import { DeprecatedFormInput } from '@dxos/react-ui-data';
|
|
11
14
|
|
|
12
|
-
import { SPACE_PLUGIN } from '
|
|
15
|
+
import { SPACE_PLUGIN } from '../../meta';
|
|
13
16
|
|
|
14
17
|
export type SpaceSettingsPanelProps = {
|
|
15
18
|
space: Space;
|
|
@@ -17,10 +20,16 @@ export type SpaceSettingsPanelProps = {
|
|
|
17
20
|
|
|
18
21
|
export const SpaceSettingsPanel = ({ space }: SpaceSettingsPanelProps) => {
|
|
19
22
|
const { t } = useTranslation(SPACE_PLUGIN);
|
|
23
|
+
|
|
24
|
+
const client = useClient();
|
|
25
|
+
const devices = useDevices();
|
|
26
|
+
const managedDeviceAvailable = devices.find((device) => device.profile?.type === DeviceType.AGENT_MANAGED);
|
|
27
|
+
const edgeAgents = Boolean(client.config.values.runtime?.client?.edgeFeatures?.agents);
|
|
28
|
+
const edgeReplicationAvailable = edgeAgents && managedDeviceAvailable;
|
|
29
|
+
|
|
20
30
|
const [edgeReplication, setEdgeReplication] = useState(
|
|
21
31
|
space.internal.data.edgeReplication === EdgeReplicationSetting.ENABLED,
|
|
22
32
|
);
|
|
23
|
-
|
|
24
33
|
const toggleEdgeReplication = useCallback(
|
|
25
34
|
async (next: boolean) => {
|
|
26
35
|
setEdgeReplication(next);
|
|
@@ -35,25 +44,21 @@ export const SpaceSettingsPanel = ({ space }: SpaceSettingsPanelProps) => {
|
|
|
35
44
|
);
|
|
36
45
|
|
|
37
46
|
return (
|
|
38
|
-
<div role='form' className='flex flex-col
|
|
39
|
-
<
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
</Input.Root>
|
|
51
|
-
<Input.Root>
|
|
52
|
-
<div role='none' className='flex justify-between'>
|
|
53
|
-
<Input.Label>{t('edge replication label')}</Input.Label>
|
|
47
|
+
<div role='form' className='flex flex-col'>
|
|
48
|
+
<DeprecatedFormInput label={t('name label')}>
|
|
49
|
+
<Input.TextInput
|
|
50
|
+
placeholder={t('unnamed space label')}
|
|
51
|
+
value={space.properties.name ?? ''}
|
|
52
|
+
onChange={(event) => {
|
|
53
|
+
space.properties.name = event.target.value;
|
|
54
|
+
}}
|
|
55
|
+
/>
|
|
56
|
+
</DeprecatedFormInput>
|
|
57
|
+
{edgeReplicationAvailable && (
|
|
58
|
+
<DeprecatedFormInput label={t('edge replication label')}>
|
|
54
59
|
<Input.Switch checked={edgeReplication} onCheckedChange={toggleEdgeReplication} />
|
|
55
|
-
</
|
|
56
|
-
|
|
60
|
+
</DeprecatedFormInput>
|
|
61
|
+
)}
|
|
57
62
|
</div>
|
|
58
63
|
);
|
|
59
64
|
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { type HTMLAttributes, useEffect, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Icon } from '@dxos/react-ui';
|
|
8
|
+
import { type ThemedClassName } from '@dxos/react-ui';
|
|
9
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
10
|
+
|
|
11
|
+
import { type Progress, type PeerSyncState } from './sync-state';
|
|
12
|
+
|
|
13
|
+
export const SYNC_STALLED_TIMEOUT = 5_000;
|
|
14
|
+
|
|
15
|
+
// TODO(wittjosiah): Define sematic color tokens.
|
|
16
|
+
const styles = {
|
|
17
|
+
barBg: 'bg-neutral-50 dark:bg-green-900 text-black',
|
|
18
|
+
barFg: 'bg-neutral-100 bg-green-500',
|
|
19
|
+
barHover: 'dark:hover:bg-green-500',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const useActive = (count: number) => {
|
|
23
|
+
const [current, setCurrent] = useState(count);
|
|
24
|
+
const [active, setActive] = useState(false);
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
let t: NodeJS.Timeout | undefined;
|
|
27
|
+
if (count !== current) {
|
|
28
|
+
setActive(true);
|
|
29
|
+
setCurrent(count);
|
|
30
|
+
t && clearTimeout(t);
|
|
31
|
+
t = setTimeout(() => {
|
|
32
|
+
setActive(false);
|
|
33
|
+
}, SYNC_STALLED_TIMEOUT);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return () => {
|
|
37
|
+
setActive(false);
|
|
38
|
+
clearTimeout(t);
|
|
39
|
+
};
|
|
40
|
+
}, [count, current]);
|
|
41
|
+
return active;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const SpaceRow = ({
|
|
45
|
+
spaceId,
|
|
46
|
+
state: { localDocumentCount, remoteDocumentCount, missingOnLocal, missingOnRemote },
|
|
47
|
+
}: {
|
|
48
|
+
spaceId: string;
|
|
49
|
+
state: PeerSyncState;
|
|
50
|
+
}) => {
|
|
51
|
+
const downActive = useActive(localDocumentCount);
|
|
52
|
+
const upActive = useActive(remoteDocumentCount);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div
|
|
56
|
+
className={mx('flex items-center mx-[2px] gap-[2px] cursor-pointer', styles.barHover)}
|
|
57
|
+
title={spaceId}
|
|
58
|
+
onClick={() => {
|
|
59
|
+
void navigator.clipboard.writeText(spaceId);
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
<Icon
|
|
63
|
+
icon='ph--arrow-fat-line-left--regular'
|
|
64
|
+
size={3}
|
|
65
|
+
classNames={mx(downActive && 'animate-[pulse_1s_infinite]')}
|
|
66
|
+
/>
|
|
67
|
+
<Candle
|
|
68
|
+
up={{ count: remoteDocumentCount, total: remoteDocumentCount + missingOnRemote }}
|
|
69
|
+
down={{ count: localDocumentCount, total: localDocumentCount + missingOnLocal }}
|
|
70
|
+
title={spaceId}
|
|
71
|
+
/>
|
|
72
|
+
<Icon
|
|
73
|
+
icon='ph--arrow-fat-line-right--regular'
|
|
74
|
+
size={3}
|
|
75
|
+
classNames={mx(upActive && 'animate-[pulse_1s_step-start_infinite]')}
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
type CandleProps = ThemedClassName<Pick<HTMLAttributes<HTMLDivElement>, 'title'>> & { up: Progress; down: Progress };
|
|
82
|
+
|
|
83
|
+
const Candle = ({ classNames, up, down }: CandleProps) => {
|
|
84
|
+
return (
|
|
85
|
+
<div className={mx('grid grid-cols-[1fr_2rem_1fr] w-full h-3', classNames)}>
|
|
86
|
+
<Bar classNames='justify-end' {...up} />
|
|
87
|
+
<div className='relative'>
|
|
88
|
+
<div className={mx('absolute inset-0 flex items-center justify-center text-xs', styles.barBg)}>{up.total}</div>
|
|
89
|
+
</div>
|
|
90
|
+
<Bar {...down} />
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const Bar = ({ classNames, count, total }: ThemedClassName<Progress>) => {
|
|
96
|
+
let p = (count / total) * 100;
|
|
97
|
+
if (count < total) {
|
|
98
|
+
p = Math.min(p, 95);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div className={mx('relative flex w-full', styles.barBg, classNames)}>
|
|
103
|
+
<div className={mx('shrink-0', styles.barFg)} style={{ width: `${p}%` }}></div>
|
|
104
|
+
{count !== total && (
|
|
105
|
+
<div className='absolute top-0 bottom-0 flex items-center mx-0.5 text-black text-xs'>{count}</div>
|
|
106
|
+
)}
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
};
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
|
-
import { type Meta } from '@storybook/react';
|
|
7
|
+
import { type Meta, type StoryObj } from '@storybook/react';
|
|
8
8
|
import React from 'react';
|
|
9
9
|
|
|
10
10
|
import { SpaceId } from '@dxos/keys';
|
|
11
11
|
import { withTheme, withLayout } from '@dxos/storybook-utils';
|
|
12
12
|
|
|
13
13
|
import { SyncStatusDetail, SyncStatusIndicator } from './SyncStatus';
|
|
14
|
-
import { getSyncSummary, type SpaceSyncStateMap } from './
|
|
14
|
+
import { getSyncSummary, type SpaceSyncStateMap } from './sync-state';
|
|
15
15
|
import translations from '../../translations';
|
|
16
16
|
|
|
17
17
|
const DefaultStory = (props: any) => {
|
|
@@ -39,13 +39,22 @@ const state: SpaceSyncStateMap = Array.from({ length: 5 }).reduce<SpaceSyncState
|
|
|
39
39
|
return map;
|
|
40
40
|
}, {});
|
|
41
41
|
|
|
42
|
-
export const Default = {
|
|
42
|
+
export const Default: StoryObj<typeof SyncStatusIndicator> = {
|
|
43
43
|
args: {
|
|
44
44
|
state,
|
|
45
|
+
saved: true,
|
|
45
46
|
},
|
|
46
47
|
};
|
|
47
48
|
|
|
48
|
-
export const
|
|
49
|
+
export const Saving: StoryObj<typeof SyncStatusIndicator> = {
|
|
50
|
+
args: {
|
|
51
|
+
state,
|
|
52
|
+
saved: false,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// TODO(wittjosiah): Separate story path for separate component.
|
|
57
|
+
export const Detail: StoryObj<typeof SyncStatusDetail> = {
|
|
49
58
|
render: SyncStatusDetail,
|
|
50
59
|
args: {
|
|
51
60
|
state,
|