further through .tsx conversion
This commit is contained in:
parent
01da06b57b
commit
6979c450e6
@ -1,3 +1,5 @@
|
||||
import React from 'react';
|
||||
import ReactHelper from './require/react-helper.js';
|
||||
import ElementsUtil from './require/elements-util.js';
|
||||
import BaseElements from './require/base-elements.js';
|
||||
|
||||
@ -9,20 +11,20 @@ import CombinedGuild from '../guild-combined.js';
|
||||
export default function createConnectionContextMenu(document: Document, q: Q, ui: UI, guild: CombinedGuild) {
|
||||
const statuses = [ 'online', 'away', 'busy', 'invisible' ];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let content: any[] = [
|
||||
{ class: 'item personalize', content: [
|
||||
{ class: 'icon', content: { tag: 'img', src: './img/pencil-icon.png' } },
|
||||
{ content: 'Personalize' }
|
||||
] },
|
||||
{ class: 'item-spacer' }
|
||||
let content: JSX.Element[] = [
|
||||
<div key="personalize" className="item personalize">
|
||||
<div className="icon"><img src="./img/pencil-icon.png"></img></div>
|
||||
<div>Personalize</div>
|
||||
</div>,
|
||||
<div key="spacer-1" className="item-spacer"></div>
|
||||
];
|
||||
content = content.concat(statuses.map(status => {
|
||||
return { class: 'item ' + status, content: [
|
||||
{ class: 'status-circle' },
|
||||
{ class: 'status-text', content: status }
|
||||
] };
|
||||
}));
|
||||
const element = BaseElements.createContextMenu(document, { class: 'member-context', content: content });
|
||||
content = content.concat(statuses.map(status => (
|
||||
<div key={status} className={'item ' + status}>
|
||||
<div className="status-circle"></div>
|
||||
<div className="status-text">{status}</div>
|
||||
</div>
|
||||
)));
|
||||
const element = BaseElements.createContextMenu(document, <div className="member-context">{content}</div>);
|
||||
|
||||
q.$$$(element, '.personalize').addEventListener('click', async () => {
|
||||
element.removeSelf();
|
@ -16,30 +16,37 @@ import createCreateChannelOverlay from './overlay-create-channel';
|
||||
import createTokenLogOverlay from './overlay-token-log';
|
||||
import CombinedGuild from '../guild-combined';
|
||||
|
||||
export default function createGuildTitleContextMenu(document: Document, q: Q, ui: UI, guild: CombinedGuild): HTMLElement {
|
||||
import React from 'react';
|
||||
import ReactHelper from './require/react-helper';
|
||||
|
||||
export default function createGuildTitleContextMenu(document: Document, q: Q, ui: UI, guild: CombinedGuild): Element {
|
||||
if (ui.activeConnection === null) {
|
||||
LOG.warn('no active connection when creating guild title context menu');
|
||||
return q.create({}) as HTMLElement;
|
||||
return ReactHelper.createElementFromJSX(<div></div>);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const menuItems: any[] = [];
|
||||
const menuItems: JSX.Element[] = [];
|
||||
|
||||
if (ui.activeConnection.privileges.includes('modify_profile')) {
|
||||
menuItems.push({ class: 'item guild-settings', content: [
|
||||
{ class: 'icon', content: BaseElements.Q_COG },
|
||||
'Guild Settings'
|
||||
] });
|
||||
menuItems.push(
|
||||
<div key="guild-settings" className="item guild-settings">
|
||||
<div className="icon">{BaseElements.COG}</div>
|
||||
<div>Guild Settings</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (ui.activeConnection.privileges.includes('modify_channels')) {
|
||||
if (ui.activeConnection.privileges.includes('modify_profile')) {
|
||||
menuItems.push({ class: 'item-spacer' });
|
||||
menuItems.push(<div key="spacer-1" className="item-spacer"></div>);
|
||||
}
|
||||
menuItems.push({ class: 'item create-channel', content: [
|
||||
{ class: 'icon', content: BaseElements.Q_CREATE },
|
||||
'Create Channel'
|
||||
] });
|
||||
menuItems.push(
|
||||
<div key="create-channel" className="item create-channel">
|
||||
<div className="icon">{BaseElements.CREATE}</div>
|
||||
<div>Create Channel</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (ui.activeConnection.privileges.includes('modify_members')) {
|
||||
@ -47,21 +54,25 @@ export default function createGuildTitleContextMenu(document: Document, q: Q, ui
|
||||
ui.activeConnection.privileges.includes('modify_profile') ||
|
||||
ui.activeConnection.privileges.includes('modify_channels')
|
||||
) {
|
||||
menuItems.push({ class: 'item-spacer' });
|
||||
menuItems.push(<div key="spacer-2" className="item-spacer"></div>);
|
||||
}
|
||||
menuItems.push({ class: 'item create-invite-token', content: [
|
||||
{ class: 'icon', content: BaseElements.Q_TOKEN },
|
||||
'Create Invite Token'
|
||||
] });
|
||||
menuItems.push({ class: 'item token-log', content: [
|
||||
{ class: 'icon', content: BaseElements.Q_TOKEN },
|
||||
'Token Log'
|
||||
] });
|
||||
menuItems.push(
|
||||
<div key="create-invite-token" className="item create-invite-token">
|
||||
<div className="icon">{BaseElements.TOKEN}</div>
|
||||
<div>Create Invite Token</div>
|
||||
</div>
|
||||
);
|
||||
menuItems.push(
|
||||
<div key="token-log" className="item token-log">
|
||||
<div className="icon">{BaseElements.TOKEN}</div>
|
||||
<div>Token Log</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const element = BaseElements.createContextMenu(document, {
|
||||
class: 'guild-title-context', content: menuItems
|
||||
});
|
||||
const element = BaseElements.createContextMenu(document, (
|
||||
<div className="guild-title-context">{menuItems}</div>
|
||||
));
|
||||
|
||||
if (ui.activeConnection.privileges.includes('modify_profile')) {
|
||||
q.$$$(element, '.item.guild-settings').addEventListener('click', async () => {
|
@ -10,12 +10,14 @@ import UI from '../ui';
|
||||
import GuildsManager from '../guilds-manager';
|
||||
import CombinedGuild from '../guild-combined';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export default function createGuildContextMenu(document: Document, q: Q, ui: UI, guildsManager: GuildsManager, guild: CombinedGuild) {
|
||||
const element = BaseElements.createContextMenu(document, {
|
||||
class: 'guild-context', content: [
|
||||
{ class: 'item red leave-guild', content: 'Leave Guild' }
|
||||
]
|
||||
});
|
||||
const element = BaseElements.createContextMenu(document, (
|
||||
<div className="guild-context">
|
||||
<div className="item red leave-guild">Leave Guild</div>
|
||||
</div>
|
||||
));
|
||||
|
||||
q.$$$(element, '.leave-guild').addEventListener('click', async () => {
|
||||
element.removeSelf();
|
@ -10,6 +10,8 @@ import ElementsUtil from './require/elements-util';
|
||||
import Q from '../q-module';
|
||||
import CombinedGuild from '../guild-combined';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export default function createImageContextMenu(
|
||||
document: Document,
|
||||
q: Q,
|
||||
@ -21,10 +23,12 @@ export default function createImageContextMenu(
|
||||
isPreview: boolean
|
||||
): HTMLElement {
|
||||
// TODO: try/catch around sharp?
|
||||
const contextMenu = BaseElements.createContextMenu(document, { class: 'image', content: [
|
||||
{ class: 'item copy-image', content: 'Copy Image' + (isPreview ? ' Preview' : '') },
|
||||
{ class: 'item save-image', content: 'Save Image' + (isPreview ? ' Preview' : '') },
|
||||
] });
|
||||
const contextMenu = BaseElements.createContextMenu(document, (
|
||||
<div className="image">
|
||||
<div className="item copy-image">Copy Image{isPreview ? ' Preview' : ''}</div>
|
||||
<div className="item save-image">Save Image{isPreview ? ' Preview' : ''}</div>
|
||||
</div>
|
||||
));
|
||||
q.$$$(contextMenu, '.copy-image').addEventListener('click', async () => {
|
||||
q.$$$(contextMenu, '.copy-image').innerText = 'Copying...';
|
||||
let nativeImage: electron.NativeImage;
|
@ -5,6 +5,8 @@ import Q from '../q-module';
|
||||
const LOG = Logger.create(__filename, electronConsole);
|
||||
|
||||
import ElementsUtil from './require/elements-util';
|
||||
import React from 'react';
|
||||
import ReactHelper from './require/react-helper';
|
||||
|
||||
export interface CreateErrorIndicatorProps {
|
||||
container: HTMLElement;
|
||||
@ -16,19 +18,19 @@ export interface CreateErrorIndicatorProps {
|
||||
}
|
||||
|
||||
// resolveFunc and rejectFunc should be the resolve/reject functions from the withPotentialError promise
|
||||
export default function createErrorIndicator(q: Q, props: CreateErrorIndicatorProps): HTMLElement {
|
||||
export default function createErrorIndicator(q: Q, props: CreateErrorIndicatorProps): Element {
|
||||
props.classes = props.classes ?? [];
|
||||
const { container, classes, message, taskFunc, resolveFunc, rejectFunc } = props;
|
||||
|
||||
const element = q.create({
|
||||
class: [ 'error-indicator', ...classes ], content: [
|
||||
{ tag: 'img', src: './img/error.png', alt: 'error' },
|
||||
{ content: [
|
||||
{ content: message },
|
||||
{ class: 'retry-button', content: 'Try Again' }
|
||||
] }
|
||||
]
|
||||
}) as HTMLElement;
|
||||
const element = ReactHelper.createElementFromJSX(
|
||||
<div className={['error-indicator', ...classes].join(' ')}>
|
||||
<img src="./img/error.png" alt="error"></img>
|
||||
<div>
|
||||
<div>{message}</div>
|
||||
<div className="retry-button">Try Again</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
if (element.parentElement == null) {
|
@ -13,7 +13,7 @@ export default function bindAddGuildTitleEvents(document: Document, q: Q, ui: UI
|
||||
!ui.activeConnection.privileges.includes('modify_members')
|
||||
) return;
|
||||
|
||||
const contextMenu = createGuildTitleContextMenu(document, q, ui, ui.activeGuild);
|
||||
const contextMenu = createGuildTitleContextMenu(document, q, ui, ui.activeGuild) as HTMLElement;
|
||||
document.body.appendChild(contextMenu);
|
||||
ElementsUtil.alignContextElement(contextMenu, q.$('#guild-name-container'), { top: 'bottom', centerX: 'centerX' });
|
||||
});
|
||||
|
@ -14,11 +14,16 @@ import createGuildContextMenu from './context-menu-guild';
|
||||
import GuildsManager from '../guilds-manager';
|
||||
import CombinedGuild from '../guild-combined';
|
||||
|
||||
export default function createGuildListGuild(document: Document, q: Q, ui: UI, guildsManager: GuildsManager, guild: CombinedGuild) {
|
||||
const element = q.create({ class: 'guild', 'data-id': guild.id, 'meta-name': guild.id, content: [
|
||||
{ class: 'pill' },
|
||||
{ tag: 'img', src: './img/loading.svg', alt: 'guild' }, // src is set later by script.js
|
||||
] }) as HTMLElement;
|
||||
import React from 'react';
|
||||
import ReactHelper from './require/react-helper';
|
||||
|
||||
export default function createGuildListGuild(document: Document, q: Q, ui: UI, guildsManager: GuildsManager, guild: CombinedGuild): Element {
|
||||
const element = ReactHelper.createElementFromJSX(
|
||||
<div className="guild" data-id={guild.id}>
|
||||
<div className="pill"></div>
|
||||
<img src="./img/loading.svg" alt="guild"></img>
|
||||
</div>
|
||||
) as HTMLElement;
|
||||
|
||||
// Hover over for name + connection info
|
||||
(async () => {
|
@ -8,8 +8,9 @@ import createResourceMessageContinued from './msg-res-cont';
|
||||
import createTextMessage from './msg-txt';
|
||||
import createTextMessageContinued from './msg-txt-cont';
|
||||
|
||||
export default function createMessage(document: Document, q: Q, guild: CombinedGuild, message: Message, lastMessage: Message | null): HTMLElement {
|
||||
let element: HTMLElement;
|
||||
// TODO: This is probably best as a react class
|
||||
export default function createMessage(document: Document, q: Q, guild: CombinedGuild, message: Message, lastMessage: Message | null): Element {
|
||||
let element: Element;
|
||||
if (message.hasResource()) {
|
||||
if (message.isImageResource()) {
|
||||
if (message.isContinued(lastMessage)) {
|
||||
|
@ -14,19 +14,26 @@ import createImageOverlay from './overlay-image';
|
||||
import createImageContextMenu from './context-menu-img';
|
||||
import CombinedGuild from '../guild-combined';
|
||||
|
||||
export default function createImageResourceMessageContinued(document: Document, q: Q, guild: CombinedGuild, message: Message): HTMLElement {
|
||||
import React from 'react';
|
||||
import ReactHelper from './require/react-helper';
|
||||
|
||||
export default function createImageResourceMessageContinued(document: Document, q: Q, guild: CombinedGuild, message: Message): Element {
|
||||
if (!message.resourceId || !message.resourcePreviewId || !message.resourceName) {
|
||||
throw new ShouldNeverHappenError('Message is not a resource message');
|
||||
}
|
||||
|
||||
const element = q.create({ class: 'message continued', 'data-id': message.id, 'meta-member-id': message.member.id, 'data-guild-id': guild.id, content: [
|
||||
{ class: 'timestamp', content: moment(message.sent).format('HH:mm') },
|
||||
{ class: 'right', content: [
|
||||
{ class: 'content image', style: `width: ${message.previewWidth}px; height: ${message.previewHeight}px;`, content:
|
||||
{ tag: 'img', src: './img/loading.svg', alt: message.resourceName } }, // src will be replaced later
|
||||
{ class: 'content text', content: ElementsUtil.parseMessageText(message.text ?? '') }
|
||||
] }
|
||||
] }) as HTMLElement;
|
||||
const element = ReactHelper.createElementFromJSX(
|
||||
<div className="message continued" data-id={message.id} data-member-id={message.member.id} data-guild-id={guild.id}>
|
||||
<div className="timestamp">{moment(message.sent).format('HH:mm')}</div>
|
||||
<div className="right">
|
||||
<div className="content image" style={{width: message.previewWidth + 'px', height: message.previewHeight + 'px'}}>
|
||||
<img src="./img/loading.svg" alt={message.resourceName}></img>
|
||||
</div>
|
||||
<div className="content text">{ElementsUtil.parseMessageText(message.text ?? '')}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
q.$$$(element, '.content.image').addEventListener('click', () => {
|
||||
document.body.appendChild(createImageOverlay(document, q, guild, message.resourceId as string, message.resourceName as string));
|
||||
});
|
||||
@ -51,5 +58,6 @@ export default function createImageResourceMessageContinued(document: Document,
|
||||
(q.$$$(element, '.content.image img') as HTMLImageElement).src = './img/error.png';
|
||||
}
|
||||
})();
|
||||
|
||||
return element;
|
||||
}
|
@ -14,7 +14,10 @@ import createImageOverlay from './overlay-image';
|
||||
import createImageContextMenu from './context-menu-img';
|
||||
import CombinedGuild from '../guild-combined';
|
||||
|
||||
export default function createImageResourceMessage(document: Document, q: Q, guild: CombinedGuild, message: Message) {
|
||||
import React from 'react';
|
||||
import ReactHelper from './require/react-helper';
|
||||
|
||||
export default function createImageResourceMessage(document: Document, q: Q, guild: CombinedGuild, message: Message): Element {
|
||||
if (!message.resourceId || !message.resourcePreviewId || !message.resourceName) {
|
||||
throw new ShouldNeverHappenError('Message is not a resource message');
|
||||
}
|
||||
@ -38,19 +41,25 @@ export default function createImageResourceMessage(document: Document, q: Q, gui
|
||||
};
|
||||
}
|
||||
|
||||
const nameStyle = memberInfo.roleColor != null ? 'color: ' + memberInfo.roleColor : '';
|
||||
const element = q.create({ class: 'message', 'data-id': message.id, 'meta-member-id': message.member.id, 'data-guild-id': guild.id, content: [
|
||||
{ class: 'member-avatar', content: { tag: 'img', src: './img/loading.svg', alt: memberInfo.displayName } },
|
||||
{ class: 'right', content: [
|
||||
{ class: 'header', content: [
|
||||
{ class: 'member-name', style: nameStyle, content: memberInfo.displayName },
|
||||
{ class: 'timestamp', content: moment(message.sent).calendar(ElementsUtil.calendarFormats) }
|
||||
] },
|
||||
{ class: 'content image', style: `width: ${message.previewWidth}px; height: ${message.previewHeight}px;`, content:
|
||||
{ tag: 'img', src: './img/loading.svg', alt: message.resourceName } }, // src will be replaced later
|
||||
{ class: 'content text', content: ElementsUtil.parseMessageText(message.text ?? '') }
|
||||
] }
|
||||
] }) as HTMLElement;
|
||||
const nameStyle = memberInfo.roleColor != null ? { color: memberInfo.roleColor } : {};
|
||||
const element = ReactHelper.createElementFromJSX(
|
||||
<div className="message" data-id={message.id} data-member-id={message.member.id} data-guild-id={guild.id}>
|
||||
<div className="member-avatar">
|
||||
<img src="./img/loading.svg" alt={memberInfo.displayName}></img>
|
||||
</div>
|
||||
<div className="right">
|
||||
<div className="header">
|
||||
<div className="member-name" style={nameStyle}>{memberInfo.displayName}</div>
|
||||
<div className="timestamp">{moment(message.sent).calendar(ElementsUtil.calendarFormats)}</div>
|
||||
</div>
|
||||
<div className="content image" style={{width: message.previewWidth + 'px', height: message.previewHeight + 'px'}}>
|
||||
<img src="./img/loading.svg" alt={message.resourceName}></img>
|
||||
</div>
|
||||
<div className="content text">{ElementsUtil.parseMessageText(message.text ?? '')}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
q.$$$(element, '.content.image').addEventListener('click', (e) => {
|
||||
document.body.appendChild(createImageOverlay(document, q, guild, message.resourceId as string, message.resourceName as string));
|
||||
});
|
@ -5,24 +5,30 @@ import Q from "../q-module";
|
||||
|
||||
import ElementsUtil from "./require/elements-util";
|
||||
|
||||
export default function createResourceMessageContinued(q: Q, guild: CombinedGuild, message: Message): HTMLElement {
|
||||
import React from 'react';
|
||||
import ReactHelper from './require/react-helper';
|
||||
|
||||
export default function createResourceMessageContinued(q: Q, guild: CombinedGuild, message: Message): Element {
|
||||
if (!message.resourceId || !message.resourceName) {
|
||||
throw new ShouldNeverHappenError('Message is not a resource message');
|
||||
}
|
||||
|
||||
const element = q.create({ class: 'message continued', 'data-id': message.id, 'meta-member-id': message.member.id, 'data-guild-id': guild.id, content: [
|
||||
{ class: 'timestamp', content: moment(message.sent).format('HH:mm') },
|
||||
{ class: 'right', content: [
|
||||
{ class: 'content resource', content: [
|
||||
{ tag: 'img', class: 'icon', src: './img/file-icon.png' }, // TODO: SVG based on content-type
|
||||
{ class: 'text', content: [
|
||||
{ class: 'filename', content: message.resourceName },
|
||||
{ class: 'download-status', content: 'Click to Download' },
|
||||
] }
|
||||
] },
|
||||
{ class: 'content text', content: ElementsUtil.parseMessageText(message.text ?? '') }
|
||||
] }
|
||||
] }) as HTMLElement;
|
||||
const element = ReactHelper.createElementFromJSX(
|
||||
<div className="message continued" data-id={message.id} data-member-id={message.member.id} data-guild-id={guild.id}>
|
||||
<div className="timestamp">{moment(message.sent).format('HH:mm')}</div>
|
||||
<div className="right">
|
||||
<div className="content resource">
|
||||
<img className="icon" src="./img/file-icon.png" alt="file"></img>
|
||||
<div className="text">
|
||||
<div className="filename">{message.resourceName}</div>
|
||||
<div className="download-status">Click to Download</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="content text">{ElementsUtil.parseMessageText(message.text ?? '')}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
q.$$$(element, '.resource').addEventListener('click', ElementsUtil.createDownloadListener({
|
||||
guild: guild, resourceId: message.resourceId, resourceName: message.resourceName,
|
||||
downloadStartFunc: () => {
|
@ -5,7 +5,10 @@ import Q from '../q-module';
|
||||
|
||||
import ElementsUtil from './require/elements-util';
|
||||
|
||||
export default function createResourceMessage(q: Q, guild: CombinedGuild, message: Message): HTMLElement {
|
||||
import React from 'react';
|
||||
import ReactHelper from './require/react-helper';
|
||||
|
||||
export default function createResourceMessage(q: Q, guild: CombinedGuild, message: Message): Element {
|
||||
if (!message.resourceId || !message.resourceName) {
|
||||
throw new ShouldNeverHappenError('Message is not a resource message');
|
||||
}
|
||||
@ -29,24 +32,29 @@ export default function createResourceMessage(q: Q, guild: CombinedGuild, messag
|
||||
};
|
||||
}
|
||||
|
||||
const nameStyle = memberInfo.roleColor ? 'color: ' + memberInfo.roleColor : '';
|
||||
const element = q.create({ class: 'message', 'data-id': message.id, 'meta-member-id': message.member.id, 'data-guild-id': guild.id, content: [
|
||||
{ class: 'member-avatar', content: { tag: 'img', src: './img/loading.svg', alt: memberInfo.displayName } },
|
||||
{ class: 'right', content: [
|
||||
{ class: 'header', content: [
|
||||
{ class: 'member-name', style: nameStyle, content: memberInfo.displayName },
|
||||
{ class: 'timestamp', content: moment(message.sent).calendar(ElementsUtil.calendarFormats) }
|
||||
] },
|
||||
{ class: 'content resource', content: [
|
||||
{ tag: 'img', class: 'icon', src: './img/file-icon.png' }, // TODO: SVG based on content-type
|
||||
{ class: 'text', content: [
|
||||
{ class: 'filename', content: message.resourceName },
|
||||
{ class: 'download-status', content: 'Click to Download' },
|
||||
] }
|
||||
] },
|
||||
{ class: 'content text', content: ElementsUtil.parseMessageText(message.text ?? '') }
|
||||
] }
|
||||
] }) as HTMLElement;
|
||||
const nameStyle = memberInfo.roleColor != null ? { color: memberInfo.roleColor } : {};
|
||||
const element = ReactHelper.createElementFromJSX(
|
||||
<div className="message" data-id={message.id} data-member-id={message.member.id} data-guild-id={guild.id}>
|
||||
<div className="member-avatar">
|
||||
<img src="./img/loading.svg" alt={memberInfo.displayName}></img>
|
||||
</div>
|
||||
<div className="right">
|
||||
<div className="header">
|
||||
<div className="member-name" style={nameStyle}>{memberInfo.displayName}</div>
|
||||
<div className="timestamp">{moment(message.sent).calendar(ElementsUtil.calendarFormats)}</div>
|
||||
</div>
|
||||
<div className="content resource">
|
||||
<img className="icon" src="./img/file-icon.png" alt="file"></img>
|
||||
<div className="text">
|
||||
<div className="filename">{message.resourceName}</div>
|
||||
<div className="download-status">Click to Download</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="content text">{ElementsUtil.parseMessageText(message.text ?? '')}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
q.$$$(element, '.resource').addEventListener('click', ElementsUtil.createDownloadListener({
|
||||
guild: guild, resourceId: message.resourceId, resourceName: message.resourceName,
|
||||
downloadStartFunc: () => {
|
@ -1,15 +0,0 @@
|
||||
import moment from 'moment';
|
||||
import { Message } from '../data-types';
|
||||
import CombinedGuild from '../guild-combined';
|
||||
import Q from '../q-module.js';
|
||||
|
||||
import ElementsUtil from './require/elements-util.js';
|
||||
|
||||
export default function createTextMessageContinued(q: Q, guild: CombinedGuild, message: Message): HTMLElement {
|
||||
return q.create({ class: 'message continued', 'data-id': message.id, 'meta-member-id': message.member.id, 'data-guild-id': guild.id, content: [
|
||||
{ class: 'timestamp', content: moment(message.sent).format('HH:mm') },
|
||||
{ class: 'right', content: [
|
||||
{ class: 'content text', content: ElementsUtil.parseMessageText(message.text ?? '') }
|
||||
] }
|
||||
] }) as HTMLElement;
|
||||
}
|
20
src/client/webapp/elements/msg-txt-cont.tsx
Normal file
20
src/client/webapp/elements/msg-txt-cont.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import moment from 'moment';
|
||||
import { Message } from '../data-types';
|
||||
import CombinedGuild from '../guild-combined';
|
||||
import Q from '../q-module.js';
|
||||
|
||||
import ElementsUtil from './require/elements-util.js';
|
||||
|
||||
import React from 'react';
|
||||
import ReactHelper from './require/react-helper';
|
||||
|
||||
export default function createTextMessageContinued(q: Q, guild: CombinedGuild, message: Message): Element {
|
||||
return ReactHelper.createElementFromJSX(
|
||||
<div className="message continued" data-id={message.id} data-member-id={message.member.id} data-guild-id={guild.id}>
|
||||
<div className="timestamp">{moment(message.sent).format('HH:mm')}</div>
|
||||
<div className="right">
|
||||
<div className="content text">{ElementsUtil.parseMessageText(message.text ?? '')}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -6,7 +6,10 @@ import { Message, Member, IDummyTextMessage } from '../data-types';
|
||||
import Q from '../q-module';
|
||||
import CombinedGuild from '../guild-combined';
|
||||
|
||||
export default function createTextMessage(q: Q, guild: CombinedGuild, message: Message | IDummyTextMessage): HTMLElement {
|
||||
import React from 'react';
|
||||
import ReactHelper from './require/react-helper';
|
||||
|
||||
export default function createTextMessage(q: Q, guild: CombinedGuild, message: Message | IDummyTextMessage): Element {
|
||||
let memberInfo: {
|
||||
roleColor: string | null,
|
||||
displayName: string,
|
||||
@ -34,17 +37,21 @@ export default function createTextMessage(q: Q, guild: CombinedGuild, message: M
|
||||
};
|
||||
}
|
||||
|
||||
const nameStyle = memberInfo.roleColor ? 'color: ' + memberInfo.roleColor : '';
|
||||
const element = q.create({ class: 'message', 'data-id': message.id, 'meta-member-id': message.member.id, 'data-guild-id': guild.id, content: [
|
||||
{ class: 'member-avatar', content: { tag: 'img', src: './img/loading.svg', alt: memberInfo.displayName } },
|
||||
{ class: 'right', content: [
|
||||
{ class: 'header', content: [
|
||||
{ class: 'member-name', style: nameStyle, content: memberInfo.displayName },
|
||||
{ class: 'timestamp', content: moment(message.sent).calendar(ElementsUtil.calendarFormats) }
|
||||
] },
|
||||
{ class: 'content text', content: ElementsUtil.parseMessageText(message.text || '') }
|
||||
] }
|
||||
] }) as HTMLElement;
|
||||
const nameStyle = memberInfo.roleColor != null ? { color: memberInfo.roleColor } : {};
|
||||
const element = ReactHelper.createElementFromJSX(
|
||||
<div className="message" data-id={message.id} data-member-id={message.member.id} data-guild-id={guild.id}>
|
||||
<div className="member-avatar">
|
||||
<img src="./img/loading.svg" alt={memberInfo.displayName}></img>
|
||||
</div>
|
||||
<div className="right">
|
||||
<div className="header">
|
||||
<div className="member-name" style={nameStyle}>{memberInfo.displayName}</div>
|
||||
<div className="timestamp">{moment(message.sent).calendar(ElementsUtil.calendarFormats)}</div>
|
||||
</div>
|
||||
<div className="content text">{ElementsUtil.parseMessageText(message.text ?? '')}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
(async () => {
|
||||
(q.$$$(element, '.member-avatar img') as HTMLImageElement).src =
|
||||
await ElementsUtil.getImageBufferFromResourceFailSoftly(guild, memberInfo.avatarResourceId);
|
@ -13,8 +13,9 @@ import ElementsUtil from './elements-util';
|
||||
import { Channel } from '../../data-types';
|
||||
import CombinedGuild from '../../guild-combined';
|
||||
import Q from '../../q-module';
|
||||
import ReactHelper from './react-helper';
|
||||
|
||||
interface HTMLElementWithRemoveSelf extends HTMLElement {
|
||||
export interface HTMLElementWithRemoveSelf extends HTMLElement {
|
||||
removeSelf: (() => void);
|
||||
}
|
||||
|
||||
@ -158,12 +159,26 @@ export default class BaseElements {
|
||||
]
|
||||
}
|
||||
|
||||
static TOKEN = (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="currentColor" fillRule="evenodd" clipRule="evenodd"
|
||||
d="M 7.9160156 0 A 8.0000004 8.0000004 0 0 0 1.8710938 2.8574219 A 8.0000004 8.0000004 0 0 0 2.8574219 14.128906 A 8.0000004 8.0000004 0 0 0 14.128906 13.142578 A 8.0000004 8.0000004 0 0 0 13.142578 1.8710938 A 8.0000004 8.0000004 0 0 0 7.9160156 0 z M 5.2324219 3.2851562 A 2.4000002 2.4000002 0 0 1 6.7851562 3.8476562 A 2.4000002 2.4000002 0 0 1 7.6054688 6.1015625 L 13.367188 10.9375 L 12.595703 11.857422 L 10.90625 12.003906 L 10.445312 11.619141 L 10.373047 10.773438 L 9.9121094 10.388672 L 9.0664062 10.462891 L 8.6074219 10.076172 L 8.5332031 9.2304688 L 8.0742188 8.8457031 L 7.2285156 8.9199219 L 6.0625 7.9414062 A 2.4000002 2.4000002 0 0 1 3.6992188 7.5253906 A 2.4000002 2.4000002 0 0 1 3.4042969 4.1425781 A 2.4000002 2.4000002 0 0 1 5.2324219 3.2851562 z"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
static Q_TOKEN = {
|
||||
ns: 'http://www.w3.org/2000/svg', tag: 'svg', width: 16, height: 16, viewBox: '0 0 16 16', content:
|
||||
{ ns: 'http://www.w3.org/2000/svg', tag: 'path', fill: 'currentColor', 'fill-rule': 'evenodd', 'clip-rule': 'evenodd',
|
||||
d: 'M 7.9160156 0 A 8.0000004 8.0000004 0 0 0 1.8710938 2.8574219 A 8.0000004 8.0000004 0 0 0 2.8574219 14.128906 A 8.0000004 8.0000004 0 0 0 14.128906 13.142578 A 8.0000004 8.0000004 0 0 0 13.142578 1.8710938 A 8.0000004 8.0000004 0 0 0 7.9160156 0 z M 5.2324219 3.2851562 A 2.4000002 2.4000002 0 0 1 6.7851562 3.8476562 A 2.4000002 2.4000002 0 0 1 7.6054688 6.1015625 L 13.367188 10.9375 L 12.595703 11.857422 L 10.90625 12.003906 L 10.445312 11.619141 L 10.373047 10.773438 L 9.9121094 10.388672 L 9.0664062 10.462891 L 8.6074219 10.076172 L 8.5332031 9.2304688 L 8.0742188 8.8457031 L 7.2285156 8.9199219 L 6.0625 7.9414062 A 2.4000002 2.4000002 0 0 1 3.6992188 7.5253906 A 2.4000002 2.4000002 0 0 1 3.4042969 4.1425781 A 2.4000002 2.4000002 0 0 1 5.2324219 3.2851562 z' }
|
||||
}
|
||||
|
||||
static CREATE = (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="currentColor" fillRule="evenodd" clipRule="evenodd"
|
||||
d="M 8 0 A 8 8 0 0 0 0 8 A 8 8 0 0 0 8 16 A 8 8 0 0 0 16 8 A 8 8 0 0 0 8 0 z M 7 4 L 9 4 L 9 7 L 12 7 L 12 9 L 9 9 L 9 12 L 7 12 L 7 9 L 4 9 L 4 7 L 7 7 L 7 4 z"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
static Q_CREATE = {
|
||||
ns: 'http://www.w3.org/2000/svg', tag: 'svg', width: 16, height: 16, viewBox: '0 0 16 16', content:
|
||||
{ ns: 'http://www.w3.org/2000/svg', tag: 'path', fill: 'currentColor', 'fill-rule': 'evenodd', 'clip-rule': 'evenodd',
|
||||
@ -210,20 +225,26 @@ export default class BaseElements {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
static createContextMenu(document: Document, content: any): HTMLElementWithRemoveSelf {
|
||||
const q = new Q(document);
|
||||
|
||||
static createContextMenu(document: Document, content: JSX.Element): HTMLElementWithRemoveSelf {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const element = q.create({ class: 'context', content: { class: 'menu', content: content }}) as any;
|
||||
const element = ReactHelper.createElementFromJSX(
|
||||
<div className="context">
|
||||
<div className="menu">{content}</div>
|
||||
</div>
|
||||
) as HTMLElementWithRemoveSelf;
|
||||
|
||||
element.addEventListener('mousedown', (e: Event) => {
|
||||
e.stopPropagation(); // stop the bubble
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
element.removeSelf = () => {
|
||||
if (element.parentElement) {
|
||||
element.parentElement.removeChild(element);
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
document.body.removeEventListener('mousedown', element.removeSelf);
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
document.body.addEventListener('mousedown', element.removeSelf);
|
||||
return element as HTMLElementWithRemoveSelf;
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import * as electron from 'electron';
|
||||
|
||||
import * as electronRemote from '@electron/remote';
|
||||
const electronConsole = electronRemote.getGlobal('console') as Console;
|
||||
|
||||
import Logger from '../../../../logger/logger';
|
||||
const LOG = Logger.create(__filename, electronConsole);
|
||||
|
||||
@ -16,6 +15,8 @@ import Globals from '../../globals';
|
||||
import CombinedGuild from '../../guild-combined';
|
||||
import { ShouldNeverHappenError } from '../../data-types';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
// TODO: pass-through Globals in init function
|
||||
// alignment: {
|
||||
// centerY: 'top'
|
||||
@ -34,6 +35,13 @@ interface IHTMLElementWithRemovalType extends HTMLElement {
|
||||
manualRemoval?: boolean;
|
||||
}
|
||||
|
||||
interface SimpleQElement {
|
||||
tag: 'span',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
content: (SimpleQElement | string)[],
|
||||
class: string | null
|
||||
}
|
||||
|
||||
interface CreateDownloadListenerProps {
|
||||
downloadBuff?: Buffer;
|
||||
guild?: CombinedGuild;
|
||||
@ -115,19 +123,19 @@ export default class ElementsUtil {
|
||||
}
|
||||
}
|
||||
|
||||
// creates <span class="bold"/"italic"/"bold italic"/"underline"> spans to format the text (all in q.js element markup)
|
||||
// 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): any {
|
||||
static parseMessageText(text: string): JSX.Element {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const obj = { tag: 'span', content: [] as any[], class: null };
|
||||
const obj: SimpleQElement = { tag: 'span', content: [] as (SimpleQElement | string)[], class: null };
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const stack: any[] = [ obj ];
|
||||
const stack: SimpleQElement[] = [ obj ];
|
||||
let idx = 0;
|
||||
function makeEscape(regex: RegExp, len: number, str: string): { matcher: RegExp, response: ((i: number) => void)} { // function for readability
|
||||
return {
|
||||
matcher: regex,
|
||||
response: (i: number) => {
|
||||
const top = stack[stack.length - 1];
|
||||
const top = stack[stack.length - 1] as SimpleQElement;
|
||||
if (idx != i) top.content.push(text.substring(idx, i));
|
||||
top.content.push(str);
|
||||
idx = i + len;
|
||||
@ -138,13 +146,14 @@ export default class ElementsUtil {
|
||||
return {
|
||||
matcher: regex,
|
||||
response: (i: number) => {
|
||||
const top = stack[stack.length - 1];
|
||||
const top = stack[stack.length - 1] as SimpleQElement;
|
||||
if (idx != i) top.content.push(text.substring(idx, i));
|
||||
if (top.class == cls) { // italic ends
|
||||
// TODO: optimise out empty elements
|
||||
stack.pop();
|
||||
} else { // italic begins
|
||||
const obj = { tag: 'span', class: cls, content: [] }
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const obj: SimpleQElement = { tag: 'span', class: cls, content: [] as any[] }
|
||||
top.content.push(obj);
|
||||
stack.push(obj);
|
||||
}
|
||||
@ -175,13 +184,23 @@ export default class ElementsUtil {
|
||||
}
|
||||
if (!matched && idx != text.length) {
|
||||
// Add any remaining content
|
||||
const top = stack[stack.length - 1];
|
||||
const top = stack[stack.length - 1] as SimpleQElement;
|
||||
top.content.push(text.substr(idx));
|
||||
idx = text.length;
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
function createReactElement(qjsObj: SimpleQElement | string): JSX.Element | string {
|
||||
if (typeof qjsObj === 'string') {
|
||||
return qjsObj
|
||||
} else {
|
||||
const content = qjsObj.content.map(qjsElement => createReactElement(qjsElement));
|
||||
return React.createElement(qjsObj.tag, { className: qjsObj.class }, content);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return createReactElement(obj) as JSX.Element; // since the first layer will always have a tag
|
||||
}
|
||||
|
||||
// NOTE: both elements must be within the document or this function will not work
|
@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import ReactDOMServer from "react-dom/server";
|
||||
import { ShouldNeverHappenError } from "../../data-types";
|
||||
|
||||
@ -6,7 +5,7 @@ import { ShouldNeverHappenError } from "../../data-types";
|
||||
|
||||
export default class ReactHelper {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
static createElementFromJSX(element: React.ReactElement<any, string | React.JSXElementConstructor<any>>): Element {
|
||||
static createElementFromJSX(element: JSX.Element): Element {
|
||||
// See also: https://www.codegrepper.com/code-examples/javascript/convert+a+string+to+html+element+in+js
|
||||
const htmlString = ReactDOMServer.renderToStaticMarkup(element);
|
||||
const parser = new DOMParser();
|
||||
|
@ -34,7 +34,7 @@ export default class UI {
|
||||
|
||||
public messagePairsGuild: CombinedGuild | null = null;
|
||||
public messagePairsChannel: Channel | { id: string } | null = null;
|
||||
public messagePairs = new Map<string | null, { message: Message, element: HTMLElement }>(); // messageId -> { message: Message, element: HTMLElement }
|
||||
public messagePairs = new Map<string | null, { message: Message, element: Element }>(); // messageId -> { message: Message, element: HTMLElement }
|
||||
|
||||
private document: Document;
|
||||
private q: Q;
|
||||
@ -311,7 +311,7 @@ export default class UI {
|
||||
}
|
||||
}
|
||||
|
||||
public async setChannelsErrorIndicator(guild: CombinedGuild, errorIndicatorElement: HTMLElement): Promise<void> {
|
||||
public async setChannelsErrorIndicator(guild: CombinedGuild, errorIndicatorElement: Element): Promise<void> {
|
||||
await this.lockChannels(guild, () => {
|
||||
Q.clearChildren(this.q.$('#channel-list'));
|
||||
this.q.$('#channel-list').appendChild(errorIndicatorElement);
|
||||
@ -381,7 +381,7 @@ export default class UI {
|
||||
const newStyle = member.roleColor ? 'color: ' + member.roleColor : null;
|
||||
const newName = member.displayName;
|
||||
// the extra query selectors may be overkill
|
||||
for (const messageElement of this.q.$$(`.message[meta-member-id="${member.id}"]`)) {
|
||||
for (const messageElement of this.q.$$(`.message[data-member-id="${member.id}"]`)) {
|
||||
const nameElement = this.q.$$$_(messageElement, '.member-name');
|
||||
if (nameElement) { // continued messages will still show up but need to be skipped
|
||||
if (newStyle) nameElement.setAttribute('style', newStyle);
|
||||
@ -396,19 +396,19 @@ export default class UI {
|
||||
await this.addMembers(guild, members, { clear: true });
|
||||
}
|
||||
|
||||
public async setMembersErrorIndicator(guild: CombinedGuild, errorIndicatorElement: HTMLElement): Promise<void> {
|
||||
public async setMembersErrorIndicator(guild: CombinedGuild, errorIndicatorElement: Element): Promise<void> {
|
||||
await this.lockMembers(guild, () => {
|
||||
Q.clearChildren(this.q.$('#guild-members'));
|
||||
this.q.$('#guild-members').appendChild(errorIndicatorElement);
|
||||
});
|
||||
}
|
||||
|
||||
public getTopMessagePair(): { message: Message, element: HTMLElement } | null {
|
||||
public getTopMessagePair(): { message: Message, element: Element } | null {
|
||||
const element = this.q.$$('#channel-feed .message')[0];
|
||||
return element && this.messagePairs.get(element.getAttribute('data-id')) || null;
|
||||
}
|
||||
|
||||
public getBottomMessagePair(): { message: Message, element: HTMLElement } | null {
|
||||
public getBottomMessagePair(): { message: Message, element: Element } | null {
|
||||
const messageElements = this.q.$$('#channel-feed .message');
|
||||
const element = messageElements[messageElements.length - 1];
|
||||
return element && this.messagePairs.get(element.getAttribute('data-id')) || null;
|
||||
@ -688,19 +688,19 @@ export default class UI {
|
||||
}
|
||||
}
|
||||
|
||||
public async addMessagesErrorIndicatorBefore(guild: CombinedGuild, channel: Channel, errorIndicatorElement: HTMLElement): Promise<void> {
|
||||
public async addMessagesErrorIndicatorBefore(guild: CombinedGuild, channel: Channel, errorIndicatorElement: Element): Promise<void> {
|
||||
await this.lockMessages(guild, channel, () => {
|
||||
this.q.$('#channel-feed').prepend(errorIndicatorElement);
|
||||
});
|
||||
}
|
||||
|
||||
public async addMessagesErrorIndicatorAfter(guild: CombinedGuild, channel: Channel, errorIndicatorElement: HTMLElement): Promise<void> {
|
||||
public async addMessagesErrorIndicatorAfter(guild: CombinedGuild, channel: Channel, errorIndicatorElement: Element): Promise<void> {
|
||||
await this.lockMessages(guild, channel, () => {
|
||||
this.q.$('#channel-feed').appendChild(errorIndicatorElement);
|
||||
});
|
||||
}
|
||||
|
||||
public async setMessagesErrorIndicator(guild: CombinedGuild, channel: Channel | { id: string }, errorIndicatorElement: HTMLElement): Promise<void> {
|
||||
public async setMessagesErrorIndicator(guild: CombinedGuild, channel: Channel | { id: string }, errorIndicatorElement: Element): Promise<void> {
|
||||
await this.lockMessages(guild, channel, () => {
|
||||
Q.clearChildren(this.q.$('#channel-feed'));
|
||||
this.q.$('#channel-feed').appendChild(errorIndicatorElement);
|
||||
|
@ -14,7 +14,7 @@ import createErrorIndicator from './elements/error-indicator';
|
||||
|
||||
interface WithPotentialErrorParams {
|
||||
taskFunc: (() => Promise<void>),
|
||||
errorIndicatorAddFunc: ((element: HTMLElement) => Promise<void>),
|
||||
errorIndicatorAddFunc: ((element: Element) => Promise<void>),
|
||||
errorContainer: HTMLElement,
|
||||
errorClasses?: string[],
|
||||
errorMessage: string,
|
||||
|
Loading…
Reference in New Issue
Block a user