// Dragging files out of a file pane: onto another file pane or a folder in // it to move or copy them there (between machines and SSH hosts too, by the // transfer the move dialog uses), or into a terminal of the same place to // paste their paths. A terminal of another place refuses them, red, with // the reason. Held over a tab's title for half a second, the tab opens to // drop into. Esc or empty space cancels. Computers only. (function () { 'use strict'; if (!Workspace.desk) return; const FV = window.FileViews; const {toast, join, esc} = FV; let d = null, justDropped = false; const box = document.createElement('div'); box.className = 'hint drophint'; box.hidden = true; document.body.appendChild(box); // start: a pointerdown on a row of pane; the drag begins past 6px. function start(ev, pane, tr) { if (ev.button !== 0 || ev.target.closest('input, button, a, .act')) return; const name = tr.dataset.name; const names = pane.selected.has(name) ? [...pane.selected] : [name]; d = {pane, names, x0: ev.clientX, y0: ev.clientY, id: ev.pointerId, on: false, item: {source: pane.source, dir: pane.cwd, paths: names.map(n => join(pane.cwd, n))}}; } function begin() { d.on = true; const g = d.ghost = document.createElement('div'); g.className = 'drag-ghost'; g.textContent = d.names.length === 1 ? d.names[0] : d.names.length + ' элементов'; document.body.appendChild(g); document.body.classList.add('dnd'); getSelection().removeAllRanges(); } function show(r, label, bad) { box.hidden = false; box.classList.toggle('bad', !!bad); box.style.left = r.left + 'px'; box.style.top = r.top + 'px'; box.style.width = r.width + 'px'; box.style.height = r.height + 'px'; box.textContent = label; } // inside: dropping folder p into itself or below it. const inside = (dir, p) => dir === p || dir.startsWith(p.replace(/\/+$/, '') + '/'); function move(ev) { if (!d || ev.pointerId !== d.id) return; if (!d.on) { if (Math.hypot(ev.clientX - d.x0, ev.clientY - d.y0) < 6) return; begin(); } d.ghost.style.transform = 'translate(' + (ev.clientX + 12) + 'px,' + (ev.clientY + 10) + 'px)'; d.target = null; box.hidden = true; d.ghost.dataset.hint = ''; // Over a tab's title: it opens after half a second. const tab = Workspace.tabAt(ev.clientX, ev.clientY); if (tab !== d.overTab) { clearTimeout(d.tabTimer); d.overTab = tab; if (tab) d.tabTimer = setTimeout(() => { if (d && d.overTab === tab) Workspace.activate(tab); }, 500); } if (tab) { d.ghost.dataset.hint = 'открыть вкладку'; return; } const pane = Workspace.paneAt(ev.clientX, ev.clientY); if (!pane || !pane.dropTarget) return; const t = pane.dropTarget(ev.clientX, ev.clientY); const a = WSUtil.dropAction(d.item, t); const r = t.kind === 'folder' ? t.row.getBoundingClientRect() : pane.el.getBoundingClientRect(); if (a.kind === 'deny') { show(r, a.reason, true); return; } if (a.kind === 'none') return; if (a.kind === 'transfer' && t.source.id === d.item.source.id && d.item.paths.some(p => inside(t.dir, p))) { show(r, 'папку не положить в саму себя', true); return; } show(r, a.kind === 'paste' ? 'вставить путь' : 'сюда: ' + t.dir); d.target = {pane, t, a}; } function end(ev, cancel) { if (!d || (ev && ev.pointerId !== d.id)) return; const x = d; d = null; clearTimeout(x.tabTimer); if (!x.on) return; x.ghost.remove(); box.hidden = true; document.body.classList.remove('dnd'); justDropped = true; setTimeout(() => { justDropped = false; }, 0); if (cancel || !x.target) return; const {pane, t, a} = x.target; Workspace.setActivePane(pane); if (a.kind === 'paste') pane.drop(x.item); else if (a.kind === 'transfer') ask(x.item, x.pane, t, ev.clientX, ev.clientY); } window.addEventListener('pointermove', move); window.addEventListener('pointerup', ev => end(ev)); window.addEventListener('pointercancel', ev => end(ev, true)); window.addEventListener('keydown', ev => { if (ev.key === 'Escape' && d) end(null, true); }); // A drop is not a click on the row it started from. document.addEventListener('click', ev => { if (justDropped) { ev.stopPropagation(); ev.preventDefault(); } }, true); // ---------- the small window where the files were dropped ---------- const dlg = document.createElement('div'); dlg.className = 'dropdlg'; dlg.hidden = true; dlg.innerHTML = '
' + '' + '