mirror of
				https://github.com/mickael-kerjean/filestash.git
				synced 2025-11-04 05:27:04 +08:00 
			
		
		
		
	chore (rewrite): rework filesystem page
This commit is contained in:
		@ -44,7 +44,6 @@ class Loader extends window.HTMLElement {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
customElements.define("component-loader", Loader);
 | 
					customElements.define("component-loader", Loader);
 | 
				
			||||||
 | 
					 | 
				
			||||||
export function createLoader($parent, opts = {}) {
 | 
					export function createLoader($parent, opts = {}) {
 | 
				
			||||||
    const { wait = 500 } = opts;
 | 
					    const { wait = 500 } = opts;
 | 
				
			||||||
    const cancel = effect(new rxjs.Observable((observer) => {
 | 
					    const cancel = effect(new rxjs.Observable((observer) => {
 | 
				
			||||||
@ -61,7 +60,7 @@ export function createLoader($parent, opts = {}) {
 | 
				
			|||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        `);
 | 
					        `);
 | 
				
			||||||
        const id = window.setTimeout(() => {
 | 
					        const id = window.setTimeout(() => {
 | 
				
			||||||
            $parent.appendChild($icon);
 | 
					            $parent.replaceChildren($icon);
 | 
				
			||||||
            animate($icon, { time: 1000, keyframes: opacityIn() });
 | 
					            animate($icon, { time: 1000, keyframes: opacityIn() });
 | 
				
			||||||
        }, wait);
 | 
					        }, wait);
 | 
				
			||||||
        return () => {
 | 
					        return () => {
 | 
				
			||||||
 | 
				
			|||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -4,7 +4,7 @@ import { qs } from "../../lib/dom.js";
 | 
				
			|||||||
import { animate } from "../../lib/animate.js";
 | 
					import { animate } from "../../lib/animate.js";
 | 
				
			||||||
import { loadCSS } from "../../helpers/loader.js";
 | 
					import { loadCSS } from "../../helpers/loader.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { getAction$, setAction } from "./model_action.js";
 | 
					import { getAction$, setAction } from "./state_event.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default async function(render) {
 | 
					export default async function(render) {
 | 
				
			||||||
    const $node = createElement(`
 | 
					    const $node = createElement(`
 | 
				
			||||||
 | 
				
			|||||||
@ -3,8 +3,10 @@ import rxjs, { effect, applyMutation, onClick, preventDefault } from "../../lib/
 | 
				
			|||||||
import { animate, slideYIn } from "../../lib/animate.js";
 | 
					import { animate, slideYIn } from "../../lib/animate.js";
 | 
				
			||||||
import { loadCSS } from "../../helpers/loader.js";
 | 
					import { loadCSS } from "../../helpers/loader.js";
 | 
				
			||||||
import { qs, qsa } from "../../lib/dom.js";
 | 
					import { qs, qsa } from "../../lib/dom.js";
 | 
				
			||||||
import { getSelection$, clearSelection } from "./model_files.js";
 | 
					
 | 
				
			||||||
import { getAction$, setAction } from "./model_action.js";
 | 
					import "../../components/dropdown.js";
 | 
				
			||||||
 | 
					import "../../components/icon.js";
 | 
				
			||||||
 | 
					import { createModal } from "../../components/modal.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import componentShare from "./modal_share.js";
 | 
					import componentShare from "./modal_share.js";
 | 
				
			||||||
import componentEmbed from "./modal_embed.js";
 | 
					import componentEmbed from "./modal_embed.js";
 | 
				
			||||||
@ -12,11 +14,9 @@ import componentTag from "./modal_tag.js";
 | 
				
			|||||||
import componentRename from "./modal_rename.js";
 | 
					import componentRename from "./modal_rename.js";
 | 
				
			||||||
import componentDelete from "./modal_delete.js";
 | 
					import componentDelete from "./modal_delete.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "../../components/dropdown.js";
 | 
					import { getSelection$, clearSelection } from "./state_selection.js";
 | 
				
			||||||
import "../../components/icon.js";
 | 
					import { getAction$, setAction } from "./state_event.js";
 | 
				
			||||||
import { createModal } from "../../components/modal.js";
 | 
					import { setState } from "./state_filesystem.js";
 | 
				
			||||||
 | 
					 | 
				
			||||||
import { setState } from "./ctrl_filesystem_state.js";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const modalOpt = {
 | 
					const modalOpt = {
 | 
				
			||||||
    withButtonsRight: "OK",
 | 
					    withButtonsRight: "OK",
 | 
				
			||||||
@ -139,13 +139,14 @@ function componentRight(render) {
 | 
				
			|||||||
    effect(getSelection$().pipe(
 | 
					    effect(getSelection$().pipe(
 | 
				
			||||||
        rxjs.filter((selections) => selections.length === 0),
 | 
					        rxjs.filter((selections) => selections.length === 0),
 | 
				
			||||||
        rxjs.map(() => render(createFragment(`
 | 
					        rxjs.map(() => render(createFragment(`
 | 
				
			||||||
 | 
					            <form style="display: inline-block;" onsubmit="event.preventDefault()">
 | 
				
			||||||
                <input class="hidden" placeholder="search" style="
 | 
					                <input class="hidden" placeholder="search" style="
 | 
				
			||||||
                    background: transparent;
 | 
					                    background: transparent;
 | 
				
			||||||
                    border: none;
 | 
					                    border: none;
 | 
				
			||||||
                    padding-left: 5px;
 | 
					                    padding-left: 5px;
 | 
				
			||||||
                    color: var(--color);
 | 
					                    color: var(--color);
 | 
				
			||||||
                 font-size: 0.95rem;
 | 
					                    font-size: 0.95rem;">
 | 
				
			||||||
                ">
 | 
					            </form>
 | 
				
			||||||
            <button data-action="search">
 | 
					            <button data-action="search">
 | 
				
			||||||
                <img class="component_icon" draggable="false" src="data:image/svg+xml;base64,${ICONS.MAGNIFYING_GLASS}" alt="search" />
 | 
					                <img class="component_icon" draggable="false" src="data:image/svg+xml;base64,${ICONS.MAGNIFYING_GLASS}" alt="search" />
 | 
				
			||||||
            </button>
 | 
					            </button>
 | 
				
			||||||
@ -257,8 +258,15 @@ function componentRight(render) {
 | 
				
			|||||||
                            time: 100,
 | 
					                            time: 100,
 | 
				
			||||||
                        });
 | 
					                        });
 | 
				
			||||||
                        $input.classList.add("hidden");
 | 
					                        $input.classList.add("hidden");
 | 
				
			||||||
 | 
					                        // setState("search", ""); TODO
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					                    return $input;
 | 
				
			||||||
                }),
 | 
					                }),
 | 
				
			||||||
 | 
					                rxjs.mergeMap(($input) => rxjs.merge(
 | 
				
			||||||
 | 
					                    rxjs.fromEvent($input, "input").pipe(rxjs.debounceTime(500)),
 | 
				
			||||||
 | 
					                    rxjs.fromEvent($input, "change"),
 | 
				
			||||||
 | 
					                ).pipe(rxjs.map(() => $input.value), rxjs.distinctUntilChanged())),
 | 
				
			||||||
 | 
					                rxjs.tap((val) => setState("search", val)),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
        )),
 | 
					        )),
 | 
				
			||||||
    ));
 | 
					    ));
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										85
									
								
								public/assets/pages/filespage/helper.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								public/assets/pages/filespage/helper.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,85 @@
 | 
				
			|||||||
 | 
					import { extname } from "../../lib/path.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function sort(files, type) {
 | 
				
			||||||
 | 
					    if (type === "name") {
 | 
				
			||||||
 | 
					        return sortByName(files);
 | 
				
			||||||
 | 
					    } else if (type === "date") {
 | 
				
			||||||
 | 
					        return sortByDate(files);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        return sortByType(files);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function _moveLoadingDownward(fileA, fileB) {
 | 
				
			||||||
 | 
					    if (fileA.icon === "loading" && fileB.icon !== "loading") {
 | 
				
			||||||
 | 
					        return +1;
 | 
				
			||||||
 | 
					    } else if (fileA.icon !== "loading" && fileB.icon === "loading") {
 | 
				
			||||||
 | 
					        return -1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					function _moveFolderUpward(fileA, fileB) {
 | 
				
			||||||
 | 
					    if (["directory", "link"].indexOf(fileA.type) === -1 &&
 | 
				
			||||||
 | 
					        ["directory", "link"].indexOf(fileB.type) !== -1) {
 | 
				
			||||||
 | 
					        return +1;
 | 
				
			||||||
 | 
					    } else if (["directory", "link"].indexOf(fileA.type) !== -1 &&
 | 
				
			||||||
 | 
					               ["directory", "link"].indexOf(fileB.type) === -1) {
 | 
				
			||||||
 | 
					        return -1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					function _moveHiddenFilesDownward(fileA, fileB) {
 | 
				
			||||||
 | 
					    if (fileA.name[0] === "." && fileB.name[0] !== ".") return +1;
 | 
				
			||||||
 | 
					    else if (fileA.name[0] !== "." && fileB.name[0] === ".") return -1;
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					function sortByType(files) {
 | 
				
			||||||
 | 
					    return files.sort((fileA, fileB) => {
 | 
				
			||||||
 | 
					        let tmp = _moveLoadingDownward(fileA, fileB);
 | 
				
			||||||
 | 
					        if (tmp !== 0) return tmp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tmp = _moveFolderUpward(fileA, fileB);
 | 
				
			||||||
 | 
					        if (tmp !== 0) return tmp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tmp = _moveHiddenFilesDownward(fileA, fileB);
 | 
				
			||||||
 | 
					        if (tmp !== 0) return tmp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const aExt = extname(fileA.name.toLowerCase());
 | 
				
			||||||
 | 
					        const bExt = extname(fileB.name.toLowerCase());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (fileA.name.toLowerCase() === fileB.name.toLowerCase()) {
 | 
				
			||||||
 | 
					            return fileA.name > fileB.name ? +1 : -1;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            if (aExt !== bExt) return aExt > bExt ? +1 : -1;
 | 
				
			||||||
 | 
					            else return fileA.name.toLowerCase() > fileB.name.toLowerCase() ? +1 : -1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					function sortByName(files) {
 | 
				
			||||||
 | 
					    return files.sort((fileA, fileB) => {
 | 
				
			||||||
 | 
					        let tmp = _moveLoadingDownward(fileA, fileB);
 | 
				
			||||||
 | 
					        if (tmp !== 0) return tmp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tmp = _moveFolderUpward(fileA, fileB);
 | 
				
			||||||
 | 
					        if (tmp !== 0) return tmp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tmp = _moveHiddenFilesDownward(fileA, fileB);
 | 
				
			||||||
 | 
					        if (tmp !== 0) return tmp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (fileA.name.toLowerCase() === fileB.name.toLowerCase()) {
 | 
				
			||||||
 | 
					            return fileA.name > fileB.name ? +1 : -1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return fileA.name.toLowerCase() > fileB.name.toLowerCase() ? +1 : -1;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					function sortByDate(files) {
 | 
				
			||||||
 | 
					    return files.sort((fileA, fileB) => {
 | 
				
			||||||
 | 
					        const tmp = _moveLoadingDownward(fileA, fileB);
 | 
				
			||||||
 | 
					        if (tmp !== 0) return tmp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (fileB.time === fileA.time) {
 | 
				
			||||||
 | 
					            return fileA.name > fileB.name ? +1 : -1;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return fileB.time - fileA.time;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,42 +1,6 @@
 | 
				
			|||||||
import { onDestroy } from "../../lib/skeleton/index.js";
 | 
					 | 
				
			||||||
import rxjs from "../../lib/rx.js";
 | 
					import rxjs from "../../lib/rx.js";
 | 
				
			||||||
import ajax from "../../lib/ajax.js";
 | 
					import ajax from "../../lib/ajax.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const selection$ = new rxjs.BehaviorSubject([]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
onDestroy(() => selection$.next([]));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function addSelection({ name, type, shift, n }) {
 | 
					 | 
				
			||||||
    selection$.next(
 | 
					 | 
				
			||||||
        selection$.value
 | 
					 | 
				
			||||||
            .concat({ name, type, shift, n })
 | 
					 | 
				
			||||||
            .sort((prev, curr) => prev.n - curr.n)
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function clearSelection() {
 | 
					 | 
				
			||||||
    selection$.next([]);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function getSelection$() {
 | 
					 | 
				
			||||||
    return selection$.asObservable();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function isSelected(n) {
 | 
					 | 
				
			||||||
    let isChecked = false;
 | 
					 | 
				
			||||||
    for (let i=0;i<selection$.value.length;i++) {
 | 
					 | 
				
			||||||
        if (selection$.value[i]["n"] === n) isChecked = !isChecked;
 | 
					 | 
				
			||||||
        else if (selection$.value[i]["shift"]
 | 
					 | 
				
			||||||
                 && isBetween(n, selection$.value[i-1]["n"], selection$.value[i]["n"]))
 | 
					 | 
				
			||||||
            isChecked = !isChecked
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return isChecked;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function isBetween(n, lowerBound, higherBound) {
 | 
					 | 
				
			||||||
    return n <= higherBound && n >= lowerBound;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// export function ls() {
 | 
					// export function ls() {
 | 
				
			||||||
//     return rxjs.from(new Error("missing cache")).pipe(
 | 
					//     return rxjs.from(new Error("missing cache")).pipe(
 | 
				
			||||||
//         rxjs.catchError(() => rxjs.of({ files: null })),
 | 
					//         rxjs.catchError(() => rxjs.of({ files: null })),
 | 
				
			||||||
@ -57,16 +21,20 @@ function isBetween(n, lowerBound, higherBound) {
 | 
				
			|||||||
//     )
 | 
					//     )
 | 
				
			||||||
// }
 | 
					// }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function ls() {
 | 
					export function search(term) {
 | 
				
			||||||
    return rxjs.pipe(
 | 
					    return rxjs.of([]).pipe(
 | 
				
			||||||
        rxjs.mergeMap((path) => ajax({
 | 
					        rxjs.delay(1500),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function ls(path) {
 | 
				
			||||||
 | 
					    return ajax({
 | 
				
			||||||
        url: `/api/files/ls?path=${path}`,
 | 
					        url: `/api/files/ls?path=${path}`,
 | 
				
			||||||
        responseType: "json"
 | 
					        responseType: "json"
 | 
				
			||||||
    }).pipe(rxjs.map(({ responseJSON }) => ({
 | 
					    }).pipe(rxjs.map(({ responseJSON }) => ({
 | 
				
			||||||
        files: responseJSON.results.sort(sortByDefault),
 | 
					        files: responseJSON.results.sort(sortByDefault),
 | 
				
			||||||
        path,
 | 
					        path,
 | 
				
			||||||
        })))),
 | 
					    })));
 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const sortByDefault = (fileA, fileB) => {
 | 
					const sortByDefault = (fileA, fileB) => {
 | 
				
			||||||
 | 
				
			|||||||
@ -2,10 +2,10 @@ import rxjs, { effect, preventDefault } from "../../lib/rx.js";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const state$ = new rxjs.BehaviorSubject({
 | 
					const state$ = new rxjs.BehaviorSubject({
 | 
				
			||||||
    view: "grid",
 | 
					    view: "grid",
 | 
				
			||||||
    sort: null,
 | 
					    sort: "type",
 | 
				
			||||||
    show_hidden: false,
 | 
					    show_hidden: false,
 | 
				
			||||||
    order: null,
 | 
					    order: null,
 | 
				
			||||||
    search_mode: false,
 | 
					    search: "",
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getState$ = () => state$.asObservable();
 | 
					export const getState$ = () => state$.asObservable();
 | 
				
			||||||
							
								
								
									
										38
									
								
								public/assets/pages/filespage/state_selection.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								public/assets/pages/filespage/state_selection.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					import rxjs from "../../lib/rx.js";
 | 
				
			||||||
 | 
					import ajax from "../../lib/ajax.js";
 | 
				
			||||||
 | 
					import { onDestroy } from "../../lib/skeleton/index.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const selection$ = new rxjs.BehaviorSubject([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onDestroy(() => selection$.next([]));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function addSelection({ name, type, shift, n }) {
 | 
				
			||||||
 | 
					    selection$.next(
 | 
				
			||||||
 | 
					        selection$.value
 | 
				
			||||||
 | 
					            .concat({ name, type, shift, n })
 | 
				
			||||||
 | 
					            .sort((prev, curr) => prev.n - curr.n)
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function clearSelection() {
 | 
				
			||||||
 | 
					    selection$.next([]);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function getSelection$() {
 | 
				
			||||||
 | 
					    return selection$.asObservable();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function isSelected(n) {
 | 
				
			||||||
 | 
					    let isChecked = false;
 | 
				
			||||||
 | 
					    for (let i=0;i<selection$.value.length;i++) {
 | 
				
			||||||
 | 
					        if (selection$.value[i]["n"] === n) isChecked = !isChecked;
 | 
				
			||||||
 | 
					        else if (selection$.value[i]["shift"]
 | 
				
			||||||
 | 
					                 && isBetween(n, selection$.value[i-1]["n"], selection$.value[i]["n"]))
 | 
				
			||||||
 | 
					            isChecked = !isChecked
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return isChecked;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isBetween(n, lowerBound, higherBound) {
 | 
				
			||||||
 | 
					    return n <= higherBound && n >= lowerBound;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,5 +1,8 @@
 | 
				
			|||||||
import { createElement } from "../../lib/skeleton/index.js";
 | 
					import { createElement } from "../../lib/skeleton/index.js";
 | 
				
			||||||
import { addSelection, isSelected } from "./model_files.js";
 | 
					import { qs } from "../../lib/dom.js";
 | 
				
			||||||
 | 
					import assert from "../../lib/assert.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { addSelection, isSelected } from "./state_selection.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const IMAGE = {
 | 
					const IMAGE = {
 | 
				
			||||||
    FILE: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiPgogIDxwYXRoIHN0eWxlPSJjb2xvcjojMDAwMDAwO3RleHQtaW5kZW50OjA7dGV4dC10cmFuc2Zvcm06bm9uZTtmaWxsOiM4YzhjOGM7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlLXdpZHRoOjAuOTg0ODEwNDEiIGQ9Im0gMiwxMy4wODI0MTIgMC4wMTk0NjIsMS40OTIzNDcgYyA1ZS02LDAuMjIyMTQ1IDAuMjA1NTkwMiwwLjQyNDI2MiAwLjQzMTE1MDIsMC40MjQyNzIgTCAxMy41ODk2MTIsMTUgQyAxMy44MTUxNzMsMTQuOTk5OTk1IDEzLjk5OTk5LDE0Ljc5Nzg3NCAxNCwxNC41NzU3MjkgdiAtMS40OTMzMTcgYyAtNC4xNzE4NjkyLDAuNjYyMDIzIC03LjY1MTY5MjgsMC4zOTg2OTYgLTEyLDAgeiIgLz4KICA8cGF0aCBzdHlsZT0iY29sb3I6IzAwMDAwMDt0ZXh0LWluZGVudDowO3RleHQtdHJhbnNmb3JtOm5vbmU7ZGlzcGxheTppbmxpbmU7ZmlsbDojYWFhYWFhO3N0cm9rZS13aWR0aDowLjk4NDA4MTI3IiBkPSJNIDIuMzUwMSwxLjAwMTMzMTIgQyAyLjE1MjU5LDEuMDM4MzI0NyAxLjk5NjU5LDEuMjI3MjcyMyAyLjAwMDA5LDEuNDI0OTM1NiBWIDE0LjEzMzQ1NyBjIDVlLTYsMC4yMjE4MTYgMC4yMDUyMywwLjQyMzYzNCAwLjQzMDc5LDAuNDIzNjQ0IGwgMTEuMTM5LC0xLjAxZS00IGMgMC4yMjU1NiwtNmUtNiAwLjQzMDExLC0wLjIwMDc1OCAwLjQzMDEyLC0wLjQyMjU3NCBsIDYuN2UtNCwtOS44MjI2NDI2IGMgLTIuNDg0MDQ2LC0xLjM1NTAwNiAtMi40MzUyMzQsLTIuMDMxMjI1NCAtMy41MDAxLC0zLjMwOTcwNyAtMC4wNDMsLTAuMDE1ODgyIDAuMDQ2LDAuMDAxNzQgMCwwIEwgMi40MzA2NywxLjAwMTEwOCBDIDIuNDAzODMsMC45OTg1OSAyLjM3Njc0LDAuOTk4NTkgMi4zNDk5LDEuMDAxMTA4IFoiIC8+CiAgPHBhdGggc3R5bGU9ImRpc3BsYXk6aW5saW5lO2ZpbGw6IzhjOGM4YztmaWxsLW9wYWNpdHk6MTtzdHJva2U6IzllNzU3NTtzdHJva2Utd2lkdGg6MDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2Utb3BhY2l0eToxIiBkPSJtIDEwLjUwMDU3LDEuMDAyMDc2NCBjIDAsMy4yNzY4MDI4IC0wLjAwNTIsMy4xNzM5MTYxIDAuMzYyOTIxLDMuMjY5ODIwMiAwLjI4MDEwOSwwLjA3Mjk4NCAzLjEzNzE4LDAuMDM5ODg3IDMuMTM3MTgsMC4wMzk4ODcgLTEuMTIwMDY3LC0xLjA1NTY2OTIgLTIuMzMzNCwtMi4yMDY0NzEzIC0zLjUwMDEsLTMuMzA5NzA3NCB6IiAvPgo8L3N2Zz4K",
 | 
					    FILE: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiPgogIDxwYXRoIHN0eWxlPSJjb2xvcjojMDAwMDAwO3RleHQtaW5kZW50OjA7dGV4dC10cmFuc2Zvcm06bm9uZTtmaWxsOiM4YzhjOGM7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlLXdpZHRoOjAuOTg0ODEwNDEiIGQ9Im0gMiwxMy4wODI0MTIgMC4wMTk0NjIsMS40OTIzNDcgYyA1ZS02LDAuMjIyMTQ1IDAuMjA1NTkwMiwwLjQyNDI2MiAwLjQzMTE1MDIsMC40MjQyNzIgTCAxMy41ODk2MTIsMTUgQyAxMy44MTUxNzMsMTQuOTk5OTk1IDEzLjk5OTk5LDE0Ljc5Nzg3NCAxNCwxNC41NzU3MjkgdiAtMS40OTMzMTcgYyAtNC4xNzE4NjkyLDAuNjYyMDIzIC03LjY1MTY5MjgsMC4zOTg2OTYgLTEyLDAgeiIgLz4KICA8cGF0aCBzdHlsZT0iY29sb3I6IzAwMDAwMDt0ZXh0LWluZGVudDowO3RleHQtdHJhbnNmb3JtOm5vbmU7ZGlzcGxheTppbmxpbmU7ZmlsbDojYWFhYWFhO3N0cm9rZS13aWR0aDowLjk4NDA4MTI3IiBkPSJNIDIuMzUwMSwxLjAwMTMzMTIgQyAyLjE1MjU5LDEuMDM4MzI0NyAxLjk5NjU5LDEuMjI3MjcyMyAyLjAwMDA5LDEuNDI0OTM1NiBWIDE0LjEzMzQ1NyBjIDVlLTYsMC4yMjE4MTYgMC4yMDUyMywwLjQyMzYzNCAwLjQzMDc5LDAuNDIzNjQ0IGwgMTEuMTM5LC0xLjAxZS00IGMgMC4yMjU1NiwtNmUtNiAwLjQzMDExLC0wLjIwMDc1OCAwLjQzMDEyLC0wLjQyMjU3NCBsIDYuN2UtNCwtOS44MjI2NDI2IGMgLTIuNDg0MDQ2LC0xLjM1NTAwNiAtMi40MzUyMzQsLTIuMDMxMjI1NCAtMy41MDAxLC0zLjMwOTcwNyAtMC4wNDMsLTAuMDE1ODgyIDAuMDQ2LDAuMDAxNzQgMCwwIEwgMi40MzA2NywxLjAwMTEwOCBDIDIuNDAzODMsMC45OTg1OSAyLjM3Njc0LDAuOTk4NTkgMi4zNDk5LDEuMDAxMTA4IFoiIC8+CiAgPHBhdGggc3R5bGU9ImRpc3BsYXk6aW5saW5lO2ZpbGw6IzhjOGM4YztmaWxsLW9wYWNpdHk6MTtzdHJva2U6IzllNzU3NTtzdHJva2Utd2lkdGg6MDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2Utb3BhY2l0eToxIiBkPSJtIDEwLjUwMDU3LDEuMDAyMDc2NCBjIDAsMy4yNzY4MDI4IC0wLjAwNTIsMy4xNzM5MTYxIDAuMzYyOTIxLDMuMjY5ODIwMiAwLjI4MDEwOSwwLjA3Mjk4NCAzLjEzNzE4LDAuMDM5ODg3IDMuMTM3MTgsMC4wMzk4ODcgLTEuMTIwMDY3LC0xLjA1NTY2OTIgLTIuMzMzNCwtMi4yMDY0NzEzIC0zLjUwMDEsLTMuMzA5NzA3NCB6IiAvPgo8L3N2Zz4K",
 | 
				
			||||||
@ -19,7 +22,7 @@ const $tmpl = createElement(`
 | 
				
			|||||||
                        <span>__TEMPLATE__<span class="extension"></span></span>
 | 
					                        <span>__TEMPLATE__<span class="extension"></span></span>
 | 
				
			||||||
                    </span>
 | 
					                    </span>
 | 
				
			||||||
                </span>
 | 
					                </span>
 | 
				
			||||||
                <span class="component_datetime"><span>06/06/2020</span></span>
 | 
					                <span class="component_datetime"></span>
 | 
				
			||||||
                <div class="component_action"></div>
 | 
					                <div class="component_action"></div>
 | 
				
			||||||
                <div class="selectionOverlay"></div>
 | 
					                <div class="selectionOverlay"></div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
@ -35,6 +38,7 @@ const $tmpl = createElement(`
 | 
				
			|||||||
export function createThing({
 | 
					export function createThing({
 | 
				
			||||||
    name = null,
 | 
					    name = null,
 | 
				
			||||||
    type = "N/A",
 | 
					    type = "N/A",
 | 
				
			||||||
 | 
					    time = 0,
 | 
				
			||||||
    path = null,
 | 
					    path = null,
 | 
				
			||||||
    // size = 0,
 | 
					    // size = 0,
 | 
				
			||||||
    // time = null,
 | 
					    // time = null,
 | 
				
			||||||
@ -44,9 +48,9 @@ export function createThing({
 | 
				
			|||||||
    n = 0,
 | 
					    n = 0,
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
    const $thing = $tmpl.cloneNode(true);
 | 
					    const $thing = $tmpl.cloneNode(true);
 | 
				
			||||||
    if (!($thing instanceof window.HTMLElement)) throw new Error("assertion failed: $thing must be an HTMLELement");
 | 
					    assert.type($thing, window.HTMLElement);
 | 
				
			||||||
    const $label = $thing.querySelector(".component_filename .file-details > span");
 | 
					    const $label = qs($thing, ".component_filename .file-details > span");
 | 
				
			||||||
    if (!($label instanceof window.HTMLElement)) throw new Error("assertion failed: $label must be an HTMLELement");
 | 
					    const $time = qs($thing, ".component_datetime");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    $label.textContent = name;
 | 
					    $label.textContent = name;
 | 
				
			||||||
    $thing.querySelector("a").setAttribute("href", link);
 | 
					    $thing.querySelector("a").setAttribute("href", link);
 | 
				
			||||||
@ -54,10 +58,7 @@ export function createThing({
 | 
				
			|||||||
    $thing.setAttribute("data-droptarget", type === "directory");
 | 
					    $thing.setAttribute("data-droptarget", type === "directory");
 | 
				
			||||||
    $thing.setAttribute("data-n", n);
 | 
					    $thing.setAttribute("data-n", n);
 | 
				
			||||||
    $thing.classList.add("view-" + view);
 | 
					    $thing.classList.add("view-" + view);
 | 
				
			||||||
    const sideEffectSelection = ($el, checked) => {
 | 
					    $time.textContent = formatTime(new Date(time));
 | 
				
			||||||
        $el.classList.add(checked ? "selected" : "not-selected");
 | 
					 | 
				
			||||||
        $el.querySelector(`input[type="checkbox"]`).checked = checked;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    sideEffectSelection($thing, isSelected(n));
 | 
					    sideEffectSelection($thing, isSelected(n));
 | 
				
			||||||
    if (type === "hidden") $thing.classList.add("hidden");
 | 
					    if (type === "hidden") $thing.classList.add("hidden");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -79,7 +80,7 @@ export function createThing({
 | 
				
			|||||||
        const crt = $thing.cloneNode(true);
 | 
					        const crt = $thing.cloneNode(true);
 | 
				
			||||||
        $thing.style.opacity = "0.7";
 | 
					        $thing.style.opacity = "0.7";
 | 
				
			||||||
        const $box = crt.querySelector(".box");
 | 
					        const $box = crt.querySelector(".box");
 | 
				
			||||||
        crt.style.opacity = "0.2 "
 | 
					        crt.style.opacity = "0.2";
 | 
				
			||||||
        crt.style.backgroundColor = "var(--border)";
 | 
					        crt.style.backgroundColor = "var(--border)";
 | 
				
			||||||
        $box.style.backgroundColor = "inherit";
 | 
					        $box.style.backgroundColor = "inherit";
 | 
				
			||||||
        $box.style.border = "none";
 | 
					        $box.style.border = "none";
 | 
				
			||||||
@ -102,3 +103,17 @@ export function createThing({
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
    return $thing;
 | 
					    return $thing;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function sideEffectSelection($el, checked) {
 | 
				
			||||||
 | 
					    $el.classList.add(checked ? "selected" : "not-selected");
 | 
				
			||||||
 | 
					    $el.querySelector(`input[type="checkbox"]`).checked = checked;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function formatTime(date) {
 | 
				
			||||||
 | 
					    if (!date) return "";
 | 
				
			||||||
 | 
					    return new Intl.DateTimeFormat(navigator.language || "en-US")
 | 
				
			||||||
 | 
					        .format(date)
 | 
				
			||||||
 | 
					        .split("/")
 | 
				
			||||||
 | 
					        .map((chunk) => chunk.padStart(2, "0"))
 | 
				
			||||||
 | 
					        .join("/");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user