From 7fb4809a5ef231af9d6b94868e4bdda26efb9c73 Mon Sep 17 00:00:00 2001 From: Michael Peters Date: Thu, 9 Dec 2021 01:13:11 -0600 Subject: [PATCH] reactify personalize overlay --- .../components/submit-overlay-lower.tsx | 8 +- .../webapp/elements/context-menu-conn.tsx | 6 +- .../displays/display-guild-overview.tsx | 1 - .../elements/overlays/overlay-personalize.tsx | 138 ++++++++++++++++++ src/client/webapp/styles/overlays.scss | 13 +- 5 files changed, 154 insertions(+), 12 deletions(-) create mode 100644 src/client/webapp/elements/overlays/overlay-personalize.tsx diff --git a/src/client/webapp/elements/components/submit-overlay-lower.tsx b/src/client/webapp/elements/components/submit-overlay-lower.tsx index da2cd6d..2954eb2 100644 --- a/src/client/webapp/elements/components/submit-overlay-lower.tsx +++ b/src/client/webapp/elements/components/submit-overlay-lower.tsx @@ -8,10 +8,11 @@ export interface SubmitOverlayLowerProps { buttonMessage: string; doSubmit: () => Promise; errorMessage: string | null; + infoMessage?: string | null; } const SubmitOverlayLower: FC = (props: SubmitOverlayLowerProps) => { - const { buttonMessage, doSubmit, errorMessage } = props; + const { buttonMessage, doSubmit, errorMessage, infoMessage } = props; const [ shaking, setShaking ] = useState(false) const [ submitting, setSubmitting ] = useState(false); @@ -38,12 +39,13 @@ const SubmitOverlayLower: FC = (props: SubmitOverlayLow } } - const isEmpty = useMemo(() => errorMessage === null, [ errorMessage ]); + const message = useMemo(() => errorMessage ?? infoMessage ?? null, [ errorMessage, infoMessage ]); + const isEmpty = useMemo(() => message === null, [ message ]); return (
-
{errorMessage}
+
{errorMessage ?? infoMessage ?? null}
diff --git a/src/client/webapp/elements/context-menu-conn.tsx b/src/client/webapp/elements/context-menu-conn.tsx index 30ffbe7..42faf2a 100644 --- a/src/client/webapp/elements/context-menu-conn.tsx +++ b/src/client/webapp/elements/context-menu-conn.tsx @@ -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, ); }); for (const status of statuses) { diff --git a/src/client/webapp/elements/displays/display-guild-overview.tsx b/src/client/webapp/elements/displays/display-guild-overview.tsx index f8c0f59..c0d6ab1 100644 --- a/src/client/webapp/elements/displays/display-guild-overview.tsx +++ b/src/client/webapp/elements/displays/display-guild-overview.tsx @@ -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'; diff --git a/src/client/webapp/elements/overlays/overlay-personalize.tsx b/src/client/webapp/elements/overlays/overlay-personalize.tsx new file mode 100644 index 0000000..a3222b7 --- /dev/null +++ b/src/client/webapp/elements/overlays/overlay-personalize.tsx @@ -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 = (props: PersonalizeOverlayProps) => { + const { document, guild, connection } = props; + + if (connection.avatarResourceId === null) { + throw new Error('bad avatar'); + } + + const avatarResourceId = connection.avatarResourceId; + + const [ savedDisplayName, setSavedDisplayName ] = useState(connection.displayName); + const [ savedAvatarBuff, setSavedAvatarBuff ] = useState(null); + + const [ displayName, setDisplayName ] = useState(connection.displayName); + const [ avatarBuff, setAvatarBuff ] = useState(null); + + const [ loadAvatarFailed, setLoadAvatarFailed ] = useState(false); + const [ saveFailed, setSaveFailed ] = useState(false); + + const [ displayNameInputValid, setDisplayNameInputValid ] = useState(false); + const [ displayNameInputMessage, setDisplayNameInputMessage ] = useState(null); + + const [ avatarInputValid, setAvatarInputValid ] = useState(false); + const [ avatarInputMessage, setAvatarInputMessage ] = useState(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 => { + 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 ( +
+
+
+ +
+
+ +
+
+ +
+ ); +} + +export default PersonalizeOverlay; diff --git a/src/client/webapp/styles/overlays.scss b/src/client/webapp/styles/overlays.scss index 27d7061..55d1884 100644 --- a/src/client/webapp/styles/overlays.scss +++ b/src/client/webapp/styles/overlays.scss @@ -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; + } } }