pinokiod 8.0.18 → 8.0.19
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/package.json
CHANGED
package/server/views/app.ejs
CHANGED
|
@@ -544,6 +544,7 @@ body.dark .snapshot-comment {
|
|
|
544
544
|
color: royalblue;
|
|
545
545
|
}
|
|
546
546
|
#sidebar-toggle {
|
|
547
|
+
--sidebar-edge-hint-rgb: 37, 99, 235;
|
|
547
548
|
color: #0f172a;
|
|
548
549
|
position: relative;
|
|
549
550
|
width: 40px;
|
|
@@ -554,6 +555,7 @@ body.dark .snapshot-comment {
|
|
|
554
555
|
transition: background-color 140ms cubic-bezier(0.25, 1, 0.5, 1), box-shadow 140ms cubic-bezier(0.25, 1, 0.5, 1), color 140ms cubic-bezier(0.25, 1, 0.5, 1);
|
|
555
556
|
}
|
|
556
557
|
body.dark #sidebar-toggle {
|
|
558
|
+
--sidebar-edge-hint-rgb: 96, 165, 250;
|
|
557
559
|
color: rgba(248, 250, 252, 0.92);
|
|
558
560
|
}
|
|
559
561
|
#sidebar-toggle[aria-expanded="true"] {
|
|
@@ -570,6 +572,32 @@ body.dark #sidebar-toggle[aria-expanded="true"] {
|
|
|
570
572
|
display: inline-flex;
|
|
571
573
|
align-items: center;
|
|
572
574
|
justify-content: center;
|
|
575
|
+
transition: filter 140ms cubic-bezier(0.25, 1, 0.5, 1), transform 140ms cubic-bezier(0.25, 1, 0.5, 1);
|
|
576
|
+
}
|
|
577
|
+
#sidebar-toggle.sidebar-edge-hint {
|
|
578
|
+
color: rgb(30, 64, 175);
|
|
579
|
+
background: rgba(var(--sidebar-edge-hint-rgb), 0.09);
|
|
580
|
+
box-shadow:
|
|
581
|
+
0 0 0 1px rgba(var(--sidebar-edge-hint-rgb), 0.42),
|
|
582
|
+
0 0 8px rgba(var(--sidebar-edge-hint-rgb), 0.28);
|
|
583
|
+
}
|
|
584
|
+
body.dark #sidebar-toggle.sidebar-edge-hint {
|
|
585
|
+
color: rgb(219, 234, 254);
|
|
586
|
+
background: rgba(var(--sidebar-edge-hint-rgb), 0.12);
|
|
587
|
+
box-shadow:
|
|
588
|
+
0 0 0 1px rgba(147, 197, 253, 0.55),
|
|
589
|
+
0 0 10px rgba(var(--sidebar-edge-hint-rgb), 0.38);
|
|
590
|
+
}
|
|
591
|
+
#sidebar-toggle.sidebar-edge-hint .sidebar-toggle-glyph {
|
|
592
|
+
transform: scale(1.03);
|
|
593
|
+
filter:
|
|
594
|
+
drop-shadow(0 0 1px rgba(var(--sidebar-edge-hint-rgb), 0.82))
|
|
595
|
+
drop-shadow(0 0 5px rgba(var(--sidebar-edge-hint-rgb), 0.55));
|
|
596
|
+
}
|
|
597
|
+
@media (prefers-reduced-motion: reduce) {
|
|
598
|
+
.sidebar-toggle-glyph {
|
|
599
|
+
transition: none;
|
|
600
|
+
}
|
|
573
601
|
}
|
|
574
602
|
.sidebar-toggle-frame {
|
|
575
603
|
position: relative;
|
|
@@ -2664,6 +2692,65 @@ body.dark .appcanvas.panel-open .menu-actions #ask-ai-tab.selected {
|
|
|
2664
2692
|
body.dark .appcanvas.vertical .menu-actions {
|
|
2665
2693
|
border-top: none;
|
|
2666
2694
|
}
|
|
2695
|
+
.appcanvas.vertical .sidebar-hover-preference {
|
|
2696
|
+
width: 100%;
|
|
2697
|
+
padding-top: 5px;
|
|
2698
|
+
border-top: 1px solid var(--pinokio-sidebar-separator);
|
|
2699
|
+
}
|
|
2700
|
+
.appcanvas.vertical .sidebar-hover-toggle,
|
|
2701
|
+
.appcanvas.vertical .sidebar-hover-toggle .tab {
|
|
2702
|
+
width: 100%;
|
|
2703
|
+
}
|
|
2704
|
+
.sidebar-hover-switch {
|
|
2705
|
+
display: inline-flex;
|
|
2706
|
+
align-items: center;
|
|
2707
|
+
width: 30px;
|
|
2708
|
+
height: 16px;
|
|
2709
|
+
margin-left: auto;
|
|
2710
|
+
padding: 2px;
|
|
2711
|
+
border: 1px solid rgba(100, 116, 139, 0.32);
|
|
2712
|
+
border-radius: 999px;
|
|
2713
|
+
background: rgba(148, 163, 184, 0.24);
|
|
2714
|
+
box-sizing: border-box;
|
|
2715
|
+
flex: 0 0 auto;
|
|
2716
|
+
transition: background 140ms ease, border-color 140ms ease;
|
|
2717
|
+
}
|
|
2718
|
+
.sidebar-hover-switch-thumb {
|
|
2719
|
+
width: 10px;
|
|
2720
|
+
height: 10px;
|
|
2721
|
+
border-radius: 50%;
|
|
2722
|
+
background: rgba(71, 85, 105, 0.82);
|
|
2723
|
+
transform: translateX(0);
|
|
2724
|
+
transition: transform 140ms cubic-bezier(0.25, 1, 0.5, 1), background 140ms ease;
|
|
2725
|
+
}
|
|
2726
|
+
.sidebar-hover-toggle[aria-checked="true"] .sidebar-hover-switch {
|
|
2727
|
+
border-color: rgba(15, 23, 42, 0.68);
|
|
2728
|
+
background: rgba(15, 23, 42, 0.82);
|
|
2729
|
+
}
|
|
2730
|
+
.sidebar-hover-toggle[aria-checked="true"] .sidebar-hover-switch-thumb {
|
|
2731
|
+
background: rgba(248, 250, 252, 0.96);
|
|
2732
|
+
transform: translateX(14px);
|
|
2733
|
+
}
|
|
2734
|
+
body.dark .sidebar-hover-switch {
|
|
2735
|
+
border-color: rgba(148, 163, 184, 0.32);
|
|
2736
|
+
background: rgba(148, 163, 184, 0.18);
|
|
2737
|
+
}
|
|
2738
|
+
body.dark .sidebar-hover-switch-thumb {
|
|
2739
|
+
background: rgba(203, 213, 225, 0.76);
|
|
2740
|
+
}
|
|
2741
|
+
body.dark .sidebar-hover-toggle[aria-checked="true"] .sidebar-hover-switch {
|
|
2742
|
+
border-color: rgba(226, 232, 240, 0.62);
|
|
2743
|
+
background: rgba(226, 232, 240, 0.72);
|
|
2744
|
+
}
|
|
2745
|
+
body.dark .sidebar-hover-toggle[aria-checked="true"] .sidebar-hover-switch-thumb {
|
|
2746
|
+
background: rgba(15, 23, 42, 0.9);
|
|
2747
|
+
}
|
|
2748
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2749
|
+
.sidebar-hover-switch,
|
|
2750
|
+
.sidebar-hover-switch-thumb {
|
|
2751
|
+
transition: none;
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2667
2754
|
.appcanvas.vertical > aside .menu-actions .header-item + .header-item {
|
|
2668
2755
|
box-shadow: none !important;
|
|
2669
2756
|
}
|
|
@@ -8346,6 +8433,17 @@ body.dark .pinokio-custom-terminal-header {
|
|
|
8346
8433
|
<% if (type === 'browse') { %>
|
|
8347
8434
|
<%- include('./partials/fs_status') %>
|
|
8348
8435
|
<% } %>
|
|
8436
|
+
<% if (type === 'run') { %>
|
|
8437
|
+
<div class="sidebar-hover-preference">
|
|
8438
|
+
<button type="button" class="btn header-item sidebar-hover-toggle" role="switch" aria-checked="true" data-app-sidebar-show-on-hover data-tippy-content="Show the sidebar when hovering over the left edge">
|
|
8439
|
+
<div class="tab">
|
|
8440
|
+
<i class="fa-solid fa-eye menu-action-leading-icon" aria-hidden="true"></i>
|
|
8441
|
+
<div class="display">Show on hover</div>
|
|
8442
|
+
<span class="sidebar-hover-switch" aria-hidden="true"><span class="sidebar-hover-switch-thumb"></span></span>
|
|
8443
|
+
</div>
|
|
8444
|
+
</button>
|
|
8445
|
+
</div>
|
|
8446
|
+
<% } %>
|
|
8349
8447
|
<button type='button' id='logs-toggle' class="btn header-item" data-tippy-content="Get help with logs" aria-expanded="false" aria-controls="app-logs-drawer">
|
|
8350
8448
|
<div class='tab'>
|
|
8351
8449
|
<i class="fa-solid fa-circle-question menu-action-leading-icon"></i>
|
|
@@ -11463,7 +11561,7 @@ const rerenderMenuSection = (container, html) => {
|
|
|
11463
11561
|
: "Protection beta is off for this app. You can change it later from the Run tab."
|
|
11464
11562
|
})
|
|
11465
11563
|
}
|
|
11466
|
-
maybeShowProtectionInstallToast()
|
|
11564
|
+
// maybeShowProtectionInstallToast()
|
|
11467
11565
|
const buildPluginLaunchFromQuery = () => {
|
|
11468
11566
|
try {
|
|
11469
11567
|
const pageUrl = new URL(window.location.href)
|
|
@@ -16498,6 +16596,7 @@ const rerenderMenuSection = (container, html) => {
|
|
|
16498
16596
|
const aside = document.getElementById("app-sidebar")
|
|
16499
16597
|
const toggle = document.getElementById("sidebar-toggle")
|
|
16500
16598
|
const peekTrigger = document.querySelector("[data-app-sidebar-peek-trigger]")
|
|
16599
|
+
const showOnHoverToggle = document.querySelector("[data-app-sidebar-show-on-hover]")
|
|
16501
16600
|
if (!appcanvas || !aside || !toggle) {
|
|
16502
16601
|
if (toggle) {
|
|
16503
16602
|
toggle.classList.add("hidden")
|
|
@@ -16509,6 +16608,21 @@ const rerenderMenuSection = (container, html) => {
|
|
|
16509
16608
|
const storageKey = workspaceName
|
|
16510
16609
|
? `pinokio.sidebar-collapsed:${workspaceName}`
|
|
16511
16610
|
: `pinokio.sidebar-collapsed:${location.pathname}`
|
|
16611
|
+
const showOnHoverStorageKey = "pinokio.sidebar-show-on-hover"
|
|
16612
|
+
const legacyAutoShowStorageKey = "pinokio.sidebar-auto-show"
|
|
16613
|
+
let showOnHoverEnabled = true
|
|
16614
|
+
try {
|
|
16615
|
+
const storedShowOnHover = window.localStorage.getItem(showOnHoverStorageKey)
|
|
16616
|
+
const storedPreference = storedShowOnHover === null
|
|
16617
|
+
? window.localStorage.getItem(legacyAutoShowStorageKey)
|
|
16618
|
+
: storedShowOnHover
|
|
16619
|
+
showOnHoverEnabled = storedPreference !== "0"
|
|
16620
|
+
} catch (_) {}
|
|
16621
|
+
|
|
16622
|
+
const syncShowOnHoverToggle = () => {
|
|
16623
|
+
if (!showOnHoverToggle) return
|
|
16624
|
+
showOnHoverToggle.setAttribute("aria-checked", showOnHoverEnabled ? "true" : "false")
|
|
16625
|
+
}
|
|
16512
16626
|
|
|
16513
16627
|
const updateToggle = (collapsed) => {
|
|
16514
16628
|
const expanded = !collapsed
|
|
@@ -16524,20 +16638,10 @@ const rerenderMenuSection = (container, html) => {
|
|
|
16524
16638
|
}
|
|
16525
16639
|
|
|
16526
16640
|
const desktopPeekMq = window.matchMedia("(min-width: 769px) and (hover: hover) and (pointer: fine)")
|
|
16527
|
-
const shouldAutoPeekOnIdle = <%- JSON.stringify(type === "run") %>
|
|
16528
16641
|
let peekCloseTimer = 0
|
|
16529
16642
|
|
|
16530
16643
|
const isCollapsed = () => appcanvas.classList.contains("sidebar-collapsed")
|
|
16531
16644
|
const isPeeking = () => appcanvas.classList.contains("sidebar-peeking")
|
|
16532
|
-
const hasVisibleBrowserSurface = () => {
|
|
16533
|
-
const browserview = document.querySelector("main.browserview")
|
|
16534
|
-
if (!browserview) return false
|
|
16535
|
-
if (browserview.querySelector("iframe:not(.hidden)")) return true
|
|
16536
|
-
const networkStatus = document.getElementById("browserview-network-status")
|
|
16537
|
-
if (networkStatus && !networkStatus.hidden) return true
|
|
16538
|
-
const launchStatus = document.querySelector("[data-launch-requirements-status]")
|
|
16539
|
-
return Boolean(launchStatus && !launchStatus.hidden)
|
|
16540
|
-
}
|
|
16541
16645
|
const syncSidebarVisibility = () => {
|
|
16542
16646
|
const peeking = isPeeking()
|
|
16543
16647
|
aside.setAttribute("aria-hidden", isCollapsed() && !peeking ? "true" : "false")
|
|
@@ -16549,9 +16653,24 @@ const rerenderMenuSection = (container, html) => {
|
|
|
16549
16653
|
const setPeeking = (peeking) => {
|
|
16550
16654
|
const next = !!peeking && canPeek()
|
|
16551
16655
|
window.clearTimeout(peekCloseTimer)
|
|
16656
|
+
if (next) {
|
|
16657
|
+
toggle.classList.remove("sidebar-edge-hint")
|
|
16658
|
+
}
|
|
16552
16659
|
appcanvas.classList.toggle("sidebar-peeking", next)
|
|
16553
16660
|
syncSidebarVisibility()
|
|
16554
16661
|
}
|
|
16662
|
+
const setShowOnHoverEnabled = (enabled, options = {}) => {
|
|
16663
|
+
showOnHoverEnabled = !!enabled
|
|
16664
|
+
syncShowOnHoverToggle()
|
|
16665
|
+
if (showOnHoverEnabled) {
|
|
16666
|
+
toggle.classList.remove("sidebar-edge-hint")
|
|
16667
|
+
}
|
|
16668
|
+
if (options.persist !== false) {
|
|
16669
|
+
try {
|
|
16670
|
+
window.localStorage.setItem(showOnHoverStorageKey, showOnHoverEnabled ? "1" : "0")
|
|
16671
|
+
} catch (_) {}
|
|
16672
|
+
}
|
|
16673
|
+
}
|
|
16555
16674
|
const shouldKeepPeekOpen = () => {
|
|
16556
16675
|
const active = document.activeElement
|
|
16557
16676
|
return (
|
|
@@ -16592,13 +16711,9 @@ const rerenderMenuSection = (container, html) => {
|
|
|
16592
16711
|
initialCollapsed = window.localStorage.getItem(storageKey) === "1"
|
|
16593
16712
|
} catch (_) {}
|
|
16594
16713
|
setCollapsed(initialCollapsed, { persist: false })
|
|
16714
|
+
syncShowOnHoverToggle()
|
|
16595
16715
|
requestAnimationFrame(() => {
|
|
16596
16716
|
appcanvas.classList.add("sidebar-motion-ready")
|
|
16597
|
-
requestAnimationFrame(() => {
|
|
16598
|
-
if (shouldAutoPeekOnIdle && initialCollapsed && !hasVisibleBrowserSurface()) {
|
|
16599
|
-
setPeeking(true)
|
|
16600
|
-
}
|
|
16601
|
-
})
|
|
16602
16717
|
})
|
|
16603
16718
|
|
|
16604
16719
|
toggle.addEventListener("click", (event) => {
|
|
@@ -16606,11 +16721,28 @@ const rerenderMenuSection = (container, html) => {
|
|
|
16606
16721
|
setCollapsed(!appcanvas.classList.contains("sidebar-collapsed"))
|
|
16607
16722
|
})
|
|
16608
16723
|
|
|
16724
|
+
if (showOnHoverToggle) {
|
|
16725
|
+
showOnHoverToggle.addEventListener("click", (event) => {
|
|
16726
|
+
event.preventDefault()
|
|
16727
|
+
event.stopPropagation()
|
|
16728
|
+
setShowOnHoverEnabled(!showOnHoverEnabled)
|
|
16729
|
+
})
|
|
16730
|
+
}
|
|
16731
|
+
|
|
16609
16732
|
if (peekTrigger) {
|
|
16610
|
-
peekTrigger.addEventListener("pointerenter", () =>
|
|
16733
|
+
peekTrigger.addEventListener("pointerenter", () => {
|
|
16734
|
+
if (showOnHoverEnabled) {
|
|
16735
|
+
setPeeking(true)
|
|
16736
|
+
} else {
|
|
16737
|
+
toggle.classList.add("sidebar-edge-hint")
|
|
16738
|
+
}
|
|
16739
|
+
})
|
|
16611
16740
|
peekTrigger.addEventListener("focus", () => setPeeking(true))
|
|
16612
16741
|
peekTrigger.addEventListener("click", () => setPeeking(true))
|
|
16613
|
-
peekTrigger.addEventListener("pointerleave",
|
|
16742
|
+
peekTrigger.addEventListener("pointerleave", () => {
|
|
16743
|
+
toggle.classList.remove("sidebar-edge-hint")
|
|
16744
|
+
closePeekSoon()
|
|
16745
|
+
})
|
|
16614
16746
|
peekTrigger.addEventListener("blur", closePeekSoon)
|
|
16615
16747
|
aside.addEventListener("pointerenter", () => {
|
|
16616
16748
|
if (isPeeking()) {
|
|
@@ -16659,8 +16791,10 @@ const rerenderMenuSection = (container, html) => {
|
|
|
16659
16791
|
window.PinokioSidebar = {
|
|
16660
16792
|
setCollapsed,
|
|
16661
16793
|
setPeeking,
|
|
16794
|
+
setShowOnHoverEnabled,
|
|
16662
16795
|
isCollapsed,
|
|
16663
|
-
isPeeking
|
|
16796
|
+
isPeeking,
|
|
16797
|
+
isShowOnHoverEnabled: () => showOnHoverEnabled
|
|
16664
16798
|
}
|
|
16665
16799
|
})()
|
|
16666
16800
|
</script>
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
</span>
|
|
17
17
|
</button>
|
|
18
18
|
<div class="home-server-popover__panel" data-home-server-panel role="dialog" aria-label="Home Server" hidden>
|
|
19
|
-
<div
|
|
19
|
+
<div data-home-server-content>
|
|
20
|
+
<div class="home-server-popover__loading">Checking Home Server...</div>
|
|
21
|
+
</div>
|
|
20
22
|
</div>
|
|
21
23
|
</div>
|
|
@@ -642,8 +642,10 @@ body.dark .home-server-popover__qr-close:focus-visible {
|
|
|
642
642
|
root.dataset.homeServerReady = "1"
|
|
643
643
|
const trigger = root.querySelector("[data-home-server-trigger]")
|
|
644
644
|
const panel = root.querySelector("[data-home-server-panel]")
|
|
645
|
+
const panelContent = root.querySelector("[data-home-server-content]")
|
|
645
646
|
let payload = null
|
|
646
647
|
let pollTimer = null
|
|
648
|
+
let qrReturnUrl = ""
|
|
647
649
|
const setTriggerHintEnabled = (enabled) => {
|
|
648
650
|
if (!trigger) {
|
|
649
651
|
return
|
|
@@ -699,11 +701,12 @@ body.dark .home-server-popover__qr-close:focus-visible {
|
|
|
699
701
|
existing.remove()
|
|
700
702
|
}
|
|
701
703
|
}
|
|
702
|
-
const showQrPreview = (url
|
|
704
|
+
const showQrPreview = (url) => {
|
|
703
705
|
if (!panel || !url) {
|
|
704
706
|
return
|
|
705
707
|
}
|
|
706
708
|
removeQrPreview()
|
|
709
|
+
qrReturnUrl = url
|
|
707
710
|
const safeUrl = escapeHtml(url)
|
|
708
711
|
const qrUrl = `/qr?data=${encodeURIComponent(url)}&s=7&m=0`
|
|
709
712
|
panel.insertAdjacentHTML("beforeend", `
|
|
@@ -719,11 +722,6 @@ body.dark .home-server-popover__qr-close:focus-visible {
|
|
|
719
722
|
</div>
|
|
720
723
|
<div class="home-server-popover__qr-url" title="${safeUrl}">${safeUrl}</div>
|
|
721
724
|
</div>`)
|
|
722
|
-
const preview = panel.querySelector("[data-home-server-qr-preview]")
|
|
723
|
-
if (preview && sourceButton) {
|
|
724
|
-
preview.dataset.returnFocus = "true"
|
|
725
|
-
root._homeServerQrReturnFocus = sourceButton
|
|
726
|
-
}
|
|
727
725
|
const closeButton = panel.querySelector("[data-home-server-qr-close]")
|
|
728
726
|
if (closeButton && typeof closeButton.focus === "function") {
|
|
729
727
|
closeButton.focus()
|
|
@@ -1002,19 +1000,18 @@ body.dark .home-server-popover__qr-close:focus-visible {
|
|
|
1002
1000
|
document.dispatchEvent(new CustomEvent("pinokio:home-server-status", {
|
|
1003
1001
|
detail: { status, payload: data || { status } }
|
|
1004
1002
|
}))
|
|
1005
|
-
if (!
|
|
1003
|
+
if (!panelContent) {
|
|
1006
1004
|
return
|
|
1007
1005
|
}
|
|
1008
1006
|
if (status === "on") {
|
|
1009
|
-
|
|
1007
|
+
panelContent.innerHTML = renderOn(data)
|
|
1010
1008
|
} else if (status === "starting") {
|
|
1011
|
-
|
|
1009
|
+
panelContent.innerHTML = renderStarting(data)
|
|
1012
1010
|
} else if (status === "setup") {
|
|
1013
|
-
|
|
1011
|
+
panelContent.innerHTML = renderSetup(data)
|
|
1014
1012
|
} else {
|
|
1015
|
-
|
|
1013
|
+
panelContent.innerHTML = renderOff(data)
|
|
1016
1014
|
}
|
|
1017
|
-
root._homeServerQrReturnFocus = null
|
|
1018
1015
|
}
|
|
1019
1016
|
const fetchState = async () => {
|
|
1020
1017
|
const response = await fetch("/info/home-server", { cache: "no-store" })
|
|
@@ -1072,7 +1069,7 @@ body.dark .home-server-popover__qr-close:focus-visible {
|
|
|
1072
1069
|
if (attempts >= 60) {
|
|
1073
1070
|
stopPolling()
|
|
1074
1071
|
setStatus("error")
|
|
1075
|
-
|
|
1072
|
+
panelContent.innerHTML = renderError()
|
|
1076
1073
|
}
|
|
1077
1074
|
}
|
|
1078
1075
|
}, 1500)
|
|
@@ -1111,11 +1108,11 @@ body.dark .home-server-popover__qr-close:focus-visible {
|
|
|
1111
1108
|
}
|
|
1112
1109
|
} catch (err) {
|
|
1113
1110
|
setStatus("error")
|
|
1114
|
-
|
|
1111
|
+
panelContent.innerHTML = renderError(err && err.message ? err.message : "")
|
|
1115
1112
|
}
|
|
1116
1113
|
}
|
|
1117
1114
|
const openPanel = async () => {
|
|
1118
|
-
if (!panel || !trigger) {
|
|
1115
|
+
if (!panel || !panelContent || !trigger) {
|
|
1119
1116
|
return
|
|
1120
1117
|
}
|
|
1121
1118
|
panel.hidden = false
|
|
@@ -1123,14 +1120,14 @@ body.dark .home-server-popover__qr-close:focus-visible {
|
|
|
1123
1120
|
trigger.setAttribute("aria-expanded", "true")
|
|
1124
1121
|
setTriggerHintEnabled(false)
|
|
1125
1122
|
if (!payload) {
|
|
1126
|
-
|
|
1123
|
+
panelContent.innerHTML = `<div class="home-server-popover__loading">Checking Home Server...</div>`
|
|
1127
1124
|
}
|
|
1128
1125
|
try {
|
|
1129
1126
|
await fetchState()
|
|
1130
1127
|
refreshPolling(payload)
|
|
1131
1128
|
} catch (err) {
|
|
1132
1129
|
setStatus("error")
|
|
1133
|
-
|
|
1130
|
+
panelContent.innerHTML = renderError(err && err.message ? err.message : "")
|
|
1134
1131
|
}
|
|
1135
1132
|
}
|
|
1136
1133
|
const closePanel = () => {
|
|
@@ -1139,7 +1136,7 @@ body.dark .home-server-popover__qr-close:focus-visible {
|
|
|
1139
1136
|
}
|
|
1140
1137
|
panel.hidden = true
|
|
1141
1138
|
removeQrPreview()
|
|
1142
|
-
|
|
1139
|
+
qrReturnUrl = ""
|
|
1143
1140
|
root.dataset.homeServerOpen = "false"
|
|
1144
1141
|
trigger.setAttribute("aria-expanded", "false")
|
|
1145
1142
|
setTriggerHintEnabled(true)
|
|
@@ -1192,17 +1189,22 @@ body.dark .home-server-popover__qr-close:focus-visible {
|
|
|
1192
1189
|
if (qrButton) {
|
|
1193
1190
|
event.preventDefault()
|
|
1194
1191
|
event.stopPropagation()
|
|
1195
|
-
showQrPreview(qrButton.getAttribute("data-home-server-qr") || ""
|
|
1192
|
+
showQrPreview(qrButton.getAttribute("data-home-server-qr") || "")
|
|
1196
1193
|
return
|
|
1197
1194
|
}
|
|
1198
1195
|
const qrCloseButton = event.target.closest("[data-home-server-qr-close]")
|
|
1199
1196
|
if (qrCloseButton) {
|
|
1200
1197
|
event.preventDefault()
|
|
1201
1198
|
event.stopPropagation()
|
|
1199
|
+
const returnUrl = qrReturnUrl
|
|
1202
1200
|
removeQrPreview()
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1201
|
+
qrReturnUrl = ""
|
|
1202
|
+
const focusTarget = panelContent
|
|
1203
|
+
? Array.from(panelContent.querySelectorAll("[data-home-server-qr]")).find((button) => {
|
|
1204
|
+
return button.getAttribute("data-home-server-qr") === returnUrl
|
|
1205
|
+
})
|
|
1206
|
+
: null
|
|
1207
|
+
if (focusTarget && typeof focusTarget.focus === "function") {
|
|
1206
1208
|
focusTarget.focus()
|
|
1207
1209
|
}
|
|
1208
1210
|
return
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const assert = require("node:assert/strict")
|
|
2
|
+
const fs = require("node:fs/promises")
|
|
3
|
+
const path = require("node:path")
|
|
4
|
+
const test = require("node:test")
|
|
5
|
+
const { JSDOM, VirtualConsole } = require("jsdom")
|
|
6
|
+
|
|
7
|
+
const root = path.resolve(__dirname, "..")
|
|
8
|
+
const popoverPath = path.join(root, "server/views/partials/home_server_popover.ejs")
|
|
9
|
+
const assetsPath = path.join(root, "server/views/partials/home_server_popover_assets.ejs")
|
|
10
|
+
|
|
11
|
+
async function waitFor(predicate, message) {
|
|
12
|
+
const deadline = Date.now() + 1000
|
|
13
|
+
while (Date.now() < deadline) {
|
|
14
|
+
if (predicate()) return
|
|
15
|
+
await new Promise((resolve) => setTimeout(resolve, 5))
|
|
16
|
+
}
|
|
17
|
+
assert.fail(`Timed out waiting for ${message}`)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function createHarness() {
|
|
21
|
+
const [popover, assets] = await Promise.all([
|
|
22
|
+
fs.readFile(popoverPath, "utf8"),
|
|
23
|
+
fs.readFile(assetsPath, "utf8")
|
|
24
|
+
])
|
|
25
|
+
const scriptStart = assets.indexOf("<script>")
|
|
26
|
+
const scriptEnd = assets.lastIndexOf("</script>")
|
|
27
|
+
assert.notEqual(scriptStart, -1)
|
|
28
|
+
assert.notEqual(scriptEnd, -1)
|
|
29
|
+
const script = assets.slice(scriptStart + "<script>".length, scriptEnd)
|
|
30
|
+
|
|
31
|
+
const state = {
|
|
32
|
+
interval: null,
|
|
33
|
+
payload: {
|
|
34
|
+
status: "on",
|
|
35
|
+
apps: [{
|
|
36
|
+
id: "maestro",
|
|
37
|
+
name: "Maestro",
|
|
38
|
+
url: "",
|
|
39
|
+
state: "starting"
|
|
40
|
+
}],
|
|
41
|
+
routes: [{
|
|
42
|
+
name: "Syncthing",
|
|
43
|
+
url: "http://192.168.1.10:42006"
|
|
44
|
+
}]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const virtualConsole = new VirtualConsole()
|
|
49
|
+
virtualConsole.on("jsdomError", (error) => {
|
|
50
|
+
throw error
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const dom = new JSDOM(`<!doctype html><body>${popover}<script>${script}</script></body>`, {
|
|
54
|
+
url: "http://127.0.0.1:42000/",
|
|
55
|
+
runScripts: "dangerously",
|
|
56
|
+
pretendToBeVisual: true,
|
|
57
|
+
virtualConsole,
|
|
58
|
+
beforeParse(window) {
|
|
59
|
+
window.fetch = async () => ({
|
|
60
|
+
ok: true,
|
|
61
|
+
json: async () => JSON.parse(JSON.stringify(state.payload))
|
|
62
|
+
})
|
|
63
|
+
window.setInterval = (callback, delay) => {
|
|
64
|
+
state.interval = { callback, delay }
|
|
65
|
+
return 1
|
|
66
|
+
}
|
|
67
|
+
window.clearInterval = () => {
|
|
68
|
+
state.interval = null
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
await waitFor(
|
|
74
|
+
() => dom.window.document.querySelector('[data-home-server-qr="http://192.168.1.10:42006"]'),
|
|
75
|
+
"initial Home Server content"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return { dom, state }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
test("Home Server polling refreshes content without closing an open QR preview", async (t) => {
|
|
82
|
+
const { dom, state } = await createHarness()
|
|
83
|
+
t.after(() => dom.window.close())
|
|
84
|
+
const { document } = dom.window
|
|
85
|
+
const panel = document.querySelector("[data-home-server-panel]")
|
|
86
|
+
const panelContent = document.querySelector("[data-home-server-content]")
|
|
87
|
+
const trigger = document.querySelector("[data-home-server-trigger]")
|
|
88
|
+
|
|
89
|
+
trigger.click()
|
|
90
|
+
await waitFor(() => state.interval, "Home Server polling")
|
|
91
|
+
|
|
92
|
+
const qrButton = panelContent.querySelector('[data-home-server-qr="http://192.168.1.10:42006"]')
|
|
93
|
+
qrButton.click()
|
|
94
|
+
const preview = panel.querySelector("[data-home-server-qr-preview]")
|
|
95
|
+
assert.ok(preview)
|
|
96
|
+
assert.equal(document.activeElement, preview.querySelector("[data-home-server-qr-close]"))
|
|
97
|
+
|
|
98
|
+
state.payload.routes[0].name = "Syncthing refreshed"
|
|
99
|
+
await state.interval.callback()
|
|
100
|
+
|
|
101
|
+
assert.equal(panel.querySelector("[data-home-server-qr-preview]"), preview)
|
|
102
|
+
assert.equal(qrButton.isConnected, false)
|
|
103
|
+
assert.match(panelContent.textContent, /Syncthing refreshed/)
|
|
104
|
+
|
|
105
|
+
preview.querySelector("[data-home-server-qr-close]").click()
|
|
106
|
+
const refreshedQrButton = panelContent.querySelector('[data-home-server-qr="http://192.168.1.10:42006"]')
|
|
107
|
+
assert.equal(panel.querySelector("[data-home-server-qr-preview]"), null)
|
|
108
|
+
assert.equal(document.activeElement, refreshedQrButton)
|
|
109
|
+
})
|
|
@@ -856,17 +856,21 @@ test("static guard: open without launching disables page-load script frame selec
|
|
|
856
856
|
assert.match(appView, /if \(!target && !automaticSelectionDisabled && preselected && preselected !== devTab\)/)
|
|
857
857
|
})
|
|
858
858
|
|
|
859
|
-
test("static guard: collapsed
|
|
859
|
+
test("static guard: collapsed app sidebar exposes a hover preference", async () => {
|
|
860
860
|
const appView = await fs.readFile(path.resolve(root, "server/views/app.ejs"), "utf8")
|
|
861
861
|
|
|
862
|
-
assert.match(appView, /
|
|
863
|
-
assert.match(appView,
|
|
864
|
-
assert.match(appView, /
|
|
862
|
+
assert.match(appView, /data-app-sidebar-show-on-hover/)
|
|
863
|
+
assert.match(appView, />Show on hover</)
|
|
864
|
+
assert.match(appView, /Show the sidebar when hovering over the left edge/)
|
|
865
|
+
assert.match(appView, /#sidebar-toggle\.sidebar-edge-hint \.sidebar-toggle-glyph/)
|
|
866
|
+
assert.match(appView, /peekTrigger\.addEventListener\("pointerenter", \(\) => \{[\s\S]*if \(showOnHoverEnabled\) \{[\s\S]*setPeeking\(true\)[\s\S]*\} else \{[\s\S]*toggle\.classList\.add\("sidebar-edge-hint"\)/)
|
|
867
|
+
assert.match(appView, /peekTrigger\.addEventListener\("pointerleave", \(\) => \{[\s\S]*toggle\.classList\.remove\("sidebar-edge-hint"\)[\s\S]*closePeekSoon\(\)/)
|
|
868
|
+
assert.doesNotMatch(appView, /shouldAutoShowOnEmptyRunView|hasVisibleBrowserSurface/)
|
|
865
869
|
assert.match(appView, /setCollapsed\(initialCollapsed, \{ persist: false \}\)/)
|
|
866
870
|
assert.match(appView, /aside\.addEventListener\("click", \(event\) => \{[\s\S]*target\.closest\("\.reveal, \.revealer, \[data-app-autolaunch-button\]"\)[\s\S]*window\.setTimeout\(\(\) => setPeeking\(false\), 0\)/)
|
|
867
871
|
})
|
|
868
872
|
|
|
869
|
-
test("app sidebar
|
|
873
|
+
test("app sidebar hover preference gates hover without closing the active peek", async () => {
|
|
870
874
|
const appView = await fs.readFile(path.resolve(root, "server/views/app.ejs"), "utf8")
|
|
871
875
|
const start = appView.indexOf('<script>\n(() => {\n const appcanvas = document.querySelector(".appcanvas")')
|
|
872
876
|
const end = appView.indexOf('</script>\n<script src="/tab-idle-notifier.js"></script>', start)
|
|
@@ -876,23 +880,20 @@ test("app sidebar auto-peeks on collapsed idle run page and dismisses after clic
|
|
|
876
880
|
const sidebarScript = appView
|
|
877
881
|
.slice(start + "<script>\n".length, end)
|
|
878
882
|
.replace(/<%- JSON\.stringify\(typeof name === "string" \? name : ""\) %>/g, '"test-app"')
|
|
879
|
-
.replace(/<%- JSON\.stringify\(type === "run"\) %>/g, "true")
|
|
880
883
|
|
|
881
|
-
const createDom = (
|
|
884
|
+
const createDom = () => {
|
|
882
885
|
return new JSDOM(`<!doctype html>
|
|
883
886
|
<button id="sidebar-toggle"></button>
|
|
884
887
|
<div class="appcanvas vertical">
|
|
885
888
|
<button type="button" data-app-sidebar-peek-trigger></button>
|
|
886
889
|
<aside id="app-sidebar">
|
|
890
|
+
<button type="button" role="switch" aria-checked="true" data-app-sidebar-show-on-hover>Show on hover</button>
|
|
887
891
|
<button type="button" class="reveal" id="sidebar-reveal">Downloads</button>
|
|
888
892
|
<button type="button" class="revealer" id="sidebar-revealer">Changes</button>
|
|
889
893
|
<button type="button" data-app-autolaunch-button id="sidebar-autolaunch">Autolaunch</button>
|
|
890
894
|
<button type="button" id="sidebar-action">Start</button>
|
|
891
895
|
</aside>
|
|
892
|
-
|
|
893
|
-
</div>
|
|
894
|
-
<div id="browserview-network-status" hidden></div>
|
|
895
|
-
<div data-launch-requirements-status hidden></div>`, {
|
|
896
|
+
</div>`, {
|
|
896
897
|
url: "http://127.0.0.1:42000/v/test-app",
|
|
897
898
|
runScripts: "outside-only",
|
|
898
899
|
pretendToBeVisual: true,
|
|
@@ -908,6 +909,8 @@ test("app sidebar auto-peeks on collapsed idle run page and dismisses after clic
|
|
|
908
909
|
|
|
909
910
|
const wait = (ms = 75) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
910
911
|
const storageKey = "pinokio.sidebar-collapsed:test-app"
|
|
912
|
+
const showOnHoverStorageKey = "pinokio.sidebar-show-on-hover"
|
|
913
|
+
const legacyAutoShowStorageKey = "pinokio.sidebar-auto-show"
|
|
911
914
|
|
|
912
915
|
const dom = createDom()
|
|
913
916
|
dom.window.localStorage.setItem(storageKey, "1")
|
|
@@ -916,10 +919,17 @@ test("app sidebar auto-peeks on collapsed idle run page and dismisses after clic
|
|
|
916
919
|
|
|
917
920
|
const appcanvas = dom.window.document.querySelector(".appcanvas")
|
|
918
921
|
const aside = dom.window.document.getElementById("app-sidebar")
|
|
922
|
+
const peekTrigger = dom.window.document.querySelector("[data-app-sidebar-peek-trigger]")
|
|
923
|
+
const sidebarToggle = dom.window.document.getElementById("sidebar-toggle")
|
|
919
924
|
assert.equal(appcanvas.classList.contains("sidebar-collapsed"), true)
|
|
925
|
+
assert.equal(appcanvas.classList.contains("sidebar-peeking"), false)
|
|
926
|
+
assert.equal(aside.getAttribute("aria-hidden"), "true")
|
|
927
|
+
assert.equal(dom.window.localStorage.getItem(storageKey), "1")
|
|
928
|
+
|
|
929
|
+
peekTrigger.dispatchEvent(new dom.window.Event("pointerenter"))
|
|
920
930
|
assert.equal(appcanvas.classList.contains("sidebar-peeking"), true)
|
|
931
|
+
assert.equal(sidebarToggle.classList.contains("sidebar-edge-hint"), false)
|
|
921
932
|
assert.equal(aside.getAttribute("aria-hidden"), "false")
|
|
922
|
-
assert.equal(dom.window.localStorage.getItem(storageKey), "1")
|
|
923
933
|
|
|
924
934
|
aside.querySelector("#sidebar-reveal").dispatchEvent(new dom.window.MouseEvent("click", { bubbles: true }))
|
|
925
935
|
await wait(5)
|
|
@@ -940,15 +950,38 @@ test("app sidebar auto-peeks on collapsed idle run page and dismisses after clic
|
|
|
940
950
|
assert.equal(appcanvas.classList.contains("sidebar-peeking"), false)
|
|
941
951
|
assert.equal(dom.window.localStorage.getItem(storageKey), "1")
|
|
942
952
|
|
|
943
|
-
const
|
|
944
|
-
|
|
945
|
-
|
|
953
|
+
const disabledDom = createDom()
|
|
954
|
+
disabledDom.window.localStorage.setItem(storageKey, "1")
|
|
955
|
+
disabledDom.window.localStorage.setItem(legacyAutoShowStorageKey, "0")
|
|
956
|
+
disabledDom.window.eval(sidebarScript)
|
|
946
957
|
await wait()
|
|
947
958
|
|
|
948
|
-
const
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
959
|
+
const disabledCanvas = disabledDom.window.document.querySelector(".appcanvas")
|
|
960
|
+
const disabledPeekTrigger = disabledDom.window.document.querySelector("[data-app-sidebar-peek-trigger]")
|
|
961
|
+
const showOnHoverToggle = disabledDom.window.document.querySelector("[data-app-sidebar-show-on-hover]")
|
|
962
|
+
const disabledSidebarToggle = disabledDom.window.document.getElementById("sidebar-toggle")
|
|
963
|
+
assert.equal(disabledCanvas.classList.contains("sidebar-collapsed"), true)
|
|
964
|
+
assert.equal(disabledCanvas.classList.contains("sidebar-peeking"), false)
|
|
965
|
+
assert.equal(showOnHoverToggle.getAttribute("aria-checked"), "false")
|
|
966
|
+
|
|
967
|
+
disabledPeekTrigger.dispatchEvent(new disabledDom.window.Event("pointerenter"))
|
|
968
|
+
assert.equal(disabledCanvas.classList.contains("sidebar-peeking"), false)
|
|
969
|
+
assert.equal(disabledSidebarToggle.classList.contains("sidebar-edge-hint"), true)
|
|
970
|
+
|
|
971
|
+
disabledPeekTrigger.dispatchEvent(new disabledDom.window.Event("pointerleave"))
|
|
972
|
+
assert.equal(disabledSidebarToggle.classList.contains("sidebar-edge-hint"), false)
|
|
973
|
+
|
|
974
|
+
disabledPeekTrigger.click()
|
|
975
|
+
assert.equal(disabledCanvas.classList.contains("sidebar-peeking"), true)
|
|
976
|
+
assert.equal(disabledSidebarToggle.classList.contains("sidebar-edge-hint"), false)
|
|
977
|
+
showOnHoverToggle.click()
|
|
978
|
+
assert.equal(showOnHoverToggle.getAttribute("aria-checked"), "true")
|
|
979
|
+
assert.equal(disabledDom.window.localStorage.getItem(showOnHoverStorageKey), "1")
|
|
980
|
+
|
|
981
|
+
showOnHoverToggle.click()
|
|
982
|
+
assert.equal(showOnHoverToggle.getAttribute("aria-checked"), "false")
|
|
983
|
+
assert.equal(disabledDom.window.localStorage.getItem(showOnHoverStorageKey), "0")
|
|
984
|
+
assert.equal(disabledCanvas.classList.contains("sidebar-peeking"), true)
|
|
952
985
|
})
|
|
953
986
|
|
|
954
987
|
test("app identity popover exposes launcher metadata and remote browser link", async () => {
|