mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-31 10:31:35 +00:00
Merge pull request #971 from RoboSats/add-roboidentity-generator
Add robo-identity generator
This commit is contained in:
commit
a2695ca169
6
frontend/package-lock.json
generated
6
frontend/package-lock.json
generated
@ -47,6 +47,7 @@
|
|||||||
"react-smooth-image": "^1.1.0",
|
"react-smooth-image": "^1.1.0",
|
||||||
"react-world-flags": "^1.6.0",
|
"react-world-flags": "^1.6.0",
|
||||||
"reconnecting-websocket": "^4.4.0",
|
"reconnecting-websocket": "^4.4.0",
|
||||||
|
"robo-identities-wasm": "^0.1.0",
|
||||||
"simple-plist": "^1.3.1",
|
"simple-plist": "^1.3.1",
|
||||||
"webln": "^0.3.2",
|
"webln": "^0.3.2",
|
||||||
"websocket": "^1.0.34"
|
"websocket": "^1.0.34"
|
||||||
@ -14282,6 +14283,11 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/robo-identities-wasm": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/robo-identities-wasm/-/robo-identities-wasm-0.1.0.tgz",
|
||||||
|
"integrity": "sha512-q6+1Vgq+8d2F5k8Nqm39qwQJYe9uTC7TlR3NbBQ6k2ImBNccdAEoZgb0ikKjN59cK4MvqejlgBV1ybaLXoHbhA=="
|
||||||
|
},
|
||||||
"node_modules/run-applescript": {
|
"node_modules/run-applescript": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz",
|
||||||
|
@ -86,6 +86,7 @@
|
|||||||
"react-smooth-image": "^1.1.0",
|
"react-smooth-image": "^1.1.0",
|
||||||
"react-world-flags": "^1.6.0",
|
"react-world-flags": "^1.6.0",
|
||||||
"reconnecting-websocket": "^4.4.0",
|
"reconnecting-websocket": "^4.4.0",
|
||||||
|
"robo-identities-wasm": "^0.1.0",
|
||||||
"simple-plist": "^1.3.1",
|
"simple-plist": "^1.3.1",
|
||||||
"webln": "^0.3.2",
|
"webln": "^0.3.2",
|
||||||
"websocket": "^1.0.34"
|
"websocket": "^1.0.34"
|
||||||
|
@ -31,7 +31,7 @@ const BookPage = (): JSX.Element => {
|
|||||||
const chartWidthEm = width - maxBookTableWidth;
|
const chartWidthEm = width - maxBookTableWidth;
|
||||||
|
|
||||||
const onOrderClicked = function (id: number, shortAlias: string): void {
|
const onOrderClicked = function (id: number, shortAlias: string): void {
|
||||||
if (garage.getSlot()?.avatarLoaded === true) {
|
if (Boolean(garage.getSlot()?.hashId)) {
|
||||||
setDelay(10000);
|
setDelay(10000);
|
||||||
navigate(`/order/${shortAlias}/${id}`);
|
navigate(`/order/${shortAlias}/${id}`);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { MemoryRouter, BrowserRouter, Routes, Route } from 'react-router-dom';
|
import { MemoryRouter, BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||||
import { Box, Slide, Typography, styled } from '@mui/material';
|
import { Box, Slide, Typography, styled } from '@mui/material';
|
||||||
import { type UseAppStoreType, AppContext, closeAll } from '../contexts/AppContext';
|
import { type UseAppStoreType, AppContext, closeAll } from '../contexts/AppContext';
|
||||||
@ -8,7 +8,6 @@ import RobotAvatar from '../components/RobotAvatar';
|
|||||||
import Notifications from '../components/Notifications';
|
import Notifications from '../components/Notifications';
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FederationContext, type UseFederationStoreType } from '../contexts/FederationContext';
|
|
||||||
import { GarageContext, type UseGarageStoreType } from '../contexts/GarageContext';
|
import { GarageContext, type UseGarageStoreType } from '../contexts/GarageContext';
|
||||||
|
|
||||||
const Router = window.NativeRobosats === undefined ? BrowserRouter : MemoryRouter;
|
const Router = window.NativeRobosats === undefined ? BrowserRouter : MemoryRouter;
|
||||||
@ -30,28 +29,13 @@ const MainBox = styled(Box)<MainBoxProps>((props) => ({
|
|||||||
|
|
||||||
const Main: React.FC = () => {
|
const Main: React.FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { settings, page, slideDirection, setOpen, windowSize, navbarHeight, hostUrl } =
|
const { settings, page, slideDirection, setOpen, windowSize, navbarHeight } =
|
||||||
useContext<UseAppStoreType>(AppContext);
|
useContext<UseAppStoreType>(AppContext);
|
||||||
const { federation, sortedCoordinators } = useContext<UseFederationStoreType>(FederationContext);
|
|
||||||
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
const [avatarBaseUrl, setAvatarBaseUrl] = useState<string>(hostUrl);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setAvatarBaseUrl(federation.getCoordinator(sortedCoordinators[0]).getBaseUrl());
|
|
||||||
}, [settings.network, settings.selfhostedClient, federation, sortedCoordinators]);
|
|
||||||
|
|
||||||
const onLoad = (): void => {
|
|
||||||
garage.updateSlot({ avatarLoaded: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<RobotAvatar
|
<RobotAvatar style={{ display: 'none' }} hashId={garage.getSlot()?.hashId} />
|
||||||
style={{ display: 'none' }}
|
|
||||||
nickname={garage.getSlot()?.getRobot()?.nickname}
|
|
||||||
baseUrl={federation.getCoordinator(sortedCoordinators[0]).getBaseUrl()}
|
|
||||||
onLoad={onLoad}
|
|
||||||
/>
|
|
||||||
<Notifications
|
<Notifications
|
||||||
page={page}
|
page={page}
|
||||||
openProfile={() => {
|
openProfile={() => {
|
||||||
@ -81,7 +65,7 @@ const Main: React.FC = () => {
|
|||||||
appear={slideDirection.in !== undefined}
|
appear={slideDirection.in !== undefined}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<RobotPage avatarBaseUrl={avatarBaseUrl} />
|
<RobotPage />
|
||||||
</div>
|
</div>
|
||||||
</Slide>
|
</Slide>
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ const MakerPage = (): JSX.Element => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const onOrderClicked = function (id: number): void {
|
const onOrderClicked = function (id: number): void {
|
||||||
if (garage.getSlot()?.avatarLoaded === true) {
|
if (Boolean(garage.getSlot()?.hashId)) {
|
||||||
navigate(`/order/${id}`);
|
navigate(`/order/${id}`);
|
||||||
} else {
|
} else {
|
||||||
setOpenNoRobot(true);
|
setOpenNoRobot(true);
|
||||||
|
@ -32,7 +32,7 @@ const NavBar = (): JSX.Element => {
|
|||||||
navbarHeight,
|
navbarHeight,
|
||||||
hostUrl,
|
hostUrl,
|
||||||
} = useContext<UseAppStoreType>(AppContext);
|
} = useContext<UseAppStoreType>(AppContext);
|
||||||
const { garage, orderUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
const { garage, orderUpdatedAt, robotUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@ -42,7 +42,7 @@ const NavBar = (): JSX.Element => {
|
|||||||
const tabSx = smallBar
|
const tabSx = smallBar
|
||||||
? {
|
? {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
bottom: garage.getSlot()?.avatarLoaded === true ? '0.9em' : '0.13em',
|
bottom: Boolean(garage.getSlot()?.hashId) ? '0.9em' : '0.13em',
|
||||||
minWidth: '1em',
|
minWidth: '1em',
|
||||||
}
|
}
|
||||||
: { position: 'relative', bottom: '1em', minWidth: '2em' };
|
: { position: 'relative', bottom: '1em', minWidth: '2em' };
|
||||||
@ -65,7 +65,7 @@ const NavBar = (): JSX.Element => {
|
|||||||
if (isPage(pathPage)) {
|
if (isPage(pathPage)) {
|
||||||
setPage(pathPage);
|
setPage(pathPage);
|
||||||
}
|
}
|
||||||
}, [location, navigate, setPage, orderUpdatedAt]);
|
}, [location, navigate, setPage, orderUpdatedAt, robotUpdatedAt]);
|
||||||
|
|
||||||
const handleSlideDirection = function (oldPage: Page, newPage: Page): void {
|
const handleSlideDirection = function (oldPage: Page, newPage: Page): void {
|
||||||
const oldPos: number = pagesPosition[oldPage];
|
const oldPos: number = pagesPosition[oldPage];
|
||||||
@ -121,17 +121,16 @@ const NavBar = (): JSX.Element => {
|
|||||||
<Tab
|
<Tab
|
||||||
sx={{ ...tabSx, minWidth: '2.5em', width: '2.5em', maxWidth: '4em' }}
|
sx={{ ...tabSx, minWidth: '2.5em', width: '2.5em', maxWidth: '4em' }}
|
||||||
value='none'
|
value='none'
|
||||||
disabled={slot?.getRobot()?.nickname === null}
|
disabled={slot?.nickname === null}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen({ ...closeAll, profile: !open.profile });
|
setOpen({ ...closeAll, profile: !open.profile });
|
||||||
}}
|
}}
|
||||||
icon={
|
icon={
|
||||||
slot?.getRobot()?.nickname != null && slot?.avatarLoaded ? (
|
slot?.hashId ? (
|
||||||
<RobotAvatar
|
<RobotAvatar
|
||||||
style={{ width: '2.3em', height: '2.3em', position: 'relative', top: '0.2em' }}
|
style={{ width: '2.3em', height: '2.3em', position: 'relative', top: '0.2em' }}
|
||||||
avatarClass={theme.palette.mode === 'dark' ? 'navBarAvatarDark' : 'navBarAvatar'}
|
avatarClass={theme.palette.mode === 'dark' ? 'navBarAvatarDark' : 'navBarAvatar'}
|
||||||
nickname={slot?.getRobot()?.nickname}
|
hashId={slot?.hashId}
|
||||||
baseUrl={hostUrl}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
@ -166,7 +165,7 @@ const NavBar = (): JSX.Element => {
|
|||||||
label={smallBar ? undefined : t('Order')}
|
label={smallBar ? undefined : t('Order')}
|
||||||
value='order'
|
value='order'
|
||||||
disabled={
|
disabled={
|
||||||
slot?.avatarLoaded === false ||
|
!Boolean(slot?.hashId) ||
|
||||||
!(slot?.getRobot(slot?.activeShortAlias ?? '')?.activeOrderId != null)
|
!(slot?.getRobot(slot?.activeShortAlias ?? '')?.activeOrderId != null)
|
||||||
}
|
}
|
||||||
icon={<Assignment />}
|
icon={<Assignment />}
|
||||||
|
@ -151,7 +151,7 @@ const Onboarding = ({
|
|||||||
<Grid container direction='column' alignItems='center' spacing={1}>
|
<Grid container direction='column' alignItems='center' spacing={1}>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Typography>
|
<Typography>
|
||||||
{slot?.avatarLoaded === true && Boolean(robot?.nickname) ? (
|
{Boolean(slot?.hashId) ? (
|
||||||
t('This is your trading avatar')
|
t('This is your trading avatar')
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@ -164,7 +164,7 @@ const Onboarding = ({
|
|||||||
|
|
||||||
<Grid item sx={{ width: '13.5em' }}>
|
<Grid item sx={{ width: '13.5em' }}>
|
||||||
<RobotAvatar
|
<RobotAvatar
|
||||||
nickname={robot?.nickname}
|
hashId={slot?.hashId}
|
||||||
smooth={true}
|
smooth={true}
|
||||||
style={{ maxWidth: '12.5em', maxHeight: '12.5em' }}
|
style={{ maxWidth: '12.5em', maxHeight: '12.5em' }}
|
||||||
placeholderType='generating'
|
placeholderType='generating'
|
||||||
@ -176,11 +176,10 @@ const Onboarding = ({
|
|||||||
width: '12.4em',
|
width: '12.4em',
|
||||||
}}
|
}}
|
||||||
tooltipPosition='top'
|
tooltipPosition='top'
|
||||||
baseUrl={hostUrl}
|
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{slot?.avatarLoaded === true && Boolean(robot?.nickname) ? (
|
{Boolean(slot?.hashId) ? (
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Typography align='center'>{t('Hi! My name is')}</Typography>
|
<Typography align='center'>{t('Hi! My name is')}</Typography>
|
||||||
<Typography component='h5' variant='h5'>
|
<Typography component='h5' variant='h5'>
|
||||||
@ -199,7 +198,7 @@ const Onboarding = ({
|
|||||||
width: '1.5em',
|
width: '1.5em',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<b>{robot.nickname}</b>
|
<b>{slot?.nickname}</b>
|
||||||
<Bolt
|
<Bolt
|
||||||
sx={{
|
sx={{
|
||||||
color: '#fcba03',
|
color: '#fcba03',
|
||||||
@ -212,7 +211,7 @@ const Onboarding = ({
|
|||||||
</Grid>
|
</Grid>
|
||||||
) : null}
|
) : null}
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Collapse in={!!(slot?.avatarLoaded === true && Boolean(robot?.nickname))}>
|
<Collapse in={!!Boolean(slot?.hashId)}>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setStep('3');
|
setStep('3');
|
||||||
|
@ -44,7 +44,7 @@ const RobotProfile = ({
|
|||||||
setView,
|
setView,
|
||||||
width,
|
width,
|
||||||
}: RobotProfileProps): JSX.Element => {
|
}: RobotProfileProps): JSX.Element => {
|
||||||
const { windowSize, hostUrl } = useContext<UseAppStoreType>(AppContext);
|
const { windowSize } = useContext<UseAppStoreType>(AppContext);
|
||||||
const { garage, robotUpdatedAt, orderUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
const { garage, robotUpdatedAt, orderUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
const { sortedCoordinators } = useContext<UseFederationStoreType>(FederationContext);
|
const { sortedCoordinators } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ const RobotProfile = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const slot = garage.getSlot();
|
const slot = garage.getSlot();
|
||||||
const robot = slot?.getRobot(sortedCoordinators[0]);
|
const robot = slot?.getRobot(sortedCoordinators[0]);
|
||||||
if (robot?.nickname != null && slot?.avatarLoaded === true) {
|
if (Boolean(slot?.hashId)) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [orderUpdatedAt, robotUpdatedAt, loading]);
|
}, [orderUpdatedAt, robotUpdatedAt, loading]);
|
||||||
@ -86,7 +86,7 @@ const RobotProfile = ({
|
|||||||
sx={{ width: '100%' }}
|
sx={{ width: '100%' }}
|
||||||
>
|
>
|
||||||
<Grid item sx={{ height: '2.3em', position: 'relative' }}>
|
<Grid item sx={{ height: '2.3em', position: 'relative' }}>
|
||||||
{slot?.avatarLoaded === true && robot?.nickname != null ? (
|
{Boolean(slot?.hashId) ? (
|
||||||
<Typography align='center' component='h5' variant='h5'>
|
<Typography align='center' component='h5' variant='h5'>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@ -105,7 +105,7 @@ const RobotProfile = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<b>{robot?.nickname}</b>
|
<b>{slot?.nickname}</b>
|
||||||
{width < 19 ? null : (
|
{width < 19 ? null : (
|
||||||
<Bolt
|
<Bolt
|
||||||
sx={{
|
sx={{
|
||||||
@ -127,7 +127,7 @@ const RobotProfile = ({
|
|||||||
|
|
||||||
<Grid item sx={{ width: `13.5em` }}>
|
<Grid item sx={{ width: `13.5em` }}>
|
||||||
<RobotAvatar
|
<RobotAvatar
|
||||||
nickname={robot?.nickname}
|
hashId={slot?.hashId}
|
||||||
smooth={true}
|
smooth={true}
|
||||||
style={{ maxWidth: '12.5em', maxHeight: '12.5em' }}
|
style={{ maxWidth: '12.5em', maxHeight: '12.5em' }}
|
||||||
placeholderType='generating'
|
placeholderType='generating'
|
||||||
@ -140,7 +140,6 @@ const RobotProfile = ({
|
|||||||
}}
|
}}
|
||||||
tooltip={t('This is your trading avatar')}
|
tooltip={t('This is your trading avatar')}
|
||||||
tooltipPosition='top'
|
tooltipPosition='top'
|
||||||
baseUrl={hostUrl}
|
|
||||||
/>
|
/>
|
||||||
{robot?.found === true && slot?.lastShortAlias != null ? (
|
{robot?.found === true && slot?.lastShortAlias != null ? (
|
||||||
<Typography align='center' variant='h6'>
|
<Typography align='center' variant='h6'>
|
||||||
@ -151,9 +150,7 @@ const RobotProfile = ({
|
|||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{Boolean(robot?.activeOrderId) &&
|
{Boolean(robot?.activeOrderId) && Boolean(slot?.hashId) ? (
|
||||||
Boolean(slot?.avatarLoaded) &&
|
|
||||||
Boolean(robot?.nickname) ? (
|
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -167,7 +164,7 @@ const RobotProfile = ({
|
|||||||
</Grid>
|
</Grid>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{Boolean(robot?.lastOrderId) && Boolean(slot?.avatarLoaded) && Boolean(robot?.nickname) ? (
|
{Boolean(robot?.lastOrderId) && Boolean(slot?.hashId) ? (
|
||||||
<Grid item container direction='column' alignItems='center'>
|
<Grid item container direction='column' alignItems='center'>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Button
|
<Button
|
||||||
@ -275,17 +272,16 @@ const RobotProfile = ({
|
|||||||
>
|
>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<RobotAvatar
|
<RobotAvatar
|
||||||
nickname={slot?.getRobot()?.nickname}
|
hashId={slot?.hashId}
|
||||||
smooth={true}
|
smooth={true}
|
||||||
style={{ width: '2.6em', height: '2.6em' }}
|
style={{ width: '2.6em', height: '2.6em' }}
|
||||||
placeholderType='loading'
|
placeholderType='loading'
|
||||||
baseUrl={hostUrl}
|
|
||||||
small={true}
|
small={true}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Typography variant={windowSize.width < 26 ? 'caption' : undefined}>
|
<Typography variant={windowSize.width < 26 ? 'caption' : undefined}>
|
||||||
{slot?.getRobot()?.nickname}
|
{slot?.nickname}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -23,11 +23,7 @@ import { validateTokenEntropy } from '../../utils';
|
|||||||
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
|
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
|
||||||
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
|
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
|
||||||
|
|
||||||
interface RobotPageProps {
|
const RobotPage = (): JSX.Element => {
|
||||||
avatarBaseUrl: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const RobotPage = ({ avatarBaseUrl }: RobotPageProps): JSX.Element => {
|
|
||||||
const { torStatus, windowSize, settings, page } = useContext<UseAppStoreType>(AppContext);
|
const { torStatus, windowSize, settings, page } = useContext<UseAppStoreType>(AppContext);
|
||||||
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
const { federation, sortedCoordinators } = useContext<UseFederationStoreType>(FederationContext);
|
const { federation, sortedCoordinators } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
@ -158,7 +154,6 @@ const RobotPage = ({ avatarBaseUrl }: RobotPageProps): JSX.Element => {
|
|||||||
inputToken={inputToken}
|
inputToken={inputToken}
|
||||||
setInputToken={setInputToken}
|
setInputToken={setInputToken}
|
||||||
getGenerateRobot={getGenerateRobot}
|
getGenerateRobot={getGenerateRobot}
|
||||||
avatarBaseUrl={avatarBaseUrl}
|
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
@ -170,7 +165,6 @@ const RobotPage = ({ avatarBaseUrl }: RobotPageProps): JSX.Element => {
|
|||||||
inputToken={inputToken}
|
inputToken={inputToken}
|
||||||
setInputToken={setInputToken}
|
setInputToken={setInputToken}
|
||||||
getGenerateRobot={getGenerateRobot}
|
getGenerateRobot={getGenerateRobot}
|
||||||
avatarBaseUrl={avatarBaseUrl}
|
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
@ -220,14 +220,13 @@ const BookTable = ({
|
|||||||
>
|
>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<RobotAvatar
|
<RobotAvatar
|
||||||
nickname={params.row.maker_nick}
|
hashId={params.row.maker_hash_id}
|
||||||
style={{ width: '3.215em', height: '3.215em' }}
|
style={{ width: '3.215em', height: '3.215em' }}
|
||||||
smooth={true}
|
smooth={true}
|
||||||
flipHorizontally={true}
|
flipHorizontally={true}
|
||||||
orderType={params.row.type}
|
orderType={params.row.type}
|
||||||
statusColor={statusBadgeColor(params.row.maker_status)}
|
statusColor={statusBadgeColor(params.row.maker_status)}
|
||||||
tooltip={t(params.row.maker_status)}
|
tooltip={t(params.row.maker_status)}
|
||||||
baseUrl={url + basePath}
|
|
||||||
small={true}
|
small={true}
|
||||||
/>
|
/>
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
@ -257,14 +256,13 @@ const BookTable = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RobotAvatar
|
<RobotAvatar
|
||||||
nickname={params.row.maker_nick}
|
hashId={params.row.maker_hash_id}
|
||||||
smooth={true}
|
smooth={true}
|
||||||
flipHorizontally={true}
|
flipHorizontally={true}
|
||||||
style={{ width: '3.215em', height: '3.215em' }}
|
style={{ width: '3.215em', height: '3.215em' }}
|
||||||
orderType={params.row.type}
|
orderType={params.row.type}
|
||||||
statusColor={statusBadgeColor(params.row.maker_status)}
|
statusColor={statusBadgeColor(params.row.maker_status)}
|
||||||
tooltip={t(params.row.maker_status)}
|
tooltip={t(params.row.maker_status)}
|
||||||
baseUrl={url + basePath}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -293,12 +291,10 @@ const BookTable = ({
|
|||||||
>
|
>
|
||||||
<ListItemAvatar>
|
<ListItemAvatar>
|
||||||
<RobotAvatar
|
<RobotAvatar
|
||||||
nickname={params.row.coordinatorShortAlias}
|
shortAlias={params.row.coordinatorShortAlias}
|
||||||
coordinator={true}
|
|
||||||
style={{ width: '3.215em', height: '3.215em' }}
|
style={{ width: '3.215em', height: '3.215em' }}
|
||||||
smooth={true}
|
smooth={true}
|
||||||
flipHorizontally={true}
|
flipHorizontally={true}
|
||||||
baseUrl={hostUrl}
|
|
||||||
small={true}
|
small={true}
|
||||||
/>
|
/>
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
|
@ -362,12 +362,10 @@ const CoordinatorDialog = ({ open = false, onClose, network, shortAlias }: Props
|
|||||||
<Grid container direction='column' alignItems='center' padding={0}>
|
<Grid container direction='column' alignItems='center' padding={0}>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<RobotAvatar
|
<RobotAvatar
|
||||||
nickname={coordinator?.shortAlias}
|
shortAlias={coordinator?.shortAlias}
|
||||||
coordinator={true}
|
|
||||||
style={{ width: '7.5em', height: '7.5em' }}
|
style={{ width: '7.5em', height: '7.5em' }}
|
||||||
smooth={true}
|
smooth={true}
|
||||||
flipHorizontally={false}
|
flipHorizontally={false}
|
||||||
baseUrl={hostUrl}
|
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
|
@ -33,7 +33,7 @@ const ProfileDialog = ({ open = false, baseUrl, onClose }: Props): JSX.Element =
|
|||||||
const [loading, setLoading] = useState<boolean>(true);
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(!(garage.getSlot()?.avatarLoaded === true));
|
setLoading(!Boolean(garage.getSlot()?.hashId));
|
||||||
}, [robotUpdatedAt]);
|
}, [robotUpdatedAt]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -57,7 +57,7 @@ const ProfileDialog = ({ open = false, baseUrl, onClose }: Props): JSX.Element =
|
|||||||
<ListItem className='profileNickname'>
|
<ListItem className='profileNickname'>
|
||||||
<ListItemText secondary={t('Your robot')}>
|
<ListItemText secondary={t('Your robot')}>
|
||||||
<Typography component='h6' variant='h6'>
|
<Typography component='h6' variant='h6'>
|
||||||
{garage.getSlot()?.getRobot()?.nickname !== undefined && (
|
{garage.getSlot()?.nickname !== undefined && (
|
||||||
<div style={{ position: 'relative', left: '-7px' }}>
|
<div style={{ position: 'relative', left: '-7px' }}>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@ -70,7 +70,7 @@ const ProfileDialog = ({ open = false, baseUrl, onClose }: Props): JSX.Element =
|
|||||||
>
|
>
|
||||||
<BoltIcon sx={{ color: '#fcba03', height: '28px', width: '24px' }} />
|
<BoltIcon sx={{ color: '#fcba03', height: '28px', width: '24px' }} />
|
||||||
|
|
||||||
<a>{garage.getSlot()?.getRobot()?.nickname}</a>
|
<a>{garage.getSlot()?.nickname}</a>
|
||||||
|
|
||||||
<BoltIcon sx={{ color: '#fcba03', height: '28px', width: '24px' }} />
|
<BoltIcon sx={{ color: '#fcba03', height: '28px', width: '24px' }} />
|
||||||
</div>
|
</div>
|
||||||
@ -83,8 +83,7 @@ const ProfileDialog = ({ open = false, baseUrl, onClose }: Props): JSX.Element =
|
|||||||
<RobotAvatar
|
<RobotAvatar
|
||||||
avatarClass='profileAvatar'
|
avatarClass='profileAvatar'
|
||||||
style={{ width: 65, height: 65 }}
|
style={{ width: 65, height: 65 }}
|
||||||
nickname={garage.getSlot()?.getRobot()?.nickname}
|
hashId={garage.getSlot()?.hashId}
|
||||||
baseUrl={baseUrl}
|
|
||||||
/>
|
/>
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
@ -97,7 +96,7 @@ const ProfileDialog = ({ open = false, baseUrl, onClose }: Props): JSX.Element =
|
|||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{Object.values(federation.coordinators).map((coordinator: Coordinator): JSX.Element => {
|
{Object.values(federation.coordinators).map((coordinator: Coordinator): JSX.Element => {
|
||||||
if (garage.getSlot()?.avatarLoaded === true) {
|
if (Boolean(garage.getSlot()?.hashId)) {
|
||||||
return (
|
return (
|
||||||
<div key={coordinator.shortAlias}>
|
<div key={coordinator.shortAlias}>
|
||||||
<RobotInfo coordinator={coordinator} onClose={onClose} />
|
<RobotInfo coordinator={coordinator} onClose={onClose} />
|
||||||
|
@ -75,12 +75,10 @@ const FederationTable = ({
|
|||||||
>
|
>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<RobotAvatar
|
<RobotAvatar
|
||||||
nickname={params.row.shortAlias}
|
shortAlias={params.row.shortAlias}
|
||||||
coordinator={true}
|
|
||||||
style={{ width: '3.215em', height: '3.215em' }}
|
style={{ width: '3.215em', height: '3.215em' }}
|
||||||
smooth={true}
|
smooth={true}
|
||||||
flipHorizontally={true}
|
flipHorizontally={true}
|
||||||
baseUrl={hostUrl}
|
|
||||||
small={true}
|
small={true}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -577,7 +577,7 @@ const MakerForm = ({
|
|||||||
setOpenDialogs(false);
|
setOpenDialogs(false);
|
||||||
}}
|
}}
|
||||||
onClickDone={handleCreateOrder}
|
onClickDone={handleCreateOrder}
|
||||||
hasRobot={garage.getSlot()?.avatarLoaded ?? false}
|
hasRobot={Boolean(garage.getSlot()?.hashId)}
|
||||||
onClickGenerateRobot={onClickGenerateRobot}
|
onClickGenerateRobot={onClickGenerateRobot}
|
||||||
/>
|
/>
|
||||||
<F2fMapDialog
|
<F2fMapDialog
|
||||||
|
@ -372,7 +372,7 @@ const TakeButton = ({
|
|||||||
setLoadingTake(true);
|
setLoadingTake(true);
|
||||||
setOpen(closeAll);
|
setOpen(closeAll);
|
||||||
}}
|
}}
|
||||||
hasRobot={garage.getSlot()?.avatarLoaded ?? false}
|
hasRobot={Boolean(garage.getSlot()?.hashId)}
|
||||||
onClickGenerateRobot={onClickGenerateRobot}
|
onClickGenerateRobot={onClickGenerateRobot}
|
||||||
/>
|
/>
|
||||||
<InactiveMakerDialog />
|
<InactiveMakerDialog />
|
||||||
|
@ -264,12 +264,7 @@ const OrderDetails = ({
|
|||||||
{' '}
|
{' '}
|
||||||
<Grid container direction='row' justifyContent='center' alignItems='center'>
|
<Grid container direction='row' justifyContent='center' alignItems='center'>
|
||||||
<Grid item xs={2}>
|
<Grid item xs={2}>
|
||||||
<RobotAvatar
|
<RobotAvatar shortAlias={coordinator.shortAlias} coordinator={true} small={true} />
|
||||||
nickname={coordinator.shortAlias}
|
|
||||||
coordinator={true}
|
|
||||||
baseUrl={hostUrl}
|
|
||||||
small={true}
|
|
||||||
/>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={4}>
|
<Grid item xs={4}>
|
||||||
<ListItemText primary={coordinator.longAlias} secondary={t('Order host')} />
|
<ListItemText primary={coordinator.longAlias} secondary={t('Order host')} />
|
||||||
@ -283,10 +278,9 @@ const OrderDetails = ({
|
|||||||
<ListItemAvatar sx={{ width: '4em', height: '4em' }}>
|
<ListItemAvatar sx={{ width: '4em', height: '4em' }}>
|
||||||
<RobotAvatar
|
<RobotAvatar
|
||||||
statusColor={statusBadgeColor(currentOrder?.maker_status ?? '')}
|
statusColor={statusBadgeColor(currentOrder?.maker_status ?? '')}
|
||||||
nickname={currentOrder?.maker_nick}
|
hashId={currentOrder?.maker_hash_id}
|
||||||
tooltip={t(currentOrder?.maker_status ?? '')}
|
tooltip={t(currentOrder?.maker_status ?? '')}
|
||||||
orderType={currentOrder?.type}
|
orderType={currentOrder?.type}
|
||||||
baseUrl={baseUrl}
|
|
||||||
small={true}
|
small={true}
|
||||||
/>
|
/>
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
@ -315,12 +309,11 @@ const OrderDetails = ({
|
|||||||
<RobotAvatar
|
<RobotAvatar
|
||||||
avatarClass='smallAvatar'
|
avatarClass='smallAvatar'
|
||||||
statusColor={statusBadgeColor(currentOrder?.taker_status ?? '')}
|
statusColor={statusBadgeColor(currentOrder?.taker_status ?? '')}
|
||||||
nickname={
|
hashId={
|
||||||
currentOrder?.taker_nick === 'None' ? undefined : currentOrder?.taker_nick
|
currentOrder?.taker_hash_id === 'None' ? undefined : currentOrder?.taker_hash_id
|
||||||
}
|
}
|
||||||
tooltip={t(currentOrder?.taker_status ?? '')}
|
tooltip={t(currentOrder?.taker_status ?? '')}
|
||||||
orderType={currentOrder?.type === 0 ? 1 : 0}
|
orderType={currentOrder?.type === 0 ? 1 : 0}
|
||||||
baseUrl={baseUrl}
|
|
||||||
small={true}
|
small={true}
|
||||||
/>
|
/>
|
||||||
</ListItemAvatar>
|
</ListItemAvatar>
|
||||||
|
73
frontend/src/components/RobotAvatar/RobohashGenerator.ts
Normal file
73
frontend/src/components/RobotAvatar/RobohashGenerator.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
class RoboGenerator {
|
||||||
|
private assetsCache: Record<string, string> = {};
|
||||||
|
private assetsPromises: Record<string, Promise<string>> = {};
|
||||||
|
private readonly workers: Worker[] = [];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// limit to 8 workers
|
||||||
|
const numCores = Math.min(navigator.hardwareConcurrency || 1, 8);
|
||||||
|
|
||||||
|
for (let i = 0; i < numCores; i++) {
|
||||||
|
const worker = new Worker(new URL('./robohash.worker.ts', import.meta.url));
|
||||||
|
this.workers.push(worker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public generate: (hash: string, size: 'small' | 'large') => Promise<string> = async (
|
||||||
|
hash,
|
||||||
|
size,
|
||||||
|
) => {
|
||||||
|
const cacheKey = `${size}px;${hash}`;
|
||||||
|
if (this.assetsCache[cacheKey]) {
|
||||||
|
return this.assetsCache[cacheKey];
|
||||||
|
} else if (cacheKey in this.assetsPromises) {
|
||||||
|
return await this.assetsPromises[cacheKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
const workerIndex = Object.keys(this.assetsPromises).length % this.workers.length;
|
||||||
|
const worker = this.workers[workerIndex];
|
||||||
|
|
||||||
|
this.assetsPromises[cacheKey] = new Promise<string>((resolve, reject) => {
|
||||||
|
// const avatarB64 = async_generate_robohash(hash, size == 'small' ? 80 : 256).then((avatarB64)=> resolve(`data:image/png;base64,${avatarB64}`));
|
||||||
|
// Create a message object with the necessary data
|
||||||
|
const message = { hash, size, cacheKey, workerIndex };
|
||||||
|
|
||||||
|
// Listen for messages from the worker
|
||||||
|
const handleMessage = (event: MessageEvent) => {
|
||||||
|
const { cacheKey, imageUrl } = event.data;
|
||||||
|
|
||||||
|
// Update the cache and resolve the promise
|
||||||
|
this.assetsCache[cacheKey] = imageUrl;
|
||||||
|
delete this.assetsPromises[cacheKey];
|
||||||
|
resolve(imageUrl);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add the event listener for messages
|
||||||
|
worker.addEventListener('message', handleMessage);
|
||||||
|
|
||||||
|
// Send the message to the worker
|
||||||
|
worker.postMessage(message);
|
||||||
|
|
||||||
|
// Clean up the event listener after receiving the result
|
||||||
|
const cleanup = () => {
|
||||||
|
worker.removeEventListener('message', handleMessage);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reject the promise if an error occurs
|
||||||
|
worker.addEventListener('error', (error) => {
|
||||||
|
cleanup();
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reject the promise if the worker times out
|
||||||
|
setTimeout(() => {
|
||||||
|
cleanup();
|
||||||
|
reject(new Error('Generation timed out'));
|
||||||
|
}, 5000); // Adjust the timeout duration as needed
|
||||||
|
});
|
||||||
|
|
||||||
|
return await this.assetsPromises[cacheKey];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const robohash = new RoboGenerator();
|
@ -2,15 +2,14 @@ import React, { useCallback, useContext, useEffect, useMemo, useState } from 're
|
|||||||
import SmoothImage from 'react-smooth-image';
|
import SmoothImage from 'react-smooth-image';
|
||||||
import { Avatar, Badge, Tooltip } from '@mui/material';
|
import { Avatar, Badge, Tooltip } from '@mui/material';
|
||||||
import { SendReceiveIcon } from '../Icons';
|
import { SendReceiveIcon } from '../Icons';
|
||||||
import { apiClient } from '../../services/api';
|
|
||||||
import placeholder from './placeholder.json';
|
import placeholder from './placeholder.json';
|
||||||
import { type UseAppStoreType, AppContext } from '../../contexts/AppContext';
|
import { robohash } from './RobohashGenerator';
|
||||||
import { type UseFederationStoreType, FederationContext } from '../../contexts/FederationContext';
|
import { AppContext, UseAppStoreType } from '../../contexts/AppContext';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
nickname: string | undefined;
|
shortAlias?: string | undefined;
|
||||||
|
hashId?: string | undefined;
|
||||||
smooth?: boolean;
|
smooth?: boolean;
|
||||||
coordinator?: boolean;
|
|
||||||
small?: boolean;
|
small?: boolean;
|
||||||
flipHorizontally?: boolean;
|
flipHorizontally?: boolean;
|
||||||
style?: object;
|
style?: object;
|
||||||
@ -22,7 +21,6 @@ interface Props {
|
|||||||
tooltipPosition?: string;
|
tooltipPosition?: string;
|
||||||
avatarClass?: string;
|
avatarClass?: string;
|
||||||
onLoad?: () => void;
|
onLoad?: () => void;
|
||||||
baseUrl: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BackgroundData {
|
interface BackgroundData {
|
||||||
@ -31,7 +29,8 @@ interface BackgroundData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const RobotAvatar: React.FC<Props> = ({
|
const RobotAvatar: React.FC<Props> = ({
|
||||||
nickname,
|
shortAlias,
|
||||||
|
hashId,
|
||||||
orderType,
|
orderType,
|
||||||
statusColor,
|
statusColor,
|
||||||
tooltip,
|
tooltip,
|
||||||
@ -44,14 +43,12 @@ const RobotAvatar: React.FC<Props> = ({
|
|||||||
avatarClass = 'flippedSmallAvatar',
|
avatarClass = 'flippedSmallAvatar',
|
||||||
imageStyle = {},
|
imageStyle = {},
|
||||||
onLoad = () => {},
|
onLoad = () => {},
|
||||||
coordinator = false,
|
|
||||||
baseUrl,
|
|
||||||
}) => {
|
}) => {
|
||||||
const [avatarSrc, setAvatarSrc] = useState<string>();
|
const [avatarSrc, setAvatarSrc] = useState<string>('');
|
||||||
const [nicknameReady, setNicknameReady] = useState<boolean>(false);
|
|
||||||
const [activeBackground, setActiveBackground] = useState<boolean>(true);
|
const [activeBackground, setActiveBackground] = useState<boolean>(true);
|
||||||
|
const { hostUrl } = useContext<UseAppStoreType>(AppContext);
|
||||||
|
const backgroundFadeTime = 3000;
|
||||||
|
|
||||||
const path = coordinator ? '/static/federation/avatars/' : '/static/assets/avatars/';
|
|
||||||
const [backgroundData] = useState<BackgroundData>(
|
const [backgroundData] = useState<BackgroundData>(
|
||||||
placeholderType === 'generating' ? placeholder.generating : placeholder.loading,
|
placeholderType === 'generating' ? placeholder.generating : placeholder.loading,
|
||||||
);
|
);
|
||||||
@ -59,21 +56,42 @@ const RobotAvatar: React.FC<Props> = ({
|
|||||||
const className = placeholderType === 'loading' ? 'loadingAvatar' : 'generatingAvatar';
|
const className = placeholderType === 'loading' ? 'loadingAvatar' : 'generatingAvatar';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (nickname !== undefined) {
|
// TODO: HANDLE ANDROID AVATARS TOO (when window.NativeRobosats !== undefined)
|
||||||
|
if (hashId !== undefined) {
|
||||||
|
robohash
|
||||||
|
.generate(hashId, small ? 'small' : 'large')
|
||||||
|
.then((avatar) => {
|
||||||
|
setAvatarSrc(avatar);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setAvatarSrc('');
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
setActiveBackground(false);
|
||||||
|
}, backgroundFadeTime);
|
||||||
|
}
|
||||||
|
}, [hashId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (shortAlias !== undefined) {
|
||||||
if (window.NativeRobosats === undefined) {
|
if (window.NativeRobosats === undefined) {
|
||||||
setAvatarSrc(`${baseUrl}${path}${nickname}${small ? '.small' : ''}.webp`);
|
setAvatarSrc(
|
||||||
setNicknameReady(true);
|
`${hostUrl}/static/federation/avatars/${shortAlias}${small ? '.small' : ''}.webp`,
|
||||||
} else if (baseUrl != null && apiClient.fileImageUrl !== undefined) {
|
);
|
||||||
setNicknameReady(true);
|
} else {
|
||||||
void apiClient
|
setAvatarSrc(
|
||||||
.fileImageUrl(baseUrl, `${path}${nickname}${small ? '.small' : ''}.webp`)
|
`file:///android_asset/Web.bundle/assets/federation/avatars/${shortAlias}${
|
||||||
.then(setAvatarSrc);
|
small ? ' .small' : ''
|
||||||
|
}.webp`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
setActiveBackground(false);
|
||||||
|
}, backgroundFadeTime);
|
||||||
} else {
|
} else {
|
||||||
setNicknameReady(false);
|
|
||||||
setActiveBackground(true);
|
setActiveBackground(true);
|
||||||
}
|
}
|
||||||
}, [nickname]);
|
}, [shortAlias]); // TODO: should hashId
|
||||||
|
|
||||||
const statusBadge = (
|
const statusBadge = (
|
||||||
<div style={{ position: 'relative', left: '0.428em', top: '0.07em' }}>
|
<div style={{ position: 'relative', left: '0.428em', top: '0.07em' }}>
|
||||||
@ -104,15 +122,12 @@ const RobotAvatar: React.FC<Props> = ({
|
|||||||
>
|
>
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<SmoothImage
|
<SmoothImage
|
||||||
src={nicknameReady ? avatarSrc : null}
|
src={avatarSrc}
|
||||||
imageStyles={{
|
imageStyles={{
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
border: '0.3px solid #55555',
|
border: '0.3px solid #55555',
|
||||||
filter: 'dropShadow(0.5px 0.5px 0.5px #000000)',
|
filter: 'dropShadow(0.5px 0.5px 0.5px #000000)',
|
||||||
...imageStyle,
|
...imageStyle,
|
||||||
onLoad: setTimeout(() => {
|
|
||||||
setActiveBackground(false);
|
|
||||||
}, 5000),
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -123,8 +138,8 @@ const RobotAvatar: React.FC<Props> = ({
|
|||||||
<Avatar
|
<Avatar
|
||||||
className={avatarClass}
|
className={avatarClass}
|
||||||
style={style}
|
style={style}
|
||||||
alt={nickname}
|
alt={hashId ?? shortAlias ?? 'unknown'}
|
||||||
src={nicknameReady ? avatarSrc : null}
|
src={avatarSrc}
|
||||||
imgProps={{
|
imgProps={{
|
||||||
sx: { transform: flipHorizontally ? 'scaleX(-1)' : '' },
|
sx: { transform: flipHorizontally ? 'scaleX(-1)' : '' },
|
||||||
style: { transform: flipHorizontally ? 'scaleX(-1)' : '' },
|
style: { transform: flipHorizontally ? 'scaleX(-1)' : '' },
|
||||||
@ -133,7 +148,7 @@ const RobotAvatar: React.FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [nickname, nicknameReady, avatarSrc, statusColor, tooltip, avatarClass]);
|
}, [hashId, shortAlias, avatarSrc, statusColor, tooltip, avatarClass]);
|
||||||
|
|
||||||
const getAvatarWithBadges = useCallback(() => {
|
const getAvatarWithBadges = useCallback(() => {
|
||||||
let component = avatar;
|
let component = avatar;
|
||||||
|
15
frontend/src/components/RobotAvatar/robohash.worker.ts
Normal file
15
frontend/src/components/RobotAvatar/robohash.worker.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { async_generate_robohash } from 'robo-identities-wasm';
|
||||||
|
|
||||||
|
// Listen for messages from the main thread
|
||||||
|
self.addEventListener('message', async (event) => {
|
||||||
|
const { hash, size, cacheKey, workerIndex } = event.data;
|
||||||
|
|
||||||
|
// Generate the image using async_image_base
|
||||||
|
const t0 = performance.now();
|
||||||
|
const avatarB64 = await async_generate_robohash(hash, size == 'small' ? 80 : 256);
|
||||||
|
const imageUrl = `data:image/png;base64,${avatarB64}`;
|
||||||
|
const t1 = performance.now();
|
||||||
|
console.log(`Worker ${workerIndex} :: Time to generate avatar: ${t1 - t0} ms`);
|
||||||
|
// Send the result back to the main thread
|
||||||
|
self.postMessage({ cacheKey, imageUrl });
|
||||||
|
});
|
@ -63,7 +63,7 @@ const EncryptedSocketChat: React.FC<Props> = ({
|
|||||||
const [error, setError] = useState<string>('');
|
const [error, setError] = useState<string>('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!connected && garage.getSlot()?.avatarLoaded === true) {
|
if (!connected && Boolean(garage.getSlot()?.hashId)) {
|
||||||
connectWebsocket();
|
connectWebsocket();
|
||||||
}
|
}
|
||||||
}, [connected, robotUpdatedAt]);
|
}, [connected, robotUpdatedAt]);
|
||||||
|
@ -152,10 +152,9 @@ export const useFederationStore = (): UseFederationStoreType => {
|
|||||||
const robot = slot?.getRobot();
|
const robot = slot?.getRobot();
|
||||||
|
|
||||||
if (robot != null && garage.currentSlot != null) {
|
if (robot != null && garage.currentSlot != null) {
|
||||||
if (open.profile && slot?.avatarLoaded === true && slot.token != null) {
|
if (open.profile && Boolean(slot?.hashId) && slot?.token) {
|
||||||
void federation.fetchRobot(garage, slot.token); // refresh/update existing robot
|
void federation.fetchRobot(garage, slot?.token); // refresh/update existing robot
|
||||||
} else if (
|
} else if (
|
||||||
!(slot?.avatarLoaded === true) &&
|
|
||||||
robot.token !== undefined &&
|
robot.token !== undefined &&
|
||||||
robot.encPrivKey !== undefined &&
|
robot.encPrivKey !== undefined &&
|
||||||
robot.pubKey !== undefined
|
robot.pubKey !== undefined
|
||||||
|
@ -20,6 +20,7 @@ export interface PublicOrder {
|
|||||||
maker: number;
|
maker: number;
|
||||||
escrow_duration: number;
|
escrow_duration: number;
|
||||||
maker_nick: string;
|
maker_nick: string;
|
||||||
|
maker_hash_id: string;
|
||||||
price: number;
|
price: number;
|
||||||
maker_status: 'Active' | 'Seen recently' | 'Inactive';
|
maker_status: 'Active' | 'Seen recently' | 'Inactive';
|
||||||
coordinatorShortAlias?: string;
|
coordinatorShortAlias?: string;
|
||||||
|
@ -10,6 +10,7 @@ import { apiClient } from '../services/api';
|
|||||||
import { validateTokenEntropy } from '../utils';
|
import { validateTokenEntropy } from '../utils';
|
||||||
import { compareUpdateLimit } from './Limit.model';
|
import { compareUpdateLimit } from './Limit.model';
|
||||||
import { defaultOrder } from './Order.model';
|
import { defaultOrder } from './Order.model';
|
||||||
|
import { robohash } from '../components/RobotAvatar/RobohashGenerator';
|
||||||
|
|
||||||
export interface Contact {
|
export interface Contact {
|
||||||
nostr?: string | undefined;
|
nostr?: string | undefined;
|
||||||
@ -156,6 +157,12 @@ export class Coordinator {
|
|||||||
this.loadInfo(onDataLoad);
|
this.loadInfo(onDataLoad);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
generateAllMakerAvatars = (data: [PublicOrder]) => {
|
||||||
|
for (const order of data) {
|
||||||
|
robohash.generate(order.maker_hash_id, 'small');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
loadBook = (onDataLoad: () => void = () => {}): void => {
|
loadBook = (onDataLoad: () => void = () => {}): void => {
|
||||||
if (this.enabled === false) return;
|
if (this.enabled === false) return;
|
||||||
if (this.loadingBook) return;
|
if (this.loadingBook) return;
|
||||||
@ -170,6 +177,7 @@ export class Coordinator {
|
|||||||
order.coordinatorShortAlias = this.shortAlias;
|
order.coordinatorShortAlias = this.shortAlias;
|
||||||
return order;
|
return order;
|
||||||
});
|
});
|
||||||
|
this.generateAllMakerAvatars(data);
|
||||||
onDataLoad();
|
onDataLoad();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -98,13 +98,12 @@ class Garage {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
updateSlot: (
|
updateSlot: (attributes: { copiedToken?: boolean }, token?: string) => Slot | null = (
|
||||||
attributes: { avatarLoaded?: boolean; copiedToken?: boolean },
|
attributes,
|
||||||
token?: string,
|
token,
|
||||||
) => Slot | null = (attributes, token) => {
|
) => {
|
||||||
const slot = this.getSlot(token);
|
const slot = this.getSlot(token);
|
||||||
if (attributes != null) {
|
if (attributes != null) {
|
||||||
if (attributes.avatarLoaded !== undefined) slot?.setAvatarLoaded(attributes.avatarLoaded);
|
|
||||||
if (attributes.copiedToken !== undefined) slot?.setCopiedToken(attributes.copiedToken);
|
if (attributes.copiedToken !== undefined) slot?.setCopiedToken(attributes.copiedToken);
|
||||||
this.triggerHook('onRobotUpdate');
|
this.triggerHook('onRobotUpdate');
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,9 @@ export interface Order {
|
|||||||
is_buyer: boolean;
|
is_buyer: boolean;
|
||||||
is_seller: boolean;
|
is_seller: boolean;
|
||||||
maker_nick: string;
|
maker_nick: string;
|
||||||
|
maker_hash_id: string;
|
||||||
taker_nick: string;
|
taker_nick: string;
|
||||||
|
taker_hash_id: string;
|
||||||
status_message: string;
|
status_message: string;
|
||||||
is_fiat_sent: boolean;
|
is_fiat_sent: boolean;
|
||||||
is_disputed: boolean;
|
is_disputed: boolean;
|
||||||
@ -150,7 +152,9 @@ export const defaultOrder: Order = {
|
|||||||
is_buyer: false,
|
is_buyer: false,
|
||||||
is_seller: false,
|
is_seller: false,
|
||||||
maker_nick: '',
|
maker_nick: '',
|
||||||
|
maker_hash_id: '',
|
||||||
taker_nick: '',
|
taker_nick: '',
|
||||||
|
taker_hash_id: '',
|
||||||
status_message: '',
|
status_message: '',
|
||||||
is_fiat_sent: false,
|
is_fiat_sent: false,
|
||||||
is_disputed: false,
|
is_disputed: false,
|
||||||
|
@ -20,7 +20,6 @@ class Robot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public nickname?: string;
|
|
||||||
public token?: string;
|
public token?: string;
|
||||||
public bitsEntropy?: number;
|
public bitsEntropy?: number;
|
||||||
public shannonEntropy?: number;
|
public shannonEntropy?: number;
|
||||||
|
@ -1,27 +1,34 @@
|
|||||||
|
import { sha256 } from 'js-sha256';
|
||||||
import { Robot, type Order } from '.';
|
import { Robot, type Order } from '.';
|
||||||
|
import { robohash } from '../components/RobotAvatar/RobohashGenerator';
|
||||||
|
import { generate_roboname } from 'robo-identities-wasm';
|
||||||
|
|
||||||
class Slot {
|
class Slot {
|
||||||
constructor(token: string) {
|
constructor(token: string) {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
|
|
||||||
|
this.hashId = sha256(sha256(this.token));
|
||||||
|
this.nickname = generate_roboname(this.hashId);
|
||||||
|
// trigger RoboHash avatar generation in webworker and store in RoboHash class cache.
|
||||||
|
robohash.generate(this.hashId, 'small');
|
||||||
|
robohash.generate(this.hashId, 'large');
|
||||||
|
|
||||||
this.robots = {};
|
this.robots = {};
|
||||||
this.order = null;
|
this.order = null;
|
||||||
|
|
||||||
this.activeShortAlias = null;
|
this.activeShortAlias = null;
|
||||||
this.lastShortAlias = null;
|
this.lastShortAlias = null;
|
||||||
this.copiedToken = false;
|
this.copiedToken = false;
|
||||||
this.avatarLoaded = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
token: string | null;
|
token: string | null;
|
||||||
|
hashId: string | null;
|
||||||
|
nickname: string | null;
|
||||||
robots: Record<string, Robot>;
|
robots: Record<string, Robot>;
|
||||||
order: Order | null;
|
order: Order | null;
|
||||||
activeShortAlias: string | null;
|
activeShortAlias: string | null;
|
||||||
lastShortAlias: string | null;
|
lastShortAlias: string | null;
|
||||||
copiedToken: boolean;
|
copiedToken: boolean;
|
||||||
avatarLoaded: boolean;
|
|
||||||
|
|
||||||
setAvatarLoaded = (avatarLoaded: boolean): void => {
|
|
||||||
this.avatarLoaded = avatarLoaded;
|
|
||||||
};
|
|
||||||
|
|
||||||
setCopiedToken = (copied: boolean): void => {
|
setCopiedToken = (copied: boolean): void => {
|
||||||
this.copiedToken = copied;
|
this.copiedToken = copied;
|
||||||
|
@ -18,6 +18,7 @@ const config: Configuration = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
experiments: { asyncWebAssembly: true },
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.tsx', '.ts', '.jsx', '.js'],
|
extensions: ['.tsx', '.ts', '.jsx', '.js'],
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user