html2apk 0.4.0 → 0.7.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 +116 -12
- 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 +102 -22
- 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 +71 -0
- package/src/desktop/renderer/renderer.js +473 -12
- package/src/desktop/renderer/styles.css +189 -0
- package/src/templates/cordova-plugin-html2apk-bridge/plugin.xml +2 -0
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/Html2ApkBridge.java +219 -20
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/NotificationClickReceiver.java +28 -0
- package/src/templates/cordova-plugin-html2apk-bridge/src/android/NotificationReceiver.java +1 -1
- package/src/templates/cordova-plugin-html2apk-bridge/www/html2apk-bridge.js +278 -11
- package/src/templates/html2apk-early-bridge.js +860 -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",
|
|
@@ -391,11 +451,11 @@ const nativeCodeEntries = [
|
|
|
391
451
|
handling: { pt: "Limite duracoes longas e deixe a permissao `VIBRATE` no app; se falhar, siga sem bloquear o fluxo principal.", en: "Keep long durations under control and include the `VIBRATE` permission; if it fails, continue without blocking the main flow." }
|
|
392
452
|
},
|
|
393
453
|
{
|
|
394
|
-
syntax: { pt: "notificar({ titulo, texto, acoes })", en: "notify({ title, text, actions })" },
|
|
454
|
+
syntax: { pt: "notificar({ titulo, texto, aoClicar?, acoes?, open? })", en: "notify({ title, text, onClick?, actions?, open? })" },
|
|
395
455
|
java: "notify",
|
|
396
|
-
description: { pt: "Cria notificacao Android imediata. `acoes`
|
|
397
|
-
returns: { pt: "Promise<void>; cliques chegam em `aoEvento('notificacao:clicada')` ou `aoClicarNotificacao()
|
|
398
|
-
handling: { pt: "
|
|
456
|
+
description: { pt: "Cria notificacao Android imediata. So `titulo` e `texto` ja bastam; `aoClicar`, `acoes` e `open` sao opcionais.", en: "Creates an immediate Android notification. `title` and `text` are enough; `onClick`, `actions` and `open` are optional." },
|
|
457
|
+
returns: { pt: "Promise<void>; cliques chegam em `aoEvento('notificacao:clicada')` ou `aoClicarNotificacao()` e tambem podem executar `aoClicar` automaticamente.", en: "Promise<void>; clicks arrive through `onEvent('notification:clicked')` or `onNotificationClick()` and can also run `onClick` automatically." },
|
|
458
|
+
handling: { pt: "Use `aoClicar: () => funcao()` enquanto o app esta vivo. Para agendada ou app fechado, prefira `aoClicar: { funcao, argumentos }`. `open:false` evita abrir a tela; JavaScript so roda assim se o app ainda estiver vivo, mas acoes externas como `abrirForaDoApp` funcionam por fallback nativo.", en: "Use `onClick: () => functionName()` while the app process is alive. For scheduled notifications or a closed app, prefer `onClick: { functionName, args }`. `open:false` avoids opening the screen; JavaScript only runs this way if the app is still alive, but external actions like `openOutsideApp` work through a native fallback." }
|
|
399
459
|
},
|
|
400
460
|
{
|
|
401
461
|
syntax: { pt: "agendarNotificacao({ titulo, texto, quando }) / agendarNotificacoes([...])", en: "scheduleNotification({ title, text, when }) / scheduleNotifications([...])" },
|
|
@@ -409,7 +469,7 @@ const nativeCodeEntries = [
|
|
|
409
469
|
java: "AlarmManager + NotificationStore",
|
|
410
470
|
description: { pt: "Cria um loop de notificacoes que continua funcionando com o app fechado e alterna os itens da lista.", en: "Creates a notification loop that keeps working with the app closed and rotates through the list items." },
|
|
411
471
|
returns: { pt: "{ id, when, repeating, loop }. Guarde o `id` para cancelar depois.", en: "{ id, when, repeating, loop }. Store the `id` to cancel later." },
|
|
412
|
-
handling: { pt: "
|
|
472
|
+
handling: { pt: "Cancele com `cancelarNotificacao(id)`. Se o clique precisar chamar funcao automaticamente mesmo apos o Android reabrir o app, use `funcao` + `argumentos` no `aoClicar`.", en: "Cancel with `cancelNotification(id)`. If the click must call a function automatically after Android reopens the app, use `functionName` + `args` in `onClick`." }
|
|
413
473
|
},
|
|
414
474
|
{
|
|
415
475
|
syntax: { pt: "solicitarPermissaoNotificacoes()", en: "requestNotificationPermission()" },
|
|
@@ -668,17 +728,102 @@ const nativeCodeRecipes = [
|
|
|
668
728
|
}
|
|
669
729
|
},
|
|
670
730
|
{
|
|
671
|
-
when: { pt: "Para mostrar uma notificacao agora.
|
|
731
|
+
when: { pt: "Para mostrar uma notificacao simples agora. `aoClicar`, `acoes` e `open` sao opcionais.", en: "To show a simple notification now. `onClick`, `actions` and `open` are optional." },
|
|
732
|
+
example: {
|
|
733
|
+
pt: `await notificar({
|
|
734
|
+
titulo: "Pedido aprovado",
|
|
735
|
+
texto: "Toque para abrir o app"
|
|
736
|
+
});`,
|
|
737
|
+
en: `await notify({
|
|
738
|
+
title: "Order approved",
|
|
739
|
+
text: "Tap to open the app"
|
|
740
|
+
});`
|
|
741
|
+
}
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
when: { pt: "Para executar algo quando a notificacao for clicada.", en: "To run something when the notification is clicked." },
|
|
672
745
|
example: {
|
|
673
746
|
pt: `await notificar({
|
|
674
747
|
titulo: "Pedido aprovado",
|
|
675
748
|
texto: "Toque para abrir os detalhes",
|
|
676
|
-
aoClicar:
|
|
749
|
+
aoClicar: () => abrirForaDoApp("https://exemplo.com/pedidos/123")
|
|
677
750
|
});`,
|
|
678
751
|
en: `await notify({
|
|
679
752
|
title: "Order approved",
|
|
680
753
|
text: "Tap to open details",
|
|
681
|
-
onClick:
|
|
754
|
+
onClick: () => openOutsideApp("https://example.com/orders/123")
|
|
755
|
+
});`
|
|
756
|
+
}
|
|
757
|
+
},
|
|
758
|
+
{
|
|
759
|
+
when: { pt: "Para colocar botoes na notificacao, cada um chamando uma funcao.", en: "To add buttons to the notification, each one calling a function." },
|
|
760
|
+
example: {
|
|
761
|
+
pt: `window.marcarPedidoLido = (id) => {
|
|
762
|
+
localStorage.setItem("pedido:" + id, "lido");
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
await notificar({
|
|
766
|
+
titulo: "Pedido aprovado",
|
|
767
|
+
texto: "Escolha uma acao",
|
|
768
|
+
acoes: [
|
|
769
|
+
{
|
|
770
|
+
id: "abrir",
|
|
771
|
+
titulo: "Abrir",
|
|
772
|
+
open: true,
|
|
773
|
+
aoClicar: { funcao: "abrirNoApp", argumentos: ["#/pedido/123"] }
|
|
774
|
+
},
|
|
775
|
+
{
|
|
776
|
+
id: "lido",
|
|
777
|
+
titulo: "Marcar lido",
|
|
778
|
+
open: false,
|
|
779
|
+
aoClicar: { funcao: "marcarPedidoLido", argumentos: [123], open: false }
|
|
780
|
+
}
|
|
781
|
+
]
|
|
782
|
+
});`,
|
|
783
|
+
en: `window.markOrderRead = (id) => {
|
|
784
|
+
localStorage.setItem("order:" + id, "read");
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
await notify({
|
|
788
|
+
title: "Order approved",
|
|
789
|
+
text: "Choose an action",
|
|
790
|
+
actions: [
|
|
791
|
+
{
|
|
792
|
+
id: "open",
|
|
793
|
+
title: "Open",
|
|
794
|
+
open: true,
|
|
795
|
+
onClick: { functionName: "openInApp", args: ["#/order/123"] }
|
|
796
|
+
},
|
|
797
|
+
{
|
|
798
|
+
id: "read",
|
|
799
|
+
title: "Mark read",
|
|
800
|
+
open: false,
|
|
801
|
+
onClick: { functionName: "markOrderRead", args: [123], open: false }
|
|
802
|
+
}
|
|
803
|
+
]
|
|
804
|
+
});`
|
|
805
|
+
}
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
when: { pt: "Para clique de notificacao agendada ou com app fechado.", en: "For scheduled notification clicks or when the app is closed." },
|
|
809
|
+
example: {
|
|
810
|
+
pt: `await agendarNotificacao({
|
|
811
|
+
titulo: "Pedido aprovado",
|
|
812
|
+
texto: "Toque para abrir os detalhes",
|
|
813
|
+
quando: Date.now() + 60 * 1000,
|
|
814
|
+
aoClicar: {
|
|
815
|
+
funcao: "abrirForaDoApp",
|
|
816
|
+
argumentos: ["https://exemplo.com/pedidos/123"]
|
|
817
|
+
}
|
|
818
|
+
});`,
|
|
819
|
+
en: `await scheduleNotification({
|
|
820
|
+
title: "Order approved",
|
|
821
|
+
text: "Tap to open details",
|
|
822
|
+
when: Date.now() + 60 * 1000,
|
|
823
|
+
onClick: {
|
|
824
|
+
functionName: "openOutsideApp",
|
|
825
|
+
args: ["https://example.com/orders/123"]
|
|
826
|
+
}
|
|
682
827
|
});`
|
|
683
828
|
}
|
|
684
829
|
},
|
|
@@ -1274,6 +1419,10 @@ const state = {
|
|
|
1274
1419
|
lastApkPath: null,
|
|
1275
1420
|
lastDistPath: null,
|
|
1276
1421
|
defaultIconPath: "",
|
|
1422
|
+
fileTree: [],
|
|
1423
|
+
currentFilePath: "",
|
|
1424
|
+
currentFileLanguage: "text",
|
|
1425
|
+
currentFileDirty: false,
|
|
1277
1426
|
animationTimer: null,
|
|
1278
1427
|
progress: 0,
|
|
1279
1428
|
logsVisible: localStorage.getItem("html2apk.logsVisible") === "true"
|
|
@@ -1313,6 +1462,7 @@ function collectElements() {
|
|
|
1313
1462
|
"appNameInput",
|
|
1314
1463
|
"packageIdInput",
|
|
1315
1464
|
"versionInput",
|
|
1465
|
+
"buildFormatInput",
|
|
1316
1466
|
"modeInput",
|
|
1317
1467
|
"orientationInput",
|
|
1318
1468
|
"minSdkVersionInput",
|
|
@@ -1325,9 +1475,15 @@ function collectElements() {
|
|
|
1325
1475
|
"iconPathInput",
|
|
1326
1476
|
"iconPreview",
|
|
1327
1477
|
"selectIconButton",
|
|
1478
|
+
"keystorePathInput",
|
|
1479
|
+
"selectKeystoreButton",
|
|
1480
|
+
"keystoreAliasInput",
|
|
1481
|
+
"keystoreStorePasswordInput",
|
|
1482
|
+
"keystoreKeyPasswordInput",
|
|
1328
1483
|
"settingsValidation",
|
|
1329
1484
|
"settingsNextButton",
|
|
1330
1485
|
"debugInput",
|
|
1486
|
+
"runtimeLogsInput",
|
|
1331
1487
|
"releaseInput",
|
|
1332
1488
|
"stepFolderText",
|
|
1333
1489
|
"stepSettingsText",
|
|
@@ -1348,6 +1504,13 @@ function collectElements() {
|
|
|
1348
1504
|
"successOpenDistButton",
|
|
1349
1505
|
"successShowApkButton",
|
|
1350
1506
|
"newBuildButton",
|
|
1507
|
+
"newFileButton",
|
|
1508
|
+
"saveFileButton",
|
|
1509
|
+
"fileTree",
|
|
1510
|
+
"currentFileName",
|
|
1511
|
+
"fileLanguageBadge",
|
|
1512
|
+
"fileEditorInput",
|
|
1513
|
+
"fileHighlight",
|
|
1351
1514
|
"logConsole",
|
|
1352
1515
|
"bottomLogConsole",
|
|
1353
1516
|
"clearLogsButton",
|
|
@@ -1384,6 +1547,12 @@ function applyLanguage() {
|
|
|
1384
1547
|
applyLogBarVisibility();
|
|
1385
1548
|
renderPermissionOptions(selectedPermissions());
|
|
1386
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
|
+
}
|
|
1387
1556
|
if (state.project) {
|
|
1388
1557
|
validateSettings();
|
|
1389
1558
|
renderReview();
|
|
@@ -1522,6 +1691,7 @@ function updateActionButtons() {
|
|
|
1522
1691
|
elements.nextSettingsButton.disabled = !hasProject || isBusy || !state.environmentOk;
|
|
1523
1692
|
elements.doctorButton.disabled = !hasProject || isBusy;
|
|
1524
1693
|
elements.settingsNextButton.disabled = !hasProject || !state.settingsValid || !state.environmentOk || isBusy;
|
|
1694
|
+
elements.newFileButton.disabled = !hasProject;
|
|
1525
1695
|
setBuildButtons(hasProject && state.settingsValid && state.environmentOk && !isBusy);
|
|
1526
1696
|
}
|
|
1527
1697
|
|
|
@@ -1592,6 +1762,182 @@ function escapeHtml(value) {
|
|
|
1592
1762
|
.replace(/"/g, """);
|
|
1593
1763
|
}
|
|
1594
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
|
+
|
|
1595
1941
|
function recipeForCode(index) {
|
|
1596
1942
|
const language = currentLanguage();
|
|
1597
1943
|
const recipe = nativeCodeRecipes[index] || {};
|
|
@@ -1723,10 +2069,41 @@ function normalizeThemeMode(value) {
|
|
|
1723
2069
|
return String(value || "").trim().toLowerCase() === "auto" ? "auto" : "fixed";
|
|
1724
2070
|
}
|
|
1725
2071
|
|
|
2072
|
+
function normalizeBuildFormat(value) {
|
|
2073
|
+
return String(value || "").trim().toLowerCase() === "aab" ? "aab" : "apk";
|
|
2074
|
+
}
|
|
2075
|
+
|
|
1726
2076
|
function normalizeOneSignalAppId(value) {
|
|
1727
2077
|
return String(value || "").trim();
|
|
1728
2078
|
}
|
|
1729
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
|
+
|
|
1730
2107
|
function oneSignalAppIdFromConfig(config = {}) {
|
|
1731
2108
|
return normalizeOneSignalAppId(
|
|
1732
2109
|
config.oneSignalAppId
|
|
@@ -1789,6 +2166,7 @@ function populateSettings(config = {}, project = state.project) {
|
|
|
1789
2166
|
elements.appNameInput.value = config.appName || projectName || "";
|
|
1790
2167
|
elements.packageIdInput.value = config.packageId || `com.html2apk.${packageSegment(projectName)}`;
|
|
1791
2168
|
elements.versionInput.value = config.version || "1.0.0";
|
|
2169
|
+
elements.buildFormatInput.value = normalizeBuildFormat(config.buildFormat || config.outputFormat || config.artifactType || config.packageType);
|
|
1792
2170
|
elements.modeInput.value = config.mode || "fullscreen";
|
|
1793
2171
|
elements.orientationInput.value = normalizeOrientationInputValue(config.orientation);
|
|
1794
2172
|
elements.minSdkVersionInput.value = String(normalizeMinSdkVersion(config.minSdkVersion || config.androidMinSdkVersion));
|
|
@@ -1802,14 +2180,22 @@ function populateSettings(config = {}, project = state.project) {
|
|
|
1802
2180
|
const iconPath = String(config.icon || "").trim() || defaultIconPath();
|
|
1803
2181
|
elements.iconPathInput.value = iconPath;
|
|
1804
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;
|
|
1805
2188
|
elements.debugInput.checked = Boolean(config.debug);
|
|
1806
|
-
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");
|
|
1807
2191
|
}
|
|
1808
2192
|
|
|
1809
2193
|
function validateSettings() {
|
|
1810
2194
|
const errors = [];
|
|
1811
2195
|
const packagePattern = /^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z][a-zA-Z0-9_]*)+$/;
|
|
1812
2196
|
const versionPattern = /^\d+\.\d+\.\d+([-.+][0-9A-Za-z.-]+)?$/;
|
|
2197
|
+
const buildFormat = normalizeBuildFormat(elements.buildFormatInput.value);
|
|
2198
|
+
const keystore = keystoreFromInputs();
|
|
1813
2199
|
|
|
1814
2200
|
if (!state.project) {
|
|
1815
2201
|
errors.push(text("missingProject"));
|
|
@@ -1835,6 +2221,11 @@ function validateSettings() {
|
|
|
1835
2221
|
if (!isValidOptionalOneSignalAppId(elements.oneSignalAppIdInput.value)) {
|
|
1836
2222
|
errors.push(text("invalidOneSignalAppId"));
|
|
1837
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
|
+
}
|
|
1838
2229
|
if (elements.iconPathInput.value.trim() && !/\.png$/i.test(elements.iconPathInput.value.trim())) {
|
|
1839
2230
|
errors.push(text("invalidIconType"));
|
|
1840
2231
|
}
|
|
@@ -1875,6 +2266,7 @@ function renderReview() {
|
|
|
1875
2266
|
[text("appName"), elements.appNameInput.value.trim()],
|
|
1876
2267
|
[text("packageId"), elements.packageIdInput.value.trim()],
|
|
1877
2268
|
[text("appVersion"), elements.versionInput.value.trim()],
|
|
2269
|
+
[text("buildFormat"), selectedOptionText(elements.buildFormatInput)],
|
|
1878
2270
|
[text("mode"), selectedOptionText(elements.modeInput)],
|
|
1879
2271
|
[text("orientation"), selectedOptionText(elements.orientationInput)],
|
|
1880
2272
|
[text("minSdkVersion"), selectedOptionText(elements.minSdkVersionInput)],
|
|
@@ -1882,7 +2274,10 @@ function renderReview() {
|
|
|
1882
2274
|
[text("appThemeColor"), elements.themeColorTextInput.value.trim()],
|
|
1883
2275
|
[text("oneSignalAppId"), elements.oneSignalAppIdInput.value.trim() || "-"],
|
|
1884
2276
|
[text("androidPermissions"), selectedPermissions().join(", ")],
|
|
1885
|
-
[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") : "-"]
|
|
1886
2281
|
];
|
|
1887
2282
|
|
|
1888
2283
|
elements.reviewGrid.innerHTML = items.map(([label, value]) => `
|
|
@@ -2081,6 +2476,7 @@ function applyProjectChange(payload) {
|
|
|
2081
2476
|
}
|
|
2082
2477
|
|
|
2083
2478
|
renderProjectSnapshot(payload.project);
|
|
2479
|
+
refreshProjectFiles();
|
|
2084
2480
|
|
|
2085
2481
|
const reloadSettings = isConfigFilePath(payload.changedPath);
|
|
2086
2482
|
if (reloadSettings && !state.buildRunning) {
|
|
@@ -2095,6 +2491,14 @@ function applyProjectChange(payload) {
|
|
|
2095
2491
|
if (document.querySelector(".nav-item.active")?.dataset.view === "build") {
|
|
2096
2492
|
renderReview();
|
|
2097
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
|
+
}
|
|
2098
2502
|
}
|
|
2099
2503
|
|
|
2100
2504
|
async function summarizeProject(project) {
|
|
@@ -2109,6 +2513,16 @@ async function summarizeProject(project) {
|
|
|
2109
2513
|
elements.nextSettingsButton.disabled = false;
|
|
2110
2514
|
elements.doctorButton.disabled = true;
|
|
2111
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;
|
|
2112
2526
|
populateSettings(project.config || {}, project);
|
|
2113
2527
|
setStep("folder", project.hasEntryFile ? "done" : "active", project.hasEntryFile ? text("folderReady") : text("missing"));
|
|
2114
2528
|
setStep("settings", "active", text("settingsMissing"));
|
|
@@ -2118,6 +2532,7 @@ async function summarizeProject(project) {
|
|
|
2118
2532
|
setStatus("ready", text("projectLoaded"));
|
|
2119
2533
|
appendLog(`${text("droppedFolder")}: ${project.projectRoot}`, "system");
|
|
2120
2534
|
validateSettings();
|
|
2535
|
+
await refreshProjectFiles();
|
|
2121
2536
|
setProgress(project.hasEntryFile ? 25 : 15, project.hasEntryFile ? text("progressFolder") : text("missing"), project.hasEntryFile ? "" : "error");
|
|
2122
2537
|
await watchCurrentProject();
|
|
2123
2538
|
await ensureEnvironmentBeforeSettings();
|
|
@@ -2155,11 +2570,14 @@ async function runDoctorOnly() {
|
|
|
2155
2570
|
}
|
|
2156
2571
|
|
|
2157
2572
|
function buildOptions() {
|
|
2158
|
-
|
|
2573
|
+
const buildFormat = normalizeBuildFormat(elements.buildFormatInput.value);
|
|
2574
|
+
const keystore = keystoreFromInputs();
|
|
2575
|
+
const options = {
|
|
2159
2576
|
projectRoot: state.project.projectRoot,
|
|
2160
2577
|
appName: elements.appNameInput.value.trim(),
|
|
2161
2578
|
packageId: elements.packageIdInput.value.trim(),
|
|
2162
2579
|
version: elements.versionInput.value.trim(),
|
|
2580
|
+
buildFormat,
|
|
2163
2581
|
mode: elements.modeInput.value,
|
|
2164
2582
|
orientation: elements.orientationInput.value,
|
|
2165
2583
|
minSdkVersion: normalizeMinSdkVersion(elements.minSdkVersionInput.value),
|
|
@@ -2171,8 +2589,15 @@ function buildOptions() {
|
|
|
2171
2589
|
icon: elements.iconPathInput.value.trim(),
|
|
2172
2590
|
androidPlatform: elements.androidPlatformInput.value.trim(),
|
|
2173
2591
|
debug: elements.debugInput.checked,
|
|
2174
|
-
|
|
2592
|
+
showRuntimeLogs: elements.runtimeLogsInput.checked,
|
|
2593
|
+
release: elements.releaseInput.checked || buildFormat === "aab"
|
|
2175
2594
|
};
|
|
2595
|
+
|
|
2596
|
+
if (hasAnyKeystoreField(keystore)) {
|
|
2597
|
+
options.keystore = keystore;
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
return options;
|
|
2176
2601
|
}
|
|
2177
2602
|
|
|
2178
2603
|
function startAnimatedLogs() {
|
|
@@ -2360,6 +2785,9 @@ function bindEvents() {
|
|
|
2360
2785
|
if (button.dataset.view === "build" && !goToReview()) {
|
|
2361
2786
|
return;
|
|
2362
2787
|
}
|
|
2788
|
+
if (button.dataset.view === "files") {
|
|
2789
|
+
await refreshProjectFiles();
|
|
2790
|
+
}
|
|
2363
2791
|
setView(button.dataset.view);
|
|
2364
2792
|
});
|
|
2365
2793
|
});
|
|
@@ -2376,6 +2804,18 @@ function bindEvents() {
|
|
|
2376
2804
|
elements.doctorButton.addEventListener("click", runDoctorOnly);
|
|
2377
2805
|
elements.buildButton.addEventListener("click", runBuildFlow);
|
|
2378
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
|
+
});
|
|
2379
2819
|
elements.clearLogsButton.addEventListener("click", clearLogs);
|
|
2380
2820
|
elements.toggleLogsButton.addEventListener("click", toggleLogBar);
|
|
2381
2821
|
elements.bottomToggleLogsButton.addEventListener("click", toggleLogBar);
|
|
@@ -2390,6 +2830,21 @@ function bindEvents() {
|
|
|
2390
2830
|
appendLog(`${text("iconSelected")}: ${iconPath}`, "system");
|
|
2391
2831
|
validateSettings();
|
|
2392
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
|
+
});
|
|
2393
2848
|
elements.themeColorInput.addEventListener("input", () => {
|
|
2394
2849
|
elements.themeColorTextInput.value = elements.themeColorInput.value;
|
|
2395
2850
|
validateSettings();
|
|
@@ -2415,12 +2870,18 @@ function bindEvents() {
|
|
|
2415
2870
|
elements.appNameInput,
|
|
2416
2871
|
elements.packageIdInput,
|
|
2417
2872
|
elements.versionInput,
|
|
2873
|
+
elements.buildFormatInput,
|
|
2418
2874
|
elements.orientationInput,
|
|
2419
2875
|
elements.minSdkVersionInput,
|
|
2420
2876
|
elements.androidPlatformInput,
|
|
2421
2877
|
elements.themeModeInput,
|
|
2422
2878
|
elements.oneSignalAppIdInput,
|
|
2879
|
+
elements.keystorePathInput,
|
|
2880
|
+
elements.keystoreAliasInput,
|
|
2881
|
+
elements.keystoreStorePasswordInput,
|
|
2882
|
+
elements.keystoreKeyPasswordInput,
|
|
2423
2883
|
elements.debugInput,
|
|
2884
|
+
elements.runtimeLogsInput,
|
|
2424
2885
|
elements.releaseInput
|
|
2425
2886
|
].forEach((input) => {
|
|
2426
2887
|
input.addEventListener("input", validateSettings);
|