mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-11 10:43:46 +08:00
Galaxy: tighten shared action picker integration
Clean up imported picker commentary and update Galaxy tests for the unified favourites/controller catalogue.
This commit is contained in:
committed by
firestar5683
parent
d8a4be7e98
commit
1b1989f794
@@ -594,8 +594,6 @@ class FavoriteRadialMenu:
|
||||
|
||||
def _open_picker(self, slot_index: int) -> None:
|
||||
options = self._refresh_option_catalog()
|
||||
# This on-road picker has no numeric editor. Keep speed actions in the
|
||||
# catalogue for activating saved slots, but configure their value in Galaxy.
|
||||
self._picker_options = [option for option in (options or []) if option.get("value_type") != "speed"]
|
||||
self._selected_slot = slot_index
|
||||
self._editing_slot = None
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { longitudinalModeLayout, LONGITUDINAL_MODE_KEY } from "./longitudinal_mode.mjs"
|
||||
|
||||
// Presentation-only: the caller owns eligibility, persistence and speed values.
|
||||
const stylesheet = new URL("./controller_action_picker.css", import.meta.url).href
|
||||
|
||||
// Use the same catalogue as both Toggles menus, not the API's alphabetic order.
|
||||
const layoutUrl = "/assets/components/tools/device_settings_layout.json?v=settings-tier-1"
|
||||
|
||||
function settingsAnchor(key) {
|
||||
@@ -75,8 +73,6 @@ export function filterActions(options, query = "", category = "", layout = []) {
|
||||
option.picker_description, actionCategory(option, layout), ...actionHierarchy(option, layout).map(part => part.label)].join(" ").toLocaleLowerCase().includes(word)))
|
||||
}
|
||||
|
||||
// Native modal supplies focus containment, inert background and Escape handling.
|
||||
// Read options/guard/selection live: polling may change availability while open.
|
||||
export function openControllerActionPicker({ theme = "classic", index, trigger, getOptions, getSlot, isDisabled, onSelect, title = `Controller Action #${index + 1}`, slotAttribute = "data-controller-action-slot", noun = "actions" }) {
|
||||
if (isDisabled()) return () => {}
|
||||
if (!document.querySelector('link[data-controller-picker]')) {
|
||||
@@ -116,7 +112,6 @@ export function openControllerActionPicker({ theme = "classic", index, trigger,
|
||||
let closed = false
|
||||
let timer
|
||||
const fitViewport = () => {
|
||||
// CSS zoom scales vh units too. Bound the modal in unzoomed layout pixels.
|
||||
let zoom = 1
|
||||
for (let node = dialog; node; node = node.parentElement) zoom *= Number.parseFloat(getComputedStyle(node).zoom) || 1
|
||||
const viewport = window.visualViewport
|
||||
@@ -131,7 +126,6 @@ export function openControllerActionPicker({ theme = "classic", index, trigger,
|
||||
window.visualViewport?.removeEventListener("resize", fitViewport)
|
||||
dialog.close()
|
||||
dialog.remove()
|
||||
// Classic polling can replace the original card while the modal is open.
|
||||
const target = trigger?.isConnected ? trigger : document.querySelector(`[${slotAttribute}="${index}"]`)
|
||||
target?.focus({ preventScroll: true })
|
||||
}
|
||||
@@ -186,7 +180,6 @@ export function openControllerActionPicker({ theme = "classic", index, trigger,
|
||||
container.append(button)
|
||||
if (focusedKey === option.key) button.focus({ preventScroll: true })
|
||||
}
|
||||
// The original empty option remains available independently of filters.
|
||||
add({ key: "", label: "Not configured", description: "Clear this assignment" }, "")
|
||||
if (grouped) {
|
||||
for (const name of categories) {
|
||||
@@ -235,7 +228,6 @@ export function openControllerActionPicker({ theme = "classic", index, trigger,
|
||||
}
|
||||
}
|
||||
input.addEventListener("input", () => {
|
||||
// Search always covers the complete eligible catalogue, not the last category.
|
||||
category.value = ""
|
||||
render()
|
||||
results.scrollTop = 0
|
||||
@@ -248,8 +240,6 @@ export function openControllerActionPicker({ theme = "classic", index, trigger,
|
||||
render()
|
||||
dialog.showModal()
|
||||
input.focus()
|
||||
// A missing layout must never hide eligible actions or block assignment.
|
||||
// Retry on the next open; don't cache a transient failure or stale catalogue.
|
||||
fetch(layoutUrl, { cache: "no-store" }).then(response => {
|
||||
if (!response.ok) throw new Error("Settings catalogue unavailable")
|
||||
return response.json()
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// Run with node; optional source-derived full option fixture from browser test.
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import { actionCategory, actionCategories, filterActions } from '../assets/components/tools/controller_action_picker.js'
|
||||
|
||||
@@ -287,7 +287,7 @@ def _install_server_import_stubs():
|
||||
sync_persist_chill_state=lambda *args, **kwargs: None,
|
||||
sync_persist_experimental_state=lambda *args, **kwargs: None,
|
||||
)
|
||||
def _trigger_stub_favorite_action(key, params_memory=None):
|
||||
def _trigger_stub_favorite_action(key, params_memory=None, **_kwargs):
|
||||
if params_memory is None:
|
||||
return False
|
||||
counter_key = (
|
||||
@@ -406,6 +406,7 @@ def _install_server_import_stubs():
|
||||
sys.modules["openpilot.starpilot.system.wheel_controls"] = _simple_module(
|
||||
"openpilot.starpilot.system.wheel_controls",
|
||||
CONTROLLER_ACTION_OPTIONS=(
|
||||
{"key": "__starpilot_controller_action__:cycle_driving_personality", "label": "Cycle Driving Personality", "section": "Controller Actions"},
|
||||
{"key": "__starpilot_controller_action__:set_speed", "label": "Set Speed To", "section": "Controller Actions", "value_type": "speed"},
|
||||
{"key": "__starpilot_controller_action__:selfie", "label": "Take Comma Selfie", "section": "Controller Actions"},
|
||||
{"key": "__starpilot_controller_action__:bookmark", "label": "Bookmark", "section": "Controller Actions"},
|
||||
|
||||
@@ -219,17 +219,7 @@ def test_wheel_controls_status_includes_favorite_slots(monkeypatch):
|
||||
assert len(response.get_json()["slots"]) == 3
|
||||
assert len(response.get_json()["controller_slots"]) == 10
|
||||
option_keys = {option["key"] for option in response.get_json()["controller_options"]}
|
||||
assert option_keys == {
|
||||
"ForceOffroad",
|
||||
"__starpilot_controller_action__:set_speed",
|
||||
"__starpilot_controller_action__:selfie",
|
||||
"__starpilot_controller_action__:bookmark",
|
||||
"__starpilot_controller_action__:pulse_and_glide",
|
||||
"__starpilot_controller_action__:force_coast",
|
||||
"__starpilot_controller_action__:toggle_aol",
|
||||
"__starpilot_controller_action__:engage_openpilot",
|
||||
"__starpilot_controller_action__:disengage_openpilot",
|
||||
}
|
||||
assert option_keys == {"ForceOffroad"}
|
||||
assert response.get_json()["speed_unit"] == "mph"
|
||||
assert response.get_json()["disconnect_controllers_offroad"] is False
|
||||
|
||||
@@ -261,24 +251,16 @@ def test_wheel_controls_configures_a_controller_only_action(monkeypatch):
|
||||
response = client.post("/api/wheel-controls/action", json={"slot": 9, "key": "ForceOffroad"})
|
||||
|
||||
assert response.status_code == 200
|
||||
expected_keys = {
|
||||
"ForceOffroad",
|
||||
"__starpilot_controller_action__:set_speed",
|
||||
"__starpilot_controller_action__:selfie",
|
||||
"__starpilot_controller_action__:bookmark",
|
||||
"__starpilot_controller_action__:pulse_and_glide",
|
||||
"__starpilot_controller_action__:force_coast",
|
||||
"__starpilot_controller_action__:toggle_aol",
|
||||
"__starpilot_controller_action__:engage_openpilot",
|
||||
"__starpilot_controller_action__:disengage_openpilot",
|
||||
}
|
||||
expected_keys = {"ForceOffroad"}
|
||||
assert calls == [((9, "ForceOffroad", "Force Offroad", the_galaxy.params), {"value": None, "eligible_keys": expected_keys})]
|
||||
|
||||
|
||||
def test_wheel_controls_configures_set_speed_in_current_units(monkeypatch):
|
||||
client, _ = _params_client(monkeypatch, {"IsOffroad": True, "IsMetric": False}, "mici")
|
||||
calls = []
|
||||
monkeypatch.setattr(the_galaxy, "_get_available_favorite_slot_options", list)
|
||||
monkeypatch.setattr(the_galaxy, "_get_available_controller_action_options", lambda: [
|
||||
{"key": "__starpilot_controller_action__:set_speed", "label": "Set Speed To", "value_type": "speed"},
|
||||
])
|
||||
monkeypatch.setattr(the_galaxy, "set_controller_action_slot", lambda *args, **kwargs: calls.append((args, kwargs)))
|
||||
|
||||
response = client.post("/api/wheel-controls/action", json={
|
||||
|
||||
@@ -438,7 +438,6 @@ def cycle_driving_personality(params: Params) -> bool:
|
||||
if params.get_bool("SafeMode"):
|
||||
return False
|
||||
try:
|
||||
# Match native UI capability selection; never use parked CP while on-road.
|
||||
cp_bytes = params.get("CarParams" if params.get_bool("IsOnroad") else "CarParamsPersistent")
|
||||
if not cp_bytes:
|
||||
return False
|
||||
@@ -447,8 +446,6 @@ def cycle_driving_personality(params: Params) -> bool:
|
||||
if not available:
|
||||
return False
|
||||
|
||||
# Typed Params decoding truncates fractional ints and defaults corrupt text.
|
||||
# Inspect the stored token so malformed selections cannot become Aggressive.
|
||||
current = Path(params.get_param_path("LongitudinalPersonality")).read_bytes()
|
||||
profiles = tuple(int(profile) for profile in (
|
||||
log.LongitudinalPersonality.aggressive,
|
||||
@@ -460,14 +457,10 @@ def cycle_driving_personality(params: Params) -> bool:
|
||||
return False
|
||||
next_personality = profiles[(tokens.index(current) + 1) % len(profiles)]
|
||||
except Exception:
|
||||
# Missing/malformed CP or selection: do not change effective driving state.
|
||||
return False
|
||||
|
||||
if params.get_bool("SafeMode"):
|
||||
return False
|
||||
# Synchronous persistence lets successive HID events see the preceding change.
|
||||
# selfdrived's existing Params reader publishes this selection; Traffic retains
|
||||
# its independent override. No toggle refresh or virtual speed input is needed.
|
||||
params.put_int("LongitudinalPersonality", next_personality)
|
||||
return True
|
||||
|
||||
|
||||
Reference in New Issue
Block a user