From 7fde5e70cfc3a73df30fda3463e3b6233a52df42 Mon Sep 17 00:00:00 2001 From: Michael Peters Date: Sat, 25 Dec 2021 12:58:20 -0600 Subject: [PATCH] react guild title --- src/client/webapp/elements/channel.tsx | 2 +- .../elements/context-menu-guild-title.tsx | 66 ------------------- ...o.tsx => context-menu-connection-info.tsx} | 6 +- .../context-menu-guild-title.tsx | 64 ++++++++++++++++++ .../webapp/elements/events-guild-title.ts | 20 ------ src/client/webapp/elements/mounts.tsx | 5 ++ .../elements/overlays/overlay-channel.tsx | 3 +- .../overlays/overlay-guild-settings.tsx | 15 ++--- .../elements/sections/connection-info.tsx | 6 +- .../webapp/elements/sections/guild-title.tsx | 50 ++++++++++++++ src/client/webapp/index.html | 2 +- src/client/webapp/preload.ts | 2 - src/client/webapp/styles/contexts.scss | 4 +- src/client/webapp/styles/guild.scss | 26 ++++---- src/client/webapp/ui.ts | 1 - 15 files changed, 147 insertions(+), 125 deletions(-) delete mode 100644 src/client/webapp/elements/context-menu-guild-title.tsx rename src/client/webapp/elements/context-menus/{context-connection-info.tsx => context-menu-connection-info.tsx} (89%) create mode 100644 src/client/webapp/elements/context-menus/context-menu-guild-title.tsx delete mode 100644 src/client/webapp/elements/events-guild-title.ts create mode 100644 src/client/webapp/elements/sections/guild-title.tsx diff --git a/src/client/webapp/elements/channel.tsx b/src/client/webapp/elements/channel.tsx index c009622..a1ab02b 100644 --- a/src/client/webapp/elements/channel.tsx +++ b/src/client/webapp/elements/channel.tsx @@ -36,7 +36,7 @@ export default function createChannel(document: Document, q: Q, ui: UI, guild: C if (modifyContextElement.parentElement) { modifyContextElement.parentElement.removeChild(modifyContextElement); } - ElementsUtil.presentReactOverlay(document, ); + ElementsUtil.presentReactOverlay(document, ); }); q.$$$(element, '.modify').addEventListener('mouseenter', () => { diff --git a/src/client/webapp/elements/context-menu-guild-title.tsx b/src/client/webapp/elements/context-menu-guild-title.tsx deleted file mode 100644 index f3d9598..0000000 --- a/src/client/webapp/elements/context-menu-guild-title.tsx +++ /dev/null @@ -1,66 +0,0 @@ -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 ElementsUtil from './require/elements-util.js'; -import BaseElements from './require/base-elements.js'; -import Q from '../q-module'; -import UI from '../ui'; - -import CombinedGuild from '../guild-combined'; - -import React from 'react'; -import ReactHelper from './require/react-helper'; -import GuildSettingsOverlay from './overlays/overlay-guild-settings'; -import ChannelOverlay from './overlays/overlay-channel'; - -export default function createGuildTitleContextMenu(document: Document, q: Q, ui: UI, guild: CombinedGuild): Element { - if (ui.activeConnection === null) { - LOG.warn('no active connection when creating guild title context menu'); - return ReactHelper.createElementFromJSX(
); - } - - const menuItems: JSX.Element[] = []; - - if (ui.activeConnection.privileges.includes('modify_profile')) { - menuItems.push( -
-
{BaseElements.COG}
-
Guild Settings
-
- ); - } - - if (ui.activeConnection.privileges.includes('modify_channels')) { - if (ui.activeConnection.privileges.includes('modify_profile')) { - menuItems.push(
); - } - menuItems.push( -
-
{BaseElements.CREATE}
-
Create Channel
-
- ); - } - - const element = BaseElements.createContextMenu(document, ( -
{menuItems}
- )); - - if (ui.activeConnection.privileges.includes('modify_profile')) { - q.$$$(element, '.item.guild-settings').addEventListener('click', async () => { - element.removeSelf(); - ElementsUtil.presentReactOverlay(document, ); - }); - } - - if (ui.activeConnection.privileges.includes('modify_channels')) { - q.$$$(element, '.item.create-channel').addEventListener('click', () => { - element.removeSelf(); - ElementsUtil.presentReactOverlay(document, ); - }); - } - - return element; -} diff --git a/src/client/webapp/elements/context-menus/context-connection-info.tsx b/src/client/webapp/elements/context-menus/context-menu-connection-info.tsx similarity index 89% rename from src/client/webapp/elements/context-menus/context-connection-info.tsx rename to src/client/webapp/elements/context-menus/context-menu-connection-info.tsx index 63c9477..7980bf4 100644 --- a/src/client/webapp/elements/context-menus/context-connection-info.tsx +++ b/src/client/webapp/elements/context-menus/context-menu-connection-info.tsx @@ -5,14 +5,14 @@ import PersonalizeOverlay from '../overlays/overlay-personalize'; import ElementsUtil from '../require/elements-util'; import ContextMenu from './components/context-menu'; -export interface ConnectionInfoContextProps { +export interface ConnectionInfoContextMenuProps { guild: CombinedGuild; selfMember: Member; relativeToRef: RefObject; close: () => void; } -const ConnectionInfoContext: FC = (props: ConnectionInfoContextProps) => { +const ConnectionInfoContextMenu: FC = (props: ConnectionInfoContextMenuProps) => { const { guild, selfMember, relativeToRef, close } = props; const setSelfStatus = useCallback(async (status: string) => { @@ -56,4 +56,4 @@ const ConnectionInfoContext: FC = (props: Connection ); } -export default ConnectionInfoContext; +export default ConnectionInfoContextMenu; diff --git a/src/client/webapp/elements/context-menus/context-menu-guild-title.tsx b/src/client/webapp/elements/context-menus/context-menu-guild-title.tsx new file mode 100644 index 0000000..6f7189f --- /dev/null +++ b/src/client/webapp/elements/context-menus/context-menu-guild-title.tsx @@ -0,0 +1,64 @@ +import React, { FC, RefObject, useCallback, useMemo } from 'react'; +import { GuildMetadata, Member } from '../../data-types'; +import CombinedGuild from '../../guild-combined'; +import ChannelOverlay from '../overlays/overlay-channel'; +import GuildSettingsOverlay from '../overlays/overlay-guild-settings'; +import BaseElements from '../require/base-elements'; +import ElementsUtil, { IAlignment } from '../require/elements-util'; +import ContextMenu from './components/context-menu'; + +export interface GuildTitleContextMenuProps { + alignment: IAlignment; + close: () => void; + relativeToRef: RefObject + guild: CombinedGuild; + guildMeta: GuildMetadata; + selfMember: Member; +} + +const GuildTitleContextMenu: FC = (props: GuildTitleContextMenuProps) => { + const { alignment, close, relativeToRef, guild, guildMeta, selfMember } = props; + + const openGuildSettings = useCallback(() => { + close(); + ElementsUtil.presentReactOverlay(document, ); + }, [ guild, guildMeta, close ]); + + const openCreateChannel = useCallback(() => { + close(); + ElementsUtil.presentReactOverlay(document, ); + }, [ guild, close ]); + + const guildSettingsElement = useMemo(() => { + if (!selfMember.privileges.includes('modify_profile')) return null; + return ( +
+
{BaseElements.COG}
+
Guild Settings
+
+ ); + }, [ selfMember, openGuildSettings ]); + + const createChannelElement = useMemo(() => { + if (!selfMember.privileges.includes('modify_channels')) return null; + return ( +
+
{BaseElements.CREATE}
+
Create Channel
+
+ ); + }, [ selfMember, openCreateChannel ]); + + if (guildSettingsElement === null && createChannelElement === null) return null; + + return ( + +
+ {guildSettingsElement} + {createChannelElement} +
+
+ ); +} + +export default GuildTitleContextMenu; diff --git a/src/client/webapp/elements/events-guild-title.ts b/src/client/webapp/elements/events-guild-title.ts deleted file mode 100644 index f6ce845..0000000 --- a/src/client/webapp/elements/events-guild-title.ts +++ /dev/null @@ -1,20 +0,0 @@ -import Q from '../q-module'; -import UI from '../ui'; -import createGuildTitleContextMenu from './context-menu-guild-title'; -import ElementsUtil from './require/elements-util'; - -export default function bindAddGuildTitleEvents(document: Document, q: Q, ui: UI) { - q.$('#guild-name-container').addEventListener('click', () => { - if (ui.activeConnection === null) return; - if (ui.activeGuild === null) return; - if (!ui.activeGuild.isSocketVerified()) return; - if ( - !ui.activeConnection.privileges.includes('modify_profile') && - !ui.activeConnection.privileges.includes('modify_members') - ) return; - - const contextMenu = createGuildTitleContextMenu(document, q, ui, ui.activeGuild) as HTMLElement; - document.body.appendChild(contextMenu); - ElementsUtil.alignContextElement(contextMenu, q.$('#guild-name-container'), { top: 'bottom', centerX: 'centerX' }); - }); -} diff --git a/src/client/webapp/elements/mounts.tsx b/src/client/webapp/elements/mounts.tsx index b294002..776d417 100644 --- a/src/client/webapp/elements/mounts.tsx +++ b/src/client/webapp/elements/mounts.tsx @@ -7,6 +7,7 @@ import MemberList from "./lists/member-list"; import MessageList from './lists/message-list'; import ChannelTitle from './sections/channel-title'; import ConnectionInfo from './sections/connection-info'; +import GuildTitle from './sections/guild-title'; export function mountBaseComponents() { // guild-list @@ -14,6 +15,10 @@ export function mountBaseComponents() { } export function mountGuildComponents(q: Q, guild: CombinedGuild) { + // guild title + ElementsUtil.unmountReactComponent(q.$('.guild-title-anchor')); + ElementsUtil.mountReactComponent(q.$('.guild-title-anchor'), ); + // connection info ElementsUtil.unmountReactComponent(q.$('.connection-anchor')); ElementsUtil.mountReactComponent(q.$('.connection-anchor'), ); diff --git a/src/client/webapp/elements/overlays/overlay-channel.tsx b/src/client/webapp/elements/overlays/overlay-channel.tsx index 01fd5c9..47103e3 100644 --- a/src/client/webapp/elements/overlays/overlay-channel.tsx +++ b/src/client/webapp/elements/overlays/overlay-channel.tsx @@ -15,12 +15,11 @@ import ReactHelper from '../require/react-helper'; import Button from '../components/button'; export interface ChannelOverlayProps { - document: Document; guild: CombinedGuild; channel?: Channel; } const ChannelOverlay: FC = (props: ChannelOverlayProps) => { - const { document, guild, channel } = props; + const { guild, channel } = props; const nameInputRef = createRef(); diff --git a/src/client/webapp/elements/overlays/overlay-guild-settings.tsx b/src/client/webapp/elements/overlays/overlay-guild-settings.tsx index 30dc1c8..378612f 100644 --- a/src/client/webapp/elements/overlays/overlay-guild-settings.tsx +++ b/src/client/webapp/elements/overlays/overlay-guild-settings.tsx @@ -3,20 +3,19 @@ const electronConsole = electronRemote.getGlobal('console') as Console; import Logger from '../../../../logger/logger'; const LOG = Logger.create(__filename, electronConsole); -import React, { FC, useEffect, useMemo, useState } from "react"; +import React, { FC, useEffect, useState } from "react"; import CombinedGuild from "../../guild-combined"; import ChoicesControl from "../components/control-choices"; import GuildInvitesDisplay from "../displays/display-guild-invites"; import GuildOverviewDisplay from "../displays/display-guild-overview"; -import GuildSubscriptions from "../require/guild-subscriptions"; +import { GuildMetadata } from '../../data-types'; export interface GuildSettingsOverlayProps { guild: CombinedGuild; + guildMeta: GuildMetadata; } const GuildSettingsOverlay: FC = (props: GuildSettingsOverlayProps) => { - const { guild } = props; - - const [ guildMeta, guildMetaError ] = GuildSubscriptions.useGuildMetadataSubscription(guild); + const { guild, guildMeta } = props; const [ selectedId, setSelectedId ] = useState('overview'); const [ display, setDisplay ] = useState(); @@ -27,13 +26,9 @@ const GuildSettingsOverlay: FC = (props: GuildSetting if (selectedId === 'invites' ) setDisplay(); }, [ selectedId ]); - const guildNameText = useMemo(() => { - return guildMetaError ? 'metadata error' : guildMeta?.name ?? 'loading...'; - }, [ guildMeta, guildMetaError ]) - return (
- = (props: ConnectionInfoProps) => const rootRef = useRef(null); - // TODO: Respond to and emit global context menu events to prevent multiple - // context menus from being open at once. (maybe this isn't very reacty though) const [ contextMenuOpen, setContextMenuOpen ] = useState(false); const [ selfMember ] = GuildSubscriptions.useSelfMemberSubscription(guild); @@ -40,7 +38,7 @@ const ConnectionInfo: FC = (props: ConnectionInfoProps) => const contextMenu = useMemo(() => { if (!selfMember) return null; - return { console.log('close'); setContextMenuOpen(false); }} /> + return { setContextMenuOpen(false); }} /> }, [ guild, selfMember, rootRef ]); const toggleContextMenu = useCallback(() => { diff --git a/src/client/webapp/elements/sections/guild-title.tsx b/src/client/webapp/elements/sections/guild-title.tsx new file mode 100644 index 0000000..afea9e4 --- /dev/null +++ b/src/client/webapp/elements/sections/guild-title.tsx @@ -0,0 +1,50 @@ +import React, { FC, useCallback, useMemo, useRef, useState } from 'react'; +import CombinedGuild from '../../guild-combined'; +import GuildTitleContextMenu from '../context-menus/context-menu-guild-title'; +import GuildSubscriptions from '../require/guild-subscriptions'; + +export interface GuildTitleProps { + guild: CombinedGuild; +} + +const GuildTitle: FC = (props: GuildTitleProps) => { + const { guild } = props; + + const rootRef = useRef(null); + + const [ contextMenuOpen, setContextMenuOpen ] = useState(false); + + // TODO: Handle fetch error + const [ guildMeta, fetchError ] = GuildSubscriptions.useGuildMetadataSubscription(guild); + const [ selfMember ] = GuildSubscriptions.useSelfMemberSubscription(guild); + + const alignment = useMemo(() => { + return { top: 'bottom', centerX: 'centerX' } + }, []); + + const contextMenu = useMemo(() => { + if (!guildMeta) return null; + if (!selfMember) return null; + return ( + { setContextMenuOpen(false); }} + guild={guild} guildMeta={guildMeta} selfMember={selfMember} + /> + ); + }, [ guild, guildMeta, selfMember, rootRef ]); + + const toggleContextMenu = useCallback(() => { + setContextMenuOpen(oldContextMenuOpen => !!contextMenu && !oldContextMenuOpen); + }, [ contextMenu ]); + + return ( +
+
+ {guildMeta?.name ?? null} +
+ {contextMenuOpen ? contextMenu : null} +
+ ); +} + +export default GuildTitle; diff --git a/src/client/webapp/index.html b/src/client/webapp/index.html index f6d5695..8dca8ac 100644 --- a/src/client/webapp/index.html +++ b/src/client/webapp/index.html @@ -44,7 +44,7 @@
-
+
diff --git a/src/client/webapp/preload.ts b/src/client/webapp/preload.ts index 53cdfe6..c00f415 100644 --- a/src/client/webapp/preload.ts +++ b/src/client/webapp/preload.ts @@ -18,7 +18,6 @@ import { Changes, Channel, ConnectionInfo, GuildMetadata, Member, Resource, Toke import Q from './q-module'; import bindWindowButtonEvents from './elements/events-window-buttons'; import bindTextInputEvents from './elements/events-text-input'; -import bindAddGuildTitleEvents from './elements/events-guild-title'; import bindAddGuildEvents from './elements/events-add-guild'; import PersonalDB from './personal-db'; import MessageRAMCache from './message-ram-cache'; @@ -73,7 +72,6 @@ window.addEventListener('DOMContentLoaded', () => { bindWindowButtonEvents(q); bindTextInputEvents(document, q, ui); - bindAddGuildTitleEvents(document, q, ui); bindAddGuildEvents(document, q, ui, guildsManager); LOG.silly('events bound'); diff --git a/src/client/webapp/styles/contexts.scss b/src/client/webapp/styles/contexts.scss index 98e020b..f1fa078 100644 --- a/src/client/webapp/styles/contexts.scss +++ b/src/client/webapp/styles/contexts.scss @@ -53,8 +53,8 @@ margin-right: 8px; } - .guild-title-context .item .icon img, - .guild-title-context .item .icon svg { + .guild-title-context-menu .item .icon img, + .guild-title-context-menu .item .icon svg { width: 16px; height: 16px; } diff --git a/src/client/webapp/styles/guild.scss b/src/client/webapp/styles/guild.scss index 699ec9e..686e982 100644 --- a/src/client/webapp/styles/guild.scss +++ b/src/client/webapp/styles/guild.scss @@ -14,25 +14,25 @@ border-top-left-radius: 8px; } -#guild-name-container { +// TODO: just do this with inline styles +.privilege-modify_profile .guild-name-container, +.privilege-modify_members .guild-name-container { + cursor: pointer; +} + +.guild-name-container { padding: 0 16px; height: 48px; font-weight: 600; display: flex; align-items: center; - cursor: pointer; color: $header-primary; border-bottom: 1px solid $background-secondary-alt; -} -#guild-name { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -#guild.privilege-modify_profile #guild-name, -#guild.privilege-modify_members #guild-name { - cursor: pointer; + .guild-name { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } } diff --git a/src/client/webapp/ui.ts b/src/client/webapp/ui.ts index 8e4146b..bef6f7d 100644 --- a/src/client/webapp/ui.ts +++ b/src/client/webapp/ui.ts @@ -180,7 +180,6 @@ export default class UI { public async updateGuildName(guild: CombinedGuild, name: string): Promise{ await this.lockGuildName(guild, () => { - this.q.$('#guild-name').innerText = name; const baseElement = this.q.$('#guild-list .guild[data-id="' + guild.id + '"]'); baseElement.setAttribute('meta-name', name); });