@dxos/shell 0.3.11-next.0fb359e → 0.3.11-next.e28df4f
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.
- package/dist/bundle/shell.mjs +36774 -0
- package/dist/bundle/shell.mjs.map +1 -0
- package/dist/bundle/shell.umd.js +210 -0
- package/dist/bundle/shell.umd.js.map +1 -0
- package/dist/bundle/style.css +1 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/react.mjs +755 -438
- package/dist/lib/browser/react.mjs.map +4 -4
- package/dist/lib/browser/testing/index.mjs +35 -14
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/types/src/components/IdentityList/DeviceList.d.ts.map +1 -1
- package/dist/types/src/components/IdentityList/DeviceList.js +5 -3
- package/dist/types/src/components/IdentityList/DeviceList.js.map +1 -1
- package/dist/types/src/components/IdentityList/SpaceMemberList.js +1 -1
- package/dist/types/src/components/IdentityList/SpaceMemberList.js.map +1 -1
- package/dist/types/src/components/Panel/Action.d.ts.map +1 -1
- package/dist/types/src/components/Panel/Action.js +3 -18
- package/dist/types/src/components/Panel/Action.js.map +1 -1
- package/dist/types/src/composites/Shell/Shell.d.ts.map +1 -1
- package/dist/types/src/composites/Shell/Shell.js +15 -5
- package/dist/types/src/composites/Shell/Shell.js.map +1 -1
- package/dist/types/src/panels/IdentityPanel/IdentityPanel.d.ts.map +1 -1
- package/dist/types/src/panels/IdentityPanel/IdentityPanel.js +24 -7
- package/dist/types/src/panels/IdentityPanel/IdentityPanel.js.map +1 -1
- package/dist/types/src/panels/IdentityPanel/IdentityPanel.stories.d.ts +6 -0
- package/dist/types/src/panels/IdentityPanel/IdentityPanel.stories.d.ts.map +1 -1
- package/dist/types/src/panels/IdentityPanel/IdentityPanel.stories.js +26 -3
- package/dist/types/src/panels/IdentityPanel/IdentityPanel.stories.js.map +1 -1
- package/dist/types/src/panels/IdentityPanel/IdentityPanelProps.d.ts +19 -8
- package/dist/types/src/panels/IdentityPanel/IdentityPanelProps.d.ts.map +1 -1
- package/dist/types/src/panels/IdentityPanel/identityMachine.d.ts +1 -1
- package/dist/types/src/panels/IdentityPanel/identityMachine.d.ts.map +1 -1
- package/dist/types/src/panels/IdentityPanel/identityMachine.js +12 -3
- package/dist/types/src/panels/IdentityPanel/identityMachine.js.map +1 -1
- package/dist/types/src/panels/IdentityPanel/steps/AgentForm.d.ts +3 -0
- package/dist/types/src/panels/IdentityPanel/steps/AgentForm.d.ts.map +1 -0
- package/dist/types/src/panels/IdentityPanel/steps/AgentForm.js +26 -0
- package/dist/types/src/panels/IdentityPanel/steps/AgentForm.js.map +1 -0
- package/dist/types/src/panels/IdentityPanel/steps/DeviceManager.d.ts +10 -0
- package/dist/types/src/panels/IdentityPanel/steps/DeviceManager.d.ts.map +1 -0
- package/dist/types/src/panels/IdentityPanel/steps/DeviceManager.js +23 -0
- package/dist/types/src/panels/IdentityPanel/steps/DeviceManager.js.map +1 -0
- package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.d.ts.map +1 -1
- package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.js +9 -1
- package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.js.map +1 -1
- package/dist/types/src/panels/IdentityPanel/steps/ProfileForm.d.ts +0 -1
- package/dist/types/src/panels/IdentityPanel/steps/ProfileForm.d.ts.map +1 -1
- package/dist/types/src/panels/IdentityPanel/steps/ProfileForm.js +2 -3
- package/dist/types/src/panels/IdentityPanel/steps/ProfileForm.js.map +1 -1
- package/dist/types/src/panels/IdentityPanel/steps/SignOutChooser.d.ts +16 -0
- package/dist/types/src/panels/IdentityPanel/steps/SignOutChooser.d.ts.map +1 -0
- package/dist/types/src/panels/IdentityPanel/steps/SignOutChooser.js +40 -0
- package/dist/types/src/panels/IdentityPanel/steps/SignOutChooser.js.map +1 -0
- package/dist/types/src/panels/IdentityPanel/steps/index.d.ts +3 -0
- package/dist/types/src/panels/IdentityPanel/steps/index.d.ts.map +1 -1
- package/dist/types/src/panels/IdentityPanel/steps/index.js +3 -0
- package/dist/types/src/panels/IdentityPanel/steps/index.js.map +1 -1
- package/dist/types/src/panels/IdentityPanel/useAgentHandlers.d.ts +15 -0
- package/dist/types/src/panels/IdentityPanel/useAgentHandlers.d.ts.map +1 -0
- package/dist/types/src/panels/IdentityPanel/useAgentHandlers.js +78 -0
- package/dist/types/src/panels/IdentityPanel/useAgentHandlers.js.map +1 -0
- package/dist/types/src/panels/JoinPanel/JoinPanel.js +4 -3
- package/dist/types/src/panels/JoinPanel/JoinPanel.js.map +1 -1
- package/dist/types/src/panels/JoinPanel/joinMachine.d.ts +1 -1
- package/dist/types/src/panels/JoinPanel/joinMachine.d.ts.map +1 -1
- package/dist/types/src/panels/JoinPanel/joinMachine.js +15 -18
- package/dist/types/src/panels/JoinPanel/joinMachine.js.map +1 -1
- package/dist/types/src/panels/JoinPanel/steps/AdditionMethodChooser.js +3 -3
- package/dist/types/src/panels/JoinPanel/steps/AdditionMethodChooser.js.map +1 -1
- package/dist/types/src/panels/JoinPanel/steps/InvitationInput.d.ts.map +1 -1
- package/dist/types/src/panels/JoinPanel/steps/InvitationInput.js +13 -6
- package/dist/types/src/panels/JoinPanel/steps/InvitationInput.js.map +1 -1
- package/dist/types/src/panels/JoinPanel/steps/ResetIdentity.js +1 -1
- package/dist/types/src/panels/JoinPanel/steps/ResetIdentity.js.map +1 -1
- package/dist/types/src/panels/SpacePanel/SpacePanel.d.ts.map +1 -1
- package/dist/types/src/panels/SpacePanel/SpacePanel.js +5 -3
- package/dist/types/src/panels/SpacePanel/SpacePanel.js.map +1 -1
- package/dist/types/src/panels/SpacePanel/SpacePanel.stories.d.ts.map +1 -1
- package/dist/types/src/panels/SpacePanel/SpacePanel.stories.js +2 -2
- package/dist/types/src/panels/SpacePanel/SpacePanel.stories.js.map +1 -1
- package/dist/types/src/panels/SpacePanel/SpacePanelProps.d.ts +1 -0
- package/dist/types/src/panels/SpacePanel/SpacePanelProps.d.ts.map +1 -1
- package/dist/types/src/panels/SpacePanel/steps/SpaceManager.d.ts +1 -0
- package/dist/types/src/panels/SpacePanel/steps/SpaceManager.d.ts.map +1 -1
- package/dist/types/src/panels/SpacePanel/steps/SpaceManager.js +3 -1
- package/dist/types/src/panels/SpacePanel/steps/SpaceManager.js.map +1 -1
- package/dist/types/src/playwright/invitations-manager.d.ts +1 -0
- package/dist/types/src/playwright/invitations-manager.d.ts.map +1 -1
- package/dist/types/src/playwright/invitations-manager.js +4 -1
- package/dist/types/src/playwright/invitations-manager.js.map +1 -1
- package/dist/types/src/playwright/invitations.spec.js +15 -0
- package/dist/types/src/playwright/invitations.spec.js.map +1 -1
- package/dist/types/src/testing/shell-manager.d.ts +5 -4
- package/dist/types/src/testing/shell-manager.d.ts.map +1 -1
- package/dist/types/src/testing/shell-manager.js +36 -12
- package/dist/types/src/testing/shell-manager.js.map +1 -1
- package/dist/types/src/translations/locales/en-US.d.ts +6 -0
- package/dist/types/src/translations/locales/en-US.d.ts.map +1 -1
- package/dist/types/src/translations/locales/en-US.js +7 -1
- package/dist/types/src/translations/locales/en-US.js.map +1 -1
- package/package.json +22 -21
- package/src/components/IdentityList/DeviceList.tsx +7 -5
- package/src/components/IdentityList/SpaceMemberList.tsx +1 -1
- package/src/components/Panel/Action.tsx +4 -19
- package/src/composites/Shell/Shell.tsx +16 -3
- package/src/panels/IdentityPanel/IdentityPanel.stories.tsx +44 -2
- package/src/panels/IdentityPanel/IdentityPanel.tsx +35 -8
- package/src/panels/IdentityPanel/IdentityPanelProps.ts +27 -8
- package/src/panels/IdentityPanel/identityMachine.ts +13 -4
- package/src/panels/IdentityPanel/steps/AgentForm.tsx +61 -0
- package/src/panels/IdentityPanel/steps/DeviceManager.tsx +41 -0
- package/src/panels/IdentityPanel/steps/IdentityActionChooser.tsx +13 -4
- package/src/panels/IdentityPanel/steps/ProfileForm.tsx +1 -1
- package/src/panels/IdentityPanel/steps/SignOutChooser.tsx +84 -0
- package/src/panels/IdentityPanel/steps/index.ts +3 -0
- package/src/panels/IdentityPanel/useAgentHandlers.ts +88 -0
- package/src/panels/JoinPanel/JoinPanel.tsx +2 -2
- package/src/panels/JoinPanel/joinMachine.ts +16 -19
- package/src/panels/JoinPanel/steps/AdditionMethodChooser.tsx +3 -3
- package/src/panels/JoinPanel/steps/InvitationInput.tsx +7 -1
- package/src/panels/JoinPanel/steps/ResetIdentity.tsx +1 -1
- package/src/panels/SpacePanel/SpacePanel.stories.tsx +2 -1
- package/src/panels/SpacePanel/SpacePanel.tsx +4 -1
- package/src/panels/SpacePanel/SpacePanelProps.ts +1 -0
- package/src/panels/SpacePanel/steps/SpaceManager.tsx +4 -1
- package/src/playwright/invitations-manager.ts +5 -1
- package/src/playwright/invitations.spec.ts +19 -0
- package/src/testing/shell-manager.ts +40 -13
- package/src/translations/locales/en-US.ts +7 -1
|
@@ -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 '
|
|
59
|
+
case 'agent manager':
|
|
60
|
+
return 'Manage Agent';
|
|
57
61
|
case 'device invitation manager':
|
|
58
|
-
return t('choose
|
|
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
|
-
|
|
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
|
-
|
|
132
|
-
|
|
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 {
|
|
6
|
-
import {
|
|
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 {
|
|
8
|
+
import type { Device, Identity } from '@dxos/react-client/halo';
|
|
9
|
+
import type { InvitationStatus } from '@dxos/react-client/invitations';
|
|
10
10
|
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
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:
|
|
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' |
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type CancellableInvitation } from '@dxos/client-protocol';
|
|
8
|
+
import { invariant } from '@dxos/invariant';
|
|
9
|
+
import { useAgentHostingProviderClient, type Client } from '@dxos/react-client';
|
|
10
|
+
import { type Identity } from '@dxos/react-client/halo';
|
|
11
|
+
import { Invitation, InvitationEncoder } from '@dxos/react-client/invitations';
|
|
12
|
+
|
|
13
|
+
export const useAgentHandlers = ({
|
|
14
|
+
client,
|
|
15
|
+
invitations,
|
|
16
|
+
identity,
|
|
17
|
+
}: {
|
|
18
|
+
client: Client;
|
|
19
|
+
invitations: CancellableInvitation[];
|
|
20
|
+
identity: Identity | null;
|
|
21
|
+
}) => {
|
|
22
|
+
const [validationMessage, setValidationMessage] = useState('');
|
|
23
|
+
const [agentStatus, setAgentStatus] = useState('');
|
|
24
|
+
const [agentActive, setAgentActive] = useState(false);
|
|
25
|
+
const agentHostingProviderClient = useAgentHostingProviderClient(client.config);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const fetchAgentStatus = async () => {
|
|
29
|
+
// TODO(nf): if agent exists, subscribe to updates instead of oneshot status
|
|
30
|
+
if (agentHostingProviderClient) {
|
|
31
|
+
const agentStatus = await agentHostingProviderClient?.getAgent(identity?.identityKey.truncate() ?? 'foo');
|
|
32
|
+
if (agentStatus) {
|
|
33
|
+
setAgentActive(true);
|
|
34
|
+
setAgentStatus(agentStatus);
|
|
35
|
+
} else {
|
|
36
|
+
setAgentActive(false);
|
|
37
|
+
setValidationMessage('No agent deployed via provider.');
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
setValidationMessage('No agent provider configured.');
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
void fetchAgentStatus();
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
const handleAgentCreate = useCallback(async (invitation: Invitation) => {
|
|
47
|
+
const invitationCode = InvitationEncoder.encode(invitation);
|
|
48
|
+
if (invitation.state === Invitation.State.CONNECTING) {
|
|
49
|
+
console.log(JSON.stringify({ invitationCode, authCode: invitation.authCode }));
|
|
50
|
+
invariant(identity, 'Identity not found');
|
|
51
|
+
if (agentHostingProviderClient) {
|
|
52
|
+
try {
|
|
53
|
+
const res = await agentHostingProviderClient?.createAgent(invitationCode, identity.identityKey.truncate());
|
|
54
|
+
// TODO(nf): human-consumable response from API
|
|
55
|
+
setAgentStatus(res);
|
|
56
|
+
setAgentActive(true);
|
|
57
|
+
setValidationMessage('');
|
|
58
|
+
} catch (err: any) {
|
|
59
|
+
setValidationMessage(`error creating agent: ${err.message}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}, []);
|
|
64
|
+
|
|
65
|
+
const onAgentCreate = async () => {
|
|
66
|
+
invitations.forEach((invitation) => invitation.cancel());
|
|
67
|
+
|
|
68
|
+
// TODO(nf): do this work in the hosting provider client?
|
|
69
|
+
const invitation = client.halo.share({ type: Invitation.Type.MULTIUSE, authMethod: Invitation.AuthMethod.NONE });
|
|
70
|
+
|
|
71
|
+
invitation.subscribe(handleAgentCreate);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const onAgentDestroy = async () => {
|
|
75
|
+
const identity = client.halo.identity.get();
|
|
76
|
+
invariant(identity, 'Identity not found');
|
|
77
|
+
try {
|
|
78
|
+
await agentHostingProviderClient?.destroyAgent(identity.identityKey.truncate());
|
|
79
|
+
setValidationMessage('No agent deployed via provider.');
|
|
80
|
+
setAgentStatus('');
|
|
81
|
+
setAgentActive(false);
|
|
82
|
+
} catch (err: any) {
|
|
83
|
+
setValidationMessage(`error destroying agent: ${err.message}`);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
return { onAgentCreate, onAgentDestroy, validationMessage, agentStatus, agentActive };
|
|
88
|
+
};
|
|
@@ -193,8 +193,6 @@ export const JoinPanel = ({
|
|
|
193
193
|
},
|
|
194
194
|
});
|
|
195
195
|
|
|
196
|
-
console.log('[joinState]', joinState);
|
|
197
|
-
|
|
198
196
|
useEffect(() => {
|
|
199
197
|
const subscription = joinService.subscribe((state) => {
|
|
200
198
|
log('[state]', state);
|
|
@@ -368,6 +366,7 @@ export const JoinPanel = ({
|
|
|
368
366
|
identityKey: joinState.context.halo.invitation?.identityKey ?? null,
|
|
369
367
|
swarmKey: joinState.context.halo.invitation?.swarmKey ?? null,
|
|
370
368
|
spaceKey: joinState.context.halo.invitation?.spaceKey ?? null,
|
|
369
|
+
target: joinState.context.halo.invitation?.target ?? null,
|
|
371
370
|
});
|
|
372
371
|
}, [joinState, propsOnDone]);
|
|
373
372
|
|
|
@@ -376,6 +375,7 @@ export const JoinPanel = ({
|
|
|
376
375
|
identityKey: joinState.context.space.invitation?.identityKey ?? null,
|
|
377
376
|
swarmKey: joinState.context.space.invitation?.swarmKey ?? null,
|
|
378
377
|
spaceKey: joinState.context.space.invitation?.spaceKey ?? null,
|
|
378
|
+
target: joinState.context.space.invitation?.target ?? null,
|
|
379
379
|
});
|
|
380
380
|
}, [joinState, propsOnDone]);
|
|
381
381
|
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from 'xstate';
|
|
16
16
|
|
|
17
17
|
import { log } from '@dxos/log';
|
|
18
|
-
import type
|
|
18
|
+
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
|
|
|
@@ -133,6 +133,12 @@ const getInvitationSubscribable = (
|
|
|
133
133
|
}
|
|
134
134
|
},
|
|
135
135
|
(error: any) => {
|
|
136
|
+
if (error instanceof AlreadyJoinedError) {
|
|
137
|
+
log('[already joined]', { Kind, error });
|
|
138
|
+
next({ type: `success${Kind}Invitation`, invitation: invitation.get() });
|
|
139
|
+
return complete?.();
|
|
140
|
+
}
|
|
141
|
+
|
|
136
142
|
log.error('[invitation errored]', { Kind, error });
|
|
137
143
|
next({ type: `fail${Kind}Invitation`, reason: 'error' });
|
|
138
144
|
return onError?.(error);
|
|
@@ -397,7 +403,7 @@ const joinMachine = createMachine<JoinMachineContext, JoinEvent>(
|
|
|
397
403
|
event.type.includes('Space') ? { ...context.space, invitation: event.invitation } : context.space,
|
|
398
404
|
}),
|
|
399
405
|
log: (context, event) => {
|
|
400
|
-
log
|
|
406
|
+
log('[transition]', {
|
|
401
407
|
event,
|
|
402
408
|
haloInvitation: context.halo.invitation,
|
|
403
409
|
spaceInvitation: context.space.invitation,
|
|
@@ -437,9 +443,8 @@ const useJoinMachine = (
|
|
|
437
443
|
({ halo }: JoinMachineContext) => {
|
|
438
444
|
if (halo.unredeemedCode) {
|
|
439
445
|
try {
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
);
|
|
446
|
+
const invitation = InvitationEncoder.decode(defaultCodeFromUrl('halo', halo.unredeemedCode));
|
|
447
|
+
const invitationObservable = client.halo.join(invitation);
|
|
443
448
|
return {
|
|
444
449
|
...halo,
|
|
445
450
|
invitationObservable,
|
|
@@ -461,20 +466,12 @@ const useJoinMachine = (
|
|
|
461
466
|
if (space.unredeemedCode) {
|
|
462
467
|
try {
|
|
463
468
|
const invitation = InvitationEncoder.decode(defaultCodeFromUrl('space', space.unredeemedCode));
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
} else {
|
|
471
|
-
const invitationObservable = client.spaces.join(invitation);
|
|
472
|
-
return {
|
|
473
|
-
...space,
|
|
474
|
-
invitationObservable,
|
|
475
|
-
invitationSubscribable: getInvitationSubscribable('Space', invitationObservable),
|
|
476
|
-
};
|
|
477
|
-
}
|
|
469
|
+
const invitationObservable = client.spaces.join(invitation);
|
|
470
|
+
return {
|
|
471
|
+
...space,
|
|
472
|
+
invitationObservable,
|
|
473
|
+
invitationSubscribable: getInvitationSubscribable('Space', invitationObservable),
|
|
474
|
+
};
|
|
478
475
|
} catch (err) {
|
|
479
476
|
log.error('Could not redeem space invitation code', err);
|
|
480
477
|
return space;
|