@dxos/shell 0.4.7-main.7b31910 → 0.4.7-main.83fe62c
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 +69914 -27222
- package/dist/bundle/shell.mjs.map +1 -1
- package/dist/bundle/shell.umd.js +796 -80
- package/dist/bundle/shell.umd.js.map +1 -1
- package/dist/bundle/style.css +1 -1
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/react.mjs +881 -669
- package/dist/lib/browser/react.mjs.map +4 -4
- package/dist/types/src/components/EmojiPicker.d.ts +11 -0
- package/dist/types/src/components/EmojiPicker.d.ts.map +1 -0
- package/dist/types/src/components/EmojiPicker.js +79 -0
- package/dist/types/src/components/EmojiPicker.js.map +1 -0
- package/dist/types/src/components/HuePicker.d.ts +11 -0
- package/dist/types/src/components/HuePicker.d.ts.map +1 -0
- package/dist/types/src/components/HuePicker.js +56 -0
- package/dist/types/src/components/HuePicker.js.map +1 -0
- package/dist/types/src/components/IdentityList/IdentityListItem.d.ts.map +1 -1
- package/dist/types/src/components/IdentityList/IdentityListItem.js +5 -5
- package/dist/types/src/components/IdentityList/IdentityListItem.js.map +1 -1
- package/dist/types/src/components/InvitationList/InvitationListItem.d.ts.map +1 -1
- package/dist/types/src/components/InvitationList/InvitationListItem.js +17 -6
- package/dist/types/src/components/InvitationList/InvitationListItem.js.map +1 -1
- package/dist/types/src/components/SpaceListItem.d.ts.map +1 -1
- package/dist/types/src/components/SpaceListItem.js +2 -3
- package/dist/types/src/components/SpaceListItem.js.map +1 -1
- package/dist/types/src/components/index.d.ts +2 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/index.js +2 -0
- package/dist/types/src/components/index.js.map +1 -1
- package/dist/types/src/panels/IdentityPanel/IdentityPanel.d.ts.map +1 -1
- package/dist/types/src/panels/IdentityPanel/IdentityPanel.js +7 -6
- package/dist/types/src/panels/IdentityPanel/IdentityPanel.js.map +1 -1
- package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.d.ts.map +1 -1
- package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.js +3 -1
- package/dist/types/src/panels/IdentityPanel/steps/IdentityActionChooser.js.map +1 -1
- package/dist/types/src/panels/IdentityPanel/steps/ProfileForm.d.ts.map +1 -1
- package/dist/types/src/panels/IdentityPanel/steps/ProfileForm.js +12 -3
- package/dist/types/src/panels/IdentityPanel/steps/ProfileForm.js.map +1 -1
- package/dist/types/src/panels/IdentityPanel/useAgentHandlers.d.ts.map +1 -1
- package/dist/types/src/panels/IdentityPanel/useAgentHandlers.js +2 -1
- package/dist/types/src/panels/IdentityPanel/useAgentHandlers.js.map +1 -1
- package/dist/types/src/panels/JoinPanel/steps/IdentityAdded.d.ts.map +1 -1
- package/dist/types/src/panels/JoinPanel/steps/IdentityAdded.js +8 -7
- package/dist/types/src/panels/JoinPanel/steps/IdentityAdded.js.map +1 -1
- package/dist/types/src/panels/JoinPanel/steps/InvitationAuthenticator.js +2 -2
- package/dist/types/src/panels/JoinPanel/steps/InvitationAuthenticator.js.map +1 -1
- package/dist/types/src/panels/Panels.stories.d.ts.map +1 -1
- package/dist/types/src/panels/Panels.stories.js +1 -3
- package/dist/types/src/panels/Panels.stories.js.map +1 -1
- package/dist/types/src/panels/SpacePanel/steps/SpaceManager.d.ts.map +1 -1
- package/dist/types/src/panels/SpacePanel/steps/SpaceManager.js +18 -1
- package/dist/types/src/panels/SpacePanel/steps/SpaceManager.js.map +1 -1
- package/dist/types/src/steps/InvitationManager.d.ts.map +1 -1
- package/dist/types/src/steps/InvitationManager.js +5 -4
- package/dist/types/src/steps/InvitationManager.js.map +1 -1
- package/dist/types/src/stories/Invitations.e2e-stories.d.ts.map +1 -1
- package/dist/types/src/stories/Invitations.e2e-stories.js +3 -2
- package/dist/types/src/stories/Invitations.e2e-stories.js.map +1 -1
- package/dist/types/src/translations/locales/en-US.d.ts +21 -0
- package/dist/types/src/translations/locales/en-US.d.ts.map +1 -1
- package/dist/types/src/translations/locales/en-US.js +21 -0
- package/dist/types/src/translations/locales/en-US.js.map +1 -1
- package/dist/types/src/util/index.d.ts +0 -1
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/src/util/index.js +0 -1
- package/dist/types/src/util/index.js.map +1 -1
- package/package.json +24 -22
- package/src/components/EmojiPicker.tsx +82 -0
- package/src/components/HuePicker.tsx +87 -0
- package/src/components/IdentityList/IdentityListItem.tsx +9 -5
- package/src/components/InvitationList/InvitationListItem.tsx +27 -13
- package/src/components/SpaceListItem.tsx +4 -5
- package/src/components/index.ts +2 -0
- package/src/panels/IdentityPanel/IdentityPanel.tsx +6 -5
- package/src/panels/IdentityPanel/steps/IdentityActionChooser.tsx +3 -1
- package/src/panels/IdentityPanel/steps/ProfileForm.tsx +29 -7
- package/src/panels/IdentityPanel/useAgentHandlers.ts +2 -1
- package/src/panels/JoinPanel/steps/IdentityAdded.tsx +7 -7
- package/src/panels/JoinPanel/steps/InvitationAuthenticator.tsx +2 -2
- package/src/panels/Panels.stories.tsx +1 -3
- package/src/panels/SpacePanel/steps/SpaceManager.tsx +18 -1
- package/src/steps/InvitationManager.tsx +4 -3
- package/src/stories/Invitations.e2e-stories.tsx +3 -2
- package/src/translations/locales/en-US.ts +21 -0
- package/src/util/index.ts +0 -1
- package/dist/types/src/util/toEmoji.d.ts +0 -2
- package/dist/types/src/util/toEmoji.d.ts.map +0 -1
- package/dist/types/src/util/toEmoji.js +0 -26
- package/dist/types/src/util/toEmoji.js.map +0 -1
- package/src/util/toEmoji.ts +0 -22
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
import emojiData from '@emoji-mart/data';
|
|
5
|
+
import EmojiMart from '@emoji-mart/react';
|
|
6
|
+
import { ArrowCounterClockwise, CaretDown } from '@phosphor-icons/react';
|
|
7
|
+
import { useControllableState } from '@radix-ui/react-use-controllable-state';
|
|
8
|
+
import React, { type Dispatch, type SetStateAction, useState } from 'react';
|
|
9
|
+
|
|
10
|
+
import { Button, Popover, useTranslation, Tooltip, type ButtonProps } from '@dxos/react-ui';
|
|
11
|
+
import { getSize } from '@dxos/react-ui-theme';
|
|
12
|
+
|
|
13
|
+
export type EmojiPickerProps = {
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
defaultEmoji?: string;
|
|
16
|
+
emoji?: string;
|
|
17
|
+
onChangeEmoji?: Dispatch<SetStateAction<string>>;
|
|
18
|
+
onClickClear?: ButtonProps['onClick'];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const EmojiPicker = ({ disabled, defaultEmoji, emoji, onChangeEmoji, onClickClear }: EmojiPickerProps) => {
|
|
22
|
+
const { t } = useTranslation('os');
|
|
23
|
+
|
|
24
|
+
const [emojiValue, setEmojiValue] = useControllableState<string>({
|
|
25
|
+
prop: emoji,
|
|
26
|
+
onChange: onChangeEmoji,
|
|
27
|
+
defaultProp: defaultEmoji,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const [emojiPickerOpen, setEmojiPickerOpen] = useState<boolean>(false);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
<Popover.Root open={emojiPickerOpen} onOpenChange={setEmojiPickerOpen}>
|
|
35
|
+
<Popover.Trigger asChild>
|
|
36
|
+
<Button variant='ghost' classNames='gap-2 text-2xl plb-1' disabled={disabled}>
|
|
37
|
+
<span className='sr-only'>{t('select emoji label')}</span>
|
|
38
|
+
<span className='grow pis-14'>{emojiValue}</span>
|
|
39
|
+
<CaretDown className={getSize(4)} />
|
|
40
|
+
</Button>
|
|
41
|
+
</Popover.Trigger>
|
|
42
|
+
<Popover.Content
|
|
43
|
+
side='right'
|
|
44
|
+
onKeyDownCapture={(event) => {
|
|
45
|
+
if (event.key === 'Escape') {
|
|
46
|
+
event.stopPropagation();
|
|
47
|
+
setEmojiPickerOpen(false);
|
|
48
|
+
}
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
<EmojiMart
|
|
52
|
+
data={emojiData}
|
|
53
|
+
onEmojiSelect={({ native }: { native?: string }) => {
|
|
54
|
+
if (native) {
|
|
55
|
+
setEmojiValue(native);
|
|
56
|
+
setEmojiPickerOpen(false);
|
|
57
|
+
}
|
|
58
|
+
}}
|
|
59
|
+
autoFocus={true}
|
|
60
|
+
maxFrequentRows={0}
|
|
61
|
+
noCountryFlags={true}
|
|
62
|
+
/>
|
|
63
|
+
<Popover.Arrow />
|
|
64
|
+
</Popover.Content>
|
|
65
|
+
</Popover.Root>
|
|
66
|
+
<Tooltip.Root>
|
|
67
|
+
<Tooltip.Trigger asChild>
|
|
68
|
+
<Button variant='ghost' onClick={onClickClear} disabled={disabled}>
|
|
69
|
+
<span className='sr-only'>{t('clear label')}</span>
|
|
70
|
+
<ArrowCounterClockwise />
|
|
71
|
+
</Button>
|
|
72
|
+
</Tooltip.Trigger>
|
|
73
|
+
<Tooltip.Portal>
|
|
74
|
+
<Tooltip.Content style={{ zIndex: 70 }} side='right'>
|
|
75
|
+
{t('clear label')}
|
|
76
|
+
<Tooltip.Arrow />
|
|
77
|
+
</Tooltip.Content>
|
|
78
|
+
</Tooltip.Portal>
|
|
79
|
+
</Tooltip.Root>
|
|
80
|
+
</>
|
|
81
|
+
);
|
|
82
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
import { ArrowCounterClockwise, CaretDown, Check } from '@phosphor-icons/react';
|
|
5
|
+
import { useControllableState } from '@radix-ui/react-use-controllable-state';
|
|
6
|
+
import React, { type Dispatch, type SetStateAction } from 'react';
|
|
7
|
+
|
|
8
|
+
import { Button, useTranslation, Tooltip, DropdownMenu, useThemeContext, type ButtonProps } from '@dxos/react-ui';
|
|
9
|
+
import { getSize, hueTokenThemes } from '@dxos/react-ui-theme';
|
|
10
|
+
|
|
11
|
+
const HuePreview = ({ hue }: { hue: string }) => {
|
|
12
|
+
const { tx } = useThemeContext();
|
|
13
|
+
return (
|
|
14
|
+
<svg width={12} height={12} viewBox='0 0 12 12'>
|
|
15
|
+
<rect x={0} y={0} width={12} height={12} className={tx('hue.fill', 'select--hue__preview', { hue })} />
|
|
16
|
+
</svg>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type HuePickerProps = {
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
defaultHue?: string;
|
|
23
|
+
hue?: string;
|
|
24
|
+
onChangeHue?: Dispatch<SetStateAction<string>>;
|
|
25
|
+
onClickClear?: ButtonProps['onClick'];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const HuePicker = ({ disabled, hue, onChangeHue, defaultHue, onClickClear }: HuePickerProps) => {
|
|
29
|
+
const { t } = useTranslation('os');
|
|
30
|
+
|
|
31
|
+
const [hueValue, setHueValue] = useControllableState<string>({
|
|
32
|
+
prop: hue,
|
|
33
|
+
onChange: onChangeHue,
|
|
34
|
+
defaultProp: defaultHue,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<>
|
|
39
|
+
<DropdownMenu.Root modal={false}>
|
|
40
|
+
<DropdownMenu.Trigger asChild>
|
|
41
|
+
<Button variant='ghost' classNames='gap-2 plb-1' disabled={disabled}>
|
|
42
|
+
<span className='sr-only'>{t('select hue label')}</span>
|
|
43
|
+
<div role='none' className='pis-14 grow flex items-center justify-center gap-2'>
|
|
44
|
+
<HuePreview hue={hueValue!} />
|
|
45
|
+
<span>{t(`${hueValue} label`)}</span>
|
|
46
|
+
</div>
|
|
47
|
+
<CaretDown className={getSize(4)} />
|
|
48
|
+
</Button>
|
|
49
|
+
</DropdownMenu.Trigger>
|
|
50
|
+
<DropdownMenu.Content side='right'>
|
|
51
|
+
<DropdownMenu.Viewport>
|
|
52
|
+
{Object.keys(hueTokenThemes).map((hue) => {
|
|
53
|
+
return (
|
|
54
|
+
<DropdownMenu.CheckboxItem
|
|
55
|
+
key={hue}
|
|
56
|
+
checked={hue === hueValue}
|
|
57
|
+
onCheckedChange={() => setHueValue(hue)}
|
|
58
|
+
>
|
|
59
|
+
<HuePreview hue={hue} />
|
|
60
|
+
<span className='grow'>{t(`${hue} label`)}</span>
|
|
61
|
+
<DropdownMenu.ItemIndicator>
|
|
62
|
+
<Check />
|
|
63
|
+
</DropdownMenu.ItemIndicator>
|
|
64
|
+
</DropdownMenu.CheckboxItem>
|
|
65
|
+
);
|
|
66
|
+
})}
|
|
67
|
+
</DropdownMenu.Viewport>
|
|
68
|
+
<DropdownMenu.Arrow />
|
|
69
|
+
</DropdownMenu.Content>
|
|
70
|
+
</DropdownMenu.Root>
|
|
71
|
+
<Tooltip.Root>
|
|
72
|
+
<Tooltip.Trigger asChild>
|
|
73
|
+
<Button variant='ghost' onClick={onClickClear} disabled={disabled}>
|
|
74
|
+
<span className='sr-only'>{t('clear label')}</span>
|
|
75
|
+
<ArrowCounterClockwise />
|
|
76
|
+
</Button>
|
|
77
|
+
</Tooltip.Trigger>
|
|
78
|
+
<Tooltip.Portal>
|
|
79
|
+
<Tooltip.Content style={{ zIndex: 70 }} side='right'>
|
|
80
|
+
{t('clear label')}
|
|
81
|
+
<Tooltip.Arrow />
|
|
82
|
+
</Tooltip.Content>
|
|
83
|
+
</Tooltip.Portal>
|
|
84
|
+
</Tooltip.Root>
|
|
85
|
+
</>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
@@ -7,8 +7,9 @@ import React, { type ComponentPropsWithoutRef, forwardRef } from 'react';
|
|
|
7
7
|
import { generateName } from '@dxos/display-name';
|
|
8
8
|
import { SpaceMember } from '@dxos/react-client/echo';
|
|
9
9
|
import { type Identity } from '@dxos/react-client/halo';
|
|
10
|
-
import { ListItem, Avatar,
|
|
10
|
+
import { ListItem, Avatar, useId, type ThemedClassName } from '@dxos/react-ui';
|
|
11
11
|
import { mx } from '@dxos/react-ui-theme';
|
|
12
|
+
import { keyToFallback } from '@dxos/util';
|
|
12
13
|
|
|
13
14
|
type IdentityListItemProps = {
|
|
14
15
|
identity: Identity;
|
|
@@ -20,9 +21,8 @@ export const IdentityListItem = forwardRef<
|
|
|
20
21
|
HTMLLIElement,
|
|
21
22
|
ThemedClassName<ComponentPropsWithoutRef<'li'>> & IdentityListItemProps
|
|
22
23
|
>(({ identity, presence, onClick, classNames, ...props }, forwardedRef) => {
|
|
23
|
-
const fallbackValue = identity.identityKey
|
|
24
|
+
const fallbackValue = keyToFallback(identity.identityKey);
|
|
24
25
|
const labelId = useId('identityListItem__label');
|
|
25
|
-
const jdenticon = useJdenticonHref(fallbackValue ?? '', 12);
|
|
26
26
|
const displayName = identity.profile?.displayName ?? generateName(identity.identityKey.toHex());
|
|
27
27
|
return (
|
|
28
28
|
<ListItem.Root
|
|
@@ -33,9 +33,13 @@ export const IdentityListItem = forwardRef<
|
|
|
33
33
|
labelId={labelId}
|
|
34
34
|
ref={forwardedRef}
|
|
35
35
|
>
|
|
36
|
-
<Avatar.Root
|
|
36
|
+
<Avatar.Root
|
|
37
|
+
status={presence === SpaceMember.PresenceState.ONLINE ? 'active' : 'inactive'}
|
|
38
|
+
labelId={labelId}
|
|
39
|
+
hue={identity.profile?.data?.hue || fallbackValue.hue}
|
|
40
|
+
>
|
|
37
41
|
<Avatar.Frame classNames='place-self-center'>
|
|
38
|
-
<Avatar.Fallback
|
|
42
|
+
<Avatar.Fallback text={identity.profile?.data?.emoji || fallbackValue.emoji} />
|
|
39
43
|
</Avatar.Frame>
|
|
40
44
|
<Avatar.Label classNames='text-sm truncate pli-2'>{displayName}</Avatar.Label>
|
|
41
45
|
</Avatar.Root>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
import { X } from '@phosphor-icons/react';
|
|
5
|
+
import { formatDistanceToNow } from 'date-fns';
|
|
5
6
|
import React, { type ComponentPropsWithoutRef, useCallback } from 'react';
|
|
6
7
|
|
|
7
8
|
import {
|
|
@@ -21,9 +22,9 @@ import {
|
|
|
21
22
|
type ThemedClassName,
|
|
22
23
|
} from '@dxos/react-ui';
|
|
23
24
|
import { focusRing, getSize, mx } from '@dxos/react-ui-theme';
|
|
25
|
+
import { hexToEmoji } from '@dxos/util';
|
|
24
26
|
|
|
25
27
|
import { type SharedInvitationListProps } from './InvitationListProps';
|
|
26
|
-
import { toEmoji } from '../../util';
|
|
27
28
|
import { AuthCode } from '../AuthCode';
|
|
28
29
|
import { CopyButtonIconOnly } from '../Clipboard';
|
|
29
30
|
|
|
@@ -111,6 +112,10 @@ export const InvitationListItemImpl = ({
|
|
|
111
112
|
|
|
112
113
|
const invitationUrl = invitationCode && createInvitationUrl(invitationCode);
|
|
113
114
|
const invitationId = invitation?.get().invitationId;
|
|
115
|
+
const invitationHasLifetime = invitation?.get().lifetime;
|
|
116
|
+
const invitationTimeLeft = invitation?.expiry
|
|
117
|
+
? formatDistanceToNow(invitation?.expiry, { addSuffix: true })
|
|
118
|
+
: undefined;
|
|
114
119
|
|
|
115
120
|
const avatarAnimation = [
|
|
116
121
|
Invitation.State.INIT,
|
|
@@ -151,7 +156,7 @@ export const InvitationListItemImpl = ({
|
|
|
151
156
|
<Avatar.Root {...avatarProps} animation={avatarAnimation} status={avatarStatus}>
|
|
152
157
|
<Tooltip.Trigger asChild>
|
|
153
158
|
<Avatar.Frame tabIndex={0} classNames={[focusRing, 'relative rounded-full place-self-center']}>
|
|
154
|
-
<Avatar.Fallback text={
|
|
159
|
+
<Avatar.Fallback text={hexToEmoji(invitationId)} />
|
|
155
160
|
</Avatar.Frame>
|
|
156
161
|
</Tooltip.Trigger>
|
|
157
162
|
</Avatar.Root>
|
|
@@ -163,17 +168,26 @@ export const InvitationListItemImpl = ({
|
|
|
163
168
|
</Tooltip.Portal>
|
|
164
169
|
</Tooltip.Root>
|
|
165
170
|
{showShare && invitationUrl ? (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
171
|
+
<Tooltip.Root>
|
|
172
|
+
<>
|
|
173
|
+
<Tooltip.Trigger asChild>
|
|
174
|
+
<Button
|
|
175
|
+
variant='ghost'
|
|
176
|
+
classNames='grow justify-start font-medium'
|
|
177
|
+
onClick={() => send({ type: 'selectInvitation', invitation })}
|
|
178
|
+
data-testid='show-qrcode'
|
|
179
|
+
>
|
|
180
|
+
<span>{t('open share panel label')}</span>
|
|
181
|
+
</Button>
|
|
182
|
+
</Tooltip.Trigger>
|
|
183
|
+
<CopyButtonIconOnly variant='ghost' value={invitationUrl} />
|
|
184
|
+
</>
|
|
185
|
+
<Tooltip.Portal>
|
|
186
|
+
<Tooltip.Content side='left' classNames='z-[70]'>
|
|
187
|
+
{invitationHasLifetime && <span>Expires {invitationTimeLeft}</span>}
|
|
188
|
+
</Tooltip.Content>
|
|
189
|
+
</Tooltip.Portal>
|
|
190
|
+
</Tooltip.Root>
|
|
177
191
|
) : showAuthCode ? (
|
|
178
192
|
<AuthCode code={authCode} classNames='grow' />
|
|
179
193
|
) : invitationStatus === Invitation.State.CONNECTING ? (
|
|
@@ -5,15 +5,14 @@
|
|
|
5
5
|
import React, { type ForwardedRef, forwardRef } from 'react';
|
|
6
6
|
|
|
7
7
|
import type { Space } from '@dxos/react-client/echo';
|
|
8
|
-
import { Avatar, useId
|
|
8
|
+
import { Avatar, useId } from '@dxos/react-ui';
|
|
9
9
|
import { mx } from '@dxos/react-ui-theme';
|
|
10
|
-
import { humanize } from '@dxos/util';
|
|
10
|
+
import { humanize, keyToEmoji } from '@dxos/util';
|
|
11
11
|
|
|
12
12
|
export const SpaceListItem = forwardRef(
|
|
13
13
|
({ space, onClick }: { space: Space; onClick?: () => void }, ref: ForwardedRef<HTMLLIElement>) => {
|
|
14
|
-
const fallbackValue = space.key
|
|
14
|
+
const fallbackValue = keyToEmoji(space.key);
|
|
15
15
|
const labelId = useId('identityListItem__label');
|
|
16
|
-
const jdenticon = useJdenticonHref(fallbackValue ?? '', 12);
|
|
17
16
|
const displayName = space.properties.name ?? humanize(space.key.toHex());
|
|
18
17
|
|
|
19
18
|
return (
|
|
@@ -25,7 +24,7 @@ export const SpaceListItem = forwardRef(
|
|
|
25
24
|
>
|
|
26
25
|
<Avatar.Root labelId={labelId}>
|
|
27
26
|
<Avatar.Frame>
|
|
28
|
-
<Avatar.Fallback
|
|
27
|
+
<Avatar.Fallback text={fallbackValue} />
|
|
29
28
|
</Avatar.Frame>
|
|
30
29
|
<Avatar.Label classNames='text-sm truncate'>{displayName}</Avatar.Label>
|
|
31
30
|
</Avatar.Root>
|
package/src/components/index.ts
CHANGED
|
@@ -9,7 +9,8 @@ import { useClient } from '@dxos/react-client';
|
|
|
9
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
|
-
import { Avatar, DensityProvider, useId,
|
|
12
|
+
import { Avatar, DensityProvider, useId, useTranslation } from '@dxos/react-ui';
|
|
13
|
+
import { keyToFallback } from '@dxos/util';
|
|
13
14
|
|
|
14
15
|
import {
|
|
15
16
|
type IdentityPanelHeadingProps,
|
|
@@ -25,12 +26,12 @@ import { InvitationManager } from '../../steps';
|
|
|
25
26
|
const viewStyles = 'pbs-1 pbe-3 pli-3';
|
|
26
27
|
|
|
27
28
|
const IdentityHeading = ({ titleId, title, identity, onDone }: IdentityPanelHeadingProps) => {
|
|
28
|
-
const
|
|
29
|
+
const fallbackValue = keyToFallback(identity.identityKey);
|
|
29
30
|
return (
|
|
30
31
|
<Heading titleId={titleId} title={title} corner={<CloseButton onDone={onDone} />}>
|
|
31
|
-
<Avatar.Root size={12} variant='circle' status='active'>
|
|
32
|
+
<Avatar.Root size={12} variant='circle' status='active' hue={identity.profile?.data?.hue || fallbackValue.hue}>
|
|
32
33
|
<Avatar.Frame classNames='block mbs-4 mbe-2 mli-auto chromatic-ignore'>
|
|
33
|
-
<Avatar.Fallback
|
|
34
|
+
<Avatar.Fallback text={identity.profile?.data?.emoji || fallbackValue.emoji} />
|
|
34
35
|
</Avatar.Frame>
|
|
35
36
|
<Avatar.Label classNames='block text-center font-light text-xl' data-testid='identityHeading.displayName'>
|
|
36
37
|
{identity.profile?.displayName ?? generateName(identity.identityKey.toHex())}
|
|
@@ -128,7 +129,7 @@ export const IdentityPanel = ({
|
|
|
128
129
|
const invitations = useHaloInvitations();
|
|
129
130
|
const agentProps = useAgentHandlers({ client, identity, invitations });
|
|
130
131
|
if (!identity) {
|
|
131
|
-
|
|
132
|
+
log.error('IdentityPanel rendered with no active identity.');
|
|
132
133
|
return null;
|
|
133
134
|
}
|
|
134
135
|
const [identityState, identitySend, identityService] = useIdentityMachine(client);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { CaretRight, Devices, Plus, Power, UserGear, HardDrive } from '@phosphor-icons/react';
|
|
6
6
|
import React, { useCallback } from 'react';
|
|
7
7
|
|
|
8
|
+
import { log } from '@dxos/log';
|
|
8
9
|
import { useAgentHostingProviderClient, useClient } from '@dxos/react-client';
|
|
9
10
|
import { useHaloInvitations } from '@dxos/react-client/halo';
|
|
10
11
|
import { Invitation, InvitationEncoder } from '@dxos/react-client/invitations';
|
|
@@ -24,12 +25,13 @@ export const IdentityActionChooser = (props: IdentityPanelStepProps) => {
|
|
|
24
25
|
const onInvitationEvent = useCallback((invitation: Invitation) => {
|
|
25
26
|
const invitationCode = InvitationEncoder.encode(invitation);
|
|
26
27
|
if (invitation.state === Invitation.State.CONNECTING) {
|
|
27
|
-
|
|
28
|
+
log.info(JSON.stringify({ invitationCode, authCode: invitation.authCode }));
|
|
28
29
|
}
|
|
29
30
|
}, []);
|
|
30
31
|
const createInvitation = (e: React.MouseEvent) => {
|
|
31
32
|
const testing = e.altKey && e.shiftKey;
|
|
32
33
|
invitations.forEach((invitation) => invitation.cancel());
|
|
34
|
+
// TODO(nf): allow user to make invitations non-persistent?
|
|
33
35
|
const invitation = client.halo.share(
|
|
34
36
|
testing ? { type: Invitation.Type.MULTIUSE, authMethod: Invitation.AuthMethod.NONE } : undefined,
|
|
35
37
|
);
|
|
@@ -8,8 +8,9 @@ import { type Event, type SingleOrArray } from 'xstate';
|
|
|
8
8
|
import { log } from '@dxos/log';
|
|
9
9
|
import { type Identity } from '@dxos/react-client/halo';
|
|
10
10
|
import { useTranslation } from '@dxos/react-ui';
|
|
11
|
+
import { hexToEmoji, hexToHue } from '@dxos/util';
|
|
11
12
|
|
|
12
|
-
import { Action, Actions, StepHeading, Input, useClipboardContext } from '../../../components';
|
|
13
|
+
import { Action, Actions, StepHeading, Input, useClipboardContext, EmojiPicker, HuePicker } from '../../../components';
|
|
13
14
|
import { type IdentityPanelStepProps } from '../IdentityPanelProps';
|
|
14
15
|
import { type IdentityEvent } from '../identityMachine';
|
|
15
16
|
|
|
@@ -36,13 +37,20 @@ export const ProfileForm = (props: ProfileFormProps) => {
|
|
|
36
37
|
return <ProfileFormImpl {...props} onUpdateProfile={handleUpdateProfile} validationMessage={validationMessage} />;
|
|
37
38
|
};
|
|
38
39
|
|
|
40
|
+
const getHueValue = (identity?: Identity) =>
|
|
41
|
+
identity?.profile?.data?.hue || hexToHue(identity?.identityKey.toHex() ?? '0');
|
|
42
|
+
const getEmojiValue = (identity?: Identity) =>
|
|
43
|
+
identity?.profile?.data?.emoji || hexToEmoji(identity?.identityKey.toHex() ?? '0');
|
|
44
|
+
|
|
39
45
|
// TODO(zhenyasav): impl shouldn't need send()
|
|
40
46
|
const ProfileFormImpl = (props: ProfileFormImplProps) => {
|
|
41
47
|
const { active, identity, send, onUpdateProfile, validationMessage } = props;
|
|
42
48
|
const profile = identity?.profile;
|
|
43
49
|
const disabled = !active;
|
|
44
50
|
const { t } = useTranslation('os');
|
|
45
|
-
const [
|
|
51
|
+
const [displayName, setDisplayName] = useState(profile?.displayName ?? '');
|
|
52
|
+
const [hue, setHue] = useState<string>(getHueValue(identity));
|
|
53
|
+
const [emoji, setEmoji] = useState<string>(getEmojiValue(identity));
|
|
46
54
|
const { textValue, setTextValue } = useClipboardContext();
|
|
47
55
|
const identityHex = identity?.identityKey.toHex();
|
|
48
56
|
const copied = textValue === identityHex;
|
|
@@ -51,14 +59,28 @@ const ProfileFormImpl = (props: ProfileFormImplProps) => {
|
|
|
51
59
|
<div role='none' className='grow flex flex-col justify-center'>
|
|
52
60
|
<Input
|
|
53
61
|
{...{ validationMessage }}
|
|
54
|
-
label={<StepHeading>{t('display name input label')}</StepHeading>}
|
|
62
|
+
label={<StepHeading className='m-0'>{t('display name input label')}</StepHeading>}
|
|
55
63
|
disabled={disabled}
|
|
56
64
|
data-testid='display-name-input'
|
|
57
65
|
placeholder={t('display name input placeholder')}
|
|
58
|
-
value={
|
|
59
|
-
onChange={({ target: { value } }) =>
|
|
60
|
-
onKeyDown={({ key }) => key === 'Enter' && onUpdateProfile?.({ displayName: inputValue })}
|
|
66
|
+
value={displayName}
|
|
67
|
+
onChange={({ target: { value } }) => setDisplayName(value)}
|
|
61
68
|
/>
|
|
69
|
+
<StepHeading className='mbe-2'>{t('emoji and color label')}</StepHeading>
|
|
70
|
+
<div role='none' className='grid grid-cols-[1fr_min-content] gap-y-2'>
|
|
71
|
+
<EmojiPicker
|
|
72
|
+
emoji={emoji}
|
|
73
|
+
onChangeEmoji={setEmoji}
|
|
74
|
+
disabled={disabled}
|
|
75
|
+
onClickClear={() => setEmoji(getEmojiValue(identity))}
|
|
76
|
+
/>
|
|
77
|
+
<HuePicker
|
|
78
|
+
hue={hue}
|
|
79
|
+
onChangeHue={setHue}
|
|
80
|
+
disabled={disabled}
|
|
81
|
+
onClickClear={() => setHue(getHueValue(identity))}
|
|
82
|
+
/>
|
|
83
|
+
</div>
|
|
62
84
|
</div>
|
|
63
85
|
<Actions>
|
|
64
86
|
<Action
|
|
@@ -84,7 +106,7 @@ const ProfileFormImpl = (props: ProfileFormImplProps) => {
|
|
|
84
106
|
<Action
|
|
85
107
|
variant='primary'
|
|
86
108
|
disabled={disabled}
|
|
87
|
-
onClick={() => onUpdateProfile?.({ displayName:
|
|
109
|
+
onClick={() => onUpdateProfile?.({ displayName, data: { emoji, hue } })}
|
|
88
110
|
data-testid='update-profile-form-continue'
|
|
89
111
|
>
|
|
90
112
|
{t('done label')}
|
|
@@ -6,6 +6,7 @@ import { useCallback, useEffect, useState } from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { type CancellableInvitation } from '@dxos/client-protocol';
|
|
8
8
|
import { invariant } from '@dxos/invariant';
|
|
9
|
+
import { log } from '@dxos/log';
|
|
9
10
|
import { useAgentHostingProviderClient, type Client } from '@dxos/react-client';
|
|
10
11
|
import { type Identity } from '@dxos/react-client/halo';
|
|
11
12
|
import { Invitation, InvitationEncoder } from '@dxos/react-client/invitations';
|
|
@@ -46,7 +47,7 @@ export const useAgentHandlers = ({
|
|
|
46
47
|
const handleAgentCreate = useCallback(async (invitation: Invitation) => {
|
|
47
48
|
const invitationCode = InvitationEncoder.encode(invitation);
|
|
48
49
|
if (invitation.state === Invitation.State.CONNECTING) {
|
|
49
|
-
|
|
50
|
+
log.info(JSON.stringify({ invitationCode, authCode: invitation.authCode }));
|
|
50
51
|
invariant(identity, 'Identity not found');
|
|
51
52
|
if (agentHostingProviderClient) {
|
|
52
53
|
try {
|
|
@@ -6,8 +6,9 @@ import React, { cloneElement } from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { generateName } from '@dxos/display-name';
|
|
8
8
|
import type { Identity } from '@dxos/react-client/halo';
|
|
9
|
-
import { Avatar, useId,
|
|
9
|
+
import { Avatar, useId, useTranslation } from '@dxos/react-ui';
|
|
10
10
|
import { mx } from '@dxos/react-ui-theme';
|
|
11
|
+
import { hexToFallback } from '@dxos/util';
|
|
11
12
|
|
|
12
13
|
import { Action, Actions, StepHeading } from '../../../components';
|
|
13
14
|
import { type JoinPanelMode, type JoinStepProps } from '../JoinPanelProps';
|
|
@@ -22,11 +23,10 @@ export const IdentityAdded = (props: IdentityAddedProps) => {
|
|
|
22
23
|
const disabled = !active;
|
|
23
24
|
const { t } = useTranslation('os');
|
|
24
25
|
|
|
25
|
-
const
|
|
26
|
+
const addedIdentityHex = addedIdentity?.identityKey.toHex() ?? '0';
|
|
27
|
+
const fallbackValue = hexToFallback(addedIdentityHex);
|
|
26
28
|
const labelId = useId('identityListItem__label');
|
|
27
|
-
const
|
|
28
|
-
const displayName =
|
|
29
|
-
addedIdentity?.profile?.displayName ?? (addedIdentity && generateName(addedIdentity?.identityKey.toHex()));
|
|
29
|
+
const displayName = addedIdentity?.profile?.displayName ?? (addedIdentity && generateName(addedIdentityHex));
|
|
30
30
|
|
|
31
31
|
const doneAction = (
|
|
32
32
|
<Action
|
|
@@ -43,9 +43,9 @@ export const IdentityAdded = (props: IdentityAddedProps) => {
|
|
|
43
43
|
<>
|
|
44
44
|
<StepHeading>{t('identity added label')}</StepHeading>
|
|
45
45
|
<div role='none' className='grow flex flex-col items-center justify-center text-center gap-2'>
|
|
46
|
-
<Avatar.Root status='active' labelId={labelId}>
|
|
46
|
+
<Avatar.Root status='active' labelId={labelId} hue={addedIdentity?.profile?.data?.hue || fallbackValue.hue}>
|
|
47
47
|
<Avatar.Frame>
|
|
48
|
-
<Avatar.Fallback
|
|
48
|
+
<Avatar.Fallback text={addedIdentity?.profile?.data?.emoji || fallbackValue.emoji} />
|
|
49
49
|
</Avatar.Frame>
|
|
50
50
|
<Avatar.Label classNames={mx('text-lg truncate', !addedIdentity?.profile?.displayName && 'font-mono')}>
|
|
51
51
|
{displayName}
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
import React, { type ChangeEvent, useState } from 'react';
|
|
6
6
|
|
|
7
7
|
import { Input, useTranslation } from '@dxos/react-ui';
|
|
8
|
+
import { hexToEmoji } from '@dxos/util';
|
|
8
9
|
|
|
9
10
|
import { Actions, Action, Emoji, StepHeading } from '../../../components';
|
|
10
|
-
import { toEmoji } from '../../../util';
|
|
11
11
|
import { type JoinStepProps } from '../JoinPanelProps';
|
|
12
12
|
|
|
13
13
|
const pinLength = 6;
|
|
@@ -45,7 +45,7 @@ export const InvitationAuthenticator = ({
|
|
|
45
45
|
return (
|
|
46
46
|
<>
|
|
47
47
|
<div role='none' className='grow flex flex-col justify-center'>
|
|
48
|
-
{invitationId && <Emoji text={
|
|
48
|
+
{invitationId && <Emoji text={hexToEmoji(invitationId)} />}
|
|
49
49
|
<Input.Root
|
|
50
50
|
{...(failed && {
|
|
51
51
|
validationValence: 'error',
|
|
@@ -16,9 +16,7 @@ const getComponentNames = (module: any): string[] =>
|
|
|
16
16
|
Object.getOwnPropertyNames(module).filter((name) => !(name === 'default' || name.startsWith('__')));
|
|
17
17
|
|
|
18
18
|
const getComponents = (module: any): React.FC[] => {
|
|
19
|
-
|
|
20
|
-
console.log(module);
|
|
21
|
-
return components;
|
|
19
|
+
return getComponentNames(module).map((name) => module[name]);
|
|
22
20
|
};
|
|
23
21
|
|
|
24
22
|
const camelCaseToSpacedName = (camelCase: string) => {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { Placeholder, UserPlus, UsersThree } from '@phosphor-icons/react';
|
|
6
6
|
import React, { type Dispatch, type SetStateAction, useCallback, useState } from 'react';
|
|
7
7
|
|
|
8
|
+
import { log } from '@dxos/log';
|
|
8
9
|
import { useConfig } from '@dxos/react-client';
|
|
9
10
|
import { useSpaceInvitations } from '@dxos/react-client/echo';
|
|
10
11
|
import { type CancellableInvitationObservable, Invitation, InvitationEncoder } from '@dxos/react-client/invitations';
|
|
@@ -45,7 +46,7 @@ export const SpaceManager = (props: SpaceManagerProps) => {
|
|
|
45
46
|
const onInvitationEvent = useCallback((invitation: Invitation) => {
|
|
46
47
|
const invitationCode = InvitationEncoder.encode(invitation);
|
|
47
48
|
if (invitation.state === Invitation.State.CONNECTING) {
|
|
48
|
-
|
|
49
|
+
log.info(JSON.stringify({ invitationCode, authCode: invitation.authCode }));
|
|
49
50
|
}
|
|
50
51
|
}, []);
|
|
51
52
|
|
|
@@ -80,6 +81,22 @@ export const SpaceManager = (props: SpaceManagerProps) => {
|
|
|
80
81
|
}
|
|
81
82
|
}, [space]),
|
|
82
83
|
},
|
|
84
|
+
// inviteNonPersistent: {
|
|
85
|
+
// label: 'Invite one (nonpersistent)',
|
|
86
|
+
// description: 'Only one user may join. Invitation does not resume if client is restarted.',
|
|
87
|
+
// icon: UserPlus,
|
|
88
|
+
// onClick: useCallback(() => {
|
|
89
|
+
// const invitation = space.share?.({
|
|
90
|
+
// type: Invitation.Type.INTERACTIVE,
|
|
91
|
+
// authMethod: Invitation.AuthMethod.SHARED_SECRET,
|
|
92
|
+
// persistent: false,
|
|
93
|
+
// target,
|
|
94
|
+
// });
|
|
95
|
+
// if (invitation && config.values.runtime?.app?.env?.DX_ENVIRONMENT !== 'production') {
|
|
96
|
+
// invitation.subscribe(onInvitationEvent);
|
|
97
|
+
// }
|
|
98
|
+
// }, [space]),
|
|
99
|
+
// },
|
|
83
100
|
};
|
|
84
101
|
|
|
85
102
|
return <SpaceManagerImpl {...props} invitations={invitations} inviteActions={inviteActions} />;
|
|
@@ -9,6 +9,7 @@ import { QR } from 'react-qr-rounded';
|
|
|
9
9
|
import { type InvitationStatus, Invitation } from '@dxos/react-client/invitations';
|
|
10
10
|
import { useId, useTranslation } from '@dxos/react-ui';
|
|
11
11
|
import { descriptionText, getSize, mx } from '@dxos/react-ui-theme';
|
|
12
|
+
import { hexToEmoji } from '@dxos/util';
|
|
12
13
|
|
|
13
14
|
import { type StepProps } from './StepProps';
|
|
14
15
|
import {
|
|
@@ -22,7 +23,7 @@ import {
|
|
|
22
23
|
CopyButton,
|
|
23
24
|
Centered,
|
|
24
25
|
} from '../components';
|
|
25
|
-
import { invitationStatusValue
|
|
26
|
+
import { invitationStatusValue } from '../util';
|
|
26
27
|
|
|
27
28
|
export type InvitationManagerProps = StepProps &
|
|
28
29
|
Partial<InvitationStatus> & {
|
|
@@ -44,13 +45,13 @@ export const InvitationManager = ({
|
|
|
44
45
|
status,
|
|
45
46
|
type,
|
|
46
47
|
authCode,
|
|
47
|
-
id,
|
|
48
|
+
id = '0',
|
|
48
49
|
}: InvitationManagerProps) => {
|
|
49
50
|
const { t } = useTranslation('os');
|
|
50
51
|
const qrLabel = useId('invitation-manager__qr-code');
|
|
51
52
|
const statusValue = type === Invitation.Type.MULTIUSE ? 0 : invitationStatusValue.get(status!) ?? 0;
|
|
52
53
|
const showAuthCode = statusValue === 3;
|
|
53
|
-
const emoji =
|
|
54
|
+
const emoji = hexToEmoji(id);
|
|
54
55
|
const activeView = useMemo(() => {
|
|
55
56
|
switch (true) {
|
|
56
57
|
case statusValue === 5:
|
|
@@ -7,6 +7,7 @@ import '@dxosTheme';
|
|
|
7
7
|
import { Laptop, Planet, Plus, PlusCircle, SignIn, QrCode, WifiHigh, WifiSlash } from '@phosphor-icons/react';
|
|
8
8
|
import React, { useMemo, useState } from 'react';
|
|
9
9
|
|
|
10
|
+
import { log } from '@dxos/log';
|
|
10
11
|
import { faker } from '@dxos/random';
|
|
11
12
|
import { useClient } from '@dxos/react-client';
|
|
12
13
|
import { type Space, type SpaceMember, SpaceProxy, useSpaces } from '@dxos/react-client/echo';
|
|
@@ -42,7 +43,7 @@ const Panel = ({ id, panel, setPanel }: { id: number; panel?: PanelType; setPane
|
|
|
42
43
|
invitation.subscribe((invitation) => {
|
|
43
44
|
const invitationCode = InvitationEncoder.encode(invitation);
|
|
44
45
|
if (invitation.state === Invitation.State.CONNECTING) {
|
|
45
|
-
|
|
46
|
+
log.info(JSON.stringify({ invitationCode, authCode: invitation.authCode }));
|
|
46
47
|
}
|
|
47
48
|
});
|
|
48
49
|
};
|
|
@@ -126,7 +127,7 @@ const Invitations = (args: { id: number; count: number }) => {
|
|
|
126
127
|
invitation.subscribe((invitation) => {
|
|
127
128
|
const invitationCode = InvitationEncoder.encode(invitation);
|
|
128
129
|
if (invitation.state === Invitation.State.CONNECTING) {
|
|
129
|
-
|
|
130
|
+
log.info(JSON.stringify({ invitationCode, authCode: invitation.authCode }));
|
|
130
131
|
}
|
|
131
132
|
});
|
|
132
133
|
};
|