dsh-apk-vm 0.6.2 → 0.8.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/CHANGELOG.md +54 -0
- package/README.md +8 -3
- package/client.js +884 -16
- package/index.js +202 -7
- package/lib/aab.js +736 -0
- package/lib/android.js +95 -1
- package/lib/har.js +423 -0
- package/lib/ui-tests.js +393 -0
- package/package.json +6 -3
package/client.js
CHANGED
|
@@ -30,7 +30,7 @@ window.__ModuleLoader__.load({
|
|
|
30
30
|
var SWIPE_THRESHOLD_PX = 24;
|
|
31
31
|
var FRAME_INTERVAL_MS = 500;
|
|
32
32
|
var STATE_INTERVAL_MS = 2000;
|
|
33
|
-
var CLIENT_VERSION = '0.
|
|
33
|
+
var CLIENT_VERSION = '0.8.0';
|
|
34
34
|
|
|
35
35
|
/* ------------------------------------------------------------------ *
|
|
36
36
|
* Optional dsh-better-sidebar integration.
|
|
@@ -53,6 +53,26 @@ window.__ModuleLoader__.load({
|
|
|
53
53
|
return betterSidebar;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
function readUiFlag(key, fallback) {
|
|
57
|
+
try {
|
|
58
|
+
if (typeof window === 'undefined' || !window.localStorage) return fallback;
|
|
59
|
+
var value = window.localStorage.getItem(key);
|
|
60
|
+
return value === null ? fallback : value === '1';
|
|
61
|
+
} catch (error) {
|
|
62
|
+
return fallback;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function writeUiFlag(key, value) {
|
|
67
|
+
try {
|
|
68
|
+
if (typeof window !== 'undefined' && window.localStorage) {
|
|
69
|
+
window.localStorage.setItem(key, value ? '1' : '0');
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
/* localStorage may be unavailable in hardened webviews */
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
56
76
|
function publishBetterSidebar(next) {
|
|
57
77
|
if (betterSidebar === next) return;
|
|
58
78
|
betterSidebar = next;
|
|
@@ -105,6 +125,18 @@ window.__ModuleLoader__.load({
|
|
|
105
125
|
'action.openSidebar': 'Open in side bar',
|
|
106
126
|
'action.expand': 'Expand',
|
|
107
127
|
'action.restore': 'Exit expanded',
|
|
128
|
+
'action.float': 'Float window',
|
|
129
|
+
'action.dock': 'Dock panel',
|
|
130
|
+
'action.tools': 'Tools',
|
|
131
|
+
'action.hideTools': 'Hide tools',
|
|
132
|
+
'stream.quality': 'Quality',
|
|
133
|
+
'stream.quality.low': 'Low 540p · 30 fps',
|
|
134
|
+
'stream.quality.balanced': 'Balanced 720p · 60 fps',
|
|
135
|
+
'stream.quality.high': 'High 960p · 60 fps',
|
|
136
|
+
'stream.quality.custom': 'Custom',
|
|
137
|
+
'stage.firstBoot': 'First boot can take ~30 s; later starts use the quick-boot snapshot.',
|
|
138
|
+
'version.mismatch': 'The panel UI is stale (client {client}, host {host}). Reload the panel; if it stays stale, restart DSH once.',
|
|
139
|
+
'version.reload': 'Reload UI',
|
|
108
140
|
'a11y.refresh': 'Refresh VM state',
|
|
109
141
|
'a11y.retry': 'Retry the request',
|
|
110
142
|
'a11y.openSidebar': 'Open the APK VM in dsh-better-sidebar',
|
|
@@ -159,7 +191,12 @@ window.__ModuleLoader__.load({
|
|
|
159
191
|
'debug.liveStop': 'Stop live',
|
|
160
192
|
'debug.crash': 'Crash log',
|
|
161
193
|
'debug.clipboardSet': 'Copy field to device',
|
|
162
|
-
'debug.clipboardGet': '
|
|
194
|
+
'debug.clipboardGet': 'Copy device clipboard to host',
|
|
195
|
+
'debug.clipboardPasteHost': 'Paste host clipboard to device',
|
|
196
|
+
'debug.clipboardPasteHostDone': 'Host clipboard pasted to device',
|
|
197
|
+
'debug.clipboardUnavailable': 'Browser clipboard is unavailable; use the field instead',
|
|
198
|
+
'debug.visualTest': 'UI check',
|
|
199
|
+
'debug.visualTestDone': 'UI check complete',
|
|
163
200
|
'debug.clipboardEmpty': 'Type text in the input field first',
|
|
164
201
|
'debug.clipboardSetDone': 'Device clipboard updated',
|
|
165
202
|
'debug.clipboardGot': 'Device clipboard copied to host',
|
|
@@ -208,6 +245,10 @@ window.__ModuleLoader__.load({
|
|
|
208
245
|
'network.testFail': 'Connectivity check failed',
|
|
209
246
|
'network.pcap': 'Download pcap',
|
|
210
247
|
'network.pcapPath': 'Host pcap path (.pcap, next start)',
|
|
248
|
+
'network.harPath': 'HAR file path (.har)',
|
|
249
|
+
'network.harLoad': 'Load HAR',
|
|
250
|
+
'network.harLoaded': 'HAR loaded',
|
|
251
|
+
'network.harEmpty': 'No matching requests.',
|
|
211
252
|
'network.restartRequired': 'Restart the VM to apply custom DNS',
|
|
212
253
|
'network.sharedHint': 'Shared forwards are reachable from other VMs through the emulator host alias.',
|
|
213
254
|
'network.guestUrl': 'Guest URL: {url}',
|
|
@@ -222,6 +263,15 @@ window.__ModuleLoader__.load({
|
|
|
222
263
|
'flow.saved': 'Saved {name}',
|
|
223
264
|
'flow.done': 'Replayed {name}',
|
|
224
265
|
'flow.count': '{n} step(s)',
|
|
266
|
+
'snapshots.title': 'VM snapshots',
|
|
267
|
+
'snapshots.placeholder': 'snapshot name',
|
|
268
|
+
'snapshots.save': 'Save',
|
|
269
|
+
'snapshots.restore': 'Restore',
|
|
270
|
+
'snapshots.delete': 'Delete',
|
|
271
|
+
'snapshots.empty': 'No named snapshots yet.',
|
|
272
|
+
'snapshots.saved': 'Snapshot saved: {name}',
|
|
273
|
+
'snapshots.restored': 'Restored snapshot: {name}',
|
|
274
|
+
'snapshots.deleted': 'Deleted snapshot: {name}',
|
|
225
275
|
'storage.title': 'Storage',
|
|
226
276
|
'storage.refresh': 'Refresh',
|
|
227
277
|
'storage.cleanTemp': 'Clean temp',
|
|
@@ -249,6 +299,9 @@ window.__ModuleLoader__.load({
|
|
|
249
299
|
'setup.installImage': 'Download image',
|
|
250
300
|
'setup.installTools': 'Install SDK tools',
|
|
251
301
|
'setup.bootstrapSdk': 'Bootstrap SDK',
|
|
302
|
+
'setup.oneClick': 'One-click setup',
|
|
303
|
+
'setup.oneClickRunning': 'Setting up SDK, image and AVD…',
|
|
304
|
+
'setup.oneClickDone': 'Setup finished; starting the VM…',
|
|
252
305
|
'setup.refresh': 'Re-check',
|
|
253
306
|
'setup.switch': 'Switch AVD',
|
|
254
307
|
'setup.loading': 'Checking environment…',
|
|
@@ -260,12 +313,16 @@ window.__ModuleLoader__.load({
|
|
|
260
313
|
'instances.created': 'Created instance {id}',
|
|
261
314
|
'instances.destroyed': 'Destroyed instance {id}',
|
|
262
315
|
'instances.manage': 'VM instances',
|
|
316
|
+
'grid.title': 'Grid view',
|
|
317
|
+
'grid.empty': 'No running instances to show.',
|
|
318
|
+
'grid.close': 'Close grid',
|
|
263
319
|
'instances.stop': 'Stop',
|
|
264
320
|
'instances.stopped': 'Stopped instance {id}',
|
|
265
321
|
'instances.idle': 'idle {seconds}s',
|
|
266
322
|
'devices.title': 'Physical/remote adb device',
|
|
267
323
|
'devices.select': 'Select device…',
|
|
268
324
|
'devices.attach': 'Attach',
|
|
325
|
+
'devices.remotePlaceholder': 'host:port',
|
|
269
326
|
'devices.detach': 'Detach',
|
|
270
327
|
'devices.attached': 'Attached {serial}',
|
|
271
328
|
'devices.detached': 'Detached external device',
|
|
@@ -276,8 +333,16 @@ window.__ModuleLoader__.load({
|
|
|
276
333
|
'apps.empty': 'No third-party apps installed.',
|
|
277
334
|
'apps.thirdParty': 'third-party only',
|
|
278
335
|
'apps.stop': 'Stop',
|
|
336
|
+
'apps.launch': 'Launch',
|
|
279
337
|
'apps.clear': 'Clear data',
|
|
280
338
|
'apps.uninstall': 'Uninstall',
|
|
339
|
+
'apps.deepLink': 'Deep link',
|
|
340
|
+
'apps.deepLinkPlaceholder': 'myapp://path or https://…',
|
|
341
|
+
'apps.openDeepLink': 'Open link',
|
|
342
|
+
'apps.permission': 'Permission',
|
|
343
|
+
'apps.permissionPlaceholder': 'android.permission.CAMERA',
|
|
344
|
+
'apps.grant': 'Grant',
|
|
345
|
+
'apps.revoke': 'Revoke',
|
|
281
346
|
'apps.done': '{action}: {packageName}',
|
|
282
347
|
'debug.empty': 'No debug output yet.',
|
|
283
348
|
'debug.identity': '{release} · API {sdk} · {abi}',
|
|
@@ -340,6 +405,18 @@ window.__ModuleLoader__.load({
|
|
|
340
405
|
'action.openSidebar': '在侧栏打开',
|
|
341
406
|
'action.expand': '放大',
|
|
342
407
|
'action.restore': '退出放大',
|
|
408
|
+
'action.float': '浮窗',
|
|
409
|
+
'action.dock': '停靠面板',
|
|
410
|
+
'action.tools': '工具',
|
|
411
|
+
'action.hideTools': '收起工具',
|
|
412
|
+
'stream.quality': '清晰度',
|
|
413
|
+
'stream.quality.low': '低 540p · 30 fps',
|
|
414
|
+
'stream.quality.balanced': '均衡 720p · 60 fps',
|
|
415
|
+
'stream.quality.high': '高 960p · 60 fps',
|
|
416
|
+
'stream.quality.custom': '自定义',
|
|
417
|
+
'stage.firstBoot': '首次启动约需 30 秒;之后会使用快速启动快照。',
|
|
418
|
+
'version.mismatch': '面板 UI 是旧缓存(客户端 {client},主机 {host})。刷新面板;仍不更新则重启一次 DSH。',
|
|
419
|
+
'version.reload': '刷新 UI',
|
|
343
420
|
'a11y.refresh': '刷新虚拟机状态',
|
|
344
421
|
'a11y.retry': '重试请求',
|
|
345
422
|
'a11y.openSidebar': '在 dsh-better-sidebar 中打开 APK 虚拟机',
|
|
@@ -394,7 +471,12 @@ window.__ModuleLoader__.load({
|
|
|
394
471
|
'debug.liveStop': '停止实时',
|
|
395
472
|
'debug.crash': '崩溃日志',
|
|
396
473
|
'debug.clipboardSet': '文本写入设备剪贴板',
|
|
397
|
-
'debug.clipboardGet': '
|
|
474
|
+
'debug.clipboardGet': '复制设备剪贴板到电脑',
|
|
475
|
+
'debug.clipboardPasteHost': '把电脑剪贴板粘贴到设备',
|
|
476
|
+
'debug.clipboardPasteHostDone': '已把电脑剪贴板写入设备',
|
|
477
|
+
'debug.clipboardUnavailable': '浏览器剪贴板不可用,请改用输入框',
|
|
478
|
+
'debug.visualTest': 'UI 检查',
|
|
479
|
+
'debug.visualTestDone': 'UI 检查完成',
|
|
398
480
|
'debug.clipboardEmpty': '请先在上方输入框输入文本',
|
|
399
481
|
'debug.clipboardSetDone': '已写入设备剪贴板',
|
|
400
482
|
'debug.clipboardGot': '已复制设备剪贴板到主机',
|
|
@@ -443,6 +525,10 @@ window.__ModuleLoader__.load({
|
|
|
443
525
|
'network.testFail': '连通性检查失败',
|
|
444
526
|
'network.pcap': '下载 pcap',
|
|
445
527
|
'network.pcapPath': '宿主机 pcap 路径(.pcap,下次启动)',
|
|
528
|
+
'network.harPath': 'HAR 文件路径(.har)',
|
|
529
|
+
'network.harLoad': '加载 HAR',
|
|
530
|
+
'network.harLoaded': 'HAR 已加载',
|
|
531
|
+
'network.harEmpty': '没有匹配的请求。',
|
|
446
532
|
'network.restartRequired': '重启虚拟机后生效自定义 DNS',
|
|
447
533
|
'network.sharedHint': '共享转发可被其他 VM 通过模拟器宿主别名访问。',
|
|
448
534
|
'network.guestUrl': 'Guest URL: {url}',
|
|
@@ -457,6 +543,15 @@ window.__ModuleLoader__.load({
|
|
|
457
543
|
'flow.saved': '已保存 {name}',
|
|
458
544
|
'flow.done': '已回放 {name}',
|
|
459
545
|
'flow.count': '{n} 步',
|
|
546
|
+
'snapshots.title': 'VM 快照',
|
|
547
|
+
'snapshots.placeholder': '快照名称',
|
|
548
|
+
'snapshots.save': '保存',
|
|
549
|
+
'snapshots.restore': '恢复',
|
|
550
|
+
'snapshots.delete': '删除',
|
|
551
|
+
'snapshots.empty': '还没有命名快照。',
|
|
552
|
+
'snapshots.saved': '已保存快照:{name}',
|
|
553
|
+
'snapshots.restored': '已恢复快照:{name}',
|
|
554
|
+
'snapshots.deleted': '已删除快照:{name}',
|
|
460
555
|
'storage.title': '存储',
|
|
461
556
|
'storage.refresh': '刷新',
|
|
462
557
|
'storage.cleanTemp': '清理临时文件',
|
|
@@ -484,6 +579,9 @@ window.__ModuleLoader__.load({
|
|
|
484
579
|
'setup.installImage': '下载镜像',
|
|
485
580
|
'setup.installTools': '安装 SDK 工具',
|
|
486
581
|
'setup.bootstrapSdk': '从零安装 SDK',
|
|
582
|
+
'setup.oneClick': '一键初始化',
|
|
583
|
+
'setup.oneClickRunning': '正在安装 SDK、镜像并创建 AVD…',
|
|
584
|
+
'setup.oneClickDone': '初始化完成,正在启动虚拟机…',
|
|
487
585
|
'setup.refresh': '重新检测',
|
|
488
586
|
'setup.switch': '切换 AVD',
|
|
489
587
|
'setup.loading': '正在检测环境…',
|
|
@@ -495,12 +593,16 @@ window.__ModuleLoader__.load({
|
|
|
495
593
|
'instances.created': '已创建实例 {id}',
|
|
496
594
|
'instances.destroyed': '已销毁实例 {id}',
|
|
497
595
|
'instances.manage': '虚拟机实例',
|
|
596
|
+
'grid.title': '网格视图',
|
|
597
|
+
'grid.empty': '没有正在运行的实例。',
|
|
598
|
+
'grid.close': '关闭网格',
|
|
498
599
|
'instances.stop': '停止',
|
|
499
600
|
'instances.stopped': '已停止实例 {id}',
|
|
500
601
|
'instances.idle': '空闲 {seconds}s',
|
|
501
602
|
'devices.title': '物理/远程 adb 设备',
|
|
502
603
|
'devices.select': '选择设备…',
|
|
503
604
|
'devices.attach': '连接',
|
|
605
|
+
'devices.remotePlaceholder': '主机:端口',
|
|
504
606
|
'devices.detach': '断开',
|
|
505
607
|
'devices.attached': '已连接 {serial}',
|
|
506
608
|
'devices.detached': '已断开外部设备',
|
|
@@ -511,8 +613,16 @@ window.__ModuleLoader__.load({
|
|
|
511
613
|
'apps.empty': '没有已安装的第三方应用。',
|
|
512
614
|
'apps.thirdParty': '仅第三方',
|
|
513
615
|
'apps.stop': '停止',
|
|
616
|
+
'apps.launch': '启动',
|
|
514
617
|
'apps.clear': '清数据',
|
|
515
618
|
'apps.uninstall': '卸载',
|
|
619
|
+
'apps.deepLink': 'Deep Link',
|
|
620
|
+
'apps.deepLinkPlaceholder': 'myapp://path 或 https://…',
|
|
621
|
+
'apps.openDeepLink': '打开链接',
|
|
622
|
+
'apps.permission': '权限',
|
|
623
|
+
'apps.permissionPlaceholder': 'android.permission.CAMERA',
|
|
624
|
+
'apps.grant': '授予',
|
|
625
|
+
'apps.revoke': '撤销',
|
|
516
626
|
'apps.done': '{action}:{packageName}',
|
|
517
627
|
'debug.empty': '暂无调试输出。',
|
|
518
628
|
'debug.identity': '{release} · API {sdk} · {abi}',
|
|
@@ -1070,7 +1180,7 @@ window.__ModuleLoader__.load({
|
|
|
1070
1180
|
'@media (max-width:900px){.apkvm-header{flex-wrap:wrap}}' +
|
|
1071
1181
|
'.apkvm-embedded .apkvm-header{flex-wrap:wrap}' +
|
|
1072
1182
|
'.apkvm-page.apkvm-maximized{position:fixed;inset:0;z-index:2147483000;width:100vw;height:100vh;background:var(--dsw-alias-bg-base);box-shadow:0 0 0 1px var(--dsw-alias-border-l2)}' +
|
|
1073
|
-
'.apkvm-page.apkvm-maximized .apkvm-header{flex:0 0 auto;background:var(--dsw-alias-bg-base)}' +
|
|
1183
|
+
'.apkvm-page.apkvm-maximized .apkvm-header{flex:0 0 auto;background:var(--dsw-alias-bg-base);padding-right:142px}' +
|
|
1074
1184
|
'.apkvm-page.apkvm-maximized .apkvm-body{flex:1 1 auto;min-height:0;padding:10px 16px 14px;gap:10px;overflow:hidden;flex-direction:row}' +
|
|
1075
1185
|
'.apkvm-page.apkvm-maximized .apkvm-aside{display:none}' +
|
|
1076
1186
|
'.apkvm-page.apkvm-maximized .apkvm-stage{flex:1 1 auto;min-height:0;max-height:none}' +
|
|
@@ -1078,7 +1188,28 @@ window.__ModuleLoader__.load({
|
|
|
1078
1188
|
'.apkvm-page.apkvm-maximized .apkvm-phone{height:100%;max-height:calc(100vh - 150px);padding:6px;border-radius:22px}' +
|
|
1079
1189
|
'.apkvm-page.apkvm-maximized .apkvm-screen{height:100%;max-height:calc(100vh - 170px);border-radius:16px}' +
|
|
1080
1190
|
'.apkvm-page.apkvm-maximized .apkvm-controls{max-width:760px}' +
|
|
1081
|
-
'.apkvm-page.apkvm-maximized .apkvm-stagehint{max-width:760px}'
|
|
1191
|
+
'.apkvm-page.apkvm-maximized .apkvm-stagehint{max-width:760px}' +
|
|
1192
|
+
'.apkvm-page.apkvm-maximized .apkvm-subtitle,.apkvm-page.apkvm-maximized .apkvm-pillmeta{display:none}' +
|
|
1193
|
+
'.apkvm-page.apkvm-tools-collapsed .apkvm-aside{display:none}' +
|
|
1194
|
+
'.apkvm-max-close{position:absolute;top:9px;right:12px;z-index:6;appearance:none;font:inherit;font-size:12px;font-weight:600;height:30px;padding:0 12px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);cursor:pointer}' +
|
|
1195
|
+
'.apkvm-max-close:hover{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-brand-primary)}' +
|
|
1196
|
+
'.apkvm-quality{font:inherit;font-size:11px;height:24px;max-width:150px;padding:0 5px;border-radius:7px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);flex:none}' +
|
|
1197
|
+
'.apkvm-version-banner{flex:0 0 auto}' +
|
|
1198
|
+
'.apkvm-version-banner .apkvm-banner-text{white-space:normal}' +
|
|
1199
|
+
'.apkvm-grid-overlay{position:fixed;inset:0;z-index:2147483000;background:var(--dsw-alias-bg-base);display:flex;flex-direction:column;gap:10px;padding:12px 14px}' +
|
|
1200
|
+
'.apkvm-grid-head{display:flex;align-items:center;gap:8px;flex:0 0 auto}' +
|
|
1201
|
+
'.apkvm-grid{flex:1 1 auto;min-height:0;overflow:auto;display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px;align-content:start}' +
|
|
1202
|
+
'.apkvm-grid-card{appearance:none;font:inherit;text-align:left;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);padding:8px;display:flex;flex-direction:column;gap:6px;min-height:260px}' +
|
|
1203
|
+
'.apkvm-grid-card:hover{border-color:var(--dsw-alias-brand-primary)}' +
|
|
1204
|
+
'.apkvm-grid-card img{flex:1 1 auto;min-height:0;width:100%;height:240px;object-fit:contain;background:var(--dsw-alias-bg-base);border-radius:8px}' +
|
|
1205
|
+
'.apkvm-grid-meta{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--dsw-alias-label-secondary)}' +
|
|
1206
|
+
'.apkvm-page.apkvm-floating .apkvm-stage{position:fixed;right:14px;bottom:14px;z-index:2147483000;width:min(360px,92vw);height:min(700px,88vh);padding:6px;border:1px solid var(--dsw-alias-border-l2);border-radius:16px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}' +
|
|
1207
|
+
'.apkvm-page.apkvm-floating .apkvm-stage .apkvm-phonewrap{min-height:0;flex:1 1 auto}' +
|
|
1208
|
+
'.apkvm-page.apkvm-floating .apkvm-stage .apkvm-phone{height:100%;padding:4px;border-radius:14px}' +
|
|
1209
|
+
'.apkvm-page.apkvm-floating .apkvm-stage .apkvm-screen{border-radius:11px}' +
|
|
1210
|
+
'.apkvm-page.apkvm-floating .apkvm-stage .apkvm-controls{gap:3px;max-width:100%}' +
|
|
1211
|
+
'.apkvm-page.apkvm-floating .apkvm-stage .apkvm-textrow{display:none}' +
|
|
1212
|
+
'.apkvm-page.apkvm-floating .apkvm-stage .apkvm-stagehint{display:none}';
|
|
1082
1213
|
|
|
1083
1214
|
/* ------------------------------------------------------------------ *
|
|
1084
1215
|
* Render-error boundary: an unexpected exception inside the panel keeps
|
|
@@ -1186,6 +1317,15 @@ window.__ModuleLoader__.load({
|
|
|
1186
1317
|
var appActionBusyState = React.useState('');
|
|
1187
1318
|
var appActionBusy = appActionBusyState[0];
|
|
1188
1319
|
var setAppActionBusy = appActionBusyState[1];
|
|
1320
|
+
var deepLinkState = React.useState('');
|
|
1321
|
+
var deepLink = deepLinkState[0];
|
|
1322
|
+
var setDeepLink = deepLinkState[1];
|
|
1323
|
+
var permissionPackageState = React.useState('');
|
|
1324
|
+
var permissionPackage = permissionPackageState[0];
|
|
1325
|
+
var setPermissionPackage = permissionPackageState[1];
|
|
1326
|
+
var permissionNameState = React.useState('');
|
|
1327
|
+
var permissionName = permissionNameState[0];
|
|
1328
|
+
var setPermissionName = permissionNameState[1];
|
|
1189
1329
|
|
|
1190
1330
|
var vmBusyState = React.useState(false);
|
|
1191
1331
|
var vmBusy = vmBusyState[0];
|
|
@@ -1226,6 +1366,9 @@ window.__ModuleLoader__.load({
|
|
|
1226
1366
|
var devicePickState = React.useState('');
|
|
1227
1367
|
var devicePick = devicePickState[0];
|
|
1228
1368
|
var setDevicePick = devicePickState[1];
|
|
1369
|
+
var remoteSerialState = React.useState('');
|
|
1370
|
+
var remoteSerial = remoteSerialState[0];
|
|
1371
|
+
var setRemoteSerial = remoteSerialState[1];
|
|
1229
1372
|
var deviceBusyState = React.useState(false);
|
|
1230
1373
|
var deviceBusy = deviceBusyState[0];
|
|
1231
1374
|
var setDeviceBusy = deviceBusyState[1];
|
|
@@ -1240,6 +1383,12 @@ window.__ModuleLoader__.load({
|
|
|
1240
1383
|
var instancesListState = React.useState([]);
|
|
1241
1384
|
var instancesList = instancesListState[0];
|
|
1242
1385
|
var setInstancesList = instancesListState[1];
|
|
1386
|
+
var gridOpenState = React.useState(false);
|
|
1387
|
+
var gridOpen = gridOpenState[0];
|
|
1388
|
+
var setGridOpen = gridOpenState[1];
|
|
1389
|
+
var gridTickState = React.useState(Date.now());
|
|
1390
|
+
var gridTick = gridTickState[0];
|
|
1391
|
+
var setGridTick = gridTickState[1];
|
|
1243
1392
|
var instanceBusyState = React.useState(false);
|
|
1244
1393
|
var instanceBusy = instanceBusyState[0];
|
|
1245
1394
|
var setInstanceBusy = instanceBusyState[1];
|
|
@@ -1303,6 +1452,15 @@ window.__ModuleLoader__.load({
|
|
|
1303
1452
|
var storageConfirmState = React.useState('');
|
|
1304
1453
|
var storageConfirm = storageConfirmState[0];
|
|
1305
1454
|
var setStorageConfirm = storageConfirmState[1];
|
|
1455
|
+
var snapshotNamesState = React.useState([]);
|
|
1456
|
+
var snapshotNames = snapshotNamesState[0];
|
|
1457
|
+
var setSnapshotNames = snapshotNamesState[1];
|
|
1458
|
+
var snapshotInputState = React.useState('');
|
|
1459
|
+
var snapshotInput = snapshotInputState[0];
|
|
1460
|
+
var setSnapshotInput = snapshotInputState[1];
|
|
1461
|
+
var snapshotBusyState = React.useState('');
|
|
1462
|
+
var snapshotBusy = snapshotBusyState[0];
|
|
1463
|
+
var setSnapshotBusy = snapshotBusyState[1];
|
|
1306
1464
|
|
|
1307
1465
|
var proxyState = React.useState({ host: '', port: '' });
|
|
1308
1466
|
var proxy = proxyState[0];
|
|
@@ -1337,6 +1495,15 @@ window.__ModuleLoader__.load({
|
|
|
1337
1495
|
var pcapPathState = React.useState('');
|
|
1338
1496
|
var pcapPath = pcapPathState[0];
|
|
1339
1497
|
var setPcapPath = pcapPathState[1];
|
|
1498
|
+
var harPathState = React.useState('');
|
|
1499
|
+
var harPath = harPathState[0];
|
|
1500
|
+
var setHarPath = harPathState[1];
|
|
1501
|
+
var harResultState = React.useState(null);
|
|
1502
|
+
var harResult = harResultState[0];
|
|
1503
|
+
var setHarResult = harResultState[1];
|
|
1504
|
+
var harBusyState = React.useState(false);
|
|
1505
|
+
var harBusy = harBusyState[0];
|
|
1506
|
+
var setHarBusy = harBusyState[1];
|
|
1340
1507
|
|
|
1341
1508
|
var actualFpsState = React.useState(0);
|
|
1342
1509
|
var actualFps = actualFpsState[0];
|
|
@@ -1388,12 +1555,31 @@ window.__ModuleLoader__.load({
|
|
|
1388
1555
|
var decoderRef = React.useRef(null);
|
|
1389
1556
|
var wsRef = React.useRef(null);
|
|
1390
1557
|
var pageRef = React.useRef(null);
|
|
1391
|
-
var maximizedState = React.useState(
|
|
1558
|
+
var maximizedState = React.useState(function () {
|
|
1559
|
+
return readUiFlag('apk-vm.maximized', false);
|
|
1560
|
+
});
|
|
1392
1561
|
var isMaximized = maximizedState[0];
|
|
1393
1562
|
var setIsMaximized = maximizedState[1];
|
|
1563
|
+
var toolsState = React.useState(!embedded);
|
|
1564
|
+
var toolsOpen = toolsState[0];
|
|
1565
|
+
var setToolsOpen = toolsState[1];
|
|
1566
|
+
var floatingState = React.useState(false);
|
|
1567
|
+
var isFloating = floatingState[0];
|
|
1568
|
+
var setIsFloating = floatingState[1];
|
|
1569
|
+
var qualityBusyState = React.useState('');
|
|
1570
|
+
var qualityBusy = qualityBusyState[0];
|
|
1571
|
+
var setQualityBusy = qualityBusyState[1];
|
|
1394
1572
|
|
|
1395
1573
|
/* ---- derived snapshot values --------------------------------- */
|
|
1396
1574
|
var vmInfo = snapshot ? snapshot.vm : null;
|
|
1575
|
+
var hostVersion = snapshot && snapshot.pluginVersion ? String(snapshot.pluginVersion) : '';
|
|
1576
|
+
var versionMismatch = Boolean(hostVersion && hostVersion !== CLIENT_VERSION);
|
|
1577
|
+
var h264Status =
|
|
1578
|
+
snapshot && isObject(snapshot.stream) && isObject(snapshot.stream.h264)
|
|
1579
|
+
? snapshot.stream.h264
|
|
1580
|
+
: null;
|
|
1581
|
+
var streamPreset = h264Status && h264Status.preset ? String(h264Status.preset) : 'custom';
|
|
1582
|
+
var streamQualityValue = qualityBusy || streamPreset;
|
|
1397
1583
|
var clientReport = snapshot && isObject(snapshot.client) ? snapshot.client : null;
|
|
1398
1584
|
var wsOrigin = snapshot && snapshot.origin ? snapshot.origin : '';
|
|
1399
1585
|
var apkInfo = snapshot ? snapshot.apk : null;
|
|
@@ -1451,6 +1637,7 @@ window.__ModuleLoader__.load({
|
|
|
1451
1637
|
pillMeta.push(T(bootKey, { s: bootSeconds }));
|
|
1452
1638
|
}
|
|
1453
1639
|
}
|
|
1640
|
+
pillMeta.push('v' + CLIENT_VERSION);
|
|
1454
1641
|
var subtitleText = apkInfo
|
|
1455
1642
|
? T('apk.current', { name: apkInfo.label || apkInfo.packageName || apkInfo.path })
|
|
1456
1643
|
: T('subtitle');
|
|
@@ -1543,9 +1730,22 @@ window.__ModuleLoader__.load({
|
|
|
1543
1730
|
// Load network details once the VM becomes ready (the first render often
|
|
1544
1731
|
// happens while the state is still loading).
|
|
1545
1732
|
React.useEffect(function () {
|
|
1546
|
-
if (ready)
|
|
1733
|
+
if (ready) {
|
|
1734
|
+
loadNetwork();
|
|
1735
|
+
loadSnapshots();
|
|
1736
|
+
}
|
|
1547
1737
|
}, [ready, activeInstance]);
|
|
1548
1738
|
|
|
1739
|
+
React.useEffect(function () {
|
|
1740
|
+
if (!gridOpen) return undefined;
|
|
1741
|
+
var timer = setInterval(function () {
|
|
1742
|
+
if (mountedRef.current) setGridTick(Date.now());
|
|
1743
|
+
}, 1500);
|
|
1744
|
+
return function () {
|
|
1745
|
+
clearInterval(timer);
|
|
1746
|
+
};
|
|
1747
|
+
}, [gridOpen]);
|
|
1748
|
+
|
|
1549
1749
|
// Escape leaves the expanded VM view; the expand button is inside the
|
|
1550
1750
|
// full-screen overlay too, so mouse-only users are never trapped.
|
|
1551
1751
|
React.useEffect(function () {
|
|
@@ -1553,7 +1753,10 @@ window.__ModuleLoader__.load({
|
|
|
1553
1753
|
return undefined;
|
|
1554
1754
|
}
|
|
1555
1755
|
function onKeyDown(event) {
|
|
1556
|
-
if (event && event.key === 'Escape')
|
|
1756
|
+
if (event && event.key === 'Escape') {
|
|
1757
|
+
setIsMaximized(false);
|
|
1758
|
+
writeUiFlag('apk-vm.maximized', false);
|
|
1759
|
+
}
|
|
1557
1760
|
}
|
|
1558
1761
|
document.addEventListener('keydown', onKeyDown);
|
|
1559
1762
|
return function () {
|
|
@@ -1920,6 +2123,11 @@ window.__ModuleLoader__.load({
|
|
|
1920
2123
|
return id === 'default' ? '/apk-vm' + pathname : '/apk-vm/instances/' + encodeURIComponent(id) + pathname;
|
|
1921
2124
|
}
|
|
1922
2125
|
|
|
2126
|
+
function instanceFrameSrc(id) {
|
|
2127
|
+
var prefix = id === 'default' ? '/apk-vm/frame' : '/apk-vm/instances/' + encodeURIComponent(id) + '/frame';
|
|
2128
|
+
return prefix + '?t=' + String(gridTick);
|
|
2129
|
+
}
|
|
2130
|
+
|
|
1923
2131
|
function setActiveInstanceId(id) {
|
|
1924
2132
|
var next = id || 'default';
|
|
1925
2133
|
activeInstanceRef.current = next;
|
|
@@ -2272,6 +2480,64 @@ window.__ModuleLoader__.load({
|
|
|
2272
2480
|
});
|
|
2273
2481
|
}
|
|
2274
2482
|
|
|
2483
|
+
function toggleMaximized() {
|
|
2484
|
+
setIsMaximized(function (value) {
|
|
2485
|
+
var next = !value;
|
|
2486
|
+
writeUiFlag('apk-vm.maximized', next);
|
|
2487
|
+
return next;
|
|
2488
|
+
});
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
function toggleTools() {
|
|
2492
|
+
setToolsOpen(function (value) {
|
|
2493
|
+
return !value;
|
|
2494
|
+
});
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
function applyStreamQuality(preset) {
|
|
2498
|
+
if (!ready || qualityBusy || activeInstance !== 'default') return;
|
|
2499
|
+
setQualityBusy(preset);
|
|
2500
|
+
requestJson(
|
|
2501
|
+
'/apk-vm/stream/quality',
|
|
2502
|
+
{ method: 'POST', body: JSON.stringify({ preset: preset }) },
|
|
2503
|
+
T
|
|
2504
|
+
).then(function (result) {
|
|
2505
|
+
if (!mountedRef.current) return;
|
|
2506
|
+
setQualityBusy('');
|
|
2507
|
+
if (!result.ok) {
|
|
2508
|
+
pushToast('error', T('err.action') + ': ' + result.error);
|
|
2509
|
+
return;
|
|
2510
|
+
}
|
|
2511
|
+
pushToast('success', T('stream.quality') + ': ' + T('stream.quality.' + preset));
|
|
2512
|
+
loadState();
|
|
2513
|
+
});
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
function reloadUi() {
|
|
2517
|
+
if (typeof window !== 'undefined' && window.location && typeof window.location.reload === 'function') {
|
|
2518
|
+
window.location.reload();
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
function loadHar() {
|
|
2523
|
+
var target = String(harPath || '').trim();
|
|
2524
|
+
if (harBusy || !target) return;
|
|
2525
|
+
setHarBusy(true);
|
|
2526
|
+
requestJson(instancePath('/network/har'), {
|
|
2527
|
+
method: 'POST',
|
|
2528
|
+
body: JSON.stringify({ path: target }),
|
|
2529
|
+
}, T).then(function (result) {
|
|
2530
|
+
if (!mountedRef.current) return;
|
|
2531
|
+
setHarBusy(false);
|
|
2532
|
+
if (!result.ok) {
|
|
2533
|
+
pushToast('error', (result.error || T('err.action')).slice(0, 500));
|
|
2534
|
+
return;
|
|
2535
|
+
}
|
|
2536
|
+
setHarResult(isObject(result.data) ? result.data : null);
|
|
2537
|
+
pushToast('success', T('network.harLoaded'));
|
|
2538
|
+
});
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2275
2541
|
function loadNetwork() {
|
|
2276
2542
|
if (!ready) return Promise.resolve(null);
|
|
2277
2543
|
setNetworkBusy('load');
|
|
@@ -2417,6 +2683,37 @@ window.__ModuleLoader__.load({
|
|
|
2417
2683
|
});
|
|
2418
2684
|
}
|
|
2419
2685
|
|
|
2686
|
+
function loadSnapshots() {
|
|
2687
|
+
return requestJson(instancePath('/snapshots'), null, T).then(function (result) {
|
|
2688
|
+
if (!mountedRef.current) return result;
|
|
2689
|
+
if (result.ok && isObject(result.data) && Array.isArray(result.data.names)) {
|
|
2690
|
+
setSnapshotNames(result.data.names);
|
|
2691
|
+
}
|
|
2692
|
+
return result;
|
|
2693
|
+
});
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
function snapshotAction(action, name) {
|
|
2697
|
+
if (snapshotBusy) return;
|
|
2698
|
+
setSnapshotBusy(action + ':' + name);
|
|
2699
|
+
requestJson(instancePath('/snapshots'), {
|
|
2700
|
+
method: 'POST',
|
|
2701
|
+
body: JSON.stringify({ action: action, name: name }),
|
|
2702
|
+
}, T).then(function (result) {
|
|
2703
|
+
if (!mountedRef.current) return;
|
|
2704
|
+
setSnapshotBusy('');
|
|
2705
|
+
if (!result.ok) {
|
|
2706
|
+
pushToast('error', (result.error || T('err.action')).slice(0, 400));
|
|
2707
|
+
return;
|
|
2708
|
+
}
|
|
2709
|
+
var key = action === 'save' ? 'snapshots.saved' : action === 'load' ? 'snapshots.restored' : 'snapshots.deleted';
|
|
2710
|
+
pushToast('success', T(key, { name: name }));
|
|
2711
|
+
if (action === 'save') setSnapshotInput('');
|
|
2712
|
+
loadSnapshots();
|
|
2713
|
+
if (action === 'load') loadState();
|
|
2714
|
+
});
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2420
2717
|
function loadStorage() {
|
|
2421
2718
|
setStorageBusy('load');
|
|
2422
2719
|
setStorageError('');
|
|
@@ -2532,6 +2829,53 @@ window.__ModuleLoader__.load({
|
|
|
2532
2829
|
});
|
|
2533
2830
|
}
|
|
2534
2831
|
|
|
2832
|
+
function openDeepLinkAction() {
|
|
2833
|
+
if (!ready || appActionBusy) return;
|
|
2834
|
+
var target = String(deepLink || '').trim();
|
|
2835
|
+
if (!target) {
|
|
2836
|
+
pushToast('error', T('apps.deepLinkPlaceholder'));
|
|
2837
|
+
return;
|
|
2838
|
+
}
|
|
2839
|
+
setAppActionBusy('open-url');
|
|
2840
|
+
requestJson(instancePath('/debug/action'), {
|
|
2841
|
+
method: 'POST',
|
|
2842
|
+
body: JSON.stringify({ action: 'open-url', url: target }),
|
|
2843
|
+
}, T).then(function (result) {
|
|
2844
|
+
if (!mountedRef.current) return;
|
|
2845
|
+
setAppActionBusy('');
|
|
2846
|
+
if (!result.ok) {
|
|
2847
|
+
pushToast('error', (result.error || T('err.action')).slice(0, 400));
|
|
2848
|
+
return;
|
|
2849
|
+
}
|
|
2850
|
+
pushToast('success', T('apps.deepLink') + ': ' + target);
|
|
2851
|
+
loadState();
|
|
2852
|
+
});
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
function changePermission(granted) {
|
|
2856
|
+
if (!ready || appActionBusy) return;
|
|
2857
|
+
var pkg = String(permissionPackage || '').trim();
|
|
2858
|
+
var permission = String(permissionName || '').trim();
|
|
2859
|
+
if (!pkg || !permission) {
|
|
2860
|
+
pushToast('error', T('apps.permissionPlaceholder'));
|
|
2861
|
+
return;
|
|
2862
|
+
}
|
|
2863
|
+
var action = granted ? 'grant' : 'revoke';
|
|
2864
|
+
setAppActionBusy(action + ':' + pkg);
|
|
2865
|
+
requestJson(instancePath('/debug/action'), {
|
|
2866
|
+
method: 'POST',
|
|
2867
|
+
body: JSON.stringify({ action: action, packageName: pkg, permission: permission }),
|
|
2868
|
+
}, T).then(function (result) {
|
|
2869
|
+
if (!mountedRef.current) return;
|
|
2870
|
+
setAppActionBusy('');
|
|
2871
|
+
if (!result.ok) {
|
|
2872
|
+
pushToast('error', (result.error || T('err.action')).slice(0, 400));
|
|
2873
|
+
return;
|
|
2874
|
+
}
|
|
2875
|
+
pushToast('success', T('apps.permission') + ': ' + permission);
|
|
2876
|
+
});
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2535
2879
|
function toggleApps() {
|
|
2536
2880
|
var next = !appsOpen;
|
|
2537
2881
|
setAppsOpen(next);
|
|
@@ -2612,6 +2956,27 @@ window.__ModuleLoader__.load({
|
|
|
2612
2956
|
});
|
|
2613
2957
|
}
|
|
2614
2958
|
|
|
2959
|
+
function attachRemoteDevice() {
|
|
2960
|
+
var serial = String(remoteSerial || '').trim();
|
|
2961
|
+
if (!serial || deviceBusy) return;
|
|
2962
|
+
setDeviceBusy(true);
|
|
2963
|
+
setDeviceError('');
|
|
2964
|
+
requestJson('/apk-vm/device', { method: 'POST', body: JSON.stringify({ serial: serial }) }, T).then(function (result) {
|
|
2965
|
+
if (!mountedRef.current) return;
|
|
2966
|
+
setDeviceBusy(false);
|
|
2967
|
+
if (!result.ok) {
|
|
2968
|
+
var data = isObject(result.data) ? result.data : {};
|
|
2969
|
+
pushToast('error', (data.error || result.error || T('err.action')).slice(0, 500));
|
|
2970
|
+
return;
|
|
2971
|
+
}
|
|
2972
|
+
pushToast('success', T('devices.attached', { serial: serial }));
|
|
2973
|
+
setRemoteSerial('');
|
|
2974
|
+
loadState();
|
|
2975
|
+
loadDevices();
|
|
2976
|
+
loadSetup(true);
|
|
2977
|
+
});
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2615
2980
|
function detachDevice() {
|
|
2616
2981
|
if (deviceBusy) return;
|
|
2617
2982
|
setDeviceBusy(true);
|
|
@@ -2633,7 +2998,11 @@ window.__ModuleLoader__.load({
|
|
|
2633
2998
|
function runSetupAction(action) {
|
|
2634
2999
|
if (setupBusy) return;
|
|
2635
3000
|
setSetupBusy(true);
|
|
2636
|
-
requestJson(
|
|
3001
|
+
requestJson(
|
|
3002
|
+
'/apk-vm/setup',
|
|
3003
|
+
{ method: 'POST', body: JSON.stringify({ action: action, confirmDownload: true }) },
|
|
3004
|
+
T
|
|
3005
|
+
).then(function (result) {
|
|
2637
3006
|
if (!mountedRef.current) return;
|
|
2638
3007
|
setSetupBusy(false);
|
|
2639
3008
|
var data = isObject(result.data) ? result.data : {};
|
|
@@ -2648,6 +3017,57 @@ window.__ModuleLoader__.load({
|
|
|
2648
3017
|
});
|
|
2649
3018
|
}
|
|
2650
3019
|
|
|
3020
|
+
function runOneClickSetup() {
|
|
3021
|
+
if (setupBusy) return;
|
|
3022
|
+
var order = ['bootstrap-sdk', 'install-tools', 'install-image', 'create-avd'];
|
|
3023
|
+
var steps = order.filter(function (id) {
|
|
3024
|
+
return (setupActions || []).some(function (entry) {
|
|
3025
|
+
return entry.id === id;
|
|
3026
|
+
});
|
|
3027
|
+
});
|
|
3028
|
+
if (!steps.length) {
|
|
3029
|
+
pushToast('error', T('setup.ok'));
|
|
3030
|
+
return;
|
|
3031
|
+
}
|
|
3032
|
+
setSetupBusy(true);
|
|
3033
|
+
pushToast('success', T('setup.oneClickRunning'));
|
|
3034
|
+
var failed = false;
|
|
3035
|
+
var chain = Promise.resolve();
|
|
3036
|
+
steps.forEach(function (action) {
|
|
3037
|
+
chain = chain.then(function () {
|
|
3038
|
+
if (failed) return null;
|
|
3039
|
+
return requestJson(
|
|
3040
|
+
'/apk-vm/setup',
|
|
3041
|
+
{ method: 'POST', body: JSON.stringify({ action: action, confirmDownload: true }) },
|
|
3042
|
+
T
|
|
3043
|
+
).then(function (result) {
|
|
3044
|
+
var data = isObject(result.data) ? result.data : {};
|
|
3045
|
+
if (!result.ok || data.ok === false) {
|
|
3046
|
+
failed = true;
|
|
3047
|
+
var detail = data.result && data.result.error ? data.result.error : (data.result && data.result.output ? data.result.output : result.error || '');
|
|
3048
|
+
pushToast('error', String(detail || T('err.state')).slice(0, 500));
|
|
3049
|
+
}
|
|
3050
|
+
});
|
|
3051
|
+
});
|
|
3052
|
+
});
|
|
3053
|
+
chain
|
|
3054
|
+
.then(function () {
|
|
3055
|
+
if (!mountedRef.current) return;
|
|
3056
|
+
setSetupBusy(false);
|
|
3057
|
+
loadSetup(true);
|
|
3058
|
+
loadState();
|
|
3059
|
+
if (!failed) {
|
|
3060
|
+
pushToast('success', T('setup.oneClickDone'));
|
|
3061
|
+
runVmAction('start');
|
|
3062
|
+
}
|
|
3063
|
+
})
|
|
3064
|
+
.catch(function (error) {
|
|
3065
|
+
if (!mountedRef.current) return;
|
|
3066
|
+
setSetupBusy(false);
|
|
3067
|
+
pushToast('error', String(error && error.message ? error.message : error).slice(0, 500));
|
|
3068
|
+
});
|
|
3069
|
+
}
|
|
3070
|
+
|
|
2651
3071
|
/* ---- actions -------------------------------------------------- */
|
|
2652
3072
|
function pushToast(kind, text) {
|
|
2653
3073
|
if (!mountedRef.current) return;
|
|
@@ -2872,6 +3292,9 @@ window.__ModuleLoader__.load({
|
|
|
2872
3292
|
});
|
|
2873
3293
|
}
|
|
2874
3294
|
|
|
3295
|
+
// Clipboard is intentionally passive: host clipboard is read or written
|
|
3296
|
+
// only inside these explicit click handlers. Never poll, listen to
|
|
3297
|
+
// clipboard events, or sync in the background.
|
|
2875
3298
|
function setDeviceClipboard() {
|
|
2876
3299
|
if (!ready) return;
|
|
2877
3300
|
var text = String(textInput == null ? '' : textInput);
|
|
@@ -2909,6 +3332,61 @@ window.__ModuleLoader__.load({
|
|
|
2909
3332
|
});
|
|
2910
3333
|
}
|
|
2911
3334
|
|
|
3335
|
+
function pasteHostClipboardToDevice() {
|
|
3336
|
+
if (!ready) return;
|
|
3337
|
+
if (
|
|
3338
|
+
typeof navigator === 'undefined' ||
|
|
3339
|
+
!navigator.clipboard ||
|
|
3340
|
+
typeof navigator.clipboard.readText !== 'function'
|
|
3341
|
+
) {
|
|
3342
|
+
pushToast('error', T('debug.clipboardUnavailable'));
|
|
3343
|
+
return;
|
|
3344
|
+
}
|
|
3345
|
+
navigator.clipboard.readText().then(
|
|
3346
|
+
function (text) {
|
|
3347
|
+
if (!mountedRef.current) return;
|
|
3348
|
+
if (!text) {
|
|
3349
|
+
pushToast('error', T('debug.clipboardEmpty'));
|
|
3350
|
+
return;
|
|
3351
|
+
}
|
|
3352
|
+
requestJson(
|
|
3353
|
+
instancePath('/clipboard'),
|
|
3354
|
+
{ method: 'POST', body: JSON.stringify({ text: text, paste: false }) },
|
|
3355
|
+
T
|
|
3356
|
+
).then(function (result) {
|
|
3357
|
+
if (!mountedRef.current) return;
|
|
3358
|
+
if (!result.ok) {
|
|
3359
|
+
pushToast('error', (result.error || T('err.action')).slice(0, 300));
|
|
3360
|
+
return;
|
|
3361
|
+
}
|
|
3362
|
+
setDebugOutput('clipboard host -> device (' + text.length + ' chars)');
|
|
3363
|
+
pushToast('success', T('debug.clipboardPasteHostDone'));
|
|
3364
|
+
});
|
|
3365
|
+
},
|
|
3366
|
+
function () {
|
|
3367
|
+
pushToast('error', T('debug.clipboardUnavailable'));
|
|
3368
|
+
}
|
|
3369
|
+
);
|
|
3370
|
+
}
|
|
3371
|
+
|
|
3372
|
+
function runUiTest() {
|
|
3373
|
+
if (!ready || debugBusy) return;
|
|
3374
|
+
setDebugBusy(true);
|
|
3375
|
+
requestJson(instancePath('/ui-test'), {
|
|
3376
|
+
method: 'POST',
|
|
3377
|
+
body: JSON.stringify({ name: 'panel', assertions: [], updateBaseline: false }),
|
|
3378
|
+
}, T).then(function (result) {
|
|
3379
|
+
if (!mountedRef.current) return;
|
|
3380
|
+
setDebugBusy(false);
|
|
3381
|
+
if (!result.ok) {
|
|
3382
|
+
pushToast('error', (result.error || T('err.action')).slice(0, 500));
|
|
3383
|
+
return;
|
|
3384
|
+
}
|
|
3385
|
+
setDebugOutput(String((result.data && (result.data.markdown || result.data.report)) || ''));
|
|
3386
|
+
pushToast('success', T('debug.visualTestDone'));
|
|
3387
|
+
});
|
|
3388
|
+
}
|
|
3389
|
+
|
|
2912
3390
|
function runDebugInfo() {
|
|
2913
3391
|
runDebugRequest(instancePath('/debug/info'), null, function (data) {
|
|
2914
3392
|
return JSON.stringify(data, null, 2);
|
|
@@ -3474,6 +3952,31 @@ window.__ModuleLoader__.load({
|
|
|
3474
3952
|
h(
|
|
3475
3953
|
'div',
|
|
3476
3954
|
{ className: 'apkvm-toolsline' },
|
|
3955
|
+
h('input', {
|
|
3956
|
+
className: 'apkvm-input',
|
|
3957
|
+
type: 'text',
|
|
3958
|
+
value: remoteSerial,
|
|
3959
|
+
placeholder: T('devices.remotePlaceholder'),
|
|
3960
|
+
spellCheck: false,
|
|
3961
|
+
disabled: deviceBusy,
|
|
3962
|
+
style: { flex: '0 1 96px', minWidth: '0' },
|
|
3963
|
+
onChange: function (event) {
|
|
3964
|
+
setRemoteSerial(event && event.target ? String(event.target.value) : '');
|
|
3965
|
+
},
|
|
3966
|
+
}),
|
|
3967
|
+
h(
|
|
3968
|
+
'button',
|
|
3969
|
+
{
|
|
3970
|
+
type: 'button',
|
|
3971
|
+
className: 'apkvm-mini',
|
|
3972
|
+
disabled: deviceBusy || !remoteSerial.trim(),
|
|
3973
|
+
onClick: function (event) {
|
|
3974
|
+
event.preventDefault();
|
|
3975
|
+
attachRemoteDevice();
|
|
3976
|
+
},
|
|
3977
|
+
},
|
|
3978
|
+
T('devices.attach')
|
|
3979
|
+
),
|
|
3477
3980
|
h(
|
|
3478
3981
|
'select',
|
|
3479
3982
|
{
|
|
@@ -3523,6 +4026,15 @@ window.__ModuleLoader__.load({
|
|
|
3523
4026
|
h(
|
|
3524
4027
|
'div',
|
|
3525
4028
|
{ style: { display: 'flex', alignItems: 'center', gap: '5px', flexWrap: 'wrap' } },
|
|
4029
|
+
setupActions.length > 1
|
|
4030
|
+
? actionBtn(T('setup.oneClick'), {
|
|
4031
|
+
key: 'setup-one-click',
|
|
4032
|
+
small: true,
|
|
4033
|
+
variant: 'primary',
|
|
4034
|
+
disabled: setupBusy,
|
|
4035
|
+
onClick: runOneClickSetup,
|
|
4036
|
+
})
|
|
4037
|
+
: null,
|
|
3526
4038
|
setupActions.map(function (entry) {
|
|
3527
4039
|
var label = entry.id === 'create-avd'
|
|
3528
4040
|
? T('setup.createAvd')
|
|
@@ -3584,7 +4096,7 @@ window.__ModuleLoader__.load({
|
|
|
3584
4096
|
ref: fileInputRef,
|
|
3585
4097
|
className: 'apkvm-fileinput',
|
|
3586
4098
|
type: 'file',
|
|
3587
|
-
accept: '.apk,.apks,.xapk,application/vnd.android.package-archive,application/octet-stream',
|
|
4099
|
+
accept: '.apk,.apks,.xapk,.aab,application/vnd.android.package-archive,application/octet-stream',
|
|
3588
4100
|
onChange: onFileInputChange,
|
|
3589
4101
|
'aria-label': T('upload.button'),
|
|
3590
4102
|
}),
|
|
@@ -3766,6 +4278,89 @@ window.__ModuleLoader__.load({
|
|
|
3766
4278
|
),
|
|
3767
4279
|
h('span', { className: 'apkvm-dim' }, T('apps.thirdParty'))
|
|
3768
4280
|
),
|
|
4281
|
+
h(
|
|
4282
|
+
'div',
|
|
4283
|
+
{ style: { display: 'flex', alignItems: 'center', gap: '4px', flexWrap: 'wrap' } },
|
|
4284
|
+
h('input', {
|
|
4285
|
+
className: 'apkvm-input',
|
|
4286
|
+
type: 'text',
|
|
4287
|
+
value: deepLink,
|
|
4288
|
+
placeholder: T('apps.deepLinkPlaceholder'),
|
|
4289
|
+
spellCheck: false,
|
|
4290
|
+
disabled: !ready || Boolean(appActionBusy),
|
|
4291
|
+
style: { flex: '1 1 160px', minWidth: '0' },
|
|
4292
|
+
onChange: function (event) {
|
|
4293
|
+
setDeepLink(event && event.target ? String(event.target.value) : '');
|
|
4294
|
+
},
|
|
4295
|
+
}),
|
|
4296
|
+
h(
|
|
4297
|
+
'button',
|
|
4298
|
+
{
|
|
4299
|
+
type: 'button',
|
|
4300
|
+
className: 'apkvm-mini',
|
|
4301
|
+
disabled: !ready || Boolean(appActionBusy) || !deepLink.trim(),
|
|
4302
|
+
onClick: function (event) {
|
|
4303
|
+
event.preventDefault();
|
|
4304
|
+
openDeepLinkAction();
|
|
4305
|
+
},
|
|
4306
|
+
},
|
|
4307
|
+
T('apps.openDeepLink')
|
|
4308
|
+
)
|
|
4309
|
+
),
|
|
4310
|
+
h(
|
|
4311
|
+
'div',
|
|
4312
|
+
{ style: { display: 'flex', alignItems: 'center', gap: '4px', flexWrap: 'wrap' } },
|
|
4313
|
+
h('input', {
|
|
4314
|
+
className: 'apkvm-input',
|
|
4315
|
+
type: 'text',
|
|
4316
|
+
value: permissionPackage,
|
|
4317
|
+
placeholder: 'package',
|
|
4318
|
+
spellCheck: false,
|
|
4319
|
+
disabled: !ready || Boolean(appActionBusy),
|
|
4320
|
+
style: { flex: '0 1 120px', minWidth: '0' },
|
|
4321
|
+
onChange: function (event) {
|
|
4322
|
+
setPermissionPackage(event && event.target ? String(event.target.value) : '');
|
|
4323
|
+
},
|
|
4324
|
+
}),
|
|
4325
|
+
h('input', {
|
|
4326
|
+
className: 'apkvm-input',
|
|
4327
|
+
type: 'text',
|
|
4328
|
+
value: permissionName,
|
|
4329
|
+
placeholder: T('apps.permissionPlaceholder'),
|
|
4330
|
+
spellCheck: false,
|
|
4331
|
+
disabled: !ready || Boolean(appActionBusy),
|
|
4332
|
+
style: { flex: '1 1 150px', minWidth: '0' },
|
|
4333
|
+
onChange: function (event) {
|
|
4334
|
+
setPermissionName(event && event.target ? String(event.target.value) : '');
|
|
4335
|
+
},
|
|
4336
|
+
}),
|
|
4337
|
+
h(
|
|
4338
|
+
'button',
|
|
4339
|
+
{
|
|
4340
|
+
type: 'button',
|
|
4341
|
+
className: 'apkvm-mini',
|
|
4342
|
+
disabled: !ready || Boolean(appActionBusy) || !permissionPackage.trim() || !permissionName.trim(),
|
|
4343
|
+
onClick: function (event) {
|
|
4344
|
+
event.preventDefault();
|
|
4345
|
+
changePermission(true);
|
|
4346
|
+
},
|
|
4347
|
+
},
|
|
4348
|
+
T('apps.grant')
|
|
4349
|
+
),
|
|
4350
|
+
h(
|
|
4351
|
+
'button',
|
|
4352
|
+
{
|
|
4353
|
+
type: 'button',
|
|
4354
|
+
className: 'apkvm-mini',
|
|
4355
|
+
disabled: !ready || Boolean(appActionBusy) || !permissionPackage.trim() || !permissionName.trim(),
|
|
4356
|
+
onClick: function (event) {
|
|
4357
|
+
event.preventDefault();
|
|
4358
|
+
changePermission(false);
|
|
4359
|
+
},
|
|
4360
|
+
},
|
|
4361
|
+
T('apps.revoke')
|
|
4362
|
+
)
|
|
4363
|
+
),
|
|
3769
4364
|
appsError ? h('div', { className: 'apkvm-error' }, appsError) : null,
|
|
3770
4365
|
installedApps.length
|
|
3771
4366
|
? h(
|
|
@@ -3781,6 +4376,19 @@ window.__ModuleLoader__.load({
|
|
|
3781
4376
|
h(
|
|
3782
4377
|
'span',
|
|
3783
4378
|
{ className: 'apkvm-appactions' },
|
|
4379
|
+
h(
|
|
4380
|
+
'button',
|
|
4381
|
+
{
|
|
4382
|
+
type: 'button',
|
|
4383
|
+
className: 'apkvm-mini',
|
|
4384
|
+
disabled: !ready || Boolean(appActionBusy),
|
|
4385
|
+
onClick: function (event) {
|
|
4386
|
+
event.preventDefault();
|
|
4387
|
+
runAppAction('launch', pkg);
|
|
4388
|
+
},
|
|
4389
|
+
},
|
|
4390
|
+
T('apps.launch')
|
|
4391
|
+
),
|
|
3784
4392
|
h(
|
|
3785
4393
|
'button',
|
|
3786
4394
|
{
|
|
@@ -3890,6 +4498,7 @@ window.__ModuleLoader__.load({
|
|
|
3890
4498
|
onClick: toggleLiveLogcat,
|
|
3891
4499
|
}),
|
|
3892
4500
|
actionBtn(T('debug.crash'), { key: 'debug-crash', small: true, disabled: !ready || debugBusy, onClick: runCrashLogcat }),
|
|
4501
|
+
actionBtn(T('debug.visualTest'), { key: 'debug-ui-test', small: true, disabled: !ready || debugBusy, onClick: runUiTest }),
|
|
3893
4502
|
actionBtn(T('debug.clipboardSet'), {
|
|
3894
4503
|
key: 'debug-clipboard-set',
|
|
3895
4504
|
small: true,
|
|
@@ -3902,6 +4511,12 @@ window.__ModuleLoader__.load({
|
|
|
3902
4511
|
disabled: !ready,
|
|
3903
4512
|
onClick: getDeviceClipboard,
|
|
3904
4513
|
}),
|
|
4514
|
+
actionBtn(T('debug.clipboardPasteHost'), {
|
|
4515
|
+
key: 'debug-clipboard-paste-host',
|
|
4516
|
+
small: true,
|
|
4517
|
+
disabled: !ready,
|
|
4518
|
+
onClick: pasteHostClipboardToDevice,
|
|
4519
|
+
}),
|
|
3905
4520
|
actionBtn(T('debug.props'), { key: 'debug-props', small: true, disabled: !ready || debugBusy, onClick: runDebugInfo }),
|
|
3906
4521
|
actionBtn(T('debug.rootAction'), { key: 'debug-root', small: true, disabled: !ready || debugBusy || rootActive, onClick: runDebugRoot }),
|
|
3907
4522
|
actionBtn(T('debug.ui'), { key: 'debug-ui', small: true, disabled: !ready || debugBusy, onClick: runDebugUi }),
|
|
@@ -4266,6 +4881,57 @@ window.__ModuleLoader__.load({
|
|
|
4266
4881
|
},
|
|
4267
4882
|
'aria-label': T('network.pcapPath'),
|
|
4268
4883
|
}),
|
|
4884
|
+
h(
|
|
4885
|
+
'div',
|
|
4886
|
+
{ style: { display: 'flex', alignItems: 'center', gap: '4px', flexWrap: 'wrap' } },
|
|
4887
|
+
h('input', {
|
|
4888
|
+
className: 'apkvm-input',
|
|
4889
|
+
type: 'text',
|
|
4890
|
+
value: harPath,
|
|
4891
|
+
placeholder: T('network.harPath'),
|
|
4892
|
+
spellCheck: false,
|
|
4893
|
+
disabled: harBusy,
|
|
4894
|
+
style: { flex: '1 1 150px', minWidth: '0' },
|
|
4895
|
+
onChange: function (event) {
|
|
4896
|
+
setHarPath(event && event.target ? String(event.target.value) : '');
|
|
4897
|
+
},
|
|
4898
|
+
}),
|
|
4899
|
+
actionBtn(harBusy ? '…' : T('network.harLoad'), {
|
|
4900
|
+
key: 'har-load',
|
|
4901
|
+
small: true,
|
|
4902
|
+
disabled: harBusy || !harPath.trim(),
|
|
4903
|
+
onClick: loadHar,
|
|
4904
|
+
})
|
|
4905
|
+
),
|
|
4906
|
+
harResult && harResult.summary
|
|
4907
|
+
? h(
|
|
4908
|
+
'div',
|
|
4909
|
+
{ className: 'apkvm-dim' },
|
|
4910
|
+
'HAR ' +
|
|
4911
|
+
harResult.summary.total +
|
|
4912
|
+
' · 2xx ' +
|
|
4913
|
+
(harResult.summary.statusClasses && harResult.summary.statusClasses['2xx'] ? harResult.summary.statusClasses['2xx'] : 0) +
|
|
4914
|
+
' · 4xx ' +
|
|
4915
|
+
(harResult.summary.statusClasses && harResult.summary.statusClasses['4xx'] ? harResult.summary.statusClasses['4xx'] : 0) +
|
|
4916
|
+
' · 5xx ' +
|
|
4917
|
+
(harResult.summary.statusClasses && harResult.summary.statusClasses['5xx'] ? harResult.summary.statusClasses['5xx'] : 0) +
|
|
4918
|
+
' · ' +
|
|
4919
|
+
formatBytes(harResult.summary.totalBytes || 0)
|
|
4920
|
+
)
|
|
4921
|
+
: null,
|
|
4922
|
+
harResult && Array.isArray(harResult.entries) && harResult.entries.length
|
|
4923
|
+
? h(
|
|
4924
|
+
'div',
|
|
4925
|
+
{ style: { display: 'flex', flexDirection: 'column', gap: '2px', maxHeight: '130px', overflow: 'auto' } },
|
|
4926
|
+
harResult.entries.slice(0, 10).map(function (entry, index) {
|
|
4927
|
+
return h(
|
|
4928
|
+
'div',
|
|
4929
|
+
{ key: 'har-entry-' + index, className: 'apkvm-dim', title: entry.curl || entry.url, style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } },
|
|
4930
|
+
String(entry.method || '') + ' ' + String(entry.status || '') + ' · ' + String(entry.url || '')
|
|
4931
|
+
);
|
|
4932
|
+
})
|
|
4933
|
+
)
|
|
4934
|
+
: null,
|
|
4269
4935
|
h(
|
|
4270
4936
|
'div',
|
|
4271
4937
|
{ style: { display: 'flex', alignItems: 'center', gap: '5px', flexWrap: 'wrap' } },
|
|
@@ -4417,6 +5083,71 @@ window.__ModuleLoader__.load({
|
|
|
4417
5083
|
)
|
|
4418
5084
|
);
|
|
4419
5085
|
|
|
5086
|
+
var snapshotCard = h(
|
|
5087
|
+
'details',
|
|
5088
|
+
{ className: 'apkvm-card apkvm-debug' },
|
|
5089
|
+
h(
|
|
5090
|
+
'div',
|
|
5091
|
+
{ className: 'apkvm-debug-summary', style: { cursor: 'default' } },
|
|
5092
|
+
h('span', { className: 'apkvm-card-title' }, T('snapshots.title')),
|
|
5093
|
+
h('span', { className: 'apkvm-dim', style: { marginLeft: 'auto' } }, String(snapshotNames.length))
|
|
5094
|
+
),
|
|
5095
|
+
h(
|
|
5096
|
+
'div',
|
|
5097
|
+
{ className: 'apkvm-debug-body' },
|
|
5098
|
+
h(
|
|
5099
|
+
'div',
|
|
5100
|
+
{ style: { display: 'flex', alignItems: 'center', gap: '4px', flexWrap: 'wrap' } },
|
|
5101
|
+
h('input', {
|
|
5102
|
+
className: 'apkvm-input',
|
|
5103
|
+
type: 'text',
|
|
5104
|
+
value: snapshotInput,
|
|
5105
|
+
placeholder: T('snapshots.placeholder'),
|
|
5106
|
+
spellCheck: false,
|
|
5107
|
+
disabled: Boolean(snapshotBusy),
|
|
5108
|
+
style: { flex: '1 1 120px', minWidth: '0' },
|
|
5109
|
+
onChange: function (event) {
|
|
5110
|
+
setSnapshotInput(event && event.target ? String(event.target.value) : '');
|
|
5111
|
+
},
|
|
5112
|
+
}),
|
|
5113
|
+
actionBtn(T('snapshots.save'), {
|
|
5114
|
+
key: 'snapshot-save',
|
|
5115
|
+
small: true,
|
|
5116
|
+
disabled: !ready || Boolean(snapshotBusy) || !snapshotInput.trim(),
|
|
5117
|
+
onClick: function () {
|
|
5118
|
+
snapshotAction('save', snapshotInput.trim());
|
|
5119
|
+
},
|
|
5120
|
+
})
|
|
5121
|
+
),
|
|
5122
|
+
snapshotNames.length
|
|
5123
|
+
? snapshotNames.map(function (name) {
|
|
5124
|
+
return h(
|
|
5125
|
+
'div',
|
|
5126
|
+
{ key: 'snapshot-' + name, style: { display: 'flex', alignItems: 'center', gap: '3px' } },
|
|
5127
|
+
h('span', { className: 'apkvm-dim', style: { flex: '1 1 auto', minWidth: '0', overflow: 'hidden', textOverflow: 'ellipsis' } }, name),
|
|
5128
|
+
actionBtn(T('snapshots.restore'), {
|
|
5129
|
+
key: 'snapshot-restore-' + name,
|
|
5130
|
+
small: true,
|
|
5131
|
+
disabled: Boolean(snapshotBusy) || !ready,
|
|
5132
|
+
onClick: function () {
|
|
5133
|
+
snapshotAction('load', name);
|
|
5134
|
+
},
|
|
5135
|
+
}),
|
|
5136
|
+
actionBtn(T('snapshots.delete'), {
|
|
5137
|
+
key: 'snapshot-delete-' + name,
|
|
5138
|
+
small: true,
|
|
5139
|
+
variant: 'danger',
|
|
5140
|
+
disabled: Boolean(snapshotBusy),
|
|
5141
|
+
onClick: function () {
|
|
5142
|
+
snapshotAction('delete', name);
|
|
5143
|
+
},
|
|
5144
|
+
})
|
|
5145
|
+
);
|
|
5146
|
+
})
|
|
5147
|
+
: h('div', { className: 'apkvm-dim' }, T('snapshots.empty'))
|
|
5148
|
+
)
|
|
5149
|
+
);
|
|
5150
|
+
|
|
4420
5151
|
var primaryAction =
|
|
4421
5152
|
ready || busyState
|
|
4422
5153
|
? actionBtn(vmBusy ? T('action.stopping') : T('action.stop'), {
|
|
@@ -4528,6 +5259,16 @@ window.__ModuleLoader__.load({
|
|
|
4528
5259
|
onClick: destroyActiveInstance,
|
|
4529
5260
|
})
|
|
4530
5261
|
: null,
|
|
5262
|
+
instancesList.length > 1
|
|
5263
|
+
? actionBtn(T('grid.title'), {
|
|
5264
|
+
key: 'grid-view',
|
|
5265
|
+
small: true,
|
|
5266
|
+
onClick: function () {
|
|
5267
|
+
setGridTick(Date.now());
|
|
5268
|
+
setGridOpen(true);
|
|
5269
|
+
},
|
|
5270
|
+
})
|
|
5271
|
+
: null,
|
|
4531
5272
|
h(
|
|
4532
5273
|
'span',
|
|
4533
5274
|
{
|
|
@@ -4548,18 +5289,48 @@ window.__ModuleLoader__.load({
|
|
|
4548
5289
|
' · ' +
|
|
4549
5290
|
(videoMode === 'h264' ? T('fps.mode.h264') : T('fps.mode.png'))
|
|
4550
5291
|
),
|
|
5292
|
+
h264Status
|
|
5293
|
+
? h(
|
|
5294
|
+
'select',
|
|
5295
|
+
{
|
|
5296
|
+
className: 'apkvm-quality',
|
|
5297
|
+
value: streamQualityValue,
|
|
5298
|
+
title: T('stream.quality'),
|
|
5299
|
+
disabled: !ready || Boolean(qualityBusy) || activeInstance !== 'default' || h264Status.enabled === false,
|
|
5300
|
+
onChange: function (event) {
|
|
5301
|
+
applyStreamQuality(event && event.target ? String(event.target.value) : '');
|
|
5302
|
+
},
|
|
5303
|
+
},
|
|
5304
|
+
['low', 'balanced', 'high'].map(function (name) {
|
|
5305
|
+
return h('option', { key: name, value: name }, T('stream.quality.' + name));
|
|
5306
|
+
}),
|
|
5307
|
+
streamPreset === 'custom' ? h('option', { key: 'custom', value: 'custom', disabled: true }, T('stream.quality.custom')) : null
|
|
5308
|
+
)
|
|
5309
|
+
: null,
|
|
4551
5310
|
primaryAction,
|
|
4552
|
-
|
|
4553
|
-
|
|
5311
|
+
embedded
|
|
5312
|
+
? actionBtn(T(toolsOpen ? 'action.hideTools' : 'action.tools'), {
|
|
5313
|
+
key: 'tools',
|
|
5314
|
+
small: true,
|
|
5315
|
+
onClick: toggleTools,
|
|
5316
|
+
})
|
|
5317
|
+
: null,
|
|
5318
|
+
actionBtn(T(isFloating ? 'action.dock' : 'action.float'), {
|
|
5319
|
+
key: 'float-window',
|
|
4554
5320
|
small: true,
|
|
4555
|
-
icon: h('span', { 'aria-hidden': 'true', style: { fontSize: '14px', lineHeight: '14px' } }, isMaximized ? '⛜' : '⛶'),
|
|
4556
|
-
ariaLabel: T(isMaximized ? 'action.restore' : 'action.expand'),
|
|
4557
5321
|
onClick: function () {
|
|
4558
|
-
|
|
5322
|
+
setIsFloating(function (value) {
|
|
4559
5323
|
return !value;
|
|
4560
5324
|
});
|
|
4561
5325
|
},
|
|
4562
5326
|
}),
|
|
5327
|
+
actionBtn(T(isMaximized ? 'action.restore' : 'action.expand'), {
|
|
5328
|
+
key: 'maximize',
|
|
5329
|
+
small: true,
|
|
5330
|
+
icon: h('span', { 'aria-hidden': 'true', style: { fontSize: '14px', lineHeight: '14px' } }, '⛶'),
|
|
5331
|
+
ariaLabel: T(isMaximized ? 'action.restore' : 'action.expand'),
|
|
5332
|
+
onClick: toggleMaximized,
|
|
5333
|
+
}),
|
|
4563
5334
|
!embedded && sidebarService
|
|
4564
5335
|
? actionBtn(T('action.openSidebar'), {
|
|
4565
5336
|
key: 'open-sidebar',
|
|
@@ -4596,6 +5367,19 @@ window.__ModuleLoader__.load({
|
|
|
4596
5367
|
)
|
|
4597
5368
|
: null;
|
|
4598
5369
|
|
|
5370
|
+
var versionBanner = versionMismatch
|
|
5371
|
+
? h(
|
|
5372
|
+
'div',
|
|
5373
|
+
{ className: 'apkvm-banner error apkvm-version-banner', role: 'alert' },
|
|
5374
|
+
h('span', { className: 'apkvm-banner-text' }, T('version.mismatch', { client: CLIENT_VERSION, host: hostVersion })),
|
|
5375
|
+
actionBtn(T('version.reload'), {
|
|
5376
|
+
key: 'version-reload',
|
|
5377
|
+
small: true,
|
|
5378
|
+
onClick: reloadUi,
|
|
5379
|
+
})
|
|
5380
|
+
)
|
|
5381
|
+
: null;
|
|
5382
|
+
|
|
4599
5383
|
var stateErrorBanner =
|
|
4600
5384
|
stateError && !toast
|
|
4601
5385
|
? h(
|
|
@@ -4654,6 +5438,13 @@ window.__ModuleLoader__.load({
|
|
|
4654
5438
|
onKeyDown: onScreenKeyDown,
|
|
4655
5439
|
onContextMenu: onContextMenu,
|
|
4656
5440
|
onDragStart: onContextMenu,
|
|
5441
|
+
onDoubleClick: function (event) {
|
|
5442
|
+
if (event) {
|
|
5443
|
+
event.preventDefault();
|
|
5444
|
+
event.stopPropagation();
|
|
5445
|
+
}
|
|
5446
|
+
toggleMaximized();
|
|
5447
|
+
},
|
|
4657
5448
|
},
|
|
4658
5449
|
frameUrl && videoMode !== 'h264'
|
|
4659
5450
|
? h('img', { className: 'apkvm-frame', src: frameUrl, alt: T('screen.alt'), draggable: false })
|
|
@@ -4699,6 +5490,12 @@ window.__ModuleLoader__.load({
|
|
|
4699
5490
|
disabled: !ready,
|
|
4700
5491
|
onClick: downloadScreenshot,
|
|
4701
5492
|
}),
|
|
5493
|
+
actionBtn(T('debug.rotate'), {
|
|
5494
|
+
key: 'quick-rotate',
|
|
5495
|
+
small: true,
|
|
5496
|
+
disabled: !ready || debugBusy,
|
|
5497
|
+
onClick: runDebugRotate,
|
|
5498
|
+
}),
|
|
4702
5499
|
actionBtn(inspectMode ? T('inspect.close') : T('inspect.toggle'), {
|
|
4703
5500
|
key: 'inspect-toggle',
|
|
4704
5501
|
small: true,
|
|
@@ -4730,6 +5527,9 @@ window.__ModuleLoader__.load({
|
|
|
4730
5527
|
)
|
|
4731
5528
|
),
|
|
4732
5529
|
h('div', { className: 'apkvm-stagehint' }, T('screen.hint')),
|
|
5530
|
+
!ready && (displayState === 'starting' || displayState === 'booting')
|
|
5531
|
+
? h('div', { className: 'apkvm-stagehint' }, T('stage.firstBoot'))
|
|
5532
|
+
: null,
|
|
4733
5533
|
ready && videoMode !== 'h264' && clientReport && clientReport.h264Supported === false
|
|
4734
5534
|
? h('div', { className: 'apkvm-stagehint' }, T('fps.fallbackHint'))
|
|
4735
5535
|
: null,
|
|
@@ -4741,7 +5541,12 @@ window.__ModuleLoader__.load({
|
|
|
4741
5541
|
return h(
|
|
4742
5542
|
'div',
|
|
4743
5543
|
{
|
|
4744
|
-
className:
|
|
5544
|
+
className:
|
|
5545
|
+
'apkvm-page' +
|
|
5546
|
+
(embedded ? ' apkvm-embedded' : '') +
|
|
5547
|
+
(isMaximized ? ' apkvm-maximized' : '') +
|
|
5548
|
+
(isFloating ? ' apkvm-floating' : '') +
|
|
5549
|
+
(embedded && !toolsOpen ? ' apkvm-tools-collapsed' : ''),
|
|
4745
5550
|
ref: pageRef,
|
|
4746
5551
|
onDragEnter: onPageDragEnter,
|
|
4747
5552
|
onDragOver: onPageDragOver,
|
|
@@ -4750,6 +5555,20 @@ window.__ModuleLoader__.load({
|
|
|
4750
5555
|
},
|
|
4751
5556
|
h('style', { key: 'apkvm-style' }, CSS_TEXT),
|
|
4752
5557
|
headerEl,
|
|
5558
|
+
isMaximized
|
|
5559
|
+
? h(
|
|
5560
|
+
'button',
|
|
5561
|
+
{
|
|
5562
|
+
type: 'button',
|
|
5563
|
+
className: 'apkvm-max-close',
|
|
5564
|
+
onClick: toggleMaximized,
|
|
5565
|
+
'aria-label': T('action.restore'),
|
|
5566
|
+
title: T('action.restore'),
|
|
5567
|
+
},
|
|
5568
|
+
'✕ ' + T('action.restore')
|
|
5569
|
+
)
|
|
5570
|
+
: null,
|
|
5571
|
+
versionBanner,
|
|
4753
5572
|
stateErrorBanner,
|
|
4754
5573
|
toastBanner,
|
|
4755
5574
|
h(
|
|
@@ -4765,6 +5584,7 @@ window.__ModuleLoader__.load({
|
|
|
4765
5584
|
flowCard,
|
|
4766
5585
|
networkCard,
|
|
4767
5586
|
storageCard,
|
|
5587
|
+
snapshotCard,
|
|
4768
5588
|
appsCard,
|
|
4769
5589
|
diagCard,
|
|
4770
5590
|
h('div', { className: 'apkvm-footnote', title: T('hint.boot') }, T('hint.short')),
|
|
@@ -4779,6 +5599,54 @@ window.__ModuleLoader__.load({
|
|
|
4779
5599
|
h('span', null, T('upload.drop')),
|
|
4780
5600
|
h('span', { className: 'apkvm-dim' }, T('upload.detail'))
|
|
4781
5601
|
)
|
|
5602
|
+
: null,
|
|
5603
|
+
gridOpen
|
|
5604
|
+
? h(
|
|
5605
|
+
'div',
|
|
5606
|
+
{ className: 'apkvm-grid-overlay' },
|
|
5607
|
+
h(
|
|
5608
|
+
'div',
|
|
5609
|
+
{ className: 'apkvm-grid-head' },
|
|
5610
|
+
h('span', { className: 'apkvm-card-title' }, T('grid.title')),
|
|
5611
|
+
h('span', { className: 'apkvm-dim' }, instancesList.length + ' VM'),
|
|
5612
|
+
h('span', { className: 'apkvm-spacer' }),
|
|
5613
|
+
actionBtn(T('grid.close'), {
|
|
5614
|
+
key: 'grid-close',
|
|
5615
|
+
small: true,
|
|
5616
|
+
onClick: function () {
|
|
5617
|
+
setGridOpen(false);
|
|
5618
|
+
},
|
|
5619
|
+
})
|
|
5620
|
+
),
|
|
5621
|
+
h(
|
|
5622
|
+
'div',
|
|
5623
|
+
{ className: 'apkvm-grid' },
|
|
5624
|
+
instancesList.map(function (entry) {
|
|
5625
|
+
var isReady = entry.state === 'ready';
|
|
5626
|
+
return h(
|
|
5627
|
+
'button',
|
|
5628
|
+
{
|
|
5629
|
+
type: 'button',
|
|
5630
|
+
className: 'apkvm-grid-card',
|
|
5631
|
+
key: 'grid-' + entry.id,
|
|
5632
|
+
onClick: function () {
|
|
5633
|
+
setActiveInstanceId(entry.id);
|
|
5634
|
+
setGridOpen(false);
|
|
5635
|
+
},
|
|
5636
|
+
},
|
|
5637
|
+
isReady
|
|
5638
|
+
? h('img', { src: instanceFrameSrc(entry.id), alt: entry.avdName || entry.id })
|
|
5639
|
+
: h('div', { className: 'apkvm-dim' }, entry.state || 'stopped'),
|
|
5640
|
+
h(
|
|
5641
|
+
'span',
|
|
5642
|
+
{ className: 'apkvm-grid-meta' },
|
|
5643
|
+
h('span', { className: 'apkvm-appname' }, entry.avdName || entry.id),
|
|
5644
|
+
h('span', { className: 'apkvm-dim' }, entry.state || '')
|
|
5645
|
+
)
|
|
5646
|
+
);
|
|
5647
|
+
})
|
|
5648
|
+
)
|
|
5649
|
+
)
|
|
4782
5650
|
: null
|
|
4783
5651
|
);
|
|
4784
5652
|
}
|