human/demo/menu.js

303 lines
12 KiB
JavaScript
Raw Normal View History

2020-10-18 18:12:09 +02:00
let instance = 0;
2020-10-30 16:57:23 +01:00
let CSScreated = false;
2020-10-18 18:12:09 +02:00
2020-10-30 16:57:23 +01:00
let theme = {
background: 'darkslategray',
hover: 'lightgray',
itemBackground: 'black',
itemColor: 'white',
buttonBackground: 'lightblue',
buttonHover: 'lightgreen',
checkboxOn: 'lightgreen',
checkboxOff: 'lightcoral',
rangeBackground: 'lightblue',
rangeLabel: 'white',
2020-11-01 19:07:53 +01:00
chartColor: 'lightblue',
2020-10-30 16:57:23 +01:00
};
function createCSS() {
if (CSScreated) return;
const css = `
2020-11-07 17:34:09 +01:00
:root { --rounded: 0.2rem; }
2020-11-05 15:06:09 +01:00
.menu { position: absolute; top: 0rem; right: 0; width: fit-content; padding: 0 0.8rem 0 0.8rem; line-height: 1.8rem; z-index: 10; max-height: calc(100% - 4rem);
2020-11-07 17:34:09 +01:00
box-shadow: 0 0 8px dimgrey; background: ${theme.background}; border-radius: var(--rounded); border-color: black; border-style: solid; border-width: thin; }
2020-10-30 16:57:23 +01:00
.menu:hover { box-shadow: 0 0 8px ${theme.hover}; }
2020-10-18 18:12:09 +02:00
.menu-container { display: block; max-height: 100vh; }
.menu-container-fadeout { max-height: 0; overflow: hidden; transition: max-height, 0.5s ease; }
.menu-container-fadein { max-height: 100vh; overflow: hidden; transition: max-height, 0.5s ease; }
2020-10-30 16:57:23 +01:00
.menu-item { display: flex; white-space: nowrap; padding: 0.2rem; width: max-content; cursor: default; }
2020-10-18 14:07:45 +02:00
.menu-title { text-align: right; cursor: pointer; }
2020-10-30 16:57:23 +01:00
.menu-hr { margin: 0.2rem; border: 1px solid rgba(0, 0, 0, 0.5) }
2020-10-18 14:07:45 +02:00
.menu-label { padding: 0; }
2020-10-18 02:59:43 +02:00
2020-10-30 15:23:49 +01:00
.menu-list { margin-right: 0.8rem; }
select:focus { outline: none; }
2020-11-07 17:34:09 +01:00
.menu-list-item { background: ${theme.itemBackground}; color: ${theme.itemColor}; border: none; padding: 0.2rem; font-family: inherit; font-variant: inherit; border-radius: var(--rounded); font-weight: 800; }
2020-10-30 15:23:49 +01:00
2020-10-30 16:57:23 +01:00
.menu-chart-title { padding: 0; font-size: 0.8rem; font-weight: 800; align-items: center}
.menu-chart-canvas { background: transparent; margin: 0.2rem 0 0.2rem 0.6rem; }
2020-10-18 02:59:43 +02:00
2020-11-07 17:25:03 +01:00
.menu-button { border: 0; background: ${theme.buttonBackground}; width: -webkit-fill-available; padding: 8px; margin: 8px 0 8px 0; cursor: pointer; box-shadow: 4px 4px 4px 0 dimgrey;
2020-11-07 17:34:09 +01:00
border-radius: var(--rounded); justify-content: center; font-family: inherit; font-variant: inherit; font-size: 1rem; font-weight: 800; }
2020-10-30 16:57:23 +01:00
.menu-button:hover { background: ${theme.buttonHover}; box-shadow: 4px 4px 4px 0 black; }
2020-10-18 14:07:45 +02:00
.menu-button:focus { outline: none; }
2020-10-18 02:59:43 +02:00
2020-11-07 17:34:09 +01:00
.menu-checkbox { width: 2.8rem; height: 1rem; background: ${theme.itemBackground}; margin: 0.5rem 0.8rem 0 0; position: relative; border-radius: var(--rounded); }
2020-10-30 16:57:23 +01:00
.menu-checkbox:after { content: 'OFF'; color: ${theme.checkboxOff}; position: absolute; right: 0.2rem; top: -0.4rem; font-weight: 800; font-size: 0.5rem; }
.menu-checkbox:before { content: 'ON'; color: ${theme.checkboxOn}; position: absolute; left: 0.3rem; top: -0.4rem; font-weight: 800; font-size: 0.5rem; }
2020-11-07 17:34:09 +01:00
.menu-checkbox-label { width: 1.3rem; height: 0.8rem; cursor: pointer; position: absolute; top: 0.1rem; left: 0.1rem; z-index: 1; background: ${theme.checkboxOff}; border-radius: var(--rounded); transition: left 0.6s ease; }
2020-10-18 02:59:43 +02:00
input[type=checkbox] { visibility: hidden; }
2020-10-30 16:57:23 +01:00
input[type=checkbox]:checked + label { left: 1.4rem; background: ${theme.checkboxOn}; }
2020-10-18 02:59:43 +02:00
2020-10-30 16:57:23 +01:00
.menu-range { margin: 0 0.8rem 0 0; width: 5rem; background: transparent; color: ${theme.rangeBackground}; }
.menu-range:before { content: attr(value); color: ${theme.rangeLabel}; margin: 0 0.4rem 0 0; font-weight: 800; font-size: 0.6rem; position: relative; top: 0.3rem; }
2020-10-18 02:59:43 +02:00
input[type=range] { -webkit-appearance: none; }
2020-11-07 17:34:09 +01:00
input[type=range]::-webkit-slider-runnable-track { width: 100%; height: 1rem; cursor: pointer; background: ${theme.itemBackground}; border-radius: var(--rounded); border: 1px; }
input[type=range]::-webkit-slider-thumb { border: 1px solid #000000; margin-top: 0.05rem; height: 0.9rem; width: 1.5rem; border-radius: var(--rounded); background: ${theme.rangeBackground}; cursor: pointer; -webkit-appearance: none; }
2020-10-18 02:59:43 +02:00
`;
const el = document.createElement('style');
el.innerHTML = css;
document.getElementsByTagName('head')[0].appendChild(el);
2020-10-30 16:57:23 +01:00
CSScreated = true;
2020-10-18 02:59:43 +02:00
}
class Menu {
2020-10-30 16:57:23 +01:00
constructor(parent, title, position, userTheme) {
if (userTheme) theme = { ...theme, ...userTheme };
2020-10-18 02:59:43 +02:00
createCSS();
2020-10-30 16:57:23 +01:00
this.createMenu(parent, title, position);
2020-10-18 18:12:09 +02:00
this.id = 0;
this.instance = instance;
instance++;
2020-10-18 02:59:43 +02:00
this._maxFPS = 0;
2020-10-18 14:07:45 +02:00
this.hidden = 0;
2020-10-30 16:57:23 +01:00
}
createMenu(parent, title = '', position = { top: null, left: null, bottom: null, right: null }) {
this.menu = document.createElement('div');
this.menu.id = `menu-${instance}`;
this.menu.className = 'menu';
if (position) {
if (position.top) this.menu.style.top = position.top;
if (position.bottom) this.menu.style.bottom = position.bottom;
if (position.left) this.menu.style.left = position.left;
if (position.right) this.menu.style.right = position.right;
}
this.container = document.createElement('div');
this.container.id = `menu-container-${instance}`;
this.container.className = 'menu-container menu-container-fadein';
if (title !== '') {
const elTitle = document.createElement('div');
elTitle.className = 'menu-title';
elTitle.id = `menu-title-${instance}`;
elTitle.innerHTML = title;
this.menu.appendChild(elTitle);
elTitle.addEventListener('click', () => {
this.container.classList.toggle('menu-container-fadeout');
this.container.classList.toggle('menu-container-fadein');
this.menu.style.borderStyle = this.container.classList.contains('menu-container-fadeout') ? 'none' : 'solid';
});
}
this.menu.appendChild(this.container);
if (typeof parent === 'object') parent.appendChild(this.menu);
else document.getElementById(parent).appendChild(this.menu);
2020-10-18 02:59:43 +02:00
}
get newID() {
2020-10-18 18:12:09 +02:00
this.id++;
return `menu-${this.instance}-${this.id}`;
2020-10-18 02:59:43 +02:00
}
get ID() {
2020-10-18 18:12:09 +02:00
return `menu-${this.instance}-${this.id}`;
2020-10-18 02:59:43 +02:00
}
get width() {
return this.menu.offsetWidth;
}
get height() {
return this.menu.offsetHeight;
}
2020-10-30 16:57:23 +01:00
hide() {
if (this.container.classList.contains('menu-container-fadein')) {
this.container.classList.toggle('menu-container-fadeout');
this.container.classList.toggle('menu-container-fadein');
}
}
visible() {
return (this.container.classList.contains('menu-container-fadein'));
}
toggle(evt) {
this.container.classList.toggle('menu-container-fadeout');
this.container.classList.toggle('menu-container-fadein');
if (this.container.classList.contains('menu-container-fadein') && evt) {
const x = evt.x || (evt.touches && evt.touches[0] ? evt.touches[0].pageX : null);
const y = evt.y || (evt.touches && evt.touches[0] ? evt.touches[0].pageY : null);
if (x) this.menu.style.left = `${x - 105}px`;
if (y) this.menu.style.top = '5.5rem'; // `${evt.y + 55}px`;
if (this.menu.offsetLeft < 0) this.menu.style.left = 0;
if ((this.menu.offsetLeft + this.menu.offsetWidth) > window.innerWidth) {
this.menu.style.left = null;
this.menu.style.right = 0;
}
this.menu.style.borderStyle = 'solid';
} else {
this.menu.style.borderStyle = 'none';
}
}
addTitle(title) {
2020-10-18 14:07:45 +02:00
const el = document.createElement('div');
el.className = 'menu-title';
el.id = this.newID;
el.innerHTML = title;
this.menu.appendChild(el);
el.addEventListener('click', () => {
this.hidden = !this.hidden;
const all = document.getElementsByClassName('menu');
2020-10-18 18:12:09 +02:00
for (const item of all) {
2020-10-30 16:57:23 +01:00
item.style.display = this.hidden ? 'none' : 'block';
2020-10-18 18:12:09 +02:00
}
2020-10-18 14:07:45 +02:00
});
2020-11-03 04:15:37 +01:00
return el;
2020-10-18 14:07:45 +02:00
}
2020-10-30 16:57:23 +01:00
addLabel(title) {
2020-10-18 02:59:43 +02:00
const el = document.createElement('div');
2020-10-18 18:12:09 +02:00
el.className = 'menu-item menu-label';
2020-10-18 02:59:43 +02:00
el.id = this.newID;
el.innerHTML = title;
2020-10-18 18:12:09 +02:00
this.container.appendChild(el);
2020-11-03 04:15:37 +01:00
return el;
2020-10-18 02:59:43 +02:00
}
2020-10-30 16:57:23 +01:00
addBool(title, object, variable, callback) {
2020-10-18 02:59:43 +02:00
const el = document.createElement('div');
2020-10-18 18:12:09 +02:00
el.className = 'menu-item';
2020-10-18 14:07:45 +02:00
el.innerHTML = `<div class="menu-checkbox"><input class="menu-checkbox" type="checkbox" id="${this.newID}" ${object[variable] ? 'checked' : ''}/><label class="menu-checkbox-label" for="${this.ID}"></label></div>${title}`;
2020-10-18 18:12:09 +02:00
this.container.appendChild(el);
2020-10-18 14:07:45 +02:00
el.addEventListener('change', (evt) => {
2020-10-18 02:59:43 +02:00
object[variable] = evt.target.checked;
if (callback) callback(evt.target.checked);
});
2020-11-03 04:15:37 +01:00
return el;
2020-10-18 02:59:43 +02:00
}
2020-10-30 15:23:49 +01:00
async addList(title, items, selected, callback) {
const el = document.createElement('div');
el.className = 'menu-item';
let options = '';
for (const item of items) {
const def = item === selected ? 'selected' : '';
options += `<option value="${item}" ${def}>${item}</option>`;
}
el.innerHTML = `<div class="menu-list"><select name="${this.ID}" class="menu-list-item">${options}</select><label for="${this.ID}"></label></div>${title}`;
el.style.fontFamily = document.body.style.fontFamily;
el.style.fontSize = document.body.style.fontSize;
el.style.fontVariant = document.body.style.fontVariant;
this.container.appendChild(el);
el.addEventListener('change', (evt) => {
if (callback) callback(items[evt.target.selectedIndex]);
});
2020-11-03 04:15:37 +01:00
return el;
2020-10-30 15:23:49 +01:00
}
2020-10-30 16:57:23 +01:00
addRange(title, object, variable, min, max, step, callback) {
2020-10-18 02:59:43 +02:00
const el = document.createElement('div');
2020-10-18 18:12:09 +02:00
el.className = 'menu-item';
2020-10-30 16:57:23 +01:00
el.innerHTML = `<input class="menu-range" type="range" id="${this.newID}" min="${min}" max="${max}" step="${step}" value="${object[variable]}">${title}`;
2020-10-18 18:12:09 +02:00
this.container.appendChild(el);
2020-10-18 14:07:45 +02:00
el.addEventListener('change', (evt) => {
2020-11-06 21:35:58 +01:00
object[variable] = parseInt(evt.target.value) === parseFloat(evt.target.value) ? parseInt(evt.target.value) : parseFloat(evt.target.value);
2020-10-18 02:59:43 +02:00
evt.target.setAttribute('value', evt.target.value);
if (callback) callback(evt.target.value);
});
2020-11-03 04:15:37 +01:00
return el;
2020-10-18 02:59:43 +02:00
}
2020-10-30 16:57:23 +01:00
addHTML(html) {
2020-10-18 02:59:43 +02:00
const el = document.createElement('div');
2020-10-18 18:12:09 +02:00
el.className = 'menu-item';
2020-10-18 02:59:43 +02:00
el.id = this.newID;
if (html) el.innerHTML = html;
2020-10-18 18:12:09 +02:00
this.container.appendChild(el);
2020-11-03 04:15:37 +01:00
return el;
2020-10-18 02:59:43 +02:00
}
2020-10-30 16:57:23 +01:00
addButton(titleOn, titleOff, callback) {
2020-10-18 02:59:43 +02:00
const el = document.createElement('button');
2020-10-18 18:12:09 +02:00
el.className = 'menu-item menu-button';
2020-10-18 14:07:45 +02:00
el.style.fontFamily = document.body.style.fontFamily;
el.style.fontSize = document.body.style.fontSize;
el.style.fontVariant = document.body.style.fontVariant;
2020-10-18 02:59:43 +02:00
el.type = 'button';
el.id = this.newID;
el.innerText = titleOn;
2020-10-18 18:12:09 +02:00
this.container.appendChild(el);
2020-10-18 14:07:45 +02:00
el.addEventListener('click', () => {
2020-10-18 02:59:43 +02:00
if (el.innerText === titleOn) el.innerText = titleOff;
else el.innerText = titleOn;
if (callback) callback(el.innerText !== titleOn);
});
2020-11-03 04:15:37 +01:00
return el;
2020-10-18 02:59:43 +02:00
}
2020-10-30 16:57:23 +01:00
addValue(title, val, suffix = '') {
2020-10-18 02:59:43 +02:00
const el = document.createElement('div');
2020-10-18 18:12:09 +02:00
el.className = 'menu-item';
2020-10-30 16:57:23 +01:00
el.id = `menu-val-${title}`;
el.innerText = `${title}: ${val}${suffix}`;
this.container.appendChild(el);
2020-11-03 04:15:37 +01:00
return el;
2020-10-18 02:59:43 +02:00
}
// eslint-disable-next-line class-methods-use-this
2020-10-30 16:57:23 +01:00
updateValue(title, val, suffix = '') {
const el = document.getElementById(`menu-val-${title}`);
if (el) el.innerText = `${title}: ${val}${suffix}`;
else this.addValue(title, val);
2020-10-18 02:59:43 +02:00
}
2020-11-01 19:07:53 +01:00
addChart(title, id, width = 200, height = 40, color) {
if (color) theme.chartColor = color;
2020-10-18 02:59:43 +02:00
const el = document.createElement('div');
2020-10-18 18:12:09 +02:00
el.className = 'menu-item menu-chart-title';
2020-10-18 02:59:43 +02:00
el.id = this.newID;
2020-11-01 19:07:53 +01:00
el.innerHTML = `<font color=${theme.chartColor}>${title}</font><canvas id="menu-canvas-${id}" class="menu-chart-canvas" width="${width}px" height="${height}px"></canvas>`;
2020-10-18 18:12:09 +02:00
this.container.appendChild(el);
2020-11-03 04:15:37 +01:00
return el;
2020-10-18 02:59:43 +02:00
}
// eslint-disable-next-line class-methods-use-this
async updateChart(id, values) {
if (!values || (values.length === 0)) return;
const canvas = document.getElementById(`menu-canvas-${id}`);
if (!canvas) return;
const ctx = canvas.getContext('2d');
2020-11-01 19:07:53 +01:00
ctx.fillStyle = theme.background;
2020-10-18 02:59:43 +02:00
ctx.fillRect(0, 0, canvas.width, canvas.height);
const width = canvas.width / values.length;
const max = 1 + Math.max(...values);
const height = canvas.height / max;
for (const i in values) {
const gradient = ctx.createLinearGradient(0, (max - values[i]) * height, 0, 0);
2020-11-01 19:07:53 +01:00
gradient.addColorStop(0.1, theme.chartColor);
gradient.addColorStop(0.4, theme.background);
2020-10-18 02:59:43 +02:00
ctx.fillStyle = gradient;
ctx.fillRect(i * width, 0, width - 4, canvas.height);
2020-11-01 19:07:53 +01:00
ctx.fillStyle = theme.background;
2020-11-01 20:16:47 +01:00
ctx.font = `${width / 1.5}px "Segoe UI"`;
2020-10-30 16:57:23 +01:00
ctx.fillText(Math.round(values[i]), i * width + 1, canvas.height - 1, width - 1);
2020-10-18 02:59:43 +02:00
}
}
}
export default Menu;