From 7e4480981d7b86ba151f7d2b57f9df9ad5ff9848 Mon Sep 17 00:00:00 2001 From: MickaelK Date: Sun, 28 Apr 2024 23:43:33 +1000 Subject: [PATCH] chore (rewrite): frontend rewrite of filesystem --- public/assets/components/dropdown.css | 64 ------------------- public/assets/css/designsystem_darkmode.css | 1 + public/assets/css/designsystem_dropdown.css | 37 +++++------ public/assets/lib/rx.js | 12 +++- public/assets/pages/connectpage/ctrl_form.js | 1 - public/assets/pages/ctrl_error.js | 24 ++++++- .../assets/pages/filespage/ctrl_filesystem.js | 55 +++++++++++----- .../pages/filespage/ctrl_filesystem_state.js | 47 +++++--------- public/assets/pages/filespage/ctrl_submenu.js | 52 ++++++++++----- .../pages/filespage/ctrl_upload_queue.js | 2 +- public/assets/pages/filespage/thing.css | 2 +- public/assets/pages/filespage/thing.js | 33 +++++++++- .../pages/viewerpage/application_editor.js | 2 +- 13 files changed, 174 insertions(+), 158 deletions(-) diff --git a/public/assets/components/dropdown.css b/public/assets/components/dropdown.css index 2edab593..e69de29b 100644 --- a/public/assets/components/dropdown.css +++ b/public/assets/components/dropdown.css @@ -1,64 +0,0 @@ -.component_dropdown { - position: relative; -} -.component_dropdown .dropdown_container { - display: none; - position: absolute; - right: 0; -} -.component_dropdown .dropdown_button { - border: 1px solid rgba(0, 0, 0, 0); - border-radius: 4px; - padding: 5px; - min-width: 20px; - text-align: center; -} -.component_dropdown .dropdown_container { - padding-top: 5px; - z-index: 3; -} -.component_dropdown .dropdown_container:before { - content: ' '; - position: absolute; - right: 10px; - top: 1px; - width: 0; - height: 0; - border-left: 6px solid transparent; - border-right: 6px solid transparent; - border-bottom: 6px solid white; -} -.component_dropdown .dropdown_container ul { - margin: 0; - list-style-type: none; - background: white; - border: 1px solid var(--border); - box-shadow: 1px 1px 2px var(--border); - color: rgba(0,10,20,0.85); - border-radius: 3px; - padding: 3px 0px; - font-size: 0.92em; -} -.component_dropdown .dropdown_container ul li { - display: flex; -} -.component_dropdown .dropdown_container ul li > div { - width: 160px; - padding: 8px 5px 8px 10px; -} - -.component_dropdown.active .dropdown_container { - display: block; -} -.component_dropdown.active .dropdown_container li { - background: white; - transition: background 0.1s ease-out; -} -.component_dropdown.active .dropdown_container li:hover { - background: rgba(0, 0, 0, 0.05); -} - -.component_dropdown.active .dropdown_button { - border-color: var(--bg-color); - box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1); -} diff --git a/public/assets/css/designsystem_darkmode.css b/public/assets/css/designsystem_darkmode.css index 63fb63ef..71ecab47 100644 --- a/public/assets/css/designsystem_darkmode.css +++ b/public/assets/css/designsystem_darkmode.css @@ -5,6 +5,7 @@ body.dark-mode { --border: #303438; + --dark: #2b2d30; } body.dark-mode input { diff --git a/public/assets/css/designsystem_dropdown.css b/public/assets/css/designsystem_dropdown.css index 7c2e0f60..70774df0 100644 --- a/public/assets/css/designsystem_dropdown.css +++ b/public/assets/css/designsystem_dropdown.css @@ -5,16 +5,8 @@ display: none; position: absolute; right: 0; -} -.component_dropdown .dropdown_button { - border: 1px solid rgba(0, 0, 0, 0); - border-radius: 4px; - padding: 5px; - min-width: 20px; - text-align: center; -} -.component_dropdown .dropdown_container { - padding-top: 9px; + padding-top: 10px; + margin-top: 3px; z-index: 3; } .component_dropdown .dropdown_container:before { @@ -26,42 +18,43 @@ height: 0; border-left: 10px solid transparent; border-right: 10px solid transparent; - border-bottom: 10px solid white; + border-bottom: 10px solid var(--dark); } .component_dropdown .dropdown_container ul { cursor: pointer; margin: 0; list-style-type: none; - background: white; - border: 1px solid var(--bg-color); box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1); - color: var(--color); + color: var(--bg-color); + background: var(--dark); border-radius: 3px; padding: 3px; font-size: 0.92em; } .component_dropdown .dropdown_container ul li { + justify-content: space-between; display: flex; -} -.component_dropdown .dropdown_container ul li > div { width: 150px; padding: 5px 5px 5px 10px; + background: var(--dark); } +.dark-mode .component_dropdown .dropdown_container ul { color: var(--color); } .component_dropdown.active .dropdown_container { display: block; } .component_dropdown.active .dropdown_container li { - background: white; transition: background 0.1s ease-out; } +.component_dropdown.active .dropdown_container li img.component_icon{ + border: 2px solid rgba(0,0,0,0); + height: 15px; + width: 15px; + box-sizing: border-box; + align-self: center; +} .component_dropdown.active .dropdown_container li:hover { background: var(--border); border-radius: 3px; } - -.component_dropdown.active .dropdown_button { - border-color: var(--bg-color); - box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1); -} diff --git a/public/assets/lib/rx.js b/public/assets/lib/rx.js index d30c45fd..3afdb324 100644 --- a/public/assets/lib/rx.js +++ b/public/assets/lib/rx.js @@ -40,10 +40,16 @@ export function preventDefault() { } export function onClick($node) { - assert.type($node, window.HTMLElement); - return rxjs.fromEvent($node, "click").pipe( - rxjs.map(() => $node) + const sideE = ($node) => { + assert.type($node, window.HTMLElement); + return rxjs.fromEvent($node, "click").pipe( + rxjs.map(() => $node) + ); + }; + if ($node instanceof window.NodeList) return rxjs.merge( + ...[...$node].map(($n) => sideE($n)), ); + return sideE($node); } export function onLoad($node) { diff --git a/public/assets/pages/connectpage/ctrl_form.js b/public/assets/pages/connectpage/ctrl_form.js index ccfa9d85..56baf0da 100644 --- a/public/assets/pages/connectpage/ctrl_form.js +++ b/public/assets/pages/connectpage/ctrl_form.js @@ -189,7 +189,6 @@ export default async function(render) { rxjs.mergeMap((url) => ajax({ url, responseType: "json" })), rxjs.tap(({ responseJSON }) => location.href = responseJSON.result), rxjs.catchError(ctrlError()), - rxjs.mergeMap(() => rxjs.EMPTY), ); }), rxjs.mergeMap((formData) => { // CASE 3: regular login diff --git a/public/assets/pages/ctrl_error.js b/public/assets/pages/ctrl_error.js index 79fa54f4..c847de21 100644 --- a/public/assets/pages/ctrl_error.js +++ b/public/assets/pages/ctrl_error.js @@ -8,12 +8,13 @@ import { AjaxError, ApplicationError } from "../lib/error.js"; import "../components/icon.js"; export default function(render = createRender(qs(document.body, "[role=\"main\"]"))) { - return async function(err) { + return function(err) { const [msg, trace] = processError(err); + const $page = createElement(`
- + home @@ -46,7 +47,7 @@ export default function(render = createRender(qs(document.body, "[role=\"main\"] rxjs.tap(() => location.reload()) )); - return rxjs.of(err); + return rxjs.EMPTY; }; } @@ -122,3 +123,20 @@ const css = ` vertical-align: middle; } `; + +function calculateBacklink(pathname) { + let url = "/"; + const listPath = pathname.replace(new RegExp("/$"), "").split("/"); + switch (listPath[1]) { + case "view": // in view mode, navigate to current folder + listPath[1] = "files"; + listPath.pop(); + url = listPath.join("/") + "/"; + break; + case "files": // in file browser mode, navigate to parent folder + listPath.pop(); + url = listPath.join("/") + "/"; + break; + } + return url === "/files/" ? "/" : url; +} diff --git a/public/assets/pages/filespage/ctrl_filesystem.js b/public/assets/pages/filespage/ctrl_filesystem.js index a5119212..f025be1e 100644 --- a/public/assets/pages/filespage/ctrl_filesystem.js +++ b/public/assets/pages/filespage/ctrl_filesystem.js @@ -8,14 +8,14 @@ import { createLoader } from "../../components/loader.js"; import ctrlError from "../ctrl_error.js"; import { createThing } from "./thing.js"; -// import { handleError, getFiles } from "./ctrl_filesystem_state.js"; +import { getState$ } from "./ctrl_filesystem_state.js"; import { ls } from "./model_files.js"; export default async function(render) { const $page = createElement(`
-
+

@@ -28,7 +28,19 @@ export default async function(render) { effect(rxjs.of(path).pipe( ls(), removeLoader, - rxjs.mergeMap(({ files, path }) => { // STEP1: setup the list of files + rxjs.mergeMap(({ files, ...rest }) => getState$().pipe(rxjs.map((p) => { + // files = files.sort() + if (p.show_hidden === false) files = files.filter(({ name }) => name[0] !== "."); + return { ...rest, files, ...p }; + }))), + rxjs.mergeMap(({ files, ...rest }) => { + if (files.length === 0) { + renderEmpty(render); + return rxjs.EMPTY; + } + return rxjs.of({...rest, files }); + }), + rxjs.mergeMap(({ files, path, view }) => { // STEP1: setup the list of files const FILE_HEIGHT = 160; const BLOCK_SIZE = Math.ceil(document.body.clientHeight / FILE_HEIGHT) + 1; // const BLOCK_SIZE = 6; @@ -38,26 +50,28 @@ export default async function(render) { if (size > VIRTUAL_SCROLL_MINIMUM_TRIGGER) { size = Math.min(files.length, BLOCK_SIZE * COLUMN_PER_ROW); } - const $list = qs($page, ".list"); + const $list = qs($page, `[data-target="list"]`); + $list.closest(".scroll-y").scrollTop = 0; const $fs = document.createDocumentFragment(); for (let i = 0; i < size; i++) { const file = files[i]; $fs.appendChild(createThing({ name: file.name, type: file.type, - link: createLink(file, path), + ...createLink(file, path), + view, })); } animate($list, { time: 200, keyframes: slideYIn(5) }); - $list.appendChild($fs); + $list.replaceChildren($fs); - /// ////////////////////////////////////// + ////////////////////////////////////// // CASE 1: virtual scroll isn't enabled if (files.length <= VIRTUAL_SCROLL_MINIMUM_TRIGGER) { return rxjs.EMPTY; } - /// ////////////////////////////////////// + ////////////////////////////////////// // CASE 2: with virtual scroll const $listBefore = qs($page, ".ifscroll-before"); const $listAfter = qs($page, ".ifscroll-after"); @@ -148,9 +162,8 @@ export default async function(render) { })); else $fs.appendChild(createThing({ name: file.name, - // name: `file ${i}`, type: file.type, - link: createLink(file, path), + ...createLink(file, path), })); n += 1; } @@ -171,6 +184,17 @@ export default async function(render) { )); } +function renderEmpty(render) { + render(createElement(` +
+

+ empty_folder +

+

There is nothing here

+
+ `)); +} + export function init() { return Promise.all([ loadCSS(import.meta.url, "./ctrl_filesystem.css"), @@ -178,9 +202,10 @@ export function init() { ]); } -function createLink(file, path) { - if (file.type === "file") { - return "/view" + path + file.name; - } - return "/files" + path + file.name + "/"; +function createLink(file, filepath) { + let path = filepath + file.name; + let link = ""; + if (file.type === "directory") path += "/"; + link = file.type === "directory" ? "/files" + path : "/view" + path; + return { path, link }; } diff --git a/public/assets/pages/filespage/ctrl_filesystem_state.js b/public/assets/pages/filespage/ctrl_filesystem_state.js index a670226b..c892cb9d 100644 --- a/public/assets/pages/filespage/ctrl_filesystem_state.js +++ b/public/assets/pages/filespage/ctrl_filesystem_state.js @@ -1,39 +1,24 @@ -import rxjs from "../../lib/rx.js"; +import rxjs, { effect } from "../../lib/rx.js"; const state$ = new rxjs.BehaviorSubject({ - search: null, + view: "grid", sort: null, - view: null, - acl: {}, - path: "/", - mutation: {}, - error: null + order: null, + show_hidden: false, }); export const getState$ = () => state$.asObservable(); -export const onNewFile = () => { - console.log("CLICK NEW FILE"); -}; +export const setState = (...args) => { + const obj = { ...state$.value }; + for (let i=0; i { - return rxjs.catchError((err) => { - if (err) { - state$.next({ - ...state$.value, - error: err - }); - } - return rxjs.empty(); - }); -}; - -export const onNewDirectory = () => { - console.log("CLICK NEW DIRECTORY"); -}; - -export const onSearch = () => { - console.log("SEARCH"); -}; - -export const getFiles = (n) => {}; +effect(rxjs.fromEvent(window, "keydown").pipe( + rxjs.tap((e) => e.preventDefault()), + rxjs.filter((e) => e.ctrlKey && e.key === "h"), + rxjs.tap(() => setState("show_hidden", !state$.value.show_hidden)), +)); diff --git a/public/assets/pages/filespage/ctrl_submenu.js b/public/assets/pages/filespage/ctrl_submenu.js index d3e2e501..54c23085 100644 --- a/public/assets/pages/filespage/ctrl_submenu.js +++ b/public/assets/pages/filespage/ctrl_submenu.js @@ -2,7 +2,7 @@ import { createElement, createRender, createFragment, onDestroy, nop } from "../ import rxjs, { effect, applyMutation, onClick, preventDefault } from "../../lib/rx.js"; import { animate } from "../../lib/animate.js"; import { loadCSS } from "../../helpers/loader.js"; -import { qs } from "../../lib/dom.js"; +import { qs, qsa } from "../../lib/dom.js"; import { getSelection$, clearSelection } from "./model_files.js"; import { setAction } from "./model_action.js"; @@ -14,8 +14,10 @@ import componentDelete from "./modal_delete.js"; import "../../components/dropdown.js"; import "../../components/icon.js"; - import { createModal } from "../../components/modal.js"; + +import { setState } from "./ctrl_filesystem_state.js"; + const modalOpt = { withButtonsRight: "OK", withButtonsLeft: "CANCEL", @@ -121,6 +123,7 @@ function componentRight(render) { MAGNIFYING_GLASS: "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj4KICA8cGF0aCBzdHlsZT0iZmlsbDojNjI2NDY5O2ZpbGwtb3BhY2l0eToxIiBkPSJNNTA1IDQ0Mi43TDQwNS4zIDM0M2MtNC41LTQuNS0xMC42LTctMTctN0gzNzJjMjcuNi0zNS4zIDQ0LTc5LjcgNDQtMTI4QzQxNiA5My4xIDMyMi45IDAgMjA4IDBTMCA5My4xIDAgMjA4czkzLjEgMjA4IDIwOCAyMDhjNDguMyAwIDkyLjctMTYuNCAxMjgtNDR2MTYuM2MwIDYuNCAyLjUgMTIuNSA3IDE3bDk5LjcgOTkuN2M5LjQgOS40IDI0LjYgOS40IDMzLjkgMGwyOC4zLTI4LjNjOS40LTkuNCA5LjQtMjQuNi4xLTM0ek0yMDggMzM2Yy03MC43IDAtMTI4LTU3LjItMTI4LTEyOCAwLTcwLjcgNTcuMi0xMjggMTI4LTEyOCA3MC43IDAgMTI4IDU3LjIgMTI4IDEyOCAwIDcwLjctNTcuMiAxMjgtMTI4IDEyOHoiIC8+Cjwvc3ZnPgo=", SORT: "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMjAgNTEyIj4KICA8cGF0aCBzdHlsZT0iZmlsbDojNjI2NDY5O2ZpbGwtb3BhY2l0eToxIiBkPSJNNDEgMjg4aDIzOGMyMS40IDAgMzIuMSAyNS45IDE3IDQxTDE3NyA0NDhjLTkuNCA5LjQtMjQuNiA5LjQtMzMuOSAwTDI0IDMyOWMtMTUuMS0xNS4xLTQuNC00MSAxNy00MXptMjU1LTEwNUwxNzcgNjRjLTkuNC05LjQtMjQuNi05LjQtMzMuOSAwTDI0IDE4M2MtMTUuMSAxNS4xLTQuNCA0MSAxNyA0MWgyMzhjMjEuNCAwIDMyLjEtMjUuOSAxNy00MXoiIC8+Cjwvc3ZnPgo=", + CHECK: "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj4KICA8cGF0aCBzdHlsZT0iZmlsbDojOTA5MDkwO2ZpbGwtb3BhY2l0eToxIiBkPSJNMTczLjg5OCA0MzkuNDA0bC0xNjYuNC0xNjYuNGMtOS45OTctOS45OTctOS45OTctMjYuMjA2IDAtMzYuMjA0bDM2LjIwMy0zNi4yMDRjOS45OTctOS45OTggMjYuMjA3LTkuOTk4IDM2LjIwNCAwTDE5MiAzMTIuNjkgNDMyLjA5NSA3Mi41OTZjOS45OTctOS45OTcgMjYuMjA3LTkuOTk3IDM2LjIwNCAwbDM2LjIwMyAzNi4yMDRjOS45OTcgOS45OTcgOS45OTcgMjYuMjA2IDAgMzYuMjA0bC0yOTQuNCAyOTQuNDAxYy05Ljk5OCA5Ljk5Ny0yNi4yMDcgOS45OTctMzYuMjA0LS4wMDF6IiAvPgo8L3N2Zz4K", }; effect(getSelection$().pipe( @@ -145,20 +148,15 @@ function componentRight(render) {
@@ -190,8 +188,32 @@ function componentRight(render) { } }), ), - onClick(qs($page, `[data-action="sort"]`)).pipe(rxjs.tap(() => { + onClick(qs($page, `[data-action="view"]`)).pipe(rxjs.tap(($button) => { + const $img = $button.querySelector("img"); + if ($img.getAttribute("alt") === "list") { + setState("view", "grid"); + $img.setAttribute("alt", "grid"); + $img.setAttribute("src", "data:image/svg+xml;base64," + ICONS.GRID_VIEW); + } else { + setState("view", "list"); + $img.setAttribute("alt", "list"); + $img.setAttribute("src", "data:image/svg+xml;base64," + ICONS.LIST_VIEW); + } + })), + onClick(qs($page, `[data-action="sort"]`)).pipe(rxjs.mergeMap(() => { qs($page, `[data-target="sort"]`).classList.toggle("active"); + const $lis = qsa($page, `.dropdown_container li`); + return onClick($lis).pipe(rxjs.tap(($el) => { + setState( + "sort", $el.getAttribute("data-target"), + "order", !!$el.querySelector("img") ? "asc" : "des", + ); + [...$lis].map(($li) => { + const $img = $li.querySelector("img"); + if ($img) $img.remove(); + }); + $el.appendChild(createElement(`check`)); + })); })), )), )); diff --git a/public/assets/pages/filespage/ctrl_upload_queue.js b/public/assets/pages/filespage/ctrl_upload_queue.js index 842bc846..f74070c2 100644 --- a/public/assets/pages/filespage/ctrl_upload_queue.js +++ b/public/assets/pages/filespage/ctrl_upload_queue.js @@ -5,7 +5,7 @@ import { qs } from "../../lib/dom.js"; export default function(render) { const $page = createElement(` -
+