cordis/client/webapp/elements/overlay-add-server.ts
2021-10-30 12:26:41 -05:00

229 lines
9.7 KiB
TypeScript

import * as electronRemote from '@electron/remote';
const electronConsole = electronRemote.getGlobal('console') as Console;
import Logger from '../../../logger/logger';
const LOG = new Logger(__filename, electronConsole);
import * as fs from 'fs/promises';
import * as path from 'path';
import * as 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 { $, $$, $$$, $$$$ } from './require/q-module';
import IState from './require/elements-state';
import ClientController from '../client-controller';
export interface IAddServerData {
name: string,
url: string,
cert: string,
token: string,
expires: number,
iconSrc: string
}
function getExampleDisplayName(): string {
let names = [
'gamer69',
'exdenoccp',
'TiggerEliminator',
'FreshDingus',
'Wonky Experiment',
'TheLegend27'
];
return names[Math.floor(Math.random() * names.length)];
}
function getExampleAvatarPath(): string {
let paths = [ // these are relative to the working directory
path.join(__dirname, '../img/default-avatars/avatar-airstrip.png'),
path.join(__dirname, '../webapp/img/default-avatars/avatar-building.png'),
path.join(__dirname, '../webapp/img/default-avatars/avatar-frog.png'),
path.join(__dirname, '../webapp/img/default-avatars/avatar-sun.png')
];
return paths[Math.floor(Math.random() * paths.length)];
}
export default function createAddServerOverlay(state: IState, addServerData: IAddServerData): HTMLElement {
const { document, controller, ui } = state;
$.setDocument(document);
let expired = addServerData.expires < new Date().getTime();
let displayName = getExampleDisplayName();
let avatarPath = getExampleAvatarPath();
//LOG.debug('addserverdata:', { addServerData });
let element = BaseElements.createOverlay(document, {
class: 'content add-server', content: [
{ class: 'preview', content: [
{ tag: 'img', class: 'icon', src: addServerData.iconSrc, alt: 'icon' },
{ content: [
{ class: 'name', content: addServerData.name },
{ class: 'url', content: addServerData.url },
{ class: 'expires',
content: (expired ? 'Invite Expired ' : 'Invite Expires ') + moment(addServerData.expires).fromNow() }
] }
] },
{ class: 'divider' },
{ class: 'message-preview message', content: [
{ class: 'member-avatar', content: { tag: 'img', src: './img/loading.svg', alt: 'avatar' } },
{ class: 'right', content: [
{ class: 'header', content: [
{ class: 'member-name', content: displayName },
{ class: 'timestamp', content: moment().calendar(ElementsUtil.calendarFormats) }
] },
{ class: 'content text', content: 'Example Message' }
] }
] },
{ class: 'display-name-input', placeholder: 'Display Name',
spellcheck: 'false', contenteditable: 'plaintext-only', content: displayName },
{ class: 'avatar-input', content: [
{ tag: 'label', class: 'avatar-upload-label button', content: [
'Select Avatar',
{ class: 'avatar-upload', tag: 'input',
type: 'file', accept: '.png,.jpg,.jpeg', style: 'display: none;' },
] }
] },
{ class: 'lower', content: [
{ class: 'error' },
{ class: 'buttons', content: [
{ class: 'button submit', content: 'Add Server' }
] }
] }
]
});
let avatarBuff: Buffer | null;
let defaultAvatarBuff: Buffer | null;
(async () => {
try {
defaultAvatarBuff = await fs.readFile(avatarPath); // TODO: on error
let src = await ElementsUtil.getImageBufferSrc(defaultAvatarBuff);
($$$(element, '.member-avatar img') as HTMLImageElement).src = src;
avatarBuff = defaultAvatarBuff;
} catch (e) {
LOG.error('error setting default avatar', e);
($$$(element, '.member-avatar img') as HTMLImageElement).src = './img/error.png';
defaultAvatarBuff = null;
avatarBuff = null;
}
})();
$$$(element, '.avatar-upload').addEventListener('change', async () => {
$$$(element, '.error').innerText = '';
let files = ($$$(element, '.avatar-upload') as HTMLInputElement).files;
if (!files || files.length == 0) {
avatarBuff = null;
return;
}
let file = files[0];
if (file.size > Globals.MAX_AVATAR_SIZE) {
$$$(element, '.error').innerText = 'Image too large. Max size: ' + ElementsUtil.humanSize(Globals.MAX_AVATAR_SIZE);
await ElementsUtil.shakeElement($$$(element, '.avatar-upload-label'), 400);
($$$(element, '.avatar-upload') as HTMLInputElement).value = '';
avatarBuff = null;
return;
}
let buff = Buffer.from(await file.arrayBuffer());
let typeResult = await FileType.fromBuffer(buff);
if (!typeResult || !['image/png', 'image/jpeg', 'image/jpg'].includes(typeResult.mime)) {
$$$(element, '.error').innerText = 'Invalid avatar image (png/jpg only)';
await ElementsUtil.shakeElement($$$(element, '.avatar-upload-label'), 400);
avatarBuff = null;
return;
}
try {
let src = await ElementsUtil.getImageBufferSrc(buff);
($$$(element, '.member-avatar img') as HTMLImageElement).src = src;
avatarBuff = buff;
} catch (e) {
LOG.warn('unable to create image src from buffer', e);
$$$(element, '.error').innerText = 'Invalid avatar image';
await ElementsUtil.shakeElement($$$(element, '.avatar-upload-label'), 400);
($$$(element, '.avatar-upload') as HTMLInputElement).value = '';
avatarBuff = null;
return;
}
});
$$$(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
$$$(element, '.button.submit').click();
}
});
$$$(element, '.display-name-input').addEventListener('input', () => {
$$$(element, '.member-name').innerText = $$$(element, '.display-name-input').innerText;
});
let submitting = false;
$$$(element, '.button.submit').addEventListener('click', async () => {
if (submitting) return;
submitting = true;
displayName = $$$(element, '.display-name-input').innerText;
$$$(element, '.display-name-input').removeAttribute('contenteditable');
let newServer: ClientController | null = null;
if (addServerData == null) {
$$$(element, '.error').innerText = 'Very bad server file';
$$$(element, '.submit').innerText = 'Try Again';
await ElementsUtil.shakeElement($$$(element, '.submit'), 400);
} else if (addServerData.expires < new Date().getTime()) {
$$$(element, '.error').innerText = 'Token expired';
$$$(element, '.submit').innerText = 'Try Again';
await ElementsUtil.shakeElement($$$(element, '.submit'), 400);
} else if (displayName.length > Globals.MAX_DISPLAY_NAME_LENGTH) {
$$$(element, '.error').innerText = 'Display name too long: ' + displayName.length + ' > ' + Globals.MAX_DISPLAY_NAME_LENGTH;
$$$(element, '.submit').innerText = 'Try Again';
await ElementsUtil.shakeElement($$$(element, '.submit'), 400);
} else if (displayName.length == 0) {
$$$(element, '.error').innerText = 'Display name is empty';
$$$(element, '.submit').innerText = 'Try Again';
await ElementsUtil.shakeElement($$$(element, '.submit'), 400);
} else if (avatarBuff === null) {
$$$(element, '.error').innerText = 'Unable to parse avatar';
$$$(element, '.submit').innerText = 'Try Again';
await ElementsUtil.shakeElement($$$(element, '.submit'), 400);
} else { // NOTE: Avatar size is checked above
$$$(element, '.submit').innerText = 'Registering...';
try {
newServer = await controller.addNewServer(addServerData, displayName, avatarBuff);
} catch (e) {
LOG.warn('error adding new server: ' + e.message); // explicitly not printing stack trace here
$$$(element, '.error').innerText = e.message;
$$$(element, '.submit').innerText = 'Try Again';
await ElementsUtil.shakeElement($$$(element, '.submit'), 400);
newServer = null;
}
}
if (newServer !== null) {
let serverElement = await ui.addServer(newServer);
element.removeSelf(); // close the overlay since we got a new server
serverElement.click(); // click on the new server
}
$$$(element, '.display-name-input').setAttribute('contenteditable', 'plaintext-only');
$$$(element, '.display-name-input').focus();
submitting = false;
});
return element;
}