pinokiod 7.3.15 → 7.4.1

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 (52) 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/bin/ffmpeg.js +9 -791
  5. package/kernel/environment.js +89 -1
  6. package/kernel/git.js +9 -19
  7. package/kernel/index.js +142 -43
  8. package/kernel/launch_requirements.js +1115 -0
  9. package/kernel/procs.js +1 -1
  10. package/kernel/ready.js +231 -0
  11. package/kernel/script.js +16 -0
  12. package/kernel/shells.js +9 -1
  13. package/kernel/workspace_status.js +111 -45
  14. package/package.json +2 -3
  15. package/server/autolaunch.js +725 -0
  16. package/server/index.js +244 -411
  17. package/server/views/app.ejs +267 -160
  18. package/server/views/autolaunch.ejs +363 -75
  19. package/server/views/index.ejs +550 -26
  20. package/server/views/partials/app_autolaunch_dependency_events.ejs +99 -0
  21. package/server/views/partials/app_autolaunch_dependency_save.ejs +10 -0
  22. package/server/views/partials/app_autolaunch_dependency_styles.ejs +357 -0
  23. package/server/views/partials/app_autolaunch_modal_helpers.ejs +347 -0
  24. package/server/views/partials/autolaunch_dependency_helpers.ejs +204 -0
  25. package/server/views/partials/autolaunch_dependency_save.ejs +13 -0
  26. package/server/views/partials/autolaunch_dependency_styles.ejs +347 -0
  27. package/server/views/partials/home_action_modal.ejs +4 -1
  28. package/server/views/partials/launch_requirements_status_client.ejs +271 -0
  29. package/server/views/partials/launch_requirements_status_styles.ejs +171 -0
  30. package/server/views/partials/launch_settings_dependency_save_factory.ejs +45 -0
  31. package/server/views/partials/launch_settings_dependency_script_loader_factory.ejs +25 -0
  32. package/server/views/terminal.ejs +196 -2
  33. package/test/home-autolaunch-live-ui.test.js +455 -0
  34. package/test/launch-requirements-browser.test.js +579 -0
  35. package/test/launch-requirements-contract-coverage.test.js +627 -0
  36. package/test/launch-requirements-real-browser.js +625 -0
  37. package/test/launch-requirements-status-client.test.js +132 -0
  38. package/test/launch-requirements.test.js +1806 -0
  39. package/test/launch-settings-ui.test.js +370 -0
  40. package/test/procs.test.js +49 -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/script/verify-ffmpeg.js +0 -459
  52. package/spec/INSTRUCTION_SYNC.md +0 -432
@@ -19,6 +19,7 @@
19
19
  <% if (agent === "electron") { %>
20
20
  <link href="/electron.css" rel="stylesheet"/>
21
21
  <% } %>
22
+ <% const appMobileBreakpointPx = 980 %>
22
23
  <style>
23
24
  :root {
24
25
  --assist-surface-light: var(--pinokio-chrome-accent-bg-light);
@@ -603,6 +604,7 @@ body.dark .browserview-network-status {
603
604
  color: rgba(248, 250, 252, 0.9);
604
605
  background: var(--app-page-canvas-bg, #1b1c1d);
605
606
  }
607
+ <%- include('./partials/launch_requirements_status_styles') %>
606
608
  .browserview-network-status-inner {
607
609
  display: flex;
608
610
  flex-direction: column;
@@ -3685,6 +3687,11 @@ body.dark .disk-usage {
3685
3687
  text-overflow: ellipsis;
3686
3688
  color: var(--pinokio-sidebar-tab-muted);
3687
3689
  }
3690
+ .app-autolaunch-label > span {
3691
+ min-width: 0;
3692
+ overflow: hidden;
3693
+ text-overflow: ellipsis;
3694
+ }
3688
3695
  .app-autolaunch-label i {
3689
3696
  width: 16px;
3690
3697
  flex: 0 0 16px;
@@ -3702,6 +3709,7 @@ body.dark .disk-usage {
3702
3709
  font-size: 11px;
3703
3710
  font-weight: 700;
3704
3711
  letter-spacing: 0.02em;
3712
+ white-space: nowrap;
3705
3713
  }
3706
3714
  .app-autolaunch-row[data-enabled="true"] .app-autolaunch-status {
3707
3715
  color: rgba(22, 101, 52, 0.95);
@@ -3793,6 +3801,21 @@ body.dark .app-autolaunch-empty {
3793
3801
  gap: 8px;
3794
3802
  flex: 0 0 auto;
3795
3803
  }
3804
+ .app-autolaunch-switch-group {
3805
+ display: inline-flex;
3806
+ align-items: center;
3807
+ gap: 7px;
3808
+ }
3809
+ .app-autolaunch-switch-caption {
3810
+ color: rgba(100, 116, 139, 0.95);
3811
+ font-size: 11px;
3812
+ font-weight: 700;
3813
+ line-height: 1;
3814
+ white-space: nowrap;
3815
+ }
3816
+ body.dark .app-autolaunch-switch-caption {
3817
+ color: rgba(148, 163, 184, 0.95);
3818
+ }
3796
3819
  .app-autolaunch-close {
3797
3820
  display: inline-flex;
3798
3821
  align-items: center;
@@ -3914,10 +3937,42 @@ body.dark .app-autolaunch-section-label {
3914
3937
  .app-autolaunch-script-option > div {
3915
3938
  min-width: 0;
3916
3939
  }
3940
+ .app-autolaunch-clear-script {
3941
+ min-height: 32px;
3942
+ border: 1px solid rgba(15, 23, 42, 0.08);
3943
+ border-radius: 6px;
3944
+ background: transparent;
3945
+ color: rgba(100, 116, 139, 0.95);
3946
+ display: inline-flex;
3947
+ align-items: center;
3948
+ justify-content: center;
3949
+ gap: 6px;
3950
+ padding: 0 9px;
3951
+ font: inherit;
3952
+ font-size: 12px;
3953
+ font-weight: 700;
3954
+ cursor: pointer;
3955
+ }
3956
+ .app-autolaunch-clear-script:hover {
3957
+ background: rgba(148, 163, 184, 0.16);
3958
+ color: rgba(15, 23, 42, 0.9);
3959
+ }
3960
+ .app-autolaunch-clear-script:disabled {
3961
+ cursor: not-allowed;
3962
+ opacity: 0.55;
3963
+ }
3917
3964
  body.dark .app-autolaunch-script-option {
3918
3965
  border-color: rgba(148, 163, 184, 0.16);
3919
3966
  background: rgba(15, 23, 42, 0.72);
3920
3967
  }
3968
+ body.dark .app-autolaunch-clear-script {
3969
+ border-color: rgba(148, 163, 184, 0.16);
3970
+ color: rgba(203, 213, 225, 0.9);
3971
+ }
3972
+ body.dark .app-autolaunch-clear-script:hover {
3973
+ background: rgba(148, 163, 184, 0.16);
3974
+ color: rgba(226, 232, 240, 0.92);
3975
+ }
3921
3976
  body.dark .app-autolaunch-script-option:hover,
3922
3977
  body.dark .app-autolaunch-script-option.selected {
3923
3978
  border-color: rgba(96, 165, 250, 0.42);
@@ -3951,6 +4006,7 @@ body.dark .app-autolaunch-tag {
3951
4006
  background: rgba(148, 163, 184, 0.18);
3952
4007
  color: rgba(203, 213, 225, 0.9);
3953
4008
  }
4009
+ <%- include('partials/app_autolaunch_dependency_styles') %>
3954
4010
  .app-autolaunch-footer {
3955
4011
  display: flex;
3956
4012
  align-items: center;
@@ -6114,7 +6170,7 @@ body.dark .pinokio-fork-dropdown-remote, body.dark .pinokio-publish-dropdown-rem
6114
6170
  }
6115
6171
  }
6116
6172
  */
6117
- @media only screen and (max-width: 768px) {
6173
+ @media only screen and (max-width: <%= appMobileBreakpointPx %>px) {
6118
6174
  body {
6119
6175
  display: flex !important;
6120
6176
  flex-direction: row !important;
@@ -6355,7 +6411,7 @@ header.navheader h1 {
6355
6411
  margin: 0 10px;
6356
6412
  }
6357
6413
  }
6358
- @media only screen and (max-width: 768px) {
6414
+ @media only screen and (max-width: <%= appMobileBreakpointPx %>px) {
6359
6415
  :root {
6360
6416
  --mobile-bottom-bar-height: calc(46px + env(safe-area-inset-bottom));
6361
6417
  }
@@ -7884,13 +7940,16 @@ body.dark .pinokio-custom-terminal-header {
7884
7940
  <div class="app-autolaunch-modal-head">
7885
7941
  <div>
7886
7942
  <div class="app-autolaunch-title">Autolaunch</div>
7887
- <div class="app-autolaunch-note">Run this app when Pinokio starts.</div>
7943
+ <div class="app-autolaunch-note">Choose launch script, startup behavior, and requirements.</div>
7888
7944
  </div>
7889
7945
  <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>
7946
+ <div class="app-autolaunch-switch-group">
7947
+ <span class="app-autolaunch-switch-caption">Start with Pinokio</span>
7948
+ <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' %>">
7949
+ <span class="app-autolaunch-switch-label" data-app-autolaunch-switch-label><%= autolaunch_app.autolaunch_enabled ? 'ON' : 'OFF' %></span>
7950
+ <span class="app-autolaunch-switch-thumb" aria-hidden="true"></span>
7951
+ </button>
7952
+ </div>
7894
7953
  <button type="button" class="app-autolaunch-close" data-app-autolaunch-close aria-label="Close">
7895
7954
  <i class="fa-solid fa-xmark" aria-hidden="true"></i>
7896
7955
  </button>
@@ -7902,6 +7961,12 @@ body.dark .pinokio-custom-terminal-header {
7902
7961
  <div class="app-autolaunch-empty">Loading scripts...</div>
7903
7962
  </div>
7904
7963
  </div>
7964
+ <div class="app-autolaunch-modal-section">
7965
+ <div class="app-autolaunch-section-label">Requires</div>
7966
+ <div class="app-autolaunch-dependencies" data-app-autolaunch-dependencies>
7967
+ <div class="app-autolaunch-empty">Loading dependencies...</div>
7968
+ </div>
7969
+ </div>
7905
7970
  <div class="app-autolaunch-footer">
7906
7971
  <div class="app-autolaunch-feedback" data-app-autolaunch-feedback></div>
7907
7972
  <a class="app-autolaunch-link" href="/autolaunch">Manage all</a>
@@ -7983,6 +8048,7 @@ body.dark .pinokio-custom-terminal-header {
7983
8048
  <iframe class='selected' src="<%=editor_tab%>" allow="fullscreen *;" allowfullscreen></iframe>
7984
8049
  <% } %>
7985
8050
  <div id="browserview-network-status" class="browserview-network-status" hidden aria-live="polite"></div>
8051
+ <div id="launch-requirements-status" class="launch-requirements-status" data-launch-requirements-status hidden aria-live="polite"></div>
7986
8052
  <%- include('./partials/browser_popout_surface') %>
7987
8053
  </main>
7988
8054
  <% if (type === 'run') { %>
@@ -8295,6 +8361,11 @@ body.dark .pinokio-custom-terminal-header {
8295
8361
  let pendingSelectionRetry = null
8296
8362
  let pendingHomeSelectionRetry = null
8297
8363
  let initialWorkspaceDiskUsageRequested = false
8364
+ const openWithoutLaunching = <%- JSON.stringify(type === "run" && !autoselect) %>
8365
+ const appMobileBreakpoint = <%= appMobileBreakpointPx %>
8366
+ const appMobileBreakpointQuery = `(max-width: ${appMobileBreakpoint}px)`
8367
+ window.PinokioAppMobileBreakpoint = appMobileBreakpoint
8368
+ window.PinokioAppMobileBreakpointQuery = appMobileBreakpointQuery
8298
8369
  const scheduleSelectionRetry = (delay = 100) => {
8299
8370
  if (pendingSelectionRetry !== null) {
8300
8371
  return
@@ -10004,7 +10075,9 @@ const rerenderMenuSection = (container, html) => {
10004
10075
  return ""
10005
10076
  }
10006
10077
  })()
10007
- let persistedSelectionRaw = selectionKey && storage ? storage.getItem(selectionKey) : null
10078
+ const triggeredByUser = Boolean(eventParam || explicitTarget)
10079
+ const automaticSelectionDisabled = openWithoutLaunching && !triggeredByUser
10080
+ let persistedSelectionRaw = automaticSelectionDisabled ? null : (selectionKey && storage ? storage.getItem(selectionKey) : null)
10008
10081
  const devRouteActive = /\/dev(?:$|\/)/.test(currentPath || "")
10009
10082
  let persistedSelectionPayload = null
10010
10083
  if (persistedSelectionRaw) {
@@ -10029,9 +10102,8 @@ const rerenderMenuSection = (container, html) => {
10029
10102
  persistedSelectionRaw = null
10030
10103
  }
10031
10104
  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]')))
10105
+ const followCurrentDefault = !automaticSelectionDisabled && forceDefaultSelection && !triggeredByUser
10106
+ const skipPersistedSelection = automaticSelectionDisabled || followCurrentDefault || (ignorePersistedSelection && Boolean(document.querySelector('[data-default]')))
10035
10107
  let hasPersistedSelection = skipPersistedSelection ? false : originalHasPersistedSelection
10036
10108
  if (skipPersistedSelection) {
10037
10109
  persistedSelectionRaw = null
@@ -10053,7 +10125,7 @@ const rerenderMenuSection = (container, html) => {
10053
10125
  target = eventParam.target?.closest(".frame-link") || null
10054
10126
  }
10055
10127
 
10056
- if (!target && global_selector) {
10128
+ if (!target && !automaticSelectionDisabled && global_selector) {
10057
10129
  const candidate = document.querySelector(global_selector)
10058
10130
  if (candidate) {
10059
10131
  target = candidate
@@ -10065,11 +10137,11 @@ const rerenderMenuSection = (container, html) => {
10065
10137
  }
10066
10138
  }
10067
10139
 
10068
- if (!target && !skipPersistedSelection) {
10140
+ if (!target && !automaticSelectionDisabled && !skipPersistedSelection) {
10069
10141
  preselected = document.querySelector('#devtab.frame-link.selected') || document.querySelector('.frame-link.selected')
10070
10142
  }
10071
10143
 
10072
- if (!target && persistedSelectionPayload) {
10144
+ if (!target && !automaticSelectionDisabled && persistedSelectionPayload) {
10073
10145
  target = restorePersistedFrameLink(persistedSelectionPayload)
10074
10146
  if (!target && originalHasPersistedSelection) {
10075
10147
  scheduleSelectionRetry()
@@ -10077,7 +10149,7 @@ const rerenderMenuSection = (container, html) => {
10077
10149
  }
10078
10150
  }
10079
10151
 
10080
- if (!target && !skipPersistedSelection) {
10152
+ if (!target && !automaticSelectionDisabled && !skipPersistedSelection) {
10081
10153
  const urlKey = selectionUrlStorageKey()
10082
10154
  const storedUrl = urlKey && storage ? storage.getItem(urlKey) : null
10083
10155
  if (storedUrl) {
@@ -10085,7 +10157,7 @@ const rerenderMenuSection = (container, html) => {
10085
10157
  }
10086
10158
  }
10087
10159
 
10088
- if (!target && followCurrentDefault) {
10160
+ if (!target && !automaticSelectionDisabled && followCurrentDefault) {
10089
10161
  const defaultSelection = getDefaultSelection()
10090
10162
  if (defaultSelection) {
10091
10163
  target = defaultSelection
@@ -10093,11 +10165,11 @@ const rerenderMenuSection = (container, html) => {
10093
10165
  }
10094
10166
 
10095
10167
  const devTab = document.querySelector('#devtab.frame-link')
10096
- if (!target && preselected && preselected !== devTab) {
10168
+ if (!target && !automaticSelectionDisabled && preselected && preselected !== devTab) {
10097
10169
  target = preselected
10098
10170
  }
10099
10171
 
10100
- if (!triggeredByUser && !resolvedByGlobalSelector && !target && devRouteActive && devTab) {
10172
+ if (!automaticSelectionDisabled && !triggeredByUser && !resolvedByGlobalSelector && !target && devRouteActive && devTab) {
10101
10173
  const defaultCandidate = getDefaultSelection()
10102
10174
  if (pluginLaunchActive && defaultCandidate) {
10103
10175
  target = defaultCandidate
@@ -10106,7 +10178,7 @@ const rerenderMenuSection = (container, html) => {
10106
10178
  }
10107
10179
  }
10108
10180
 
10109
- if (!target && preselected) {
10181
+ if (!target && !automaticSelectionDisabled && preselected) {
10110
10182
  target = preselected
10111
10183
  }
10112
10184
 
@@ -10141,7 +10213,7 @@ const rerenderMenuSection = (container, html) => {
10141
10213
  }
10142
10214
  <% } %>
10143
10215
 
10144
- if (!target && preselected) {
10216
+ if (!target && !automaticSelectionDisabled && preselected) {
10145
10217
  target = preselected
10146
10218
  }
10147
10219
 
@@ -10278,7 +10350,6 @@ const rerenderMenuSection = (container, html) => {
10278
10350
  eventParam.preventDefault()
10279
10351
  eventParam.stopPropagation()
10280
10352
  }
10281
-
10282
10353
  // save target.href
10283
10354
  <% if (type !== "run") { %>
10284
10355
  let _url = new URL(target.href)
@@ -10372,7 +10443,7 @@ const rerenderMenuSection = (container, html) => {
10372
10443
  writeFrameInjectDescriptors(targetFrame, targetInjectDescriptors)
10373
10444
  publishBrowserviewInjectTargets([], { sync: true })
10374
10445
  targetFrame.classList.remove("hidden")
10375
- let mode = target.getAttribute("data-mode")
10446
+ let mode = target.getAttribute("data-mode")
10376
10447
  if (mode === "refresh") {
10377
10448
  if (targetFrame.src !== target.href) {
10378
10449
  targetFrame.src = target.href
@@ -11031,14 +11102,12 @@ const rerenderMenuSection = (container, html) => {
11031
11102
  }
11032
11103
 
11033
11104
 
11034
-
11035
-
11036
- const mobileMenuQuery = window.matchMedia ? window.matchMedia("(max-width: 768px)") : null
11105
+ const mobileMenuQuery = window.matchMedia ? window.matchMedia(appMobileBreakpointQuery) : null
11037
11106
  const isMobileMenuOpen = () => document.body.classList.contains("mobile-menu-open")
11038
11107
  const appcanvas = document.querySelector(".appcanvas")
11039
11108
  const closeWindowButton = document.querySelector("#close-window")
11040
11109
  const mobileMenuBackdrop = document.querySelector("[data-mobile-menu-backdrop]")
11041
- const isMobilePanelLayout = () => mobileMenuQuery ? mobileMenuQuery.matches : window.innerWidth <= 768
11110
+ const isMobilePanelLayout = () => mobileMenuQuery ? mobileMenuQuery.matches : window.innerWidth <= appMobileBreakpoint
11042
11111
  const normalizeMobilePanelName = (value) => {
11043
11112
  return value === "logs" || value === "ask-ai" || value === "feed" ? value : ""
11044
11113
  }
@@ -11778,7 +11847,7 @@ const rerenderMenuSection = (container, html) => {
11778
11847
  if (appcanvas && sidebarResizer && sidebarContainer) {
11779
11848
  const SIDEBAR_MIN_WIDTH = 140
11780
11849
  const SIDEBAR_MAX_WIDTH = 560
11781
- const sidebarMobileQuery = window.matchMedia ? window.matchMedia("(max-width: 768px)") : null
11850
+ const sidebarMobileQuery = window.matchMedia ? window.matchMedia(appMobileBreakpointQuery) : null
11782
11851
  const workspaceName = "<%= typeof name === 'string' ? name : '' %>"
11783
11852
  const sidebarWidthStorageKey = workspaceName
11784
11853
  ? `pinokio-app-sidebar-width:${workspaceName}`
@@ -17796,7 +17865,7 @@ document.addEventListener("DOMContentLoaded", () => {
17796
17865
  enabled = false
17797
17866
  }
17798
17867
 
17799
- const communityModeQuery = window.matchMedia ? window.matchMedia("(max-width: 768px)") : null
17868
+ const communityModeQuery = window.matchMedia ? window.matchMedia(window.PinokioAppMobileBreakpointQuery || "(max-width: <%= appMobileBreakpointPx %>px)") : null
17800
17869
  let communityMode = false
17801
17870
 
17802
17871
  const setCommunityMode = (next, opts) => {
@@ -18480,6 +18549,8 @@ document.addEventListener("DOMContentLoaded", () => {
18480
18549
  }
18481
18550
  })()
18482
18551
 
18552
+ <%- include('partials/launch_requirements_status_client') %>
18553
+
18483
18554
  ;(function() {
18484
18555
  const root = document.querySelector("[data-app-autolaunch]")
18485
18556
  const initialState = <%- JSON.stringify((typeof autolaunch_app !== "undefined" && autolaunch_app) ? autolaunch_app : null).replace(/</g, "\\u003c") %>
@@ -18493,8 +18564,9 @@ document.addEventListener("DOMContentLoaded", () => {
18493
18564
  const switchLabel = root.querySelector("[data-app-autolaunch-switch-label]")
18494
18565
  const closeButton = root.querySelector("[data-app-autolaunch-close]")
18495
18566
  const scriptsEl = root.querySelector("[data-app-autolaunch-scripts]")
18567
+ const dependenciesEl = root.querySelector("[data-app-autolaunch-dependencies]")
18496
18568
  const feedbackEl = root.querySelector("[data-app-autolaunch-feedback]")
18497
- if (!appId || !button || !status || !modal || !switchButton || !switchLabel || !closeButton || !scriptsEl || !feedbackEl) return
18569
+ if (!appId || !button || !status || !modal || !switchButton || !switchLabel || !closeButton || !scriptsEl || !dependenciesEl || !feedbackEl) return
18498
18570
 
18499
18571
  let state = Object.assign({}, initialState)
18500
18572
  let candidateState = null
@@ -18502,6 +18574,13 @@ document.addEventListener("DOMContentLoaded", () => {
18502
18574
  let selectedScript = state.autolaunch || ""
18503
18575
  let loadingCandidates = false
18504
18576
  let saving = false
18577
+ let savingDependencies = false
18578
+ let savingDependencyScript = false
18579
+ let dependencyPickerOpen = false
18580
+ let dependencyScriptPickerOpen = ""
18581
+ let pendingDependencyId = ""
18582
+ let dependencyScriptCandidates = {}
18583
+ let dependencyScriptSelections = {}
18505
18584
 
18506
18585
  const escapeHtml = (value) => {
18507
18586
  if (value === null || value === undefined) return ""
@@ -18520,124 +18599,17 @@ document.addEventListener("DOMContentLoaded", () => {
18520
18599
  if (!candidateState) return []
18521
18600
  return [].concat(candidateState.menu || [], candidateState.other || [])
18522
18601
  }
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()
18602
+ <%- include('partials/app_autolaunch_modal_helpers') %>
18603
+ const hasLaunchRequirementConfig = () => {
18604
+ return !!(
18605
+ state.autolaunch &&
18606
+ Array.isArray(state.autolaunch_depends) &&
18607
+ state.autolaunch_depends.length > 0
18608
+ )
18609
+ }
18610
+ const notifyLaunchRequirementsConfigured = () => {
18611
+ if (!hasLaunchRequirementConfig()) return
18612
+ window.dispatchEvent(new CustomEvent("pinokio:launch-requirements-configured"))
18641
18613
  }
18642
18614
  const loadCandidates = async () => {
18643
18615
  loadingCandidates = true
@@ -18654,16 +18626,84 @@ document.addEventListener("DOMContentLoaded", () => {
18654
18626
  }
18655
18627
  candidateState = data
18656
18628
  syncState(data.app)
18657
- selectedScript = chooseInitialScript(data)
18629
+ selectedScript = configuredScript(data)
18658
18630
  setFeedback("")
18659
18631
  } catch (error) {
18660
18632
  candidateError = error && error.message ? error.message : "Failed to load scripts."
18661
18633
  } finally {
18662
18634
  loadingCandidates = false
18663
18635
  renderScripts()
18636
+ renderDependencies()
18664
18637
  }
18665
18638
  }
18666
- const saveScript = async (script) => {
18639
+ <%- include('partials/app_autolaunch_dependency_save') %>
18640
+ const updateDependencyCandidateState = (updatedApp) => {
18641
+ if (!updatedApp || !updatedApp.id || !candidateState || !Array.isArray(candidateState.dependency_apps)) return
18642
+ candidateState.dependency_apps = candidateState.dependency_apps.map((app) => app.id === updatedApp.id ? updatedApp : app)
18643
+ }
18644
+ <%- include('partials/launch_settings_dependency_script_loader_factory') %>
18645
+ const loadDependencyScriptCandidates = createLaunchSettingsDependencyScriptLoader({
18646
+ getCache: () => dependencyScriptCandidates,
18647
+ render: renderDependencies
18648
+ })
18649
+ const saveDependencyScript = async (dependencyId, script) => {
18650
+ const dependency = dependencyById(dependencyId)
18651
+ if (!dependency || !script || savingDependencyScript) return
18652
+ savingDependencyScript = true
18653
+ setBusy(true)
18654
+ setFeedback("Saving requirement script...")
18655
+ try {
18656
+ const response = await fetch("/autolaunch", {
18657
+ method: "POST",
18658
+ headers: {
18659
+ "Content-Type": "application/json",
18660
+ "Accept": "application/json"
18661
+ },
18662
+ body: JSON.stringify({
18663
+ app: dependencyId,
18664
+ script,
18665
+ enabled: !!dependency.autolaunch_enabled
18666
+ })
18667
+ })
18668
+ const data = await response.json()
18669
+ if (!response.ok || !data.ok) {
18670
+ throw new Error(data.error || "Failed to save launch script.")
18671
+ }
18672
+ updateDependencyCandidateState(data.app)
18673
+ if (pendingDependencyId === dependencyId) {
18674
+ const dependencies = Array.isArray(state.autolaunch_depends) ? state.autolaunch_depends.slice() : []
18675
+ if (!dependencies.includes(dependencyId)) {
18676
+ const savedDependency = await saveDependencies(dependencies.concat(dependencyId), "Requirement saved.")
18677
+ if (savedDependency) {
18678
+ notifyLaunchRequirementsConfigured()
18679
+ pendingDependencyId = ""
18680
+ delete dependencyScriptSelections[dependencyId]
18681
+ dependencyScriptPickerOpen = ""
18682
+ renderDependencies()
18683
+ }
18684
+ } else {
18685
+ pendingDependencyId = ""
18686
+ delete dependencyScriptSelections[dependencyId]
18687
+ dependencyScriptPickerOpen = ""
18688
+ renderDependencies()
18689
+ notifyLaunchRequirementsConfigured()
18690
+ setFeedback("Requirement saved.")
18691
+ }
18692
+ } else {
18693
+ dependencyScriptPickerOpen = ""
18694
+ delete dependencyScriptSelections[dependencyId]
18695
+ renderDependencies()
18696
+ setFeedback("Requirement script saved.")
18697
+ }
18698
+ } catch (error) {
18699
+ setFeedback(error && error.message ? error.message : "Failed to save launch script.", true)
18700
+ renderDependencies()
18701
+ } finally {
18702
+ savingDependencyScript = false
18703
+ setBusy(false)
18704
+ }
18705
+ }
18706
+ const saveScript = async (script, enabled = !!state.autolaunch_enabled) => {
18667
18707
  if (saving) return
18668
18708
  setBusy(true)
18669
18709
  setFeedback(script ? "Saving..." : "Disabling...")
@@ -18674,17 +18714,52 @@ document.addEventListener("DOMContentLoaded", () => {
18674
18714
  "Content-Type": "application/json",
18675
18715
  "Accept": "application/json"
18676
18716
  },
18677
- body: JSON.stringify({ app: appId, script: script || "" })
18717
+ body: JSON.stringify({ app: appId, script: script || "", enabled: !!enabled })
18718
+ })
18719
+ const data = await response.json()
18720
+ if (!response.ok || !data.ok) {
18721
+ throw new Error(data.error || "Failed to save Autolaunch settings.")
18722
+ }
18723
+ syncState(data.app)
18724
+ notifyLaunchRequirementsConfigured()
18725
+ setFeedback(data.app && data.app.autolaunch_enabled ? "Saved." : (script ? "Launch script saved." : "Disabled."))
18726
+ renderScripts()
18727
+ renderDependencies()
18728
+ } catch (error) {
18729
+ setFeedback(error && error.message ? error.message : "Failed to save Autolaunch settings.", true)
18730
+ renderStatus()
18731
+ } finally {
18732
+ setBusy(false)
18733
+ }
18734
+ }
18735
+ const clearScript = async () => {
18736
+ if (saving) return
18737
+ if (Array.isArray(state.autolaunch_depends) && state.autolaunch_depends.length > 0) {
18738
+ setFeedback("Remove requirements before clearing this app's launch script.", true)
18739
+ return
18740
+ }
18741
+ setBusy(true)
18742
+ setFeedback("Clearing launch script...")
18743
+ try {
18744
+ const response = await fetch("/autolaunch", {
18745
+ method: "POST",
18746
+ headers: {
18747
+ "Content-Type": "application/json",
18748
+ "Accept": "application/json"
18749
+ },
18750
+ body: JSON.stringify({ app: appId, clear_script: true })
18678
18751
  })
18679
18752
  const data = await response.json()
18680
18753
  if (!response.ok || !data.ok) {
18681
- throw new Error(data.error || "Failed to save autolaunch.")
18754
+ throw new Error(data.error || "Failed to clear launch script.")
18682
18755
  }
18683
18756
  syncState(data.app)
18684
- setFeedback(data.app && data.app.autolaunch_enabled ? "Saved." : "Disabled.")
18757
+ notifyLaunchRequirementsConfigured()
18758
+ setFeedback("Launch script cleared.")
18685
18759
  renderScripts()
18760
+ renderDependencies()
18686
18761
  } catch (error) {
18687
- setFeedback(error && error.message ? error.message : "Failed to save autolaunch.", true)
18762
+ setFeedback(error && error.message ? error.message : "Failed to clear launch script.", true)
18688
18763
  renderStatus()
18689
18764
  } finally {
18690
18765
  setBusy(false)
@@ -18693,23 +18768,25 @@ document.addEventListener("DOMContentLoaded", () => {
18693
18768
  const toggleAutolaunch = async () => {
18694
18769
  if (saving || loadingCandidates) return
18695
18770
  if (state.autolaunch_enabled) {
18696
- await saveScript("")
18771
+ const script = selectedScript || state.autolaunch || ""
18772
+ await saveScript(script, false)
18697
18773
  return
18698
18774
  }
18699
18775
  if (!candidateState) {
18700
18776
  await loadCandidates()
18701
18777
  }
18702
- const script = selectedScript || chooseInitialScript(candidateState)
18778
+ const script = selectedScript || ""
18703
18779
  if (!script) {
18704
- setFeedback("Choose a script first.", true)
18780
+ setFeedback("Choose a launch script first.", true)
18705
18781
  return
18706
18782
  }
18707
18783
  selectedScript = script
18708
- await saveScript(script)
18784
+ await saveScript(script, true)
18709
18785
  }
18710
18786
 
18711
18787
  renderStatus()
18712
18788
  renderScripts()
18789
+ renderDependencies()
18713
18790
 
18714
18791
  button.addEventListener("click", (event) => {
18715
18792
  event.preventDefault()
@@ -18738,11 +18815,41 @@ document.addEventListener("DOMContentLoaded", () => {
18738
18815
  if (!input) return
18739
18816
  selectedScript = input.value
18740
18817
  renderScripts()
18741
- if (state.autolaunch_enabled && selectedScript !== state.autolaunch) {
18742
- saveScript(selectedScript)
18818
+ if (selectedScript !== state.autolaunch) {
18819
+ saveScript(selectedScript, !!state.autolaunch_enabled)
18743
18820
  }
18744
18821
  })
18745
- document.addEventListener("keydown", (event) => {
18822
+ scriptsEl.addEventListener("click", (event) => {
18823
+ const clear = event.target && event.target.closest("[data-app-autolaunch-clear-script]")
18824
+ if (!clear) return
18825
+ event.preventDefault()
18826
+ clearScript()
18827
+ })
18828
+ <%- include('partials/app_autolaunch_dependency_events') %>
18829
+ window.addEventListener("pinokio:launch-requirements-choose-script", async (event) => {
18830
+ const dependency = event && event.detail ? event.detail.app : ""
18831
+ if (!dependency) return
18832
+ event.preventDefault()
18833
+ if (!candidateState && !loadingCandidates) {
18834
+ await loadCandidates()
18835
+ }
18836
+ setOpen(true)
18837
+ pendingDependencyId = ""
18838
+ dependencyScriptPickerOpen = dependency
18839
+ const dependencyApp = dependencyById(dependency)
18840
+ dependencyScriptSelections[dependency] = dependencyApp && dependencyApp.autolaunch ? dependencyApp.autolaunch : ""
18841
+ renderDependencies()
18842
+ loadDependencyScriptCandidates(dependency)
18843
+ })
18844
+ ;(async () => {
18845
+ const params = new URLSearchParams(window.location.search || "")
18846
+ if (params.get("autolaunch") !== "1") return
18847
+ setOpen(true)
18848
+ if (!candidateState && !loadingCandidates) {
18849
+ await loadCandidates()
18850
+ }
18851
+ })()
18852
+ document.addEventListener("keydown", (event) => {
18746
18853
  if (event.key === "Escape" && !modal.classList.contains("hidden")) {
18747
18854
  setOpen(false)
18748
18855
  }