@dxos/shell 0.3.11-next.0fb359e → 0.3.11-next.ee2b64c
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 +38268 -0
- package/dist/bundle/shell.mjs.map +1 -0
- package/dist/bundle/shell.umd.js +216 -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/InvitationRescuer.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/styles/invitationStatusStyles.d.ts.map +1 -1
- package/dist/types/src/styles/invitationStatusStyles.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/InvitationRescuer.tsx +2 -2
- 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/styles/invitationStatusStyles.ts +6 -6
- package/src/testing/shell-manager.ts +40 -13
- package/src/translations/locales/en-US.ts +7 -1
|
@@ -8,6 +8,7 @@ import { faker } from '@faker-js/faker';
|
|
|
8
8
|
import React from 'react';
|
|
9
9
|
|
|
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: '
|
|
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 '
|
|
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
|
|