jsx is for professionals

This commit is contained in:
Michael Peters 2021-12-05 23:52:10 -06:00
parent 6979c450e6
commit 6b17f569bb
14 changed files with 284 additions and 240 deletions

View File

@ -19,6 +19,9 @@ 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,
@ -58,45 +61,44 @@ export default function createAddGuildOverlay(document: Document, q: Q, ui: UI,
//LOG.debug('addguilddata:', { addGuildData });
const element = BaseElements.createOverlay(document, {
class: 'content add-guild', content: [
{ class: 'preview', content: [
{ tag: 'img', class: 'icon', src: addGuildData.iconSrc, alt: 'icon' },
{ content: [
{ class: 'name', content: addGuildData.name },
{ class: 'url', content: addGuildData.url },
{ class: 'expires',
content: (expired ? 'Invite Expired ' : 'Invite Expires ') + moment(addGuildData.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 Guild' }
] }
] }
]
});
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;

View File

@ -10,26 +10,30 @@ 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, { class: 'content submit-dialog modify-channel', content: [
{ class: 'preview channel-title', content: [
{ class: 'channel-icon', content: BaseElements.Q_TEXT_CHANNEL_ICON },
{ class: 'channel-name', content: 'channel-name' },
{ class: 'channel-flavor-divider' },
{ class: 'channel-flavor-text', content: '' }
] },
{ class: 'text-input channel-name', 'data-placeholder': 'channel-name', content: '', contenteditable: 'plaintext-only' },
{ class: 'text-input channel-flavor-text', 'data-placeholder': 'Flavor Text (optional)', content: '' || '', contenteditable: 'plaintext-only' },
{ class: 'lower', content: [
{ class: 'error' },
{ class: 'buttons', content: [
{ class: 'button submit', content: '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"></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 = '';

View File

@ -1,24 +0,0 @@
import CombinedGuild from "../guild-combined";
import BaseElements from "./require/base-elements";
export default function createCreateInviteTokenOverlay(document: Document, guild: CombinedGuild): HTMLElement {
const element = BaseElements.createOverlay(document, { class: 'content submit-dialog', content: [
{ class: 'role-select category-select', content: [
{ class: 'label', content: 'Select Starting Roles' },
{ class: 'categories', content: [
{ class: 'category suggestion-1', content: '+Gamer' },
{ class: 'category suggestion-2', content: '+Chodist' },
{ class: 'category suggestion-3', content: '+Gamer' },
{ class: 'category more', content: 'More...' },
] }
] },
{ class: 'lower', content: [
{ class: 'error' },
{ class: 'buttons', content: [
{ class: 'button submit', content: 'Create Invite Token' }
] }
] }
] });
return element;
}

View File

@ -0,0 +1,28 @@
import CombinedGuild from "../guild-combined";
import BaseElements, { HTMLElementWithRemoveSelf } from "./require/base-elements";
import React from "react";
export default function createCreateInviteTokenOverlay(document: Document, guild: CombinedGuild): HTMLElementWithRemoveSelf {
const element = BaseElements.createOverlay(document, (
<div className="content submit-dialog">
<div className="role-select category-select">
<div className="label">Select Starting Roles</div>
<div className="categories">
<div className="category suggestion-1">+Gamer</div>
<div className="category suggestion-2">+Chodist</div>
<div className="category suggestion-3">+Gamer</div>
<div className="category more">More...</div>
</div>
</div>
<div className="lower">
<div className="error"></div>
<div className="buttons">
<div className="button submit">Create Invite Token</div>
</div>
</div>
</div>
));
return element;
}

View File

@ -1,10 +0,0 @@
import BaseElements from './require/base-elements.js';
export default function createErrorMessageOverlay(document: Document, title: string, message: string): HTMLElement {
return BaseElements.createOverlay(document, {
class: 'content error-message', content: [
{ class: 'title', content: title },
{ class: 'message', content: message }
]
});
}

View File

@ -0,0 +1,11 @@
import React from 'react';
import BaseElements, { HTMLElementWithRemoveSelf } from './require/base-elements.js';
export default function createErrorMessageOverlay(document: Document, title: string, message: string): HTMLElementWithRemoveSelf {
return BaseElements.createOverlay(document, (
<div className="content error-message">
<div className="title">{title}</div>
<div className="message">{message}</div>
</div>
));
}

View File

@ -6,64 +6,61 @@ const LOG = Logger.create(__filename, electronConsole);
import Globals from '../globals';
import BaseElements from './require/base-elements';
import BaseElements, { HTMLElementWithRemoveSelf } from './require/base-elements';
import ElementsUtil from './require/elements-util';
import { GuildMetadata } from '../data-types';
import Q from '../q-module';
import CombinedGuild from '../guild-combined';
export default function createGuildSettingsOverlay(document: Document, q: Q, guild: CombinedGuild, guildMeta: GuildMetadata): HTMLElement {
const element = BaseElements.createOverlay(document, {
class: 'content display-swapper guild-settings', content: [
{ class: 'options', content: [
{ class: 'title', content: guildMeta.name }, // TODO: update on change
{ class: 'choosable chosen', content: 'Overview' },
{ class: 'choosable', content: 'Channels' },
{ class: 'choosable', content: 'Roles' },
{ class: 'choosable', content: 'Invites' },
] },
{ class: 'display', content: [
{ class: 'scroll', content: [
{ class: 'metadata', content: [
{ tag: 'label', class: 'image-input-label', content: [
{ class: 'image-input', content: [
{ class: 'icon', content: [
{ tag: 'img', class: 'guild-icon', src: './img/loading.svg', alt: 'icon' },
{ class: 'modify', content: [
{ tag: 'img', src: './img/pencil-icon.png' }
] }
] },
] },
{ tag: 'input', class: 'image-input-upload', type: 'file', accept: '.png,.jpg,.jpeg', style: 'display: none' }
] },
{ class: 'name', content: [
{ class: 'label', content: 'Guild Name' },
{ tag: 'input', type: 'text', class: 'guild-name', 'placeholder': guildMeta.name,
contenteditable: 'plaintext-only', content: guildMeta.name },
] },
] }
] },
{ class: 'popup changes', content: [
{ class: 'content', content: [
{ class: 'tip', content: 'You have unsaved changes' },
{ class: 'actions', content: [
{ class: 'button perdu reset', content: 'Reset' },
{ class: 'button positive save-changes', content: 'Save Changes' }
] }
]}
] },
{ class: 'popup error', content: [
{ class: 'content', content: [
{ class: 'tip' },
{ class: 'actions', content: [
{ class: 'button perdu close', content: 'X' },
] }
] }
] },
] }
]
});
import React from 'react';
export default function createGuildSettingsOverlay(document: Document, q: Q, guild: CombinedGuild, guildMeta: GuildMetadata): HTMLElementWithRemoveSelf {
const element = BaseElements.createOverlay(document, (
<div className="content display-swapper guild-settings">
<div className="options">
<div className="title">{guildMeta.name}</div>
<div className="choosable chosen">Overview</div>
<div className="choosable">Channels</div>
<div className="choosable">Roles</div>
<div className="choosable">Invites</div>
</div>
<div className="display">
<div className="scroll">
<div className="metadata">
<label className="image-input-label">
<div className="icon">
<img className="guild-icon" src="./img/loading.svg" alt="icon"></img>
<div className="modify"><img src="./img/pencil-icon.png" alt="modify"></img></div>
</div>
<input className="image-input-upload" type="file" accept=".png,.jpg,.jpeg" style={{ display: 'none' }}></input>
</label>
<div className="name">
<div className="label">Guild Name</div>
<input className="guild-name" type="text" placeholder={guildMeta.name}></input>
</div>
</div>
</div>
<div className="popup changes">
<div className="content">
<div className="tip">You have unsaved changes</div>
<div className="actions">
<div className="button perdu reset">Reset</div>
<div className="button positive save-changes">Save Changes</div>
</div>
</div>
</div>
<div className="popup error">
<div className="content">
<div className="tip"></div>
<div className="actions">
<div className="button perdu close">X</div>
</div>
</div>
</div>
</div>
</div>
));
let newIconBuff: Buffer | null = null;
let oldName = guildMeta.name;
@ -87,8 +84,10 @@ export default function createGuildSettingsOverlay(document: Document, q: Q, gui
}
function reset() {
q.$$$<HTMLInputElement>(element, '.image-input-upload').value = '';
q.$$$<HTMLInputElement>(element, 'input.guild-name').value = oldName;
(async () => {
LOG.debug('resetting icon');
q.$$$<HTMLImageElement>(element, '.icon img').src = await ElementsUtil.getImageBufferFromResourceFailSoftly(guild, guildMeta.iconResourceId);
})();
@ -114,6 +113,7 @@ export default function createGuildSettingsOverlay(document: Document, q: Q, gui
onCleared: () => {},
onError: async (errMsg) => await updatePopups(errMsg),
onLoaded: async (buff, src) => {
LOG.debug('image loaded');
newIconBuff = buff;
(q.$$$(element, 'img.guild-icon') as HTMLImageElement).src = src;
await updatePopups();

View File

@ -5,24 +5,28 @@ const LOG = Logger.create(__filename, electronConsole);
import * as FileType from 'file-type'
import BaseElements from './require/base-elements';
import BaseElements, { HTMLElementWithRemoveSelf } from './require/base-elements';
import ElementsUtil from './require/elements-util';
import Q from '../q-module';
import createImageContextMenu from './context-menu-img';
import CombinedGuild from '../guild-combined';
export default function createImageOverlay(document: Document, q: Q, guild: CombinedGuild, resourceId: string, resourceName: string): HTMLElement {
const element = BaseElements.createOverlay(document, { class: 'content popup-image', content: [
{ tag: 'img', src: './img/loading.svg', alt: resourceName, title: resourceName },
{ class: 'download', content: [
{ class: 'info', content: [
{ class: 'name', content: resourceName },
{ class: 'size', content: 'Loading Size...' }
] },
{ class: 'button', content: 'Loading...' }
] }
] });
import React from 'react';
export default function createImageOverlay(document: Document, q: Q, guild: CombinedGuild, resourceId: string, resourceName: string): HTMLElementWithRemoveSelf {
const element = BaseElements.createOverlay(document, (
<div className="content popup-image">
<img src="./img/loading.svg" alt={resourceName} title={resourceName}></img>
<div className="download">
<div className="info">
<div className="name">{resourceName}</div>
<div className="size">Loading Size...</div>
</div>
<div className="button">Loading...</div>
</div>
</div>
));
(async () => {
try {

View File

@ -6,30 +6,39 @@ const LOG = Logger.create(__filename, electronConsole);
import { Channel } from '../data-types';
import Globals from '../globals.js';
import BaseElements from './require/base-elements.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';
export default function createModifyChannelOverlay(document: Document, q: Q, guild: CombinedGuild, channel: Channel): HTMLElement {
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, { class: 'content submit-dialog modify-channel', content: [
{ class: 'preview channel-title', content: [
{ class: 'channel-icon', content: BaseElements.Q_TEXT_CHANNEL_ICON },
{ class: 'channel-name', content: channel.name },
{ class: 'channel-flavor-divider' },
{ class: 'channel-flavor-text', content: channel.flavorText || '' }
] },
{ class: 'text-input channel-name', 'data-placeholder': 'channel-name', content: channel.name, contenteditable: 'plaintext-only' },
{ class: 'text-input channel-flavor-text', 'data-placeholder': 'Flavor Text (optional)', content: channel.flavorText || '', contenteditable: 'plaintext-only' },
{ class: 'lower', content: [
{ class: 'error' },
{ class: 'buttons', content: [
{ class: 'button submit', content: 'Save Changes' }
] }
] }
] });
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;

View File

@ -4,7 +4,7 @@ const electronConsole = electronRemote.getGlobal('console') as Console;
import Logger from '../../../logger/logger';
const LOG = Logger.create(__filename, electronConsole);
import BaseElements from './require/base-elements';
import BaseElements, { HTMLElementWithRemoveSelf } from './require/base-elements';
import ElementsUtil from './require/elements-util';
import Globals from '../globals';
@ -14,27 +14,46 @@ import createTextMessage from './msg-txt';
import CombinedGuild from '../guild-combined';
import { ConnectionInfo } from '../data-types';
export default function createPersonalizeOverlay(document: Document, q: Q, guild: CombinedGuild, connection: ConnectionInfo): HTMLElement {
const element = BaseElements.createOverlay(document, {
class: 'content submit-dialog personalize', content: [
createTextMessage(q, guild, { id: 'test-message', member: connection, sent: new Date(), text: 'Example Message' }),
{ class: 'text-input', placeholder: 'New Display Name',
spellcheck: 'false', contenteditable: 'plaintext-only', content: connection.displayName },
{ class: 'image-input avatar-input', content: [
{ tag: 'label', class: 'image-input-label avatar-input-label button', content: [
'Select New Avatar',
{ class: 'image-input-upload 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: 'Save Changes' }
] }
] }
]
});
import React from 'react';
import moment from 'moment';
export default function createPersonalizeOverlay(document: Document, q: Q, guild: CombinedGuild, connection: ConnectionInfo): HTMLElementWithRemoveSelf {
const element = BaseElements.createOverlay(document, (
<div className="content submit-dialog personalize">
<div className="message">
<div className="member-avatar">
<img src="./img/loading.svg" alt={connection.displayName}></img>
</div>
<div className="right">
<div className="header">
<div className="member-name">{connection.displayName}</div>
<div className="timestamp">{moment(new Date()).calendar(ElementsUtil.calendarFormats)}</div>
</div>
<div className="content text">Example Message</div>
</div>
</div>
<div className="text-input display-name-input" data-placeholder="New Display 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="image-input avatar-input">
<label className="image-input-label avatar-input-label button">
Select New Avatar
<input className="image-input-upload 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">Save Changes</div>
</div>
</div>
</div>
));
q.$$$(element, '.text-input.display-name-input').innerText = connection.displayName;
(async () => {
(q.$$$(element, '.member-avatar img') as HTMLImageElement).src =
await ElementsUtil.getImageBufferFromResourceFailSoftly(guild, connection.avatarResourceId);
})();
let newAvatarBuffer: Buffer | null = null;
BaseElements.bindImageUploadEvents(q.$$$(element, '.avatar-upload') as HTMLInputElement, {

View File

@ -13,12 +13,14 @@ import { Member } from '../data-types';
import Q from '../q-module';
import CombinedGuild from '../guild-combined';
import React from 'react';
export default function createTokenLogOverlay(document: Document, q: Q, guild: CombinedGuild): HTMLElement {
const element = BaseElements.createOverlay(document, {
class: 'content token-log', content: [
{ class: 'tokens', content: { tag: 'img', src: './img/loading.svg', alt: 'loading...' } },
]
});
const element = BaseElements.createOverlay(document, (
<div className="content token-log">
<div className="tokens"><img src="./img/loading.svg" alt="Loading..."></img></div>
</div>
));
(async () => {
Util.withPotentialErrorWarnOnCancel(q, {

View File

@ -9,11 +9,16 @@ import Q from '../q-module';
import createUploadOverlayFromDataTransferItem from './overlay-upload-datatransfer';
import CombinedGuild from '../guild-combined';
import React from 'react';
export default function createUploadDropTarget(document: Document, q: Q, guild: CombinedGuild, channel: Channel): HTMLElement {
const element = BaseElements.createOverlay(document, { class: 'content drop-target', content: [
// TODO: icon?
{ class: 'message', content: 'Upload to #' + channel.name }
] });
const element = BaseElements.createOverlay(document, (
<div className="content drop-target">
{/* TODO: icon? */}
<div className="message">Upload to #{channel.name}</div>
</div>
));
element.addEventListener('dragover', (e) => {
e.preventDefault();
e.stopPropagation();

View File

@ -250,12 +250,18 @@ export default class BaseElements {
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static createOverlay(document: Document, content: any): HTMLElementWithRemoveSelf {
static createOverlay(document: Document, content: JSX.Element): HTMLElementWithRemoveSelf {
const q = new Q(document);
let wasDownInternal = false; // because 'click' fires on the overlay element anyway
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const element = q.create({ class: 'overlay', content: content }) as any;
const element: HTMLElementWithRemoveSelf = ReactHelper.createElementFromJSX(<div className="overlay">{content}</div>) as HTMLElementWithRemoveSelf;
element.removeSelf = () => {
if (element.parentElement) {
element.parentElement.removeChild(element);
}
window.removeEventListener('keydown', onKeyEscape);
}
const onKeyEscape = (e: KeyboardEvent) => {
if (e.key == 'Escape') {
element.removeSelf();
@ -269,12 +275,7 @@ export default class BaseElements {
}
element.removeSelf();
});
element.removeSelf = () => {
if (element.parentElement) {
element.parentElement.removeChild(element);
}
window.removeEventListener('keydown', onKeyEscape);
}
q.$$$(element, '.content').addEventListener('click', (e) => {
e.stopPropagation(); // prevent the element from closing if the content is clicked on
});
@ -290,23 +291,25 @@ export default class BaseElements {
const { guild, channel, resourceName, resourceBuffFunc, resourceSizeFunc } = props;
const element = BaseElements.createOverlay(document, { class: 'content upload', content: [
{ class: 'title', content: [
{ tag: 'img', src: './img/loading.svg', alt: resourceName },
{ class: 'right', content: [
{ class: 'name', content: resourceName },
{ class: 'size', content: '? B' }
] }
] },
{ class: 'text-input', placeholder: 'Add a comment (optional)', contenteditable: 'plaintext-only' },
{ class: 'lower', content: [
{ class: 'error' },
{ class: 'buttons', content: [
{ class: 'button upload', content: 'Upload to #' + channel.name }
] }
] }
] });
const element = BaseElements.createOverlay(document, (
<div className="content upload">
<div className="title">
<img src="./img/loading.svg" alt={resourceName}></img>
<div className="right">
<div className="name">{resourceName}</div>
<div className="size">? B</div>
</div>
</div>
<div className="text-input" data-placeholder="Add a comment (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 upload">Upload to #{channel.name}</div>
</div>
</div>
</div>
));
q.$$$(element, '.text-input').addEventListener('keydown', async (e) => {
if (e.key == 'Enter' && !e.shiftKey) {
e.preventDefault();

View File

@ -9,6 +9,7 @@ import Logger from '../../../../logger/logger';
const LOG = Logger.create(__filename, electronConsole);
import * as FileType from 'file-type';
import * as uuid from 'uuid';
import Util from '../../util';
import Globals from '../../globals';
@ -17,11 +18,6 @@ import { ShouldNeverHappenError } from '../../data-types';
import React from 'react';
// TODO: pass-through Globals in init function
// alignment: {
// centerY: 'top'
// left: 'right + 20'
// }
interface IAlignment {
left?: string;
centerX?: string;
@ -37,7 +33,6 @@ interface IHTMLElementWithRemovalType extends HTMLElement {
interface SimpleQElement {
tag: 'span',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
content: (SimpleQElement | string)[],
class: string | null
}
@ -124,11 +119,8 @@ export default class ElementsUtil {
}
// creates <span class="bold"/"italic"/"bold italic"/"underline"> spans to format the text (all in q.js element markup that gets converted to jsx [aka hypergaming])
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static parseMessageText(text: string): JSX.Element {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const obj: SimpleQElement = { tag: 'span', content: [] as (SimpleQElement | string)[], class: null };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const obj: SimpleQElement = { tag: 'span', content: [], class: null };
const stack: SimpleQElement[] = [ obj ];
let idx = 0;
function makeEscape(regex: RegExp, len: number, str: string): { matcher: RegExp, response: ((i: number) => void)} { // function for readability
@ -152,8 +144,7 @@ export default class ElementsUtil {
// TODO: optimise out empty elements
stack.pop();
} else { // italic begins
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const obj: SimpleQElement = { tag: 'span', class: cls, content: [] as any[] }
const obj: SimpleQElement = { tag: 'span', class: cls, content: [] }
top.content.push(obj);
stack.push(obj);
}
@ -195,7 +186,7 @@ export default class ElementsUtil {
return qjsObj
} else {
const content = qjsObj.content.map(qjsElement => createReactElement(qjsElement));
return React.createElement(qjsObj.tag, { className: qjsObj.class }, content);
return React.createElement(qjsObj.tag, { key: uuid.v4(), className: qjsObj.class }, content);
}
}