plusui-native-core 0.1.7 → 0.1.9
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/CMakeLists.txt +2 -1
- package/Core/Features/Bindings/ARCHITECTURE.md +328 -0
- package/Core/Features/Bindings/CustomBindings/custom_bindings.cpp +53 -0
- package/Core/Features/Bindings/CustomBindings/custom_bindings.ts +35 -0
- package/Core/Features/Bindings/EXAMPLE_USAGE.hpp +143 -0
- package/Core/Features/Bindings/EXAMPLE_USAGE.tsx +210 -0
- package/Core/Features/Bindings/IPC_GUIDE.md +325 -0
- package/Core/Features/Bindings/NativeBindings/native_bindings.cpp +30 -0
- package/Core/Features/Bindings/NativeBindings/native_bindings.ts +29 -0
- package/Core/Features/Bindings/UNIFIED_SYSTEM.md +351 -0
- package/Core/Features/FileDrop/filedrop.cpp +316 -0
- package/Core/Features/FileDrop/filedrop.css +421 -0
- package/Core/Features/FileDrop/filedrop.ts +269 -0
- package/Core/include/plusui/bindings.hpp +65 -0
- package/Core/include/plusui/custom_bindings.hpp +17 -0
- package/Core/include/plusui/filedrop.hpp +77 -0
- package/Core/include/plusui/native_bindings.hpp +19 -0
- package/package.json +1 -1
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileDrop Styles - Drop zone styling for drag & drop functionality
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* <div class="filedrop-zone">
|
|
6
|
+
* Drop files here
|
|
7
|
+
* </div>
|
|
8
|
+
*
|
|
9
|
+
* States automatically applied via JavaScript:
|
|
10
|
+
* - .filedrop-active: When drag enters the zone
|
|
11
|
+
* - .filedrop-disabled: When file drop is disabled
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/* ============================================================================
|
|
15
|
+
* BASE DROP ZONE STYLES
|
|
16
|
+
* ============================================================================ */
|
|
17
|
+
|
|
18
|
+
.filedrop-zone {
|
|
19
|
+
position: relative;
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
align-items: center;
|
|
23
|
+
justify-content: center;
|
|
24
|
+
min-height: 200px;
|
|
25
|
+
padding: 2rem;
|
|
26
|
+
border: 2px dashed #cbd5e1;
|
|
27
|
+
border-radius: 0.75rem;
|
|
28
|
+
background-color: #f8fafc;
|
|
29
|
+
transition: all 0.2s ease-in-out;
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
user-select: none;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.filedrop-zone:hover {
|
|
35
|
+
border-color: #94a3b8;
|
|
36
|
+
background-color: #f1f5f9;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* ============================================================================
|
|
40
|
+
* ACTIVE STATE (Drag Over)
|
|
41
|
+
* ============================================================================ */
|
|
42
|
+
|
|
43
|
+
.filedrop-zone.filedrop-active {
|
|
44
|
+
border-color: #3b82f6;
|
|
45
|
+
background-color: #eff6ff;
|
|
46
|
+
border-width: 3px;
|
|
47
|
+
transform: scale(1.02);
|
|
48
|
+
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.filedrop-zone.filedrop-active::before {
|
|
52
|
+
content: '';
|
|
53
|
+
position: absolute;
|
|
54
|
+
inset: 0;
|
|
55
|
+
border-radius: 0.5rem;
|
|
56
|
+
background: linear-gradient(135deg, rgba(59, 130, 246, 0.05), rgba(147, 51, 234, 0.05));
|
|
57
|
+
pointer-events: none;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* ============================================================================
|
|
61
|
+
* DISABLED STATE
|
|
62
|
+
* ============================================================================ */
|
|
63
|
+
|
|
64
|
+
.filedrop-zone.filedrop-disabled {
|
|
65
|
+
opacity: 0.5;
|
|
66
|
+
cursor: not-allowed;
|
|
67
|
+
border-color: #e2e8f0;
|
|
68
|
+
background-color: #f8fafc;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.filedrop-zone.filedrop-disabled:hover {
|
|
72
|
+
border-color: #e2e8f0;
|
|
73
|
+
background-color: #f8fafc;
|
|
74
|
+
transform: none;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* ============================================================================
|
|
78
|
+
* DROP ZONE CONTENT
|
|
79
|
+
* ============================================================================ */
|
|
80
|
+
|
|
81
|
+
.filedrop-content {
|
|
82
|
+
display: flex;
|
|
83
|
+
flex-direction: column;
|
|
84
|
+
align-items: center;
|
|
85
|
+
gap: 1rem;
|
|
86
|
+
text-align: center;
|
|
87
|
+
pointer-events: none;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.filedrop-icon {
|
|
91
|
+
width: 3rem;
|
|
92
|
+
height: 3rem;
|
|
93
|
+
color: #94a3b8;
|
|
94
|
+
transition: all 0.2s ease-in-out;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.filedrop-zone.filedrop-active .filedrop-icon {
|
|
98
|
+
color: #3b82f6;
|
|
99
|
+
transform: scale(1.2);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.filedrop-zone.filedrop-disabled .filedrop-icon {
|
|
103
|
+
color: #cbd5e1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.filedrop-text {
|
|
107
|
+
font-size: 1rem;
|
|
108
|
+
font-weight: 500;
|
|
109
|
+
color: #475569;
|
|
110
|
+
transition: color 0.2s ease-in-out;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.filedrop-zone.filedrop-active .filedrop-text {
|
|
114
|
+
color: #3b82f6;
|
|
115
|
+
font-weight: 600;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.filedrop-zone.filedrop-disabled .filedrop-text {
|
|
119
|
+
color: #cbd5e1;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.filedrop-hint {
|
|
123
|
+
font-size: 0.875rem;
|
|
124
|
+
color: #94a3b8;
|
|
125
|
+
transition: color 0.2s ease-in-out;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.filedrop-zone.filedrop-active .filedrop-hint {
|
|
129
|
+
color: #60a5fa;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.filedrop-zone.filedrop-disabled .filedrop-hint {
|
|
133
|
+
color: #cbd5e1;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* ============================================================================
|
|
137
|
+
* COMPACT VARIANT
|
|
138
|
+
* ============================================================================ */
|
|
139
|
+
|
|
140
|
+
.filedrop-zone.filedrop-compact {
|
|
141
|
+
min-height: 120px;
|
|
142
|
+
padding: 1.5rem;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.filedrop-zone.filedrop-compact .filedrop-icon {
|
|
146
|
+
width: 2rem;
|
|
147
|
+
height: 2rem;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.filedrop-zone.filedrop-compact .filedrop-text {
|
|
151
|
+
font-size: 0.875rem;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.filedrop-zone.filedrop-compact .filedrop-hint {
|
|
155
|
+
font-size: 0.75rem;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* ============================================================================
|
|
159
|
+
* INLINE VARIANT (Full Width)
|
|
160
|
+
* ============================================================================ */
|
|
161
|
+
|
|
162
|
+
.filedrop-zone.filedrop-inline {
|
|
163
|
+
min-height: 80px;
|
|
164
|
+
padding: 1rem;
|
|
165
|
+
flex-direction: row;
|
|
166
|
+
justify-content: flex-start;
|
|
167
|
+
gap: 1rem;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.filedrop-zone.filedrop-inline .filedrop-content {
|
|
171
|
+
flex-direction: row;
|
|
172
|
+
align-items: center;
|
|
173
|
+
text-align: left;
|
|
174
|
+
gap: 0.75rem;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.filedrop-zone.filedrop-inline .filedrop-icon {
|
|
178
|
+
width: 2rem;
|
|
179
|
+
height: 2rem;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/* ============================================================================
|
|
183
|
+
* FILE LIST DISPLAY
|
|
184
|
+
* ============================================================================ */
|
|
185
|
+
|
|
186
|
+
.filedrop-files {
|
|
187
|
+
margin-top: 1rem;
|
|
188
|
+
width: 100%;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.filedrop-file-item {
|
|
192
|
+
display: flex;
|
|
193
|
+
align-items: center;
|
|
194
|
+
gap: 0.75rem;
|
|
195
|
+
padding: 0.75rem;
|
|
196
|
+
background-color: white;
|
|
197
|
+
border: 1px solid #e2e8f0;
|
|
198
|
+
border-radius: 0.5rem;
|
|
199
|
+
margin-bottom: 0.5rem;
|
|
200
|
+
transition: all 0.2s ease-in-out;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.filedrop-file-item:hover {
|
|
204
|
+
border-color: #cbd5e1;
|
|
205
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.filedrop-file-icon {
|
|
209
|
+
width: 2rem;
|
|
210
|
+
height: 2rem;
|
|
211
|
+
flex-shrink: 0;
|
|
212
|
+
display: flex;
|
|
213
|
+
align-items: center;
|
|
214
|
+
justify-content: center;
|
|
215
|
+
background-color: #f1f5f9;
|
|
216
|
+
border-radius: 0.375rem;
|
|
217
|
+
color: #64748b;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.filedrop-file-info {
|
|
221
|
+
flex: 1;
|
|
222
|
+
min-width: 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.filedrop-file-name {
|
|
226
|
+
font-size: 0.875rem;
|
|
227
|
+
font-weight: 500;
|
|
228
|
+
color: #334155;
|
|
229
|
+
white-space: nowrap;
|
|
230
|
+
overflow: hidden;
|
|
231
|
+
text-overflow: ellipsis;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.filedrop-file-meta {
|
|
235
|
+
font-size: 0.75rem;
|
|
236
|
+
color: #94a3b8;
|
|
237
|
+
margin-top: 0.125rem;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.filedrop-file-remove {
|
|
241
|
+
flex-shrink: 0;
|
|
242
|
+
width: 1.5rem;
|
|
243
|
+
height: 1.5rem;
|
|
244
|
+
display: flex;
|
|
245
|
+
align-items: center;
|
|
246
|
+
justify-content: center;
|
|
247
|
+
border-radius: 0.25rem;
|
|
248
|
+
color: #94a3b8;
|
|
249
|
+
cursor: pointer;
|
|
250
|
+
transition: all 0.2s ease-in-out;
|
|
251
|
+
pointer-events: auto;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.filedrop-file-remove:hover {
|
|
255
|
+
background-color: #fee2e2;
|
|
256
|
+
color: #ef4444;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/* ============================================================================
|
|
260
|
+
* DARK MODE SUPPORT
|
|
261
|
+
* ============================================================================ */
|
|
262
|
+
|
|
263
|
+
@media (prefers-color-scheme: dark) {
|
|
264
|
+
.filedrop-zone {
|
|
265
|
+
border-color: #475569;
|
|
266
|
+
background-color: #1e293b;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.filedrop-zone:hover {
|
|
270
|
+
border-color: #64748b;
|
|
271
|
+
background-color: #334155;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.filedrop-zone.filedrop-active {
|
|
275
|
+
border-color: #60a5fa;
|
|
276
|
+
background-color: #1e3a8a;
|
|
277
|
+
box-shadow: 0 0 0 4px rgba(96, 165, 250, 0.1);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.filedrop-zone.filedrop-active::before {
|
|
281
|
+
background: linear-gradient(135deg, rgba(96, 165, 250, 0.1), rgba(168, 85, 247, 0.1));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.filedrop-zone.filedrop-disabled {
|
|
285
|
+
border-color: #334155;
|
|
286
|
+
background-color: #1e293b;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.filedrop-icon {
|
|
290
|
+
color: #64748b;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.filedrop-zone.filedrop-active .filedrop-icon {
|
|
294
|
+
color: #60a5fa;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.filedrop-zone.filedrop-disabled .filedrop-icon {
|
|
298
|
+
color: #475569;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.filedrop-text {
|
|
302
|
+
color: #cbd5e1;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.filedrop-zone.filedrop-active .filedrop-text {
|
|
306
|
+
color: #93c5fd;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.filedrop-zone.filedrop-disabled .filedrop-text {
|
|
310
|
+
color: #64748b;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.filedrop-hint {
|
|
314
|
+
color: #64748b;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.filedrop-zone.filedrop-active .filedrop-hint {
|
|
318
|
+
color: #93c5fd;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.filedrop-file-item {
|
|
322
|
+
background-color: #334155;
|
|
323
|
+
border-color: #475569;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.filedrop-file-item:hover {
|
|
327
|
+
border-color: #64748b;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.filedrop-file-icon {
|
|
331
|
+
background-color: #1e293b;
|
|
332
|
+
color: #94a3b8;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.filedrop-file-name {
|
|
336
|
+
color: #e2e8f0;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.filedrop-file-meta {
|
|
340
|
+
color: #64748b;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.filedrop-file-remove:hover {
|
|
344
|
+
background-color: #7f1d1d;
|
|
345
|
+
color: #fca5a5;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/* ============================================================================
|
|
350
|
+
* ANIMATION UTILITIES
|
|
351
|
+
* ============================================================================ */
|
|
352
|
+
|
|
353
|
+
@keyframes filedrop-pulse {
|
|
354
|
+
0%, 100% {
|
|
355
|
+
opacity: 1;
|
|
356
|
+
}
|
|
357
|
+
50% {
|
|
358
|
+
opacity: 0.7;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.filedrop-zone.filedrop-processing {
|
|
363
|
+
animation: filedrop-pulse 1.5s ease-in-out infinite;
|
|
364
|
+
pointer-events: none;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
@keyframes filedrop-shake {
|
|
368
|
+
0%, 100% {
|
|
369
|
+
transform: translateX(0);
|
|
370
|
+
}
|
|
371
|
+
25% {
|
|
372
|
+
transform: translateX(-4px);
|
|
373
|
+
}
|
|
374
|
+
75% {
|
|
375
|
+
transform: translateX(4px);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.filedrop-zone.filedrop-error {
|
|
380
|
+
border-color: #ef4444;
|
|
381
|
+
background-color: #fee2e2;
|
|
382
|
+
animation: filedrop-shake 0.3s ease-in-out;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
@media (prefers-color-scheme: dark) {
|
|
386
|
+
.filedrop-zone.filedrop-error {
|
|
387
|
+
border-color: #f87171;
|
|
388
|
+
background-color: #7f1d1d;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/* ============================================================================
|
|
393
|
+
* CUSTOM VARIANTS
|
|
394
|
+
* ============================================================================ */
|
|
395
|
+
|
|
396
|
+
/* Minimal variant - subtle styling */
|
|
397
|
+
.filedrop-zone.filedrop-minimal {
|
|
398
|
+
border-style: solid;
|
|
399
|
+
border-width: 1px;
|
|
400
|
+
background-color: transparent;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.filedrop-zone.filedrop-minimal:hover {
|
|
404
|
+
background-color: rgba(0, 0, 0, 0.02);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
@media (prefers-color-scheme: dark) {
|
|
408
|
+
.filedrop-zone.filedrop-minimal:hover {
|
|
409
|
+
background-color: rgba(255, 255, 255, 0.02);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/* Bold variant - prominent styling */
|
|
414
|
+
.filedrop-zone.filedrop-bold {
|
|
415
|
+
border-width: 3px;
|
|
416
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.filedrop-zone.filedrop-bold.filedrop-active {
|
|
420
|
+
box-shadow: 0 8px 16px rgba(59, 130, 246, 0.2);
|
|
421
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileDrop API - Cross-platform drag & drop file handling
|
|
3
|
+
*
|
|
4
|
+
* Enables dragging files into the webview window and from the window to the desktop.
|
|
5
|
+
* Supports all file types cross-platform (Windows, macOS, Linux).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Information about a dropped file
|
|
10
|
+
*/
|
|
11
|
+
export interface FileInfo {
|
|
12
|
+
/** Full path to the file */
|
|
13
|
+
path: string;
|
|
14
|
+
/** File name with extension */
|
|
15
|
+
name: string;
|
|
16
|
+
/** MIME type (e.g., "image/png", "text/plain") */
|
|
17
|
+
type: string;
|
|
18
|
+
/** File size in bytes */
|
|
19
|
+
size: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Drag event data
|
|
24
|
+
*/
|
|
25
|
+
export interface DragEvent {
|
|
26
|
+
/** X coordinate of drag position */
|
|
27
|
+
x?: number;
|
|
28
|
+
/** Y coordinate of drag position */
|
|
29
|
+
y?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface FileDropAPI {
|
|
33
|
+
/**
|
|
34
|
+
* Enable or disable file drop into window
|
|
35
|
+
* @param enabled - true to allow files to be dropped into the window
|
|
36
|
+
*/
|
|
37
|
+
setEnabled(enabled: boolean): Promise<void>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Check if file drop is currently enabled
|
|
41
|
+
* @returns true if file drop is enabled
|
|
42
|
+
*/
|
|
43
|
+
isEnabled(): Promise<boolean>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Start a drag operation to drag files out of the window
|
|
47
|
+
* @param filePaths - Paths to files to be dragged
|
|
48
|
+
* @returns true if drag operation started successfully
|
|
49
|
+
*/
|
|
50
|
+
startDrag(filePaths: string[]): Promise<boolean>;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Clear all callbacks
|
|
54
|
+
*/
|
|
55
|
+
clearCallbacks(): Promise<void>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Listen for files dropped into the window
|
|
59
|
+
* @param callback - Function called with array of dropped files
|
|
60
|
+
* @returns Unsubscribe function
|
|
61
|
+
*/
|
|
62
|
+
onFilesDropped(callback: (files: FileInfo[]) => void): () => void;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Listen for drag enter events
|
|
66
|
+
* @param callback - Function called when drag enters the window
|
|
67
|
+
* @returns Unsubscribe function
|
|
68
|
+
*/
|
|
69
|
+
onDragEnter(callback: (event: DragEvent) => void): () => void;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Listen for drag leave events
|
|
73
|
+
* @param callback - Function called when drag leaves the window
|
|
74
|
+
* @returns Unsubscribe function
|
|
75
|
+
*/
|
|
76
|
+
onDragLeave(callback: (event: DragEvent) => void): () => void;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export class FileDrop implements FileDropAPI {
|
|
80
|
+
private invokeFn: (name: string, args?: unknown[]) => Promise<unknown>;
|
|
81
|
+
private onFn: (event: string, callback: (data: unknown) => void) => () => void;
|
|
82
|
+
|
|
83
|
+
constructor(
|
|
84
|
+
invokeFn: (name: string, args?: unknown[]) => Promise<unknown>,
|
|
85
|
+
onFn: (event: string, callback: (data: unknown) => void) => () => void
|
|
86
|
+
) {
|
|
87
|
+
this.invokeFn = invokeFn;
|
|
88
|
+
this.onFn = onFn;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Enable or disable file drop into window
|
|
93
|
+
* @param enabled - true to allow files to be dropped into the window
|
|
94
|
+
*/
|
|
95
|
+
async setEnabled(enabled: boolean): Promise<void> {
|
|
96
|
+
await this.invokeFn('fileDrop.setEnabled', [enabled]);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Check if file drop is currently enabled
|
|
101
|
+
* @returns true if file drop is enabled
|
|
102
|
+
*/
|
|
103
|
+
async isEnabled(): Promise<boolean> {
|
|
104
|
+
return (await this.invokeFn('fileDrop.isEnabled')) as boolean;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Start a drag operation to drag files out of the window
|
|
109
|
+
* @param filePaths - Paths to files to be dragged
|
|
110
|
+
* @returns true if drag operation started successfully
|
|
111
|
+
*/
|
|
112
|
+
async startDrag(filePaths: string[]): Promise<boolean> {
|
|
113
|
+
if (!filePaths || filePaths.length === 0) {
|
|
114
|
+
throw new Error('filePaths must be a non-empty array');
|
|
115
|
+
}
|
|
116
|
+
return (await this.invokeFn('fileDrop.startDrag', [filePaths])) as boolean;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Clear all callbacks
|
|
121
|
+
*/
|
|
122
|
+
async clearCallbacks(): Promise<void> {
|
|
123
|
+
await this.invokeFn('fileDrop.clearCallbacks');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Listen for files dropped into the window
|
|
128
|
+
* @param callback - Function called with array of dropped files
|
|
129
|
+
* @returns Unsubscribe function
|
|
130
|
+
*/
|
|
131
|
+
onFilesDropped(callback: (files: FileInfo[]) => void): () => void {
|
|
132
|
+
return this.onFn('fileDrop.filesDropped', (data) => {
|
|
133
|
+
callback(data as FileInfo[]);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Listen for drag enter events
|
|
139
|
+
* @param callback - Function called when drag enters the window
|
|
140
|
+
* @returns Unsubscribe function
|
|
141
|
+
*/
|
|
142
|
+
onDragEnter(callback: (event: DragEvent) => void): () => void {
|
|
143
|
+
return this.onFn('fileDrop.dragEnter', (data) => {
|
|
144
|
+
callback((data as DragEvent) || {});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Listen for drag leave events
|
|
150
|
+
* @param callback - Function called when drag leaves the window
|
|
151
|
+
* @returns Unsubscribe function
|
|
152
|
+
*/
|
|
153
|
+
onDragLeave(callback: (event: DragEvent) => void): () => void {
|
|
154
|
+
return this.onFn('fileDrop.dragLeave', (data) => {
|
|
155
|
+
callback((data as DragEvent) || {});
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Helper functions for common use cases
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Read dropped file as text
|
|
164
|
+
* @param filePath - Path to the file
|
|
165
|
+
* @returns File contents as text
|
|
166
|
+
*/
|
|
167
|
+
export async function readFileAsText(filePath: string): Promise<string> {
|
|
168
|
+
const response = await fetch(`file://${filePath}`);
|
|
169
|
+
return await response.text();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Read dropped file as data URL (useful for images)
|
|
174
|
+
* @param filePath - Path to the file
|
|
175
|
+
* @returns File contents as data URL
|
|
176
|
+
*/
|
|
177
|
+
export async function readFileAsDataUrl(filePath: string): Promise<string> {
|
|
178
|
+
const response = await fetch(`file://${filePath}`);
|
|
179
|
+
const blob = await response.blob();
|
|
180
|
+
return new Promise((resolve, reject) => {
|
|
181
|
+
const reader = new FileReader();
|
|
182
|
+
reader.onload = () => resolve(reader.result as string);
|
|
183
|
+
reader.onerror = reject;
|
|
184
|
+
reader.readAsDataURL(blob);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Filter files by extension
|
|
190
|
+
* @param files - Array of file info
|
|
191
|
+
* @param extensions - Array of extensions to filter (e.g., ['.png', '.jpg'])
|
|
192
|
+
* @returns Filtered array of files
|
|
193
|
+
*/
|
|
194
|
+
export function filterFilesByExtension(
|
|
195
|
+
files: FileInfo[],
|
|
196
|
+
extensions: string[]
|
|
197
|
+
): FileInfo[] {
|
|
198
|
+
const lowerExtensions = extensions.map((ext) => ext.toLowerCase());
|
|
199
|
+
return files.filter((file) => {
|
|
200
|
+
const ext = file.name.substring(file.name.lastIndexOf('.')).toLowerCase();
|
|
201
|
+
return lowerExtensions.includes(ext);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Filter files by MIME type
|
|
207
|
+
* @param files - Array of file info
|
|
208
|
+
* @param mimeTypes - Array of MIME types to filter (e.g., ['image/png', 'image/jpeg'])
|
|
209
|
+
* @returns Filtered array of files
|
|
210
|
+
*/
|
|
211
|
+
export function filterFilesByMimeType(
|
|
212
|
+
files: FileInfo[],
|
|
213
|
+
mimeTypes: string[]
|
|
214
|
+
): FileInfo[] {
|
|
215
|
+
return files.filter((file) => mimeTypes.includes(file.type));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Check if file is an image
|
|
220
|
+
* @param file - File info
|
|
221
|
+
* @returns true if file is an image
|
|
222
|
+
*/
|
|
223
|
+
export function isImageFile(file: FileInfo): boolean {
|
|
224
|
+
return file.type.startsWith('image/');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Check if file is a video
|
|
229
|
+
* @param file - File info
|
|
230
|
+
* @returns true if file is a video
|
|
231
|
+
*/
|
|
232
|
+
export function isVideoFile(file: FileInfo): boolean {
|
|
233
|
+
return file.type.startsWith('video/');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Check if file is audio
|
|
238
|
+
* @param file - File info
|
|
239
|
+
* @returns true if file is audio
|
|
240
|
+
*/
|
|
241
|
+
export function isAudioFile(file: FileInfo): boolean {
|
|
242
|
+
return file.type.startsWith('audio/');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Check if file is text
|
|
247
|
+
* @param file - File info
|
|
248
|
+
* @returns true if file is text
|
|
249
|
+
*/
|
|
250
|
+
export function isTextFile(file: FileInfo): boolean {
|
|
251
|
+
return file.type.startsWith('text/') ||
|
|
252
|
+
file.type === 'application/json' ||
|
|
253
|
+
file.type === 'application/xml';
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Format file size for display
|
|
258
|
+
* @param bytes - File size in bytes
|
|
259
|
+
* @returns Formatted string (e.g., "1.5 MB")
|
|
260
|
+
*/
|
|
261
|
+
export function formatFileSize(bytes: number): string {
|
|
262
|
+
if (bytes === 0) return '0 Bytes';
|
|
263
|
+
|
|
264
|
+
const k = 1024;
|
|
265
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
266
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
267
|
+
|
|
268
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
269
|
+
}
|