plusui-native-core 0.1.66 → 0.1.69

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.
@@ -1,283 +1,163 @@
1
- import { connect } from '../Connection/connect';
2
-
3
- /**
4
- * FileDrop API - Cross-platform drag & drop file handling
5
- *
6
- * Enables dragging files into the webview window and from the window to the desktop.
7
- * Supports all file types cross-platform (Windows, macOS, Linux).
8
- */
9
-
10
- /**
11
- * Information about a dropped file
12
- */
13
- export interface FileInfo {
14
- /** Full path to the file */
15
- path: string;
16
- /** File name with extension */
17
- name: string;
18
- /** MIME type (e.g., "image/png", "text/plain") */
19
- type: string;
20
- /** File size in bytes */
21
- size: number;
22
- }
23
-
24
- /**
25
- * Drag event data
26
- */
27
- export interface DragEvent {
28
- /** X coordinate of drag position */
29
- x?: number;
30
- /** Y coordinate of drag position */
31
- y?: number;
32
- }
33
-
34
- export interface FileDropAPI {
35
- on<TData = unknown>(name: string, callback: (payload: TData) => void): () => void;
36
- emit<TIn = Record<string, unknown>>(name: string, payload: TIn): void;
37
-
38
- /**
39
- * Enable or disable file drop into window
40
- * @param enabled - true to allow files to be dropped into the window
41
- */
42
- setEnabled(enabled: boolean): Promise<void>;
43
-
44
- /**
45
- * Check if file drop is currently enabled
46
- * @returns true if file drop is enabled
47
- */
48
- isEnabled(): Promise<boolean>;
49
-
50
- /**
51
- * Start a drag operation to drag files out of the window
52
- * @param filePaths - Paths to files to be dragged
53
- * @returns true if drag operation started successfully
54
- */
55
- startDrag(filePaths: string[]): Promise<boolean>;
56
-
57
- /**
58
- * Clear all callbacks
59
- */
60
- clearCallbacks(): Promise<void>;
61
-
62
- /**
63
- * Listen for files dropped into the window
64
- * @param callback - Function called with array of dropped files
65
- * @returns Unsubscribe function
66
- */
67
- onFilesDropped(callback: (files: FileInfo[]) => void): () => void;
68
-
69
- /**
70
- * Listen for drag enter events
71
- * @param callback - Function called when drag enters the window
72
- * @returns Unsubscribe function
73
- */
74
- onDragEnter(callback: (event: DragEvent) => void): () => void;
75
-
76
- /**
77
- * Listen for drag leave events
78
- * @param callback - Function called when drag leaves the window
79
- * @returns Unsubscribe function
80
- */
81
- onDragLeave(callback: (event: DragEvent) => void): () => void;
82
- }
83
-
84
- export class FileDrop implements FileDropAPI {
85
- public readonly connect = connect.feature('fileDrop');
86
- private invokeFn: (name: string, args?: unknown[]) => Promise<unknown>;
87
- private onFn: (event: string, callback: (data: unknown) => void) => () => void;
88
-
89
- constructor(
90
- invokeFn: (name: string, args?: unknown[]) => Promise<unknown>,
91
- onFn: (event: string, callback: (data: unknown) => void) => () => void
92
- ) {
93
- this.invokeFn = invokeFn;
94
- this.onFn = onFn;
95
- }
96
-
97
- on<TData = unknown>(name: string, callback: (payload: TData) => void): () => void {
98
- return this.connect.on<TData>(name, callback);
99
- }
100
-
101
- emit<TIn = Record<string, unknown>>(name: string, payload: TIn): void {
102
- this.connect.emit<TIn>(name, payload);
103
- }
104
-
105
- /**
106
- * Enable or disable file drop into window
107
- * @param enabled - true to allow files to be dropped into the window
108
- */
109
- async setEnabled(enabled: boolean): Promise<void> {
110
- await this.invokeFn('fileDrop.setEnabled', [enabled]);
111
- }
112
-
113
- /**
114
- * Check if file drop is currently enabled
115
- * @returns true if file drop is enabled
116
- */
117
- async isEnabled(): Promise<boolean> {
118
- return (await this.invokeFn('fileDrop.isEnabled')) as boolean;
119
- }
120
-
121
- /**
122
- * Start a drag operation to drag files out of the window
123
- * @param filePaths - Paths to files to be dragged
124
- * @returns true if drag operation started successfully
125
- */
126
- async startDrag(filePaths: string[]): Promise<boolean> {
127
- if (!filePaths || filePaths.length === 0) {
128
- throw new Error('filePaths must be a non-empty array');
129
- }
130
- return (await this.invokeFn('fileDrop.startDrag', [filePaths])) as boolean;
131
- }
132
-
133
- /**
134
- * Clear all callbacks
135
- */
136
- async clearCallbacks(): Promise<void> {
137
- await this.invokeFn('fileDrop.clearCallbacks');
138
- }
139
-
140
- /**
141
- * Listen for files dropped into the window
142
- * @param callback - Function called with array of dropped files
143
- * @returns Unsubscribe function
144
- */
145
- onFilesDropped(callback: (files: FileInfo[]) => void): () => void {
146
- return this.onFn('fileDrop.filesDropped', (data) => {
147
- callback(data as FileInfo[]);
148
- });
149
- }
150
-
151
- /**
152
- * Listen for drag enter events
153
- * @param callback - Function called when drag enters the window
154
- * @returns Unsubscribe function
155
- */
156
- onDragEnter(callback: (event: DragEvent) => void): () => void {
157
- return this.onFn('fileDrop.dragEnter', (data) => {
158
- callback((data as DragEvent) || {});
159
- });
160
- }
161
-
162
- /**
163
- * Listen for drag leave events
164
- * @param callback - Function called when drag leaves the window
165
- * @returns Unsubscribe function
166
- */
167
- onDragLeave(callback: (event: DragEvent) => void): () => void {
168
- return this.onFn('fileDrop.dragLeave', (data) => {
169
- callback((data as DragEvent) || {});
170
- });
171
- }
172
- }
173
-
174
- // Helper functions for common use cases
175
-
176
- /**
177
- * Read dropped file as text
178
- * @param filePath - Path to the file
179
- * @returns File contents as text
180
- */
181
- export async function readFileAsText(filePath: string): Promise<string> {
182
- const response = await fetch(`file://${filePath}`);
183
- return await response.text();
184
- }
185
-
186
- /**
187
- * Read dropped file as data URL (useful for images)
188
- * @param filePath - Path to the file
189
- * @returns File contents as data URL
190
- */
191
- export async function readFileAsDataUrl(filePath: string): Promise<string> {
192
- const response = await fetch(`file://${filePath}`);
193
- const blob = await response.blob();
194
- return new Promise((resolve, reject) => {
195
- const reader = new FileReader();
196
- reader.onload = () => resolve(reader.result as string);
197
- reader.onerror = reject;
198
- reader.readAsDataURL(blob);
199
- });
200
- }
201
-
202
- /**
203
- * Filter files by extension
204
- * @param files - Array of file info
205
- * @param extensions - Array of extensions to filter (e.g., ['.png', '.jpg'])
206
- * @returns Filtered array of files
207
- */
208
- export function filterFilesByExtension(
209
- files: FileInfo[],
210
- extensions: string[]
211
- ): FileInfo[] {
212
- const lowerExtensions = extensions.map((ext) => ext.toLowerCase());
213
- return files.filter((file) => {
214
- const ext = file.name.substring(file.name.lastIndexOf('.')).toLowerCase();
215
- return lowerExtensions.includes(ext);
216
- });
217
- }
218
-
219
- /**
220
- * Filter files by MIME type
221
- * @param files - Array of file info
222
- * @param mimeTypes - Array of MIME types to filter (e.g., ['image/png', 'image/jpeg'])
223
- * @returns Filtered array of files
224
- */
225
- export function filterFilesByMimeType(
226
- files: FileInfo[],
227
- mimeTypes: string[]
228
- ): FileInfo[] {
229
- return files.filter((file) => mimeTypes.includes(file.type));
230
- }
231
-
232
- /**
233
- * Check if file is an image
234
- * @param file - File info
235
- * @returns true if file is an image
236
- */
237
- export function isImageFile(file: FileInfo): boolean {
238
- return file.type.startsWith('image/');
239
- }
240
-
241
- /**
242
- * Check if file is a video
243
- * @param file - File info
244
- * @returns true if file is a video
245
- */
246
- export function isVideoFile(file: FileInfo): boolean {
247
- return file.type.startsWith('video/');
248
- }
249
-
250
- /**
251
- * Check if file is audio
252
- * @param file - File info
253
- * @returns true if file is audio
254
- */
255
- export function isAudioFile(file: FileInfo): boolean {
256
- return file.type.startsWith('audio/');
257
- }
258
-
259
- /**
260
- * Check if file is text
261
- * @param file - File info
262
- * @returns true if file is text
263
- */
264
- export function isTextFile(file: FileInfo): boolean {
265
- return file.type.startsWith('text/') ||
266
- file.type === 'application/json' ||
267
- file.type === 'application/xml';
268
- }
269
-
270
- /**
271
- * Format file size for display
272
- * @param bytes - File size in bytes
273
- * @returns Formatted string (e.g., "1.5 MB")
274
- */
275
- export function formatFileSize(bytes: number): string {
276
- if (bytes === 0) return '0 Bytes';
277
-
278
- const k = 1024;
279
- const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
280
- const i = Math.floor(Math.log(bytes) / Math.log(k));
281
-
282
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
283
- }
1
+ /**
2
+ * FileDrop API - Cross-platform drag & drop file handling
3
+ *
4
+ * Direct method calls - no .on/.emit
5
+ *
6
+ * Usage:
7
+ * import { fileDrop } from '@plusui/api';
8
+ *
9
+ * await fileDrop.setEnabled(true);
10
+ * fileDrop.onFilesDropped((files) => { ... });
11
+ */
12
+
13
+ export interface FileInfo {
14
+ path: string;
15
+ name: string;
16
+ type: string;
17
+ size: number;
18
+ }
19
+
20
+ export interface DragEvent {
21
+ x?: number;
22
+ y?: number;
23
+ }
24
+
25
+ export interface FileDropAPI {
26
+ setEnabled(enabled: boolean): Promise<void>;
27
+ isEnabled(): Promise<boolean>;
28
+ startDrag(filePaths: string[]): Promise<boolean>;
29
+ clearCallbacks(): Promise<void>;
30
+ onFilesDropped(callback: (files: FileInfo[]) => void): () => void;
31
+ onDragEnter(callback: (event: DragEvent) => void): () => void;
32
+ onDragLeave(callback: (event: DragEvent) => void): () => void;
33
+ }
34
+
35
+ let _invoke: ((name: string, args?: unknown[]) => Promise<unknown>) | null = null;
36
+ let _event: ((event: string, callback: (data: unknown) => void) => () => void) | null = null;
37
+
38
+ export function setInvokeFn(fn: (name: string, args?: unknown[]) => Promise<unknown>) {
39
+ _invoke = fn;
40
+ }
41
+
42
+ export function setEventFn(fn: (event: string, callback: (data: unknown) => void) => () => void) {
43
+ _event = fn;
44
+ }
45
+
46
+ async function invoke<T = unknown>(name: string, args?: unknown[]): Promise<T> {
47
+ if (!_invoke) {
48
+ if (typeof window !== 'undefined' && (window as any).__invoke__) {
49
+ _invoke = (window as any).__invoke__;
50
+ } else {
51
+ throw new Error('FileDrop API not initialized');
52
+ }
53
+ }
54
+ return _invoke!(name, args) as Promise<T>;
55
+ }
56
+
57
+ function eventHandler(event: string, callback: (data: unknown) => void): () => void {
58
+ if (!_event) {
59
+ if (typeof window !== 'undefined' && (window as any).__on__) {
60
+ _event = (window as any).__on__;
61
+ } else {
62
+ return () => {};
63
+ }
64
+ }
65
+ return _event!(event, callback);
66
+ }
67
+
68
+ export const fileDrop: FileDropAPI = {
69
+ async setEnabled(enabled: boolean): Promise<void> {
70
+ await invoke('fileDrop.setEnabled', [enabled]);
71
+ },
72
+
73
+ async isEnabled(): Promise<boolean> {
74
+ return invoke<boolean>('fileDrop.isEnabled');
75
+ },
76
+
77
+ async startDrag(filePaths: string[]): Promise<boolean> {
78
+ if (!filePaths || filePaths.length === 0) {
79
+ throw new Error('filePaths must be a non-empty array');
80
+ }
81
+ return invoke<boolean>('fileDrop.startDrag', [filePaths]);
82
+ },
83
+
84
+ async clearCallbacks(): Promise<void> {
85
+ await invoke('fileDrop.clearCallbacks');
86
+ },
87
+
88
+ onFilesDropped(callback: (files: FileInfo[]) => void): () => void {
89
+ return eventHandler('fileDrop:filesDropped', callback as (data: unknown) => void);
90
+ },
91
+
92
+ onDragEnter(callback: (event: DragEvent) => void): () => void {
93
+ return eventHandler('fileDrop:dragEnter', callback as (data: unknown) => void);
94
+ },
95
+
96
+ onDragLeave(callback: (event: DragEvent) => void): () => void {
97
+ return eventHandler('fileDrop:dragLeave', callback as (data: unknown) => void);
98
+ },
99
+ };
100
+
101
+ export async function readFileAsText(filePath: string): Promise<string> {
102
+ const response = await fetch(`file://${filePath}`);
103
+ return await response.text();
104
+ }
105
+
106
+ export async function readFileAsDataUrl(filePath: string): Promise<string> {
107
+ const response = await fetch(`file://${filePath}`);
108
+ const blob = await response.blob();
109
+ return new Promise((resolve, reject) => {
110
+ const reader = new FileReader();
111
+ reader.onload = () => resolve(reader.result as string);
112
+ reader.onerror = reject;
113
+ reader.readAsDataURL(blob);
114
+ });
115
+ }
116
+
117
+ export function filterFilesByExtension(
118
+ files: FileInfo[],
119
+ extensions: string[]
120
+ ): FileInfo[] {
121
+ const lowerExtensions = extensions.map((ext) => ext.toLowerCase());
122
+ return files.filter((file) => {
123
+ const ext = file.name.substring(file.name.lastIndexOf('.')).toLowerCase();
124
+ return lowerExtensions.includes(ext);
125
+ });
126
+ }
127
+
128
+ export function filterFilesByMimeType(
129
+ files: FileInfo[],
130
+ mimeTypes: string[]
131
+ ): FileInfo[] {
132
+ return files.filter((file) => mimeTypes.includes(file.type));
133
+ }
134
+
135
+ export function isImageFile(file: FileInfo): boolean {
136
+ return file.type.startsWith('image/');
137
+ }
138
+
139
+ export function isVideoFile(file: FileInfo): boolean {
140
+ return file.type.startsWith('video/');
141
+ }
142
+
143
+ export function isAudioFile(file: FileInfo): boolean {
144
+ return file.type.startsWith('audio/');
145
+ }
146
+
147
+ export function isTextFile(file: FileInfo): boolean {
148
+ return file.type.startsWith('text/') ||
149
+ file.type === 'application/json' ||
150
+ file.type === 'application/xml';
151
+ }
152
+
153
+ export function formatFileSize(bytes: number): string {
154
+ if (bytes === 0) return '0 Bytes';
155
+
156
+ const k = 1024;
157
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
158
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
159
+
160
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
161
+ }
162
+
163
+ export default fileDrop;