@fy-/fws-vue 2.3.47 → 2.3.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -243,10 +243,10 @@ const handleBackdropClick = useDebounceFn((event: MouseEvent) => {
243
243
  <template>
244
244
  <ClientOnly>
245
245
  <transition
246
- enter-active-class="duration-300 ease-out"
246
+ enter-active-class="duration-50 ease-out"
247
247
  enter-from-class="opacity-0"
248
248
  enter-to-class="opacity-100"
249
- leave-active-class="duration-200 ease-in"
249
+ leave-active-class="duration-50 ease-in"
250
250
  leave-from-class="opacity-100"
251
251
  leave-to-class="opacity-0"
252
252
  >
@@ -30,11 +30,19 @@ export interface APIResult {
30
30
  // Cache for URL parsing to avoid repeated parsing of the same URL
31
31
  const urlParseCache = new Map<string, string>()
32
32
 
33
+ // Global request hash cache shared across all instances
34
+ const globalHashCache = new Map<string, number>()
35
+
36
+ // Track in-flight requests to avoid duplicates
37
+ const inFlightRequests = new Map<number, Promise<any>>()
38
+
39
+ // Detect if we're in SSR mode once and cache the result
40
+ let isSSRMode: boolean | null = null
41
+
33
42
  // Memoized function to extract URL pathname and search for hashing
34
43
  function getUrlForHash(url: string): string {
35
- if (urlParseCache.has(url)) {
36
- return urlParseCache.get(url)!
37
- }
44
+ const cached = urlParseCache.get(url)
45
+ if (cached) return cached
38
46
 
39
47
  let urlForHash: string
40
48
  try {
@@ -49,20 +57,34 @@ function getUrlForHash(url: string): string {
49
57
  return urlForHash
50
58
  }
51
59
 
52
- // Detect if we're in SSR mode once and cache the result
53
- let isSSRMode: boolean | null = null
60
+ // Check SSR mode with caching
54
61
  function checkSSRMode(): boolean {
55
62
  if (isSSRMode === null) {
56
63
  isSSRMode = getMode() === 'ssr'
57
64
  }
65
+
58
66
  return isSSRMode
59
67
  }
60
68
 
61
- // Optimized JSON.stringify for params
69
+ // Fast JSON.stringify for params
62
70
  function stringifyParams(params?: RestParams): string {
63
71
  return params ? JSON.stringify(params) : ''
64
72
  }
65
73
 
74
+ // Compute request hash with global caching
75
+ function computeRequestHash(url: string, method: RestMethod, params?: RestParams): number {
76
+ const cacheKey = `${url}|${method}|${stringifyParams(params)}`
77
+
78
+ const cached = globalHashCache.get(cacheKey)
79
+ if (cached !== undefined) return cached
80
+
81
+ const urlForHash = getUrlForHash(url)
82
+ const hash = stringHash(urlForHash + method + stringifyParams(params))
83
+
84
+ globalHashCache.set(cacheKey, hash)
85
+ return hash
86
+ }
87
+
66
88
  export function useRest(): <ResultType extends APIResult>(
67
89
  url: string,
68
90
  method: RestMethod,
@@ -71,23 +93,8 @@ params?: RestParams,
71
93
  const serverRouter = useServerRouter()
72
94
  const eventBus = useEventBus()
73
95
 
74
- // Cache for request hash computations
75
- const hashCache = new Map<string, number>()
76
-
77
- // Function to compute and cache request hash
78
- function computeRequestHash(url: string, method: RestMethod, params?: RestParams): number {
79
- const cacheKey = `${url}|${method}|${stringifyParams(params)}`
80
-
81
- if (hashCache.has(cacheKey)) {
82
- return hashCache.get(cacheKey)!
83
- }
84
-
85
- const urlForHash = getUrlForHash(url)
86
- const hash = stringHash(urlForHash + method + stringifyParams(params))
87
-
88
- hashCache.set(cacheKey, hash)
89
- return hash
90
- }
96
+ // Pre-check for server rendering state
97
+ const isSSR = isServerRendered()
91
98
 
92
99
  // Handle API error response consistently
93
100
  function handleErrorResult<ResultType extends APIResult>(result: ResultType): Promise<ResultType> {
@@ -104,7 +111,7 @@ params?: RestParams,
104
111
  const requestHash = computeRequestHash(url, method, params)
105
112
 
106
113
  // Check for server-rendered results first
107
- if (isServerRendered()) {
114
+ if (isSSR) {
108
115
  const hasResult = serverRouter.getResult(requestHash)
109
116
  if (hasResult !== undefined) {
110
117
  const result = hasResult as ResultType
@@ -118,35 +125,55 @@ params?: RestParams,
118
125
  }
119
126
  }
120
127
 
121
- try {
122
- const restResult: ResultType = await rest(url, method, params)
128
+ // Check if this exact request is already in-flight
129
+ const existingRequest = inFlightRequests.get(requestHash)
130
+ if (existingRequest) {
131
+ // Return the existing promise for the in-flight request
132
+ return existingRequest
133
+ }
123
134
 
124
- // Store result in server router if in SSR mode
125
- if (checkSSRMode()) {
126
- // Use structuredClone if available for better performance than JSON.parse/stringify
127
- const resultCopy = typeof structuredClone !== 'undefined'
128
- ? structuredClone(restResult)
129
- : JSON.parse(JSON.stringify(restResult))
135
+ // Create the actual request function
136
+ const performRequest = async (): Promise<ResultType> => {
137
+ try {
138
+ const restResult: ResultType = await rest(url, method, params)
130
139
 
131
- serverRouter.addResult(requestHash, resultCopy)
132
- }
140
+ // Store result in server router if in SSR mode
141
+ if (checkSSRMode()) {
142
+ // Use structuredClone if available for better performance than JSON.parse/stringify
143
+ const resultCopy = typeof structuredClone !== 'undefined'
144
+ ? structuredClone(restResult)
145
+ : JSON.parse(JSON.stringify(restResult))
133
146
 
134
- if (restResult.result === 'error') {
135
- return handleErrorResult(restResult)
136
- }
147
+ serverRouter.addResult(requestHash, resultCopy)
148
+ }
137
149
 
138
- return Promise.resolve(restResult)
139
- }
140
- catch (error) {
141
- const restError = error as ResultType
150
+ if (restResult.result === 'error') {
151
+ return handleErrorResult(restResult)
152
+ }
142
153
 
143
- if (checkSSRMode()) {
144
- serverRouter.addResult(requestHash, restError)
154
+ return Promise.resolve(restResult)
145
155
  }
156
+ catch (error) {
157
+ const restError = error as ResultType
158
+
159
+ if (checkSSRMode()) {
160
+ serverRouter.addResult(requestHash, restError)
161
+ }
146
162
 
147
- eventBus.emit('main-loading', false)
148
- eventBus.emit('rest-error', restError)
149
- return Promise.resolve(restError)
163
+ eventBus.emit('main-loading', false)
164
+ eventBus.emit('rest-error', restError)
165
+ return Promise.resolve(restError)
166
+ }
167
+ finally {
168
+ // Always remove from in-flight requests when done
169
+ inFlightRequests.delete(requestHash)
170
+ }
150
171
  }
172
+
173
+ // Track this request as in-flight
174
+ const requestPromise = performRequest()
175
+ inFlightRequests.set(requestHash, requestPromise)
176
+
177
+ return requestPromise
151
178
  }
152
179
  }
@@ -11,86 +11,112 @@ import ru from 'timeago.js/esm/lang/ru'
11
11
  import zh_CN from 'timeago.js/esm/lang/zh_CN'
12
12
  import { useTranslation } from './translations'
13
13
 
14
- // Register common locales
15
- register('fr', fr)
16
- register('fr_FR', fr)
17
- register('es', es)
18
- register('es_ES', es)
19
- register('de', de)
20
- register('de_DE', de)
21
- register('it', it)
22
- register('it_IT', it)
23
- register('nl', nl)
24
- register('nl_NL', nl)
25
- register('ru', ru)
26
- register('ru_RU', ru)
27
- register('ja', ja)
28
- register('ja_JP', ja)
29
- register('zh_CN', zh_CN)
14
+ // Register common locales - do this once at module initialization
15
+ const localeMap = {
16
+ fr,
17
+ fr_FR: fr,
18
+ es,
19
+ es_ES: es,
20
+ de,
21
+ de_DE: de,
22
+ it,
23
+ it_IT: it,
24
+ nl,
25
+ nl_NL: nl,
26
+ ru,
27
+ ru_RU: ru,
28
+ ja,
29
+ ja_JP: ja,
30
+ zh_CN,
31
+ }
32
+
33
+ // Register all locales at once
34
+ Object.entries(localeMap).forEach(([locale, func]) => register(locale, func))
30
35
 
31
36
  // Cache common constants and patterns
32
37
  const k = 1024
33
38
  const byteSizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
39
+
40
+ // Precompute log(k) for formatBytes
41
+ const logK = Math.log(k)
42
+
43
+ // Define date format options once
34
44
  const dateFormatOptions = {
35
- date: {
45
+ date: Object.freeze({
36
46
  year: 'numeric',
37
47
  month: 'long',
38
48
  day: 'numeric',
39
- },
40
- datetime: {
49
+ }),
50
+ datetime: Object.freeze({
41
51
  year: 'numeric',
42
52
  month: 'long',
43
53
  day: 'numeric',
44
54
  hour: 'numeric',
45
55
  minute: 'numeric',
46
56
  second: 'numeric',
47
- },
57
+ }),
48
58
  }
49
59
 
50
60
  // Color cache for contrast calculations to avoid repeated calculations
51
61
  const colorContrastCache = new Map<string, string>()
52
62
 
63
+ // Locale transform cache to avoid repeated replacements
64
+ const localeTransformCache = new Map<string, string>()
65
+
66
+ // Default colors for contrast errors
67
+ const DEFAULT_DARK_COLOR = '#000000'
68
+ const DEFAULT_LIGHT_COLOR = '#FFFFFF'
69
+
53
70
  function cropText(str: string, ml = 100, end = '...') {
54
- if (!str) return str
71
+ if (!str) {
72
+ return str
73
+ }
55
74
  if (str.length > ml) {
56
75
  return `${str.slice(0, ml)}${end}`
57
76
  }
77
+
58
78
  return str
59
79
  }
60
80
 
61
81
  function getContrastingTextColor(backgroundColor: string) {
62
82
  // Return from cache if available
63
- if (colorContrastCache.has(backgroundColor)) {
64
- return colorContrastCache.get(backgroundColor)!
83
+ const cached = colorContrastCache.get(backgroundColor)
84
+ if (cached) {
85
+ return cached
65
86
  }
66
87
 
67
88
  // Input validation
68
89
  if (!backgroundColor || backgroundColor.length !== 7 || backgroundColor[0] !== '#') {
69
- return '#000000'
90
+ return DEFAULT_DARK_COLOR
70
91
  }
71
92
 
72
93
  try {
94
+ // Parse hex color components more efficiently
73
95
  const r = Number.parseInt(backgroundColor.substring(1, 3), 16)
74
96
  const g = Number.parseInt(backgroundColor.substring(3, 5), 16)
75
97
  const b = Number.parseInt(backgroundColor.substring(5, 7), 16)
76
98
 
99
+ // Calculate relative luminance (standard formula for text contrast)
77
100
  const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255
78
- const result = luminance > 0.5 ? '#000000' : '#FFFFFF'
101
+ const result = luminance > 0.5 ? DEFAULT_DARK_COLOR : DEFAULT_LIGHT_COLOR
79
102
 
80
103
  // Cache the result
81
104
  colorContrastCache.set(backgroundColor, result)
82
105
  return result
83
106
  }
84
107
  catch {
85
- return '#000000'
108
+ return DEFAULT_DARK_COLOR
86
109
  }
87
110
  }
88
111
 
89
112
  function formatBytes(bytes: number, decimals = 2) {
90
- if (!+bytes) return '0 Bytes'
113
+ if (!+bytes) {
114
+ return '0 Bytes'
115
+ }
91
116
 
92
117
  const dm = decimals < 0 ? 0 : decimals
93
- const i = Math.floor(Math.log(bytes) / Math.log(k))
118
+ // Use precomputed logK for better performance
119
+ const i = Math.floor(Math.log(bytes) / logK)
94
120
 
95
121
  return `${Number.parseFloat((bytes / k ** i).toFixed(dm))} ${byteSizes[i]}`
96
122
  }
@@ -103,7 +129,7 @@ function parseDateInput(dt: Date | string | number): number {
103
129
 
104
130
  if (typeof dt === 'string') {
105
131
  const parsed = Date.parse(dt)
106
- return Number.isNaN(parsed) ? Number.parseInt(dt) : parsed
132
+ return Number.isNaN(parsed) ? Number.parseInt(dt, 10) : parsed
107
133
  }
108
134
 
109
135
  return dt
@@ -137,14 +163,17 @@ function formatTimeago(dt: Date | string | number) {
137
163
  const timestamp = parseDateInput(dt)
138
164
  const dateObj = new Date(timestamp)
139
165
 
140
- // Get browser locale and format it for timeago.js
166
+ // Get browser locale and lookup in cache
141
167
  const fullLocale = getLocale()
142
168
 
143
- // Convert locale format (e.g., fr-FR to fr_FR)
144
- const localeWithUnderscore = fullLocale.replace('-', '_')
169
+ // Use cached locale transformation if available
170
+ let localeWithUnderscore = localeTransformCache.get(fullLocale)
171
+ if (!localeWithUnderscore) {
172
+ localeWithUnderscore = fullLocale.replace('-', '_')
173
+ localeTransformCache.set(fullLocale, localeWithUnderscore)
174
+ }
145
175
 
146
176
  // Use the locale directly - the registration above ensures support
147
- // timeago.js will fall back to en_US if no matching locale is found
148
177
  return formatDateTimeago(dateObj, localeWithUnderscore)
149
178
  }
150
179
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fy-/fws-vue",
3
- "version": "2.3.47",
3
+ "version": "2.3.49",
4
4
  "author": "Florian 'Fy' Gasquez <m@fy.to>",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/fy-to/FWJS#readme",