diff --git a/public/assets/pages/filespage/ctrl_filesystem.css b/public/assets/pages/filespage/ctrl_filesystem.css
index 35ab9b97..cef65071 100644
--- a/public/assets/pages/filespage/ctrl_filesystem.css
+++ b/public/assets/pages/filespage/ctrl_filesystem.css
@@ -1,6 +1,16 @@
[is="component_filesystem"] .component_filesystem {
padding-top: 3px;
}
+[is="component_filesystem"] .component_filesystem [data-target="dragselect"] {
+ position: fixed;
+ border-radius: 2px;
+ z-index: 3;
+ background: var(--primary);
+ border: 2px solid rgba(0, 0, 0, 0.1);
+ opacity: 0.25;
+ width: 500px;
+ height: 100px;
+}
[is="component_filesystem"] .component_filesystem [data-type="list"] {
grid-gap: 2px;
}
diff --git a/public/assets/pages/filespage/ctrl_filesystem.js b/public/assets/pages/filespage/ctrl_filesystem.js
index e93e33e3..f68efce5 100644
--- a/public/assets/pages/filespage/ctrl_filesystem.js
+++ b/public/assets/pages/filespage/ctrl_filesystem.js
@@ -28,6 +28,7 @@ export const files$ = new rxjs.BehaviorSubject(null);
export default async function(render) {
const $page = createElement(`
+
@@ -280,7 +281,7 @@ export default async function(render) {
rxjs.catchError(ctrlError()),
));
- // feature: selection
+ // feature: keyboard selection
effect(rxjs.fromEvent(window, "keydown").pipe(
rxjs.filter((e) => e.keyCode === 27),
rxjs.tap(() => clearSelection()),
@@ -313,13 +314,84 @@ export default async function(render) {
));
effect(getSelection$().pipe(rxjs.tap(() => {
for (const $thing of $page.querySelectorAll(".component_thing")) {
- const checked = isSelected(parseInt(assert.truthy($thing.getAttribute("data-n"))));
+ let checked = isSelected(parseInt(assert.truthy($thing.getAttribute("data-n"))));
+ if ($thing.getAttribute("data-selectable") === "false") checked = false;
$thing.classList.add(checked ? "selected" : "not-selected");
$thing.classList.remove(checked ? "not-selected" : "selected");
qs(assert.type($thing, HTMLElement), `input[type="checkbox"]`).checked = checked;
};
})));
+ // feature: mouse drag selection
+ const $dragContainer = assert.type($page.closest(".component_page_filespage"), HTMLElement);
+ const $dragselect = qs($page, `[data-target="dragselect"]`);
+ const dragmove = (x, y, w, h) => {
+ $dragselect.style.left = `${x}px`;
+ $dragselect.style.top = `${y}px`;
+ $dragselect.style.width = `${w}px`;
+ $dragselect.style.height = `${h}px`;
+ if ((w+1) * (h+1) < 50) {
+ $dragselect.style.display = "none";
+ return false;
+ }
+ $dragselect.style.display = "block";
+ return true;
+ };
+ if (isMobile === false) effect(rxjs.fromEvent($dragContainer, "mousedown").pipe(
+ rxjs.filter((e) => !e.target.closest(`[draggable="true"]`)),
+ rxjs.map((e) => ({
+ start: [e.clientX, e.clientY],
+ state: [...$page.querySelectorAll(".component_thing")].map(($file) => {
+ const bounds = $file.getBoundingClientRect();
+ const $checkbox = qs(assert.type($file, HTMLElement), ".component_checkbox");
+ return {
+ $checkbox,
+ checked: () => $checkbox.firstElementChild.checked,
+ bounds: {
+ x: bounds.x,
+ y: bounds.y,
+ w: bounds.width,
+ h: bounds.height,
+ },
+ };
+ }),
+ })),
+ rxjs.mergeMap(({ start, state }) => rxjs.fromEvent(document, "mousemove").pipe(
+ rxjs.takeUntil(rxjs.merge(
+ rxjs.fromEvent(window, "mouseup"),
+ rxjs.fromEvent(window, "keydown").pipe(rxjs.filter(({ key }) => key === "Escape")),
+ )),
+ rxjs.finalize(() => dragmove(0, 0, 0, 0)),
+ rxjs.map((e) => ({ start, end: [e.clientX, e.clientY], state })),
+ )),
+ rxjs.map(({ start, end, state }) => ({
+ state,
+ obj: {
+ x: Math.min(start[0], end[0]),
+ y: Math.min(start[1], end[1]),
+ w: Math.abs(start[0] - end[0]),
+ h: Math.abs(start[1] - end[1]),
+ },
+ })),
+ rxjs.filter(({ obj }) => dragmove(obj.x, obj.y, obj.w, obj.h)),
+ rxjs.tap(({ obj, state }) => {
+ for (let i=0; i
bounds.x + bounds.w ||
+ obj.y + obj.h < bounds.y ||
+ obj.y > bounds.y + bounds.h
+ );
+ if (collision && !checked()) {
+ $checkbox.click();
+ } else if (!collision && checked()) {
+ $checkbox.click();
+ }
+ }
+ }),
+ ));
+
// feature: remove long touch popup on mobile
const disableLongTouch = (e) => {
if (isMobile === false) return;