diff --git a/src/client/webapp/elements/mounts.tsx b/src/client/webapp/elements/mounts.tsx
deleted file mode 100644
index 7ca7e80..0000000
--- a/src/client/webapp/elements/mounts.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from 'react';
-import CombinedGuild from "../guild-combined";
-import GuildsManager from '../guilds-manager';
-import Q from "../q-module";
-import UI from '../ui';
-import GuildList from './lists/guild-list';
-import ElementsUtil from "./require/elements-util";
-import GuildElement from './sections/guild';
-
-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, guild: CombinedGuild) {
- console.log(q.$('.guild-anchor'));
- ElementsUtil.unmountReactComponent(q.$('.guild-anchor'));
- ElementsUtil.mountReactComponent(q.$('.guild-anchor'),
);
-}
diff --git a/src/client/webapp/elements/overlays/overlay-add-guild.tsx b/src/client/webapp/elements/overlays/overlay-add-guild.tsx
index c68c393..1b57f2d 100644
--- a/src/client/webapp/elements/overlays/overlay-add-guild.tsx
+++ b/src/client/webapp/elements/overlays/overlay-add-guild.tsx
@@ -3,7 +3,7 @@ const electronConsole = electronRemote.getGlobal('console') as Console;
import Logger from '../../../../logger/logger';
const LOG = Logger.create(__filename, electronConsole);
-import React, { FC, useEffect, useMemo, useRef, useState } from 'react';
+import React, { Dispatch, FC, SetStateAction, useEffect, useMemo, useRef, useState } from 'react';
import GuildsManager from '../../guilds-manager';
import moment from 'moment';
import TextInput from '../components/input-text';
@@ -11,7 +11,6 @@ import ImageEditInput from '../components/input-image-edit';
import Globals from '../../globals';
import SubmitOverlayLower from '../components/submit-overlay-lower';
import path from 'path';
-import UI from '../../ui';
import CombinedGuild from '../../guild-combined';
import ElementsUtil from '../require/elements-util';
import InvitePreview from '../components/invite-preview';
@@ -52,14 +51,13 @@ function getExampleAvatarPath(): string {
}
export interface AddGuildOverlayProps {
- document: Document;
- ui: UI;
guildsManager: GuildsManager;
addGuildData: IAddGuildData;
+ setActiveGuild: Dispatch
>;
}
const AddGuildOverlay: FC = (props: AddGuildOverlayProps) => {
- const { document, ui, guildsManager, addGuildData } = props;
+ const { guildsManager, addGuildData, setActiveGuild } = props;
const rootRef = useRef(null);
@@ -108,10 +106,9 @@ const AddGuildOverlay: FC = (props: AddGuildOverlayProps)
return { result: null, errorMessage: 'Error adding new guild' };
}
- const guildElement = await ui.addGuild(guildsManager, newGuild);
- ElementsUtil.closeReactOverlay(document);
- guildElement.click();
+ setActiveGuild(newGuild);
+ ElementsUtil.closeReactOverlay(document);
return { result: newGuild, errorMessage: null };
},
[ displayName, avatarBuff, displayNameInputValid, avatarInputValid ]
diff --git a/src/client/webapp/elements/require/base-elements.tsx b/src/client/webapp/elements/require/base-elements.tsx
index a94f797..7342ac6 100644
--- a/src/client/webapp/elements/require/base-elements.tsx
+++ b/src/client/webapp/elements/require/base-elements.tsx
@@ -5,37 +5,10 @@ const LOG = Logger.create(__filename, electronConsole);
import React from 'react';
-import * as FileType from 'file-type';
-
-import Globals from '../../globals';
-
-import ElementsUtil from './elements-util';
-import { Channel } from '../../data-types';
-import CombinedGuild from '../../guild-combined';
-import Q from '../../q-module';
-import ReactHelper from './react-helper';
-
export interface HTMLElementWithRemoveSelf extends HTMLElement {
removeSelf: (() => void);
}
-interface CreateUploadOverlayProps {
- guild: CombinedGuild;
- channel: Channel;
- resourceName: string;
- resourceBuffFunc: (() => Promise);
- resourceSizeFunc: (() => Promise | number);
-}
-
-interface BindImageUploadEventsProps {
- maxSize: number;
- acceptedMimeTypes: string[];
- onChangeStart: (() => Promise | void);
- onCleared: (() => Promise | void);
- onError: ((message: string) => Promise | void);
- onLoaded: ((buff: Buffer, src: string) => Promise | void);
-}
-
export default class BaseElements {
// Scraped directly from discord (#)
static TEXT_CHANNEL_ICON = (
@@ -268,220 +241,5 @@ export default class BaseElements {
y="-5"
transform="rotate(-135)" />
- )
-
- static createContextMenu(document: Document, content: JSX.Element): HTMLElementWithRemoveSelf {
- const element = ReactHelper.createElementFromJSX(
-
- ) as HTMLElementWithRemoveSelf;
-
- element.addEventListener('mousedown', (e: Event) => {
- e.stopPropagation(); // stop the bubble
- });
- element.removeSelf = () => {
- if (element.parentElement) {
- element.parentElement.removeChild(element);
- }
- document.body.removeEventListener('mousedown', element.removeSelf);
- };
- document.body.addEventListener('mousedown', element.removeSelf);
- return element as HTMLElementWithRemoveSelf;
- }
-
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- static createOverlay(document: Document, content: JSX.Element): HTMLElementWithRemoveSelf {
- const q = new Q(document);
-
- let wasDownInternal = false; // because 'click' fires on the overlay element anyway
- const element: HTMLElementWithRemoveSelf = ReactHelper.createElementFromJSX({content}
) as HTMLElementWithRemoveSelf;
- element.removeSelf = () => {
- if (element.parentElement) {
- element.parentElement.removeChild(element);
- }
- window.removeEventListener('keydown', onKeyEscape);
- }
- const onKeyEscape = (e: KeyboardEvent) => {
- if (e.key == 'Escape') {
- element.removeSelf();
- }
- };
- window.addEventListener('keydown', onKeyEscape);
- element.addEventListener('mouseup', () => {
- if (wasDownInternal) {
- wasDownInternal = false;
- return;
- }
- element.removeSelf();
- });
-
- q.$$$(element, '.content').addEventListener('click', (e) => {
- e.stopPropagation(); // prevent the element from closing if the content is clicked on
- });
- q.$$$(element, '.content').addEventListener('mousedown', (e) => {
- wasDownInternal = true;
- });
-
- return element as HTMLElementWithRemoveSelf;
- }
-
- static createUploadOverlay(document: Document, props: CreateUploadOverlayProps): HTMLElementWithRemoveSelf {
- const q = new Q(document);
-
- const { guild, channel, resourceName, resourceBuffFunc, resourceSizeFunc } = props;
-
- const element = BaseElements.createOverlay(document, (
-
-
-
-
-
-
-
-
-
-
Upload to #{channel.name}
-
-
-
- ));
-
- q.$$$(element, '.text-input').addEventListener('keydown', async (e) => {
- if (e.key == 'Enter' && !e.shiftKey) {
- e.preventDefault();
- q.$$$(element, '.button.upload').click();
- }
- });
- q.$$$(element, '.text-input').addEventListener('keyup', (e) => {
- if (e.key == 'Backspace') {
- if (q.$$$(element, '.text-input').innerText == '\n') { // sometimes, a \n gets left behind
- q.$$$(element, '.text-input').innerText = '';
- }
- }
- });
-
- let sending = false; // prevent double-clicking from sending 2 messages
- q.$$$(element, '.button.upload').addEventListener('click', async () => {
- if (sending) {
- return;
- }
- sending = true;
- if (!guild.isSocketVerified()) {
- LOG.warn('client attempted to send message with resource while not verified');
- q.$$$(element, '.error').innerText = 'Not Connected to Server';
- q.$$$(element, '.button.upload').innerText = 'Try Again';
- await ElementsUtil.shakeElement(q.$$$(element, '.button.upload'), 400);
- sending = false;
- return;
- }
- q.$$$(element, '.button.upload').innerText = 'Uploading...';
- let text: string | null = q.$$$(element, '.text-input').innerText;
- text = text.trim(); // this is not done server-side, just a client-side 'feature'
- if (text == '') {
- text = null;
- }
- if (text && text.length > Globals.MAX_TEXT_MESSAGE_LENGTH) {
- LOG.warn('Attempted to upload oversized resource text message: ' + text.length + ' / ' + Globals.MAX_TEXT_MESSAGE_LENGTH + ' characters');
- q.$$$(element, '.error').innerText = 'Text too long: ' + text.length + ' / ' + Globals.MAX_TEXT_MESSAGE_LENGTH + ' characters';
- q.$$$(element, '.button.upload').innerText = 'Try Again';
- await ElementsUtil.shakeElement(q.$$$(element, '.button.upload'), 400);
- sending = false;
- return;
- }
- let resourceBuff: Buffer;
- try {
- resourceBuff = await resourceBuffFunc();
- } catch (e) {
- LOG.error('Error loading resource', e);
- q.$$$(element, '.error').innerText = 'Error loading resource. Was it moved?';
- q.$$$(element, '.button.upload').innerText = 'Try Again';
- await ElementsUtil.shakeElement(q.$$$(element, '.button.upload'), 400);
- sending = false;
- return;
- }
- if (resourceBuff.length > Globals.MAX_RESOURCE_SIZE) {
- LOG.warn('Attempted to upload oversized resource: ' + ElementsUtil.humanSize(resourceBuff.length) + ' > ' + ElementsUtil.humanSize(Globals.MAX_RESOURCE_SIZE));
- q.$$$(element, '.error').innerText = 'Resource too large: ' + ElementsUtil.humanSize(resourceBuff.length) + ' > ' + ElementsUtil.humanSize(Globals.MAX_RESOURCE_SIZE);
- q.$$$(element, '.button.upload').innerText = 'Try Again';
- await ElementsUtil.shakeElement(q.$$$(element, '.button.upload'), 400);
- sending = false;
- return;
- }
- try {
- await guild.requestSendMessageWithResource(channel.id, text, resourceBuff, resourceName);
- } catch (e) {
- q.$$$(element, '.error').innerText = 'Error uploading resource.';
- q.$$$(element, '.button.upload').innerText = 'Try Again';
- await ElementsUtil.shakeElement(q.$$$(element, '.button.upload'), 400);
- sending = false;
- return;
- }
- element.removeSelf(); // get rid of the overlay after the message gets sent
- });
- (async () => {
- try {
- const size = await resourceSizeFunc();
- q.$$$(element, '.title .size').innerText = ElementsUtil.humanSize(size);
- } catch (e) {
- LOG.error('Error fetching file stat', e);
- q.$$$(element, '.title .size').innerText = 'Unknown Size';
- }
- })();
- (async () => {
- if (
- resourceName.toLowerCase().endsWith('.png') ||
- resourceName.toLowerCase().endsWith('.jpg') ||
- resourceName.toLowerCase().endsWith('.jpeg') ||
- resourceName.toLowerCase().endsWith('.gif')
- ) {
- try {
- const resourceBuff = await resourceBuffFunc();
- const resourceSrc = await ElementsUtil.getImageBufferSrc(resourceBuff);
- (q.$$$(element, '.title img') as HTMLImageElement).src = resourceSrc;
- } catch (e) {
- LOG.error('Error loading image resource', e);
- (q.$$$(element, 'img.avatar') as HTMLImageElement).src = './img/file-improved.svg'; // not the error icon here
- }
- } else {
- (q.$$$(element, '.title img') as HTMLImageElement).src = './img/file-improved.svg';
- }
- })();
- return element;
- }
-
- static bindImageUploadEvents(element: HTMLInputElement, props: BindImageUploadEventsProps): void {
- const { maxSize, acceptedMimeTypes, onChangeStart, onCleared, onError, onLoaded } = props;
-
- element.addEventListener('change', async () => {
- await onChangeStart();
- const files = element.files;
- if (!files || files.length == 0) {
- await onCleared();
- return;
- }
- const file = files[0] as File; // only one file at a time
- if (file.size > maxSize) {
- await onError('Image Too Large. ' + ElementsUtil.humanSize(file.size) + ' > ' + ElementsUtil.humanSize(maxSize));
- return;
- }
- const buff = Buffer.from(await file.arrayBuffer());
- const typeResult = await FileType.fromBuffer(buff);
- if (!typeResult || !acceptedMimeTypes.includes(typeResult.mime)) {
- await onError('Invalid Image Type. Accepted Types: ' + acceptedMimeTypes.map(type => type.replace('image/', '')).join(', '));
- return;
- }
- let src: string | null = null;
- try {
- src = await ElementsUtil.getImageBufferSrc(buff);
- } catch (e) {
- await onError('Unable Parse Image');
- return;
- }
- await onLoaded(buff, src);
- });
- }
+ );
}
diff --git a/src/client/webapp/elements/require/elements-util.tsx b/src/client/webapp/elements/require/elements-util.tsx
index 9c3e96c..d25f86d 100644
--- a/src/client/webapp/elements/require/elements-util.tsx
+++ b/src/client/webapp/elements/require/elements-util.tsx
@@ -1,8 +1,5 @@
-import * as path from 'path';
import * as fs from 'fs/promises';
-import * as electron from 'electron';
-
import * as electronRemote from '@electron/remote';
const electronConsole = electronRemote.getGlobal('console') as Console;
import Logger from '../../../../logger/logger';
@@ -12,9 +9,7 @@ import * as FileType from 'file-type';
import * as uuid from 'uuid';
import Util from '../../util';
-import Globals from '../../globals';
import CombinedGuild from '../../guild-combined';
-import { ShouldNeverHappenError } from '../../data-types';
import React from 'react';
import ReactDOM from 'react-dom';
@@ -28,41 +23,12 @@ export interface IAlignment {
bottom?: string;
}
-interface IHTMLElementWithRemovalType extends HTMLElement {
- manualRemoval?: boolean;
-}
-
interface SimpleQElement {
tag: 'span',
content: (SimpleQElement | string)[],
class: string | null
}
-interface CreateDownloadListenerProps {
- downloadBuff?: Buffer;
- guild?: CombinedGuild;
- resourceId?: string;
- resourceName: string;
- downloadStartFunc: (() => Promise | void);
- downloadFailFunc?: ((message: string) => Promise | void);
- writeStartFunc: (() => Promise | void);
- writeFailFunc: ((e: unknown) => Promise | void);
- successFunc: ((path: string) => Promise | void);
-}
-
-interface ShakingOnSubmitProps {
- doSubmit: () => Promise,
- setSubmitting: React.Dispatch>,
- setSubmitFailed: React.Dispatch>,
- setShaking: React.Dispatch>
-}
-
-async function sleep(ms: number): Promise {
- return await new Promise((resolve, reject) => {
- setTimeout(resolve, ms);
- });
-}
-
export default class ElementsUtil {
static calendarFormats = {
sameDay: '[Today at] HH:mm',
@@ -78,23 +44,6 @@ export default class ElementsUtil {
return (bytes / Math.pow(1024, e)).toFixed(2) + ' ' + ' KMGTP'.charAt(e) + 'B';
}
- // See https://stackoverflow.com/q/1125292/
- static setCursorToEnd(element: HTMLElement): void {
- const range = document.createRange();
- range.selectNodeContents(element);
- range.collapse(false); // false for end rather than start
- const selection = window.getSelection();
- selection?.removeAllRanges();
- selection?.addRange(range);
- }
-
- // Shakes an element for specified ms
- static async shakeElement(element: HTMLElement, ms: number): Promise {
- element.classList.add('shaking-horizontal');
- await sleep(ms);
- element.classList.remove('shaking-horizontal');
- }
-
// TODO: Remove this in favor of useSubmitButton style stuff from ReactHelper
// Calls a function with the start parameter and then the inverse of the start parameter after a determined number of ms
// There is no way to cancel this function
@@ -369,17 +318,9 @@ export default class ElementsUtil {
}
}
- static mountReactComponent(element: Element, component: JSX.Element) {
- ReactDOM.render(component, element);
- }
-
- static unmountReactComponent(element: Element) {
- ReactDOM.unmountComponentAtNode(element);
- }
-
static presentReactOverlay(document: Document, overlay: JSX.Element) {
// for aids reasons, the click event gets sent through to the overlay so we're just adding a sleep
- // here to break the event loop. Hopefully this gets better when we don't have to do a seperate render piece.
+ // here to break the event loop. Hopefully this gets better when we don't have to do a seperate render piece
// and we handle overlays through 100% react
(async () => {
await Util.sleep(0);
@@ -390,84 +331,4 @@ export default class ElementsUtil {
static closeReactOverlay(document: Document) {
ReactDOM.unmountComponentAtNode(document.querySelector('#react-overlays') as HTMLElement);
}
-
- static bindHoverableContextElement(
- hoverElement: HTMLElement,
- contextElement: IHTMLElementWithRemovalType,
- rootElement: HTMLElement,
- alignment: IAlignment,
- neverRemove?: boolean
- ): void {
- hoverElement.addEventListener('mouseenter', () => {
- document.body.appendChild(contextElement);
- ElementsUtil.alignContextElement(contextElement, rootElement, alignment);
- });
-
- if (neverRemove) {
- LOG.warn('hoverable context menu created with neverRemove flag set.');
- return;
- }
-
- hoverElement.addEventListener('mouseleave', () => {
- if (contextElement.parentElement && !contextElement.manualRemoval) {
- contextElement.parentElement.removeChild(contextElement);
- }
- });
- }
-
- static createDownloadListener(props: CreateDownloadListenerProps): (() => Promise) {
- const {
- downloadBuff, // pre-downloaded buffer to save rather than submit a download request (downloadStartFunc still required)
- guild, resourceId, resourceName,
- downloadStartFunc, downloadFailFunc,
- writeStartFunc, writeFailFunc,
- successFunc
- } = props;
-
- let downloading = false;
- let downloadPath: string | null = null;
- return async () => {
- if (downloading) return;
- if (downloadPath && await Util.exists(downloadPath)) {
- electron.shell.showItemInFolder(downloadPath);
- return;
- }
-
- downloading = true;
-
- await downloadStartFunc();
- let resourceBuff: Buffer;
- if (downloadBuff) {
- resourceBuff = downloadBuff;
- } else {
- if (!guild) throw new ShouldNeverHappenError('guild is null and we are not using a pre-download');
- if (!resourceId) throw new ShouldNeverHappenError('resourceId is null and we are not using a pre-download');
- try {
- resourceBuff = (await guild.fetchResource(resourceId)).data;
- } catch (e: unknown) {
- LOG.error('Error downloading resource', { e });
- if (downloadFailFunc) await downloadFailFunc(e as string);
- downloading = false;
- return;
- }
- }
-
- await writeStartFunc();
- try {
- const availableName = await Util.getAvailableFileName(Globals.DOWNLOAD_DIR, resourceName);
- downloadPath = path.join(Globals.DOWNLOAD_DIR, availableName);
- await fs.writeFile(downloadPath, resourceBuff);
- } catch (e) {
- LOG.error('Error writing download file', e);
- await writeFailFunc(e);
- downloadPath = null;
- downloading = false;
- return;
- }
-
- await successFunc(downloadPath);
-
- downloading = false;
- }
- }
}
diff --git a/src/client/webapp/elements/require/guild-manager-subscriptions.ts b/src/client/webapp/elements/require/guilds-manager-subscriptions.ts
similarity index 100%
rename from src/client/webapp/elements/require/guild-manager-subscriptions.ts
rename to src/client/webapp/elements/require/guilds-manager-subscriptions.ts
diff --git a/src/client/webapp/elements/sections/guild-list-container.tsx b/src/client/webapp/elements/sections/guild-list-container.tsx
new file mode 100644
index 0000000..1230224
--- /dev/null
+++ b/src/client/webapp/elements/sections/guild-list-container.tsx
@@ -0,0 +1,94 @@
+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, SetStateAction, useRef } from 'react';
+import CombinedGuild from '../../guild-combined';
+import GuildsManager from '../../guilds-manager';
+import GuildList from '../lists/guild-list';
+import ReactHelper from '../require/react-helper';
+import fs from 'fs/promises';
+import ElementsUtil from '../require/elements-util';
+import AddGuildOverlay from '../overlays/overlay-add-guild';
+import ErrorMessageOverlay from '../overlays/overlay-error-message';
+import BasicHover, { BasicHoverSide } from '../contexts/context-hover-basic';
+import BaseElements from '../require/base-elements';
+
+export interface GuildListContainerProps {
+ guildsManager: GuildsManager;
+ guilds: CombinedGuild[];
+ activeGuild: CombinedGuild | null;
+ setActiveGuild: Dispatch>;
+}
+
+const GuildListContainer: FC = (props: GuildListContainerProps) => {
+ const { guildsManager, guilds, activeGuild, setActiveGuild } = props;
+
+ const addGuildRef = useRef(null);
+
+ const [ contextHover, onMouseEnter, onMouseLeave ] = ReactHelper.useContextHover(() => {
+ return (
+
+
+
{BaseElements.TAB_LEFT}
+
Add Guild
+
+
+ );
+ }, []);
+
+ const [ onAddGuildClickCallback ] = ReactHelper.useAsyncVoidCallback(async () => {
+ // TODO: Change this to a file input
+ // We'll probably have to do this eventually for PWA.
+ const result = await electronRemote.dialog.showOpenDialog({
+ title: 'Select Guild File',
+ defaultPath: '.', // TODO: better path name
+ properties: [ 'openFile' ],
+ filters: [
+ { name: 'Cordis Guild Files', extensions: [ 'cordis' ] }
+ ]
+ });
+
+ if (result.canceled || result.filePaths.length === 0) return;
+
+ const filePath = result.filePaths[0] as string;
+ const fileText = (await fs.readFile(filePath)).toString('utf-8'); // TODO: error handling here
+
+ const addGuildData = JSON.parse(fileText);
+ if (
+ typeof addGuildData !== 'object' ||
+ typeof addGuildData?.name !== 'string' ||
+ typeof addGuildData?.url !== 'string' ||
+ typeof addGuildData?.cert !== 'string' ||
+ typeof addGuildData?.token !== 'string' ||
+ typeof addGuildData?.expires !== 'number' ||
+ typeof addGuildData?.iconSrc !== 'string'
+ ) {
+ LOG.debug('bad guild data:', { addGuildData, fileText });
+ ElementsUtil.presentReactOverlay(document, );
+ } else {
+ ElementsUtil.presentReactOverlay(document, );
+ }
+ }, [ guildsManager ]);
+
+ return (
+
+
+
+
+
+
+ {contextHover}
+
+ );
+}
+
+export default GuildListContainer;
diff --git a/src/client/webapp/elements/sections/guild.tsx b/src/client/webapp/elements/sections/guild.tsx
index d8cd09c..65b5ec4 100644
--- a/src/client/webapp/elements/sections/guild.tsx
+++ b/src/client/webapp/elements/sections/guild.tsx
@@ -29,6 +29,10 @@ const GuildElement: FC = (props: GuildElementProps) => {
const [ activeChannel, setActiveChannel ] = useState(null);
+ useEffect(() => {
+ setActiveChannel(null);
+ }, [ guild ]);
+
useEffect(() => {
if (activeChannel === null) {
// initial active channel is the first one in the list
diff --git a/src/client/webapp/elements/sections/guilds-manager.tsx b/src/client/webapp/elements/sections/guilds-manager.tsx
new file mode 100644
index 0000000..68895bb
--- /dev/null
+++ b/src/client/webapp/elements/sections/guilds-manager.tsx
@@ -0,0 +1,37 @@
+import React, { FC, useEffect, useState } from 'react';
+import CombinedGuild from '../../guild-combined';
+import GuildsManager from '../../guilds-manager';
+import { useGuildListSubscription } from '../require/guilds-manager-subscriptions';
+import GuildElement from './guild';
+import GuildListContainer from './guild-list-container';
+
+export interface GuildsManagerElementProps {
+ guildsManager: GuildsManager;
+}
+
+const GuildsManagerElement: FC = (props: GuildsManagerElementProps) => {
+ const { guildsManager } = props;
+
+ const [ guilds ] = useGuildListSubscription(guildsManager);
+ const [ activeGuild, setActiveGuild ] = useState(null);
+
+ useEffect(() => {
+ if (activeGuild === null) {
+ // initial active channel is the first one in the list
+ if (guilds && guilds.length > 0) {
+ setActiveGuild(guilds[0] as CombinedGuild);
+ }
+ }
+ }, [ guilds, activeGuild ]);
+
+ return (
+
+
+ {activeGuild && }
+
+ );
+}
+
+export default GuildsManagerElement;
diff --git a/src/client/webapp/index.html b/src/client/webapp/index.html
index d2813d6..be47ce3 100644
--- a/src/client/webapp/index.html
+++ b/src/client/webapp/index.html
@@ -34,17 +34,7 @@
-