Skip to content

Commit

Permalink
paginated files list
Browse files Browse the repository at this point in the history
  • Loading branch information
rejetto committed Jan 23, 2022
1 parent 546dfe9 commit ef52033
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 29 deletions.
52 changes: 42 additions & 10 deletions frontend/src/BrowseFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function usePath() {
}

export interface DirEntry { n:string, s?:number, m?:string, c?:string,
ext:string, isFolder:boolean, t?:Date, hidden?:boolean } // we memoize these value for speed
ext:string, isFolder:boolean, t?:Date } // we memoize these value for speed
export type DirList = DirEntry[]

export function BrowseFiles() {
Expand All @@ -25,14 +25,46 @@ export function BrowseFiles() {
}

function FilesList() {
const snap = useSnapState()
const { list, loading } = snap
const { filteredList, list, loading, stoppedSearch } = useSnapState()
const midnight = useMidnight() // as an optimization we calculate this only once per list
return h('ul', { className: 'dir' },
!list.length ? (!loading && (snap.stoppedSearch ? 'Stopped before finding anything' : 'Nothing here'))
: list.map((entry: DirEntry) =>
h(Entry, { key: entry.n, midnight, ...entry })),
loading && h(Spinner))
const pageSize = 100
const [page, setPage] = useState(0)
const offset = page * pageSize
const theList = filteredList || list
const total = theList.length

useEffect(() => setPage(0), [theList])
useEffect(() => document.scrollingElement?.scrollTo(0,0), [page])

return h(Fragment, {},
h('ul', { className: 'dir' },
!list.length ? (!loading && (stoppedSearch ? 'Stopped before finding anything' : 'Nothing here'))
: filteredList && !filteredList.length ? 'No match for this filter'
: theList.slice(offset, offset + pageSize).map((entry: DirEntry) =>
h(Entry, { key: entry.n, midnight, ...entry })),
loading && h(Spinner),
),
total > pageSize && h(Paging, { total, current:page, pageSize, pageChange:setPage })
)
}

interface PagingProps {
total: number
current: number
pageSize: number
pageChange:(newPage:number) => void
}
function Paging({ total, current, pageSize, pageChange }: PagingProps) {
const nPages = Math.ceil(total / pageSize)
const pages = []
for (let i=0; i<nPages; i++)
pages.push(h('button', {
...i===current && { className:'toggled' },
onClick(){
pageChange(i)
}
}, i*pageSize || 1))
return h('div', { id:'paging' }, ...pages)
}

function useMidnight() {
Expand All @@ -56,13 +88,13 @@ function isMobile() {
}

const Entry = memo(function(entry: DirEntry & { midnight: Date }) {
let { n: relativePath, hidden, isFolder } = entry
let { n: relativePath, isFolder } = entry
const base = usePath()
const { showFilter, selected } = useSnapState()
const href = fixUrl(relativePath)
const containerDir = isFolder ? '' : relativePath.substring(0, relativePath.lastIndexOf('/')+1)
const name = relativePath.substring(containerDir.length)
return h('li', { className: (isFolder ? 'folder' : 'file') + (hidden ? ' hidden' : '') },
return h('li', { className: isFolder ? 'folder' : 'file' },
showFilter && h(Checkbox, {
value: selected[relativePath],
onChange(v){
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/Head.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function Head() {
}

function FolderStats() {
const { list, loading, filteredEntries, selected, stoppedSearch } = useSnapState()
const { list, loading, filteredList, selected, stoppedSearch } = useSnapState()
const stats = useMemo(() =>{
let files = 0, folders = 0, size = 0
for (const x of list) {
Expand All @@ -28,15 +28,16 @@ function FolderStats() {
return { files, folders, size }
}, [list])
const sel = Object.keys(selected).length
const fil = filteredList?.length
return h('div', { id:'folder-stats' },
stoppedSearch ? hIcon('interrupted', { title:'Search was interrupted' })
: list?.length>0 && loading && h(Spinner),
: list.length>0 && loading && h(Spinner),
[
prefix('', stats.files,' file(s)'),
prefix('', stats.folders, ' folder(s)'),
stats.size ? formatBytes(stats.size) : '',
sel && sel+' selected',
filteredEntries >= 0 && filteredEntries+' displayed',
fil !== undefined && fil < list.length && fil+' displayed',
].filter(Boolean).join(', ')
)
}
40 changes: 40 additions & 0 deletions frontend/src/index.scss
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
:root {
--bg: #fff;
--text: #555;
--faint-contrast: #0002;
--mild-contrast: #0005;
--good-contrast: #000a;
--button-bg: #a4bac9;
--button-text: #444;
.theme-dark {
--bg: #000;
--text: #999;
--faint-contrast: #fff2;
--mild-contrast: #fff5;
--good-contrast: #fffa;
--button-bg: #345;
Expand All @@ -31,6 +34,9 @@ body, button, select, input { font-size: 12pt; }
#root {
max-width: 50em;
margin: auto;
min-height: 100vh;
display: flex;
flex-direction: column;
}
body, input {
color: var(--text);
Expand Down Expand Up @@ -144,6 +150,7 @@ header input {
}

ul.dir {
flex: 1;
padding: 0;
margin: 0;
clear: both;
Expand Down Expand Up @@ -206,6 +213,39 @@ button label {
animation: 1s fade-in;
}

#paging {
display: flex;
position: sticky;
bottom: 0;
background: var(--bg);
gap: .5em;
overflow-x: auto;
&>button {
flex: 1;
background: var(--button-bg);
text-align: center;
}
}

/* Works on Firefox */
* {
scrollbar-width: thin;
scrollbar-color: var(--button-bg) var(--faint-contrast);
}

/* Works on Chrome, Edge, and Safari */
*::-webkit-scrollbar {
width: 12px;
}
*::-webkit-scrollbar-track {
background: var(--faint-contrast);
}
*::-webkit-scrollbar-thumb {
background-color: var(--button-bg);
border-radius: 20px;
border: 1px solid var(--faint-contrast)
}

@media (max-width: 40em) {
body, button, select { font-size: 14pt; }
#menu-bar {
Expand Down
11 changes: 5 additions & 6 deletions frontend/src/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,11 @@ export function MenuPanel() {
label: 'Invert selection',
onClick() {
const sel = state.selected
for (const { hidden, n } of state.list)
if (!hidden)
if (sel[n])
delete sel[n]
else
sel[n] = true
for (const { n } of state.list)
if (sel[n])
delete sel[n]
else
sel[n] = true
}
})
)
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ export const state = proxy<{
iconsClass: string,
username: string,
list: DirList,
filteredList?: DirList,
loading: boolean,
error: Error | null,
listReloader: number,
patternFilter: string,
showFilter: boolean,
selected: Record<string,true>, // optimization: by using an object instead of an array, components are not rendered when the array changes, but only when their specific property change
remoteSearch: string,
filteredEntries: number,
sortBy: string,
invertOrder: boolean,
foldersFirst: boolean,
Expand All @@ -25,14 +25,14 @@ export const state = proxy<{
iconsClass: '',
username: '',
list: [],
filteredList: undefined,
loading: false,
error: null,
listReloader: 0,
patternFilter: '',
showFilter: false,
selected: {},
remoteSearch: '',
filteredEntries: -1,
sortBy: 'name',
invertOrder: false,
foldersFirst: true,
Expand Down
17 changes: 9 additions & 8 deletions frontend/src/useFetchList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export default function useFetchList() {
const API = 'file_list'
const baseParams = { path:desiredPath, search, sse:true, omit:'c' }
state.list = []
state.filteredList = undefined
state.selected = {}
state.loading = true
state.error = null
Expand Down Expand Up @@ -113,12 +114,12 @@ subscribeKey(state, 'invertOrder', sortAgain)
subscribeKey(state, 'foldersFirst', sortAgain)

subscribeKey(state, 'patternFilter', v => {
const filter = v > '' && new RegExp(_.escapeRegExp(v),'i')
let n = 0
for (const entry of state.list) {
entry.hidden = filter && !filter.test(entry.n)
if (!entry.hidden)
++n
}
state.filteredEntries = filter ? n : -1
if (!v)
return state.filteredList = undefined
const filter = new RegExp(_.escapeRegExp(v),'i')
const newList = []
for (const entry of state.list)
if (filter.test(entry.n))
newList.push(entry)
state.filteredList = newList
})

0 comments on commit ef52033

Please sign in to comment.