draggable selection in front

This commit is contained in:
b1ek 2024-11-11 20:34:05 +10:00
parent 9f7c9f5e10
commit 1617846d86
Signed by: blek
GPG Key ID: A622C22C9BC616B2
9 changed files with 179 additions and 13 deletions

View File

@ -11,6 +11,7 @@
"dependencies": {
"ky": "^1.7.2",
"preact": "^10.24.3",
"react-draggable-list": "^4.2.1",
"react-infinite-scroller": "^1.2.6"
},
"devDependencies": {

View File

@ -0,0 +1,28 @@
import ky from "ky";
import { DataRow } from "./DataRow";
export class DataRowSelection {
constructor(
public rows: DataRow[]
) { }
set(newRows: DataRow[]): DataRow[] {
return this.rows = newRows;
}
async save() {
await ky('/api/selection', {
method: 'PUT',
json: {
selection: this.rows
}
})
}
async get(): Promise<DataRow[]> {
const req = await ky('/api/selection', { method: 'GET' });
const data = await req.json();
if (!(data instanceof Array)) throw new Array('API returned invalid data');
return data.map(DataRow.reviveJSON)
}
}

View File

@ -0,0 +1,17 @@
.draggableListRow {
padding: 0;
display: flex;
flex-direction: row;
gap: 1rem;
span {
display: flex;
align-items: center;
}
.posts {
&.selected {
transform: scale(1.1);
}
}
}

View File

@ -0,0 +1,42 @@
import DraggableList from 'react-draggable-list';
import { DataRow } from '../api/DataRow';
import { Post } from './display/Post';
import style from './DraggablePostsList.module.scss';
type SetRowsFunction = (data: DataRow[]) => void;
function PostDraggableListItem({ item, dragHandleProps, rows, setRows }: { item: DataRow, dragHandleProps: object, setRows: SetRowsFunction, rows: DataRow[] }) {
function onChange() {
rows.splice(rows.indexOf(item), 1);
setRows(rows);
}
return (
<div className={style.draggableListRow}>
<span>
<label for={`row-list-${item.id}`} style={{ display: 'none' }}>Select</label>
<input type='checkbox' id={`row-list-${item.id}`} onChange={onChange}/>
</span>
<Post className={style.posts} row={item} {...dragHandleProps} />
</div>
)
}
export type DraggablePostsList = {
rows: DataRow[],
setRows: SetRowsFunction
};
export function DraggablePostsList(props: DraggablePostsList) {
return (
<DraggableList
itemKey={'id'}
// @ts-expect-error
template={p => PostDraggableListItem({ ...p, setRows: props.setRows, rows: props.rows })}
list={props.rows}
onMoveEnd={rows => props.setRows([ ...rows ])}
/>
);
}

View File

@ -1,4 +1,3 @@
import { ComponentChildren, toChildArray } from "preact";
import { DataRow } from "../api/DataRow";
import style from './Posts.module.scss';
import { Post } from "./display/Post";

View File

@ -1,10 +1,10 @@
import { DataRow } from "../../api/DataRow";
import style from './Post.module.scss';
export type PostProps = { row: DataRow };
export function Post({ row }: PostProps) {
export type PostProps = { row: DataRow, className?: string };
export function Post({ row, className, ...props }: PostProps) {
return (
<div id={`post-${row.id}`} className={style.post}>
<div id={`post-${row.id} ${className}`} className={style.post} {...props}>
<pre>{row.data}</pre>
</div>
)

View File

@ -1,6 +1,10 @@
.index {
.selection {
background: antiquewhite;
padding: 1em 1rem;
h2 {
margin-top: 0;
}
}
.paginator {
border-bottom: 1px solid black;

View File

@ -1,25 +1,32 @@
import { useCallback, useState } from "preact/hooks";
import { useCallback, useEffect, useState } from "preact/hooks";
import { DataRow } from "../api/DataRow";
import style from './index.module.scss';
import { Posts } from "../components/Posts";
import InfiniteScroller from 'react-infinite-scroller';
// let page = -1;
import { DraggablePostsList } from "../components/DraggablePostsList";
import { DataRowSelection } from "../api/DataRowSelection";
export function Index() {
const [ data, setData ] = useState(null as null | DataRow[]);
const [ selected, setSelected ] = useState(null as null | DataRow[]);
const dataRowSelection = new DataRowSelection(selected ?? []);
const [ hasMore, setHasMore ] = useState(true);
const [ fetching, setFetching ] = useState(false);
useEffect(() => {
(async () => {
setSelected(await dataRowSelection.get());
})();
}, []);
const fetchMore = useCallback(
async function(_page: number) {
if (fetching) return;
setFetching(true);
await new Promise(r => setTimeout(r, 1000));
// await new Promise(r => setTimeout(r, 1000));
const newData = await DataRow.find({
page: data ? Math.floor(data.length / 20) : 0
});
@ -30,11 +37,17 @@ export function Index() {
[ fetching, hasMore ]
);
async function saveSelection(rows: DataRow[]) {
setSelected(rows);
dataRowSelection.set(rows);
await dataRowSelection.save();
}
function select(row: DataRow) {
console.log(row);
if (data) {
if (data.indexOf(row) == -1) throw new Error('Selected element that does not exist');
setSelected([ ...selected ?? [], row ])
saveSelection([ ...selected ?? [], row ])
setData(data)
}
}
@ -47,7 +60,7 @@ export function Index() {
?
<div className={style.selection}>
<h2>Selection</h2>
<Posts rows={selected} checked />
<DraggablePostsList rows={selected} setRows={saveSelection} />
</div>
: null
}
@ -57,7 +70,7 @@ export function Index() {
loadMore={fetchMore}
hasMore={hasMore}
loader={<pre>loading . . .</pre>}
useWindow={false}
useWindow={true}
>
{
data

View File

@ -151,6 +151,13 @@
"@babel/plugin-syntax-jsx" "^7.25.9"
"@babel/types" "^7.25.9"
"@babel/runtime@^7.0.0", "@babel/runtime@^7.24.4":
version "7.26.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1"
integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==
dependencies:
regenerator-runtime "^0.14.0"
"@babel/template@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016"
@ -569,7 +576,7 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
"@types/prop-types@*":
"@types/prop-types@*", "@types/prop-types@^15.7.3":
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==
@ -787,6 +794,11 @@ he@1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
immutability-helper@^3.0.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/immutability-helper/-/immutability-helper-3.1.1.tgz#2b86b2286ed3b1241c9e23b7b21e0444f52f77b7"
integrity sha512-Q0QaXjPjwIju/28TsugCHNEASwoCcJSyJV3uO1sOIQGI0jKgm9f41Lvz0DZj3n46cNCyAZTsEYoY4C2bVRUzyQ==
immutable@^4.0.0:
version "4.3.7"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.7.tgz#c70145fc90d89fb02021e65c84eb0226e4e5a381"
@ -903,6 +915,16 @@ object-assign@^4.1.1:
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
performance-now@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
integrity sha512-YHk5ez1hmMR5LOkb9iJkLKqoBlL7WD5M8ljC75ZfzXriuBIVNuecaXuU7e+hOwyqf24Wxhh7Vxgt7Hnw9288Tg==
performance-now@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==
picocolors@^1.0.0, picocolors@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
@ -927,7 +949,7 @@ preact@^10.24.3:
resolved "https://registry.yarnpkg.com/preact/-/preact-10.24.3.tgz#086386bd47071e3b45410ef20844c21e23828f64"
integrity sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==
prop-types@^15.5.8:
prop-types@^15.5.8, prop-types@^15.6.0:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@ -936,6 +958,25 @@ prop-types@^15.5.8:
object-assign "^4.1.1"
react-is "^16.13.1"
raf@^3.1.0:
version "3.4.1"
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==
dependencies:
performance-now "^2.1.0"
react-draggable-list@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/react-draggable-list/-/react-draggable-list-4.2.1.tgz#5a62f4aa6ff7074fd9181dab182ec594257ce7aa"
integrity sha512-seSieUYYfMElCJAgYWZvnwCdg42Ca9lta3K9mHOWOI4v2temDHEodhk3UCvqJQGtOaKTeMUVWXnWLvEo+gSJ6g==
dependencies:
"@babel/runtime" "^7.0.0"
"@types/prop-types" "^15.7.3"
immutability-helper "^3.0.0"
prop-types "^15.6.0"
react-motion "^0.5.2"
react-multi-ref "^1.0.0"
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"
@ -948,11 +989,32 @@ react-is@^16.13.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-motion@^0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.2.tgz#0dd3a69e411316567927917c6626551ba0607316"
integrity sha512-9q3YAvHoUiWlP3cK0v+w1N5Z23HXMj4IF4YuvjvWegWqNPfLXsOBE/V7UvQGpXxHFKRQQcNcVQE31g9SB/6qgQ==
dependencies:
performance-now "^0.2.0"
prop-types "^15.5.8"
raf "^3.1.0"
react-multi-ref@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/react-multi-ref/-/react-multi-ref-1.0.2.tgz#40623789eb0afd257e624f8359e02d4be9434d52"
integrity sha512-6oS5yzrZ4UrdMHbF6QAnnaoIe9h8R+Xv4m8uJWVK8/Q4RCc6RTT0XJ/LZ7llVgFcVbnDHeUAcVIhtRgFyzjJpA==
dependencies:
"@babel/runtime" "^7.24.4"
readdirp@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.0.2.tgz#388fccb8b75665da3abffe2d8f8ed59fe74c230a"
integrity sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==
regenerator-runtime@^0.14.0:
version "0.14.1"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f"
integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==
rollup@^4.20.0:
version "4.24.4"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.24.4.tgz#fdc76918de02213c95447c9ffff5e35dddb1d058"