mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Auto-executable QR
This commit is contained in:
@ -1,65 +1,65 @@
|
||||
import { MenuItem } from "./MenuItem.js";
|
||||
|
||||
export class ContextMenu {
|
||||
/**@type {MenuItem[]}*/ itemList = [];
|
||||
/**@type {Boolean}*/ isActive = false;
|
||||
|
||||
/**@type {HTMLElement}*/ root;
|
||||
/**@type {HTMLElement}*/ menu;
|
||||
/**@type {MenuItem[]}*/ itemList = [];
|
||||
/**@type {Boolean}*/ isActive = false;
|
||||
|
||||
/**@type {HTMLElement}*/ root;
|
||||
/**@type {HTMLElement}*/ menu;
|
||||
|
||||
|
||||
|
||||
|
||||
constructor(/**@type {MenuItem[]}*/items) {
|
||||
this.itemList = items;
|
||||
items.forEach(item=>{
|
||||
item.onExpand = ()=>{
|
||||
items.filter(it=>it!=item)
|
||||
.forEach(it=>it.collapse());
|
||||
};
|
||||
});
|
||||
}
|
||||
constructor(/**@type {MenuItem[]}*/items) {
|
||||
this.itemList = items;
|
||||
items.forEach(item => {
|
||||
item.onExpand = () => {
|
||||
items.filter(it => it != item)
|
||||
.forEach(it => it.collapse());
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.root) {
|
||||
const blocker = document.createElement('div'); {
|
||||
this.root = blocker;
|
||||
blocker.classList.add('ctx-blocker');
|
||||
blocker.addEventListener('click', ()=>this.hide());
|
||||
const menu = document.createElement('ul'); {
|
||||
this.menu = menu;
|
||||
menu.classList.add('list-group');
|
||||
menu.classList.add('ctx-menu');
|
||||
this.itemList.forEach(it=>menu.append(it.render()));
|
||||
blocker.append(menu);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.root;
|
||||
}
|
||||
render() {
|
||||
if (!this.root) {
|
||||
const blocker = document.createElement('div'); {
|
||||
this.root = blocker;
|
||||
blocker.classList.add('ctx-blocker');
|
||||
blocker.addEventListener('click', () => this.hide());
|
||||
const menu = document.createElement('ul'); {
|
||||
this.menu = menu;
|
||||
menu.classList.add('list-group');
|
||||
menu.classList.add('ctx-menu');
|
||||
this.itemList.forEach(it => menu.append(it.render()));
|
||||
blocker.append(menu);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.root;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
show({clientX, clientY}) {
|
||||
if (this.isActive) return;
|
||||
this.isActive = true;
|
||||
this.render();
|
||||
this.menu.style.bottom = `${window.innerHeight - clientY}px`;
|
||||
this.menu.style.left = `${clientX}px`;
|
||||
document.body.append(this.root);
|
||||
}
|
||||
hide() {
|
||||
if (this.root) {
|
||||
this.root.remove();
|
||||
}
|
||||
this.isActive = false;
|
||||
}
|
||||
toggle(/**@type {PointerEvent}*/evt) {
|
||||
if (this.isActive) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show(evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
show({ clientX, clientY }) {
|
||||
if (this.isActive) return;
|
||||
this.isActive = true;
|
||||
this.render();
|
||||
this.menu.style.bottom = `${window.innerHeight - clientY}px`;
|
||||
this.menu.style.left = `${clientX}px`;
|
||||
document.body.append(this.root);
|
||||
}
|
||||
hide() {
|
||||
if (this.root) {
|
||||
this.root.remove();
|
||||
}
|
||||
this.isActive = false;
|
||||
}
|
||||
toggle(/**@type {PointerEvent}*/evt) {
|
||||
if (this.isActive) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show(evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,20 @@
|
||||
import { MenuItem } from "./MenuItem.js";
|
||||
import { SubMenu } from "./SubMenu.js";
|
||||
|
||||
export class MenuHeader extends MenuItem {
|
||||
constructor(/**@type {String}*/label) {
|
||||
super(label, null, null);
|
||||
}
|
||||
constructor(/**@type {String}*/label) {
|
||||
super(label, null, null);
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
if (!this.root) {
|
||||
const item = document.createElement('li'); {
|
||||
this.root = item;
|
||||
item.classList.add('list-group-item');
|
||||
item.classList.add('ctx-header');
|
||||
item.append(this.label);
|
||||
}
|
||||
}
|
||||
return this.root;
|
||||
}
|
||||
}
|
||||
render() {
|
||||
if (!this.root) {
|
||||
const item = document.createElement('li'); {
|
||||
this.root = item;
|
||||
item.classList.add('list-group-item');
|
||||
item.classList.add('ctx-header');
|
||||
item.append(this.label);
|
||||
}
|
||||
}
|
||||
return this.root;
|
||||
}
|
||||
}
|
||||
|
@ -1,76 +1,76 @@
|
||||
import { SubMenu } from "./SubMenu.js";
|
||||
|
||||
export class MenuItem {
|
||||
/**@type {String}*/ label;
|
||||
/**@type {Object}*/ value;
|
||||
/**@type {Function}*/ callback;
|
||||
/**@type {MenuItem[]}*/ childList = [];
|
||||
/**@type {SubMenu}*/ subMenu;
|
||||
/**@type {Boolean}*/ isForceExpanded = false;
|
||||
/**@type {String}*/ label;
|
||||
/**@type {Object}*/ value;
|
||||
/**@type {Function}*/ callback;
|
||||
/**@type {MenuItem[]}*/ childList = [];
|
||||
/**@type {SubMenu}*/ subMenu;
|
||||
/**@type {Boolean}*/ isForceExpanded = false;
|
||||
|
||||
/**@type {HTMLElement}*/ root;
|
||||
|
||||
/**@type {Function}*/ onExpand;
|
||||
/**@type {HTMLElement}*/ root;
|
||||
|
||||
/**@type {Function}*/ onExpand;
|
||||
|
||||
|
||||
|
||||
|
||||
constructor(/**@type {String}*/label, /**@type {Object}*/value, /**@type {function}*/callback, /**@type {MenuItem[]}*/children=[]) {
|
||||
this.label = label;
|
||||
this.value = value;
|
||||
this.callback = callback;
|
||||
this.childList = children;
|
||||
}
|
||||
constructor(/**@type {String}*/label, /**@type {Object}*/value, /**@type {function}*/callback, /**@type {MenuItem[]}*/children = []) {
|
||||
this.label = label;
|
||||
this.value = value;
|
||||
this.callback = callback;
|
||||
this.childList = children;
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
if (!this.root) {
|
||||
const item = document.createElement('li'); {
|
||||
this.root = item;
|
||||
item.classList.add('list-group-item');
|
||||
item.classList.add('ctx-item');
|
||||
item.title = this.value;
|
||||
if (this.callback) {
|
||||
item.addEventListener('click', (evt)=>this.callback(evt, this));
|
||||
}
|
||||
item.append(this.label);
|
||||
if (this.childList.length > 0) {
|
||||
item.classList.add('ctx-has-children');
|
||||
const sub = new SubMenu(this.childList);
|
||||
this.subMenu = sub;
|
||||
const trigger = document.createElement('div'); {
|
||||
trigger.classList.add('ctx-expander');
|
||||
trigger.textContent = '⋮';
|
||||
trigger.addEventListener('click', (evt)=>{
|
||||
evt.stopPropagation();
|
||||
this.toggle();
|
||||
});
|
||||
item.append(trigger);
|
||||
}
|
||||
item.addEventListener('mouseover', ()=>sub.show(item));
|
||||
item.addEventListener('mouseleave', ()=>sub.hide());
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.root;
|
||||
}
|
||||
render() {
|
||||
if (!this.root) {
|
||||
const item = document.createElement('li'); {
|
||||
this.root = item;
|
||||
item.classList.add('list-group-item');
|
||||
item.classList.add('ctx-item');
|
||||
item.title = this.value;
|
||||
if (this.callback) {
|
||||
item.addEventListener('click', (evt) => this.callback(evt, this));
|
||||
}
|
||||
item.append(this.label);
|
||||
if (this.childList.length > 0) {
|
||||
item.classList.add('ctx-has-children');
|
||||
const sub = new SubMenu(this.childList);
|
||||
this.subMenu = sub;
|
||||
const trigger = document.createElement('div'); {
|
||||
trigger.classList.add('ctx-expander');
|
||||
trigger.textContent = '⋮';
|
||||
trigger.addEventListener('click', (evt) => {
|
||||
evt.stopPropagation();
|
||||
this.toggle();
|
||||
});
|
||||
item.append(trigger);
|
||||
}
|
||||
item.addEventListener('mouseover', () => sub.show(item));
|
||||
item.addEventListener('mouseleave', () => sub.hide());
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.root;
|
||||
}
|
||||
|
||||
|
||||
expand() {
|
||||
this.subMenu?.show(this.root);
|
||||
if (this.onExpand) {
|
||||
this.onExpand();
|
||||
}
|
||||
}
|
||||
collapse() {
|
||||
this.subMenu?.hide();
|
||||
}
|
||||
toggle() {
|
||||
if (this.subMenu.isActive) {
|
||||
this.expand();
|
||||
} else {
|
||||
this.collapse();
|
||||
}
|
||||
}
|
||||
}
|
||||
expand() {
|
||||
this.subMenu?.show(this.root);
|
||||
if (this.onExpand) {
|
||||
this.onExpand();
|
||||
}
|
||||
}
|
||||
collapse() {
|
||||
this.subMenu?.hide();
|
||||
}
|
||||
toggle() {
|
||||
if (this.subMenu.isActive) {
|
||||
this.expand();
|
||||
} else {
|
||||
this.collapse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,64 +1,64 @@
|
||||
import { MenuItem } from "./MenuItem.js";
|
||||
|
||||
export class SubMenu {
|
||||
/**@type {MenuItem[]}*/ itemList = [];
|
||||
/**@type {Boolean}*/ isActive = false;
|
||||
|
||||
/**@type {HTMLElement}*/ root;
|
||||
/**@type {MenuItem[]}*/ itemList = [];
|
||||
/**@type {Boolean}*/ isActive = false;
|
||||
|
||||
/**@type {HTMLElement}*/ root;
|
||||
|
||||
|
||||
|
||||
|
||||
constructor(/**@type {MenuItem[]}*/items) {
|
||||
this.itemList = items;
|
||||
}
|
||||
constructor(/**@type {MenuItem[]}*/items) {
|
||||
this.itemList = items;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.root) {
|
||||
const menu = document.createElement('ul'); {
|
||||
this.root = menu;
|
||||
menu.classList.add('list-group');
|
||||
menu.classList.add('ctx-menu');
|
||||
menu.classList.add('ctx-sub-menu');
|
||||
this.itemList.forEach(it=>menu.append(it.render()));
|
||||
}
|
||||
}
|
||||
return this.root;
|
||||
}
|
||||
render() {
|
||||
if (!this.root) {
|
||||
const menu = document.createElement('ul'); {
|
||||
this.root = menu;
|
||||
menu.classList.add('list-group');
|
||||
menu.classList.add('ctx-menu');
|
||||
menu.classList.add('ctx-sub-menu');
|
||||
this.itemList.forEach(it => menu.append(it.render()));
|
||||
}
|
||||
}
|
||||
return this.root;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
show(/**@type {HTMLElement}*/parent) {
|
||||
if (this.isActive) return;
|
||||
this.isActive = true;
|
||||
this.render();
|
||||
parent.append(this.root);
|
||||
requestAnimationFrame(()=>{
|
||||
const rect = this.root.getBoundingClientRect();
|
||||
console.log(window.innerHeight, rect);
|
||||
if (rect.bottom > window.innerHeight - 5) {
|
||||
this.root.style.top = `${window.innerHeight - 5 - rect.bottom}px`;
|
||||
}
|
||||
if (rect.right > window.innerWidth - 5) {
|
||||
this.root.style.left = 'unset';
|
||||
this.root.style.right = '100%';
|
||||
}
|
||||
});
|
||||
}
|
||||
hide() {
|
||||
if (this.root) {
|
||||
this.root.remove();
|
||||
this.root.style.top = '';
|
||||
this.root.style.left = '';
|
||||
}
|
||||
this.isActive = false;
|
||||
}
|
||||
toggle(/**@type {HTMLElement}*/parent) {
|
||||
if (this.isActive) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
show(/**@type {HTMLElement}*/parent) {
|
||||
if (this.isActive) return;
|
||||
this.isActive = true;
|
||||
this.render();
|
||||
parent.append(this.root);
|
||||
requestAnimationFrame(() => {
|
||||
const rect = this.root.getBoundingClientRect();
|
||||
console.log(window.innerHeight, rect);
|
||||
if (rect.bottom > window.innerHeight - 5) {
|
||||
this.root.style.top = `${window.innerHeight - 5 - rect.bottom}px`;
|
||||
}
|
||||
if (rect.right > window.innerWidth - 5) {
|
||||
this.root.style.left = 'unset';
|
||||
this.root.style.right = '100%';
|
||||
}
|
||||
});
|
||||
}
|
||||
hide() {
|
||||
if (this.root) {
|
||||
this.root.remove();
|
||||
this.root.style.top = '';
|
||||
this.root.style.left = '';
|
||||
}
|
||||
this.isActive = false;
|
||||
}
|
||||
toggle(/**@type {HTMLElement}*/parent) {
|
||||
if (this.isActive) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user