Buttons can have hover text

This commit is contained in:
Michael Peters 2022-01-01 21:31:22 -06:00
parent c289f28bb3
commit 6c419440d5
25 changed files with 251 additions and 100 deletions

View File

@ -14,7 +14,7 @@ build-tsc:
./node_modules/.bin/tsc -p ./src/tsconfig.json
build-sass:
./node_modules/.bin/sass ./src/client/webapp/styles/styles.scss ./dist/client/webapp/styles/styles.css
./node_modules/.bin/sass ./src/client/webapp/index.scss ./dist/client/webapp/index.css
build: lint build-tsc build-sass

View File

@ -316,7 +316,7 @@ export class GuildMetadata implements WithEquals<GuildMetadata> {
}
}
export class GuildMetadataWithIds extends GuildMetadata {
export class GuildMetadataLocal extends GuildMetadata {
private constructor(
public readonly id: number,
name: string,
@ -327,8 +327,8 @@ export class GuildMetadataWithIds extends GuildMetadata {
super(name, iconResourceId, source);
}
static fromDBData(data: any): GuildMetadataWithIds {
return new GuildMetadataWithIds(
static fromDBData(data: any): GuildMetadataLocal {
return new GuildMetadataLocal(
data.id,
data.name,
data.icon_resource_id,

View File

@ -9,4 +9,33 @@
&.react:not(.aligned) {
visibility: hidden;
}
.basic-hover {
display: flex;
align-items: center;
color: theme.$background-floating;
&.left {
flex-direction: row;
}
&.right {
flex-direction: row-reverse;
}
&.bottom {
flex-direction: column;
}
&.top {
flex-direction: column-reverse;
}
.hover-text {
padding: 8px;
border-radius: 4px;
color: theme.$interactive-normal;
background-color: theme.$background-floating;
}
}
}

View File

@ -1,4 +1,7 @@
import React, { FC, ReactNode, Ref, useCallback, useMemo } from 'react';
import React, { FC, ReactNode, Ref, useCallback, useMemo, useRef } from 'react';
import BasicHover, { BasicHoverSide } from '../contexts/context-hover-basic';
import BaseElements from '../require/base-elements';
import { useContextHover } from '../require/react-helper';
export enum ButtonColorType {
BRAND = '',
@ -12,17 +15,21 @@ interface ButtonProps {
colorType?: ButtonColorType;
hoverText?: string | null;
onClick?: () => void;
shaking?: boolean;
children?: ReactNode;
}
const DefaultButtonProps = {
colorType: ButtonColorType.BRAND,
}
colorType: ButtonColorType.BRAND
};
const Button: FC<ButtonProps> = React.forwardRef((props: ButtonProps, ref: Ref<HTMLDivElement>) => {
const { colorType, onClick, shaking, children } = { ...DefaultButtonProps, ...props };
const Button: FC<ButtonProps> = (props: ButtonProps) => {
const { colorType, hoverText, onClick, shaking, children } = { ...DefaultButtonProps, ...props };
const buttonRef = useRef<HTMLDivElement>(null);
const className = useMemo(
() => [
@ -38,7 +45,30 @@ const Button: FC<ButtonProps> = React.forwardRef((props: ButtonProps, ref: Ref<H
if (onClick) onClick();
}, [ shaking, onClick ]);
return <div ref={ref} className={className} onClick={clickHandler}>{children}</div>
});
const [ contextHover, mouseEnterCallable, mouseLeaveCallable ] = useContextHover(
() => {
if (!hoverText) return null;
return (
<BasicHover relativeToRef={buttonRef} realignDeps={[ hoverText ]} side={BasicHoverSide.BOTTOM}>
{BaseElements.TAB_ABOVE}
<div className="hover-text">{hoverText}</div>
</BasicHover>
);
},
[ hoverText ]
);
return (
<div>
<div
ref={buttonRef} className={className}
onClick={clickHandler}
onMouseEnter={hoverText ? mouseEnterCallable : undefined}
onMouseLeave={hoverText ? mouseLeaveCallable : undefined}
>{children}</div>
{contextHover}
</div>
);
};
export default Button;

View File

@ -0,0 +1,80 @@
import moment from 'moment';
import React, { FC } from 'react';
import { GuildMetadata, Member, Token } from '../../data-types';
import CombinedGuild from '../../guild-combined';
import Util from '../../util';
import { IAddGuildData } from '../overlays/overlay-add-guild';
import BaseElements from '../require/base-elements';
import { useSoftImageSrcResourceSubscription } from '../require/guild-subscriptions';
import { useAsyncVoidCallback, useDownloadButton, useOneTimeAsyncAction } from '../require/react-helper';
import Button, { ButtonColorType } from './button';
export interface TokenRowProps {
url: string;
guild: CombinedGuild;
guildMeta: GuildMetadata;
token: Token;
}
const TokenRow: FC<TokenRowProps> = (props: TokenRowProps) => {
const { guild, guildMeta, token } = props;
const [ guildSocketConfigs ] = useOneTimeAsyncAction(
async () => await guild.fetchSocketConfigs(),
null,
[ guild ]
);
const [ iconSrc ] = useSoftImageSrcResourceSubscription(guild, guildMeta.iconResourceId);
const [ revoke ] = useAsyncVoidCallback(async () => {
await guild.requestDoRevokeToken(token.token);
}, [ guild, token ]);
const [ downloadFunc, downloadText, downloadShaking ] = useDownloadButton(
guildMeta.name + '.cordis',
async () => {
if (guildSocketConfigs === null) return null;
const guildSocketConfig = Util.randomChoice(guildSocketConfigs);
const addGuildData: IAddGuildData = {
name: guildMeta.name,
url: guildSocketConfig.url,
cert: guildSocketConfig.cert,
token: token.token,
expires: token.expires?.getTime() ?? null,
iconSrc: iconSrc,
};
const json = JSON.stringify(addGuildData);
return Buffer.from(json);
},
[ guildSocketConfigs, guildMeta, token, iconSrc ]
);
const userText = (token.member instanceof Member ? 'Used by ' + token.member.displayName : token.member?.id) ?? 'Unused Token';
return (
<div key={token.token} className="token-row">
<div className="user-token">
<div className="user">{userText}</div>
<div className="token">{token.token}</div>
</div>
<div className="created-expires">
<div className="created">{'Created ' + moment(token.created).fromNow()}</div>
<div className="expires">{token.expires ? 'Expires ' + moment(token.expires).fromNow() : 'Never expires'}</div>
</div>
<div className="actions">
<Button
colorType={ButtonColorType.BRAND}
onClick={downloadFunc}
hoverText={downloadText}
shaking={downloadShaking}
>{BaseElements.DOWNLOAD}</Button>
<Button
colorType={ButtonColorType.NEGATIVE}
onClick={revoke}
hoverText="Revoke"
>{BaseElements.TRASHCAN}</Button>
</div>
</div>
);
}
export default TokenRow;

View File

@ -1,4 +1,4 @@
import React, { FC, ReactNode, RefObject, useRef } from 'react'
import React, { DependencyList, FC, ReactNode, RefObject, useRef } from 'react'
import { IAlignment } from '../../require/elements-util';
import { useCloseWhenEscapeOrClickedOrContextOutsideEffect } from '../../require/react-helper';
import Context from './context';
@ -7,20 +7,24 @@ export interface ContextMenuProps {
relativeToRef?: RefObject<HTMLElement>;
relativeToPos?: { x: number, y: number };
alignment: IAlignment;
realignDeps: DependencyList;
children: ReactNode;
close: () => void;
}
// Automatically closes when clicked outside of and includes a <div class="menu"> subelement
const ContextMenu: FC<ContextMenuProps> = (props: ContextMenuProps) => {
const { relativeToRef, relativeToPos, alignment, children, close } = props;
const { relativeToRef, relativeToPos, alignment, realignDeps, children, close } = props;
const rootRef = useRef<HTMLDivElement>(null);
useCloseWhenEscapeOrClickedOrContextOutsideEffect(rootRef, close);
return (
<Context rootRef={rootRef} relativeToRef={relativeToRef} relativeToPos={relativeToPos} alignment={alignment}>
<Context
rootRef={rootRef} relativeToRef={relativeToRef} relativeToPos={relativeToPos}
alignment={alignment} realignDeps={realignDeps}
>
<div className="menu">
{children}
</div>

View File

@ -1,4 +1,4 @@
import React, { FC, ReactNode, RefObject, useRef } from 'react';
import React, { DependencyList, FC, ReactNode, RefObject, useRef } from 'react';
import { IAlignment } from '../../require/elements-util';
import { useAlignment } from '../../require/react-helper';
@ -7,17 +7,23 @@ export interface ContextProps {
relativeToRef?: RefObject<HTMLElement>;
relativeToPos?: { x: number, y: number };
alignment: IAlignment;
realignDeps: DependencyList;
children: ReactNode;
}
// You should create a component like context-menu.tsx instead of using this class directly.
const Context: FC<ContextProps> = (props: ContextProps) => {
const { rootRef, relativeToRef, relativeToPos, alignment, children } = props;
const { rootRef, relativeToRef, relativeToPos, alignment, realignDeps, children } = props;
const myRootRef = useRef<HTMLDivElement>(null);
const [ className ] = useAlignment(
rootRef ?? myRootRef, relativeToRef ?? null, relativeToPos ?? null, alignment, 'context react'
rootRef ?? myRootRef,
relativeToRef ?? null,
relativeToPos ?? null,
alignment,
realignDeps,
'context react'
);
return (

View File

@ -1,4 +1,4 @@
import React, { FC, ReactNode, RefObject, useMemo } from 'react';
import React, { DependencyList, FC, ReactNode, RefObject, useMemo } from 'react';
import { ShouldNeverHappenError } from '../../data-types';
import Context from './components/context';
@ -12,11 +12,12 @@ export enum BasicHoverSide {
export interface BasicHoverProps {
side: BasicHoverSide;
relativeToRef: RefObject<HTMLElement>;
realignDeps: DependencyList;
children: ReactNode;
}
const BasicHover: FC<BasicHoverProps> = (props: BasicHoverProps) => {
const { side, relativeToRef, children } = props;
const { side, relativeToRef, realignDeps, children } = props;
const alignment = useMemo(() => {
switch (side) {
@ -34,7 +35,7 @@ const BasicHover: FC<BasicHoverProps> = (props: BasicHoverProps) => {
}, [ side ]);
return (
<Context alignment={alignment} relativeToRef={relativeToRef}>
<Context alignment={alignment} relativeToRef={relativeToRef} realignDeps={realignDeps}>
<div className={'basic-hover ' + side}>
{children}
</div>

View File

@ -43,7 +43,7 @@ const ConnectionInfoContextMenu: FC<ConnectionInfoContextMenuProps> = (props: Co
}, []);
return (
<ContextMenu relativeToRef={relativeToRef} alignment={alignment} close={close}>
<ContextMenu relativeToRef={relativeToRef} alignment={alignment} realignDeps={[]} close={close}>
<div className="member-context">
<div onClick={openPersonalize} className="item">
<div className="icon"><img src="./img/pencil-icon.png" /></div>

View File

@ -8,7 +8,7 @@ import ContextMenu from './components/context-menu';
export interface GuildTitleContextMenuProps {
close: () => void;
relativeToRef: RefObject<HTMLElement>
relativeToRef: RefObject<HTMLElement>;
guild: CombinedGuild;
guildMeta: GuildMetadata;
selfMember: Member;
@ -55,7 +55,7 @@ const GuildTitleContextMenu: FC<GuildTitleContextMenuProps> = (props: GuildTitle
}, []);
return (
<ContextMenu alignment={alignment} close={close} relativeToRef={relativeToRef}>
<ContextMenu alignment={alignment} close={close} relativeToRef={relativeToRef} realignDeps={[ guild, guildMeta, selfMember ]}>
<div className="guild-title-context-menu">
{guildSettingsElement}
{createChannelElement}

View File

@ -51,7 +51,8 @@ const ImageContextMenu: FC<ImageContextMenuProps> = (props: ImageContextMenuProp
);
const [ saveCallable, saveText, saveShaking ] = useDownloadButton(
resourceName + (isPreview ? '-preview.jpg' : ''), resourceBuff,
resourceName + (isPreview ? '-preview.jpg' : ''),
async () => resourceBuff, [ resourceBuff ],
{
start: 'Save Image' + previewText,
pendingSave: 'Saving' + previewText + '...',
@ -62,7 +63,7 @@ const ImageContextMenu: FC<ImageContextMenuProps> = (props: ImageContextMenuProp
const alignment = useMemo(() => ({ top: 'centerY', left: 'centerX' }), []);
return (
<ContextMenu alignment={alignment} relativeToPos={relativeToPos} close={close}>
<ContextMenu alignment={alignment} relativeToPos={relativeToPos} realignDeps={[ copyText, saveText ]} close={close}>
<div className="image">
<div className="item copy-image" onClick={copyCallable}>{copyText}</div>
<div className="item save-image" onClick={saveCallable}>{saveText}</div>

View File

@ -7,14 +7,14 @@ import React, { FC, useEffect, useMemo, useState } from 'react';
import CombinedGuild from '../../guild-combined';
import Display from '../components/display';
import InvitePreview from '../components/invite-preview';
import { Member, Token } from '../../data-types';
import { Token } from '../../data-types';
import { useAsyncSubmitButton } from '../require/react-helper';
import { Duration } from 'moment';
import moment from 'moment';
import DropdownInput from '../components/input-dropdown';
import Button, { ButtonColorType } from '../components/button';
import BaseElements from '../require/base-elements';
import Button from '../components/button';
import { useTokensSubscription, useGuildMetadataSubscription, useSoftImageSrcResourceSubscription } from '../require/guild-subscriptions';
import TokenRow from '../components/token-row';
export interface GuildInvitesDisplayProps {
@ -69,29 +69,11 @@ const GuildInvitesDisplay: FC<GuildInvitesDisplayProps> = (props: GuildInvitesDi
// TODO: Try Again
return <div className="tokens-failed">Unable to load tokens</div>;
}
return tokens?.map((token: Token) => {
const userText = (token.member instanceof Member ? 'Used by ' + token.member.displayName : token.member?.id) ?? 'Unused Token';
return (
<div key={token.token} className="token-row">
<div className="user-token">
<div className="user">{userText}</div>
<div className="token">{token.token}</div>
</div>
<div className="created-expires">
<div className="created">{'Created ' + moment(token.created).fromNow()}</div>
<div className="expires">{token.expires ? 'Expires ' + moment(token.expires).fromNow() : 'Never expires'}</div>
</div>
<div className="actions">
<Button colorType={ButtonColorType.BRAND}>{BaseElements.DOWNLOAD}</Button>
<Button
colorType={ButtonColorType.NEGATIVE}
onClick={async () => await guild.requestDoRevokeToken(token.token)}
>{BaseElements.TRASHCAN}</Button>
</div>
</div>
);
});
}, [ tokens, tokensError ]);
if (!guildMeta) {
return <div className="no-guild-meta">No Guild Metadata</div>;
}
return tokens?.map((token: Token) => <TokenRow key={token.token} url={url} guild={guild} token={token} guildMeta={guildMeta} />);
}, [ url, guild, tokens, tokensError ]);
return (
<Display

View File

@ -30,10 +30,10 @@ const ChannelElement: FC<ChannelElementProps> = (props: ChannelElementProps) =>
const [ modifyContextHover, modifyMouseEnterCallable, modifyMouseLeaveCallable ] = useContextHover(
() => {
return (
<BasicHover side={BasicHoverSide.RIGHT} relativeToRef={modifyRef}>
<BasicHover side={BasicHoverSide.RIGHT} relativeToRef={modifyRef} realignDeps={[]}>
<div className="channel-gear-hover">
<div className="tab">{BaseElements.TAB_LEFT}</div>
<div className="text">Modify Channel</div>
<div className="hover-text">Modify Channel</div>
</div>
</BasicHover>
);
@ -55,7 +55,8 @@ const ChannelElement: FC<ChannelElementProps> = (props: ChannelElementProps) =>
<div className="name">{channel.name}</div>
<div
className="modify" ref={modifyRef} onClick={launchModify}
onMouseEnter={modifyMouseEnterCallable} onMouseLeave={modifyMouseLeaveCallable}
onMouseEnter={modifyMouseEnterCallable}
onMouseLeave={modifyMouseLeaveCallable}
>{BaseElements.COG}</div>
{modifyContextHover}
</div>

View File

@ -31,7 +31,7 @@ const GuildListElement: FC<GuildListElementProps> = (props: GuildListElementProp
if (!selfMember) return null;
const nameStyle = selfMember.roleColor ? { color: selfMember.roleColor } : {};
return (
<BasicHover relativeToRef={rootRef} side={BasicHoverSide.RIGHT}>
<BasicHover relativeToRef={rootRef} side={BasicHoverSide.RIGHT} realignDeps={[ guildMeta, selfMember ]}>
<div className="guild-hover">
<div className="tab">{BaseElements.TAB_LEFT}</div>
<div className="info">
@ -54,7 +54,7 @@ const GuildListElement: FC<GuildListElementProps> = (props: GuildListElementProp
const [ contextClickMenu, onContextClick ] = useContextClickContextMenu((alignment: IAlignment, relativeToPos: { x: number, y: number }, close: () => void) => {
return (
<ContextMenu
alignment={alignment} relativeToPos={relativeToPos} close={close}
alignment={alignment} relativeToPos={relativeToPos} realignDeps={[]} close={close}
>
<div className="guild-context">
<div className="item red leave-guild" onClick={leaveGuildCallable}>Leave Guild</div>

View File

@ -19,7 +19,7 @@ const ResourceElement: FC<ResourceElementProps> = (props: ResourceElementProps)
const [ callable, text, shaking ] = useDownloadButton(
resourceName,
{ guild, resourceId },
async () => (await guild.fetchResource(resourceId)).data, [ guild, resourceId ],
{
start: 'Click to Download',
pendingFetch: 'Fetching...', errorFetch: 'Unable to Download. Try Again',

View File

@ -23,7 +23,7 @@ export interface IAddGuildData {
url: string,
cert: string,
token: string,
expires: number,
expires: number | null,
iconSrc: string
}
@ -61,7 +61,7 @@ const AddGuildOverlay: FC<AddGuildOverlayProps> = (props: AddGuildOverlayProps)
const rootRef = useRef<HTMLDivElement>(null);
const expired = addGuildData.expires < new Date().getTime();
const expired = addGuildData.expires && addGuildData.expires < new Date().getTime();
const exampleDisplayName = useMemo(() => getExampleDisplayName(), []);
const exampleAvatarPath = useMemo(() => getExampleAvatarPath(), []);
@ -125,7 +125,7 @@ const AddGuildOverlay: FC<AddGuildOverlayProps> = (props: AddGuildOverlayProps)
<div ref={rootRef} className="content add-guild">
<InvitePreview
name={addGuildData.name} iconSrc={addGuildData.iconSrc}
url={addGuildData.url} expiresFromNow={moment.duration(addGuildData.expires - Date.now(), 'ms')}
url={addGuildData.url} expiresFromNow={addGuildData.expires ? moment.duration(addGuildData.expires - Date.now(), 'ms') : null}
/>
<div className="divider"></div>
<div className="personalization">

View File

@ -17,7 +17,7 @@ const ErrorMessageOverlay: FC<ErrorMessageOverlayProps> = (props: ErrorMessageOv
<div className="icon">
<img src="./img/error.png" alt="error" />
</div>
<div className="text">
<div className="hover-text">
<div className="title">{title}</div>
<div className="message">{message}</div>
</div>

View File

@ -192,6 +192,11 @@ export default class BaseElements {
L 0,8
Z` }
};
static TAB_ABOVE = (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="10" viewBox="0 0 12 8">
<path fill="currentColor" d="M 6,0 L 12,8 L 0,8 Z"></path>
</svg>
);
static Q_TAB_BELOW = {
ns: 'http://www.w3.org/2000/svg', tag: 'svg', width: 12, height: 8, viewBox: '0 0 12 8', content:

View File

@ -6,7 +6,6 @@ const LOG = Logger.create(__filename, electronConsole);
import { DependencyList, Dispatch, MutableRefObject, ReactNode, RefObject, SetStateAction, UIEvent, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { ShouldNeverHappenError } from "../../data-types";
import Util from '../../util';
import CombinedGuild from '../../guild-combined';
import Globals from '../../globals';
import path from 'path';
import fs from 'fs/promises';
@ -124,17 +123,22 @@ export function useAsyncVoidCallback(
return [ callable ];
}
// TODO: If this function gets expanded one more time, downloadSrc needs to be changed to fetchBuff and only
// be a function. Having 3 types for downloadSrc is a bit iffy.
export function useDownloadButton(
downloadName: string,
downloadSrc: { guild: CombinedGuild, resourceId: string } | Buffer,
stateTextMapping?: { start?: string, pendingFetch?: string, errorFetch?: string, pendingSave?: string, errorSave?: string, success?: string }
): [ callable: () => void, text: string, shaking: boolean ] {
const textMapping = { ...{ start: 'Download', pendingFetch: 'Downloading...', errorFetch: 'Try Again', pendingSave: 'Saving...', errorSave: 'Try Again', success: 'Open in Explorer' }, ...stateTextMapping };
const downloadBuff = downloadSrc instanceof Buffer ? downloadSrc : null;
fetchBuff: () => Promise<Buffer | null>,
fetchBuffDeps: DependencyList,
stateTextMapping?: { start?: string, pendingFetch?: string, errorFetch?: string, notReadyFetch?: string, pendingSave?: string, errorSave?: string, success?: string }
): [
callable: () => void,
text: string,
shaking: boolean
] {
const textMapping = { ...{ start: 'Download', pendingFetch: 'Downloading...', errorFetch: 'Try Again', notReadyFetch: 'Not Ready', pendingSave: 'Saving...', errorSave: 'Try Again', success: 'Open in Explorer' }, ...stateTextMapping };
const [ filePath, setFilePath ] = useState<string | null>(null);
const [ fileBuffer, setFileBuffer ] = useState<Buffer | null>(downloadBuff);
const [ fileBuffer, setFileBuffer ] = useState<Buffer | null>(null);
const [ text, setText ] = useState<string>(textMapping.start);
const [ shaking, doShake ] = useShake(400);
@ -159,25 +163,20 @@ export function useDownloadButton(
let saveBuffer = fileBuffer;
if (saveBuffer === null) {
if (!(downloadSrc instanceof Buffer)) {
// fetch the buffer
const { guild, resourceId } = downloadSrc;
try {
setText(textMapping.pendingFetch);
const resource = await guild.fetchResource(resourceId);
if (!isMounted.current) return;
setFileBuffer(resource.data);
saveBuffer = resource.data;
} catch (e: unknown) {
LOG.error('Error fetching resource for download button', e);
if (!isMounted.current) return;
setText(textMapping.errorFetch);
try {
setText(textMapping.pendingFetch);
const data = await fetchBuff();
if (!isMounted.current) return;
if (data === null) {
setText(textMapping.notReadyFetch);
doShake();
return;
}
} else {
// Save buffer not specified
LOG.error('Bad download setup');
setFileBuffer(data);
saveBuffer = data;
} catch (e: unknown) {
LOG.error('Error executing download function for download button', e);
if (!isMounted.current) return;
setText(textMapping.errorFetch);
doShake();
return;
@ -204,7 +203,7 @@ export function useDownloadButton(
setText(textMapping.success);
},
[ downloadName, downloadSrc, filePath, fileBuffer ]
[ downloadName, fetchBuff, ...fetchBuffDeps, filePath, fileBuffer ]
);
return [ callable, text, shaking ];
@ -420,6 +419,7 @@ export function useAlignment(
relativeToRef: RefObject<HTMLElement | null> | null,
relativeToPos: { x: number, y: number } | null,
alignment: IAlignment,
realignDeps: DependencyList,
baseClassName: string
): [
className: string
@ -432,7 +432,7 @@ export function useAlignment(
if (!relativeTo) throw new ShouldNeverHappenError('invalid alignment props');
ElementsUtil.alignContextElement(rootRef.current, relativeTo, alignment);
setAligned(true);
}, [ rootRef, relativeToRef, relativeToPos ]);
}, [ rootRef, relativeToRef, relativeToPos, ...realignDeps ]);
const className = useMemo(() => {
return baseClassName + (aligned ? ' aligned' : '');

View File

@ -29,7 +29,7 @@ const GuildListContainer: FC<GuildListContainerProps> = (props: GuildListContain
const [ contextHover, onMouseEnter, onMouseLeave ] = useContextHover(() => {
return (
<BasicHover relativeToRef={addGuildRef} side={BasicHoverSide.RIGHT}>
<BasicHover relativeToRef={addGuildRef} realignDeps={[]} side={BasicHoverSide.RIGHT}>
<div className="add-guild-hover">
<div className="tab">{BaseElements.TAB_LEFT}</div>
<div className="info">Add Guild</div>

View File

@ -7,7 +7,7 @@ import * as socketio from 'socket.io-client';
import PersonalDBGuild from './guild-personal-db';
import RAMGuild from './guild-ram';
import SocketGuild from './guild-socket';
import { Changes, Channel, ConnectionInfo, GuildMetadata, GuildMetadataWithIds, Member, Message, Resource, SocketConfig, Token } from './data-types';
import { Changes, Channel, ConnectionInfo, GuildMetadata, GuildMetadataLocal, Member, Message, Resource, SocketConfig, Token } from './data-types';
import MessageRAMCache from "./message-ram-cache";
import PersonalDB from "./personal-db";
@ -195,7 +195,7 @@ export default class CombinedGuild extends EventEmitter<Connectable & Conflictab
}
static async create(
guildMetadata: GuildMetadataWithIds,
guildMetadata: GuildMetadataLocal,
socketConfig: SocketConfig,
messageRAMCache: MessageRAMCache,
resourceRAMCache: ResourceRAMCache,
@ -268,6 +268,10 @@ export default class CombinedGuild extends EventEmitter<Connectable & Conflictab
return this.ramGuild.getChannels();
}
async fetchSocketConfigs(): Promise<SocketConfig[]> {
return await this.personalDBGuild.fetchSocketConfigs();
}
async fetchConnectionInfo(): Promise<ConnectionInfo> {
const connection: ConnectionInfo = {
id: null,

View File

@ -4,7 +4,7 @@ import Logger from '../../logger/logger';
const LOG = Logger.create(__filename, electronConsole);
import { AsyncFetchable, AsyncLackable } from "./guild-types";
import { Channel, GuildMetadata, Member, Message, Resource, Token } from "./data-types";
import { Channel, GuildMetadata, GuildMetadataLocal, Member, Message, Resource, SocketConfig, Token } from "./data-types";
import PersonalDB from "./personal-db";
@ -17,7 +17,11 @@ export default class PersonalDBGuild implements AsyncFetchable, AsyncLackable {
// Fetched Methods
async fetchMetadata(): Promise<GuildMetadata | null> {
async fetchSocketConfigs(): Promise<SocketConfig[]> {
return await this.db.fetchGuildSockets(this.guildId);
}
async fetchMetadata(): Promise<GuildMetadataLocal | null> {
return this.db.fetchGuild(this.guildId);
}

View File

@ -10,7 +10,7 @@ import * as socketio from 'socket.io-client';
import * as crypto from 'crypto';
import { Changes, Channel, GuildMetadata, GuildMetadataWithIds, Member, Message, Resource, SocketConfig, Token } from './data-types';
import { Changes, Channel, GuildMetadata, GuildMetadataLocal, Member, Message, Resource, SocketConfig, Token } from './data-types';
import { IAddGuildData } from './elements/overlays/overlay-add-guild';
import { EventEmitter } from 'tsee';
import CombinedGuild from './guild-combined';
@ -65,7 +65,7 @@ export default class GuildsManager extends EventEmitter<{
super();
}
async _connectFromConfig(guildMetadata: GuildMetadataWithIds, socketConfig: SocketConfig): Promise<CombinedGuild> {
async _connectFromConfig(guildMetadata: GuildMetadataLocal, socketConfig: SocketConfig): Promise<CombinedGuild> {
LOG.debug(`connecting to g#${guildMetadata.id} at ${socketConfig.url}`);
const guild = await CombinedGuild.create(
@ -166,7 +166,7 @@ export default class GuildsManager extends EventEmitter<{
try {
const member = Member.fromDBData(dataMember);
const meta = GuildMetadata.fromDBData(dataMetadata);
let guildMeta: GuildMetadataWithIds | null = null;
let guildMeta: GuildMetadataLocal | null = null;
let socketConfig: SocketConfig | null = null;
await this.personalDB.queueTransaction(async () => {
const guildId = await this.personalDB.addGuild(meta.name, meta.iconResourceId, member.id);

View File

@ -10,7 +10,7 @@ import ConcurrentQueue from "../../concurrent-queue/concurrent-queue";
import * as sqlite from 'sqlite';
import * as sqlite3 from 'sqlite3';
import { Channel, GuildMetadataWithIds, Member, Message, Resource, SocketConfig } from "./data-types";
import { Channel, GuildMetadataLocal, Member, Message, Resource, SocketConfig } from "./data-types";
export default class PersonalDB {
private transactions = new ConcurrentQueue(1);
@ -192,18 +192,18 @@ export default class PersonalDB {
if (result?.changes !== 1) throw new Error('unable to update guild icon');
}
async fetchGuild(guildId: number): Promise<GuildMetadataWithIds> {
async fetchGuild(guildId: number): Promise<GuildMetadataLocal> {
const result = await this.db.get(
`SELECT * FROM guilds WHERE id=:id`,
{ ':id': guildId }
);
if (!result) throw new Error('unable to fetch guild');
return GuildMetadataWithIds.fromDBData(result);
return GuildMetadataLocal.fromDBData(result);
}
async fetchGuilds(): Promise<GuildMetadataWithIds[]> {
async fetchGuilds(): Promise<GuildMetadataLocal[]> {
const result = await this.db.all('SELECT * FROM guilds');
return result.map(dataGuild => GuildMetadataWithIds.fromDBData(dataGuild));
return result.map(dataGuild => GuildMetadataLocal.fromDBData(dataGuild));
}
// Guild Sockets

View File

@ -104,4 +104,8 @@ export default class Util {
});
});
}
static randomChoice<T>(list: T[]): T {
return list[Math.floor(Math.random() * list.length)] as T;
}
}