admin/options: new option "KB for size"

Massimo Melina committed Dec 8, 2024 at 21:03 UTC ab3fc1267adb64d8728207aa65665f553ac17c42
5 files changed +12 -8
admin/src/OptionsPage.ts
+1
@@ -220,6 +220,7 @@ export default function OptionsPage() {
220
221 { k: 'descript_ion', comp: BoolField, ...isWindows && { sm: 4, md: 3 }, label: "Enable comments", helperText: "In file DESCRIPT.ION" },
222 { k: 'show_hidden_files', comp: BoolField, sm: 4, md: 3 },
223 + { k: CFG.size_1024, label: "KB size", comp: SelectField, md: 3, options: { 1000: false, 1024: true } },
224 { k: 'descript_ion_encoding', sm: 4, md: 6, label: "Encoding of file DESCRIPT.ION", comp: SelectField, disabled: !values.descript_ion,
225 options: ['utf8',720,775,819,850,852,862,869,874,808, ..._.range(1250,1257),10029,20866,21866] },
226
frontend/src/fileMenu.ts
+1 -1
@@ -72,7 +72,7 @@ export function openFileMenu(entry: DirEntry, ev: MouseEvent, addToMenu: (Falsy
72 const props = [
73 { id: 'name', label: t`Name`, value: entry.name },
74 typeof s === 'number' && { id: 'size', label: t`Size`,
75 - value: h(Fragment, {}, formatBytes(s), h('small', {}, prefix(' (', s > 1024 && s.toLocaleString(), ')')) ) },
75 + value: h(Fragment, {}, formatBytes(s), h('small', {}, prefix(' (', s > getHFS().kb && s.toLocaleString(), ')')) ) },
76 entry.t && { id: 'timestamp', label: t`Timestamp`, value: entry.t.toLocaleString() },
77 folder && {
78 id: 'folder',
shared/index.ts
+2 -1
@@ -2,7 +2,7 @@
2
3 import _ from 'lodash'
4 import { apiCall } from './api'
5 -import { DAY, Dict, HOUR, MINUTE, objSameKeys, typedEntries } from '../src/cross'
5 +import { DAY, Dict, formatBytes, HOUR, MINUTE, objSameKeys, typedEntries } from '../src/cross'
6 export * from './react'
7 export * from './dialogs'
8 export * from './md'
@@ -35,6 +35,7 @@ Object.assign(HFS, {
35 copyTextToClipboard,
36 urlParams,
37 })
38 +formatBytes.k = HFS.kb
39
40 export const IMAGE_FILEMASK = '*.jpg|*.jpeg|*.gif|*.svg'
41
src/cross.ts
+5 -5
@@ -28,7 +28,7 @@ export const THEME_OPTIONS = { auto: '', light: 'light', dark: 'dark' }
28 export const CFG = constMap(['geo_enable', 'geo_allow', 'geo_list', 'geo_allow_unknown', 'dynamic_dns_url',
29 'log', 'error_log', 'log_rotation', 'dont_log_net', 'log_gui', 'log_api', 'log_ua', 'log_spam', 'track_ips',
30 'max_downloads', 'max_downloads_per_ip', 'max_downloads_per_account', 'roots', 'force_address', 'split_uploads',
31 - 'allow_session_ip_change', 'force_lang', 'suspend_plugins', 'base_url'])
31 + 'allow_session_ip_change', 'force_lang', 'suspend_plugins', 'base_url', 'size_1024'])
32 export const LIST = { add: '+', remove: '-', update: '=', props: 'props', ready: 'ready', error: 'e' }
33 export type Dict<T=any> = Record<string, T>
34 export type Falsy = false | null | undefined | '' | 0
@@ -89,9 +89,11 @@ export function isWhoObject(v: undefined | Who): v is WhoObject {
89 }
90
91 const MULTIPLIERS = ['', 'K', 'M', 'G', 'T']
92 -export function formatBytes(n: number, { post='B', k=1024, digits=NaN, sep=' ' }={}) {
92 +export declare namespace formatBytes { let k: number }
93 +export function formatBytes(n: number, { post='B', k=0, digits=NaN, sep=' ' }={}) {
94 if (isNaN(Number(n)) || n < 0)
95 return ''
96 + k ||= formatBytes.k ?? 1024 // default value
97 const i = n && Math.min(MULTIPLIERS.length - 1, Math.floor(Math.log2(n) / Math.log2(k)))
98 n /= k ** i
99 const nAsString = i && !isNaN(digits) ? n.toFixed(digits)
@@ -100,9 +102,7 @@ export function formatBytes(n: number, { post='B', k=1024, digits=NaN, sep=' ' }
102 } // formatBytes
103
104 export function formatSpeed(n: number, options: { digits?: number }={}) {
103 - return formatBytes(n, { post: 'B/s', k: 1000, ...options })
104 - .replace('K', 'k') // ref https://en.wikipedia.org/wiki/Data-rate_units
105 -
105 + return formatBytes(n, { post: 'B/s', ...options })
106 }
107
108 export function prefix(pre: Falsy | string, v: string | number | undefined | null | false, post: Falsy | string='') {
src/serveGuiFiles.ts
+3 -1
@@ -9,7 +9,7 @@ import { getPluginConfigFields, getPluginInfo, mapPlugins, pluginsConfig } from
9 import { refresh_session } from './api.auth'
10 import { ApiError } from './apiMiddleware'
11 import { join, extname } from 'path'
12 -import { CFG, debounceAsync, FRONTEND_OPTIONS, isPrimitive, newObj, onlyTruthy, parseFile } from './misc'
12 +import { CFG, debounceAsync, formatBytes, FRONTEND_OPTIONS, isPrimitive, newObj, onlyTruthy, parseFile } from './misc'
13 import { favicon, title } from './adminApis'
14 import { customHtml, getAllSections, getSection } from './customHtml'
15 import _ from 'lodash'
@@ -17,6 +17,7 @@ import { defineConfig, getConfig } from './config'
17 import { getLangData } from './lang'
18 import { dontOverwriteUploading } from './upload'
19
20 +const size1024 = defineConfig(CFG.size_1024, false, x => formatBytes.k = x ? 1024 : 1000) // we both configure formatBytes, and also provide a compiled version (number instead of boolean)
21 const splitUploads = defineConfig(CFG.split_uploads, 0)
22 export const logGui = defineConfig(CFG.log_gui, false)
23 _.each(FRONTEND_OPTIONS, (v,k) => defineConfig(k, v)) // define default values
@@ -107,6 +108,7 @@ async function treatIndex(ctx: Koa.Context, filesUri: string, body: string) {
108 prefixUrl: ctx.state.revProxyPath,
109 dontOverwriteUploading: dontOverwriteUploading.get(),
110 splitUploads: splitUploads.get(),
111 + kb: size1024.compiled(),
112 forceTheme: mapPlugins(p => _.isString(p.isTheme) ? p.isTheme : undefined).find(Boolean),
113 customHtml: _.omit(getAllSections(), ['top', 'bottom', 'htmlHead', 'style']), // exclude the sections we already apply in this phase
114 ...newObj(FRONTEND_OPTIONS, (v, k) => getConfig(k)),