plusui-native-core 0.1.66 → 0.1.68

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.
@@ -0,0 +1,160 @@
1
+ /**
2
+ * PlusUI Connect API - Custom User Channels
3
+ *
4
+ * This is for user-defined channels generated by `plusui connect` tool.
5
+ * Built-in features (window, clipboard, etc.) use direct methods only.
6
+ *
7
+ * Usage:
8
+ * import { createChannel, connect } from '@plusui/api';
9
+ *
10
+ * // Create a custom channel
11
+ * const search = createChannel('search');
12
+ * search.emit({ query: 'test' });
13
+ * search.on((result) => { ... });
14
+ *
15
+ * // Or use connect directly
16
+ * connect.emit('myEvent', { data: 123 });
17
+ * connect.on('myEvent', (data) => { ... });
18
+ *
19
+ * The `plusui connect` CLI tool scans your codebase for:
20
+ * - TypeScript: name.on() and name.emit()
21
+ * - C++: name.on() and name.emit()
22
+ * And generates typed channel objects for both frontend and backend.
23
+ */
24
+
25
+ import { _client } from '../Features/Connection/connect';
26
+
27
+ /**
28
+ * Channel object returned by createChannel()
29
+ * Mirrors the C++ Connect::Channel API
30
+ */
31
+ export interface Channel<T = unknown> {
32
+ /**
33
+ * Listen for messages on this channel
34
+ * @param callback - Function called when message is received
35
+ * @returns Unsubscribe function
36
+ */
37
+ on(callback: (data: T) => void): () => void;
38
+
39
+ /**
40
+ * Send a message on this channel
41
+ * @param data - Data to send
42
+ */
43
+ emit(data: T): void;
44
+
45
+ /**
46
+ * Channel name
47
+ */
48
+ readonly name: string;
49
+ }
50
+
51
+ /**
52
+ * Create a named channel object
53
+ *
54
+ * @param name - Channel name (must match backend)
55
+ * @returns Channel object with .on() and .emit()
56
+ *
57
+ * @example
58
+ * const download = createChannel<DownloadProgress>('download');
59
+ * download.emit({ url: 'https://example.com/file.zip' });
60
+ * download.on((progress) => console.log(progress.percent));
61
+ */
62
+ export function createChannel<T = unknown>(name: string): Channel<T> {
63
+ return {
64
+ name,
65
+ on: (callback: (data: T) => void): (() => void) => {
66
+ return _client.on<T>(name, callback);
67
+ },
68
+ emit: (data: T): void => {
69
+ _client.fire(name, data);
70
+ }
71
+ };
72
+ }
73
+
74
+ /**
75
+ * Direct connect API for custom channels
76
+ *
77
+ * Use this for quick one-off events, or use createChannel()
78
+ * for typed, reusable channel objects.
79
+ */
80
+ export const connect = {
81
+ /**
82
+ * Send a message on a channel
83
+ * @param name - Channel name
84
+ * @param data - Data to send
85
+ */
86
+ emit: <T = unknown>(name: string, data: T): void => {
87
+ _client.fire(name, data);
88
+ },
89
+
90
+ /**
91
+ * Listen for messages on a channel
92
+ * @param name - Channel name
93
+ * @param callback - Function called when message is received
94
+ * @returns Unsubscribe function
95
+ */
96
+ on: <T = unknown>(name: string, callback: (data: T) => void): (() => void) => {
97
+ return _client.on<T>(name, callback);
98
+ },
99
+
100
+ /**
101
+ * Make a call and wait for response
102
+ * @param name - Channel name
103
+ * @param data - Request data
104
+ * @returns Promise with response
105
+ */
106
+ call: <TResponse = unknown, TRequest = unknown>(name: string, data: TRequest): Promise<TResponse> => {
107
+ return _client.call<TResponse, TRequest>(name, data);
108
+ },
109
+
110
+ /**
111
+ * Create a typed channel object
112
+ * @param name - Channel name
113
+ * @returns Channel object with .on() and .emit()
114
+ */
115
+ channel: <T = unknown>(name: string): Channel<T> => {
116
+ return createChannel<T>(name);
117
+ }
118
+ };
119
+
120
+ /**
121
+ * Feature-scoped connect
122
+ *
123
+ * Creates a connect API that automatically prefixes all channel names
124
+ * with a scope. Useful for organizing channels by feature.
125
+ *
126
+ * @param scope - Feature scope (e.g., 'auth', 'data', 'ui')
127
+ * @returns Scoped connect API
128
+ *
129
+ * @example
130
+ * const authConnect = createFeatureConnect('auth');
131
+ * authConnect.emit('login', { user: 'john' }); // sends to 'auth.login'
132
+ * authConnect.on('logout', () => {}); // listens to 'auth.logout'
133
+ */
134
+ export function createFeatureConnect(scope: string) {
135
+ const scopedName = (name: string): string => {
136
+ const prefix = `${scope}.`;
137
+ if (name.startsWith(prefix)) return name;
138
+ return prefix + name;
139
+ };
140
+
141
+ return {
142
+ emit: <T = unknown>(name: string, data: T): void => {
143
+ _client.fire(scopedName(name), data);
144
+ },
145
+
146
+ on: <T = unknown>(name: string, callback: (data: T) => void): (() => void) => {
147
+ return _client.on<T>(scopedName(name), callback);
148
+ },
149
+
150
+ call: <TResponse = unknown, TRequest = unknown>(name: string, data: TRequest): Promise<TResponse> => {
151
+ return _client.call<TResponse, TRequest>(scopedName(name), data);
152
+ },
153
+
154
+ channel: <T = unknown>(name: string): Channel<T> => {
155
+ return createChannel<T>(scopedName(name));
156
+ }
157
+ };
158
+ }
159
+
160
+ export default connect;
@@ -0,0 +1,127 @@
1
+ /**
2
+ * PlusUI API - Unified Entry Point
3
+ *
4
+ * TWO DISTINCT PATTERNS:
5
+ *
6
+ * 1. BUILT-IN FEATURES - Direct methods only (NO .on/.emit)
7
+ * window.setTitle('My App');
8
+ * window.minimize();
9
+ * clipboard.setText('hello');
10
+ * window.onResize((size) => { ... });
11
+ *
12
+ * 2. CUSTOM CHANNELS - Use .on() and .emit()
13
+ * const search = createChannel('search');
14
+ * search.emit({ query: 'test' });
15
+ * search.on((result) => { ... });
16
+ *
17
+ * The `plusui connect` CLI scans your codebase for name.on() and name.emit()
18
+ * and generates typed channel objects for both frontend and backend.
19
+ */
20
+
21
+ // ============================================================
22
+ // BUILT-IN FEATURES - Direct methods only (NO .on/.emit)
23
+ // ============================================================
24
+
25
+ export { window } from '../Features/Window/window';
26
+ export { clipboard } from '../Features/Clipboard/clipboard';
27
+ export { keyboard } from '../Features/Keyboard/keyboard';
28
+ export { tray } from '../Features/Tray/tray';
29
+ export { display } from '../Features/Display/display';
30
+ export { fileDrop } from '../Features/FileDrop/filedrop';
31
+ export { menu } from '../Features/Menu/menu';
32
+ export { app } from '../Features/App/app';
33
+ export { gpu } from '../Features/WebGPU/webgpu';
34
+
35
+ // ============================================================
36
+ // CUSTOM CHANNELS - For user-defined events (uses .on/.emit)
37
+ // ============================================================
38
+
39
+ export {
40
+ connect,
41
+ createChannel,
42
+ createFeatureConnect,
43
+ type Channel
44
+ } from './Connect_API';
45
+
46
+ // ============================================================
47
+ // TYPES - Re-exported for convenience
48
+ // ============================================================
49
+
50
+ export type {
51
+ WindowSize,
52
+ WindowPosition,
53
+ WindowRect,
54
+ WindowState,
55
+ WindowId
56
+ } from '../Features/Window/window';
57
+
58
+ export type {
59
+ ClipboardAPI
60
+ } from '../Features/Clipboard/clipboard';
61
+
62
+ export type {
63
+ KeyEvent,
64
+ Shortcut,
65
+ KeyEventCallback,
66
+ ShortcutCallback,
67
+ KeyCode,
68
+ KeyMod
69
+ } from '../Features/Keyboard/keyboard';
70
+
71
+ export type {
72
+ TrayMenuItem,
73
+ TrayIconData
74
+ } from '../Features/Tray/tray';
75
+
76
+ export type {
77
+ Display,
78
+ DisplayMode,
79
+ DisplayBounds,
80
+ DisplayResolution
81
+ } from '../Features/Display/display';
82
+
83
+ export type {
84
+ FileInfo,
85
+ DragEvent,
86
+ FileDropAPI
87
+ } from '../Features/FileDrop/filedrop';
88
+
89
+ export type {
90
+ MenuItem,
91
+ MenuItemType,
92
+ MenuBarData,
93
+ ContextMenuOptions,
94
+ ContextInfo
95
+ } from '../Features/Menu/menu';
96
+
97
+ export type {
98
+ AppConfig
99
+ } from '../Features/App/app';
100
+
101
+ export type {
102
+ GPUAdapter,
103
+ GPUDevice,
104
+ GPUBuffer,
105
+ GPUTexture,
106
+ GPUShaderModule,
107
+ GPURenderPipeline,
108
+ GPUComputePipeline,
109
+ GPUQueue,
110
+ GPUCommandEncoder
111
+ } from '../Features/WebGPU/webgpu';
112
+
113
+ // ============================================================
114
+ // HELPER FUNCTIONS
115
+ // ============================================================
116
+
117
+ export {
118
+ readFileAsText,
119
+ readFileAsDataUrl,
120
+ filterFilesByExtension,
121
+ filterFilesByMimeType,
122
+ isImageFile,
123
+ isVideoFile,
124
+ isAudioFile,
125
+ isTextFile,
126
+ formatFileSize
127
+ } from '../Features/FileDrop/filedrop';
@@ -1,131 +1,192 @@
1
- import { connect } from '../Connection/connect';
2
-
3
- export interface AppConfig {
4
- title?: string;
5
- width?: number;
6
- height?: number;
7
- resizable?: boolean;
8
- devtools?: boolean;
9
- trayIcon?: string;
10
- trayTooltip?: string;
11
- alwaysOnTop?: boolean;
12
- centered?: boolean;
13
- transparent?: boolean;
14
- decorations?: boolean;
15
- skipTaskbar?: boolean;
16
- scrollbars?: boolean;
17
- enableFileDrop?: boolean;
18
- }
19
-
20
- export interface InvokeOptions {
21
- timeout?: number;
22
- }
23
-
24
- export class App {
25
- public readonly connect = connect.feature('app');
26
- private initialized = false;
27
-
28
- constructor(
29
- private config: AppConfig = {},
30
- private invokeFn?: (name: string, args?: unknown[]) => Promise<unknown>
31
- ) {
32
- if (typeof window !== 'undefined') {
33
- this.init();
34
- }
35
- }
36
-
37
- private init(): void {
38
- if (this.initialized) return;
39
- this.initialized = true;
40
-
41
- if (typeof window !== 'undefined') {
42
- const win = window as Window & {
43
- __plusui_listeners__?: Map<string, Set<(...args: unknown[]) => void>>;
44
- };
45
-
46
- win.__plusui_listeners__ = new Map();
47
-
48
- window.addEventListener('message', (event: MessageEvent) => {
49
- if (event.data?.type === '__plusui_event__') {
50
- const { event: eventName, payload } = event.data;
51
- const listeners = win.__plusui_listeners__?.get(eventName);
52
- if (listeners) {
53
- listeners.forEach(cb => cb(payload));
54
- }
55
- }
56
- });
57
- }
58
- }
59
-
60
- async invoke<T = unknown>(name: string, args?: unknown[], options: InvokeOptions = {}): Promise<T> {
61
- const { timeout = 30000 } = options;
62
-
63
- if (this.invokeFn) {
64
- return this.invokeFn(name, args) as Promise<T>;
65
- }
66
-
67
- if (typeof window !== 'undefined') {
68
- const win = window as Window & {
69
- __plusui_invoke__?: (name: string, args?: unknown[]) => Promise<unknown>;
70
- __invoke__?: (name: string, args?: unknown[]) => Promise<unknown>;
71
- __native_invoke__?: (request: string) => void;
72
- };
73
- // Check for SDK bridge first (__invoke__ from plusui-native-core)
74
- if (win.__invoke__) {
75
- return win.__invoke__(name, args) as Promise<T>;
76
- }
77
- // Check for legacy __plusui_invoke__
78
- if (win.__plusui_invoke__) {
79
- return win.__plusui_invoke__(name, args) as Promise<T>;
80
- }
81
- }
82
-
83
- return new Promise((_, reject) => {
84
- setTimeout(() => reject(new Error(`Invocation ${name} timed out`)), timeout);
85
- });
86
- }
87
-
88
- on(event: string, callback: (...args: unknown[]) => void): () => void {
89
- if (typeof window === 'undefined') {
90
- return () => { };
91
- }
92
-
93
- const win = window as Window & {
94
- __plusui_listeners__?: Map<string, Set<(...args: unknown[]) => void>>;
95
- };
96
-
97
- if (!win.__plusui_listeners__) {
98
- win.__plusui_listeners__ = new Map();
99
- }
100
-
101
- if (!win.__plusui_listeners__.has(event)) {
102
- win.__plusui_listeners__.set(event, new Set());
103
- }
104
-
105
- win.__plusui_listeners__.get(event)!.add(callback);
106
-
107
- return () => {
108
- win.__plusui_listeners__.get(event)?.delete(callback);
109
- };
110
- }
111
-
112
- once(event: string, callback: (...args: unknown[]) => void): void {
113
- const unsubscribe = this.on(event, (...args) => {
114
- unsubscribe();
115
- callback(...args);
116
- });
117
- }
118
-
119
- emit(event: string, payload?: unknown): void {
120
- if (typeof window !== 'undefined') {
121
- const customEvent = new CustomEvent(event, { detail: payload });
122
- window.dispatchEvent(customEvent);
123
- }
124
- }
125
- }
126
-
127
- export function createApp(config?: AppConfig): App {
128
- return new App(config);
129
- }
130
-
131
- export default App;
1
+ /**
2
+ * App API - Application lifecycle
3
+ *
4
+ * Direct method calls - no .on/.emit
5
+ *
6
+ * Usage:
7
+ * import { app } from '@plusui/api';
8
+ *
9
+ * app.quit();
10
+ * app.onReady(() => { ... });
11
+ * app.onQuit(() => { ... });
12
+ */
13
+
14
+ export interface AppConfig {
15
+ title?: string;
16
+ width?: number;
17
+ height?: number;
18
+ resizable?: boolean;
19
+ devtools?: boolean;
20
+ trayIcon?: string;
21
+ trayTooltip?: string;
22
+ alwaysOnTop?: boolean;
23
+ centered?: boolean;
24
+ transparent?: boolean;
25
+ decorations?: boolean;
26
+ skipTaskbar?: boolean;
27
+ scrollbars?: boolean;
28
+ enableFileDrop?: boolean;
29
+ }
30
+
31
+ export interface InvokeOptions {
32
+ timeout?: number;
33
+ }
34
+
35
+ let _invoke: ((name: string, args?: unknown[]) => Promise<unknown>) | null = null;
36
+ let _event: ((event: string, callback: (...args: unknown[]) => void) => () => void) | null = null;
37
+ let _initialized = false;
38
+
39
+ export function setInvokeFn(fn: (name: string, args?: unknown[]) => Promise<unknown>) {
40
+ _invoke = fn;
41
+ }
42
+
43
+ export function setEventFn(fn: (event: string, callback: (...args: unknown[]) => void) => () => void) {
44
+ _event = fn;
45
+ }
46
+
47
+ function init(): void {
48
+ if (_initialized) return;
49
+ _initialized = true;
50
+
51
+ if (typeof window !== 'undefined') {
52
+ const win = window as Window & {
53
+ __plusui_listeners__?: Map<string, Set<(...args: unknown[]) => void>>;
54
+ };
55
+
56
+ win.__plusui_listeners__ = new Map();
57
+
58
+ window.addEventListener('message', (event: MessageEvent) => {
59
+ if (event.data?.type === '__plusui_event__') {
60
+ const { event: eventName, payload } = event.data;
61
+ const listeners = win.__plusui_listeners__?.get(eventName);
62
+ if (listeners) {
63
+ listeners.forEach(cb => cb(payload));
64
+ }
65
+ }
66
+ });
67
+ }
68
+ }
69
+
70
+ async function invoke<T = unknown>(name: string, args?: unknown[], options: InvokeOptions = {}): Promise<T> {
71
+ const { timeout = 30000 } = options;
72
+
73
+ if (_invoke) {
74
+ return _invoke(name, args) as Promise<T>;
75
+ }
76
+
77
+ if (typeof window !== 'undefined') {
78
+ const win = window as Window & {
79
+ __invoke__?: (name: string, args?: unknown[]) => Promise<unknown>;
80
+ __native_invoke__?: (request: string) => void;
81
+ };
82
+ if (win.__invoke__) {
83
+ return win.__invoke__(name, args) as Promise<T>;
84
+ }
85
+ }
86
+
87
+ return new Promise((_, reject) => {
88
+ setTimeout(() => reject(new Error(`Invocation ${name} timed out`)), timeout);
89
+ });
90
+ }
91
+
92
+ function eventHandler(event: string, callback: (...args: unknown[]) => void): () => void {
93
+ init();
94
+
95
+ if (!_event) {
96
+ if (typeof window !== 'undefined' && (window as any).__on__) {
97
+ _event = (window as any).__on__;
98
+ } else {
99
+ const win = window as Window & {
100
+ __plusui_listeners__?: Map<string, Set<(...args: unknown[]) => void>>;
101
+ };
102
+
103
+ if (!win.__plusui_listeners__) {
104
+ win.__plusui_listeners__ = new Map();
105
+ }
106
+
107
+ const listeners = win.__plusui_listeners__!;
108
+
109
+ if (!listeners.has(event)) {
110
+ listeners.set(event, new Set());
111
+ }
112
+
113
+ listeners.get(event)!.add(callback);
114
+
115
+ return () => {
116
+ listeners.get(event)?.delete(callback);
117
+ };
118
+ }
119
+ }
120
+ return _event!(event, callback);
121
+ }
122
+
123
+ export const app = {
124
+ async invoke<T = unknown>(name: string, args?: unknown[], options: InvokeOptions = {}): Promise<T> {
125
+ return invoke<T>(name, args, options);
126
+ },
127
+
128
+ async quit(): Promise<void> {
129
+ await invoke('app.quit');
130
+ },
131
+
132
+ async getVersion(): Promise<string> {
133
+ return invoke<string>('app.getVersion');
134
+ },
135
+
136
+ async getName(): Promise<string> {
137
+ return invoke<string>('app.getName');
138
+ },
139
+
140
+ async getPath(name: 'home' | 'appData' | 'userData' | 'temp' | 'desktop' | 'documents' | 'downloads'): Promise<string> {
141
+ return invoke<string>('app.getPath', [name]);
142
+ },
143
+
144
+ async setBadge(count: number): Promise<void> {
145
+ await invoke('app.setBadge', [count]);
146
+ },
147
+
148
+ async getBadge(): Promise<number> {
149
+ return invoke<number>('app.getBadge');
150
+ },
151
+
152
+ onReady(callback: () => void): () => void {
153
+ return eventHandler('app:ready', callback);
154
+ },
155
+
156
+ onQuit(callback: (reason?: string) => void): () => void {
157
+ return eventHandler('app:quit', callback as (...args: unknown[]) => void);
158
+ },
159
+
160
+ onActivate(callback: () => void): () => void {
161
+ return eventHandler('app:activate', callback);
162
+ },
163
+
164
+ onWindowAllClosed(callback: () => void): () => void {
165
+ return eventHandler('app:windowAllClosed', callback);
166
+ },
167
+
168
+ onSecondInstance(callback: (args: string[], cwd: string) => void): () => void {
169
+ return eventHandler('app:secondInstance', callback as (...args: unknown[]) => void);
170
+ },
171
+
172
+ on(event: string, callback: (...args: unknown[]) => void): () => void {
173
+ return eventHandler(event, callback);
174
+ },
175
+
176
+ once(event: string, callback: (...args: unknown[]) => void): () => void {
177
+ const unsubscribe = this.on(event, (...args) => {
178
+ unsubscribe();
179
+ callback(...args);
180
+ });
181
+ return unsubscribe;
182
+ },
183
+
184
+ emit(event: string, payload?: unknown): void {
185
+ if (typeof window !== 'undefined') {
186
+ const customEvent = new CustomEvent(event, { detail: payload });
187
+ window.dispatchEvent(customEvent);
188
+ }
189
+ },
190
+ };
191
+
192
+ export default app;