Reactify modify channel
This commit is contained in:
parent
78093316ea
commit
4c81488187
@ -4,11 +4,11 @@ import ReactHelper from './require/react-helper';
|
||||
import ElementsUtil from './require/elements-util';
|
||||
import BaseElements from './require/base-elements';
|
||||
import { Channel } from '../data-types';
|
||||
import createModifyChannelOverlay from './overlay-modify-channel';
|
||||
import UI from '../ui';
|
||||
import Actions from '../actions';
|
||||
import Q from '../q-module';
|
||||
import CombinedGuild from '../guild-combined';
|
||||
import ModifyChannelOverlay from './overlays/overlay-modify-channel';
|
||||
|
||||
export default function createChannel(document: Document, q: Q, ui: UI, guild: CombinedGuild, channel: Channel) {
|
||||
const element = ReactHelper.createElementFromJSX(
|
||||
@ -38,10 +38,7 @@ export default function createChannel(document: Document, q: Q, ui: UI, guild: C
|
||||
if (modifyContextElement.parentElement) {
|
||||
modifyContextElement.parentElement.removeChild(modifyContextElement);
|
||||
}
|
||||
const modifyOverlay = createModifyChannelOverlay(document, q, guild, channel);
|
||||
document.body.appendChild(modifyOverlay);
|
||||
q.$$$(modifyOverlay, '.text-input.channel-name').focus();
|
||||
ElementsUtil.setCursorToEnd(q.$$$(modifyOverlay, '.text-input.channel-name'));
|
||||
ElementsUtil.presentReactOverlay(document, <ModifyChannelOverlay document={document} guild={guild} channel={channel} />);
|
||||
});
|
||||
|
||||
q.$$$(element, '.modify').addEventListener('mouseenter', () => {
|
||||
|
@ -10,7 +10,6 @@ import Q from '../q-module';
|
||||
import UI from '../ui';
|
||||
|
||||
import createCreateInviteTokenOverlay from './overlay-create-invite-token';
|
||||
import createCreateChannelOverlay from './overlay-create-channel';
|
||||
import createTokenLogOverlay from './overlay-token-log';
|
||||
import CombinedGuild from '../guild-combined';
|
||||
|
||||
|
@ -1,155 +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 Globals from "../globals";
|
||||
|
||||
import ElementsUtil from "./require/elements-util";
|
||||
import BaseElements from "./require/base-elements";
|
||||
import Q from '../q-module';
|
||||
import CombinedGuild from '../guild-combined';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export default function createCreateChannelOverlay(document: Document, q: Q, guild: CombinedGuild): HTMLElement {
|
||||
|
||||
// See also overlay-modify-channel
|
||||
|
||||
const element = BaseElements.createOverlay(document, (
|
||||
<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">channel-name</div>
|
||||
<div className="channel-flavor-divider"></div>
|
||||
<div className="channel-flavor-text"></div>
|
||||
</div>
|
||||
<div className="text-input channel-name" data-placeholder="channel-name" contentEditable={'plaintext-only' as unknown as boolean /* React doesn't have plaintext-only in its typings (https://github.com/DefinitelyTyped/DefinitelyTyped/pull/54779) */}></div>
|
||||
<div className="text-input channel-flavor-text" data-placeholder="Flavor Text (optional)" contentEditable={'plaintext-only' as unknown as boolean /* React doesn't have plaintext-only in its typings (https://github.com/DefinitelyTyped/DefinitelyTyped/pull/54779) */}></div>
|
||||
<div className="lower">
|
||||
<div className="error"></div>
|
||||
<div className="buttons">
|
||||
<div className="button submit">Create Channel</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
|
||||
let newName = '';
|
||||
let newFlavorText: string | null = '';
|
||||
function updatePreview() {
|
||||
newName = q.$$$(element, '.text-input.channel-name').innerText;
|
||||
newFlavorText = q.$$$(element, '.text-input.channel-flavor-text').innerText;
|
||||
q.$$$(element, '.channel-title .channel-name').innerText = newName;
|
||||
q.$$$(element, '.channel-title .channel-flavor-text').innerText = newFlavorText;
|
||||
|
||||
if (newFlavorText != '') {
|
||||
q.$$$(element, '.channel-title .channel-flavor-divider').style.visibility = 'visible';
|
||||
} else {
|
||||
q.$$$(element, '.channel-title .channel-flavor-divider').style.visibility = 'hidden';
|
||||
}
|
||||
}
|
||||
updatePreview();
|
||||
|
||||
let submitting = false;
|
||||
async function submit() {
|
||||
if (submitting) return;
|
||||
submitting = true;
|
||||
|
||||
q.$$$(element, '.error').innerText = '';
|
||||
q.$$$(element, '.button.submit').innerText = 'Submitting...';
|
||||
q.$$$(element, '.text-input.channel-name').removeAttribute('contenteditable');
|
||||
q.$$$(element, '.text-input.channel-flavor-text').removeAttribute('contenteditable');
|
||||
|
||||
let success = false;
|
||||
if (newName.length == 0) {
|
||||
LOG.warn('attempted to set empty channel name');
|
||||
q.$$$(element, '.error').innerText = 'Channel name cannot be empty';
|
||||
q.$$$(element, '.button.submit').innerText = 'Try Again';
|
||||
await ElementsUtil.shakeElement(q.$$$(element, '.button.submit'), 400);
|
||||
} else if (newName.length > Globals.MAX_CHANNEL_NAME_LENGTH) {
|
||||
LOG.warn('attempted to set too long channel name');
|
||||
q.$$$(element, '.error').innerText = 'Channel name is too long. ' + newName.length + ' > ' + Globals.MAX_CHANNEL_NAME_LENGTH;
|
||||
q.$$$(element, '.button.submit').innerText = 'Try Again';
|
||||
await ElementsUtil.shakeElement(q.$$$(element, '.button.submit'), 400);
|
||||
} else if (!(/^[A-Za-z0-9-]+$/.exec(newName))) {
|
||||
LOG.warn('attempted to set channel name with illegal characters');
|
||||
q.$$$(element, '.error').innerText = 'Please use only [A-Za-z0-9-]+ in channel name';
|
||||
q.$$$(element, '.button.submit').innerText = 'Try Again';
|
||||
await ElementsUtil.shakeElement(q.$$$(element, '.button.submit'), 400);
|
||||
} else if (newFlavorText != null && newFlavorText.length > Globals.MAX_CHANNEL_FLAVOR_TEXT_LENGTH) {
|
||||
LOG.warn('attempted to set too long flavor text');
|
||||
q.$$$(element, '.error').innerText = 'Flavor text is too long. ' + newName.length + ' > ' + Globals.MAX_CHANNEL_NAME_LENGTH;
|
||||
q.$$$(element, '.button.submit').innerText = 'Try Again';
|
||||
await ElementsUtil.shakeElement(q.$$$(element, '.button.submit'), 400);
|
||||
} else {
|
||||
if (newFlavorText != null && newFlavorText.length == 0) {
|
||||
newFlavorText = null;
|
||||
}
|
||||
try {
|
||||
await guild.requestDoCreateChannel(newName, newFlavorText);
|
||||
success = true;
|
||||
} catch (e) {
|
||||
LOG.error('error updating channel', e);
|
||||
q.$$$(element, '.error').innerText = 'Error updating channel';
|
||||
q.$$$(element, '.button.submit').innerText = 'Try Again';
|
||||
await ElementsUtil.shakeElement(q.$$$(element, '.button.submit'), 400);
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
element.removeSelf();
|
||||
}
|
||||
|
||||
q.$$$(element, '.text-input.channel-name').setAttribute('contenteditable', 'plaintext-only');
|
||||
q.$$$(element, '.text-input.channel-flavor-text').setAttribute('contenteditable', 'plaintext-only');
|
||||
|
||||
submitting = false;
|
||||
}
|
||||
|
||||
const textInputs = q.$$$$(element, '.text-input');
|
||||
for (const textInput of textInputs) {
|
||||
textInput.addEventListener('input', () => {
|
||||
updatePreview();
|
||||
});
|
||||
}
|
||||
|
||||
q.$$$(element, '.text-input.channel-name').addEventListener('keydown', async (e) => {
|
||||
if (e.key == 'Backspace' || e.key == 'Escape' || e.key == 'F4') {
|
||||
// these keys are good
|
||||
} else if (e.key == 'Tab') {
|
||||
// have to hard-code this one because otherwise, it just picks the beginning of the next input
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
q.$$$(element, '.text-input.channel-flavor-text').focus();
|
||||
ElementsUtil.setCursorToEnd(q.$$$(element, '.text-input.channel-flavor-text'));
|
||||
} else if (e.key == 'Enter') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
await submit();
|
||||
} else if (!/^[A-Za-z0-9-]$/.exec(e.key)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
q.$$$(element, '.text-input.channel-flavor-text').addEventListener('keydown', async (e) => {
|
||||
if (e.key == 'Tab' && e.shiftKey) {
|
||||
// have to hard-code this one because otherwise, it just picks the beginning of the next input
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
q.$$$(element, '.text-input.channel-name').focus();
|
||||
ElementsUtil.setCursorToEnd(q.$$$(element, '.text-input.channel-name'));
|
||||
} else if (e.key == 'Enter') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
await submit();
|
||||
}
|
||||
});
|
||||
|
||||
q.$$$(element, '.button.submit').addEventListener('click', async () => {
|
||||
await submit();
|
||||
});
|
||||
|
||||
return element;
|
||||
}
|
@ -1,162 +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 { Channel } from '../data-types';
|
||||
import Globals from '../globals.js';
|
||||
|
||||
import BaseElements, { HTMLElementWithRemoveSelf } from './require/base-elements.js';
|
||||
import ElementsUtil from './require/elements-util.js';
|
||||
import Q from '../q-module';
|
||||
import CombinedGuild from '../guild-combined';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export default function createModifyChannelOverlay(document: Document, q: Q, guild: CombinedGuild, channel: Channel): HTMLElementWithRemoveSelf {
|
||||
// See also overlay-create-channel
|
||||
|
||||
const element = BaseElements.createOverlay(document, (
|
||||
<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">{channel.name}</div>
|
||||
<div className="channel-flavor-divider"></div>
|
||||
<div className="channel-flavor-text">{channel.flavorText || ''}</div>
|
||||
</div>
|
||||
<div className="text-input channel-name" data-placeholder="channel-name"
|
||||
contentEditable={'plaintext-only' as unknown as boolean /* React doesn't have plaintext-only in its typings (https://github.com/DefinitelyTyped/DefinitelyTyped/pull/54779) */}></div>
|
||||
<div className="text-input channel-flavor-text" data-placeholder="Flavor Text (optional)"
|
||||
contentEditable={'plaintext-only' as unknown as boolean /* React doesn't have plaintext-only in its typings (https://github.com/DefinitelyTyped/DefinitelyTyped/pull/54779) */}></div>
|
||||
<div className="lower">
|
||||
<div className="error"></div>
|
||||
<div className="buttons">
|
||||
<div className="button submit">Save Changes</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
|
||||
q.$$$(element, '.text-input.channel-name').innerText = channel.name;
|
||||
q.$$$(element, '.text-input.channel-flavor-text').innerText = channel.flavorText || '';
|
||||
|
||||
let newName = channel.name;
|
||||
let newFlavorText = channel.flavorText;
|
||||
function updatePreview() {
|
||||
newName = q.$$$(element, '.text-input.channel-name').innerText;
|
||||
newFlavorText = q.$$$(element, '.text-input.channel-flavor-text').innerText;
|
||||
q.$$$(element, '.channel-title .channel-name').innerText = newName;
|
||||
q.$$$(element, '.channel-title .channel-flavor-text').innerText = newFlavorText;
|
||||
|
||||
if (newFlavorText != '') {
|
||||
q.$$$(element, '.channel-title .channel-flavor-divider').style.visibility = 'visible';
|
||||
} else {
|
||||
q.$$$(element, '.channel-title .channel-flavor-divider').style.visibility = 'hidden';
|
||||
}
|
||||
}
|
||||
updatePreview();
|
||||
|
||||
let submitting = false;
|
||||
async function submit() {
|
||||
if (submitting) return;
|
||||
submitting = true;
|
||||
|
||||
q.$$$(element, '.error').innerText = '';
|
||||
q.$$$(element, '.button.submit').innerText = 'Submitting...';
|
||||
q.$$$(element, '.text-input.channel-name').removeAttribute('contenteditable');
|
||||
q.$$$(element, '.text-input.channel-flavor-text').removeAttribute('contenteditable');
|
||||
|
||||
let success = false;
|
||||
if (newName == channel.name && (newFlavorText || '') == (channel.flavorText || '')) {
|
||||
success = true; // nothing changed
|
||||
} else if (newName.length == 0) {
|
||||
LOG.warn('attempted to set empty channel name');
|
||||
q.$$$(element, '.error').innerText = 'Channel name cannot be empty';
|
||||
q.$$$(element, '.button.submit').innerText = 'Try Again';
|
||||
await ElementsUtil.shakeElement(q.$$$(element, '.button.submit'), 400);
|
||||
} else if (newName.length > Globals.MAX_CHANNEL_NAME_LENGTH) {
|
||||
LOG.warn('attempted to set too long channel name');
|
||||
q.$$$(element, '.error').innerText = 'Channel name is too long. ' + newName.length + ' > ' + Globals.MAX_CHANNEL_NAME_LENGTH;
|
||||
q.$$$(element, '.button.submit').innerText = 'Try Again';
|
||||
await ElementsUtil.shakeElement(q.$$$(element, '.button.submit'), 400);
|
||||
} else if (!(/^[A-Za-z0-9-]+$/.exec(newName))) {
|
||||
LOG.warn('attempted to set channel name with illegal characters');
|
||||
q.$$$(element, '.error').innerText = 'Please use only [A-Za-z0-9-]+ in channel name';
|
||||
q.$$$(element, '.button.submit').innerText = 'Try Again';
|
||||
await ElementsUtil.shakeElement(q.$$$(element, '.button.submit'), 400);
|
||||
} else if (newFlavorText != null && newFlavorText.length > Globals.MAX_CHANNEL_FLAVOR_TEXT_LENGTH) {
|
||||
LOG.warn('attempted to set too long flavor text');
|
||||
q.$$$(element, '.error').innerText = 'Flavor text is too long. ' + newName.length + ' > ' + Globals.MAX_CHANNEL_NAME_LENGTH;
|
||||
q.$$$(element, '.button.submit').innerText = 'Try Again';
|
||||
await ElementsUtil.shakeElement(q.$$$(element, '.button.submit'), 400);
|
||||
} else {
|
||||
if (newFlavorText != null && newFlavorText.length == 0) {
|
||||
newFlavorText = null;
|
||||
}
|
||||
try {
|
||||
await guild.requestDoUpdateChannel(channel.id, newName, newFlavorText);
|
||||
success = true;
|
||||
} catch (e) {
|
||||
LOG.error('error updating channel', e);
|
||||
q.$$$(element, '.error').innerText = 'Error updating channel';
|
||||
q.$$$(element, '.button.submit').innerText = 'Try Again';
|
||||
await ElementsUtil.shakeElement(q.$$$(element, '.button.submit'), 400);
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
element.removeSelf();
|
||||
}
|
||||
|
||||
q.$$$(element, '.text-input.channel-name').setAttribute('contenteditable', 'plaintext-only');
|
||||
q.$$$(element, '.text-input.channel-flavor-text').setAttribute('contenteditable', 'plaintext-only');
|
||||
|
||||
submitting = false;
|
||||
}
|
||||
|
||||
const textInputs = q.$$$$(element, '.text-input');
|
||||
for (const textInput of textInputs) {
|
||||
textInput.addEventListener('input', () => {
|
||||
updatePreview();
|
||||
});
|
||||
}
|
||||
|
||||
q.$$$(element, '.text-input.channel-name').addEventListener('keydown', async (e) => {
|
||||
if (e.key == 'Backspace' || e.key == 'Escape' || e.key == 'F4') {
|
||||
// these keys are good
|
||||
} else if (e.key == 'Tab') {
|
||||
// have to hard-code this one because otherwise, it just picks the beginning of the next input
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
q.$$$(element, '.text-input.channel-flavor-text').focus();
|
||||
ElementsUtil.setCursorToEnd(q.$$$(element, '.text-input.channel-flavor-text'));
|
||||
} else if (e.key == 'Enter') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
await submit();
|
||||
} else if (!/^[A-Za-z0-9-]$/.exec(e.key)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
q.$$$(element, '.text-input.channel-flavor-text').addEventListener('keydown', async (e) => {
|
||||
if (e.key == 'Tab' && e.shiftKey) {
|
||||
// have to hard-code this one because otherwise, it just picks the beginning of the next input
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
q.$$$(element, '.text-input.channel-name').focus();
|
||||
ElementsUtil.setCursorToEnd(q.$$$(element, '.text-input.channel-name'));
|
||||
} else if (e.key == 'Enter') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
await submit();
|
||||
}
|
||||
});
|
||||
|
||||
q.$$$(element, '.button.submit').addEventListener('click', async () => {
|
||||
await submit();
|
||||
});
|
||||
|
||||
return element;
|
||||
}
|
@ -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, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import React, { createRef, FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import GuildsManager from '../../guilds-manager';
|
||||
import moment from 'moment';
|
||||
import TextInput from '../components/input-text';
|
||||
@ -62,6 +62,12 @@ const AddGuildOverlay: FC<AddGuildOverlayProps> = (props: AddGuildOverlayProps)
|
||||
const exampleDisplayName = useMemo(() => getExampleDisplayName(), []);
|
||||
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 [ avatarBuff, setAvatarBuff ] = useState<Buffer | null>(null);
|
||||
|
||||
@ -133,6 +139,11 @@ const AddGuildOverlay: FC<AddGuildOverlayProps> = (props: AddGuildOverlayProps)
|
||||
|
||||
}, [ displayName, avatarBuff, displayNameInputValid, avatarInputValid ]);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
ElementsUtil.createShakingOnSubmit({ doSubmit, setShaking, setSubmitFailed, setSubmitting }),
|
||||
[ doSubmit, shaking, submitFailed, submitFailed, submitting ]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="content add-guild">
|
||||
<div className="preview">
|
||||
@ -158,10 +169,16 @@ const AddGuildOverlay: FC<AddGuildOverlayProps> = (props: AddGuildOverlayProps)
|
||||
maxLength={Globals.MAX_DISPLAY_NAME_LENGTH}
|
||||
value={displayName} setValue={setDisplayName}
|
||||
setValid={setDisplayNameInputValid} setMessage={setDisplayNameInputMessage}
|
||||
onEnterKeyDown={() => { submitButtonRef.current?.click(); }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<SubmitOverlayLower buttonMessage="Add Guild" doSubmit={doSubmit} errorMessage={errorMessage} />
|
||||
<SubmitOverlayLower
|
||||
ref={submitButtonRef}
|
||||
buttonMessage="Add Guild"
|
||||
submitting={submitting} submitFailed={submitFailed} shaking={shaking}
|
||||
onSubmit={onSubmit} errorMessage={errorMessage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ const CreateChannelOverlay: FC<CreateChannelOverlayProps> = (props: CreateChanne
|
||||
<div className="flavor-text">
|
||||
<TextInput
|
||||
ref={flavorTextInputRef}
|
||||
label="Flavor Text" placeholder="Flavor text (optional)"
|
||||
label="Flavor Text" placeholder="(optional)"
|
||||
maxLength={Globals.MAX_CHANNEL_FLAVOR_TEXT_LENGTH}
|
||||
allowEmpty={true}
|
||||
value={flavorText} setValue={setFlavorText}
|
||||
|
130
src/client/webapp/elements/overlays/overlay-modify-channel.tsx
Normal file
130
src/client/webapp/elements/overlays/overlay-modify-channel.tsx
Normal file
@ -0,0 +1,130 @@
|
||||
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, useRef, 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';
|
||||
import { Channel } from '../../data-types';
|
||||
|
||||
export interface ModifyChannelOverlayProps {
|
||||
document: Document;
|
||||
guild: CombinedGuild;
|
||||
channel: Channel;
|
||||
}
|
||||
const ModifyChannelOverlay: FC<ModifyChannelOverlayProps> = (props: ModifyChannelOverlayProps) => {
|
||||
const { document, guild, channel } = 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>(channel.name);
|
||||
const [ flavorText, setFlavorText ] = useState<string>(channel.flavorText ?? '');
|
||||
|
||||
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.requestDoUpdateChannel(channel.id, 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={channel.flavorText ?? '(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 ModifyChannelOverlay;
|
@ -398,7 +398,7 @@ function bindAdminEvents(io: socketio.Server, client: socketio.Socket, identity:
|
||||
bindEvent(
|
||||
client, identity,
|
||||
{ verified: true, privileges: [ 'modify_channels' ] },
|
||||
'update-channel', [ 'string', 'string', 'string', 'function' ],
|
||||
'update-channel', [ 'string', 'string', 'string?', 'function' ],
|
||||
async (channelId, name, flavorText, respond) => {
|
||||
if (!identity.guildId) throw new EventError('identity no guildId');
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user