@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.
Files changed (99) hide show
  1. package/dist/lib/browser/{chunk-DJE2HYFV.mjs → chunk-FTKV32QZ.mjs} +9 -2
  2. package/dist/lib/browser/chunk-FTKV32QZ.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-OWZKSWMX.mjs → chunk-MWKXNS5S.mjs} +13 -3
  4. package/dist/lib/browser/{chunk-OWZKSWMX.mjs.map → chunk-MWKXNS5S.mjs.map} +3 -3
  5. package/dist/lib/browser/index.mjs +1167 -786
  6. package/dist/lib/browser/index.mjs.map +4 -4
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/meta.mjs +3 -1
  9. package/dist/lib/browser/types/index.mjs +5 -3
  10. package/dist/lib/node/{chunk-FYWGZYJB.cjs → chunk-6SNOZF7Y.cjs} +18 -7
  11. package/dist/lib/node/chunk-6SNOZF7Y.cjs.map +7 -0
  12. package/dist/lib/node/{chunk-JFDDZI4Y.cjs → chunk-QNVEU2UD.cjs} +12 -4
  13. package/dist/lib/node/chunk-QNVEU2UD.cjs.map +7 -0
  14. package/dist/lib/node/index.cjs +1210 -839
  15. package/dist/lib/node/index.cjs.map +4 -4
  16. package/dist/lib/node/meta.cjs +7 -5
  17. package/dist/lib/node/meta.cjs.map +2 -2
  18. package/dist/lib/node/meta.json +1 -1
  19. package/dist/lib/node/types/index.cjs +14 -12
  20. package/dist/lib/node/types/index.cjs.map +2 -2
  21. package/dist/lib/node-esm/{chunk-MCEAI4CV.mjs → chunk-OHEAWSCA.mjs} +13 -3
  22. package/dist/lib/node-esm/{chunk-MCEAI4CV.mjs.map → chunk-OHEAWSCA.mjs.map} +3 -3
  23. package/dist/lib/node-esm/{chunk-DVUZ7A7G.mjs → chunk-UMV7XREB.mjs} +9 -2
  24. package/dist/lib/node-esm/chunk-UMV7XREB.mjs.map +7 -0
  25. package/dist/lib/node-esm/index.mjs +1167 -786
  26. package/dist/lib/node-esm/index.mjs.map +4 -4
  27. package/dist/lib/node-esm/meta.json +1 -1
  28. package/dist/lib/node-esm/meta.mjs +3 -1
  29. package/dist/lib/node-esm/types/index.mjs +5 -3
  30. package/dist/types/src/SpacePlugin.d.ts.map +1 -1
  31. package/dist/types/src/components/CreateDialog/CreateObjectDialog.d.ts +9 -0
  32. package/dist/types/src/components/CreateDialog/CreateObjectDialog.d.ts.map +1 -0
  33. package/dist/types/src/components/CreateDialog/CreateObjectDialog.stories.d.ts +10 -0
  34. package/dist/types/src/components/CreateDialog/CreateObjectDialog.stories.d.ts.map +1 -0
  35. package/dist/types/src/components/CreateDialog/CreateObjectPanel.d.ts +22 -0
  36. package/dist/types/src/components/CreateDialog/CreateObjectPanel.d.ts.map +1 -0
  37. package/dist/types/src/components/CreateDialog/CreateSpaceDialog.d.ts +3 -0
  38. package/dist/types/src/components/CreateDialog/CreateSpaceDialog.d.ts.map +1 -0
  39. package/dist/types/src/components/CreateDialog/index.d.ts +3 -0
  40. package/dist/types/src/components/CreateDialog/index.d.ts.map +1 -0
  41. package/dist/types/src/components/PopoverRenameObject.d.ts +1 -1
  42. package/dist/types/src/components/SpacePluginSettings.d.ts.map +1 -1
  43. package/dist/types/src/components/SpaceSettings/SpaceSettingsDialog.d.ts.map +1 -1
  44. package/dist/types/src/components/SpaceSettings/SpaceSettingsPanel.d.ts.map +1 -1
  45. package/dist/types/src/components/SyncStatus/InlineSyncStatus.d.ts +7 -0
  46. package/dist/types/src/components/SyncStatus/InlineSyncStatus.d.ts.map +1 -0
  47. package/dist/types/src/components/SyncStatus/InlineSyncStatus.stories.d.ts +6 -0
  48. package/dist/types/src/components/SyncStatus/InlineSyncStatus.stories.d.ts.map +1 -0
  49. package/dist/types/src/components/SyncStatus/Space.d.ts +8 -3
  50. package/dist/types/src/components/SyncStatus/Space.d.ts.map +1 -1
  51. package/dist/types/src/components/SyncStatus/SyncStatus.d.ts +3 -2
  52. package/dist/types/src/components/SyncStatus/SyncStatus.d.ts.map +1 -1
  53. package/dist/types/src/components/SyncStatus/SyncStatus.stories.d.ts +4 -4
  54. package/dist/types/src/components/SyncStatus/SyncStatus.stories.d.ts.map +1 -1
  55. package/dist/types/src/components/SyncStatus/SyncStatusDetail.stories.d.ts +9 -0
  56. package/dist/types/src/components/SyncStatus/SyncStatusDetail.stories.d.ts.map +1 -0
  57. package/dist/types/src/components/SyncStatus/index.d.ts +1 -0
  58. package/dist/types/src/components/SyncStatus/index.d.ts.map +1 -1
  59. package/dist/types/src/components/SyncStatus/sync-state.d.ts +5 -1
  60. package/dist/types/src/components/SyncStatus/sync-state.d.ts.map +1 -1
  61. package/dist/types/src/components/index.d.ts +1 -0
  62. package/dist/types/src/components/index.d.ts.map +1 -1
  63. package/dist/types/src/meta.d.ts +5 -0
  64. package/dist/types/src/meta.d.ts.map +1 -1
  65. package/dist/types/src/translations.d.ts +224 -0
  66. package/dist/types/src/translations.d.ts.map +1 -1
  67. package/dist/types/src/types/types.d.ts +14 -14
  68. package/dist/types/src/types/types.d.ts.map +1 -1
  69. package/dist/types/src/util.d.ts +3 -13
  70. package/dist/types/src/util.d.ts.map +1 -1
  71. package/package.json +38 -35
  72. package/src/SpacePlugin.tsx +203 -128
  73. package/src/components/AwaitingObject.tsx +2 -2
  74. package/src/components/CreateDialog/CreateObjectDialog.stories.tsx +83 -0
  75. package/src/components/CreateDialog/CreateObjectDialog.tsx +98 -0
  76. package/src/components/CreateDialog/CreateObjectPanel.tsx +169 -0
  77. package/src/components/CreateDialog/CreateSpaceDialog.tsx +57 -0
  78. package/src/components/CreateDialog/index.ts +6 -0
  79. package/src/components/PopoverRenameObject.tsx +1 -1
  80. package/src/components/SpacePluginSettings.tsx +3 -32
  81. package/src/components/SpaceSettings/SpaceSettingsDialog.tsx +5 -5
  82. package/src/components/SpaceSettings/SpaceSettingsPanel.tsx +2 -6
  83. package/src/components/SyncStatus/InlineSyncStatus.stories.tsx +57 -0
  84. package/src/components/SyncStatus/InlineSyncStatus.tsx +61 -0
  85. package/src/components/SyncStatus/Space.tsx +30 -6
  86. package/src/components/SyncStatus/SyncStatus.stories.tsx +15 -42
  87. package/src/components/SyncStatus/SyncStatus.tsx +32 -14
  88. package/src/components/SyncStatus/SyncStatusDetail.stories.tsx +85 -0
  89. package/src/components/SyncStatus/index.ts +1 -0
  90. package/src/components/SyncStatus/sync-state.ts +24 -0
  91. package/src/components/index.ts +1 -0
  92. package/src/meta.ts +6 -0
  93. package/src/translations.ts +15 -0
  94. package/src/types/types.ts +20 -16
  95. package/src/util.tsx +51 -141
  96. package/dist/lib/browser/chunk-DJE2HYFV.mjs.map +0 -7
  97. package/dist/lib/node/chunk-FYWGZYJB.cjs.map +0 -7
  98. package/dist/lib/node/chunk-JFDDZI4Y.cjs.map +0 -7
  99. 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
+ };
@@ -0,0 +1,6 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './CreateObjectDialog';
6
+ export * from './CreateSpaceDialog';
@@ -4,7 +4,7 @@
4
4
 
5
5
  import React, { useCallback, useRef, useState } from 'react';
6
6
 
7
- import { type ReactiveObject } from '@dxos/echo-schema';
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, useResolvePlugins } from '@dxos/app-framework';
8
- import { Input, Select, toLocalizedString, useTranslation } from '@dxos/react-ui';
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 { parseSpaceInitPlugin, type SpaceSettingsProps } from '../types';
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 { ClipboardProvider, SpacePanel, type SpacePanelProps } from '@dxos/shell/react';
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={3} />
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
- <ClipboardProvider>
95
+ <Clipboard.Provider>
96
96
  <SpacePanel space={space} hideHeading target={target} createInvitationUrl={createInvitationUrl} />
97
- </ClipboardProvider>
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 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;
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
- {edgeReplicationAvailable && (
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
+ };