plugins: entry.getNext/Previous
Massimo Melina committed
May 24, 2023 at 14:47 UTC
9a8758017c3de21604c87665fa31cb1d94025635
7 files changed
+78
-43
dev-plugins.md
+7
-1
@@ -189,6 +189,8 @@ This is a list of available frontend-events, with respective object parameter an
189
190
The `Entry` type is an object with the following properties:
191
- `name: string` name of the entry.
192
+ - `ext: string` just the extension part of the name, dot excluded.
193
+ - `isFolder: boolean` true if it's a folder.
194
- `n: string` name of the entry, including relative path when searched in sub-folders.
195
- `uri: string` relative url of the entry.
196
- `s?: number` size of the entry, in bytes. It may be missing, for example for folders.
@@ -197,6 +199,8 @@ This is a list of available frontend-events, with respective object parameter an
199
- `m?: Date` modified-time.
200
- `p?: string` permissions missing
201
- `cantOpen: boolean` true if current user has no permission to open this entry
202
+ - `getNext/getPrevious: ()=>Entry` return next/previous Entry in list
203
+ - `getNextFiltered/getPreviousFiltered: ()=>Entry` as above, but considers the filtered-list instead
204
- output `Html`
205
- `afterEntryName`
206
- you receive each entry of the list, and optionally produce HTML code that will be added after the name of the entry.
@@ -256,10 +260,12 @@ HFS will scan through them in inverted alphabetical order searching for a compat
260
261
## API version history
262
263
+- 8.2 (v0.46.0)
264
+ - entry.getNext, getPrevious, getNextFiltered, getPreviousFiltered
265
- 8.1 (v0.45.0) should have been 0.44.0 but forgot to update number
266
- full URL support for frontend_js and frontend_css
267
- custom.html
262
- - entry.cantOpen
268
+ - entry.cantOpen, ext, isFolder
269
- HFS.apiCall, reloadList, logout, h, React, state, t, _, dialogLib, Icon, getPluginPublic
270
- second parameter of onEvent is now deprecated
271
- renamed: additionalEntryProps > additionalEntryDetails & entry-props > entry-details
frontend/src/Breadcrumbs.ts
+11
-8
@@ -3,7 +3,7 @@
3
import { Link } from 'react-router-dom'
4
import { createElement as h, Fragment, ReactElement } from 'react'
5
import { getPrefixUrl, hIcon } from './misc'
6
-import { state } from './state'
6
+import { DirEntry, state } from './state'
7
import { usePath, reloadList } from './useFetchList'
8
import { useI18N } from './i18n'
9
import { openFileMenu } from './fileMenu'
@@ -38,18 +38,21 @@ function Breadcrumb({ path, label, current }:{ current?: boolean, path?: string,
38
to: path || '/',
39
async onClick(ev) {
40
if (!current) return
41
- const asEntry = { n: '', uri: '', name: label as string, ext: '', isFolder: true, cantOpen: false }
42
- openFileMenu(asEntry, ev as any as MouseEvent, [
41
+ if (typeof label !== 'string')
42
+ return reload()
43
+ openFileMenu(new DirEntry(label), ev as any as MouseEvent, [
44
{
45
label: t`Reload`,
46
icon: 'reload',
46
- onClick() {
47
- state.remoteSearch = ''
48
- state.stopSearch?.()
49
- reloadList()
50
- }
47
+ onClick: reload
48
}
49
])
50
+
51
+ function reload() {
52
+ state.remoteSearch = ''
53
+ state.stopSearch?.()
54
+ reloadList()
55
+ }
56
}
57
}, label)
58
}
frontend/src/BrowseFiles.ts
+1
-5
@@ -14,7 +14,7 @@ import {
14
import { domOn, formatBytes, ErrorMsg, hIcon, isMobile, getHFS } from './misc'
15
import { Checkbox, CustomCode, Spinner } from './components'
16
import { Head } from './Head'
17
-import { state, useSnapState } from './state'
17
+import { DirEntry, state, useSnapState } from './state'
18
import { alertDialog } from './dialog'
19
import useFetchList, { usePath } from './useFetchList'
20
import { useAuthorized } from './login'
@@ -24,10 +24,6 @@ import { t, useI18N } from './i18n'
24
import { deleteFiles } from './menu'
25
import { openFileMenu } from './fileMenu'
26
27
-export interface DirEntry { n:string, s?:number, m?:string, c?:string, p?:string,
28
- name: string, uri: string, ext:string, isFolder:boolean, t?:Date, cantOpen: boolean } // we memoize these value for speed
29
-export type DirList = DirEntry[]
30
-
27
export const MISSING_PERM = "Missing permission"
28
29
export function BrowseFiles() {
frontend/src/fileMenu.ts
+2
-1
@@ -2,7 +2,8 @@ import { t, useI18N } from './i18n'
2
import { dontBotherWithKeys, formatBytes, hfsEvent, hIcon, isMobile, newDialog } from './misc'
3
import { createElement as h, Fragment, isValidElement, MouseEventHandler, ReactNode } from 'react'
4
import _ from 'lodash'
5
-import { DirEntry, getEntryIcon, MISSING_PERM } from './BrowseFiles'
5
+import { getEntryIcon, MISSING_PERM } from './BrowseFiles'
6
+import { DirEntry } from './state'
7
8
interface FileMenuEntry {
9
label: ReactNode
frontend/src/state.ts
+53
-1
@@ -3,7 +3,6 @@
3
import _ from 'lodash'
4
import { proxy, useSnapshot } from 'valtio'
5
import { subscribeKey } from 'valtio/utils'
6
-import { DirList } from './BrowseFiles'
6
7
export const state = proxy<{
8
stopSearch?: ()=>void,
@@ -78,3 +77,56 @@ function loadSettings() {
77
function storeSettings() {
78
localStorage.setItem(SETTINGS_KEY, JSON.stringify(_.pick(state, SETTINGS_TO_STORE)))
79
}
80
+
81
+export class DirEntry {
82
+ public readonly s?: number
83
+ public readonly m?: string
84
+ public readonly c?: string
85
+ public readonly p?: string
86
+ // we memoize these value for speed
87
+ public readonly name: string
88
+ public readonly uri: string
89
+ public readonly ext: string = ''
90
+ public readonly isFolder:boolean
91
+ public readonly t?:Date
92
+ public readonly cantOpen: boolean
93
+
94
+ constructor(public readonly n: string, rest?: object) {
95
+ Object.assign(this, rest) // we actually allow any custom property to be memorized
96
+ this.uri = pathEncode(this.n)
97
+ this.isFolder = this.n.endsWith('/')
98
+ if (!this.isFolder) {
99
+ const i = this.n.lastIndexOf('.') + 1
100
+ this.ext = i ? this.n.substring(i) : ''
101
+ }
102
+ const t = this.m || this.c
103
+ if (t)
104
+ this.t = new Date(t)
105
+ this.name = this.isFolder ? this.n.slice(this.n.lastIndexOf('/', this.n.length - 2) + 1, -1)
106
+ : this.n.slice(this.n.lastIndexOf('/') + 1)
107
+ this.cantOpen = Boolean(this.p?.includes(this.isFolder ? 'l' : 'r')) // to open we need list for folders and read for files
108
+ }
109
+
110
+ getNext() {
111
+ return this.getSibling(+1)
112
+ }
113
+ getPrevious() {
114
+ return this.getSibling(-1)
115
+ }
116
+ getNextFiltered() {
117
+ return this.getSibling(+1, state.filteredList)
118
+ }
119
+ getPreviousFiltered() {
120
+ return this.getSibling(-1, state.filteredList)
121
+ }
122
+ getSibling(ofs: number, list: DirList=state.list) {
123
+ return list[ofs + list.findIndex(x => x.n === this.n)]
124
+ }
125
+
126
+}
127
+export type DirList = DirEntry[]
128
+
129
+function pathEncode(s: string) {
130
+ return encodeURI(s).replace(/#/g, encodeURIComponent)
131
+}
132
+//unused function pathDecode(s: string) { return decodeURI(s).replace(/%23/g, '#') }
frontend/src/useFetchList.ts
+3
-26
@@ -1,9 +1,8 @@
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 { state, useSnapState } from './state'
3
+import { DirEntry, DirList, state, useSnapState } from './state'
4
import { useEffect, useRef } from 'react'
5
import { apiEvents } from '@hfs/shared/api'
6
-import { DirList } from './BrowseFiles'
6
import _ from 'lodash'
7
import { subscribeKey } from 'valtio/utils'
8
import { useIsMounted } from 'usehooks-ts'
@@ -18,14 +17,6 @@ export function usePath() {
17
return useLocation().pathname
18
}
19
21
-export function pathEncode(s: string) {
22
- return encodeURI(s).replace(/#/g, encodeURIComponent)
23
-}
24
-
25
-export function pathDecode(s: string) {
26
- return decodeURI(s).replace(/%23/g, '#')
27
-}
28
-
20
export default function useFetchList() {
21
const snap = useSnapState()
22
const desiredPath = usePath()
@@ -112,22 +103,8 @@ export default function useFetchList() {
103
state.can_upload ??= false
104
state.can_delete ??= false
105
const { add } = entry
115
- if (add) {
116
- add.uri = pathEncode(add.n)
117
- add.isFolder = add.n.endsWith('/')
118
- if (add.isFolder) {
119
- const i = add.n.lastIndexOf('.') + 1
120
- add.ext = i ? add.n.substring(i) : ''
121
- }
122
- const t = add.m || add.c
123
- if (t)
124
- add.t = new Date(t)
125
- add.name = add.isFolder ? add.n.slice(add.n.lastIndexOf('/', add.n.length - 2) +1, -1)
126
- : add.n.slice(add.n.lastIndexOf('/') + 1)
127
- add.cantOpen = add.p?.includes(add.isFolder ? 'l' : 'r') // to open we need list for folders and read for files
128
-
129
- buffer.push(add)
130
- }
106
+ if (add)
107
+ buffer.push(new DirEntry(add.n, add))
108
}
109
if (src?.readyState === src?.CLOSED)
110
return state.stopSearch?.()
src/const.ts
+1
-1
@@ -16,7 +16,7 @@ const pkg = JSON.parse(fs.readFileSync(PKG_PATH,'utf8'))
16
export const VERSION = pkg.version
17
export const DAY = 86_400_000
18
19
-export const API_VERSION = 8.1 // entry.uri + script.plugin + absolute frontend_*
19
+export const API_VERSION = 8.2
20
export const COMPATIBLE_API_VERSION = 1 // while changes in the api are not breaking, this number stays the same, otherwise it is made equal to API_VERSION
21
22
export const HFS_REPO = 'rejetto/hfs'