transmission/web/src/overflow-menu.js

467 lines
13 KiB
JavaScript

/*
* This file Copyright (C) 2020 Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*/
import { Formatter } from './formatter.js';
import { Prefs } from './prefs.js';
import { RPC } from './remote.js';
import { OutsideClickListener, setEnabled } from './utils.js';
export class OverflowMenu extends EventTarget {
constructor(session_manager, prefs, remote, action_manager) {
super();
this.action_listener = this._onActionChange.bind(this);
this.action_manager = action_manager;
this.action_manager.addEventListener('change', this.action_listener);
this.prefs_listener = this._onPrefsChange.bind(this);
this.prefs = prefs;
this.prefs.addEventListener('change', this.prefs_listener);
this.closed = false;
this.remote = remote;
this.name = 'overflow-menu';
this.session_listener = this._onSessionChange.bind(this);
this.session_manager = session_manager;
this.session_manager.addEventListener(
'session-change',
this.session_listener
);
const { session_properties } = session_manager;
Object.assign(this, this._create(session_properties));
this.outside = new OutsideClickListener(this.root);
this.outside.addEventListener('click', () => this.close());
Object.seal(this);
this.show();
}
show() {
document.body.append(this.root);
}
close() {
if (!this.closed) {
this.outside.stop();
this.session_manager.removeEventListener(
'session-change',
this.session_listener
);
this.action_manager.removeEventListener('change', this.action_listener);
this.prefs.removeEventListener('change', this.prefs_listener);
this.root.remove();
this.dispatchEvent(new Event('close'));
for (const key of Object.keys(this)) {
this[key] = null;
}
this.closed = true;
}
}
_onSessionChange(event_) {
const { alt_speed_check } = this.elements;
const { session_properties } = event_;
alt_speed_check.checked = session_properties[RPC._TurtleState];
}
_onPrefsChange(event_) {
switch (event_.key) {
case Prefs.SortDirection:
case Prefs.SortMode:
this.root.querySelector(`[data-pref="${event_.key}"]`).value =
event_.value;
break;
default:
break;
}
}
_onActionChange(event_) {
const element = this.actions[event_.action];
if (element) {
this._updateElement(element);
}
}
_updateElement(element) {
if (element.dataset.action) {
const { action } = element.dataset;
const shortcuts = this.action_manager.keyshortcuts(action);
if (shortcuts) {
element.setAttribute('aria-keyshortcuts', shortcuts);
}
setEnabled(element, this.action_manager.isEnabled(action));
}
}
_onClick(event_) {
const { action, pref } = event_.target.dataset;
if (action) {
this.action_manager.click(action);
return;
}
if (pref) {
this.prefs[pref] = event_.target.value;
return;
}
console.log('unhandled');
console.log(event_);
console.trace();
}
_create(session_properties) {
const actions = {};
const on_click = this._onClick.bind(this);
const elements = {};
const make_section = (classname, title) => {
const section = document.createElement('fieldset');
section.classList.add('section', classname);
const legend = document.createElement('legend');
legend.classList.add('title');
legend.textContent = title;
section.append(legend);
return section;
};
const make_button = (parent, text, action) => {
const e = document.createElement('button');
e.textContent = text;
e.addEventListener('click', on_click);
parent.append(e);
if (action) {
e.dataset.action = action;
}
return e;
};
const root = document.createElement('div');
root.classList.add('overflow-menu', 'popup');
let section = make_section('display', 'Display');
root.append(section);
let options = document.createElement('div');
options.id = 'display-options';
section.append(options);
// sort mode
let div = document.createElement('div');
options.append(div);
let label = document.createElement('label');
label.id = 'display-sort-mode-label';
label.textContent = 'Sort by';
div.append(label);
let select = document.createElement('select');
select.id = 'display-sort-mode-select';
select.dataset.pref = Prefs.SortMode;
div.append(select);
const sort_modes = [
[Prefs.SortByActivity, 'Activity'],
[Prefs.SortByAge, 'Age'],
[Prefs.SortByName, 'Name'],
[Prefs.SortByProgress, 'Progress'],
[Prefs.SortByQueue, 'Queue order'],
[Prefs.SortByRatio, 'Ratio'],
[Prefs.SortBySize, 'Size'],
[Prefs.SortByState, 'State'],
];
for (const [value, text] of sort_modes) {
const option = document.createElement('option');
option.value = value;
option.textContent = text;
select.append(option);
}
label.setAttribute('for', select.id);
select.value = this.prefs.sort_mode;
select.addEventListener('change', (event_) => {
this.prefs.sort_mode = event_.target.value;
});
// sort direction
div = document.createElement('div');
options.append(div);
let check = document.createElement('input');
check.id = 'display-sort-reverse-check';
check.dataset.pref = Prefs.SortDirection;
check.type = 'checkbox';
div.append(check);
label = document.createElement('label');
label.id = 'display-sort-reverse-label';
label.setAttribute('for', check.id);
label.textContent = 'Reverse sort';
div.append(label);
check.checked = this.prefs.sort_direction !== Prefs.SortAscending;
check.addEventListener('input', (event_) => {
this.prefs.sort_direction = event_.target.checked
? Prefs.SortDescending
: Prefs.SortAscending;
});
// compact
div = document.createElement('div');
options.append(div);
const action = 'toggle-compact-rows';
check = document.createElement('input');
check.id = 'display-compact-check';
check.dataset.action = action;
check.type = 'checkbox';
div.append(check);
label = document.createElement('label');
label.id = 'display-compact-label';
label.for = check.id;
label.setAttribute('for', check.id);
label.textContent = this.action_manager.text(action);
div.append(label);
check.checked = this.prefs.display_mode === Prefs.DisplayCompact;
check.addEventListener('input', (event_) => {
const { checked } = event_.target;
this.prefs.display_mode = checked
? Prefs.DisplayCompact
: Prefs.DisplayFull;
});
// fullscreen
div = document.createElement('div');
options.append(div);
check = document.createElement('input');
check.id = 'display-fullscreen-check';
check.type = 'checkbox';
const is_fullscreen = () => document.fullscreenElement !== null;
check.checked = is_fullscreen();
check.addEventListener('input', () => {
if (is_fullscreen()) {
document.exitFullscreen();
} else {
document.body.requestFullscreen();
}
});
document.addEventListener('fullscreenchange', () => {
check.checked = is_fullscreen();
});
div.append(check);
label = document.createElement('label');
label.id = 'display-fullscreen-label';
label.for = check.id;
label.setAttribute('for', check.id);
label.textContent = 'Fullscreen';
div.append(label);
section = make_section('speed', 'Speed Limit');
root.append(section);
options = document.createElement('div');
options.id = 'speed-options';
section.append(options);
// speed up
div = document.createElement('div');
div.classList.add('speed-up');
options.append(div);
label = document.createElement('label');
label.id = 'speed-up-label';
label.textContent = 'Upload:';
div.append(label);
const unlimited = 'Unlimited';
select = document.createElement('select');
select.id = 'speed-up-select';
div.append(select);
const speeds = ['10', '100', '200', '500', '750', unlimited];
for (const speed of [
...new Set(speeds)
.add(`${session_properties[RPC._UpSpeedLimit]}`)
.values(),
].sort()) {
const option = document.createElement('option');
option.value = speed;
option.textContent =
speed === unlimited ? unlimited : Formatter.speed(speed);
select.append(option);
}
label.setAttribute('for', select.id);
select.value = session_properties[RPC._UpSpeedLimited]
? `${session_properties[RPC._UpSpeedLimit]}`
: unlimited;
select.addEventListener('change', (event_) => {
const { value } = event_.target;
console.log(event_);
if (event_.target.value === unlimited) {
this.remote.savePrefs({ [RPC._UpSpeedLimited]: false });
} else {
this.remote.savePrefs({
[RPC._UpSpeedLimited]: true,
[RPC._UpSpeedLimit]: Number.parseInt(value, 10),
});
}
});
// speed down
div = document.createElement('div');
div.classList.add('speed-down');
options.append(div);
label = document.createElement('label');
label.id = 'speed-down-label';
label.textContent = 'Download:';
div.append(label);
select = document.createElement('select');
select.id = 'speed-down-select';
div.append(select);
for (const speed of [
...new Set(speeds)
.add(`${session_properties[RPC._DownSpeedLimit]}`)
.values(),
].sort()) {
const option = document.createElement('option');
option.value = speed;
option.textContent = speed;
select.append(option);
}
label.setAttribute('for', select.id);
select.value = session_properties[RPC._DownSpeedLimited]
? `${session_properties[RPC._DownSpeedLimit]}`
: unlimited;
select.addEventListener('change', (event_) => {
const { value } = event_.target;
console.log(event_);
if (event_.target.value === unlimited) {
this.remote.savePrefs({ [RPC._DownSpeedLimited]: false });
} else {
this.remote.savePrefs({
[RPC._DownSpeedLimited]: true,
[RPC._DownSpeedLimit]: Number.parseInt(value, 10),
});
}
});
// alt speed
div = document.createElement('div');
div.classList.add('alt-speed');
options.append(div);
check = document.createElement('input');
check.id = 'alt-speed-check';
check.type = 'checkbox';
check.checked = session_properties[RPC._TurtleState];
check.addEventListener('change', (event_) => {
this.remote.savePrefs({
[RPC._TurtleState]: event_.target.checked,
});
});
div.append(check);
elements.alt_speed_check = check;
label = document.createElement('label');
label.id = 'alt-speed-image';
label.setAttribute('for', check.id);
div.append(label);
label = document.createElement('label');
label.id = 'alt-speed-label';
label.setAttribute('for', check.id);
label.textContent = 'Use Temp limits';
div.append(label);
label = document.createElement('label');
label.id = 'alt-speed-values-label';
label.setAttribute('for', check.id);
const up = Formatter.speed(session_properties[RPC._TurtleUpSpeedLimit]);
const dn = Formatter.speed(session_properties[RPC._TurtleDownSpeedLimit]);
label.textContent = `(${up} up, ${dn} down)`;
div.append(label);
section = make_section('actions', 'Actions');
root.append(section);
for (const action_name of [
'show-preferences-dialog',
'pause-all-torrents',
'start-all-torrents',
]) {
const text = this.action_manager.text(action_name);
actions[action_name] = make_button(section, text, action_name);
}
section = make_section('info', 'Info');
root.append(section);
options = document.createElement('div');
section.append(options);
for (const action_name of [
'show-about-dialog',
'show-shortcuts-dialog',
'show-statistics-dialog',
]) {
const text = this.action_manager.text(action_name);
actions[action_name] = make_button(options, text, action_name);
}
section = make_section('links', 'Links');
root.append(section);
options = document.createElement('div');
section.append(options);
let e = document.createElement('a');
e.href = 'https://transmissionbt.com/';
e.tabindex = '0';
e.textContent = 'Homepage';
options.append(e);
e = document.createElement('a');
e.href = 'https://transmissionbt.com/donate/';
e.tabindex = '0';
e.textContent = 'Tip Jar';
options.append(e);
e = document.createElement('a');
e.href = 'https://github.com/transmission/transmission/';
e.tabindex = '0';
e.textContent = 'Source Code';
options.append(e);
Object.values(actions).forEach(this._updateElement.bind(this));
return { actions, elements, root };
}
}