react guild list

This commit is contained in:
Michael Peters 2021-12-29 18:50:03 -06:00
parent 7136fbfb0b
commit 22c258bafd
11 changed files with 166 additions and 56 deletions

View File

@ -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<GuildListElementProps> = (props: GuildListElementProps) => {
const { guild, activeGuild, setSelfActiveGuild } = props;
const rootRef = useRef<HTMLDivElement>(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 (
<BasicHover relativeToRef={rootRef} side={BasicHoverSide.RIGHT}>
<div className="guild-hover">
<div className="tab">{BaseElements.TAB_LEFT}</div>
<div className="info">
<div className="guild-name">{guildMeta.name}</div>
<div className={'connection ' + selfMember.status}>
<div className="status-circle" />
<div className="display-name" style={nameStyle}>{selfMember.displayName}</div>
</div>
</div>
</div>
</BasicHover>
)
}, [ 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 (
<div>
<div
className={className} ref={rootRef}
onClick={setSelfActiveGuild}
onMouseEnter={mouseEnterCallable} onMouseLeave={mouseLeaveCallable}
>
<div className="pill" />
<img src={iconSrc} alt="guild" />
</div>
{contextHover}
</div>
);
}
export default GuildListElement;

View File

@ -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<GuildListProps> = (props: GuildListProps) => {
const { guildsManager, ui } = props;
const [ guilds ] = useGuildListSubscription(guildsManager);
const [ activeGuild, setActiveGuild ] = useState<CombinedGuild | null>(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) => <GuildListElement key={guild.id} guild={guild} activeGuild={activeGuild} setSelfActiveGuild={() => { setActiveGuild(guild); } } />);
}, [ guilds ]);
return (
<div className="guild-list">
{guildElements}
</div>
);
}
export default GuildList;

View File

@ -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'), <GuildList ui={ui} guildsManager={guildsManager} />);
}
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'), <GuildElement guild={guild} />);
// // guild title
// ElementsUtil.unmountReactComponent(q.$('.guild-title-anchor'));
// ElementsUtil.mountReactComponent(q.$('.guild-title-anchor'), <GuildTitle guild={guild} />);
// // connection info
// ElementsUtil.unmountReactComponent(q.$('.connection-anchor'));
// ElementsUtil.mountReactComponent(q.$('.connection-anchor'), <ConnectionInfo guild={guild} />);
// // member-list
// ElementsUtil.unmountReactComponent(q.$('.member-list-anchor'));
// ElementsUtil.mountReactComponent(q.$('.member-list-anchor'), <MemberList guild={guild} />);
// // channel-list
// ElementsUtil.unmountReactComponent(q.$('.channel-list-anchor'));
// ElementsUtil.mountReactComponent(q.$('.channel-list-anchor'), <ChannelList guild={guild} ui={ui} />);
}
export function mountGuildChannelComponents(q: Q, guild: CombinedGuild, channel: Channel) {
// // channel-title pieces
// ElementsUtil.unmountReactComponent(q.$('.channel-title-anchor'));
// ElementsUtil.mountReactComponent(q.$('.channel-title-anchor'), <ChannelTitle channel={channel} />);
// // message-list
// ElementsUtil.unmountReactComponent(q.$('.message-list-anchor'));
// ElementsUtil.mountReactComponent(q.$('.message-list-anchor'), <MessageList guild={guild} channel={channel} />);
// // send-message
// ElementsUtil.unmountReactComponent(q.$('.send-message-input-wrapper-anchor'));
// ElementsUtil.mountReactComponent(q.$('.send-message-input-wrapper-anchor'), <SendMessage guild={guild} channel={channel} />);
}

View File

@ -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<string>(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 ];
}

View File

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

View File

@ -36,6 +36,7 @@
</div>
<div id="content">
<div id="guild-list-container">
<div class="guild-list-anchor"></div>
<div id="guild-list"></div>
<div id="add-guild">
<div class="pill"></div>

View File

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

View File

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

View File

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

View File

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