plusui-native 0.2.4 → 0.2.7
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/package.json +5 -6
- package/src/index.js +957 -904
- package/templates/base/README.md.template +13 -13
- package/templates/manager.js +173 -217
- package/templates/react/CMakeLists.txt.template +25 -15
- package/templates/react/frontend/package.json.template +3 -4
- package/templates/react/frontend/src/App.tsx +3 -3
- package/templates/react/frontend/src/plusui.ts +117 -0
- package/templates/react/main.cpp.template +3 -3
- package/templates/react/package.json.template +2 -1
- package/templates/solid/CMakeLists.txt.template +25 -15
- package/templates/solid/frontend/package.json.template +3 -4
- package/templates/solid/frontend/src/App.tsx +4 -5
- package/templates/solid/frontend/src/plusui.ts +117 -0
- package/templates/solid/main.cpp.template +3 -3
- package/templates/solid/package.json.template +2 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
type InvokeFn = (method: string, args?: unknown[]) => Promise<unknown>;
|
|
2
|
+
type PendingMap = Record<string, { resolve: (value: unknown) => void; reject: (reason?: unknown) => void }>;
|
|
3
|
+
|
|
4
|
+
type WindowSize = { width: number; height: number };
|
|
5
|
+
type WindowPosition = { x: number; y: number };
|
|
6
|
+
type RouteMap = Record<string, string>;
|
|
7
|
+
|
|
8
|
+
let _invoke: InvokeFn | null = null;
|
|
9
|
+
let _pending: PendingMap = {};
|
|
10
|
+
let _routes: RouteMap = {};
|
|
11
|
+
|
|
12
|
+
function initBridge() {
|
|
13
|
+
if (typeof window === 'undefined') return;
|
|
14
|
+
|
|
15
|
+
const w = window as any;
|
|
16
|
+
if (typeof w.__invoke__ === 'function') {
|
|
17
|
+
_invoke = w.__invoke__ as InvokeFn;
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
_pending = {};
|
|
22
|
+
w.__pending__ = _pending;
|
|
23
|
+
|
|
24
|
+
w.__invoke__ = (method: string, args?: unknown[]): Promise<unknown> => {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const id = Math.random().toString(36).slice(2, 11);
|
|
27
|
+
const request = JSON.stringify({ id, method, params: args ?? [] });
|
|
28
|
+
|
|
29
|
+
_pending[id] = { resolve, reject };
|
|
30
|
+
|
|
31
|
+
if (typeof w.__native_invoke__ === 'function') {
|
|
32
|
+
w.__native_invoke__(request);
|
|
33
|
+
} else {
|
|
34
|
+
setTimeout(() => {
|
|
35
|
+
delete _pending[id];
|
|
36
|
+
resolve(null);
|
|
37
|
+
}, 0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
setTimeout(() => {
|
|
41
|
+
if (_pending[id]) {
|
|
42
|
+
delete _pending[id];
|
|
43
|
+
reject(new Error(`${method} timed out`));
|
|
44
|
+
}
|
|
45
|
+
}, 30000);
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
w.__response__ = (id: string, result: unknown) => {
|
|
50
|
+
const pending = _pending[id];
|
|
51
|
+
if (pending) {
|
|
52
|
+
pending.resolve(result);
|
|
53
|
+
delete _pending[id];
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
_invoke = w.__invoke__ as InvokeFn;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function invoke(method: string, args?: unknown[]) {
|
|
61
|
+
if (!_invoke) {
|
|
62
|
+
initBridge();
|
|
63
|
+
if (!_invoke) {
|
|
64
|
+
throw new Error('PlusUI bridge not initialized');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return _invoke(method, args);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
initBridge();
|
|
72
|
+
|
|
73
|
+
export const win = {
|
|
74
|
+
minimize: async () => invoke('window.minimize', []),
|
|
75
|
+
maximize: async () => invoke('window.maximize', []),
|
|
76
|
+
close: async () => invoke('window.close', []),
|
|
77
|
+
setPosition: async (x: number, y: number) => invoke('window.setPosition', [x, y]),
|
|
78
|
+
getSize: async (): Promise<WindowSize> => invoke('window.getSize', []) as Promise<WindowSize>,
|
|
79
|
+
getPosition: async (): Promise<WindowPosition> => invoke('window.getPosition', []) as Promise<WindowPosition>,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const browser = {
|
|
83
|
+
getUrl: async (): Promise<string> => invoke('browser.getUrl', []) as Promise<string>,
|
|
84
|
+
goBack: async () => invoke('browser.goBack', []),
|
|
85
|
+
goForward: async () => invoke('browser.goForward', []),
|
|
86
|
+
reload: async () => invoke('browser.reload', []),
|
|
87
|
+
canGoBack: async (): Promise<boolean> => invoke('browser.canGoBack', []) as Promise<boolean>,
|
|
88
|
+
canGoForward: async (): Promise<boolean> => invoke('browser.canGoForward', []) as Promise<boolean>,
|
|
89
|
+
onNavigate: (handler: (url: string) => void) => {
|
|
90
|
+
if (typeof window === 'undefined') {
|
|
91
|
+
return () => {};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const eventHandler = (event: Event) => {
|
|
95
|
+
const custom = event as CustomEvent<{ url?: string }>;
|
|
96
|
+
const nextUrl = custom.detail?.url ?? '';
|
|
97
|
+
handler(nextUrl);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
window.addEventListener('plusui:navigate', eventHandler);
|
|
101
|
+
return () => window.removeEventListener('plusui:navigate', eventHandler);
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const router = {
|
|
106
|
+
setRoutes: (routes: RouteMap) => {
|
|
107
|
+
_routes = routes;
|
|
108
|
+
},
|
|
109
|
+
push: async (path: string) => {
|
|
110
|
+
const target = _routes[path] ?? path;
|
|
111
|
+
return invoke('browser.navigate', [target]);
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const app = {
|
|
116
|
+
quit: async () => invoke('app.quit', []),
|
|
117
|
+
};
|
|
@@ -181,7 +181,7 @@ int main() {
|
|
|
181
181
|
// ============================================================================
|
|
182
182
|
// FRONTEND API REFERENCE
|
|
183
183
|
// ============================================================================
|
|
184
|
-
// import { win, browser, router, app
|
|
184
|
+
// import { win, browser, router, app } from './frontend/src/plusui';
|
|
185
185
|
//
|
|
186
186
|
// WINDOW: win.minimize(), win.maximize(), win.close(), win.center(),
|
|
187
187
|
// win.setSize(w, h), win.setPosition(x, y), win.setTitle(str),
|
|
@@ -197,5 +197,5 @@ int main() {
|
|
|
197
197
|
//
|
|
198
198
|
// DISPLAY: display.getAll(), display.getPrimary(), display.getCurrent()
|
|
199
199
|
//
|
|
200
|
-
// CLIPBOARD: clipboard.writeText(str), clipboard.readText(), clipboard.clear()
|
|
201
|
-
|
|
200
|
+
// CLIPBOARD: clipboard.writeText(str), clipboard.readText(), clipboard.clear()
|
|
201
|
+
|
|
@@ -24,7 +24,15 @@ endif()
|
|
|
24
24
|
# Strategy: Check multiple locations for the Core library
|
|
25
25
|
set(PLUSUI_FOUND FALSE)
|
|
26
26
|
|
|
27
|
-
# Location 1:
|
|
27
|
+
# Location 1: Project-local Core directory
|
|
28
|
+
set(PLUSUI_CORE_LOCAL "${CMAKE_SOURCE_DIR}/Core")
|
|
29
|
+
if(EXISTS "${PLUSUI_CORE_LOCAL}/CMakeLists.txt")
|
|
30
|
+
message(STATUS "Found PlusUI Core at: ${PLUSUI_CORE_LOCAL}")
|
|
31
|
+
add_subdirectory("${PLUSUI_CORE_LOCAL}" "${CMAKE_BINARY_DIR}/plusui-core")
|
|
32
|
+
set(PLUSUI_FOUND TRUE)
|
|
33
|
+
endif()
|
|
34
|
+
|
|
35
|
+
# Location 2: Local development (sibling to project inside PlusUI repo)
|
|
28
36
|
set(PLUSUI_CORE_DEV "${CMAKE_SOURCE_DIR}/../Core")
|
|
29
37
|
if(EXISTS "${PLUSUI_CORE_DEV}/CMakeLists.txt")
|
|
30
38
|
message(STATUS "Found PlusUI Core at: ${PLUSUI_CORE_DEV}")
|
|
@@ -32,17 +40,23 @@ if(EXISTS "${PLUSUI_CORE_DEV}/CMakeLists.txt")
|
|
|
32
40
|
set(PLUSUI_FOUND TRUE)
|
|
33
41
|
endif()
|
|
34
42
|
|
|
35
|
-
# Location
|
|
43
|
+
# Location 3: Installed via npm (separate core package)
|
|
36
44
|
if(NOT PLUSUI_FOUND)
|
|
37
|
-
set(PLUSUI_CORE_NPM "${CMAKE_SOURCE_DIR}/node_modules/plusui-native-core
|
|
45
|
+
set(PLUSUI_CORE_NPM "${CMAKE_SOURCE_DIR}/node_modules/plusui-native-core")
|
|
46
|
+
set(PLUSUI_CORE_NPM_LEGACY "${CMAKE_SOURCE_DIR}/node_modules/plusui-native-core/Core")
|
|
47
|
+
|
|
38
48
|
if(EXISTS "${PLUSUI_CORE_NPM}/CMakeLists.txt")
|
|
39
49
|
message(STATUS "Found PlusUI Core in node_modules: ${PLUSUI_CORE_NPM}")
|
|
40
50
|
add_subdirectory("${PLUSUI_CORE_NPM}" "${CMAKE_BINARY_DIR}/plusui-core")
|
|
41
51
|
set(PLUSUI_FOUND TRUE)
|
|
52
|
+
elseif(EXISTS "${PLUSUI_CORE_NPM_LEGACY}/CMakeLists.txt")
|
|
53
|
+
message(STATUS "Found PlusUI Core in node_modules (legacy): ${PLUSUI_CORE_NPM_LEGACY}")
|
|
54
|
+
add_subdirectory("${PLUSUI_CORE_NPM_LEGACY}" "${CMAKE_BINARY_DIR}/plusui-core")
|
|
55
|
+
set(PLUSUI_FOUND TRUE)
|
|
42
56
|
endif()
|
|
43
57
|
endif()
|
|
44
58
|
|
|
45
|
-
# Location
|
|
59
|
+
# Location 4: Parent directory development structure
|
|
46
60
|
if(NOT PLUSUI_FOUND)
|
|
47
61
|
set(PLUSUI_CORE_PARENT "${CMAKE_SOURCE_DIR}/../../Core")
|
|
48
62
|
if(EXISTS "${PLUSUI_CORE_PARENT}/CMakeLists.txt")
|
|
@@ -56,11 +70,13 @@ if(NOT PLUSUI_FOUND)
|
|
|
56
70
|
message(FATAL_ERROR "
|
|
57
71
|
PlusUI Core not found!
|
|
58
72
|
|
|
59
|
-
|
|
73
|
+
Install dependencies (npm install) or add a Core folder in this project root.
|
|
60
74
|
|
|
61
75
|
Searched locations:
|
|
76
|
+
- ${PLUSUI_CORE_LOCAL}
|
|
62
77
|
- ${PLUSUI_CORE_DEV}
|
|
63
|
-
|
|
78
|
+
- ${PLUSUI_CORE_NPM}
|
|
79
|
+
- ${PLUSUI_CORE_NPM_LEGACY}
|
|
64
80
|
- ${PLUSUI_CORE_PARENT}
|
|
65
81
|
")
|
|
66
82
|
endif()
|
|
@@ -101,13 +117,7 @@ if(WIN32)
|
|
|
101
117
|
endif()
|
|
102
118
|
|
|
103
119
|
target_link_libraries({{PROJECT_NAME}} PRIVATE ole32 shell32 shlwapi user32 version)
|
|
104
|
-
|
|
105
|
-
# Set subsystem to Windows for release builds (no console window)
|
|
106
|
-
if(NOT PLUSUI_DEV_MODE)
|
|
107
|
-
set_target_properties({{PROJECT_NAME}} PROPERTIES
|
|
108
|
-
WIN32_EXECUTABLE TRUE
|
|
109
|
-
)
|
|
110
|
-
endif()
|
|
120
|
+
# Keep default console subsystem so standard int main() works in all build modes.
|
|
111
121
|
elseif(APPLE)
|
|
112
122
|
# macOS: WebKit
|
|
113
123
|
find_library(WEBKIT_LIBRARY WebKit REQUIRED)
|
|
@@ -147,5 +157,5 @@ endif()
|
|
|
147
157
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
|
148
158
|
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
|
|
149
159
|
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
|
|
150
|
-
|
|
151
|
-
|
|
160
|
+
|
|
161
|
+
|
|
@@ -9,13 +9,12 @@
|
|
|
9
9
|
"preview": "vite preview"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"solid-js": "^1.8.0"
|
|
13
|
-
"plusui-native-core": "{{PLUSUI_CORE_PATH}}"
|
|
12
|
+
"solid-js": "^1.8.0"
|
|
14
13
|
},
|
|
15
14
|
"devDependencies": {
|
|
16
15
|
"typescript": "^5.3.0",
|
|
17
16
|
"vite": "^5.0.0",
|
|
18
17
|
"vite-plugin-solid": "^2.10.0"
|
|
19
18
|
}
|
|
20
|
-
}
|
|
21
|
-
|
|
19
|
+
}
|
|
20
|
+
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createSignal, onMount } from 'solid-js';
|
|
2
|
-
import { win, browser, router, app } from 'plusui
|
|
1
|
+
import { createSignal, onMount, Show } from 'solid-js';
|
|
2
|
+
import { win, browser, router, app } from './plusui';
|
|
3
3
|
|
|
4
4
|
// Define routes for your app (optional - for SPA routing)
|
|
5
5
|
const routes = {
|
|
@@ -48,7 +48,6 @@ function App() {
|
|
|
48
48
|
const handleGoBack = async () => await browser.goBack();
|
|
49
49
|
const handleGoForward = async () => await browser.goForward();
|
|
50
50
|
const handleReload = async () => await browser.reload();
|
|
51
|
-
const handleNavigate = async (url: string) => await browser.navigate(url);
|
|
52
51
|
|
|
53
52
|
// Router navigation
|
|
54
53
|
const handleGoHome = async () => await router.push('/');
|
|
@@ -129,5 +128,5 @@ function App() {
|
|
|
129
128
|
);
|
|
130
129
|
}
|
|
131
130
|
|
|
132
|
-
export default App;
|
|
133
|
-
|
|
131
|
+
export default App;
|
|
132
|
+
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
type InvokeFn = (method: string, args?: unknown[]) => Promise<unknown>;
|
|
2
|
+
type PendingMap = Record<string, { resolve: (value: unknown) => void; reject: (reason?: unknown) => void }>;
|
|
3
|
+
|
|
4
|
+
type WindowSize = { width: number; height: number };
|
|
5
|
+
type WindowPosition = { x: number; y: number };
|
|
6
|
+
type RouteMap = Record<string, string>;
|
|
7
|
+
|
|
8
|
+
let _invoke: InvokeFn | null = null;
|
|
9
|
+
let _pending: PendingMap = {};
|
|
10
|
+
let _routes: RouteMap = {};
|
|
11
|
+
|
|
12
|
+
function initBridge() {
|
|
13
|
+
if (typeof window === 'undefined') return;
|
|
14
|
+
|
|
15
|
+
const w = window as any;
|
|
16
|
+
if (typeof w.__invoke__ === 'function') {
|
|
17
|
+
_invoke = w.__invoke__ as InvokeFn;
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
_pending = {};
|
|
22
|
+
w.__pending__ = _pending;
|
|
23
|
+
|
|
24
|
+
w.__invoke__ = (method: string, args?: unknown[]): Promise<unknown> => {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const id = Math.random().toString(36).slice(2, 11);
|
|
27
|
+
const request = JSON.stringify({ id, method, params: args ?? [] });
|
|
28
|
+
|
|
29
|
+
_pending[id] = { resolve, reject };
|
|
30
|
+
|
|
31
|
+
if (typeof w.__native_invoke__ === 'function') {
|
|
32
|
+
w.__native_invoke__(request);
|
|
33
|
+
} else {
|
|
34
|
+
setTimeout(() => {
|
|
35
|
+
delete _pending[id];
|
|
36
|
+
resolve(null);
|
|
37
|
+
}, 0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
setTimeout(() => {
|
|
41
|
+
if (_pending[id]) {
|
|
42
|
+
delete _pending[id];
|
|
43
|
+
reject(new Error(`${method} timed out`));
|
|
44
|
+
}
|
|
45
|
+
}, 30000);
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
w.__response__ = (id: string, result: unknown) => {
|
|
50
|
+
const pending = _pending[id];
|
|
51
|
+
if (pending) {
|
|
52
|
+
pending.resolve(result);
|
|
53
|
+
delete _pending[id];
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
_invoke = w.__invoke__ as InvokeFn;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function invoke(method: string, args?: unknown[]) {
|
|
61
|
+
if (!_invoke) {
|
|
62
|
+
initBridge();
|
|
63
|
+
if (!_invoke) {
|
|
64
|
+
throw new Error('PlusUI bridge not initialized');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return _invoke(method, args);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
initBridge();
|
|
72
|
+
|
|
73
|
+
export const win = {
|
|
74
|
+
minimize: async () => invoke('window.minimize', []),
|
|
75
|
+
maximize: async () => invoke('window.maximize', []),
|
|
76
|
+
close: async () => invoke('window.close', []),
|
|
77
|
+
setPosition: async (x: number, y: number) => invoke('window.setPosition', [x, y]),
|
|
78
|
+
getSize: async (): Promise<WindowSize> => invoke('window.getSize', []) as Promise<WindowSize>,
|
|
79
|
+
getPosition: async (): Promise<WindowPosition> => invoke('window.getPosition', []) as Promise<WindowPosition>,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const browser = {
|
|
83
|
+
getUrl: async (): Promise<string> => invoke('browser.getUrl', []) as Promise<string>,
|
|
84
|
+
goBack: async () => invoke('browser.goBack', []),
|
|
85
|
+
goForward: async () => invoke('browser.goForward', []),
|
|
86
|
+
reload: async () => invoke('browser.reload', []),
|
|
87
|
+
canGoBack: async (): Promise<boolean> => invoke('browser.canGoBack', []) as Promise<boolean>,
|
|
88
|
+
canGoForward: async (): Promise<boolean> => invoke('browser.canGoForward', []) as Promise<boolean>,
|
|
89
|
+
onNavigate: (handler: (url: string) => void) => {
|
|
90
|
+
if (typeof window === 'undefined') {
|
|
91
|
+
return () => {};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const eventHandler = (event: Event) => {
|
|
95
|
+
const custom = event as CustomEvent<{ url?: string }>;
|
|
96
|
+
const nextUrl = custom.detail?.url ?? '';
|
|
97
|
+
handler(nextUrl);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
window.addEventListener('plusui:navigate', eventHandler);
|
|
101
|
+
return () => window.removeEventListener('plusui:navigate', eventHandler);
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const router = {
|
|
106
|
+
setRoutes: (routes: RouteMap) => {
|
|
107
|
+
_routes = routes;
|
|
108
|
+
},
|
|
109
|
+
push: async (path: string) => {
|
|
110
|
+
const target = _routes[path] ?? path;
|
|
111
|
+
return invoke('browser.navigate', [target]);
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const app = {
|
|
116
|
+
quit: async () => invoke('app.quit', []),
|
|
117
|
+
};
|
|
@@ -172,7 +172,7 @@ int main() {
|
|
|
172
172
|
// ============================================================================
|
|
173
173
|
// FRONTEND API REFERENCE
|
|
174
174
|
// ============================================================================
|
|
175
|
-
// import { win, browser, router, app
|
|
175
|
+
// import { win, browser, router, app } from './frontend/src/plusui';
|
|
176
176
|
//
|
|
177
177
|
// WINDOW: win.minimize(), win.maximize(), win.close(), win.center(),
|
|
178
178
|
// win.setSize(w, h), win.setPosition(x, y), win.setTitle(str),
|
|
@@ -188,5 +188,5 @@ int main() {
|
|
|
188
188
|
//
|
|
189
189
|
// DISPLAY: display.getAll(), display.getPrimary(), display.getCurrent()
|
|
190
190
|
//
|
|
191
|
-
// CLIPBOARD: clipboard.writeText(str), clipboard.readText(), clipboard.clear()
|
|
192
|
-
|
|
191
|
+
// CLIPBOARD: clipboard.writeText(str), clipboard.readText(), clipboard.clear()
|
|
192
|
+
|