restructuring

This commit is contained in:
LenAnderson
2023-12-20 15:49:27 +00:00
parent 34decf1c05
commit 2648b3c801
9 changed files with 338 additions and 184 deletions

View File

@ -1,19 +1,98 @@
import { QuickReplyLink } from './QuickReplyLink.js';
import { getSortableDelay } from '../../../utils.js';
import { QuickReplySetLink } from './QuickReplySetLink.js';
import { QuickReplySet } from './QuickReplySet.js';
export class QuickReplyConfig {
/**@type {QuickReplyLink[]}*/ setList = [];
/**@type {QuickReplySetLink[]}*/ setList = [];
/**@type {Boolean}*/ isGlobal;
/**@type {Function}*/ onUpdate;
/**@type {Function}*/ onRequestEditSet;
/**@type {HTMLElement}*/ dom;
/**@type {HTMLElement}*/ setListDom;
static from(props) {
props.setList = props.setList?.map(it=>QuickReplyLink.from(it)) ?? [];
return Object.assign(new this(), props);
props.setList = props.setList?.map(it=>QuickReplySetLink.from(it)) ?? [];
const instance = Object.assign(new this(), props);
instance.init();
return instance;
}
init() {
this.setList.forEach(it=>this.hookQuickReplyLink(it));
}
renderSettingsInto(/**@type {HTMLElement}*/root) {
/**@type {HTMLElement}*/
const setList = root.querySelector('.qr--setList');
this.setListDom = setList;
setList.innerHTML = '';
root.querySelector('.qr--setListAdd').addEventListener('click', ()=>{
const qrl = new QuickReplySetLink();
qrl.set = QuickReplySet.list[0];
this.hookQuickReplyLink(qrl);
this.setList.push(qrl);
setList.append(qrl.renderSettings(this.setList.length - 1));
this.update();
});
// @ts-ignore
$(setList).sortable({
delay: getSortableDelay(),
stop: ()=>this.onSetListSort(),
});
this.setList.forEach((qrl,idx)=>setList.append(qrl.renderSettings(idx)));
}
onSetListSort() {
this.setList = Array.from(this.setListDom.children).map((it,idx)=>{
const qrl = this.setList[Number(it.getAttribute('data-order'))];
qrl.index = idx;
it.setAttribute('data-order', String(idx));
return qrl;
});
this.update();
}
/**
* @param {QuickReplySetLink} qrl
*/
hookQuickReplyLink(qrl) {
qrl.onDelete = ()=>this.deleteQuickReplyLink(qrl);
qrl.onUpdate = ()=>this.update();
qrl.onRequestEditSet = ()=>this.requestEditSet(qrl.set);
}
deleteQuickReplyLink(qrl) {
this.setList.splice(this.setList.indexOf(qrl), 1);
this.update();
}
update() {
if (this.onUpdate) {
this.onUpdate(this);
}
}
requestEditSet(qrs) {
if (this.onRequestEditSet) {
this.onRequestEditSet(qrs);
}
}
toJSON() {
return {
setList: this.setList,

View File

@ -13,9 +13,6 @@ export class QuickReplyContextLink {
/**@type {QuickReplySet}*/ set;
/**@type {Boolean}*/ isChained = false;
toJSON() {
return {
set: this.set?.name,

View File

@ -1,24 +0,0 @@
import { QuickReplySet } from './QuickReplySet.js';
export class QuickReplyLink {
/**@type {QuickReplySet}*/ set;
/**@type {Boolean}*/ isVisible = true;
static from(props) {
props.set = QuickReplySet.get(props.set);
return Object.assign(new this(), props);
}
toJSON() {
return {
set: this.set.name,
isVisible: this.isVisible,
};
}
}

View File

@ -189,4 +189,20 @@ export class QuickReplySet {
warn(`Failed to save Quick Reply Set: ${this.name}`);
}
}
async delete() {
const response = await fetch('/deletequickreply', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify(this),
});
if (response.ok) {
this.unrender();
const idx = QuickReplySet.list.indexOf(this);
QuickReplySet.list.splice(idx, 1);
} else {
warn(`Failed to delete Quick Reply Set: ${this.name}`);
}
}
}

View File

@ -0,0 +1,126 @@
import { QuickReplySet } from './QuickReplySet.js';
export class QuickReplySetLink {
static from(props) {
props.set = QuickReplySet.get(props.set);
return Object.assign(new this(), props);
}
/**@type {QuickReplySet}*/ set;
/**@type {Boolean}*/ isVisible = true;
/**@type {Number}*/ index;
/**@type {Function}*/ onUpdate;
/**@type {Function}*/ onRequestEditSet;
/**@type {Function}*/ onDelete;
/**@type {HTMLElement}*/ settingsDom;
renderSettings(idx) {
if (!this.settingsDom || idx != this.index) {
this.index = idx;
const item = document.createElement('div'); {
this.settingsDom = item;
item.classList.add('qr--item');
item.setAttribute('data-order', String(this.index));
const drag = document.createElement('div'); {
drag.classList.add('drag-handle');
drag.classList.add('ui-sortable-handle');
drag.textContent = '☰';
item.append(drag);
}
const set = document.createElement('select'); {
set.classList.add('qr--set');
set.addEventListener('change', ()=>{
this.set = QuickReplySet.get(set.value);
this.update();
});
QuickReplySet.list.forEach(qrs=>{
const opt = document.createElement('option'); {
opt.value = qrs.name;
opt.textContent = qrs.name;
opt.selected = qrs == this.set;
set.append(opt);
}
});
item.append(set);
}
const visible = document.createElement('label'); {
visible.classList.add('qr--visible');
const cb = document.createElement('input'); {
cb.type = 'checkbox';
cb.checked = this.isVisible;
cb.addEventListener('click', ()=>{
this.isVisible = cb.checked;
this.update();
});
visible.append(cb);
}
visible.append('Show buttons');
item.append(visible);
}
const edit = document.createElement('div'); {
edit.classList.add('menu_button');
edit.classList.add('menu_button_icon');
edit.classList.add('fa-solid');
edit.classList.add('fa-pencil');
edit.title = 'Edit quick reply set';
edit.addEventListener('click', ()=>this.requestEditSet());
item.append(edit);
}
const del = document.createElement('div'); {
del.classList.add('qr--del');
del.classList.add('menu_button');
del.classList.add('menu_button_icon');
del.classList.add('fa-solid');
del.classList.add('fa-trash-can');
del.title = 'Remove quick reply set';
del.addEventListener('click', ()=>this.delete());
item.append(del);
}
}
}
return this.settingsDom;
}
unrenderSettings() {
this.settingsDom?.remove();
this.settingsDom = null;
}
update() {
if (this.onUpdate) {
this.onUpdate(this);
}
}
requestEditSet() {
if (this.onRequestEditSet) {
this.onRequestEditSet(this.set);
}
}
delete() {
this.unrenderSettings();
if (this.onDelete) {
this.onDelete();
}
}
toJSON() {
return {
set: this.set.name,
isVisible: this.isVisible,
};
}
}

View File

@ -5,7 +5,9 @@ import { QuickReplyConfig } from './QuickReplyConfig.js';
export class QuickReplySettings {
static from(props) {
props.config = QuickReplyConfig.from(props.config);
return Object.assign(new this(), props);
const instance = Object.assign(new this(), props);
instance.init();
return instance;
}
@ -14,9 +16,41 @@ export class QuickReplySettings {
/**@type {Boolean}*/ isEnabled = false;
/**@type {Boolean}*/ isCombined = false;
/**@type {QuickReplyConfig}*/ config;
/**@type {QuickReplyConfig}*/ chatConfig;
/**@type {QuickReplyConfig}*/ _chatConfig;
get chatConfig() {
return this._chatConfig;
}
set chatConfig(value) {
if (this._chatConfig != value) {
this.unhookConfig(this._chatConfig);
this._chatConfig = value;
this.hookConfig(this._chatConfig);
}
}
/**@type {Function}*/ onSave;
/**@type {Function}*/ onRequestEditSet;
init() {
this.hookConfig(this.config);
this.hookConfig(this.chatConfig);
}
hookConfig(config) {
if (config) {
config.onUpdate = ()=>this.save();
config.onRequestEditSet = (qrs)=>this.requestEditSet(qrs);
}
}
unhookConfig(config) {
if (config) {
config.onUpdate = null;
config.onRequestEditSet = null;
}
}
@ -33,6 +67,12 @@ export class QuickReplySettings {
}
}
requestEditSet(qrs) {
if (this.onRequestEditSet) {
this.onRequestEditSet(qrs);
}
}
toJSON() {
return {
isEnabled: this.isEnabled,

View File

@ -1,7 +1,6 @@
import { callPopup } from '../../../../../script.js';
import { getSortableDelay } from '../../../../utils.js';
import { warn } from '../../index.js';
import { QuickReplyLink } from '../QuickReplyLink.js';
import { QuickReplySet } from '../QuickReplySet.js';
// eslint-disable-next-line no-unused-vars
import { QuickReplySettings } from '../QuickReplySettings.js';
@ -30,6 +29,7 @@ export class SettingsUi {
constructor(/**@type {QuickReplySettings}*/settings) {
this.settings = settings;
settings.onRequestEditSet = (qrs) => this.selectQrSet(qrs);
}
@ -78,135 +78,23 @@ export class SettingsUi {
this.isCombined.addEventListener('click', ()=>this.onIsCombined());
}
/**
* @param {QuickReplyLink} qrl
* @param {Number} idx
*/
renderQrLinkItem(qrl, idx, isGlobal) {
const item = document.createElement('div'); {
item.classList.add('qr--item');
item.setAttribute('data-order', String(idx));
const drag = document.createElement('div'); {
drag.classList.add('drag-handle');
drag.classList.add('ui-sortable-handle');
drag.textContent = '☰';
item.append(drag);
}
const set = document.createElement('select'); {
set.addEventListener('change', ()=>{
qrl.set = QuickReplySet.get(set.value);
this.settings.save();
});
QuickReplySet.list.forEach(qrs=>{
const opt = document.createElement('option'); {
opt.value = qrs.name;
opt.textContent = qrs.name;
opt.selected = qrs == qrl.set;
set.append(opt);
}
});
item.append(set);
}
const visible = document.createElement('label'); {
visible.classList.add('qr--visible');
const cb = document.createElement('input'); {
cb.type = 'checkbox';
cb.checked = qrl.isVisible;
cb.addEventListener('click', ()=>{
qrl.isVisible = cb.checked;
this.settings.save();
});
visible.append(cb);
}
visible.append('Show buttons');
item.append(visible);
}
const edit = document.createElement('div'); {
edit.classList.add('menu_button');
edit.classList.add('menu_button_icon');
edit.classList.add('fa-solid');
edit.classList.add('fa-pencil');
edit.title = 'Edit quick reply set';
edit.addEventListener('click', ()=>{
this.currentSet.value = qrl.set.name;
this.onQrSetChange();
});
item.append(edit);
}
const del = document.createElement('div'); {
del.classList.add('menu_button');
del.classList.add('menu_button_icon');
del.classList.add('fa-solid');
del.classList.add('fa-trash-can');
del.title = 'Remove quick reply set';
del.addEventListener('click', ()=>{
item.remove();
if (isGlobal) {
this.settings.config.setList.splice(this.settings.config.setList.indexOf(qrl), 1);
this.updateOrder(this.globalSetList);
} else {
this.settings.chatConfig.setList.splice(this.settings.chatConfig.setList.indexOf(qrl), 1);
this.updateOrder(this.chatSetList);
}
this.settings.save();
});
item.append(del);
}
}
return item;
}
prepareGlobalSetList() {
// global set list
this.dom.querySelector('#qr--global-setListAdd').addEventListener('click', ()=>{
const qrl = new QuickReplyLink();
qrl.set = QuickReplySet.list[0];
this.settings.config.setList.push(qrl);
this.globalSetList.append(this.renderQrLinkItem(qrl, this.settings.config.setList.length - 1, true));
this.settings.save();
});
this.globalSetList = this.dom.querySelector('#qr--global-setList');
// @ts-ignore
$(this.globalSetList).sortable({
delay: getSortableDelay(),
stop: ()=>this.onGlobalSetListSort(),
});
this.settings.config.setList.forEach((qrl,idx)=>{
this.globalSetList.append(this.renderQrLinkItem(qrl, idx, true));
});
this.settings.config.renderSettingsInto(this.dom.querySelector('#qr--global'));
}
prepareChatSetList() {
// chat set list
this.dom.querySelector('#qr--chat-setListAdd').addEventListener('click', ()=>{
if (!this.settings.chatConfig) {
toastr.warning('No active chat.');
return;
}
const qrl = new QuickReplyLink();
qrl.set = QuickReplySet.list[0];
this.settings.chatConfig.setList.push(qrl);
this.chatSetList.append(this.renderQrLinkItem(qrl, this.settings.chatConfig.setList.length - 1, false));
this.settings.save();
});
this.chatSetList = this.dom.querySelector('#qr--chat-setList');
if (!this.settings.chatConfig) {
const info = document.createElement('small'); {
if (this.settings.chatConfig) {
this.settings.chatConfig.renderSettingsInto(this.dom.querySelector('#qr--chat'));
} else {
const info = document.createElement('div'); {
info.textContent = 'No active chat.';
this.chatSetList.append(info);
}
this.dom.querySelector('#qr--chat').append(info);
}
// @ts-ignore
$(this.chatSetList).sortable({
delay: getSortableDelay(),
stop: ()=>this.onChatSetListSort(),
});
this.settings.chatConfig?.setList?.forEach((qrl,idx)=>{
this.chatSetList.append(this.renderQrLinkItem(qrl, idx, false));
});
}
prepareQrEditor() {
// qr editor
this.dom.querySelector('#qr--set-delete').addEventListener('click', async()=>this.deleteQrSet());
this.dom.querySelector('#qr--set-new').addEventListener('click', async()=>this.addQrSet());
this.dom.querySelector('#qr--set-add').addEventListener('click', async()=>{
this.currentQrSet.addQuickReply();
@ -310,12 +198,31 @@ export class SettingsUi {
this.currentQrSet.save();
}
async deleteQrSet() {
const confirmed = await callPopup(`Are you sure you want to delete the Quick Reply Set "${this.currentQrSet.name}"? This cannot be undone.`, 'confirm');
if (confirmed) {
const idx = QuickReplySet.list.indexOf(this.currentQrSet);
await this.currentQrSet.delete();
this.currentSet.children[idx].remove();
[
...Array.from(this.globalSetList.querySelectorAll('.qr--item > .qr--set')),
...Array.from(this.chatSetList.querySelectorAll('.qr--item > .qr--set')),
]
// @ts-ignore
.filter(it=>it.value == this.currentQrSet.name)
// @ts-ignore
.forEach(it=>it.closest('.qr--item').querySelector('.qr--del').click())
;
this.onQrSetChange();
}
}
async addQrSet() {
const name = await callPopup('Quick Reply Set Name:', 'input');
if (name && name.length > 0) {
const oldQrs = QuickReplySet.get(name);
if (oldQrs) {
const replace = await callPopup(`A Quick Reply Set named "${name} already exists.\nDo you want to overwrite the existing Quick Reply Set?\nThe existing set will be deleted. This cannot be undone.`, 'confirm');
const replace = await callPopup(`A Quick Reply Set named "${name}" already exists.\nDo you want to overwrite the existing Quick Reply Set?\nThe existing set will be deleted. This cannot be undone.`, 'confirm');
if (replace) {
const qrs = new QuickReplySet();
qrs.name = name;
@ -340,4 +247,9 @@ export class SettingsUi {
}
}
}
selectQrSet(qrs) {
this.currentSet.value = qrs.name;
this.onQrSetChange();
}
}