@dxos/shell 0.3.11-main.9c729f3 → 0.3.11-main.9fa973d
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 +36540 -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 +606 -375
- package/dist/lib/browser/react.mjs.map +4 -4
- 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/composites/Shell/Shell.d.ts.map +1 -1
- package/dist/types/src/composites/Shell/Shell.js +10 -4
- 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 +16 -3
- 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 +8 -7
- 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 +10 -1
- package/dist/types/src/panels/IdentityPanel/identityMachine.js.map +1 -1
- package/dist/types/src/panels/IdentityPanel/steps/AgentPanel.d.ts +19 -0
- package/dist/types/src/panels/IdentityPanel/steps/AgentPanel.d.ts.map +1 -0
- package/dist/types/src/panels/IdentityPanel/steps/AgentPanel.js +122 -0
- package/dist/types/src/panels/IdentityPanel/steps/AgentPanel.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 +8 -0
- 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/index.d.ts +2 -0
- package/dist/types/src/panels/IdentityPanel/steps/index.d.ts.map +1 -1
- package/dist/types/src/panels/IdentityPanel/steps/index.js +2 -0
- package/dist/types/src/panels/IdentityPanel/steps/index.js.map +1 -1
- 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.js +1 -1
- package/dist/types/src/panels/JoinPanel/joinMachine.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/translations/locales/en-US.d.ts +1 -0
- package/dist/types/src/translations/locales/en-US.d.ts.map +1 -1
- package/dist/types/src/translations/locales/en-US.js +2 -1
- package/dist/types/src/translations/locales/en-US.js.map +1 -1
- package/package.json +18 -17
- package/src/components/IdentityList/DeviceList.tsx +7 -5
- package/src/composites/Shell/Shell.tsx +9 -2
- package/src/panels/IdentityPanel/IdentityPanel.stories.tsx +44 -2
- package/src/panels/IdentityPanel/IdentityPanel.tsx +20 -4
- package/src/panels/IdentityPanel/IdentityPanelProps.ts +13 -7
- package/src/panels/IdentityPanel/identityMachine.ts +11 -2
- package/src/panels/IdentityPanel/steps/AgentPanel.tsx +165 -0
- package/src/panels/IdentityPanel/steps/DeviceManager.tsx +41 -0
- package/src/panels/IdentityPanel/steps/IdentityActionChooser.tsx +11 -1
- package/src/panels/IdentityPanel/steps/ProfileForm.tsx +1 -1
- package/src/panels/IdentityPanel/steps/index.ts +2 -0
- package/src/panels/JoinPanel/JoinPanel.tsx +2 -2
- package/src/panels/JoinPanel/joinMachine.ts +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/translations/locales/en-US.ts +2 -1
|
@@ -22,11 +22,13 @@ export const DeviceList = ({ devices, onSelect }: DeviceListProps) => {
|
|
|
22
22
|
{devices.map((device) => {
|
|
23
23
|
const identity = {
|
|
24
24
|
identityKey: device.deviceKey,
|
|
25
|
-
profile:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
profile: device.profile
|
|
26
|
+
? {
|
|
27
|
+
displayName: `${device.profile?.platform} (${device.profile?.platformVersion}) on ${
|
|
28
|
+
device.profile?.os
|
|
29
|
+
} (${device.profile?.osVersion}) ${device.profile?.architecture ?? 'Unknown'}-bit`,
|
|
30
|
+
}
|
|
31
|
+
: undefined,
|
|
30
32
|
};
|
|
31
33
|
return (
|
|
32
34
|
<IdentityListItem
|
|
@@ -13,10 +13,11 @@ import { JoinDialog } from '../JoinDialog';
|
|
|
13
13
|
import { SpaceDialog } from '../SpaceDialog';
|
|
14
14
|
|
|
15
15
|
export const Shell = ({ runtime, origin }: { runtime: ShellRuntime; origin: string }) => {
|
|
16
|
-
const [{ layout, invitationCode, spaceKey }, setLayout] = useState<LayoutRequest>({
|
|
16
|
+
const [{ layout, invitationCode, spaceKey, target }, setLayout] = useState<LayoutRequest>({
|
|
17
17
|
layout: runtime.layout,
|
|
18
18
|
invitationCode: runtime.invitationCode,
|
|
19
19
|
spaceKey: runtime.spaceKey,
|
|
20
|
+
target: runtime.target,
|
|
20
21
|
});
|
|
21
22
|
|
|
22
23
|
const space = useSpace(spaceKey);
|
|
@@ -69,6 +70,7 @@ export const Shell = ({ runtime, origin }: { runtime: ShellRuntime; origin: stri
|
|
|
69
70
|
return space ? (
|
|
70
71
|
<SpaceDialog
|
|
71
72
|
space={space}
|
|
73
|
+
target={target}
|
|
72
74
|
createInvitationUrl={(invitationCode) => `${origin}?spaceInvitationCode=${invitationCode}`}
|
|
73
75
|
onDone={async () => {
|
|
74
76
|
await runtime.setAppContext({ display: ShellDisplay.NONE });
|
|
@@ -82,7 +84,12 @@ export const Shell = ({ runtime, origin }: { runtime: ShellRuntime; origin: stri
|
|
|
82
84
|
<JoinDialog
|
|
83
85
|
initialInvitationCode={invitationCode}
|
|
84
86
|
onDone={async (result) => {
|
|
85
|
-
|
|
87
|
+
const target = result?.target ?? undefined;
|
|
88
|
+
await runtime.setAppContext({
|
|
89
|
+
display: ShellDisplay.NONE,
|
|
90
|
+
spaceKey: result?.spaceKey ?? undefined,
|
|
91
|
+
target,
|
|
92
|
+
});
|
|
86
93
|
runtime.setLayout({ layout: ShellLayout.DEFAULT });
|
|
87
94
|
}}
|
|
88
95
|
onExit={async () => {
|
|
@@ -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 } 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,7 @@ import {
|
|
|
17
17
|
type IdentityPanelProps,
|
|
18
18
|
} from './IdentityPanelProps';
|
|
19
19
|
import { useIdentityMachine } from './identityMachine';
|
|
20
|
-
import { IdentityActionChooser, ProfileForm } from './steps';
|
|
20
|
+
import { DeviceManager, IdentityActionChooser, ProfileForm, AgentForm } from './steps';
|
|
21
21
|
import { Viewport, Heading, CloseButton } from '../../components';
|
|
22
22
|
import { InvitationManager } from '../../steps';
|
|
23
23
|
|
|
@@ -53,13 +53,15 @@ export const IdentityPanelImpl = (props: IdentityPanelImplProps) => {
|
|
|
53
53
|
const { t } = useTranslation('os');
|
|
54
54
|
const title = useMemo(() => {
|
|
55
55
|
switch (activeView) {
|
|
56
|
-
case '
|
|
56
|
+
case 'agent manager':
|
|
57
|
+
return 'Manage Agent';
|
|
57
58
|
case 'device invitation manager':
|
|
58
|
-
return t('choose
|
|
59
|
+
return t('choose add device label');
|
|
59
60
|
default:
|
|
60
61
|
return t('identity heading');
|
|
61
62
|
}
|
|
62
63
|
}, [activeView, t]);
|
|
64
|
+
console.log('[IdentityPanelImpl]', { activeView });
|
|
63
65
|
return (
|
|
64
66
|
<DensityProvider density='fine'>
|
|
65
67
|
<IdentityHeading {...{ identity, titleId, title, onDone }} />
|
|
@@ -75,6 +77,9 @@ export const IdentityPanelImpl = (props: IdentityPanelImplProps) => {
|
|
|
75
77
|
invitationUrl={rest.createInvitationUrl(rest.invitationCode!)}
|
|
76
78
|
/>
|
|
77
79
|
</Viewport.View>
|
|
80
|
+
<Viewport.View id='device manager' classNames={viewStyles}>
|
|
81
|
+
<DeviceManager active={activeView === 'device manager'} {...rest} />
|
|
82
|
+
</Viewport.View>
|
|
78
83
|
<Viewport.View classNames={viewStyles} id='update profile form'>
|
|
79
84
|
<ProfileForm
|
|
80
85
|
send={rest.send}
|
|
@@ -83,6 +88,9 @@ export const IdentityPanelImpl = (props: IdentityPanelImplProps) => {
|
|
|
83
88
|
onUpdateProfile={onUpdateProfile}
|
|
84
89
|
/>
|
|
85
90
|
</Viewport.View>
|
|
91
|
+
<Viewport.View classNames={viewStyles} id='agent manager'>
|
|
92
|
+
<AgentForm send={rest.send} active={activeView === 'agent manager'} identity={identity} />
|
|
93
|
+
</Viewport.View>
|
|
86
94
|
{/* <Viewport.View id='signing out'></Viewport.View> */}
|
|
87
95
|
</Viewport.Views>
|
|
88
96
|
</Viewport.Root>
|
|
@@ -105,6 +113,7 @@ export const IdentityPanel = ({
|
|
|
105
113
|
}: IdentityPanelProps) => {
|
|
106
114
|
const titleId = useId('identityPanel__heading', propsTitleId);
|
|
107
115
|
const client = useClient();
|
|
116
|
+
const devices = useDevices();
|
|
108
117
|
const identity = useIdentity();
|
|
109
118
|
if (!identity) {
|
|
110
119
|
console.error('IdentityPanel rendered with no active identity.');
|
|
@@ -126,8 +135,12 @@ export const IdentityPanel = ({
|
|
|
126
135
|
return 'identity action chooser';
|
|
127
136
|
case identityState.matches('managingDeviceInvitation'):
|
|
128
137
|
return 'device invitation manager';
|
|
138
|
+
case identityState.matches('managingDevices'):
|
|
139
|
+
return 'device manager';
|
|
129
140
|
case [{ managingProfile: 'idle' }, { managingProfile: 'pending' }].some(identityState.matches):
|
|
130
141
|
return 'update profile form';
|
|
142
|
+
case [{ managingAgent: 'idle' }, { managingAgent: 'pending' }].some(identityState.matches):
|
|
143
|
+
return 'agent manager';
|
|
131
144
|
// case identityState.matches('signingOut'):
|
|
132
145
|
// return 'identity exit';
|
|
133
146
|
default:
|
|
@@ -135,6 +148,8 @@ export const IdentityPanel = ({
|
|
|
135
148
|
}
|
|
136
149
|
}, [identityState]);
|
|
137
150
|
|
|
151
|
+
console.log({ activeView, identityState });
|
|
152
|
+
|
|
138
153
|
const onUpdateProfile = async (profile: NonNullable<Identity['profile']>) => {
|
|
139
154
|
identitySend({ type: 'updateProfile' });
|
|
140
155
|
await client.halo.updateProfile(profile);
|
|
@@ -143,6 +158,7 @@ export const IdentityPanel = ({
|
|
|
143
158
|
const implProps = {
|
|
144
159
|
...props,
|
|
145
160
|
identity,
|
|
161
|
+
devices,
|
|
146
162
|
activeView,
|
|
147
163
|
send: identitySend,
|
|
148
164
|
titleId,
|
|
@@ -2,19 +2,25 @@
|
|
|
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
13
|
|
|
14
14
|
export type IdentityPanelImplProps = {
|
|
15
15
|
titleId: string;
|
|
16
|
-
activeView:
|
|
16
|
+
activeView:
|
|
17
|
+
| 'device manager'
|
|
18
|
+
| 'agent manager'
|
|
19
|
+
| 'update profile form'
|
|
20
|
+
| 'device invitation manager'
|
|
21
|
+
| 'identity action chooser';
|
|
17
22
|
identity: Identity;
|
|
23
|
+
devices: Device[];
|
|
18
24
|
onUpdateProfile?: (profile: NonNullable<Identity['profile']>) => Promise<void>;
|
|
19
25
|
createInvitationUrl: (invitationCode: string) => string;
|
|
20
26
|
send?: (event: SingleOrArray<Event<IdentityEvent>>) => void;
|
|
@@ -19,7 +19,7 @@ type IdentityMachineContext = {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
type IdentityChooseActionEvent = {
|
|
22
|
-
type: 'chooseDevices' | 'chooseProfile' | /* 'chooseSignOut' | */ 'unchooseAction';
|
|
22
|
+
type: 'chooseDevices' | 'chooseAgent' | 'chooseProfile' | /* 'chooseSignOut' | */ '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: {
|
|
@@ -73,10 +81,11 @@ const identityMachine = createMachine<IdentityMachineContext, IdentityEvent>(
|
|
|
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' },
|
|
88
|
+
chooseAgent: { target: '.managingAgent', actions: 'log' },
|
|
80
89
|
// chooseSignOut: { target: '.signingOut', actions: 'log' },
|
|
81
90
|
},
|
|
82
91
|
},
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useState, useCallback, useEffect } from 'react';
|
|
6
|
+
import { type Event, type SingleOrArray } from 'xstate';
|
|
7
|
+
|
|
8
|
+
import { invariant } from '@dxos/invariant';
|
|
9
|
+
import { useClient, useAgentHostingProviderClient } from '@dxos/react-client';
|
|
10
|
+
import { type Identity, useHaloInvitations } from '@dxos/react-client/halo';
|
|
11
|
+
import { Invitation, InvitationEncoder } from '@dxos/react-client/invitations';
|
|
12
|
+
import { useTranslation } from '@dxos/react-ui';
|
|
13
|
+
|
|
14
|
+
import { Action, Actions, StepHeading, Input } from '../../../components';
|
|
15
|
+
import { type IdentityPanelStepProps } from '../IdentityPanelProps';
|
|
16
|
+
import { type IdentityEvent } from '../identityMachine';
|
|
17
|
+
|
|
18
|
+
export interface AgentFormProps extends Omit<IdentityPanelStepProps, 'send'> {
|
|
19
|
+
send?: (event: SingleOrArray<Event<IdentityEvent>>) => void;
|
|
20
|
+
onAgentCreate?: () => Promise<void>;
|
|
21
|
+
onAgentDestroy?: () => Promise<void>;
|
|
22
|
+
identity?: Identity;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type AgentFormImplProps = AgentFormProps & {
|
|
26
|
+
agentStatus?: string;
|
|
27
|
+
agentActive?: boolean;
|
|
28
|
+
agentProviderDisabled?: boolean;
|
|
29
|
+
validationMessage?: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const AgentForm = (props: AgentFormProps) => {
|
|
33
|
+
const { identity } = props;
|
|
34
|
+
// const { t } = useTranslation('os');
|
|
35
|
+
const [validationMessage, setValidationMessage] = useState('');
|
|
36
|
+
const [agentStatus, setAgentStatus] = useState('');
|
|
37
|
+
const [agentActive, setAgentActive] = useState(false);
|
|
38
|
+
const client = useClient();
|
|
39
|
+
const invitations = useHaloInvitations();
|
|
40
|
+
const agentHostingProviderClient = useAgentHostingProviderClient(client.config);
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const fetchAgentStatus = async () => {
|
|
44
|
+
// TODO(nf): if agent exists, subscribe to updates instead of oneshot status
|
|
45
|
+
if (agentHostingProviderClient) {
|
|
46
|
+
const agentStatus = await agentHostingProviderClient?.getAgent(identity?.identityKey.truncate() ?? 'foo');
|
|
47
|
+
if (agentStatus) {
|
|
48
|
+
setAgentActive(true);
|
|
49
|
+
setAgentStatus(agentStatus);
|
|
50
|
+
} else {
|
|
51
|
+
setAgentActive(false);
|
|
52
|
+
setValidationMessage('No agent deployed via provider.');
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
setValidationMessage('No agent provider configured.');
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
void fetchAgentStatus();
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
const onAgentCreateEvent = useCallback(async (invitation: Invitation) => {
|
|
62
|
+
const invitationCode = InvitationEncoder.encode(invitation);
|
|
63
|
+
if (invitation.state === Invitation.State.CONNECTING) {
|
|
64
|
+
console.log(JSON.stringify({ invitationCode, authCode: invitation.authCode }));
|
|
65
|
+
invariant(identity, 'Identity not found');
|
|
66
|
+
if (agentHostingProviderClient) {
|
|
67
|
+
try {
|
|
68
|
+
const res = await agentHostingProviderClient?.createAgent(invitationCode, identity.identityKey.truncate());
|
|
69
|
+
// TODO(nf): human-consumable response from API
|
|
70
|
+
setAgentStatus(res);
|
|
71
|
+
setAgentActive(true);
|
|
72
|
+
setValidationMessage('');
|
|
73
|
+
} catch (err: any) {
|
|
74
|
+
setValidationMessage(`error creating agent: ${err.message}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}, []);
|
|
79
|
+
|
|
80
|
+
const handleAgentCreate = async () => {
|
|
81
|
+
invitations.forEach((invitation) => invitation.cancel());
|
|
82
|
+
|
|
83
|
+
// TODO(nf): do this work in the hosting provider client?
|
|
84
|
+
const invitation = client.halo.share({ type: Invitation.Type.MULTIUSE, authMethod: Invitation.AuthMethod.NONE });
|
|
85
|
+
|
|
86
|
+
invitation.subscribe(onAgentCreateEvent);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const handleAgentDestroy = async () => {
|
|
90
|
+
const identity = client.halo.identity.get();
|
|
91
|
+
invariant(identity, 'Identity not found');
|
|
92
|
+
try {
|
|
93
|
+
await agentHostingProviderClient?.destroyAgent(identity.identityKey.truncate());
|
|
94
|
+
setValidationMessage('No agent deployed via provider.');
|
|
95
|
+
setAgentStatus('');
|
|
96
|
+
setAgentActive(false);
|
|
97
|
+
} catch (err: any) {
|
|
98
|
+
setValidationMessage(`error destroying agent: ${err.message}`);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<AgentFormImpl
|
|
104
|
+
{...props}
|
|
105
|
+
onAgentCreate={handleAgentCreate}
|
|
106
|
+
onAgentDestroy={handleAgentDestroy}
|
|
107
|
+
validationMessage={validationMessage}
|
|
108
|
+
agentStatus={agentStatus}
|
|
109
|
+
agentActive={agentActive}
|
|
110
|
+
agentProviderDisabled={!agentHostingProviderClient}
|
|
111
|
+
/>
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// TODO(zhenyasav): impl shouldn't need send()
|
|
116
|
+
export const AgentFormImpl = (props: AgentFormImplProps) => {
|
|
117
|
+
const {
|
|
118
|
+
active,
|
|
119
|
+
onAgentCreate,
|
|
120
|
+
onAgentDestroy,
|
|
121
|
+
send,
|
|
122
|
+
validationMessage,
|
|
123
|
+
agentStatus,
|
|
124
|
+
agentActive,
|
|
125
|
+
agentProviderDisabled,
|
|
126
|
+
} = props;
|
|
127
|
+
const disabled = !active;
|
|
128
|
+
const { t } = useTranslation('os');
|
|
129
|
+
return (
|
|
130
|
+
<>
|
|
131
|
+
<div role='none' className='grow flex flex-col justify-center'>
|
|
132
|
+
<Input
|
|
133
|
+
{...{ validationMessage }}
|
|
134
|
+
label={<StepHeading>Agent Status</StepHeading>}
|
|
135
|
+
disabled={disabled}
|
|
136
|
+
data-testid='agent-status'
|
|
137
|
+
value={agentStatus}
|
|
138
|
+
/>
|
|
139
|
+
</div>
|
|
140
|
+
<Actions>
|
|
141
|
+
<Action
|
|
142
|
+
disabled={agentActive || agentProviderDisabled}
|
|
143
|
+
onClick={() => onAgentCreate?.()}
|
|
144
|
+
data-testid={'create-agent'}
|
|
145
|
+
>
|
|
146
|
+
Create Agent
|
|
147
|
+
</Action>
|
|
148
|
+
<Action
|
|
149
|
+
disabled={!agentActive || agentProviderDisabled}
|
|
150
|
+
onClick={() => onAgentDestroy?.()}
|
|
151
|
+
data-testid={'destroy-agent'}
|
|
152
|
+
>
|
|
153
|
+
Destroy Agent
|
|
154
|
+
</Action>
|
|
155
|
+
<Action
|
|
156
|
+
disabled={disabled}
|
|
157
|
+
onClick={() => send?.({ type: 'unchooseAction' })}
|
|
158
|
+
data-testid={'update-profile-form-back'}
|
|
159
|
+
>
|
|
160
|
+
{t('back label')}
|
|
161
|
+
</Action>
|
|
162
|
+
</Actions>
|
|
163
|
+
</>
|
|
164
|
+
);
|
|
165
|
+
};
|
|
@@ -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>
|
|
@@ -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');
|
|
@@ -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
|
|
|
@@ -397,7 +397,7 @@ const joinMachine = createMachine<JoinMachineContext, JoinEvent>(
|
|
|
397
397
|
event.type.includes('Space') ? { ...context.space, invitation: event.invitation } : context.space,
|
|
398
398
|
}),
|
|
399
399
|
log: (context, event) => {
|
|
400
|
-
log
|
|
400
|
+
log('[transition]', {
|
|
401
401
|
event,
|
|
402
402
|
haloInvitation: context.halo.invitation,
|
|
403
403
|
spaceInvitation: context.space.invitation,
|
|
@@ -129,6 +129,7 @@ export const SpaceManagerWithEvenMoreInvites = () => {
|
|
|
129
129
|
identityKey = PublicKey.random(),
|
|
130
130
|
swarmKey,
|
|
131
131
|
spaceKey = PublicKey.random(),
|
|
132
|
+
target = null,
|
|
132
133
|
} = invitation;
|
|
133
134
|
return (
|
|
134
135
|
<InvitationListItemImpl
|
|
@@ -140,7 +141,7 @@ export const SpaceManagerWithEvenMoreInvites = () => {
|
|
|
140
141
|
invitationCode: id,
|
|
141
142
|
authCode: state === Invitation.State.READY_FOR_AUTHENTICATION ? '123414' : undefined,
|
|
142
143
|
id,
|
|
143
|
-
result: { identityKey, swarmKey, spaceKey },
|
|
144
|
+
result: { identityKey, swarmKey, spaceKey, target },
|
|
144
145
|
cancel: () => {},
|
|
145
146
|
authenticate: async (authCode: string) => {},
|
|
146
147
|
connect: () => {},
|
|
@@ -39,17 +39,19 @@ export const SpacePanelImpl = (props: SpacePanelImplProps) => {
|
|
|
39
39
|
titleId,
|
|
40
40
|
activeView,
|
|
41
41
|
space,
|
|
42
|
+
target,
|
|
42
43
|
SpaceManager: SpaceManagerComponent = SpaceManager,
|
|
43
44
|
InvitationManager: InvitationManagerComponent = InvitationManager,
|
|
44
45
|
...rest
|
|
45
46
|
} = props;
|
|
47
|
+
|
|
46
48
|
return (
|
|
47
49
|
<DensityProvider density='fine'>
|
|
48
50
|
<SpacePanelHeading {...rest} {...{ titleId, space }} />
|
|
49
51
|
<Viewport.Root activeView={activeView}>
|
|
50
52
|
<Viewport.Views>
|
|
51
53
|
<Viewport.View id='space manager' classNames={stepStyles}>
|
|
52
|
-
<SpaceManagerComponent active={activeView === 'space manager'} space={space} {...rest} />
|
|
54
|
+
<SpaceManagerComponent active={activeView === 'space manager'} space={space} target={target} {...rest} />
|
|
53
55
|
</Viewport.View>
|
|
54
56
|
<Viewport.View id='space invitation manager' classNames={stepStyles}>
|
|
55
57
|
<InvitationManagerComponent
|
|
@@ -115,6 +117,7 @@ export const SpacePanel = ({
|
|
|
115
117
|
};
|
|
116
118
|
|
|
117
119
|
return spaceState.context.invitation ? (
|
|
120
|
+
// TODO(wittjosiah): Unused? Remove?
|
|
118
121
|
<SpacePanelWithInvitationImpl {...implProps} invitation={spaceState.context.invitation} />
|
|
119
122
|
) : (
|
|
120
123
|
<SpacePanelImpl {...implProps} />
|
|
@@ -16,6 +16,7 @@ export type SpacePanelImplProps = {
|
|
|
16
16
|
titleId: string;
|
|
17
17
|
activeView: string;
|
|
18
18
|
send: (event: SingleOrArray<Event<any>>) => void;
|
|
19
|
+
target?: string;
|
|
19
20
|
createInvitationUrl: (invitationCode: string) => string;
|
|
20
21
|
space: ErsatzSpace | Space;
|
|
21
22
|
invitationUrl?: string;
|