@dxos/shell 0.4.10-main.b9b4602 → 0.4.10-main.c2d5db5

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 (122) hide show
  1. package/dist/bundle/shell.mjs +81827 -10
  2. package/dist/bundle/shell.mjs.map +1 -1
  3. package/dist/bundle/shell.umd.js +89 -152
  4. package/dist/bundle/shell.umd.js.map +1 -1
  5. package/dist/bundle/style.css +1 -1
  6. package/dist/lib/browser/meta.json +1 -1
  7. package/dist/lib/browser/react.mjs +792 -831
  8. package/dist/lib/browser/react.mjs.map +4 -4
  9. package/dist/lib/browser/testing/index.mjs +2 -4
  10. package/dist/lib/browser/testing/index.mjs.map +2 -2
  11. package/dist/types/src/components/EmojiPicker.d.ts +2 -9
  12. package/dist/types/src/components/EmojiPicker.d.ts.map +1 -1
  13. package/dist/types/src/components/EmojiPicker.js +3 -57
  14. package/dist/types/src/components/EmojiPicker.js.map +1 -1
  15. package/dist/types/src/components/HuePicker.d.ts +2 -9
  16. package/dist/types/src/components/HuePicker.d.ts.map +1 -1
  17. package/dist/types/src/components/HuePicker.js +6 -75
  18. package/dist/types/src/components/HuePicker.js.map +1 -1
  19. package/dist/types/src/components/IdentityList/DeviceList.d.ts +7 -0
  20. package/dist/types/src/components/IdentityList/DeviceList.d.ts.map +1 -0
  21. package/dist/types/src/components/IdentityList/DeviceList.js +34 -0
  22. package/dist/types/src/components/IdentityList/DeviceList.js.map +1 -0
  23. package/dist/types/src/components/IdentityList/index.d.ts +1 -0
  24. package/dist/types/src/components/IdentityList/index.d.ts.map +1 -1
  25. package/dist/types/src/components/IdentityList/index.js +1 -0
  26. package/dist/types/src/components/IdentityList/index.js.map +1 -1
  27. package/dist/types/src/components/Panel/Action.d.ts +2 -2
  28. package/dist/types/src/components/Viewport/Viewport.d.ts +1 -1
  29. package/dist/types/src/components/index.d.ts +0 -2
  30. package/dist/types/src/components/index.d.ts.map +1 -1
  31. package/dist/types/src/components/index.js +0 -2
  32. package/dist/types/src/components/index.js.map +1 -1
  33. package/dist/types/src/composites/Shell/Shell.d.ts.map +1 -1
  34. package/dist/types/src/composites/Shell/Shell.js +5 -1
  35. package/dist/types/src/composites/Shell/Shell.js.map +1 -1
  36. package/dist/types/src/panels/IdentityPanel/IdentityPanel.d.ts.map +1 -1
  37. package/dist/types/src/panels/IdentityPanel/IdentityPanel.js +40 -80
  38. package/dist/types/src/panels/IdentityPanel/IdentityPanel.js.map +1 -1
  39. package/dist/types/src/panels/IdentityPanel/IdentityPanel.stories.d.ts +3 -0
  40. package/dist/types/src/panels/IdentityPanel/IdentityPanel.stories.d.ts.map +1 -1
  41. package/dist/types/src/panels/IdentityPanel/IdentityPanel.stories.js +43 -1
  42. package/dist/types/src/panels/IdentityPanel/IdentityPanel.stories.js.map +1 -1
  43. package/dist/types/src/panels/IdentityPanel/IdentityPanelProps.d.ts +11 -8
  44. package/dist/types/src/panels/IdentityPanel/IdentityPanelProps.d.ts.map +1 -1
  45. package/dist/types/src/panels/IdentityPanel/identityMachine.d.ts +1 -1
  46. package/dist/types/src/panels/IdentityPanel/identityMachine.d.ts.map +1 -1
  47. package/dist/types/src/panels/IdentityPanel/identityMachine.js +24 -0
  48. package/dist/types/src/panels/IdentityPanel/identityMachine.js.map +1 -1
  49. package/dist/types/src/panels/IdentityPanel/steps/AgentForm.d.ts +3 -0
  50. package/dist/types/src/panels/IdentityPanel/steps/AgentForm.d.ts.map +1 -0
  51. package/dist/types/src/panels/IdentityPanel/steps/AgentForm.js +26 -0
  52. package/dist/types/src/panels/IdentityPanel/steps/AgentForm.js.map +1 -0
  53. package/dist/types/src/panels/IdentityPanel/steps/DeviceManager.d.ts +10 -0
  54. package/dist/types/src/panels/IdentityPanel/steps/DeviceManager.d.ts.map +1 -0
  55. package/dist/types/src/panels/IdentityPanel/steps/DeviceManager.js +23 -0
  56. package/dist/types/src/panels/IdentityPanel/steps/DeviceManager.js.map +1 -0
  57. package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.d.ts +4 -4
  58. package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.d.ts.map +1 -1
  59. package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.js +43 -9
  60. package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.js.map +1 -1
  61. package/dist/types/src/panels/IdentityPanel/steps/ProfileForm.d.ts +1 -1
  62. package/dist/types/src/panels/IdentityPanel/steps/ProfileForm.d.ts.map +1 -1
  63. package/dist/types/src/panels/IdentityPanel/steps/ProfileForm.js +2 -2
  64. package/dist/types/src/panels/IdentityPanel/steps/ProfileForm.js.map +1 -1
  65. package/dist/types/src/panels/IdentityPanel/steps/index.d.ts +2 -0
  66. package/dist/types/src/panels/IdentityPanel/steps/index.d.ts.map +1 -1
  67. package/dist/types/src/panels/IdentityPanel/steps/index.js +2 -0
  68. package/dist/types/src/panels/IdentityPanel/steps/index.js.map +1 -1
  69. package/dist/types/src/testing/shell-manager.d.ts.map +1 -1
  70. package/dist/types/src/testing/shell-manager.js +2 -4
  71. package/dist/types/src/testing/shell-manager.js.map +1 -1
  72. package/dist/types/src/translations/locales/en-US.d.ts +2 -12
  73. package/dist/types/src/translations/locales/en-US.d.ts.map +1 -1
  74. package/dist/types/src/translations/locales/en-US.js +2 -12
  75. package/dist/types/src/translations/locales/en-US.js.map +1 -1
  76. package/package.json +19 -19
  77. package/src/components/EmojiPicker.tsx +4 -101
  78. package/src/components/HuePicker.tsx +5 -99
  79. package/src/components/IdentityList/DeviceList.tsx +49 -0
  80. package/src/components/IdentityList/index.ts +1 -0
  81. package/src/components/index.ts +0 -2
  82. package/src/composites/Shell/Shell.tsx +7 -1
  83. package/src/panels/IdentityPanel/IdentityPanel.stories.tsx +63 -0
  84. package/src/panels/IdentityPanel/IdentityPanel.tsx +34 -123
  85. package/src/panels/IdentityPanel/IdentityPanelProps.ts +14 -10
  86. package/src/panels/IdentityPanel/identityMachine.ts +31 -1
  87. package/src/panels/IdentityPanel/steps/AgentForm.tsx +61 -0
  88. package/src/panels/IdentityPanel/steps/DeviceManager.tsx +41 -0
  89. package/src/panels/IdentityPanel/steps/IdentityActionChooser.tsx +76 -23
  90. package/src/panels/IdentityPanel/steps/ProfileForm.tsx +4 -12
  91. package/src/panels/IdentityPanel/steps/index.ts +2 -0
  92. package/src/testing/shell-manager.ts +2 -4
  93. package/src/translations/locales/en-US.ts +2 -13
  94. package/dist/bundle/index-D-K0uxrm.mjs +0 -94
  95. package/dist/bundle/index-D-K0uxrm.mjs.map +0 -1
  96. package/dist/bundle/index-DRtLjStG.mjs +0 -96550
  97. package/dist/bundle/index-DRtLjStG.mjs.map +0 -1
  98. package/dist/types/src/components/AgentConfig.d.ts +0 -3
  99. package/dist/types/src/components/AgentConfig.d.ts.map +0 -1
  100. package/dist/types/src/components/AgentConfig.js +0 -37
  101. package/dist/types/src/components/AgentConfig.js.map +0 -1
  102. package/dist/types/src/components/DeviceList/DeviceList.d.ts +0 -3
  103. package/dist/types/src/components/DeviceList/DeviceList.d.ts.map +0 -1
  104. package/dist/types/src/components/DeviceList/DeviceList.js +0 -29
  105. package/dist/types/src/components/DeviceList/DeviceList.js.map +0 -1
  106. package/dist/types/src/components/DeviceList/DeviceListItem.d.ts +0 -6
  107. package/dist/types/src/components/DeviceList/DeviceListItem.d.ts.map +0 -1
  108. package/dist/types/src/components/DeviceList/DeviceListItem.js +0 -72
  109. package/dist/types/src/components/DeviceList/DeviceListItem.js.map +0 -1
  110. package/dist/types/src/components/DeviceList/DeviceListProps.d.ts +0 -27
  111. package/dist/types/src/components/DeviceList/DeviceListProps.d.ts.map +0 -1
  112. package/dist/types/src/components/DeviceList/DeviceListProps.js +0 -6
  113. package/dist/types/src/components/DeviceList/DeviceListProps.js.map +0 -1
  114. package/dist/types/src/components/DeviceList/index.d.ts +0 -3
  115. package/dist/types/src/components/DeviceList/index.d.ts.map +0 -1
  116. package/dist/types/src/components/DeviceList/index.js +0 -22
  117. package/dist/types/src/components/DeviceList/index.js.map +0 -1
  118. package/src/components/AgentConfig.tsx +0 -66
  119. package/src/components/DeviceList/DeviceList.tsx +0 -54
  120. package/src/components/DeviceList/DeviceListItem.tsx +0 -120
  121. package/src/components/DeviceList/DeviceListProps.ts +0 -33
  122. package/src/components/DeviceList/index.ts +0 -6
@@ -1,21 +1,12 @@
1
1
  //
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
- import { ArrowCounterClockwise, CaretDown, Check, Palette } from '@phosphor-icons/react';
4
+ import { ArrowCounterClockwise, CaretDown, Check } from '@phosphor-icons/react';
5
5
  import { useControllableState } from '@radix-ui/react-use-controllable-state';
6
- import React, { type Dispatch, type SetStateAction, useRef, useState } from 'react';
6
+ import React, { type Dispatch, type SetStateAction } from 'react';
7
7
 
8
- import {
9
- Button,
10
- useTranslation,
11
- Tooltip,
12
- DropdownMenu,
13
- useThemeContext,
14
- type ButtonProps,
15
- Toolbar,
16
- type ThemedClassName,
17
- } from '@dxos/react-ui';
18
- import { getSize, hueTokenThemes, mx } from '@dxos/react-ui-theme';
8
+ import { Button, useTranslation, Tooltip, DropdownMenu, useThemeContext, type ButtonProps } from '@dxos/react-ui';
9
+ import { getSize, hueTokenThemes } from '@dxos/react-ui-theme';
19
10
 
20
11
  const HuePreview = ({ hue }: { hue: string }) => {
21
12
  const { tx } = useThemeContext();
@@ -34,92 +25,7 @@ export type HuePickerProps = {
34
25
  onClickClear?: ButtonProps['onClick'];
35
26
  };
36
27
 
37
- /**
38
- * A toolbar button for picking hue. Use only in `role=toolbar` elements. Unable to unset the value.
39
- */
40
- export const HuePickerToolbarButton = ({
41
- disabled,
42
- hue,
43
- onChangeHue,
44
- classNames,
45
- defaultHue,
46
- }: ThemedClassName<Omit<HuePickerProps, 'onClickClear'>>) => {
47
- const { t } = useTranslation('os');
48
-
49
- const [hueValue, setHueValue] = useControllableState<string>({
50
- prop: hue,
51
- onChange: onChangeHue,
52
- defaultProp: defaultHue,
53
- });
54
-
55
- const [huePickerOpen, setHuePickerOpen] = useState<boolean>(false);
56
-
57
- const suppressNextTooltip = useRef<boolean>(false);
58
- const [triggerTooltipOpen, setTriggerTooltipOpen] = useState(false);
59
-
60
- return (
61
- <Tooltip.Root
62
- open={triggerTooltipOpen}
63
- onOpenChange={(nextOpen) => {
64
- if (suppressNextTooltip.current) {
65
- setTriggerTooltipOpen(false);
66
- suppressNextTooltip.current = false;
67
- } else {
68
- setTriggerTooltipOpen(nextOpen);
69
- }
70
- }}
71
- >
72
- <DropdownMenu.Root
73
- modal={false}
74
- open={huePickerOpen}
75
- onOpenChange={(nextOpen) => {
76
- setHuePickerOpen(nextOpen);
77
- suppressNextTooltip.current = true;
78
- }}
79
- >
80
- <Tooltip.Trigger asChild>
81
- <DropdownMenu.Trigger asChild>
82
- <Toolbar.Button classNames={mx('gap-2 plb-1', classNames)} disabled={disabled}>
83
- <span className='sr-only'>{t('select hue label')}</span>
84
- <Palette className={getSize(5)} />
85
- </Toolbar.Button>
86
- </DropdownMenu.Trigger>
87
- </Tooltip.Trigger>
88
- <Tooltip.Portal>
89
- <Tooltip.Content side='bottom' classNames='z-50'>
90
- {t('select hue label')}
91
- <Tooltip.Arrow />
92
- </Tooltip.Content>
93
- </Tooltip.Portal>
94
- <DropdownMenu.Content side='bottom'>
95
- <DropdownMenu.Viewport>
96
- {Object.keys(hueTokenThemes).map((hue) => {
97
- return (
98
- <DropdownMenu.CheckboxItem
99
- key={hue}
100
- checked={hue === hueValue}
101
- onCheckedChange={() => setHueValue(hue)}
102
- >
103
- <HuePreview hue={hue} />
104
- <span className='grow'>{t(`${hue} label`)}</span>
105
- <DropdownMenu.ItemIndicator>
106
- <Check />
107
- </DropdownMenu.ItemIndicator>
108
- </DropdownMenu.CheckboxItem>
109
- );
110
- })}
111
- </DropdownMenu.Viewport>
112
- <DropdownMenu.Arrow />
113
- </DropdownMenu.Content>
114
- </DropdownMenu.Root>
115
- </Tooltip.Root>
116
- );
117
- };
118
-
119
- /**
120
- * A button for picking hue alongside a button for unsetting it.
121
- */
122
- export const HuePickerBlock = ({ disabled, hue, onChangeHue, defaultHue, onClickClear }: HuePickerProps) => {
28
+ export const HuePicker = ({ disabled, hue, onChangeHue, defaultHue, onClickClear }: HuePickerProps) => {
123
29
  const { t } = useTranslation('os');
124
30
 
125
31
  const [hueValue, setHueValue] = useControllableState<string>({
@@ -0,0 +1,49 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { generateName } from '@dxos/display-name';
8
+ import { type Device, DeviceType } from '@dxos/react-client/halo';
9
+ import { List, useTranslation } from '@dxos/react-ui';
10
+ import { descriptionText, mx } from '@dxos/react-ui-theme';
11
+
12
+ import { IdentityListItem } from './IdentityListItem';
13
+
14
+ export interface DeviceListProps {
15
+ devices: Device[];
16
+ onSelect?: (device: Device) => void;
17
+ }
18
+
19
+ export const DeviceList = ({ devices, onSelect }: DeviceListProps) => {
20
+ const { t } = useTranslation('os');
21
+ return devices.length > 0 ? (
22
+ <List classNames='flex flex-col gap-2'>
23
+ {devices.map((device) => {
24
+ // TODO(wittjosiah): Render this better. Consider communicating these as apps rather than devices.
25
+ const deviceType = device.profile?.type || DeviceType.UNKNOWN;
26
+ const displayName = device.profile?.label
27
+ ? `${device.profile.label} (${DeviceType[deviceType]})`
28
+ : generateName(device.deviceKey.toHex());
29
+
30
+ const identity = {
31
+ identityKey: device.deviceKey,
32
+ profile: { displayName },
33
+ };
34
+ return (
35
+ <IdentityListItem
36
+ key={device.deviceKey.toHex()}
37
+ identity={identity}
38
+ presence={device.presence}
39
+ onClick={onSelect && (() => onSelect(device))}
40
+ />
41
+ );
42
+ })}
43
+ </List>
44
+ ) : (
45
+ <div role='none' className='grow flex items-center p-2'>
46
+ <p className={mx(descriptionText, 'text-center is-full')}>{t('empty device list message')}</p>
47
+ </div>
48
+ );
49
+ };
@@ -2,5 +2,6 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ export * from './DeviceList';
5
6
  export * from './IdentityListItem';
6
7
  export * from './SpaceMemberList';
@@ -2,13 +2,11 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- export * from './AgentConfig';
6
5
  export * from './Clipboard';
7
6
  export * from './CompoundButton';
8
7
  export * from './EmojiPicker';
9
8
  export * from './HuePicker';
10
9
  export * from './IdentityList';
11
- export * from './DeviceList';
12
10
  export * from './InvitationList';
13
11
  export * from './Panel';
14
12
  export * from './Viewport';
@@ -81,7 +81,13 @@ export const Shell = ({ runtime, origin }: { runtime: ShellRuntime; origin: stri
81
81
  await runtime.setAppContext({ display: ShellDisplay.NONE });
82
82
  runtime.setLayout({ layout: ShellLayout.DEFAULT });
83
83
  }}
84
- initialDisposition={layout === ShellLayout.SHARE_IDENTITY ? 'manage-device-invitation' : 'default'}
84
+ initialDisposition={
85
+ layout === ShellLayout.SHARE_IDENTITY
86
+ ? 'manage-device-invitation'
87
+ : layout === ShellLayout.EDIT_PROFILE
88
+ ? 'manage-profile'
89
+ : 'default'
90
+ }
85
91
  />
86
92
  );
87
93
 
@@ -8,6 +8,7 @@ import React from 'react';
8
8
 
9
9
  import { faker } from '@dxos/random';
10
10
  import { PublicKey } from '@dxos/react-client';
11
+ import { Device, DeviceKind } from '@dxos/react-client/halo';
11
12
  import { Invitation } from '@dxos/react-client/invitations';
12
13
  import { withTheme } from '@dxos/storybook-utils';
13
14
 
@@ -52,6 +53,58 @@ export const IdentityActionChooser = () => {
52
53
  );
53
54
  };
54
55
 
56
+ export const UpdateProfileForm = () => {
57
+ return (
58
+ <StorybookDialog inOverlayLayout>
59
+ <IdentityPanelImpl
60
+ {...noOpProps}
61
+ activeView='update profile form'
62
+ IdentityActionChooser={IdentityActionChooserImpl}
63
+ />
64
+ </StorybookDialog>
65
+ );
66
+ };
67
+
68
+ export const DeviceManager = () => {
69
+ return (
70
+ <StorybookDialog inOverlayLayout>
71
+ <IdentityPanelImpl
72
+ {...{
73
+ ...noOpProps,
74
+ // TODO(nf): update
75
+ devices: [
76
+ {
77
+ deviceKey: PublicKey.random(),
78
+ kind: DeviceKind.CURRENT,
79
+ presence: Device.PresenceState.ONLINE,
80
+ },
81
+ {
82
+ deviceKey: PublicKey.random(),
83
+ kind: DeviceKind.TRUSTED,
84
+ presence: Device.PresenceState.ONLINE,
85
+ },
86
+ {
87
+ deviceKey: PublicKey.random(),
88
+ kind: DeviceKind.TRUSTED,
89
+ presence: Device.PresenceState.OFFLINE,
90
+ },
91
+ ],
92
+ }}
93
+ activeView='device manager'
94
+ IdentityActionChooser={IdentityActionChooserImpl}
95
+ />
96
+ </StorybookDialog>
97
+ );
98
+ };
99
+
100
+ export const DeviceManagerEmpty = () => {
101
+ return (
102
+ <StorybookDialog inOverlayLayout>
103
+ <IdentityPanelImpl {...noOpProps} activeView='device manager' IdentityActionChooser={IdentityActionChooserImpl} />
104
+ </StorybookDialog>
105
+ );
106
+ };
107
+
55
108
  const DeviceInvitationManagerWithState = (extraProps: InvitationManagerProps) => (
56
109
  <StorybookDialog inOverlayLayout>
57
110
  <IdentityPanelImpl
@@ -103,3 +156,13 @@ export const DeviceInvitationManagerCancelled = () =>
103
156
  DeviceInvitationManagerWithState({
104
157
  status: Invitation.State.CANCELLED,
105
158
  });
159
+
160
+ // export const IdentityActionChooser = {
161
+ // decorators: [ClientDecorator()],
162
+ // args: { activeView: 'identity action chooser' },
163
+ // };
164
+
165
+ // export const DeviceManager = {
166
+ // decorators: [ClientDecorator()],
167
+ // args: { activeView: 'device manager' },
168
+ // };
@@ -1,8 +1,7 @@
1
1
  //
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
- import { CopySimple, Plugs, PlugsConnected } from '@phosphor-icons/react';
5
- import React, { useEffect, useMemo, useState } from 'react';
4
+ import React, { useEffect, useMemo } from 'react';
6
5
 
7
6
  import { generateName } from '@dxos/display-name';
8
7
  import { log } from '@dxos/log';
@@ -10,10 +9,8 @@ import { useClient } from '@dxos/react-client';
10
9
  import { type Identity, useIdentity, useDevices, useHaloInvitations } from '@dxos/react-client/halo';
11
10
  import { useInvitationStatus } from '@dxos/react-client/invitations';
12
11
  import type { CancellableInvitationObservable } from '@dxos/react-client/invitations';
13
- import { useNetworkStatus, ConnectionState } from '@dxos/react-client/mesh';
14
- import { Avatar, DensityProvider, Input, Toolbar, Tooltip, useId, useTranslation } from '@dxos/react-ui';
15
- import { getSize } from '@dxos/react-ui-theme';
16
- import { hexToEmoji, hexToHue, keyToFallback } from '@dxos/util';
12
+ import { Avatar, DensityProvider, useId, useTranslation } from '@dxos/react-ui';
13
+ import { keyToFallback } from '@dxos/util';
17
14
 
18
15
  import {
19
16
  type IdentityPanelHeadingProps,
@@ -21,122 +18,24 @@ import {
21
18
  type IdentityPanelProps,
22
19
  } from './IdentityPanelProps';
23
20
  import { useIdentityMachine } from './identityMachine';
24
- import { IdentityActionChooser } from './steps';
21
+ import { AgentForm, DeviceManager, IdentityActionChooser, ProfileForm } from './steps';
25
22
  import { useAgentHandlers } from './useAgentHandlers';
26
- import {
27
- Viewport,
28
- Heading,
29
- CloseButton,
30
- EmojiPickerToolbarButton,
31
- useClipboardContext,
32
- HuePickerToolbarButton,
33
- } from '../../components';
23
+ import { Viewport, Heading, CloseButton } from '../../components';
34
24
  import { ConfirmReset, InvitationManager } from '../../steps';
35
25
 
36
26
  const viewStyles = 'pbs-1 pbe-3 pli-3';
37
27
 
38
- // TODO(thure): Factor out?
39
- const getHueValue = (identity?: Identity) =>
40
- identity?.profile?.data?.hue || hexToHue(identity?.identityKey.toHex() ?? '0');
41
- const getEmojiValue = (identity?: Identity) =>
42
- identity?.profile?.data?.emoji || hexToEmoji(identity?.identityKey.toHex() ?? '0');
43
-
44
- const IdentityHeading = ({
45
- titleId,
46
- title,
47
- identity,
48
- onDone,
49
- onUpdateProfile,
50
- connectionState,
51
- onChangeConnectionState,
52
- }: IdentityPanelHeadingProps) => {
28
+ const IdentityHeading = ({ titleId, title, identity, onDone }: IdentityPanelHeadingProps) => {
53
29
  const fallbackValue = keyToFallback(identity.identityKey);
54
- const { t } = useTranslation('os');
55
- const [displayName, setDisplayName] = useState(identity.profile?.displayName ?? '');
56
- const [emoji, setEmoji] = useState<string>(getEmojiValue(identity));
57
- const [hue, setHue] = useState<string>(getHueValue(identity));
58
- const { textValue, setTextValue } = useClipboardContext();
59
- const identityHex = identity.identityKey.toHex();
60
- const copied = textValue === identityHex;
61
-
62
- const handleUpdateProfile = async () => {
63
- const nextProfile = {
64
- ...(displayName && { displayName }),
65
- ...((emoji || hue) && { data: { ...(emoji && { emoji }), ...(hue && { hue }) } }),
66
- };
67
- return onUpdateProfile?.(nextProfile);
68
- };
69
-
70
- useEffect(() => {
71
- void handleUpdateProfile();
72
- }, [emoji, hue]);
73
-
74
- const isConnected = connectionState === ConnectionState.ONLINE;
75
-
76
30
  return (
77
31
  <Heading titleId={titleId} title={title} corner={<CloseButton onDone={onDone} />}>
78
- <Avatar.Root size={16} variant='circle' status={isConnected ? 'active' : 'error'} hue={hue || fallbackValue.hue}>
79
- <Toolbar.Root classNames='grid grid-cols-[1fr_var(--rail-action)_min-content_var(--rail-action)_1fr] items-center gap-2'>
80
- <Tooltip.Root>
81
- <Tooltip.Trigger asChild>
82
- <Toolbar.Button
83
- classNames='bs-[--rail-action] is-[--rail-action] justify-self-end'
84
- onClick={() =>
85
- onChangeConnectionState?.(isConnected ? ConnectionState.OFFLINE : ConnectionState.ONLINE)
86
- }
87
- >
88
- <span className='sr-only'>{t(isConnected ? 'disconnect label' : 'connect label')}</span>
89
- {isConnected ? <PlugsConnected className={getSize(5)} /> : <Plugs className={getSize(5)} />}
90
- </Toolbar.Button>
91
- </Tooltip.Trigger>
92
- <Tooltip.Portal>
93
- <Tooltip.Content side='bottom' classNames='z-50'>
94
- {t(isConnected ? 'disconnect label' : 'connect label')}
95
- <Tooltip.Arrow />
96
- </Tooltip.Content>
97
- </Tooltip.Portal>
98
- </Tooltip.Root>
99
- <EmojiPickerToolbarButton emoji={emoji} onChangeEmoji={setEmoji} classNames='bs-[--rail-action]' />
100
- <Avatar.Frame classNames='relative z-[2] -mli-4 chromatic-ignore'>
101
- <Avatar.Fallback text={emoji || fallbackValue.emoji} />
102
- </Avatar.Frame>
103
- <HuePickerToolbarButton hue={hue} onChangeHue={setHue} classNames='bs-[--rail-action]' />
104
- <Tooltip.Root>
105
- <Tooltip.Trigger asChild>
106
- <Toolbar.Button
107
- classNames='bs-[--rail-action] justify-self-start'
108
- data-testid='update-profile-form-copy-key'
109
- onClick={() => setTextValue(identityHex)}
110
- >
111
- <span className='sr-only'>{t(copied ? 'copy success label' : 'copy self public key label')}</span>
112
- <CopySimple className={getSize(5)} />
113
- </Toolbar.Button>
114
- </Tooltip.Trigger>
115
- <Tooltip.Portal>
116
- <Tooltip.Content side='bottom' classNames='z-50'>
117
- {t(copied ? 'copy success label' : 'copy self public key label')}
118
- <Tooltip.Arrow />
119
- </Tooltip.Content>
120
- </Tooltip.Portal>
121
- </Tooltip.Root>
122
- </Toolbar.Root>
123
- <Avatar.Label classNames='sr-only' data-testid='identityHeading.displayName'>
32
+ <Avatar.Root size={12} variant='circle' status='active' hue={identity.profile?.data?.hue || fallbackValue.hue}>
33
+ <Avatar.Frame classNames='block mbs-4 mbe-2 mli-auto chromatic-ignore'>
34
+ <Avatar.Fallback text={identity.profile?.data?.emoji || fallbackValue.emoji} />
35
+ </Avatar.Frame>
36
+ <Avatar.Label classNames='block text-center font-light text-xl' data-testid='identityHeading.displayName'>
124
37
  {identity.profile?.displayName ?? generateName(identity.identityKey.toHex())}
125
38
  </Avatar.Label>
126
- <Input.Root>
127
- <Input.Label srOnly>{t('display name input label')}</Input.Label>
128
- <Input.TextInput
129
- variant='subdued'
130
- data-testid='display-name-input'
131
- placeholder={t('display name input placeholder')}
132
- classNames='mbs-2 text-center font-light text-xl'
133
- value={displayName}
134
- onChange={({ target: { value } }) => {
135
- setDisplayName(value);
136
- }}
137
- onBlur={handleUpdateProfile}
138
- />
139
- </Input.Root>
140
39
  </Avatar.Root>
141
40
  </Heading>
142
41
  );
@@ -152,14 +51,14 @@ export const IdentityPanelImpl = (props: IdentityPanelImplProps) => {
152
51
  onJoinNewIdentity,
153
52
  IdentityActionChooser: IdentityActionChooserComponent = IdentityActionChooser,
154
53
  InvitationManager: InvitationManagerComponent = InvitationManager,
155
- connectionState,
156
- onChangeConnectionState,
157
54
  onDone,
158
55
  ...rest
159
56
  } = props;
160
57
  const { t } = useTranslation('os');
161
58
  const title = useMemo(() => {
162
59
  switch (activeView) {
60
+ case 'agent manager':
61
+ return 'Manage Agent';
163
62
  case 'device invitation manager':
164
63
  return t('choose add device label');
165
64
  default:
@@ -171,9 +70,7 @@ export const IdentityPanelImpl = (props: IdentityPanelImplProps) => {
171
70
 
172
71
  return (
173
72
  <DensityProvider density='fine'>
174
- <IdentityHeading
175
- {...{ identity, titleId, title, onDone, onUpdateProfile, connectionState, onChangeConnectionState }}
176
- />
73
+ <IdentityHeading {...{ identity, titleId, title, onDone }} />
177
74
  <Viewport.Root activeView={activeView}>
178
75
  <Viewport.Views>
179
76
  <Viewport.View id='identity action chooser' classNames={viewStyles}>
@@ -186,6 +83,20 @@ export const IdentityPanelImpl = (props: IdentityPanelImplProps) => {
186
83
  invitationUrl={rest.createInvitationUrl(rest.invitationCode!)}
187
84
  />
188
85
  </Viewport.View>
86
+ <Viewport.View id='device manager' classNames={viewStyles}>
87
+ <DeviceManager active={activeView === 'device manager'} {...rest} />
88
+ </Viewport.View>
89
+ <Viewport.View classNames={viewStyles} id='update profile form'>
90
+ <ProfileForm
91
+ send={rest.send}
92
+ active={activeView === 'update profile form'}
93
+ identity={identity}
94
+ onUpdateProfile={onUpdateProfile}
95
+ />
96
+ </Viewport.View>
97
+ <Viewport.View classNames={viewStyles} id='agent manager'>
98
+ <AgentForm send={rest.send} active={activeView === 'agent manager'} {...rest} />
99
+ </Viewport.View>
189
100
  <Viewport.View classNames={viewStyles} id='confirm join new identity'>
190
101
  <ConfirmReset
191
102
  active={activeView === 'confirm join new identity'}
@@ -238,10 +149,6 @@ export const IdentityPanel = ({
238
149
  context: { initialDisposition },
239
150
  });
240
151
 
241
- const { swarm: connectionState } = useNetworkStatus();
242
-
243
- const handleChangeConnectionState = async (nextState: ConnectionState) => client?.mesh.updateConfig(nextState);
244
-
245
152
  useEffect(() => {
246
153
  const subscription = identityService.subscribe((state) => {
247
154
  log('[state]', state);
@@ -256,10 +163,16 @@ export const IdentityPanel = ({
256
163
  return 'identity action chooser';
257
164
  case identityState.matches('managingDeviceInvitation'):
258
165
  return 'device invitation manager';
166
+ case identityState.matches('managingDevices'):
167
+ return 'device manager';
168
+ case [{ managingProfile: 'idle' }, { managingProfile: 'pending' }].some(identityState.matches):
169
+ return 'update profile form';
259
170
  case identityState.matches('confirmingResetStorage'):
260
171
  return 'confirm reset storage';
261
172
  case identityState.matches('confirmingJoinNewIdentity'):
262
173
  return 'confirm join new identity';
174
+ case [{ managingAgent: 'idle' }, { managingAgent: 'pending' }].some(identityState.matches):
175
+ return 'agent manager';
263
176
  default:
264
177
  return 'identity action chooser';
265
178
  }
@@ -279,8 +192,6 @@ export const IdentityPanel = ({
279
192
  titleId,
280
193
  createInvitationUrl,
281
194
  onUpdateProfile,
282
- connectionState,
283
- onChangeConnectionState: handleChangeConnectionState,
284
195
  ...agentProps,
285
196
  } satisfies IdentityPanelImplProps;
286
197
 
@@ -7,17 +7,27 @@ import type { Event, SingleOrArray } from 'xstate';
7
7
 
8
8
  import type { Device, Identity } from '@dxos/react-client/halo';
9
9
  import type { InvitationStatus } from '@dxos/react-client/invitations';
10
- import type { ConnectionState } from '@dxos/react-client/mesh';
11
10
 
12
11
  import type { IdentityEvent } from './identityMachine';
13
- import { type AgentFormProps } from '../../components';
14
12
  import type { InvitationManagerProps } from '../../steps';
15
13
 
16
- export type IdentityPanelInitialDisposition = 'default' | 'manage-device-invitation';
14
+ export type IdentityPanelInitialDisposition = 'default' | 'manage-device-invitation' | 'manage-profile';
15
+
16
+ export type AgentFormProps = {
17
+ onAgentCreate?: () => Promise<void>;
18
+ onAgentDestroy?: () => Promise<void>;
19
+ agentStatus?: string;
20
+ agentActive?: boolean;
21
+ agentProviderDisabled?: boolean;
22
+ validationMessage?: string;
23
+ };
17
24
 
18
25
  export type IdentityPanelImplProps = {
19
26
  titleId: string;
20
27
  activeView:
28
+ | 'device manager'
29
+ | 'agent manager'
30
+ | 'update profile form'
21
31
  | 'device invitation manager'
22
32
  | 'identity action chooser'
23
33
  | 'confirm join new identity'
@@ -25,8 +35,6 @@ export type IdentityPanelImplProps = {
25
35
  initialDisposition?: IdentityPanelInitialDisposition;
26
36
  identity: Identity;
27
37
  devices: Device[];
28
- connectionState?: ConnectionState;
29
- onChangeConnectionState?: (nextState: ConnectionState) => Promise<void>;
30
38
  onUpdateProfile?: (profile: NonNullable<Identity['profile']>) => Promise<void>;
31
39
  onResetStorage?: () => Promise<void>;
32
40
  onJoinNewIdentity?: () => Promise<void>;
@@ -42,15 +50,11 @@ export type IdentityPanelImplProps = {
42
50
 
43
51
  export type IdentityPanelProps = Partial<Omit<IdentityPanelImplProps, 'send' | 'activeView' | 'identity'>>;
44
52
 
45
- export type IdentityPanelHeadingProps = Pick<
46
- IdentityPanelImplProps,
47
- 'titleId' | 'identity' | 'onDone' | 'onUpdateProfile' | 'connectionState' | 'onChangeConnectionState'
48
- > & {
53
+ export type IdentityPanelHeadingProps = Pick<IdentityPanelImplProps, 'titleId' | 'identity' | 'onDone'> & {
49
54
  title: string;
50
55
  };
51
56
 
52
57
  export type IdentityPanelStepProps = Pick<IdentityPanelImplProps, 'send' | 'onDone' | 'doneActionParent'> & {
53
58
  active?: boolean;
54
59
  agentHostingEnabled?: boolean;
55
- devices: Device[];
56
60
  };
@@ -21,7 +21,13 @@ type IdentityMachineContext = {
21
21
  };
22
22
 
23
23
  type IdentityChooseActionEvent = {
24
- type: 'chooseResetStorage' | 'chooseJoinNewIdentity' | 'unchooseAction';
24
+ type:
25
+ | 'chooseDevices'
26
+ | 'chooseAgent'
27
+ | 'chooseProfile'
28
+ | 'chooseResetStorage'
29
+ | 'chooseJoinNewIdentity'
30
+ | 'unchooseAction';
25
31
  };
26
32
 
27
33
  type IdentitySelectDeviceInvitationEvent = {
@@ -61,11 +67,31 @@ const identityMachine = createMachine<IdentityMachineContext, IdentityEvent>(
61
67
  unknown: {
62
68
  always: [
63
69
  { cond: 'initiallyManagingDeviceInvitation', target: 'managingDeviceInvitation', actions: 'log' },
70
+ { cond: 'initiallyManagingProfile', target: 'managingProfile', actions: 'log' },
64
71
  { target: 'choosingAction', actions: 'log' },
65
72
  ],
66
73
  },
67
74
  choosingAction: {},
68
75
  managingDeviceInvitation: {},
76
+ managingAgent: {
77
+ initial: 'idle',
78
+ states: {
79
+ idle: {},
80
+ pending: {},
81
+ },
82
+ },
83
+ managingDevices: {},
84
+ managingProfile: {
85
+ initial: 'idle',
86
+ states: {
87
+ idle: {},
88
+ pending: {},
89
+ },
90
+ on: {
91
+ setIdentity: { target: 'choosingAction', actions: ['setIdentity', 'log'] },
92
+ updateProfile: { target: '.pending', actions: 'log' },
93
+ },
94
+ },
69
95
  confirmingResetStorage: {},
70
96
  confirmingJoinNewIdentity: {},
71
97
  },
@@ -73,14 +99,18 @@ const identityMachine = createMachine<IdentityMachineContext, IdentityEvent>(
73
99
  unchooseAction: { target: '.choosingAction', actions: ['unsetInvitation', 'log'] },
74
100
  deselectInvitation: { target: '.choosingAction', actions: ['unsetInvitation', 'log'] },
75
101
  selectInvitation: { target: '.managingDeviceInvitation', actions: ['setInvitation', 'log'] },
102
+ chooseDevices: { target: '.managingDevices', actions: 'log' },
103
+ chooseProfile: { target: '.managingProfile', actions: 'log' },
76
104
  chooseJoinNewIdentity: { target: '.confirmingJoinNewIdentity', actions: 'log' },
77
105
  chooseResetStorage: { target: '.confirmingResetStorage', actions: 'log' },
106
+ chooseAgent: { target: '.managingAgent', actions: 'log' },
78
107
  },
79
108
  },
80
109
  {
81
110
  guards: {
82
111
  initiallyManagingDeviceInvitation: ({ initialDisposition }, _event) =>
83
112
  initialDisposition === 'manage-device-invitation',
113
+ initiallyManagingProfile: ({ initialDisposition }, _event) => initialDisposition === 'manage-profile',
84
114
  },
85
115
  actions: {
86
116
  setIdentity: assign<IdentityMachineContext, IdentityEvent>({