plusui-native-core 0.1.98 → 0.1.101

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,43 +1,2 @@
1
- /**
2
- * FileDrop API
3
- *
4
- * Handle files dragged and dropped onto the application window.
5
- * Also supports initiating drag operations from the app.
6
- *
7
- * Actions:
8
- * fileDrop.setEnabled(enabled) - Enable or disable file drop
9
- * fileDrop.isEnabled() - Check if file drop is enabled
10
- * fileDrop.startDrag(filePaths) - Start a drag operation with the given files
11
- * fileDrop.clearCallbacks() - Remove all registered drop callbacks
12
- *
13
- * Events:
14
- * fileDrop.onFilesDropped(cb) - Fires when files are dropped onto the window
15
- * fileDrop.onDragEnter(cb) - Fires when a drag enters the window
16
- * fileDrop.onDragLeave(cb) - Fires when a drag leaves the window
17
- *
18
- * Helpers (pure functions):
19
- * readFileAsText(path) - Read a file path as a text string
20
- * readFileAsDataUrl(path) - Read a file path as a base64 data URL
21
- * filterFilesByExtension(files, ext) - Filter FileInfo[] by extension(s)
22
- * filterFilesByMimeType(files, mime) - Filter FileInfo[] by MIME type(s)
23
- * isImageFile(file) - Check if FileInfo is an image
24
- * isVideoFile(file) - Check if FileInfo is a video
25
- * isAudioFile(file) - Check if FileInfo is audio
26
- * isTextFile(file) - Check if FileInfo is a text file
27
- * formatFileSize(bytes) - Format byte count as a human-readable string
28
- */
29
-
30
- export type { FileInfo, DragEvent, FileDropAPI } from '../FileDrop/filedrop';
31
-
32
- export {
33
- fileDrop,
34
- readFileAsText,
35
- readFileAsDataUrl,
36
- filterFilesByExtension,
37
- filterFilesByMimeType,
38
- isImageFile,
39
- isVideoFile,
40
- isAudioFile,
41
- isTextFile,
42
- formatFileSize,
43
- } from '../FileDrop/filedrop';
1
+ export type { FileInfo, DropZone } from '../FileDrop/filedrop';
2
+ export { fileDrop, formatFileSize, createDropZone } from '../FileDrop/filedrop';
@@ -1,9 +1,5 @@
1
1
  /**
2
2
  * PlusUI API
3
- *
4
- * window.show(), window.hide(), window.close()
5
- * window.maximize(), window.minimize(), window.restore()
6
- * window.setSize(w, h), window.setPosition(x, y)
7
3
  */
8
4
 
9
5
  export { window, win } from './window-api';
@@ -14,7 +10,7 @@ export { router } from './router-api';
14
10
  export { keyboard, KeyCode, KeyMod } from './keyboard-api';
15
11
  export { tray } from './tray-api';
16
12
  export { display } from './display-api';
17
- export { fileDrop, formatFileSize } from './filedrop-api';
13
+ export { fileDrop, formatFileSize, createDropZone } from './filedrop-api';
18
14
  export { menu } from './menu-api';
19
15
  export { gpu, GPUBufferUsage, GPUTextureUsage, GPUMapMode, GPUShaderStage, GPUColorWrite } from './webgpu-api';
20
16
 
@@ -22,19 +18,17 @@ export { connect, createChannel, type Channel } from './Connect_API';
22
18
  export { _client } from '../Connection/connect';
23
19
 
24
20
  export type { WindowSize, WindowPosition, WindowRect } from './window-api';
21
+ export type { FileInfo, DropZone } from './filedrop-api';
25
22
  export type { ClipboardAPI } from './clipboard-api';
26
23
  export type { KeyEvent, Shortcut } from './keyboard-api';
27
24
  export type { TrayMenuItem, TrayIconData } from './tray-api';
28
25
  export type { Display, DisplayMode, DisplayBounds, DisplayResolution } from './display-api';
29
- export type { FileInfo, FileDropAPI } from './filedrop-api';
30
26
  export type { MenuItem, MenuItemType, MenuBarData, ContextMenuOptions, ContextInfo } from './menu-api';
31
27
  export type { AppConfig } from './app-api';
32
28
  export type { GPUAdapter, GPUDevice, GPUBuffer, GPUTexture, GPUShaderModule, GPURenderPipeline, GPUComputePipeline, GPUQueue, GPUCommandEncoder } from './webgpu-api';
33
29
  export type { BrowserState, NavigateCallback, StateCallback, LoadCallback, ErrorCallback } from './browser-api';
34
30
  export type { RouteMap, RouteChangeCallback, RouteConfig } from './router-api';
35
31
 
36
- export { readFileAsText, readFileAsDataUrl, filterFilesByExtension, filterFilesByMimeType, isImageFile, isVideoFile, isAudioFile, isTextFile } from './filedrop-api';
37
-
38
32
  import { window, win } from './window-api';
39
33
  import { clipboard } from './clipboard-api';
40
34
  import { app } from './app-api';
@@ -43,7 +37,7 @@ import { router } from './router-api';
43
37
  import { keyboard, KeyCode, KeyMod } from './keyboard-api';
44
38
  import { tray } from './tray-api';
45
39
  import { display } from './display-api';
46
- import { fileDrop, formatFileSize } from './filedrop-api';
40
+ import { fileDrop, formatFileSize, createDropZone } from './filedrop-api';
47
41
  import { menu } from './menu-api';
48
42
  import { gpu, GPUBufferUsage, GPUTextureUsage, GPUMapMode, GPUShaderStage, GPUColorWrite } from './webgpu-api';
49
43
  import { connect, createChannel } from './Connect_API';
@@ -60,6 +54,7 @@ const plusui = {
60
54
  tray,
61
55
  display,
62
56
  fileDrop,
57
+ createDropZone,
63
58
  menu,
64
59
  gpu,
65
60
  connect,
@@ -1,15 +1,3 @@
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
1
  export interface FileInfo {
14
2
  path: string;
15
3
  name: string;
@@ -17,147 +5,107 @@ export interface FileInfo {
17
5
  size: number;
18
6
  }
19
7
 
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;
8
+ let callId = 0;
9
+ const pending = new Map<string, { resolve: (v: any) => void; reject: (e: any) => void }>();
10
+ const zoneCallbacks = new Map<string, { onFiles: (files: FileInfo[]) => void }>();
37
11
 
38
- export function setInvokeFn(fn: (name: string, args?: unknown[]) => Promise<unknown>) {
39
- _invoke = fn;
12
+ function getGlobal(): any {
13
+ if (typeof window !== 'undefined') return window;
14
+ if (typeof globalThis !== 'undefined') return globalThis;
15
+ return {};
40
16
  }
41
17
 
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
- }
18
+ const g = getGlobal();
56
19
 
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
- }
20
+ g.__response__ = function(id: string, result: any, error?: any) {
21
+ const p = pending.get(id);
22
+ if (p) {
23
+ pending.delete(id);
24
+ error ? p.reject(new Error(error)) : p.resolve(result);
64
25
  }
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
26
  };
100
27
 
101
- export async function readFileAsText(filePath: string): Promise<string> {
102
- const response = await fetch(`file://${filePath}`);
103
- return await response.text();
104
- }
28
+ g.__plusui_fileDrop__ = function(zoneName: string, files: FileInfo[]) {
29
+ const zone = zoneCallbacks.get(zoneName);
30
+ if (zone && zone.onFiles) zone.onFiles(files);
31
+ };
105
32
 
106
- export async function readFileAsDataUrl(filePath: string): Promise<string> {
107
- const response = await fetch(`file://${filePath}`);
108
- const blob = await response.blob();
33
+ async function invoke<T>(method: string, params: any[] = []): Promise<T> {
109
34
  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);
35
+ const id = String(++callId);
36
+ pending.set(id, { resolve, reject });
37
+ const payload = JSON.stringify({ id, method, params });
38
+
39
+ if (g.__native_invoke__) {
40
+ g.__native_invoke__(payload);
41
+ } else if (g.chrome && g.chrome.webview && g.chrome.webview.postMessage) {
42
+ g.chrome.webview.postMessage(payload);
43
+ } else {
44
+ pending.delete(id);
45
+ console.warn('[PlusUI] ' + method + ' - native bridge not ready');
46
+ resolve(null as T);
47
+ }
125
48
  });
126
49
  }
127
50
 
128
- export function filterFilesByMimeType(
129
- files: FileInfo[],
130
- mimeTypes: string[]
131
- ): FileInfo[] {
132
- return files.filter((file) => mimeTypes.includes(file.type));
51
+ export interface DropZone {
52
+ onFiles: (callback: (files: FileInfo[]) => void) => () => void;
53
+ element: HTMLElement | null;
133
54
  }
134
55
 
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
- }
56
+ export function createDropZone(name: string, el?: HTMLElement | null): DropZone {
57
+ const element = el || document.querySelector(`[data-dropzone="${name}"]`) as HTMLElement;
58
+
59
+ if (element) {
60
+ element.setAttribute('data-dropzone', name);
61
+
62
+ // Prevent browser default on drop (file navigation)
63
+ // The C++ backend handles actual file delivery via WM_DROPFILES
64
+ element.addEventListener('drop', (e: DragEvent) => {
65
+ e.preventDefault();
66
+ e.stopPropagation();
67
+ element.classList.remove('dropzone-active');
68
+ });
69
+
70
+ // Visual feedback: the injected C++ script manages dropzone-active
71
+ // via document-level listeners, but we also handle cleanup here
72
+ element.addEventListener('dragleave', (e: DragEvent) => {
73
+ // Only remove if actually leaving the element (not entering a child)
74
+ const related = e.relatedTarget as Node | null;
75
+ if (!related || !element.contains(related)) {
76
+ element.classList.remove('dropzone-active');
77
+ }
78
+ });
79
+
80
+ element.addEventListener('dragover', (e: DragEvent) => {
81
+ e.preventDefault();
82
+ if (e.dataTransfer) {
83
+ try { e.dataTransfer.dropEffect = 'copy'; } catch (_) {}
84
+ }
85
+ });
86
+ }
142
87
 
143
- export function isAudioFile(file: FileInfo): boolean {
144
- return file.type.startsWith('audio/');
88
+ return {
89
+ element,
90
+ onFiles: (callback: (files: FileInfo[]) => void) => {
91
+ zoneCallbacks.set(name, { onFiles: callback });
92
+ return () => zoneCallbacks.delete(name);
93
+ }
94
+ };
145
95
  }
146
96
 
147
- export function isTextFile(file: FileInfo): boolean {
148
- return file.type.startsWith('text/') ||
149
- file.type === 'application/json' ||
150
- file.type === 'application/xml';
151
- }
97
+ export const fileDrop = {
98
+ setEnabled: (enabled: boolean) => invoke<void>('fileDrop.setEnabled', [enabled]),
99
+ isEnabled: () => invoke<boolean>('fileDrop.isEnabled'),
100
+ createDropZone,
101
+ };
152
102
 
153
103
  export function formatFileSize(bytes: number): string {
154
- if (bytes === 0) return '0 Bytes';
155
-
104
+ if (bytes === 0) return '0 B';
156
105
  const k = 1024;
157
- const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
106
+ const sizes = ['B', 'KB', 'MB', 'GB'];
158
107
  const i = Math.floor(Math.log(bytes) / Math.log(k));
159
-
160
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
108
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
161
109
  }
162
110
 
163
111
  export default fileDrop;