From 1617846d863c9360a2c52f0a09076bb4ce900c79 Mon Sep 17 00:00:00 2001 From: b1ek Date: Mon, 11 Nov 2024 20:34:05 +1000 Subject: [PATCH] draggable selection in front --- front/package.json | 1 + front/src/api/DataRowSelection.ts | 28 ++++++++ .../components/DraggablePostsList.module.scss | 17 +++++ front/src/components/DraggablePostsList.tsx | 42 ++++++++++++ front/src/components/Posts.tsx | 1 - front/src/components/display/Post.tsx | 6 +- front/src/pages/index.module.scss | 4 ++ front/src/pages/index.tsx | 27 ++++++-- front/yarn.lock | 66 ++++++++++++++++++- 9 files changed, 179 insertions(+), 13 deletions(-) create mode 100644 front/src/api/DataRowSelection.ts create mode 100644 front/src/components/DraggablePostsList.module.scss create mode 100644 front/src/components/DraggablePostsList.tsx diff --git a/front/package.json b/front/package.json index 84aefe3..09d1c21 100644 --- a/front/package.json +++ b/front/package.json @@ -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": { diff --git a/front/src/api/DataRowSelection.ts b/front/src/api/DataRowSelection.ts new file mode 100644 index 0000000..a0769f6 --- /dev/null +++ b/front/src/api/DataRowSelection.ts @@ -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 { + 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) + } +} \ No newline at end of file diff --git a/front/src/components/DraggablePostsList.module.scss b/front/src/components/DraggablePostsList.module.scss new file mode 100644 index 0000000..012efb0 --- /dev/null +++ b/front/src/components/DraggablePostsList.module.scss @@ -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); + } + } +} \ No newline at end of file diff --git a/front/src/components/DraggablePostsList.tsx b/front/src/components/DraggablePostsList.tsx new file mode 100644 index 0000000..babaabf --- /dev/null +++ b/front/src/components/DraggablePostsList.tsx @@ -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 ( +
+ + + + + +
+ ) +} + +export type DraggablePostsList = { + rows: DataRow[], + setRows: SetRowsFunction +}; +export function DraggablePostsList(props: DraggablePostsList) { + return ( + PostDraggableListItem({ ...p, setRows: props.setRows, rows: props.rows })} + list={props.rows} + onMoveEnd={rows => props.setRows([ ...rows ])} + + /> + ); +} \ No newline at end of file diff --git a/front/src/components/Posts.tsx b/front/src/components/Posts.tsx index 12431a7..2b7d28c 100644 --- a/front/src/components/Posts.tsx +++ b/front/src/components/Posts.tsx @@ -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"; diff --git a/front/src/components/display/Post.tsx b/front/src/components/display/Post.tsx index 6c2b3b1..96d85b2 100644 --- a/front/src/components/display/Post.tsx +++ b/front/src/components/display/Post.tsx @@ -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 ( -
+
{row.data}
) diff --git a/front/src/pages/index.module.scss b/front/src/pages/index.module.scss index 7058559..fa8685e 100644 --- a/front/src/pages/index.module.scss +++ b/front/src/pages/index.module.scss @@ -1,6 +1,10 @@ .index { .selection { background: antiquewhite; + padding: 1em 1rem; + h2 { + margin-top: 0; + } } .paginator { border-bottom: 1px solid black; diff --git a/front/src/pages/index.tsx b/front/src/pages/index.tsx index 6010e6a..3d98473 100644 --- a/front/src/pages/index.tsx +++ b/front/src/pages/index.tsx @@ -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() { ?

Selection

- +
: null } @@ -57,7 +70,7 @@ export function Index() { loadMore={fetchMore} hasMore={hasMore} loader={
loading . . .
} - useWindow={false} + useWindow={true} > { data diff --git a/front/yarn.lock b/front/yarn.lock index d84267f..4b22974 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -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"