@samitouri / QOSami-HFS / commits / f188ba3c

better code

Massimo Melina committed Apr 19, 2026 at 17:37 UTC f188ba3c40923b6e7fb1329f2defffdfaa0cff53
4 files changed +34 -163
admin/src/App.ts
+15 -2
@@ -15,7 +15,7 @@ import ConfigFilePage from './ConfigFilePage'
15 import { useSnapState } from './state'
16 import { useEventListener } from 'usehooks-ts'
17 import { AriaOnly, isMac, useFixSticky, xlate } from './misc'
18 -import { getLocale } from './locale'
18 +import { loadLocale } from './locale'
19 import { fillFlexParentSx } from './DataTable'
20
21 // always use useMemo with setTitleSide
@@ -24,7 +24,7 @@ export interface PageProps { setTitleSide: (content: ReactNode, fullWidth?: bool
24 function App() {
25 return h(ThemeProvider, { theme: useMyTheme() },
26 h(ApplyTheme, {},
27 - h(LocalizationProvider, { dateAdapter: AdapterDayjs, adapterLocale: getLocale() },
27 + h(Localization, {},
28 h(LoginRequired, {},
29 h(HashRouter, {},
30 h(Dialogs, {
@@ -36,6 +36,19 @@ function App() {
36 }, h(Routed) ))) )))
37 }
38
39 +function Localization(props: any) {
40 + const [locale, setLocale] = useState('')
41 + useEffect(() => {
42 + let cancelled = false
43 + loadLocale().then(locale => {
44 + if (!cancelled)
45 + setLocale(locale || '')
46 + })
47 + return () => { cancelled = true }
48 + }, [])
49 + return h(LocalizationProvider, { dateAdapter: AdapterDayjs, adapterLocale: locale, ...props })
50 +}
51 +
52 function ApplyTheme(props:any) {
53 return h(Box, {
54 sx: { bgcolor: 'background.default', color: 'text.primary', flex: 1,
admin/src/ArrayField.ts
+2 -2
@@ -1,5 +1,5 @@
1 import { createElement as h, Fragment, isValidElement, useMemo, useState } from 'react'
2 -import { callable, Dict, Functionable, isOrderedEqual, setHidden, swap } from './misc'
2 +import { callable, Dict, formatTimestamp, Functionable, isOrderedEqual, setHidden, swap } from './misc'
3 import { Add, Edit, Delete, ArrowUpward, ArrowDownward, Undo, Check } from '@mui/icons-material'
4 import { DialogOptions, FormDialog, formDialog } from './dialog'
5 import { GridActionsCellItem, GridAlignment, GridColDef } from '@mui/x-data-grid'
@@ -212,7 +212,7 @@ const byType: Dict<{ field?: Partial<FieldDescriptor>, column?: Partial<GridColD
212 field: { comp: DateTimeField },
213 column: {
214 minWidth: 96, flex: 0.5,
215 - renderCell: ({ value }) => value && new Date(value).toLocaleString(),
215 + renderCell: ({ value }) => formatTimestamp(value),
216 }
217 }
218 }
admin/src/OptionsPage.ts
+1 -1
@@ -54,7 +54,7 @@ export default function OptionsPage() {
54 const isLH = hn === 'localhost'
55 const isV6 = hn.includes(':')
56 const listenInterfaceOptions = [
57 - { label: "any", value: '' },
57 + { label: "any", value: '', disabled: false },
58 { label: "any IPv4", value: '0.0.0.0', disabled: !isLH && isV6 },
59 { label: "any IPv6", value: '::', disabled: !isLH && !isV6 },
60 ...['127.0.0.1', '::1'].map(x => ({ label: x, value: x, disabled: !isLH && hn !== x })),
admin/src/locale.ts
+16 -158
@@ -1,165 +1,15 @@
1 -import 'dayjs/locale/af'
2 -import 'dayjs/locale/bi'
3 -import 'dayjs/locale/de-ch'
4 -import 'dayjs/locale/en'
5 -import 'dayjs/locale/fr-ch'
6 -import 'dayjs/locale/hy-am'
7 -import 'dayjs/locale/ky'
8 -import 'dayjs/locale/mt'
9 -import 'dayjs/locale/ro'
10 -import 'dayjs/locale/sv'
11 -import 'dayjs/locale/tzm'
12 -import 'dayjs/locale/am'
13 -import 'dayjs/locale/bm'
14 -import 'dayjs/locale/de'
15 -import 'dayjs/locale/eo'
16 -import 'dayjs/locale/fr'
17 -import 'dayjs/locale/id'
18 -import 'dayjs/locale/lb'
19 -import 'dayjs/locale/my'
20 -import 'dayjs/locale/ru'
21 -import 'dayjs/locale/sw'
22 -import 'dayjs/locale/ug-cn'
23 -import 'dayjs/locale/ar-dz'
24 -import 'dayjs/locale/bn-bd'
25 -import 'dayjs/locale/dv'
26 -import 'dayjs/locale/es-do'
27 -import 'dayjs/locale/fy'
28 -import 'dayjs/locale/is'
29 -import 'dayjs/locale/lo'
30 -import 'dayjs/locale/nb'
31 -import 'dayjs/locale/rw'
32 -import 'dayjs/locale/ta'
33 -import 'dayjs/locale/uk'
34 -import 'dayjs/locale/ar-iq'
35 -import 'dayjs/locale/bn'
36 -import 'dayjs/locale/el'
37 -import 'dayjs/locale/es-mx'
38 -import 'dayjs/locale/ga'
39 -import 'dayjs/locale/it-ch'
40 -import 'dayjs/locale/lt'
41 -import 'dayjs/locale/ne'
42 -import 'dayjs/locale/sd'
43 -import 'dayjs/locale/te'
44 -import 'dayjs/locale/ur'
45 -import 'dayjs/locale/ar-kw'
46 -import 'dayjs/locale/bo'
47 -import 'dayjs/locale/en-au'
48 -import 'dayjs/locale/es-pr'
49 -import 'dayjs/locale/gd'
50 -import 'dayjs/locale/it'
51 -import 'dayjs/locale/lv'
52 -import 'dayjs/locale/nl-be'
53 -import 'dayjs/locale/se'
54 -import 'dayjs/locale/tet'
55 -import 'dayjs/locale/uz-latn'
56 -import 'dayjs/locale/ar-ly'
57 -import 'dayjs/locale/br'
58 -import 'dayjs/locale/en-ca'
59 -import 'dayjs/locale/es-us'
60 -import 'dayjs/locale/gl'
61 -import 'dayjs/locale/ja'
62 -import 'dayjs/locale/me'
63 -import 'dayjs/locale/nl'
64 -import 'dayjs/locale/si'
65 -import 'dayjs/locale/tg'
66 -import 'dayjs/locale/uz'
67 -import 'dayjs/locale/ar-ma'
68 -import 'dayjs/locale/bs'
69 -import 'dayjs/locale/en-gb'
70 -import 'dayjs/locale/es'
71 -import 'dayjs/locale/gom-latn'
72 -import 'dayjs/locale/jv'
73 -import 'dayjs/locale/mi'
74 -import 'dayjs/locale/nn'
75 -import 'dayjs/locale/sk'
76 -import 'dayjs/locale/th'
77 -import 'dayjs/locale/vi'
78 -import 'dayjs/locale/ar-sa'
79 -import 'dayjs/locale/ca'
80 -import 'dayjs/locale/en-ie'
81 -import 'dayjs/locale/et'
82 -import 'dayjs/locale/gu'
83 -import 'dayjs/locale/ka'
84 -import 'dayjs/locale/mk'
85 -import 'dayjs/locale/oc-lnc'
86 -import 'dayjs/locale/sl'
87 -import 'dayjs/locale/tk'
88 -import 'dayjs/locale/x-pseudo'
89 -import 'dayjs/locale/ar-tn'
90 -import 'dayjs/locale/cs'
91 -import 'dayjs/locale/en-il'
92 -import 'dayjs/locale/eu'
93 -import 'dayjs/locale/he'
94 -import 'dayjs/locale/kk'
95 -import 'dayjs/locale/ml'
96 -import 'dayjs/locale/pa-in'
97 -import 'dayjs/locale/sq'
98 -import 'dayjs/locale/tl-ph'
99 -import 'dayjs/locale/yo'
100 -import 'dayjs/locale/ar'
101 -import 'dayjs/locale/cv'
102 -import 'dayjs/locale/en-in'
103 -import 'dayjs/locale/fa'
104 -import 'dayjs/locale/hi'
105 -import 'dayjs/locale/km'
106 -import 'dayjs/locale/mn'
107 -import 'dayjs/locale/pl'
108 -import 'dayjs/locale/sr-cyrl'
109 -import 'dayjs/locale/tlh'
110 -import 'dayjs/locale/zh-cn'
111 -import 'dayjs/locale/az'
112 -import 'dayjs/locale/cy'
113 -import 'dayjs/locale/en-nz'
114 -import 'dayjs/locale/fi'
115 -import 'dayjs/locale/hr'
116 -import 'dayjs/locale/kn'
117 -import 'dayjs/locale/mr'
118 -import 'dayjs/locale/pt-br'
119 -import 'dayjs/locale/sr'
120 -import 'dayjs/locale/tr'
121 -import 'dayjs/locale/zh-hk'
122 -import 'dayjs/locale/be'
123 -import 'dayjs/locale/da'
124 -import 'dayjs/locale/en-sg'
125 -import 'dayjs/locale/fo'
126 -import 'dayjs/locale/ht'
127 -import 'dayjs/locale/ko'
128 -import 'dayjs/locale/ms-my'
129 -import 'dayjs/locale/pt'
130 -import 'dayjs/locale/ss'
131 -import 'dayjs/locale/tzl'
132 -import 'dayjs/locale/zh-tw'
133 -import 'dayjs/locale/bg'
134 -import 'dayjs/locale/de-at'
135 -import 'dayjs/locale/en-tt'
136 -import 'dayjs/locale/fr-ca'
137 -import 'dayjs/locale/hu'
138 -import 'dayjs/locale/ku'
139 -import 'dayjs/locale/ms'
140 -import 'dayjs/locale/rn'
141 -import 'dayjs/locale/sv-fi'
142 -import 'dayjs/locale/tzm-latn'
143 -import 'dayjs/locale/zh'
1 +/// <reference types="vite/client" />
2 import { findDefined } from './misc'
3
146 -const dayjs_locales = ['af', 'ar', 'ar-dz', 'ar-kw', 'ar-ly', 'ar-ma', 'ar-sa', 'ar-tn', 'az', 'be', 'bg', 'bm', 'bn',
147 - 'bo', 'br', 'bs', 'ca', 'cs', 'cv', 'cy', 'da', 'de', 'de-at', 'de-ch', 'dv', 'el', 'en', 'en-au', 'en-ca', 'en-gb',
148 - 'en-ie', 'en-il', 'en-nz', 'en-SG', 'eo', 'es', 'es-do', 'es-us', 'et', 'eu', 'fa', 'fi', 'fo', 'fr', 'fr-ca',
149 - 'fr-ch', 'fy', 'ga', 'gd', 'gl', 'gom-latn', 'gu', 'he', 'hi', 'hr', 'hu', 'hy-am', 'id', 'is', 'it', 'it-ch',
150 - 'ja', 'jv', 'ka', 'kk', 'km', 'kn', 'ko', 'ku', 'ky', 'lb', 'lo', 'lt', 'lv', 'me', 'mi', 'mk', 'ml', 'mn', 'mr',
151 - 'ms', 'ms-my', 'mt', 'my', 'nb', 'ne', 'nl', 'nl-be', 'nn', 'oc-lnc', 'pa-in', 'pl', 'pt', 'pt-br', 'ro', 'ru',
152 - 'sd', 'se', 'si', 'sk', 'sl', 'sq', 'sr', 'sr-cyrl', 'ss', 'sv', 'sw', 'ta', 'te', 'tet', 'tg', 'th', 'tl-ph',
153 - 'tlh', 'tr', 'tzl', 'tzm', 'tzm-latn', 'ug-cn', 'uk', 'ur', 'uz', 'uz-latn', 'vi', 'x-pseudo', 'yo', 'zh-cn',
154 - 'zh-hk', 'zh-tw']
4 +const localeLoaders = import.meta.glob('../../node_modules/dayjs/locale/*.js')
5 +const localePaths = new Map(Object.keys(localeLoaders).map(path => [path.match(/([^/]+)\.js$/)![1], path]))
6 +const localeAliases: Record<string, string> = { zn: 'zh-cn', no: 'nb' }
7
8 function lang2locale(lang: string): string | undefined {
157 - return dayjs_locales.includes(lang) && lang
158 - || {
159 - zn: 'zh-cn',
160 - no: 'nb',
161 - }[lang]
162 - || lang.includes('-') && lang2locale(lang.split('-')[0])
9 + const normalized = lang.toLowerCase()
10 + return localePaths.has(normalized) && normalized
11 + || localeAliases[normalized]
12 + || normalized.includes('-') && lang2locale(normalized.split('-')[0])
13 || undefined
14 }
15
@@ -167,3 +17,11 @@ export function getLocale() {
17 return findDefined([navigator.language, ...navigator.languages], lang2locale)
18 }
19
20 +export async function loadLocale() {
21 + const locale = getLocale()
22 + if (!locale)
23 + return
24 + // AdapterDayjs needs the matching dayjs locale module registered before adapterLocale can use it.
25 + await localeLoaders[localePaths.get(locale)!]?.()
26 + return locale
27 +}