plusui-native-core 0.1.57 → 0.1.61
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/Browser/browser.ts +20 -2
- package/Core/Features/Clipboard/clipboard.ts +11 -0
- package/Core/Features/Connection/README.md +46 -9
- package/Core/Features/Display/display.ts +8 -0
- package/Core/Features/FileDrop/filedrop.ts +11 -0
- package/Core/Features/Keyboard/keyboard.ts +8 -0
- package/Core/Features/Menu/menu.ts +8 -0
- package/Core/Features/Tray/tray.ts +8 -0
- package/Core/Features/WebGPU/webgpu.ts +8 -0
- package/Core/Features/Window/window.ts +11 -1
- package/Core/include/plusui/connect.hpp +77 -0
- package/Core/include/plusui/plusui.hpp +0 -1
- package/package.json +1 -1
- package/Core/include/plusui/native_bindings.hpp +0 -19
|
@@ -38,6 +38,8 @@ const _loadErrorCallbacks: ErrorCallback[] = [];
|
|
|
38
38
|
let _currentState: BrowserState = { url: '', title: '', canGoBack: false, canGoForward: false, isLoading: false };
|
|
39
39
|
let _routes: Record<string, string> = {};
|
|
40
40
|
let _currentRoute: string = '/';
|
|
41
|
+
const browserFeatureEvents = connect.feature('browser');
|
|
42
|
+
const routerFeatureEvents = connect.feature('router');
|
|
41
43
|
|
|
42
44
|
// Setup event listeners from native backend
|
|
43
45
|
if (typeof window !== 'undefined') {
|
|
@@ -64,7 +66,15 @@ if (typeof window !== 'undefined') {
|
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
export const browser = {
|
|
67
|
-
connect:
|
|
69
|
+
connect: browserFeatureEvents,
|
|
70
|
+
|
|
71
|
+
on: <TData = unknown>(name: string, callback: (payload: TData) => void): (() => void) => {
|
|
72
|
+
return browserFeatureEvents.on<TData>(name, callback);
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
emit: <TIn = Record<string, unknown>>(name: string, payload: TIn): void => {
|
|
76
|
+
browserFeatureEvents.emit<TIn>(name, payload);
|
|
77
|
+
},
|
|
68
78
|
|
|
69
79
|
async navigate(url: string): Promise<void> {
|
|
70
80
|
await invoke('browser.navigate', [url]);
|
|
@@ -164,7 +174,15 @@ export const browser = {
|
|
|
164
174
|
};
|
|
165
175
|
|
|
166
176
|
export const router = {
|
|
167
|
-
connect:
|
|
177
|
+
connect: routerFeatureEvents,
|
|
178
|
+
|
|
179
|
+
on: <TData = unknown>(name: string, callback: (payload: TData) => void): (() => void) => {
|
|
180
|
+
return routerFeatureEvents.on<TData>(name, callback);
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
emit: <TIn = Record<string, unknown>>(name: string, payload: TIn): void => {
|
|
184
|
+
routerFeatureEvents.emit<TIn>(name, payload);
|
|
185
|
+
},
|
|
168
186
|
|
|
169
187
|
setRoutes(routes: Record<string, string>): void {
|
|
170
188
|
_routes = routes;
|
|
@@ -8,6 +8,9 @@ import { connect } from '../Connection/connect';
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
export interface ClipboardAPI {
|
|
11
|
+
on<TData = unknown>(name: string, callback: (payload: TData) => void): () => void;
|
|
12
|
+
emit<TIn = Record<string, unknown>>(name: string, payload: TIn): void;
|
|
13
|
+
|
|
11
14
|
// Text operations
|
|
12
15
|
getText(): Promise<string>;
|
|
13
16
|
setText(text: string): Promise<void>;
|
|
@@ -38,6 +41,14 @@ export class Clipboard implements ClipboardAPI {
|
|
|
38
41
|
this.onFn = onFn;
|
|
39
42
|
}
|
|
40
43
|
|
|
44
|
+
on<TData = unknown>(name: string, callback: (payload: TData) => void): () => void {
|
|
45
|
+
return this.connect.on<TData>(name, callback);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
emit<TIn = Record<string, unknown>>(name: string, payload: TIn): void {
|
|
49
|
+
this.connect.emit<TIn>(name, payload);
|
|
50
|
+
}
|
|
51
|
+
|
|
41
52
|
/**
|
|
42
53
|
* Get text from clipboard
|
|
43
54
|
* @returns Current clipboard text content
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## 🎯 Overview
|
|
4
4
|
|
|
5
|
-
The **Connection Feature** is PlusUI's absurdly simple system for frontend ↔ backend communication.
|
|
5
|
+
The **Connection Feature** is PlusUI's absurdly simple system for frontend ↔ backend communication. Use **`feature.emit()`** and **`feature.on()`** on frontend, mirrored by scoped channels on backend.
|
|
6
6
|
|
|
7
7
|
## ✨ The 5 Primitives That Cover Everything
|
|
8
8
|
|
|
@@ -32,13 +32,15 @@ Just `emit()` and `on()` handle all communication patterns:
|
|
|
32
32
|
```typescript
|
|
33
33
|
import { connect } from 'plusui-native-core/connect';
|
|
34
34
|
|
|
35
|
+
const custom = connect.feature('custom');
|
|
36
|
+
|
|
35
37
|
// Listen for response
|
|
36
|
-
|
|
38
|
+
custom.on('greetResponse', (data) => {
|
|
37
39
|
console.log(data.message); // "Hello, World!"
|
|
38
40
|
});
|
|
39
41
|
|
|
40
42
|
// Send message to backend
|
|
41
|
-
|
|
43
|
+
custom.emit('greet', { name: 'World' });
|
|
42
44
|
```
|
|
43
45
|
|
|
44
46
|
### Receive & Respond
|
|
@@ -48,23 +50,25 @@ connect.emit('greet', { name: 'World' });
|
|
|
48
50
|
|
|
49
51
|
plusui::Connect connect;
|
|
50
52
|
|
|
51
|
-
connect.
|
|
53
|
+
auto customFeature = connect.feature("custom");
|
|
54
|
+
|
|
55
|
+
customFeature.on("greet", [&connect](const nlohmann::json& payload) {
|
|
52
56
|
std::string user = payload.value("name", "World");
|
|
53
|
-
connect.emit("greetResponse", {{"message", "Hello, " + user + "!"}});
|
|
57
|
+
connect.emit("custom.greetResponse", {{"message", "Hello, " + user + "!"}});
|
|
54
58
|
});
|
|
55
59
|
|
|
56
60
|
// Wire backend connect ↔ window bridge once
|
|
57
61
|
plusui::bindConnect(mainWindow, connect);
|
|
58
62
|
|
|
59
63
|
// Example: backend push to frontend
|
|
60
|
-
connect.emit("resize", {{"width", 1280}, {"height", 720}});
|
|
64
|
+
connect.emit("custom.resize", {{"width", 1280}, {"height", 720}});
|
|
61
65
|
```
|
|
62
66
|
|
|
63
67
|
### Listen on Frontend
|
|
64
68
|
|
|
65
69
|
```typescript
|
|
66
70
|
// Listen for messages from backend
|
|
67
|
-
|
|
71
|
+
custom.on('resize', (data) => {
|
|
68
72
|
console.log(`${data.width}x${data.height}`);
|
|
69
73
|
updateLayout(data);
|
|
70
74
|
});
|
|
@@ -117,14 +121,47 @@ connect.onSubscription("cpu", [](bool subscribed, const nlohmann::json&) {
|
|
|
117
121
|
|
|
118
122
|
### Feature-Scoped Names
|
|
119
123
|
|
|
120
|
-
Use `connect` for custom global channels, and
|
|
124
|
+
Use `connect` for custom global channels, and feature-scoped APIs when you want automatic namespacing:
|
|
121
125
|
|
|
122
126
|
```typescript
|
|
127
|
+
window.emit('resized', { width: 1200, height: 800 }); // -> "window.resized"
|
|
128
|
+
clipboard.emit('changed', { text: 'hello' }); // -> "clipboard.changed"
|
|
129
|
+
|
|
130
|
+
// Also supported when you need explicit feature connect access:
|
|
123
131
|
window.connect.emit('resized', { width: 1200, height: 800 }); // -> "window.resized"
|
|
124
|
-
clipboard.connect.emit('changed', { text: 'hello' }); // -> "clipboard.changed"
|
|
125
132
|
connect.emit('custom.appEvent', { ok: true }); // custom/global channel
|
|
126
133
|
```
|
|
127
134
|
|
|
135
|
+
Backend can listen using fully scoped names, or use a scoped helper:
|
|
136
|
+
|
|
137
|
+
```cpp
|
|
138
|
+
plusui::Connect connect;
|
|
139
|
+
plusui::bindConnect(mainWindow, connect);
|
|
140
|
+
|
|
141
|
+
auto windowFeature = connect.feature("window");
|
|
142
|
+
windowFeature.on("resized", [](const nlohmann::json& payload) {
|
|
143
|
+
// receives "window.resized"
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
connect.on("window.resized", [](const nlohmann::json& payload) {
|
|
147
|
+
// from window.emit('resized', ...) or window.connect.emit('resized', ...)
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
connect.on("clipboard.changed", [](const nlohmann::json& payload) {
|
|
151
|
+
// from clipboard.emit('changed', ...) or clipboard.connect.emit('changed', ...)
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
connect.on("custom.appEvent", [](const nlohmann::json& payload) {
|
|
155
|
+
// from connect.emit('custom.appEvent', ...)
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`plusui connect` scans frontend and backend `.emit()` / `.on()` calls and understands feature aliases, including:
|
|
160
|
+
|
|
161
|
+
- `const custom = createFeatureConnect('custom'); custom.on('event', ...)`
|
|
162
|
+
- `const custom = connect.feature('custom'); custom.emit('event', ...)`
|
|
163
|
+
- `auto custom = connect.feature("custom"); custom.on("event", ...)`
|
|
164
|
+
|
|
128
165
|
## 💡 Design Your Own Patterns
|
|
129
166
|
|
|
130
167
|
### Request/Response Pattern
|
|
@@ -40,6 +40,14 @@ export class DisplayAPI {
|
|
|
40
40
|
private invokeFn: (name: string, args?: unknown[]) => Promise<unknown>
|
|
41
41
|
) {}
|
|
42
42
|
|
|
43
|
+
on<TData = unknown>(name: string, callback: (payload: TData) => void): () => void {
|
|
44
|
+
return this.connect.on<TData>(name, callback);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
emit<TIn = Record<string, unknown>>(name: string, payload: TIn): void {
|
|
48
|
+
this.connect.emit<TIn>(name, payload);
|
|
49
|
+
}
|
|
50
|
+
|
|
43
51
|
async getAllDisplays(): Promise<Display[]> {
|
|
44
52
|
return await this.invokeFn<Display[]>('display.getAllDisplays', []);
|
|
45
53
|
}
|
|
@@ -32,6 +32,9 @@ export interface DragEvent {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
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
|
+
|
|
35
38
|
/**
|
|
36
39
|
* Enable or disable file drop into window
|
|
37
40
|
* @param enabled - true to allow files to be dropped into the window
|
|
@@ -91,6 +94,14 @@ export class FileDrop implements FileDropAPI {
|
|
|
91
94
|
this.onFn = onFn;
|
|
92
95
|
}
|
|
93
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
|
+
|
|
94
105
|
/**
|
|
95
106
|
* Enable or disable file drop into window
|
|
96
107
|
* @param enabled - true to allow files to be dropped into the window
|
|
@@ -65,6 +65,14 @@ export class KeyboardAPI {
|
|
|
65
65
|
this.setupEventListeners();
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
on<TData = unknown>(name: string, callback: (payload: TData) => void): () => void {
|
|
69
|
+
return this.connect.on<TData>(name, callback);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
emit<TIn = Record<string, unknown>>(name: string, payload: TIn): void {
|
|
73
|
+
this.connect.emit<TIn>(name, payload);
|
|
74
|
+
}
|
|
75
|
+
|
|
68
76
|
private setupEventListeners(): void {
|
|
69
77
|
this.eventFn('keyboard:keydown', (data) => {
|
|
70
78
|
const event = data as KeyEvent;
|
|
@@ -88,6 +88,14 @@ export class MenuAPI {
|
|
|
88
88
|
});
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
on<TData = unknown>(name: string, callback: (payload: TData) => void): () => void {
|
|
92
|
+
return this.connect.on<TData>(name, callback);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
emit<TIn = Record<string, unknown>>(name: string, payload: TIn): void {
|
|
96
|
+
this.connect.emit<TIn>(name, payload);
|
|
97
|
+
}
|
|
98
|
+
|
|
91
99
|
// ========================================================================
|
|
92
100
|
// Popup Menus
|
|
93
101
|
// ========================================================================
|
|
@@ -25,6 +25,14 @@ export class TrayAPI {
|
|
|
25
25
|
private eventFn: (event: string, callback: (...args: unknown[]) => void) => () => void
|
|
26
26
|
) {}
|
|
27
27
|
|
|
28
|
+
on<TData = unknown>(name: string, callback: (payload: TData) => void): () => void {
|
|
29
|
+
return this.connect.on<TData>(name, callback);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
emit<TIn = Record<string, unknown>>(name: string, payload: TIn): void {
|
|
33
|
+
this.connect.emit<TIn>(name, payload);
|
|
34
|
+
}
|
|
35
|
+
|
|
28
36
|
async setIcon(iconPath: string): Promise<void> {
|
|
29
37
|
await this.invokeFn('tray.setIcon', [iconPath]);
|
|
30
38
|
}
|
|
@@ -575,6 +575,14 @@ class WebGPUNamespace {
|
|
|
575
575
|
this.onFn = onFn;
|
|
576
576
|
}
|
|
577
577
|
|
|
578
|
+
on<TData = unknown>(name: string, callback: (payload: TData) => void): () => void {
|
|
579
|
+
return this.connect.on<TData>(name, callback);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
emit<TIn = Record<string, unknown>>(name: string, payload: TIn): void {
|
|
583
|
+
this.connect.emit<TIn>(name, payload);
|
|
584
|
+
}
|
|
585
|
+
|
|
578
586
|
async requestAdapter(options?: GPURequestAdapterOptions): Promise<GPUAdapter | null> {
|
|
579
587
|
const result = await this.invokeFn('webgpu.requestAdapter', [options || {}]);
|
|
580
588
|
if (!result) {
|
|
@@ -43,8 +43,18 @@ async function invoke(method: string, args?: unknown[]): Promise<unknown> {
|
|
|
43
43
|
return _invoke(method, args);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
const windowFeatureEvents = connect.feature('window');
|
|
47
|
+
|
|
46
48
|
export const window = {
|
|
47
|
-
connect:
|
|
49
|
+
connect: windowFeatureEvents,
|
|
50
|
+
|
|
51
|
+
on: <TData = unknown>(name: string, callback: (payload: TData) => void): (() => void) => {
|
|
52
|
+
return windowFeatureEvents.on<TData>(name, callback);
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
emit: <TIn = Record<string, unknown>>(name: string, payload: TIn): void => {
|
|
56
|
+
windowFeatureEvents.emit<TIn>(name, payload);
|
|
57
|
+
},
|
|
48
58
|
|
|
49
59
|
async minimize(id?: WindowId): Promise<void> {
|
|
50
60
|
await invoke('window.minimize', id ? [id] : []);
|
|
@@ -72,6 +72,72 @@ public:
|
|
|
72
72
|
using SubscriptionHandler =
|
|
73
73
|
std::function<void(bool subscribed, const nlohmann::json &)>;
|
|
74
74
|
|
|
75
|
+
class Feature {
|
|
76
|
+
Connect *owner;
|
|
77
|
+
std::string scope;
|
|
78
|
+
|
|
79
|
+
std::string scopedName(const std::string &name) const {
|
|
80
|
+
const std::string prefix = scope + ".";
|
|
81
|
+
if (name.rfind(prefix, 0) == 0) {
|
|
82
|
+
return name;
|
|
83
|
+
}
|
|
84
|
+
return prefix + name;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public:
|
|
88
|
+
Feature(Connect *parent, std::string featureScope)
|
|
89
|
+
: owner(parent), scope(std::move(featureScope)) {}
|
|
90
|
+
|
|
91
|
+
void on(const std::string &name, EventHandler handler) {
|
|
92
|
+
owner->on(scopedName(name), std::move(handler));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
void emit(const std::string &name, const nlohmann::json &payload) {
|
|
96
|
+
owner->emit(scopedName(name), payload);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
void onCall(const std::string &name, CallHandler handler) {
|
|
100
|
+
owner->onCall(scopedName(name), std::move(handler));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
void onSubscription(const std::string &name,
|
|
104
|
+
SubscriptionHandler handler) {
|
|
105
|
+
owner->onSubscription(scopedName(name), std::move(handler));
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// ============================================================
|
|
110
|
+
// Channel — single-name channel object
|
|
111
|
+
//
|
|
112
|
+
// Mirror of the TypeScript createChannel() object:
|
|
113
|
+
// download.on([](const json& p) { ... });
|
|
114
|
+
// download.emit({{"progress", 50}});
|
|
115
|
+
//
|
|
116
|
+
// Obtained via connect.channel("download") or declared in
|
|
117
|
+
// the auto-generated connections.gen.hpp.
|
|
118
|
+
// ============================================================
|
|
119
|
+
class Channel {
|
|
120
|
+
Connect *owner;
|
|
121
|
+
std::string name_;
|
|
122
|
+
|
|
123
|
+
public:
|
|
124
|
+
Channel() : owner(nullptr) {}
|
|
125
|
+
Channel(Connect *parent, std::string channelName)
|
|
126
|
+
: owner(parent), name_(std::move(channelName)) {}
|
|
127
|
+
|
|
128
|
+
// Register a listener — mirrors TypeScript: download.on((d) => { ... })
|
|
129
|
+
void on(EventHandler handler) {
|
|
130
|
+
if (owner) owner->on(name_, std::move(handler));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Send a message — mirrors TypeScript: download.emit({ key: value })
|
|
134
|
+
void emit(const nlohmann::json &payload = nlohmann::json::object()) {
|
|
135
|
+
if (owner) owner->emit(name_, payload);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const std::string &name() const { return name_; }
|
|
139
|
+
};
|
|
140
|
+
|
|
75
141
|
private:
|
|
76
142
|
std::function<void(const std::string &)> outboundHandler;
|
|
77
143
|
std::map<std::string, std::vector<EventHandler>> eventHandlers;
|
|
@@ -100,6 +166,8 @@ private:
|
|
|
100
166
|
outboundHandler(j.dump());
|
|
101
167
|
}
|
|
102
168
|
|
|
169
|
+
public:
|
|
170
|
+
|
|
103
171
|
virtual ~Connect() = default;
|
|
104
172
|
|
|
105
173
|
// Mirror frontend API: register message listener by name
|
|
@@ -107,6 +175,15 @@ private:
|
|
|
107
175
|
eventHandlers[name].push_back(std::move(handler));
|
|
108
176
|
}
|
|
109
177
|
|
|
178
|
+
Feature feature(const std::string &scope) { return Feature(this, scope); }
|
|
179
|
+
|
|
180
|
+
// Create a named channel object — mirrors TypeScript createChannel('name')
|
|
181
|
+
// Returns by value; store as a member or local:
|
|
182
|
+
// auto download = connect.channel("download");
|
|
183
|
+
// download.on([](const json& p) { ... });
|
|
184
|
+
// download.emit({{"progress", 50}});
|
|
185
|
+
Channel channel(const std::string &name) { return Channel(this, name); }
|
|
186
|
+
|
|
110
187
|
// Request/response helper for call primitive
|
|
111
188
|
void onCall(const std::string &name, CallHandler handler) {
|
|
112
189
|
callHandlers[name] = std::move(handler);
|
package/package.json
CHANGED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
#pragma once
|
|
2
|
-
|
|
3
|
-
#include <string>
|
|
4
|
-
#include <vector>
|
|
5
|
-
|
|
6
|
-
namespace plusui {
|
|
7
|
-
|
|
8
|
-
struct BindingEntry {
|
|
9
|
-
const char *source;
|
|
10
|
-
const char *feature;
|
|
11
|
-
const char *method;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
class NativeBindings {
|
|
15
|
-
public:
|
|
16
|
-
static const std::vector<BindingEntry> &getEntries();
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
} // namespace plusui
|