pinokiod 7.3.14 → 7.4.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.
Files changed (51) hide show
  1. package/kernel/api/index.js +111 -15
  2. package/kernel/api/script/index.js +10 -0
  3. package/kernel/autolaunch.js +111 -0
  4. package/kernel/environment.js +89 -1
  5. package/kernel/git.js +9 -19
  6. package/kernel/index.js +142 -43
  7. package/kernel/launch_requirements.js +1115 -0
  8. package/kernel/ready.js +231 -0
  9. package/kernel/script.js +16 -0
  10. package/kernel/shells.js +9 -1
  11. package/kernel/workspace_status.js +111 -45
  12. package/package.json +1 -1
  13. package/server/autolaunch.js +725 -0
  14. package/server/index.js +244 -411
  15. package/server/public/logs.js +15 -1
  16. package/server/public/style.css +99 -31
  17. package/server/views/app.ejs +263 -159
  18. package/server/views/autolaunch.ejs +363 -75
  19. package/server/views/index.ejs +550 -26
  20. package/server/views/logs.ejs +14 -12
  21. package/server/views/partials/app_autolaunch_dependency_events.ejs +99 -0
  22. package/server/views/partials/app_autolaunch_dependency_save.ejs +10 -0
  23. package/server/views/partials/app_autolaunch_dependency_styles.ejs +357 -0
  24. package/server/views/partials/app_autolaunch_modal_helpers.ejs +347 -0
  25. package/server/views/partials/autolaunch_dependency_helpers.ejs +204 -0
  26. package/server/views/partials/autolaunch_dependency_save.ejs +13 -0
  27. package/server/views/partials/autolaunch_dependency_styles.ejs +347 -0
  28. package/server/views/partials/home_action_modal.ejs +4 -1
  29. package/server/views/partials/launch_requirements_status_client.ejs +271 -0
  30. package/server/views/partials/launch_requirements_status_styles.ejs +171 -0
  31. package/server/views/partials/launch_settings_dependency_save_factory.ejs +45 -0
  32. package/server/views/partials/launch_settings_dependency_script_loader_factory.ejs +25 -0
  33. package/server/views/terminal.ejs +196 -2
  34. package/test/home-autolaunch-live-ui.test.js +455 -0
  35. package/test/launch-requirements-browser.test.js +579 -0
  36. package/test/launch-requirements-contract-coverage.test.js +627 -0
  37. package/test/launch-requirements-real-browser.js +625 -0
  38. package/test/launch-requirements-status-client.test.js +132 -0
  39. package/test/launch-requirements.test.js +1806 -0
  40. package/test/launch-settings-ui.test.js +370 -0
  41. package/test/ready-state.test.js +49 -0
  42. package/test/server-autolaunch.test.js +1052 -0
  43. package/test/startup-git-index-benchmark.js +409 -0
  44. package/test/startup-git-index-browser.js +320 -0
  45. package/test/startup-git-index-performance.test.js +380 -0
  46. package/test/startup-git-index-refactor.test.js +450 -0
  47. package/test/startup-git-index-route.test.js +588 -0
  48. package/test/universal-launcher.smoke.spec.js +10 -9
  49. package/test/workspace-gitignore-benchmark.js +815 -0
  50. package/test/workspace-gitignore-path-scoped.test.js +256 -0
  51. package/spec/INSTRUCTION_SYNC.md +0 -432
@@ -603,6 +603,7 @@ body.dark .browserview-network-status {
603
603
  color: rgba(248, 250, 252, 0.9);
604
604
  background: var(--app-page-canvas-bg, #1b1c1d);
605
605
  }
606
+ <%- include('./partials/launch_requirements_status_styles') %>
606
607
  .browserview-network-status-inner {
607
608
  display: flex;
608
609
  flex-direction: column;
@@ -3685,6 +3686,11 @@ body.dark .disk-usage {
3685
3686
  text-overflow: ellipsis;
3686
3687
  color: var(--pinokio-sidebar-tab-muted);
3687
3688
  }
3689
+ .app-autolaunch-label > span {
3690
+ min-width: 0;
3691
+ overflow: hidden;
3692
+ text-overflow: ellipsis;
3693
+ }
3688
3694
  .app-autolaunch-label i {
3689
3695
  width: 16px;
3690
3696
  flex: 0 0 16px;
@@ -3702,6 +3708,7 @@ body.dark .disk-usage {
3702
3708
  font-size: 11px;
3703
3709
  font-weight: 700;
3704
3710
  letter-spacing: 0.02em;
3711
+ white-space: nowrap;
3705
3712
  }
3706
3713
  .app-autolaunch-row[data-enabled="true"] .app-autolaunch-status {
3707
3714
  color: rgba(22, 101, 52, 0.95);
@@ -3793,6 +3800,21 @@ body.dark .app-autolaunch-empty {
3793
3800
  gap: 8px;
3794
3801
  flex: 0 0 auto;
3795
3802
  }
3803
+ .app-autolaunch-switch-group {
3804
+ display: inline-flex;
3805
+ align-items: center;
3806
+ gap: 7px;
3807
+ }
3808
+ .app-autolaunch-switch-caption {
3809
+ color: rgba(100, 116, 139, 0.95);
3810
+ font-size: 11px;
3811
+ font-weight: 700;
3812
+ line-height: 1;
3813
+ white-space: nowrap;
3814
+ }
3815
+ body.dark .app-autolaunch-switch-caption {
3816
+ color: rgba(148, 163, 184, 0.95);
3817
+ }
3796
3818
  .app-autolaunch-close {
3797
3819
  display: inline-flex;
3798
3820
  align-items: center;
@@ -3914,10 +3936,42 @@ body.dark .app-autolaunch-section-label {
3914
3936
  .app-autolaunch-script-option > div {
3915
3937
  min-width: 0;
3916
3938
  }
3939
+ .app-autolaunch-clear-script {
3940
+ min-height: 32px;
3941
+ border: 1px solid rgba(15, 23, 42, 0.08);
3942
+ border-radius: 6px;
3943
+ background: transparent;
3944
+ color: rgba(100, 116, 139, 0.95);
3945
+ display: inline-flex;
3946
+ align-items: center;
3947
+ justify-content: center;
3948
+ gap: 6px;
3949
+ padding: 0 9px;
3950
+ font: inherit;
3951
+ font-size: 12px;
3952
+ font-weight: 700;
3953
+ cursor: pointer;
3954
+ }
3955
+ .app-autolaunch-clear-script:hover {
3956
+ background: rgba(148, 163, 184, 0.16);
3957
+ color: rgba(15, 23, 42, 0.9);
3958
+ }
3959
+ .app-autolaunch-clear-script:disabled {
3960
+ cursor: not-allowed;
3961
+ opacity: 0.55;
3962
+ }
3917
3963
  body.dark .app-autolaunch-script-option {
3918
3964
  border-color: rgba(148, 163, 184, 0.16);
3919
3965
  background: rgba(15, 23, 42, 0.72);
3920
3966
  }
3967
+ body.dark .app-autolaunch-clear-script {
3968
+ border-color: rgba(148, 163, 184, 0.16);
3969
+ color: rgba(203, 213, 225, 0.9);
3970
+ }
3971
+ body.dark .app-autolaunch-clear-script:hover {
3972
+ background: rgba(148, 163, 184, 0.16);
3973
+ color: rgba(226, 232, 240, 0.92);
3974
+ }
3921
3975
  body.dark .app-autolaunch-script-option:hover,
3922
3976
  body.dark .app-autolaunch-script-option.selected {
3923
3977
  border-color: rgba(96, 165, 250, 0.42);
@@ -3951,6 +4005,7 @@ body.dark .app-autolaunch-tag {
3951
4005
  background: rgba(148, 163, 184, 0.18);
3952
4006
  color: rgba(203, 213, 225, 0.9);
3953
4007
  }
4008
+ <%- include('partials/app_autolaunch_dependency_styles') %>
3954
4009
  .app-autolaunch-footer {
3955
4010
  display: flex;
3956
4011
  align-items: center;
@@ -7819,10 +7874,10 @@ body.dark .pinokio-custom-terminal-header {
7819
7874
  </div>
7820
7875
  </div>
7821
7876
  <div class='mobile-drawer-actions' aria-label="App actions">
7822
- <button type='button' class='btn header-item mobile-nav-logs' data-mobile-proxy="#logs-toggle" data-mobile-panel="logs" data-mobile-close-menu="true" aria-pressed="false" aria-label="Logs">
7877
+ <button type='button' class='btn header-item mobile-nav-logs' data-mobile-proxy="#logs-toggle" data-mobile-panel="logs" data-mobile-close-menu="true" aria-pressed="false" aria-label="Report Issue">
7823
7878
  <div class='tab'>
7824
- <i class="fa-solid fa-laptop-code menu-action-leading-icon"></i>
7825
- <div class='display'>Logs</div>
7879
+ <i class="fa-solid fa-circle-exclamation menu-action-leading-icon"></i>
7880
+ <div class='display'>Report Issue</div>
7826
7881
  <div class='flexible'></div>
7827
7882
  </div>
7828
7883
  </button>
@@ -7856,10 +7911,10 @@ body.dark .pinokio-custom-terminal-header {
7856
7911
  <% if (type === 'browse') { %>
7857
7912
  <%- include('./partials/fs_status') %>
7858
7913
  <% } %>
7859
- <button type='button' id='logs-toggle' class="btn header-item" data-tippy-content="logs" aria-expanded="false" aria-controls="app-logs-drawer">
7914
+ <button type='button' id='logs-toggle' class="btn header-item" data-tippy-content="Report issue with logs" aria-expanded="false" aria-controls="app-logs-drawer">
7860
7915
  <div class='tab'>
7861
- <i class="fa-solid fa-laptop-code menu-action-leading-icon"></i>
7862
- <div class='display'>Logs</div>
7916
+ <i class="fa-solid fa-circle-exclamation menu-action-leading-icon"></i>
7917
+ <div class='display'>Report Issue</div>
7863
7918
  <div class='flexible'></div>
7864
7919
  <span class='logs-new-badge hidden' id='logs-new-badge'>New</span>
7865
7920
  </div>
@@ -7884,13 +7939,16 @@ body.dark .pinokio-custom-terminal-header {
7884
7939
  <div class="app-autolaunch-modal-head">
7885
7940
  <div>
7886
7941
  <div class="app-autolaunch-title">Autolaunch</div>
7887
- <div class="app-autolaunch-note">Run this app when Pinokio starts.</div>
7942
+ <div class="app-autolaunch-note">Choose launch script, startup behavior, and requirements.</div>
7888
7943
  </div>
7889
7944
  <div class="app-autolaunch-modal-actions">
7890
- <button type="button" class="app-autolaunch-switch" role="switch" aria-checked="<%= autolaunch_app.autolaunch_enabled ? 'true' : 'false' %>" data-app-autolaunch-switch aria-label="Autolaunch" title="Autolaunch is <%= autolaunch_app.autolaunch_enabled ? 'on' : 'off' %>">
7891
- <span class="app-autolaunch-switch-label" data-app-autolaunch-switch-label><%= autolaunch_app.autolaunch_enabled ? 'ON' : 'OFF' %></span>
7892
- <span class="app-autolaunch-switch-thumb" aria-hidden="true"></span>
7893
- </button>
7945
+ <div class="app-autolaunch-switch-group">
7946
+ <span class="app-autolaunch-switch-caption">Start with Pinokio</span>
7947
+ <button type="button" class="app-autolaunch-switch" role="switch" aria-checked="<%= autolaunch_app.autolaunch_enabled ? 'true' : 'false' %>" data-app-autolaunch-switch aria-label="Start with Pinokio" title="Start with Pinokio is <%= autolaunch_app.autolaunch_enabled ? 'on' : 'off' %>">
7948
+ <span class="app-autolaunch-switch-label" data-app-autolaunch-switch-label><%= autolaunch_app.autolaunch_enabled ? 'ON' : 'OFF' %></span>
7949
+ <span class="app-autolaunch-switch-thumb" aria-hidden="true"></span>
7950
+ </button>
7951
+ </div>
7894
7952
  <button type="button" class="app-autolaunch-close" data-app-autolaunch-close aria-label="Close">
7895
7953
  <i class="fa-solid fa-xmark" aria-hidden="true"></i>
7896
7954
  </button>
@@ -7902,6 +7960,12 @@ body.dark .pinokio-custom-terminal-header {
7902
7960
  <div class="app-autolaunch-empty">Loading scripts...</div>
7903
7961
  </div>
7904
7962
  </div>
7963
+ <div class="app-autolaunch-modal-section">
7964
+ <div class="app-autolaunch-section-label">Requires</div>
7965
+ <div class="app-autolaunch-dependencies" data-app-autolaunch-dependencies>
7966
+ <div class="app-autolaunch-empty">Loading dependencies...</div>
7967
+ </div>
7968
+ </div>
7905
7969
  <div class="app-autolaunch-footer">
7906
7970
  <div class="app-autolaunch-feedback" data-app-autolaunch-feedback></div>
7907
7971
  <a class="app-autolaunch-link" href="/autolaunch">Manage all</a>
@@ -7983,6 +8047,7 @@ body.dark .pinokio-custom-terminal-header {
7983
8047
  <iframe class='selected' src="<%=editor_tab%>" allow="fullscreen *;" allowfullscreen></iframe>
7984
8048
  <% } %>
7985
8049
  <div id="browserview-network-status" class="browserview-network-status" hidden aria-live="polite"></div>
8050
+ <div id="launch-requirements-status" class="launch-requirements-status" data-launch-requirements-status hidden aria-live="polite"></div>
7986
8051
  <%- include('./partials/browser_popout_surface') %>
7987
8052
  </main>
7988
8053
  <% if (type === 'run') { %>
@@ -8295,6 +8360,7 @@ body.dark .pinokio-custom-terminal-header {
8295
8360
  let pendingSelectionRetry = null
8296
8361
  let pendingHomeSelectionRetry = null
8297
8362
  let initialWorkspaceDiskUsageRequested = false
8363
+ const openWithoutLaunching = <%- JSON.stringify(type === "run" && !autoselect) %>
8298
8364
  const scheduleSelectionRetry = (delay = 100) => {
8299
8365
  if (pendingSelectionRetry !== null) {
8300
8366
  return
@@ -10004,7 +10070,9 @@ const rerenderMenuSection = (container, html) => {
10004
10070
  return ""
10005
10071
  }
10006
10072
  })()
10007
- let persistedSelectionRaw = selectionKey && storage ? storage.getItem(selectionKey) : null
10073
+ const triggeredByUser = Boolean(eventParam || explicitTarget)
10074
+ const automaticSelectionDisabled = openWithoutLaunching && !triggeredByUser
10075
+ let persistedSelectionRaw = automaticSelectionDisabled ? null : (selectionKey && storage ? storage.getItem(selectionKey) : null)
10008
10076
  const devRouteActive = /\/dev(?:$|\/)/.test(currentPath || "")
10009
10077
  let persistedSelectionPayload = null
10010
10078
  if (persistedSelectionRaw) {
@@ -10029,9 +10097,8 @@ const rerenderMenuSection = (container, html) => {
10029
10097
  persistedSelectionRaw = null
10030
10098
  }
10031
10099
  const originalHasPersistedSelection = Boolean(persistedSelectionPayload)
10032
- const triggeredByUser = Boolean(eventParam || explicitTarget)
10033
- const followCurrentDefault = forceDefaultSelection && !triggeredByUser
10034
- const skipPersistedSelection = followCurrentDefault || (ignorePersistedSelection && Boolean(document.querySelector('[data-default]')))
10100
+ const followCurrentDefault = !automaticSelectionDisabled && forceDefaultSelection && !triggeredByUser
10101
+ const skipPersistedSelection = automaticSelectionDisabled || followCurrentDefault || (ignorePersistedSelection && Boolean(document.querySelector('[data-default]')))
10035
10102
  let hasPersistedSelection = skipPersistedSelection ? false : originalHasPersistedSelection
10036
10103
  if (skipPersistedSelection) {
10037
10104
  persistedSelectionRaw = null
@@ -10053,7 +10120,7 @@ const rerenderMenuSection = (container, html) => {
10053
10120
  target = eventParam.target?.closest(".frame-link") || null
10054
10121
  }
10055
10122
 
10056
- if (!target && global_selector) {
10123
+ if (!target && !automaticSelectionDisabled && global_selector) {
10057
10124
  const candidate = document.querySelector(global_selector)
10058
10125
  if (candidate) {
10059
10126
  target = candidate
@@ -10065,11 +10132,11 @@ const rerenderMenuSection = (container, html) => {
10065
10132
  }
10066
10133
  }
10067
10134
 
10068
- if (!target && !skipPersistedSelection) {
10135
+ if (!target && !automaticSelectionDisabled && !skipPersistedSelection) {
10069
10136
  preselected = document.querySelector('#devtab.frame-link.selected') || document.querySelector('.frame-link.selected')
10070
10137
  }
10071
10138
 
10072
- if (!target && persistedSelectionPayload) {
10139
+ if (!target && !automaticSelectionDisabled && persistedSelectionPayload) {
10073
10140
  target = restorePersistedFrameLink(persistedSelectionPayload)
10074
10141
  if (!target && originalHasPersistedSelection) {
10075
10142
  scheduleSelectionRetry()
@@ -10077,7 +10144,7 @@ const rerenderMenuSection = (container, html) => {
10077
10144
  }
10078
10145
  }
10079
10146
 
10080
- if (!target && !skipPersistedSelection) {
10147
+ if (!target && !automaticSelectionDisabled && !skipPersistedSelection) {
10081
10148
  const urlKey = selectionUrlStorageKey()
10082
10149
  const storedUrl = urlKey && storage ? storage.getItem(urlKey) : null
10083
10150
  if (storedUrl) {
@@ -10085,7 +10152,7 @@ const rerenderMenuSection = (container, html) => {
10085
10152
  }
10086
10153
  }
10087
10154
 
10088
- if (!target && followCurrentDefault) {
10155
+ if (!target && !automaticSelectionDisabled && followCurrentDefault) {
10089
10156
  const defaultSelection = getDefaultSelection()
10090
10157
  if (defaultSelection) {
10091
10158
  target = defaultSelection
@@ -10093,11 +10160,11 @@ const rerenderMenuSection = (container, html) => {
10093
10160
  }
10094
10161
 
10095
10162
  const devTab = document.querySelector('#devtab.frame-link')
10096
- if (!target && preselected && preselected !== devTab) {
10163
+ if (!target && !automaticSelectionDisabled && preselected && preselected !== devTab) {
10097
10164
  target = preselected
10098
10165
  }
10099
10166
 
10100
- if (!triggeredByUser && !resolvedByGlobalSelector && !target && devRouteActive && devTab) {
10167
+ if (!automaticSelectionDisabled && !triggeredByUser && !resolvedByGlobalSelector && !target && devRouteActive && devTab) {
10101
10168
  const defaultCandidate = getDefaultSelection()
10102
10169
  if (pluginLaunchActive && defaultCandidate) {
10103
10170
  target = defaultCandidate
@@ -10106,7 +10173,7 @@ const rerenderMenuSection = (container, html) => {
10106
10173
  }
10107
10174
  }
10108
10175
 
10109
- if (!target && preselected) {
10176
+ if (!target && !automaticSelectionDisabled && preselected) {
10110
10177
  target = preselected
10111
10178
  }
10112
10179
 
@@ -10141,7 +10208,7 @@ const rerenderMenuSection = (container, html) => {
10141
10208
  }
10142
10209
  <% } %>
10143
10210
 
10144
- if (!target && preselected) {
10211
+ if (!target && !automaticSelectionDisabled && preselected) {
10145
10212
  target = preselected
10146
10213
  }
10147
10214
 
@@ -10278,7 +10345,6 @@ const rerenderMenuSection = (container, html) => {
10278
10345
  eventParam.preventDefault()
10279
10346
  eventParam.stopPropagation()
10280
10347
  }
10281
-
10282
10348
  // save target.href
10283
10349
  <% if (type !== "run") { %>
10284
10350
  let _url = new URL(target.href)
@@ -10372,7 +10438,7 @@ const rerenderMenuSection = (container, html) => {
10372
10438
  writeFrameInjectDescriptors(targetFrame, targetInjectDescriptors)
10373
10439
  publishBrowserviewInjectTargets([], { sync: true })
10374
10440
  targetFrame.classList.remove("hidden")
10375
- let mode = target.getAttribute("data-mode")
10441
+ let mode = target.getAttribute("data-mode")
10376
10442
  if (mode === "refresh") {
10377
10443
  if (targetFrame.src !== target.href) {
10378
10444
  targetFrame.src = target.href
@@ -10661,7 +10727,7 @@ const rerenderMenuSection = (container, html) => {
10661
10727
  }
10662
10728
  if (mobileLogsButton) {
10663
10729
  mobileLogsButton.classList.toggle("has-new", show)
10664
- mobileLogsButton.setAttribute("aria-label", show ? "Logs, new issue" : "Logs")
10730
+ mobileLogsButton.setAttribute("aria-label", show ? "Report Issue, new report available" : "Report Issue")
10665
10731
  }
10666
10732
  try {
10667
10733
  if (show) {
@@ -18480,6 +18546,8 @@ document.addEventListener("DOMContentLoaded", () => {
18480
18546
  }
18481
18547
  })()
18482
18548
 
18549
+ <%- include('partials/launch_requirements_status_client') %>
18550
+
18483
18551
  ;(function() {
18484
18552
  const root = document.querySelector("[data-app-autolaunch]")
18485
18553
  const initialState = <%- JSON.stringify((typeof autolaunch_app !== "undefined" && autolaunch_app) ? autolaunch_app : null).replace(/</g, "\\u003c") %>
@@ -18493,8 +18561,9 @@ document.addEventListener("DOMContentLoaded", () => {
18493
18561
  const switchLabel = root.querySelector("[data-app-autolaunch-switch-label]")
18494
18562
  const closeButton = root.querySelector("[data-app-autolaunch-close]")
18495
18563
  const scriptsEl = root.querySelector("[data-app-autolaunch-scripts]")
18564
+ const dependenciesEl = root.querySelector("[data-app-autolaunch-dependencies]")
18496
18565
  const feedbackEl = root.querySelector("[data-app-autolaunch-feedback]")
18497
- if (!appId || !button || !status || !modal || !switchButton || !switchLabel || !closeButton || !scriptsEl || !feedbackEl) return
18566
+ if (!appId || !button || !status || !modal || !switchButton || !switchLabel || !closeButton || !scriptsEl || !dependenciesEl || !feedbackEl) return
18498
18567
 
18499
18568
  let state = Object.assign({}, initialState)
18500
18569
  let candidateState = null
@@ -18502,6 +18571,13 @@ document.addEventListener("DOMContentLoaded", () => {
18502
18571
  let selectedScript = state.autolaunch || ""
18503
18572
  let loadingCandidates = false
18504
18573
  let saving = false
18574
+ let savingDependencies = false
18575
+ let savingDependencyScript = false
18576
+ let dependencyPickerOpen = false
18577
+ let dependencyScriptPickerOpen = ""
18578
+ let pendingDependencyId = ""
18579
+ let dependencyScriptCandidates = {}
18580
+ let dependencyScriptSelections = {}
18505
18581
 
18506
18582
  const escapeHtml = (value) => {
18507
18583
  if (value === null || value === undefined) return ""
@@ -18520,124 +18596,17 @@ document.addEventListener("DOMContentLoaded", () => {
18520
18596
  if (!candidateState) return []
18521
18597
  return [].concat(candidateState.menu || [], candidateState.other || [])
18522
18598
  }
18523
- const chooseInitialScript = (data) => {
18524
- const candidates = data ? [].concat(data.menu || [], data.other || []) : []
18525
- if (data && data.current) return data.current
18526
- const defaultCandidate = candidates.find((candidate) => candidate && candidate.menu_default)
18527
- if (defaultCandidate) return defaultCandidate.script
18528
- return candidates.length > 0 ? candidates[0].script : ""
18529
- }
18530
- const setFeedback = (message, isError = false) => {
18531
- feedbackEl.textContent = message || ""
18532
- feedbackEl.classList.toggle("error", !!isError)
18533
- }
18534
- const setBusy = (busy) => {
18535
- saving = !!busy
18536
- switchButton.disabled = saving || loadingCandidates
18537
- button.toggleAttribute("aria-busy", saving)
18538
- scriptsEl.querySelectorAll("input").forEach((input) => {
18539
- input.disabled = saving
18540
- })
18541
- }
18542
- const renderStatus = () => {
18543
- const enabled = !!(state && state.autolaunch_enabled)
18544
- status.textContent = enabled ? "ON" : "OFF"
18545
- button.dataset.enabled = enabled ? "true" : "false"
18546
- button.setAttribute("aria-label", `Auto launch ${enabled ? "On" : "Off"}`)
18547
- switchButton.setAttribute("aria-checked", enabled ? "true" : "false")
18548
- switchButton.title = `Auto launch is ${enabled ? "on" : "off"}`
18549
- switchLabel.textContent = enabled ? "ON" : "OFF"
18550
- root.classList.toggle("enabled", enabled)
18551
- }
18552
- const setOpen = (open) => {
18553
- root.classList.toggle("open", !!open)
18554
- button.setAttribute("aria-expanded", open ? "true" : "false")
18555
- modal.classList.toggle("hidden", !open)
18556
- if (open) {
18557
- if (!candidateState && !loadingCandidates) {
18558
- loadCandidates()
18559
- }
18560
- window.setTimeout(() => {
18561
- if (!modal.classList.contains("hidden")) {
18562
- switchButton.focus()
18563
- }
18564
- }, 0)
18565
- } else {
18566
- button.focus()
18567
- }
18568
- }
18569
- const renderScripts = () => {
18570
- if (loadingCandidates) {
18571
- scriptsEl.innerHTML = '<div class="app-autolaunch-empty">Loading scripts...</div>'
18572
- setBusy(false)
18573
- return
18574
- }
18575
- if (candidateError) {
18576
- scriptsEl.innerHTML = `<div class="app-autolaunch-empty">${escapeHtml(candidateError)}</div>`
18577
- setBusy(false)
18578
- return
18579
- }
18580
- if (!candidateState) {
18581
- scriptsEl.innerHTML = '<div class="app-autolaunch-empty">Open to load scripts.</div>'
18582
- setBusy(false)
18583
- return
18584
- }
18585
- const candidates = allCandidates().slice()
18586
- const current = candidateState.current || state.autolaunch || ""
18587
- if (current && !candidates.some((candidate) => candidate && candidate.script === current)) {
18588
- candidates.unshift({
18589
- script: current,
18590
- label: "Current",
18591
- source: "current",
18592
- current: true
18593
- })
18594
- }
18595
- if (!selectedScript) {
18596
- selectedScript = chooseInitialScript(candidateState)
18597
- }
18598
- if (candidates.length === 0) {
18599
- scriptsEl.innerHTML = '<div class="app-autolaunch-empty">No local scripts were found for this app.</div>'
18600
- setBusy(false)
18601
- return
18602
- }
18603
- scriptsEl.innerHTML = candidates.map((candidate) => {
18604
- const script = candidate && candidate.script ? candidate.script : ""
18605
- if (!script) return ""
18606
- const label = candidate.label || script
18607
- const checked = script === selectedScript ? "checked" : ""
18608
- const selected = script === selectedScript ? " selected" : ""
18609
- const tags = [
18610
- candidate.current ? '<span class="app-autolaunch-tag">Current</span>' : "",
18611
- candidate.menu_default ? '<span class="app-autolaunch-tag">Default</span>' : "",
18612
- candidate.has_params ? '<span class="app-autolaunch-tag">Params ignored</span>' : ""
18613
- ].filter(Boolean).join("")
18614
- const pathLine = label === script
18615
- ? ""
18616
- : `<div class="app-autolaunch-script-path" title="${escapeHtml(script)}">${escapeHtml(script)}</div>`
18617
- return `
18618
- <label class="app-autolaunch-script-option${selected}">
18619
- <input type="radio" name="app-autolaunch-script" value="${escapeHtml(script)}" ${checked}>
18620
- <div>
18621
- <div class="app-autolaunch-script-title"><span>${escapeHtml(label)}</span>${tags}</div>
18622
- ${pathLine}
18623
- </div>
18624
- </label>
18625
- `
18626
- }).join("")
18627
- setBusy(false)
18628
- }
18629
- const syncState = (updated) => {
18630
- if (updated) {
18631
- state = Object.assign({}, state, updated)
18632
- if (candidateState) {
18633
- candidateState.app = Object.assign({}, candidateState.app || {}, updated)
18634
- candidateState.current = updated.autolaunch || ""
18635
- }
18636
- if (updated.autolaunch) {
18637
- selectedScript = updated.autolaunch
18638
- }
18639
- }
18640
- renderStatus()
18599
+ <%- include('partials/app_autolaunch_modal_helpers') %>
18600
+ const hasLaunchRequirementConfig = () => {
18601
+ return !!(
18602
+ state.autolaunch &&
18603
+ Array.isArray(state.autolaunch_depends) &&
18604
+ state.autolaunch_depends.length > 0
18605
+ )
18606
+ }
18607
+ const notifyLaunchRequirementsConfigured = () => {
18608
+ if (!hasLaunchRequirementConfig()) return
18609
+ window.dispatchEvent(new CustomEvent("pinokio:launch-requirements-configured"))
18641
18610
  }
18642
18611
  const loadCandidates = async () => {
18643
18612
  loadingCandidates = true
@@ -18654,16 +18623,84 @@ document.addEventListener("DOMContentLoaded", () => {
18654
18623
  }
18655
18624
  candidateState = data
18656
18625
  syncState(data.app)
18657
- selectedScript = chooseInitialScript(data)
18626
+ selectedScript = configuredScript(data)
18658
18627
  setFeedback("")
18659
18628
  } catch (error) {
18660
18629
  candidateError = error && error.message ? error.message : "Failed to load scripts."
18661
18630
  } finally {
18662
18631
  loadingCandidates = false
18663
18632
  renderScripts()
18633
+ renderDependencies()
18634
+ }
18635
+ }
18636
+ <%- include('partials/app_autolaunch_dependency_save') %>
18637
+ const updateDependencyCandidateState = (updatedApp) => {
18638
+ if (!updatedApp || !updatedApp.id || !candidateState || !Array.isArray(candidateState.dependency_apps)) return
18639
+ candidateState.dependency_apps = candidateState.dependency_apps.map((app) => app.id === updatedApp.id ? updatedApp : app)
18640
+ }
18641
+ <%- include('partials/launch_settings_dependency_script_loader_factory') %>
18642
+ const loadDependencyScriptCandidates = createLaunchSettingsDependencyScriptLoader({
18643
+ getCache: () => dependencyScriptCandidates,
18644
+ render: renderDependencies
18645
+ })
18646
+ const saveDependencyScript = async (dependencyId, script) => {
18647
+ const dependency = dependencyById(dependencyId)
18648
+ if (!dependency || !script || savingDependencyScript) return
18649
+ savingDependencyScript = true
18650
+ setBusy(true)
18651
+ setFeedback("Saving requirement script...")
18652
+ try {
18653
+ const response = await fetch("/autolaunch", {
18654
+ method: "POST",
18655
+ headers: {
18656
+ "Content-Type": "application/json",
18657
+ "Accept": "application/json"
18658
+ },
18659
+ body: JSON.stringify({
18660
+ app: dependencyId,
18661
+ script,
18662
+ enabled: !!dependency.autolaunch_enabled
18663
+ })
18664
+ })
18665
+ const data = await response.json()
18666
+ if (!response.ok || !data.ok) {
18667
+ throw new Error(data.error || "Failed to save launch script.")
18668
+ }
18669
+ updateDependencyCandidateState(data.app)
18670
+ if (pendingDependencyId === dependencyId) {
18671
+ const dependencies = Array.isArray(state.autolaunch_depends) ? state.autolaunch_depends.slice() : []
18672
+ if (!dependencies.includes(dependencyId)) {
18673
+ const savedDependency = await saveDependencies(dependencies.concat(dependencyId), "Requirement saved.")
18674
+ if (savedDependency) {
18675
+ notifyLaunchRequirementsConfigured()
18676
+ pendingDependencyId = ""
18677
+ delete dependencyScriptSelections[dependencyId]
18678
+ dependencyScriptPickerOpen = ""
18679
+ renderDependencies()
18680
+ }
18681
+ } else {
18682
+ pendingDependencyId = ""
18683
+ delete dependencyScriptSelections[dependencyId]
18684
+ dependencyScriptPickerOpen = ""
18685
+ renderDependencies()
18686
+ notifyLaunchRequirementsConfigured()
18687
+ setFeedback("Requirement saved.")
18688
+ }
18689
+ } else {
18690
+ dependencyScriptPickerOpen = ""
18691
+ delete dependencyScriptSelections[dependencyId]
18692
+ renderDependencies()
18693
+ setFeedback("Requirement script saved.")
18694
+ }
18695
+ } catch (error) {
18696
+ setFeedback(error && error.message ? error.message : "Failed to save launch script.", true)
18697
+ renderDependencies()
18698
+ } finally {
18699
+ savingDependencyScript = false
18700
+ setBusy(false)
18664
18701
  }
18665
18702
  }
18666
- const saveScript = async (script) => {
18703
+ const saveScript = async (script, enabled = !!state.autolaunch_enabled) => {
18667
18704
  if (saving) return
18668
18705
  setBusy(true)
18669
18706
  setFeedback(script ? "Saving..." : "Disabling...")
@@ -18674,17 +18711,52 @@ document.addEventListener("DOMContentLoaded", () => {
18674
18711
  "Content-Type": "application/json",
18675
18712
  "Accept": "application/json"
18676
18713
  },
18677
- body: JSON.stringify({ app: appId, script: script || "" })
18714
+ body: JSON.stringify({ app: appId, script: script || "", enabled: !!enabled })
18715
+ })
18716
+ const data = await response.json()
18717
+ if (!response.ok || !data.ok) {
18718
+ throw new Error(data.error || "Failed to save Autolaunch settings.")
18719
+ }
18720
+ syncState(data.app)
18721
+ notifyLaunchRequirementsConfigured()
18722
+ setFeedback(data.app && data.app.autolaunch_enabled ? "Saved." : (script ? "Launch script saved." : "Disabled."))
18723
+ renderScripts()
18724
+ renderDependencies()
18725
+ } catch (error) {
18726
+ setFeedback(error && error.message ? error.message : "Failed to save Autolaunch settings.", true)
18727
+ renderStatus()
18728
+ } finally {
18729
+ setBusy(false)
18730
+ }
18731
+ }
18732
+ const clearScript = async () => {
18733
+ if (saving) return
18734
+ if (Array.isArray(state.autolaunch_depends) && state.autolaunch_depends.length > 0) {
18735
+ setFeedback("Remove requirements before clearing this app's launch script.", true)
18736
+ return
18737
+ }
18738
+ setBusy(true)
18739
+ setFeedback("Clearing launch script...")
18740
+ try {
18741
+ const response = await fetch("/autolaunch", {
18742
+ method: "POST",
18743
+ headers: {
18744
+ "Content-Type": "application/json",
18745
+ "Accept": "application/json"
18746
+ },
18747
+ body: JSON.stringify({ app: appId, clear_script: true })
18678
18748
  })
18679
18749
  const data = await response.json()
18680
18750
  if (!response.ok || !data.ok) {
18681
- throw new Error(data.error || "Failed to save autolaunch.")
18751
+ throw new Error(data.error || "Failed to clear launch script.")
18682
18752
  }
18683
18753
  syncState(data.app)
18684
- setFeedback(data.app && data.app.autolaunch_enabled ? "Saved." : "Disabled.")
18754
+ notifyLaunchRequirementsConfigured()
18755
+ setFeedback("Launch script cleared.")
18685
18756
  renderScripts()
18757
+ renderDependencies()
18686
18758
  } catch (error) {
18687
- setFeedback(error && error.message ? error.message : "Failed to save autolaunch.", true)
18759
+ setFeedback(error && error.message ? error.message : "Failed to clear launch script.", true)
18688
18760
  renderStatus()
18689
18761
  } finally {
18690
18762
  setBusy(false)
@@ -18693,23 +18765,25 @@ document.addEventListener("DOMContentLoaded", () => {
18693
18765
  const toggleAutolaunch = async () => {
18694
18766
  if (saving || loadingCandidates) return
18695
18767
  if (state.autolaunch_enabled) {
18696
- await saveScript("")
18768
+ const script = selectedScript || state.autolaunch || ""
18769
+ await saveScript(script, false)
18697
18770
  return
18698
18771
  }
18699
18772
  if (!candidateState) {
18700
18773
  await loadCandidates()
18701
18774
  }
18702
- const script = selectedScript || chooseInitialScript(candidateState)
18775
+ const script = selectedScript || ""
18703
18776
  if (!script) {
18704
- setFeedback("Choose a script first.", true)
18777
+ setFeedback("Choose a launch script first.", true)
18705
18778
  return
18706
18779
  }
18707
18780
  selectedScript = script
18708
- await saveScript(script)
18781
+ await saveScript(script, true)
18709
18782
  }
18710
18783
 
18711
18784
  renderStatus()
18712
18785
  renderScripts()
18786
+ renderDependencies()
18713
18787
 
18714
18788
  button.addEventListener("click", (event) => {
18715
18789
  event.preventDefault()
@@ -18738,11 +18812,41 @@ document.addEventListener("DOMContentLoaded", () => {
18738
18812
  if (!input) return
18739
18813
  selectedScript = input.value
18740
18814
  renderScripts()
18741
- if (state.autolaunch_enabled && selectedScript !== state.autolaunch) {
18742
- saveScript(selectedScript)
18815
+ if (selectedScript !== state.autolaunch) {
18816
+ saveScript(selectedScript, !!state.autolaunch_enabled)
18743
18817
  }
18744
18818
  })
18745
- document.addEventListener("keydown", (event) => {
18819
+ scriptsEl.addEventListener("click", (event) => {
18820
+ const clear = event.target && event.target.closest("[data-app-autolaunch-clear-script]")
18821
+ if (!clear) return
18822
+ event.preventDefault()
18823
+ clearScript()
18824
+ })
18825
+ <%- include('partials/app_autolaunch_dependency_events') %>
18826
+ window.addEventListener("pinokio:launch-requirements-choose-script", async (event) => {
18827
+ const dependency = event && event.detail ? event.detail.app : ""
18828
+ if (!dependency) return
18829
+ event.preventDefault()
18830
+ if (!candidateState && !loadingCandidates) {
18831
+ await loadCandidates()
18832
+ }
18833
+ setOpen(true)
18834
+ pendingDependencyId = ""
18835
+ dependencyScriptPickerOpen = dependency
18836
+ const dependencyApp = dependencyById(dependency)
18837
+ dependencyScriptSelections[dependency] = dependencyApp && dependencyApp.autolaunch ? dependencyApp.autolaunch : ""
18838
+ renderDependencies()
18839
+ loadDependencyScriptCandidates(dependency)
18840
+ })
18841
+ ;(async () => {
18842
+ const params = new URLSearchParams(window.location.search || "")
18843
+ if (params.get("autolaunch") !== "1") return
18844
+ setOpen(true)
18845
+ if (!candidateState && !loadingCandidates) {
18846
+ await loadCandidates()
18847
+ }
18848
+ })()
18849
+ document.addEventListener("keydown", (event) => {
18746
18850
  if (event.key === "Escape" && !modal.classList.contains("hidden")) {
18747
18851
  setOpen(false)
18748
18852
  }