chore (rewrite): drag and drop to move things around

This commit is contained in:
MickaelK 2024-06-14 08:12:16 +10:00
parent f0895fc483
commit f1375f27d0
5 changed files with 59 additions and 42 deletions

View file

@ -1,8 +1,14 @@
import { animate, slideYOut, slideYIn, opacityOut } from "../lib/animate.js";
import { loadCSS } from "../helpers/loader.js";
import { mv } from "../pages/filespage/model_files.js";
import { extractPath, isDir } from "../pages/filespage/helper.js";
import { extractPath, isDir, isNativeFileUpload } from "../pages/filespage/helper.js";
import { mv as mv$ } from "../pages/filespage/model_files.js";
import { mv as mvVL, withVirtualLayer } from "../pages/filespage/model_virtual_layer.js";
const mv = (from, to) => withVirtualLayer(
mv$(from, to),
mvVL(from, to),
);
class ComponentBreadcrumb extends window.HTMLDivElement {
constructor() {
@ -158,13 +164,6 @@ class ComponentBreadcrumb extends window.HTMLDivElement {
setupDragDropTarget() {
this.querySelectorAll("a.label").forEach(($folder) => {
const $path = $folder.closest(".component_path-element");
$folder.ondragover = (e) => {
e.preventDefault();
$path.classList.add("highlight");
};
$folder.ondragleave = () => {
$path.classList.remove("highlight");
};
$folder.ondrop = async (e) => {
$path.classList.remove("highlight");
const from = e.dataTransfer.getData("path");
@ -175,6 +174,14 @@ class ComponentBreadcrumb extends window.HTMLDivElement {
if (isDir(from)) to += "/";
await mv(from, to).toPromise();
};
$folder.ondragover = (e) => {
if (isNativeFileUpload(e)) return;
e.preventDefault();
$path.classList.add("highlight");
};
$folder.ondragleave = () => {
$path.classList.remove("highlight");
};
});
}

View file

@ -5,7 +5,7 @@ import { loadCSS } from "../../helpers/loader.js";
import { qs } from "../../lib/dom.js";
import { AjaxError } from "../../lib/error.js";
import assert from "../../lib/assert.js";
import { currentPath } from "./helper.js";
import { currentPath, isNativeFileUpload } from "./helper.js";
import { mkdir, save } from "./model_virtual_layer.js";
import t from "../../locales/index.js";
@ -55,22 +55,17 @@ function componentUploadFAB(render, { workers$ }) {
}
function componentFilezone(render, { workers$ }) {
const $target = document.body.querySelector(`[data-bind="filemanager-children"]`);
const selector = `[data-bind="filemanager-children"]`;
const $target = document.body.querySelector(selector);
$target.ondragenter = (e) => {
e.preventDefault();
e.stopPropagation();
if (!isNativeFileUpload(e)) return;
$target.classList.add("dropzone");
e.dataTransfer.setData("type", "fileupload");
};
$target.ondragover = (e) => {
e.preventDefault();
};
$target.ondragleave = () => {
// console.log("DRAGLEAVE");
};
$target.ondrop = async (e) => {
if (!isNativeFileUpload(e)) return;
$target.classList.remove("dropzone");
e.preventDefault();
e.stopPropagation();
const loadID = setTimeout(() => render(createElement("<div>LOADING</div>")), 2000);
if (e.dataTransfer.items instanceof window.DataTransferItemList) {
workers$.next(await processItems(e.dataTransfer.items));
@ -79,10 +74,17 @@ function componentFilezone(render, { workers$ }) {
} else {
assert.fail("NOT_IMPLEMENTED - unknown entry type in ctrl_upload.js", entry);
}
$target.classList.remove("dropzone");
clearTimeout(loadID);
render(createFragment(""));
};
$target.ondragleave = (e) => {
if (!isNativeFileUpload(e)) return;
if (!(e.relatedTarget === null || // eg: drag outside the window
!e.relatedTarget.closest(selector) // eg: drag on the breadcrumb, ...
)) return;
$target.classList.remove("dropzone");
};
$target.ondragover = (e) => e.preventDefault();
}
const MAX_WORKERS = 4;
@ -113,13 +115,12 @@ function componentUploadQueue(render, { workers$ }) {
// feature1: close the queue
onClick(qs($page, `img[alt="close"]`)).pipe(
rxjs.mergeMap(() => animate($page, { time: 200, keyframes: slideYOut(50) })),
// rxjs.mergeMap(() => animate($page, { time: 200, keyframes: slideYOut(50) })),
rxjs.tap(() => $page.classList.add("hidden")),
).subscribe();
// feature2: setup the task queue in the dom
workers$.subscribe(({ tasks }) => {
console.log("TASKS SETUP DOM", tasks);
if (tasks.length === 0) return;
$page.classList.remove("hidden");
const $fragment = document.createDocumentFragment();
@ -216,7 +217,6 @@ function componentUploadQueue(render, { workers$ }) {
};
const noFailureAllowed = (fn) => fn().catch(() => noFailureAllowed(fn));
workers$.subscribe(async ({ tasks: newTasks }) => {
console.log("TASKS PROCESS", newTasks);
tasks = tasks.concat(newTasks); // add new tasks to the pool
while(true) {
const nworker = reservations.indexOf(false);

View file

@ -111,3 +111,5 @@ function _moveHiddenFilesDownward(fileA, fileB) {
if (!aIsHidden && bIsHidden) return -1;
return 0;
}
export const isNativeFileUpload = (e) => JSON.stringify(e.dataTransfer.types) === '["Files"]';

View file

@ -63,7 +63,7 @@ export function touch(path) {
async afterSuccess() {
removeLoading(virtualFiles$, basepath, filename);
onDestroy(() => statePop(virtualFiles$, basepath, filename));
await fscache().update(basepath, ({ files, ...rest }) => ({
await fscache().update(basepath, ({ files = [], ...rest }) => ({
files: files.concat([file]),
...rest,
}));
@ -100,7 +100,7 @@ export function mkdir(path) {
async afterSuccess() {
removeLoading(virtualFiles$, basepath, dirname);
onDestroy(() => statePop(virtualFiles$, basepath, dirname));
await fscache().update(basepath, ({ files, ...rest }) => ({
await fscache().update(basepath, ({ files = [], ...rest }) => ({
files: files.concat([file]),
...rest,
}));
@ -137,7 +137,7 @@ export function save(path, size) {
async afterSuccess() {
removeLoading(virtualFiles$, basepath, filename);
onDestroy(() => statePop(virtualFiles$, basepath, filename));
await fscache().update(basepath, ({ files, ...rest }) => ({
await fscache().update(basepath, ({ files = [], ...rest }) => ({
files: files.concat([file]),
...rest,
}));
@ -190,7 +190,7 @@ export function rm(...paths) {
});
onDestroy(() => statePop(mutationFiles$, basepath, basepath));
await Promise.all(paths.map((path) => fscache().remove(path, false)));
await fscache().update(basepath, ({ files, ...rest }) => ({
await fscache().update(basepath, ({ files = [], ...rest }) => ({
files: files.filter(({ name }) => {
for (let i=0;i<arr.length;i+=2) {
if (name === arr[i+1]) {
@ -278,7 +278,7 @@ export function mv(fromPath, toPath) {
return file;
},
});
await fscache().update(fromBasepath, ({ files, ...rest }) => {
await fscache().update(fromBasepath, ({ files = [], ...rest }) => {
return {
files: files.map((file) => {
if (file.name === fromName) {
@ -300,11 +300,11 @@ export function mv(fromPath, toPath) {
});
onDestroy(() => statePop(mutationFiles$, fromBasepath, fromName));
statePop(virtualFiles$, toBasepath, toName);
await fscache().update(fromBasepath, ({ files, ...rest }) => ({
await fscache().update(fromBasepath, ({ files = [], ...rest }) => ({
files: files.filter((file) => file.name === fromName ? false : true),
...rest,
}))
await fscache().update(toBasepath, ({ files, ...rest }) => ({
await fscache().update(toBasepath, ({ files = [], ...rest }) => ({
files: files.concat([{
name: fromName,
time: new Date().getTime(),

View file

@ -3,12 +3,19 @@ import { qs } from "../../lib/dom.js";
import { animate, opacityIn } from "../../lib/animate.js";
import assert from "../../lib/assert.js";
import { extractPath, isDir } from "./helper.js";
import { mv } from "./model_files.js";
import { extractPath, isDir, isNativeFileUpload } from "./helper.js";
import { get as getConfig } from "./model_config.js";
import { files$ } from "./ctrl_filesystem.js";
import { addSelection, isSelected, clearSelection } from "./state_selection.js";
import { mv as mv$ } from "./model_files.js";
import { mv as mvVL, withVirtualLayer } from "./model_virtual_layer.js";
const mv = (from, to) => withVirtualLayer(
mv$(from, to),
mvVL(from, to),
);
const IMAGE = {
FILE: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiPgogIDxwYXRoIHN0eWxlPSJjb2xvcjojMDAwMDAwO3RleHQtaW5kZW50OjA7dGV4dC10cmFuc2Zvcm06bm9uZTtmaWxsOiM4YzhjOGM7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlLXdpZHRoOjAuOTg0ODEwNDEiIGQ9Im0gMiwxMy4wODI0MTIgMC4wMTk0NjIsMS40OTIzNDcgYyA1ZS02LDAuMjIyMTQ1IDAuMjA1NTkwMiwwLjQyNDI2MiAwLjQzMTE1MDIsMC40MjQyNzIgTCAxMy41ODk2MTIsMTUgQyAxMy44MTUxNzMsMTQuOTk5OTk1IDEzLjk5OTk5LDE0Ljc5Nzg3NCAxNCwxNC41NzU3MjkgdiAtMS40OTMzMTcgYyAtNC4xNzE4NjkyLDAuNjYyMDIzIC03LjY1MTY5MjgsMC4zOTg2OTYgLTEyLDAgeiIgLz4KICA8cGF0aCBzdHlsZT0iY29sb3I6IzAwMDAwMDt0ZXh0LWluZGVudDowO3RleHQtdHJhbnNmb3JtOm5vbmU7ZGlzcGxheTppbmxpbmU7ZmlsbDojYWFhYWFhO3N0cm9rZS13aWR0aDowLjk4NDA4MTI3IiBkPSJNIDIuMzUwMSwxLjAwMTMzMTIgQyAyLjE1MjU5LDEuMDM4MzI0NyAxLjk5NjU5LDEuMjI3MjcyMyAyLjAwMDA5LDEuNDI0OTM1NiBWIDE0LjEzMzQ1NyBjIDVlLTYsMC4yMjE4MTYgMC4yMDUyMywwLjQyMzYzNCAwLjQzMDc5LDAuNDIzNjQ0IGwgMTEuMTM5LC0xLjAxZS00IGMgMC4yMjU1NiwtNmUtNiAwLjQzMDExLC0wLjIwMDc1OCAwLjQzMDEyLC0wLjQyMjU3NCBsIDYuN2UtNCwtOS44MjI2NDI2IGMgLTIuNDg0MDQ2LC0xLjM1NTAwNiAtMi40MzUyMzQsLTIuMDMxMjI1NCAtMy41MDAxLC0zLjMwOTcwNyAtMC4wNDMsLTAuMDE1ODgyIDAuMDQ2LDAuMDAxNzQgMCwwIEwgMi40MzA2NywxLjAwMTEwOCBDIDIuNDAzODMsMC45OTg1OSAyLjM3Njc0LDAuOTk4NTkgMi4zNDk5LDEuMDAxMTA4IFoiIC8+CiAgPHBhdGggc3R5bGU9ImRpc3BsYXk6aW5saW5lO2ZpbGw6IzhjOGM4YztmaWxsLW9wYWNpdHk6MTtzdHJva2U6IzllNzU3NTtzdHJva2Utd2lkdGg6MDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2Utb3BhY2l0eToxIiBkPSJtIDEwLjUwMDU3LDEuMDAyMDc2NCBjIDAsMy4yNzY4MDI4IC0wLjAwNTIsMy4xNzM5MTYxIDAuMzYyOTIxLDMuMjY5ODIwMiAwLjI4MDEwOSwwLjA3Mjk4NCAzLjEzNzE4LDAuMDM5ODg3IDMuMTM3MTgsMC4wMzk4ODcgLTEuMTIwMDY3LC0xLjA1NTY2OTIgLTIuMzMzNCwtMi4yMDY0NzEzIC0zLjUwMDEsLTMuMzA5NzA3NCB6IiAvPgo8L3N2Zz4K",
FOLDER: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiPgogIDxnIHRyYW5zZm9ybT0ibWF0cml4KDAuODY2NjY0MzEsMCwwLDAuODY2NjcsLTE3Mi4wNDU3OCwtODY0LjMyNzU5KSIgc3R5bGU9ImZpbGw6Izc1YmJkOTtmaWxsLW9wYWNpdHk6MC45NDExNzY0NztmaWxsLXJ1bGU6ZXZlbm9kZCI+CiAgICA8cGF0aCBzdHlsZT0iZmlsbDojNzViYmQ5O2ZpbGwtb3BhY2l0eTowLjk0MTE3NjQ3O2ZpbGwtcnVsZTpldmVub2RkIiBkPSJtIDIwMC4yLDk5OS43MiBjIC0wLjI4OTEzLDAgLTAuNTMxMjUsMC4yNDIxIC0wLjUzMTI1LDAuNTMxMiB2IDEyLjc4NCBjIDAsMC4yOTg1IDAuMjMyNjQsMC41MzEyIDAuNTMxMjUsMC41MzEyIGggMTUuMDkxIGMgMC4yOTg2LDAgMC41MzEyNCwtMC4yMzI3IDAuNTMxMjQsLTAuNTMxMiBsIDRlLTQsLTEwLjQ3NCBjIDAsLTAuMjg4OSAtMC4yNDIxMSwtMC41MzM4IC0wLjUzMTI0LC0wLjUzMzggbCAtNy41NDU3LDVlLTQgLTIuMzA3NiwtMi4zMDc4MyB6IiAvPgogIDwvZz4KICA8ZyB0cmFuc2Zvcm09Im1hdHJpeCgwLjg2NjY3LDAsMCwwLjg2NjY3LC0xNzIuMDQ2OTIsLTg2NC43ODM0KSIgc3R5bGU9ImZpbGw6IzlhZDFlZDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6ZXZlbm9kZCI+CiAgICA8cGF0aCBzdHlsZT0iZmlsbDojOWFkMWVkO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpldmVub2RkIiBkPSJtIDIwMC4yLDk5OS43MiBjIC0wLjI4OTEzLDAgLTAuNTMxMjUsMC4yNDIxIC0wLjUzMTI1LDAuNTMxMiB2IDEyLjc4NCBjIDAsMC4yOTg1IDAuMjMyNjQsMC41MzEyIDAuNTMxMjUsMC41MzEyIGggMTUuMDkxIGMgMC4yOTg2LDAgMC41MzEyNCwtMC4yMzI3IDAuNTMxMjQsLTAuNTMxMiBsIDRlLTQsLTEwLjQ3NCBjIDAsLTAuMjg4OSAtMC4yNDIxMSwtMC41MzM4IC0wLjUzMTI0LC0wLjUzMzggbCAtNy41NDU3LDVlLTQgLTIuMzA3NiwtMi4zMDc4MyB6IiAvPgogIDwvZz4KPC9zdmc+Cg==",
@ -153,14 +160,6 @@ export function createThing({
e.dataTransfer.setData("path", path);
e.dataTransfer.setDragImage($thing, e.offsetX, -10);
};
$thing.ondragover = (e) => {
if ($thing.getAttribute("data-droptarget") !== "true") return;
e.preventDefault();
$thing.classList.add("hover");
};
$thing.ondragleave = () => {
$thing.classList.remove("hover");
};
$thing.ondrop = async (e) => {
$thing.classList.remove("hover");
const from = e.dataTransfer.getData("path");
@ -173,6 +172,15 @@ export function createThing({
}
await mv(from, to).toPromise();
};
$thing.ondragover = (e) => {
if(isNativeFileUpload(e)) return;
else if ($thing.getAttribute("data-droptarget") !== "true") return;
e.preventDefault();
$thing.classList.add("hover");
};
$thing.ondragleave = () => {
$thing.classList.remove("hover");
};
return $thing;
}