@samitouri / QOSami-HFS / commits / b989b898

admin/fs: new "order" field

Massimo Melina committed Jan 23, 2025 at 20:59 UTC b989b898ca5ace8793cb8df7f8490c8c7bb20c64
7 files changed +16 -7
admin/src/FileForm.ts
+6 -3
@@ -3,7 +3,8 @@
3 import { state, useSnapState } from './state'
4 import { createElement as h, forwardRef, ReactElement, ReactNode, useEffect, useMemo, useState } from 'react'
5 import { Alert, Box, Collapse, FormHelperText, Link, MenuItem, MenuList, useTheme } from '@mui/material'
6 -import { BoolField, DisplayField, Field, FieldProps, Form, MultiSelectField, SelectField, StringField
6 +import {
7 + BoolField, DisplayField, Field, FieldProps, Form, MultiSelectField, NumberField, SelectField, StringField
8 } from '@hfs/mui-grid-form'
9 import { apiCall, UseApi } from './api'
10 import {
@@ -158,15 +159,17 @@ export default function FileForm({ file, addToBar, statusApi, accounts, saved }:
159 : isDir ? "Content from this path on disk will be listed, but you can also add more" : undefined,
160 },
161 { k: 'id', comp: LinkField, statusApi, xs: 12 },
162 + { k: 'order', comp: NumberField, min: -1E5, max: 1E5, placeholder: 'default', sm: 4, helperText: wikiLink('Virtual-file-system#order', "To force position") },
163 {
164 k: 'iconType',
165 comp: SelectField,
166 options: ['default', 'file', 'embedded'],
167 value: !values.icon ? 'default' : embeddedIcon ? 'embedded' : 'file',
168 fromField: v => setValues({ ...values, icon: v === 'default' ? '' : v === 'file' ? 'select.a.file' : Object.keys(SYS_ICONS)[0] }),
167 - xs: defaultIcon ? 12 : true,
169 + xs: true,
170 + sm: defaultIcon ? 8 : true,
171 },
169 - !defaultIcon && { k: 'icon', xs: 7, md: 8, xl: 10,
172 + !defaultIcon && { k: 'icon', xs: 8, sm: 4,
173 ...embeddedIcon ? {
174 comp: SelectField, // uniqBy to avoid same icon (with different names), but it works only on array, so first step is to convert the object
175 options: _.map(_.uniqBy(_.map(SYS_ICONS, (v,k) => [k, v[0], v[1] ?? k] as const), x => x[2]), ([k, emoji]) =>
config.md
+2
@@ -140,6 +140,8 @@ Valid keys in a node are:
140 Value is a dictionary, where the key is the original name. No UI.
141 - `mime`: specify what mime to use for this resource. Use "auto" for automatic detection.
142 - `url`: when this value is present, the element is a link to the URL you specify.
143 +- `order`: a number that you can set if you want to force the position of this element to the top or the bottom of the list.
144 + A positive number for the top, negative for the bottom. If you set "1" for an entry and "2" for another, the "2" will be the topmost.
145 - `target`: optional, for links only, used to [open the link in a new browser](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#target). E.g. `_blank`
146 - `accept`: valid only on upload folders, used to restrict the type of files you can upload. E.g. `.zip,.rar`
147 - `default`: to be used with a folder where you want to serve a default html. E.g.: "index.html". Using this will make `mime` default to "auto".
frontend/src/state.ts
+1
@@ -102,6 +102,7 @@ export class DirEntry implements ServerDirEntry {
102 public readonly web?: true
103 public readonly url?: string
104 public readonly target?: string
105 + public readonly order?: number
106 public comment?: string
107 // we memoize these value for speed
108 public readonly name: string
frontend/src/useFetchList.ts
+3 -2
@@ -179,7 +179,8 @@ function sort(list: DirList) {
179 const byCreation = sort_by === 'creation'
180 const invert = state.invert_order ? -1 : 1
181 return list.sort((a,b) =>
182 - hfsEvent('sortCompare', { a, b }).find(Boolean)
182 + -compare(a.order||0, b.order||0)
183 + || hfsEvent('sortCompare', { a, b }).find(Boolean)
184 || folders_first && -compare(a.isFolder, b.isFolder)
185 || invert * (bySize ? compare(a.s||0, b.s||0)
186 : byExt ? localCompare(a.ext, b.ext)
@@ -206,7 +207,7 @@ function sort(list: DirList) {
207
208 // generic comparison
209 function compare(a:any, b:any) {
209 - return a < b ? -1 : a > b ? 1 : 0
210 + return a - b
211 }
212
213 // update list on sorting criteria
src/api.get_file_list.ts
+2 -1
@@ -17,7 +17,7 @@ import { ctxAdminAccess } from './adminApis'
17 import { dontOverwriteUploading } from './upload'
18 import { SendListReadable } from './SendList'
19
20 -export interface DirEntry { n:string, s?:number, m?:Date, c?:Date, p?: string, comment?: string, web?: boolean, url?: string, target?: string, icon?: string | true }
20 +export interface DirEntry { n:string, s?:number, m?:Date, c?:Date, p?: string, comment?: string, web?: boolean, url?: string, target?: string, icon?: string | true, order?: number }
21
22 export function paramsToFilter({ search, wild, searchComment }: any) {
23 search = String(search || '').toLocaleLowerCase()
@@ -133,6 +133,7 @@ export const get_file_list: ApiHandler = async ({ uri='/', offset, limit, c, onl
133 m: !st || Math.abs(st.mtimeMs - st.birthtimeMs) < 1000 ? undefined : st.mtime,
134 s: isFolder ? undefined : st?.size,
135 p: (pr + pl + pd + pa) || undefined,
136 + order: node.order,
137 comment: node.comment ?? await getCommentFor(source),
138 icon: getNodeIcon(node),
139 web: await hasDefaultFile(node, ctx) ? true : undefined,
src/api.vfs.ts
+1 -1
@@ -25,7 +25,7 @@ async function urlToNodeOriginal(uri: string) {
25 return n?.isTemp ? n.original : n
26 }
27
28 -const ALLOWED_KEYS = ['name','source','masks','default','accept','rename','mime','url','target','comment','icon', ...PERM_KEYS]
28 +const ALLOWED_KEYS = ['name','source','masks','default','accept','rename','mime','url','target','comment','icon','order', ...PERM_KEYS]
29
30 export interface LsEntry { n:string, s?:number, m?:string, c?:string, k?:'d' }
31
src/vfs.ts
+1
@@ -36,6 +36,7 @@ export interface VfsNodeStored extends VfsPerms {
36 accept?: string
37 comment?: string
38 icon?: string
39 + order?: number
40 }
41 export interface VfsNode extends VfsNodeStored { // include fields that are only filled at run-time
42 isTemp?: true // this node doesn't belong to the tree and was created by necessity