admin/fs: offer reset buttons on "name" field when there's a source, and the name was customized
Massimo Melina committed
Mar 29, 2025 at 12:07 UTC
a86925a44fd427bc73a882c814c775535b9d1ee1
4 files changed
+20
-9
admin/src/AccountForm.ts
+1
-1
@@ -6,7 +6,7 @@ import { Alert, Box } from '@mui/material'
6
import { apiCall } from './api'
7
import { alertDialog, useDialogBarColors } from './dialog'
8
import { formatTimestamp, isEqualLax, prefix, reactJoin, useIsMobile, wantArray } from './misc'
9
-import { Btn, IconBtn, NetmaskField, propsForModifiedValues, WildcardsSupported } from './mui'
9
+import { Btn, IconBtn, NetmaskField, propsForModifiedValues } from './mui'
10
import { Account } from './AccountsPage'
11
import { createVerifierAndSalt, SRPParameters, SRPRoutines } from 'tssrp6a'
12
import { AutoDelete, Delete } from '@mui/icons-material'
admin/src/FileForm.ts
+12
-5
@@ -19,7 +19,9 @@ import _ from 'lodash'
19
import FileField from './FileField'
20
import { alertDialog, toast, useDialogBarColors } from './dialog'
21
import yaml from 'yaml'
22
-import { Add, Check, ContentCopy, ContentCut, ContentPaste, Delete, Edit, QrCode2, Save } from '@mui/icons-material'
22
+import {
23
+ Add, Check, ContentCopy, ContentCut, ContentPaste, Delete, Edit, QrCode2, Save, RestartAlt
24
+} from '@mui/icons-material'
25
import { moveVfs } from './VfsTree'
26
import QrCreator from 'qr-creator';
27
import MenuButton from './MenuButton'
@@ -78,13 +80,16 @@ export default function FileForm({ file, addToBar, statusApi, accounts, saved }:
80
}
81
const defaultIcon = !values.icon
82
const embeddedIcon = values.icon && !values.icon.includes('.')
83
+ const nameFromSource = source && basename(source)
84
+ const nameIsDerivedFromSource = nameFromSource === values.name
85
return h(Form, {
86
values,
87
set(v, k) {
88
setValues(values => {
85
- const nameIsVirtual = k === 'source' && values.name && values.source?.endsWith(values.name)
86
- const name = nameIsVirtual ? basename(v) : values.name // update name if virtual
87
- return { ...values, name, [k]: v }
89
+ // updating the source, if the name is virtual, we must update that too
90
+ if (k === 'source' && nameIsDerivedFromSource)
91
+ values.name = basename(v)
92
+ return { ...values, [k]: v }
93
})
94
},
95
barSx: { gap: 2, width: '100%', ...barColors },
@@ -150,7 +155,9 @@ export default function FileForm({ file, addToBar, statusApi, accounts, saved }:
155
fields: [
156
isRoot ? h(Alert, { severity: 'info' }, "This is Home, the root of your shared files. Options set here will be applied to all files.")
157
: isDir && hasSource && h(Alert, { severity: 'info' }, `To set permissions on individual items in folder, add them by clicking Add button, and then "from disk"`),
153
- !isRoot && { k: 'name', required: true, xl: true, helperText: hasSource && "You can decide a name that's different from the one on your disk" },
158
+ !isRoot && { k: 'name', required: true, xl: true, helperText: hasSource && "You can decide a name that's different from the one on your disk",
159
+ end: nameFromSource && !nameIsDerivedFromSource && h(Btn, { icon: RestartAlt, title: "Reset", onClick: () => setValues({ ...values, name: nameFromSource }) }),
160
+ },
161
isLink ? { k: 'url', label: "URL", lg: 12, required: true }
162
: { k: 'source', label: "Disk source", xl: true, comp: FileField, files: isUnknown || !isDir, folders: isUnknown || isDir,
163
placeholder: "none",
src/comments.ts
+2
-2
@@ -1,6 +1,6 @@
1
import { defineConfig } from './config'
2
-import { dirname, join } from 'path'
3
-import { basename, CFG } from './cross'
2
+import { dirname, basename, join } from 'path'
3
+import { CFG } from './cross'
4
import { parseFileContent, parseFileCache } from './util-files'
5
import { createWriteStream } from 'fs'
6
import { loadFileAttr, singleWorkerFromBatchWorker, storeFileAttr } from './misc'
src/cross.ts
+5
-1
@@ -141,6 +141,10 @@ export function enforceFinal(sub:string, s:string, evenEmpty=false) {
141
return (s ? !s.endsWith(sub) : evenEmpty) ? s + sub : s
142
}
143
144
+export function removeFinal(sub:string, s:string) {
145
+ return s.endsWith(sub) ? s.slice(0, -sub.length) : s
146
+}
147
+
148
export function enforceStarting(sub:string, s:string, evenEmpty=false) {
149
return (s ? !s.startsWith(sub) : evenEmpty) ? sub + s : s
150
}
@@ -221,7 +225,7 @@ export function pendingPromise<T>() {
225
}
226
227
export function basename(path: string) {
224
- return path.slice(path.lastIndexOf('/') + 1 || path.lastIndexOf('\\') + 1)
228
+ return path.match(/([^\\/]+)[\\/]*$/)?.[1] || ''
229
}
230
231
export function dirname(path: string) {