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'