@talex-touch/utils 1.0.29 → 1.0.31
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/animation/window-node.ts +202 -0
- package/auth/clerk-types.ts +56 -0
- package/auth/index.ts +4 -0
- package/auth/useAuth.ts +0 -0
- package/auth/useAuthState.ts +44 -0
- package/auth/useClerkConfig.ts +40 -0
- package/auth/useClerkProvider.ts +51 -0
- package/channel/index.ts +6 -0
- package/common/file-scan-utils.ts +4 -1
- package/common/storage/entity/app-settings.ts +15 -1
- package/common/utils/file.ts +4 -1
- package/common/utils/polling.ts +51 -3
- package/core-box/README.md +1 -1
- package/core-box/builder/tuff-builder.ts +1 -1
- package/core-box/tuff/tuff-dsl.ts +112 -48
- package/index.ts +4 -0
- package/package.json +1 -1
- package/plugin/index.ts +35 -12
- package/plugin/preload.ts +5 -2
- package/plugin/sdk/features.ts +1 -1
- package/plugin/sdk/index.ts +2 -7
- package/plugin/sdk/storage.ts +64 -0
- package/plugin/sdk/types.ts +96 -1
- package/preload/renderer.ts +1 -1
- package/renderer/hooks/index.ts +1 -0
- package/renderer/hooks/initialize.ts +5 -0
- package/renderer/hooks/performance.ts +87 -0
- package/renderer/index.ts +1 -0
- package/renderer/storage/app-settings.ts +0 -2
- package/renderer/storage/base-storage.ts +52 -13
- package/search/types.ts +7 -7
- package/types/download.ts +162 -0
- package/types/icon.ts +45 -0
- package/types/index.ts +1 -0
- package/types/modules/module-lifecycle.ts +1 -1
- package/types/storage.ts +56 -0
- package/types/update.ts +99 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { TalexTouch } from '../types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Window animation controller return type
|
|
5
|
+
*/
|
|
6
|
+
export interface WindowAnimationController {
|
|
7
|
+
/**
|
|
8
|
+
* Update window height with animation
|
|
9
|
+
* @param newHeight - The new height to animate to
|
|
10
|
+
* @param duration - Animation duration in seconds (default: 0.5)
|
|
11
|
+
* @returns Promise that resolves to true if animation completed successfully, false if interrupted
|
|
12
|
+
*/
|
|
13
|
+
updateHeight: (newHeight: number, duration?: number) => Promise<boolean>
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Cancel current animation
|
|
17
|
+
* @returns Promise that resolves to true if there was an animation to cancel, false otherwise
|
|
18
|
+
*/
|
|
19
|
+
cancel: () => Promise<boolean>
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Toggle window visibility
|
|
23
|
+
* @param visible - Optional parameter to explicitly set visibility state
|
|
24
|
+
* @returns Promise that resolves to true if operation completed successfully, false otherwise
|
|
25
|
+
*/
|
|
26
|
+
toggleWindow: (visible?: boolean) => Promise<boolean>
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Change current window instance
|
|
30
|
+
* @param newWindow - New TouchWindow instance
|
|
31
|
+
*/
|
|
32
|
+
changeWindow: (newWindow: TalexTouch.ITouchWindow) => void
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Tracks the state of an animation
|
|
37
|
+
*/
|
|
38
|
+
interface AnimationState {
|
|
39
|
+
intervalId: NodeJS.Timeout | null
|
|
40
|
+
completed: boolean
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Simple easing function for smooth animation
|
|
45
|
+
*/
|
|
46
|
+
function easeInOutCubic(t: number): number {
|
|
47
|
+
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Use native Node.js animation for window (main process only)
|
|
52
|
+
* @param window - TouchWindow instance (optional, can be set later with changeWindow)
|
|
53
|
+
* @returns WindowAnimationController with updateHeight, cancel, toggleWindow, and changeWindow methods
|
|
54
|
+
*/
|
|
55
|
+
export function useWindowAnimation(window?: TalexTouch.ITouchWindow): WindowAnimationController {
|
|
56
|
+
// Store current window reference inside the function scope
|
|
57
|
+
let currentWindow: TalexTouch.ITouchWindow | null = window || null
|
|
58
|
+
|
|
59
|
+
const animationState: AnimationState = {
|
|
60
|
+
intervalId: null,
|
|
61
|
+
completed: false
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Check if current window is valid
|
|
66
|
+
* @returns true if window is valid, false otherwise
|
|
67
|
+
*/
|
|
68
|
+
const isWindowValid = (): boolean => {
|
|
69
|
+
return (
|
|
70
|
+
currentWindow !== null &&
|
|
71
|
+
currentWindow.window !== null &&
|
|
72
|
+
!currentWindow.window.isDestroyed()
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get current window with validation
|
|
78
|
+
* @returns current window or throws error if invalid
|
|
79
|
+
*/
|
|
80
|
+
const getCurrentWindow = (): TalexTouch.ITouchWindow => {
|
|
81
|
+
if (!isWindowValid()) {
|
|
82
|
+
throw new Error('Window is not valid or has been destroyed')
|
|
83
|
+
}
|
|
84
|
+
return currentWindow!
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const updateHeight = async (newHeight: number, duration: number = 0.5): Promise<boolean> => {
|
|
88
|
+
try {
|
|
89
|
+
const window = getCurrentWindow()
|
|
90
|
+
|
|
91
|
+
// Cancel any existing animation
|
|
92
|
+
if (animationState.intervalId) {
|
|
93
|
+
clearInterval(animationState.intervalId)
|
|
94
|
+
animationState.intervalId = null
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Reset state for new animation
|
|
98
|
+
animationState.completed = false
|
|
99
|
+
|
|
100
|
+
const browserWindow = window.window
|
|
101
|
+
const [currentWidth, currentHeight] = browserWindow.getSize()
|
|
102
|
+
const [x, y] = browserWindow.getPosition()
|
|
103
|
+
|
|
104
|
+
const startHeight = currentHeight
|
|
105
|
+
const heightDelta = newHeight - startHeight
|
|
106
|
+
const startTime = Date.now()
|
|
107
|
+
const durationMs = duration * 1000
|
|
108
|
+
|
|
109
|
+
return new Promise<boolean>((resolve) => {
|
|
110
|
+
animationState.intervalId = setInterval(() => {
|
|
111
|
+
// Check if window is still valid
|
|
112
|
+
if (!isWindowValid()) {
|
|
113
|
+
if (animationState.intervalId) {
|
|
114
|
+
clearInterval(animationState.intervalId)
|
|
115
|
+
animationState.intervalId = null
|
|
116
|
+
}
|
|
117
|
+
resolve(false)
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const elapsed = Date.now() - startTime
|
|
122
|
+
const progress = Math.min(elapsed / durationMs, 1)
|
|
123
|
+
const easedProgress = easeInOutCubic(progress)
|
|
124
|
+
const animatedHeight = Math.round(startHeight + heightDelta * easedProgress)
|
|
125
|
+
|
|
126
|
+
browserWindow.setSize(currentWidth, animatedHeight)
|
|
127
|
+
browserWindow.setPosition(x, y)
|
|
128
|
+
|
|
129
|
+
if (progress >= 1) {
|
|
130
|
+
if (animationState.intervalId) {
|
|
131
|
+
clearInterval(animationState.intervalId)
|
|
132
|
+
animationState.intervalId = null
|
|
133
|
+
}
|
|
134
|
+
animationState.completed = true
|
|
135
|
+
resolve(true)
|
|
136
|
+
}
|
|
137
|
+
}, 16) // ~60fps
|
|
138
|
+
})
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error('Error in updateHeight:', error)
|
|
141
|
+
return Promise.resolve(false)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const cancel = async (): Promise<boolean> => {
|
|
146
|
+
if (animationState.intervalId) {
|
|
147
|
+
clearInterval(animationState.intervalId)
|
|
148
|
+
animationState.intervalId = null
|
|
149
|
+
return Promise.resolve(true)
|
|
150
|
+
}
|
|
151
|
+
return Promise.resolve(false)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const toggleWindow = async (visible?: boolean): Promise<boolean> => {
|
|
155
|
+
try {
|
|
156
|
+
const window = getCurrentWindow()
|
|
157
|
+
const browserWindow = window.window
|
|
158
|
+
|
|
159
|
+
// Determine target visibility state
|
|
160
|
+
const targetVisible = visible !== undefined ? visible : !browserWindow.isVisible()
|
|
161
|
+
|
|
162
|
+
if (targetVisible) {
|
|
163
|
+
// Show window
|
|
164
|
+
browserWindow.show()
|
|
165
|
+
} else {
|
|
166
|
+
// Hide window
|
|
167
|
+
if (process.platform === 'darwin') {
|
|
168
|
+
// On macOS, we can simply hide the window
|
|
169
|
+
browserWindow.hide()
|
|
170
|
+
} else {
|
|
171
|
+
// On other platforms, move window far off-screen before hiding
|
|
172
|
+
browserWindow.setPosition(-100000, -100000)
|
|
173
|
+
browserWindow.hide()
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return Promise.resolve(true)
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.error('Error in toggleWindow:', error)
|
|
180
|
+
return Promise.resolve(false)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const changeWindow = (newWindow: TalexTouch.ITouchWindow): void => {
|
|
185
|
+
// Cancel any ongoing animation
|
|
186
|
+
if (animationState.intervalId) {
|
|
187
|
+
clearInterval(animationState.intervalId)
|
|
188
|
+
animationState.intervalId = null
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Set new window
|
|
192
|
+
currentWindow = newWindow
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
updateHeight,
|
|
197
|
+
cancel,
|
|
198
|
+
toggleWindow,
|
|
199
|
+
changeWindow
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface ClerkUser {
|
|
2
|
+
id: string
|
|
3
|
+
emailAddresses: Array<{
|
|
4
|
+
emailAddress: string
|
|
5
|
+
id: string
|
|
6
|
+
}>
|
|
7
|
+
firstName?: string
|
|
8
|
+
lastName?: string
|
|
9
|
+
username?: string
|
|
10
|
+
imageUrl?: string
|
|
11
|
+
createdAt: string
|
|
12
|
+
updatedAt: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ClerkAuthState {
|
|
16
|
+
isLoaded: boolean
|
|
17
|
+
isSignedIn: boolean
|
|
18
|
+
user: ClerkUser | null
|
|
19
|
+
sessionId: string | null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface LoginOptions {
|
|
23
|
+
onSuccess?: (user: any) => void
|
|
24
|
+
onError?: (error: any) => void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface LoginResult {
|
|
28
|
+
success: boolean
|
|
29
|
+
user?: any
|
|
30
|
+
error?: any
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface CurrentUser {
|
|
34
|
+
id: string
|
|
35
|
+
name: string
|
|
36
|
+
email: string
|
|
37
|
+
avatar?: string
|
|
38
|
+
provider: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Clerk Auth Config
|
|
43
|
+
*/
|
|
44
|
+
export interface ClerkConfig {
|
|
45
|
+
publishableKey: string
|
|
46
|
+
domain?: string
|
|
47
|
+
signInUrl?: string
|
|
48
|
+
signUpUrl?: string
|
|
49
|
+
afterSignInUrl?: string
|
|
50
|
+
afterSignUpUrl?: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type ClerkResourceSnapshot = {
|
|
54
|
+
user?: any | null
|
|
55
|
+
session?: { id?: string | null } | null
|
|
56
|
+
}
|
package/auth/index.ts
ADDED
package/auth/useAuth.ts
ADDED
|
File without changes
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createGlobalState } from '@vueuse/core'
|
|
2
|
+
import { shallowReactive, computed } from 'vue'
|
|
3
|
+
import { ClerkAuthState, CurrentUser } from './clerk-types'
|
|
4
|
+
|
|
5
|
+
export const useAuthState = createGlobalState(() => {
|
|
6
|
+
const authState = shallowReactive<ClerkAuthState>({
|
|
7
|
+
isLoaded: false,
|
|
8
|
+
isSignedIn: false,
|
|
9
|
+
user: null,
|
|
10
|
+
sessionId: null
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
return { authState }
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export function useCurrentUser() {
|
|
17
|
+
const { authState } = useAuthState()
|
|
18
|
+
|
|
19
|
+
const currentUser = computed((): CurrentUser | null => {
|
|
20
|
+
if (!authState.isSignedIn || !authState.user) {
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { firstName, lastName, username, imageUrl } = authState.user
|
|
25
|
+
let name = ''
|
|
26
|
+
if (firstName || lastName) {
|
|
27
|
+
name = [firstName, lastName].filter(Boolean).join(' ')
|
|
28
|
+
} else {
|
|
29
|
+
name = username || ''
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const email = authState.user.emailAddresses?.[0]?.emailAddress || ''
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
id: authState.user.id,
|
|
36
|
+
name,
|
|
37
|
+
email,
|
|
38
|
+
avatar: imageUrl,
|
|
39
|
+
provider: 'clerk'
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
return { currentUser, authState }
|
|
44
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ClerkConfig } from "./clerk-types"
|
|
2
|
+
|
|
3
|
+
const clerkPublishableKey = (import.meta.env as any).VITE_CLERK_PUBLISHABLE_KEY
|
|
4
|
+
const clerkDomain = (import.meta.env as any).VITE_CLERK_DOMAIN
|
|
5
|
+
|
|
6
|
+
if (!clerkPublishableKey?.length) {
|
|
7
|
+
throw new Error('VITE_CLERK_PUBLISHABLE_KEY is not set')
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const CLERK_PUBLISHABLE_KEY_KEY = 'clerk-publishable-key'
|
|
11
|
+
|
|
12
|
+
export const defaultClerkConfig: ClerkConfig = {
|
|
13
|
+
publishableKey: clerkPublishableKey,
|
|
14
|
+
domain: clerkDomain,
|
|
15
|
+
signInUrl: '/sign-in',
|
|
16
|
+
signUpUrl: '/sign-up',
|
|
17
|
+
afterSignInUrl: '/home',
|
|
18
|
+
afterSignUpUrl: '/home'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function useClerkConfig() {
|
|
22
|
+
const getClerkConfig = (): ClerkConfig => {
|
|
23
|
+
return {
|
|
24
|
+
...defaultClerkConfig,
|
|
25
|
+
publishableKey:
|
|
26
|
+
localStorage.getItem(CLERK_PUBLISHABLE_KEY_KEY) || defaultClerkConfig.publishableKey
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const setClerkConfig = (config: Partial<ClerkConfig>): void => {
|
|
31
|
+
if (config.publishableKey) {
|
|
32
|
+
localStorage.setItem(CLERK_PUBLISHABLE_KEY_KEY, config.publishableKey)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
getClerkConfig,
|
|
38
|
+
setClerkConfig
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Clerk } from '@clerk/clerk-js'
|
|
2
|
+
import { useClerkConfig } from './useClerkConfig'
|
|
3
|
+
|
|
4
|
+
let clerkInstance: Clerk | null = null
|
|
5
|
+
|
|
6
|
+
export function useClerkProvider() {
|
|
7
|
+
const initializeClerk = async (): Promise<Clerk> => {
|
|
8
|
+
if (clerkInstance) {
|
|
9
|
+
return clerkInstance
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { getClerkConfig } = useClerkConfig()
|
|
13
|
+
const config = getClerkConfig()
|
|
14
|
+
|
|
15
|
+
if (!config.publishableKey) {
|
|
16
|
+
throw new Error('Clerk publishable key is required')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
clerkInstance = new Clerk(config.publishableKey)
|
|
21
|
+
await clerkInstance.load()
|
|
22
|
+
|
|
23
|
+
console.log('Clerk initialized successfully')
|
|
24
|
+
return clerkInstance
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error('Failed to initialize Clerk:', error)
|
|
27
|
+
throw error
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const getClerk = (): Clerk | null => {
|
|
32
|
+
return clerkInstance
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const isClerkInitialized = (): boolean => {
|
|
36
|
+
return clerkInstance !== null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const cleanupClerk = (): void => {
|
|
40
|
+
if (clerkInstance) {
|
|
41
|
+
clerkInstance = null
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
initializeClerk,
|
|
47
|
+
getClerk,
|
|
48
|
+
isClerkInitialized,
|
|
49
|
+
cleanupClerk
|
|
50
|
+
}
|
|
51
|
+
}
|
package/channel/index.ts
CHANGED
|
@@ -175,3 +175,9 @@ export interface StandardChannelData extends RawStandardChannelData {
|
|
|
175
175
|
export type IChannelData = any //boolean | number | string | null | undefined | {
|
|
176
176
|
// [prop: string]: any
|
|
177
177
|
// }
|
|
178
|
+
|
|
179
|
+
// Default export for Vite compatibility (only values, not types)
|
|
180
|
+
export default {
|
|
181
|
+
ChannelType,
|
|
182
|
+
DataCode
|
|
183
|
+
}
|
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
* @version 1.0.0
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
11
|
+
const path = typeof window === 'undefined'
|
|
12
|
+
? require('path')
|
|
13
|
+
: require('path-browserify')
|
|
11
14
|
import {
|
|
12
15
|
type FileScanOptions,
|
|
13
16
|
DEFAULT_SCAN_OPTIONS,
|
|
@@ -14,7 +14,7 @@ const _appSettingOriginData = {
|
|
|
14
14
|
},
|
|
15
15
|
lang: {
|
|
16
16
|
followSystem: true,
|
|
17
|
-
locale:
|
|
17
|
+
locale: 'zh-CN',
|
|
18
18
|
},
|
|
19
19
|
keyBind: {
|
|
20
20
|
summon: 'CTRL + E',
|
|
@@ -39,6 +39,20 @@ const _appSettingOriginData = {
|
|
|
39
39
|
searchEngine: {
|
|
40
40
|
logsEnabled: false,
|
|
41
41
|
},
|
|
42
|
+
window: {
|
|
43
|
+
closeToTray: true,
|
|
44
|
+
startMinimized: false,
|
|
45
|
+
startSilent: false,
|
|
46
|
+
},
|
|
47
|
+
setup: {
|
|
48
|
+
accessibility: false,
|
|
49
|
+
notifications: false,
|
|
50
|
+
autoStart: false,
|
|
51
|
+
showTray: true,
|
|
52
|
+
adminPrivileges: false,
|
|
53
|
+
hideDock: false,
|
|
54
|
+
},
|
|
55
|
+
layout: 'simple',
|
|
42
56
|
};
|
|
43
57
|
|
|
44
58
|
export const appSettingOriginData = Object.freeze(_appSettingOriginData)
|
package/common/utils/file.ts
CHANGED
package/common/utils/polling.ts
CHANGED
|
@@ -19,6 +19,7 @@ export class PollingService {
|
|
|
19
19
|
private tasks = new Map<string, PollingTask>();
|
|
20
20
|
private timerId: NodeJS.Timeout | null = null;
|
|
21
21
|
private isRunning = false;
|
|
22
|
+
private quitListenerCleanup?: () => void;
|
|
22
23
|
|
|
23
24
|
private constructor() {
|
|
24
25
|
// Private constructor to enforce singleton pattern
|
|
@@ -111,17 +112,53 @@ export class PollingService {
|
|
|
111
112
|
*/
|
|
112
113
|
public start(): void {
|
|
113
114
|
if (this.isRunning) {
|
|
115
|
+
console.warn('[PollingService] Already running, skipping start.');
|
|
114
116
|
return;
|
|
115
117
|
}
|
|
116
118
|
this.isRunning = true;
|
|
117
|
-
console.
|
|
119
|
+
console.log('[PollingService] Polling service started');
|
|
120
|
+
this._setupQuitListener();
|
|
118
121
|
this._reschedule();
|
|
119
122
|
}
|
|
120
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Sets up Electron app quit listener if running in Electron environment
|
|
126
|
+
* Uses lazy resolution to avoid hard dependency on Electron
|
|
127
|
+
*/
|
|
128
|
+
private _setupQuitListener(): void {
|
|
129
|
+
// Check if we're in Electron environment
|
|
130
|
+
try {
|
|
131
|
+
// Use dynamic require to avoid hard dependency on Electron
|
|
132
|
+
// Similar to the approach used in packages/utils/plugin/channel.ts
|
|
133
|
+
const electron = (globalThis as any)?.electron ??
|
|
134
|
+
(typeof require !== 'undefined' ? require('electron') : null);
|
|
135
|
+
|
|
136
|
+
if (electron?.app) {
|
|
137
|
+
const app = electron.app;
|
|
138
|
+
|
|
139
|
+
// Listen to before-quit event
|
|
140
|
+
const quitHandler = () => {
|
|
141
|
+
this.stop('app quit');
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
app.on('before-quit', quitHandler);
|
|
145
|
+
|
|
146
|
+
// Store cleanup function
|
|
147
|
+
this.quitListenerCleanup = () => {
|
|
148
|
+
app.removeListener('before-quit', quitHandler);
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
} catch (error) {
|
|
152
|
+
// Not in Electron environment or Electron not available
|
|
153
|
+
// This is fine, just skip the quit listener setup
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
121
157
|
/**
|
|
122
158
|
* Stops the polling service and clears all scheduled tasks.
|
|
159
|
+
* @param reason - Optional reason for stopping the service (for logging purposes)
|
|
123
160
|
*/
|
|
124
|
-
public stop(): void {
|
|
161
|
+
public stop(reason?: string): void {
|
|
125
162
|
if (!this.isRunning) {
|
|
126
163
|
return;
|
|
127
164
|
}
|
|
@@ -130,7 +167,18 @@ export class PollingService {
|
|
|
130
167
|
clearTimeout(this.timerId);
|
|
131
168
|
this.timerId = null;
|
|
132
169
|
}
|
|
133
|
-
|
|
170
|
+
|
|
171
|
+
// Clean up quit listener
|
|
172
|
+
if (this.quitListenerCleanup) {
|
|
173
|
+
this.quitListenerCleanup();
|
|
174
|
+
this.quitListenerCleanup = undefined;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (reason) {
|
|
178
|
+
console.log(`[PollingService] Stopping polling service: ${reason}`);
|
|
179
|
+
} else {
|
|
180
|
+
console.log('[PollingService] Polling service stopped');
|
|
181
|
+
}
|
|
134
182
|
}
|
|
135
183
|
|
|
136
184
|
private _reschedule(): void {
|
package/core-box/README.md
CHANGED
|
@@ -140,7 +140,7 @@ The core interface for all search results:
|
|
|
140
140
|
interface ISearchItem {
|
|
141
141
|
name: string; // Display name
|
|
142
142
|
desc: string; // Description
|
|
143
|
-
icon:
|
|
143
|
+
icon: ITuffIcon; // Icon configuration
|
|
144
144
|
push: boolean; // Push mode support
|
|
145
145
|
names: string[]; // Searchable names
|
|
146
146
|
keyWords: string[]; // Search keywords
|
|
@@ -972,7 +972,7 @@ class TuffUtils {
|
|
|
972
972
|
* @param type - 图标类型
|
|
973
973
|
* @returns {TuffIcon} 创建的图标对象
|
|
974
974
|
*/
|
|
975
|
-
static createIcon(value: string, type: 'emoji' | 'url' | '
|
|
975
|
+
static createIcon(value: string, type: 'emoji' | 'url' | 'file' = 'emoji'): TuffIcon {
|
|
976
976
|
return {
|
|
977
977
|
type,
|
|
978
978
|
value
|