This commit is contained in:
Domantas 2025-11-23 08:23:38 -06:00 committed by GitHub
commit cc0462b11a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 514 additions and 135 deletions

View file

@ -123,6 +123,7 @@ web/ui.css
web/up2k.js
web/util.js
web/w.hash.js
web/keybindings.js
"""
RES = set(zs.strip().split("\n"))
RESM = {

View file

@ -68,6 +68,7 @@
<div id="op_up2k" class="opview"></div>
<div id="op_cfg" class="opview opbox opwide"></div>
<div id="op_hotkey" class="opview opbox opwide"></div>
<h1 id="path">
<a href="#" id="entree">🌲</a>

View file

@ -90,6 +90,48 @@ if (1)
]
],
"hotkeys": {
"ESC": "close various things",
"ENTER": "open selected file",
"HELP": "open help",
"PREV_SONG": "prev song",
"NEXT_SONG": "next song",
"TREE_PREV": "go to the previous dir",
"TREE_NEXT": "go to the next dir",
"PLAY_PAUSE": "play/pause",
"DOWNLOAD": "download selected files",
"CUT": "cut selected file(s)",
"COPY": "copy selected file(s)",
"PASTE": "paste (move/copy) here",
"DELETE": "delete selected file(s)",
"RENAME": "rename selected file(s)",
"TOGGLE_BREADCRUMBS": "toggle breadcrumbs / navpane",
"TOGGLE_GRID": "toggle grid / list view",
"TOGGLE_THUMBNAILS": "toggle thumbnails / icons",
"TOGGLE_FOLDER_TREE": "toggle folders / textfiles in navpane",
"FAST_FORWARD": "skip 10sec forward",
"REWIND": "skip 10sec back",
"NAVPANE_SHRINK": "shrink navpane",
"NAVPANE_GROW": "grow navpane",
"NAVPANE_SHRINK_DESC": "While in grid mode with nav panel visible",
"NAVPANE_GROW_DESC": "While in grid mode with nav panel visible",
"SELECT_SONG": "Select playing song",
"SELECT_SONG_DESC": "While audio is playing",
"SELECT_FILE": "Select file",
"SELECT_FILE_DESC": "While in file list",
"EDIT_FILE": "Edit file",
"EDIT_FILE_DESC": "While in file list",
"TOGGLE_GRID_MULTISELECT": "Toggle grid multi select",
"TOGGLE_GRID_MULTISELECT_DESC": "While in grid mode",
"GRID_SHRINK_THUMBNAILS": "Shrink thumbnails",
"GRID_SHRINK_THUMBNAILS_DESC": "While in grid mode",
"GRID_GROW_THUMBNAILS": "Grow thumbnails",
"GRID_GROW_THUMBNAILS_DESC": "While in grid mode",
},
"hotkey_binding": "Hotkey keybindings",
"hotkey_action": "Hotkey action",
"hotkey_context": "Context",
"m_ok": "OK",
"m_ng": "Cancel",
@ -712,6 +754,7 @@ ebi('ops').innerHTML = (
'<a href="#" id="opa_msg" data-dest="msg" tt="' + L.ot_msg + '">📟</a>' +
'<a href="#" id="opa_auc" data-dest="player" tt="' + L.ot_mp + '">🎺</a>' +
'<a href="#" id="opa_cfg" data-dest="cfg" tt="' + L.ot_cfg + '">⚙️</a>' +
'<a href="#" id="opa_hotkey" data-dest="hotkey" tt="Hotkeys">⚙️</a>' +
(IE ? '<span id="noie">' + L.ot_noie + '</span>' : '') +
'<div id="opdesc"></div>'
);
@ -963,6 +1006,16 @@ ebi('op_cfg').innerHTML = (
'<div><h3>' + L.cl_hiddenc + ' &nbsp;' + (MOBILE ? '<a href="#" id="hcolsh">' + L.cl_hidec + '</a> / ' : '') + '<a href="#" id="hcolsr">' + L.cl_reset + '</a></h3><div id="hcols"></div></div>'
);
//DDD kbd
lazyLoad('keybindings.js', function () {
const rows = [];
Object.entries(currentKeybindings).forEach(([key, value]) => {
rows.push(`<tr><td>${key}</td><td>${value.tr}</td><td>${value.desc || ''}</td></tr>`);
});
const table = '<table><thead><tr><th>' + L.hotkey_binding + '</th><th>' + L.hotkey_action + '</th><th>' + L.hotkey_context + '</th></tr></thead><tbody>' + rows.join('') + '</tbody></table>';
ebi('op_hotkey').innerHTML = '<div id="hotkey-root">' + table + '</div>';
});
// navpane
ebi('tree').innerHTML = (
@ -1003,6 +1056,7 @@ QS('#op_msg input[type="submit"]').value = L.ab_msg;
function opclick(e) {
var dest = this.getAttribute('data-dest');
console.log('Clicked on the top thing with dest', dest);
if (QS('#op_' + dest + '.act'))
dest = '';
@ -5880,24 +5934,25 @@ var ahotkeys = function (e) {
if (QS('#bbox-overlay.visible') || modal.busy)
return;
var k = (e.key || e.code) + '', pos = -1, n,
ae = document.activeElement,
aet = ae && ae != document.body ? ae.nodeName.toLowerCase() : '';
if (k.startsWith('Key'))
k = k.slice(3);
else if (k.startsWith('Digit'))
k = k.slice(5);
var kkey = (e.key || e.code) + '', pos = -1, n,
activeElement = document.activeElement,
activeNode = activeElement && activeElement != document.body ? activeElement.nodeName.toLowerCase() : '';
var kl = k.toLowerCase();
if (kkey.startsWith('Key'))
kkey = kkey.slice(3);
else if (kkey.startsWith('Digit'))
kkey = kkey.slice(5);
var keyLower = kkey.toLowerCase();
if (dbg_kbd)
console.log('KBD', k, kl, e.key, e.code, e.keyCode, e.which);
console.log('KBD', kkey, keyLower, e.key, e.code, e.keyCode, e.which);
if (konmai < 0)
noop();
else if (konmak[konmai] != kl)
konmai = konmai && kl == konmak[0] ? (konmai<3?konmai:1):0;
else if (konmak[konmai] != keyLower)
konmai = konmai && keyLower == konmak[0] ? (konmai<3?konmai:1):0;
else if (++konmai >= konmak.length) {
konmai = -1;
document.documentElement.scrollTop = 0;
@ -5912,62 +5967,46 @@ var ahotkeys = function (e) {
return ev(e);
}
if (k == 'Escape' || k == 'Esc') {
ae && ae.blur();
tt.hide();
var in_ftab = (activeNode == 'tr' || activeNode == 'td') && activeElement.closest('#files');
if (ebi('hkhelp'))
return qsr('#hkhelp');
//QQQ
const action = getHotkeyEvent(e, {
activeElement,
activeNode,
in_ftab,
treectl,
gridMode: thegrid.en,
mp,
showfile
});
if (toast.visible)
return toast.hide();
if (!action) return;
if (ebi('rn_cancel'))
return ebi('rn_cancel').click();
if (ebi('sh_abrt'))
return ebi('sh_abrt').click();
if (QS('.opview.act'))
return QS('#ops>a').click();
if (widget.is_open)
return widget.close();
if (showfile.active())
return thegrid.setvis(true);
if (!treectl.hidden)
return treectl.detree();
if (QS('#unsearch'))
return QS('#unsearch').click();
if (thegrid.en)
return ebi('griden').click();
if (action === HOTKEY_ACTIONS.ESCAPE) {
return escape();
}
var in_ftab = (aet == 'tr' || aet == 'td') && ae.closest('#files');
//TODO Ftab in context. can listen for these
if (in_ftab) {
var d = '', rem = 0;
if (aet == 'td') ae = ae.closest('tr'); //ie11
if (k == 'ArrowUp' || k == 'Up') d = 'previous';
if (k == 'ArrowDown' || k == 'Down') d = 'next';
if (k == 'PageUp') { d = 'previous'; rem = 0.6; }
if (k == 'PageDown') { d = 'next'; rem = 0.6; }
if (activeNode == 'td') activeElement = activeElement.closest('tr'); //ie11
if (kkey == 'ArrowUp' || kkey == 'Up') d = 'previous';
if (kkey == 'ArrowDown' || kkey == 'Down') d = 'next';
if (kkey == 'PageUp') { d = 'previous'; rem = 0.6; }
if (kkey == 'PageDown') { d = 'next'; rem = 0.6; }
if (d) {
fselfunw(e, ae, d, rem);
fselfunw(e, activeElement, d, rem);
return ev(e);
}
if (k == 'Space' || k == 'Spacebar' || k == ' ') {
clmod(ae, 'sel', 't');
msel.origin_tr(ae);
if (kkey == 'Space' || kkey == 'Spacebar' || kkey == ' ') {
clmod(activeElement, 'sel', 't');
msel.origin_tr(activeElement);
msel.selui();
return ev(e);
}
}
if (in_ftab || !aet || (ae && ae.closest('#ggrid'))) {
if ((kl == 'a') && ctrl(e)) {
if (in_ftab || !activeNode || (activeElement && activeElement.closest('#ggrid'))) {
if ((keyLower == 'a') && ctrl(e)) {
var ntot = treectl.lsc.files.length + treectl.lsc.dirs.length,
sel = msel.getsel(),
all = msel.getall();
@ -5982,124 +6021,111 @@ var ahotkeys = function (e) {
}
}
if (ae && ae.closest('pre')) {
if ((kl == 'a') && ctrl(e)) {
var sel = document.getSelection(),
ran = document.createRange();
//TODO does not work
if (action === HOTKEY_ACTIONS.SELECT_ALL) {
var sel = document.getSelection(),
ran = document.createRange();
sel.removeAllRanges();
ran.selectNode(ae.closest('pre'));
sel.addRange(ran);
return ev(e);
}
sel.removeAllRanges();
ran.selectNode(activeElement.closest('pre'));
sel.addRange(ran);
return ev(e);
}
if (k.endsWith('Enter') && ae && (ae.onclick || ae.hasAttribute('tabIndex')))
return ev(e) && ae.click() || true;
if (action === HOTKEY_ACTIONS.ENTER && activeElement && (activeElement.onclick || activeElement.hasAttribute('tabIndex')))
return ev(e) && activeElement.click() || true;
if (aet && aet != 'a' && aet != 'tr' && aet != 'td' && aet != 'div' && aet != 'pre')
return;
if (k == '?')
if (action === HOTKEY_ACTIONS.HELP)
return hkhelp();
if (!e.shiftKey && ctrl(e)) {
var sel = window.getSelection && window.getSelection() || {};
sel = sel && !sel.isCollapsed && sel.direction != 'none';
if (kl == 'x')
return fileman.cut(e);
if (kl == 'c' && !sel)
return fileman.cpy(e);
if (kl == 'v')
return fileman.d_paste(e);
if (kl == 'k')
return fileman.delete(e);
return;
if (action === HOTKEY_ACTIONS.CUT) {
return fileman.cut(e);
}
if (action === HOTKEY_ACTIONS.COPY) {
return fileman.cpy(e);
}
if (action === HOTKEY_ACTIONS.PASTE) {
return fileman.d_paste(e);
}
if (action === HOTKEY_ACTIONS.DELETE) {
return fileman.delete(e);
}
if (e.shiftKey && kl != 'a' && kl != 'd')
return;
if (action === HOTKEY_ACTIONS.PREV_SONG) {
return prev_song() || true;
}
if (action === HOTKEY_ACTIONS.NEXT_SONG) {
return next_song() || true;
}
if (action === HOTKEY_ACTIONS.TREE_PREV) {
return tree_neigh(-1, 1) || true;
}
if (action === HOTKEY_ACTIONS.TREE_NEXT) {
return tree_neigh(1, 1) || true;
}
if (/^[0-9]$/.test(k))
pos = parseInt(k) * 0.1;
if (action === HOTKEY_ACTIONS.PLAY_PAUSE)
return playpause() || true;
//TODO uncovered use case
if (/^[0-9]$/.test(kkey))
pos = parseInt(kkey) * 0.1;
if (pos !== -1)
return seek_au_mul(pos) || true;
if (kl == 'j')
return prev_song() || true;
if (action === HOTKEY_ACTIONS.FAST_FORWARD)
return seek_au_rel(10) || true;
if (action === HOTKEY_ACTIONS.REWIND)
return seek_au_rel(-10) || true;
if (kl == 'l')
return next_song() || true;
if (kl == 'p')
return playpause() || true;
n = kl == 'u' ? -10 : kl == 'o' ? 10 : 0;
if (n !== 0)
return seek_au_rel(n) || true;
if (kl == 'y')
if (action === HOTKEY_ACTIONS.DOWNLOAD)
return msel.getsel().length ? ebi('seldl').click() :
showfile.active() ? ebi('dldoc').click() :
dl_song();
n = kl == 'i' ? -1 : kl == 'k' ? 1 : 0;
if (n !== 0)
return tree_neigh(n, 1);
if (kl == 'm')
if (action === HOTKEY_ACTIONS.TREE_UP)
return tree_up();
if (kl == 'b')
if (action === HOTKEY_ACTIONS.TOGGLE_BREADCRUMBS)
return treectl.hidden ? treectl.entree() : treectl.detree();
if (kl == 'g')
if (action === HOTKEY_ACTIONS.TOGGLE_GRID)
return ebi('griden').click();
if (kl == 't')
if (action === HOTKEY_ACTIONS.TOGGLE_THUMBNAILS)
return ebi('thumbs').click();
if (kl == 'v')
if (action === HOTKEY_ACTIONS.TOGGLE_FOLDER_TREE)
return ebi('filetree').click();
if (k == 'F2')
if (action === HOTKEY_ACTIONS.RENAME)
return fileman.rename();
if (!treectl.hidden && (!e.shiftKey || !thegrid.en)) {
if (kl == 'a')
return QS('#twig').click();
if (action === HOTKEY_ACTIONS.NAVPANE_SHRINK)
return QS('#twig').click();
if (action === HOTKEY_ACTIONS.NAVPANE_GROW)
return QS('#twobytwo').click();
if (kl == 'd')
return QS('#twobytwo').click();
if (action === HOTKEY_ACTIONS.SELECT_FILE)
showfile.tglsel();
if (action === HOTKEY_ACTIONS.EDIT_FILE && ebi('editdoc').style.display != 'none')
ebi('editdoc').click();
if (action === HOTKEY_ACTIONS.SELECT_SONG) {
return sel_song();
}
if (showfile.active()) {
if (kl == 's')
showfile.tglsel();
if (kl == 'e' && ebi('editdoc').style.display != 'none')
ebi('editdoc').click();
if (action === HOTKEY_ACTIONS.TOGGLE_GRID_MULTISELECT) {
return ebi('gridsel').click();
}
if (mp && mp.au && !mp.au.paused) {
if (kl == 's')
return sel_song();
if (action === HOTKEY_ACTIONS.GRID_SHRINK_THUMBNAILS) {
return QSA('#ghead a[z]')[0].click();
}
if (thegrid.en) {
if (kl == 's')
return ebi('gridsel').click();
if (kl == 'a')
return QSA('#ghead a[z]')[0].click();
if (kl == 'd')
return QSA('#ghead a[z]')[1].click();
if (action === HOTKEY_ACTIONS.GRID_GROW_THUMBNAILS) {
return QSA('#ghead a[z]')[1].click();
}
};
@ -9380,6 +9406,42 @@ function reload_browser() {
thegrid.setdirty();
msel.render();
}
function escape() {
window.activeElement && window.activeElement.blur();
tt.hide();
if (ebi('hkhelp'))
return qsr('#hkhelp');
if (toast.visible)
return toast.hide();
if (ebi('rn_cancel'))
return ebi('rn_cancel').click();
if (ebi('sh_abrt'))
return ebi('sh_abrt').click();
if (QS('.opview.act'))
return QS('#ops>a').click();
if (widget.is_open)
return widget.close();
if (showfile.active())
return thegrid.setvis(true);
if (!treectl.hidden)
return treectl.detree();
if (QS('#unsearch'))
return QS('#unsearch').click();
if (thegrid.en)
return ebi('griden').click();
}
treectl.hydrate();
if (!fullui && (window.ui_nombar || /[?&]nombar\b/.exec(sloc0))) ebi('ops').style.display = 'none';

View file

@ -0,0 +1,291 @@
const HOTKEY_ACTIONS = {
ESCAPE: {
code: "escape",
tr: L.hotkeys.ESC,
},
ENTER: {
code: "enter",
tr: L.hotkeys.ENTER,
},
HELP: {
code: "?",
tr: L.hotkeys.HELP,
},
PREV_SONG: {
code: "prevSong",
tr: L.hotkeys.PREV_SONG,
},
NEXT_SONG: {
code: "nextSong",
tr: L.hotkeys.NEXT_SONG,
},
TREE_PREV: {
code: "treePrev",
tr: L.hotkeys.TREE_PREV,
},
TREE_NEXT: {
code: "treeNext",
tr: L.hotkeys.TREE_NEXT,
},
TREE_UP: {
code: "treeUp",
tr: L.hotkeys.TREE_UP,
},
PLAY_PAUSE: {
code: "playPause",
tr: L.hotkeys.PLAY_PAUSE,
},
DOWNLOAD: {
code: "download",
tr: L.hotkeys.DOWNLOAD,
},
CUT: {
code: "cut",
tr: L.hotkeys.CUT,
},
COPY: {
code: "copy",
tr: L.hotkeys.COPY,
context: (ctx) => {
var sel = window.getSelection && window.getSelection() || {};
sel = sel && !sel.isCollapsed && sel.direction != 'none';
return !sel;
},
},
PASTE: {
code: "paste",
tr: L.hotkeys.PASTE,
},
DELETE: {
code: "delete",
tr: L.hotkeys.DELETE,
},
FAST_FORWARD: {
code: "fastForward",
tr: L.hotkeys.FAST_FORWARD,
},
REWIND: {
code: "rewind",
tr: L.hotkeys.REWIND,
},
TOGGLE_BREADCRUMBS: {
code: "toggleBreadcrumbs",
tr: L.hotkeys.TOGGLE_BREADCRUMBS,
},
TOGGLE_GRID: {
code: "toggleGrid",
tr: L.hotkeys.TOGGLE_GRID,
},
TOGGLE_THUMBNAILS: {
code: "toggleThumbnails",
tr: L.hotkeys.TOGGLE_THUMBNAILS,
},
TOGGLE_FOLDER_TREE: {
code: "toggleFolderTree",
tr: L.hotkeys.TOGGLE_FOLDER_TREE,
},
RENAME: {
code: "rename",
tr: L.hotkeys.RENAME,
},
NAVPANE_SHRINK: {
code: "navpaneShrink",
tr: L.hotkeys.NAVPANE_SHRINK,
context: (ctx) => {
return !ctx.treectl.hidden && ctx.gridMode;
},
desc: L.hotkeys.NAVPANE_SHRINK_DESC
},
NAVPANE_GROW: {
code: "navpaneGrow",
tr: L.hotkeys.NAVPANE_GROW,
context: (ctx) => {
return !ctx.treectl.hidden && ctx.gridMode;
},
desc: L.hotkeys.NAVPANE_GROW_DESC
},
SELECT_ALL: {
code: "selectAll",
tr: L.hotkeys.SELECT_ALL,
context: (ctx) => {
return ctx.activeElement && ctx.activeElement.closest('pre')
},
},
SELECT_SONG: {
code: "selectSong",
tr: L.hotkeys.SELECT_SONG,
context: (ctx) => {
return ctx.mp && ctx.mp.au && !ctx.mp.au.paused;
},
desc: L.hotkeys.SELECT_SONG_DESC
},
SELECT_FILE: {
code: "selectFile",
tr: L.hotkeys.SELECT_FILE,
context: (ctx) => {
return ctx.showfile.active();
},
desc: L.hotkeys.SELECT_FILE_DESC
},
EDIT_FILE: {
code: "editFile",
tr: L.hotkeys.EDIT_FILE,
context: (ctx) => {
return ctx.showfile.active();
},
desc: L.hotkeys.EDIT_FILE_DESC
},
TOGGLE_GRID_MULTISELECT: {
code: "toggleGridMultiSelect",
tr: L.hotkeys.TOGGLE_GRID_MULTISELECT,
context: (ctx) => {
return ctx.gridMode;
},
desc: L.hotkeys.TOGGLE_GRID_MULTISELECT_DESC
},
GRID_SHRINK_THUMBNAILS: {
code: "gridShrinkThumbnails",
tr: L.hotkeys.GRID_SHRINK_THUMBNAILS,
context: (ctx) => {
return ctx.gridMode;
},
desc: L.hotkeys.GRID_SHRINK_THUMBNAILS_DESC
},
GRID_GROW_THUMBNAILS: {
code: "gridGrowThumbnails",
tr: L.hotkeys.GRID_GROW_THUMBNAILS,
context: (ctx) => {
return ctx.gridMode;
},
desc: L.hotkeys.GRID_GROW_THUMBNAILS_DESC
},
}
const defaultKeybindings = {
"escape": HOTKEY_ACTIONS.ESCAPE,
"enter": HOTKEY_ACTIONS.ENTER,
"shift+?": HOTKEY_ACTIONS.HELP,
"?": HOTKEY_ACTIONS.HELP,
"J": HOTKEY_ACTIONS.PREV_SONG,
"L": HOTKEY_ACTIONS.NEXT_SONG,
"K": HOTKEY_ACTIONS.TREE_NEXT,
"I": HOTKEY_ACTIONS.TREE_PREV,
"P": HOTKEY_ACTIONS.PLAY_PAUSE,
"Y": HOTKEY_ACTIONS.DOWNLOAD,
"ctrl+X": HOTKEY_ACTIONS.CUT,
"ctrl+C": HOTKEY_ACTIONS.COPY,
"ctrl+V": HOTKEY_ACTIONS.PASTE,
"ctrl+K": HOTKEY_ACTIONS.DELETE,
"meta+X": HOTKEY_ACTIONS.CUT,
"meta+C": HOTKEY_ACTIONS.COPY,
"meta+V": HOTKEY_ACTIONS.PASTE,
"meta+K": HOTKEY_ACTIONS.DELETE,
"O": HOTKEY_ACTIONS.FAST_FORWARD,
"U": HOTKEY_ACTIONS.REWIND,
"M": HOTKEY_ACTIONS.TREE_UP,
"B": HOTKEY_ACTIONS.TOGGLE_BREADCRUMBS,
"G": HOTKEY_ACTIONS.TOGGLE_GRID,
"T": HOTKEY_ACTIONS.TOGGLE_THUMBNAILS,
"V": HOTKEY_ACTIONS.TOGGLE_FOLDER_TREE,
"F2": HOTKEY_ACTIONS.RENAME,
"A": HOTKEY_ACTIONS.NAVPANE_SHRINK,
"D": HOTKEY_ACTIONS.NAVPANE_GROW,
"ctrl+A": HOTKEY_ACTIONS.SELECT_ALL,
"S": HOTKEY_ACTIONS.SELECT_SONG,
"E": HOTKEY_ACTIONS.EDIT_FILE,
"S": HOTKEY_ACTIONS.SELECT_FILE,
"shift+S": HOTKEY_ACTIONS.TOGGLE_GRID_MULTISELECT,
"shift+A": HOTKEY_ACTIONS.GRID_SHRINK_THUMBNAILS,
"shift+D": HOTKEY_ACTIONS.GRID_GROW_THUMBNAILS,
};
//TODO bit of a mess
const currentKeybindings = readKeybindings();
const keybindingTree = toTree(currentKeybindings);
/**
* Reads the keybindings from localStorage
* @returns {object} Current user Keybidnds
*/
function readKeybindings() {
const s = sread("keybindings");
if (!s) {
return defaultKeybindings;
}
return JSON.parse(s);
}
/**
* Resets the keybindings to default
* @returns {object} Default keybindings
*/
function resetKeybindings() {
srem("keybindings");
return defaultKeybindings;
}
/**
* Sets a keybinding for a specific action
* @param {string} combo Key combination
* @param {function} callback Callback function
*/
function setKeybinding(event) {
console.log('Not implemented setKeybinding', event);
// sset("keybindings", JSON.stringify(keybindings));
}
function getHotkeyEvent(event, context = {}) {
const hash = event.ctrlKey << 2 | event.metaKey << 1 | event.shiftKey;
let kkey = (event.key || event.code) + '';
const matchingHotkeys = keybindingTree[hash][kkey.toLowerCase()] || [];
console.log('getHotkeyEvent', event, context, hash, matchingHotkeys);
if (matchingHotkeys.length < 2) {
return matchingHotkeys[0];
}
var validIndex = -1;
for (var i = 0; i < matchingHotkeys.length; i++) {
var hotkey = matchingHotkeys[i];
if (hotkey.context) {
if (hotkey.context(context)) return hotkey;
} else {
validIndex = i;
}
}
console.log('More than 1 matching hotkey without sufficient context check found', event, context, hash, matchingHotkeys);
return matchingHotkeys[validIndex];
}
/**
* Convers human readable keybindings into a tree structure for fast lookup
* @param {object} keybindings
* @see getHotkeyEvent for usage
* @returns {object[]} tree of keybindings for fast lookup
*/
function toTree(keybindings) {
var tree = [...Array(8)].map(() => ({}));
for (var combo in keybindings) {
var action = keybindings[combo];
var parts = combo.split("+").map(function (p) {
return p.trim().toLowerCase();
});
var hash = 0;
hash += parts.indexOf("shift") !== -1 ? 1 : 0;
hash += parts.indexOf("ctrl") !== -1 ? 2 : 0;
hash += parts.indexOf("meta") !== -1 ? 4 : 0;
var key = parts[parts.length - 1]; // last part is the key
console.log('binding', combo, action, hash, key);
if (!tree[hash][key]) tree[hash][key] = [];
tree[hash][key].push(action);
}
console.log('Tree', keybindings, '=>', tree);
return tree;
}

View file

@ -2330,3 +2330,26 @@ function xhrchk(xhr, prefix, e404, lvl, tag) {
return fun(0, prefix + xhr.status + ": " + errtxt, tag);
}
/**
* Lazy load a script from the server
* @param {string} scriptName name of the script you want to load
* @param {function|undefined} callback callback to run when the script is loaded
*/
function lazyLoad(scriptName, callback) {
const scripts = Array.from(QSA('script'));
if (scripts.some(s => s.src.endsWith(scriptName))) {
callback && callback();
return;
}
var s = document.createElement('script');
s.src = '/.cpr/' + scriptName;
s.onload = function () {
callback && callback();
};
s.onerror = function (e) {
console.log('failed loading ' + scriptName, e);
};
document.head.appendChild(s);
}

View file

@ -137,3 +137,4 @@ copyparty/web/ui.css,
copyparty/web/up2k.js,
copyparty/web/util.js,
copyparty/web/w.hash.js,
copyparty/web/keybindings.js,