/* * 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 }; } }