scroll view and boilderplate selection

This commit is contained in:
b1ek 2024-11-11 13:28:33 +10:00
parent 6dcce42cab
commit 9f7c9f5e10
Signed by: blek
GPG Key ID: A622C22C9BC616B2
6 changed files with 146 additions and 48 deletions

View File

@ -10,10 +10,12 @@
}, },
"dependencies": { "dependencies": {
"ky": "^1.7.2", "ky": "^1.7.2",
"preact": "^10.24.3" "preact": "^10.24.3",
"react-infinite-scroller": "^1.2.6"
}, },
"devDependencies": { "devDependencies": {
"@preact/preset-vite": "^2.9.1", "@preact/preset-vite": "^2.9.1",
"@types/react-infinite-scroller": "^1.2.5",
"sass": "^1.80.6", "sass": "^1.80.6",
"sass-embedded": "^1.80.6", "sass-embedded": "^1.80.6",
"typescript": "~5.6.2", "typescript": "~5.6.2",

View File

@ -1,3 +1,10 @@
.posts { .posts {
display: block; display: flex;
flex-direction: column;
li {
display: flex;
align-items: center;
gap: 1rem;
}
} }

View File

@ -1,13 +1,34 @@
import { ComponentChildren, toChildArray } from "preact";
import { DataRow } from "../api/DataRow"; import { DataRow } from "../api/DataRow";
import style from './Posts.module.scss'; import style from './Posts.module.scss';
import { Post } from "./display/Post"; import { Post } from "./display/Post";
export type PostsProps = { rows: DataRow[] }; export type PostsProps = {
rows: DataRow[],
checked?: boolean,
onSelectChange?: (row: DataRow) => void
};
export function Posts(props: PostsProps) {
function onChange(row: DataRow) {
if (props.onSelectChange) props.onSelectChange(row)
}
export function Posts({ rows }: PostsProps) {
return ( return (
<div className={style.posts}> <ul className={style.posts}>
{ rows.map(row => <Post row={row} />) } {
</div> props.rows.map(
row =>
<li>
<span>
<label for={`row-list-${row.id}`} style={{ display: 'none' }}>Select</label>
<input type='checkbox' checked={props.checked ?? false} id={`row-list-${row.id}`} onChange={_ => onChange(row)}/>
</span>
<Post row={row} />
</li>
)
}
</ul>
); );
} }

View File

@ -1,4 +1,7 @@
.index { .index {
.selection {
background: antiquewhite;
}
.paginator { .paginator {
border-bottom: 1px solid black; border-bottom: 1px solid black;
padding-bottom: 1rem; padding-bottom: 1rem;

View File

@ -1,63 +1,70 @@
import { useEffect, useState } from "preact/hooks"; import { useCallback, useState } from "preact/hooks";
import { DataRow } from "../api/DataRow"; import { DataRow } from "../api/DataRow";
import style from './index.module.scss'; import style from './index.module.scss';
import { Posts } from "../components/Posts"; import { Posts } from "../components/Posts";
import { Paginator } from "../components/display/Paginator";
const cachedPages: { import InfiniteScroller from 'react-infinite-scroller';
[ key: number ]: DataRow[]
} = {};
let firstRender = true; // let page = -1;
export function Index() { export function Index() {
const [ data, setData ] = useState(null as null | DataRow[]); const [ data, setData ] = useState(null as null | DataRow[]);
const [ page, setPage ] = useState(NaN); const [ selected, setSelected ] = useState(null as null | DataRow[]);
const [ pageBlocked, setPageBlocked ] = useState(false);
async function goToPage(newPage: number) { const [ hasMore, setHasMore ] = useState(true);
if (newPage < 0) newPage = 0; const [ fetching, setFetching ] = useState(false);
if (pageBlocked) return;
let newData = null as DataRow[] | null; const fetchMore = useCallback(
if (cachedPages[newPage]) { async function(_page: number) {
newData = cachedPages[newPage]; if (fetching) return;
} else {
setData(null);
}
setPage(newPage); setFetching(true);
setPageBlocked(true); await new Promise(r => setTimeout(r, 1000));
try { const newData = await DataRow.find({
newData = await DataRow.find({ page: data ? Math.floor(data.length / 20) : 0
page: newPage
}); });
} catch (_) { setData([ ...data ?? [], ...newData ]);
return setPageBlocked(false) setHasMore(newData.length === 20);
} setFetching(false);
},
[ fetching, hasMore ]
);
if (newData === undefined) { function select(row: DataRow) {
newData = [] as DataRow[]; console.log(row);
if (data) {
if (data.indexOf(row) == -1) throw new Error('Selected element that does not exist');
setSelected([ ...selected ?? [], row ])
setData(data)
} }
cachedPages[newPage] = newData;
setPageBlocked(false);
setData(newData);
} }
if (firstRender) {
goToPage(0);
firstRender = false;
}
return ( return (
<div className={style.index}> <div className={style.index}>
<Paginator page={page} onChange={goToPage} className={style.paginator} />
{ {
data selected
? <Posts rows={data} /> ?
: <pre>loading . . .</pre> <div className={style.selection}>
<h2>Selection</h2>
<Posts rows={selected} checked />
</div>
: null
} }
<InfiniteScroller
pageStart={0}
loadMore={fetchMore}
hasMore={hasMore}
loader={<pre>loading . . .</pre>}
useWindow={false}
>
{
data
? <Posts rows={data.filter(x => selected ? selected?.indexOf(x) === -1 : true)} onSelectChange={select} />
: <></>
}
</InfiniteScroller>
</div> </div>
) )
} }

View File

@ -569,6 +569,26 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
"@types/prop-types@*":
version "15.7.13"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.13.tgz#2af91918ee12d9d32914feb13f5326658461b451"
integrity sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==
"@types/react-infinite-scroller@^1.2.5":
version "1.2.5"
resolved "https://registry.yarnpkg.com/@types/react-infinite-scroller/-/react-infinite-scroller-1.2.5.tgz#7c770be59465f3aaa1b86377d792d52de5e74047"
integrity sha512-fJU1jhMgoL6NJFrqTM0Ob7tnd2sQWGxe2ESwiU6FZWbJK/VO/Er5+AOhc+e2zbT0dk5pLygqctsulOLJ8xnSzw==
dependencies:
"@types/react" "*"
"@types/react@*":
version "18.3.12"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.12.tgz#99419f182ccd69151813b7ee24b792fe08774f60"
integrity sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==
dependencies:
"@types/prop-types" "*"
csstype "^3.0.2"
babel-plugin-transform-hook-names@^1.0.2: babel-plugin-transform-hook-names@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-hook-names/-/babel-plugin-transform-hook-names-1.0.2.tgz#0d75c2d78e8bbcdb258241131562b9cf07f010f3" resolved "https://registry.yarnpkg.com/babel-plugin-transform-hook-names/-/babel-plugin-transform-hook-names-1.0.2.tgz#0d75c2d78e8bbcdb258241131562b9cf07f010f3"
@ -639,6 +659,11 @@ css-what@^6.1.0:
resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
csstype@^3.0.2:
version "3.1.3"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
debug@^4.1.0, debug@^4.3.1, debug@^4.3.4: debug@^4.1.0, debug@^4.3.1, debug@^4.3.4:
version "4.3.7" version "4.3.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
@ -784,7 +809,7 @@ is-number@^7.0.0:
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
js-tokens@^4.0.0: "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
@ -809,6 +834,13 @@ ky@^1.7.2:
resolved "https://registry.yarnpkg.com/ky/-/ky-1.7.2.tgz#b97d9b997ba51ff1e152f0815d3d27b86513eb1c" resolved "https://registry.yarnpkg.com/ky/-/ky-1.7.2.tgz#b97d9b997ba51ff1e152f0815d3d27b86513eb1c"
integrity sha512-OzIvbHKKDpi60TnF9t7UUVAF1B4mcqc02z5PIvrm08Wyb+yOcz63GRvEuVxNT18a9E1SrNouhB4W2NNLeD7Ykg== integrity sha512-OzIvbHKKDpi60TnF9t7UUVAF1B4mcqc02z5PIvrm08Wyb+yOcz63GRvEuVxNT18a9E1SrNouhB4W2NNLeD7Ykg==
loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
lru-cache@^5.1.1: lru-cache@^5.1.1:
version "5.1.1" version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
@ -866,6 +898,11 @@ nth-check@^2.0.1:
dependencies: dependencies:
boolbase "^1.0.0" boolbase "^1.0.0"
object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
picocolors@^1.0.0, picocolors@^1.1.0: picocolors@^1.0.0, picocolors@^1.1.0:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
@ -890,6 +927,27 @@ preact@^10.24.3:
resolved "https://registry.yarnpkg.com/preact/-/preact-10.24.3.tgz#086386bd47071e3b45410ef20844c21e23828f64" resolved "https://registry.yarnpkg.com/preact/-/preact-10.24.3.tgz#086386bd47071e3b45410ef20844c21e23828f64"
integrity sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA== integrity sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==
prop-types@^15.5.8:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.13.1"
react-infinite-scroller@^1.2.6:
version "1.2.6"
resolved "https://registry.yarnpkg.com/react-infinite-scroller/-/react-infinite-scroller-1.2.6.tgz#8b80233226dc753a597a0eb52621247f49b15f18"
integrity sha512-mGdMyOD00YArJ1S1F3TVU9y4fGSfVVl6p5gh/Vt4u99CJOptfVu/q5V/Wlle72TMgYlBwIhbxK5wF0C/R33PXQ==
dependencies:
prop-types "^15.5.8"
react-is@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
readdirp@^4.0.1: readdirp@^4.0.1:
version "4.0.2" version "4.0.2"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.0.2.tgz#388fccb8b75665da3abffe2d8f8ed59fe74c230a" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.0.2.tgz#388fccb8b75665da3abffe2d8f8ed59fe74c230a"