plusui-native-core 0.1.102 → 0.1.104
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/Features/API/Connect_API.ts +62 -146
- package/Core/Features/API/filedrop-api.ts +12 -1
- package/Core/Features/API/index.ts +3 -4
- package/Core/Features/App/app.cpp +32 -32
- package/Core/Features/Connection/README.md +176 -52
- package/Core/Features/Connection/connect.ts +136 -21
- package/Core/Features/FileDrop/filedrop.ts +139 -36
- package/Core/Features/Keyboard/keyboard.cpp +218 -189
- package/Core/Features/Keyboard/keyboard.ts +100 -31
- package/Core/Features/Keyboard/keyboard_linux.cpp +226 -0
- package/Core/Features/Keyboard/keyboard_macos.cpp +220 -0
- package/Core/Features/Keyboard/keyboard_windows.cpp +56 -0
- package/Core/Features/Window/webview.cpp +248 -57
- package/Core/Features/Window/webview.ts +1 -1
- package/Core/Features/Window/window.cpp +433 -63
- package/Core/include/plusui/api.hpp +2 -2
- package/Core/include/plusui/app.hpp +41 -41
- package/Core/include/plusui/connect.hpp +125 -81
- package/Core/include/plusui/window.hpp +38 -40
- package/package.json +1 -1
|
@@ -1,160 +1,76 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PlusUI Connect API
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* import
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
2
|
+
* PlusUI Connect API — Semantic Frontend ↔ Backend Communication
|
|
3
|
+
*
|
|
4
|
+
* ZERO CONFIG. SEMANTIC SYNTAX. ALL 5 PATTERNS.
|
|
5
|
+
*
|
|
6
|
+
* IMPORTANT: `connect` is for CUSTOM user-defined communication only!
|
|
7
|
+
* Built-in features (window, clipboard, app, etc.) use direct imports:
|
|
8
|
+
* import plusui from 'plusui';
|
|
9
|
+
* plusui.window.minimize();
|
|
10
|
+
* plusui.clipboard.setText('hello');
|
|
11
|
+
*
|
|
12
|
+
* For custom communication, use `connect.namespace.method()`:
|
|
13
|
+
*
|
|
14
|
+
* import { connect } from 'plusui';
|
|
15
|
+
*
|
|
16
|
+
* // Request/Response — await a call to the backend
|
|
17
|
+
* const user = await connect.user.fetch(123);
|
|
18
|
+
*
|
|
19
|
+
* // Fire & Forget — one-way notification
|
|
20
|
+
* connect.files.upload({ file: myFile });
|
|
21
|
+
*
|
|
22
|
+
* // Event Listener — listen for backend events
|
|
23
|
+
* connect.app.onNotify((msg) => console.log(msg));
|
|
24
|
+
*
|
|
25
|
+
* // Register a handler — backend can call frontend
|
|
26
|
+
* connect.ui.handlePrompt = async (data) => {
|
|
27
|
+
* return await Dialog.confirm(data.msg);
|
|
28
|
+
* };
|
|
29
|
+
*
|
|
30
|
+
* Then run `plusui connect` to generate typed bindings.
|
|
31
|
+
*
|
|
32
|
+
* Pattern detection is automatic:
|
|
33
|
+
* - `await connect.x.y()` → CALL (Request/Response)
|
|
34
|
+
* - `connect.x.onY(cb)` → EVENT (Subscribe)
|
|
35
|
+
* - `connect.x.handleY = fn` → HANDLER (Backend calls frontend)
|
|
36
|
+
* - `connect.x.y()` → FIRE (Simplex, one-way)
|
|
23
37
|
*/
|
|
24
38
|
|
|
25
|
-
import { _client } from '../Connection/connect';
|
|
39
|
+
import { connect, _client, createFeatureConnect } from '../Connection/connect';
|
|
40
|
+
import type { FeatureConnect, ConnectionKind, ConnectionEnvelope } from '../Connection/connect';
|
|
26
41
|
|
|
27
42
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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));
|
|
43
|
+
* connect — Semantic channel proxy
|
|
44
|
+
*
|
|
45
|
+
* Usage:
|
|
46
|
+
* connect.namespace.method(...args)
|
|
47
|
+
*
|
|
48
|
+
* The proxy routes to the correct wire format automatically:
|
|
49
|
+
* connect.user.fetch(123) → _client.call('user.fetch', 123)
|
|
50
|
+
* connect.app.onNotify(cb) → _client.on('app.onNotify', cb)
|
|
51
|
+
* connect.ui.handlePrompt = fn → _client.handle('ui.handlePrompt', fn)
|
|
52
|
+
* connect.system.minimize() → _client.fire('system.minimize', {})
|
|
53
|
+
*
|
|
54
|
+
* Works dynamically even before running `plusui connect`.
|
|
55
|
+
* After running `plusui connect`, you get IDE autocomplete.
|
|
61
56
|
*/
|
|
62
|
-
export
|
|
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
|
-
}
|
|
57
|
+
export { connect };
|
|
73
58
|
|
|
74
59
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
60
|
+
* _client — Low-level connection client
|
|
61
|
+
*
|
|
62
|
+
* Direct access to the underlying client for advanced use cases.
|
|
63
|
+
* Prefer using `connect.namespace.method()` for normal communication.
|
|
79
64
|
*/
|
|
80
|
-
export
|
|
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
|
-
};
|
|
65
|
+
export { _client };
|
|
119
66
|
|
|
120
67
|
/**
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* Creates a connect API
|
|
124
|
-
*
|
|
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'
|
|
68
|
+
* createFeatureConnect — Scoped connect for framework internals
|
|
69
|
+
*
|
|
70
|
+
* Creates a connect API scoped to a feature namespace.
|
|
71
|
+
* Used internally by PlusUI features like window, clipboard, etc.
|
|
133
72
|
*/
|
|
134
|
-
export
|
|
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
|
-
}
|
|
73
|
+
export { createFeatureConnect };
|
|
159
74
|
|
|
75
|
+
export type { FeatureConnect, ConnectionKind, ConnectionEnvelope };
|
|
160
76
|
export default connect;
|
|
@@ -1,2 +1,13 @@
|
|
|
1
|
+
export {
|
|
2
|
+
fileDrop,
|
|
3
|
+
createDropZone,
|
|
4
|
+
formatFileSize,
|
|
5
|
+
filterFilesByExtension,
|
|
6
|
+
filterFilesByMimeType,
|
|
7
|
+
isImageFile,
|
|
8
|
+
isVideoFile,
|
|
9
|
+
isAudioFile,
|
|
10
|
+
isTextFile
|
|
11
|
+
} from '../FileDrop/filedrop';
|
|
12
|
+
|
|
1
13
|
export type { FileInfo, DropZone } from '../FileDrop/filedrop';
|
|
2
|
-
export { fileDrop, formatFileSize, createDropZone } from '../FileDrop/filedrop';
|
|
@@ -10,11 +10,11 @@ export { router } from './router-api';
|
|
|
10
10
|
export { keyboard, KeyCode, KeyMod } from './keyboard-api';
|
|
11
11
|
export { tray } from './tray-api';
|
|
12
12
|
export { display } from './display-api';
|
|
13
|
-
export { fileDrop, formatFileSize, createDropZone } from './filedrop-api';
|
|
13
|
+
export { fileDrop, formatFileSize, createDropZone, filterFilesByExtension, filterFilesByMimeType, isImageFile, isVideoFile, isAudioFile, isTextFile } from './filedrop-api';
|
|
14
14
|
export { menu } from './menu-api';
|
|
15
15
|
export { gpu, GPUBufferUsage, GPUTextureUsage, GPUMapMode, GPUShaderStage, GPUColorWrite } from './webgpu-api';
|
|
16
16
|
|
|
17
|
-
export { connect
|
|
17
|
+
export { connect } from './Connect_API';
|
|
18
18
|
export { _client } from '../Connection/connect';
|
|
19
19
|
|
|
20
20
|
export type { WindowSize, WindowPosition, WindowRect } from './window-api';
|
|
@@ -40,7 +40,7 @@ import { display } from './display-api';
|
|
|
40
40
|
import { fileDrop, formatFileSize, createDropZone } from './filedrop-api';
|
|
41
41
|
import { menu } from './menu-api';
|
|
42
42
|
import { gpu, GPUBufferUsage, GPUTextureUsage, GPUMapMode, GPUShaderStage, GPUColorWrite } from './webgpu-api';
|
|
43
|
-
import { connect
|
|
43
|
+
import { connect } from './Connect_API';
|
|
44
44
|
|
|
45
45
|
const plusui = {
|
|
46
46
|
window,
|
|
@@ -58,7 +58,6 @@ const plusui = {
|
|
|
58
58
|
menu,
|
|
59
59
|
gpu,
|
|
60
60
|
connect,
|
|
61
|
-
createChannel,
|
|
62
61
|
formatFileSize,
|
|
63
62
|
KeyCode,
|
|
64
63
|
KeyMod,
|
|
@@ -77,38 +77,38 @@ App::Builder &App::Builder::transparent(bool transparent) {
|
|
|
77
77
|
App::Builder &App::Builder::decorations(bool decorations) {
|
|
78
78
|
config.decorations = decorations;
|
|
79
79
|
return *this;
|
|
80
|
-
}
|
|
81
|
-
App::Builder &App::Builder::skipTaskbar(bool skip) {
|
|
82
|
-
config.skipTaskbar = skip;
|
|
83
|
-
return *this;
|
|
84
|
-
}
|
|
85
|
-
App::Builder &App::Builder::scrollbars(bool show) {
|
|
86
|
-
config.scrollbars = show;
|
|
87
|
-
return *this;
|
|
88
|
-
}
|
|
89
|
-
App::Builder &App::Builder::
|
|
90
|
-
config.
|
|
91
|
-
return *this;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
Window App::Builder::build() {
|
|
95
|
-
WindowConfig winConfig;
|
|
96
|
-
winConfig.title = config.title;
|
|
97
|
-
winConfig.width = config.width;
|
|
98
|
-
winConfig.height = config.height;
|
|
99
|
-
winConfig.resizable = config.resizable;
|
|
100
|
-
winConfig.alwaysOnTop = config.alwaysOnTop;
|
|
101
|
-
winConfig.center = config.centered;
|
|
102
|
-
winConfig.transparent = config.transparent;
|
|
103
|
-
winConfig.decorations = config.decorations;
|
|
104
|
-
winConfig.skipTaskbar = config.skipTaskbar;
|
|
105
|
-
winConfig.
|
|
106
|
-
|
|
107
|
-
// WebView configuration (now part of WindowConfig)
|
|
108
|
-
winConfig.devtools = config.devtools;
|
|
109
|
-
winConfig.scrollbars = config.scrollbars;
|
|
110
|
-
winConfig.disableWebviewDragDrop =
|
|
111
|
-
config.
|
|
80
|
+
}
|
|
81
|
+
App::Builder &App::Builder::skipTaskbar(bool skip) {
|
|
82
|
+
config.skipTaskbar = skip;
|
|
83
|
+
return *this;
|
|
84
|
+
}
|
|
85
|
+
App::Builder &App::Builder::scrollbars(bool show) {
|
|
86
|
+
config.scrollbars = show;
|
|
87
|
+
return *this;
|
|
88
|
+
}
|
|
89
|
+
App::Builder &App::Builder::fileDrop(bool enable) {
|
|
90
|
+
config.fileDrop = enable;
|
|
91
|
+
return *this;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
Window App::Builder::build() {
|
|
95
|
+
WindowConfig winConfig;
|
|
96
|
+
winConfig.title = config.title;
|
|
97
|
+
winConfig.width = config.width;
|
|
98
|
+
winConfig.height = config.height;
|
|
99
|
+
winConfig.resizable = config.resizable;
|
|
100
|
+
winConfig.alwaysOnTop = config.alwaysOnTop;
|
|
101
|
+
winConfig.center = config.centered;
|
|
102
|
+
winConfig.transparent = config.transparent;
|
|
103
|
+
winConfig.decorations = config.decorations;
|
|
104
|
+
winConfig.skipTaskbar = config.skipTaskbar;
|
|
105
|
+
winConfig.fileDrop = config.fileDrop;
|
|
106
|
+
|
|
107
|
+
// WebView configuration (now part of WindowConfig)
|
|
108
|
+
winConfig.devtools = config.devtools;
|
|
109
|
+
winConfig.scrollbars = config.scrollbars;
|
|
110
|
+
winConfig.disableWebviewDragDrop =
|
|
111
|
+
config.fileDrop; // Auto-disable browser drag-drop when OS file drop enabled
|
|
112
112
|
|
|
113
113
|
// Create native window
|
|
114
114
|
auto nativeWinPtr = std::make_shared<Window>(Window::create(winConfig));
|
|
@@ -1,66 +1,158 @@
|
|
|
1
|
-
# PlusUI
|
|
1
|
+
# PlusUI Connect
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Semantic syntax. Zero config. All 5 patterns.
|
|
4
4
|
|
|
5
5
|
```typescript
|
|
6
|
-
// TypeScript
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
// TypeScript — just write what you mean
|
|
7
|
+
const user = await connect.user.fetch(123);
|
|
8
|
+
connect.app.onNotify((msg) => console.log(msg));
|
|
9
|
+
connect.system.minimize();
|
|
9
10
|
```
|
|
10
11
|
|
|
11
12
|
```cpp
|
|
12
|
-
// C++
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
// C++ — identical semantic style
|
|
14
|
+
connect::user.handleFetch = [](const json& p) -> json {
|
|
15
|
+
return database.getUser(p["id"]);
|
|
16
|
+
};
|
|
17
|
+
connect::app.notify({{"msg", "Hello!"}});
|
|
15
18
|
```
|
|
16
19
|
|
|
17
20
|
## Quick Start
|
|
18
21
|
|
|
19
|
-
### 1. Write your code
|
|
22
|
+
### 1. Write your code — anywhere
|
|
20
23
|
|
|
24
|
+
**Frontend (TypeScript):**
|
|
21
25
|
```typescript
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
import { connect } from 'plusui';
|
|
27
|
+
|
|
28
|
+
// Call the backend and get a response
|
|
29
|
+
const user = await connect.database.getUser(123);
|
|
24
30
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
// Listen for events from the backend
|
|
32
|
+
connect.app.onNotify((msg) => {
|
|
33
|
+
toast.success(msg);
|
|
28
34
|
});
|
|
35
|
+
|
|
36
|
+
// Fire a one-way command
|
|
37
|
+
connect.system.minimize();
|
|
29
38
|
```
|
|
30
39
|
|
|
40
|
+
**Backend (C++):**
|
|
31
41
|
```cpp
|
|
32
|
-
|
|
42
|
+
#include <plusui/plusui>
|
|
33
43
|
#include "Connections/connections.gen.hpp"
|
|
34
44
|
|
|
35
|
-
|
|
45
|
+
// Handle calls from the frontend
|
|
46
|
+
connect::database.handleGetUser = [](const json& p) -> json {
|
|
47
|
+
auto id = p.get<int>();
|
|
48
|
+
return {{"name", "Dannan"}, {"id", id}};
|
|
49
|
+
};
|
|
36
50
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
51
|
+
// Send events to the frontend
|
|
52
|
+
connect::app.notify({{"msg", "Backend ready!"}});
|
|
53
|
+
|
|
54
|
+
// Listen for one-way commands from the frontend
|
|
55
|
+
connect::system.onMinimize = [](const json&) {
|
|
56
|
+
window.minimize();
|
|
57
|
+
};
|
|
41
58
|
```
|
|
42
59
|
|
|
43
|
-
### 2. Generate
|
|
60
|
+
### 2. Generate bindings
|
|
44
61
|
|
|
45
62
|
```bash
|
|
46
63
|
plusui connect
|
|
47
64
|
```
|
|
48
65
|
|
|
49
|
-
Scans your `.ts`, `.tsx`, `.cpp`, `.hpp` files for
|
|
50
|
-
- `Connections/connections.gen.ts`
|
|
51
|
-
- `Connections/connections.gen.hpp`
|
|
66
|
+
Scans your `.ts`, `.tsx`, `.cpp`, `.hpp` files for `connect.namespace.method()` patterns and generates:
|
|
67
|
+
- `Connections/connections.gen.ts` — Typed semantic API for frontend
|
|
68
|
+
- `Connections/connections.gen.hpp` — Typed semantic API for backend
|
|
69
|
+
|
|
70
|
+
### 3. That's it.
|
|
71
|
+
|
|
72
|
+
No schemas. No config files. No imports to manage (beyond `plusui`).
|
|
73
|
+
Write your code, run `plusui connect`, done.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## The 5 Communication Patterns
|
|
78
|
+
|
|
79
|
+
All 5 patterns work **bidirectionally** — frontend ↔ backend. The pattern used depends on which side is sending vs receiving.
|
|
80
|
+
|
|
81
|
+
### 1. Request-Response (Call)
|
|
82
|
+
|
|
83
|
+
A request is sent, processed, and a response is returned. Works both ways.
|
|
52
84
|
|
|
53
|
-
|
|
85
|
+
| Sender | Receiver |
|
|
86
|
+
|--------|----------|
|
|
87
|
+
| `await connect.user.fetch(123)` | `connect::user.handleFetch = [](json p) -> json { ... }` |
|
|
88
|
+
| `connect.call("ui.prompt", data)` | `connect.ui.handlePrompt = async (data) => { ... }` |
|
|
54
89
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
|
60
|
-
|
|
61
|
-
|
|
|
90
|
+
### 2. Simplex (Fire & Forget)
|
|
91
|
+
|
|
92
|
+
One-way message. Sender doesn't wait for a response. Works both ways.
|
|
93
|
+
|
|
94
|
+
| Sender | Receiver |
|
|
95
|
+
|--------|----------|
|
|
96
|
+
| `connect.files.upload({ file })` | `connect::files.onUpload = [](json) { ... }` |
|
|
97
|
+
| `connect::app.notify(data)` | `connect.app.onNotify((msg) => { ... })` |
|
|
98
|
+
|
|
99
|
+
### 3. Publish-Subscribe (One-to-Many)
|
|
100
|
+
|
|
101
|
+
Broadcast to multiple listeners. Works both ways.
|
|
102
|
+
|
|
103
|
+
| Sender | Subscribers |
|
|
104
|
+
|--------|-------------|
|
|
105
|
+
| `connect::sensors.publishTemp(data)` | `connect.sensors.onTemp(cb)` |
|
|
106
|
+
| `connect.auth.login(data)` | `connect::auth.onLogin = [](json) { ... }` |
|
|
107
|
+
|
|
108
|
+
### 4. Full-Duplex (Bidirectional Stream)
|
|
109
|
+
|
|
110
|
+
Both sides send and receive continuously. Works both ways.
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// Frontend: stream to backend
|
|
114
|
+
connect.files.onChunk(chunk);
|
|
115
|
+
connect.files.onProgress((p) => console.log(p));
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```cpp
|
|
119
|
+
// Backend: receive and stream back
|
|
120
|
+
connect::files.onChunk = [](json chunk) { saveFile(chunk); };
|
|
121
|
+
connect::files.progress({{"percent", 50}});
|
|
122
|
+
```
|
|
62
123
|
|
|
63
|
-
|
|
124
|
+
### 5. Half-Duplex (State Sync)
|
|
125
|
+
|
|
126
|
+
Both sides can update, but one at a time. Works both ways.
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// Frontend updates and listens
|
|
130
|
+
connect.settings.setTheme("dark");
|
|
131
|
+
connect.settings.onThemeChange((t) => applyTheme(t));
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```cpp
|
|
135
|
+
// Backend updates and listens
|
|
136
|
+
connect::settings.onSetTheme = [](json p) { applyNativeTheme(p); };
|
|
137
|
+
connect::settings.themeChange({{"mode", "dark"}});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## How Patterns Are Detected
|
|
143
|
+
|
|
144
|
+
The `plusui connect` tool automatically infers the communication pattern from your code:
|
|
145
|
+
|
|
146
|
+
| Your Code | Detected Pattern |
|
|
147
|
+
|-----------|-----------------|
|
|
148
|
+
| `await connect.x.y()` | **Call** (Request/Response) |
|
|
149
|
+
| `connect.x.onY(callback)` | **Event** (Listener/Subscribe) |
|
|
150
|
+
| `connect::x.handleY = fn` | **Call Handler** (Backend responds) |
|
|
151
|
+
| `connect.x.y()` (no await) | **Fire** (Simplex/One-way) |
|
|
152
|
+
|
|
153
|
+
You never need to specify the pattern. Just write natural, semantic code.
|
|
154
|
+
|
|
155
|
+
---
|
|
64
156
|
|
|
65
157
|
## Generated Files
|
|
66
158
|
|
|
@@ -69,12 +161,17 @@ All handled with just `.on()` and `.emit()`.
|
|
|
69
161
|
```typescript
|
|
70
162
|
import { _client } from 'plusui';
|
|
71
163
|
|
|
72
|
-
export const
|
|
73
|
-
|
|
74
|
-
emit: (data: unknown) => _client.fire('download', data)
|
|
164
|
+
export const user = {
|
|
165
|
+
fetch: <T = unknown>(...args: unknown[]) => _client.call<T>('user.fetch', args[0]),
|
|
75
166
|
};
|
|
76
167
|
|
|
77
|
-
export const
|
|
168
|
+
export const app = {
|
|
169
|
+
onNotify: <T = unknown>(cb: (data: T) => void) => _client.on<T>('app.onNotify', cb),
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export const system = {
|
|
173
|
+
minimize: (...args: unknown[]) => _client.fire('system.minimize', args[0]),
|
|
174
|
+
};
|
|
78
175
|
```
|
|
79
176
|
|
|
80
177
|
### C++ (`connections.gen.hpp`)
|
|
@@ -83,29 +180,56 @@ export const CONNECT_CHANNELS = ['download'] as const;
|
|
|
83
180
|
#pragma once
|
|
84
181
|
#include <plusui/plusui.hpp>
|
|
85
182
|
|
|
86
|
-
namespace
|
|
87
|
-
inline plusui::Connect*
|
|
88
|
-
inline plusui::Connect::Channel download;
|
|
89
|
-
}
|
|
183
|
+
namespace connect {
|
|
184
|
+
inline plusui::Connect* _conn = nullptr;
|
|
90
185
|
|
|
91
|
-
inline
|
|
92
|
-
|
|
93
|
-
|
|
186
|
+
inline struct user_t {
|
|
187
|
+
std::function<nlohmann::json(const nlohmann::json&)> handleFetch;
|
|
188
|
+
} user;
|
|
189
|
+
|
|
190
|
+
inline struct app_t {
|
|
191
|
+
void notify(const nlohmann::json& p = nlohmann::json::object()) {
|
|
192
|
+
if (_conn) _conn->emit("app.notify", p);
|
|
193
|
+
}
|
|
194
|
+
} app;
|
|
195
|
+
|
|
196
|
+
inline struct system_t {
|
|
197
|
+
std::function<void(const nlohmann::json&)> onMinimize;
|
|
198
|
+
} system;
|
|
94
199
|
}
|
|
95
200
|
|
|
96
|
-
|
|
97
|
-
|
|
201
|
+
inline void initConnect(plusui::Connect& c) {
|
|
202
|
+
connect::_conn = &c;
|
|
203
|
+
c.onCall("user.handleFetch", [](const nlohmann::json& p) -> nlohmann::json {
|
|
204
|
+
if (connect::user.handleFetch) return connect::user.handleFetch(p);
|
|
205
|
+
return nlohmann::json::object();
|
|
206
|
+
});
|
|
207
|
+
c.on("system.onMinimize", [](const nlohmann::json& p) {
|
|
208
|
+
if (connect::system.onMinimize) connect::system.onMinimize(p);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
98
211
|
```
|
|
99
212
|
|
|
100
|
-
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Built-in Features
|
|
216
|
+
|
|
217
|
+
Built-in features use direct imports from `plusui` — they're not custom channels:
|
|
101
218
|
|
|
102
219
|
```typescript
|
|
103
|
-
import
|
|
220
|
+
import plusui from 'plusui';
|
|
104
221
|
|
|
105
|
-
|
|
106
|
-
clipboard.
|
|
222
|
+
plusui.window.minimize();
|
|
223
|
+
plusui.clipboard.write('copied!');
|
|
107
224
|
```
|
|
108
225
|
|
|
109
|
-
##
|
|
226
|
+
## Legacy Support
|
|
227
|
+
|
|
228
|
+
The old flat `name.on()` / `name.emit()` pattern still works for backward compatibility:
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
download.on((data) => console.log(data.progress));
|
|
232
|
+
download.emit({ url: 'https://example.com' });
|
|
233
|
+
```
|
|
110
234
|
|
|
111
|
-
|
|
235
|
+
These are detected alongside the new semantic patterns.
|