@fy-/fws-vue 2.3.48 → 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.
- package/composables/rest.ts +73 -46
- package/composables/templating.ts +62 -33
- package/package.json +1 -1
package/composables/rest.ts
CHANGED
|
@@ -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
|
-
|
|
36
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
75
|
-
const
|
|
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 (
|
|
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
|
-
|
|
122
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
147
|
+
serverRouter.addResult(requestHash, resultCopy)
|
|
148
|
+
}
|
|
137
149
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const restError = error as ResultType
|
|
150
|
+
if (restResult.result === 'error') {
|
|
151
|
+
return handleErrorResult(restResult)
|
|
152
|
+
}
|
|
142
153
|
|
|
143
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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)
|
|
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
|
-
|
|
64
|
-
|
|
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
|
|
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 ?
|
|
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
|
|
108
|
+
return DEFAULT_DARK_COLOR
|
|
86
109
|
}
|
|
87
110
|
}
|
|
88
111
|
|
|
89
112
|
function formatBytes(bytes: number, decimals = 2) {
|
|
90
|
-
if (!+bytes)
|
|
113
|
+
if (!+bytes) {
|
|
114
|
+
return '0 Bytes'
|
|
115
|
+
}
|
|
91
116
|
|
|
92
117
|
const dm = decimals < 0 ? 0 : decimals
|
|
93
|
-
|
|
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
|
|
166
|
+
// Get browser locale and lookup in cache
|
|
141
167
|
const fullLocale = getLocale()
|
|
142
168
|
|
|
143
|
-
//
|
|
144
|
-
|
|
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
|
|