frontend: paging with fixed first/last page

Massimo Melina committed Feb 9, 2023 at 15:23 UTC 5c1410d63a691a1810252b221afcd40412c2a024
6 files changed +83 -30
frontend/fontello-config.json renamed
+18 -6
@@ -78,12 +78,6 @@
78 "code": 61894,
79 "src": "fontawesome"
80 },
81 - {
82 - "uid": "bbfb51903f40597f0b70fd75bc7b5cac",
83 - "css": "trash",
84 - "code": 61944,
85 - "src": "fontawesome"
86 - },
81 {
82 "uid": "9dd9e835aebe1060ba7190ad2b2ed951",
83 "css": "search",
@@ -161,6 +155,24 @@
155 "css": "crown",
156 "code": 59460,
157 "src": "fontelico"
158 + },
159 + {
160 + "uid": "f48ae54adfb27d8ada53d0fd9e34ee10",
161 + "css": "trash",
162 + "code": 59405,
163 + "src": "fontawesome"
164 + },
165 + {
166 + "uid": "12052b30d23a1a70d6b32962d5464cae",
167 + "css": "to-start",
168 + "code": 59406,
169 + "src": "fontawesome"
170 + },
171 + {
172 + "uid": "c47efa0e3e74f6ba4c2562c1258fff1f",
173 + "css": "to-end",
174 + "code": 59408,
175 + "src": "fontawesome"
176 }
177 ]
178 }
\ No newline at end of file
frontend/public/fontello.css
+5 -3
@@ -1,6 +1,6 @@
1 @font-face {
2 font-family: 'fontello';
3 - src: url('fontello.woff2?13171865') format('woff2');
3 + src: url('fontello.woff2?1378305') format('woff2');
4 font-weight: normal;
5 font-style: normal;
6 }
@@ -10,7 +10,7 @@
10 @media screen and (-webkit-min-device-pixel-ratio:0) {
11 @font-face {
12 font-family: 'fontello';
13 - src: url('../font/fontello.svg?13171865#fontello') format('svg');
13 + src: url('../font/fontello.svg?1378305#fontello') format('svg');
14 }
15 }
16 */
@@ -62,7 +62,9 @@
62 .fa-user:before { content: '\e80a'; } /* '' */
63 .fa-home:before { content: '\e80b'; } /* '' */
64 .fa-key:before { content: '\e80c'; } /* '' */
65 +.fa-to-start:before { content: '\e80e'; } /* '' */
66 .fa-retweet:before { content: '\e80f'; } /* '' */
67 +.fa-to-end:before { content: '\e810'; } /* '' */
68 .fa-cancel-circled:before { content: '\e811'; } /* '' */
69 .fa-search:before { content: '\e813'; } /* '' */
70 .fa-logout:before { content: '\e814'; } /* '' */
@@ -74,4 +76,4 @@
76 .fa-unlink:before { content: '\f127'; } /* '' */
77 .fa-level-up:before { content: '\f148'; } /* '' */
78 .fa-file-archive:before { content: '\f1c6'; } /* '' */
77 -.fa-trash:before { content: '\f1f8'; } /* '' */
79 +.fa-trash:before { content: '\e80d'; } /* '' */
frontend/public/fontello.woff2
Binary files a/frontend/public/fontello.woff2 and b/frontend/public/fontello.woff2 differ
frontend/src/BrowseFiles.ts
+38 -14
@@ -1,7 +1,16 @@
1 // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3 import { Link, useLocation } from 'react-router-dom'
4 -import { createElement as h, Fragment, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
4 +import {
5 + createElement as h,
6 + Fragment,
7 + memo,
8 + useCallback,
9 + useEffect,
10 + useMemo,
11 + useRef,
12 + useState
13 +} from 'react'
14 import { domOn, formatBytes, hError, hfsEvent, hIcon, isMobile } from './misc'
15 import { Checkbox, Html, Spinner } from './components'
16 import { Head } from './Head'
@@ -65,7 +74,15 @@ function FilesList() {
74
75 const ref = useRef<HTMLElement>()
76
68 - const pageChange = useCallback((i: number) => {
77 + const [goBottom, setGoBottom] = useState(false)
78 + useEffect(() => {
79 + if (!goBottom) return
80 + setGoBottom(false)
81 + window.scrollTo(0, document.body.scrollHeight)
82 + }, [goBottom])
83 + const pageChange = useCallback((i: number, pleaseGoBottom?: boolean) => {
84 + if (pleaseGoBottom)
85 + setGoBottom(true)
86 if (i < page || i > page + extraPages)
87 return setPage(i)
88 i -= page + 1
@@ -100,21 +117,28 @@ interface PagingProps {
117 nPages: number
118 current: number
119 pageSize: number
103 - pageChange:(newPage:number) => void
120 + pageChange:(newPage:number, goBottom?:boolean) => void
121 }
122 const Paging = memo(({ nPages, current, pageSize, pageChange }: PagingProps) => {
123 const ref = useRef<HTMLElement>()
107 - const pages = []
108 - for (let i=0; i<nPages; i++)
109 - pages.push(h('button', {
110 - ...i===current && { className:'toggled', ref },
111 - //@ts-ignore
112 - onClick(){
113 - pageChange(i)
114 - }
115 - }, i*pageSize || "Page 1"))
124 useEffect(() => ref.current?.scrollIntoView({ block: 'nearest' }), [current])
117 - return h('div', { id:'paging' }, ...pages)
125 + return h('div', { id:'paging' },
126 + h('button', {
127 + className: !current ? 'toggled' : undefined,
128 + onClick() { pageChange(0) },
129 + }, hIcon('to-start')),
130 + h('div', { id: 'paging-middle' }, // using sticky first/last would prevent scrollIntoView from working
131 + _.range(1, nPages).map(i =>
132 + h('button', {
133 + key: i,
134 + ...i === current && { className: 'toggled', ref },
135 + onClick: () => pageChange(i),
136 + }, i * pageSize) )
137 + ),
138 + h('button', {
139 + onClick(){ pageChange(nPages-1, true) }
140 + }, hIcon('to-end')),
141 + )
142 })
143
144 function useMidnight() {
@@ -189,4 +213,4 @@ const EntryProps = memo(function(entry: DirEntry & { midnight: Date }) {
213 }
214 }, !shortTs ? t.toLocaleString() : today ? t.toLocaleTimeString() : t.toLocaleDateString()),
215 )
192 -})
216 +})
\ No newline at end of file
frontend/src/index.scss
+22 -6
@@ -87,7 +87,7 @@ button {
87 cursor: pointer;
88 }
89 button.toggled {
90 - opacity: .6;
90 + filter: brightness(.6);
91 }
92 button, .breadcrumb { /* consistent focus color */
93 &:focus-visible {
@@ -215,7 +215,7 @@ ul.dir {
215 padding-top: 1em;
216 position: relative;
217 &::before {
218 - content: "Page " attr(label);
218 + content: attr(label);
219 position: absolute;
220 top: -.8em;
221 background: var(--bg);
@@ -275,17 +275,33 @@ button label {
275 }
276
277 #paging {
278 - display: flex;
278 position: sticky;
279 bottom: 0;
280 + display: flex;
281 + gap: .2em;
282 background: var(--bg);
282 - gap: .5em;
283 - overflow-x: auto;
284 - &>button {
283 + &,&>button {
284 + box-shadow: 0 0 .3em .3em var(--bg);
285 + z-index: 1; /* make shadow visible also when page-first button is not selected */
286 + }
287 + padding: 0 0.2em 0.2em;
288 + & #paging-middle {
289 + display: flex;
290 + gap: .5em;
291 flex: 1;
292 + overflow-x: auto;
293 + }
294 + & *::-webkit-scrollbar { height: 8px; } /* keep this thin */
295 + & #paging-middle>button {
296 + flex: 1;
297 + padding-top: 0; padding-bottom: 0; /* reduce vertically in case a horizontal scrolling bar is needed */
298 + }
299 + & button {
300 + min-width: 2.5em;
301 background: var(--button-bg);
302 text-align: center;
303 white-space: nowrap;
304 + padding: .5em; /* fit more buttons on screen */
305 }
306 }
307
todo.md
-1
@@ -1,6 +1,5 @@
1 # To do
2 - frontend: new-folder button in upload
3 -- frontend: button to reach the bottom of the list
3 - admin/fs: check if source exists when set
4 - plugins: after installing, switch to installed (and perhaps highlight new one)
5 - plugins' log, accessible in admin