plusui-native-core 0.1.98 → 0.1.99
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
|
-
|
|
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,87 @@ export interface FileInfo {
|
|
|
17
5
|
size: number;
|
|
18
6
|
}
|
|
19
7
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
102
|
-
const
|
|
103
|
-
|
|
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
|
-
|
|
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
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
129
|
-
files: FileInfo[]
|
|
130
|
-
|
|
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
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
+
element.addEventListener('drop', (e: DragEvent) => {
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
e.stopPropagation();
|
|
64
|
+
element.classList.remove('dropzone-active');
|
|
65
|
+
});
|
|
66
|
+
}
|
|
142
67
|
|
|
143
|
-
|
|
144
|
-
|
|
68
|
+
return {
|
|
69
|
+
element,
|
|
70
|
+
onFiles: (callback: (files: FileInfo[]) => void) => {
|
|
71
|
+
zoneCallbacks.set(name, { onFiles: callback });
|
|
72
|
+
return () => zoneCallbacks.delete(name);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
145
75
|
}
|
|
146
76
|
|
|
147
|
-
export
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
77
|
+
export const fileDrop = {
|
|
78
|
+
setEnabled: (enabled: boolean) => invoke<void>('fileDrop.setEnabled', [enabled]),
|
|
79
|
+
isEnabled: () => invoke<boolean>('fileDrop.isEnabled'),
|
|
80
|
+
createDropZone,
|
|
81
|
+
};
|
|
152
82
|
|
|
153
83
|
export function formatFileSize(bytes: number): string {
|
|
154
|
-
if (bytes === 0) return '0
|
|
155
|
-
|
|
84
|
+
if (bytes === 0) return '0 B';
|
|
156
85
|
const k = 1024;
|
|
157
|
-
const sizes = ['
|
|
86
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
158
87
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
159
|
-
|
|
160
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
88
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
161
89
|
}
|
|
162
90
|
|
|
163
91
|
export default fileDrop;
|
|
@@ -1024,65 +1024,56 @@ Window Window::create(void *windowHandle, const WindowConfig &config) {
|
|
|
1024
1024
|
// This prevents files from being loaded in the browser
|
|
1025
1025
|
// and allows the native FileDrop API to handle them
|
|
1026
1026
|
// instead
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
(
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
document.addEventListener(eventName, block, true);
|
|
1078
|
-
});
|
|
1079
|
-
})();
|
|
1080
|
-
)";
|
|
1081
|
-
pImpl->webview->AddScriptToExecuteOnDocumentCreated(
|
|
1082
|
-
std::wstring(disableDragDropScript.begin(),
|
|
1083
|
-
disableDragDropScript.end())
|
|
1084
|
-
.c_str(),
|
|
1085
|
-
nullptr);
|
|
1027
|
+
std::string dropzoneScript = R"(
|
|
1028
|
+
(function() {
|
|
1029
|
+
if (window.__plusui_dropzone_init) return;
|
|
1030
|
+
window.__plusui_dropzone_init = true;
|
|
1031
|
+
|
|
1032
|
+
var activeZone = null;
|
|
1033
|
+
|
|
1034
|
+
var findDropZone = function(e) {
|
|
1035
|
+
var target = null;
|
|
1036
|
+
if (e && typeof e.clientX === 'number' && document.elementFromPoint) {
|
|
1037
|
+
target = document.elementFromPoint(e.clientX, e.clientY);
|
|
1038
|
+
}
|
|
1039
|
+
if (!target && e && e.target) target = e.target;
|
|
1040
|
+
if (!target || !target.closest) return null;
|
|
1041
|
+
return target.closest('[data-dropzone]');
|
|
1042
|
+
};
|
|
1043
|
+
|
|
1044
|
+
var updateActiveZone = function(zone) {
|
|
1045
|
+
if (activeZone === zone) return;
|
|
1046
|
+
if (activeZone) activeZone.classList.remove('dropzone-active');
|
|
1047
|
+
activeZone = zone;
|
|
1048
|
+
if (activeZone) activeZone.classList.add('dropzone-active');
|
|
1049
|
+
};
|
|
1050
|
+
|
|
1051
|
+
var handleDrag = function(e) {
|
|
1052
|
+
var zone = findDropZone(e);
|
|
1053
|
+
updateActiveZone(zone);
|
|
1054
|
+
if (e.dataTransfer) {
|
|
1055
|
+
try { e.dataTransfer.dropEffect = zone ? 'copy' : 'none'; } catch(_) {}
|
|
1056
|
+
}
|
|
1057
|
+
if (!zone) {
|
|
1058
|
+
e.preventDefault();
|
|
1059
|
+
e.stopPropagation();
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
['dragenter', 'dragover', 'dragleave'].forEach(function(ev) {
|
|
1064
|
+
document.addEventListener(ev, handleDrag, true);
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
document.addEventListener('drop', function(e) {
|
|
1068
|
+
updateActiveZone(null);
|
|
1069
|
+
}, true);
|
|
1070
|
+
})();
|
|
1071
|
+
)";
|
|
1072
|
+
pImpl->webview->AddScriptToExecuteOnDocumentCreated(
|
|
1073
|
+
std::wstring(dropzoneScript.begin(),
|
|
1074
|
+
dropzoneScript.end())
|
|
1075
|
+
.c_str(),
|
|
1076
|
+
nullptr);
|
|
1086
1077
|
}
|
|
1087
1078
|
|
|
1088
1079
|
// Set up WebMessageReceived handler for JS->C++ bridge
|