@dxos/plugin-space 0.7.2 → 0.7.3-staging.0905f03
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-DJE2HYFV.mjs → chunk-FTKV32QZ.mjs} +9 -2
- package/dist/lib/browser/chunk-FTKV32QZ.mjs.map +7 -0
- package/dist/lib/browser/{chunk-OWZKSWMX.mjs → chunk-MWKXNS5S.mjs} +13 -3
- package/dist/lib/browser/{chunk-OWZKSWMX.mjs.map → chunk-MWKXNS5S.mjs.map} +3 -3
- package/dist/lib/browser/index.mjs +1167 -786
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/meta.mjs +3 -1
- package/dist/lib/browser/types/index.mjs +5 -3
- package/dist/lib/node/{chunk-FYWGZYJB.cjs → chunk-6SNOZF7Y.cjs} +18 -7
- package/dist/lib/node/chunk-6SNOZF7Y.cjs.map +7 -0
- package/dist/lib/node/{chunk-JFDDZI4Y.cjs → chunk-QNVEU2UD.cjs} +12 -4
- package/dist/lib/node/chunk-QNVEU2UD.cjs.map +7 -0
- package/dist/lib/node/index.cjs +1210 -839
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.cjs +7 -5
- package/dist/lib/node/meta.cjs.map +2 -2
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/types/index.cjs +14 -12
- package/dist/lib/node/types/index.cjs.map +2 -2
- package/dist/lib/node-esm/{chunk-MCEAI4CV.mjs → chunk-OHEAWSCA.mjs} +13 -3
- package/dist/lib/node-esm/{chunk-MCEAI4CV.mjs.map → chunk-OHEAWSCA.mjs.map} +3 -3
- package/dist/lib/node-esm/{chunk-DVUZ7A7G.mjs → chunk-UMV7XREB.mjs} +9 -2
- package/dist/lib/node-esm/chunk-UMV7XREB.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +1167 -786
- 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 +3 -1
- package/dist/lib/node-esm/types/index.mjs +5 -3
- package/dist/types/src/SpacePlugin.d.ts.map +1 -1
- package/dist/types/src/components/CreateDialog/CreateObjectDialog.d.ts +9 -0
- package/dist/types/src/components/CreateDialog/CreateObjectDialog.d.ts.map +1 -0
- package/dist/types/src/components/CreateDialog/CreateObjectDialog.stories.d.ts +10 -0
- package/dist/types/src/components/CreateDialog/CreateObjectDialog.stories.d.ts.map +1 -0
- package/dist/types/src/components/CreateDialog/CreateObjectPanel.d.ts +22 -0
- package/dist/types/src/components/CreateDialog/CreateObjectPanel.d.ts.map +1 -0
- package/dist/types/src/components/CreateDialog/CreateSpaceDialog.d.ts +3 -0
- package/dist/types/src/components/CreateDialog/CreateSpaceDialog.d.ts.map +1 -0
- package/dist/types/src/components/CreateDialog/index.d.ts +3 -0
- package/dist/types/src/components/CreateDialog/index.d.ts.map +1 -0
- package/dist/types/src/components/PopoverRenameObject.d.ts +1 -1
- package/dist/types/src/components/SpacePluginSettings.d.ts.map +1 -1
- package/dist/types/src/components/SpaceSettings/SpaceSettingsDialog.d.ts.map +1 -1
- package/dist/types/src/components/SpaceSettings/SpaceSettingsPanel.d.ts.map +1 -1
- package/dist/types/src/components/SyncStatus/InlineSyncStatus.d.ts +7 -0
- package/dist/types/src/components/SyncStatus/InlineSyncStatus.d.ts.map +1 -0
- package/dist/types/src/components/SyncStatus/InlineSyncStatus.stories.d.ts +6 -0
- package/dist/types/src/components/SyncStatus/InlineSyncStatus.stories.d.ts.map +1 -0
- package/dist/types/src/components/SyncStatus/Space.d.ts +8 -3
- package/dist/types/src/components/SyncStatus/Space.d.ts.map +1 -1
- 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 +4 -4
- package/dist/types/src/components/SyncStatus/SyncStatus.stories.d.ts.map +1 -1
- package/dist/types/src/components/SyncStatus/SyncStatusDetail.stories.d.ts +9 -0
- package/dist/types/src/components/SyncStatus/SyncStatusDetail.stories.d.ts.map +1 -0
- package/dist/types/src/components/SyncStatus/index.d.ts +1 -0
- package/dist/types/src/components/SyncStatus/index.d.ts.map +1 -1
- package/dist/types/src/components/SyncStatus/sync-state.d.ts +5 -1
- package/dist/types/src/components/SyncStatus/sync-state.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +5 -0
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +224 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/types.d.ts +14 -14
- package/dist/types/src/types/types.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +3 -13
- package/dist/types/src/util.d.ts.map +1 -1
- package/package.json +38 -35
- package/src/SpacePlugin.tsx +203 -128
- package/src/components/AwaitingObject.tsx +2 -2
- package/src/components/CreateDialog/CreateObjectDialog.stories.tsx +83 -0
- package/src/components/CreateDialog/CreateObjectDialog.tsx +98 -0
- package/src/components/CreateDialog/CreateObjectPanel.tsx +169 -0
- package/src/components/CreateDialog/CreateSpaceDialog.tsx +57 -0
- package/src/components/CreateDialog/index.ts +6 -0
- package/src/components/PopoverRenameObject.tsx +1 -1
- package/src/components/SpacePluginSettings.tsx +3 -32
- package/src/components/SpaceSettings/SpaceSettingsDialog.tsx +5 -5
- package/src/components/SpaceSettings/SpaceSettingsPanel.tsx +2 -6
- package/src/components/SyncStatus/InlineSyncStatus.stories.tsx +57 -0
- package/src/components/SyncStatus/InlineSyncStatus.tsx +61 -0
- package/src/components/SyncStatus/Space.tsx +30 -6
- package/src/components/SyncStatus/SyncStatus.stories.tsx +15 -42
- package/src/components/SyncStatus/SyncStatus.tsx +32 -14
- package/src/components/SyncStatus/SyncStatusDetail.stories.tsx +85 -0
- package/src/components/SyncStatus/index.ts +1 -0
- package/src/components/SyncStatus/sync-state.ts +24 -0
- package/src/components/index.ts +1 -0
- package/src/meta.ts +6 -0
- package/src/translations.ts +15 -0
- package/src/types/types.ts +20 -16
- package/src/util.tsx +51 -141
- package/dist/lib/browser/chunk-DJE2HYFV.mjs.map +0 -7
- package/dist/lib/node/chunk-FYWGZYJB.cjs.map +0 -7
- package/dist/lib/node/chunk-JFDDZI4Y.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-DVUZ7A7G.mjs.map +0 -7
|
@@ -0,0 +1,83 @@
|
|
|
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, { useEffect } from 'react';
|
|
9
|
+
|
|
10
|
+
import { create, Filter, useQuery, useSpace } from '@dxos/react-client/echo';
|
|
11
|
+
import { withClientProvider } from '@dxos/react-client/testing';
|
|
12
|
+
import { Dialog } from '@dxos/react-ui';
|
|
13
|
+
import { osTranslations } from '@dxos/shell/react';
|
|
14
|
+
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
15
|
+
|
|
16
|
+
import { CreateObjectDialog, type CreateObjectDialogProps } from './CreateObjectDialog';
|
|
17
|
+
import translations from '../../translations';
|
|
18
|
+
import { CollectionType } from '../../types';
|
|
19
|
+
|
|
20
|
+
const Story = (args: CreateObjectDialogProps) => {
|
|
21
|
+
return (
|
|
22
|
+
<Dialog.Root open>
|
|
23
|
+
<Dialog.Overlay blockAlign='start'>
|
|
24
|
+
<CreateObjectDialog {...args} />
|
|
25
|
+
</Dialog.Overlay>
|
|
26
|
+
</Dialog.Root>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// TODO(wittjosiah): Story should be for CreateObjectPanel.
|
|
31
|
+
const meta: Meta<typeof CreateObjectDialog> = {
|
|
32
|
+
title: 'plugins/plugin-space/CreateObjectDialog',
|
|
33
|
+
component: CreateObjectDialog,
|
|
34
|
+
render: Story,
|
|
35
|
+
decorators: [
|
|
36
|
+
withClientProvider({ createIdentity: true, createSpace: true, types: [CollectionType] }),
|
|
37
|
+
withTheme,
|
|
38
|
+
withLayout({ tooltips: true }),
|
|
39
|
+
],
|
|
40
|
+
parameters: { translations: [...translations, osTranslations] },
|
|
41
|
+
args: {
|
|
42
|
+
schemas: [CollectionType],
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export default meta;
|
|
47
|
+
|
|
48
|
+
export const Default: StoryObj<typeof CreateObjectDialog> = {};
|
|
49
|
+
|
|
50
|
+
export const Typename: StoryObj<typeof CreateObjectDialog> = {
|
|
51
|
+
args: { typename: CollectionType.typename },
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const TargetSpace: StoryObj<typeof CreateObjectDialog> = {
|
|
55
|
+
render: (args) => {
|
|
56
|
+
const space = useSpace();
|
|
57
|
+
|
|
58
|
+
if (!space) {
|
|
59
|
+
return <></>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return <Story {...args} target={space} />;
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const TargetCollection: StoryObj<typeof CreateObjectDialog> = {
|
|
67
|
+
render: (args) => {
|
|
68
|
+
const space = useSpace();
|
|
69
|
+
const [collection] = useQuery(space, Filter.schema(CollectionType));
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (space) {
|
|
73
|
+
space.db.add(create(CollectionType, { name: 'My Collection', objects: [], views: {} }));
|
|
74
|
+
}
|
|
75
|
+
}, [space]);
|
|
76
|
+
|
|
77
|
+
if (!collection) {
|
|
78
|
+
return <></>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return <Story {...args} target={collection} />;
|
|
82
|
+
},
|
|
83
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback, useRef } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type MetadataResolver, NavigationAction, useIntentDispatcher } from '@dxos/app-framework';
|
|
8
|
+
import { useClient } from '@dxos/react-client';
|
|
9
|
+
import { type AbstractTypedObject, getSpace, isReactiveObject, isSpace, useSpaces } from '@dxos/react-client/echo';
|
|
10
|
+
import { Button, Dialog, Icon, useTranslation } from '@dxos/react-ui';
|
|
11
|
+
|
|
12
|
+
import { CreateObjectPanel, type CreateObjectPanelProps } from './CreateObjectPanel';
|
|
13
|
+
import { SPACE_PLUGIN, SpaceAction } from '../../meta';
|
|
14
|
+
import { CollectionType } from '../../types';
|
|
15
|
+
|
|
16
|
+
export type CreateObjectDialogProps = Pick<CreateObjectPanelProps, 'schemas' | 'target' | 'typename' | 'name'> & {
|
|
17
|
+
resolve?: MetadataResolver;
|
|
18
|
+
navigableCollections?: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const CreateObjectDialog = ({
|
|
22
|
+
schemas,
|
|
23
|
+
target,
|
|
24
|
+
typename,
|
|
25
|
+
name,
|
|
26
|
+
navigableCollections,
|
|
27
|
+
resolve,
|
|
28
|
+
}: CreateObjectDialogProps) => {
|
|
29
|
+
const closeRef = useRef<HTMLButtonElement | null>(null);
|
|
30
|
+
const { t } = useTranslation(SPACE_PLUGIN);
|
|
31
|
+
const client = useClient();
|
|
32
|
+
const spaces = useSpaces();
|
|
33
|
+
const dispatch = useIntentDispatcher();
|
|
34
|
+
|
|
35
|
+
const handleCreateObject = useCallback(
|
|
36
|
+
async ({
|
|
37
|
+
schema,
|
|
38
|
+
target: _target,
|
|
39
|
+
name,
|
|
40
|
+
}: {
|
|
41
|
+
schema: AbstractTypedObject;
|
|
42
|
+
target: CreateObjectPanelProps['target'];
|
|
43
|
+
name?: string;
|
|
44
|
+
}) => {
|
|
45
|
+
const target = isSpace(_target) ? (_target.properties[CollectionType.typename] as CollectionType) : _target;
|
|
46
|
+
const createObjectAction = resolve?.(schema.typename)?.createObject;
|
|
47
|
+
if (!createObjectAction || !target) {
|
|
48
|
+
// TODO(wittjosiah): UI feedback.
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// NOTE: Must close before navigating or attention won't follow object.
|
|
53
|
+
closeRef.current?.click();
|
|
54
|
+
|
|
55
|
+
const space = isSpace(target) ? target : getSpace(target);
|
|
56
|
+
const result = await dispatch({ action: createObjectAction, data: { name, space } });
|
|
57
|
+
const object = result?.data;
|
|
58
|
+
if (isReactiveObject(object)) {
|
|
59
|
+
await dispatch([
|
|
60
|
+
{
|
|
61
|
+
plugin: SPACE_PLUGIN,
|
|
62
|
+
action: SpaceAction.ADD_OBJECT,
|
|
63
|
+
data: { target, object },
|
|
64
|
+
},
|
|
65
|
+
...(!(object instanceof CollectionType) || navigableCollections ? [{ action: NavigationAction.OPEN }] : []),
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
[dispatch, resolve],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
// TODO(wittjosiah): The tablist dialog pattern is copied from @dxos/plugin-manager.
|
|
74
|
+
// Consider factoring it out to the tabs package.
|
|
75
|
+
<Dialog.Content classNames='p-0 bs-content min-bs-[15rem] max-bs-full md:max-is-[40rem] overflow-hidden'>
|
|
76
|
+
<div role='none' className='flex justify-between pbs-3 pis-2 pie-3 @md:pbs-4 @md:pis-4 @md:pie-5'>
|
|
77
|
+
<Dialog.Title>{t('create object dialog title')}</Dialog.Title>
|
|
78
|
+
<Dialog.Close asChild>
|
|
79
|
+
<Button ref={closeRef} density='fine' variant='ghost' autoFocus>
|
|
80
|
+
<Icon icon='ph--x--regular' size={4} />
|
|
81
|
+
</Button>
|
|
82
|
+
</Dialog.Close>
|
|
83
|
+
</div>
|
|
84
|
+
<div className='p-4'>
|
|
85
|
+
<CreateObjectPanel
|
|
86
|
+
schemas={schemas}
|
|
87
|
+
spaces={spaces}
|
|
88
|
+
target={target}
|
|
89
|
+
typename={typename}
|
|
90
|
+
name={name}
|
|
91
|
+
defaultSpaceId={client.spaces.default.id}
|
|
92
|
+
resolve={resolve}
|
|
93
|
+
onCreateObject={handleCreateObject}
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
</Dialog.Content>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type MetadataResolver } from '@dxos/app-framework';
|
|
8
|
+
import { type AbstractTypedObject, getObjectAnnotation, S } from '@dxos/echo-schema';
|
|
9
|
+
import { type SpaceId, type Space, isSpace } from '@dxos/react-client/echo';
|
|
10
|
+
import { Icon, IconButton, Input, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
11
|
+
import { Form, InputHeader } from '@dxos/react-ui-form';
|
|
12
|
+
import { SearchList } from '@dxos/react-ui-searchlist';
|
|
13
|
+
import { nonNullable, type MaybePromise } from '@dxos/util';
|
|
14
|
+
|
|
15
|
+
import { SPACE_PLUGIN } from '../../meta';
|
|
16
|
+
import { type CollectionType } from '../../types';
|
|
17
|
+
import { getSpaceDisplayName } from '../../util';
|
|
18
|
+
|
|
19
|
+
export type CreateObjectPanelProps = {
|
|
20
|
+
schemas: AbstractTypedObject[];
|
|
21
|
+
spaces: Space[];
|
|
22
|
+
typename?: string;
|
|
23
|
+
target?: Space | CollectionType;
|
|
24
|
+
name?: string;
|
|
25
|
+
defaultSpaceId?: SpaceId;
|
|
26
|
+
resolve?: MetadataResolver;
|
|
27
|
+
onCreateObject?: (params: {
|
|
28
|
+
schema: AbstractTypedObject;
|
|
29
|
+
target: Space | CollectionType;
|
|
30
|
+
name?: string;
|
|
31
|
+
}) => MaybePromise<void>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const CreateObjectPanel = ({
|
|
35
|
+
schemas,
|
|
36
|
+
spaces,
|
|
37
|
+
typename: initialTypename,
|
|
38
|
+
target: initialTarget,
|
|
39
|
+
name: initialName,
|
|
40
|
+
defaultSpaceId,
|
|
41
|
+
resolve,
|
|
42
|
+
onCreateObject,
|
|
43
|
+
}: CreateObjectPanelProps) => {
|
|
44
|
+
const { t } = useTranslation(SPACE_PLUGIN);
|
|
45
|
+
const [typename, setTypename] = useState<string | undefined>(initialTypename);
|
|
46
|
+
const [target, setTarget] = useState<Space | CollectionType | undefined>(initialTarget);
|
|
47
|
+
const schema = schemas.find((schema) => getObjectAnnotation(schema)?.typename === typename);
|
|
48
|
+
const options = schemas.map(getObjectAnnotation).filter(nonNullable);
|
|
49
|
+
|
|
50
|
+
const handleClearSchema = useCallback(() => setTypename(undefined), []);
|
|
51
|
+
const handleClearTarget = useCallback(() => setTarget(undefined), []);
|
|
52
|
+
|
|
53
|
+
const handleCreateObject = useCallback(
|
|
54
|
+
async ({ name }: { name?: string }) => {
|
|
55
|
+
if (!schema || !target) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
await onCreateObject?.({ schema, target, name });
|
|
60
|
+
},
|
|
61
|
+
[onCreateObject, schema, target],
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// TODO(wittjosiah): All of these inputs should be rolled into a `Form` once it supports the necessary variants.
|
|
65
|
+
const schemaInput = (
|
|
66
|
+
<SearchList.Root label={t('schema input label')} classNames='flex flex-col grow overflow-hidden my-2 px-2'>
|
|
67
|
+
<SearchList.Input
|
|
68
|
+
autoFocus
|
|
69
|
+
data-testid='create-object-form.schema-input'
|
|
70
|
+
placeholder={t('schema input placeholder')}
|
|
71
|
+
classNames='px-1 my-2'
|
|
72
|
+
/>
|
|
73
|
+
<SearchList.Content classNames='max-bs-[24rem] overflow-auto'>
|
|
74
|
+
{options.map((option) => (
|
|
75
|
+
<SearchList.Item
|
|
76
|
+
key={option.typename}
|
|
77
|
+
value={t('typename label', { ns: option.typename, defaultValue: option.typename })}
|
|
78
|
+
onSelect={() => setTypename(option.typename)}
|
|
79
|
+
classNames='flex items-center gap-2'
|
|
80
|
+
>
|
|
81
|
+
<span className='flex gap-2 items-center grow truncate'>
|
|
82
|
+
<Icon icon={resolve?.(option.typename).icon ?? 'ph--placeholder--regular'} size={5} />
|
|
83
|
+
{t('typename label', { ns: option.typename, defaultValue: option.typename })}
|
|
84
|
+
</span>
|
|
85
|
+
</SearchList.Item>
|
|
86
|
+
))}
|
|
87
|
+
</SearchList.Content>
|
|
88
|
+
</SearchList.Root>
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const spaceInput = (
|
|
92
|
+
<SearchList.Root label={t('space input label')} classNames='flex flex-col grow overflow-hidden my-2 px-2'>
|
|
93
|
+
<SearchList.Input
|
|
94
|
+
autoFocus
|
|
95
|
+
data-testid='create-object-form.space-input'
|
|
96
|
+
placeholder={t('space input placeholder')}
|
|
97
|
+
classNames='px-1 my-2'
|
|
98
|
+
/>
|
|
99
|
+
<SearchList.Content classNames='max-bs-[24rem] overflow-auto'>
|
|
100
|
+
{spaces.map((space) => (
|
|
101
|
+
<SearchList.Item
|
|
102
|
+
key={space.id}
|
|
103
|
+
value={toLocalizedString(getSpaceDisplayName(space, { personal: space.id === defaultSpaceId }), t)}
|
|
104
|
+
onSelect={() => setTarget(space)}
|
|
105
|
+
classNames='flex items-center gap-2'
|
|
106
|
+
>
|
|
107
|
+
<span className='grow truncate'>
|
|
108
|
+
{toLocalizedString(getSpaceDisplayName(space, { personal: space.id === defaultSpaceId }), t)}
|
|
109
|
+
</span>
|
|
110
|
+
</SearchList.Item>
|
|
111
|
+
))}
|
|
112
|
+
</SearchList.Content>
|
|
113
|
+
</SearchList.Root>
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
const form = (
|
|
117
|
+
<Form
|
|
118
|
+
autoFocus
|
|
119
|
+
values={{ name: initialName }}
|
|
120
|
+
schema={S.Struct({ name: S.optional(S.String) })}
|
|
121
|
+
testId='create-object-form'
|
|
122
|
+
onSave={handleCreateObject}
|
|
123
|
+
/>
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<div role='form' className='flex flex-col gap-2'>
|
|
128
|
+
{target && (
|
|
129
|
+
<div role='none' className='px-2'>
|
|
130
|
+
<Input.Root>
|
|
131
|
+
<InputHeader>
|
|
132
|
+
<Input.Label>
|
|
133
|
+
{t(isSpace(target) ? 'creating in space label' : 'creating in collection label')}
|
|
134
|
+
</Input.Label>
|
|
135
|
+
</InputHeader>
|
|
136
|
+
<div role='none' className='flex gap-2'>
|
|
137
|
+
<Input.TextInput
|
|
138
|
+
disabled
|
|
139
|
+
value={
|
|
140
|
+
isSpace(target)
|
|
141
|
+
? toLocalizedString(getSpaceDisplayName(target, { personal: target.id === defaultSpaceId }), t)
|
|
142
|
+
: target.name || t('unnamed collection label')
|
|
143
|
+
}
|
|
144
|
+
/>
|
|
145
|
+
<IconButton iconOnly icon='ph--x--regular' label={t('clear input label')} onClick={handleClearTarget} />
|
|
146
|
+
</div>
|
|
147
|
+
</Input.Root>
|
|
148
|
+
</div>
|
|
149
|
+
)}
|
|
150
|
+
{schema && (
|
|
151
|
+
<div role='none' className='px-2'>
|
|
152
|
+
<Input.Root>
|
|
153
|
+
<InputHeader>
|
|
154
|
+
<Input.Label>{t('creating object type label')}</Input.Label>
|
|
155
|
+
</InputHeader>
|
|
156
|
+
<div role='none' className='flex gap-2'>
|
|
157
|
+
<Input.TextInput
|
|
158
|
+
disabled
|
|
159
|
+
value={t('typename label', { ns: schema.typename, defaultValue: schema.typename })}
|
|
160
|
+
/>
|
|
161
|
+
<IconButton iconOnly icon='ph--x--regular' label={t('clear input label')} onClick={handleClearSchema} />
|
|
162
|
+
</div>
|
|
163
|
+
</Input.Root>
|
|
164
|
+
</div>
|
|
165
|
+
)}
|
|
166
|
+
{!schema ? schemaInput : !target ? spaceInput : form}
|
|
167
|
+
</div>
|
|
168
|
+
);
|
|
169
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback, useRef } from 'react';
|
|
6
|
+
|
|
7
|
+
import { useIntentDispatcher } from '@dxos/app-framework';
|
|
8
|
+
import { type S } from '@dxos/echo-schema';
|
|
9
|
+
import { Button, Dialog, Icon, useTranslation } from '@dxos/react-ui';
|
|
10
|
+
import { Form } from '@dxos/react-ui-form';
|
|
11
|
+
|
|
12
|
+
import { SPACE_PLUGIN, SpaceAction } from '../../meta';
|
|
13
|
+
import { SpaceForm } from '../../types';
|
|
14
|
+
|
|
15
|
+
type FormValues = S.Schema.Type<typeof SpaceForm>;
|
|
16
|
+
const initialValues: FormValues = { edgeReplication: true };
|
|
17
|
+
|
|
18
|
+
export const CreateSpaceDialog = () => {
|
|
19
|
+
const closeRef = useRef<HTMLButtonElement | null>(null);
|
|
20
|
+
const { t } = useTranslation(SPACE_PLUGIN);
|
|
21
|
+
const dispatch = useIntentDispatcher();
|
|
22
|
+
|
|
23
|
+
const handleCreateSpace = useCallback(
|
|
24
|
+
async (data: FormValues) => {
|
|
25
|
+
const result = await dispatch({
|
|
26
|
+
action: SpaceAction.CREATE,
|
|
27
|
+
data,
|
|
28
|
+
});
|
|
29
|
+
const target = result?.data.space;
|
|
30
|
+
if (target) {
|
|
31
|
+
await dispatch({
|
|
32
|
+
action: SpaceAction.OPEN_CREATE_OBJECT,
|
|
33
|
+
data: { target },
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
[dispatch],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
// TODO(wittjosiah): The tablist dialog pattern is copied from @dxos/plugin-manager.
|
|
42
|
+
// Consider factoring it out to the tabs package.
|
|
43
|
+
<Dialog.Content classNames='p-0 bs-content min-bs-[15rem] max-bs-full md:max-is-[40rem] overflow-hidden'>
|
|
44
|
+
<div role='none' className='flex justify-between pbs-3 pis-2 pie-3 @md:pbs-4 @md:pis-4 @md:pie-5'>
|
|
45
|
+
<Dialog.Title>{t('create space dialog title')}</Dialog.Title>
|
|
46
|
+
<Dialog.Close asChild>
|
|
47
|
+
<Button ref={closeRef} density='fine' variant='ghost' autoFocus>
|
|
48
|
+
<Icon icon='ph--x--regular' size={4} />
|
|
49
|
+
</Button>
|
|
50
|
+
</Dialog.Close>
|
|
51
|
+
</div>
|
|
52
|
+
<div className='p-4'>
|
|
53
|
+
<Form testId='create-space-form' values={initialValues} schema={SpaceForm} onSave={handleCreateSpace} />
|
|
54
|
+
</div>
|
|
55
|
+
</Dialog.Content>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import React, { useCallback, useRef, useState } from 'react';
|
|
6
6
|
|
|
7
|
-
import { type ReactiveObject } from '@dxos/
|
|
7
|
+
import { type ReactiveObject } from '@dxos/live-object';
|
|
8
8
|
import { log } from '@dxos/log';
|
|
9
9
|
import { Button, Input, Popover, useTranslation } from '@dxos/react-ui';
|
|
10
10
|
|
|
@@ -4,17 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
import React from 'react';
|
|
6
6
|
|
|
7
|
-
import { useIntentDispatcher
|
|
8
|
-
import { Input,
|
|
7
|
+
import { useIntentDispatcher } from '@dxos/app-framework';
|
|
8
|
+
import { Input, useTranslation } from '@dxos/react-ui';
|
|
9
9
|
import { DeprecatedFormInput } from '@dxos/react-ui-form';
|
|
10
10
|
|
|
11
11
|
import { SpaceAction, SPACE_PLUGIN } from '../meta';
|
|
12
|
-
import {
|
|
12
|
+
import { type SpaceSettingsProps } from '../types';
|
|
13
13
|
|
|
14
14
|
export const SpacePluginSettings = ({ settings }: { settings: SpaceSettingsProps }) => {
|
|
15
15
|
const { t } = useTranslation(SPACE_PLUGIN);
|
|
16
16
|
const dispatch = useIntentDispatcher();
|
|
17
|
-
const plugins = useResolvePlugins(parseSpaceInitPlugin);
|
|
18
17
|
|
|
19
18
|
return (
|
|
20
19
|
<>
|
|
@@ -30,34 +29,6 @@ export const SpacePluginSettings = ({ settings }: { settings: SpaceSettingsProps
|
|
|
30
29
|
}
|
|
31
30
|
/>
|
|
32
31
|
</DeprecatedFormInput>
|
|
33
|
-
|
|
34
|
-
<DeprecatedFormInput 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
|
-
</DeprecatedFormInput>
|
|
61
32
|
</>
|
|
62
33
|
);
|
|
63
34
|
};
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
import React, { useState } from 'react';
|
|
6
6
|
|
|
7
7
|
import { useClient } from '@dxos/react-client';
|
|
8
|
-
import { Button, Dialog, Icon, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
8
|
+
import { Button, Clipboard, Dialog, Icon, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
9
9
|
import { Tabs, type TabsRootProps, type TabsActivePart } from '@dxos/react-ui-tabs';
|
|
10
|
-
import {
|
|
10
|
+
import { SpacePanel, type SpacePanelProps } from '@dxos/shell/react';
|
|
11
11
|
|
|
12
12
|
import { SpaceSettingsPanel, type SpaceSettingsPanelProps } from './SpaceSettingsPanel';
|
|
13
13
|
import { SPACE_PLUGIN } from '../../meta';
|
|
@@ -62,7 +62,7 @@ export const SpaceSettingsDialog = ({
|
|
|
62
62
|
</Dialog.Title>
|
|
63
63
|
<Dialog.Close asChild>
|
|
64
64
|
<Button density='fine' variant='ghost' autoFocus>
|
|
65
|
-
<Icon icon='ph--x--regular' size={
|
|
65
|
+
<Icon icon='ph--x--regular' size={4} />
|
|
66
66
|
</Button>
|
|
67
67
|
</Dialog.Close>
|
|
68
68
|
</div>
|
|
@@ -92,9 +92,9 @@ export const SpaceSettingsDialog = ({
|
|
|
92
92
|
|
|
93
93
|
{/* TODO(wittjosiah): Weird focus ring when tabpanel is focused. */}
|
|
94
94
|
<Tabs.Tabpanel value='members' classNames='pli-3 @md:pli-5 max-bs-dvh overflow-y-auto'>
|
|
95
|
-
<
|
|
95
|
+
<Clipboard.Provider>
|
|
96
96
|
<SpacePanel space={space} hideHeading target={target} createInvitationUrl={createInvitationUrl} />
|
|
97
|
-
</
|
|
97
|
+
</Clipboard.Provider>
|
|
98
98
|
</Tabs.Tabpanel>
|
|
99
99
|
</Tabs.Viewport>
|
|
100
100
|
</Tabs.Root>
|
|
@@ -8,7 +8,6 @@ import { log } from '@dxos/log';
|
|
|
8
8
|
import { EdgeReplicationSetting } from '@dxos/protocols/proto/dxos/echo/metadata';
|
|
9
9
|
import { useClient } from '@dxos/react-client';
|
|
10
10
|
import { type Space } from '@dxos/react-client/echo';
|
|
11
|
-
import { DeviceType, useDevices } from '@dxos/react-client/halo';
|
|
12
11
|
import { Input, useTranslation } from '@dxos/react-ui';
|
|
13
12
|
import { DeprecatedFormInput } from '@dxos/react-ui-form';
|
|
14
13
|
|
|
@@ -22,10 +21,7 @@ export const SpaceSettingsPanel = ({ space }: SpaceSettingsPanelProps) => {
|
|
|
22
21
|
const { t } = useTranslation(SPACE_PLUGIN);
|
|
23
22
|
|
|
24
23
|
const client = useClient();
|
|
25
|
-
const
|
|
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;
|
|
24
|
+
const edgeEnabled = Boolean(client.config.values.runtime?.client?.edgeFeatures?.echoReplicator);
|
|
29
25
|
|
|
30
26
|
const [edgeReplication, setEdgeReplication] = useState(
|
|
31
27
|
space.internal.data.edgeReplication === EdgeReplicationSetting.ENABLED,
|
|
@@ -54,7 +50,7 @@ export const SpaceSettingsPanel = ({ space }: SpaceSettingsPanelProps) => {
|
|
|
54
50
|
}}
|
|
55
51
|
/>
|
|
56
52
|
</DeprecatedFormInput>
|
|
57
|
-
{
|
|
53
|
+
{edgeEnabled && (
|
|
58
54
|
<DeprecatedFormInput label={t('edge replication label')}>
|
|
59
55
|
<Input.Switch checked={edgeReplication} onCheckedChange={toggleEdgeReplication} />
|
|
60
56
|
</DeprecatedFormInput>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import '@dxos-theme';
|
|
6
|
+
|
|
7
|
+
import { type Meta } from '@storybook/react';
|
|
8
|
+
import React, { useEffect, useState } from 'react';
|
|
9
|
+
|
|
10
|
+
import { withTheme, withLayout } from '@dxos/storybook-utils';
|
|
11
|
+
import { range } from '@dxos/util';
|
|
12
|
+
|
|
13
|
+
import { InlineSyncStatusIndicator } from './InlineSyncStatus';
|
|
14
|
+
import translations from '../../translations';
|
|
15
|
+
|
|
16
|
+
const DefaultStory = () => {
|
|
17
|
+
const [spaces, setSpaces] = useState(
|
|
18
|
+
new Map<string, boolean>(range(8).map((i) => [`space-${i + 1}`, Math.random() > 0.5])),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const t = setInterval(
|
|
23
|
+
() => {
|
|
24
|
+
setSpaces((spaces) => {
|
|
25
|
+
const space = Array.from(spaces.keys())[Math.floor(Math.random() * spaces.size)];
|
|
26
|
+
spaces.set(space, !spaces.get(space));
|
|
27
|
+
return new Map(spaces);
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
2_000 + Math.random() * 3_000,
|
|
31
|
+
);
|
|
32
|
+
return () => clearInterval(t);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className='flex flex-col p-2 w-[200px]'>
|
|
37
|
+
{Array.from(spaces.entries()).map(([space, sync]) => (
|
|
38
|
+
<div key={space} className='flex items-center'>
|
|
39
|
+
<div className='grow'>{space}</div>
|
|
40
|
+
{sync && <InlineSyncStatusIndicator />}
|
|
41
|
+
</div>
|
|
42
|
+
))}
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const meta: Meta = {
|
|
48
|
+
title: 'plugins/plugin-space/InlineSyncStatusIndicator',
|
|
49
|
+
component: InlineSyncStatusIndicator,
|
|
50
|
+
render: DefaultStory,
|
|
51
|
+
decorators: [withTheme, withLayout({ fullscreen: true })],
|
|
52
|
+
parameters: { translations },
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export default meta;
|
|
56
|
+
|
|
57
|
+
export const Default = {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { type CSSProperties, useEffect, useMemo, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { QueryEdgeStatusResponse } from '@dxos/protocols/proto/dxos/client/services';
|
|
8
|
+
import { EdgeReplicationSetting } from '@dxos/protocols/proto/dxos/echo/metadata';
|
|
9
|
+
import { useClient } from '@dxos/react-client';
|
|
10
|
+
import { type Space } from '@dxos/react-client/echo';
|
|
11
|
+
import { Icon, useTranslation } from '@dxos/react-ui';
|
|
12
|
+
|
|
13
|
+
import { useSpaceSyncState } from './sync-state';
|
|
14
|
+
import { SPACE_PLUGIN } from '../../meta';
|
|
15
|
+
|
|
16
|
+
const useEdgeStatus = (): QueryEdgeStatusResponse.EdgeStatus => {
|
|
17
|
+
const [status, setStatus] = useState(QueryEdgeStatusResponse.EdgeStatus.NOT_CONNECTED);
|
|
18
|
+
const client = useClient();
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
client.services.services.EdgeAgentService?.queryEdgeStatus().subscribe(({ status }) => {
|
|
22
|
+
setStatus(status);
|
|
23
|
+
});
|
|
24
|
+
}, [client]);
|
|
25
|
+
|
|
26
|
+
return status;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const InlineSyncStatus = ({ space }: { space: Space }) => {
|
|
30
|
+
const connectedToEdge = useEdgeStatus() === QueryEdgeStatusResponse.EdgeStatus.CONNECTED;
|
|
31
|
+
// TODO(wittjosiah): This is not reactive.
|
|
32
|
+
const edgeSyncEnabled = space.internal.data.edgeReplication === EdgeReplicationSetting.ENABLED;
|
|
33
|
+
const syncState = useSpaceSyncState(space);
|
|
34
|
+
if (!connectedToEdge || !edgeSyncEnabled || !syncState || syncState.missingOnLocal === 0) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return <InlineSyncStatusIndicator />;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const InlineSyncStatusIndicator = () => {
|
|
42
|
+
const { t } = useTranslation(SPACE_PLUGIN);
|
|
43
|
+
const animationProps = useMemo<CSSProperties>(
|
|
44
|
+
() => ({
|
|
45
|
+
// Synchronize animations.
|
|
46
|
+
animationDelay: `-${Date.now() % 2_000}ms`,
|
|
47
|
+
}),
|
|
48
|
+
[],
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div role='status' aria-label={t('syncing message')} className='flex items-center'>
|
|
53
|
+
<Icon
|
|
54
|
+
icon='ph--circle-notch--regular'
|
|
55
|
+
size={3}
|
|
56
|
+
style={animationProps}
|
|
57
|
+
classNames='text-subdued animate-[spin_2s_linear_infinite]'
|
|
58
|
+
/>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
};
|