@dxos/shell 0.4.10-main.ec8b427 → 0.4.10-main.eedd150

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 (130) hide show
  1. package/dist/bundle/shell.mjs +12821 -11822
  2. package/dist/bundle/shell.mjs.map +1 -1
  3. package/dist/bundle/shell.umd.js +73 -65
  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 +435 -333
  8. package/dist/lib/browser/react.mjs.map +4 -4
  9. package/dist/lib/browser/testing/index.mjs +5 -7
  10. package/dist/lib/browser/testing/index.mjs.map +3 -3
  11. package/dist/types/src/components/EmojiPicker.d.ts.map +1 -1
  12. package/dist/types/src/components/EmojiPicker.js +2 -1
  13. package/dist/types/src/components/EmojiPicker.js.map +1 -1
  14. package/dist/types/src/composites/IdentityDialog/IdentityDialog.js +1 -1
  15. package/dist/types/src/composites/IdentityDialog/IdentityDialog.js.map +1 -1
  16. package/dist/types/src/composites/Shell/Shell.d.ts.map +1 -1
  17. package/dist/types/src/composites/Shell/Shell.js +18 -6
  18. package/dist/types/src/composites/Shell/Shell.js.map +1 -1
  19. package/dist/types/src/composites/StatusDialog/StatusDialog.d.ts +2 -0
  20. package/dist/types/src/composites/StatusDialog/StatusDialog.d.ts.map +1 -0
  21. package/dist/types/src/composites/StatusDialog/StatusDialog.js +22 -0
  22. package/dist/types/src/composites/StatusDialog/StatusDialog.js.map +1 -0
  23. package/dist/types/src/composites/StatusDialog/index.d.ts +2 -0
  24. package/dist/types/src/composites/StatusDialog/index.d.ts.map +1 -0
  25. package/dist/types/src/composites/StatusDialog/index.js +21 -0
  26. package/dist/types/src/composites/StatusDialog/index.js.map +1 -0
  27. package/dist/types/src/panels/IdentityPanel/IdentityPanel.d.ts +1 -1
  28. package/dist/types/src/panels/IdentityPanel/IdentityPanel.d.ts.map +1 -1
  29. package/dist/types/src/panels/IdentityPanel/IdentityPanel.js +15 -8
  30. package/dist/types/src/panels/IdentityPanel/IdentityPanel.js.map +1 -1
  31. package/dist/types/src/panels/IdentityPanel/IdentityPanelProps.d.ts +5 -3
  32. package/dist/types/src/panels/IdentityPanel/IdentityPanelProps.d.ts.map +1 -1
  33. package/dist/types/src/panels/IdentityPanel/identityMachine.d.ts +3 -1
  34. package/dist/types/src/panels/IdentityPanel/identityMachine.d.ts.map +1 -1
  35. package/dist/types/src/panels/IdentityPanel/identityMachine.js +15 -2
  36. package/dist/types/src/panels/IdentityPanel/identityMachine.js.map +1 -1
  37. package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.d.ts.map +1 -1
  38. package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.js +9 -5
  39. package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.js.map +1 -1
  40. package/dist/types/src/panels/IdentityPanel/steps/ProfileForm.js +4 -1
  41. package/dist/types/src/panels/IdentityPanel/steps/ProfileForm.js.map +1 -1
  42. package/dist/types/src/panels/IdentityPanel/steps/index.d.ts +0 -1
  43. package/dist/types/src/panels/IdentityPanel/steps/index.d.ts.map +1 -1
  44. package/dist/types/src/panels/IdentityPanel/steps/index.js +0 -1
  45. package/dist/types/src/panels/IdentityPanel/steps/index.js.map +1 -1
  46. package/dist/types/src/panels/JoinPanel/JoinPanel.d.ts +1 -1
  47. package/dist/types/src/panels/JoinPanel/JoinPanel.d.ts.map +1 -1
  48. package/dist/types/src/panels/JoinPanel/JoinPanel.js +12 -5
  49. package/dist/types/src/panels/JoinPanel/JoinPanel.js.map +1 -1
  50. package/dist/types/src/panels/JoinPanel/JoinPanel.stories.d.ts +1 -1
  51. package/dist/types/src/panels/JoinPanel/JoinPanel.stories.d.ts.map +1 -1
  52. package/dist/types/src/panels/JoinPanel/JoinPanel.stories.js +2 -1
  53. package/dist/types/src/panels/JoinPanel/JoinPanel.stories.js.map +1 -1
  54. package/dist/types/src/panels/JoinPanel/JoinPanelProps.d.ts +8 -3
  55. package/dist/types/src/panels/JoinPanel/JoinPanelProps.d.ts.map +1 -1
  56. package/dist/types/src/panels/JoinPanel/joinMachine.d.ts +3 -2
  57. package/dist/types/src/panels/JoinPanel/joinMachine.d.ts.map +1 -1
  58. package/dist/types/src/panels/JoinPanel/joinMachine.js +7 -0
  59. package/dist/types/src/panels/JoinPanel/joinMachine.js.map +1 -1
  60. package/dist/types/src/panels/JoinPanel/steps/index.d.ts +0 -1
  61. package/dist/types/src/panels/JoinPanel/steps/index.d.ts.map +1 -1
  62. package/dist/types/src/panels/JoinPanel/steps/index.js +0 -1
  63. package/dist/types/src/panels/JoinPanel/steps/index.js.map +1 -1
  64. package/dist/types/src/panels/StatusPanel/StatusPanel.d.ts +4 -0
  65. package/dist/types/src/panels/StatusPanel/StatusPanel.d.ts.map +1 -0
  66. package/dist/types/src/panels/StatusPanel/StatusPanel.js +19 -0
  67. package/dist/types/src/panels/StatusPanel/StatusPanel.js.map +1 -0
  68. package/dist/types/src/panels/StatusPanel/index.d.ts +2 -0
  69. package/dist/types/src/panels/StatusPanel/index.d.ts.map +1 -0
  70. package/dist/types/src/panels/StatusPanel/index.js +21 -0
  71. package/dist/types/src/panels/StatusPanel/index.js.map +1 -0
  72. package/dist/types/src/panels/index.d.ts +1 -0
  73. package/dist/types/src/panels/index.d.ts.map +1 -1
  74. package/dist/types/src/panels/index.js +1 -0
  75. package/dist/types/src/panels/index.js.map +1 -1
  76. package/dist/types/src/steps/ConfirmReset.d.ts +16 -0
  77. package/dist/types/src/steps/ConfirmReset.d.ts.map +1 -0
  78. package/dist/types/src/steps/ConfirmReset.js +71 -0
  79. package/dist/types/src/steps/ConfirmReset.js.map +1 -0
  80. package/dist/types/src/steps/StepProps.d.ts +4 -1
  81. package/dist/types/src/steps/StepProps.d.ts.map +1 -1
  82. package/dist/types/src/steps/index.d.ts +1 -0
  83. package/dist/types/src/steps/index.d.ts.map +1 -1
  84. package/dist/types/src/steps/index.js +1 -0
  85. package/dist/types/src/steps/index.js.map +1 -1
  86. package/dist/types/src/testing/shell-manager.d.ts.map +1 -1
  87. package/dist/types/src/testing/shell-manager.js +5 -7
  88. package/dist/types/src/testing/shell-manager.js.map +1 -1
  89. package/dist/types/src/translations/locales/en-US.d.ts +4 -1
  90. package/dist/types/src/translations/locales/en-US.d.ts.map +1 -1
  91. package/dist/types/src/translations/locales/en-US.js +8 -5
  92. package/dist/types/src/translations/locales/en-US.js.map +1 -1
  93. package/dist/types/src/types/index.js +1 -0
  94. package/dist/types/src/types/index.js.map +1 -1
  95. package/package.json +20 -20
  96. package/src/components/EmojiPicker.tsx +3 -1
  97. package/src/composites/IdentityDialog/IdentityDialog.tsx +1 -1
  98. package/src/composites/Shell/Shell.tsx +23 -4
  99. package/src/composites/StatusDialog/StatusDialog.tsx +24 -0
  100. package/src/composites/StatusDialog/index.ts +5 -0
  101. package/src/panels/IdentityPanel/IdentityPanel.tsx +31 -14
  102. package/src/panels/IdentityPanel/IdentityPanelProps.ts +7 -3
  103. package/src/panels/IdentityPanel/identityMachine.ts +25 -3
  104. package/src/panels/IdentityPanel/steps/IdentityActionChooser.tsx +16 -7
  105. package/src/panels/IdentityPanel/steps/ProfileForm.tsx +6 -1
  106. package/src/panels/IdentityPanel/steps/index.ts +0 -1
  107. package/src/panels/JoinPanel/JoinPanel.stories.tsx +3 -2
  108. package/src/panels/JoinPanel/JoinPanel.tsx +21 -5
  109. package/src/panels/JoinPanel/JoinPanelProps.ts +9 -3
  110. package/src/panels/JoinPanel/joinMachine.ts +12 -2
  111. package/src/panels/JoinPanel/steps/index.ts +0 -1
  112. package/src/panels/StatusPanel/StatusPanel.tsx +19 -0
  113. package/src/panels/StatusPanel/index.ts +5 -0
  114. package/src/panels/index.ts +1 -0
  115. package/src/steps/ConfirmReset.tsx +112 -0
  116. package/src/steps/StepProps.ts +2 -1
  117. package/src/steps/index.ts +1 -0
  118. package/src/testing/shell-manager.ts +5 -7
  119. package/src/translations/locales/en-US.ts +9 -5
  120. package/src/types/index.ts +2 -0
  121. package/dist/types/src/panels/IdentityPanel/steps/SignOutChooser.d.ts +0 -16
  122. package/dist/types/src/panels/IdentityPanel/steps/SignOutChooser.d.ts.map +0 -1
  123. package/dist/types/src/panels/IdentityPanel/steps/SignOutChooser.js +0 -40
  124. package/dist/types/src/panels/IdentityPanel/steps/SignOutChooser.js.map +0 -1
  125. package/dist/types/src/panels/JoinPanel/steps/ResetIdentity.d.ts +0 -13
  126. package/dist/types/src/panels/JoinPanel/steps/ResetIdentity.d.ts.map +0 -1
  127. package/dist/types/src/panels/JoinPanel/steps/ResetIdentity.js +0 -66
  128. package/dist/types/src/panels/JoinPanel/steps/ResetIdentity.js.map +0 -1
  129. package/src/panels/IdentityPanel/steps/SignOutChooser.tsx +0 -84
  130. package/src/panels/JoinPanel/steps/ResetIdentity.tsx +0 -80
@@ -11,6 +11,8 @@ import type { InvitationStatus } from '@dxos/react-client/invitations';
11
11
  import type { IdentityEvent } from './identityMachine';
12
12
  import type { InvitationManagerProps } from '../../steps';
13
13
 
14
+ export type IdentityPanelInitialDisposition = 'default' | 'manage-device-invitation' | 'manage-profile';
15
+
14
16
  export type AgentFormProps = {
15
17
  onAgentCreate?: () => Promise<void>;
16
18
  onAgentDestroy?: () => Promise<void>;
@@ -28,12 +30,14 @@ export type IdentityPanelImplProps = {
28
30
  | 'update profile form'
29
31
  | 'device invitation manager'
30
32
  | 'identity action chooser'
31
- | 'signing out';
33
+ | 'confirm join new identity'
34
+ | 'confirm reset storage';
35
+ initialDisposition?: IdentityPanelInitialDisposition;
32
36
  identity: Identity;
33
37
  devices: Device[];
34
38
  onUpdateProfile?: (profile: NonNullable<Identity['profile']>) => Promise<void>;
35
- onResetDevice?: () => Promise<void>;
36
- onJoinNewIdentity?: () => void;
39
+ onResetStorage?: () => Promise<void>;
40
+ onJoinNewIdentity?: () => Promise<void>;
37
41
  createInvitationUrl: (invitationCode: string) => string;
38
42
  send?: (event: SingleOrArray<Event<IdentityEvent>>) => void;
39
43
  onDone?: () => void;
@@ -10,16 +10,24 @@ import { type Client } from '@dxos/react-client';
10
10
  import { type Identity } from '@dxos/react-client/halo';
11
11
  import { type CancellableInvitationObservable } from '@dxos/react-client/invitations';
12
12
 
13
+ import { type IdentityPanelInitialDisposition } from './IdentityPanelProps';
13
14
  import { type StepEvent } from '../../steps';
14
15
 
15
16
  type IdentityMachineContext = {
17
+ initialDisposition?: IdentityPanelInitialDisposition;
16
18
  invitation?: CancellableInvitationObservable;
17
19
  identity: Identity | null;
18
20
  identitySubscribable: Subscribable<SetIdentityEvent> | null;
19
21
  };
20
22
 
21
23
  type IdentityChooseActionEvent = {
22
- type: 'chooseDevices' | 'chooseAgent' | 'chooseProfile' | 'chooseSignOut' | 'sigingOut' | 'unchooseAction';
24
+ type:
25
+ | 'chooseDevices'
26
+ | 'chooseAgent'
27
+ | 'chooseProfile'
28
+ | 'chooseResetStorage'
29
+ | 'chooseJoinNewIdentity'
30
+ | 'unchooseAction';
23
31
  };
24
32
 
25
33
  type IdentitySelectDeviceInvitationEvent = {
@@ -56,6 +64,13 @@ const identityMachine = createMachine<IdentityMachineContext, IdentityEvent>(
56
64
  src: (context) => context.identitySubscribable!,
57
65
  },
58
66
  states: {
67
+ unknown: {
68
+ always: [
69
+ { cond: 'initiallyManagingDeviceInvitation', target: 'managingDeviceInvitation', actions: 'log' },
70
+ { cond: 'initiallyManagingProfile', target: 'managingProfile', actions: 'log' },
71
+ { target: 'choosingAction', actions: 'log' },
72
+ ],
73
+ },
59
74
  choosingAction: {},
60
75
  managingDeviceInvitation: {},
61
76
  managingAgent: {
@@ -77,7 +92,8 @@ const identityMachine = createMachine<IdentityMachineContext, IdentityEvent>(
77
92
  updateProfile: { target: '.pending', actions: 'log' },
78
93
  },
79
94
  },
80
- signingOut: {},
95
+ confirmingResetStorage: {},
96
+ confirmingJoinNewIdentity: {},
81
97
  },
82
98
  on: {
83
99
  unchooseAction: { target: '.choosingAction', actions: ['unsetInvitation', 'log'] },
@@ -85,11 +101,17 @@ const identityMachine = createMachine<IdentityMachineContext, IdentityEvent>(
85
101
  selectInvitation: { target: '.managingDeviceInvitation', actions: ['setInvitation', 'log'] },
86
102
  chooseDevices: { target: '.managingDevices', actions: 'log' },
87
103
  chooseProfile: { target: '.managingProfile', actions: 'log' },
88
- chooseSignOut: { target: '.signingOut', actions: 'log' },
104
+ chooseJoinNewIdentity: { target: '.confirmingJoinNewIdentity', actions: 'log' },
105
+ chooseResetStorage: { target: '.confirmingResetStorage', actions: 'log' },
89
106
  chooseAgent: { target: '.managingAgent', actions: 'log' },
90
107
  },
91
108
  },
92
109
  {
110
+ guards: {
111
+ initiallyManagingDeviceInvitation: ({ initialDisposition }, _event) =>
112
+ initialDisposition === 'manage-device-invitation',
113
+ initiallyManagingProfile: ({ initialDisposition }, _event) => initialDisposition === 'manage-profile',
114
+ },
93
115
  actions: {
94
116
  setIdentity: assign<IdentityMachineContext, IdentityEvent>({
95
117
  identity: (context, event) => (event as SetIdentityEvent)?.identity ?? null,
@@ -2,7 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { CaretRight, Devices, Plus, Power, UserGear, HardDrive } from '@phosphor-icons/react';
5
+ import { CaretRight, Devices, Plus, Power, UserGear, HardDrive, Intersect } from '@phosphor-icons/react';
6
6
  import React, { useCallback } from 'react';
7
7
 
8
8
  import { log } from '@dxos/log';
@@ -73,6 +73,11 @@ export const IdentityActionChooserImpl = ({
73
73
  <div role='none' className='grow flex flex-col gap-1'>
74
74
  <DensityProvider density='coarse'>
75
75
  <div className='grow justify-center flex flex-col gap-1'>
76
+ <Action data-testid='manage-profile' onClick={() => send?.({ type: 'chooseProfile' })} classNames='plb-4'>
77
+ <UserGear className={getSize(6)} />
78
+ <span className='grow mli-3'>{t('choose profile label')}</span>
79
+ <CaretRight weight='bold' className={getSize(4)} />
80
+ </Action>
76
81
  {agentHostingEnabled && (
77
82
  <Action data-testid='manage-agent' onClick={() => send?.({ type: 'chooseAgent' })} classNames='plb-4'>
78
83
  <HardDrive className={getSize(6)} />
@@ -90,19 +95,23 @@ export const IdentityActionChooserImpl = ({
90
95
  <span className='grow mli-3'>{t('choose add device label')}</span>
91
96
  <CaretRight weight='bold' className={getSize(4)} />
92
97
  </Action>
98
+ <Action
99
+ data-testid='devices-panel.join-new-identity'
100
+ onClick={() => send?.({ type: 'chooseJoinNewIdentity' })}
101
+ classNames='plb-4'
102
+ >
103
+ <Intersect className={getSize(6)} />
104
+ <span className='grow mli-3'>{t('choose join new identity label')}</span>
105
+ <CaretRight weight='bold' className={getSize(4)} />
106
+ </Action>
93
107
  <Action data-testid='manage-devices' onClick={() => send?.({ type: 'chooseDevices' })} classNames='plb-4'>
94
108
  <Devices className={getSize(6)} />
95
109
  <span className='grow mli-3'>{t('choose devices label')}</span>
96
110
  <CaretRight weight='bold' className={getSize(4)} />
97
111
  </Action>
98
- <Action data-testid='manage-profile' onClick={() => send?.({ type: 'chooseProfile' })} classNames='plb-4'>
99
- <UserGear className={getSize(6)} />
100
- <span className='grow mli-3'>{t('choose profile label')}</span>
101
- <CaretRight weight='bold' className={getSize(4)} />
102
- </Action>
103
112
  <Action
104
113
  data-testid='devices-panel.sign-out'
105
- onClick={() => send?.({ type: 'chooseSignOut' })}
114
+ onClick={() => send?.({ type: 'chooseResetStorage' })}
106
115
  classNames='plb-4'
107
116
  >
108
117
  <Power className={getSize(6)} />
@@ -106,7 +106,12 @@ const ProfileFormImpl = (props: ProfileFormImplProps) => {
106
106
  <Action
107
107
  variant='primary'
108
108
  disabled={disabled}
109
- onClick={() => onUpdateProfile?.({ displayName, data: { emoji, hue } })}
109
+ onClick={() =>
110
+ onUpdateProfile?.({
111
+ ...(displayName && { displayName }),
112
+ ...((emoji || hue) && { data: { ...(emoji && { emoji }), ...(hue && { hue }) } }),
113
+ })
114
+ }
110
115
  data-testid='update-profile-form-continue'
111
116
  >
112
117
  {t('done label')}
@@ -6,4 +6,3 @@ export * from './AgentForm';
6
6
  export * from './DeviceManager';
7
7
  export * from './IdentityActionChooser';
8
8
  export * from './ProfileForm';
9
- export * from './SignOutChooser';
@@ -10,8 +10,9 @@ import { withTheme } from '@dxos/storybook-utils';
10
10
 
11
11
  import { JoinPanelImpl } from './JoinPanel';
12
12
  import { type JoinPanelImplProps } from './JoinPanelProps';
13
- import { IdentityInputImpl, ResetIdentityImpl } from './steps';
13
+ import { IdentityInputImpl } from './steps';
14
14
  import { StorybookDialog } from '../../components/StorybookDialog';
15
+ import { ConfirmResetImpl } from '../../steps';
15
16
 
16
17
  const noOpProps: JoinPanelImplProps = {
17
18
  titleId: 'storybookJoinPanel__title',
@@ -34,7 +35,7 @@ export default {
34
35
  parameters: { chromatic: { disableSnapshot: false } },
35
36
  args: {
36
37
  IdentityInput: IdentityInputImpl,
37
- ResetIdentity: ResetIdentityImpl,
38
+ ResetIdentity: ConfirmResetImpl,
38
39
  },
39
40
  };
40
41
 
@@ -19,9 +19,9 @@ import {
19
19
  InvitationRescuer,
20
20
  InvitationInput,
21
21
  InvitationAccepted,
22
- ResetIdentity,
23
22
  } from './steps';
24
23
  import { Viewport } from '../../components';
24
+ import { ConfirmReset } from '../../steps';
25
25
  import { stepStyles } from '../../styles';
26
26
 
27
27
  export const JoinPanelImpl = (props: JoinPanelImplProps) => {
@@ -44,8 +44,10 @@ export const JoinPanelImpl = (props: JoinPanelImplProps) => {
44
44
  onSpaceInvitationCancel,
45
45
  onHaloInvitationAuthenticate,
46
46
  onSpaceInvitationAuthenticate,
47
+ onCancelResetStorage,
48
+ onConfirmResetStorage,
47
49
  IdentityInput: IdentityInputComponent = IdentityInput,
48
- ResetIdentity: ResetIdentityComponent = ResetIdentity,
50
+ ConfirmReset: ConfirmResetComponent = ConfirmReset,
49
51
  } = props;
50
52
  return (
51
53
  <DensityProvider density='fine'>
@@ -55,11 +57,12 @@ export const JoinPanelImpl = (props: JoinPanelImplProps) => {
55
57
  <Viewport.View classNames={stepStyles} id='addition method chooser'>
56
58
  <AdditionMethodChooser send={send} active={activeView === 'addition method chooser'} />
57
59
  </Viewport.View>
58
- <Viewport.View classNames={stepStyles} id='reset identity confirmation'>
59
- <ResetIdentityComponent
60
+ <Viewport.View classNames={stepStyles} id='reset storage confirmation'>
61
+ <ConfirmResetComponent
60
62
  send={send}
61
- method='reset identity'
62
63
  active={activeView === 'reset identity confirmation'}
64
+ onCancel={onCancelResetStorage}
65
+ onConfirm={onConfirmResetStorage}
63
66
  />
64
67
  </Viewport.View>
65
68
  <Viewport.View classNames={stepStyles} id='create identity input'>
@@ -172,11 +175,13 @@ export const JoinPanelImpl = (props: JoinPanelImplProps) => {
172
175
  export const JoinPanel = ({
173
176
  titleId: propsTitleId,
174
177
  mode = 'default',
178
+ initialDisposition = 'default',
175
179
  initialInvitationCode,
176
180
  exitActionParent,
177
181
  onExit,
178
182
  doneActionParent,
179
183
  onDone: propsOnDone,
184
+ onCancelResetStorage,
180
185
  }: JoinPanelProps) => {
181
186
  const client = useClient();
182
187
  const identity = useIdentity();
@@ -186,6 +191,7 @@ export const JoinPanel = ({
186
191
  const [joinState, joinSend, joinService] = useJoinMachine(client, {
187
192
  context: {
188
193
  mode,
194
+ initialDisposition,
189
195
  identity,
190
196
  ...(initialInvitationCode && {
191
197
  [mode === 'halo-only' ? 'halo' : 'space']: { unredeemedCode: initialInvitationCode },
@@ -379,6 +385,14 @@ export const JoinPanel = ({
379
385
  });
380
386
  }, [joinState, propsOnDone]);
381
387
 
388
+ const onConfirmResetStorage = useCallback(
389
+ () =>
390
+ client.reset().then(() => {
391
+ joinSend({ type: 'resetIdentity' });
392
+ }),
393
+ [client, joinSend],
394
+ );
395
+
382
396
  return (
383
397
  <JoinPanelImpl
384
398
  {...{
@@ -409,6 +423,8 @@ export const JoinPanel = ({
409
423
  joinSend({ type: 'authenticateSpaceVerificationCode' });
410
424
  return joinState.context.space.invitationObservable?.authenticate(authCode);
411
425
  },
426
+ onConfirmResetStorage,
427
+ onCancelResetStorage,
412
428
  }}
413
429
  />
414
430
  );
@@ -8,12 +8,14 @@ import type { Identity } from '@dxos/react-client/halo';
8
8
  import type { Invitation, AuthenticatingInvitationObservable, InvitationResult } from '@dxos/react-client/invitations';
9
9
 
10
10
  import { type JoinSend } from './joinMachine';
11
- import { type ResetIdentityProps, type IdentityInputProps } from './steps';
12
- import { type StepProps } from '../../steps';
11
+ import { type IdentityInputProps } from './steps';
12
+ import { type ConfirmResetProps, type StepProps } from '../../steps';
13
13
  import { type FailReason } from '../../types';
14
14
 
15
15
  export type JoinPanelMode = 'default' | 'halo-only';
16
16
 
17
+ export type JoinPanelInitialDisposition = 'default' | 'accept-halo-invitation';
18
+
17
19
  export type JoinStepProps = Omit<StepProps, 'send' | 'onDone'> & {
18
20
  send: JoinSend;
19
21
  onDone?: (result: InvitationResult | null) => void;
@@ -21,6 +23,7 @@ export type JoinStepProps = Omit<StepProps, 'send' | 'onDone'> & {
21
23
 
22
24
  export interface JoinPanelProps {
23
25
  mode?: JoinPanelMode;
26
+ initialDisposition?: JoinPanelInitialDisposition;
24
27
  initialInvitationCode?: string;
25
28
  titleId?: string;
26
29
  exitActionParent?: Parameters<typeof cloneElement>[0];
@@ -28,6 +31,7 @@ export interface JoinPanelProps {
28
31
  onExit?: () => void;
29
32
  onDone?: (result: InvitationResult | null) => void;
30
33
  parseInvitationCodeInput?: (invitationCodeInput: string) => string;
34
+ onCancelResetStorage?: () => void;
31
35
  }
32
36
 
33
37
  export type JoinPanelImplProps = Pick<
@@ -61,8 +65,10 @@ export type JoinPanelImplProps = Pick<
61
65
  onSpaceInvitationCancel?: () => Promise<void> | undefined;
62
66
  onHaloInvitationAuthenticate?: (authCode: string) => Promise<void> | undefined;
63
67
  onSpaceInvitationAuthenticate?: (authCode: string) => Promise<void> | undefined;
68
+ onConfirmResetStorage?: () => Promise<void>;
69
+ onCancelResetStorage?: () => void;
64
70
  IdentityInput?: React.FC<IdentityInputProps>;
65
- ResetIdentity?: React.FC<ResetIdentityProps>;
71
+ ConfirmReset?: React.FC<ConfirmResetProps>;
66
72
  };
67
73
 
68
74
  export interface IdentityAction {
@@ -19,7 +19,7 @@ import { type Client, AlreadyJoinedError } from '@dxos/react-client';
19
19
  import { type Identity } from '@dxos/react-client/halo';
20
20
  import { type AuthenticatingInvitationObservable, Invitation, InvitationEncoder } from '@dxos/react-client/invitations';
21
21
 
22
- import { type JoinPanelMode } from './JoinPanelProps';
22
+ import { type JoinPanelInitialDisposition, type JoinPanelMode } from './JoinPanelProps';
23
23
  import { type FailReason } from '../../types';
24
24
 
25
25
  type InvitationKindContext = Partial<{
@@ -33,6 +33,7 @@ type InvitationKindContext = Partial<{
33
33
 
34
34
  type JoinMachineContext = {
35
35
  mode: JoinPanelMode;
36
+ initialDisposition: JoinPanelInitialDisposition;
36
37
  identity: Identity | null;
37
38
  halo: InvitationKindContext;
38
39
  space: InvitationKindContext;
@@ -74,7 +75,8 @@ type EmptyInvitationEvent = {
74
75
  | 'authenticateHaloVerificationCode'
75
76
  | 'authenticateSpaceVerificationCode'
76
77
  | 'resetHaloInvitation'
77
- | 'resetSpaceInvitation';
78
+ | 'resetSpaceInvitation'
79
+ | 'deselectInvitation';
78
80
  };
79
81
 
80
82
  type InvitationEvent = FailInvitationEvent | SetInvitationCodeEvent | SetInvitationEvent | EmptyInvitationEvent;
@@ -294,6 +296,7 @@ const joinMachine = createMachine<JoinMachineContext, JoinEvent>(
294
296
  id: 'join',
295
297
  predictableActionArguments: true,
296
298
  context: {
299
+ initialDisposition: 'default',
297
300
  mode: 'default',
298
301
  identity: null,
299
302
  halo: {},
@@ -304,6 +307,11 @@ const joinMachine = createMachine<JoinMachineContext, JoinEvent>(
304
307
  unknown: {
305
308
  always: [
306
309
  { cond: 'existingIdentity', target: 'resettingIdentity', actions: 'log' },
310
+ {
311
+ cond: 'initiallyAcceptingHaloInvitation',
312
+ target: '#join.choosingIdentity.acceptingHaloInvitation',
313
+ actions: 'log',
314
+ },
307
315
  { cond: 'noSelectedIdentity', target: 'choosingIdentity', actions: 'log' },
308
316
  { target: 'acceptingSpaceInvitation', actions: 'log' },
309
317
  ],
@@ -352,6 +360,8 @@ const joinMachine = createMachine<JoinMachineContext, JoinEvent>(
352
360
  guards: {
353
361
  existingIdentity: ({ identity, mode }, _event) => mode === 'halo-only' && !!identity,
354
362
  noSelectedIdentity: ({ identity }, _event) => !identity,
363
+ initiallyAcceptingHaloInvitation: ({ identity, initialDisposition }, _event) =>
364
+ !identity && initialDisposition === 'accept-halo-invitation',
355
365
  hasHaloUnredeemedCode: ({ halo }, _event) => !!halo.unredeemedCode,
356
366
  noSpaceInvitation: ({ space }, _event) => !space.invitation && !space.unredeemedCode,
357
367
  },
@@ -9,4 +9,3 @@ export * from './InvitationAccepted';
9
9
  export * from './InvitationInput';
10
10
  export * from './InvitationRescuer';
11
11
  export * from './InvitationAuthenticator';
12
- export * from './ResetIdentity';
@@ -0,0 +1,19 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { Status, useTranslation } from '@dxos/react-ui';
8
+
9
+ export const StatusPanel = ({ titleId }: { titleId?: string }) => {
10
+ const { t } = useTranslation('os');
11
+ return (
12
+ <div className='grid place-items-center p-2 gap-2'>
13
+ <p id={titleId} className='font-medium text-center'>
14
+ {t('resetting message')}
15
+ </p>
16
+ <Status indeterminate>{t('resetting message')}</Status>
17
+ </div>
18
+ );
19
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './StatusPanel';
@@ -5,3 +5,4 @@
5
5
  export * from './IdentityPanel';
6
6
  export * from './JoinPanel';
7
7
  export * from './SpacePanel';
8
+ export * from './StatusPanel';
@@ -0,0 +1,112 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import { Warning } from '@phosphor-icons/react';
6
+ import React, { useState } from 'react';
7
+
8
+ import { log } from '@dxos/log';
9
+ import { Message, useTranslation } from '@dxos/react-ui';
10
+ import { getSize, mx } from '@dxos/react-ui-theme';
11
+
12
+ import { type StepProps } from './StepProps';
13
+ import { Action, Actions, StepHeading, Input } from '../components';
14
+
15
+ type ConfirmResetOptions = Partial<{
16
+ onCancel: () => void;
17
+ onConfirm: () => Promise<void>;
18
+ mode: 'join new identity' | 'reset storage';
19
+ }>;
20
+
21
+ export type ConfirmResetProps = StepProps & ConfirmResetOptions;
22
+
23
+ export type ConfirmResetImplProps = ConfirmResetOptions & {
24
+ disabled?: boolean;
25
+ pending?: boolean;
26
+ validationMessage?: string;
27
+ };
28
+
29
+ export const ConfirmReset = ({ active, onCancel, onConfirm, mode }: ConfirmResetProps) => {
30
+ const { t } = useTranslation('os');
31
+ const [validationMessage, setValidationMessage] = useState('');
32
+ const [pending, setPending] = useState(false);
33
+ const processReset = async () => {
34
+ setPending(true);
35
+ return onConfirm?.().then(
36
+ () => setPending(false),
37
+ (error) => {
38
+ log.catch(error);
39
+ setValidationMessage(t('failed to reset identity message'));
40
+ setPending(false);
41
+ },
42
+ );
43
+ };
44
+ return (
45
+ <ConfirmResetImpl
46
+ disabled={!active}
47
+ pending={pending}
48
+ validationMessage={validationMessage}
49
+ onConfirm={processReset}
50
+ onCancel={onCancel}
51
+ mode={mode}
52
+ />
53
+ );
54
+ };
55
+
56
+ export const ConfirmResetImpl = ({
57
+ disabled,
58
+ pending,
59
+ validationMessage,
60
+ onConfirm,
61
+ onCancel,
62
+ mode,
63
+ }: ConfirmResetImplProps) => {
64
+ const { t } = useTranslation('os');
65
+ const confirmationValue = t('confirmation value');
66
+ const [inputValue, setInputValue] = useState('');
67
+ const testIdAffix = mode === 'join new identity' ? 'join-new-identity' : 'reset-storage';
68
+ return (
69
+ <>
70
+ <div role='none' className='grow flex flex-col gap-2 justify-center'>
71
+ <Message.Root valence='error'>
72
+ <Message.Title>
73
+ <Warning className={mx(getSize(6), 'inline mie-2')} />
74
+ {t('sign out chooser title')}
75
+ </Message.Title>
76
+ <Message.Body>{t('sign out chooser message')}</Message.Body>
77
+ </Message.Root>
78
+ <Input
79
+ {...{ validationMessage }}
80
+ label={
81
+ <StepHeading className='text-start mlb-2'>
82
+ {t(mode === 'join new identity' ? 'join new identity input label' : 'reset storage input label', {
83
+ confirmationValue,
84
+ })}
85
+ </StepHeading>
86
+ }
87
+ disabled={disabled}
88
+ data-testid={`${testIdAffix}.reset-identity-input`}
89
+ placeholder={confirmationValue}
90
+ onChange={({ target: { value } }) => setInputValue(value)}
91
+ />
92
+ </div>
93
+ <Actions>
94
+ {onCancel && (
95
+ <Action disabled={disabled} onClick={onCancel} data-testid={`${testIdAffix}.reset-identity-cancel`}>
96
+ {t('cancel label')}
97
+ </Action>
98
+ )}
99
+ {onConfirm && (
100
+ <Action
101
+ variant='destructive'
102
+ disabled={disabled || pending || inputValue !== confirmationValue}
103
+ onClick={onConfirm}
104
+ data-testid={`${testIdAffix}.reset-identity-confirm`}
105
+ >
106
+ {pending ? t('reset in progress label') : t('reset device label')}
107
+ </Action>
108
+ )}
109
+ </Actions>
110
+ </>
111
+ );
112
+ };
@@ -6,8 +6,9 @@ import { type cloneElement } from 'react';
6
6
  import { type Event, type SingleOrArray } from 'xstate';
7
7
 
8
8
  type StepDeselectInvitationEvent = { type: 'deselectInvitation' };
9
+ type StepResetIdentityEvent = { type: 'resetIdentity' };
9
10
 
10
- export type StepEvent = StepDeselectInvitationEvent;
11
+ export type StepEvent = StepDeselectInvitationEvent | StepResetIdentityEvent;
11
12
 
12
13
  export type StepProps = {
13
14
  active?: boolean;
@@ -4,3 +4,4 @@
4
4
 
5
5
  export * from './InvitationManager';
6
6
  export * from './StepProps';
7
+ export * from './ConfirmReset';
@@ -66,16 +66,14 @@ export class ShellManager extends ScopedShellManager {
66
66
 
67
67
  this.page.on('dialog', handleDialog);
68
68
  await this.shell.getByTestId('devices-panel.sign-out').click();
69
- await this.shell.getByTestId('sign-out.reset-device').click();
70
- this.page.off('dialog', handleDialog);
69
+ await this.shell.getByTestId('reset-storage.reset-identity-input').fill('CONFIRM');
70
+ await this.shell.getByTestId('reset-storage.reset-identity-confirm').click();
71
71
  }
72
72
 
73
73
  async joinNewIdentity(invitationCode: string) {
74
- await this.shell.getByTestId('devices-panel.sign-out').click();
75
- await this.shell.getByTestId('sign-out.join-new-identity').click();
76
- await this.shell.getByTestId('reset-identity-input').fill('CONFIRM');
77
- await this.shell.getByTestId('reset-identity-input-confirm').click();
78
- await this.shell.getByTestId('identity-chooser.join-identity').click();
74
+ await this.shell.getByTestId('devices-panel.join-new-identity').click();
75
+ await this.shell.getByTestId('join-new-identity.reset-identity-input').fill('CONFIRM');
76
+ await this.shell.getByTestId('join-new-identity.reset-identity-input-confirm').click();
79
77
  await this.inputInvitation('device', invitationCode, this.shell);
80
78
  }
81
79
 
@@ -73,7 +73,8 @@ export const os = {
73
73
  'choose add device label': 'Add device',
74
74
  'choose devices label': 'Manage devices',
75
75
  'choose profile label': 'Edit profile',
76
- 'choose sign out label': 'Sign out',
76
+ 'choose sign out label': 'Reset storage',
77
+ 'choose join new identity label': 'Join existing device',
77
78
  'empty space members message': 'There is nobody in this space yet.',
78
79
  'back to all invitations label': 'Back to all invitations',
79
80
  'invitation heading': 'Invitation',
@@ -97,19 +98,22 @@ export const os = {
97
98
  'invite options label': 'Change the active invite option.',
98
99
  'reset in progress label': 'Resetting...',
99
100
  'confirm label': 'Confirm',
100
- 'reset identity input label':
101
+ 'reset storage input label':
101
102
  'This will clear this device and all associated data. Type {{confirmationValue}} and confirm to continue.',
103
+ 'join new identity input label':
104
+ 'In order to join an existing device, storage on this device must be cleared first. Type {{confirmationValue}} to continue.',
102
105
  'failed to reset identity message': 'Failed to reset identity.',
103
106
  'confirmation value': 'CONFIRM',
104
- 'sign out chooser title': 'DANGER ZONE',
105
- 'sign out chooser message': 'Proceeding with either action below will erase all data on this device.',
107
+ 'sign out chooser title': 'Danger zone',
108
+ 'sign out chooser message': 'Proceeding with the action below will erase all data on this device.',
106
109
  'join new identity label': 'Join a new identity',
107
- 'reset device label': 'Reset device',
110
+ 'reset device label': 'Reset storage',
108
111
  'reset device confirm message': 'Are you sure you want to reset your device? All data will be lost.',
109
112
  'emoji and color label': 'Emoji and color',
110
113
  'clear label': 'Use default',
111
114
  'select emoji label': 'Choose an emoji to use in your avatar',
112
115
  'select hue label': 'Choose a hue to use in your avatar',
116
+ 'resetting message': 'One moment while things are reset…',
113
117
  'red label': 'Red',
114
118
  'orange label': 'Orange',
115
119
  'amber label': 'Amber',
@@ -4,3 +4,5 @@
4
4
 
5
5
  export * from './FailReason';
6
6
  export * from './IInvitation';
7
+
8
+ // This comment is here to bust NX cache. Remove it after the code changes.
@@ -1,16 +0,0 @@
1
- import { type Event, type SingleOrArray } from 'xstate';
2
- import { type IdentityPanelStepProps } from '../IdentityPanelProps';
3
- import { type IdentityEvent } from '../identityMachine';
4
- export interface SignOutChooserProps extends Omit<IdentityPanelStepProps, 'send'> {
5
- send?: (event: SingleOrArray<Event<IdentityEvent>>) => void;
6
- onResetDevice?: () => Promise<void>;
7
- onJoinNewIdentity?: () => void;
8
- }
9
- export declare const SignOutChooser: (props: SignOutChooserProps) => JSX.Element;
10
- export type SignOutChooserImplProps = {
11
- disabled?: boolean;
12
- onResetDevice?: () => void;
13
- onJoinNewIdentity?: () => void;
14
- onBack?: () => void;
15
- };
16
- //# sourceMappingURL=SignOutChooser.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SignOutChooser.d.ts","sourceRoot":"","sources":["../../../../../../src/panels/IdentityPanel/steps/SignOutChooser.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,KAAK,EAAE,KAAK,aAAa,EAAE,MAAM,QAAQ,CAAC;AAMxD,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAAC,sBAAsB,EAAE,MAAM,CAAC;IAC/E,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,KAAK,IAAI,CAAC;IAC5D,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;CAChC;AAED,eAAO,MAAM,cAAc,UAAW,mBAAmB,gBAexD,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC"}