removed redundant code

Massimo Melina committed May 22, 2026 at 18:11 UTC 7528c8ba6b2433277e296569c150f70674a9c8ee
12 files changed +26 -28
admin/src/DateTimeField.ts
+3 -2
@@ -3,13 +3,14 @@ import dayjs from 'dayjs'
3 import { FieldProps } from '@hfs/mui-grid-form'
4 import { createElement as h } from 'react'
5 import { Box } from '@mui/material'
6 -import { isTimestampString, objSameKeys } from './misc'
6 +import { isTimestampString } from './misc'
7 +import _ from 'lodash'
8 import { mergeSx } from './mui'
9
10 export function DateTimeField({ onChange, error, helperText, ...rest }: FieldProps<Date>) {
11 return h(Box, {},
12 h(DateTimePicker, {
12 - ...objSameKeys(rest, x => isTimestampString(x) || x && x instanceof Date ? dayjs(x) : (x ?? null)), // null to not be considered uncontrolled
13 + ..._.mapValues(rest, x => isTimestampString(x) || x && x instanceof Date ? dayjs(x) : (x ?? null)), // null to not be considered uncontrolled
14 sx: mergeSx({ width: '100%', color: 'error.main' }, rest.sx),
15 onChange(v: any) {
16 onChange(v && new Date(v), { was: rest.value, event: undefined })
admin/src/FileForm.ts
+2 -2
@@ -8,7 +8,7 @@ import {
8 } from '@hfs/mui-grid-form'
9 import { apiCall, UseApi, useApiEx } from './api'
10 import {
11 - basename, defaultPerms, formatBytes, formatTimestamp, isWhoObject, newDialog, objSameKeys, useRequestRender, try_,
11 + basename, defaultPerms, formatBytes, formatTimestamp, isWhoObject, newDialog, useRequestRender, try_,
12 onlyTruthy, prefix, VfsPerms, wantArray, WhoVfs, WhoObject, matches, xlate, md, Callback, copyTextToClipboard,
13 normalizeHost, splitAt, IMAGE_FILEMASK, CFG, MASK_IN_TESTS, WHO_ANY_ACCOUNT, WHO_ADMIN, WHO_NO_ONE, WHO_ANYONE,
14 } from './misc'
@@ -45,7 +45,7 @@ export default function FileForm({ file, addToBar, statusApi, accountsApi, saved
45 const { parent, children, isRoot, byMasks, ...rest } = file
46 const [values, setValues] = useState(rest)
47 useEffect(() => {
48 - setValues(Object.assign(objSameKeys(defaultPerms, () => null), rest))
48 + setValues(Object.assign(_.mapValues(defaultPerms, () => null), rest))
49 }, [file]) //eslint-disable-line
50
51 const inheritedDefault = useMemo(() => {
admin/src/HomePage.ts
+2 -2
@@ -4,7 +4,7 @@ import { createElement as h, ReactNode, useState } from 'react'
4 import { Box, Card, CardContent, Link } from '@mui/material'
5 import { apiCall, useApiEx, useApiList } from './api'
6 import {
7 - dontBotherWithKeys, objSameKeys, onlyTruthy, prefix, REPO_URL, md,
7 + dontBotherWithKeys, onlyTruthy, prefix, REPO_URL, md,
8 replaceStringToReact, wait, with_, DAY, HOUR, PREVIOUS_TAG
9 } from './misc'
10 import { Btn, Flex, InLink, LinkBtn, wikiLink, } from './mui'
@@ -40,7 +40,7 @@ export default function HomePage() {
40 const goSecure = !http?.listening && https?.listening ? 's' : ''
41 const srv = goSecure ? https : (http?.listening && http)
42 const href = srv && `http${goSecure}://`+window.location.hostname + (srv.port === (goSecure ? 443 : 80) ? '' : ':'+srv.port)
43 - const serverErrors = objSameKeys({ http, https }, v =>
43 + const serverErrors = _.mapValues({ http, https }, v =>
44 v.busy ? [`port ${v.configuredPort} already used by ${v.busy}${SOLUTION_SEP}choose a `, cfgLink('different port'), ` or stop ${v.busy}`]
45 : v.error )
46 const errors = serverErrors && onlyTruthy(Object.entries(serverErrors).map(([k,v]) =>
admin/src/api.ts
+2 -2
@@ -1,7 +1,7 @@
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 { createElement as h, useCallback, useEffect, useMemo, useRef, useState } from 'react'
4 -import { Dict, err2msg, Falsy, LIST, useStateMounted, wantArray, xlate, objSameKeys,
4 +import { Dict, err2msg, Falsy, LIST, useStateMounted, wantArray, xlate,
5 HTTP_FORBIDDEN, HTTP_UNAUTHORIZED } from './misc'
6 import { IconBtn, spinner } from './mui'
7 import { Alert } from '@mui/material'
@@ -73,7 +73,7 @@ export function useApiList<T=any, S=T>(cmd:string|Falsy, params: Dict={}, { map,
73 setConnecting(true)
74 setInitializing(true)
75 setList([])
76 - const src = apiEvents(cmd, objSameKeys(params, x => x === false ? undefined : x), (type, data) => {
76 + const src = apiEvents(cmd, _.mapValues(params, x => x === false ? undefined : x), (type, data) => {
77 switch (type) {
78 case 'connected':
79 setConnecting(false)
frontend/src/state.ts
+2 -2
@@ -3,7 +3,7 @@
3 import _ from 'lodash'
4 import { proxy, useSnapshot } from 'valtio'
5 import { subscribeKey } from 'valtio/utils'
6 -import { Dict, FRONTEND_OPTIONS, getHFS, hfsEvent, hIcon, objSameKeys, pathEncode, typedKeys } from './misc'
6 +import { FRONTEND_OPTIONS, getHFS, hfsEvent, hIcon, pathEncode, typedKeys } from './misc'
7 import { DirEntry as ServerDirEntry } from '../../src/api.get_file_list'
8
9 export const state = proxy<typeof FRONTEND_OPTIONS & {
@@ -45,7 +45,7 @@ export const state = proxy<typeof FRONTEND_OPTIONS & {
45 uri: '',
46 canChangePassword: false,
47 props: {},
48 - ...objSameKeys(FRONTEND_OPTIONS, (v,k) => getHFS()[k] ?? v),
48 + ..._.mapValues(FRONTEND_OPTIONS, (v,k) => getHFS()[k] ?? v),
49 iconsReady: false,
50 username: '',
51 list: [],
shared/dialogs.ts
+3 -2
@@ -3,7 +3,8 @@
3 import { createElement as h, Fragment, FunctionComponent, isValidElement, ReactNode, useEffect, useRef,
4 HTMLAttributes, useState } from 'react'
5 import { proxy, ref, useSnapshot } from 'valtio'
6 -import { domOn, isPrimitive, objSameKeys, wait } from '.'
6 +import _ from 'lodash'
7 +import { domOn, isPrimitive, wait } from '.'
8
9 export interface DialogOptions {
10 Content: FunctionComponent<any>,
@@ -209,7 +210,7 @@ export function componentOrNode(x: ReactNode | FunctionComponent) {
210 export function newDialog(options: DialogOptions) {
211 const $id = Math.random()
212 const ts = performance.now()
212 - const d: Dialog = Object.assign(objSameKeys(options, x => isValidElement(x) ? ref(x) : x) as typeof options, { // encapsulate elements as React will try to write, but valtio makes them readonly
213 + const d: Dialog = Object.assign(_.mapValues(options, x => isValidElement(x) ? ref(x) : x) as typeof options, { // encapsulate elements as React will try to write, but valtio makes them readonly
214 close, ts, $id, // object identity is not working on dialog object because it's proxied (valtio). This is a possible workaround
215 restoreFocus: options.restoreFocus ?? ref(document.activeElement || {}),
216 })
shared/index.ts
+2 -2
@@ -3,7 +3,7 @@
3 import _ from 'lodash'
4 import { apiCall } from './api'
5 import {
6 - DAY, Dict, formatBytes, HOUR, MINUTE, objFromKeys, objSameKeys, typedEntries, wantArray, stringBefore,
6 + DAY, Dict, formatBytes, HOUR, MINUTE, objFromKeys, typedEntries, wantArray, stringBefore,
7 PLUGINS_PUB_URI
8 } from '../src/cross'
9 export * from './react'
@@ -202,7 +202,7 @@ type DurationUnit = 'day' | 'hour' | 'minute' | 'second'
202 export function createDurationFormatter({ locale=undefined, unitDisplay='narrow', largest='day', smallest='second', maxTokens, skipZeroes }:
203 { skipZeroes?: boolean, largest?: DurationUnit, smallest?: DurationUnit, locale?: string, unitDisplay?: 'long' | 'short' | 'narrow', maxTokens?: 1 | 2 | 3 }={}) {
204 const multipliers: Record<DurationUnit, number> = { day: DAY, hour: HOUR, minute: MINUTE, second: 1000 }
205 - const fmt = objSameKeys(multipliers, (v,k) => Intl.NumberFormat(locale, { style: 'unit', unit: k, unitDisplay }).format)
205 + const fmt = _.mapValues(multipliers, (v,k) => Intl.NumberFormat(locale, { style: 'unit', unit: k, unitDisplay }).format)
206 const fmtList = new Intl.ListFormat(locale, { style: 'narrow', type: 'unit' })
207 return (ms: number) => {
208 const a = []
src/cross.ts
-4
@@ -143,10 +143,6 @@ export function haveTimeout<T>(ms: number, job: Promise<T>, error?: any) {
143 ])
144 }
145
146 -export function objSameKeys<S extends object,VR=any>(src: S, newValue:(value:Truthy<S[keyof S]>, key:keyof S)=>VR) {
147 - return Object.fromEntries(Object.entries(src).map(([k,v]) => [k, newValue(v,k as keyof S)])) as { [K in keyof S]:VR }
148 -}
149 -
146 export function objFromKeys<K extends string, VR=unknown>(src: K[], getValue: (value: K)=> VR) {
147 return Object.fromEntries(src.map(k => [k, getValue(k)]))
148 }
src/listen.ts
+2 -2
@@ -10,7 +10,7 @@ import { getConnections, newConnection } from './connections'
10 import { TLSSocket } from 'node:tls'
11 import open from 'open'
12 import {
13 - CFG, debounceAsync, ipForUrl, makeNetMatcher, MINUTE, objSameKeys, onlyTruthy, prefix, runAt, wait, xlate
13 + CFG, debounceAsync, ipForUrl, makeNetMatcher, MINUTE, onlyTruthy, prefix, runAt, wait, xlate
14 } from './misc'
15 import { PORT_DISABLED, ADMIN_URI, IS_WINDOWS } from './const'
16 import findProcess from 'find-process'
@@ -105,7 +105,7 @@ export function getCertObject() {
105 if (!c) return
106 const all = new X509Certificate(c)
107 const some = _.pick(all, ['subject', 'issuer', 'validFrom', 'validTo'])
108 - const ret = objSameKeys(some, v => v?.includes('=') ? Object.fromEntries(v.split('\n').map(x => x.split('='))) : v)
108 + const ret = _.mapValues(some, v => v?.includes('=') ? Object.fromEntries(v.split('\n').map(x => x.split('='))) : v)
109 return Object.assign(ret, { altNames: all.subjectAltName?.replace(/DNS:/g, '').split(/, */) })
110 }
111
src/perm.ts
+2 -2
@@ -1,7 +1,7 @@
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 _ from 'lodash'
4 -import { objRenameKey, objSameKeys, setHidden, typedEntries, wantArray } from './misc'
4 +import { objRenameKey, setHidden, typedEntries, wantArray } from './misc'
5 import { defineConfig, saveConfigAsap } from './config'
6 import { createVerifierAndSalt, SRPParameters, SRPRoutines } from 'tssrp6a'
7 import events from './events'
@@ -88,7 +88,7 @@ export async function updateAccount(account: Account, change: Partial<Account> |
88 const u = normalizeUsername(change.username || '')
89 if (u && u !== usernameWas && getAccount(u))
90 throw "username already exists"
91 - Object.assign(account, objSameKeys(change, x => x || undefined))
91 + Object.assign(account, _.mapValues(change, x => x || undefined))
92 }
93 for (const [k,v] of typedEntries(account))
94 if (!v) delete account[k] // we consider all account fields, when falsy, as equivalent to be missing (so, default value applies)
src/plugins.ts
+3 -3
@@ -10,7 +10,7 @@ import {
10 import * as Const from './const'
11 import Koa from 'koa'
12 import {
13 - escapeGlobPath, callable, Callback, CFG, debounceAsync, Dict, objSameKeys, onlyTruthy, prefix,
13 + escapeGlobPath, callable, Callback, CFG, debounceAsync, Dict, onlyTruthy, prefix,
14 PendingPromise, pendingPromise, Promisable, same, tryJson, wait, waitFor, wantArray, watchDir, objFromKeys, patchKey
15 } from './misc'
16 import * as misc from './misc'
@@ -129,7 +129,7 @@ async function initPlugin(pl: any, morePassedToInit?: { id: string } & Dict) {
129 const controlledEvents = Object.create(events, objFromKeys(['on', 'once', 'multi'], k => ({
130 value() {
131 if (k === 'multi')
132 - arguments[0] = objSameKeys(arguments[0], trap)
132 + arguments[0] = _.mapValues(arguments[0], trap)
133 else
134 arguments[1] = trap(arguments[1])
135 const ret = (events[k] as any)(...arguments)
@@ -581,7 +581,7 @@ function watchPlugin(id: string, path: string) {
581 getConfig(cfgKey?: string) {
582 const cur = pluginsConfig.get()?.[id]
583 return cfgKey ? cur?.[cfgKey] ?? pluginData.config?.[cfgKey]?.defaultValue
584 - : _.defaults(cur, objSameKeys(pluginData.config, x => x.defaultValue))
584 + : _.defaults(cur, _.mapValues(pluginData.config, x => x.defaultValue))
585 },
586 setConfig: (cfgKey: string, value: any) =>
587 setPluginConfig(id, { [cfgKey]: value }),
src/serveGuiFiles.ts
+3 -3
@@ -11,7 +11,7 @@ import { authApis } from './api.auth'
11 import { ApiError } from './apiMiddleware'
12 import { join, extname, sep } from 'path'
13 import {
14 - CFG, debounceAsync, formatBytes, FRONTEND_OPTIONS, isPrimitive, newObj, objSameKeys, onlyTruthy, parseFile,
14 + CFG, debounceAsync, formatBytes, FRONTEND_OPTIONS, isPrimitive, newObj, onlyTruthy, parseFile,
15 enforceStarting, statWithTimeout, shortenAgent
16 } from './misc'
17 import { favicon, title } from './adminApis'
@@ -130,7 +130,7 @@ async function treatIndex(ctx: Koa.Context, filesUri: string, body: string) {
130 ${getSection('htmlHead')}`}
131 `
132 function iconsToObj(icons: CustomizedIcons, pre='') {
133 - return icons && objSameKeys(icons, (v, k) => ctx.state.revProxyPath + ICONS_URI + pre + k)
133 + return icons && _.mapValues(icons, (v, k) => ctx.state.revProxyPath + ICONS_URI + pre + k)
134 }
135
136 if (isBody && isOpen)
@@ -171,7 +171,7 @@ async function treatIndex(ctx: Koa.Context, filesUri: string, body: string) {
171 v = ctx.state.revProxyPath + v
172 }
173 else if (type === 'array' && Array.isArray(v))
174 - v = v.map(x => objSameKeys(x, (xv, xk) => adjustValueByConfig(xv, cfg.fields[xk])))
174 + v = v.map(x => _.mapValues(x, (xv, xk) => adjustValueByConfig(xv, cfg.fields[xk])))
175 return v
176 }
177