async button subscription, overlay channel consolidation
This commit is contained in:
parent
965de83433
commit
b5f22212d9
@ -80,6 +80,10 @@ export class Channel implements WithEquals<Channel> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static sortByIndex(a: Channel, b: Channel) {
|
||||||
|
return a.index - b.index;
|
||||||
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return `${this.name}#${this.index}`;
|
return `${this.name}#${this.index}`;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import UI from '../ui';
|
|||||||
import Actions from '../actions';
|
import Actions from '../actions';
|
||||||
import Q from '../q-module';
|
import Q from '../q-module';
|
||||||
import CombinedGuild from '../guild-combined';
|
import CombinedGuild from '../guild-combined';
|
||||||
import ModifyChannelOverlay from './overlays/overlay-modify-channel';
|
import ChannelOverlay from './overlays/overlay-channel';
|
||||||
|
|
||||||
export default function createChannel(document: Document, q: Q, ui: UI, guild: CombinedGuild, channel: Channel) {
|
export default function createChannel(document: Document, q: Q, ui: UI, guild: CombinedGuild, channel: Channel) {
|
||||||
const element = ReactHelper.createElementFromJSX(
|
const element = ReactHelper.createElementFromJSX(
|
||||||
@ -38,7 +38,7 @@ export default function createChannel(document: Document, q: Q, ui: UI, guild: C
|
|||||||
if (modifyContextElement.parentElement) {
|
if (modifyContextElement.parentElement) {
|
||||||
modifyContextElement.parentElement.removeChild(modifyContextElement);
|
modifyContextElement.parentElement.removeChild(modifyContextElement);
|
||||||
}
|
}
|
||||||
ElementsUtil.presentReactOverlay(document, <ModifyChannelOverlay document={document} guild={guild} channel={channel} />);
|
ElementsUtil.presentReactOverlay(document, <ChannelOverlay document={document} guild={guild} channel={channel} />);
|
||||||
});
|
});
|
||||||
|
|
||||||
q.$$$(element, '.modify').addEventListener('mouseenter', () => {
|
q.$$$(element, '.modify').addEventListener('mouseenter', () => {
|
||||||
|
@ -1,35 +1,21 @@
|
|||||||
import React, { FC, Ref, useMemo } from 'react';
|
import React, { FC, Ref, useMemo } from 'react';
|
||||||
import Button from './button';
|
|
||||||
|
|
||||||
// Includes a submit button and error message
|
// Includes a submit button and error message
|
||||||
|
|
||||||
export interface SubmitOverlayLowerProps {
|
export interface SubmitOverlayLowerProps {
|
||||||
ref?: Ref<HTMLDivElement>;
|
ref?: Ref<HTMLDivElement>;
|
||||||
|
|
||||||
buttonMessage: string;
|
|
||||||
submitting: boolean;
|
|
||||||
submitFailed: boolean;
|
|
||||||
shaking: boolean;
|
|
||||||
onSubmit: () => void;
|
|
||||||
errorMessage: string | null;
|
errorMessage: string | null;
|
||||||
infoMessage?: string | null;
|
infoMessage?: string | null;
|
||||||
|
|
||||||
|
children: JSX.Element; // buttons list
|
||||||
}
|
}
|
||||||
|
|
||||||
const SubmitOverlayLower: FC<SubmitOverlayLowerProps> = React.forwardRef((props: SubmitOverlayLowerProps, ref: Ref<HTMLDivElement>) => {
|
const SubmitOverlayLower: FC<SubmitOverlayLowerProps> = React.forwardRef((props: SubmitOverlayLowerProps, ref: Ref<HTMLDivElement>) => {
|
||||||
const { buttonMessage, submitting, submitFailed, shaking, onSubmit, errorMessage, infoMessage } = props;
|
const { errorMessage, infoMessage, children } = props;
|
||||||
|
|
||||||
// TODO: ref should be an imperative handle
|
// TODO: ref should be an imperative handle
|
||||||
|
|
||||||
const buttonText = useMemo(() => {
|
|
||||||
if (submitting) {
|
|
||||||
return 'Submitting...';
|
|
||||||
} else if (submitFailed) {
|
|
||||||
return 'Try Again';
|
|
||||||
} else {
|
|
||||||
return buttonMessage;
|
|
||||||
}
|
|
||||||
}, [ submitting, submitFailed ]);
|
|
||||||
|
|
||||||
const message = useMemo(() => errorMessage ?? infoMessage ?? null, [ errorMessage, infoMessage ]);
|
const message = useMemo(() => errorMessage ?? infoMessage ?? null, [ errorMessage, infoMessage ]);
|
||||||
const isEmpty = useMemo(() => message === null, [ message ]);
|
const isEmpty = useMemo(() => message === null, [ message ]);
|
||||||
|
|
||||||
@ -39,7 +25,7 @@ const SubmitOverlayLower: FC<SubmitOverlayLowerProps> = React.forwardRef((props:
|
|||||||
<div className="error">{errorMessage ?? infoMessage ?? null}</div>
|
<div className="error">{errorMessage ?? infoMessage ?? null}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="buttons">
|
<div className="buttons">
|
||||||
<Button ref={ref} onClick={onSubmit} shaking={shaking}>{buttonText}</Button>
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -15,7 +15,7 @@ import CombinedGuild from '../guild-combined';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactHelper from './require/react-helper';
|
import ReactHelper from './require/react-helper';
|
||||||
import GuildSettingsOverlay from './overlays/overlay-guild-settings';
|
import GuildSettingsOverlay from './overlays/overlay-guild-settings';
|
||||||
import CreateChannelOverlay from './overlays/overlay-create-channel';
|
import ChannelOverlay from './overlays/overlay-channel';
|
||||||
|
|
||||||
export default function createGuildTitleContextMenu(document: Document, q: Q, ui: UI, guild: CombinedGuild): Element {
|
export default function createGuildTitleContextMenu(document: Document, q: Q, ui: UI, guild: CombinedGuild): Element {
|
||||||
if (ui.activeConnection === null) {
|
if (ui.activeConnection === null) {
|
||||||
@ -81,7 +81,7 @@ export default function createGuildTitleContextMenu(document: Document, q: Q, ui
|
|||||||
if (ui.activeConnection.privileges.includes('modify_channels')) {
|
if (ui.activeConnection.privileges.includes('modify_channels')) {
|
||||||
q.$$$(element, '.item.create-channel').addEventListener('click', () => {
|
q.$$$(element, '.item.create-channel').addEventListener('click', () => {
|
||||||
element.removeSelf();
|
element.removeSelf();
|
||||||
ElementsUtil.presentReactOverlay(document, <CreateChannelOverlay document={document} guild={guild} />);
|
ElementsUtil.presentReactOverlay(document, <ChannelOverlay document={document} guild={guild} />);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,8 +3,7 @@ const electronConsole = electronRemote.getGlobal('console') as Console;
|
|||||||
import Logger from '../../../../logger/logger';
|
import Logger from '../../../../logger/logger';
|
||||||
const LOG = Logger.create(__filename, electronConsole);
|
const LOG = Logger.create(__filename, electronConsole);
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { FC } from 'react';
|
|
||||||
import CombinedGuild from '../../guild-combined';
|
import CombinedGuild from '../../guild-combined';
|
||||||
import Display from '../components/display';
|
import Display from '../components/display';
|
||||||
import InvitePreview from '../components/invite-preview';
|
import InvitePreview from '../components/invite-preview';
|
||||||
|
@ -15,8 +15,9 @@ import UI from '../../ui';
|
|||||||
import CombinedGuild from '../../guild-combined';
|
import CombinedGuild from '../../guild-combined';
|
||||||
import ElementsUtil from '../require/elements-util';
|
import ElementsUtil from '../require/elements-util';
|
||||||
import InvitePreview from '../components/invite-preview';
|
import InvitePreview from '../components/invite-preview';
|
||||||
import ReactHelper from '../require/react-helper';
|
import ReactHelper, { ExpectedError } from '../require/react-helper';
|
||||||
import * as fs from 'fs/promises';
|
import * as fs from 'fs/promises';
|
||||||
|
import Button from '../components/button';
|
||||||
|
|
||||||
export interface IAddGuildData {
|
export interface IAddGuildData {
|
||||||
name: string,
|
name: string,
|
||||||
@ -59,16 +60,12 @@ export interface AddGuildOverlayProps {
|
|||||||
const AddGuildOverlay: FC<AddGuildOverlayProps> = (props: AddGuildOverlayProps) => {
|
const AddGuildOverlay: FC<AddGuildOverlayProps> = (props: AddGuildOverlayProps) => {
|
||||||
const { document, ui, guildsManager, addGuildData } = props;
|
const { document, ui, guildsManager, addGuildData } = props;
|
||||||
|
|
||||||
|
const isMounted = ReactHelper.useIsMountedRef();
|
||||||
|
|
||||||
const expired = addGuildData.expires < new Date().getTime();
|
const expired = addGuildData.expires < new Date().getTime();
|
||||||
const exampleDisplayName = useMemo(() => getExampleDisplayName(), []);
|
const exampleDisplayName = useMemo(() => getExampleDisplayName(), []);
|
||||||
const exampleAvatarPath = useMemo(() => getExampleAvatarPath(), []);
|
const exampleAvatarPath = useMemo(() => getExampleAvatarPath(), []);
|
||||||
|
|
||||||
const submitButtonRef = createRef<HTMLDivElement>();
|
|
||||||
|
|
||||||
const [ submitting, setSubmitting ] = useState<boolean>(false);
|
|
||||||
const [ submitFailed, setSubmitFailed ] = useState<boolean>(false);
|
|
||||||
const [ shaking, setShaking ] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const [ displayName, setDisplayName ] = useState<string>(exampleDisplayName);
|
const [ displayName, setDisplayName ] = useState<string>(exampleDisplayName);
|
||||||
const [ avatarBuff, setAvatarBuff ] = useState<Buffer | null>(null);
|
const [ avatarBuff, setAvatarBuff ] = useState<Buffer | null>(null);
|
||||||
|
|
||||||
@ -92,49 +89,47 @@ const AddGuildOverlay: FC<AddGuildOverlayProps> = (props: AddGuildOverlayProps)
|
|||||||
}
|
}
|
||||||
}, [ exampleAvatarBuff ]);
|
}, [ exampleAvatarBuff ]);
|
||||||
|
|
||||||
const errorMessage = useMemo(() => {
|
const validationErrorMessage = useMemo(() => {
|
||||||
if (exampleAvatarBuffError && !avatarBuff) return 'Unable to load example avatar';
|
if (exampleAvatarBuffError && !avatarBuff) return 'Unable to load example avatar';
|
||||||
if ( !avatarInputValid && avatarInputMessage) return avatarInputMessage;
|
if ( !avatarInputValid && avatarInputMessage) return avatarInputMessage;
|
||||||
if (!displayNameInputValid && displayNameInputMessage) return displayNameInputMessage;
|
if (!displayNameInputValid && displayNameInputMessage) return displayNameInputMessage;
|
||||||
if (addGuildFailedMessage !== null) return addGuildFailedMessage;
|
|
||||||
return null;
|
return null;
|
||||||
}, [ exampleAvatarBuffError, avatarBuff, displayNameInputValid, displayNameInputMessage, avatarInputValid, avatarInputMessage, addGuildFailedMessage ]);
|
}, [ exampleAvatarBuffError, avatarBuff, displayNameInputValid, displayNameInputMessage, avatarInputValid, avatarInputMessage, addGuildFailedMessage ]);
|
||||||
|
|
||||||
const doSubmit = useCallback(async (): Promise<boolean> => {
|
const [ _, submitError, submitButtonText, submitButtonShaking, submitButtonCallback ] = ReactHelper.useAsyncButtonSubscription(async (): Promise<void> => {
|
||||||
if (!displayNameInputValid || !avatarInputValid || avatarBuff === null) {
|
if (validationErrorMessage || !avatarBuff) throw new ExpectedError('invalid input');
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (expired) {
|
if (expired) {
|
||||||
setAddGuildFailedMessage('token expired');
|
setAddGuildFailedMessage('token expired');
|
||||||
return false;
|
throw new ExpectedError('token expired');
|
||||||
}
|
}
|
||||||
|
|
||||||
let newGuild: CombinedGuild;
|
let newGuild: CombinedGuild;
|
||||||
try {
|
try {
|
||||||
newGuild = await guildsManager.addNewGuild(addGuildData, displayName, avatarBuff);
|
newGuild = await guildsManager.addNewGuild(addGuildData, displayName, avatarBuff);
|
||||||
|
if (!isMounted.current) return;
|
||||||
setAddGuildFailedMessage(null);
|
setAddGuildFailedMessage(null);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
|
if (!isMounted.current) return;
|
||||||
LOG.error('error adding new guild', e);
|
LOG.error('error adding new guild', e);
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
setAddGuildFailedMessage(e.message);
|
setAddGuildFailedMessage(e.message);
|
||||||
} else {
|
} else {
|
||||||
setAddGuildFailedMessage('error adding new guild');
|
setAddGuildFailedMessage('error adding new guild');
|
||||||
}
|
}
|
||||||
return false;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
const guildElement = await ui.addGuild(guildsManager, newGuild);
|
const guildElement = await ui.addGuild(guildsManager, newGuild);
|
||||||
ElementsUtil.closeReactOverlay(document);
|
ElementsUtil.closeReactOverlay(document);
|
||||||
guildElement.click();
|
guildElement.click();
|
||||||
|
}, { start: 'Submit', pending: 'Submitting...', error: 'Try Again', done: 'Done' }, [ displayName, avatarBuff, displayNameInputValid, avatarInputValid ]);
|
||||||
|
|
||||||
return true;
|
const errorMessage = useMemo(() => {
|
||||||
|
if (validationErrorMessage) return validationErrorMessage;
|
||||||
}, [ displayName, avatarBuff, displayNameInputValid, avatarInputValid ]);
|
if (addGuildFailedMessage) return addGuildFailedMessage;
|
||||||
|
return null;
|
||||||
const onSubmit = useCallback(
|
}, [ validationErrorMessage, addGuildFailedMessage ])
|
||||||
ElementsUtil.createShakingOnSubmit({ doSubmit, setShaking, setSubmitFailed, setSubmitting }),
|
|
||||||
[ doSubmit, shaking, submitFailed, submitFailed, submitting ]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="content add-guild">
|
<div className="content add-guild">
|
||||||
@ -157,16 +152,13 @@ const AddGuildOverlay: FC<AddGuildOverlayProps> = (props: AddGuildOverlayProps)
|
|||||||
maxLength={Globals.MAX_DISPLAY_NAME_LENGTH}
|
maxLength={Globals.MAX_DISPLAY_NAME_LENGTH}
|
||||||
value={displayName} setValue={setDisplayName}
|
value={displayName} setValue={setDisplayName}
|
||||||
setValid={setDisplayNameInputValid} setMessage={setDisplayNameInputMessage}
|
setValid={setDisplayNameInputValid} setMessage={setDisplayNameInputMessage}
|
||||||
onEnterKeyDown={() => { submitButtonRef.current?.click(); }}
|
onEnterKeyDown={submitButtonCallback}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SubmitOverlayLower
|
<SubmitOverlayLower errorMessage={errorMessage}>
|
||||||
ref={submitButtonRef}
|
<Button shaking={submitButtonShaking} onClick={submitButtonCallback}>{submitButtonText}</Button>
|
||||||
buttonMessage="Add Guild"
|
</SubmitOverlayLower>
|
||||||
submitting={submitting} submitFailed={submitFailed} shaking={shaking}
|
|
||||||
onSubmit={onSubmit} errorMessage={errorMessage}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ const electronConsole = electronRemote.getGlobal('console') as Console;
|
|||||||
import Logger from '../../../../logger/logger';
|
import Logger from '../../../../logger/logger';
|
||||||
const LOG = Logger.create(__filename, electronConsole);
|
const LOG = Logger.create(__filename, electronConsole);
|
||||||
|
|
||||||
import React, { createRef, FC, useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { createRef, FC, useEffect, useMemo, useState } from 'react';
|
||||||
import CombinedGuild from '../../guild-combined';
|
import CombinedGuild from '../../guild-combined';
|
||||||
import BaseElements from '../require/base-elements';
|
import BaseElements from '../require/base-elements';
|
||||||
import TextInput from '../components/input-text';
|
import TextInput from '../components/input-text';
|
||||||
@ -11,28 +11,27 @@ import SubmitOverlayLower from '../components/submit-overlay-lower';
|
|||||||
import Globals from '../../globals';
|
import Globals from '../../globals';
|
||||||
import ElementsUtil from '../require/elements-util';
|
import ElementsUtil from '../require/elements-util';
|
||||||
import { Channel } from '../../data-types';
|
import { Channel } from '../../data-types';
|
||||||
|
import ReactHelper, { ExpectedError } from '../require/react-helper';
|
||||||
|
import Button from '../components/button';
|
||||||
|
|
||||||
export interface ModifyChannelOverlayProps {
|
export interface ChannelOverlayProps {
|
||||||
document: Document;
|
document: Document;
|
||||||
guild: CombinedGuild;
|
guild: CombinedGuild;
|
||||||
channel: Channel;
|
channel?: Channel;
|
||||||
}
|
}
|
||||||
const ModifyChannelOverlay: FC<ModifyChannelOverlayProps> = (props: ModifyChannelOverlayProps) => {
|
const ChannelOverlay: FC<ChannelOverlayProps> = (props: ChannelOverlayProps) => {
|
||||||
const { document, guild, channel } = props;
|
const { document, guild, channel } = props;
|
||||||
|
|
||||||
|
const isMounted = ReactHelper.useIsMountedRef();
|
||||||
|
|
||||||
const nameInputRef = createRef<HTMLInputElement>();
|
const nameInputRef = createRef<HTMLInputElement>();
|
||||||
const flavorTextInputRef = createRef<HTMLInputElement>();
|
const flavorTextInputRef = createRef<HTMLInputElement>();
|
||||||
const submitButtonRef = createRef<HTMLDivElement>();
|
const submitButtonRef = createRef<HTMLDivElement>();
|
||||||
|
|
||||||
const [ edited, setEdited ] = useState<boolean>(false);
|
const [ edited, setEdited ] = useState<boolean>(false);
|
||||||
const [ submitting, setSubmitting ] = useState<boolean>(false);
|
|
||||||
const [ submitFailed, setSubmitFailed ] = useState<boolean>(false);
|
|
||||||
const [ shaking, setShaking ] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const [ name, setName ] = useState<string>(channel.name);
|
const [ name, setName ] = useState<string>(channel?.name ?? '');
|
||||||
const [ flavorText, setFlavorText ] = useState<string>(channel.flavorText ?? '');
|
const [ flavorText, setFlavorText ] = useState<string>(channel?.flavorText ?? '');
|
||||||
|
|
||||||
const [ queryFailed, setQueryFailed ] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const [ nameInputValid, setNameInputValid ] = useState<boolean>(false);
|
const [ nameInputValid, setNameInputValid ] = useState<boolean>(false);
|
||||||
const [ nameInputMessage, setNameInputMessage ] = useState<string | null>(null);
|
const [ nameInputMessage, setNameInputMessage ] = useState<string | null>(null);
|
||||||
@ -40,17 +39,26 @@ const ModifyChannelOverlay: FC<ModifyChannelOverlayProps> = (props: ModifyChanne
|
|||||||
const [ flavorTextInputValid, setFlavorTextInputValid ] = useState<boolean>(false);
|
const [ flavorTextInputValid, setFlavorTextInputValid ] = useState<boolean>(false);
|
||||||
const [ flavorTextInputMessage, setFlavorTextInputMessage ] = useState<string | null>(null);
|
const [ flavorTextInputMessage, setFlavorTextInputMessage ] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const [ submitFailMessage, setSubmitFailMessage ] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
nameInputRef.current?.focus();
|
nameInputRef.current?.focus();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const errorMessage = useMemo(() => {
|
useEffect(() => {
|
||||||
|
if (channel) {
|
||||||
|
setEdited(name !== channel.name || ((flavorText === '' ? null : flavorText) !== channel.flavorText));
|
||||||
|
} else {
|
||||||
|
setEdited(name.length > 0 && flavorText.length > 0);
|
||||||
|
}
|
||||||
|
}, [ name, flavorText ]);
|
||||||
|
|
||||||
|
const validationErrorMessage = useMemo(() => {
|
||||||
if (!edited) return null;
|
if (!edited) return null;
|
||||||
if ( !nameInputValid && nameInputMessage) return nameInputMessage;
|
if ( !nameInputValid && nameInputMessage) return nameInputMessage;
|
||||||
if (!flavorTextInputValid && flavorTextInputMessage) return flavorTextInputMessage;
|
if (!flavorTextInputValid && flavorTextInputMessage) return flavorTextInputMessage;
|
||||||
if (queryFailed) return 'Unable to create new channel';
|
|
||||||
return null;
|
return null;
|
||||||
}, [ nameInputValid, nameInputMessage, flavorTextInputValid, flavorTextInputMessage, queryFailed ]);
|
}, [ nameInputValid, nameInputMessage, flavorTextInputValid, flavorTextInputMessage ]);
|
||||||
|
|
||||||
const infoMessage = useMemo(() => {
|
const infoMessage = useMemo(() => {
|
||||||
if ( nameInputValid && nameInputMessage) return nameInputMessage;
|
if ( nameInputValid && nameInputMessage) return nameInputMessage;
|
||||||
@ -58,35 +66,37 @@ const ModifyChannelOverlay: FC<ModifyChannelOverlayProps> = (props: ModifyChanne
|
|||||||
return null;
|
return null;
|
||||||
}, [ nameInputValid, nameInputMessage, flavorTextInputValid, flavorTextInputMessage ]);
|
}, [ nameInputValid, nameInputMessage, flavorTextInputValid, flavorTextInputMessage ]);
|
||||||
|
|
||||||
useEffect(() => {
|
const [ _, submitError, submitButtonText, submitButtonShaking, submit ] = ReactHelper.useAsyncButtonSubscription(async (): Promise<void> => {
|
||||||
if (name.length > 0 || flavorText.length > 0) {
|
if (validationErrorMessage) throw new ExpectedError('invalid input');
|
||||||
setEdited(true);
|
setSubmitFailMessage(null);
|
||||||
}
|
|
||||||
}, [ name, flavorText ]);
|
|
||||||
|
|
||||||
const doSubmit = useCallback(async (): Promise<boolean> => {
|
if (!edited) {
|
||||||
LOG.debug('submitting');
|
ElementsUtil.closeReactOverlay(document);
|
||||||
if (!edited) return false;
|
return;
|
||||||
if (errorMessage) return false;
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Make sure to null out flavor text if empty
|
// Make sure to null out flavor text if empty
|
||||||
await guild.requestDoUpdateChannel(channel.id, name, flavorText === '' ? null : flavorText);
|
if (channel) {
|
||||||
|
await guild.requestDoUpdateChannel(channel.id, name, flavorText === '' ? null : flavorText);
|
||||||
|
} else {
|
||||||
|
await guild.requestDoCreateChannel(name, flavorText === '' ? null : flavorText);
|
||||||
|
}
|
||||||
|
if (!isMounted.current) return;
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
LOG.error('error creating channel', e);
|
if (!isMounted.current) return;
|
||||||
setQueryFailed(true);
|
setSubmitFailMessage('Error submitting');
|
||||||
return false;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
ElementsUtil.closeReactOverlay(document);
|
ElementsUtil.closeReactOverlay(document);
|
||||||
|
}, { start: channel ? 'Modify' : 'Create', pending: 'Submitting...', error: 'Error', done: 'Done' }, [ edited, validationErrorMessage, name, flavorText ]);
|
||||||
|
|
||||||
return true;
|
const errorMessage = useMemo(() => {
|
||||||
}, [ errorMessage, name, flavorText ]);
|
if (validationErrorMessage) return validationErrorMessage;
|
||||||
|
if (submitError) return 'Unable to modify channel';
|
||||||
const onSubmit = useCallback(
|
return null;
|
||||||
ElementsUtil.createShakingOnSubmit({ doSubmit, setShaking, setSubmitFailed, setSubmitting }),
|
}, [ validationErrorMessage, submitError ]);
|
||||||
[ doSubmit, shaking, submitFailed, submitFailed, submitting ]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="content submit-dialog modify-channel">
|
<div className="content submit-dialog modify-channel">
|
||||||
@ -99,7 +109,7 @@ const ModifyChannelOverlay: FC<ModifyChannelOverlayProps> = (props: ModifyChanne
|
|||||||
<div className="channel-name">
|
<div className="channel-name">
|
||||||
<TextInput
|
<TextInput
|
||||||
ref={nameInputRef}
|
ref={nameInputRef}
|
||||||
label="Channel Name" placeholder={channel.name}
|
label="Channel Name" placeholder={channel?.name ?? 'channel-name'}
|
||||||
maxLength={Globals.MAX_CHANNEL_NAME_LENGTH}
|
maxLength={Globals.MAX_CHANNEL_NAME_LENGTH}
|
||||||
value={name} setValue={setName}
|
value={name} setValue={setName}
|
||||||
setValid={setNameInputValid} setMessage={setNameInputMessage}
|
setValid={setNameInputValid} setMessage={setNameInputMessage}
|
||||||
@ -110,21 +120,19 @@ const ModifyChannelOverlay: FC<ModifyChannelOverlayProps> = (props: ModifyChanne
|
|||||||
<div className="flavor-text">
|
<div className="flavor-text">
|
||||||
<TextInput
|
<TextInput
|
||||||
ref={flavorTextInputRef}
|
ref={flavorTextInputRef}
|
||||||
label="Flavor Text" placeholder={channel.flavorText ?? '(optional)'}
|
label="Flavor Text" placeholder={channel?.flavorText ?? '(optional)'}
|
||||||
maxLength={Globals.MAX_CHANNEL_FLAVOR_TEXT_LENGTH}
|
maxLength={Globals.MAX_CHANNEL_FLAVOR_TEXT_LENGTH}
|
||||||
allowEmpty={true}
|
allowEmpty={true}
|
||||||
value={flavorText} setValue={setFlavorText}
|
value={flavorText} setValue={setFlavorText}
|
||||||
setValid={setFlavorTextInputValid} setMessage={setFlavorTextInputMessage}
|
setValid={setFlavorTextInputValid} setMessage={setFlavorTextInputMessage}
|
||||||
onEnterKeyDown={() => { submitButtonRef.current?.click(); }}
|
onEnterKeyDown={submit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<SubmitOverlayLower
|
<SubmitOverlayLower errorMessage={errorMessage} infoMessage={infoMessage}>
|
||||||
ref={submitButtonRef}
|
<Button shaking={submitButtonShaking} onClick={submit}>{submitButtonText}</Button>
|
||||||
submitting={submitting} submitFailed={submitFailed} shaking={shaking}
|
</SubmitOverlayLower>
|
||||||
buttonMessage="Create Channel" onSubmit={onSubmit}
|
|
||||||
errorMessage={errorMessage} infoMessage={infoMessage}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default ModifyChannelOverlay;
|
|
||||||
|
export default ChannelOverlay;
|
@ -1,128 +0,0 @@
|
|||||||
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, { createRef, FC, useCallback, useEffect, useMemo, useState } from 'react';
|
|
||||||
import CombinedGuild from '../../guild-combined';
|
|
||||||
import BaseElements from '../require/base-elements';
|
|
||||||
import TextInput from '../components/input-text';
|
|
||||||
import SubmitOverlayLower from '../components/submit-overlay-lower';
|
|
||||||
import Globals from '../../globals';
|
|
||||||
import ElementsUtil from '../require/elements-util';
|
|
||||||
|
|
||||||
export interface CreateChannelOverlayProps {
|
|
||||||
document: Document;
|
|
||||||
guild: CombinedGuild;
|
|
||||||
}
|
|
||||||
const CreateChannelOverlay: FC<CreateChannelOverlayProps> = (props: CreateChannelOverlayProps) => {
|
|
||||||
const { document, guild } = props;
|
|
||||||
|
|
||||||
const nameInputRef = createRef<HTMLInputElement>();
|
|
||||||
const flavorTextInputRef = createRef<HTMLInputElement>();
|
|
||||||
const submitButtonRef = createRef<HTMLDivElement>();
|
|
||||||
|
|
||||||
const [ edited, setEdited ] = useState<boolean>(false);
|
|
||||||
const [ submitting, setSubmitting ] = useState<boolean>(false);
|
|
||||||
const [ submitFailed, setSubmitFailed ] = useState<boolean>(false);
|
|
||||||
const [ shaking, setShaking ] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const [ name, setName ] = useState<string>('');
|
|
||||||
const [ flavorText, setFlavorText ] = useState<string>('');
|
|
||||||
|
|
||||||
const [ queryFailed, setQueryFailed ] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const [ nameInputValid, setNameInputValid ] = useState<boolean>(false);
|
|
||||||
const [ nameInputMessage, setNameInputMessage ] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const [ flavorTextInputValid, setFlavorTextInputValid ] = useState<boolean>(false);
|
|
||||||
const [ flavorTextInputMessage, setFlavorTextInputMessage ] = useState<string | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
nameInputRef.current?.focus();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const errorMessage = useMemo(() => {
|
|
||||||
if (!edited) return null;
|
|
||||||
if ( !nameInputValid && nameInputMessage) return nameInputMessage;
|
|
||||||
if (!flavorTextInputValid && flavorTextInputMessage) return flavorTextInputMessage;
|
|
||||||
if (queryFailed) return 'Unable to create new channel';
|
|
||||||
return null;
|
|
||||||
}, [ nameInputValid, nameInputMessage, flavorTextInputValid, flavorTextInputMessage, queryFailed ]);
|
|
||||||
|
|
||||||
const infoMessage = useMemo(() => {
|
|
||||||
if ( nameInputValid && nameInputMessage) return nameInputMessage;
|
|
||||||
if (flavorTextInputValid && flavorTextInputMessage) return flavorTextInputMessage;
|
|
||||||
return null;
|
|
||||||
}, [ nameInputValid, nameInputMessage, flavorTextInputValid, flavorTextInputMessage ]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (name.length > 0 || flavorText.length > 0) {
|
|
||||||
setEdited(true);
|
|
||||||
}
|
|
||||||
}, [ name, flavorText ]);
|
|
||||||
|
|
||||||
const doSubmit = useCallback(async (): Promise<boolean> => {
|
|
||||||
LOG.debug('submitting');
|
|
||||||
if (!edited) return false;
|
|
||||||
if (errorMessage) return false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Make sure to null out flavor text if empty
|
|
||||||
await guild.requestDoCreateChannel(name, flavorText === '' ? null : flavorText);
|
|
||||||
} catch (e: unknown) {
|
|
||||||
LOG.error('error creating channel', e);
|
|
||||||
setQueryFailed(true);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ElementsUtil.closeReactOverlay(document);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}, [ errorMessage, name, flavorText ]);
|
|
||||||
|
|
||||||
const onSubmit = useCallback(
|
|
||||||
ElementsUtil.createShakingOnSubmit({ doSubmit, setShaking, setSubmitFailed, setSubmitting }),
|
|
||||||
[ doSubmit, shaking, submitFailed, submitFailed, submitting ]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="content submit-dialog modify-channel">
|
|
||||||
<div className="preview channel-title">
|
|
||||||
<div className="channel-icon">{BaseElements.TEXT_CHANNEL_ICON}</div>
|
|
||||||
<div className="channel-name">{name}</div>
|
|
||||||
<div className="channel-flavor-divider"></div>
|
|
||||||
<div className="channel-flavor-text">{flavorText}</div>
|
|
||||||
</div>
|
|
||||||
<div className="channel-name">
|
|
||||||
<TextInput
|
|
||||||
ref={nameInputRef}
|
|
||||||
label="Channel Name" placeholder="channel-name"
|
|
||||||
maxLength={Globals.MAX_CHANNEL_NAME_LENGTH}
|
|
||||||
value={name} setValue={setName}
|
|
||||||
setValid={setNameInputValid} setMessage={setNameInputMessage}
|
|
||||||
valueMap={value => value.toLowerCase().replace(' ', '-').replace(/[^a-z0-9-]/, '')}
|
|
||||||
onEnterKeyDown={() => flavorTextInputRef.current?.focus()}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flavor-text">
|
|
||||||
<TextInput
|
|
||||||
ref={flavorTextInputRef}
|
|
||||||
label="Flavor Text" placeholder="(optional)"
|
|
||||||
maxLength={Globals.MAX_CHANNEL_FLAVOR_TEXT_LENGTH}
|
|
||||||
allowEmpty={true}
|
|
||||||
value={flavorText} setValue={setFlavorText}
|
|
||||||
setValid={setFlavorTextInputValid} setMessage={setFlavorTextInputMessage}
|
|
||||||
onEnterKeyDown={() => { submitButtonRef.current?.click(); }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<SubmitOverlayLower
|
|
||||||
ref={submitButtonRef}
|
|
||||||
submitting={submitting} submitFailed={submitFailed} shaking={shaking}
|
|
||||||
buttonMessage="Create Channel" onSubmit={onSubmit}
|
|
||||||
errorMessage={errorMessage} infoMessage={infoMessage}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
export default CreateChannelOverlay;
|
|
@ -23,7 +23,6 @@ const GuildSettingsOverlay: FC<GuildSettingsOverlayProps> = (props: GuildSetting
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedId === 'overview') setDisplay(<GuildOverviewDisplay guild={guild} />);
|
if (selectedId === 'overview') setDisplay(<GuildOverviewDisplay guild={guild} />);
|
||||||
//if (selectedId === 'channels') setDisplay(<GuildOverviewDisplay guild={guild} guildMeta={guildMeta} />);
|
|
||||||
//if (selectedId === 'roles' ) setDisplay(<GuildOverviewDisplay guild={guild} guildMeta={guildMeta} />);
|
//if (selectedId === 'roles' ) setDisplay(<GuildOverviewDisplay guild={guild} guildMeta={guildMeta} />);
|
||||||
if (selectedId === 'invites' ) setDisplay(<GuildInvitesDisplay guild={guild} />);
|
if (selectedId === 'invites' ) setDisplay(<GuildInvitesDisplay guild={guild} />);
|
||||||
}, [ selectedId ]);
|
}, [ selectedId ]);
|
||||||
@ -36,7 +35,6 @@ const GuildSettingsOverlay: FC<GuildSettingsOverlayProps> = (props: GuildSetting
|
|||||||
<div className="content display-swapper guild-settings">
|
<div className="content display-swapper guild-settings">
|
||||||
<ChoicesControl title={guildNameText} selectedId={selectedId} setSelectedId={setSelectedId} choices={[
|
<ChoicesControl title={guildNameText} selectedId={selectedId} setSelectedId={setSelectedId} choices={[
|
||||||
{ id: 'overview', display: 'Overview' },
|
{ id: 'overview', display: 'Overview' },
|
||||||
{ id: 'channels', display: 'Channels' },
|
|
||||||
{ id: 'roles', display: 'Roles' },
|
{ id: 'roles', display: 'Roles' },
|
||||||
{ id: 'invites', display: 'Invites' },
|
{ id: 'invites', display: 'Invites' },
|
||||||
]} />
|
]} />
|
||||||
|
@ -3,7 +3,7 @@ const electronConsole = electronRemote.getGlobal('console') as Console;
|
|||||||
import Logger from '../../../../logger/logger';
|
import Logger from '../../../../logger/logger';
|
||||||
const LOG = Logger.create(__filename, electronConsole);
|
const LOG = Logger.create(__filename, electronConsole);
|
||||||
|
|
||||||
import React, { createRef, FC, useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { createRef, FC, useEffect, useMemo, useState } from 'react';
|
||||||
import { ConnectionInfo } from '../../data-types';
|
import { ConnectionInfo } from '../../data-types';
|
||||||
import Globals from '../../globals';
|
import Globals from '../../globals';
|
||||||
import CombinedGuild from '../../guild-combined';
|
import CombinedGuild from '../../guild-combined';
|
||||||
@ -12,6 +12,8 @@ import TextInput from '../components/input-text';
|
|||||||
import SubmitOverlayLower from '../components/submit-overlay-lower';
|
import SubmitOverlayLower from '../components/submit-overlay-lower';
|
||||||
import ElementsUtil from '../require/elements-util';
|
import ElementsUtil from '../require/elements-util';
|
||||||
import GuildSubscriptions from '../require/guild-subscriptions';
|
import GuildSubscriptions from '../require/guild-subscriptions';
|
||||||
|
import ReactHelper, { ExpectedError } from '../require/react-helper';
|
||||||
|
import Button from '../components/button';
|
||||||
|
|
||||||
export interface PersonalizeOverlayProps {
|
export interface PersonalizeOverlayProps {
|
||||||
document: Document;
|
document: Document;
|
||||||
@ -25,28 +27,26 @@ const PersonalizeOverlay: FC<PersonalizeOverlayProps> = (props: PersonalizeOverl
|
|||||||
throw new Error('bad avatar');
|
throw new Error('bad avatar');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isMounted = ReactHelper.useIsMountedRef();
|
||||||
|
|
||||||
const [ avatarResource, avatarResourceError ] = GuildSubscriptions.useResourceSubscription(guild, connection.avatarResourceId)
|
const [ avatarResource, avatarResourceError ] = GuildSubscriptions.useResourceSubscription(guild, connection.avatarResourceId)
|
||||||
|
|
||||||
const displayNameInputRef = createRef<HTMLInputElement>();
|
const displayNameInputRef = createRef<HTMLInputElement>();
|
||||||
|
|
||||||
const [ submitting, setSubmitting ] = useState<boolean>(false);
|
|
||||||
const [ submitFailed, setSubmitFailed ] = useState<boolean>(false);
|
|
||||||
const [ shaking, setShaking ] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const [ savedDisplayName, setSavedDisplayName ] = useState<string>(connection.displayName);
|
const [ savedDisplayName, setSavedDisplayName ] = useState<string>(connection.displayName);
|
||||||
const [ savedAvatarBuff, setSavedAvatarBuff ] = useState<Buffer | null>(null);
|
const [ savedAvatarBuff, setSavedAvatarBuff ] = useState<Buffer | null>(null);
|
||||||
|
|
||||||
const [ displayName, setDisplayName ] = useState<string>(connection.displayName);
|
const [ displayName, setDisplayName ] = useState<string>(connection.displayName);
|
||||||
const [ avatarBuff, setAvatarBuff ] = useState<Buffer | null>(null);
|
const [ avatarBuff, setAvatarBuff ] = useState<Buffer | null>(null);
|
||||||
|
|
||||||
const [ saveFailed, setSaveFailed ] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const [ displayNameInputValid, setDisplayNameInputValid ] = useState<boolean>(false);
|
const [ displayNameInputValid, setDisplayNameInputValid ] = useState<boolean>(false);
|
||||||
const [ displayNameInputMessage, setDisplayNameInputMessage ] = useState<string | null>(null);
|
const [ displayNameInputMessage, setDisplayNameInputMessage ] = useState<string | null>(null);
|
||||||
|
|
||||||
const [ avatarInputValid, setAvatarInputValid ] = useState<boolean>(false);
|
const [ avatarInputValid, setAvatarInputValid ] = useState<boolean>(false);
|
||||||
const [ avatarInputMessage, setAvatarInputMessage ] = useState<string | null>(null);
|
const [ avatarInputMessage, setAvatarInputMessage ] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const [ submitFailMessage, setSubmitFailMessage ] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (avatarResource) {
|
if (avatarResource) {
|
||||||
if (avatarBuff === savedAvatarBuff) setAvatarBuff(avatarResource.data);
|
if (avatarBuff === savedAvatarBuff) setAvatarBuff(avatarResource.data);
|
||||||
@ -58,13 +58,12 @@ const PersonalizeOverlay: FC<PersonalizeOverlayProps> = (props: PersonalizeOverl
|
|||||||
displayNameInputRef.current?.focus();
|
displayNameInputRef.current?.focus();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const errorMessage = useMemo(() => {
|
const validationErrorMessage = useMemo(() => {
|
||||||
if (avatarResourceError) return 'Unable to load avatar';
|
if (avatarResourceError) return 'Unable to load avatar';
|
||||||
if ( !avatarInputValid && avatarInputMessage) return avatarInputMessage;
|
if ( !avatarInputValid && avatarInputMessage) return avatarInputMessage;
|
||||||
if (!displayNameInputValid && displayNameInputMessage) return displayNameInputMessage;
|
if (!displayNameInputValid && displayNameInputMessage) return displayNameInputMessage;
|
||||||
if (saveFailed) return 'Unable to save personalization';
|
|
||||||
return null;
|
return null;
|
||||||
}, [ saveFailed, avatarResourceError, displayNameInputValid, displayNameInputMessage, avatarInputValid, avatarInputMessage ]);
|
}, [ avatarResourceError, displayNameInputValid, displayNameInputMessage, avatarInputValid, avatarInputMessage ]);
|
||||||
|
|
||||||
const infoMessage = useMemo(() => {
|
const infoMessage = useMemo(() => {
|
||||||
if (avatarInputValid && avatarInputMessage) return avatarInputMessage;
|
if (avatarInputValid && avatarInputMessage) return avatarInputMessage;
|
||||||
@ -72,18 +71,20 @@ const PersonalizeOverlay: FC<PersonalizeOverlayProps> = (props: PersonalizeOverl
|
|||||||
return null;
|
return null;
|
||||||
}, [ displayNameInputValid, displayNameInputMessage, avatarInputValid, avatarInputMessage ]);
|
}, [ displayNameInputValid, displayNameInputMessage, avatarInputValid, avatarInputMessage ]);
|
||||||
|
|
||||||
const doSubmit = useCallback(async (): Promise<boolean> => {
|
const [ _, submitError, submitButtonText, submitButtonShaking, submitButtonCallback ] = ReactHelper.useAsyncButtonSubscription(async (): Promise<void> => {
|
||||||
if (errorMessage) return false;
|
if (validationErrorMessage) throw new ExpectedError('invalid input');
|
||||||
|
setSubmitFailMessage(null);
|
||||||
|
|
||||||
if (displayName !== savedDisplayName) {
|
if (displayName !== savedDisplayName) {
|
||||||
// Save display name
|
// Save display name
|
||||||
try {
|
try {
|
||||||
await guild.requestSetDisplayName(displayName);
|
await guild.requestSetDisplayName(displayName);
|
||||||
|
if (!isMounted.current) return;
|
||||||
setSavedDisplayName(displayName);
|
setSavedDisplayName(displayName);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
LOG.error('error setting guild name', e);
|
if (!isMounted.current) return;
|
||||||
setSaveFailed(true);
|
setSubmitFailMessage('error setting guild name');
|
||||||
return false;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,23 +92,24 @@ const PersonalizeOverlay: FC<PersonalizeOverlayProps> = (props: PersonalizeOverl
|
|||||||
// Save avatar
|
// Save avatar
|
||||||
try {
|
try {
|
||||||
await guild.requestSetAvatar(avatarBuff);
|
await guild.requestSetAvatar(avatarBuff);
|
||||||
|
if (!isMounted.current) return;
|
||||||
setSavedAvatarBuff(avatarBuff);
|
setSavedAvatarBuff(avatarBuff);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
LOG.error('error setting avatar', e);
|
if (!isMounted.current) return;
|
||||||
setSaveFailed(true);
|
setSubmitFailMessage('error setting avatar');
|
||||||
return false;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ElementsUtil.closeReactOverlay(document);
|
ElementsUtil.closeReactOverlay(document);
|
||||||
|
}, { start: 'Submit', pending: 'Submitting...', error: 'Try Again', done: 'Done' }, [ validationErrorMessage, displayName, savedDisplayName, avatarBuff, savedAvatarBuff ]);
|
||||||
|
|
||||||
return true;
|
//if (saveFailed) return 'Unable to save personalization';
|
||||||
}, [ errorMessage, displayName, savedDisplayName, avatarBuff, savedAvatarBuff ]);
|
const errorMessage = useMemo(() => {
|
||||||
|
if (validationErrorMessage) return validationErrorMessage;
|
||||||
const onSubmit = useCallback(
|
if (submitFailMessage) return submitFailMessage;
|
||||||
ElementsUtil.createShakingOnSubmit({ doSubmit, setShaking, setSubmitFailed, setSubmitting }),
|
return null;
|
||||||
[ doSubmit, shaking, submitFailed, submitFailed, submitting ]
|
}, [ validationErrorMessage, submitError ]);
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="content personalize">
|
<div className="content personalize">
|
||||||
@ -126,15 +128,13 @@ const PersonalizeOverlay: FC<PersonalizeOverlayProps> = (props: PersonalizeOverl
|
|||||||
maxLength={Globals.MAX_DISPLAY_NAME_LENGTH}
|
maxLength={Globals.MAX_DISPLAY_NAME_LENGTH}
|
||||||
value={displayName} setValue={setDisplayName}
|
value={displayName} setValue={setDisplayName}
|
||||||
setValid={setDisplayNameInputValid} setMessage={setDisplayNameInputMessage}
|
setValid={setDisplayNameInputValid} setMessage={setDisplayNameInputMessage}
|
||||||
onEnterKeyDown={onSubmit}
|
onEnterKeyDown={submitButtonCallback}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SubmitOverlayLower
|
<SubmitOverlayLower errorMessage={errorMessage} infoMessage={infoMessage}>
|
||||||
submitting={submitting} submitFailed={submitFailed} shaking={shaking}
|
<Button shaking={submitButtonShaking} onClick={submitButtonCallback}>{submitButtonText}</Button>
|
||||||
buttonMessage="Save" onSubmit={onSubmit}
|
</SubmitOverlayLower>
|
||||||
errorMessage={errorMessage} infoMessage={infoMessage}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,5 +2,5 @@
|
|||||||
@import "./overlay-error-message.scss";
|
@import "./overlay-error-message.scss";
|
||||||
@import "./overlay-guild-settings.scss";
|
@import "./overlay-guild-settings.scss";
|
||||||
@import "./overlay-image.scss";
|
@import "./overlay-image.scss";
|
||||||
@import "./overlay-modify-channel.scss";
|
@import "./overlay-channel.scss";
|
||||||
@import "./overlay-personalize.scss";
|
@import "./overlay-personalize.scss";
|
||||||
|
@ -11,7 +11,7 @@ import { AutoVerifierChangesType } from "../../auto-verifier";
|
|||||||
import { Conflictable, Connectable } from "../../guild-types";
|
import { Conflictable, Connectable } from "../../guild-types";
|
||||||
import { EventEmitter } from 'tsee';
|
import { EventEmitter } from 'tsee';
|
||||||
import { IDQuery } from '../../auto-verifier-with-args';
|
import { IDQuery } from '../../auto-verifier-with-args';
|
||||||
import { Token } from '../../data-types';
|
import { Token, Channel } from '../../data-types';
|
||||||
|
|
||||||
export type SingleSubscriptionEvents = {
|
export type SingleSubscriptionEvents = {
|
||||||
'fetch': () => void;
|
'fetch': () => void;
|
||||||
@ -323,6 +323,23 @@ export default class GuildSubscriptions {
|
|||||||
}, fetchResourceFunc);
|
}, fetchResourceFunc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static useChannelsSubscription(guild: CombinedGuild) {
|
||||||
|
const fetchChannelsFunc = useCallback(async () => {
|
||||||
|
return await guild.fetchChannels();
|
||||||
|
}, [ guild ]);
|
||||||
|
return GuildSubscriptions.useMultipleGuildSubscription<Channel, 'new-channels', 'update-channels', 'remove-channels', 'conflict-channels'>(guild, {
|
||||||
|
newEventName: 'new-channels',
|
||||||
|
newEventArgsMap: (channels: Channel[]) => channels,
|
||||||
|
updatedEventName: 'update-channels',
|
||||||
|
updatedEventArgsMap: (updatedChannels: Channel[]) => updatedChannels,
|
||||||
|
removedEventName: 'remove-channels',
|
||||||
|
removedEventArgsMap: (removedChannels: Channel[]) => removedChannels,
|
||||||
|
conflictEventName: 'conflict-channels',
|
||||||
|
conflictEventArgsMap: (changesType: AutoVerifierChangesType, changes: Changes<Channel>) => changes,
|
||||||
|
sortFunc: Channel.sortByIndex
|
||||||
|
}, fetchChannelsFunc);
|
||||||
|
}
|
||||||
|
|
||||||
static useTokensSubscription(guild: CombinedGuild) {
|
static useTokensSubscription(guild: CombinedGuild) {
|
||||||
const fetchTokensFunc = useCallback(async () => {
|
const fetchTokensFunc = useCallback(async () => {
|
||||||
//LOG.silly('fetching tokens for subscription');
|
//LOG.silly('fetching tokens for subscription');
|
||||||
|
@ -8,6 +8,8 @@ import ReactDOMServer from "react-dom/server";
|
|||||||
import { ShouldNeverHappenError } from "../../data-types";
|
import { ShouldNeverHappenError } from "../../data-types";
|
||||||
import Util from '../../util';
|
import Util from '../../util';
|
||||||
|
|
||||||
|
export class ExpectedError extends Error {}
|
||||||
|
|
||||||
// Helper function so we can use JSX before fully committing to React
|
// Helper function so we can use JSX before fully committing to React
|
||||||
|
|
||||||
export default class ReactHelper {
|
export default class ReactHelper {
|
||||||
@ -21,6 +23,15 @@ export default class ReactHelper {
|
|||||||
return document.body.firstElementChild;
|
return document.body.firstElementChild;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static useIsMountedRef() {
|
||||||
|
const isMounted = useRef<boolean>(false);
|
||||||
|
useEffect(() => {
|
||||||
|
isMounted.current = true;
|
||||||
|
return () => { isMounted.current = false; }
|
||||||
|
});
|
||||||
|
return isMounted;
|
||||||
|
}
|
||||||
|
|
||||||
static useAsyncActionSubscription<T, V>(
|
static useAsyncActionSubscription<T, V>(
|
||||||
actionFunc: () => Promise<T>,
|
actionFunc: () => Promise<T>,
|
||||||
initialValue: V,
|
initialValue: V,
|
||||||
@ -80,6 +91,7 @@ export default class ReactHelper {
|
|||||||
|
|
||||||
const callback = useCallback(async () => {
|
const callback = useCallback(async () => {
|
||||||
if (pending) return;
|
if (pending) return;
|
||||||
|
setError(null);
|
||||||
setPending(true);
|
setPending(true);
|
||||||
try {
|
try {
|
||||||
const value = await actionFunc();
|
const value = await actionFunc();
|
||||||
|
Loading…
Reference in New Issue
Block a user