diff --git a/public/assets/components/loader.js b/public/assets/components/loader.js index 9d0651b9..ef595e0e 100644 --- a/public/assets/components/loader.js +++ b/public/assets/components/loader.js @@ -44,7 +44,6 @@ class Loader extends window.HTMLElement { } customElements.define("component-loader", Loader); - export function createLoader($parent, opts = {}) { const { wait = 500 } = opts; const cancel = effect(new rxjs.Observable((observer) => { @@ -61,7 +60,7 @@ export function createLoader($parent, opts = {}) { `); const id = window.setTimeout(() => { - $parent.appendChild($icon); + $parent.replaceChildren($icon); animate($icon, { time: 1000, keyframes: opacityIn() }); }, wait); return () => { diff --git a/public/assets/pages/filespage/ctrl_filesystem.js b/public/assets/pages/filespage/ctrl_filesystem.js index 2d063dc7..4a929ea0 100644 --- a/public/assets/pages/filespage/ctrl_filesystem.js +++ b/public/assets/pages/filespage/ctrl_filesystem.js @@ -1,4 +1,4 @@ -import { createElement } from "../../lib/skeleton/index.js"; +import { createElement, createRender } from "../../lib/skeleton/index.js"; import { animate, slideYIn } from "../../lib/animate.js"; import rxjs, { effect } from "../../lib/rx.js"; import { loadCSS } from "../../helpers/loader.js"; @@ -7,9 +7,10 @@ import { ApplicationError } from "../../lib/error.js"; import { createLoader } from "../../components/loader.js"; import ctrlError from "../ctrl_error.js"; +import { sort } from "./helper.js"; import { createThing } from "./thing.js"; -import { getState$ } from "./ctrl_filesystem_state.js"; -import { ls } from "./model_files.js"; +import { getState$ } from "./state_filesystem.js"; +import { ls, search } from "./model_files.js"; const ICONS = { EMPTY_FILES: "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMzAwIiBoZWlnaHQ9IjE3MCIgdmlld0JveD0iMCAwIDMwMCAxNzAiIGZpbGw9Im5vbmUiPgogIDxwYXRoCiAgICAgZD0ibSA1Mi42Mjk5MDUsMTYwLjE2Nzg1IGMgMS41NDYsMCAyLjgsLTEuMjUzNiAyLjgsLTIuOCAwLC0xLjU0NjQgLTEuMjU0LC0yLjggLTIuOCwtMi44IC0xLjU0NywwIC0yLjgsMS4yNTM2IC0yLjgsMi44IDAsMS41NDY0IDEuMjUzLDIuOCAyLjgsMi44IHoiCiAgICAgZmlsbD0iIzkwOTA5MCIKICAgICBpZD0icGF0aDUwNjciCiAgICAgc3R5bGU9ImZpbGw6IzkwOTA5MDtmaWxsLW9wYWNpdHk6MC4xMzMzMzMiIC8+CiAgPHBhdGgKICAgICBkPSJtIDExMy4wMzAxOCwyMi4zOTM2NDkgYyAxLjU0NjQsMCAyLjgsLTEuMjUzNiAyLjgsLTIuOCAwLC0xLjU0NjQgLTEuMjUzNiwtMi44IC0yLjgsLTIuOCAtMS41NDY0LDAgLTIuOCwxLjI1MzYgLTIuOCwyLjggMCwxLjU0NjQgMS4yNTM2LDIuOCAyLjgsMi44IHoiCiAgICAgZmlsbD0iIzkwOTA5MCIKICAgICBpZD0icGF0aDUwNjkiCiAgICAgc3R5bGU9ImZpbGw6IzkwOTA5MDtmaWxsLW9wYWNpdHk6MC4xMzMzMzMiIC8+CiAgPHBhdGgKICAgICBkPSJtIDczLjI5MzU2NSwxMTIuNDYyMTYgYyAyLjg3MTg5LDAgNS4yMDAwMywtMi4zMjgxIDUuMjAwMDMsLTUuMiAwLC0yLjg3MTkgLTIuMzI4MTQsLTUuMiAtNS4yMDAwMywtNS4yIC0yLjg3MTg4LDAgLTUuMTk5OTk1LDIuMzI4MSAtNS4xOTk5OTUsNS4yIDAsMi44NzE5IDIuMzI4MTE1LDUuMiA1LjE5OTk5NSw1LjIgeiIKICAgICBmaWxsPSIjOTA5MDkwIgogICAgIGlkPSJwYXRoNTA3MSIKICAgICBzdHlsZT0iZmlsbDojOTA5MDkwO2ZpbGwtb3BhY2l0eTowLjEzMzMzMzM0IiAvPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM1MTE3Ij4KICAgIDxmaWx0ZXIKICAgICAgIGlkPSJmaWx0ZXIwX2QiCiAgICAgICB4PSIxNi4wNzYyIgogICAgICAgeT0iMTIuOTU3NSIKICAgICAgIHdpZHRoPSIxMTQuOCIKICAgICAgIGhlaWdodD0iMTM0LjYwMDAxIgogICAgICAgZmlsdGVyVW5pdHM9InVzZXJTcGFjZU9uVXNlIgogICAgICAgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KICAgICAgPGZlRmxvb2QKICAgICAgICAgZmxvb2Qtb3BhY2l0eT0iMCIKICAgICAgICAgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiCiAgICAgICAgIGlkPSJmZUZsb29kNTA5NyIgLz4KICAgICAgPGZlQ29sb3JNYXRyaXgKICAgICAgICAgaW49IlNvdXJjZUFscGhhIgogICAgICAgICB0eXBlPSJtYXRyaXgiCiAgICAgICAgIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMTI3IDAiCiAgICAgICAgIGlkPSJmZUNvbG9yTWF0cml4NTA5OSIgLz4KICAgICAgPGZlT2Zmc2V0CiAgICAgICAgIGR5PSIxMSIKICAgICAgICAgaWQ9ImZlT2Zmc2V0NTEwMSIgLz4KICAgICAgPGZlR2F1c3NpYW5CbHVyCiAgICAgICAgIHN0ZERldmlhdGlvbj0iMTEiCiAgICAgICAgIGlkPSJmZUdhdXNzaWFuQmx1cjUxMDMiIC8+CiAgICAgIDxmZUNvbG9yTWF0cml4CiAgICAgICAgIHR5cGU9Im1hdHJpeCIKICAgICAgICAgdmFsdWVzPSIwIDAgMCAwIDAuMzk3NzA4IDAgMCAwIDAgMC40Nzc0OSAwIDAgMCAwIDAuNTc1IDAgMCAwIDAuMjcgMCIKICAgICAgICAgaWQ9ImZlQ29sb3JNYXRyaXg1MTA1IiAvPgogICAgICA8ZmVCbGVuZAogICAgICAgICBtb2RlPSJub3JtYWwiCiAgICAgICAgIGluMj0iQmFja2dyb3VuZEltYWdlRml4IgogICAgICAgICByZXN1bHQ9ImVmZmVjdDFfZHJvcFNoYWRvdyIKICAgICAgICAgaWQ9ImZlQmxlbmQ1MTA3IiAvPgogICAgICA8ZmVCbGVuZAogICAgICAgICBtb2RlPSJub3JtYWwiCiAgICAgICAgIGluPSJTb3VyY2VHcmFwaGljIgogICAgICAgICBpbjI9ImVmZmVjdDFfZHJvcFNoYWRvdyIKICAgICAgICAgcmVzdWx0PSJzaGFwZSIKICAgICAgICAgaWQ9ImZlQmxlbmQ1MTA5IiAvPgogICAgPC9maWx0ZXI+CiAgICA8bGluZWFyR3JhZGllbnQKICAgICAgIGlkPSJwYWludDBfbGluZWFyIgogICAgICAgeDE9IjczLjQ1MzEwMiIKICAgICAgIHkxPSIyMS44NjE5IgogICAgICAgeDI9IjczLjQ1MzEwMiIKICAgICAgIHkyPSIxMTUuNTM0IgogICAgICAgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgogICAgICA8c3RvcAogICAgICAgICBzdG9wLWNvbG9yPSIjRkRGRUZGIgogICAgICAgICBpZD0ic3RvcDUxMTIiIC8+CiAgICAgIDxzdG9wCiAgICAgICAgIG9mZnNldD0iMC45OTY0IgogICAgICAgICBzdG9wLWNvbG9yPSIjRUNGMEY1IgogICAgICAgICBpZD0ic3RvcDUxMTQiIC8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogICAgPGZpbHRlcgogICAgICAgaWQ9ImZpbHRlcjBfZC03IgogICAgICAgeD0iMC4zOTExMTMwMSIKICAgICAgIHk9IjM1LjM5NDc5OCIKICAgICAgIHdpZHRoPSIxNDUuNTk1IgogICAgICAgaGVpZ2h0PSIxMDIuOCIKICAgICAgIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIKICAgICAgIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CiAgICAgIDxmZUZsb29kCiAgICAgICAgIGZsb29kLW9wYWNpdHk9IjAiCiAgICAgICAgIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4IgogICAgICAgICBpZD0iZmVGbG9vZDEyMjciIC8+CiAgICAgIDxmZUNvbG9yTWF0cml4CiAgICAgICAgIGluPSJTb3VyY2VBbHBoYSIKICAgICAgICAgdHlwZT0ibWF0cml4IgogICAgICAgICB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDEyNyAwIgogICAgICAgICBpZD0iZmVDb2xvck1hdHJpeDEyMjkiIC8+CiAgICAgIDxmZU9mZnNldAogICAgICAgICBkeT0iMTEiCiAgICAgICAgIGlkPSJmZU9mZnNldDEyMzEiIC8+CiAgICAgIDxmZUdhdXNzaWFuQmx1cgogICAgICAgICBzdGREZXZpYXRpb249IjExIgogICAgICAgICBpZD0iZmVHYXVzc2lhbkJsdXIxMjMzIiAvPgogICAgICA8ZmVDb2xvck1hdHJpeAogICAgICAgICB0eXBlPSJtYXRyaXgiCiAgICAgICAgIHZhbHVlcz0iMCAwIDAgMCAwLjM5NzcwOCAwIDAgMCAwIDAuNDc3NDkgMCAwIDAgMCAwLjU3NSAwIDAgMCAwLjI3IDAiCiAgICAgICAgIGlkPSJmZUNvbG9yTWF0cml4MTIzNSIgLz4KICAgICAgPGZlQmxlbmQKICAgICAgICAgbW9kZT0ibm9ybWFsIgogICAgICAgICBpbjI9IkJhY2tncm91bmRJbWFnZUZpeCIKICAgICAgICAgcmVzdWx0PSJlZmZlY3QxX2Ryb3BTaGFkb3ciCiAgICAgICAgIGlkPSJmZUJsZW5kMTIzNyIgLz4KICAgICAgPGZlQmxlbmQKICAgICAgICAgbW9kZT0ibm9ybWFsIgogICAgICAgICBpbj0iU291cmNlR3JhcGhpYyIKICAgICAgICAgaW4yPSJlZmZlY3QxX2Ryb3BTaGFkb3ciCiAgICAgICAgIHJlc3VsdD0ic2hhcGUiCiAgICAgICAgIGlkPSJmZUJsZW5kMTIzOSIgLz4KICAgIDwvZmlsdGVyPgogIDwvZGVmcz4KICA8cGF0aAogICAgIGQ9Im0gMjEuMjIwODMyLDkyLjI2NDAxNiBoIC00LjkgYyAtMC4zLDAgLTAuNiwwLjEgLTAuNywwLjQgbCAtMi40LDQuMyBjIC0wLjEsMC4zIC0wLjEsMC42IDAsMC45IGwgMi40LDQuMzAwMDA0IGMgMC4xLDAuMyAwLjQsMC40IDAuNywwLjQgaCA0LjkgYyAwLjMsMCAwLjYsLTAuMSAwLjcsLTAuNCBsIDIuNCwtNC4zMDAwMDQgYyAwLjEsLTAuMyAwLjEsLTAuNiAwLC0wLjkgbCAtMi40LC00LjMgYyAtMC4xLC0wLjMgLTAuNCwtMC40IC0wLjcsLTAuNCB6IgogICAgIGlkPSJwYXRoNCIKICAgICBzdHlsZT0iZmlsbDojOTA5MDkwO2ZpbGwtb3BhY2l0eTowLjEzMzMzMyIgLz4KICA8cGF0aAogICAgIGNsYXNzPSJmaWxlIgogICAgIGQ9Im0gMjcxLjA5MjU5LDExLjY1MjMxMiAtNy4zNzAxOSwzLjU4MzkxOCBjIC0wLjc2MTk0LDAuNDMxMTE2IC0xLjcwNDkxLDAuNTgzNTAxIC0yLjU3MjQ2LDAuNjA1MjIyIC0wLjg2NzU0LDAuMDIxNzIgLTEuNzg3OTEsLTAuMTYxMjQ0IC0yLjYzMjg1LC0wLjQ3NDg2NyAtMS42MTQ0NCwtMC43NTc4OTggLTMuMDU1NCwtMi4xMTI0NDIgLTMuOTM4MTUsLTMuODQxNTA5IDAsMCAtMS41MDg5NiwtMy4zMTAwMzcyIC0zLjA3MDc1LC02LjgyNDc3NDUgbCAtMC4yMzM4OCwtMC40ODM0MzY4IC0xNS45NjI1Myw3LjUwNzQ2OTMgYyAtMi41NDIyMywxLjE0NTI3MSAtMy42MTMyOSw0LjE4NTE0NCAtMi41MTkyOCw2LjczMjk5OCBsIDExLjUyODU1LDI1LjY0NDA3NCBjIDEuMDk0MDIsMi41NDc4NTUgNC4wODksMy41ODAyMDUgNi42MzEyNSwyLjQzNDkzMSBsIDI1LjU5NTg0LC0xMi4wNDk0MDIgYyAyLjU0MjI0LC0xLjE0NTI3NSAzLjYxMzMxLC00LjE4NTE0MyAyLjUxOTMsLTYuNzMyOTk4IEwgMjcxLjcwMzY1LDExLjQ4MjQ5IFoiCiAgICAgc3R5bGU9ImNsaXAtcnVsZTpldmVub2RkO2ZpbGw6IzkwOTA5MDtmaWxsLW9wYWNpdHk6MC4xMzMzMzMzNDtmaWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2Utd2lkdGg6MS40OTQ3MSIKICAgICBpZD0icGF0aDYiIC8+CiAgPHBhdGgKICAgICBjbGFzcz0iZmlsZSIKICAgICBkPSJtIDI1OS40MTQ2OCwxMi45MjMzNjggYyAtMS4xMDE0NCwtMC40NjE3MTMgLTEuOTc2NTksLTEuMzE1MzggLTIuNjI1NDMsLTIuNTYxMDA1IGwgLTAuMDUyOCwtMC4yMDQ3IC0wLjI4NjY4LC0wLjY4ODE0NzIgLTEuMDQxMTksLTIuMzQzMTU4IC0wLjU3MzQxLC0xLjM3NjI3NiAxMy4wMDU5OSw0LjcyMTc3MDIgLTUuMTU5OTMsMi40MjEyMDIgYyAtMC40ODI3NywwLjI0Mzg2NiAtMS4wMTg0MSwwLjI4MzAyOCAtMS41NTQwMSwwLjMyMjE5NiAtMC41MzU2NCwwLjAzOTE2IC0xLjEyNDA2LC0wLjEyNjM2NCAtMS43MTI0OSwtMC4yOTE4OTkgeiIKICAgICBzdHlsZT0iY2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbDojOTA5MDkwO2ZpbGwtb3BhY2l0eTowLjEzMzMzMzM0O2ZpbGwtcnVsZTpldmVub2RkO3N0cm9rZS13aWR0aDoxLjQ5NDcxIgogICAgIGlkPSJwYXRoOCIgLz4KICA8cGF0aAogICAgIGQ9Im0gMjMuOTQ0NDAzLDE3LjYzNTU2MyBjIC0yLjA3NzQxOSwxLjE5OTQxNSAtMi40NzQ2NjksMy44MzI3OTggLTEuMzAzNTc5LDUuODYxMjAyIGwgMTEuMTU3OTY5LDE5LjMyNjEzMyBjIDEuMjY4NjksMi4xOTc0MzUgMy41MzE5NiwyLjkxOTEzNSA1LjY2MjY0LDEuNjg4OTc1IGwgMjQuMzQzMTIsLTE0LjA1NDQ5MSBjIDEuMzg0OTYsLTAuNzk5NjEgMS45MjQxMSwtMy4wNjQxNjkgMC42MjI4OCwtNS4zMTc5MzkgTCA1NC45Mjg1NDMsOC42ODY4NjI3IGMgLTEuMDQwOTksLTEuODAzMDExIC0zLjA3NjU0LC0yLjEzMDMxMyAtNC40NjE0NiwtMS4zMzA3MzQgbCAtMTIuNTE3ODIsNy4yMjcxNjkzIC00Ljk0OTQ0LC0yLjE3NTg2OSB6IgogICAgIGlkPSJwYXRoMTYiCiAgICAgc3R5bGU9ImZpbGw6IzkwOTA5MDtmaWxsLW9wYWNpdHk6MC4xMzMzMzMzNDtzdHJva2Utd2lkdGg6MC42MzI1OTQiIC8+CiAgPGcKICAgICBpZD0iZzIwIgogICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI0My4yODEwNCwxMzEuMDczNDYpIgogICAgIHN0eWxlPSJmaWxsOiM5MDkwOTA7ZmlsbC1vcGFjaXR5OjAuMTMzMzMzMzQiPgogICAgPHBhdGgKICAgICAgIGQ9Im0gNDIuNCwyMy4yIDguNiw5LjkgYyAwLjMsMC4zIDAuMSwwLjggLTAuMywwLjkgTCAzOCwzNi4yIGMgLTAuNCwwLjEgLTAuNywtMC4zIC0wLjYsLTAuNyBsIDQuMSwtMTIuMSBjIDAuMSwtMC40IDAuNywtMC41IDAuOSwtMC4yIHoiCiAgICAgICBpZD0icGF0aDE4IgogICAgICAgc3R5bGU9ImZpbGw6IzkwOTA5MDtmaWxsLW9wYWNpdHk6MC4xMzMzMzMzNCIgLz4KICA8L2c+CiAgPHBhdGgKICAgICBkPSJtIDI4Ni4wODgzMiw4NS43MTYxIC0yLDEgYyAtMC4zLDAuMSAtMC42LDAgLTAuOCwtMC4zIGwgLTEsLTIgYyAtMC4xLC0wLjMgMCwtMC42IDAuMywtMC44IGwgMiwtMSBjIDAuMywtMC4xIDAuNiwwIDAuOCwwLjMgbCAxLDIgYyAwLjEsMC4zIDAsMC43IC0wLjMsMC44IHoiCiAgICAgaWQ9InBhdGgxMCIKICAgICBzdHlsZT0iZmlsbDojOTA5MDkwO2ZpbGwtb3BhY2l0eTowLjEzMzMzMyIgLz4KICA8cGF0aAogICAgIGQ9Im0gMTExLjM1ODEsNjcuODkzNDgzIGggNzIuMDk5NyBjIDIuOSwwIDUuMSwyLjIgNS4xLDUuMSB2IDQ1LjY5OTk5NyBjIDAsMi45IC0yLjIsNS4xIC01LjEsNS4xIGggLTcyLjA5OTcgYyAtMi45LDAgLTUuMSwtMi4yIC01LjEsLTUuMSBWIDcyLjk5MzQ4MyBjIDAsLTIuOSAyLjQsLTUuMSA1LjEsLTUuMSB6IgogICAgIGZpbGw9IiM5MDkwOTAiCiAgICAgaWQ9InBhdGgxMTk5IiAvPgogIDxnCiAgICAgZmlsdGVyPSJ1cmwoI2ZpbHRlcjBfZCkiCiAgICAgaWQ9ImcxMjAzIgogICAgIHN0eWxlPSJmaWx0ZXI6dXJsKCNmaWx0ZXIwX2QtNykiCiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNzYuNDY2OCwzMS4xOTg2ODMpIj4KICAgIDxwYXRoCiAgICAgICBkPSJNIDExNy42OTEsNDYuNDk0OCBIIDg3LjI5MTEgYyAtMywwIC01LjgsMSAtOC4xLDIuOSBsIC04LDYuNSBjIC0yLjIsMS44IC01LjEsMi45IC04LjEsMi45IGggLTM0LjQgYyAtMy41LDAgLTYuMywyLjkgLTYuMyw2LjMgMCwwLjMgMCwwLjYgMC4xLDAuOSBsIDYuMywzMy43IGMgMC41LDMuMjAwMiAzLjIsNS41MDAyIDYuMyw1LjUwMDIgaCA3My41OTk5IGMgMy4yLDAgNS44LC0yLjIgNi4zLC01LjQwMDIgbCA4LjksLTQ2LjEgYyAwLjYsLTMuNSAtMS43LC02LjYgLTUuMiwtNy4zIC0wLjMsMC4xIC0wLjcsMC4xIC0xLDAuMSB6IgogICAgICAgZmlsbD0iI2ZmZmZmZiIKICAgICAgIGlkPSJwYXRoMTIwMSIgLz4KICA8L2c+CiAgPHBhdGgKICAgICBkPSJtIDEzNS4zNTgxLDExMi41OTM1OCBjIDEuOCwwIDMuMywtMS41IDMuMywtMy4zIDAsLTEuOCAtMS41LC0zLjMgLTMuMywtMy4zIC0xLjgsMCAtMy4zLDEuNSAtMy4zLDMuMyAwLDEuOCAxLjUsMy4zIDMuMywzLjMgeiIKICAgICBmaWxsPSIjOTA5MDkwIgogICAgIGlkPSJwYXRoMTIwNSIgLz4KICA8cGF0aAogICAgIGQ9Im0gMTYxLjA1ODEsMTEyLjQ5MzQ4IGMgMS44LDAgMy4zLC0xLjUgMy4zLC0zLjMgMCwtMS44IC0xLjUsLTMuMyAtMy4zLC0zLjMgLTEuOCwwIC0zLjMsMS41IC0zLjMsMy4zIDAsMS45IDEuNSwzLjMgMy4zLDMuMyB6IgogICAgIGZpbGw9IiM5MDkwOTAiCiAgICAgaWQ9InBhdGgxMjA3IiAvPgogIDxwYXRoCiAgICAgZD0ibSAxNDQuMTU4LDg4Ljg5MzQ4MSBjIC0zLjYsLTcgLTQuNCwtMTUuMzk5OTk4IC0yLC0yMi45OTk5OTggMi4zLC03LjYgNy44LC0xNC4xIDE0LjYsLTE3LjggMi4xLC0xLjEgNC41LC0yIDYuOSwtMi4xIDIuNCwtMC4xIDUsMC43IDYuNiwyLjcgMS42LDEuOCAxLjksNC44IDAuNiw2LjggLTEuNCwxLjkgLTQuMiwyLjcgLTYuNSwyLjEgLTMuNywtMC43IC02LjcsLTMuNiAtNy42LC03LjEgLTAuOSwtMy41IDAuMywtNy42IDMuMSwtOS45IDEuOCwtMS42IDQuMywtMi41IDYuNiwtMy4yIDExLjE5OTgsLTMuMyAyMy4zOTk4LC0zLjcgMzQuNzk5OCwtMS4yIgogICAgIHN0cm9rZT0iIzU3NTk1YSIKICAgICBzdHJva2Utd2lkdGg9IjIiCiAgICAgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIgogICAgIHN0cm9rZS1kYXNoYXJyYXk9IjQsIDQiCiAgICAgaWQ9InBhdGgxMjA5IiAvPgogIDxwYXRoCiAgICAgZD0ibSAxNTEuMzU4LDExNi4wOTM0OCBoIC02LjIgdiAxLjUgaCA2LjIgeiIKICAgICBmaWxsPSIjOTA5MDkwIgogICAgIGlkPSJwYXRoMTIxMSIgLz4KICA8cGF0aAogICAgIGQ9Im0gMjA3LjI1NzgsMzMuNTkzNDMzIGMgLTAuMSwxLjUgLTAuMiwyLjkgLTEuMywzLjIgLTEuMSwwLjMgLTEuNiwtMC43IC0yLjMsLTIuMSAtMC43LC0xLjMgLTAuMywtMi42OTk5OTYgMC45LC0yLjk5OTk5NiAxLjEsLTAuMyAyLjksMC4xIDIuNywxLjg5OTk5NiB6IgogICAgIGZpbGw9IiM5MDkwOTAiCiAgICAgaWQ9InBhdGgxMjEzIiAvPgogIDxwYXRoCiAgICAgZD0ibSAyMDYuMDU3OCw0MC43OTM0NTMgYyAwLjMsLTEuOCAwLjYsLTIuOCAtMC40LC0zLjMgLTEuMSwtMC41IC0xLjgsMC40IC0zLDEuNiAtMSwxLjEgLTAuNCwyLjcwMDAzIDAuNiwzLjIwMDAzIDEuMiwwLjYgMi41LDAgMi44LC0xLjUwMDAzIHoiCiAgICAgZmlsbD0iIzkwOTA5MCIKICAgICBpZD0icGF0aDEyMTUiIC8+CiAgPHBhdGgKICAgICBkPSJtIDIwNy40NTc4LDM3LjQ5MzUzMyBjIC0wLjEsMC43IC0wLjYsMS4yIC0xLjMsMS4zIC0wLjMsMCAtMC42LDAgLTEsMCAtMS40LC0wLjIgLTIuNSwtMS4xIC0yLjQsLTIgMC4xLC0wLjkgMS40LC0xLjQgMywtMS4yIDAuMywwIDAuNiwwLjEgMC44LDAuMiAwLjYsMC4yIDEsMC45IDAuOSwxLjcgMCwwIDAsLTAuMSAwLDAgeiIKICAgICBmaWxsPSIjNTc1OTVhIgogICAgIGlkPSJwYXRoMTIxNyIgLz4KICA8cGF0aAogICAgIGQ9Im0gOTUuODU3OSw2NS41OTM0ODMgYyAwLC0xLjcgMCwtMy40IDEuMiwtMy45IDEuMywtMC41IDIsMC43IDMsMi40IDAuOSwxLjUgMC41LDMuMSAtMC44LDMuNiAtMS4xLDAuNSAtMy40LDAuMiAtMy40LC0yLjEgeiIKICAgICBmaWxsPSIjOTA5MDkwIgogICAgIGlkPSJwYXRoMTIxOSIgLz4KICA8cGF0aAogICAgIGQ9Im0gOTYuNTU3OSw1Ny4xOTM1ODMgYyAtMC4yLDIuMSAtMC41LDMuMyAwLjgsMy44IDEuMywwLjUgMiwtMC42IDMuMywtMi4yIDEsLTEuNCAwLjMsLTMuMiAtMSwtMy43IC0xLjMsLTAuNSAtMi45LDAuNCAtMy4xLDIuMSB6IgogICAgIGZpbGw9IiM5MDkwOTAiCiAgICAgaWQ9InBhdGgxMjIxIiAvPgogIDxwYXRoCiAgICAgZD0ibSA5NS40NTgsNjEuMTkzNTgzIGMgMCwtMC44IDAuNiwtMS40IDEuMywtMS41IDAuMywtMC4xIDAuNywtMC4xIDEuMSwwIDEuNiwwLjEgMywxIDIuOSwyIC0wLjEsMSAtMS40LDEuNyAtMy4xLDEuNSAtMC4zLDAgLTAuNiwtMC4xIC0wLjksLTAuMiAtMC44LC0wLjEgLTEuMywtMC45IC0xLjMsLTEuOCB6IgogICAgIGZpbGw9IiM1NzU5NWEiCiAgICAgaWQ9InBhdGgxMjIzIiAvPgogIDxwYXRoCiAgICAgZD0ibSAxMDIuMjU4MSw2MS40OTM1ODMgYyAxMC41LDAgMjkuOSw2LjEgMzAuMiwyOC40OTk5OTgiCiAgICAgc3Ryb2tlPSIjNTc1OTVhIgogICAgIHN0cm9rZS13aWR0aD0iMiIKICAgICBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiCiAgICAgc3Ryb2tlLWRhc2hhcnJheT0iNCwgNCIKICAgICBpZD0icGF0aDEyMjUiIC8+CiAgPHBhdGgKICAgICBkPSJtIDE3NS4wNzg0MSw0LjE1ODgzOCBoIC0yLjk4ODA4IGMgLTAuMTgyOTQsMCAtMC4zNjU4OCwwLjA2NTAwMyAtMC40MjY4NywwLjI2MDAxMDIgbCAtMS40NjM1NCwyLjc5NTExMDEgYyAtMC4wNjEsMC4xOTUwMDcgLTAuMDYxLDAuMzkwMDE1IDAsMC41ODUwMjMgbCAxLjQ2MzU0LDIuNzk1MTEwNyBjIDAuMDYxLDAuMTk1MDA4IDAuMjQzOTMsMC4yNjAwMTEgMC40MjY4NywwLjI2MDAxMSBoIDIuOTg4MDggYyAwLjE4Mjk0LDAgMC4zNjU4OSwtMC4wNjUgMC40MjY4NywtMC4yNjAwMTEgbCAxLjQ2MzU1LC0yLjc5NTExMDcgYyAwLjA2MSwtMC4xOTUwMDggMC4wNjEsLTAuMzkwMDE2IDAsLTAuNTg1MDIzIGwgLTEuNDYzNTUsLTIuNzk1MTEwMSBjIC0wLjA2MSwtMC4xOTUwMDc2IC0wLjI0MzkzLC0wLjI2MDAxMDIgLTAuNDI2ODcsLTAuMjYwMDEwMiB6IgogICAgIGlkPSJwYXRoNC01IgogICAgIHN0eWxlPSJmaWxsOiM5MDkwOTA7ZmlsbC1vcGFjaXR5OjAuMTMzMzMzO3N0cm9rZS13aWR0aDowLjYyOTU5OCIgLz4KPC9zdmc+Cg==", @@ -19,6 +20,7 @@ const ICONS = { export default async function(render) { const $page = createElement(`
+
@@ -37,24 +39,37 @@ export default async function(render) { return prev[0] === curr[0] && prev[1] && curr[1] }), ); - effect(rxjs.of(path).pipe( - ls(), - removeLoader, + const $list = qs($page, `[data-target="list"]`); + + effect(ls(path).pipe( rxjs.mergeMap(({ files, ...rest }) => getState$().pipe(rxjs.mergeMap((state) => { - if (state.show_hidden === false) files = files.filter(({ name }) => name[0] !== "."); - // files = files.sort() + const $header = qs($page, `[data-target="header"]`); + $header.innerHTML = ""; + $list.innerHTML = ""; + if (!!state.search) { + const removeLoader = createLoader($header); + return search(state.search_q).pipe(rxjs.map((files) => ({ + ...rest, files, ...state, + })), removeLoader); + } return rxjs.of({ ...rest, files, ...state }); }))), - rxjs.mergeMap(({ files, search_mode, ...rest }) => { + rxjs.mergeMap(({ show_hidden, files, ...rest }) => { + if (show_hidden === false) files = files.filter(({ name }) => name[0] !== "."); + console.log(rest); + files = sort(files, rest.sort); + return rxjs.of({ ...rest, files }) + }), + removeLoader, + rxjs.mergeMap(({ files, search, ...rest }) => { if (files.length === 0) { - renderEmpty(render, search_mode ? ICONS.EMPTY_SEARCH : ICONS.EMPTY_FILES); + renderEmpty(createRender(qs($page, `[data-target="header"]`)), !!search ? ICONS.EMPTY_SEARCH : ICONS.EMPTY_FILES); return rxjs.EMPTY; } return rxjs.of({...rest, files }); }), rxjs.mergeMap((obj) => refreshOnResize$.pipe(rxjs.mapTo(obj))), rxjs.mergeMap(({ files, path, view }) => { // STEP1: setup the list of files - const $list = qs($page, `[data-target="list"]`); $list.closest(".scroll-y").scrollTop = 0; let FILE_HEIGHT, COLUMN_PER_ROW; switch(view) { @@ -82,8 +97,7 @@ export default async function(render) { for (let i = 0; i < size; i++) { const file = files[i]; $fs.appendChild(createThing({ - name: file.name, - type: file.type, + ...file, ...createLink(file, path), view, n: i, @@ -193,8 +207,7 @@ export default async function(render) { type: "hidden", })); else $fs.appendChild(createThing({ - name: file.name, - type: file.type, + ...file, ...createLink(file, path), view, n: i, diff --git a/public/assets/pages/filespage/ctrl_newitem.js b/public/assets/pages/filespage/ctrl_newitem.js index ac3fb50f..6ca1a7ec 100644 --- a/public/assets/pages/filespage/ctrl_newitem.js +++ b/public/assets/pages/filespage/ctrl_newitem.js @@ -4,7 +4,7 @@ import { qs } from "../../lib/dom.js"; import { animate } from "../../lib/animate.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) { const $node = createElement(` diff --git a/public/assets/pages/filespage/ctrl_submenu.js b/public/assets/pages/filespage/ctrl_submenu.js index 4bba408a..c5d916b5 100644 --- a/public/assets/pages/filespage/ctrl_submenu.js +++ b/public/assets/pages/filespage/ctrl_submenu.js @@ -3,8 +3,10 @@ import rxjs, { effect, applyMutation, onClick, preventDefault } from "../../lib/ import { animate, slideYIn } from "../../lib/animate.js"; import { loadCSS } from "../../helpers/loader.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 componentEmbed from "./modal_embed.js"; @@ -12,11 +14,9 @@ import componentTag from "./modal_tag.js"; import componentRename from "./modal_rename.js"; 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"; +import { getSelection$, clearSelection } from "./state_selection.js"; +import { getAction$, setAction } from "./state_event.js"; +import { setState } from "./state_filesystem.js"; const modalOpt = { withButtonsRight: "OK", @@ -139,13 +139,14 @@ function componentRight(render) { effect(getSelection$().pipe( rxjs.filter((selections) => selections.length === 0), rxjs.map(() => render(createFragment(` - +
+ +
@@ -257,8 +258,15 @@ function componentRight(render) { time: 100, }); $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)), ), )), )); diff --git a/public/assets/pages/filespage/helper.js b/public/assets/pages/filespage/helper.js new file mode 100644 index 00000000..cc2af8b5 --- /dev/null +++ b/public/assets/pages/filespage/helper.js @@ -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; + }); +} diff --git a/public/assets/pages/filespage/model_files.js b/public/assets/pages/filespage/model_files.js index 75483839..a671aa66 100644 --- a/public/assets/pages/filespage/model_files.js +++ b/public/assets/pages/filespage/model_files.js @@ -1,42 +1,6 @@ -import { onDestroy } from "../../lib/skeleton/index.js"; import rxjs from "../../lib/rx.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= lowerBound; -} - // export function ls() { // return rxjs.from(new Error("missing cache")).pipe( // rxjs.catchError(() => rxjs.of({ files: null })), @@ -57,18 +21,22 @@ function isBetween(n, lowerBound, higherBound) { // ) // } -export function ls() { - return rxjs.pipe( - rxjs.mergeMap((path) => ajax({ - url: `/api/files/ls?path=${path}`, - responseType: "json" - }).pipe(rxjs.map(({ responseJSON }) => ({ - files: responseJSON.results.sort(sortByDefault), - path, - })))), +export function search(term) { + return rxjs.of([]).pipe( + rxjs.delay(1500), ); } +export function ls(path) { + return ajax({ + url: `/api/files/ls?path=${path}`, + responseType: "json" + }).pipe(rxjs.map(({ responseJSON }) => ({ + files: responseJSON.results.sort(sortByDefault), + path, + }))); +} + const sortByDefault = (fileA, fileB) => { if (fileA.type !== fileB.type) { if (fileA.type === "file") return +1; diff --git a/public/assets/pages/filespage/model_action.js b/public/assets/pages/filespage/state_event.js similarity index 100% rename from public/assets/pages/filespage/model_action.js rename to public/assets/pages/filespage/state_event.js diff --git a/public/assets/pages/filespage/ctrl_filesystem_state.js b/public/assets/pages/filespage/state_filesystem.js similarity index 93% rename from public/assets/pages/filespage/ctrl_filesystem_state.js rename to public/assets/pages/filespage/state_filesystem.js index 5561185a..d40c93e3 100644 --- a/public/assets/pages/filespage/ctrl_filesystem_state.js +++ b/public/assets/pages/filespage/state_filesystem.js @@ -2,10 +2,10 @@ import rxjs, { effect, preventDefault } from "../../lib/rx.js"; const state$ = new rxjs.BehaviorSubject({ view: "grid", - sort: null, + sort: "type", show_hidden: false, order: null, - search_mode: false, + search: "", }); export const getState$ = () => state$.asObservable(); diff --git a/public/assets/pages/filespage/state_selection.js b/public/assets/pages/filespage/state_selection.js new file mode 100644 index 00000000..5a0c2830 --- /dev/null +++ b/public/assets/pages/filespage/state_selection.js @@ -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= lowerBound; +} diff --git a/public/assets/pages/filespage/thing.js b/public/assets/pages/filespage/thing.js index dbb03e14..2d6b6e8d 100644 --- a/public/assets/pages/filespage/thing.js +++ b/public/assets/pages/filespage/thing.js @@ -1,5 +1,8 @@ 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 = { FILE: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiPgogIDxwYXRoIHN0eWxlPSJjb2xvcjojMDAwMDAwO3RleHQtaW5kZW50OjA7dGV4dC10cmFuc2Zvcm06bm9uZTtmaWxsOiM4YzhjOGM7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlLXdpZHRoOjAuOTg0ODEwNDEiIGQ9Im0gMiwxMy4wODI0MTIgMC4wMTk0NjIsMS40OTIzNDcgYyA1ZS02LDAuMjIyMTQ1IDAuMjA1NTkwMiwwLjQyNDI2MiAwLjQzMTE1MDIsMC40MjQyNzIgTCAxMy41ODk2MTIsMTUgQyAxMy44MTUxNzMsMTQuOTk5OTk1IDEzLjk5OTk5LDE0Ljc5Nzg3NCAxNCwxNC41NzU3MjkgdiAtMS40OTMzMTcgYyAtNC4xNzE4NjkyLDAuNjYyMDIzIC03LjY1MTY5MjgsMC4zOTg2OTYgLTEyLDAgeiIgLz4KICA8cGF0aCBzdHlsZT0iY29sb3I6IzAwMDAwMDt0ZXh0LWluZGVudDowO3RleHQtdHJhbnNmb3JtOm5vbmU7ZGlzcGxheTppbmxpbmU7ZmlsbDojYWFhYWFhO3N0cm9rZS13aWR0aDowLjk4NDA4MTI3IiBkPSJNIDIuMzUwMSwxLjAwMTMzMTIgQyAyLjE1MjU5LDEuMDM4MzI0NyAxLjk5NjU5LDEuMjI3MjcyMyAyLjAwMDA5LDEuNDI0OTM1NiBWIDE0LjEzMzQ1NyBjIDVlLTYsMC4yMjE4MTYgMC4yMDUyMywwLjQyMzYzNCAwLjQzMDc5LDAuNDIzNjQ0IGwgMTEuMTM5LC0xLjAxZS00IGMgMC4yMjU1NiwtNmUtNiAwLjQzMDExLC0wLjIwMDc1OCAwLjQzMDEyLC0wLjQyMjU3NCBsIDYuN2UtNCwtOS44MjI2NDI2IGMgLTIuNDg0MDQ2LC0xLjM1NTAwNiAtMi40MzUyMzQsLTIuMDMxMjI1NCAtMy41MDAxLC0zLjMwOTcwNyAtMC4wNDMsLTAuMDE1ODgyIDAuMDQ2LDAuMDAxNzQgMCwwIEwgMi40MzA2NywxLjAwMTEwOCBDIDIuNDAzODMsMC45OTg1OSAyLjM3Njc0LDAuOTk4NTkgMi4zNDk5LDEuMDAxMTA4IFoiIC8+CiAgPHBhdGggc3R5bGU9ImRpc3BsYXk6aW5saW5lO2ZpbGw6IzhjOGM4YztmaWxsLW9wYWNpdHk6MTtzdHJva2U6IzllNzU3NTtzdHJva2Utd2lkdGg6MDtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2Utb3BhY2l0eToxIiBkPSJtIDEwLjUwMDU3LDEuMDAyMDc2NCBjIDAsMy4yNzY4MDI4IC0wLjAwNTIsMy4xNzM5MTYxIDAuMzYyOTIxLDMuMjY5ODIwMiAwLjI4MDEwOSwwLjA3Mjk4NCAzLjEzNzE4LDAuMDM5ODg3IDMuMTM3MTgsMC4wMzk4ODcgLTEuMTIwMDY3LC0xLjA1NTY2OTIgLTIuMzMzNCwtMi4yMDY0NzEzIC0zLjUwMDEsLTMuMzA5NzA3NCB6IiAvPgo8L3N2Zz4K", @@ -19,7 +22,7 @@ const $tmpl = createElement(` __TEMPLATE__ - 06/06/2020 +
@@ -35,6 +38,7 @@ const $tmpl = createElement(` export function createThing({ name = null, type = "N/A", + time = 0, path = null, // size = 0, // time = null, @@ -44,9 +48,9 @@ export function createThing({ n = 0, }) { const $thing = $tmpl.cloneNode(true); - if (!($thing instanceof window.HTMLElement)) throw new Error("assertion failed: $thing must be an HTMLELement"); - const $label = $thing.querySelector(".component_filename .file-details > span"); - if (!($label instanceof window.HTMLElement)) throw new Error("assertion failed: $label must be an HTMLELement"); + assert.type($thing, window.HTMLElement); + const $label = qs($thing, ".component_filename .file-details > span"); + const $time = qs($thing, ".component_datetime"); $label.textContent = name; $thing.querySelector("a").setAttribute("href", link); @@ -54,10 +58,7 @@ export function createThing({ $thing.setAttribute("data-droptarget", type === "directory"); $thing.setAttribute("data-n", n); $thing.classList.add("view-" + view); - const sideEffectSelection = ($el, checked) => { - $el.classList.add(checked ? "selected" : "not-selected"); - $el.querySelector(`input[type="checkbox"]`).checked = checked; - }; + $time.textContent = formatTime(new Date(time)); sideEffectSelection($thing, isSelected(n)); if (type === "hidden") $thing.classList.add("hidden"); @@ -79,7 +80,7 @@ export function createThing({ const crt = $thing.cloneNode(true); $thing.style.opacity = "0.7"; const $box = crt.querySelector(".box"); - crt.style.opacity = "0.2 " + crt.style.opacity = "0.2"; crt.style.backgroundColor = "var(--border)"; $box.style.backgroundColor = "inherit"; $box.style.border = "none"; @@ -102,3 +103,17 @@ export function createThing({ }; 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("/"); +}