html2apk 0.8.0 → 0.11.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.
@@ -16,6 +16,11 @@
16
16
  var consoleList = null;
17
17
  var networkList = null;
18
18
  var badge = null;
19
+ var consoleCount = null;
20
+ var networkCount = null;
21
+ var errorCount = null;
22
+ var copyAllButton = null;
23
+ var copyOnlyErrorsButton = null;
19
24
  var activeTab = "console";
20
25
  var opened = false;
21
26
  var dragMoved = false;
@@ -29,6 +34,9 @@
29
34
  "cancelarNotificacao", "cancelNotification",
30
35
  "lanterna", "flashlight",
31
36
  "alternarLanterna", "toggleFlashlight",
37
+ "tirarFoto", "takePhoto", "capturePhoto",
38
+ "capturarVideo", "captureVideo",
39
+ "escanearQRCode", "scanQRCode", "scanQrCode",
32
40
  "ouvirMic", "listenMic", "startMic",
33
41
  "pararMic", "stopMic",
34
42
  "solicitarPermissaoCamera", "requestCameraPermission",
@@ -36,9 +44,74 @@
36
44
  "solicitarPermissaoNotificacoes", "requestNotificationPermission",
37
45
  "escolherArquivo", "pickFile",
38
46
  "escolherImagem", "pickImage",
47
+ "escolherImagens", "pickImages",
48
+ "escolherPasta", "pickFolder",
49
+ "salvarArquivo", "saveFile",
50
+ "lerArquivo", "readFile", "readStoredFile",
51
+ "excluirArquivo", "deleteFile", "removeFile",
52
+ "listarArquivos", "listFiles",
53
+ "abrirArquivo", "openFile",
54
+ "compartilharArquivo", "shareFile",
55
+ "compartilhar", "share",
56
+ "compartilharApp", "shareApp", "share_me",
57
+ "aguardar", "loading",
58
+ "aoReceberCompartilhamento", "onShareReceived",
59
+ "obterCompartilhamentoInicial", "getInitialShare",
60
+ "procurarBT", "scanBluetooth", "procurarBluetooth", "buscarBT",
61
+ "conectarBT", "connectBluetooth", "conectarBluetooth",
62
+ "enviarBT", "sendBluetooth",
63
+ "aoConectarBT", "onBluetoothConnect",
64
+ "aoReceberDadosBT", "onBluetoothData",
65
+ "aoDarErroBT", "onBluetoothError",
66
+ "procurarWiFi", "scanWiFi", "scanWifi",
67
+ "conectarWiFi", "connectWiFi", "connectWifi",
68
+ "enviarWiFi", "sendWiFi", "sendWifi",
69
+ "aoConectarWiFi", "onWiFiConnect", "onWifiConnect",
70
+ "aoReceberDadosWiFi", "onWiFiData", "onWifiData",
71
+ "aoDarErroWiFi", "onWiFiError", "onWifiError",
72
+ "ocr", "recognizeText", "textFromImage",
73
+ "falar", "speak", "textToSpeech",
74
+ "pararFala", "stopSpeaking",
75
+ "ouvir", "recognizeSpeech", "speechToText",
76
+ "baixarArquivo", "downloadFile",
77
+ "baixarBase64", "downloadBase64", "downloadFromBase64",
78
+ "baixarArquivoLocal", "downloadLocalFile", "downloadFromFile",
79
+ "aoConectarUSB", "onUSBConnect", "onUsbConnect",
80
+ "aoDesconectarUSB", "onUSBDisconnect", "onUsbDisconnect",
81
+ "aoConectarFone", "onHeadphoneConnect",
82
+ "aoDesconectarFone", "onHeadphoneDisconnect",
83
+ "aoMudarVolume", "onVolumeChange",
84
+ "aoAbrirTeclado", "onKeyboardOpen",
85
+ "aoFecharTeclado", "onKeyboardClose",
86
+ "aoSacudirCelular", "onPhoneShake", "onShake",
87
+ "aoVirarCelularParaBaixo", "onPhoneFaceDown",
88
+ "aoAproximarObjeto", "onProximityNear",
89
+ "aoTirarPrint", "onScreenshot",
90
+ "aoMudarOrientacao", "onOrientationChange",
91
+ "aoNFC", "onNFC", "onNfc",
92
+ "aoReceberNotificacao", "onNotificationReceived",
93
+ "definirPapelParede", "setWallpaper", "setPhoneWallpaper",
94
+ "infoPapelParede", "wallpaperInfo",
95
+ "abrirConfiguracaoPapelParede", "openWallpaperSettings",
96
+ "capturarTela", "tirarPrint", "captureScreen", "takeScreenshot", "screenshot",
97
+ "volumeAtual", "currentVolume", "getVolume",
98
+ "definirVolume", "setVolume",
99
+ "aumentarVolume", "increaseVolume",
100
+ "diminuirVolume", "decreaseVolume",
101
+ "configurarIconeFlutuante", "configureFloatingIcon",
102
+ "definirOpacidadeIconeFlutuante", "setFloatingIconOpacity",
103
+ "minimizarApp", "minimizeApp",
104
+ "fecharApp", "closeApp", "exitApp",
39
105
  "abrirNoApp", "openInApp",
40
106
  "abrirForaDoApp", "openOutsideApp",
41
107
  "abrirUrl", "openUrl",
108
+ "obterLocalizacao", "getLocation",
109
+ "acompanharLocalizacao", "watchLocation",
110
+ "pararLocalizacao", "stopLocationWatch",
111
+ "autenticarBiometria", "authenticateBiometric",
112
+ "salvarSeguro", "saveSecure", "secureSet",
113
+ "lerSeguro", "readSecure", "secureGet",
114
+ "removerSeguro", "deleteSecure", "removeSecure",
42
115
  "vibrar", "vibrate",
43
116
  "toast",
44
117
  "statusPermissoes", "permissionStatus",
@@ -100,6 +173,8 @@
100
173
  function renderEntry(entry, target) {
101
174
  var item;
102
175
  var meta;
176
+ var label;
177
+ var time;
103
178
  var body;
104
179
 
105
180
  if (!target) {
@@ -111,11 +186,17 @@
111
186
 
112
187
  meta = document.createElement("div");
113
188
  meta.className = "html2apk-console-meta";
114
- meta.textContent = "[" + entry.time + "] " + entry.kind.toUpperCase();
189
+ label = document.createElement("span");
190
+ label.className = "html2apk-console-kind";
191
+ label.textContent = entry.kind.toUpperCase();
192
+ time = document.createElement("time");
193
+ time.textContent = entry.time;
115
194
 
116
195
  body = document.createElement("pre");
117
196
  body.textContent = entry.message;
118
197
 
198
+ meta.appendChild(label);
199
+ meta.appendChild(time);
119
200
  item.appendChild(meta);
120
201
  item.appendChild(body);
121
202
  target.appendChild(item);
@@ -124,7 +205,19 @@
124
205
  target.removeChild(target.firstChild);
125
206
  }
126
207
 
208
+ scrollListToBottom(target);
209
+ }
210
+
211
+ function scrollListToBottom(target) {
212
+ if (!target) {
213
+ return;
214
+ }
127
215
  target.scrollTop = target.scrollHeight;
216
+ if (typeof window.requestAnimationFrame === "function") {
217
+ window.requestAnimationFrame(function () {
218
+ target.scrollTop = target.scrollHeight;
219
+ });
220
+ }
128
221
  }
129
222
 
130
223
  function renderNetworkEntry(entry) {
@@ -164,23 +257,32 @@
164
257
 
165
258
  function updateBadge() {
166
259
  var count;
167
- if (!badge) {
168
- return;
169
- }
170
260
  count = entries.filter(function (entry) {
171
261
  return entry.kind === "error";
172
262
  }).length + networkEntries.filter(function (entry) {
173
263
  return !entry.ok;
174
264
  }).length;
175
- badge.textContent = count ? String(count) : "";
176
- badge.style.display = count ? "inline-flex" : "none";
265
+ if (badge) {
266
+ badge.textContent = count ? String(count) : "";
267
+ badge.style.display = count ? "inline-flex" : "none";
268
+ }
269
+ if (consoleCount) {
270
+ consoleCount.textContent = String(entries.length);
271
+ }
272
+ if (networkCount) {
273
+ networkCount.textContent = String(networkEntries.length);
274
+ }
275
+ if (errorCount) {
276
+ errorCount.textContent = String(count);
277
+ }
177
278
  }
178
279
 
179
280
  function add(kind, message, detail) {
281
+ var hasDetail = arguments.length > 2 && typeof detail !== "undefined";
180
282
  var entry = {
181
283
  kind: kind || "info",
182
284
  time: now(),
183
- message: detail ? message + "\n" + short(detail, MAX_DETAIL) : String(message || "")
285
+ message: hasDetail ? String(message || "") + "\n" + short(detail, MAX_DETAIL) : String(message || "")
184
286
  };
185
287
 
186
288
  keepEntry(entries, entry);
@@ -204,6 +306,133 @@
204
306
  }
205
307
  }
206
308
 
309
+ function consoleEntryText(entry) {
310
+ return "[" + (entry.time || "") + "] " + String(entry.kind || "info").toUpperCase() + "\n" + String(entry.message || "");
311
+ }
312
+
313
+ function networkEntryText(entry) {
314
+ var status = entry.status || "ERR";
315
+ var method = entry.method || "GET";
316
+ var title = method + " " + entry.url + " -> " + status + " (" + (entry.durationMs || 0) + "ms)";
317
+ var detail = {
318
+ type: entry.type,
319
+ ok: entry.ok,
320
+ status: entry.status,
321
+ statusText: entry.statusText,
322
+ durationMs: entry.durationMs,
323
+ requestBody: entry.requestBody,
324
+ responseBody: entry.responseBody,
325
+ error: entry.error
326
+ };
327
+
328
+ return "[" + (entry.time || "") + "] " + (entry.ok ? "NETWORK" : "ERROR") + "\n" + title + "\n" + short(detail, MAX_DETAIL);
329
+ }
330
+
331
+ function runtimeConsoleText(onlyErrors) {
332
+ var output = [];
333
+ entries.forEach(function (entry) {
334
+ if (!onlyErrors || entry.kind === "error") {
335
+ output.push(consoleEntryText(entry));
336
+ }
337
+ });
338
+ networkEntries.forEach(function (entry) {
339
+ if (!onlyErrors || !entry.ok) {
340
+ output.push(networkEntryText(entry));
341
+ }
342
+ });
343
+ if (!output.length) {
344
+ return onlyErrors ? "Nenhum erro registrado." : "Console vazio.";
345
+ }
346
+ return output.join("\n\n");
347
+ }
348
+
349
+ function fallbackCopyText(text) {
350
+ return new Promise(function (resolve, reject) {
351
+ var target = document.body || document.documentElement;
352
+ var textarea;
353
+ var copied = false;
354
+
355
+ if (!target || !document.createElement) {
356
+ reject(new Error("Clipboard indisponivel."));
357
+ return;
358
+ }
359
+
360
+ textarea = document.createElement("textarea");
361
+ textarea.value = text;
362
+ textarea.setAttribute("readonly", "readonly");
363
+ textarea.style.position = "fixed";
364
+ textarea.style.left = "-9999px";
365
+ textarea.style.top = "0";
366
+ textarea.style.opacity = "0";
367
+ target.appendChild(textarea);
368
+ textarea.focus();
369
+ textarea.select();
370
+
371
+ try {
372
+ copied = document.execCommand("copy");
373
+ } catch (error) {
374
+ copied = false;
375
+ }
376
+
377
+ target.removeChild(textarea);
378
+ if (copied) {
379
+ resolve();
380
+ } else {
381
+ reject(new Error("Nao foi possivel copiar."));
382
+ }
383
+ });
384
+ }
385
+
386
+ function copyTextToClipboard(text) {
387
+ if (typeof navigator !== "undefined" && navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
388
+ return navigator.clipboard.writeText(text).catch(function () {
389
+ return fallbackCopyText(text);
390
+ });
391
+ }
392
+ return fallbackCopyText(text);
393
+ }
394
+
395
+ function flashActionButton(target, label) {
396
+ var original;
397
+ if (!target) {
398
+ return;
399
+ }
400
+ original = target.getAttribute("data-original-label") || target.textContent;
401
+ target.setAttribute("data-original-label", original);
402
+ target.textContent = label;
403
+ target.disabled = true;
404
+ setTimeout(function () {
405
+ target.textContent = original;
406
+ target.disabled = false;
407
+ }, 1200);
408
+ }
409
+
410
+ function copyConsole() {
411
+ var text = runtimeConsoleText(false);
412
+ return copyTextToClipboard(text).then(function () {
413
+ add("info", "Console copiado.");
414
+ flashActionButton(copyAllButton, "Copiado");
415
+ return { ok: true, copied: "console" };
416
+ }, function (error) {
417
+ add("error", "Falha ao copiar console", error);
418
+ flashActionButton(copyAllButton, "Falhou");
419
+ return { ok: false, copied: "console", message: error && error.message ? error.message : String(error) };
420
+ });
421
+ }
422
+
423
+ function copyErrors() {
424
+ var text = runtimeConsoleText(true);
425
+ return copyTextToClipboard(text).then(function () {
426
+ add("info", "Erros copiados.");
427
+ flashActionButton(copyOnlyErrorsButton, "Copiado");
428
+ return { ok: true, copied: "errors" };
429
+ }, function (error) {
430
+ add("error", "Falha ao copiar erros", error);
431
+ flashActionButton(copyOnlyErrorsButton, "Falhou");
432
+ return { ok: false, copied: "errors", message: error && error.message ? error.message : String(error) };
433
+ });
434
+ }
435
+
207
436
  function setActiveTab(tab) {
208
437
  activeTab = tab === "network" ? "network" : "console";
209
438
  if (!modal) {
@@ -218,6 +447,7 @@
218
447
  if (networkList) {
219
448
  networkList.classList.toggle("active", activeTab === "network");
220
449
  }
450
+ updateBadge();
221
451
  }
222
452
 
223
453
  function clearActiveTab() {
@@ -358,7 +588,14 @@
358
588
  var tabs;
359
589
  var consoleTab;
360
590
  var networkTab;
591
+ var summary;
592
+ var consoleStat;
593
+ var networkStat;
594
+ var errorStat;
595
+ var controls;
361
596
  var actions;
597
+ var copyButton;
598
+ var copyErrorsButton;
362
599
  var clearButton;
363
600
  var closeButton;
364
601
  var body;
@@ -369,20 +606,23 @@
369
606
 
370
607
  style = document.createElement("style");
371
608
  style.textContent = [
372
- ".html2apk-console-button{position:fixed;right:14px;bottom:14px;z-index:2147483640;width:54px;height:54px;border:0;border-radius:999px;background:#126fff;color:#fff;padding:0;box-shadow:0 10px 30px rgba(0,0,0,.3);touch-action:none}",
373
- ".html2apk-console-button img{width:100%;height:100%;object-fit:cover;border-radius:999px;display:block}.html2apk-console-button.fallback:before{content:'Console';font:800 10px system-ui,Segoe UI,Arial}",
374
- ".html2apk-console-badge{position:absolute;right:-3px;top:-3px;display:none;align-items:center;justify-content:center;min-width:20px;height:20px;border-radius:999px;background:#ff4d5d;color:#fff;font:800 11px system-ui,Segoe UI,Arial;border:2px solid #fff}",
375
- ".html2apk-console-modal{position:fixed;inset:0;z-index:2147483641;display:none;background:rgba(8,13,22,.42);padding:16px}",
609
+ ".html2apk-console-button{position:fixed;right:14px;bottom:14px;z-index:2147483640;width:58px;height:58px;border:0;border-radius:999px;background:#126fff;color:#fff;padding:0;box-shadow:0 16px 36px rgba(18,111,255,.34),0 8px 24px rgba(0,0,0,.28);touch-action:none}",
610
+ ".html2apk-console-button:active{transform:scale(.98)}.html2apk-console-button img{width:100%;height:100%;object-fit:cover;border-radius:999px;display:block}.html2apk-console-button.fallback:before{content:'Console';font:800 10px system-ui,Segoe UI,Arial}",
611
+ ".html2apk-console-badge{position:absolute;right:-3px;top:-3px;display:none;align-items:center;justify-content:center;min-width:21px;height:21px;border-radius:999px;background:#ef4444;color:#fff;font:900 11px system-ui,Segoe UI,Arial;border:2px solid #fff}",
612
+ ".html2apk-console-modal{position:fixed;inset:0;z-index:2147483641;display:none;background:rgba(5,10,20,.56);padding:12px;font-family:system-ui,Segoe UI,Arial}",
376
613
  ".html2apk-console-modal.open{display:flex;align-items:flex-end;justify-content:center}",
377
- ".html2apk-console-panel{width:min(980px,calc(100vw - 24px));max-height:min(74vh,700px);background:#10141b;color:#edf4ff;border:1px solid rgba(255,255,255,.14);border-radius:14px;box-shadow:0 28px 90px rgba(0,0,0,.42);overflow:hidden;font-family:system-ui,Segoe UI,Arial}",
378
- ".html2apk-console-header{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:12px;padding:12px 14px;border-bottom:1px solid rgba(255,255,255,.12);background:#161d27;touch-action:none}",
379
- ".html2apk-console-header strong{font-size:15px}.html2apk-console-tabs{display:flex;gap:8px}.html2apk-console-actions{display:flex;gap:8px}",
380
- ".html2apk-console-header button{border:1px solid rgba(255,255,255,.18);background:#202b3a;color:#edf4ff;border-radius:8px;padding:7px 10px;font:700 12px system-ui,Segoe UI,Arial}",
381
- ".html2apk-console-header button.active{background:#126fff;border-color:#126fff;color:#fff}",
382
- ".html2apk-console-body{height:min(59vh,570px);background:#0d1118}.html2apk-console-list{display:none;height:100%;overflow:auto;padding:10px}.html2apk-console-list.active{display:block}",
383
- ".html2apk-console-entry{border:1px solid rgba(255,255,255,.1);border-radius:10px;padding:8px 10px;margin-bottom:8px;background:#141b25}",
384
- ".html2apk-console-entry.error{border-color:rgba(255,110,123,.65)}.html2apk-console-entry.warn{border-color:rgba(231,173,76,.6)}.html2apk-console-entry.call,.html2apk-console-entry.network{border-color:rgba(98,160,255,.55)}.html2apk-console-entry.ok{border-color:rgba(67,201,133,.55)}",
385
- ".html2apk-console-meta{color:#9baabd;font-size:11px;font-weight:800;margin-bottom:4px}.html2apk-console-entry pre{white-space:pre-wrap;word-break:break-word;margin:0;font:12px ui-monospace,SFMono-Regular,Consolas,monospace;line-height:1.45}"
614
+ ".html2apk-console-panel{width:min(1040px,calc(100vw - 18px));max-height:min(82vh,760px);background:#0b1020;color:#edf4ff;border:1px solid rgba(148,163,184,.25);border-radius:16px;box-shadow:0 28px 90px rgba(0,0,0,.5);overflow:hidden}",
615
+ ".html2apk-console-header{display:grid;grid-template-columns:minmax(0,1fr);gap:10px;padding:12px;border-bottom:1px solid rgba(148,163,184,.22);background:linear-gradient(180deg,#111827,#0f172a);touch-action:none}",
616
+ ".html2apk-console-title{display:flex;align-items:center;justify-content:space-between;gap:10px;min-width:0}.html2apk-console-title strong{font-size:15px;line-height:1.2}.html2apk-console-subtitle{display:block;color:#94a3b8;font-size:11px;font-weight:700;margin-top:2px}",
617
+ ".html2apk-console-summary{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}.html2apk-console-stat{border:1px solid rgba(148,163,184,.22);background:rgba(15,23,42,.8);border-radius:10px;padding:8px}.html2apk-console-stat span{display:block;color:#94a3b8;font-size:10px;font-weight:900;text-transform:uppercase;letter-spacing:.04em}.html2apk-console-stat strong{display:block;margin-top:2px;font-size:18px;line-height:1;color:#f8fafc}",
618
+ ".html2apk-console-controls{display:grid;grid-template-columns:minmax(0,1fr);gap:8px}.html2apk-console-tabs,.html2apk-console-actions{display:flex;gap:8px;flex-wrap:wrap}.html2apk-console-actions{justify-content:flex-start}",
619
+ ".html2apk-console-header button{border:1px solid rgba(148,163,184,.28);background:#1e293b;color:#e2e8f0;border-radius:9px;padding:8px 10px;font:800 12px system-ui,Segoe UI,Arial}",
620
+ ".html2apk-console-header button.active{background:#126fff;border-color:#60a5fa;color:#fff}.html2apk-console-header button:hover{border-color:#93c5fd}.html2apk-console-header button:disabled{opacity:.72}",
621
+ ".html2apk-console-body{height:min(58vh,560px);background:#050816;overflow:hidden}.html2apk-console-list{display:none;height:100%;box-sizing:border-box;overflow:auto;padding:10px 10px 30px;scroll-padding-bottom:30px;overscroll-behavior:contain}.html2apk-console-list:after{content:'';display:block;height:14px}.html2apk-console-list.active{display:block}",
622
+ ".html2apk-console-entry{border:1px solid rgba(148,163,184,.16);border-left:4px solid #64748b;border-radius:11px;padding:9px 10px;margin-bottom:8px;background:#0f172a}",
623
+ ".html2apk-console-entry.error{border-left-color:#ef4444;background:#1b1117}.html2apk-console-entry.warn{border-left-color:#f59e0b;background:#1a1610}.html2apk-console-entry.call,.html2apk-console-entry.network{border-left-color:#60a5fa}.html2apk-console-entry.ok{border-left-color:#22c55e;background:#0f1a15}.html2apk-console-entry.info{border-left-color:#94a3b8}",
624
+ ".html2apk-console-meta{display:flex;align-items:center;justify-content:space-between;gap:10px;color:#94a3b8;font-size:11px;font-weight:900;margin-bottom:6px}.html2apk-console-kind{color:#e2e8f0}.html2apk-console-entry pre{white-space:pre-wrap;word-break:break-word;margin:0;color:#dbeafe;font:12px ui-monospace,SFMono-Regular,Consolas,monospace;line-height:1.48}",
625
+ "@media (min-width:760px){.html2apk-console-header{grid-template-columns:minmax(170px,.8fr) minmax(220px,1fr) minmax(300px,1.2fr);align-items:center}.html2apk-console-controls{grid-template-columns:minmax(0,.9fr) minmax(0,1.1fr)}.html2apk-console-actions{justify-content:flex-end}.html2apk-console-summary{grid-template-columns:repeat(3,minmax(80px,1fr))}}"
386
626
  ].join("");
387
627
  document.head.appendChild(style);
388
628
 
@@ -422,11 +662,49 @@
422
662
 
423
663
  header = document.createElement("div");
424
664
  header.className = "html2apk-console-header";
425
- title = document.createElement("strong");
426
- title.textContent = "Console";
665
+ title = document.createElement("div");
666
+ title.className = "html2apk-console-title";
667
+ title.innerHTML = "<div><strong>Console html2apk</strong><span class=\"html2apk-console-subtitle\">Logs, erros, rede e chamadas nativas</span></div>";
668
+
669
+ summary = document.createElement("div");
670
+ summary.className = "html2apk-console-summary";
671
+ consoleStat = document.createElement("div");
672
+ consoleStat.className = "html2apk-console-stat";
673
+ consoleStat.innerHTML = "<span>Console</span>";
674
+ consoleCount = document.createElement("strong");
675
+ consoleCount.textContent = "0";
676
+ consoleStat.appendChild(consoleCount);
677
+ networkStat = document.createElement("div");
678
+ networkStat.className = "html2apk-console-stat";
679
+ networkStat.innerHTML = "<span>Rede</span>";
680
+ networkCount = document.createElement("strong");
681
+ networkCount.textContent = "0";
682
+ networkStat.appendChild(networkCount);
683
+ errorStat = document.createElement("div");
684
+ errorStat.className = "html2apk-console-stat";
685
+ errorStat.innerHTML = "<span>Erros</span>";
686
+ errorCount = document.createElement("strong");
687
+ errorCount.textContent = "0";
688
+ errorStat.appendChild(errorCount);
689
+ summary.appendChild(consoleStat);
690
+ summary.appendChild(networkStat);
691
+ summary.appendChild(errorStat);
692
+
693
+ controls = document.createElement("div");
694
+ controls.className = "html2apk-console-controls";
427
695
 
428
696
  tabs = document.createElement("div");
429
697
  tabs.className = "html2apk-console-tabs";
698
+ controls.appendChild(tabs);
699
+
700
+ closeButton = document.createElement("button");
701
+ closeButton.type = "button";
702
+ closeButton.textContent = "Fechar";
703
+ title.appendChild(closeButton);
704
+
705
+ header.appendChild(title);
706
+ header.appendChild(summary);
707
+ header.appendChild(controls);
430
708
  consoleTab = document.createElement("button");
431
709
  consoleTab.type = "button";
432
710
  consoleTab.textContent = "Console";
@@ -446,16 +724,25 @@
446
724
 
447
725
  actions = document.createElement("div");
448
726
  actions.className = "html2apk-console-actions";
727
+ copyButton = document.createElement("button");
728
+ copyButton.type = "button";
729
+ copyButton.textContent = "Copiar console";
730
+ copyButton.addEventListener("click", copyConsole);
731
+ copyAllButton = copyButton;
732
+ copyErrorsButton = document.createElement("button");
733
+ copyErrorsButton.type = "button";
734
+ copyErrorsButton.textContent = "Copiar apenas erros";
735
+ copyErrorsButton.addEventListener("click", copyErrors);
736
+ copyOnlyErrorsButton = copyErrorsButton;
449
737
  clearButton = document.createElement("button");
450
738
  clearButton.type = "button";
451
739
  clearButton.textContent = "Limpar";
452
740
  clearButton.addEventListener("click", clearActiveTab);
453
- closeButton = document.createElement("button");
454
- closeButton.type = "button";
455
- closeButton.textContent = "Fechar";
456
741
  closeButton.addEventListener("click", closeConsole);
742
+ actions.appendChild(copyButton);
743
+ actions.appendChild(copyErrorsButton);
457
744
  actions.appendChild(clearButton);
458
- actions.appendChild(closeButton);
745
+ controls.appendChild(actions);
459
746
 
460
747
  body = document.createElement("div");
461
748
  body.className = "html2apk-console-body";
@@ -466,9 +753,6 @@
466
753
  body.appendChild(consoleList);
467
754
  body.appendChild(networkList);
468
755
 
469
- header.appendChild(title);
470
- header.appendChild(tabs);
471
- header.appendChild(actions);
472
756
  panel.appendChild(header);
473
757
  panel.appendChild(body);
474
758
  modal.appendChild(panel);
@@ -766,6 +1050,14 @@
766
1050
  open: openConsole,
767
1051
  close: closeConsole,
768
1052
  clear: clearActiveTab,
1053
+ copy: copyConsole,
1054
+ copyErrors: copyErrors,
1055
+ text: function (onlyErrors) {
1056
+ return runtimeConsoleText(!!onlyErrors);
1057
+ },
1058
+ errorText: function () {
1059
+ return runtimeConsoleText(true);
1060
+ },
769
1061
  log: add,
770
1062
  network: addNetwork,
771
1063
  entries: function () {