finish reactify add guild overlay
This commit is contained in:
parent
f57ef4848f
commit
f87fdeff3c
4
makefile
4
makefile
@ -28,8 +28,8 @@ move:
|
||||
|
||||
clean:
|
||||
mkdir -p ./dist
|
||||
rm -r ./dist
|
||||
rm -r ./db
|
||||
rm -r ./dist || true
|
||||
rm -r ./db || true
|
||||
|
||||
reset-server:
|
||||
psql postgres postgres < ./src/server/sql/init.sql
|
||||
|
@ -5,12 +5,14 @@ const LOG = Logger.create(__filename, electronConsole);
|
||||
|
||||
import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import ElementsUtil from '../require/elements-util';
|
||||
|
||||
interface OverlayProps {
|
||||
document: Document;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
const Overlay: FC<OverlayProps> = (props: OverlayProps) => {
|
||||
const { children } = props;
|
||||
const { document, children } = props;
|
||||
|
||||
const node = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
@ -23,12 +25,14 @@ const Overlay: FC<OverlayProps> = (props: OverlayProps) => {
|
||||
setMouseUpInChild(false);
|
||||
return;
|
||||
}
|
||||
if (node.current) {
|
||||
ReactDOM.unmountComponentAtNode(node.current.parentElement as Element);
|
||||
}
|
||||
// TODO: This is pretty messy and should be re-thought when we full-convert to react.
|
||||
// The window event listener could be re-called if we call closeReactOverlay elsewhere...
|
||||
if (keyDownHandler) {
|
||||
window.removeEventListener('keydown', keyDownHandler);
|
||||
}
|
||||
if (node.current) {
|
||||
ElementsUtil.closeReactOverlay(document);
|
||||
}
|
||||
// otherwise, this isn't in the DOM anyway
|
||||
};
|
||||
|
||||
|
@ -36,8 +36,6 @@ const SubmitOverlayLower: FC<SubmitOverlayLowerProps> = (props: SubmitOverlayLow
|
||||
if (!succeeded) {
|
||||
await ElementsUtil.delayToggleState(setShaking, 400);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
const isEmpty = useMemo(() => errorMessage === null, [ errorMessage ]);
|
||||
|
@ -7,7 +7,6 @@ import * as fs from 'fs/promises';
|
||||
|
||||
import ElementsUtil from './require/elements-util';
|
||||
|
||||
import createAddGuildOverlay, { IAddGuildData } from './overlay-add-guild';
|
||||
import Q from '../q-module';
|
||||
import UI from '../ui';
|
||||
import GuildsManager from '../guilds-manager';
|
||||
@ -54,7 +53,7 @@ export default function bindAddGuildEvents(document: Document, q: Q, ui: UI, gui
|
||||
LOG.debug('bad guild data:', { addGuildData, fileText })
|
||||
throw new Error('bad guild data');
|
||||
}
|
||||
ElementsUtil.presentReactOverlay(document, <AddGuildOverlay guildsManager={guildsManager} addGuildData={addGuildData} />)
|
||||
ElementsUtil.presentReactOverlay(document, <AddGuildOverlay document={document} ui={ui} guildsManager={guildsManager} addGuildData={addGuildData} />)
|
||||
// const overlayElement = createAddGuildOverlay(document, q, ui, guildsManager, addGuildData as IAddGuildData);
|
||||
// document.body.appendChild(overlayElement);
|
||||
} catch (e: unknown) {
|
||||
|
@ -1,228 +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 * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
|
||||
import moment from 'moment';
|
||||
|
||||
import * as FileType from 'file-type';
|
||||
|
||||
import Globals from '../globals';
|
||||
import BaseElements from './require/base-elements';
|
||||
import ElementsUtil from './require/elements-util';
|
||||
|
||||
import Q from '../q-module';
|
||||
import UI from '../ui';
|
||||
import GuildsManager from '../guilds-manager';
|
||||
import CombinedGuild from '../guild-combined';
|
||||
|
||||
import React from 'react';
|
||||
import ReactHelper from './require/react-helper';
|
||||
|
||||
export interface IAddGuildData {
|
||||
name: string,
|
||||
url: string,
|
||||
cert: string,
|
||||
token: string,
|
||||
expires: number,
|
||||
iconSrc: string
|
||||
}
|
||||
|
||||
function getExampleDisplayName(): string {
|
||||
const names = [
|
||||
'gamer69',
|
||||
'exdenoccp',
|
||||
'TiggerEliminator',
|
||||
'FreshDingus',
|
||||
'Wonky Experiment',
|
||||
'TheLegend27'
|
||||
];
|
||||
return names[Math.floor(Math.random() * names.length)] as string;
|
||||
}
|
||||
|
||||
function getExampleAvatarPath(): string {
|
||||
const paths = [ // these are relative to the working directory
|
||||
path.join(__dirname, '../img/default-avatars/avatar-airstrip.png'),
|
||||
path.join(__dirname, '../img/default-avatars/avatar-building.png'),
|
||||
path.join(__dirname, '../img/default-avatars/avatar-frog.png'),
|
||||
path.join(__dirname, '../img/default-avatars/avatar-sun.png')
|
||||
];
|
||||
return paths[Math.floor(Math.random() * paths.length)] as string;
|
||||
}
|
||||
|
||||
export default function createAddGuildOverlay(document: Document, q: Q, ui: UI, guildsManager: GuildsManager, addGuildData: IAddGuildData): HTMLElement {
|
||||
const expired = addGuildData.expires < new Date().getTime();
|
||||
|
||||
let displayName = getExampleDisplayName();
|
||||
const avatarPath = getExampleAvatarPath();
|
||||
|
||||
//LOG.debug('addguilddata:', { addGuildData });
|
||||
|
||||
|
||||
const element = BaseElements.createOverlay(document, (
|
||||
<div className="content add-guild">
|
||||
<div className="preview">
|
||||
<img className="icon" src={addGuildData.iconSrc} alt="icon"></img>
|
||||
<div>
|
||||
<div className="name">{addGuildData.name}</div>
|
||||
<div className="url">{addGuildData.url}</div>
|
||||
<div className="expires">{(expired ? 'Invite Expired ' : 'Invite Expires ') + moment(addGuildData.expires).fromNow()}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="divider"></div>
|
||||
<div className="message-preview message">
|
||||
<div className="member-avatar"><img src="./img/loading.svg" alt="avatar"></img></div>
|
||||
<div className="right">
|
||||
<div className="header">
|
||||
<div className="member-name">{displayName}</div>
|
||||
<div className="timestamp">{moment().calendar(ElementsUtil.calendarFormats)}</div>
|
||||
</div>
|
||||
<div className="content text">What's up, gamers?</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="display-name-input" placeholder="Display Name" spellCheck="false"
|
||||
contentEditable={'plaintext-only' as unknown as boolean /* React doesn't have plaintext-only in its types (https://github.com/DefinitelyTyped/DefinitelyTyped/pull/54779) */}></div>
|
||||
<div className="avatar-input">
|
||||
<label className="avatar-upload-label button">
|
||||
Select Avatar
|
||||
<input className="avatar-upload" type="file" accept=".png,.jpg.,.jpeg" style={{ display: 'none' }}></input>
|
||||
</label>
|
||||
</div>
|
||||
<div className="lower">
|
||||
<div className="error"></div>
|
||||
<div className="buttons">
|
||||
<div className="button submit">Add Guild</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
|
||||
let avatarBuff: Buffer | null;
|
||||
let defaultAvatarBuff: Buffer | null;
|
||||
(async () => {
|
||||
try {
|
||||
defaultAvatarBuff = await fs.readFile(avatarPath); // TODO: on error
|
||||
const src = await ElementsUtil.getImageBufferSrc(defaultAvatarBuff);
|
||||
(q.$$$(element, '.member-avatar img') as HTMLImageElement).src = src;
|
||||
avatarBuff = defaultAvatarBuff;
|
||||
} catch (e) {
|
||||
LOG.error('error setting default avatar', e);
|
||||
(q.$$$(element, '.member-avatar img') as HTMLImageElement).src = './img/error.png';
|
||||
defaultAvatarBuff = null;
|
||||
avatarBuff = null;
|
||||
}
|
||||
})();
|
||||
|
||||
q.$$$(element, '.avatar-upload').addEventListener('change', async () => {
|
||||
q.$$$(element, '.error').innerText = '';
|
||||
const files = (q.$$$(element, '.avatar-upload') as HTMLInputElement).files;
|
||||
if (!files || files.length == 0) {
|
||||
avatarBuff = null;
|
||||
return;
|
||||
}
|
||||
const file = files[0] as File;
|
||||
|
||||
if (file.size > Globals.MAX_AVATAR_SIZE) {
|
||||
q.$$$(element, '.error').innerText = 'Image too large. Max size: ' + ElementsUtil.humanSize(Globals.MAX_AVATAR_SIZE);
|
||||
await ElementsUtil.shakeElement(q.$$$(element, '.avatar-upload-label'), 400);
|
||||
(q.$$$(element, '.avatar-upload') as HTMLInputElement).value = '';
|
||||
avatarBuff = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const buff = Buffer.from(await file.arrayBuffer());
|
||||
|
||||
const typeResult = await FileType.fromBuffer(buff);
|
||||
if (!typeResult || !['image/png', 'image/jpeg', 'image/jpg'].includes(typeResult.mime)) {
|
||||
q.$$$(element, '.error').innerText = 'Invalid avatar image (png/jpg only)';
|
||||
await ElementsUtil.shakeElement(q.$$$(element, '.avatar-upload-label'), 400);
|
||||
avatarBuff = null;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const src = await ElementsUtil.getImageBufferSrc(buff);
|
||||
(q.$$$(element, '.member-avatar img') as HTMLImageElement).src = src;
|
||||
avatarBuff = buff;
|
||||
} catch (e) {
|
||||
LOG.warn('unable to create image src from buffer', e);
|
||||
q.$$$(element, '.error').innerText = 'Invalid avatar image';
|
||||
await ElementsUtil.shakeElement(q.$$$(element, '.avatar-upload-label'), 400);
|
||||
(q.$$$(element, '.avatar-upload') as HTMLInputElement).value = '';
|
||||
avatarBuff = null;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
q.$$$(element, '.display-name-input').addEventListener('keydown', (e) => {
|
||||
if (e.key == 'Enter') {
|
||||
e.preventDefault();
|
||||
if (e.shiftKey) return; // since the shift key makes newlines other places
|
||||
q.$$$(element, '.button.submit').click();
|
||||
}
|
||||
});
|
||||
|
||||
q.$$$(element, '.display-name-input').addEventListener('input', () => {
|
||||
q.$$$(element, '.member-name').innerText = q.$$$(element, '.display-name-input').innerText;
|
||||
});
|
||||
|
||||
let submitting = false;
|
||||
q.$$$(element, '.button.submit').addEventListener('click', async () => {
|
||||
if (submitting) return;
|
||||
submitting = true;
|
||||
|
||||
displayName = q.$$$(element, '.display-name-input').innerText;
|
||||
|
||||
q.$$$(element, '.display-name-input').removeAttribute('contenteditable');
|
||||
|
||||
let newGuild: CombinedGuild | null = null;
|
||||
if (addGuildData == null) {
|
||||
q.$$$(element, '.error').innerText = 'Very bad guild file';
|
||||
q.$$$(element, '.submit').innerText = 'Try Again';
|
||||
await ElementsUtil.shakeElement(q.$$$(element, '.submit'), 400);
|
||||
} else if (addGuildData.expires < new Date().getTime()) {
|
||||
q.$$$(element, '.error').innerText = 'Token expired';
|
||||
q.$$$(element, '.submit').innerText = 'Try Again';
|
||||
await ElementsUtil.shakeElement(q.$$$(element, '.submit'), 400);
|
||||
} else if (displayName.length > Globals.MAX_DISPLAY_NAME_LENGTH) {
|
||||
q.$$$(element, '.error').innerText = 'Display name too long: ' + displayName.length + ' > ' + Globals.MAX_DISPLAY_NAME_LENGTH;
|
||||
q.$$$(element, '.submit').innerText = 'Try Again';
|
||||
await ElementsUtil.shakeElement(q.$$$(element, '.submit'), 400);
|
||||
} else if (displayName.length == 0) {
|
||||
q.$$$(element, '.error').innerText = 'Display name is empty';
|
||||
q.$$$(element, '.submit').innerText = 'Try Again';
|
||||
await ElementsUtil.shakeElement(q.$$$(element, '.submit'), 400);
|
||||
} else if (avatarBuff === null) {
|
||||
q.$$$(element, '.error').innerText = 'Unable to parse avatar';
|
||||
q.$$$(element, '.submit').innerText = 'Try Again';
|
||||
await ElementsUtil.shakeElement(q.$$$(element, '.submit'), 400);
|
||||
} else { // NOTE: Avatar size is checked above
|
||||
q.$$$(element, '.submit').innerText = 'Registering...';
|
||||
try {
|
||||
newGuild = await guildsManager.addNewGuild(addGuildData, displayName, avatarBuff);
|
||||
} catch (e: unknown) {
|
||||
LOG.warn('error adding new guild: ' + (e as Error).message, e); // explicitly not printing stack trace here
|
||||
q.$$$(element, '.error').innerText = (e as Error).message;
|
||||
q.$$$(element, '.submit').innerText = 'Try Again';
|
||||
await ElementsUtil.shakeElement(q.$$$(element, '.submit'), 400);
|
||||
newGuild = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (newGuild !== null) {
|
||||
const guildElement = await ui.addGuild(guildsManager, newGuild);
|
||||
element.removeSelf(); // close the overlay since we got a new guild
|
||||
guildElement.click(); // click on the new guild
|
||||
}
|
||||
|
||||
q.$$$(element, '.display-name-input').setAttribute('contenteditable', 'plaintext-only');
|
||||
q.$$$(element, '.display-name-input').focus();
|
||||
|
||||
submitting = false;
|
||||
});
|
||||
|
||||
return element;
|
||||
}
|
@ -5,7 +5,6 @@ const LOG = Logger.create(__filename, electronConsole);
|
||||
|
||||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import GuildsManager from '../../guilds-manager';
|
||||
import { IAddGuildData } from '../overlay-add-guild';
|
||||
import moment from 'moment';
|
||||
import TextInput from '../components/input-text';
|
||||
import ImageEditInput from '../components/input-image-edit';
|
||||
@ -14,6 +13,18 @@ import SubmitOverlayLower from '../components/submit-overlay-lower';
|
||||
import path from 'path';
|
||||
import fs from 'fs/promises';
|
||||
import Util from '../../util';
|
||||
import UI from '../../ui';
|
||||
import CombinedGuild from '../../guild-combined';
|
||||
import ElementsUtil from '../require/elements-util';
|
||||
|
||||
export interface IAddGuildData {
|
||||
name: string,
|
||||
url: string,
|
||||
cert: string,
|
||||
token: string,
|
||||
expires: number,
|
||||
iconSrc: string
|
||||
}
|
||||
|
||||
function getExampleDisplayName(): string {
|
||||
const names = [
|
||||
@ -38,12 +49,14 @@ function getExampleAvatarPath(): string {
|
||||
}
|
||||
|
||||
export interface AddGuildOverlayProps {
|
||||
document: Document;
|
||||
ui: UI;
|
||||
guildsManager: GuildsManager;
|
||||
addGuildData: IAddGuildData;
|
||||
}
|
||||
|
||||
const AddGuildOverlay: FC<AddGuildOverlayProps> = (props: AddGuildOverlayProps) => {
|
||||
const { guildsManager, addGuildData } = props;
|
||||
const { document, ui, guildsManager, addGuildData } = props;
|
||||
|
||||
const expired = addGuildData.expires < new Date().getTime();
|
||||
const exampleDisplayName = useMemo(() => getExampleDisplayName(), []);
|
||||
@ -97,10 +110,11 @@ const AddGuildOverlay: FC<AddGuildOverlayProps> = (props: AddGuildOverlayProps)
|
||||
setAddGuildFailedMessage('token expired');
|
||||
return false;
|
||||
}
|
||||
|
||||
let newGuild: CombinedGuild;
|
||||
try {
|
||||
await guildsManager.addNewGuild(addGuildData, displayName, avatarBuff);
|
||||
newGuild = await guildsManager.addNewGuild(addGuildData, displayName, avatarBuff);
|
||||
setAddGuildFailedMessage(null);
|
||||
return true;
|
||||
} catch (e: unknown) {
|
||||
LOG.error('error adding new guild', e);
|
||||
if (e instanceof Error) {
|
||||
@ -110,6 +124,12 @@ const AddGuildOverlay: FC<AddGuildOverlayProps> = (props: AddGuildOverlayProps)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const guildElement = await ui.addGuild(guildsManager, newGuild);
|
||||
ElementsUtil.closeReactOverlay(document);
|
||||
guildElement.click();
|
||||
|
||||
return true;
|
||||
|
||||
}, [ displayName, avatarBuff, displayNameInputValid, avatarInputValid ]);
|
||||
|
||||
|
@ -339,10 +339,14 @@ export default class ElementsUtil {
|
||||
}
|
||||
|
||||
static presentReactOverlay(document: Document, content: JSX.Element) {
|
||||
const overlay = <Overlay>{content}</Overlay>;
|
||||
const overlay = <Overlay document={document}>{content}</Overlay>;
|
||||
ReactDOM.render(overlay, document.querySelector('#react-overlays'));
|
||||
}
|
||||
|
||||
static closeReactOverlay(document: Document) {
|
||||
ReactDOM.unmountComponentAtNode(document.querySelector('#react-overlays') as HTMLElement);
|
||||
}
|
||||
|
||||
static bindHoverableContextElement(
|
||||
hoverElement: HTMLElement,
|
||||
contextElement: IHTMLElementWithRemovalType,
|
||||
|
@ -11,7 +11,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 { IAddGuildData } from './elements/overlay-add-guild';
|
||||
import { IAddGuildData } from './elements/overlays/overlay-add-guild';
|
||||
import { EventEmitter } from 'tsee';
|
||||
import CombinedGuild from './guild-combined';
|
||||
import PersonalDB from './personal-db';
|
||||
|
Loading…
Reference in New Issue
Block a user