dsh-apk-vm 0.6.1 → 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 +66 -0
- package/README.md +28 -3
- package/client.js +921 -8
- 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;
|
|
@@ -103,6 +123,20 @@ window.__ModuleLoader__.load({
|
|
|
103
123
|
'action.installLaunch': 'Install & launch',
|
|
104
124
|
'action.dismiss': 'Dismiss',
|
|
105
125
|
'action.openSidebar': 'Open in side bar',
|
|
126
|
+
'action.expand': 'Expand',
|
|
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',
|
|
106
140
|
'a11y.refresh': 'Refresh VM state',
|
|
107
141
|
'a11y.retry': 'Retry the request',
|
|
108
142
|
'a11y.openSidebar': 'Open the APK VM in dsh-better-sidebar',
|
|
@@ -157,7 +191,12 @@ window.__ModuleLoader__.load({
|
|
|
157
191
|
'debug.liveStop': 'Stop live',
|
|
158
192
|
'debug.crash': 'Crash log',
|
|
159
193
|
'debug.clipboardSet': 'Copy field to device',
|
|
160
|
-
'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',
|
|
161
200
|
'debug.clipboardEmpty': 'Type text in the input field first',
|
|
162
201
|
'debug.clipboardSetDone': 'Device clipboard updated',
|
|
163
202
|
'debug.clipboardGot': 'Device clipboard copied to host',
|
|
@@ -206,6 +245,10 @@ window.__ModuleLoader__.load({
|
|
|
206
245
|
'network.testFail': 'Connectivity check failed',
|
|
207
246
|
'network.pcap': 'Download pcap',
|
|
208
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.',
|
|
209
252
|
'network.restartRequired': 'Restart the VM to apply custom DNS',
|
|
210
253
|
'network.sharedHint': 'Shared forwards are reachable from other VMs through the emulator host alias.',
|
|
211
254
|
'network.guestUrl': 'Guest URL: {url}',
|
|
@@ -220,6 +263,15 @@ window.__ModuleLoader__.load({
|
|
|
220
263
|
'flow.saved': 'Saved {name}',
|
|
221
264
|
'flow.done': 'Replayed {name}',
|
|
222
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}',
|
|
223
275
|
'storage.title': 'Storage',
|
|
224
276
|
'storage.refresh': 'Refresh',
|
|
225
277
|
'storage.cleanTemp': 'Clean temp',
|
|
@@ -247,6 +299,9 @@ window.__ModuleLoader__.load({
|
|
|
247
299
|
'setup.installImage': 'Download image',
|
|
248
300
|
'setup.installTools': 'Install SDK tools',
|
|
249
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…',
|
|
250
305
|
'setup.refresh': 'Re-check',
|
|
251
306
|
'setup.switch': 'Switch AVD',
|
|
252
307
|
'setup.loading': 'Checking environment…',
|
|
@@ -258,12 +313,16 @@ window.__ModuleLoader__.load({
|
|
|
258
313
|
'instances.created': 'Created instance {id}',
|
|
259
314
|
'instances.destroyed': 'Destroyed instance {id}',
|
|
260
315
|
'instances.manage': 'VM instances',
|
|
316
|
+
'grid.title': 'Grid view',
|
|
317
|
+
'grid.empty': 'No running instances to show.',
|
|
318
|
+
'grid.close': 'Close grid',
|
|
261
319
|
'instances.stop': 'Stop',
|
|
262
320
|
'instances.stopped': 'Stopped instance {id}',
|
|
263
321
|
'instances.idle': 'idle {seconds}s',
|
|
264
322
|
'devices.title': 'Physical/remote adb device',
|
|
265
323
|
'devices.select': 'Select device…',
|
|
266
324
|
'devices.attach': 'Attach',
|
|
325
|
+
'devices.remotePlaceholder': 'host:port',
|
|
267
326
|
'devices.detach': 'Detach',
|
|
268
327
|
'devices.attached': 'Attached {serial}',
|
|
269
328
|
'devices.detached': 'Detached external device',
|
|
@@ -274,8 +333,16 @@ window.__ModuleLoader__.load({
|
|
|
274
333
|
'apps.empty': 'No third-party apps installed.',
|
|
275
334
|
'apps.thirdParty': 'third-party only',
|
|
276
335
|
'apps.stop': 'Stop',
|
|
336
|
+
'apps.launch': 'Launch',
|
|
277
337
|
'apps.clear': 'Clear data',
|
|
278
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',
|
|
279
346
|
'apps.done': '{action}: {packageName}',
|
|
280
347
|
'debug.empty': 'No debug output yet.',
|
|
281
348
|
'debug.identity': '{release} · API {sdk} · {abi}',
|
|
@@ -336,6 +403,20 @@ window.__ModuleLoader__.load({
|
|
|
336
403
|
'action.installLaunch': '安装并启动',
|
|
337
404
|
'action.dismiss': '关闭',
|
|
338
405
|
'action.openSidebar': '在侧栏打开',
|
|
406
|
+
'action.expand': '放大',
|
|
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',
|
|
339
420
|
'a11y.refresh': '刷新虚拟机状态',
|
|
340
421
|
'a11y.retry': '重试请求',
|
|
341
422
|
'a11y.openSidebar': '在 dsh-better-sidebar 中打开 APK 虚拟机',
|
|
@@ -390,7 +471,12 @@ window.__ModuleLoader__.load({
|
|
|
390
471
|
'debug.liveStop': '停止实时',
|
|
391
472
|
'debug.crash': '崩溃日志',
|
|
392
473
|
'debug.clipboardSet': '文本写入设备剪贴板',
|
|
393
|
-
'debug.clipboardGet': '
|
|
474
|
+
'debug.clipboardGet': '复制设备剪贴板到电脑',
|
|
475
|
+
'debug.clipboardPasteHost': '把电脑剪贴板粘贴到设备',
|
|
476
|
+
'debug.clipboardPasteHostDone': '已把电脑剪贴板写入设备',
|
|
477
|
+
'debug.clipboardUnavailable': '浏览器剪贴板不可用,请改用输入框',
|
|
478
|
+
'debug.visualTest': 'UI 检查',
|
|
479
|
+
'debug.visualTestDone': 'UI 检查完成',
|
|
394
480
|
'debug.clipboardEmpty': '请先在上方输入框输入文本',
|
|
395
481
|
'debug.clipboardSetDone': '已写入设备剪贴板',
|
|
396
482
|
'debug.clipboardGot': '已复制设备剪贴板到主机',
|
|
@@ -439,6 +525,10 @@ window.__ModuleLoader__.load({
|
|
|
439
525
|
'network.testFail': '连通性检查失败',
|
|
440
526
|
'network.pcap': '下载 pcap',
|
|
441
527
|
'network.pcapPath': '宿主机 pcap 路径(.pcap,下次启动)',
|
|
528
|
+
'network.harPath': 'HAR 文件路径(.har)',
|
|
529
|
+
'network.harLoad': '加载 HAR',
|
|
530
|
+
'network.harLoaded': 'HAR 已加载',
|
|
531
|
+
'network.harEmpty': '没有匹配的请求。',
|
|
442
532
|
'network.restartRequired': '重启虚拟机后生效自定义 DNS',
|
|
443
533
|
'network.sharedHint': '共享转发可被其他 VM 通过模拟器宿主别名访问。',
|
|
444
534
|
'network.guestUrl': 'Guest URL: {url}',
|
|
@@ -453,6 +543,15 @@ window.__ModuleLoader__.load({
|
|
|
453
543
|
'flow.saved': '已保存 {name}',
|
|
454
544
|
'flow.done': '已回放 {name}',
|
|
455
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}',
|
|
456
555
|
'storage.title': '存储',
|
|
457
556
|
'storage.refresh': '刷新',
|
|
458
557
|
'storage.cleanTemp': '清理临时文件',
|
|
@@ -480,6 +579,9 @@ window.__ModuleLoader__.load({
|
|
|
480
579
|
'setup.installImage': '下载镜像',
|
|
481
580
|
'setup.installTools': '安装 SDK 工具',
|
|
482
581
|
'setup.bootstrapSdk': '从零安装 SDK',
|
|
582
|
+
'setup.oneClick': '一键初始化',
|
|
583
|
+
'setup.oneClickRunning': '正在安装 SDK、镜像并创建 AVD…',
|
|
584
|
+
'setup.oneClickDone': '初始化完成,正在启动虚拟机…',
|
|
483
585
|
'setup.refresh': '重新检测',
|
|
484
586
|
'setup.switch': '切换 AVD',
|
|
485
587
|
'setup.loading': '正在检测环境…',
|
|
@@ -491,12 +593,16 @@ window.__ModuleLoader__.load({
|
|
|
491
593
|
'instances.created': '已创建实例 {id}',
|
|
492
594
|
'instances.destroyed': '已销毁实例 {id}',
|
|
493
595
|
'instances.manage': '虚拟机实例',
|
|
596
|
+
'grid.title': '网格视图',
|
|
597
|
+
'grid.empty': '没有正在运行的实例。',
|
|
598
|
+
'grid.close': '关闭网格',
|
|
494
599
|
'instances.stop': '停止',
|
|
495
600
|
'instances.stopped': '已停止实例 {id}',
|
|
496
601
|
'instances.idle': '空闲 {seconds}s',
|
|
497
602
|
'devices.title': '物理/远程 adb 设备',
|
|
498
603
|
'devices.select': '选择设备…',
|
|
499
604
|
'devices.attach': '连接',
|
|
605
|
+
'devices.remotePlaceholder': '主机:端口',
|
|
500
606
|
'devices.detach': '断开',
|
|
501
607
|
'devices.attached': '已连接 {serial}',
|
|
502
608
|
'devices.detached': '已断开外部设备',
|
|
@@ -507,8 +613,16 @@ window.__ModuleLoader__.load({
|
|
|
507
613
|
'apps.empty': '没有已安装的第三方应用。',
|
|
508
614
|
'apps.thirdParty': '仅第三方',
|
|
509
615
|
'apps.stop': '停止',
|
|
616
|
+
'apps.launch': '启动',
|
|
510
617
|
'apps.clear': '清数据',
|
|
511
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': '撤销',
|
|
512
626
|
'apps.done': '{action}:{packageName}',
|
|
513
627
|
'debug.empty': '暂无调试输出。',
|
|
514
628
|
'debug.identity': '{release} · API {sdk} · {abi}',
|
|
@@ -1064,7 +1178,38 @@ window.__ModuleLoader__.load({
|
|
|
1064
1178
|
'.apkvm-embedded .apkvm-phonewrap{flex:0 0 auto;min-height:320px}' +
|
|
1065
1179
|
'.apkvm-embedded .apkvm-controls{max-width:none}' +
|
|
1066
1180
|
'@media (max-width:900px){.apkvm-header{flex-wrap:wrap}}' +
|
|
1067
|
-
'.apkvm-embedded .apkvm-header{flex-wrap:wrap}'
|
|
1181
|
+
'.apkvm-embedded .apkvm-header{flex-wrap:wrap}' +
|
|
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)}' +
|
|
1183
|
+
'.apkvm-page.apkvm-maximized .apkvm-header{flex:0 0 auto;background:var(--dsw-alias-bg-base);padding-right:142px}' +
|
|
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}' +
|
|
1185
|
+
'.apkvm-page.apkvm-maximized .apkvm-aside{display:none}' +
|
|
1186
|
+
'.apkvm-page.apkvm-maximized .apkvm-stage{flex:1 1 auto;min-height:0;max-height:none}' +
|
|
1187
|
+
'.apkvm-page.apkvm-maximized .apkvm-phonewrap{flex:1 1 auto;min-height:0;padding:0}' +
|
|
1188
|
+
'.apkvm-page.apkvm-maximized .apkvm-phone{height:100%;max-height:calc(100vh - 150px);padding:6px;border-radius:22px}' +
|
|
1189
|
+
'.apkvm-page.apkvm-maximized .apkvm-screen{height:100%;max-height:calc(100vh - 170px);border-radius:16px}' +
|
|
1190
|
+
'.apkvm-page.apkvm-maximized .apkvm-controls{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}';
|
|
1068
1213
|
|
|
1069
1214
|
/* ------------------------------------------------------------------ *
|
|
1070
1215
|
* Render-error boundary: an unexpected exception inside the panel keeps
|
|
@@ -1172,6 +1317,15 @@ window.__ModuleLoader__.load({
|
|
|
1172
1317
|
var appActionBusyState = React.useState('');
|
|
1173
1318
|
var appActionBusy = appActionBusyState[0];
|
|
1174
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];
|
|
1175
1329
|
|
|
1176
1330
|
var vmBusyState = React.useState(false);
|
|
1177
1331
|
var vmBusy = vmBusyState[0];
|
|
@@ -1212,6 +1366,9 @@ window.__ModuleLoader__.load({
|
|
|
1212
1366
|
var devicePickState = React.useState('');
|
|
1213
1367
|
var devicePick = devicePickState[0];
|
|
1214
1368
|
var setDevicePick = devicePickState[1];
|
|
1369
|
+
var remoteSerialState = React.useState('');
|
|
1370
|
+
var remoteSerial = remoteSerialState[0];
|
|
1371
|
+
var setRemoteSerial = remoteSerialState[1];
|
|
1215
1372
|
var deviceBusyState = React.useState(false);
|
|
1216
1373
|
var deviceBusy = deviceBusyState[0];
|
|
1217
1374
|
var setDeviceBusy = deviceBusyState[1];
|
|
@@ -1226,6 +1383,12 @@ window.__ModuleLoader__.load({
|
|
|
1226
1383
|
var instancesListState = React.useState([]);
|
|
1227
1384
|
var instancesList = instancesListState[0];
|
|
1228
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];
|
|
1229
1392
|
var instanceBusyState = React.useState(false);
|
|
1230
1393
|
var instanceBusy = instanceBusyState[0];
|
|
1231
1394
|
var setInstanceBusy = instanceBusyState[1];
|
|
@@ -1289,6 +1452,15 @@ window.__ModuleLoader__.load({
|
|
|
1289
1452
|
var storageConfirmState = React.useState('');
|
|
1290
1453
|
var storageConfirm = storageConfirmState[0];
|
|
1291
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];
|
|
1292
1464
|
|
|
1293
1465
|
var proxyState = React.useState({ host: '', port: '' });
|
|
1294
1466
|
var proxy = proxyState[0];
|
|
@@ -1323,6 +1495,15 @@ window.__ModuleLoader__.load({
|
|
|
1323
1495
|
var pcapPathState = React.useState('');
|
|
1324
1496
|
var pcapPath = pcapPathState[0];
|
|
1325
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];
|
|
1326
1507
|
|
|
1327
1508
|
var actualFpsState = React.useState(0);
|
|
1328
1509
|
var actualFps = actualFpsState[0];
|
|
@@ -1373,9 +1554,32 @@ window.__ModuleLoader__.load({
|
|
|
1373
1554
|
var canvasRef = React.useRef(null);
|
|
1374
1555
|
var decoderRef = React.useRef(null);
|
|
1375
1556
|
var wsRef = React.useRef(null);
|
|
1557
|
+
var pageRef = React.useRef(null);
|
|
1558
|
+
var maximizedState = React.useState(function () {
|
|
1559
|
+
return readUiFlag('apk-vm.maximized', false);
|
|
1560
|
+
});
|
|
1561
|
+
var isMaximized = maximizedState[0];
|
|
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];
|
|
1376
1572
|
|
|
1377
1573
|
/* ---- derived snapshot values --------------------------------- */
|
|
1378
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;
|
|
1379
1583
|
var clientReport = snapshot && isObject(snapshot.client) ? snapshot.client : null;
|
|
1380
1584
|
var wsOrigin = snapshot && snapshot.origin ? snapshot.origin : '';
|
|
1381
1585
|
var apkInfo = snapshot ? snapshot.apk : null;
|
|
@@ -1433,6 +1637,7 @@ window.__ModuleLoader__.load({
|
|
|
1433
1637
|
pillMeta.push(T(bootKey, { s: bootSeconds }));
|
|
1434
1638
|
}
|
|
1435
1639
|
}
|
|
1640
|
+
pillMeta.push('v' + CLIENT_VERSION);
|
|
1436
1641
|
var subtitleText = apkInfo
|
|
1437
1642
|
? T('apk.current', { name: apkInfo.label || apkInfo.packageName || apkInfo.path })
|
|
1438
1643
|
: T('subtitle');
|
|
@@ -1525,9 +1730,40 @@ window.__ModuleLoader__.load({
|
|
|
1525
1730
|
// Load network details once the VM becomes ready (the first render often
|
|
1526
1731
|
// happens while the state is still loading).
|
|
1527
1732
|
React.useEffect(function () {
|
|
1528
|
-
if (ready)
|
|
1733
|
+
if (ready) {
|
|
1734
|
+
loadNetwork();
|
|
1735
|
+
loadSnapshots();
|
|
1736
|
+
}
|
|
1529
1737
|
}, [ready, activeInstance]);
|
|
1530
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
|
+
|
|
1749
|
+
// Escape leaves the expanded VM view; the expand button is inside the
|
|
1750
|
+
// full-screen overlay too, so mouse-only users are never trapped.
|
|
1751
|
+
React.useEffect(function () {
|
|
1752
|
+
if (!isMaximized || typeof document === 'undefined' || typeof document.addEventListener !== 'function') {
|
|
1753
|
+
return undefined;
|
|
1754
|
+
}
|
|
1755
|
+
function onKeyDown(event) {
|
|
1756
|
+
if (event && event.key === 'Escape') {
|
|
1757
|
+
setIsMaximized(false);
|
|
1758
|
+
writeUiFlag('apk-vm.maximized', false);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
document.addEventListener('keydown', onKeyDown);
|
|
1762
|
+
return function () {
|
|
1763
|
+
document.removeEventListener('keydown', onKeyDown);
|
|
1764
|
+
};
|
|
1765
|
+
}, [isMaximized]);
|
|
1766
|
+
|
|
1531
1767
|
// Track document visibility so the frame stream pauses when hidden.
|
|
1532
1768
|
React.useEffect(function () {
|
|
1533
1769
|
if (typeof document === 'undefined' || typeof document.addEventListener !== 'function') {
|
|
@@ -1887,6 +2123,11 @@ window.__ModuleLoader__.load({
|
|
|
1887
2123
|
return id === 'default' ? '/apk-vm' + pathname : '/apk-vm/instances/' + encodeURIComponent(id) + pathname;
|
|
1888
2124
|
}
|
|
1889
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
|
+
|
|
1890
2131
|
function setActiveInstanceId(id) {
|
|
1891
2132
|
var next = id || 'default';
|
|
1892
2133
|
activeInstanceRef.current = next;
|
|
@@ -2239,6 +2480,64 @@ window.__ModuleLoader__.load({
|
|
|
2239
2480
|
});
|
|
2240
2481
|
}
|
|
2241
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
|
+
|
|
2242
2541
|
function loadNetwork() {
|
|
2243
2542
|
if (!ready) return Promise.resolve(null);
|
|
2244
2543
|
setNetworkBusy('load');
|
|
@@ -2384,6 +2683,37 @@ window.__ModuleLoader__.load({
|
|
|
2384
2683
|
});
|
|
2385
2684
|
}
|
|
2386
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
|
+
|
|
2387
2717
|
function loadStorage() {
|
|
2388
2718
|
setStorageBusy('load');
|
|
2389
2719
|
setStorageError('');
|
|
@@ -2499,6 +2829,53 @@ window.__ModuleLoader__.load({
|
|
|
2499
2829
|
});
|
|
2500
2830
|
}
|
|
2501
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
|
+
|
|
2502
2879
|
function toggleApps() {
|
|
2503
2880
|
var next = !appsOpen;
|
|
2504
2881
|
setAppsOpen(next);
|
|
@@ -2579,6 +2956,27 @@ window.__ModuleLoader__.load({
|
|
|
2579
2956
|
});
|
|
2580
2957
|
}
|
|
2581
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
|
+
|
|
2582
2980
|
function detachDevice() {
|
|
2583
2981
|
if (deviceBusy) return;
|
|
2584
2982
|
setDeviceBusy(true);
|
|
@@ -2600,7 +2998,11 @@ window.__ModuleLoader__.load({
|
|
|
2600
2998
|
function runSetupAction(action) {
|
|
2601
2999
|
if (setupBusy) return;
|
|
2602
3000
|
setSetupBusy(true);
|
|
2603
|
-
requestJson(
|
|
3001
|
+
requestJson(
|
|
3002
|
+
'/apk-vm/setup',
|
|
3003
|
+
{ method: 'POST', body: JSON.stringify({ action: action, confirmDownload: true }) },
|
|
3004
|
+
T
|
|
3005
|
+
).then(function (result) {
|
|
2604
3006
|
if (!mountedRef.current) return;
|
|
2605
3007
|
setSetupBusy(false);
|
|
2606
3008
|
var data = isObject(result.data) ? result.data : {};
|
|
@@ -2615,6 +3017,57 @@ window.__ModuleLoader__.load({
|
|
|
2615
3017
|
});
|
|
2616
3018
|
}
|
|
2617
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
|
+
|
|
2618
3071
|
/* ---- actions -------------------------------------------------- */
|
|
2619
3072
|
function pushToast(kind, text) {
|
|
2620
3073
|
if (!mountedRef.current) return;
|
|
@@ -2839,6 +3292,9 @@ window.__ModuleLoader__.load({
|
|
|
2839
3292
|
});
|
|
2840
3293
|
}
|
|
2841
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.
|
|
2842
3298
|
function setDeviceClipboard() {
|
|
2843
3299
|
if (!ready) return;
|
|
2844
3300
|
var text = String(textInput == null ? '' : textInput);
|
|
@@ -2876,6 +3332,61 @@ window.__ModuleLoader__.load({
|
|
|
2876
3332
|
});
|
|
2877
3333
|
}
|
|
2878
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
|
+
|
|
2879
3390
|
function runDebugInfo() {
|
|
2880
3391
|
runDebugRequest(instancePath('/debug/info'), null, function (data) {
|
|
2881
3392
|
return JSON.stringify(data, null, 2);
|
|
@@ -3441,6 +3952,31 @@ window.__ModuleLoader__.load({
|
|
|
3441
3952
|
h(
|
|
3442
3953
|
'div',
|
|
3443
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
|
+
),
|
|
3444
3980
|
h(
|
|
3445
3981
|
'select',
|
|
3446
3982
|
{
|
|
@@ -3490,6 +4026,15 @@ window.__ModuleLoader__.load({
|
|
|
3490
4026
|
h(
|
|
3491
4027
|
'div',
|
|
3492
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,
|
|
3493
4038
|
setupActions.map(function (entry) {
|
|
3494
4039
|
var label = entry.id === 'create-avd'
|
|
3495
4040
|
? T('setup.createAvd')
|
|
@@ -3551,7 +4096,7 @@ window.__ModuleLoader__.load({
|
|
|
3551
4096
|
ref: fileInputRef,
|
|
3552
4097
|
className: 'apkvm-fileinput',
|
|
3553
4098
|
type: 'file',
|
|
3554
|
-
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',
|
|
3555
4100
|
onChange: onFileInputChange,
|
|
3556
4101
|
'aria-label': T('upload.button'),
|
|
3557
4102
|
}),
|
|
@@ -3733,6 +4278,89 @@ window.__ModuleLoader__.load({
|
|
|
3733
4278
|
),
|
|
3734
4279
|
h('span', { className: 'apkvm-dim' }, T('apps.thirdParty'))
|
|
3735
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
|
+
),
|
|
3736
4364
|
appsError ? h('div', { className: 'apkvm-error' }, appsError) : null,
|
|
3737
4365
|
installedApps.length
|
|
3738
4366
|
? h(
|
|
@@ -3748,6 +4376,19 @@ window.__ModuleLoader__.load({
|
|
|
3748
4376
|
h(
|
|
3749
4377
|
'span',
|
|
3750
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
|
+
),
|
|
3751
4392
|
h(
|
|
3752
4393
|
'button',
|
|
3753
4394
|
{
|
|
@@ -3857,6 +4498,7 @@ window.__ModuleLoader__.load({
|
|
|
3857
4498
|
onClick: toggleLiveLogcat,
|
|
3858
4499
|
}),
|
|
3859
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 }),
|
|
3860
4502
|
actionBtn(T('debug.clipboardSet'), {
|
|
3861
4503
|
key: 'debug-clipboard-set',
|
|
3862
4504
|
small: true,
|
|
@@ -3869,6 +4511,12 @@ window.__ModuleLoader__.load({
|
|
|
3869
4511
|
disabled: !ready,
|
|
3870
4512
|
onClick: getDeviceClipboard,
|
|
3871
4513
|
}),
|
|
4514
|
+
actionBtn(T('debug.clipboardPasteHost'), {
|
|
4515
|
+
key: 'debug-clipboard-paste-host',
|
|
4516
|
+
small: true,
|
|
4517
|
+
disabled: !ready,
|
|
4518
|
+
onClick: pasteHostClipboardToDevice,
|
|
4519
|
+
}),
|
|
3872
4520
|
actionBtn(T('debug.props'), { key: 'debug-props', small: true, disabled: !ready || debugBusy, onClick: runDebugInfo }),
|
|
3873
4521
|
actionBtn(T('debug.rootAction'), { key: 'debug-root', small: true, disabled: !ready || debugBusy || rootActive, onClick: runDebugRoot }),
|
|
3874
4522
|
actionBtn(T('debug.ui'), { key: 'debug-ui', small: true, disabled: !ready || debugBusy, onClick: runDebugUi }),
|
|
@@ -4233,6 +4881,57 @@ window.__ModuleLoader__.load({
|
|
|
4233
4881
|
},
|
|
4234
4882
|
'aria-label': T('network.pcapPath'),
|
|
4235
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,
|
|
4236
4935
|
h(
|
|
4237
4936
|
'div',
|
|
4238
4937
|
{ style: { display: 'flex', alignItems: 'center', gap: '5px', flexWrap: 'wrap' } },
|
|
@@ -4384,6 +5083,71 @@ window.__ModuleLoader__.load({
|
|
|
4384
5083
|
)
|
|
4385
5084
|
);
|
|
4386
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
|
+
|
|
4387
5151
|
var primaryAction =
|
|
4388
5152
|
ready || busyState
|
|
4389
5153
|
? actionBtn(vmBusy ? T('action.stopping') : T('action.stop'), {
|
|
@@ -4495,6 +5259,16 @@ window.__ModuleLoader__.load({
|
|
|
4495
5259
|
onClick: destroyActiveInstance,
|
|
4496
5260
|
})
|
|
4497
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,
|
|
4498
5272
|
h(
|
|
4499
5273
|
'span',
|
|
4500
5274
|
{
|
|
@@ -4515,7 +5289,48 @@ window.__ModuleLoader__.load({
|
|
|
4515
5289
|
' · ' +
|
|
4516
5290
|
(videoMode === 'h264' ? T('fps.mode.h264') : T('fps.mode.png'))
|
|
4517
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,
|
|
4518
5310
|
primaryAction,
|
|
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',
|
|
5320
|
+
small: true,
|
|
5321
|
+
onClick: function () {
|
|
5322
|
+
setIsFloating(function (value) {
|
|
5323
|
+
return !value;
|
|
5324
|
+
});
|
|
5325
|
+
},
|
|
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
|
+
}),
|
|
4519
5334
|
!embedded && sidebarService
|
|
4520
5335
|
? actionBtn(T('action.openSidebar'), {
|
|
4521
5336
|
key: 'open-sidebar',
|
|
@@ -4552,6 +5367,19 @@ window.__ModuleLoader__.load({
|
|
|
4552
5367
|
)
|
|
4553
5368
|
: null;
|
|
4554
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
|
+
|
|
4555
5383
|
var stateErrorBanner =
|
|
4556
5384
|
stateError && !toast
|
|
4557
5385
|
? h(
|
|
@@ -4610,6 +5438,13 @@ window.__ModuleLoader__.load({
|
|
|
4610
5438
|
onKeyDown: onScreenKeyDown,
|
|
4611
5439
|
onContextMenu: onContextMenu,
|
|
4612
5440
|
onDragStart: onContextMenu,
|
|
5441
|
+
onDoubleClick: function (event) {
|
|
5442
|
+
if (event) {
|
|
5443
|
+
event.preventDefault();
|
|
5444
|
+
event.stopPropagation();
|
|
5445
|
+
}
|
|
5446
|
+
toggleMaximized();
|
|
5447
|
+
},
|
|
4613
5448
|
},
|
|
4614
5449
|
frameUrl && videoMode !== 'h264'
|
|
4615
5450
|
? h('img', { className: 'apkvm-frame', src: frameUrl, alt: T('screen.alt'), draggable: false })
|
|
@@ -4655,6 +5490,12 @@ window.__ModuleLoader__.load({
|
|
|
4655
5490
|
disabled: !ready,
|
|
4656
5491
|
onClick: downloadScreenshot,
|
|
4657
5492
|
}),
|
|
5493
|
+
actionBtn(T('debug.rotate'), {
|
|
5494
|
+
key: 'quick-rotate',
|
|
5495
|
+
small: true,
|
|
5496
|
+
disabled: !ready || debugBusy,
|
|
5497
|
+
onClick: runDebugRotate,
|
|
5498
|
+
}),
|
|
4658
5499
|
actionBtn(inspectMode ? T('inspect.close') : T('inspect.toggle'), {
|
|
4659
5500
|
key: 'inspect-toggle',
|
|
4660
5501
|
small: true,
|
|
@@ -4686,6 +5527,9 @@ window.__ModuleLoader__.load({
|
|
|
4686
5527
|
)
|
|
4687
5528
|
),
|
|
4688
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,
|
|
4689
5533
|
ready && videoMode !== 'h264' && clientReport && clientReport.h264Supported === false
|
|
4690
5534
|
? h('div', { className: 'apkvm-stagehint' }, T('fps.fallbackHint'))
|
|
4691
5535
|
: null,
|
|
@@ -4697,7 +5541,13 @@ window.__ModuleLoader__.load({
|
|
|
4697
5541
|
return h(
|
|
4698
5542
|
'div',
|
|
4699
5543
|
{
|
|
4700
|
-
className:
|
|
5544
|
+
className:
|
|
5545
|
+
'apkvm-page' +
|
|
5546
|
+
(embedded ? ' apkvm-embedded' : '') +
|
|
5547
|
+
(isMaximized ? ' apkvm-maximized' : '') +
|
|
5548
|
+
(isFloating ? ' apkvm-floating' : '') +
|
|
5549
|
+
(embedded && !toolsOpen ? ' apkvm-tools-collapsed' : ''),
|
|
5550
|
+
ref: pageRef,
|
|
4701
5551
|
onDragEnter: onPageDragEnter,
|
|
4702
5552
|
onDragOver: onPageDragOver,
|
|
4703
5553
|
onDragLeave: onPageDragLeave,
|
|
@@ -4705,6 +5555,20 @@ window.__ModuleLoader__.load({
|
|
|
4705
5555
|
},
|
|
4706
5556
|
h('style', { key: 'apkvm-style' }, CSS_TEXT),
|
|
4707
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,
|
|
4708
5572
|
stateErrorBanner,
|
|
4709
5573
|
toastBanner,
|
|
4710
5574
|
h(
|
|
@@ -4720,6 +5584,7 @@ window.__ModuleLoader__.load({
|
|
|
4720
5584
|
flowCard,
|
|
4721
5585
|
networkCard,
|
|
4722
5586
|
storageCard,
|
|
5587
|
+
snapshotCard,
|
|
4723
5588
|
appsCard,
|
|
4724
5589
|
diagCard,
|
|
4725
5590
|
h('div', { className: 'apkvm-footnote', title: T('hint.boot') }, T('hint.short')),
|
|
@@ -4734,6 +5599,54 @@ window.__ModuleLoader__.load({
|
|
|
4734
5599
|
h('span', null, T('upload.drop')),
|
|
4735
5600
|
h('span', { className: 'apkvm-dim' }, T('upload.detail'))
|
|
4736
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
|
+
)
|
|
4737
5650
|
: null
|
|
4738
5651
|
);
|
|
4739
5652
|
}
|