reactify personalize overlay
This commit is contained in:
parent
711c2d1b75
commit
7fb4809a5e
@ -8,10 +8,11 @@ export interface SubmitOverlayLowerProps {
|
||||
buttonMessage: string;
|
||||
doSubmit: () => Promise<boolean>;
|
||||
errorMessage: string | null;
|
||||
infoMessage?: string | null;
|
||||
}
|
||||
|
||||
const SubmitOverlayLower: FC<SubmitOverlayLowerProps> = (props: SubmitOverlayLowerProps) => {
|
||||
const { buttonMessage, doSubmit, errorMessage } = props;
|
||||
const { buttonMessage, doSubmit, errorMessage, infoMessage } = props;
|
||||
|
||||
const [ shaking, setShaking ] = useState<boolean>(false)
|
||||
const [ submitting, setSubmitting ] = useState<boolean>(false);
|
||||
@ -38,12 +39,13 @@ const SubmitOverlayLower: FC<SubmitOverlayLowerProps> = (props: SubmitOverlayLow
|
||||
}
|
||||
}
|
||||
|
||||
const isEmpty = useMemo(() => errorMessage === null, [ errorMessage ]);
|
||||
const message = useMemo(() => errorMessage ?? infoMessage ?? null, [ errorMessage, infoMessage ]);
|
||||
const isEmpty = useMemo(() => message === null, [ message ]);
|
||||
|
||||
return (
|
||||
<div className="lower-react">
|
||||
<div className={'error-container' + (isEmpty ? ' empty' : '')}>
|
||||
<div className="error">{errorMessage}</div>
|
||||
<div className="error">{errorMessage ?? infoMessage ?? null}</div>
|
||||
</div>
|
||||
<div className="buttons">
|
||||
<Button onClick={onSubmit} shaking={shaking}>{buttonText}</Button>
|
||||
|
@ -7,6 +7,7 @@ import createPersonalizeOverlay from './overlay-personalize.js';
|
||||
import Q from '../q-module.js';
|
||||
import UI from '../ui.js';
|
||||
import CombinedGuild from '../guild-combined.js';
|
||||
import PersonalizeOverlay from './overlays/overlay-personalize.js';
|
||||
|
||||
export default function createConnectionContextMenu(document: Document, q: Q, ui: UI, guild: CombinedGuild) {
|
||||
const statuses = [ 'online', 'away', 'busy', 'invisible' ];
|
||||
@ -29,10 +30,7 @@ export default function createConnectionContextMenu(document: Document, q: Q, ui
|
||||
q.$$$(element, '.personalize').addEventListener('click', async () => {
|
||||
element.removeSelf();
|
||||
if (ui.activeConnection === null) return;
|
||||
const overlayElement = createPersonalizeOverlay(document, q, guild, ui.activeConnection);
|
||||
document.body.appendChild(overlayElement);
|
||||
q.$$$(overlayElement, '.text-input').focus();
|
||||
ElementsUtil.setCursorToEnd(q.$$$(overlayElement, '.text-input'));
|
||||
ElementsUtil.presentReactOverlay(document, <PersonalizeOverlay document={document} guild={guild} connection={ui.activeConnection} />);
|
||||
});
|
||||
|
||||
for (const status of statuses) {
|
||||
|
@ -9,7 +9,6 @@ import { GuildMetadata } from '../../data-types';
|
||||
import Globals from '../../globals';
|
||||
import CombinedGuild from '../../guild-combined';
|
||||
import Display from '../components/display';
|
||||
import ElementsUtil from '../require/elements-util';
|
||||
import TextInput from '../components/input-text';
|
||||
import ImageEditInput from '../components/input-image-edit';
|
||||
|
||||
|
138
src/client/webapp/elements/overlays/overlay-personalize.tsx
Normal file
138
src/client/webapp/elements/overlays/overlay-personalize.tsx
Normal file
@ -0,0 +1,138 @@
|
||||
import * as electronRemote from '@electron/remote';
|
||||
const electronConsole = electronRemote.getGlobal('console') as Console;
|
||||
import Logger from '../../../../logger/logger';
|
||||
const LOG = Logger.create(__filename, electronConsole);
|
||||
|
||||
import moment from 'moment';
|
||||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { ConnectionInfo } from '../../data-types';
|
||||
import Globals from '../../globals';
|
||||
import CombinedGuild from '../../guild-combined';
|
||||
import ImageEditInput from '../components/input-image-edit';
|
||||
import TextInput from '../components/input-text';
|
||||
import SubmitOverlayLower from '../components/submit-overlay-lower';
|
||||
import ElementsUtil from '../require/elements-util';
|
||||
|
||||
export interface PersonalizeOverlayProps {
|
||||
document: Document;
|
||||
guild: CombinedGuild;
|
||||
connection: ConnectionInfo;
|
||||
}
|
||||
const PersonalizeOverlay: FC<PersonalizeOverlayProps> = (props: PersonalizeOverlayProps) => {
|
||||
const { document, guild, connection } = props;
|
||||
|
||||
if (connection.avatarResourceId === null) {
|
||||
throw new Error('bad avatar');
|
||||
}
|
||||
|
||||
const avatarResourceId = connection.avatarResourceId;
|
||||
|
||||
const [ savedDisplayName, setSavedDisplayName ] = useState<string>(connection.displayName);
|
||||
const [ savedAvatarBuff, setSavedAvatarBuff ] = useState<Buffer | null>(null);
|
||||
|
||||
const [ displayName, setDisplayName ] = useState<string>(connection.displayName);
|
||||
const [ avatarBuff, setAvatarBuff ] = useState<Buffer | null>(null);
|
||||
|
||||
const [ loadAvatarFailed, setLoadAvatarFailed ] = useState<boolean>(false);
|
||||
const [ saveFailed, setSaveFailed ] = useState<boolean>(false);
|
||||
|
||||
const [ displayNameInputValid, setDisplayNameInputValid ] = useState<boolean>(false);
|
||||
const [ displayNameInputMessage, setDisplayNameInputMessage ] = useState<string | null>(null);
|
||||
|
||||
const [ avatarInputValid, setAvatarInputValid ] = useState<boolean>(false);
|
||||
const [ avatarInputMessage, setAvatarInputMessage ] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const avatarResource = await guild.fetchResource(avatarResourceId);
|
||||
setSavedAvatarBuff(avatarResource.data);
|
||||
setAvatarBuff(avatarResource.data);
|
||||
} catch (e: unknown) {
|
||||
LOG.error('Error loading icon resource', e);
|
||||
setLoadAvatarFailed(true);
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const errorMessage = useMemo(() => {
|
||||
if (loadAvatarFailed) {
|
||||
return 'Unable to load avatar';
|
||||
} else if (!avatarInputValid && avatarInputMessage) {
|
||||
return avatarInputMessage;
|
||||
} else if (!displayNameInputValid && displayNameInputMessage) {
|
||||
return displayNameInputMessage;
|
||||
} else if (saveFailed) {
|
||||
return 'Unable to save personalization';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, [ saveFailed, loadAvatarFailed, displayNameInputValid, displayNameInputMessage, avatarInputValid, avatarInputMessage ]);
|
||||
|
||||
const infoMessage = useMemo(() => {
|
||||
if (avatarInputValid && avatarInputMessage) {
|
||||
return avatarInputMessage;
|
||||
} else if (displayNameInputValid && displayNameInputMessage) {
|
||||
return displayNameInputMessage;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}, [ displayNameInputValid, displayNameInputMessage, avatarInputValid, avatarInputMessage ]);
|
||||
|
||||
const doSubmit = useCallback(async (): Promise<boolean> => {
|
||||
if (errorMessage) return false;
|
||||
|
||||
if (displayName !== savedDisplayName) {
|
||||
// Save display name
|
||||
try {
|
||||
await guild.requestSetDisplayName(displayName);
|
||||
setSavedDisplayName(displayName);
|
||||
} catch (e: unknown) {
|
||||
LOG.error('error setting guild name', e);
|
||||
setSaveFailed(true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (avatarBuff && avatarBuff?.toString('hex') !== savedAvatarBuff?.toString('hex')) {
|
||||
// Save avatar
|
||||
try {
|
||||
await guild.requestSetAvatar(avatarBuff);
|
||||
setSavedAvatarBuff(avatarBuff);
|
||||
} catch (e: unknown) {
|
||||
LOG.error('error setting guild icon', e);
|
||||
setSaveFailed(true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ElementsUtil.closeReactOverlay(document);
|
||||
|
||||
return true;
|
||||
}, [ errorMessage, displayName, savedDisplayName, avatarBuff, savedAvatarBuff ]);
|
||||
|
||||
return (
|
||||
<div className="content personalize">
|
||||
<div className="personalization">
|
||||
<div className="avatar">
|
||||
<ImageEditInput
|
||||
maxSize={Globals.MAX_AVATAR_SIZE}
|
||||
value={avatarBuff} setValue={setAvatarBuff}
|
||||
setValid={setAvatarInputValid} setMessage={setAvatarInputMessage}
|
||||
/>
|
||||
</div>
|
||||
<div className="display-name">
|
||||
<TextInput
|
||||
label="Display Name" placeholder={savedDisplayName}
|
||||
maxLength={Globals.MAX_DISPLAY_NAME_LENGTH}
|
||||
value={displayName} setValue={setDisplayName}
|
||||
setValid={setDisplayNameInputValid} setMessage={setDisplayNameInputMessage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<SubmitOverlayLower buttonMessage="Save" doSubmit={doSubmit} errorMessage={errorMessage} infoMessage={infoMessage} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PersonalizeOverlay;
|
@ -203,11 +203,16 @@ body > .overlay,
|
||||
/* Personalization Overlay */
|
||||
|
||||
> .content.personalize {
|
||||
min-width: 350px;
|
||||
background-color: $background-primary;
|
||||
border-radius: 8px;
|
||||
|
||||
.message {
|
||||
margin: 16px;
|
||||
padding: 0 4px;
|
||||
.personalization {
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
|
||||
.display-name {
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user