229 lines
9.7 KiB
TypeScript
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;
|
|
}
|