@dxos/shell 0.3.11-main.fbbdc2a → 0.3.11-main.fc97a54

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 (137) hide show
  1. package/dist/bundle/shell.mjs +38268 -0
  2. package/dist/bundle/shell.mjs.map +1 -0
  3. package/dist/bundle/shell.umd.js +216 -0
  4. package/dist/bundle/shell.umd.js.map +1 -0
  5. package/dist/bundle/style.css +1 -0
  6. package/dist/lib/browser/meta.json +1 -1
  7. package/dist/lib/browser/react.mjs +755 -438
  8. package/dist/lib/browser/react.mjs.map +4 -4
  9. package/dist/lib/browser/testing/index.mjs +35 -14
  10. package/dist/lib/browser/testing/index.mjs.map +3 -3
  11. package/dist/types/src/components/IdentityList/DeviceList.d.ts.map +1 -1
  12. package/dist/types/src/components/IdentityList/DeviceList.js +5 -3
  13. package/dist/types/src/components/IdentityList/DeviceList.js.map +1 -1
  14. package/dist/types/src/components/IdentityList/SpaceMemberList.js +1 -1
  15. package/dist/types/src/components/IdentityList/SpaceMemberList.js.map +1 -1
  16. package/dist/types/src/components/Panel/Action.d.ts.map +1 -1
  17. package/dist/types/src/components/Panel/Action.js +3 -18
  18. package/dist/types/src/components/Panel/Action.js.map +1 -1
  19. package/dist/types/src/composites/Shell/Shell.d.ts.map +1 -1
  20. package/dist/types/src/composites/Shell/Shell.js +15 -5
  21. package/dist/types/src/composites/Shell/Shell.js.map +1 -1
  22. package/dist/types/src/panels/IdentityPanel/IdentityPanel.d.ts.map +1 -1
  23. package/dist/types/src/panels/IdentityPanel/IdentityPanel.js +24 -7
  24. package/dist/types/src/panels/IdentityPanel/IdentityPanel.js.map +1 -1
  25. package/dist/types/src/panels/IdentityPanel/IdentityPanel.stories.d.ts +6 -0
  26. package/dist/types/src/panels/IdentityPanel/IdentityPanel.stories.d.ts.map +1 -1
  27. package/dist/types/src/panels/IdentityPanel/IdentityPanel.stories.js +29 -6
  28. package/dist/types/src/panels/IdentityPanel/IdentityPanel.stories.js.map +1 -1
  29. package/dist/types/src/panels/IdentityPanel/IdentityPanelProps.d.ts +19 -8
  30. package/dist/types/src/panels/IdentityPanel/IdentityPanelProps.d.ts.map +1 -1
  31. package/dist/types/src/panels/IdentityPanel/identityMachine.d.ts +1 -1
  32. package/dist/types/src/panels/IdentityPanel/identityMachine.d.ts.map +1 -1
  33. package/dist/types/src/panels/IdentityPanel/identityMachine.js +12 -3
  34. package/dist/types/src/panels/IdentityPanel/identityMachine.js.map +1 -1
  35. package/dist/types/src/panels/IdentityPanel/steps/AgentForm.d.ts +3 -0
  36. package/dist/types/src/panels/IdentityPanel/steps/AgentForm.d.ts.map +1 -0
  37. package/dist/types/src/panels/IdentityPanel/steps/AgentForm.js +26 -0
  38. package/dist/types/src/panels/IdentityPanel/steps/AgentForm.js.map +1 -0
  39. package/dist/types/src/panels/IdentityPanel/steps/DeviceManager.d.ts +10 -0
  40. package/dist/types/src/panels/IdentityPanel/steps/DeviceManager.d.ts.map +1 -0
  41. package/dist/types/src/panels/IdentityPanel/steps/DeviceManager.js +23 -0
  42. package/dist/types/src/panels/IdentityPanel/steps/DeviceManager.js.map +1 -0
  43. package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.d.ts.map +1 -1
  44. package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.js +9 -1
  45. package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.js.map +1 -1
  46. package/dist/types/src/panels/IdentityPanel/steps/ProfileForm.d.ts +0 -1
  47. package/dist/types/src/panels/IdentityPanel/steps/ProfileForm.d.ts.map +1 -1
  48. package/dist/types/src/panels/IdentityPanel/steps/ProfileForm.js +2 -3
  49. package/dist/types/src/panels/IdentityPanel/steps/ProfileForm.js.map +1 -1
  50. package/dist/types/src/panels/IdentityPanel/steps/SignOutChooser.d.ts +16 -0
  51. package/dist/types/src/panels/IdentityPanel/steps/SignOutChooser.d.ts.map +1 -0
  52. package/dist/types/src/panels/IdentityPanel/steps/SignOutChooser.js +40 -0
  53. package/dist/types/src/panels/IdentityPanel/steps/SignOutChooser.js.map +1 -0
  54. package/dist/types/src/panels/IdentityPanel/steps/index.d.ts +3 -0
  55. package/dist/types/src/panels/IdentityPanel/steps/index.d.ts.map +1 -1
  56. package/dist/types/src/panels/IdentityPanel/steps/index.js +3 -0
  57. package/dist/types/src/panels/IdentityPanel/steps/index.js.map +1 -1
  58. package/dist/types/src/panels/IdentityPanel/useAgentHandlers.d.ts +15 -0
  59. package/dist/types/src/panels/IdentityPanel/useAgentHandlers.d.ts.map +1 -0
  60. package/dist/types/src/panels/IdentityPanel/useAgentHandlers.js +78 -0
  61. package/dist/types/src/panels/IdentityPanel/useAgentHandlers.js.map +1 -0
  62. package/dist/types/src/panels/JoinPanel/JoinPanel.js +4 -3
  63. package/dist/types/src/panels/JoinPanel/JoinPanel.js.map +1 -1
  64. package/dist/types/src/panels/JoinPanel/joinMachine.d.ts +1 -1
  65. package/dist/types/src/panels/JoinPanel/joinMachine.d.ts.map +1 -1
  66. package/dist/types/src/panels/JoinPanel/joinMachine.js +15 -18
  67. package/dist/types/src/panels/JoinPanel/joinMachine.js.map +1 -1
  68. package/dist/types/src/panels/JoinPanel/steps/AdditionMethodChooser.js +3 -3
  69. package/dist/types/src/panels/JoinPanel/steps/AdditionMethodChooser.js.map +1 -1
  70. package/dist/types/src/panels/JoinPanel/steps/InvitationInput.d.ts.map +1 -1
  71. package/dist/types/src/panels/JoinPanel/steps/InvitationInput.js +13 -6
  72. package/dist/types/src/panels/JoinPanel/steps/InvitationInput.js.map +1 -1
  73. package/dist/types/src/panels/JoinPanel/steps/InvitationRescuer.js.map +1 -1
  74. package/dist/types/src/panels/JoinPanel/steps/ResetIdentity.js +1 -1
  75. package/dist/types/src/panels/JoinPanel/steps/ResetIdentity.js.map +1 -1
  76. package/dist/types/src/panels/SpacePanel/SpacePanel.d.ts.map +1 -1
  77. package/dist/types/src/panels/SpacePanel/SpacePanel.js +5 -3
  78. package/dist/types/src/panels/SpacePanel/SpacePanel.js.map +1 -1
  79. package/dist/types/src/panels/SpacePanel/SpacePanel.stories.d.ts.map +1 -1
  80. package/dist/types/src/panels/SpacePanel/SpacePanel.stories.js +2 -2
  81. package/dist/types/src/panels/SpacePanel/SpacePanel.stories.js.map +1 -1
  82. package/dist/types/src/panels/SpacePanel/SpacePanelProps.d.ts +1 -0
  83. package/dist/types/src/panels/SpacePanel/SpacePanelProps.d.ts.map +1 -1
  84. package/dist/types/src/panels/SpacePanel/steps/SpaceManager.d.ts +1 -0
  85. package/dist/types/src/panels/SpacePanel/steps/SpaceManager.d.ts.map +1 -1
  86. package/dist/types/src/panels/SpacePanel/steps/SpaceManager.js +3 -1
  87. package/dist/types/src/panels/SpacePanel/steps/SpaceManager.js.map +1 -1
  88. package/dist/types/src/playwright/invitations-manager.d.ts +1 -0
  89. package/dist/types/src/playwright/invitations-manager.d.ts.map +1 -1
  90. package/dist/types/src/playwright/invitations-manager.js +4 -1
  91. package/dist/types/src/playwright/invitations-manager.js.map +1 -1
  92. package/dist/types/src/playwright/invitations.spec.js +15 -0
  93. package/dist/types/src/playwright/invitations.spec.js.map +1 -1
  94. package/dist/types/src/stories/Invitations.e2e-stories.js +3 -3
  95. package/dist/types/src/stories/Invitations.e2e-stories.js.map +1 -1
  96. package/dist/types/src/styles/invitationStatusStyles.d.ts.map +1 -1
  97. package/dist/types/src/styles/invitationStatusStyles.js.map +1 -1
  98. package/dist/types/src/testing/shell-manager.d.ts +5 -4
  99. package/dist/types/src/testing/shell-manager.d.ts.map +1 -1
  100. package/dist/types/src/testing/shell-manager.js +36 -12
  101. package/dist/types/src/testing/shell-manager.js.map +1 -1
  102. package/dist/types/src/translations/locales/en-US.d.ts +6 -0
  103. package/dist/types/src/translations/locales/en-US.d.ts.map +1 -1
  104. package/dist/types/src/translations/locales/en-US.js +7 -1
  105. package/dist/types/src/translations/locales/en-US.js.map +1 -1
  106. package/package.json +22 -21
  107. package/src/components/IdentityList/DeviceList.tsx +7 -5
  108. package/src/components/IdentityList/SpaceMemberList.tsx +1 -1
  109. package/src/components/Panel/Action.tsx +4 -19
  110. package/src/composites/Shell/Shell.tsx +16 -3
  111. package/src/panels/IdentityPanel/IdentityPanel.stories.tsx +45 -3
  112. package/src/panels/IdentityPanel/IdentityPanel.tsx +35 -8
  113. package/src/panels/IdentityPanel/IdentityPanelProps.ts +27 -8
  114. package/src/panels/IdentityPanel/identityMachine.ts +13 -4
  115. package/src/panels/IdentityPanel/steps/AgentForm.tsx +61 -0
  116. package/src/panels/IdentityPanel/steps/DeviceManager.tsx +41 -0
  117. package/src/panels/IdentityPanel/steps/IdentityActionChooser.tsx +13 -4
  118. package/src/panels/IdentityPanel/steps/ProfileForm.tsx +1 -1
  119. package/src/panels/IdentityPanel/steps/SignOutChooser.tsx +84 -0
  120. package/src/panels/IdentityPanel/steps/index.ts +3 -0
  121. package/src/panels/IdentityPanel/useAgentHandlers.ts +88 -0
  122. package/src/panels/JoinPanel/JoinPanel.tsx +2 -2
  123. package/src/panels/JoinPanel/joinMachine.ts +16 -19
  124. package/src/panels/JoinPanel/steps/AdditionMethodChooser.tsx +3 -3
  125. package/src/panels/JoinPanel/steps/InvitationInput.tsx +7 -1
  126. package/src/panels/JoinPanel/steps/InvitationRescuer.tsx +2 -2
  127. package/src/panels/JoinPanel/steps/ResetIdentity.tsx +1 -1
  128. package/src/panels/SpacePanel/SpacePanel.stories.tsx +2 -1
  129. package/src/panels/SpacePanel/SpacePanel.tsx +4 -1
  130. package/src/panels/SpacePanel/SpacePanelProps.ts +1 -0
  131. package/src/panels/SpacePanel/steps/SpaceManager.tsx +4 -1
  132. package/src/playwright/invitations-manager.ts +5 -1
  133. package/src/playwright/invitations.spec.ts +19 -0
  134. package/src/stories/Invitations.e2e-stories.tsx +2 -2
  135. package/src/styles/invitationStatusStyles.ts +6 -6
  136. package/src/testing/shell-manager.ts +40 -13
  137. package/src/translations/locales/en-US.ts +7 -1
@@ -5,7 +5,7 @@
5
5
  import React, { useEffect, useState } from 'react';
6
6
 
7
7
  import { log } from '@dxos/log';
8
- import { type LayoutRequest, ShellDisplay, ShellLayout, type ShellRuntime } from '@dxos/react-client';
8
+ import { type LayoutRequest, ShellDisplay, ShellLayout, type ShellRuntime, useClient } from '@dxos/react-client';
9
9
  import { useSpace } from '@dxos/react-client/echo';
10
10
 
11
11
  import { IdentityDialog } from '../IdentityDialog';
@@ -13,12 +13,14 @@ import { JoinDialog } from '../JoinDialog';
13
13
  import { SpaceDialog } from '../SpaceDialog';
14
14
 
15
15
  export const Shell = ({ runtime, origin }: { runtime: ShellRuntime; origin: string }) => {
16
- const [{ layout, invitationCode, spaceKey }, setLayout] = useState<LayoutRequest>({
16
+ const [{ layout, invitationCode, spaceKey, target }, setLayout] = useState<LayoutRequest>({
17
17
  layout: runtime.layout,
18
18
  invitationCode: runtime.invitationCode,
19
19
  spaceKey: runtime.spaceKey,
20
+ target: runtime.target,
20
21
  });
21
22
 
23
+ const client = useClient();
22
24
  const space = useSpace(spaceKey);
23
25
 
24
26
  useEffect(() => {
@@ -58,6 +60,11 @@ export const Shell = ({ runtime, origin }: { runtime: ShellRuntime; origin: stri
58
60
  return (
59
61
  <IdentityDialog
60
62
  createInvitationUrl={(invitationCode) => `${origin}?deviceInvitationCode=${invitationCode}`}
63
+ onResetDevice={async () => {
64
+ await client.reset();
65
+ await runtime.setAppContext({ display: ShellDisplay.NONE, reload: true });
66
+ }}
67
+ onJoinNewIdentity={() => runtime.setLayout({ layout: ShellLayout.INITIALIZE_IDENTITY })}
61
68
  onDone={async () => {
62
69
  await runtime.setAppContext({ display: ShellDisplay.NONE });
63
70
  runtime.setLayout({ layout: ShellLayout.DEFAULT });
@@ -69,6 +76,7 @@ export const Shell = ({ runtime, origin }: { runtime: ShellRuntime; origin: stri
69
76
  return space ? (
70
77
  <SpaceDialog
71
78
  space={space}
79
+ target={target}
72
80
  createInvitationUrl={(invitationCode) => `${origin}?spaceInvitationCode=${invitationCode}`}
73
81
  onDone={async () => {
74
82
  await runtime.setAppContext({ display: ShellDisplay.NONE });
@@ -82,7 +90,12 @@ export const Shell = ({ runtime, origin }: { runtime: ShellRuntime; origin: stri
82
90
  <JoinDialog
83
91
  initialInvitationCode={invitationCode}
84
92
  onDone={async (result) => {
85
- await runtime.setAppContext({ display: ShellDisplay.NONE, spaceKey: result?.spaceKey ?? undefined });
93
+ const target = result?.target ?? undefined;
94
+ await runtime.setAppContext({
95
+ display: ShellDisplay.NONE,
96
+ spaceKey: result?.spaceKey ?? undefined,
97
+ target,
98
+ });
86
99
  runtime.setLayout({ layout: ShellLayout.DEFAULT });
87
100
  }}
88
101
  onExit={async () => {
@@ -4,10 +4,11 @@
4
4
 
5
5
  import '@dxosTheme';
6
6
 
7
- import { faker } from '@faker-js/faker';
8
7
  import React from 'react';
9
8
 
9
+ import { faker } from '@dxos/random';
10
10
  import { PublicKey } from '@dxos/react-client';
11
+ import { 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
 
@@ -16,6 +17,7 @@ import type { IdentityPanelImplProps } from './IdentityPanelProps';
16
17
  import { IdentityActionChooserImpl } from './steps';
17
18
  import { StorybookDialog } from '../../components/StorybookDialog';
18
19
  import { InvitationManager, type InvitationManagerProps } from '../../steps';
20
+ import { osTranslations } from '../../translations';
19
21
 
20
22
  faker.seed(1234);
21
23
 
@@ -30,12 +32,13 @@ const noOpProps: IdentityPanelImplProps = {
30
32
  displayName: faker.person.firstName(),
31
33
  },
32
34
  },
35
+ devices: [],
33
36
  };
34
37
 
35
38
  export default {
36
- title: 'Panels/Identity',
39
+ title: 'react-shell/IdentityPanel',
37
40
  decorators: [withTheme],
38
- parameters: { chromatic: { disableSnapshot: false } },
41
+ parameters: { translations: [osTranslations], chromatic: { disableSnapshot: false } },
39
42
  };
40
43
 
41
44
  export const IdentityActionChooser = () => {
@@ -50,6 +53,45 @@ export const IdentityActionChooser = () => {
50
53
  );
51
54
  };
52
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
+ devices: [
75
+ { deviceKey: PublicKey.random(), kind: DeviceKind.CURRENT },
76
+ { deviceKey: PublicKey.random(), kind: DeviceKind.TRUSTED },
77
+ { deviceKey: PublicKey.random(), kind: DeviceKind.TRUSTED },
78
+ ],
79
+ }}
80
+ activeView='device manager'
81
+ IdentityActionChooser={IdentityActionChooserImpl}
82
+ />
83
+ </StorybookDialog>
84
+ );
85
+ };
86
+
87
+ export const DeviceManagerEmpty = () => {
88
+ return (
89
+ <StorybookDialog inOverlayLayout>
90
+ <IdentityPanelImpl {...noOpProps} activeView='device manager' IdentityActionChooser={IdentityActionChooserImpl} />
91
+ </StorybookDialog>
92
+ );
93
+ };
94
+
53
95
  const DeviceInvitationManagerWithState = (extraProps: InvitationManagerProps) => (
54
96
  <StorybookDialog inOverlayLayout>
55
97
  <IdentityPanelImpl
@@ -6,7 +6,7 @@ import React, { useEffect, useMemo } from 'react';
6
6
  import { generateName } from '@dxos/display-name';
7
7
  import { log } from '@dxos/log';
8
8
  import { useClient } from '@dxos/react-client';
9
- import { type Identity, useIdentity } from '@dxos/react-client/halo';
9
+ import { type Identity, useIdentity, useDevices, useHaloInvitations } from '@dxos/react-client/halo';
10
10
  import { useInvitationStatus } from '@dxos/react-client/invitations';
11
11
  import type { CancellableInvitationObservable } from '@dxos/react-client/invitations';
12
12
  import { Avatar, DensityProvider, useId, useJdenticonHref, useTranslation } from '@dxos/react-ui';
@@ -17,7 +17,8 @@ import {
17
17
  type IdentityPanelProps,
18
18
  } from './IdentityPanelProps';
19
19
  import { useIdentityMachine } from './identityMachine';
20
- import { IdentityActionChooser, ProfileForm } from './steps';
20
+ import { AgentForm, DeviceManager, IdentityActionChooser, ProfileForm, SignOutChooser } from './steps';
21
+ import { useAgentHandlers } from './useAgentHandlers';
21
22
  import { Viewport, Heading, CloseButton } from '../../components';
22
23
  import { InvitationManager } from '../../steps';
23
24
 
@@ -31,7 +32,7 @@ const IdentityHeading = ({ titleId, title, identity, onDone }: IdentityPanelHead
31
32
  <Avatar.Frame classNames='block mbs-4 mbe-2 mli-auto chromatic-ignore'>
32
33
  <Avatar.Fallback href={fallbackHref} />
33
34
  </Avatar.Frame>
34
- <Avatar.Label classNames='block text-center font-light text-xl'>
35
+ <Avatar.Label classNames='block text-center font-light text-xl' data-testid='identityHeading.displayName'>
35
36
  {identity.profile?.displayName ?? generateName(identity.identityKey.toHex())}
36
37
  </Avatar.Label>
37
38
  </Avatar.Root>
@@ -45,6 +46,8 @@ export const IdentityPanelImpl = (props: IdentityPanelImplProps) => {
45
46
  titleId,
46
47
  activeView,
47
48
  onUpdateProfile,
49
+ onResetDevice,
50
+ onJoinNewIdentity,
48
51
  IdentityActionChooser: IdentityActionChooserComponent = IdentityActionChooser,
49
52
  InvitationManager: InvitationManagerComponent = InvitationManager,
50
53
  onDone,
@@ -53,13 +56,15 @@ export const IdentityPanelImpl = (props: IdentityPanelImplProps) => {
53
56
  const { t } = useTranslation('os');
54
57
  const title = useMemo(() => {
55
58
  switch (activeView) {
56
- case 'device manager':
59
+ case 'agent manager':
60
+ return 'Manage Agent';
57
61
  case 'device invitation manager':
58
- return t('choose devices label');
62
+ return t('choose add device label');
59
63
  default:
60
64
  return t('identity heading');
61
65
  }
62
66
  }, [activeView, t]);
67
+
63
68
  return (
64
69
  <DensityProvider density='fine'>
65
70
  <IdentityHeading {...{ identity, titleId, title, onDone }} />
@@ -75,6 +80,9 @@ export const IdentityPanelImpl = (props: IdentityPanelImplProps) => {
75
80
  invitationUrl={rest.createInvitationUrl(rest.invitationCode!)}
76
81
  />
77
82
  </Viewport.View>
83
+ <Viewport.View id='device manager' classNames={viewStyles}>
84
+ <DeviceManager active={activeView === 'device manager'} {...rest} />
85
+ </Viewport.View>
78
86
  <Viewport.View classNames={viewStyles} id='update profile form'>
79
87
  <ProfileForm
80
88
  send={rest.send}
@@ -83,7 +91,17 @@ export const IdentityPanelImpl = (props: IdentityPanelImplProps) => {
83
91
  onUpdateProfile={onUpdateProfile}
84
92
  />
85
93
  </Viewport.View>
86
- {/* <Viewport.View id='signing out'></Viewport.View> */}
94
+ <Viewport.View classNames={viewStyles} id='signing out'>
95
+ <SignOutChooser
96
+ send={rest.send}
97
+ active={activeView === 'signing out'}
98
+ onResetDevice={onResetDevice}
99
+ onJoinNewIdentity={onJoinNewIdentity}
100
+ />
101
+ </Viewport.View>
102
+ <Viewport.View classNames={viewStyles} id='agent manager'>
103
+ <AgentForm send={rest.send} active={activeView === 'agent manager'} {...rest} />
104
+ </Viewport.View>
87
105
  </Viewport.Views>
88
106
  </Viewport.Root>
89
107
  </DensityProvider>
@@ -105,7 +123,10 @@ export const IdentityPanel = ({
105
123
  }: IdentityPanelProps) => {
106
124
  const titleId = useId('identityPanel__heading', propsTitleId);
107
125
  const client = useClient();
126
+ const devices = useDevices();
108
127
  const identity = useIdentity();
128
+ const invitations = useHaloInvitations();
129
+ const agentProps = useAgentHandlers({ client, identity, invitations });
109
130
  if (!identity) {
110
131
  console.error('IdentityPanel rendered with no active identity.');
111
132
  return null;
@@ -126,10 +147,14 @@ export const IdentityPanel = ({
126
147
  return 'identity action chooser';
127
148
  case identityState.matches('managingDeviceInvitation'):
128
149
  return 'device invitation manager';
150
+ case identityState.matches('managingDevices'):
151
+ return 'device manager';
129
152
  case [{ managingProfile: 'idle' }, { managingProfile: 'pending' }].some(identityState.matches):
130
153
  return 'update profile form';
131
- // case identityState.matches('signingOut'):
132
- // return 'identity exit';
154
+ case identityState.matches('signingOut'):
155
+ return 'signing out';
156
+ case [{ managingAgent: 'idle' }, { managingAgent: 'pending' }].some(identityState.matches):
157
+ return 'agent manager';
133
158
  default:
134
159
  return 'identity action chooser';
135
160
  }
@@ -143,11 +168,13 @@ export const IdentityPanel = ({
143
168
  const implProps = {
144
169
  ...props,
145
170
  identity,
171
+ devices,
146
172
  activeView,
147
173
  send: identitySend,
148
174
  titleId,
149
175
  createInvitationUrl,
150
176
  onUpdateProfile,
177
+ ...agentProps,
151
178
  } satisfies IdentityPanelImplProps;
152
179
 
153
180
  return identityState.context.invitation ? (
@@ -2,20 +2,38 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { type cloneElement } from 'react';
6
- import { type Event, type SingleOrArray } from 'xstate';
5
+ import type { cloneElement } from 'react';
6
+ import type { Event, SingleOrArray } from 'xstate';
7
7
 
8
- import type { Identity } from '@dxos/react-client/halo';
9
- import { type InvitationStatus } from '@dxos/react-client/invitations';
8
+ import type { Device, Identity } from '@dxos/react-client/halo';
9
+ import type { InvitationStatus } from '@dxos/react-client/invitations';
10
10
 
11
- import { type IdentityEvent } from './identityMachine';
12
- import { type InvitationManagerProps } from '../../steps';
11
+ import type { IdentityEvent } from './identityMachine';
12
+ import type { InvitationManagerProps } from '../../steps';
13
+
14
+ export type AgentFormProps = {
15
+ onAgentCreate?: () => Promise<void>;
16
+ onAgentDestroy?: () => Promise<void>;
17
+ agentStatus?: string;
18
+ agentActive?: boolean;
19
+ agentProviderDisabled?: boolean;
20
+ validationMessage?: string;
21
+ };
13
22
 
14
23
  export type IdentityPanelImplProps = {
15
24
  titleId: string;
16
- activeView: 'device manager' | 'update profile form' | 'device invitation manager' | 'identity action chooser';
25
+ activeView:
26
+ | 'device manager'
27
+ | 'agent manager'
28
+ | 'update profile form'
29
+ | 'device invitation manager'
30
+ | 'identity action chooser'
31
+ | 'signing out';
17
32
  identity: Identity;
33
+ devices: Device[];
18
34
  onUpdateProfile?: (profile: NonNullable<Identity['profile']>) => Promise<void>;
35
+ onResetDevice?: () => Promise<void>;
36
+ onJoinNewIdentity?: () => void;
19
37
  createInvitationUrl: (invitationCode: string) => string;
20
38
  send?: (event: SingleOrArray<Event<IdentityEvent>>) => void;
21
39
  onDone?: () => void;
@@ -23,7 +41,8 @@ export type IdentityPanelImplProps = {
23
41
  invitationUrl?: string;
24
42
  IdentityActionChooser?: React.FC<IdentityPanelStepProps>;
25
43
  InvitationManager?: React.FC<InvitationManagerProps>;
26
- } & Partial<InvitationStatus>;
44
+ } & Partial<InvitationStatus> &
45
+ Partial<AgentFormProps>;
27
46
 
28
47
  export type IdentityPanelProps = Partial<Omit<IdentityPanelImplProps, 'send' | 'activeView' | 'identity'>>;
29
48
 
@@ -19,7 +19,7 @@ type IdentityMachineContext = {
19
19
  };
20
20
 
21
21
  type IdentityChooseActionEvent = {
22
- type: 'chooseDevices' | 'chooseProfile' | /* 'chooseSignOut' | */ 'unchooseAction';
22
+ type: 'chooseDevices' | 'chooseAgent' | 'chooseProfile' | 'chooseSignOut' | 'sigingOut' | 'unchooseAction';
23
23
  };
24
24
 
25
25
  type IdentitySelectDeviceInvitationEvent = {
@@ -58,6 +58,14 @@ const identityMachine = createMachine<IdentityMachineContext, IdentityEvent>(
58
58
  states: {
59
59
  choosingAction: {},
60
60
  managingDeviceInvitation: {},
61
+ managingAgent: {
62
+ initial: 'idle',
63
+ states: {
64
+ idle: {},
65
+ pending: {},
66
+ },
67
+ },
68
+ managingDevices: {},
61
69
  managingProfile: {
62
70
  initial: 'idle',
63
71
  states: {
@@ -69,15 +77,16 @@ const identityMachine = createMachine<IdentityMachineContext, IdentityEvent>(
69
77
  updateProfile: { target: '.pending', actions: 'log' },
70
78
  },
71
79
  },
72
- // signingOut: {},
80
+ signingOut: {},
73
81
  },
74
82
  on: {
75
83
  unchooseAction: { target: '.choosingAction', actions: ['unsetInvitation', 'log'] },
76
- chooseDevices: { target: '.managingDeviceInvitation', actions: ['unsetInvitation', 'log'] },
77
84
  deselectInvitation: { target: '.choosingAction', actions: ['unsetInvitation', 'log'] },
78
85
  selectInvitation: { target: '.managingDeviceInvitation', actions: ['setInvitation', 'log'] },
86
+ chooseDevices: { target: '.managingDevices', actions: 'log' },
79
87
  chooseProfile: { target: '.managingProfile', actions: 'log' },
80
- // chooseSignOut: { target: '.signingOut', actions: 'log' },
88
+ chooseSignOut: { target: '.signingOut', actions: 'log' },
89
+ chooseAgent: { target: '.managingAgent', actions: 'log' },
81
90
  },
82
91
  },
83
92
  {
@@ -0,0 +1,61 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { useTranslation } from '@dxos/react-ui';
8
+
9
+ import { Action, Actions, StepHeading, Input } from '../../../components';
10
+ import { type AgentFormProps, type IdentityPanelStepProps } from '../IdentityPanelProps';
11
+
12
+ export const AgentForm = (props: IdentityPanelStepProps & AgentFormProps) => {
13
+ const {
14
+ active,
15
+ onAgentCreate,
16
+ onAgentDestroy,
17
+ send,
18
+ validationMessage,
19
+ agentStatus,
20
+ agentActive,
21
+ agentProviderDisabled,
22
+ } = props;
23
+ const disabled = !active;
24
+ const { t } = useTranslation('os');
25
+ return (
26
+ <>
27
+ <div role='none' className='grow flex flex-col justify-center'>
28
+ <Input
29
+ {...{ validationMessage }}
30
+ label={<StepHeading>Agent Status</StepHeading>}
31
+ disabled={disabled}
32
+ data-testid='agent-status'
33
+ value={agentStatus}
34
+ />
35
+ </div>
36
+ <Actions>
37
+ <Action
38
+ disabled={agentActive || agentProviderDisabled}
39
+ onClick={() => onAgentCreate?.()}
40
+ data-testid={'create-agent'}
41
+ >
42
+ Create Agent
43
+ </Action>
44
+ <Action
45
+ disabled={!agentActive || agentProviderDisabled}
46
+ onClick={() => onAgentDestroy?.()}
47
+ data-testid={'destroy-agent'}
48
+ >
49
+ Destroy Agent
50
+ </Action>
51
+ <Action
52
+ disabled={disabled}
53
+ onClick={() => send?.({ type: 'unchooseAction' })}
54
+ data-testid={'update-profile-form-back'}
55
+ >
56
+ {t('back label')}
57
+ </Action>
58
+ </Actions>
59
+ </>
60
+ );
61
+ };
@@ -0,0 +1,41 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+ import { type Event, type SingleOrArray } from 'xstate';
7
+
8
+ import { type Device } from '@dxos/react-client/halo';
9
+ import { useTranslation } from '@dxos/react-ui';
10
+
11
+ import { Action, Actions, DeviceList } from '../../../components';
12
+ import type { IdentityPanelStepProps } from '../IdentityPanelProps';
13
+ import type { IdentityEvent } from '../identityMachine';
14
+
15
+ export interface DeviceManagerProps extends Omit<IdentityPanelStepProps, 'send'> {
16
+ send?: (event: SingleOrArray<Event<IdentityEvent>>) => void;
17
+ devices: Device[];
18
+ }
19
+
20
+ export const DeviceManager = ({ devices, active, send }: DeviceManagerProps) => {
21
+ const { t } = useTranslation('os');
22
+ const disabled = !active;
23
+
24
+ return (
25
+ <>
26
+ <div role='none' className='grow'>
27
+ <DeviceList devices={devices} />
28
+ </div>
29
+ <Actions>
30
+ <Action
31
+ variant='ghost'
32
+ disabled={disabled}
33
+ onClick={() => send?.({ type: 'unchooseAction' })}
34
+ data-testid={'manage-devices-back'}
35
+ >
36
+ {t('back label')}
37
+ </Action>
38
+ </Actions>
39
+ </>
40
+ );
41
+ };
@@ -2,7 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { CaretRight, Plus, Power, UserGear } from '@phosphor-icons/react';
5
+ import { CaretRight, Devices, Plus, Power, UserGear, HardDrive } from '@phosphor-icons/react';
6
6
  import React, { useCallback } from 'react';
7
7
 
8
8
  import { useClient } from '@dxos/react-client';
@@ -63,6 +63,11 @@ export const IdentityActionChooserImpl = ({
63
63
  <div role='none' className='grow flex flex-col gap-1'>
64
64
  <DensityProvider density='coarse'>
65
65
  <div className='grow justify-center flex flex-col gap-1'>
66
+ <Action data-testid='manage-agent' onClick={() => send?.({ type: 'chooseAgent' })} classNames='plb-4'>
67
+ <HardDrive className={getSize(6)} />
68
+ <span className='grow mli-3'>Manage Agent</span>
69
+ <CaretRight weight='bold' className={getSize(4)} />
70
+ </Action>
66
71
  <Action
67
72
  disabled={!active}
68
73
  data-testid='devices-panel.create-invitation'
@@ -70,6 +75,11 @@ export const IdentityActionChooserImpl = ({
70
75
  classNames='plb-4'
71
76
  >
72
77
  <Plus className={getSize(6)} />
78
+ <span className='grow mli-3'>{t('choose add device label')}</span>
79
+ <CaretRight weight='bold' className={getSize(4)} />
80
+ </Action>
81
+ <Action data-testid='manage-devices' onClick={() => send?.({ type: 'chooseDevices' })} classNames='plb-4'>
82
+ <Devices className={getSize(6)} />
73
83
  <span className='grow mli-3'>{t('choose devices label')}</span>
74
84
  <CaretRight weight='bold' className={getSize(4)} />
75
85
  </Action>
@@ -79,9 +89,8 @@ export const IdentityActionChooserImpl = ({
79
89
  <CaretRight weight='bold' className={getSize(4)} />
80
90
  </Action>
81
91
  <Action
82
- disabled
83
- data-testid='sign-out'
84
- onClick={() => {} /* send({ type: 'chooseSignOut' }) */}
92
+ data-testid='devices-panel.sign-out'
93
+ onClick={() => send?.({ type: 'chooseSignOut' })}
85
94
  classNames='plb-4'
86
95
  >
87
96
  <Power className={getSize(6)} />
@@ -37,7 +37,7 @@ export const ProfileForm = (props: ProfileFormProps) => {
37
37
  };
38
38
 
39
39
  // TODO(zhenyasav): impl shouldn't need send()
40
- export const ProfileFormImpl = (props: ProfileFormImplProps) => {
40
+ const ProfileFormImpl = (props: ProfileFormImplProps) => {
41
41
  const { active, profile, send, onUpdateProfile, validationMessage } = props;
42
42
  const disabled = !active;
43
43
  const { t } = useTranslation('os');
@@ -0,0 +1,84 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { Warning } from '@phosphor-icons/react';
6
+ import React from 'react';
7
+ import { type Event, type SingleOrArray } from 'xstate';
8
+
9
+ import { Message, useTranslation } from '@dxos/react-ui';
10
+ import { getSize, mx } from '@dxos/react-ui-theme';
11
+
12
+ import { Action, Actions } from '../../../components';
13
+ import { type IdentityPanelStepProps } from '../IdentityPanelProps';
14
+ import { type IdentityEvent } from '../identityMachine';
15
+
16
+ export interface SignOutChooserProps extends Omit<IdentityPanelStepProps, 'send'> {
17
+ send?: (event: SingleOrArray<Event<IdentityEvent>>) => void;
18
+ onResetDevice?: () => Promise<void>;
19
+ onJoinNewIdentity?: () => void;
20
+ }
21
+
22
+ export const SignOutChooser = (props: SignOutChooserProps) => {
23
+ const { t } = useTranslation('os');
24
+ const onResetDevice = () => {
25
+ if (window.confirm(t('reset device confirm message'))) {
26
+ void props.onResetDevice?.();
27
+ }
28
+ };
29
+ return (
30
+ <SignOutChooserImpl
31
+ disabled={!props.active}
32
+ onBack={() => props.send?.({ type: 'unchooseAction' })}
33
+ onResetDevice={onResetDevice}
34
+ onJoinNewIdentity={props.onJoinNewIdentity}
35
+ />
36
+ );
37
+ };
38
+
39
+ export type SignOutChooserImplProps = {
40
+ disabled?: boolean;
41
+ onResetDevice?: () => void;
42
+ onJoinNewIdentity?: () => void;
43
+ onBack?: () => void;
44
+ };
45
+
46
+ const SignOutChooserImpl = ({ disabled, onResetDevice, onJoinNewIdentity, onBack }: SignOutChooserImplProps) => {
47
+ const { t } = useTranslation('os');
48
+ return (
49
+ <>
50
+ <div role='none' className='grow flex flex-col justify-center'>
51
+ <Message.Root valence='error'>
52
+ <Message.Title>
53
+ <Warning className={mx(getSize(6), 'inline mie-2')} />
54
+ {t('sign out chooser title')}
55
+ </Message.Title>
56
+ <Message.Body>{t('sign out chooser message')}</Message.Body>
57
+ </Message.Root>
58
+ </div>
59
+ <Actions>
60
+ <Action disabled={disabled} onClick={onBack}>
61
+ {t('back label')}
62
+ </Action>
63
+ <Action
64
+ variant='primary'
65
+ data-testid='sign-out.join-new-identity'
66
+ classNames='!bg-error-500 hover:!bg-error-550'
67
+ disabled={disabled}
68
+ onClick={onJoinNewIdentity}
69
+ >
70
+ {t('join new identity label')}
71
+ </Action>
72
+ <Action
73
+ variant='primary'
74
+ data-testid='sign-out.reset-device'
75
+ classNames='!bg-error-500 hover:!bg-error-550'
76
+ disabled={disabled}
77
+ onClick={onResetDevice}
78
+ >
79
+ {t('reset device label')}
80
+ </Action>
81
+ </Actions>
82
+ </>
83
+ );
84
+ };
@@ -2,5 +2,8 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ export * from './AgentForm';
6
+ export * from './DeviceManager';
5
7
  export * from './IdentityActionChooser';
6
8
  export * from './ProfileForm';
9
+ export * from './SignOutChooser';