react text channel list
This commit is contained in:
parent
2f526c8ecc
commit
6f26181b43
37
.vscode/cordis.code-snippets
vendored
Normal file
37
.vscode/cordis.code-snippets
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
// Place your cordis-ts workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
|
||||
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
|
||||
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
|
||||
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
|
||||
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
|
||||
// Placeholders with the same ids are connected.
|
||||
// Example:
|
||||
// "Print to console": {
|
||||
// "scope": "javascript,typescript",
|
||||
// "prefix": "log",
|
||||
// "body": [
|
||||
// "console.log('$1');",
|
||||
// "$2"
|
||||
// ],
|
||||
// "description": "Log output to console"
|
||||
// }
|
||||
"TSX Base": {
|
||||
"description": "Generally you'll want this when you create a TSX file",
|
||||
"prefix": [ "tsx" ],
|
||||
"body": [
|
||||
"import React, { FC } from 'react'",
|
||||
"",
|
||||
"export interface ${1:Element}Props {",
|
||||
"\ttext: string;",
|
||||
"}",
|
||||
"",
|
||||
"const ${1:Element}: FC<${1:Element}Props> = (props: ${1:Element}Props) => {",
|
||||
"\tconst { text } = props;",
|
||||
"\treturn null;$0",
|
||||
"}",
|
||||
"",
|
||||
"export default ${1:Element};",
|
||||
""
|
||||
]
|
||||
}
|
||||
}
|
0
src/client/webapp/elements/lists/channel-list.scss
Normal file
0
src/client/webapp/elements/lists/channel-list.scss
Normal file
58
src/client/webapp/elements/lists/channel-list.tsx
Normal file
58
src/client/webapp/elements/lists/channel-list.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import React, { FC, useEffect, useMemo, useState } from 'react'
|
||||
import { Channel } from '../../data-types';
|
||||
import CombinedGuild from '../../guild-combined';
|
||||
import UI from '../../ui';
|
||||
import Util from '../../util';
|
||||
import GuildSubscriptions from '../require/guild-subscriptions';
|
||||
import ReactHelper from '../require/react-helper';
|
||||
import ChannelElement from './components/channel-element';
|
||||
|
||||
export interface ChannelListProps {
|
||||
guild: CombinedGuild;
|
||||
ui: UI; // TODO: Remove dependency on UI
|
||||
}
|
||||
|
||||
const ChannelList: FC<ChannelListProps> = (props: ChannelListProps) => {
|
||||
const { guild, ui } = props;
|
||||
|
||||
// TODO: Retry button on error
|
||||
// TODO: Load selfMember as a property so its state can be stored higher up and re-used
|
||||
const [ selfMember ] = GuildSubscriptions.useSelfMemberSubscription(guild);
|
||||
const [ fetchRetryCallable, channels, channelsFetchError ] = GuildSubscriptions.useChannelsSubscription(guild);
|
||||
|
||||
const [ activeChannel, setActiveChannel ] = useState<Channel | null>(ui.activeChannel);
|
||||
|
||||
// This chunk is aids but it'll be gotten rid of soon when we make the whole guild section one big component.
|
||||
// DO NOT COPY THIS ANYWHERE ELSE
|
||||
const isMounted = ReactHelper.useIsMountedRef();
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
await Util.sleep(100);
|
||||
if (!isMounted.current) return;
|
||||
setActiveChannel(ui.activeChannel);
|
||||
})();
|
||||
}, [ ui ]);
|
||||
|
||||
const channelElements = useMemo(() => {
|
||||
if (!selfMember) return null; // TODO: Hopefully selfMember will be non-null in the future
|
||||
if (channelsFetchError) {
|
||||
// TODO: Try again
|
||||
return <div className="channels-failed">Unable to load channels</div>
|
||||
}
|
||||
return channels?.map((channel: Channel) => {
|
||||
return (
|
||||
<ChannelElement
|
||||
key={channel.id} guild={guild} channel={channel}
|
||||
selfMember={selfMember} activeChannel={activeChannel} setActiveChannel={() => { ui.setActiveChannel(guild, channel); setActiveChannel(channel); }} />
|
||||
);
|
||||
});
|
||||
}, [ selfMember, channelsFetchError, channels, guild, selfMember, activeChannel, ui ]);
|
||||
|
||||
return (
|
||||
<div className="channel-list">
|
||||
{channelElements}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChannelList;
|
@ -0,0 +1,47 @@
|
||||
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 React, { Dispatch, FC, MouseEvent, SetStateAction, useCallback, useRef } from 'react'
|
||||
import { Channel, Member } from '../../../data-types';
|
||||
import CombinedGuild from '../../../guild-combined';
|
||||
import ChannelOverlay from '../../overlays/overlay-channel';
|
||||
import BaseElements from '../../require/base-elements';
|
||||
import ElementsUtil from '../../require/elements-util';
|
||||
|
||||
export interface ChannelElementProps {
|
||||
guild: CombinedGuild;
|
||||
channel: Channel;
|
||||
selfMember: Member; // Note: Expected to use this later since it may not be best to have css-based hiding
|
||||
activeChannel: Channel | null;
|
||||
setActiveChannel: Dispatch<SetStateAction<Channel | null>>;
|
||||
}
|
||||
|
||||
const ChannelElement: FC<ChannelElementProps> = (props: ChannelElementProps) => {
|
||||
const { guild, channel, activeChannel, setActiveChannel } = props;
|
||||
|
||||
const modifyRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const baseClassName = activeChannel?.id === channel.id ? 'channel text active' : 'channel text';
|
||||
|
||||
const setSelfActiveChannel = useCallback((event: MouseEvent<HTMLDivElement>) => {
|
||||
if (modifyRef.current?.contains(event.target as Node)) return; // ignore "modify" button clicks
|
||||
setActiveChannel(channel);
|
||||
}, [ modifyRef, channel ]);
|
||||
|
||||
// Note: this element will be hidden by CSS
|
||||
const launchModify = useCallback(() => {
|
||||
ElementsUtil.presentReactOverlay(document, <ChannelOverlay guild={guild} channel={channel} />);
|
||||
}, [ guild, channel ]);
|
||||
|
||||
return (
|
||||
<div className={baseClassName} onClick={setSelfActiveChannel}>
|
||||
<div className="icon">{BaseElements.TEXT_CHANNEL_ICON}</div>
|
||||
<div className="name">{channel.name}</div>
|
||||
<div className="modify" ref={modifyRef} onClick={launchModify}>{BaseElements.COG}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChannelElement;
|
@ -1,5 +1,7 @@
|
||||
@import "./channel-list.scss";
|
||||
@import "./member-list.scss";
|
||||
@import "./message-list.scss";
|
||||
|
||||
@import "./components/channel-element.scss";
|
||||
@import "./components/member-element.scss";
|
||||
@import "./components/message-element.scss";
|
||||
|
@ -8,13 +8,15 @@ import MessageList from './lists/message-list';
|
||||
import ChannelTitle from './sections/channel-title';
|
||||
import ConnectionInfo from './sections/connection-info';
|
||||
import GuildTitle from './sections/guild-title';
|
||||
import UI from '../ui';
|
||||
import ChannelList from './lists/channel-list';
|
||||
|
||||
export function mountBaseComponents() {
|
||||
// guild-list
|
||||
// TODO
|
||||
}
|
||||
|
||||
export function mountGuildComponents(q: Q, guild: CombinedGuild) {
|
||||
export function mountGuildComponents(q: Q, ui: UI, guild: CombinedGuild) {
|
||||
// guild title
|
||||
ElementsUtil.unmountReactComponent(q.$('.guild-title-anchor'));
|
||||
ElementsUtil.mountReactComponent(q.$('.guild-title-anchor'), <GuildTitle guild={guild} />);
|
||||
@ -28,7 +30,8 @@ export function mountGuildComponents(q: Q, guild: CombinedGuild) {
|
||||
ElementsUtil.mountReactComponent(q.$('.member-list-anchor'), <MemberList guild={guild} />);
|
||||
|
||||
// channel-list
|
||||
// TODO
|
||||
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) {
|
||||
|
@ -45,6 +45,7 @@
|
||||
<div id="guild">
|
||||
<div id="guild-sidebar">
|
||||
<div class="guild-title-anchor"></div>
|
||||
<div class="channel-list-anchor"></div>
|
||||
<div id="channel-list"></div>
|
||||
<div class="connection-anchor"></div>
|
||||
</div>
|
||||
|
@ -1,6 +1,10 @@
|
||||
@import "theme.scss";
|
||||
|
||||
#channel-list {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.channel-list {
|
||||
box-sizing: border-box;
|
||||
padding-top: 8px;
|
||||
overflow-y: scroll;
|
||||
|
@ -107,7 +107,7 @@ export default class UI {
|
||||
next.classList.add('active');
|
||||
this.q.$('#guild').setAttribute('data-id', guild.id + '');
|
||||
|
||||
mountGuildComponents(this.q, guild);
|
||||
mountGuildComponents(this.q, this, guild);
|
||||
|
||||
this.activeGuild = guild;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user