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