reactify personalize overlay

This commit is contained in:
Michael Peters 2021-12-09 01:13:11 -06:00
parent 711c2d1b75
commit 7fb4809a5e
5 changed files with 154 additions and 12 deletions

View File

@ -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>

View File

@ -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) {

View File

@ -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';

View 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;

View File

@ -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;
}
}
}