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.
- package/kernel/api/index.js +111 -15
- package/kernel/api/script/index.js +10 -0
- package/kernel/autolaunch.js +111 -0
- package/kernel/environment.js +89 -1
- package/kernel/git.js +9 -19
- package/kernel/index.js +142 -43
- package/kernel/launch_requirements.js +1115 -0
- package/kernel/ready.js +231 -0
- package/kernel/script.js +16 -0
- package/kernel/shells.js +9 -1
- package/kernel/workspace_status.js +111 -45
- package/package.json +1 -1
- package/server/autolaunch.js +725 -0
- package/server/index.js +244 -411
- package/server/public/logs.js +15 -1
- package/server/public/style.css +99 -31
- package/server/views/app.ejs +263 -159
- package/server/views/autolaunch.ejs +363 -75
- package/server/views/index.ejs +550 -26
- package/server/views/logs.ejs +14 -12
- package/server/views/partials/app_autolaunch_dependency_events.ejs +99 -0
- package/server/views/partials/app_autolaunch_dependency_save.ejs +10 -0
- package/server/views/partials/app_autolaunch_dependency_styles.ejs +357 -0
- package/server/views/partials/app_autolaunch_modal_helpers.ejs +347 -0
- package/server/views/partials/autolaunch_dependency_helpers.ejs +204 -0
- package/server/views/partials/autolaunch_dependency_save.ejs +13 -0
- package/server/views/partials/autolaunch_dependency_styles.ejs +347 -0
- package/server/views/partials/home_action_modal.ejs +4 -1
- package/server/views/partials/launch_requirements_status_client.ejs +271 -0
- package/server/views/partials/launch_requirements_status_styles.ejs +171 -0
- package/server/views/partials/launch_settings_dependency_save_factory.ejs +45 -0
- package/server/views/partials/launch_settings_dependency_script_loader_factory.ejs +25 -0
- package/server/views/terminal.ejs +196 -2
- package/test/home-autolaunch-live-ui.test.js +455 -0
- package/test/launch-requirements-browser.test.js +579 -0
- package/test/launch-requirements-contract-coverage.test.js +627 -0
- package/test/launch-requirements-real-browser.js +625 -0
- package/test/launch-requirements-status-client.test.js +132 -0
- package/test/launch-requirements.test.js +1806 -0
- package/test/launch-settings-ui.test.js +370 -0
- package/test/ready-state.test.js +49 -0
- package/test/server-autolaunch.test.js +1052 -0
- package/test/startup-git-index-benchmark.js +409 -0
- package/test/startup-git-index-browser.js +320 -0
- package/test/startup-git-index-performance.test.js +380 -0
- package/test/startup-git-index-refactor.test.js +450 -0
- package/test/startup-git-index-route.test.js +588 -0
- package/test/universal-launcher.smoke.spec.js +10 -9
- package/test/workspace-gitignore-benchmark.js +815 -0
- package/test/workspace-gitignore-path-scoped.test.js +256 -0
- package/spec/INSTRUCTION_SYNC.md +0 -432
|
@@ -140,6 +140,12 @@ body.autolaunch-page .autolaunch-app-row {
|
|
|
140
140
|
cursor: pointer;
|
|
141
141
|
transition: background 140ms ease, box-shadow 140ms ease;
|
|
142
142
|
}
|
|
143
|
+
body.autolaunch-page .autolaunch-app-row:focus {
|
|
144
|
+
outline: none;
|
|
145
|
+
}
|
|
146
|
+
body.autolaunch-page .autolaunch-app-row:focus-visible {
|
|
147
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--task-accent) 48%, transparent);
|
|
148
|
+
}
|
|
143
149
|
body.autolaunch-page .autolaunch-app-row:last-child {
|
|
144
150
|
border-bottom: 0;
|
|
145
151
|
}
|
|
@@ -282,6 +288,18 @@ body.autolaunch-page .autolaunch-switch-label {
|
|
|
282
288
|
min-width: 18px;
|
|
283
289
|
text-align: left;
|
|
284
290
|
}
|
|
291
|
+
body.autolaunch-page .autolaunch-startup-control {
|
|
292
|
+
display: inline-flex;
|
|
293
|
+
align-items: center;
|
|
294
|
+
gap: 8px;
|
|
295
|
+
justify-self: end;
|
|
296
|
+
}
|
|
297
|
+
body.autolaunch-page .autolaunch-startup-label {
|
|
298
|
+
color: var(--task-muted);
|
|
299
|
+
font-size: 11px;
|
|
300
|
+
font-weight: 700;
|
|
301
|
+
white-space: nowrap;
|
|
302
|
+
}
|
|
285
303
|
body.autolaunch-page .autolaunch-detail {
|
|
286
304
|
min-height: 480px;
|
|
287
305
|
}
|
|
@@ -382,6 +400,39 @@ body.autolaunch-page .autolaunch-script-title span:first-child {
|
|
|
382
400
|
text-overflow: ellipsis;
|
|
383
401
|
white-space: nowrap;
|
|
384
402
|
}
|
|
403
|
+
body.autolaunch-page .autolaunch-current-row {
|
|
404
|
+
display: flex;
|
|
405
|
+
align-items: center;
|
|
406
|
+
gap: 10px;
|
|
407
|
+
}
|
|
408
|
+
body.autolaunch-page .autolaunch-current-row .autolaunch-current {
|
|
409
|
+
flex: 1;
|
|
410
|
+
min-width: 0;
|
|
411
|
+
}
|
|
412
|
+
body.autolaunch-page .autolaunch-clear-script {
|
|
413
|
+
flex: 0 0 auto;
|
|
414
|
+
min-height: 28px;
|
|
415
|
+
border: 1px solid var(--task-border);
|
|
416
|
+
border-radius: 7px;
|
|
417
|
+
background: transparent;
|
|
418
|
+
color: var(--task-muted);
|
|
419
|
+
display: inline-flex;
|
|
420
|
+
align-items: center;
|
|
421
|
+
gap: 6px;
|
|
422
|
+
padding: 0 9px;
|
|
423
|
+
font: inherit;
|
|
424
|
+
font-size: 12px;
|
|
425
|
+
font-weight: 650;
|
|
426
|
+
cursor: pointer;
|
|
427
|
+
}
|
|
428
|
+
body.autolaunch-page .autolaunch-clear-script:hover {
|
|
429
|
+
background: color-mix(in srgb, var(--task-border) 55%, transparent);
|
|
430
|
+
color: var(--task-text);
|
|
431
|
+
}
|
|
432
|
+
body.autolaunch-page .autolaunch-clear-script:disabled {
|
|
433
|
+
cursor: not-allowed;
|
|
434
|
+
opacity: 0.55;
|
|
435
|
+
}
|
|
385
436
|
body.autolaunch-page .autolaunch-tag {
|
|
386
437
|
border-radius: 999px;
|
|
387
438
|
padding: 2px 6px 3px;
|
|
@@ -391,26 +442,7 @@ body.autolaunch-page .autolaunch-tag {
|
|
|
391
442
|
font-weight: 650;
|
|
392
443
|
line-height: 1;
|
|
393
444
|
}
|
|
394
|
-
|
|
395
|
-
padding: 16px;
|
|
396
|
-
border-top: 1px solid var(--task-border);
|
|
397
|
-
}
|
|
398
|
-
body.autolaunch-page .autolaunch-manual-row {
|
|
399
|
-
display: flex;
|
|
400
|
-
align-items: center;
|
|
401
|
-
gap: 8px;
|
|
402
|
-
}
|
|
403
|
-
body.autolaunch-page .autolaunch-manual input {
|
|
404
|
-
flex: 1 1 auto;
|
|
405
|
-
min-height: 32px;
|
|
406
|
-
padding: 0 10px;
|
|
407
|
-
border: 1px solid var(--task-border-strong);
|
|
408
|
-
border-radius: 7px;
|
|
409
|
-
background: color-mix(in srgb, var(--task-panel) 98%, white);
|
|
410
|
-
}
|
|
411
|
-
body.dark.autolaunch-page .autolaunch-manual input {
|
|
412
|
-
background: color-mix(in srgb, var(--task-panel) 95%, black);
|
|
413
|
-
}
|
|
445
|
+
<%- include('partials/autolaunch_dependency_styles') %>
|
|
414
446
|
body.autolaunch-page .autolaunch-feedback {
|
|
415
447
|
min-height: 16px;
|
|
416
448
|
margin-top: 7px;
|
|
@@ -479,15 +511,15 @@ body.autolaunch-page .autolaunch-loading i {
|
|
|
479
511
|
<section class="task-shell autolaunch-shell">
|
|
480
512
|
<header class="task-shell-header">
|
|
481
513
|
<div class="task-shell-header-main">
|
|
482
|
-
<h1 class="task-title">
|
|
483
|
-
<p class="task-description">Choose
|
|
514
|
+
<h1 class="task-title">Autolaunch</h1>
|
|
515
|
+
<p class="task-description">Choose app launch scripts, startup behavior, and requirements.</p>
|
|
484
516
|
</div>
|
|
485
517
|
<div class="task-header-actions autolaunch-summary">
|
|
486
|
-
<span class="autolaunch-summary-item"><strong data-enabled-count><%= enabledCount %></strong> enabled</span>
|
|
518
|
+
<span class="autolaunch-summary-item"><strong data-enabled-count><%= enabledCount %></strong> startup enabled</span>
|
|
487
519
|
<span class="autolaunch-summary-item"><strong data-app-count><%= apps.length %></strong> apps</span>
|
|
488
520
|
<button type="button" class="task-button danger" data-disable-all <%= enabledCount ? '' : 'disabled' %>>
|
|
489
521
|
<i class="fa-solid fa-power-off"></i>
|
|
490
|
-
Disable
|
|
522
|
+
Disable startup
|
|
491
523
|
</button>
|
|
492
524
|
</div>
|
|
493
525
|
</header>
|
|
@@ -497,7 +529,7 @@ body.autolaunch-page .autolaunch-loading i {
|
|
|
497
529
|
<div class="task-section-head">
|
|
498
530
|
<div>
|
|
499
531
|
<h2 class="task-section-title">Installed apps</h2>
|
|
500
|
-
<p class="task-section-subcopy">Apps with
|
|
532
|
+
<p class="task-section-subcopy">Apps with startup enabled appear first. Requirements are used for startup and manual launches.</p>
|
|
501
533
|
</div>
|
|
502
534
|
</div>
|
|
503
535
|
<div class="autolaunch-toolbar">
|
|
@@ -512,7 +544,7 @@ body.autolaunch-page .autolaunch-loading i {
|
|
|
512
544
|
</aside>
|
|
513
545
|
|
|
514
546
|
<section class="autolaunch-detail" data-detail>
|
|
515
|
-
<div class="autolaunch-empty">Select an app to configure
|
|
547
|
+
<div class="autolaunch-empty">Select an app to configure Autolaunch.</div>
|
|
516
548
|
</section>
|
|
517
549
|
</div>
|
|
518
550
|
</section>
|
|
@@ -528,6 +560,13 @@ let selectedAppId = "";
|
|
|
528
560
|
let selectedScript = "";
|
|
529
561
|
let candidateState = null;
|
|
530
562
|
let savingAutolaunch = false;
|
|
563
|
+
let savingDependencies = false;
|
|
564
|
+
let savingDependencyScript = false;
|
|
565
|
+
let dependencyPickerOpen = false;
|
|
566
|
+
let dependencyScriptPickerOpen = "";
|
|
567
|
+
let pendingDependencyId = "";
|
|
568
|
+
let dependencyScriptCandidates = {};
|
|
569
|
+
let dependencyScriptSelections = {};
|
|
531
570
|
|
|
532
571
|
const appListEl = document.querySelector("[data-app-list]");
|
|
533
572
|
const searchEl = document.querySelector("[data-app-search]");
|
|
@@ -586,11 +625,24 @@ const syncAppState = (updatedApp) => {
|
|
|
586
625
|
};
|
|
587
626
|
|
|
588
627
|
const setDetailBusy = (busy) => {
|
|
589
|
-
|
|
590
|
-
|
|
628
|
+
const blocked = !!busy || savingAutolaunch || savingDependencies || savingDependencyScript;
|
|
629
|
+
detailEl.querySelectorAll("[data-autolaunch-switch], [data-apply-manual], [data-clear-script], [data-toggle-dependency-picker], [data-add-dependency], [data-remove-dependency], [data-configure-dependency], [data-dependency-search], [data-dependency-script-choice], [data-close-dependency-script-modal]").forEach((control) => {
|
|
630
|
+
control.disabled = blocked;
|
|
591
631
|
});
|
|
592
632
|
};
|
|
593
633
|
|
|
634
|
+
const updateSelectedAppUrl = (appId) => {
|
|
635
|
+
try {
|
|
636
|
+
const url = new URL(window.location.href);
|
|
637
|
+
if (appId) {
|
|
638
|
+
url.searchParams.set("app", appId);
|
|
639
|
+
} else {
|
|
640
|
+
url.searchParams.delete("app");
|
|
641
|
+
}
|
|
642
|
+
window.history.replaceState({}, "", `${url.pathname}${url.search}${url.hash}`);
|
|
643
|
+
} catch (_) {}
|
|
644
|
+
};
|
|
645
|
+
|
|
594
646
|
const renderAppList = () => {
|
|
595
647
|
const query = (searchEl.value || "").trim().toLowerCase();
|
|
596
648
|
const visible = orderedApps().filter((app) => {
|
|
@@ -608,24 +660,24 @@ const renderAppList = () => {
|
|
|
608
660
|
visible.forEach((app) => {
|
|
609
661
|
const folderPath = formatAppFolderPath(app);
|
|
610
662
|
const fullFolderPath = app.workspace_path || app.launcher_path || folderPath;
|
|
611
|
-
const scriptLine = app.
|
|
612
|
-
? `<div class="autolaunch-app-script">
|
|
663
|
+
const scriptLine = app.autolaunch
|
|
664
|
+
? `<div class="autolaunch-app-script">Launches ${escapeHtml(app.autolaunch)}</div>`
|
|
613
665
|
: "";
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
666
|
+
const row = document.createElement("button");
|
|
667
|
+
row.type = "button";
|
|
668
|
+
row.className = `autolaunch-app-row${app.id === selectedAppId ? " selected" : ""}`;
|
|
669
|
+
row.dataset.appId = app.id;
|
|
670
|
+
row.innerHTML = `
|
|
619
671
|
<img src="${escapeHtml(app.icon || "/pinokio-black.png")}" alt="" onerror="this.onerror=null; this.src='/pinokio-black.png'">
|
|
620
672
|
<div class="autolaunch-app-meta">
|
|
621
673
|
<div class="autolaunch-app-name">${escapeHtml(app.title || app.id)}</div>
|
|
622
674
|
<div class="autolaunch-app-folder" title="${escapeHtml(fullFolderPath)}">${escapeHtml(folderPath)}</div>
|
|
623
675
|
${scriptLine}
|
|
624
676
|
</div>
|
|
625
|
-
<span class="autolaunch-pill ${app.autolaunch_enabled ? "on" : ""}">${app.autolaunch_enabled ? "ON" : "OFF"}</span>
|
|
677
|
+
<span class="autolaunch-pill ${app.autolaunch_enabled ? "on" : ""}">${app.autolaunch_enabled ? "Startup ON" : "Startup OFF"}</span>
|
|
626
678
|
`;
|
|
627
|
-
|
|
628
|
-
appListEl.appendChild(
|
|
679
|
+
row.addEventListener("click", () => selectApp(app.id));
|
|
680
|
+
appListEl.appendChild(row);
|
|
629
681
|
});
|
|
630
682
|
};
|
|
631
683
|
|
|
@@ -639,10 +691,13 @@ const renderDetailShell = (app, bodyHtml) => {
|
|
|
639
691
|
<h2 class="autolaunch-detail-title">${escapeHtml(app.title || app.id)}</h2>
|
|
640
692
|
<div class="autolaunch-current">${escapeHtml(app.workspace_path || app.id)}</div>
|
|
641
693
|
</div>
|
|
642
|
-
<
|
|
643
|
-
<span class="autolaunch-
|
|
644
|
-
<
|
|
645
|
-
|
|
694
|
+
<div class="autolaunch-startup-control">
|
|
695
|
+
<span class="autolaunch-startup-label">Start with Pinokio</span>
|
|
696
|
+
<button type="button" class="autolaunch-switch" role="switch" aria-checked="${checked}" aria-label="Start with Pinokio" data-autolaunch-switch ${savingAutolaunch ? "disabled" : ""}>
|
|
697
|
+
<span class="autolaunch-switch-track" aria-hidden="true"><span class="autolaunch-switch-thumb"></span></span>
|
|
698
|
+
<span class="autolaunch-switch-label">${label}</span>
|
|
699
|
+
</button>
|
|
700
|
+
</div>
|
|
646
701
|
</div>
|
|
647
702
|
${bodyHtml}
|
|
648
703
|
`;
|
|
@@ -692,18 +747,9 @@ const renderScriptSection = (title, note, candidates) => {
|
|
|
692
747
|
`;
|
|
693
748
|
};
|
|
694
749
|
|
|
695
|
-
const
|
|
696
|
-
const all = [].concat(data.menu || [], data.other || []);
|
|
697
|
-
if (data.current) {
|
|
698
|
-
return data.current;
|
|
699
|
-
}
|
|
700
|
-
const defaultCandidate = all.find((candidate) => candidate.menu_default);
|
|
701
|
-
if (defaultCandidate) {
|
|
702
|
-
return defaultCandidate.script;
|
|
703
|
-
}
|
|
704
|
-
return all.length > 0 ? all[0].script : (data.current || "");
|
|
705
|
-
};
|
|
750
|
+
const configuredScript = (data) => data && data.current ? data.current : "";
|
|
706
751
|
|
|
752
|
+
<%- include('partials/autolaunch_dependency_helpers') %>
|
|
707
753
|
const bindDetailControls = () => {
|
|
708
754
|
detailEl.querySelectorAll("input[name='autolaunch-script']").forEach((input) => {
|
|
709
755
|
input.addEventListener("change", () => {
|
|
@@ -712,11 +758,13 @@ const bindDetailControls = () => {
|
|
|
712
758
|
if (manual) manual.value = selectedScript;
|
|
713
759
|
renderCandidates();
|
|
714
760
|
const app = currentApp();
|
|
715
|
-
if (app
|
|
716
|
-
saveScript(selectedScript);
|
|
761
|
+
if (app) {
|
|
762
|
+
saveScript(selectedScript, !!app.autolaunch_enabled);
|
|
717
763
|
}
|
|
718
764
|
});
|
|
719
765
|
});
|
|
766
|
+
const clear = detailEl.querySelector("[data-clear-script]");
|
|
767
|
+
if (clear) clear.addEventListener("click", clearScript);
|
|
720
768
|
const toggle = detailEl.querySelector("[data-autolaunch-switch]");
|
|
721
769
|
if (toggle) toggle.addEventListener("click", toggleAutolaunch);
|
|
722
770
|
const manual = detailEl.querySelector("[data-manual-script]");
|
|
@@ -743,6 +791,103 @@ const bindDetailControls = () => {
|
|
|
743
791
|
}
|
|
744
792
|
const applyManual = detailEl.querySelector("[data-apply-manual]");
|
|
745
793
|
if (applyManual) applyManual.addEventListener("click", saveSelectedScript);
|
|
794
|
+
const toggleDependencyPicker = detailEl.querySelector("[data-toggle-dependency-picker]");
|
|
795
|
+
if (toggleDependencyPicker) {
|
|
796
|
+
toggleDependencyPicker.addEventListener("click", () => {
|
|
797
|
+
dependencyPickerOpen = !dependencyPickerOpen;
|
|
798
|
+
renderCandidates();
|
|
799
|
+
if (dependencyPickerOpen) {
|
|
800
|
+
window.setTimeout(() => {
|
|
801
|
+
const search = detailEl.querySelector("[data-dependency-search]");
|
|
802
|
+
if (search) search.focus();
|
|
803
|
+
}, 0);
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
const dependencySearch = detailEl.querySelector("[data-dependency-search]");
|
|
808
|
+
if (dependencySearch) {
|
|
809
|
+
dependencySearch.addEventListener("input", () => {
|
|
810
|
+
filterDependencyPicker(detailEl.querySelector("[data-dependency-picker]"));
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
detailEl.querySelectorAll("[data-add-dependency]").forEach((control) => {
|
|
814
|
+
control.addEventListener("click", () => {
|
|
815
|
+
const app = currentApp();
|
|
816
|
+
const dependency = control.getAttribute("data-add-dependency");
|
|
817
|
+
if (!app || !dependency) return;
|
|
818
|
+
if (!(selectedScript || app.autolaunch)) {
|
|
819
|
+
dependencyPickerOpen = false;
|
|
820
|
+
renderCandidates();
|
|
821
|
+
setFeedback("Choose this app's launch script before adding requirements.", true);
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
const dependencies = Array.isArray(app.autolaunch_depends) ? app.autolaunch_depends.slice() : [];
|
|
825
|
+
if (!dependencies.includes(dependency)) {
|
|
826
|
+
dependencyPickerOpen = false;
|
|
827
|
+
pendingDependencyId = dependency;
|
|
828
|
+
dependencyScriptPickerOpen = dependency;
|
|
829
|
+
renderCandidates();
|
|
830
|
+
loadDependencyScriptCandidates(dependency);
|
|
831
|
+
setFeedback("");
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
});
|
|
835
|
+
detailEl.querySelectorAll("[data-close-dependency-script-modal]").forEach((control) => {
|
|
836
|
+
control.addEventListener("click", () => {
|
|
837
|
+
if (pendingDependencyId === dependencyScriptPickerOpen) {
|
|
838
|
+
pendingDependencyId = "";
|
|
839
|
+
}
|
|
840
|
+
if (dependencyScriptPickerOpen) {
|
|
841
|
+
delete dependencyScriptSelections[dependencyScriptPickerOpen];
|
|
842
|
+
}
|
|
843
|
+
dependencyScriptPickerOpen = "";
|
|
844
|
+
renderCandidates();
|
|
845
|
+
setFeedback("");
|
|
846
|
+
});
|
|
847
|
+
});
|
|
848
|
+
detailEl.querySelectorAll("[data-remove-dependency]").forEach((control) => {
|
|
849
|
+
control.addEventListener("click", () => {
|
|
850
|
+
const app = currentApp();
|
|
851
|
+
if (!app) return;
|
|
852
|
+
const dependency = control.getAttribute("data-remove-dependency");
|
|
853
|
+
const dependencies = Array.isArray(app.autolaunch_depends) ? app.autolaunch_depends : [];
|
|
854
|
+
if (dependency) {
|
|
855
|
+
saveDependencies(dependencies.filter((id) => id !== dependency));
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
});
|
|
859
|
+
detailEl.querySelectorAll("[data-configure-dependency]").forEach((control) => {
|
|
860
|
+
control.addEventListener("click", () => {
|
|
861
|
+
const dependency = control.getAttribute("data-configure-dependency");
|
|
862
|
+
if (dependency) {
|
|
863
|
+
pendingDependencyId = "";
|
|
864
|
+
dependencyScriptPickerOpen = dependency;
|
|
865
|
+
renderCandidates();
|
|
866
|
+
if (dependencyScriptPickerOpen) {
|
|
867
|
+
loadDependencyScriptCandidates(dependency);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
});
|
|
872
|
+
detailEl.querySelectorAll("[data-dependency-script-choice]").forEach((input) => {
|
|
873
|
+
input.addEventListener("change", () => {
|
|
874
|
+
const dependency = input.getAttribute("data-dependency-script-app");
|
|
875
|
+
if (dependency && input.value) {
|
|
876
|
+
dependencyScriptSelections[dependency] = input.value;
|
|
877
|
+
renderCandidates();
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
});
|
|
881
|
+
detailEl.querySelectorAll("[data-confirm-dependency-script]").forEach((control) => {
|
|
882
|
+
control.addEventListener("click", () => {
|
|
883
|
+
const dependency = control.getAttribute("data-dependency-script-app");
|
|
884
|
+
const dependencyApp = dependency ? dependencyById(dependency) : null;
|
|
885
|
+
const script = dependency ? (dependencyScriptSelections[dependency] || (dependencyApp && dependencyApp.autolaunch) || "") : "";
|
|
886
|
+
if (dependency && script) {
|
|
887
|
+
saveDependencyScript(dependency, script);
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
});
|
|
746
891
|
};
|
|
747
892
|
|
|
748
893
|
const renderCandidates = () => {
|
|
@@ -750,15 +895,31 @@ const renderCandidates = () => {
|
|
|
750
895
|
if (!app || !candidateState) return;
|
|
751
896
|
const hasCandidates = (candidateState.menu && candidateState.menu.length) || (candidateState.other && candidateState.other.length);
|
|
752
897
|
const currentValue = candidateState.current || "";
|
|
898
|
+
const dependencies = Array.isArray(app.autolaunch_depends) ? app.autolaunch_depends : [];
|
|
899
|
+
const hasRequirements = dependencies.length > 0;
|
|
900
|
+
const requirementWarning = hasRequirements && !currentValue
|
|
901
|
+
? '<div class="autolaunch-feedback error">Choose this app\'s launch script before these requirements can run.</div>'
|
|
902
|
+
: "";
|
|
903
|
+
const clearButton = currentValue
|
|
904
|
+
? `<button type="button" class="autolaunch-clear-script" data-clear-script title="${hasRequirements ? "Remove requirements before clearing this app's launch script." : "Clear launch script"}" ${hasRequirements ? "disabled" : ""}>
|
|
905
|
+
<i class="fa-solid fa-xmark" aria-hidden="true"></i>
|
|
906
|
+
<span>Clear</span>
|
|
907
|
+
</button>`
|
|
908
|
+
: "";
|
|
753
909
|
renderDetailShell(app, `
|
|
754
910
|
<div class="autolaunch-section">
|
|
755
911
|
<div class="autolaunch-section-header">
|
|
756
912
|
<h3 class="autolaunch-section-title">Current</h3>
|
|
757
|
-
<span class="autolaunch-section-note">
|
|
913
|
+
<span class="autolaunch-section-note">Launch script</span>
|
|
758
914
|
</div>
|
|
759
|
-
<div class="autolaunch-current"
|
|
915
|
+
<div class="autolaunch-current-row">
|
|
916
|
+
<div class="autolaunch-current">${escapeHtml(currentValue || "OFF")}</div>
|
|
917
|
+
${clearButton}
|
|
918
|
+
</div>
|
|
919
|
+
${requirementWarning}
|
|
760
920
|
<div class="autolaunch-feedback" data-feedback></div>
|
|
761
921
|
</div>
|
|
922
|
+
${renderDependencySection(app)}
|
|
762
923
|
${hasCandidates ? "" : '<div class="autolaunch-section"><div class="autolaunch-empty">No local scripts were found for this app.</div></div>'}
|
|
763
924
|
${renderScriptSection("Menu scripts", "From pinokio.js", candidateState.menu || [])}
|
|
764
925
|
${renderScriptSection("Other local scripts", "Advanced", candidateState.other || [])}
|
|
@@ -783,17 +944,19 @@ const toggleAutolaunch = () => {
|
|
|
783
944
|
const app = currentApp();
|
|
784
945
|
if (!app || savingAutolaunch) return;
|
|
785
946
|
if (app.autolaunch_enabled) {
|
|
786
|
-
|
|
947
|
+
const manual = detailEl.querySelector("[data-manual-script]");
|
|
948
|
+
const script = manual && manual.value.trim() ? manual.value.trim() : (selectedScript || app.autolaunch || "");
|
|
949
|
+
saveScript(script, false);
|
|
787
950
|
return;
|
|
788
951
|
}
|
|
789
952
|
const manual = detailEl.querySelector("[data-manual-script]");
|
|
790
|
-
const script = manual && manual.value.trim() ? manual.value.trim() : selectedScript;
|
|
953
|
+
const script = manual && manual.value.trim() ? manual.value.trim() : (selectedScript || "");
|
|
791
954
|
if (!script) {
|
|
792
|
-
setFeedback("Choose a script first.", true);
|
|
955
|
+
setFeedback("Choose a launch script first.", true);
|
|
793
956
|
return;
|
|
794
957
|
}
|
|
795
958
|
selectedScript = script;
|
|
796
|
-
saveScript(script);
|
|
959
|
+
saveScript(script, true);
|
|
797
960
|
};
|
|
798
961
|
|
|
799
962
|
const loadCandidates = async (appId) => {
|
|
@@ -812,7 +975,7 @@ const loadCandidates = async (appId) => {
|
|
|
812
975
|
}
|
|
813
976
|
candidateState = data;
|
|
814
977
|
syncAppState(data.app);
|
|
815
|
-
selectedScript =
|
|
978
|
+
selectedScript = configuredScript(data);
|
|
816
979
|
renderCandidates();
|
|
817
980
|
} catch (error) {
|
|
818
981
|
renderDetailShell(app, `
|
|
@@ -823,13 +986,88 @@ const loadCandidates = async (appId) => {
|
|
|
823
986
|
}
|
|
824
987
|
};
|
|
825
988
|
|
|
826
|
-
const selectApp = (appId) => {
|
|
989
|
+
const selectApp = (appId, updateUrl = true) => {
|
|
827
990
|
selectedAppId = appId;
|
|
991
|
+
dependencyPickerOpen = false;
|
|
992
|
+
dependencyScriptPickerOpen = "";
|
|
993
|
+
pendingDependencyId = "";
|
|
994
|
+
if (updateUrl) {
|
|
995
|
+
updateSelectedAppUrl(appId);
|
|
996
|
+
}
|
|
828
997
|
renderAppList();
|
|
829
998
|
loadCandidates(appId);
|
|
830
999
|
};
|
|
831
1000
|
|
|
832
|
-
|
|
1001
|
+
<%- include('partials/autolaunch_dependency_save') %>
|
|
1002
|
+
const updateDependencyCandidateState = (updatedApp) => {
|
|
1003
|
+
if (!updatedApp || !updatedApp.id || !candidateState || !Array.isArray(candidateState.dependency_apps)) return;
|
|
1004
|
+
candidateState.dependency_apps = candidateState.dependency_apps.map((app) => app.id === updatedApp.id ? updatedApp : app);
|
|
1005
|
+
};
|
|
1006
|
+
|
|
1007
|
+
<%- include('partials/launch_settings_dependency_script_loader_factory') %>
|
|
1008
|
+
const loadDependencyScriptCandidates = createLaunchSettingsDependencyScriptLoader({
|
|
1009
|
+
getCache: () => dependencyScriptCandidates,
|
|
1010
|
+
render: renderCandidates
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
const saveDependencyScript = async (appId, script) => {
|
|
1014
|
+
const dependency = autolaunchApps.find((app) => app.id === appId);
|
|
1015
|
+
if (!dependency || !script || savingDependencyScript) return;
|
|
1016
|
+
savingDependencyScript = true;
|
|
1017
|
+
setDetailBusy(true);
|
|
1018
|
+
setFeedback("Saving requirement script...");
|
|
1019
|
+
try {
|
|
1020
|
+
const response = await fetch("/autolaunch", {
|
|
1021
|
+
method: "POST",
|
|
1022
|
+
headers: {
|
|
1023
|
+
"Content-Type": "application/json",
|
|
1024
|
+
"Accept": "application/json"
|
|
1025
|
+
},
|
|
1026
|
+
body: JSON.stringify({
|
|
1027
|
+
app: appId,
|
|
1028
|
+
script,
|
|
1029
|
+
enabled: !!dependency.autolaunch_enabled
|
|
1030
|
+
})
|
|
1031
|
+
});
|
|
1032
|
+
const data = await response.json();
|
|
1033
|
+
if (!response.ok || !data.ok) {
|
|
1034
|
+
throw new Error(data.error || "Failed to save launch script.");
|
|
1035
|
+
}
|
|
1036
|
+
syncAppState(data.app);
|
|
1037
|
+
updateDependencyCandidateState(data.app);
|
|
1038
|
+
if (pendingDependencyId === appId) {
|
|
1039
|
+
const app = currentApp();
|
|
1040
|
+
const dependencies = app && Array.isArray(app.autolaunch_depends) ? app.autolaunch_depends.slice() : [];
|
|
1041
|
+
if (!dependencies.includes(appId)) {
|
|
1042
|
+
const savedDependency = await saveDependencies(dependencies.concat(appId), "Requirement saved.");
|
|
1043
|
+
if (savedDependency) {
|
|
1044
|
+
pendingDependencyId = "";
|
|
1045
|
+
delete dependencyScriptSelections[appId];
|
|
1046
|
+
dependencyScriptPickerOpen = "";
|
|
1047
|
+
renderCandidates();
|
|
1048
|
+
}
|
|
1049
|
+
} else {
|
|
1050
|
+
pendingDependencyId = "";
|
|
1051
|
+
delete dependencyScriptSelections[appId];
|
|
1052
|
+
dependencyScriptPickerOpen = "";
|
|
1053
|
+
renderCandidates();
|
|
1054
|
+
setFeedback("Requirement saved.");
|
|
1055
|
+
}
|
|
1056
|
+
} else {
|
|
1057
|
+
dependencyScriptPickerOpen = "";
|
|
1058
|
+
delete dependencyScriptSelections[appId];
|
|
1059
|
+
renderCandidates();
|
|
1060
|
+
setFeedback("Requirement script saved.");
|
|
1061
|
+
}
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
setFeedback(error && error.message ? error.message : "Failed to save launch script.", true);
|
|
1064
|
+
} finally {
|
|
1065
|
+
savingDependencyScript = false;
|
|
1066
|
+
setDetailBusy(false);
|
|
1067
|
+
}
|
|
1068
|
+
};
|
|
1069
|
+
|
|
1070
|
+
const saveScript = async (script, enabled = !!(currentApp() && currentApp().autolaunch_enabled)) => {
|
|
833
1071
|
const app = currentApp();
|
|
834
1072
|
if (!app || savingAutolaunch) return;
|
|
835
1073
|
savingAutolaunch = true;
|
|
@@ -844,12 +1082,13 @@ const saveScript = async (script) => {
|
|
|
844
1082
|
},
|
|
845
1083
|
body: JSON.stringify({
|
|
846
1084
|
app: app.id,
|
|
847
|
-
script
|
|
1085
|
+
script,
|
|
1086
|
+
enabled: !!enabled
|
|
848
1087
|
})
|
|
849
1088
|
});
|
|
850
1089
|
const data = await response.json();
|
|
851
1090
|
if (!response.ok || !data.ok) {
|
|
852
|
-
throw new Error(data.error || "Failed to save
|
|
1091
|
+
throw new Error(data.error || "Failed to save Autolaunch settings.");
|
|
853
1092
|
}
|
|
854
1093
|
syncAppState(data.app);
|
|
855
1094
|
if (candidateState) {
|
|
@@ -857,9 +1096,50 @@ const saveScript = async (script) => {
|
|
|
857
1096
|
}
|
|
858
1097
|
selectedScript = data.app.autolaunch || selectedScript;
|
|
859
1098
|
renderCandidates();
|
|
860
|
-
setFeedback(data.app.autolaunch_enabled ? "Saved." : "Disabled.");
|
|
1099
|
+
setFeedback(data.app.autolaunch_enabled ? "Saved." : (script ? "Launch script saved." : "Disabled."));
|
|
1100
|
+
} catch (error) {
|
|
1101
|
+
setFeedback(error && error.message ? error.message : "Failed to save Autolaunch settings.", true);
|
|
1102
|
+
} finally {
|
|
1103
|
+
savingAutolaunch = false;
|
|
1104
|
+
setDetailBusy(false);
|
|
1105
|
+
}
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1108
|
+
const clearScript = async () => {
|
|
1109
|
+
const app = currentApp();
|
|
1110
|
+
if (!app || savingAutolaunch) return;
|
|
1111
|
+
if (Array.isArray(app.autolaunch_depends) && app.autolaunch_depends.length > 0) {
|
|
1112
|
+
setFeedback("Remove requirements before clearing this app's launch script.", true);
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
savingAutolaunch = true;
|
|
1116
|
+
setDetailBusy(true);
|
|
1117
|
+
setFeedback("Clearing launch script...");
|
|
1118
|
+
try {
|
|
1119
|
+
const response = await fetch("/autolaunch", {
|
|
1120
|
+
method: "POST",
|
|
1121
|
+
headers: {
|
|
1122
|
+
"Content-Type": "application/json",
|
|
1123
|
+
"Accept": "application/json"
|
|
1124
|
+
},
|
|
1125
|
+
body: JSON.stringify({
|
|
1126
|
+
app: app.id,
|
|
1127
|
+
clear_script: true
|
|
1128
|
+
})
|
|
1129
|
+
});
|
|
1130
|
+
const data = await response.json();
|
|
1131
|
+
if (!response.ok || !data.ok) {
|
|
1132
|
+
throw new Error(data.error || "Failed to clear launch script.");
|
|
1133
|
+
}
|
|
1134
|
+
syncAppState(data.app);
|
|
1135
|
+
if (candidateState) {
|
|
1136
|
+
candidateState.current = "";
|
|
1137
|
+
}
|
|
1138
|
+
selectedScript = "";
|
|
1139
|
+
renderCandidates();
|
|
1140
|
+
setFeedback("Launch script cleared.");
|
|
861
1141
|
} catch (error) {
|
|
862
|
-
setFeedback(error && error.message ? error.message : "Failed to
|
|
1142
|
+
setFeedback(error && error.message ? error.message : "Failed to clear launch script.", true);
|
|
863
1143
|
} finally {
|
|
864
1144
|
savingAutolaunch = false;
|
|
865
1145
|
setDetailBusy(false);
|
|
@@ -873,11 +1153,12 @@ const saveSelectedScript = () => {
|
|
|
873
1153
|
setFeedback("Choose a script first.", true);
|
|
874
1154
|
return;
|
|
875
1155
|
}
|
|
876
|
-
|
|
1156
|
+
const app = currentApp();
|
|
1157
|
+
saveScript(script, !!(app && app.autolaunch_enabled));
|
|
877
1158
|
};
|
|
878
1159
|
|
|
879
1160
|
const disableAll = async () => {
|
|
880
|
-
if (!window.confirm("
|
|
1161
|
+
if (!window.confirm("Turn off startup launch for every installed app?")) {
|
|
881
1162
|
return;
|
|
882
1163
|
}
|
|
883
1164
|
disableAllButton.disabled = true;
|
|
@@ -888,16 +1169,16 @@ const disableAll = async () => {
|
|
|
888
1169
|
});
|
|
889
1170
|
const data = await response.json();
|
|
890
1171
|
if (!response.ok || !data.ok) {
|
|
891
|
-
throw new Error(data.error || "Failed to disable
|
|
1172
|
+
throw new Error(data.error || "Failed to disable startup launch.");
|
|
892
1173
|
}
|
|
893
|
-
autolaunchApps = data.apps || autolaunchApps.map((app) => ({ ...app,
|
|
1174
|
+
autolaunchApps = data.apps || autolaunchApps.map((app) => ({ ...app, autolaunch_enabled: false }));
|
|
894
1175
|
updateStats();
|
|
895
1176
|
renderAppList();
|
|
896
1177
|
if (selectedAppId) {
|
|
897
1178
|
loadCandidates(selectedAppId);
|
|
898
1179
|
}
|
|
899
1180
|
} catch (error) {
|
|
900
|
-
window.alert(error && error.message ? error.message : "Failed to disable
|
|
1181
|
+
window.alert(error && error.message ? error.message : "Failed to disable startup launch.");
|
|
901
1182
|
updateStats();
|
|
902
1183
|
}
|
|
903
1184
|
};
|
|
@@ -907,9 +1188,16 @@ disableAllButton.addEventListener("click", disableAll);
|
|
|
907
1188
|
|
|
908
1189
|
updateStats();
|
|
909
1190
|
renderAppList();
|
|
910
|
-
|
|
1191
|
+
let initialApp = null;
|
|
1192
|
+
try {
|
|
1193
|
+
const requestedAppId = new URLSearchParams(window.location.search).get("app");
|
|
1194
|
+
if (requestedAppId) {
|
|
1195
|
+
initialApp = autolaunchApps.find((app) => app && app.id === requestedAppId) || null;
|
|
1196
|
+
}
|
|
1197
|
+
} catch (_) {}
|
|
1198
|
+
const firstApp = initialApp || orderedApps()[0];
|
|
911
1199
|
if (firstApp) {
|
|
912
|
-
selectApp(firstApp.id);
|
|
1200
|
+
selectApp(firstApp.id, false);
|
|
913
1201
|
}
|
|
914
1202
|
</script>
|
|
915
1203
|
</body>
|