import { createElement } from "../../lib/skeleton/index.js"; import { animate, slideYIn } from "../../lib/animate.js"; import rxjs, { effect } from "../../lib/rx.js"; import { CSS } from "../../helpers/loader.js"; import { qs } from "../../lib/dom.js"; import { ApplicationError } from "../../lib/error.js"; import { toggle as toggleLoader } from "../../components/loader.js"; import ctrlError from "../ctrl_error.js"; import { createThing, css } from "./thing.js"; import { handleError, getFiles } from "./ctrl_filesystem_state.js"; import { ls } from "./model_files.js"; export default async function(render) { const $page = createElement(`
`); render($page); // feature: virtual scrolling const path = location.pathname.replace(new RegExp("^/files"), ""); effect(rxjs.of(path).pipe( toggleLoader($page, true), rxjs.mergeMap(() => new Promise((done) => setTimeout(() => done({ files: new Array(400).fill(1), }), 1000))), toggleLoader($page, false), rxjs.mergeMap(({ files }) => { // STEP1: setup the list of files const FILE_HEIGHT = 160; const BLOCK_SIZE = Math.ceil(document.body.clientHeight / FILE_HEIGHT) + 1; // const BLOCK_SIZE = 7; const COLUMN_PER_ROW = 2; const VIRTUAL_SCROLL_MINIMUM_TRIGGER = 10; let size = files.length; if (size > VIRTUAL_SCROLL_MINIMUM_TRIGGER) { size = BLOCK_SIZE * COLUMN_PER_ROW; } const $list = qs($page, ".list"); const $fs = document.createDocumentFragment(); for (let i = 0; i < size; i++) { $fs.appendChild(createThing({ name: `file ${i}`, type: "file", link: "/view/test.txt", })); } animate($list, { time: 200, keyframes: slideYIn(5) }); $list.appendChild($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"); const height = (Math.ceil(files.length / COLUMN_PER_ROW) - BLOCK_SIZE) * FILE_HEIGHT; if (height > 33554400) { console.log(`maximum CSS height reached, requested height ${height} is too large`); } const setHeight = (size) => { if (size < 0 || size > height) throw new ApplicationError( "INTERNAL ERROR", `assertion on size failed: size[${size}] height[${height}]` ); $listBefore.style.height = `${size}px`; $listAfter.style.height = `${height - size}px`; }; setHeight(0); const top = ($node) => $node.getBoundingClientRect().top; return rxjs.of({ files, currentState: 0, $list, setHeight, FILE_HEIGHT, BLOCK_SIZE, COLUMN_PER_ROW, MARGIN: 35, // TODO: top($list) - top($list.closest(".scroll-y")); }); }), rxjs.mergeMap(({ files, BLOCK_SIZE, COLUMN_PER_ROW, FILE_HEIGHT, MARGIN, currentState, height, setHeight, $list, }) => rxjs.fromEvent($page.closest(".scroll-y"), "scroll", { passive: true }).pipe( rxjs.map((e) => { // 0-------------0-----------1-----------2-----------3 .... // [padding] $block1 $block2 $block3 .... const nextState = Math.floor((e.target.scrollTop - MARGIN) / FILE_HEIGHT); return Math.max(nextState, 0); }), rxjs.distinctUntilChanged(), rxjs.debounce(() => new rxjs.Observable((observer) => { const id = requestAnimationFrame(() => observer.next()); return () => cancelAnimationFrame(id); })), rxjs.tap((nextState) => { // STEP1: calculate the virtual scroll paramameters let diff = nextState - currentState; const diffSgn = Math.sign(diff); if (Math.abs(diff) > BLOCK_SIZE) { // diff is bound by BLOCK_SIZE // we can't be moving more than what is on the screen diff = diffSgn * BLOCK_SIZE; } let fileStart = nextState * COLUMN_PER_ROW; if (diffSgn > 0) { // => scroll down fileStart += BLOCK_SIZE * COLUMN_PER_ROW; fileStart -= Math.min(diff, BLOCK_SIZE) * COLUMN_PER_ROW; } let fileEnd = fileStart + diffSgn * diff * COLUMN_PER_ROW; if (fileStart >= files.length) { // occur when BLOCK_SIZE is larger than its absolute minimum return; } else if (fileEnd > files.length) { // occur when files.length isn't a multiple of COLUMN_PER_ROW and // we've scrolled to the bottom of the list already nextState = Math.ceil(files.length / COLUMN_PER_ROW) - BLOCK_SIZE; fileEnd = files.length - 1; for (let i=0; i 0) { // scroll down $list.appendChild($fs); for (let i = 0; i < n; i++) $list.firstChild.remove(); } else { // scroll up $list.insertBefore($fs, $list.firstChild); for (let i = 0; i < n; i++) $list.lastChild.remove(); } setHeight(nextState * FILE_HEIGHT); currentState = nextState; }), )), rxjs.catchError(ctrlError()), )); }