@dxos/shell 0.4.10-main.1f36d57 → 0.4.10-main.2b2626f

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 (129) hide show
  1. package/dist/bundle/index-Ba36DtoM.mjs +94 -0
  2. package/dist/bundle/index-Ba36DtoM.mjs.map +1 -0
  3. package/dist/bundle/index-CnChDHv2.mjs +96553 -0
  4. package/dist/bundle/index-CnChDHv2.mjs.map +1 -0
  5. package/dist/bundle/shell.mjs +10 -81099
  6. package/dist/bundle/shell.mjs.map +1 -1
  7. package/dist/bundle/shell.umd.js +156 -88
  8. package/dist/bundle/shell.umd.js.map +1 -1
  9. package/dist/bundle/style.css +1 -1
  10. package/dist/lib/browser/meta.json +1 -1
  11. package/dist/lib/browser/react.mjs +835 -795
  12. package/dist/lib/browser/react.mjs.map +4 -4
  13. package/dist/lib/browser/testing/index.mjs +8 -8
  14. package/dist/lib/browser/testing/index.mjs.map +3 -3
  15. package/dist/types/src/components/AgentConfig.d.ts +3 -0
  16. package/dist/types/src/components/AgentConfig.d.ts.map +1 -0
  17. package/dist/types/src/components/AgentConfig.js +37 -0
  18. package/dist/types/src/components/AgentConfig.js.map +1 -0
  19. package/dist/types/src/components/DeviceList/DeviceList.d.ts +3 -0
  20. package/dist/types/src/components/DeviceList/DeviceList.d.ts.map +1 -0
  21. package/dist/types/src/components/DeviceList/DeviceList.js +29 -0
  22. package/dist/types/src/components/DeviceList/DeviceList.js.map +1 -0
  23. package/dist/types/src/components/DeviceList/DeviceListItem.d.ts +6 -0
  24. package/dist/types/src/components/DeviceList/DeviceListItem.d.ts.map +1 -0
  25. package/dist/types/src/components/DeviceList/DeviceListItem.js +72 -0
  26. package/dist/types/src/components/DeviceList/DeviceListItem.js.map +1 -0
  27. package/dist/types/src/components/DeviceList/DeviceListProps.d.ts +27 -0
  28. package/dist/types/src/components/DeviceList/DeviceListProps.d.ts.map +1 -0
  29. package/dist/types/src/components/DeviceList/DeviceListProps.js +6 -0
  30. package/dist/types/src/components/DeviceList/DeviceListProps.js.map +1 -0
  31. package/dist/types/src/components/DeviceList/index.d.ts +3 -0
  32. package/dist/types/src/components/DeviceList/index.d.ts.map +1 -0
  33. package/dist/types/src/components/DeviceList/index.js +22 -0
  34. package/dist/types/src/components/DeviceList/index.js.map +1 -0
  35. package/dist/types/src/components/EmojiPicker.d.ts +9 -2
  36. package/dist/types/src/components/EmojiPicker.d.ts.map +1 -1
  37. package/dist/types/src/components/EmojiPicker.js +57 -3
  38. package/dist/types/src/components/EmojiPicker.js.map +1 -1
  39. package/dist/types/src/components/HuePicker.d.ts +9 -2
  40. package/dist/types/src/components/HuePicker.d.ts.map +1 -1
  41. package/dist/types/src/components/HuePicker.js +75 -6
  42. package/dist/types/src/components/HuePicker.js.map +1 -1
  43. package/dist/types/src/components/IdentityList/index.d.ts +0 -1
  44. package/dist/types/src/components/IdentityList/index.d.ts.map +1 -1
  45. package/dist/types/src/components/IdentityList/index.js +0 -1
  46. package/dist/types/src/components/IdentityList/index.js.map +1 -1
  47. package/dist/types/src/components/Panel/Action.d.ts +2 -2
  48. package/dist/types/src/components/Viewport/Viewport.d.ts +1 -1
  49. package/dist/types/src/components/index.d.ts +2 -0
  50. package/dist/types/src/components/index.d.ts.map +1 -1
  51. package/dist/types/src/components/index.js +2 -0
  52. package/dist/types/src/components/index.js.map +1 -1
  53. package/dist/types/src/composites/Shell/Shell.d.ts.map +1 -1
  54. package/dist/types/src/composites/Shell/Shell.js +1 -5
  55. package/dist/types/src/composites/Shell/Shell.js.map +1 -1
  56. package/dist/types/src/panels/IdentityPanel/IdentityPanel.d.ts.map +1 -1
  57. package/dist/types/src/panels/IdentityPanel/IdentityPanel.js +80 -40
  58. package/dist/types/src/panels/IdentityPanel/IdentityPanel.js.map +1 -1
  59. package/dist/types/src/panels/IdentityPanel/IdentityPanel.stories.d.ts +0 -3
  60. package/dist/types/src/panels/IdentityPanel/IdentityPanel.stories.d.ts.map +1 -1
  61. package/dist/types/src/panels/IdentityPanel/IdentityPanel.stories.js +1 -43
  62. package/dist/types/src/panels/IdentityPanel/IdentityPanel.stories.js.map +1 -1
  63. package/dist/types/src/panels/IdentityPanel/IdentityPanelProps.d.ts +8 -11
  64. package/dist/types/src/panels/IdentityPanel/IdentityPanelProps.d.ts.map +1 -1
  65. package/dist/types/src/panels/IdentityPanel/identityMachine.d.ts +1 -1
  66. package/dist/types/src/panels/IdentityPanel/identityMachine.d.ts.map +1 -1
  67. package/dist/types/src/panels/IdentityPanel/identityMachine.js +0 -24
  68. package/dist/types/src/panels/IdentityPanel/identityMachine.js.map +1 -1
  69. package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.d.ts +4 -4
  70. package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.d.ts.map +1 -1
  71. package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.js +9 -43
  72. package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.js.map +1 -1
  73. package/dist/types/src/panels/IdentityPanel/steps/ProfileForm.d.ts +1 -1
  74. package/dist/types/src/panels/IdentityPanel/steps/ProfileForm.d.ts.map +1 -1
  75. package/dist/types/src/panels/IdentityPanel/steps/ProfileForm.js +2 -2
  76. package/dist/types/src/panels/IdentityPanel/steps/ProfileForm.js.map +1 -1
  77. package/dist/types/src/panels/IdentityPanel/steps/index.d.ts +0 -2
  78. package/dist/types/src/panels/IdentityPanel/steps/index.d.ts.map +1 -1
  79. package/dist/types/src/panels/IdentityPanel/steps/index.js +0 -2
  80. package/dist/types/src/panels/IdentityPanel/steps/index.js.map +1 -1
  81. package/dist/types/src/steps/ConfirmReset.d.ts.map +1 -1
  82. package/dist/types/src/steps/ConfirmReset.js +4 -3
  83. package/dist/types/src/steps/ConfirmReset.js.map +1 -1
  84. package/dist/types/src/testing/shell-manager.d.ts.map +1 -1
  85. package/dist/types/src/testing/shell-manager.js +8 -8
  86. package/dist/types/src/testing/shell-manager.js.map +1 -1
  87. package/dist/types/src/translations/locales/en-US.d.ts +12 -2
  88. package/dist/types/src/translations/locales/en-US.d.ts.map +1 -1
  89. package/dist/types/src/translations/locales/en-US.js +12 -2
  90. package/dist/types/src/translations/locales/en-US.js.map +1 -1
  91. package/dist/types/src/types/index.js +1 -0
  92. package/dist/types/src/types/index.js.map +1 -1
  93. package/package.json +20 -20
  94. package/src/components/AgentConfig.tsx +66 -0
  95. package/src/components/DeviceList/DeviceList.tsx +54 -0
  96. package/src/components/DeviceList/DeviceListItem.tsx +120 -0
  97. package/src/components/DeviceList/DeviceListProps.ts +33 -0
  98. package/src/components/DeviceList/index.ts +6 -0
  99. package/src/components/EmojiPicker.tsx +101 -4
  100. package/src/components/HuePicker.tsx +99 -5
  101. package/src/components/IdentityList/index.ts +0 -1
  102. package/src/components/index.ts +2 -0
  103. package/src/composites/Shell/Shell.tsx +1 -7
  104. package/src/panels/IdentityPanel/IdentityPanel.stories.tsx +0 -63
  105. package/src/panels/IdentityPanel/IdentityPanel.tsx +123 -34
  106. package/src/panels/IdentityPanel/IdentityPanelProps.ts +10 -14
  107. package/src/panels/IdentityPanel/identityMachine.ts +1 -31
  108. package/src/panels/IdentityPanel/steps/IdentityActionChooser.tsx +23 -76
  109. package/src/panels/IdentityPanel/steps/ProfileForm.tsx +12 -4
  110. package/src/panels/IdentityPanel/steps/index.ts +0 -2
  111. package/src/steps/ConfirmReset.tsx +4 -3
  112. package/src/testing/shell-manager.ts +8 -8
  113. package/src/translations/locales/en-US.ts +13 -2
  114. package/src/types/index.ts +2 -0
  115. package/dist/types/src/components/IdentityList/DeviceList.d.ts +0 -7
  116. package/dist/types/src/components/IdentityList/DeviceList.d.ts.map +0 -1
  117. package/dist/types/src/components/IdentityList/DeviceList.js +0 -34
  118. package/dist/types/src/components/IdentityList/DeviceList.js.map +0 -1
  119. package/dist/types/src/panels/IdentityPanel/steps/AgentForm.d.ts +0 -3
  120. package/dist/types/src/panels/IdentityPanel/steps/AgentForm.d.ts.map +0 -1
  121. package/dist/types/src/panels/IdentityPanel/steps/AgentForm.js +0 -26
  122. package/dist/types/src/panels/IdentityPanel/steps/AgentForm.js.map +0 -1
  123. package/dist/types/src/panels/IdentityPanel/steps/DeviceManager.d.ts +0 -10
  124. package/dist/types/src/panels/IdentityPanel/steps/DeviceManager.d.ts.map +0 -1
  125. package/dist/types/src/panels/IdentityPanel/steps/DeviceManager.js +0 -23
  126. package/dist/types/src/panels/IdentityPanel/steps/DeviceManager.js.map +0 -1
  127. package/src/components/IdentityList/DeviceList.tsx +0 -49
  128. package/src/panels/IdentityPanel/steps/AgentForm.tsx +0 -61
  129. package/src/panels/IdentityPanel/steps/DeviceManager.tsx +0 -41
@@ -1,12 +1,21 @@
1
1
  //
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
- import { ArrowCounterClockwise, CaretDown, Check } from '@phosphor-icons/react';
4
+ import { ArrowCounterClockwise, CaretDown, Check, Palette } from '@phosphor-icons/react';
5
5
  import { useControllableState } from '@radix-ui/react-use-controllable-state';
6
- import React, { type Dispatch, type SetStateAction } from 'react';
6
+ import React, { type Dispatch, type SetStateAction, useRef, useState } from 'react';
7
7
 
8
- import { Button, useTranslation, Tooltip, DropdownMenu, useThemeContext, type ButtonProps } from '@dxos/react-ui';
9
- import { getSize, hueTokenThemes } from '@dxos/react-ui-theme';
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';
10
19
 
11
20
  const HuePreview = ({ hue }: { hue: string }) => {
12
21
  const { tx } = useThemeContext();
@@ -25,7 +34,92 @@ export type HuePickerProps = {
25
34
  onClickClear?: ButtonProps['onClick'];
26
35
  };
27
36
 
28
- export const HuePicker = ({ disabled, hue, onChangeHue, defaultHue, onClickClear }: HuePickerProps) => {
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) => {
29
123
  const { t } = useTranslation('os');
30
124
 
31
125
  const [hueValue, setHueValue] = useControllableState<string>({
@@ -2,6 +2,5 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- export * from './DeviceList';
6
5
  export * from './IdentityListItem';
7
6
  export * from './SpaceMemberList';
@@ -2,11 +2,13 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ export * from './AgentConfig';
5
6
  export * from './Clipboard';
6
7
  export * from './CompoundButton';
7
8
  export * from './EmojiPicker';
8
9
  export * from './HuePicker';
9
10
  export * from './IdentityList';
11
+ export * from './DeviceList';
10
12
  export * from './InvitationList';
11
13
  export * from './Panel';
12
14
  export * from './Viewport';
@@ -81,13 +81,7 @@ 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={
85
- layout === ShellLayout.SHARE_IDENTITY
86
- ? 'manage-device-invitation'
87
- : layout === ShellLayout.EDIT_PROFILE
88
- ? 'manage-profile'
89
- : 'default'
90
- }
84
+ initialDisposition={layout === ShellLayout.SHARE_IDENTITY ? 'manage-device-invitation' : 'default'}
91
85
  />
92
86
  );
93
87
 
@@ -8,7 +8,6 @@ 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';
12
11
  import { Invitation } from '@dxos/react-client/invitations';
13
12
  import { withTheme } from '@dxos/storybook-utils';
14
13
 
@@ -53,58 +52,6 @@ export const IdentityActionChooser = () => {
53
52
  );
54
53
  };
55
54
 
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
-
108
55
  const DeviceInvitationManagerWithState = (extraProps: InvitationManagerProps) => (
109
56
  <StorybookDialog inOverlayLayout>
110
57
  <IdentityPanelImpl
@@ -156,13 +103,3 @@ export const DeviceInvitationManagerCancelled = () =>
156
103
  DeviceInvitationManagerWithState({
157
104
  status: Invitation.State.CANCELLED,
158
105
  });
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,7 +1,8 @@
1
1
  //
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
- import React, { useEffect, useMemo } from 'react';
4
+ import { CopySimple, Plugs, PlugsConnected } from '@phosphor-icons/react';
5
+ import React, { useEffect, useMemo, useState } from 'react';
5
6
 
6
7
  import { generateName } from '@dxos/display-name';
7
8
  import { log } from '@dxos/log';
@@ -9,8 +10,10 @@ import { useClient } from '@dxos/react-client';
9
10
  import { type Identity, useIdentity, useDevices, useHaloInvitations } from '@dxos/react-client/halo';
10
11
  import { useInvitationStatus } from '@dxos/react-client/invitations';
11
12
  import type { CancellableInvitationObservable } from '@dxos/react-client/invitations';
12
- import { Avatar, DensityProvider, useId, useTranslation } from '@dxos/react-ui';
13
- import { keyToFallback } from '@dxos/util';
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';
14
17
 
15
18
  import {
16
19
  type IdentityPanelHeadingProps,
@@ -18,24 +21,122 @@ import {
18
21
  type IdentityPanelProps,
19
22
  } from './IdentityPanelProps';
20
23
  import { useIdentityMachine } from './identityMachine';
21
- import { AgentForm, DeviceManager, IdentityActionChooser, ProfileForm } from './steps';
24
+ import { IdentityActionChooser } from './steps';
22
25
  import { useAgentHandlers } from './useAgentHandlers';
23
- import { Viewport, Heading, CloseButton } from '../../components';
26
+ import {
27
+ Viewport,
28
+ Heading,
29
+ CloseButton,
30
+ EmojiPickerToolbarButton,
31
+ useClipboardContext,
32
+ HuePickerToolbarButton,
33
+ } from '../../components';
24
34
  import { ConfirmReset, InvitationManager } from '../../steps';
25
35
 
26
36
  const viewStyles = 'pbs-1 pbe-3 pli-3';
27
37
 
28
- const IdentityHeading = ({ titleId, title, identity, onDone }: IdentityPanelHeadingProps) => {
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) => {
29
53
  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
+
30
76
  return (
31
77
  <Heading titleId={titleId} title={title} corner={<CloseButton onDone={onDone} />}>
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'>
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'>
37
124
  {identity.profile?.displayName ?? generateName(identity.identityKey.toHex())}
38
125
  </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>
39
140
  </Avatar.Root>
40
141
  </Heading>
41
142
  );
@@ -51,14 +152,14 @@ export const IdentityPanelImpl = (props: IdentityPanelImplProps) => {
51
152
  onJoinNewIdentity,
52
153
  IdentityActionChooser: IdentityActionChooserComponent = IdentityActionChooser,
53
154
  InvitationManager: InvitationManagerComponent = InvitationManager,
155
+ connectionState,
156
+ onChangeConnectionState,
54
157
  onDone,
55
158
  ...rest
56
159
  } = props;
57
160
  const { t } = useTranslation('os');
58
161
  const title = useMemo(() => {
59
162
  switch (activeView) {
60
- case 'agent manager':
61
- return 'Manage Agent';
62
163
  case 'device invitation manager':
63
164
  return t('choose add device label');
64
165
  default:
@@ -70,7 +171,9 @@ export const IdentityPanelImpl = (props: IdentityPanelImplProps) => {
70
171
 
71
172
  return (
72
173
  <DensityProvider density='fine'>
73
- <IdentityHeading {...{ identity, titleId, title, onDone }} />
174
+ <IdentityHeading
175
+ {...{ identity, titleId, title, onDone, onUpdateProfile, connectionState, onChangeConnectionState }}
176
+ />
74
177
  <Viewport.Root activeView={activeView}>
75
178
  <Viewport.Views>
76
179
  <Viewport.View id='identity action chooser' classNames={viewStyles}>
@@ -83,20 +186,6 @@ export const IdentityPanelImpl = (props: IdentityPanelImplProps) => {
83
186
  invitationUrl={rest.createInvitationUrl(rest.invitationCode!)}
84
187
  />
85
188
  </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>
100
189
  <Viewport.View classNames={viewStyles} id='confirm join new identity'>
101
190
  <ConfirmReset
102
191
  active={activeView === 'confirm join new identity'}
@@ -149,6 +238,10 @@ export const IdentityPanel = ({
149
238
  context: { initialDisposition },
150
239
  });
151
240
 
241
+ const { swarm: connectionState } = useNetworkStatus();
242
+
243
+ const handleChangeConnectionState = async (nextState: ConnectionState) => client?.mesh.updateConfig(nextState);
244
+
152
245
  useEffect(() => {
153
246
  const subscription = identityService.subscribe((state) => {
154
247
  log('[state]', state);
@@ -163,16 +256,10 @@ export const IdentityPanel = ({
163
256
  return 'identity action chooser';
164
257
  case identityState.matches('managingDeviceInvitation'):
165
258
  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';
170
259
  case identityState.matches('confirmingResetStorage'):
171
260
  return 'confirm reset storage';
172
261
  case identityState.matches('confirmingJoinNewIdentity'):
173
262
  return 'confirm join new identity';
174
- case [{ managingAgent: 'idle' }, { managingAgent: 'pending' }].some(identityState.matches):
175
- return 'agent manager';
176
263
  default:
177
264
  return 'identity action chooser';
178
265
  }
@@ -192,6 +279,8 @@ export const IdentityPanel = ({
192
279
  titleId,
193
280
  createInvitationUrl,
194
281
  onUpdateProfile,
282
+ connectionState,
283
+ onChangeConnectionState: handleChangeConnectionState,
195
284
  ...agentProps,
196
285
  } satisfies IdentityPanelImplProps;
197
286
 
@@ -7,27 +7,17 @@ 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';
10
11
 
11
12
  import type { IdentityEvent } from './identityMachine';
13
+ import { type AgentFormProps } from '../../components';
12
14
  import type { InvitationManagerProps } from '../../steps';
13
15
 
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
- };
16
+ export type IdentityPanelInitialDisposition = 'default' | 'manage-device-invitation';
24
17
 
25
18
  export type IdentityPanelImplProps = {
26
19
  titleId: string;
27
20
  activeView:
28
- | 'device manager'
29
- | 'agent manager'
30
- | 'update profile form'
31
21
  | 'device invitation manager'
32
22
  | 'identity action chooser'
33
23
  | 'confirm join new identity'
@@ -35,6 +25,8 @@ export type IdentityPanelImplProps = {
35
25
  initialDisposition?: IdentityPanelInitialDisposition;
36
26
  identity: Identity;
37
27
  devices: Device[];
28
+ connectionState?: ConnectionState;
29
+ onChangeConnectionState?: (nextState: ConnectionState) => Promise<void>;
38
30
  onUpdateProfile?: (profile: NonNullable<Identity['profile']>) => Promise<void>;
39
31
  onResetStorage?: () => Promise<void>;
40
32
  onJoinNewIdentity?: () => Promise<void>;
@@ -50,11 +42,15 @@ export type IdentityPanelImplProps = {
50
42
 
51
43
  export type IdentityPanelProps = Partial<Omit<IdentityPanelImplProps, 'send' | 'activeView' | 'identity'>>;
52
44
 
53
- export type IdentityPanelHeadingProps = Pick<IdentityPanelImplProps, 'titleId' | 'identity' | 'onDone'> & {
45
+ export type IdentityPanelHeadingProps = Pick<
46
+ IdentityPanelImplProps,
47
+ 'titleId' | 'identity' | 'onDone' | 'onUpdateProfile' | 'connectionState' | 'onChangeConnectionState'
48
+ > & {
54
49
  title: string;
55
50
  };
56
51
 
57
52
  export type IdentityPanelStepProps = Pick<IdentityPanelImplProps, 'send' | 'onDone' | 'doneActionParent'> & {
58
53
  active?: boolean;
59
54
  agentHostingEnabled?: boolean;
55
+ devices: Device[];
60
56
  };
@@ -21,13 +21,7 @@ type IdentityMachineContext = {
21
21
  };
22
22
 
23
23
  type IdentityChooseActionEvent = {
24
- type:
25
- | 'chooseDevices'
26
- | 'chooseAgent'
27
- | 'chooseProfile'
28
- | 'chooseResetStorage'
29
- | 'chooseJoinNewIdentity'
30
- | 'unchooseAction';
24
+ type: 'chooseResetStorage' | 'chooseJoinNewIdentity' | 'unchooseAction';
31
25
  };
32
26
 
33
27
  type IdentitySelectDeviceInvitationEvent = {
@@ -67,31 +61,11 @@ const identityMachine = createMachine<IdentityMachineContext, IdentityEvent>(
67
61
  unknown: {
68
62
  always: [
69
63
  { cond: 'initiallyManagingDeviceInvitation', target: 'managingDeviceInvitation', actions: 'log' },
70
- { cond: 'initiallyManagingProfile', target: 'managingProfile', actions: 'log' },
71
64
  { target: 'choosingAction', actions: 'log' },
72
65
  ],
73
66
  },
74
67
  choosingAction: {},
75
68
  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
- },
95
69
  confirmingResetStorage: {},
96
70
  confirmingJoinNewIdentity: {},
97
71
  },
@@ -99,18 +73,14 @@ const identityMachine = createMachine<IdentityMachineContext, IdentityEvent>(
99
73
  unchooseAction: { target: '.choosingAction', actions: ['unsetInvitation', 'log'] },
100
74
  deselectInvitation: { target: '.choosingAction', actions: ['unsetInvitation', 'log'] },
101
75
  selectInvitation: { target: '.managingDeviceInvitation', actions: ['setInvitation', 'log'] },
102
- chooseDevices: { target: '.managingDevices', actions: 'log' },
103
- chooseProfile: { target: '.managingProfile', actions: 'log' },
104
76
  chooseJoinNewIdentity: { target: '.confirmingJoinNewIdentity', actions: 'log' },
105
77
  chooseResetStorage: { target: '.confirmingResetStorage', actions: 'log' },
106
- chooseAgent: { target: '.managingAgent', actions: 'log' },
107
78
  },
108
79
  },
109
80
  {
110
81
  guards: {
111
82
  initiallyManagingDeviceInvitation: ({ initialDisposition }, _event) =>
112
83
  initialDisposition === 'manage-device-invitation',
113
- initiallyManagingProfile: ({ initialDisposition }, _event) => initialDisposition === 'manage-profile',
114
84
  },
115
85
  actions: {
116
86
  setIdentity: assign<IdentityMachineContext, IdentityEvent>({