plusui-native 0.2.4 → 0.2.5
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 +4 -5
- package/src/index.js +904 -904
- package/templates/base/README.md.template +8 -10
- package/templates/manager.js +173 -217
- package/templates/react/CMakeLists.txt.template +24 -8
- 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 +24 -8
- 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
|
@@ -142,20 +142,18 @@ just help # Show all commands
|
|
|
142
142
|
|
|
143
143
|
## Calling C++ from TypeScript
|
|
144
144
|
|
|
145
|
-
Use the `plusui
|
|
145
|
+
Use the local `frontend/src/plusui.ts` bridge to interact with the native backend:
|
|
146
146
|
|
|
147
147
|
```tsx
|
|
148
|
-
import {
|
|
149
|
-
|
|
150
|
-
const plusui = createPlusUI();
|
|
148
|
+
import { win } from './plusui';
|
|
151
149
|
|
|
152
150
|
// Window controls
|
|
153
|
-
await
|
|
154
|
-
await
|
|
155
|
-
await
|
|
151
|
+
await win.minimize();
|
|
152
|
+
await win.maximize();
|
|
153
|
+
await win.close();
|
|
156
154
|
|
|
157
155
|
// Get window info
|
|
158
|
-
const size = await
|
|
156
|
+
const size = await win.getSize();
|
|
159
157
|
console.log(`Window size: ${size.width}x${size.height}`);
|
|
160
158
|
```
|
|
161
159
|
|
|
@@ -164,5 +162,5 @@ console.log(`Window size: ${size.width}x${size.height}`);
|
|
|
164
162
|
- [PlusUI Documentation](https://github.com/yourorg/plusui)
|
|
165
163
|
- [React Documentation](https://react.dev/)
|
|
166
164
|
- [Vite Documentation](https://vitejs.dev/)
|
|
167
|
-
|
|
168
|
-
|
|
165
|
+
|
|
166
|
+
|
package/templates/manager.js
CHANGED
|
@@ -1,217 +1,173 @@
|
|
|
1
|
-
import { mkdir, readdir, readFile, writeFile, copyFile, stat } from 'fs/promises';
|
|
2
|
-
import { join, dirname, resolve } from 'path';
|
|
3
|
-
import { existsSync } from 'fs';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
import { EnvironmentDoctor } from '../src/doctor/index.js';
|
|
6
|
-
import chalk from 'chalk';
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
-
const __dirname = dirname(__filename);
|
|
11
|
-
|
|
12
|
-
export class TemplateManager {
|
|
13
|
-
constructor() {
|
|
14
|
-
this.templatesDir = __dirname;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async create(projectName, options = {}) {
|
|
18
|
-
const template = options.template || 'react';
|
|
19
|
-
const templatePath = join(this.templatesDir, template);
|
|
20
|
-
|
|
21
|
-
console.log(chalk.bold(`\nCreating PlusUI project: ${projectName}\n`));
|
|
22
|
-
|
|
23
|
-
// 1. Check environment first
|
|
24
|
-
console.log(chalk.blue('Checking environment...'));
|
|
25
|
-
const doctor = new EnvironmentDoctor();
|
|
26
|
-
const quickCheck = await doctor.quickCheck();
|
|
27
|
-
|
|
28
|
-
if (!quickCheck.ready) {
|
|
29
|
-
console.log(chalk.red('\n✗ Environment not ready!\n'));
|
|
30
|
-
console.log(chalk.yellow('Run: plusui doctor --fix\n'));
|
|
31
|
-
throw new Error('Environment check failed');
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
console.log(chalk.green('✓ Environment ready\n'));
|
|
35
|
-
|
|
36
|
-
// 2. Create project directory
|
|
37
|
-
console.log(chalk.blue('Creating project structure...'));
|
|
38
|
-
const projectPath = resolve(projectName);
|
|
39
|
-
|
|
40
|
-
if (existsSync(projectPath)) {
|
|
41
|
-
throw new Error(`Directory ${projectName} already exists`);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
await mkdir(projectPath, { recursive: true });
|
|
45
|
-
|
|
46
|
-
// 3. Prepare template variables
|
|
47
|
-
const variables = {
|
|
48
|
-
PROJECT_NAME: projectName,
|
|
49
|
-
PROJECT_NAME_LOWER: projectName.toLowerCase(),
|
|
50
|
-
PROJECT_VERSION: '0.1.0'
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
await
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
reject(new Error(`npm install failed with code ${code}`));
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Install frontend dependencies
|
|
179
|
-
const frontendPath = join(projectPath, 'frontend');
|
|
180
|
-
if (existsSync(join(frontendPath, 'package.json'))) {
|
|
181
|
-
const frontendProc = spawn(npm, ['install'], {
|
|
182
|
-
cwd: frontendPath,
|
|
183
|
-
stdio: 'inherit',
|
|
184
|
-
shell: true
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
frontendProc.on('close', (feCode) => {
|
|
188
|
-
if (feCode === 0) {
|
|
189
|
-
resolve();
|
|
190
|
-
} else {
|
|
191
|
-
reject(new Error(`frontend npm install failed with code ${feCode}`));
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
frontendProc.on('error', reject);
|
|
196
|
-
} else {
|
|
197
|
-
resolve();
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
rootProc.on('error', reject);
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
printSuccessMessage(projectName) {
|
|
206
|
-
console.log(chalk.green.bold('\n✓ Project created successfully!\n'));
|
|
207
|
-
console.log(chalk.bold('Next steps:\n'));
|
|
208
|
-
console.log(chalk.cyan(` cd ${projectName}`));
|
|
209
|
-
console.log(chalk.cyan(` npm run dev`));
|
|
210
|
-
console.log();
|
|
211
|
-
console.log(chalk.gray('This will start the development server with hot reload.'));
|
|
212
|
-
console.log(chalk.gray('Edit frontend/src/App.tsx and main.cpp to get started!'));
|
|
213
|
-
console.log();
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
|
|
1
|
+
import { mkdir, readdir, readFile, writeFile, copyFile, stat } from 'fs/promises';
|
|
2
|
+
import { join, dirname, resolve } from 'path';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { EnvironmentDoctor } from '../src/doctor/index.js';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { spawn } from 'child_process';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
|
|
12
|
+
export class TemplateManager {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.templatesDir = __dirname;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async create(projectName, options = {}) {
|
|
18
|
+
const template = options.template || 'react';
|
|
19
|
+
const templatePath = join(this.templatesDir, template);
|
|
20
|
+
|
|
21
|
+
console.log(chalk.bold(`\nCreating PlusUI project: ${projectName}\n`));
|
|
22
|
+
|
|
23
|
+
// 1. Check environment first
|
|
24
|
+
console.log(chalk.blue('Checking environment...'));
|
|
25
|
+
const doctor = new EnvironmentDoctor();
|
|
26
|
+
const quickCheck = await doctor.quickCheck();
|
|
27
|
+
|
|
28
|
+
if (!quickCheck.ready) {
|
|
29
|
+
console.log(chalk.red('\n✗ Environment not ready!\n'));
|
|
30
|
+
console.log(chalk.yellow('Run: plusui doctor --fix\n'));
|
|
31
|
+
throw new Error('Environment check failed');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log(chalk.green('✓ Environment ready\n'));
|
|
35
|
+
|
|
36
|
+
// 2. Create project directory
|
|
37
|
+
console.log(chalk.blue('Creating project structure...'));
|
|
38
|
+
const projectPath = resolve(projectName);
|
|
39
|
+
|
|
40
|
+
if (existsSync(projectPath)) {
|
|
41
|
+
throw new Error(`Directory ${projectName} already exists`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await mkdir(projectPath, { recursive: true });
|
|
45
|
+
|
|
46
|
+
// 3. Prepare template variables
|
|
47
|
+
const variables = {
|
|
48
|
+
PROJECT_NAME: projectName,
|
|
49
|
+
PROJECT_NAME_LOWER: projectName.toLowerCase(),
|
|
50
|
+
PROJECT_VERSION: '0.1.0'
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// 4. Copy template files
|
|
54
|
+
await this.copyTemplate(templatePath, projectPath, variables);
|
|
55
|
+
|
|
56
|
+
// 5. Copy base files
|
|
57
|
+
const basePath = join(this.templatesDir, 'base');
|
|
58
|
+
await this.copyTemplate(basePath, projectPath, variables, true);
|
|
59
|
+
|
|
60
|
+
console.log(chalk.green('✓ Project structure created\n'));
|
|
61
|
+
|
|
62
|
+
// 6. Install npm dependencies
|
|
63
|
+
console.log(chalk.blue('Installing dependencies...'));
|
|
64
|
+
await this.runNpmInstall(projectPath);
|
|
65
|
+
console.log(chalk.green('✓ Dependencies installed\n'));
|
|
66
|
+
|
|
67
|
+
// 7. Print success message
|
|
68
|
+
this.printSuccessMessage(projectName);
|
|
69
|
+
|
|
70
|
+
return { success: true, path: projectPath };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async copyTemplate(templatePath, destPath, variables, skipSubdirs = false) {
|
|
74
|
+
if (!existsSync(templatePath)) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const entries = await readdir(templatePath, { withFileTypes: true });
|
|
79
|
+
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
const srcPath = join(templatePath, entry.name);
|
|
82
|
+
const destName = entry.name.replace('.template', '');
|
|
83
|
+
const destFilePath = join(destPath, destName);
|
|
84
|
+
|
|
85
|
+
if (entry.isDirectory()) {
|
|
86
|
+
if (!skipSubdirs) {
|
|
87
|
+
await mkdir(destFilePath, { recursive: true });
|
|
88
|
+
await this.copyTemplate(srcPath, destFilePath, variables, false);
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
// Ensure parent directory exists
|
|
92
|
+
await mkdir(dirname(destFilePath), { recursive: true });
|
|
93
|
+
|
|
94
|
+
// Read file content
|
|
95
|
+
let content = await readFile(srcPath, 'utf8');
|
|
96
|
+
|
|
97
|
+
// Replace variables
|
|
98
|
+
content = this.replaceVariables(content, variables);
|
|
99
|
+
|
|
100
|
+
// Write to destination
|
|
101
|
+
await writeFile(destFilePath, content, 'utf8');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
replaceVariables(content, variables) {
|
|
107
|
+
let result = content;
|
|
108
|
+
|
|
109
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
110
|
+
const placeholder = `{{${key}}}`;
|
|
111
|
+
result = result.split(placeholder).join(value);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async runNpmInstall(projectPath) {
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
120
|
+
|
|
121
|
+
// Install root dependencies
|
|
122
|
+
const rootProc = spawn(npm, ['install'], {
|
|
123
|
+
cwd: projectPath,
|
|
124
|
+
stdio: 'inherit',
|
|
125
|
+
shell: true
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
rootProc.on('close', (code) => {
|
|
129
|
+
if (code !== 0) {
|
|
130
|
+
reject(new Error(`npm install failed with code ${code}`));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Install frontend dependencies
|
|
135
|
+
const frontendPath = join(projectPath, 'frontend');
|
|
136
|
+
if (existsSync(join(frontendPath, 'package.json'))) {
|
|
137
|
+
const frontendProc = spawn(npm, ['install'], {
|
|
138
|
+
cwd: frontendPath,
|
|
139
|
+
stdio: 'inherit',
|
|
140
|
+
shell: true
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
frontendProc.on('close', (feCode) => {
|
|
144
|
+
if (feCode === 0) {
|
|
145
|
+
resolve();
|
|
146
|
+
} else {
|
|
147
|
+
reject(new Error(`frontend npm install failed with code ${feCode}`));
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
frontendProc.on('error', reject);
|
|
152
|
+
} else {
|
|
153
|
+
resolve();
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
rootProc.on('error', reject);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
printSuccessMessage(projectName) {
|
|
162
|
+
console.log(chalk.green.bold('\n✓ Project created successfully!\n'));
|
|
163
|
+
console.log(chalk.bold('Next steps:\n'));
|
|
164
|
+
console.log(chalk.cyan(` cd ${projectName}`));
|
|
165
|
+
console.log(chalk.cyan(` npm run dev`));
|
|
166
|
+
console.log();
|
|
167
|
+
console.log(chalk.gray('This will start the development server with hot reload.'));
|
|
168
|
+
console.log(chalk.gray('Edit frontend/src/App.tsx and main.cpp to get started!'));
|
|
169
|
+
console.log();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
@@ -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()
|
|
@@ -147,5 +163,5 @@ endif()
|
|
|
147
163
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
|
|
148
164
|
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
|
|
149
165
|
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
|
|
150
|
-
|
|
151
|
-
|
|
166
|
+
|
|
167
|
+
|
|
@@ -10,8 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"react": "^18.2.0",
|
|
13
|
-
"react-dom": "^18.2.0"
|
|
14
|
-
"plusui-native-core": "{{PLUSUI_CORE_PATH}}"
|
|
13
|
+
"react-dom": "^18.2.0"
|
|
15
14
|
},
|
|
16
15
|
"devDependencies": {
|
|
17
16
|
"@types/react": "^18.2.0",
|
|
@@ -20,5 +19,5 @@
|
|
|
20
19
|
"typescript": "^5.3.0",
|
|
21
20
|
"vite": "^5.0.0"
|
|
22
21
|
}
|
|
23
|
-
}
|
|
24
|
-
|
|
22
|
+
}
|
|
23
|
+
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react';
|
|
2
|
-
import { win, browser, router, app } from 'plusui
|
|
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 = {
|
|
@@ -130,5 +130,5 @@ function App() {
|
|
|
130
130
|
);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
export default App;
|
|
134
|
-
|
|
133
|
+
export default App;
|
|
134
|
+
|
|
@@ -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
|
+
|