transmission/web/src/utils.js

285 lines
7.1 KiB
JavaScript
Raw Normal View History

/* @license This file Copyright © Mnemosyne LLC.
It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
or any future license endorsed by Mnemosyne LLC.
License text can be found in the licenses/ folder. */
import isEqual from 'lodash.isequal';
export const Utils = {
/** Given a numerator and denominator, return a ratio string */
ratio(numerator, denominator) {
let result = Math.floor((100 * numerator) / denominator) / 100;
// check for special cases
if (
result === Number.POSITIVE_INFINITY ||
result === Number.NEGATIVE_INFINITY
) {
result = -2;
} else if (Number.isNaN(result)) {
result = -1;
}
return result;
},
};
function toggleClass(buttons, button, pages, page, callback) {
for (const element of buttons.children) {
element.classList.toggle('selected', element === button);
}
for (const element of pages.children) {
element.classList.toggle('hidden', element !== page);
}
if (callback) {
callback(page);
}
}
export function createTextualTabsContainer(id, tabs, callback) {
const root = document.createElement('div');
root.id = id;
root.classList.add('tabs-container');
const buttons = document.createElement('div');
buttons.classList.add('tabs-buttons');
root.append(buttons);
2023-09-01 22:52:17 +00:00
const dismiss = document.createElement('button');
dismiss.classList.add('tabs-container-close');
dismiss.innerHTML = '×';
root.append(dismiss);
const pages = document.createElement('div');
pages.classList.add('tabs-pages');
root.append(pages);
const button_array = [];
for (const [button_id, page, tabname] of tabs) {
const button = document.createElement('button');
button.id = button_id;
button.classList.add('tabs-button');
button.setAttribute('type', 'button');
button.textContent = tabname;
buttons.append(button);
button_array.push(button);
page.classList.add('hidden', 'tabs-page');
pages.append(page);
button.addEventListener('click', () =>
toggleClass(buttons, button, pages, page, callback),
);
}
button_array[0].classList.add('selected');
pages.children[0].classList.remove('hidden');
return {
buttons: button_array,
2023-09-01 22:52:17 +00:00
dismiss,
root,
};
}
export function createTabsContainer(id, tabs, callback) {
const root = document.createElement('div');
root.id = id;
root.classList.add('tabs-container');
const buttons = document.createElement('div');
buttons.classList.add('tabs-buttons');
root.append(buttons);
const pages = document.createElement('div');
pages.classList.add('tabs-pages');
root.append(pages);
const button_array = [];
for (const [button_id, page] of tabs) {
const button = document.createElement('button');
button.id = button_id;
button.classList.add('tabs-button');
button.setAttribute('type', 'button');
buttons.append(button);
button_array.push(button);
page.classList.add('hidden', 'tabs-page');
pages.append(page);
button.addEventListener('click', () =>
toggleClass(buttons, button, pages, page, callback),
);
}
button_array[0].classList.add('selected');
pages.children[0].classList.remove('hidden');
return {
buttons: button_array,
root,
};
}
export function createDialogContainer(id) {
const root = document.createElement('dialog');
root.classList.add('dialog-container', 'popup', id);
root.open = true;
root.setAttribute('role', 'dialog');
const win = document.createElement('div');
win.classList.add('dialog-window');
root.append(win);
const logo = document.createElement('div');
logo.classList.add('dialog-logo');
win.append(logo);
const heading = document.createElement('div');
heading.classList.add('dialog-heading');
win.append(heading);
const message = document.createElement('div');
message.classList.add('dialog-message');
win.append(message);
const workarea = document.createElement('div');
workarea.classList.add('dialog-workarea');
win.append(workarea);
const buttons = document.createElement('div');
buttons.classList.add('dialog-buttons');
win.append(buttons);
const bbegin = document.createElement('span');
bbegin.classList.add('dialog-buttons-begin');
buttons.append(bbegin);
const dismiss = document.createElement('button');
dismiss.classList.add('dialog-dismiss-button');
dismiss.textContent = 'Cancel';
buttons.append(dismiss);
const confirm = document.createElement('button');
confirm.textContent = 'OK';
buttons.append(confirm);
const bend = document.createElement('span');
bend.classList.add('dialog-buttons-end');
buttons.append(bend);
return {
confirm,
dismiss,
heading,
message,
root,
workarea,
};
}
export function makeUUID() {
// source: https://stackoverflow.com/a/2117523/6568470
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replaceAll(/[018]/g, (c) =>
(
c ^
(crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
).toString(16),
);
}
export function createSection(title) {
const root = document.createElement('fieldset');
root.classList.add('section');
const legend = document.createElement('legend');
legend.classList.add('title');
legend.textContent = title;
root.append(legend);
const content = document.createElement('div');
content.classList.add('content');
root.append(content);
return { content, root };
}
export function createInfoSection(title, labels) {
const children = [];
const { root, content } = createSection(title);
for (const label_text of labels) {
const label_element = document.createElement('label');
label_element.textContent = label_text;
content.append(label_element);
const item = document.createElement('div');
item.id = makeUUID();
content.append(item);
label_element.setAttribute('for', item.id);
children.push(item);
}
return { children, root };
}
export function debounce(callback, wait = 100) {
let timeout = null;
return (...arguments_) => {
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
callback(...arguments_);
}, wait);
}
};
}
export function deepEqual(a, b) {
return isEqual(a, b);
}
function setOrDeleteAttribute(element, attribute, b) {
if (b) {
element.setAttribute(attribute, true);
} else {
element.removeAttribute(attribute);
}
}
export function setEnabled(element, b) {
setOrDeleteAttribute(element, 'disabled', !b);
}
export function setChecked(element, b) {
setOrDeleteAttribute(element, 'checked', b);
}
export function addCheckedClass(element, b) {
element.classList.toggle('checked', b);
}
export class OutsideClickListener extends EventTarget {
constructor(element) {
super();
this.listener = (event_) => {
if (!element.contains(event_.target)) {
this.dispatchEvent(new MouseEvent(event_.type, event_));
event_.preventDefault();
}
};
Object.seal(this);
this.start();
}
start() {
setTimeout(() => document.addEventListener('click', this.listener), 0);
}
stop() {
document.removeEventListener('click', this.listener);
}
}
export function setTextContent(e, text) {
if (e.textContent !== text) {
e.textContent = text;
}
}