diff --git a/src/client/webapp/elements/lists/components/guild-list-element.tsx b/src/client/webapp/elements/lists/components/guild-list-element.tsx new file mode 100644 index 0000000..1f724db --- /dev/null +++ b/src/client/webapp/elements/lists/components/guild-list-element.tsx @@ -0,0 +1,65 @@ +import React, { FC, useMemo, useRef } from 'react'; +import CombinedGuild from '../../../guild-combined'; +import BasicHover, { BasicHoverSide } from '../../contexts/context-hover-basic'; +import BaseElements from '../../require/base-elements'; +import GuildSubscriptions from '../../require/guild-subscriptions'; +import ReactHelper from '../../require/react-helper'; + +export interface GuildListElementProps { + guild: CombinedGuild; + activeGuild: CombinedGuild | null; + setSelfActiveGuild: () => void; +} + +const GuildListElement: FC = (props: GuildListElementProps) => { + const { guild, activeGuild, setSelfActiveGuild } = props; + + const rootRef = useRef(null); + + // TODO: state higher up + // TODO: handle metadata error + const [ guildMeta, guildMetaError ] = GuildSubscriptions.useGuildMetadataSubscription(guild); + const [ selfMember ] = GuildSubscriptions.useSelfMemberSubscription(guild); + const [ iconSrc ] = GuildSubscriptions.useSoftImageSrcResourceSubscription(guild, guildMeta?.iconResourceId ?? null); + + const [ contextHover, mouseEnterCallable, mouseLeaveCallable ] = ReactHelper.useContextHover(() => { + if (!guildMeta) return null; + if (!selfMember) return null; + const nameStyle = selfMember.roleColor ? { color: selfMember.roleColor } : {}; + return ( + +
+
{BaseElements.TAB_LEFT}
+
+
{guildMeta.name}
+
+
+
{selfMember.displayName}
+
+
+
+ + ) + }, [ guildMeta, selfMember ]); + + const className = useMemo(() => { + console.log('active: ' + activeGuild?.id + '/ me: ' + guild.id); + return activeGuild && guild.id === activeGuild.id ? 'guild active' : 'guild'; + }, [ guild, activeGuild ]); + + return ( +
+
+
+ guild +
+ {contextHover} +
+ ); +} + +export default GuildListElement; diff --git a/src/client/webapp/elements/lists/guild-list.scss b/src/client/webapp/elements/lists/guild-list.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/client/webapp/elements/lists/guild-list.tsx b/src/client/webapp/elements/lists/guild-list.tsx new file mode 100644 index 0000000..5286c7a --- /dev/null +++ b/src/client/webapp/elements/lists/guild-list.tsx @@ -0,0 +1,41 @@ +import React, { FC, useEffect, useMemo, useState } from 'react'; +import CombinedGuild from '../../guild-combined'; +import GuildsManager from '../../guilds-manager'; +import UI from '../../ui'; +import Util from '../../util'; +import { useGuildListSubscription } from '../require/guild-manager-subscriptions'; +import GuildListElement from './components/guild-list-element'; + +export interface GuildListProps { + guildsManager: GuildsManager; + ui: UI; +} + +const GuildList: FC = (props: GuildListProps) => { + const { guildsManager, ui } = props; + + const [ guilds ] = useGuildListSubscription(guildsManager); + const [ activeGuild, setActiveGuild ] = useState(null); + + // TODO: Remove dependency on UI + useEffect(() => { + if (activeGuild !== null) { + (async () => { + await Util.sleep(0); + ui.setActiveGuild(activeGuild); + })(); + } + }, [ activeGuild ]); + + const guildElements = useMemo(() => { + return guilds.map((guild: CombinedGuild) => { setActiveGuild(guild); } } />); + }, [ guilds ]); + + return ( +
+ {guildElements} +
+ ); +} + +export default GuildList; diff --git a/src/client/webapp/elements/mounts.tsx b/src/client/webapp/elements/mounts.tsx index 64dd15d..7ca7e80 100644 --- a/src/client/webapp/elements/mounts.tsx +++ b/src/client/webapp/elements/mounts.tsx @@ -1,47 +1,22 @@ import React from 'react'; -import { Channel } from "../data-types"; import CombinedGuild from "../guild-combined"; +import GuildsManager from '../guilds-manager'; import Q from "../q-module"; -import ElementsUtil from "./require/elements-util"; import UI from '../ui'; +import GuildList from './lists/guild-list'; +import ElementsUtil from "./require/elements-util"; import GuildElement from './sections/guild'; -export function mountBaseComponents() { +export function mountBaseComponents(q: Q, ui: UI, guildsManager: GuildsManager) { // guild-list // TODO + console.log(q.$('.guild-list-anchor')); + ElementsUtil.unmountReactComponent(q.$('.guild-list-anchor')); + ElementsUtil.mountReactComponent(q.$('.guild-list-anchor'), ); } -export function mountGuildComponents(q: Q, ui: UI, guild: CombinedGuild) { +export function mountGuildComponents(q: Q, guild: CombinedGuild) { + console.log(q.$('.guild-anchor')); ElementsUtil.unmountReactComponent(q.$('.guild-anchor')); ElementsUtil.mountReactComponent(q.$('.guild-anchor'), ); - - // // 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'), ); - - // // member-list - // ElementsUtil.unmountReactComponent(q.$('.member-list-anchor')); - // ElementsUtil.mountReactComponent(q.$('.member-list-anchor'), ); - - // // channel-list - // ElementsUtil.unmountReactComponent(q.$('.channel-list-anchor')); - // ElementsUtil.mountReactComponent(q.$('.channel-list-anchor'), ); -} - -export function mountGuildChannelComponents(q: Q, guild: CombinedGuild, channel: Channel) { - // // channel-title pieces - // ElementsUtil.unmountReactComponent(q.$('.channel-title-anchor')); - // ElementsUtil.mountReactComponent(q.$('.channel-title-anchor'), ); - - // // message-list - // ElementsUtil.unmountReactComponent(q.$('.message-list-anchor')); - // ElementsUtil.mountReactComponent(q.$('.message-list-anchor'), ); - - // // send-message - // ElementsUtil.unmountReactComponent(q.$('.send-message-input-wrapper-anchor')); - // ElementsUtil.mountReactComponent(q.$('.send-message-input-wrapper-anchor'), ); } diff --git a/src/client/webapp/elements/require/guild-manager-subscriptions.ts b/src/client/webapp/elements/require/guild-manager-subscriptions.ts new file mode 100644 index 0000000..ea790bb --- /dev/null +++ b/src/client/webapp/elements/require/guild-manager-subscriptions.ts @@ -0,0 +1,25 @@ +import { useCallback, useEffect, useMemo, useState } from 'react'; +import * as uuid from 'uuid'; +import CombinedGuild from '../../guild-combined'; +import GuildsManager from "../../guilds-manager"; + +export function useGuildListSubscription(guildsManager: GuildsManager): [ guilds: CombinedGuild[] ] { + const [ refreshId, setRefreshId ] = useState(uuid.v4()); + + const refresh = useCallback(() => { + setRefreshId(uuid.v4()); + }, []); + + useEffect(() => { + guildsManager.on('update-guilds', refresh); + return () => { + guildsManager.off('update-guilds', refresh); + } + }, []); + + const guilds = useMemo(() => { + return guildsManager.guilds.slice(); + }, [ refreshId ]); + + return [ guilds ]; +} diff --git a/src/client/webapp/guilds-manager.ts b/src/client/webapp/guilds-manager.ts index a0ddb7a..aa78c6f 100644 --- a/src/client/webapp/guilds-manager.ts +++ b/src/client/webapp/guilds-manager.ts @@ -22,6 +22,8 @@ import { AutoVerifierChangesType } from './auto-verifier'; import { IDQuery, PartialMessageListQuery } from './auto-verifier-with-args'; export default class GuildsManager extends EventEmitter<{ + 'update-guilds': () => void; + 'connect': (guild: CombinedGuild) => void; 'disconnect': (guild: CombinedGuild) => void; 'verified': (guild: CombinedGuild) => void; @@ -77,6 +79,7 @@ export default class GuildsManager extends EventEmitter<{ await this.personalDB.clearAllMembersStatus(guild.id); this.guilds.push(guild); + this.emit('update-guilds'); // Forward guild events through this event emitter for (const eventName of GuildEventNames) { @@ -194,5 +197,6 @@ export default class GuildsManager extends EventEmitter<{ await this.personalDB.removeGuild(guild.id); }); this.guilds = this.guilds.filter(g => g.id != guild.id); + this.emit('update-guilds'); } } diff --git a/src/client/webapp/index.html b/src/client/webapp/index.html index cca919b..d2813d6 100644 --- a/src/client/webapp/index.html +++ b/src/client/webapp/index.html @@ -36,6 +36,7 @@
+
diff --git a/src/client/webapp/preload.ts b/src/client/webapp/preload.ts index 6fad0d8..7a3bffc 100644 --- a/src/client/webapp/preload.ts +++ b/src/client/webapp/preload.ts @@ -13,7 +13,7 @@ import GuildsManager from './guilds-manager'; import Globals from './globals'; import UI from './ui'; -import { Changes, GuildMetadata, Resource, Token } from './data-types'; +import { GuildMetadata } from './data-types'; import Q from './q-module'; import bindWindowButtonEvents from './elements/events-window-buttons'; import bindAddGuildEvents from './elements/events-add-guild'; @@ -22,7 +22,7 @@ import MessageRAMCache from './message-ram-cache'; import ResourceRAMCache from './resource-ram-cache'; import CombinedGuild from './guild-combined'; import { AutoVerifierChangesType } from './auto-verifier'; -import { IDQuery } from './auto-verifier-with-args'; +import { mountBaseComponents } from './elements/mounts'; LOG.silly('modules loaded'); @@ -73,6 +73,8 @@ window.addEventListener('DOMContentLoaded', () => { LOG.silly('events bound'); + mountBaseComponents(q, ui, guildsManager); + // Add guild icons await ui.setGuilds(guildsManager, guildsManager.guilds); diff --git a/src/client/webapp/styles/contexts.scss b/src/client/webapp/styles/contexts.scss index 63207dd..653bd84 100644 --- a/src/client/webapp/styles/contexts.scss +++ b/src/client/webapp/styles/contexts.scss @@ -100,22 +100,19 @@ border-radius: 4px; } - .info { + .guild-hover { display: flex; align-items: center; color: $background-floating; /* for the tab */ - .content { + .info { background-color: $background-floating; color: $header-primary; padding: 12px; border-radius: 4px; - } - - .content.guild { line-height: 1; - .name:not(:last-child) { + > :not(:last-child) { margin-bottom: 4px; } @@ -130,10 +127,6 @@ border-radius: 5px; margin-right: 4px; } - - .connection .display-name { - font-size: 12px; - } } } } diff --git a/src/client/webapp/styles/guild-list.scss b/src/client/webapp/styles/guild-list.scss index 7715aa9..7ee93ed 100644 --- a/src/client/webapp/styles/guild-list.scss +++ b/src/client/webapp/styles/guild-list.scss @@ -13,16 +13,20 @@ } #guild-list { + display: none; +} + +.guild-list { display: flex; flex-flow: column; } -#guild-list::-webkit-scrollbar { +.guild-listguild-list::-webkit-scrollbar { display: none; } #add-guild, -#guild-list .guild { +.guild-list .guild { cursor: pointer; margin-bottom: 8px; display: flex; @@ -30,7 +34,7 @@ } #add-guild .pill, -#guild-list .guild .pill { +.guild-list .guild .pill { background-color: $header-primary; width: 8px; height: 0; @@ -40,21 +44,21 @@ transition: height .1s ease-in-out; } -#guild-list .guild.active .pill { +.guild-list .guild.active .pill { height: 40px; } -#guild-list .guild.unread:not(.active) .pill { +.guild-list .guild.unread:not(.active) .pill { height: 8px; } #add-guild:hover .pill, -#guild-list .guild:not(.active):hover .pill { +.guild-list .guild:not(.active):hover .pill { height: 20px; } #add-guild img, -#guild-list .guild img { +.guild-list .guild img { width: 48px; height: 48px; border-radius: 24px; @@ -62,7 +66,7 @@ } #add-guild:hover img, -#guild-list .guild:hover img, -#guild-list .guild.active img { +.guild-list .guild:hover img, +.guild-list .guild.active img { border-radius: 16px; } diff --git a/src/client/webapp/ui.ts b/src/client/webapp/ui.ts index a42fb2e..9796e32 100644 --- a/src/client/webapp/ui.ts +++ b/src/client/webapp/ui.ts @@ -46,7 +46,7 @@ export default class UI { next.classList.add('active'); this.activeGuild = guild; - mountGuildComponents(this.q, this, guild); + mountGuildComponents(this.q, guild); } public async setGuilds(guildsManager: GuildsManager, guilds: CombinedGuild[]): Promise {