mirror of
https://github.com/9001/copyparty.git
synced 2025-11-24 07:23:22 -07:00
feat: WIP rebindable hotkleys
This commit is contained in:
parent
77f74ddb2f
commit
3c042149dd
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 + ' ' + (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';
|
||||
|
|
|
|||
291
copyparty/web/keybindings.js
Normal file
291
copyparty/web/keybindings.js
Normal 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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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,
|
||||
Loading…
Reference in a new issue