mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-10 18:23:44 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c35e376e9 |
@@ -36,7 +36,6 @@
|
|||||||
--glass-border-light: rgba(255, 255, 255, 0.2);
|
--glass-border-light: rgba(255, 255, 255, 0.2);
|
||||||
--glass-shadow: 0 8px 24px 0 rgba(0, 0, 0, 0.35);
|
--glass-shadow: 0 8px 24px 0 rgba(0, 0, 0, 0.35);
|
||||||
--glass-blur: blur(4px);
|
--glass-blur: blur(4px);
|
||||||
--card-scroll-bg: rgba(22, 18, 38, 0.95);
|
|
||||||
--glass-text: #f3f2f8;
|
--glass-text: #f3f2f8;
|
||||||
--glass-accent: #9d72ff;
|
--glass-accent: #9d72ff;
|
||||||
|
|
||||||
@@ -106,7 +105,6 @@
|
|||||||
--surface-container: rgba(255, 255, 255, 0.75);
|
--surface-container: rgba(255, 255, 255, 0.75);
|
||||||
--surface-container-high: rgba(255, 255, 255, 0.95);
|
--surface-container-high: rgba(255, 255, 255, 0.95);
|
||||||
--surface-container-low: rgba(240, 243, 252, 0.75);
|
--surface-container-low: rgba(240, 243, 252, 0.75);
|
||||||
--card-scroll-bg: rgba(245, 246, 252, 0.96);
|
|
||||||
|
|
||||||
--on-surface: #171526;
|
--on-surface: #171526;
|
||||||
--on-surface-variant: #5f5b7d;
|
--on-surface-variant: #5f5b7d;
|
||||||
@@ -188,8 +186,6 @@ body {
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transform: translateZ(0);
|
transform: translateZ(0);
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
isolation: isolate;
|
|
||||||
contain: strict;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#galaxy-bg::before {
|
#galaxy-bg::before {
|
||||||
@@ -208,6 +204,7 @@ body {
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
transform: translate3d(var(--gx-para-x), 0, 0);
|
transform: translate3d(var(--gx-para-x), 0, 0);
|
||||||
|
animation: galaxy-drift-smooth 160s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
#galaxy-bg::after {
|
#galaxy-bg::after {
|
||||||
@@ -224,6 +221,7 @@ body {
|
|||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
transform: translate3d(var(--gx-para-x), 0, 0);
|
transform: translate3d(var(--gx-para-x), 0, 0);
|
||||||
|
animation: galaxy-drift-smooth 100s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
#galaxy-bg .galaxy-hero {
|
#galaxy-bg .galaxy-hero {
|
||||||
@@ -236,6 +234,11 @@ body {
|
|||||||
animation: galaxy-twinkle 6s ease-in-out infinite alternate;
|
animation: galaxy-twinkle 6s ease-in-out infinite alternate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes galaxy-drift-smooth {
|
||||||
|
from { transform: translate3d(0, 0, 0); }
|
||||||
|
to { transform: translate3d(0, -400px, 0); }
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes galaxy-twinkle {
|
@keyframes galaxy-twinkle {
|
||||||
0%, 100% { opacity: 0.3; transform: scale(0.9); }
|
0%, 100% { opacity: 0.3; transform: scale(0.9); }
|
||||||
50% { opacity: 1; transform: scale(1.2); }
|
50% { opacity: 1; transform: scale(1.2); }
|
||||||
@@ -247,11 +250,6 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Keep decorative animation out of modal backdrops and drawer content. */
|
|
||||||
body:has(.gx-scrim, .gx-underlay, .gx-drawer.open) #galaxy-bg .galaxy-hero {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="light"] #galaxy-bg {
|
[data-theme="light"] #galaxy-bg {
|
||||||
background-color: #eef1fb;
|
background-color: #eef1fb;
|
||||||
background-image:
|
background-image:
|
||||||
@@ -308,8 +306,6 @@ ul { list-style: none; margin: 0; padding: 0; }
|
|||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: var(--z-appbar);
|
z-index: var(--z-appbar);
|
||||||
transform: translateZ(0);
|
|
||||||
isolation: isolate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.gx-appbar__pill {
|
.gx-appbar__pill {
|
||||||
@@ -329,8 +325,7 @@ ul { list-style: none; margin: 0; padding: 0; }
|
|||||||
transition: background-color var(--motion-fast), border-color var(--motion-fast);
|
transition: background-color var(--motion-fast), border-color var(--motion-fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
.gx-appbar .gx-appbar__back,
|
.gx-appbar__back {
|
||||||
.gx-appbar .gx-theme-toggle {
|
|
||||||
background: var(--glass-bg);
|
background: var(--glass-bg);
|
||||||
backdrop-filter: var(--glass-blur);
|
backdrop-filter: var(--glass-blur);
|
||||||
-webkit-backdrop-filter: var(--glass-blur);
|
-webkit-backdrop-filter: var(--glass-blur);
|
||||||
@@ -344,8 +339,7 @@ ul { list-style: none; margin: 0; padding: 0; }
|
|||||||
transition: transform var(--motion-fast), background-color var(--motion-fast);
|
transition: transform var(--motion-fast), background-color var(--motion-fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
.gx-appbar .gx-appbar__back:active,
|
.gx-appbar__back:active { transform: scale(0.92); }
|
||||||
.gx-appbar .gx-theme-toggle:active { transform: scale(0.92); }
|
|
||||||
|
|
||||||
.gx-appbar__title {
|
.gx-appbar__title {
|
||||||
background: linear-gradient(135deg, #8b6cc5 0%, #5ec8c8 55%, #d4789c 100%);
|
background: linear-gradient(135deg, #8b6cc5 0%, #5ec8c8 55%, #d4789c 100%);
|
||||||
@@ -364,24 +358,6 @@ ul { list-style: none; margin: 0; padding: 0; }
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gx-appbar__home {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 2px 8px;
|
|
||||||
border-radius: var(--radius-full);
|
|
||||||
transition: background-color var(--motion-fast), transform var(--motion-fast);
|
|
||||||
}
|
|
||||||
|
|
||||||
.gx-appbar__home:hover { background-color: var(--glass-bg-hover); }
|
|
||||||
|
|
||||||
.gx-appbar__home:active { transform: scale(0.96); }
|
|
||||||
|
|
||||||
.gx-appbar__home:focus-visible {
|
|
||||||
outline: 2px solid var(--primary);
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gx-appbar__right {
|
.gx-appbar__right {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -435,6 +411,27 @@ ul { list-style: none; margin: 0; padding: 0; }
|
|||||||
.gx-back-btn { display: none; }
|
.gx-back-btn { display: none; }
|
||||||
.gx-menu-btn { display: inline-flex; }
|
.gx-menu-btn { display: inline-flex; }
|
||||||
|
|
||||||
|
.gx-theme-toggle {
|
||||||
|
background: var(--glass-bg);
|
||||||
|
backdrop-filter: var(--glass-blur);
|
||||||
|
-webkit-backdrop-filter: var(--glass-blur);
|
||||||
|
border: 1px solid var(--glass-border);
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: var(--glass-shadow);
|
||||||
|
color: var(--glass-text);
|
||||||
|
flex: none;
|
||||||
|
height: var(--touch-target);
|
||||||
|
width: var(--touch-target);
|
||||||
|
transition: transform var(--motion-fast), background-color var(--motion-fast), border-color var(--motion-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gx-theme-toggle:active { transform: scale(0.92); }
|
||||||
|
|
||||||
|
[data-theme="light"] .gx-theme-toggle {
|
||||||
|
border: 1.5px solid var(--primary);
|
||||||
|
box-shadow: 0 2px 8px rgba(120, 73, 232, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
.gx-searchwrap {
|
.gx-searchwrap {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -516,10 +513,7 @@ ul { list-style: none; margin: 0; padding: 0; }
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: calc(18px + env(safe-area-inset-bottom, 0px));
|
bottom: calc(18px + env(safe-area-inset-bottom, 0px));
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%) translateZ(0);
|
transform: translateX(-50%);
|
||||||
will-change: transform;
|
|
||||||
isolation: isolate;
|
|
||||||
contain: layout style;
|
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -595,37 +589,7 @@ ul { list-style: none; margin: 0; padding: 0; }
|
|||||||
box-shadow: var(--elev-1);
|
box-shadow: var(--elev-1);
|
||||||
margin-bottom: var(--sp-4);
|
margin-bottom: var(--sp-4);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: transform var(--motion-fast), box-shadow var(--motion-fast), background-color 0.15s ease-out;
|
transition: transform var(--motion-fast), box-shadow var(--motion-fast);
|
||||||
}
|
|
||||||
|
|
||||||
/* Surgical content-visibility for top-level non-collapsible cards */
|
|
||||||
.dh-card:not(.dh-stat),
|
|
||||||
.gx-content > div > .gx-card {
|
|
||||||
content-visibility: auto;
|
|
||||||
contain-intrinsic-size: 280px;
|
|
||||||
contain-intrinsic-size: auto 280px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dh-stat {
|
|
||||||
content-visibility: auto;
|
|
||||||
contain-intrinsic-size: 110px;
|
|
||||||
contain-intrinsic-size: auto 110px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Disarm containment on collapsible sections and nested cards */
|
|
||||||
.gx-card:has(.gx-section__body),
|
|
||||||
.gx-card .gx-card,
|
|
||||||
.gx-tree-node .gx-card {
|
|
||||||
content-visibility: visible !important;
|
|
||||||
contain-intrinsic-size: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Velocity-Adaptive Glass: bypass blur on scrolling cards with contrast boost */
|
|
||||||
body.is-scrolling .gx-card,
|
|
||||||
body.is-scrolling .gx-tile {
|
|
||||||
backdrop-filter: none !important;
|
|
||||||
-webkit-backdrop-filter: none !important;
|
|
||||||
background: var(--card-scroll-bg) !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.gx-unit-note {
|
.gx-unit-note {
|
||||||
@@ -1495,9 +1459,6 @@ input[type="color"].gx-color {
|
|||||||
.gx-fade-enter-active, .gx-fade-leave-active { transition: opacity var(--motion-base); }
|
.gx-fade-enter-active, .gx-fade-leave-active { transition: opacity var(--motion-base); }
|
||||||
.gx-fade-enter-from, .gx-fade-leave-to { opacity: 0; }
|
.gx-fade-enter-from, .gx-fade-leave-to { opacity: 0; }
|
||||||
|
|
||||||
/* Keep the underlay (and star suppression) until the drawer finishes closing. */
|
|
||||||
.gx-underlay.gx-fade-leave-active { transition: opacity var(--motion-slow); }
|
|
||||||
|
|
||||||
.gx-slide-enter-active, .gx-slide-leave-active {
|
.gx-slide-enter-active, .gx-slide-leave-active {
|
||||||
transition: transform var(--motion-base), opacity var(--motion-base);
|
transition: transform var(--motion-base), opacity var(--motion-base);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,71 +90,6 @@ app.mount("#galaxy-app")
|
|||||||
|
|
||||||
initRouter()
|
initRouter()
|
||||||
|
|
||||||
// Disable card blur during document scrolling.
|
|
||||||
;(() => {
|
|
||||||
let timer = null
|
|
||||||
let scrollEnded = true
|
|
||||||
const touches = new Set()
|
|
||||||
const nativeScrollEnd = "onscrollend" in document
|
|
||||||
const body = document.body
|
|
||||||
|
|
||||||
const isModalEvent = (e) => {
|
|
||||||
const t = e.target
|
|
||||||
return t instanceof Element && t.closest(".gx-scrim, .gx-sheet, .gx-dialog, .gx-drawer") !== null
|
|
||||||
}
|
|
||||||
|
|
||||||
const setScrolling = (active) => {
|
|
||||||
if (active) {
|
|
||||||
if (!body.classList.contains("is-scrolling")) body.classList.add("is-scrolling")
|
|
||||||
} else if (!touches.size && body.classList.contains("is-scrolling")) {
|
|
||||||
body.classList.remove("is-scrolling")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const scheduleRestore = () => {
|
|
||||||
clearTimeout(timer)
|
|
||||||
const scrollY = window.scrollY
|
|
||||||
// Finger release and completion notifications can precede the last movement.
|
|
||||||
// Keep glass disabled until the scroll position has also settled.
|
|
||||||
timer = setTimeout(() => {
|
|
||||||
if (window.scrollY !== scrollY) scheduleRestore()
|
|
||||||
else setScrolling(false)
|
|
||||||
}, 120)
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("scroll", () => {
|
|
||||||
scrollEnded = false
|
|
||||||
setScrolling(true)
|
|
||||||
clearTimeout(timer)
|
|
||||||
if (!nativeScrollEnd) scheduleRestore()
|
|
||||||
}, { passive: true })
|
|
||||||
|
|
||||||
document.addEventListener("scrollend", () => {
|
|
||||||
scrollEnded = true
|
|
||||||
scheduleRestore()
|
|
||||||
}, { passive: true })
|
|
||||||
|
|
||||||
window.addEventListener("touchstart", (e) => {
|
|
||||||
if (isModalEvent(e)) return
|
|
||||||
for (const touch of e.changedTouches) touches.add(touch.identifier)
|
|
||||||
}, { passive: true })
|
|
||||||
|
|
||||||
const releaseTouches = (e) => {
|
|
||||||
for (const touch of e.changedTouches) touches.delete(touch.identifier)
|
|
||||||
if (!touches.size && (scrollEnded || !nativeScrollEnd)) scheduleRestore()
|
|
||||||
}
|
|
||||||
window.addEventListener("touchend", releaseTouches, { passive: true })
|
|
||||||
window.addEventListener("touchcancel", releaseTouches, { passive: true })
|
|
||||||
|
|
||||||
window.addEventListener("hashchange", () => {
|
|
||||||
scrollEnded = true
|
|
||||||
touches.clear()
|
|
||||||
clearTimeout(timer)
|
|
||||||
body.classList.remove("is-scrolling")
|
|
||||||
}, { passive: true })
|
|
||||||
})()
|
|
||||||
|
|
||||||
// Layer 2: Ambient Hero Stars Spawner
|
|
||||||
;(() => {
|
;(() => {
|
||||||
const bg = document.getElementById("galaxy-bg")
|
const bg = document.getElementById("galaxy-bg")
|
||||||
if (!bg) return
|
if (!bg) return
|
||||||
|
|||||||
@@ -81,9 +81,6 @@ export const AppShell = {
|
|||||||
bottomNavTo(item) {
|
bottomNavTo(item) {
|
||||||
navigate(item.link)
|
navigate(item.link)
|
||||||
},
|
},
|
||||||
goHome() {
|
|
||||||
navigate("/")
|
|
||||||
},
|
|
||||||
isActive(link) {
|
isActive(link) {
|
||||||
return this.activePath === link || (link !== "/" && this.activePath.startsWith(link))
|
return this.activePath === link || (link !== "/" && this.activePath.startsWith(link))
|
||||||
},
|
},
|
||||||
@@ -105,10 +102,7 @@ export const AppShell = {
|
|||||||
<button type="button" class="gx-icon-btn gx-menu-btn" aria-label="Menu" @click="store.drawerOpen = true">
|
<button type="button" class="gx-icon-btn gx-menu-btn" aria-label="Menu" @click="store.drawerOpen = true">
|
||||||
<i class="bi bi-list"></i>
|
<i class="bi bi-list"></i>
|
||||||
</button>
|
</button>
|
||||||
<span class="gx-appbar__home" role="button" tabindex="0"
|
<span class="gx-appbar__title">Galaxy</span>
|
||||||
aria-label="Galaxy home" @click="goHome" @keydown.enter="goHome" @keydown.space.prevent="goHome">
|
|
||||||
<span class="gx-appbar__title">Galaxy</span>
|
|
||||||
</span>
|
|
||||||
<div class="gx-searchwrap">
|
<div class="gx-searchwrap">
|
||||||
<input ref="searchInput" class="gx-search gx-appbar__search" type="search" placeholder="Search toggles..."
|
<input ref="searchInput" class="gx-search gx-appbar__search" type="search" placeholder="Search toggles..."
|
||||||
v-model="search" aria-label="Search toggles" />
|
v-model="search" aria-label="Search toggles" />
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ export const DevModeBanner = {
|
|||||||
<i class="bi bi-shield-lock gx-alert__icon"></i>
|
<i class="bi bi-shield-lock gx-alert__icon"></i>
|
||||||
<div class="gx-alert__body">
|
<div class="gx-alert__body">
|
||||||
<strong>{{ hiddenCount }} advanced setting{{ hiddenCount !== 1 ? "s" : "" }} hidden.</strong>
|
<strong>{{ hiddenCount }} advanced setting{{ hiddenCount !== 1 ? "s" : "" }} hidden.</strong>
|
||||||
<span>Want more advanced features or missing a few toggles? Enable Developer Mode whenever you’re ready.</span>
|
<span>Advanced features are tucked away until you enable Developer Mode.</span>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="gx-btn gx-btn--tonal" @click="unlock">Go to Developer Tab</button>
|
<button type="button" class="gx-btn gx-btn--tonal" @click="unlock">Enable Developer Mode</button>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ def test_ui_eliminates_slider_toggle_flicker():
|
|||||||
|
|
||||||
def test_ui_developer_mode_banner_offers_unlock():
|
def test_ui_developer_mode_banner_offers_unlock():
|
||||||
banner = _read("js/components/DevModeBanner.js")
|
banner = _read("js/components/DevModeBanner.js")
|
||||||
assert "Go to Developer Tab" in banner
|
assert "Enable Developer Mode" in banner
|
||||||
assert 'navigate("/settings/developer")' in banner
|
assert 'navigate("/settings/developer")' in banner
|
||||||
assert "advanced setting" in banner
|
assert "advanced setting" in banner
|
||||||
|
|
||||||
@@ -295,8 +295,6 @@ def test_ui_has_bottom_navigation_and_drawer():
|
|||||||
assert "gx-appbar" in shell
|
assert "gx-appbar" in shell
|
||||||
assert "Search toggles" in shell
|
assert "Search toggles" in shell
|
||||||
assert ">Galaxy</span>" in shell
|
assert ">Galaxy</span>" in shell
|
||||||
assert "gx-appbar__home" in shell
|
|
||||||
assert "goHome" in shell and 'navigate("/")' in shell
|
|
||||||
|
|
||||||
|
|
||||||
def test_ui_search_visible_on_mobile_and_content_full_width():
|
def test_ui_search_visible_on_mobile_and_content_full_width():
|
||||||
@@ -540,110 +538,6 @@ def test_ui_mobile_polish_regressions():
|
|||||||
assert 'tr("Longitudinal Maneuver Mode")' not in c4_developer
|
assert 'tr("Longitudinal Maneuver Mode")' not in c4_developer
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("native_scrollend", [False, True])
|
|
||||||
def test_scroll_coordinator_gesture_lifecycle(native_scrollend):
|
|
||||||
node = _node_exe()
|
|
||||||
if node is None:
|
|
||||||
pytest.skip("no node.js runtime available")
|
|
||||||
script = r"""
|
|
||||||
const assert = require('node:assert/strict');
|
|
||||||
const fs = require('node:fs');
|
|
||||||
const vm = require('node:vm');
|
|
||||||
const native = process.argv[2] === 'true';
|
|
||||||
const source = fs.readFileSync(process.argv[1], 'utf8');
|
|
||||||
const handlers = { document: {}, window: {} };
|
|
||||||
const classes = new Set();
|
|
||||||
const timers = new Map();
|
|
||||||
let nextTimer = 0;
|
|
||||||
const window = {
|
|
||||||
scrollY: 0,
|
|
||||||
addEventListener: (name, fn) => { handlers.window[name] = fn; },
|
|
||||||
};
|
|
||||||
class Element {
|
|
||||||
constructor(modal = false) { this.modal = modal; }
|
|
||||||
closest() { return this.modal ? this : null; }
|
|
||||||
}
|
|
||||||
const document = {
|
|
||||||
body: { classList: {
|
|
||||||
contains: k => classes.has(k), add: k => classes.add(k), remove: k => classes.delete(k),
|
|
||||||
} },
|
|
||||||
addEventListener: (name, fn) => { handlers.document[name] = fn; },
|
|
||||||
};
|
|
||||||
if (native) document.onscrollend = null;
|
|
||||||
vm.runInNewContext(source.slice(source.indexOf('// Disable card blur'), source.indexOf('// Layer 2')), {
|
|
||||||
document, Element,
|
|
||||||
window,
|
|
||||||
setTimeout: fn => { timers.set(++nextTimer, fn); return nextTimer; },
|
|
||||||
clearTimeout: id => timers.delete(id),
|
|
||||||
});
|
|
||||||
const fire = (scope, name, ids = [], modal = false) => handlers[scope][name]?.({
|
|
||||||
target: new Element(modal), changedTouches: ids.map(identifier => ({ identifier })),
|
|
||||||
});
|
|
||||||
const tick = () => { const pending = [...timers.values()]; timers.clear(); pending.forEach(fn => fn()); };
|
|
||||||
const active = () => classes.has('is-scrolling');
|
|
||||||
// Inertial movement can continue between delivered scroll events after release.
|
|
||||||
fire('window', 'touchstart', [10]);
|
|
||||||
fire('document', 'scroll');
|
|
||||||
fire('window', 'touchend', [10]);
|
|
||||||
if (native) fire('document', 'scrollend');
|
|
||||||
window.scrollY = 100;
|
|
||||||
tick();
|
|
||||||
assert.equal(active(), true, 'finger release must not end momentum scrolling');
|
|
||||||
window.scrollY = 160;
|
|
||||||
tick();
|
|
||||||
assert.equal(active(), true, 'continued movement must keep blur disabled');
|
|
||||||
tick();
|
|
||||||
assert.equal(active(), false, 'restore after completion and a stable position');
|
|
||||||
fire('document', 'scrollend');
|
|
||||||
fire('window', 'wheel');
|
|
||||||
assert.equal(active(), false, 'wheel without document movement');
|
|
||||||
fire('window', 'touchstart', [1, 2]);
|
|
||||||
fire('document', 'scroll');
|
|
||||||
fire('window', 'touchend', [1]);
|
|
||||||
fire('window', 'touchstart', [3], true);
|
|
||||||
fire('window', 'touchend', [3], true);
|
|
||||||
tick();
|
|
||||||
assert.equal(active(), true, 'remaining page finger holds blur disabled');
|
|
||||||
fire('window', 'touchcancel', [2]);
|
|
||||||
tick();
|
|
||||||
assert.equal(active(), native, 'native completion must be authoritative');
|
|
||||||
fire('document', 'scrollend');
|
|
||||||
tick();
|
|
||||||
assert.equal(active(), false);
|
|
||||||
// More scroll events after a completion signal invalidate its pending restore.
|
|
||||||
fire('document', 'scroll');
|
|
||||||
fire('document', 'scrollend');
|
|
||||||
fire('document', 'scroll');
|
|
||||||
tick();
|
|
||||||
assert.equal(active(), native, 'new scrolling cancels the previous native completion');
|
|
||||||
fire('document', 'scrollend');
|
|
||||||
tick();
|
|
||||||
assert.equal(active(), false);
|
|
||||||
fire('window', 'touchstart', [4]);
|
|
||||||
fire('document', 'scroll');
|
|
||||||
fire('document', 'scrollend');
|
|
||||||
assert.equal(active(), true, 'hold survives completion');
|
|
||||||
fire('window', 'touchend', [4]);
|
|
||||||
tick();
|
|
||||||
assert.equal(active(), false, 'release after completion cannot leave state stuck');
|
|
||||||
fire('document', 'scroll');
|
|
||||||
fire('window', 'hashchange');
|
|
||||||
tick();
|
|
||||||
assert.equal(active(), false, 'navigation clears pending gesture');
|
|
||||||
fire('document', 'scroll');
|
|
||||||
tick();
|
|
||||||
assert.equal(active(), native, 'fallback only on unsupported browsers');
|
|
||||||
fire('document', 'scrollend');
|
|
||||||
tick();
|
|
||||||
assert.equal(active(), false);
|
|
||||||
"""
|
|
||||||
result = subprocess.run(
|
|
||||||
[node, "-e", script, str(UI_ROOT / "js/app.js"), str(native_scrollend).lower()],
|
|
||||||
capture_output=True, text=True,
|
|
||||||
)
|
|
||||||
assert result.returncode == 0, result.stdout + result.stderr
|
|
||||||
|
|
||||||
|
|
||||||
def _node_exe():
|
def _node_exe():
|
||||||
candidates = [
|
candidates = [
|
||||||
shutil.which("node"),
|
shutil.which("node"),
|
||||||
|
|||||||
@@ -7,12 +7,9 @@ from openpilot.common.swaglog import cloudlog
|
|||||||
from openpilot.common.pid import PIDController
|
from openpilot.common.pid import PIDController
|
||||||
from openpilot.system.hardware import HARDWARE
|
from openpilot.system.hardware import HARDWARE
|
||||||
|
|
||||||
# comma 3/3X (tici/tizi) run a more aggressive, cooler-targeting curve than comma 4 (mici)
|
# raise fan setpoint on tici/tizi to reduce noise
|
||||||
IS_MICI = HARDWARE.get_device_type() == "mici"
|
# after raising LMH threshold in AGNOS 18.1 to prevent CPU throttling
|
||||||
OFFSET = 0 if IS_MICI else -5
|
OFFSET = 0 if HARDWARE.get_device_type() == "mici" else 5
|
||||||
K_P = 0 if IS_MICI else 1.0
|
|
||||||
FF_LOW = 60.0 if IS_MICI else 55.0
|
|
||||||
FF_HIGH = 100.0 if IS_MICI else 80.0
|
|
||||||
|
|
||||||
class BaseFanController(ABC):
|
class BaseFanController(ABC):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@@ -26,7 +23,7 @@ class TiciFanController(BaseFanController):
|
|||||||
cloudlog.info("Setting up TICI fan handler")
|
cloudlog.info("Setting up TICI fan handler")
|
||||||
|
|
||||||
self.last_ignition = False
|
self.last_ignition = False
|
||||||
self.controller = PIDController(k_p=K_P, k_i=4e-3, rate=(1 / DT_HW))
|
self.controller = PIDController(k_p=0, k_i=4e-3, rate=(1 / DT_HW))
|
||||||
|
|
||||||
def update(self, cur_temp: float, ignition: bool) -> int:
|
def update(self, cur_temp: float, ignition: bool) -> int:
|
||||||
self.controller.pos_limit = 100 if ignition else 30
|
self.controller.pos_limit = 100 if ignition else 30
|
||||||
@@ -38,7 +35,7 @@ class TiciFanController(BaseFanController):
|
|||||||
error = cur_temp - (75 + OFFSET)
|
error = cur_temp - (75 + OFFSET)
|
||||||
fan_pwr_out = int(self.controller.update(
|
fan_pwr_out = int(self.controller.update(
|
||||||
error=error,
|
error=error,
|
||||||
feedforward=np.interp(cur_temp, [FF_LOW, FF_HIGH], [0, 100])
|
feedforward=np.interp(cur_temp, [60.0 + OFFSET, 100.0 + OFFSET], [0, 100])
|
||||||
))
|
))
|
||||||
|
|
||||||
self.last_ignition = ignition
|
self.last_ignition = ignition
|
||||||
|
|||||||
Reference in New Issue
Block a user