html2apk 0.5.0 → 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/README.md +28 -2
- package/examples/minimal/app.json +2 -0
- package/examples/minimal/dist/MeuApp-1.0.0-debug.apk +0 -0
- package/examples/minimal/dist/MeuApp-1.0.0-release.aab +0 -0
- package/package.json +1 -1
- package/src/cli/index.js +12 -1
- package/src/cordova/apk-finder.js +22 -10
- package/src/cordova/project.js +5 -0
- package/src/core/build-apk.js +88 -19
- package/src/core/config.js +16 -0
- package/src/core/defaults.js +2 -0
- package/src/desktop/main.js +191 -2
- package/src/desktop/preload.js +5 -0
- package/src/desktop/renderer/index.html +72 -1
- package/src/desktop/renderer/renderer.js +381 -5
- package/src/desktop/renderer/styles.css +189 -0
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/Html2ApkBridge.java +55 -0
- package/src/templates/html2apk-runtime-console.js +805 -0
|
@@ -10,6 +10,7 @@ const i18n = {
|
|
|
10
10
|
navSettings: "Configuracoes",
|
|
11
11
|
navAppearance: "Aparencia",
|
|
12
12
|
navBuild: "Build",
|
|
13
|
+
navFiles: "Arquivos",
|
|
13
14
|
navCodes: "Codigos",
|
|
14
15
|
navLogs: "Logs",
|
|
15
16
|
navHelp: "Ajuda",
|
|
@@ -32,6 +33,10 @@ const i18n = {
|
|
|
32
33
|
appName: "Nome do app",
|
|
33
34
|
packageId: "Package ID",
|
|
34
35
|
appVersion: "Versao do app",
|
|
36
|
+
buildFormat: "Formato",
|
|
37
|
+
formatApk: "APK",
|
|
38
|
+
formatAab: "AAB",
|
|
39
|
+
buildFormatHint: "APK para instalar direto; AAB para loja.",
|
|
35
40
|
mode: "Modo",
|
|
36
41
|
chooseMode: "Escolha o modo",
|
|
37
42
|
modeFullscreen: "Tela cheia",
|
|
@@ -51,13 +56,35 @@ const i18n = {
|
|
|
51
56
|
oneSignalAppIdHint: "Opcional. Use o App ID do OneSignal, nao a REST API Key.",
|
|
52
57
|
androidPermissions: "Permissoes Android",
|
|
53
58
|
chooseIcon: "Trocar icone PNG",
|
|
59
|
+
keystoreTitle: "Assinatura / keystore",
|
|
60
|
+
keystoreFile: "Arquivo keystore",
|
|
61
|
+
chooseKeystore: "Escolher",
|
|
62
|
+
keystoreAlias: "Alias",
|
|
63
|
+
keystoreStorePassword: "Senha da store",
|
|
64
|
+
keystoreKeyPassword: "Senha da key",
|
|
65
|
+
keystoreHint: "Opcional para APK debug. Para AAB/release, preencha para assinar o arquivo.",
|
|
54
66
|
reviewBuild: "Revisar build",
|
|
55
67
|
debugBuild: "Debug tecnico",
|
|
56
68
|
debugBuildText: "Mantem a pasta Cordova temporaria para inspecao.",
|
|
69
|
+
runtimeLogsBuild: "Console no APK",
|
|
70
|
+
runtimeLogsBuildText: "Mostra um modal Console no app gerado para depurar erros e funcoes interpretadas.",
|
|
57
71
|
releaseBuild: "Release",
|
|
58
72
|
releaseBuildText: "Usa configuracao de assinatura se houver keystore.",
|
|
59
73
|
appearanceEyebrow: "Preferencias",
|
|
60
74
|
appearanceTitle: "Idioma e tema",
|
|
75
|
+
filesEyebrow: "Editor visual",
|
|
76
|
+
filesTitle: "Arquivos do projeto",
|
|
77
|
+
fileTreeTitle: "Pastas e arquivos",
|
|
78
|
+
newFile: "Novo arquivo",
|
|
79
|
+
saveFile: "Salvar",
|
|
80
|
+
noFileSelected: "Nenhum arquivo selecionado",
|
|
81
|
+
syntaxPreview: "Previa com sintaxe",
|
|
82
|
+
newFilePrompt: "Digite o caminho do novo arquivo dentro do projeto:",
|
|
83
|
+
fileSaved: "Arquivo salvo",
|
|
84
|
+
fileCreated: "Arquivo criado",
|
|
85
|
+
fileOpenFail: "Nao foi possivel abrir o arquivo",
|
|
86
|
+
fileSaveFail: "Nao foi possivel salvar o arquivo",
|
|
87
|
+
unsavedFileConfirm: "Ha alteracoes nao salvas. Deseja trocar de arquivo mesmo assim?",
|
|
61
88
|
language: "Idioma",
|
|
62
89
|
languageText: "Escolha como os feedbacks aparecem durante o build.",
|
|
63
90
|
themeText: "O botao tambem fica na barra lateral para acesso rapido.",
|
|
@@ -133,7 +160,10 @@ const i18n = {
|
|
|
133
160
|
invalidThemeColor: "Use uma cor hexadecimal valida, exemplo: #126fff.",
|
|
134
161
|
invalidOneSignalAppId: "Use um OneSignal App ID valido ou deixe vazio.",
|
|
135
162
|
invalidMinSdkVersion: "Escolha uma versao minima do Android entre API 24 e API 36.",
|
|
163
|
+
missingKeystoreForAab: "Para gerar AAB, informe o arquivo keystore, alias e senhas.",
|
|
164
|
+
incompleteKeystore: "Complete arquivo keystore, alias, senha da store e senha da key.",
|
|
136
165
|
iconSelected: "Icone selecionado",
|
|
166
|
+
keystoreSelected: "Keystore selecionado",
|
|
137
167
|
progressLabel: "Progresso",
|
|
138
168
|
progressIdle: "Aguardando pasta",
|
|
139
169
|
progressFolder: "Pasta recebida",
|
|
@@ -166,6 +196,7 @@ const i18n = {
|
|
|
166
196
|
navSettings: "Settings",
|
|
167
197
|
navAppearance: "Appearance",
|
|
168
198
|
navBuild: "Build",
|
|
199
|
+
navFiles: "Files",
|
|
169
200
|
navCodes: "Code",
|
|
170
201
|
navLogs: "Logs",
|
|
171
202
|
navHelp: "Help",
|
|
@@ -188,6 +219,10 @@ const i18n = {
|
|
|
188
219
|
appName: "App name",
|
|
189
220
|
packageId: "Package ID",
|
|
190
221
|
appVersion: "App version",
|
|
222
|
+
buildFormat: "Format",
|
|
223
|
+
formatApk: "APK",
|
|
224
|
+
formatAab: "AAB",
|
|
225
|
+
buildFormatHint: "APK to install directly; AAB for stores.",
|
|
191
226
|
mode: "Mode",
|
|
192
227
|
chooseMode: "Choose mode",
|
|
193
228
|
modeFullscreen: "Fullscreen",
|
|
@@ -207,13 +242,35 @@ const i18n = {
|
|
|
207
242
|
oneSignalAppIdHint: "Optional. Use the OneSignal App ID, not the REST API Key.",
|
|
208
243
|
androidPermissions: "Android permissions",
|
|
209
244
|
chooseIcon: "Change PNG icon",
|
|
245
|
+
keystoreTitle: "Signing / keystore",
|
|
246
|
+
keystoreFile: "Keystore file",
|
|
247
|
+
chooseKeystore: "Choose",
|
|
248
|
+
keystoreAlias: "Alias",
|
|
249
|
+
keystoreStorePassword: "Store password",
|
|
250
|
+
keystoreKeyPassword: "Key password",
|
|
251
|
+
keystoreHint: "Optional for debug APK. For AAB/release, fill this to sign the file.",
|
|
210
252
|
reviewBuild: "Review build",
|
|
211
253
|
debugBuild: "Technical debug",
|
|
212
254
|
debugBuildText: "Keeps the temporary Cordova folder for inspection.",
|
|
255
|
+
runtimeLogsBuild: "APK console",
|
|
256
|
+
runtimeLogsBuildText: "Shows a Console modal in the generated app to debug errors and interpreted functions.",
|
|
213
257
|
releaseBuild: "Release",
|
|
214
258
|
releaseBuildText: "Uses signing configuration when a keystore exists.",
|
|
215
259
|
appearanceEyebrow: "Preferences",
|
|
216
260
|
appearanceTitle: "Language and theme",
|
|
261
|
+
filesEyebrow: "Visual editor",
|
|
262
|
+
filesTitle: "Project files",
|
|
263
|
+
fileTreeTitle: "Folders and files",
|
|
264
|
+
newFile: "New file",
|
|
265
|
+
saveFile: "Save",
|
|
266
|
+
noFileSelected: "No file selected",
|
|
267
|
+
syntaxPreview: "Syntax preview",
|
|
268
|
+
newFilePrompt: "Enter the new file path inside the project:",
|
|
269
|
+
fileSaved: "File saved",
|
|
270
|
+
fileCreated: "File created",
|
|
271
|
+
fileOpenFail: "Could not open the file",
|
|
272
|
+
fileSaveFail: "Could not save the file",
|
|
273
|
+
unsavedFileConfirm: "There are unsaved changes. Switch files anyway?",
|
|
217
274
|
language: "Language",
|
|
218
275
|
languageText: "Choose how feedback appears during the build.",
|
|
219
276
|
themeText: "The button also stays in the sidebar for quick access.",
|
|
@@ -289,7 +346,10 @@ const i18n = {
|
|
|
289
346
|
invalidThemeColor: "Use a valid hex color, example: #126fff.",
|
|
290
347
|
invalidOneSignalAppId: "Use a valid OneSignal App ID or leave it empty.",
|
|
291
348
|
invalidMinSdkVersion: "Choose a minimum Android version between API 24 and API 36.",
|
|
349
|
+
missingKeystoreForAab: "To build AAB, enter the keystore file, alias and passwords.",
|
|
350
|
+
incompleteKeystore: "Complete keystore file, alias, store password and key password.",
|
|
292
351
|
iconSelected: "Icon selected",
|
|
352
|
+
keystoreSelected: "Keystore selected",
|
|
293
353
|
progressLabel: "Progress",
|
|
294
354
|
progressIdle: "Waiting for folder",
|
|
295
355
|
progressFolder: "Folder received",
|
|
@@ -1359,6 +1419,10 @@ const state = {
|
|
|
1359
1419
|
lastApkPath: null,
|
|
1360
1420
|
lastDistPath: null,
|
|
1361
1421
|
defaultIconPath: "",
|
|
1422
|
+
fileTree: [],
|
|
1423
|
+
currentFilePath: "",
|
|
1424
|
+
currentFileLanguage: "text",
|
|
1425
|
+
currentFileDirty: false,
|
|
1362
1426
|
animationTimer: null,
|
|
1363
1427
|
progress: 0,
|
|
1364
1428
|
logsVisible: localStorage.getItem("html2apk.logsVisible") === "true"
|
|
@@ -1398,6 +1462,7 @@ function collectElements() {
|
|
|
1398
1462
|
"appNameInput",
|
|
1399
1463
|
"packageIdInput",
|
|
1400
1464
|
"versionInput",
|
|
1465
|
+
"buildFormatInput",
|
|
1401
1466
|
"modeInput",
|
|
1402
1467
|
"orientationInput",
|
|
1403
1468
|
"minSdkVersionInput",
|
|
@@ -1410,9 +1475,15 @@ function collectElements() {
|
|
|
1410
1475
|
"iconPathInput",
|
|
1411
1476
|
"iconPreview",
|
|
1412
1477
|
"selectIconButton",
|
|
1478
|
+
"keystorePathInput",
|
|
1479
|
+
"selectKeystoreButton",
|
|
1480
|
+
"keystoreAliasInput",
|
|
1481
|
+
"keystoreStorePasswordInput",
|
|
1482
|
+
"keystoreKeyPasswordInput",
|
|
1413
1483
|
"settingsValidation",
|
|
1414
1484
|
"settingsNextButton",
|
|
1415
1485
|
"debugInput",
|
|
1486
|
+
"runtimeLogsInput",
|
|
1416
1487
|
"releaseInput",
|
|
1417
1488
|
"stepFolderText",
|
|
1418
1489
|
"stepSettingsText",
|
|
@@ -1433,6 +1504,13 @@ function collectElements() {
|
|
|
1433
1504
|
"successOpenDistButton",
|
|
1434
1505
|
"successShowApkButton",
|
|
1435
1506
|
"newBuildButton",
|
|
1507
|
+
"newFileButton",
|
|
1508
|
+
"saveFileButton",
|
|
1509
|
+
"fileTree",
|
|
1510
|
+
"currentFileName",
|
|
1511
|
+
"fileLanguageBadge",
|
|
1512
|
+
"fileEditorInput",
|
|
1513
|
+
"fileHighlight",
|
|
1436
1514
|
"logConsole",
|
|
1437
1515
|
"bottomLogConsole",
|
|
1438
1516
|
"clearLogsButton",
|
|
@@ -1469,6 +1547,12 @@ function applyLanguage() {
|
|
|
1469
1547
|
applyLogBarVisibility();
|
|
1470
1548
|
renderPermissionOptions(selectedPermissions());
|
|
1471
1549
|
renderNativeCodeGrid();
|
|
1550
|
+
renderFileTree();
|
|
1551
|
+
if (state.currentFilePath && elements.currentFileName) {
|
|
1552
|
+
elements.currentFileName.textContent = `${state.currentFilePath}${state.currentFileDirty ? " *" : ""}`;
|
|
1553
|
+
} else if (elements.currentFileName) {
|
|
1554
|
+
elements.currentFileName.textContent = text("noFileSelected");
|
|
1555
|
+
}
|
|
1472
1556
|
if (state.project) {
|
|
1473
1557
|
validateSettings();
|
|
1474
1558
|
renderReview();
|
|
@@ -1607,6 +1691,7 @@ function updateActionButtons() {
|
|
|
1607
1691
|
elements.nextSettingsButton.disabled = !hasProject || isBusy || !state.environmentOk;
|
|
1608
1692
|
elements.doctorButton.disabled = !hasProject || isBusy;
|
|
1609
1693
|
elements.settingsNextButton.disabled = !hasProject || !state.settingsValid || !state.environmentOk || isBusy;
|
|
1694
|
+
elements.newFileButton.disabled = !hasProject;
|
|
1610
1695
|
setBuildButtons(hasProject && state.settingsValid && state.environmentOk && !isBusy);
|
|
1611
1696
|
}
|
|
1612
1697
|
|
|
@@ -1677,6 +1762,182 @@ function escapeHtml(value) {
|
|
|
1677
1762
|
.replace(/"/g, """);
|
|
1678
1763
|
}
|
|
1679
1764
|
|
|
1765
|
+
function highlightSource(value, language) {
|
|
1766
|
+
let html = escapeHtml(value);
|
|
1767
|
+
const lang = String(language || "").toLowerCase();
|
|
1768
|
+
|
|
1769
|
+
if (["html", "xml", "svg"].includes(lang)) {
|
|
1770
|
+
html = html
|
|
1771
|
+
.replace(/(<!--[\s\S]*?-->)/g, "<span class=\"syntax-token-comment\">$1</span>")
|
|
1772
|
+
.replace(/(<\/?[a-zA-Z][^&]*?>)/g, "<span class=\"syntax-token-tag\">$1</span>");
|
|
1773
|
+
return html;
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
if (["js", "mjs", "cjs", "ts", "tsx", "jsx"].includes(lang)) {
|
|
1777
|
+
html = html
|
|
1778
|
+
.replace(/\b(await|async|const|let|var|function|return|if|else|for|while|try|catch|throw|new|class|import|export|from|true|false|null|undefined)\b/g, "<span class=\"syntax-token-keyword\">$1</span>")
|
|
1779
|
+
.replace(/\b(\d+(?:\.\d+)?)\b/g, "<span class=\"syntax-token-number\">$1</span>")
|
|
1780
|
+
.replace(/(\/\*[\s\S]*?\*\/|\/\/[^\n\r]*)/g, "<span class=\"syntax-token-comment\">$1</span>")
|
|
1781
|
+
.replace(/(".*?"|'.*?'|`[\s\S]*?`)/g, "<span class=\"syntax-token-string\">$1</span>");
|
|
1782
|
+
return html;
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
if (lang === "css") {
|
|
1786
|
+
html = html
|
|
1787
|
+
.replace(/(\/\*[\s\S]*?\*\/)/g, "<span class=\"syntax-token-comment\">$1</span>")
|
|
1788
|
+
.replace(/([.#]?[a-zA-Z0-9_-]+)(\s*\{)/g, "<span class=\"syntax-token-tag\">$1</span>$2")
|
|
1789
|
+
.replace(/(:\s*)([^;\n]+)(;?)/g, "$1<span class=\"syntax-token-string\">$2</span>$3");
|
|
1790
|
+
return html;
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
if (lang === "json") {
|
|
1794
|
+
html = html
|
|
1795
|
+
.replace(/("[^&]+")(\s*:)/g, "<span class=\"syntax-token-keyword\">$1</span>$2")
|
|
1796
|
+
.replace(/(:\s*)(".*?")/g, "$1<span class=\"syntax-token-string\">$2</span>")
|
|
1797
|
+
.replace(/\b(true|false|null)\b/g, "<span class=\"syntax-token-keyword\">$1</span>")
|
|
1798
|
+
.replace(/\b(\d+(?:\.\d+)?)\b/g, "<span class=\"syntax-token-number\">$1</span>");
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
return html;
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
function updateFilePreview() {
|
|
1805
|
+
if (!elements.fileHighlight) {
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
elements.fileHighlight.innerHTML = `<code>${highlightSource(elements.fileEditorInput.value, state.currentFileLanguage)}</code>`;
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
function setCurrentFileDirty(value) {
|
|
1813
|
+
state.currentFileDirty = Boolean(value);
|
|
1814
|
+
elements.saveFileButton.disabled = !state.currentFilePath || !state.currentFileDirty;
|
|
1815
|
+
if (state.currentFilePath) {
|
|
1816
|
+
elements.currentFileName.textContent = `${state.currentFilePath}${state.currentFileDirty ? " *" : ""}`;
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
function renderFileNodes(nodes, depth = 0) {
|
|
1821
|
+
return (nodes || []).map((node) => {
|
|
1822
|
+
const padding = 8 + (depth * 16);
|
|
1823
|
+
if (node.type === "directory") {
|
|
1824
|
+
return `
|
|
1825
|
+
<div class="folder-row" style="padding-left:${padding}px">${escapeHtml(node.name)}</div>
|
|
1826
|
+
${renderFileNodes(node.children, depth + 1)}
|
|
1827
|
+
`;
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
const active = node.path === state.currentFilePath ? " active" : "";
|
|
1831
|
+
const disabled = node.editable ? "" : " disabled";
|
|
1832
|
+
const marker = node.editable ? "file" : "bin";
|
|
1833
|
+
return `
|
|
1834
|
+
<button class="file-row${active}" type="button" style="padding-left:${padding}px" data-file-path="${escapeHtml(node.path)}"${disabled}>
|
|
1835
|
+
<span>${marker}</span>
|
|
1836
|
+
<strong>${escapeHtml(node.name)}</strong>
|
|
1837
|
+
</button>
|
|
1838
|
+
`;
|
|
1839
|
+
}).join("");
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
function renderFileTree() {
|
|
1843
|
+
if (!elements.fileTree) {
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
if (!state.project) {
|
|
1848
|
+
elements.fileTree.className = "file-tree-empty";
|
|
1849
|
+
elements.fileTree.textContent = text("chooseProjectFirst");
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
if (!state.fileTree.length) {
|
|
1854
|
+
elements.fileTree.className = "file-tree-empty";
|
|
1855
|
+
elements.fileTree.textContent = text("missing");
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
elements.fileTree.className = "file-tree";
|
|
1860
|
+
elements.fileTree.innerHTML = renderFileNodes(state.fileTree);
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
async function refreshProjectFiles() {
|
|
1864
|
+
if (!state.project || !api.listProjectFiles) {
|
|
1865
|
+
renderFileTree();
|
|
1866
|
+
return;
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
try {
|
|
1870
|
+
state.fileTree = await api.listProjectFiles(state.project.projectRoot);
|
|
1871
|
+
renderFileTree();
|
|
1872
|
+
} catch (error) {
|
|
1873
|
+
appendLog(`${text("projectWatcherFail")}: ${error.message}`, "error");
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
async function openProjectFile(relativePath) {
|
|
1878
|
+
if (!state.project || !relativePath) {
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
if (state.currentFileDirty && !window.confirm(text("unsavedFileConfirm"))) {
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
try {
|
|
1887
|
+
const file = await api.readProjectFile(state.project.projectRoot, relativePath);
|
|
1888
|
+
state.currentFilePath = file.path;
|
|
1889
|
+
state.currentFileLanguage = file.language || "text";
|
|
1890
|
+
elements.currentFileName.textContent = file.path;
|
|
1891
|
+
elements.fileLanguageBadge.textContent = state.currentFileLanguage;
|
|
1892
|
+
elements.fileEditorInput.disabled = false;
|
|
1893
|
+
elements.fileEditorInput.value = file.content || "";
|
|
1894
|
+
setCurrentFileDirty(false);
|
|
1895
|
+
updateFilePreview();
|
|
1896
|
+
renderFileTree();
|
|
1897
|
+
} catch (error) {
|
|
1898
|
+
appendLog(`${text("fileOpenFail")}: ${error.message}`, "error");
|
|
1899
|
+
setStatus("error", text("fileOpenFail"));
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
async function saveCurrentFile() {
|
|
1904
|
+
if (!state.project || !state.currentFilePath) {
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
try {
|
|
1909
|
+
await api.writeProjectFile(state.project.projectRoot, state.currentFilePath, elements.fileEditorInput.value);
|
|
1910
|
+
setCurrentFileDirty(false);
|
|
1911
|
+
appendLog(`${text("fileSaved")}: ${state.currentFilePath}`, "success");
|
|
1912
|
+
await refreshProjectFiles();
|
|
1913
|
+
} catch (error) {
|
|
1914
|
+
appendLog(`${text("fileSaveFail")}: ${error.message}`, "error");
|
|
1915
|
+
setStatus("error", text("fileSaveFail"));
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
async function createNewProjectFile() {
|
|
1920
|
+
if (!state.project) {
|
|
1921
|
+
setStatus("error", text("chooseProjectFirst"));
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
const relativePath = window.prompt(text("newFilePrompt"), "js/app.js");
|
|
1926
|
+
if (!relativePath) {
|
|
1927
|
+
return;
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
try {
|
|
1931
|
+
const file = await api.createProjectFile(state.project.projectRoot, relativePath);
|
|
1932
|
+
appendLog(`${text("fileCreated")}: ${file.path}`, "success");
|
|
1933
|
+
await refreshProjectFiles();
|
|
1934
|
+
await openProjectFile(file.path);
|
|
1935
|
+
} catch (error) {
|
|
1936
|
+
appendLog(`${text("fileSaveFail")}: ${error.message}`, "error");
|
|
1937
|
+
setStatus("error", text("fileSaveFail"));
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1680
1941
|
function recipeForCode(index) {
|
|
1681
1942
|
const language = currentLanguage();
|
|
1682
1943
|
const recipe = nativeCodeRecipes[index] || {};
|
|
@@ -1808,10 +2069,41 @@ function normalizeThemeMode(value) {
|
|
|
1808
2069
|
return String(value || "").trim().toLowerCase() === "auto" ? "auto" : "fixed";
|
|
1809
2070
|
}
|
|
1810
2071
|
|
|
2072
|
+
function normalizeBuildFormat(value) {
|
|
2073
|
+
return String(value || "").trim().toLowerCase() === "aab" ? "aab" : "apk";
|
|
2074
|
+
}
|
|
2075
|
+
|
|
1811
2076
|
function normalizeOneSignalAppId(value) {
|
|
1812
2077
|
return String(value || "").trim();
|
|
1813
2078
|
}
|
|
1814
2079
|
|
|
2080
|
+
function keystoreFromConfig(config = {}) {
|
|
2081
|
+
const keystore = config.keystore && typeof config.keystore === "object" ? config.keystore : {};
|
|
2082
|
+
return {
|
|
2083
|
+
path: String(keystore.path || "").trim(),
|
|
2084
|
+
alias: String(keystore.alias || "").trim(),
|
|
2085
|
+
storePassword: String(keystore.storePassword || keystore.password || "").trim(),
|
|
2086
|
+
keyPassword: String(keystore.keyPassword || keystore.password || "").trim()
|
|
2087
|
+
};
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
function keystoreFromInputs() {
|
|
2091
|
+
return {
|
|
2092
|
+
path: elements.keystorePathInput.value.trim(),
|
|
2093
|
+
alias: elements.keystoreAliasInput.value.trim(),
|
|
2094
|
+
storePassword: elements.keystoreStorePasswordInput.value,
|
|
2095
|
+
keyPassword: elements.keystoreKeyPasswordInput.value
|
|
2096
|
+
};
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
function hasAnyKeystoreField(keystore) {
|
|
2100
|
+
return Boolean(keystore.path || keystore.alias || keystore.storePassword || keystore.keyPassword);
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
function hasCompleteKeystore(keystore) {
|
|
2104
|
+
return Boolean(keystore.path && keystore.alias && keystore.storePassword && keystore.keyPassword);
|
|
2105
|
+
}
|
|
2106
|
+
|
|
1815
2107
|
function oneSignalAppIdFromConfig(config = {}) {
|
|
1816
2108
|
return normalizeOneSignalAppId(
|
|
1817
2109
|
config.oneSignalAppId
|
|
@@ -1874,6 +2166,7 @@ function populateSettings(config = {}, project = state.project) {
|
|
|
1874
2166
|
elements.appNameInput.value = config.appName || projectName || "";
|
|
1875
2167
|
elements.packageIdInput.value = config.packageId || `com.html2apk.${packageSegment(projectName)}`;
|
|
1876
2168
|
elements.versionInput.value = config.version || "1.0.0";
|
|
2169
|
+
elements.buildFormatInput.value = normalizeBuildFormat(config.buildFormat || config.outputFormat || config.artifactType || config.packageType);
|
|
1877
2170
|
elements.modeInput.value = config.mode || "fullscreen";
|
|
1878
2171
|
elements.orientationInput.value = normalizeOrientationInputValue(config.orientation);
|
|
1879
2172
|
elements.minSdkVersionInput.value = String(normalizeMinSdkVersion(config.minSdkVersion || config.androidMinSdkVersion));
|
|
@@ -1887,14 +2180,22 @@ function populateSettings(config = {}, project = state.project) {
|
|
|
1887
2180
|
const iconPath = String(config.icon || "").trim() || defaultIconPath();
|
|
1888
2181
|
elements.iconPathInput.value = iconPath;
|
|
1889
2182
|
elements.iconPreview.src = iconPreviewPath(iconPath);
|
|
2183
|
+
const keystore = keystoreFromConfig(config);
|
|
2184
|
+
elements.keystorePathInput.value = keystore.path;
|
|
2185
|
+
elements.keystoreAliasInput.value = keystore.alias;
|
|
2186
|
+
elements.keystoreStorePasswordInput.value = keystore.storePassword;
|
|
2187
|
+
elements.keystoreKeyPasswordInput.value = keystore.keyPassword;
|
|
1890
2188
|
elements.debugInput.checked = Boolean(config.debug);
|
|
1891
|
-
elements.
|
|
2189
|
+
elements.runtimeLogsInput.checked = Boolean(config.showRuntimeLogs || config.mostrarLogs || config.runtimeLogs || config.debugConsole || config.console);
|
|
2190
|
+
elements.releaseInput.checked = Boolean(config.release || elements.buildFormatInput.value === "aab");
|
|
1892
2191
|
}
|
|
1893
2192
|
|
|
1894
2193
|
function validateSettings() {
|
|
1895
2194
|
const errors = [];
|
|
1896
2195
|
const packagePattern = /^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z][a-zA-Z0-9_]*)+$/;
|
|
1897
2196
|
const versionPattern = /^\d+\.\d+\.\d+([-.+][0-9A-Za-z.-]+)?$/;
|
|
2197
|
+
const buildFormat = normalizeBuildFormat(elements.buildFormatInput.value);
|
|
2198
|
+
const keystore = keystoreFromInputs();
|
|
1898
2199
|
|
|
1899
2200
|
if (!state.project) {
|
|
1900
2201
|
errors.push(text("missingProject"));
|
|
@@ -1920,6 +2221,11 @@ function validateSettings() {
|
|
|
1920
2221
|
if (!isValidOptionalOneSignalAppId(elements.oneSignalAppIdInput.value)) {
|
|
1921
2222
|
errors.push(text("invalidOneSignalAppId"));
|
|
1922
2223
|
}
|
|
2224
|
+
if (buildFormat === "aab" && !hasCompleteKeystore(keystore)) {
|
|
2225
|
+
errors.push(text("missingKeystoreForAab"));
|
|
2226
|
+
} else if (hasAnyKeystoreField(keystore) && !hasCompleteKeystore(keystore)) {
|
|
2227
|
+
errors.push(text("incompleteKeystore"));
|
|
2228
|
+
}
|
|
1923
2229
|
if (elements.iconPathInput.value.trim() && !/\.png$/i.test(elements.iconPathInput.value.trim())) {
|
|
1924
2230
|
errors.push(text("invalidIconType"));
|
|
1925
2231
|
}
|
|
@@ -1960,6 +2266,7 @@ function renderReview() {
|
|
|
1960
2266
|
[text("appName"), elements.appNameInput.value.trim()],
|
|
1961
2267
|
[text("packageId"), elements.packageIdInput.value.trim()],
|
|
1962
2268
|
[text("appVersion"), elements.versionInput.value.trim()],
|
|
2269
|
+
[text("buildFormat"), selectedOptionText(elements.buildFormatInput)],
|
|
1963
2270
|
[text("mode"), selectedOptionText(elements.modeInput)],
|
|
1964
2271
|
[text("orientation"), selectedOptionText(elements.orientationInput)],
|
|
1965
2272
|
[text("minSdkVersion"), selectedOptionText(elements.minSdkVersionInput)],
|
|
@@ -1967,7 +2274,10 @@ function renderReview() {
|
|
|
1967
2274
|
[text("appThemeColor"), elements.themeColorTextInput.value.trim()],
|
|
1968
2275
|
[text("oneSignalAppId"), elements.oneSignalAppIdInput.value.trim() || "-"],
|
|
1969
2276
|
[text("androidPermissions"), selectedPermissions().join(", ")],
|
|
1970
|
-
[text("appIcon"), displayIconValue(elements.iconPathInput.value.trim())]
|
|
2277
|
+
[text("appIcon"), displayIconValue(elements.iconPathInput.value.trim())],
|
|
2278
|
+
[text("keystoreTitle"), elements.keystorePathInput.value.trim() ? elements.keystorePathInput.value.trim() : "-"],
|
|
2279
|
+
[text("runtimeLogsBuild"), elements.runtimeLogsInput.checked ? text("selected") : "-"],
|
|
2280
|
+
[text("releaseBuild"), elements.releaseInput.checked || normalizeBuildFormat(elements.buildFormatInput.value) === "aab" ? text("selected") : "-"]
|
|
1971
2281
|
];
|
|
1972
2282
|
|
|
1973
2283
|
elements.reviewGrid.innerHTML = items.map(([label, value]) => `
|
|
@@ -2166,6 +2476,7 @@ function applyProjectChange(payload) {
|
|
|
2166
2476
|
}
|
|
2167
2477
|
|
|
2168
2478
|
renderProjectSnapshot(payload.project);
|
|
2479
|
+
refreshProjectFiles();
|
|
2169
2480
|
|
|
2170
2481
|
const reloadSettings = isConfigFilePath(payload.changedPath);
|
|
2171
2482
|
if (reloadSettings && !state.buildRunning) {
|
|
@@ -2180,6 +2491,14 @@ function applyProjectChange(payload) {
|
|
|
2180
2491
|
if (document.querySelector(".nav-item.active")?.dataset.view === "build") {
|
|
2181
2492
|
renderReview();
|
|
2182
2493
|
}
|
|
2494
|
+
if (
|
|
2495
|
+
state.currentFilePath &&
|
|
2496
|
+
payload.changedPath &&
|
|
2497
|
+
String(payload.changedPath).replace(/\\/g, "/").endsWith(`/${state.currentFilePath}`) &&
|
|
2498
|
+
!state.currentFileDirty
|
|
2499
|
+
) {
|
|
2500
|
+
openProjectFile(state.currentFilePath);
|
|
2501
|
+
}
|
|
2183
2502
|
}
|
|
2184
2503
|
|
|
2185
2504
|
async function summarizeProject(project) {
|
|
@@ -2194,6 +2513,16 @@ async function summarizeProject(project) {
|
|
|
2194
2513
|
elements.nextSettingsButton.disabled = false;
|
|
2195
2514
|
elements.doctorButton.disabled = true;
|
|
2196
2515
|
setBuildButtons(false);
|
|
2516
|
+
state.fileTree = [];
|
|
2517
|
+
state.currentFilePath = "";
|
|
2518
|
+
state.currentFileLanguage = "text";
|
|
2519
|
+
state.currentFileDirty = false;
|
|
2520
|
+
elements.currentFileName.textContent = text("noFileSelected");
|
|
2521
|
+
elements.fileLanguageBadge.textContent = "text";
|
|
2522
|
+
elements.fileEditorInput.value = "";
|
|
2523
|
+
elements.fileEditorInput.disabled = true;
|
|
2524
|
+
elements.fileHighlight.innerHTML = "<code></code>";
|
|
2525
|
+
elements.saveFileButton.disabled = true;
|
|
2197
2526
|
populateSettings(project.config || {}, project);
|
|
2198
2527
|
setStep("folder", project.hasEntryFile ? "done" : "active", project.hasEntryFile ? text("folderReady") : text("missing"));
|
|
2199
2528
|
setStep("settings", "active", text("settingsMissing"));
|
|
@@ -2203,6 +2532,7 @@ async function summarizeProject(project) {
|
|
|
2203
2532
|
setStatus("ready", text("projectLoaded"));
|
|
2204
2533
|
appendLog(`${text("droppedFolder")}: ${project.projectRoot}`, "system");
|
|
2205
2534
|
validateSettings();
|
|
2535
|
+
await refreshProjectFiles();
|
|
2206
2536
|
setProgress(project.hasEntryFile ? 25 : 15, project.hasEntryFile ? text("progressFolder") : text("missing"), project.hasEntryFile ? "" : "error");
|
|
2207
2537
|
await watchCurrentProject();
|
|
2208
2538
|
await ensureEnvironmentBeforeSettings();
|
|
@@ -2240,11 +2570,14 @@ async function runDoctorOnly() {
|
|
|
2240
2570
|
}
|
|
2241
2571
|
|
|
2242
2572
|
function buildOptions() {
|
|
2243
|
-
|
|
2573
|
+
const buildFormat = normalizeBuildFormat(elements.buildFormatInput.value);
|
|
2574
|
+
const keystore = keystoreFromInputs();
|
|
2575
|
+
const options = {
|
|
2244
2576
|
projectRoot: state.project.projectRoot,
|
|
2245
2577
|
appName: elements.appNameInput.value.trim(),
|
|
2246
2578
|
packageId: elements.packageIdInput.value.trim(),
|
|
2247
2579
|
version: elements.versionInput.value.trim(),
|
|
2580
|
+
buildFormat,
|
|
2248
2581
|
mode: elements.modeInput.value,
|
|
2249
2582
|
orientation: elements.orientationInput.value,
|
|
2250
2583
|
minSdkVersion: normalizeMinSdkVersion(elements.minSdkVersionInput.value),
|
|
@@ -2256,8 +2589,15 @@ function buildOptions() {
|
|
|
2256
2589
|
icon: elements.iconPathInput.value.trim(),
|
|
2257
2590
|
androidPlatform: elements.androidPlatformInput.value.trim(),
|
|
2258
2591
|
debug: elements.debugInput.checked,
|
|
2259
|
-
|
|
2592
|
+
showRuntimeLogs: elements.runtimeLogsInput.checked,
|
|
2593
|
+
release: elements.releaseInput.checked || buildFormat === "aab"
|
|
2260
2594
|
};
|
|
2595
|
+
|
|
2596
|
+
if (hasAnyKeystoreField(keystore)) {
|
|
2597
|
+
options.keystore = keystore;
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
return options;
|
|
2261
2601
|
}
|
|
2262
2602
|
|
|
2263
2603
|
function startAnimatedLogs() {
|
|
@@ -2445,6 +2785,9 @@ function bindEvents() {
|
|
|
2445
2785
|
if (button.dataset.view === "build" && !goToReview()) {
|
|
2446
2786
|
return;
|
|
2447
2787
|
}
|
|
2788
|
+
if (button.dataset.view === "files") {
|
|
2789
|
+
await refreshProjectFiles();
|
|
2790
|
+
}
|
|
2448
2791
|
setView(button.dataset.view);
|
|
2449
2792
|
});
|
|
2450
2793
|
});
|
|
@@ -2461,6 +2804,18 @@ function bindEvents() {
|
|
|
2461
2804
|
elements.doctorButton.addEventListener("click", runDoctorOnly);
|
|
2462
2805
|
elements.buildButton.addEventListener("click", runBuildFlow);
|
|
2463
2806
|
elements.usbDebugButton.addEventListener("click", runUsbDebugFlow);
|
|
2807
|
+
elements.newFileButton.addEventListener("click", createNewProjectFile);
|
|
2808
|
+
elements.saveFileButton.addEventListener("click", saveCurrentFile);
|
|
2809
|
+
elements.fileTree.addEventListener("click", (event) => {
|
|
2810
|
+
const button = event.target.closest("[data-file-path]");
|
|
2811
|
+
if (button && !button.disabled) {
|
|
2812
|
+
openProjectFile(button.dataset.filePath);
|
|
2813
|
+
}
|
|
2814
|
+
});
|
|
2815
|
+
elements.fileEditorInput.addEventListener("input", () => {
|
|
2816
|
+
setCurrentFileDirty(true);
|
|
2817
|
+
updateFilePreview();
|
|
2818
|
+
});
|
|
2464
2819
|
elements.clearLogsButton.addEventListener("click", clearLogs);
|
|
2465
2820
|
elements.toggleLogsButton.addEventListener("click", toggleLogBar);
|
|
2466
2821
|
elements.bottomToggleLogsButton.addEventListener("click", toggleLogBar);
|
|
@@ -2475,6 +2830,21 @@ function bindEvents() {
|
|
|
2475
2830
|
appendLog(`${text("iconSelected")}: ${iconPath}`, "system");
|
|
2476
2831
|
validateSettings();
|
|
2477
2832
|
});
|
|
2833
|
+
elements.selectKeystoreButton.addEventListener("click", async () => {
|
|
2834
|
+
const keystorePath = await api.selectKeystore();
|
|
2835
|
+
if (!keystorePath) {
|
|
2836
|
+
return;
|
|
2837
|
+
}
|
|
2838
|
+
elements.keystorePathInput.value = keystorePath;
|
|
2839
|
+
appendLog(`${text("keystoreSelected")}: ${keystorePath}`, "system");
|
|
2840
|
+
validateSettings();
|
|
2841
|
+
});
|
|
2842
|
+
elements.buildFormatInput.addEventListener("change", () => {
|
|
2843
|
+
if (normalizeBuildFormat(elements.buildFormatInput.value) === "aab") {
|
|
2844
|
+
elements.releaseInput.checked = true;
|
|
2845
|
+
}
|
|
2846
|
+
validateSettings();
|
|
2847
|
+
});
|
|
2478
2848
|
elements.themeColorInput.addEventListener("input", () => {
|
|
2479
2849
|
elements.themeColorTextInput.value = elements.themeColorInput.value;
|
|
2480
2850
|
validateSettings();
|
|
@@ -2500,12 +2870,18 @@ function bindEvents() {
|
|
|
2500
2870
|
elements.appNameInput,
|
|
2501
2871
|
elements.packageIdInput,
|
|
2502
2872
|
elements.versionInput,
|
|
2873
|
+
elements.buildFormatInput,
|
|
2503
2874
|
elements.orientationInput,
|
|
2504
2875
|
elements.minSdkVersionInput,
|
|
2505
2876
|
elements.androidPlatformInput,
|
|
2506
2877
|
elements.themeModeInput,
|
|
2507
2878
|
elements.oneSignalAppIdInput,
|
|
2879
|
+
elements.keystorePathInput,
|
|
2880
|
+
elements.keystoreAliasInput,
|
|
2881
|
+
elements.keystoreStorePasswordInput,
|
|
2882
|
+
elements.keystoreKeyPasswordInput,
|
|
2508
2883
|
elements.debugInput,
|
|
2884
|
+
elements.runtimeLogsInput,
|
|
2509
2885
|
elements.releaseInput
|
|
2510
2886
|
].forEach((input) => {
|
|
2511
2887
|
input.addEventListener("input", validateSettings);
|
|
@@ -2604,7 +2980,7 @@ async function init() {
|
|
|
2604
2980
|
elements.iconPreview.src = iconPreviewPath(state.defaultIconPath);
|
|
2605
2981
|
}
|
|
2606
2982
|
} catch {
|
|
2607
|
-
elements.appVersion.textContent = "v0.
|
|
2983
|
+
elements.appVersion.textContent = "v0.7.0";
|
|
2608
2984
|
}
|
|
2609
2985
|
|
|
2610
2986
|
setTimeout(finishBoot, 1800);
|