@macle-community/simulator-web 0.0.0-stage → 1.0.0
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/README.md +125 -2
- package/bin/macle-web-simulator.js +108 -0
- package/package.json +37 -4
- package/runtime/framework/service/service.js +1 -0
- package/runtime/framework/view/view.js +1 -0
- package/runtime/remoteDebug/adapter.js +1 -0
- package/runtime/remoteDebug/service.html +16 -0
- package/runtime/remoteDebug/sub-packages.js +1 -0
- package/runtime/simulator/config/config.json +40 -0
- package/runtime/simulator/css/common.css +945 -0
- package/runtime/simulator/index.html +95 -0
- package/runtime/simulator/mockApi.json +1 -0
- package/runtime/simulator/native/native.js +1 -0
- package/runtime/simulator/public/default-user-avatar@128w.jpg +0 -0
- package/runtime/simulator/service/app.js +1 -0
- package/runtime/simulator/service/service.html +14 -0
- package/runtime/simulator/service/sync-api.js +1 -0
- package/runtime/simulator/version.json +1 -0
- package/runtime/simulator/view/webview.js +1 -0
- package/src/compiler.js +117 -0
- package/src/mock-backend.js +225 -0
- package/src/project-parser.js +87 -0
- package/src/server.js +334 -0
- package/src/watcher.js +76 -0
- package/web/index.html +234 -0
- package/web/qrcode.js +1145 -0
- package/web/simulator.css +526 -0
- package/web/simulator.js +510 -0
package/web/simulator.js
ADDED
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
// Macle MiniProgram Web Simulator Controller
|
|
2
|
+
(function() {
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const DEVICES = [
|
|
6
|
+
{ name: 'Huawei Mate 60 Pro', width: 392, height: 848, dpr: 3, notch: false },
|
|
7
|
+
{ name: 'Huawei Pura 70 Ultra', width: 400, height: 900, dpr: 3, notch: true },
|
|
8
|
+
{ name: 'iPhone 15 Pro', width: 393, height: 852, dpr: 3, notch: true },
|
|
9
|
+
{ name: 'iPhone 14 Pro Max', width: 430, height: 932, dpr: 3, notch: true },
|
|
10
|
+
{ name: 'iPhone 13', width: 390, height: 844, dpr: 3, notch: true },
|
|
11
|
+
{ name: 'iPhone 8 / SE', width: 375, height: 667, dpr: 2, notch: false },
|
|
12
|
+
{ name: 'Nexus 6', width: 412, height: 732, dpr: 3.5, notch: false },
|
|
13
|
+
{ name: 'iPad Pro 11"', width: 834, height: 1194, dpr: 2, notch: false }
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
let currentDevice = DEVICES[0];
|
|
17
|
+
let currentScale = 1.0;
|
|
18
|
+
let isLandscape = false;
|
|
19
|
+
let touchEnabled = false;
|
|
20
|
+
let autoReloadEnabled = true;
|
|
21
|
+
let projectInfo = null;
|
|
22
|
+
const logs = [];
|
|
23
|
+
const networkRequests = [];
|
|
24
|
+
|
|
25
|
+
// DOM Elements
|
|
26
|
+
const deviceSelect = document.getElementById('device-select');
|
|
27
|
+
const pageSelect = document.getElementById('page-select');
|
|
28
|
+
const scaleSelect = document.getElementById('scale-select');
|
|
29
|
+
const rotateBtn = document.getElementById('rotate-btn');
|
|
30
|
+
const refreshBtn = document.getElementById('refresh-btn');
|
|
31
|
+
const touchBtn = document.getElementById('touch-btn');
|
|
32
|
+
const networkSelect = document.getElementById('network-select');
|
|
33
|
+
const qrBtn = document.getElementById('qr-btn');
|
|
34
|
+
const devtoolsBtn = document.getElementById('devtools-btn');
|
|
35
|
+
const devtoolsDrawer = document.getElementById('devtools-drawer');
|
|
36
|
+
const closeDevtoolsBtn = document.getElementById('close-devtools');
|
|
37
|
+
const clearLogsBtn = document.getElementById('clear-logs');
|
|
38
|
+
const filterInput = document.getElementById('filter-logs');
|
|
39
|
+
const logsContainer = document.getElementById('logs-container');
|
|
40
|
+
const evalInput = document.getElementById('eval-input');
|
|
41
|
+
const evalForm = document.getElementById('eval-form');
|
|
42
|
+
const deviceWrapper = document.getElementById('device-wrapper');
|
|
43
|
+
const deviceFrame = document.getElementById('device-frame');
|
|
44
|
+
const deviceNotch = document.getElementById('device-notch');
|
|
45
|
+
const simulatorIframe = document.getElementById('simulator-iframe');
|
|
46
|
+
const touchCursor = document.getElementById('touch-cursor');
|
|
47
|
+
const pagePathEl = document.getElementById('page-path');
|
|
48
|
+
const copyPathBtn = document.getElementById('copy-path-btn');
|
|
49
|
+
const qrModal = document.getElementById('qr-modal');
|
|
50
|
+
const closeQrModalBtn = document.getElementById('close-qr-modal');
|
|
51
|
+
const qrcodeContainer = document.getElementById('qrcode-container');
|
|
52
|
+
const qrUrlEl = document.getElementById('qr-url');
|
|
53
|
+
const copyQrUrlBtn = document.getElementById('copy-qr-url-btn');
|
|
54
|
+
|
|
55
|
+
// DevTools Tabs
|
|
56
|
+
const devtoolsTabs = document.querySelectorAll('.devtools-tab');
|
|
57
|
+
const tabPanes = {
|
|
58
|
+
'console': document.getElementById('tab-console'),
|
|
59
|
+
'storage': document.getElementById('tab-storage'),
|
|
60
|
+
'network': document.getElementById('tab-network'),
|
|
61
|
+
'app-info': document.getElementById('tab-app-info')
|
|
62
|
+
};
|
|
63
|
+
const storageBody = document.getElementById('storage-body');
|
|
64
|
+
const refreshStorageBtn = document.getElementById('refresh-storage-btn');
|
|
65
|
+
const networkBody = document.getElementById('network-body');
|
|
66
|
+
const clearNetworkBtn = document.getElementById('clear-network-btn');
|
|
67
|
+
|
|
68
|
+
// App Info Elements
|
|
69
|
+
const infoProjectName = document.getElementById('info-project-name');
|
|
70
|
+
const infoEngineVersion = document.getElementById('info-engine-version');
|
|
71
|
+
const infoRuntimeVersion = document.getElementById('info-runtime-version');
|
|
72
|
+
const infoEntryPage = document.getElementById('info-entry-page');
|
|
73
|
+
const infoPagesList = document.getElementById('info-pages-list');
|
|
74
|
+
|
|
75
|
+
// Populate Device Select
|
|
76
|
+
DEVICES.forEach((d, idx) => {
|
|
77
|
+
const opt = document.createElement('option');
|
|
78
|
+
opt.value = idx;
|
|
79
|
+
opt.textContent = `${d.name} (${d.width}×${d.height})`;
|
|
80
|
+
deviceSelect.appendChild(opt);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Apply device size
|
|
84
|
+
function updateDeviceDimensions() {
|
|
85
|
+
let w = currentDevice.width;
|
|
86
|
+
let h = currentDevice.height;
|
|
87
|
+
if (isLandscape) {
|
|
88
|
+
const tmp = w; w = h; h = tmp;
|
|
89
|
+
}
|
|
90
|
+
deviceFrame.style.width = `${w + 24}px`;
|
|
91
|
+
deviceFrame.style.height = `${h + 24}px`;
|
|
92
|
+
|
|
93
|
+
if (currentDevice.notch && !isLandscape) {
|
|
94
|
+
deviceNotch.style.display = 'flex';
|
|
95
|
+
} else {
|
|
96
|
+
deviceNotch.style.display = 'none';
|
|
97
|
+
}
|
|
98
|
+
applyScale();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Apply zoom scale
|
|
102
|
+
function applyScale() {
|
|
103
|
+
if (scaleSelect.value === 'fit') {
|
|
104
|
+
const canvas = document.querySelector('.canvas');
|
|
105
|
+
const pad = 60;
|
|
106
|
+
const availW = canvas.clientWidth - pad;
|
|
107
|
+
const availH = canvas.clientHeight - pad;
|
|
108
|
+
let w = isLandscape ? currentDevice.height : currentDevice.width;
|
|
109
|
+
let h = isLandscape ? currentDevice.width : currentDevice.height;
|
|
110
|
+
const scaleW = availW / (w + 24);
|
|
111
|
+
const scaleH = availH / (h + 24);
|
|
112
|
+
currentScale = Math.min(1.0, scaleW, scaleH);
|
|
113
|
+
} else {
|
|
114
|
+
currentScale = parseFloat(scaleSelect.value);
|
|
115
|
+
}
|
|
116
|
+
deviceWrapper.style.transform = `scale(${currentScale})`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Event Listeners for Controls
|
|
120
|
+
deviceSelect.addEventListener('change', (e) => {
|
|
121
|
+
currentDevice = DEVICES[parseInt(e.target.value, 10)];
|
|
122
|
+
updateDeviceDimensions();
|
|
123
|
+
sendToSimulator({
|
|
124
|
+
command: 'SYSTEM_INFO_CHANGE',
|
|
125
|
+
inputParams: {
|
|
126
|
+
screenWidth: currentDevice.width,
|
|
127
|
+
screenHeight: currentDevice.height,
|
|
128
|
+
pixelRatio: currentDevice.dpr
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
scaleSelect.addEventListener('change', applyScale);
|
|
134
|
+
window.addEventListener('resize', () => {
|
|
135
|
+
if (scaleSelect.value === 'fit') applyScale();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
rotateBtn.addEventListener('click', () => {
|
|
139
|
+
isLandscape = !isLandscape;
|
|
140
|
+
rotateBtn.classList.toggle('active', isLandscape);
|
|
141
|
+
updateDeviceDimensions();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
refreshBtn.addEventListener('click', () => {
|
|
145
|
+
refreshBtn.classList.add('active');
|
|
146
|
+
simulatorIframe.src = simulatorIframe.src;
|
|
147
|
+
setTimeout(() => refreshBtn.classList.remove('active'), 500);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Touch Simulation
|
|
151
|
+
touchBtn.addEventListener('click', () => {
|
|
152
|
+
touchEnabled = !touchEnabled;
|
|
153
|
+
touchBtn.classList.toggle('active', touchEnabled);
|
|
154
|
+
touchCursor.style.display = touchEnabled ? 'block' : 'none';
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
window.addEventListener('mousemove', (e) => {
|
|
158
|
+
if (!touchEnabled) return;
|
|
159
|
+
touchCursor.style.left = `${e.clientX}px`;
|
|
160
|
+
touchCursor.style.top = `${e.clientY}px`;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
window.addEventListener('mousedown', (e) => {
|
|
164
|
+
if (!touchEnabled) return;
|
|
165
|
+
const ripple = document.createElement('div');
|
|
166
|
+
ripple.className = 'touch-ripple';
|
|
167
|
+
ripple.style.left = `${e.clientX}px`;
|
|
168
|
+
ripple.style.top = `${e.clientY}px`;
|
|
169
|
+
document.body.appendChild(ripple);
|
|
170
|
+
setTimeout(() => ripple.remove(), 400);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Network Selection
|
|
174
|
+
networkSelect.addEventListener('change', (e) => {
|
|
175
|
+
const net = e.target.value;
|
|
176
|
+
sendToSimulator({
|
|
177
|
+
callbackId: 1,
|
|
178
|
+
command: 'NETWORK_STATUS_CHANGE',
|
|
179
|
+
inputParams: { type: net }
|
|
180
|
+
});
|
|
181
|
+
addLog('info', `Network status changed to: ${net}`);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// DevTools Toggle
|
|
185
|
+
devtoolsBtn.addEventListener('click', () => {
|
|
186
|
+
devtoolsDrawer.classList.toggle('open');
|
|
187
|
+
devtoolsBtn.classList.toggle('active', devtoolsDrawer.classList.contains('open'));
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
closeDevtoolsBtn.addEventListener('click', () => {
|
|
191
|
+
devtoolsDrawer.classList.remove('open');
|
|
192
|
+
devtoolsBtn.classList.remove('active');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
clearLogsBtn.addEventListener('click', () => {
|
|
196
|
+
logs.length = 0;
|
|
197
|
+
renderLogs();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
filterInput.addEventListener('input', renderLogs);
|
|
201
|
+
|
|
202
|
+
// Tab Switching
|
|
203
|
+
devtoolsTabs.forEach(tab => {
|
|
204
|
+
tab.addEventListener('click', () => {
|
|
205
|
+
devtoolsTabs.forEach(t => t.classList.remove('active'));
|
|
206
|
+
tab.classList.add('active');
|
|
207
|
+
const targetKey = tab.dataset.tab;
|
|
208
|
+
Object.keys(tabPanes).forEach(k => {
|
|
209
|
+
if (tabPanes[k]) {
|
|
210
|
+
tabPanes[k].style.display = k === targetKey ? (k === 'console' ? 'flex' : 'block') : 'none';
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
if (targetKey === 'storage') renderStorage();
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Storage Inspector
|
|
218
|
+
function renderStorage() {
|
|
219
|
+
if (!storageBody) return;
|
|
220
|
+
try {
|
|
221
|
+
const iframeWin = simulatorIframe.contentWindow;
|
|
222
|
+
const keys = [];
|
|
223
|
+
if (iframeWin && iframeWin.localStorage) {
|
|
224
|
+
for (let i = 0; i < iframeWin.localStorage.length; i++) {
|
|
225
|
+
const k = iframeWin.localStorage.key(i);
|
|
226
|
+
keys.push({ key: k, value: iframeWin.localStorage.getItem(k) });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (keys.length === 0) {
|
|
230
|
+
storageBody.innerHTML = '<tr><td colspan="3" style="padding:10px;text-align:center;color:var(--text-muted);">No storage items found</td></tr>';
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
storageBody.innerHTML = '';
|
|
234
|
+
keys.forEach(item => {
|
|
235
|
+
const tr = document.createElement('tr');
|
|
236
|
+
tr.style.borderBottom = '1px solid var(--border-color)';
|
|
237
|
+
tr.innerHTML = `
|
|
238
|
+
<td style="padding:6px;color:var(--accent-color);">${escapeHtml(item.key)}</td>
|
|
239
|
+
<td style="padding:6px;color:var(--text-primary);max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${escapeHtml(item.value)}</td>
|
|
240
|
+
<td style="padding:6px;"><button class="tool-btn" style="padding:2px 6px;font-size:10px;" onclick="window.__removeStorage('${escapeHtml(item.key)}')">Delete</button></td>
|
|
241
|
+
`;
|
|
242
|
+
storageBody.appendChild(tr);
|
|
243
|
+
});
|
|
244
|
+
} catch (e) {
|
|
245
|
+
storageBody.innerHTML = `<tr><td colspan="3" style="padding:10px;color:var(--danger-color);">${e.message}</td></tr>`;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
window.__removeStorage = function(key) {
|
|
250
|
+
try {
|
|
251
|
+
const iframeWin = simulatorIframe.contentWindow;
|
|
252
|
+
if (iframeWin && iframeWin.localStorage) {
|
|
253
|
+
iframeWin.localStorage.removeItem(key);
|
|
254
|
+
renderStorage();
|
|
255
|
+
}
|
|
256
|
+
} catch (e) {}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
if (refreshStorageBtn) refreshStorageBtn.addEventListener('click', renderStorage);
|
|
260
|
+
|
|
261
|
+
// Network Inspector
|
|
262
|
+
function addNetworkRequest(method, url, status, time) {
|
|
263
|
+
networkRequests.push({ method, url, status, time });
|
|
264
|
+
renderNetwork();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function renderNetwork() {
|
|
268
|
+
if (!networkBody) return;
|
|
269
|
+
if (networkRequests.length === 0) {
|
|
270
|
+
networkBody.innerHTML = '<tr><td colspan="4" style="padding:10px;text-align:center;color:var(--text-muted);">No network requests recorded</td></tr>';
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
networkBody.innerHTML = '';
|
|
274
|
+
networkRequests.forEach(req => {
|
|
275
|
+
const tr = document.createElement('tr');
|
|
276
|
+
tr.style.borderBottom = '1px solid var(--border-color)';
|
|
277
|
+
const statusColor = (req.status >= 200 && req.status < 300) ? 'var(--success-color)' : 'var(--danger-color)';
|
|
278
|
+
tr.innerHTML = `
|
|
279
|
+
<td style="padding:6px;color:var(--accent-color);font-weight:bold;">${req.method}</td>
|
|
280
|
+
<td style="padding:6px;color:${statusColor};">${req.status}</td>
|
|
281
|
+
<td style="padding:6px;max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${escapeHtml(req.url)}</td>
|
|
282
|
+
<td style="padding:6px;color:var(--text-muted);">${req.time}</td>
|
|
283
|
+
`;
|
|
284
|
+
networkBody.appendChild(tr);
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (clearNetworkBtn) {
|
|
289
|
+
clearNetworkBtn.addEventListener('click', () => {
|
|
290
|
+
networkRequests.length = 0;
|
|
291
|
+
renderNetwork();
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// DevTools JS Evaluator
|
|
296
|
+
evalForm.addEventListener('submit', (e) => {
|
|
297
|
+
e.preventDefault();
|
|
298
|
+
const code = evalInput.value.trim();
|
|
299
|
+
if (!code) return;
|
|
300
|
+
addLog('info', `> ${code}`);
|
|
301
|
+
evalInput.value = '';
|
|
302
|
+
try {
|
|
303
|
+
const res = simulatorIframe.contentWindow.eval(code);
|
|
304
|
+
addLog('log', `< ${typeof res === 'object' ? JSON.stringify(res) : String(res)}`);
|
|
305
|
+
} catch (err) {
|
|
306
|
+
addLog('error', `< ${err.message}`);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// Logs management
|
|
311
|
+
function addLog(level, msg) {
|
|
312
|
+
const time = new Date().toLocaleTimeString();
|
|
313
|
+
logs.push({ level, msg, time });
|
|
314
|
+
if (logs.length > 500) logs.shift();
|
|
315
|
+
renderLogs();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function renderLogs() {
|
|
319
|
+
const filter = filterInput.value.toLowerCase();
|
|
320
|
+
logsContainer.innerHTML = '';
|
|
321
|
+
const filtered = logs.filter(l => !filter || l.msg.toLowerCase().includes(filter));
|
|
322
|
+
filtered.forEach(l => {
|
|
323
|
+
const entry = document.createElement('div');
|
|
324
|
+
entry.className = 'log-entry';
|
|
325
|
+
entry.innerHTML = `
|
|
326
|
+
<span class="log-time">${l.time}</span>
|
|
327
|
+
<span class="log-level ${l.level}">${l.level}</span>
|
|
328
|
+
<span class="log-msg">${escapeHtml(l.msg)}</span>
|
|
329
|
+
`;
|
|
330
|
+
logsContainer.appendChild(entry);
|
|
331
|
+
});
|
|
332
|
+
logsContainer.scrollTop = logsContainer.scrollHeight;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function escapeHtml(str) {
|
|
336
|
+
return String(str)
|
|
337
|
+
.replace(/&/g, '&')
|
|
338
|
+
.replace(/</g, '<')
|
|
339
|
+
.replace(/>/g, '>')
|
|
340
|
+
.replace(/"/g, '"');
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Communication with simulator iframe
|
|
344
|
+
function sendToSimulator(msg) {
|
|
345
|
+
try {
|
|
346
|
+
simulatorIframe.contentWindow.postMessage(msg, '*');
|
|
347
|
+
} catch (e) {
|
|
348
|
+
console.warn('Failed to send message to simulator:', e);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
window.addEventListener('message', (event) => {
|
|
353
|
+
const data = event.data;
|
|
354
|
+
if (!data) return;
|
|
355
|
+
|
|
356
|
+
// Handle cur-page-res
|
|
357
|
+
if (data.channel === 'cur-page-res' && data.args && data.args[0]) {
|
|
358
|
+
const p = data.args[0];
|
|
359
|
+
pagePathEl.textContent = p;
|
|
360
|
+
pagePathEl.title = p;
|
|
361
|
+
if (pageSelect) {
|
|
362
|
+
pageSelect.value = p;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Handle miniprogram console logs
|
|
367
|
+
if (data.type === 'macle-console') {
|
|
368
|
+
const msg = data.args.join(' ');
|
|
369
|
+
addLog(data.level, msg);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Handle network requests logging
|
|
373
|
+
if (data.type === 'macle-network') {
|
|
374
|
+
addNetworkRequest(data.method || 'GET', data.url, data.status || 200, new Date().toLocaleTimeString());
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// Copy Path
|
|
379
|
+
copyPathBtn.addEventListener('click', () => {
|
|
380
|
+
const text = pagePathEl.textContent;
|
|
381
|
+
if (text && text !== '-') {
|
|
382
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
383
|
+
copyPathBtn.title = 'Copied!';
|
|
384
|
+
setTimeout(() => copyPathBtn.title = 'Copy Path', 1500);
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
// QR Code Modal
|
|
390
|
+
qrBtn.addEventListener('click', () => {
|
|
391
|
+
qrModal.classList.add('open');
|
|
392
|
+
if (!qrcodeContainer.hasChildNodes() && window.QRCode) {
|
|
393
|
+
new QRCode(qrcodeContainer, {
|
|
394
|
+
text: qrUrlEl.textContent,
|
|
395
|
+
width: 180,
|
|
396
|
+
height: 180,
|
|
397
|
+
colorDark: '#0f172a',
|
|
398
|
+
colorLight: '#ffffff',
|
|
399
|
+
correctLevel: QRCode.CorrectLevel.M
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
closeQrModalBtn.addEventListener('click', () => {
|
|
405
|
+
qrModal.classList.remove('open');
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
copyQrUrlBtn.addEventListener('click', () => {
|
|
409
|
+
navigator.clipboard.writeText(qrUrlEl.textContent).then(() => {
|
|
410
|
+
copyQrUrlBtn.textContent = 'Copied!';
|
|
411
|
+
setTimeout(() => copyQrUrlBtn.textContent = 'Copy URL', 1500);
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
// Setup Live Reload EventSource
|
|
416
|
+
function setupLiveReload() {
|
|
417
|
+
if (!window.EventSource) return;
|
|
418
|
+
try {
|
|
419
|
+
const es = new EventSource('/api/live-reload');
|
|
420
|
+
es.onmessage = (e) => {
|
|
421
|
+
if (e.data === 'reload' && autoReloadEnabled) {
|
|
422
|
+
addLog('info', 'File change detected, auto-reloading simulator...');
|
|
423
|
+
simulatorIframe.src = simulatorIframe.src;
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
es.onerror = () => {
|
|
427
|
+
setTimeout(setupLiveReload, 5000);
|
|
428
|
+
};
|
|
429
|
+
} catch (e) {
|
|
430
|
+
console.warn('Live reload unavailable:', e);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Page Selection
|
|
435
|
+
if (pageSelect) {
|
|
436
|
+
pageSelect.addEventListener('change', (e) => {
|
|
437
|
+
const page = e.target.value;
|
|
438
|
+
if (!page) return;
|
|
439
|
+
try {
|
|
440
|
+
if (simulatorIframe.contentWindow && typeof simulatorIframe.contentWindow.goToPage === 'function') {
|
|
441
|
+
simulatorIframe.contentWindow.goToPage(page);
|
|
442
|
+
addLog('info', `Navigated simulator to: ${page}`);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
} catch (err) {}
|
|
446
|
+
const proj = projectInfo ? projectInfo.projectName : 'dist';
|
|
447
|
+
simulatorIframe.src = `/${proj}/root/debug/simulator/index.html?entryPagePath=${encodeURIComponent(page)}`;
|
|
448
|
+
addLog('info', `Switched entry page to: ${page}`);
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function renderAppInfo(info) {
|
|
453
|
+
if (infoProjectName) infoProjectName.textContent = info.projectName || 'Macle App';
|
|
454
|
+
if (infoEngineVersion) infoEngineVersion.textContent = info.engineVersion || '26.6.0-LG0001';
|
|
455
|
+
if (infoRuntimeVersion) infoRuntimeVersion.textContent = info.runtimeVersion || '100.4.2';
|
|
456
|
+
if (infoEntryPage) infoEntryPage.textContent = info.entryPagePath || 'pages/index/index';
|
|
457
|
+
if (infoPagesList && Array.isArray(info.pages)) {
|
|
458
|
+
infoPagesList.innerHTML = '';
|
|
459
|
+
info.pages.forEach(p => {
|
|
460
|
+
const li = document.createElement('li');
|
|
461
|
+
li.textContent = p;
|
|
462
|
+
infoPagesList.appendChild(li);
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Initialize
|
|
468
|
+
function init() {
|
|
469
|
+
updateDeviceDimensions();
|
|
470
|
+
setupLiveReload();
|
|
471
|
+
|
|
472
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
473
|
+
const targetPage = urlParams.get('page') || urlParams.get('entryPagePath');
|
|
474
|
+
const entryQuery = targetPage ? `?entryPagePath=${encodeURIComponent(targetPage)}` : '';
|
|
475
|
+
|
|
476
|
+
// Fetch project info
|
|
477
|
+
fetch('/api/project-info')
|
|
478
|
+
.then(res => res.json())
|
|
479
|
+
.then(info => {
|
|
480
|
+
projectInfo = info;
|
|
481
|
+
renderAppInfo(info);
|
|
482
|
+
|
|
483
|
+
if (info.pages && Array.isArray(info.pages) && pageSelect) {
|
|
484
|
+
pageSelect.innerHTML = '';
|
|
485
|
+
info.pages.forEach(p => {
|
|
486
|
+
const opt = document.createElement('option');
|
|
487
|
+
opt.value = p;
|
|
488
|
+
opt.textContent = p;
|
|
489
|
+
pageSelect.appendChild(opt);
|
|
490
|
+
});
|
|
491
|
+
if (targetPage) pageSelect.value = targetPage;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const finalUrl = info.simulatorUrl + entryQuery;
|
|
495
|
+
simulatorIframe.src = finalUrl;
|
|
496
|
+
const fullUrl = new URL(finalUrl, window.location.href).href;
|
|
497
|
+
qrUrlEl.textContent = fullUrl;
|
|
498
|
+
addLog('info', `Connected to Macle project: ${info.projectName}`);
|
|
499
|
+
addLog('info', `Dual-thread runtime initialized: ${finalUrl}`);
|
|
500
|
+
})
|
|
501
|
+
.catch(err => {
|
|
502
|
+
const simUrl = `/dist/root/debug/simulator/index.html${entryQuery}`;
|
|
503
|
+
simulatorIframe.src = simUrl;
|
|
504
|
+
qrUrlEl.textContent = new URL(simUrl, window.location.href).href;
|
|
505
|
+
addLog('info', `Loaded fallback simulator at: ${simUrl}`);
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
window.addEventListener('DOMContentLoaded', init);
|
|
510
|
+
})();
|