@talex-touch/utils 1.0.40 → 1.0.42
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/core-box/tuff/tuff-dsl.ts +65 -0
- package/intelligence/client.ts +8 -8
- package/market/constants.ts +14 -0
- package/market/types.ts +1 -1
- package/package.json +1 -1
- package/plugin/index.ts +2 -1
- package/plugin/providers/index.ts +4 -0
- package/plugin/providers/market-client.ts +215 -0
- package/plugin/providers/npm-provider.ts +213 -0
- package/plugin/providers/tpex-provider.ts +283 -0
- package/plugin/providers/tpex-types.ts +34 -0
- package/plugin/sdk/hooks/bridge.ts +105 -48
- package/plugin/sdk/performance.ts +1 -16
- package/renderer/hooks/arg-mapper.ts +20 -6
- package/renderer/hooks/use-intelligence.ts +291 -34
- package/renderer/storage/intelligence-storage.ts +9 -9
- package/types/division-box.ts +20 -0
- package/types/intelligence.ts +1496 -78
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import type { IManifest } from '..'
|
|
2
|
+
import type {
|
|
3
|
+
PluginInstallRequest,
|
|
4
|
+
PluginInstallResult,
|
|
5
|
+
PluginProvider,
|
|
6
|
+
PluginProviderContext,
|
|
7
|
+
} from './types'
|
|
8
|
+
import { PluginProviderType } from './types'
|
|
9
|
+
|
|
10
|
+
const DEFAULT_TPEX_API = 'https://tuff.tagzxia.com'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check if source is a .tpex file path or URL
|
|
14
|
+
*/
|
|
15
|
+
function isTpexFile(source: string): boolean {
|
|
16
|
+
return source.trim().toLowerCase().endsWith('.tpex')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Check if source is a remote URL
|
|
21
|
+
*/
|
|
22
|
+
function isRemoteUrl(source: string): boolean {
|
|
23
|
+
return /^https?:\/\//i.test(source)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface TpexPluginInfo {
|
|
27
|
+
id: string
|
|
28
|
+
slug: string
|
|
29
|
+
name: string
|
|
30
|
+
summary: string
|
|
31
|
+
category: string
|
|
32
|
+
installs: number
|
|
33
|
+
homepage?: string | null
|
|
34
|
+
isOfficial: boolean
|
|
35
|
+
badges: string[]
|
|
36
|
+
author?: { name: string, avatarColor?: string } | null
|
|
37
|
+
iconUrl?: string | null
|
|
38
|
+
latestVersion?: {
|
|
39
|
+
id: string
|
|
40
|
+
version: string
|
|
41
|
+
channel: string
|
|
42
|
+
packageUrl: string
|
|
43
|
+
packageSize: number
|
|
44
|
+
manifest?: Record<string, unknown> | null
|
|
45
|
+
changelog?: string | null
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface TpexListResponse {
|
|
50
|
+
plugins: TpexPluginInfo[]
|
|
51
|
+
total: number
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface TpexDetailResponse {
|
|
55
|
+
plugin: TpexPluginInfo & {
|
|
56
|
+
versions?: Array<{
|
|
57
|
+
id: string
|
|
58
|
+
version: string
|
|
59
|
+
channel: string
|
|
60
|
+
packageUrl: string
|
|
61
|
+
packageSize: number
|
|
62
|
+
manifest?: Record<string, unknown> | null
|
|
63
|
+
changelog?: string | null
|
|
64
|
+
}>
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Parse TPEX source string to extract slug and optional version
|
|
70
|
+
* Formats: "tpex:slug", "tpex:slug@version", "slug" (when hintType is TPEX)
|
|
71
|
+
*/
|
|
72
|
+
function parseTpexSource(source: string): { slug: string, version?: string } | null {
|
|
73
|
+
const tpexMatch = source.match(/^tpex:([a-z0-9][a-z0-9\-_.]{1,62}[a-z0-9])(?:@(.+))?$/i)
|
|
74
|
+
if (tpexMatch) {
|
|
75
|
+
return { slug: tpexMatch[1], version: tpexMatch[2] }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const slugMatch = source.match(/^([a-z0-9][a-z0-9\-_.]{1,62}[a-z0-9])(?:@(.+))?$/i)
|
|
79
|
+
if (slugMatch) {
|
|
80
|
+
return { slug: slugMatch[1], version: slugMatch[2] }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class TpexProvider implements PluginProvider {
|
|
87
|
+
readonly type = PluginProviderType.TPEX
|
|
88
|
+
private apiBase: string
|
|
89
|
+
|
|
90
|
+
constructor(apiBase: string = DEFAULT_TPEX_API) {
|
|
91
|
+
this.apiBase = apiBase.replace(/\/$/, '')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
canHandle(request: PluginInstallRequest): boolean {
|
|
95
|
+
// Handle .tpex file paths (local or remote URL)
|
|
96
|
+
if (isTpexFile(request.source)) {
|
|
97
|
+
return true
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (request.hintType === PluginProviderType.TPEX) {
|
|
101
|
+
return parseTpexSource(request.source) !== null
|
|
102
|
+
}
|
|
103
|
+
return request.source.startsWith('tpex:')
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async install(
|
|
107
|
+
request: PluginInstallRequest,
|
|
108
|
+
context?: PluginProviderContext,
|
|
109
|
+
): Promise<PluginInstallResult> {
|
|
110
|
+
// Handle .tpex file directly (local path or remote URL)
|
|
111
|
+
if (isTpexFile(request.source)) {
|
|
112
|
+
return this.installFromFile(request, context)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Handle tpex:slug format - fetch from API
|
|
116
|
+
return this.installFromRegistry(request, context)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Install from a .tpex file (local path or remote URL)
|
|
121
|
+
*/
|
|
122
|
+
private async installFromFile(
|
|
123
|
+
request: PluginInstallRequest,
|
|
124
|
+
context?: PluginProviderContext,
|
|
125
|
+
): Promise<PluginInstallResult> {
|
|
126
|
+
let filePath = request.source
|
|
127
|
+
let arrayBuffer: ArrayBuffer | undefined
|
|
128
|
+
|
|
129
|
+
if (isRemoteUrl(request.source)) {
|
|
130
|
+
// Download remote .tpex file
|
|
131
|
+
const downloadRes = await fetch(request.source)
|
|
132
|
+
if (!downloadRes.ok) {
|
|
133
|
+
throw new Error(`Failed to download TPEX file: ${downloadRes.statusText}`)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
arrayBuffer = await downloadRes.arrayBuffer()
|
|
137
|
+
const tempDir = context?.tempDir ?? '/tmp'
|
|
138
|
+
const fileName = `tpex-${Date.now()}.tpex`
|
|
139
|
+
filePath = `${tempDir}/${fileName}`
|
|
140
|
+
|
|
141
|
+
if (typeof globalThis.process !== 'undefined') {
|
|
142
|
+
const fs = await import('node:fs/promises')
|
|
143
|
+
await fs.writeFile(filePath, Buffer.from(arrayBuffer))
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// For local files, just return the path - manifest extraction happens in core-app
|
|
148
|
+
return {
|
|
149
|
+
provider: PluginProviderType.TPEX,
|
|
150
|
+
filePath,
|
|
151
|
+
official: false,
|
|
152
|
+
metadata: {
|
|
153
|
+
sourceType: 'file',
|
|
154
|
+
originalSource: request.source,
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Install from TPEX registry (tpex:slug format)
|
|
161
|
+
*/
|
|
162
|
+
private async installFromRegistry(
|
|
163
|
+
request: PluginInstallRequest,
|
|
164
|
+
context?: PluginProviderContext,
|
|
165
|
+
): Promise<PluginInstallResult> {
|
|
166
|
+
const parsed = parseTpexSource(request.source)
|
|
167
|
+
if (!parsed) {
|
|
168
|
+
throw new Error(`Invalid TPEX source format: ${request.source}`)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const { slug, version } = parsed
|
|
172
|
+
|
|
173
|
+
const detailRes = await fetch(`${this.apiBase}/api/market/plugins/${slug}`)
|
|
174
|
+
if (!detailRes.ok) {
|
|
175
|
+
if (detailRes.status === 404) {
|
|
176
|
+
throw new Error(`Plugin not found: ${slug}`)
|
|
177
|
+
}
|
|
178
|
+
throw new Error(`Failed to fetch plugin details: ${detailRes.statusText}`)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const detail: TpexDetailResponse = await detailRes.json()
|
|
182
|
+
const plugin = detail.plugin
|
|
183
|
+
|
|
184
|
+
let targetVersion = plugin.latestVersion
|
|
185
|
+
if (version && plugin.versions) {
|
|
186
|
+
targetVersion = plugin.versions.find(v => v.version === version) ?? targetVersion
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!targetVersion?.packageUrl) {
|
|
190
|
+
throw new Error(`No downloadable version found for plugin: ${slug}`)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const downloadUrl = targetVersion.packageUrl.startsWith('http')
|
|
194
|
+
? targetVersion.packageUrl
|
|
195
|
+
: `${this.apiBase}${targetVersion.packageUrl}`
|
|
196
|
+
|
|
197
|
+
const downloadRes = await fetch(downloadUrl)
|
|
198
|
+
if (!downloadRes.ok) {
|
|
199
|
+
throw new Error(`Failed to download plugin package: ${downloadRes.statusText}`)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const arrayBuffer = await downloadRes.arrayBuffer()
|
|
203
|
+
const tempDir = context?.tempDir ?? '/tmp'
|
|
204
|
+
const fileName = `${slug}-${targetVersion.version}.tpex`
|
|
205
|
+
const filePath = `${tempDir}/${fileName}`
|
|
206
|
+
|
|
207
|
+
if (typeof globalThis.process !== 'undefined') {
|
|
208
|
+
const fs = await import('node:fs/promises')
|
|
209
|
+
await fs.writeFile(filePath, Buffer.from(arrayBuffer))
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const manifest: IManifest | undefined = targetVersion.manifest
|
|
213
|
+
? {
|
|
214
|
+
id: plugin.slug,
|
|
215
|
+
name: plugin.name,
|
|
216
|
+
version: targetVersion.version,
|
|
217
|
+
description: plugin.summary,
|
|
218
|
+
author: plugin.author?.name ?? 'Unknown',
|
|
219
|
+
main: (targetVersion.manifest as Record<string, unknown>).main as string ?? 'index.js',
|
|
220
|
+
icon: plugin.iconUrl ?? undefined,
|
|
221
|
+
...targetVersion.manifest,
|
|
222
|
+
}
|
|
223
|
+
: undefined
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
provider: PluginProviderType.TPEX,
|
|
227
|
+
filePath,
|
|
228
|
+
official: plugin.isOfficial,
|
|
229
|
+
manifest,
|
|
230
|
+
metadata: {
|
|
231
|
+
sourceType: 'registry',
|
|
232
|
+
slug: plugin.slug,
|
|
233
|
+
version: targetVersion.version,
|
|
234
|
+
channel: targetVersion.channel,
|
|
235
|
+
packageSize: targetVersion.packageSize,
|
|
236
|
+
installs: plugin.installs,
|
|
237
|
+
},
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* List all available plugins from TPEX registry
|
|
243
|
+
*/
|
|
244
|
+
async listPlugins(): Promise<TpexPluginInfo[]> {
|
|
245
|
+
const res = await fetch(`${this.apiBase}/api/market/plugins`)
|
|
246
|
+
if (!res.ok) {
|
|
247
|
+
throw new Error(`Failed to fetch plugin list: ${res.statusText}`)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const data: TpexListResponse = await res.json()
|
|
251
|
+
return data.plugins
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Get plugin details by slug
|
|
256
|
+
*/
|
|
257
|
+
async getPlugin(slug: string): Promise<TpexDetailResponse['plugin'] | null> {
|
|
258
|
+
const res = await fetch(`${this.apiBase}/api/market/plugins/${slug}`)
|
|
259
|
+
if (!res.ok) {
|
|
260
|
+
if (res.status === 404) return null
|
|
261
|
+
throw new Error(`Failed to fetch plugin: ${res.statusText}`)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const data: TpexDetailResponse = await res.json()
|
|
265
|
+
return data.plugin
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Search plugins by keyword
|
|
270
|
+
*/
|
|
271
|
+
async searchPlugins(keyword: string): Promise<TpexPluginInfo[]> {
|
|
272
|
+
const plugins = await this.listPlugins()
|
|
273
|
+
const lowerKeyword = keyword.toLowerCase()
|
|
274
|
+
|
|
275
|
+
return plugins.filter(plugin =>
|
|
276
|
+
plugin.name.toLowerCase().includes(lowerKeyword)
|
|
277
|
+
|| plugin.slug.toLowerCase().includes(lowerKeyword)
|
|
278
|
+
|| plugin.summary.toLowerCase().includes(lowerKeyword),
|
|
279
|
+
)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export const tpexProvider = new TpexProvider()
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata extracted from a .tpex package file
|
|
3
|
+
*/
|
|
4
|
+
export interface TpexMetadata {
|
|
5
|
+
readmeMarkdown?: string | null
|
|
6
|
+
manifest?: Record<string, unknown> | null
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Result of package preview operation
|
|
11
|
+
*/
|
|
12
|
+
export interface TpexPackagePreviewResult {
|
|
13
|
+
manifest: Record<string, unknown> | null
|
|
14
|
+
readmeMarkdown: string | null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Extracted manifest fields from tpex package
|
|
19
|
+
*/
|
|
20
|
+
export interface TpexExtractedManifest {
|
|
21
|
+
id?: string
|
|
22
|
+
name?: string
|
|
23
|
+
description?: string
|
|
24
|
+
version?: string
|
|
25
|
+
homepage?: string
|
|
26
|
+
changelog?: string
|
|
27
|
+
channel?: string
|
|
28
|
+
category?: string
|
|
29
|
+
icon?: {
|
|
30
|
+
type?: string
|
|
31
|
+
value?: string
|
|
32
|
+
}
|
|
33
|
+
[key: string]: unknown
|
|
34
|
+
}
|
|
@@ -3,11 +3,23 @@ import { ensureRendererChannel } from '../channel'
|
|
|
3
3
|
|
|
4
4
|
export type BridgeEvent = BridgeEventForCoreBox
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
export interface BridgeEventMeta {
|
|
7
|
+
timestamp: number
|
|
8
|
+
fromCache: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface BridgeEventPayload<T = any> {
|
|
12
|
+
data: T
|
|
13
|
+
meta: BridgeEventMeta
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** @template T The type of data the hook will receive. */
|
|
17
|
+
export type BridgeHook<T = any> = (payload: BridgeEventPayload<T>) => void
|
|
18
|
+
|
|
19
|
+
interface CachedEvent<T = any> {
|
|
20
|
+
data: T
|
|
21
|
+
timestamp: number
|
|
22
|
+
}
|
|
11
23
|
|
|
12
24
|
const __hooks: Record<BridgeEvent, Array<BridgeHook>> = {
|
|
13
25
|
[BridgeEventForCoreBox.CORE_BOX_INPUT_CHANGE]: [],
|
|
@@ -15,42 +27,85 @@ const __hooks: Record<BridgeEvent, Array<BridgeHook>> = {
|
|
|
15
27
|
[BridgeEventForCoreBox.CORE_BOX_KEY_EVENT]: [],
|
|
16
28
|
}
|
|
17
29
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* @param type The bridge event type.
|
|
21
|
-
* @param hook The hook function to inject.
|
|
22
|
-
* @returns The wrapped hook function.
|
|
23
|
-
* @internal
|
|
24
|
-
* @template T The type of data the hook will receive.
|
|
25
|
-
*/
|
|
26
|
-
export function injectBridgeEvent<T>(type: BridgeEvent, hook: BridgeHook<T>) {
|
|
27
|
-
const hooks: Array<BridgeHook<T>> = __hooks[type] || (__hooks[type] = [])
|
|
30
|
+
const __eventCache: Map<BridgeEvent, CachedEvent[]> = new Map()
|
|
31
|
+
const __channelRegistered = new Set<BridgeEvent>()
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
const CACHE_MAX_SIZE: Record<BridgeEvent, number> = {
|
|
34
|
+
[BridgeEventForCoreBox.CORE_BOX_INPUT_CHANGE]: 1,
|
|
35
|
+
[BridgeEventForCoreBox.CORE_BOX_CLIPBOARD_CHANGE]: 1,
|
|
36
|
+
[BridgeEventForCoreBox.CORE_BOX_KEY_EVENT]: 10,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function invokeHook<T>(hook: BridgeHook<T>, data: T, fromCache: boolean, timestamp: number): void {
|
|
40
|
+
try {
|
|
41
|
+
hook({ data, meta: { timestamp, fromCache } })
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
console.error('[TouchSDK] Bridge hook error:', e)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function registerEarlyListener(type: BridgeEvent): void {
|
|
49
|
+
if (__channelRegistered.has(type)) return
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const channel = ensureRendererChannel()
|
|
32
53
|
channel.regChannel(type, ({ data }) => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
|
|
54
|
+
const timestamp = Date.now()
|
|
55
|
+
const hooks = __hooks[type]
|
|
56
|
+
|
|
57
|
+
if (hooks && hooks.length > 0) {
|
|
58
|
+
hooks.forEach(h => invokeHook(h, data, false, timestamp))
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
if (!__eventCache.has(type)) __eventCache.set(type, [])
|
|
62
|
+
const cache = __eventCache.get(type)!
|
|
63
|
+
const maxSize = CACHE_MAX_SIZE[type] ?? 1
|
|
64
|
+
cache.push({ data, timestamp })
|
|
65
|
+
while (cache.length > maxSize) cache.shift()
|
|
66
|
+
console.debug(`[TouchSDK] ${type} cached, size: ${cache.length}`)
|
|
38
67
|
}
|
|
39
68
|
})
|
|
69
|
+
__channelRegistered.add(type)
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Channel not ready yet
|
|
40
73
|
}
|
|
74
|
+
}
|
|
41
75
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
catch (e) {
|
|
47
|
-
console.error(`[TouchSDK] ${type} hook error: `, e)
|
|
48
|
-
}
|
|
76
|
+
/** Clears the event cache for a specific event type or all types. */
|
|
77
|
+
export function clearBridgeEventCache(type?: BridgeEvent): void {
|
|
78
|
+
if (type) {
|
|
79
|
+
__eventCache.delete(type)
|
|
49
80
|
}
|
|
81
|
+
else {
|
|
82
|
+
__eventCache.clear()
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Auto-init on module load
|
|
87
|
+
;(function initBridgeEventCache() {
|
|
88
|
+
setTimeout(() => {
|
|
89
|
+
Object.values(BridgeEventForCoreBox).forEach(e => registerEarlyListener(e as BridgeEvent))
|
|
90
|
+
}, 0)
|
|
91
|
+
})()
|
|
50
92
|
|
|
51
|
-
|
|
93
|
+
/** @internal Injects a hook for a given bridge event with cache replay. */
|
|
94
|
+
export function injectBridgeEvent<T>(type: BridgeEvent, hook: BridgeHook<T>) {
|
|
95
|
+
const hooks: Array<BridgeHook<T>> = __hooks[type] || (__hooks[type] = [])
|
|
96
|
+
|
|
97
|
+
// Ensure channel listener is registered
|
|
98
|
+
registerEarlyListener(type)
|
|
52
99
|
|
|
53
|
-
|
|
100
|
+
// Replay cached events to this new hook
|
|
101
|
+
const cached = __eventCache.get(type)
|
|
102
|
+
if (cached && cached.length > 0) {
|
|
103
|
+
cached.forEach(({ data, timestamp }) => invokeHook(hook, data as T, true, timestamp))
|
|
104
|
+
__eventCache.delete(type)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
hooks.push(hook)
|
|
108
|
+
return hook
|
|
54
109
|
}
|
|
55
110
|
|
|
56
111
|
/**
|
|
@@ -61,22 +116,11 @@ export function injectBridgeEvent<T>(type: BridgeEvent, hook: BridgeHook<T>) {
|
|
|
61
116
|
*/
|
|
62
117
|
export const createBridgeHook = <T>(type: BridgeEvent) => (hook: BridgeHook<T>) => injectBridgeEvent<T>(type, hook)
|
|
63
118
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
* @param data The input change data (string).
|
|
68
|
-
*/
|
|
69
|
-
export const onCoreBoxInputChange = createBridgeHook<{ query: { inputs: Array<any>, text: string } }>(BridgeEventForCoreBox.CORE_BOX_INPUT_CHANGE)
|
|
70
|
-
|
|
71
|
-
export const onCoreBoxClipboardChange = createBridgeHook<{ item: any }>(BridgeEventForCoreBox.CORE_BOX_CLIPBOARD_CHANGE)
|
|
119
|
+
export interface CoreBoxInputData {
|
|
120
|
+
query: { inputs: Array<any>, text: string }
|
|
121
|
+
}
|
|
72
122
|
|
|
73
|
-
|
|
74
|
-
* Hook for when a keyboard event is forwarded from CoreBox.
|
|
75
|
-
* This is triggered when the plugin's UI view is attached and the user
|
|
76
|
-
* presses certain keys (Enter, Arrow keys, Meta+key combinations).
|
|
77
|
-
* @param data The forwarded keyboard event data.
|
|
78
|
-
*/
|
|
79
|
-
export const onCoreBoxKeyEvent = createBridgeHook<{
|
|
123
|
+
export interface CoreBoxKeyEventData {
|
|
80
124
|
key: string
|
|
81
125
|
code: string
|
|
82
126
|
metaKey: boolean
|
|
@@ -84,4 +128,17 @@ export const onCoreBoxKeyEvent = createBridgeHook<{
|
|
|
84
128
|
altKey: boolean
|
|
85
129
|
shiftKey: boolean
|
|
86
130
|
repeat: boolean
|
|
87
|
-
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface CoreBoxClipboardData {
|
|
134
|
+
item: any
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Hook for CoreBox input changes. Payload includes `data` and `meta` (timestamp, fromCache). */
|
|
138
|
+
export const onCoreBoxInputChange = createBridgeHook<CoreBoxInputData>(BridgeEventForCoreBox.CORE_BOX_INPUT_CHANGE)
|
|
139
|
+
|
|
140
|
+
/** Hook for CoreBox clipboard changes. Payload includes `data` and `meta` (timestamp, fromCache). */
|
|
141
|
+
export const onCoreBoxClipboardChange = createBridgeHook<CoreBoxClipboardData>(BridgeEventForCoreBox.CORE_BOX_CLIPBOARD_CHANGE)
|
|
142
|
+
|
|
143
|
+
/** Hook for keyboard events forwarded from CoreBox. Payload includes `data` and `meta` (timestamp, fromCache). */
|
|
144
|
+
export const onCoreBoxKeyEvent = createBridgeHook<CoreBoxKeyEventData>(BridgeEventForCoreBox.CORE_BOX_KEY_EVENT)
|
|
@@ -5,24 +5,9 @@
|
|
|
5
5
|
* and storage statistics.
|
|
6
6
|
*/
|
|
7
7
|
import type { ITouchClientChannel } from '@talex-touch/utils/channel'
|
|
8
|
+
import type { StorageStats } from '../../types/storage'
|
|
8
9
|
import { ensureRendererChannel } from './channel'
|
|
9
10
|
|
|
10
|
-
/**
|
|
11
|
-
* Storage statistics interface
|
|
12
|
-
*/
|
|
13
|
-
export interface StorageStats {
|
|
14
|
-
/** Total size in bytes */
|
|
15
|
-
totalSize: number
|
|
16
|
-
/** Number of files */
|
|
17
|
-
fileCount: number
|
|
18
|
-
/** Number of directories */
|
|
19
|
-
dirCount: number
|
|
20
|
-
/** Maximum allowed size in bytes */
|
|
21
|
-
maxSize: number
|
|
22
|
-
/** Usage percentage (0-100) */
|
|
23
|
-
usagePercent: number
|
|
24
|
-
}
|
|
25
|
-
|
|
26
11
|
/**
|
|
27
12
|
* Performance metrics interface
|
|
28
13
|
*/
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
export interface IArgMapperOptions {
|
|
6
6
|
/** The type of touch window - either main window or core-box popup */
|
|
7
7
|
touchType?: 'main' | 'core-box'
|
|
8
|
+
/** The sub-type for core-box windows (e.g., division-box) */
|
|
9
|
+
coreType?: 'division-box'
|
|
8
10
|
/** User data directory path */
|
|
9
11
|
userDataDir?: string
|
|
10
12
|
/** Application path */
|
|
@@ -32,22 +34,17 @@ declare global {
|
|
|
32
34
|
* @returns Mapped command line arguments as key-value pairs
|
|
33
35
|
*/
|
|
34
36
|
export function useArgMapper(args: string[] = process.argv): IArgMapperOptions {
|
|
35
|
-
if (window.$argMapper)
|
|
36
|
-
return window.$argMapper
|
|
37
|
-
}
|
|
37
|
+
if (window.$argMapper) return window.$argMapper
|
|
38
38
|
|
|
39
39
|
const mapper: IArgMapperOptions = {}
|
|
40
|
-
|
|
41
40
|
for (const arg of args) {
|
|
42
41
|
if (arg.startsWith('--') && arg.includes('=')) {
|
|
43
42
|
const [key, ...valueParts] = arg.slice(2).split('=')
|
|
44
43
|
const value = valueParts.join('=')
|
|
45
|
-
|
|
46
44
|
const camelCaseKey = key.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())
|
|
47
45
|
mapper[camelCaseKey] = value
|
|
48
46
|
}
|
|
49
47
|
}
|
|
50
|
-
|
|
51
48
|
return window.$argMapper = mapper
|
|
52
49
|
}
|
|
53
50
|
|
|
@@ -76,3 +73,20 @@ export function isMainWindow() {
|
|
|
76
73
|
export function isCoreBox() {
|
|
77
74
|
return useTouchType() === 'core-box'
|
|
78
75
|
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Gets the core-box sub-type from command line arguments
|
|
79
|
+
* @returns The core type ('division-box') or undefined
|
|
80
|
+
*/
|
|
81
|
+
export function useCoreType() {
|
|
82
|
+
const argMapper = useArgMapper()
|
|
83
|
+
return argMapper.coreType
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Checks if the current window is a division-box window
|
|
88
|
+
* @returns True if the current window is a division-box
|
|
89
|
+
*/
|
|
90
|
+
export function isDivisionBox() {
|
|
91
|
+
return isCoreBox() && useCoreType() === 'division-box'
|
|
92
|
+
}
|