@talex-touch/utils 1.0.22 → 1.0.24
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/common/file-scan-constants.ts +543 -0
- package/common/file-scan-utils.ts +432 -0
- package/common/index.ts +2 -0
- package/common/storage/entity/app-settings.ts +7 -1
- package/common/utils/index.ts +2 -0
- package/common/utils/polling.ts +4 -4
- package/common/utils/timing.ts +494 -0
- package/core-box/tuff/tuff-dsl.ts +17 -0
- package/package.json +1 -1
- package/plugin/index.ts +29 -2
- package/plugin/sdk/clipboard.ts +46 -3
- package/plugin/sdk/core-box.ts +27 -0
- package/plugin/sdk/features.ts +324 -0
- package/plugin/sdk/index.ts +3 -0
- package/plugin/sdk/system.ts +14 -0
- package/plugin/sdk/types.ts +157 -0
- package/renderer/touch-sdk/index.ts +9 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
export type TimingMeta = Record<string, unknown>
|
|
2
|
+
|
|
3
|
+
export interface TimingRecord {
|
|
4
|
+
label: string
|
|
5
|
+
durationMs: number
|
|
6
|
+
startedAt: number
|
|
7
|
+
endedAt: number
|
|
8
|
+
iteration?: number
|
|
9
|
+
meta?: TimingMeta
|
|
10
|
+
error?: unknown
|
|
11
|
+
logLevel?: TimingLogLevel
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type TimingLogLevel = 'none' | 'info' | 'warn' | 'error'
|
|
15
|
+
|
|
16
|
+
export type TimingLogThresholdOverrides = Partial<Record<Exclude<TimingLogLevel, 'error'>, number>>
|
|
17
|
+
|
|
18
|
+
type ResolvedTimingLogThresholds = Record<Exclude<TimingLogLevel, 'error'>, number>
|
|
19
|
+
|
|
20
|
+
const DEFAULT_AUTO_LOG = true
|
|
21
|
+
const DEFAULT_STORE_HISTORY = true
|
|
22
|
+
const DEFAULT_HISTORY_LIMIT = 50
|
|
23
|
+
|
|
24
|
+
export const DEFAULT_TIMING_LOG_THRESHOLDS: Readonly<ResolvedTimingLogThresholds> = Object.freeze({
|
|
25
|
+
none: 16.7,
|
|
26
|
+
info: 200,
|
|
27
|
+
warn: 500
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
export const DEFAULT_TIMING_OPTIONS: Readonly<Required<Pick<TimingOptions, 'autoLog' | 'storeHistory'>> & {
|
|
31
|
+
logThresholds: Readonly<ResolvedTimingLogThresholds>
|
|
32
|
+
}> = Object.freeze({
|
|
33
|
+
autoLog: DEFAULT_AUTO_LOG,
|
|
34
|
+
storeHistory: DEFAULT_STORE_HISTORY,
|
|
35
|
+
logThresholds: DEFAULT_TIMING_LOG_THRESHOLDS
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
export const DEFAULT_TIMING_MANAGER_CONFIG: Readonly<
|
|
39
|
+
Required<Pick<TimingManagerConfig, 'autoLog' | 'storeHistory' | 'historyLimit'>> & {
|
|
40
|
+
logThresholds: Readonly<ResolvedTimingLogThresholds>
|
|
41
|
+
}
|
|
42
|
+
> = Object.freeze({
|
|
43
|
+
autoLog: DEFAULT_AUTO_LOG,
|
|
44
|
+
storeHistory: DEFAULT_STORE_HISTORY,
|
|
45
|
+
historyLimit: DEFAULT_HISTORY_LIMIT,
|
|
46
|
+
logThresholds: DEFAULT_TIMING_LOG_THRESHOLDS
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
export interface TimingStats {
|
|
50
|
+
label: string
|
|
51
|
+
count: number
|
|
52
|
+
totalMs: number
|
|
53
|
+
avgMs: number
|
|
54
|
+
maxMs: number
|
|
55
|
+
minMs: number
|
|
56
|
+
lastMs: number
|
|
57
|
+
lastStartedAt?: number
|
|
58
|
+
lastEndedAt?: number
|
|
59
|
+
errorCount: number
|
|
60
|
+
lastError?: unknown
|
|
61
|
+
lastLogLevel?: TimingLogLevel
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface TimingSummary extends TimingStats {
|
|
65
|
+
history: TimingRecord[]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface TimingManagerConfig {
|
|
69
|
+
autoLog?: boolean
|
|
70
|
+
storeHistory?: boolean
|
|
71
|
+
historyLimit?: number
|
|
72
|
+
logger?: (message: string, entry: TimingRecord, stats: TimingStats) => void
|
|
73
|
+
formatter?: (entry: TimingRecord, stats: TimingStats) => string
|
|
74
|
+
logThresholds?: TimingLogThresholdOverrides
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface TimingOptions {
|
|
78
|
+
autoLog?: boolean
|
|
79
|
+
storeHistory?: boolean
|
|
80
|
+
logger?: (message: string, entry: TimingRecord, stats: TimingStats) => void
|
|
81
|
+
formatter?: (entry: TimingRecord, stats: TimingStats) => string
|
|
82
|
+
historyLimit?: number
|
|
83
|
+
logThresholds?: TimingLogThresholdOverrides
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
type ResolvedTimingOptions = {
|
|
87
|
+
autoLog: boolean
|
|
88
|
+
storeHistory: boolean
|
|
89
|
+
historyLimit: number
|
|
90
|
+
logThresholds: ResolvedTimingLogThresholds
|
|
91
|
+
formatter: (entry: TimingRecord, stats: TimingStats) => string
|
|
92
|
+
logger: (message: string, entry: TimingRecord, stats: TimingStats) => void
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export class TimingManager {
|
|
96
|
+
private readonly stats = new Map<string, TimingStats>()
|
|
97
|
+
private readonly history = new Map<string, TimingRecord[]>()
|
|
98
|
+
private readonly moduleStats = new Map<string, TimingStats>()
|
|
99
|
+
private readonly config: TimingManagerConfig
|
|
100
|
+
|
|
101
|
+
constructor(config: TimingManagerConfig = {}) {
|
|
102
|
+
const mergedThresholds: ResolvedTimingLogThresholds = {
|
|
103
|
+
...DEFAULT_TIMING_LOG_THRESHOLDS,
|
|
104
|
+
...(config.logThresholds ?? {})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
this.config = {
|
|
108
|
+
...DEFAULT_TIMING_MANAGER_CONFIG,
|
|
109
|
+
...config,
|
|
110
|
+
logThresholds: mergedThresholds
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
createTiming(label: string, options: TimingOptions = {}): TimingScope {
|
|
115
|
+
return new TimingScope(this, label, options)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
record(label: string, record: TimingRecord, options: TimingOptions = {}): void {
|
|
119
|
+
const resolved = this.resolveOptions(options)
|
|
120
|
+
const logLevel = determineLogLevel(record.durationMs, resolved.logThresholds)
|
|
121
|
+
const recordWithLevel: TimingRecord = { ...record, logLevel }
|
|
122
|
+
|
|
123
|
+
const stats = this.updateStats(this.stats, label, recordWithLevel)
|
|
124
|
+
const moduleKey = this.extractModuleKey(label)
|
|
125
|
+
this.updateStats(this.moduleStats, moduleKey, { ...recordWithLevel, label: moduleKey })
|
|
126
|
+
|
|
127
|
+
if (resolved.storeHistory) {
|
|
128
|
+
const limit = resolved.historyLimit
|
|
129
|
+
const list = this.history.get(label) ?? []
|
|
130
|
+
list.push(recordWithLevel)
|
|
131
|
+
if (list.length > limit) {
|
|
132
|
+
list.splice(0, list.length - limit)
|
|
133
|
+
}
|
|
134
|
+
this.history.set(label, list)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (resolved.autoLog && logLevel !== 'none') {
|
|
138
|
+
const message = resolved.formatter(recordWithLevel, stats)
|
|
139
|
+
resolved.logger(message, recordWithLevel, stats)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
getStats(label: string): TimingStats | undefined {
|
|
144
|
+
const stats = this.stats.get(label)
|
|
145
|
+
if (!stats) return undefined
|
|
146
|
+
return { ...stats }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
getAllStats(): TimingStats[] {
|
|
150
|
+
return Array.from(this.stats.values()).map((s) => ({ ...s }))
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
getHistory(label: string): TimingRecord[] {
|
|
154
|
+
return [...(this.history.get(label) ?? [])]
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
getModuleStats(moduleKey?: string): TimingStats[] | TimingStats | undefined {
|
|
158
|
+
if (moduleKey) {
|
|
159
|
+
const stats = this.moduleStats.get(moduleKey)
|
|
160
|
+
return stats ? { ...stats } : undefined
|
|
161
|
+
}
|
|
162
|
+
return Array.from(this.moduleStats.values()).map((s) => ({ ...s }))
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
reset(label?: string): void {
|
|
166
|
+
if (!label) {
|
|
167
|
+
this.stats.clear()
|
|
168
|
+
this.history.clear()
|
|
169
|
+
this.moduleStats.clear()
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
this.stats.delete(label)
|
|
173
|
+
this.history.delete(label)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private updateStats(target: Map<string, TimingStats>, label: string, record: TimingRecord): TimingStats {
|
|
177
|
+
const { durationMs, error } = record
|
|
178
|
+
const next = target.get(label) ?? {
|
|
179
|
+
label,
|
|
180
|
+
count: 0,
|
|
181
|
+
totalMs: 0,
|
|
182
|
+
avgMs: 0,
|
|
183
|
+
maxMs: Number.NEGATIVE_INFINITY,
|
|
184
|
+
minMs: Number.POSITIVE_INFINITY,
|
|
185
|
+
lastMs: 0,
|
|
186
|
+
errorCount: 0
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
next.count += 1
|
|
190
|
+
next.totalMs += durationMs
|
|
191
|
+
next.avgMs = next.totalMs / next.count
|
|
192
|
+
next.maxMs = Math.max(next.maxMs, durationMs)
|
|
193
|
+
next.minMs = Math.min(next.minMs, durationMs)
|
|
194
|
+
next.lastMs = durationMs
|
|
195
|
+
next.lastStartedAt = record.startedAt
|
|
196
|
+
next.lastEndedAt = record.endedAt
|
|
197
|
+
next.lastLogLevel = record.logLevel
|
|
198
|
+
|
|
199
|
+
if (error) {
|
|
200
|
+
next.errorCount += 1
|
|
201
|
+
next.lastError = error
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
target.set(label, next)
|
|
205
|
+
return next
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private extractModuleKey(label: string): string {
|
|
209
|
+
const [moduleKey] = label.split(':')
|
|
210
|
+
return moduleKey || label
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private resolveOptions(options: TimingOptions = {}): ResolvedTimingOptions {
|
|
214
|
+
const logThresholds: ResolvedTimingLogThresholds = {
|
|
215
|
+
...DEFAULT_TIMING_LOG_THRESHOLDS,
|
|
216
|
+
...(this.config.logThresholds ?? {}),
|
|
217
|
+
...(options.logThresholds ?? {})
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
autoLog: options.autoLog ?? this.config.autoLog ?? DEFAULT_AUTO_LOG,
|
|
222
|
+
storeHistory: options.storeHistory ?? this.config.storeHistory ?? DEFAULT_STORE_HISTORY,
|
|
223
|
+
historyLimit: options.historyLimit ?? this.config.historyLimit ?? DEFAULT_HISTORY_LIMIT,
|
|
224
|
+
logThresholds,
|
|
225
|
+
formatter: options.formatter ?? this.config.formatter ?? defaultFormatter,
|
|
226
|
+
logger: options.logger ?? this.config.logger ?? defaultLogger
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export class TimingScope {
|
|
232
|
+
constructor(
|
|
233
|
+
private readonly manager: TimingManager,
|
|
234
|
+
private readonly label: string,
|
|
235
|
+
private readonly options: TimingOptions
|
|
236
|
+
) {}
|
|
237
|
+
|
|
238
|
+
async cost<T>(fn: () => Promise<T> | T, meta: TimingMeta = {}, overrides: TimingOptions = {}): Promise<T> {
|
|
239
|
+
const startedAt = now()
|
|
240
|
+
try {
|
|
241
|
+
const result = await fn()
|
|
242
|
+
this.finish(startedAt, meta, undefined, overrides)
|
|
243
|
+
return result
|
|
244
|
+
} catch (error) {
|
|
245
|
+
this.finish(startedAt, meta, error, overrides)
|
|
246
|
+
throw error
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async count<T>(iterations: number, fn: (iteration: number) => Promise<T> | T, meta: TimingMeta | ((iteration: number) => TimingMeta) = {}, overrides: TimingOptions = {}): Promise<T[]> {
|
|
251
|
+
const results: T[] = []
|
|
252
|
+
for (let index = 0; index < iterations; index++) {
|
|
253
|
+
const iterationMeta = typeof meta === 'function' ? meta(index) : meta
|
|
254
|
+
const startedAt = now()
|
|
255
|
+
try {
|
|
256
|
+
const value = await fn(index)
|
|
257
|
+
this.finish(startedAt, { ...iterationMeta, iteration: index }, undefined, overrides)
|
|
258
|
+
results.push(value)
|
|
259
|
+
} catch (error) {
|
|
260
|
+
this.finish(startedAt, { ...iterationMeta, iteration: index }, error, overrides)
|
|
261
|
+
throw error
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return results
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
mark(durationMs: number, meta: TimingMeta = {}, overrides: TimingOptions = {}): void {
|
|
268
|
+
const endedAt = now()
|
|
269
|
+
const startedAt = endedAt - durationMs
|
|
270
|
+
this.manager.record(
|
|
271
|
+
this.label,
|
|
272
|
+
{
|
|
273
|
+
label: this.label,
|
|
274
|
+
durationMs,
|
|
275
|
+
startedAt,
|
|
276
|
+
endedAt,
|
|
277
|
+
meta
|
|
278
|
+
},
|
|
279
|
+
{ ...this.options, ...overrides }
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
getStats(): TimingStats | undefined {
|
|
284
|
+
return this.manager.getStats(this.label)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
getHistory(): TimingRecord[] {
|
|
288
|
+
return this.manager.getHistory(this.label)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private finish(startedAt: number, meta: TimingMeta, error: unknown, overrides: TimingOptions): void {
|
|
292
|
+
const endedAt = now()
|
|
293
|
+
const record: TimingRecord = {
|
|
294
|
+
label: this.label,
|
|
295
|
+
durationMs: endedAt - startedAt,
|
|
296
|
+
startedAt,
|
|
297
|
+
endedAt,
|
|
298
|
+
meta,
|
|
299
|
+
error
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
this.manager.record(this.label, record, { ...this.options, ...overrides })
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function defaultFormatter(record: TimingRecord, stats: TimingStats): string {
|
|
307
|
+
const duration = record.durationMs.toFixed(2)
|
|
308
|
+
const levelTag =
|
|
309
|
+
record.logLevel && record.logLevel !== 'info' ? ` [${record.logLevel.toUpperCase()}]` : ''
|
|
310
|
+
return `⏱ [${record.label}] ${duration} ms${levelTag} (avg: ${stats.avgMs.toFixed(
|
|
311
|
+
2
|
|
312
|
+
)} ms, max: ${stats.maxMs.toFixed(2)} ms, count: ${stats.count})`
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function defaultLogger(message: string, entry: TimingRecord, _stats: TimingStats): void {
|
|
316
|
+
if (entry.logLevel === 'none') {
|
|
317
|
+
return
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (entry.logLevel === 'warn') {
|
|
321
|
+
console.warn(message)
|
|
322
|
+
return
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (entry.logLevel === 'error') {
|
|
326
|
+
console.error(message)
|
|
327
|
+
return
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (typeof console.info === 'function') {
|
|
331
|
+
console.info(message)
|
|
332
|
+
return
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
console.log(message)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function determineLogLevel(durationMs: number, thresholds: ResolvedTimingLogThresholds): TimingLogLevel {
|
|
339
|
+
if (durationMs <= thresholds.none) {
|
|
340
|
+
return 'none'
|
|
341
|
+
}
|
|
342
|
+
if (durationMs <= thresholds.info) {
|
|
343
|
+
return 'info'
|
|
344
|
+
}
|
|
345
|
+
if (durationMs <= thresholds.warn) {
|
|
346
|
+
return 'warn'
|
|
347
|
+
}
|
|
348
|
+
return 'error'
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const now = (() => {
|
|
352
|
+
if (typeof globalThis !== 'undefined') {
|
|
353
|
+
const perf = (globalThis as typeof globalThis & { performance?: { now?: () => number } }).performance
|
|
354
|
+
if (perf?.now) {
|
|
355
|
+
return () => perf.now()
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return () => Date.now()
|
|
359
|
+
})()
|
|
360
|
+
|
|
361
|
+
const timingManagerInstance = new TimingManager()
|
|
362
|
+
|
|
363
|
+
export const timingManager = timingManagerInstance
|
|
364
|
+
|
|
365
|
+
export function startTiming(): number {
|
|
366
|
+
return now()
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export function completeTiming(
|
|
370
|
+
label: string,
|
|
371
|
+
startedAt: number,
|
|
372
|
+
meta: TimingMeta = {},
|
|
373
|
+
options: TimingOptions = {}
|
|
374
|
+
): number {
|
|
375
|
+
const endedAt = now()
|
|
376
|
+
const durationMs = endedAt - startedAt
|
|
377
|
+
timingManagerInstance.record(
|
|
378
|
+
label,
|
|
379
|
+
{
|
|
380
|
+
label,
|
|
381
|
+
durationMs,
|
|
382
|
+
startedAt,
|
|
383
|
+
endedAt,
|
|
384
|
+
meta
|
|
385
|
+
},
|
|
386
|
+
options
|
|
387
|
+
)
|
|
388
|
+
return durationMs
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function logTiming(
|
|
392
|
+
label: string,
|
|
393
|
+
durationMs: number,
|
|
394
|
+
meta: TimingMeta = {},
|
|
395
|
+
options: TimingOptions = {}
|
|
396
|
+
): void {
|
|
397
|
+
const endedAt = now()
|
|
398
|
+
const startedAt = endedAt - durationMs
|
|
399
|
+
timingManagerInstance.record(
|
|
400
|
+
label,
|
|
401
|
+
{
|
|
402
|
+
label,
|
|
403
|
+
durationMs,
|
|
404
|
+
startedAt,
|
|
405
|
+
endedAt,
|
|
406
|
+
meta
|
|
407
|
+
},
|
|
408
|
+
options
|
|
409
|
+
)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export interface TimingLoggerToken {
|
|
413
|
+
label: string
|
|
414
|
+
startedAt: number
|
|
415
|
+
meta: TimingMeta
|
|
416
|
+
options: TimingOptions
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export const timingLogger = {
|
|
420
|
+
start(label: string, meta: TimingMeta = {}, options: TimingOptions = {}): TimingLoggerToken {
|
|
421
|
+
return {
|
|
422
|
+
label,
|
|
423
|
+
startedAt: startTiming(),
|
|
424
|
+
meta,
|
|
425
|
+
options
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
|
|
429
|
+
finish(
|
|
430
|
+
token: TimingLoggerToken,
|
|
431
|
+
meta: TimingMeta = {},
|
|
432
|
+
overrides: TimingOptions = {}
|
|
433
|
+
): number {
|
|
434
|
+
const mergedMeta = { ...token.meta, ...meta }
|
|
435
|
+
const mergedOptions = mergeTimingOptions(token.options, overrides)
|
|
436
|
+
return completeTiming(token.label, token.startedAt, mergedMeta, mergedOptions)
|
|
437
|
+
},
|
|
438
|
+
|
|
439
|
+
print(
|
|
440
|
+
label: string,
|
|
441
|
+
durationMs: number,
|
|
442
|
+
meta: TimingMeta = {},
|
|
443
|
+
options: TimingOptions = {}
|
|
444
|
+
): number {
|
|
445
|
+
logTiming(label, durationMs, meta, options)
|
|
446
|
+
return durationMs
|
|
447
|
+
},
|
|
448
|
+
|
|
449
|
+
async cost<T>(
|
|
450
|
+
label: string,
|
|
451
|
+
fn: () => Promise<T> | T,
|
|
452
|
+
meta: TimingMeta = {},
|
|
453
|
+
options: TimingOptions = {}
|
|
454
|
+
): Promise<T> {
|
|
455
|
+
const scope = createTiming(label, options)
|
|
456
|
+
return scope.cost(fn, meta)
|
|
457
|
+
},
|
|
458
|
+
|
|
459
|
+
mark(
|
|
460
|
+
label: string,
|
|
461
|
+
durationMs: number,
|
|
462
|
+
meta: TimingMeta = {},
|
|
463
|
+
options: TimingOptions = {}
|
|
464
|
+
): number {
|
|
465
|
+
logTiming(label, durationMs, meta, options)
|
|
466
|
+
return durationMs
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export function createTiming(label: string, options: TimingOptions = {}): TimingScope {
|
|
471
|
+
return timingManagerInstance.createTiming(label, options)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function mergeTimingOptions(
|
|
475
|
+
base: TimingOptions = {},
|
|
476
|
+
override: TimingOptions = {}
|
|
477
|
+
): TimingOptions {
|
|
478
|
+
if (!base && !override) return {}
|
|
479
|
+
if (!override || Object.keys(override).length === 0) {
|
|
480
|
+
return { ...base }
|
|
481
|
+
}
|
|
482
|
+
if (!base || Object.keys(base).length === 0) {
|
|
483
|
+
return { ...override }
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return {
|
|
487
|
+
...base,
|
|
488
|
+
...override,
|
|
489
|
+
logThresholds: {
|
|
490
|
+
...(base.logThresholds ?? {}),
|
|
491
|
+
...(override.logThresholds ?? {})
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
@@ -863,12 +863,29 @@ export interface TuffMeta {
|
|
|
863
863
|
* @description The ID of the feature.
|
|
864
864
|
*/
|
|
865
865
|
featureId?: string
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* For plugin feature items, this holds the interaction configuration.
|
|
869
|
+
* @description Defines how the feature should be rendered (widget, webcontent, or index).
|
|
870
|
+
*/
|
|
871
|
+
interaction?: {
|
|
872
|
+
type: 'webcontent' | 'widget' | 'index'
|
|
873
|
+
path?: string
|
|
874
|
+
}
|
|
875
|
+
|
|
866
876
|
/**
|
|
867
877
|
* Defines the default action to be taken when the item is executed (e.g., by pressing Enter).
|
|
868
878
|
* This is used to distinguish simple actions (like 'copy') from feature activations.
|
|
869
879
|
* @description The default action type.
|
|
870
880
|
*/
|
|
871
881
|
defaultAction?: string;
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* Priority of the item for sorting in search results
|
|
885
|
+
* Higher numbers have higher priority (displayed first)
|
|
886
|
+
* @description Priority value for search result ordering
|
|
887
|
+
*/
|
|
888
|
+
priority?: number;
|
|
872
889
|
/**
|
|
873
890
|
* 原始数据
|
|
874
891
|
* @description 项目的原始数据对象,用于特殊处理
|
package/package.json
CHANGED
package/plugin/index.ts
CHANGED
|
@@ -98,6 +98,12 @@ export interface IPluginFeature {
|
|
|
98
98
|
platform: IPlatform
|
|
99
99
|
commands: IFeatureCommand[]
|
|
100
100
|
interaction?: IFeatureInteraction
|
|
101
|
+
/**
|
|
102
|
+
* Priority of the feature for sorting in search results
|
|
103
|
+
* Higher numbers have higher priority (displayed first)
|
|
104
|
+
* Default is 0
|
|
105
|
+
*/
|
|
106
|
+
priority?: number
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
export type IFeatureInteraction = {
|
|
@@ -113,6 +119,11 @@ export type IFeatureInteraction = {
|
|
|
113
119
|
* These hooks are triggered based on real user interaction and system events.
|
|
114
120
|
*/
|
|
115
121
|
export interface IFeatureLifeCycle {
|
|
122
|
+
/**
|
|
123
|
+
* onInit is called when the feature is initialized.
|
|
124
|
+
* Can be used to prepare data or UI specific to this session.
|
|
125
|
+
*/
|
|
126
|
+
onInit?(): void
|
|
116
127
|
/**
|
|
117
128
|
* Called when a feature is actively launched from the launcher.
|
|
118
129
|
* Can be used to prepare data or UI specific to this session.
|
|
@@ -156,8 +167,16 @@ export interface IFeatureLifeCycle {
|
|
|
156
167
|
* This is used for handling actions on the items themselves,
|
|
157
168
|
* rather than triggering a new feature.
|
|
158
169
|
* @param item The TuffItem that was executed.
|
|
170
|
+
* @returns Object indicating whether to activate the feature and any activation data
|
|
159
171
|
*/
|
|
160
|
-
onItemAction?(item: any): Promise<
|
|
172
|
+
onItemAction?(item: any): Promise<{
|
|
173
|
+
/** Whether the action executed an external operation (e.g., opened browser) */
|
|
174
|
+
externalAction?: boolean
|
|
175
|
+
/** Whether the feature should be activated after this action */
|
|
176
|
+
shouldActivate?: boolean
|
|
177
|
+
/** Activation data if shouldActivate is true */
|
|
178
|
+
activation?: any
|
|
179
|
+
} | void>
|
|
161
180
|
}
|
|
162
181
|
|
|
163
182
|
/**
|
|
@@ -206,8 +225,16 @@ export interface ITargetFeatureLifeCycle {
|
|
|
206
225
|
* This is used for handling actions on the items themselves,
|
|
207
226
|
* rather than triggering a new feature.
|
|
208
227
|
* @param item The TuffItem that was executed.
|
|
228
|
+
* @returns Object indicating whether to activate the feature and any activation data
|
|
209
229
|
*/
|
|
210
|
-
onItemAction?(item: any): Promise<
|
|
230
|
+
onItemAction?(item: any): Promise<{
|
|
231
|
+
/** Whether the action executed an external operation (e.g., opened browser) */
|
|
232
|
+
externalAction?: boolean
|
|
233
|
+
/** Whether the feature should be activated after this action */
|
|
234
|
+
shouldActivate?: boolean
|
|
235
|
+
/** Activation data if shouldActivate is true */
|
|
236
|
+
activation?: any
|
|
237
|
+
} | void>
|
|
211
238
|
}
|
|
212
239
|
|
|
213
240
|
/**
|
package/plugin/sdk/clipboard.ts
CHANGED
|
@@ -8,6 +8,19 @@ const ensurePluginChannel = () => {
|
|
|
8
8
|
return channel
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
const normalizeItem = (item: PluginClipboardItem | null): PluginClipboardItem | null => {
|
|
12
|
+
if (!item) return item
|
|
13
|
+
if (!item.meta && typeof item.metadata === 'string') {
|
|
14
|
+
try {
|
|
15
|
+
const parsed = JSON.parse(item.metadata)
|
|
16
|
+
return { ...item, meta: parsed }
|
|
17
|
+
} catch {
|
|
18
|
+
return { ...item, meta: null }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return item
|
|
22
|
+
}
|
|
23
|
+
|
|
11
24
|
export interface ClipboardHistoryOptions {
|
|
12
25
|
page?: number
|
|
13
26
|
}
|
|
@@ -21,17 +34,35 @@ export interface ClipboardDeleteOptions {
|
|
|
21
34
|
id: number
|
|
22
35
|
}
|
|
23
36
|
|
|
37
|
+
export interface ClipboardApplyOptions {
|
|
38
|
+
item?: PluginClipboardItem
|
|
39
|
+
text?: string
|
|
40
|
+
html?: string | null
|
|
41
|
+
files?: string[]
|
|
42
|
+
delayMs?: number
|
|
43
|
+
hideCoreBox?: boolean
|
|
44
|
+
type?: PluginClipboardItem['type']
|
|
45
|
+
}
|
|
46
|
+
|
|
24
47
|
export function useClipboardHistory() {
|
|
25
48
|
const channel = ensurePluginChannel()
|
|
26
49
|
|
|
27
50
|
return {
|
|
28
51
|
async getLatest(): Promise<PluginClipboardItem | null> {
|
|
29
|
-
|
|
52
|
+
const result = await channel.send('clipboard:get-latest')
|
|
53
|
+
return normalizeItem(result)
|
|
30
54
|
},
|
|
31
55
|
|
|
32
56
|
async getHistory(options: ClipboardHistoryOptions = {}): Promise<PluginClipboardHistoryResponse> {
|
|
33
57
|
const { page = 1 } = options
|
|
34
|
-
|
|
58
|
+
const response = await channel.send('clipboard:get-history', { page })
|
|
59
|
+
const history = Array.isArray(response?.history)
|
|
60
|
+
? response.history.map((item: PluginClipboardItem) => normalizeItem(item) ?? item)
|
|
61
|
+
: []
|
|
62
|
+
return {
|
|
63
|
+
...response,
|
|
64
|
+
history
|
|
65
|
+
}
|
|
35
66
|
},
|
|
36
67
|
|
|
37
68
|
async setFavorite(options: ClipboardFavoriteOptions): Promise<void> {
|
|
@@ -49,8 +80,20 @@ export function useClipboardHistory() {
|
|
|
49
80
|
onDidChange(callback: (item: PluginClipboardItem) => void): () => void {
|
|
50
81
|
return channel.regChannel('core-box:clipboard-change', ({ data }) => {
|
|
51
82
|
const item = (data && 'item' in data ? data.item : data) as PluginClipboardItem
|
|
52
|
-
callback(item)
|
|
83
|
+
callback(normalizeItem(item) ?? item)
|
|
53
84
|
})
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Writes the provided clipboard payload to the system clipboard and issues a paste command
|
|
89
|
+
* to the foreground application.
|
|
90
|
+
*/
|
|
91
|
+
async applyToActiveApp(options: ClipboardApplyOptions = {}): Promise<boolean> {
|
|
92
|
+
const response = await channel.send('clipboard:apply-to-active-app', options)
|
|
93
|
+
if (typeof response === 'object' && response) {
|
|
94
|
+
return Boolean((response as any).success)
|
|
95
|
+
}
|
|
96
|
+
return true
|
|
54
97
|
}
|
|
55
98
|
}
|
|
56
99
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { IPluginRendererChannel } from './types'
|
|
2
|
+
|
|
3
|
+
const ensurePluginContext = (): { channel: IPluginRendererChannel; pluginName: string } => {
|
|
4
|
+
const plugin = (window as any)?.$plugin
|
|
5
|
+
if (!plugin?.name) {
|
|
6
|
+
throw new Error('[TouchSDK] Unable to resolve plugin name inside renderer context.')
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const channel = (window as any)?.$channel as IPluginRendererChannel | undefined
|
|
10
|
+
if (!channel) {
|
|
11
|
+
throw new Error('[TouchSDK] Channel bridge is not available for the current plugin renderer.')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
channel,
|
|
16
|
+
pluginName: plugin.name as string
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Clears all CoreBox items associated with the current plugin.
|
|
22
|
+
*/
|
|
23
|
+
export async function clearCoreBoxItems(): Promise<void> {
|
|
24
|
+
const { channel, pluginName } = ensurePluginContext()
|
|
25
|
+
await channel.send('core-box:clear-items', { pluginName })
|
|
26
|
+
}
|
|
27
|
+
|