Compare commits

...

1 Commits

Author SHA1 Message Date
firestar5683 1c35e376e9 uno mas lil dip 2026-09-08 21:15:28 -05:00
6 changed files with 67 additions and 53 deletions
@@ -1,15 +1,35 @@
import { BluetoothPanel } from "../components/BluetoothPanel.js" import { BluetoothPanel } from "../components/BluetoothPanel.js"
import { WheelControls } from "../components/WheelControls.js"
import { GalaxySection } from "../components/GalaxySection.js" import { GalaxySection } from "../components/GalaxySection.js"
import { GalaxyTabs } from "../components/GalaxyTabs.js"
import { useTabRouting } from "../composables.js"
const TABS = {
bluetooth: "Bluetooth",
controllers: "Controllers",
}
export const Bluetooth = { export const Bluetooth = {
name: "Bluetooth", name: "Bluetooth",
components: { BluetoothPanel, GalaxySection }, components: { BluetoothPanel, WheelControls, GalaxySection, GalaxyTabs },
setup() {
return useTabRouting("/bluetooth", { bluetooth: "bluetooth", controllers: "controllers" })
},
data() { return { TABS } },
template: ` template: `
<div class="gx-view"> <div class="gx-view">
<h2 style="margin-top:0;">Bluetooth</h2> <h2 style="margin-top:0;">Bluetooth</h2>
<GalaxySection title="Bluetooth Devices" icon="bi-bluetooth" :collapsible="false"> <GalaxyTabs :items="TABS" :active="tab" @select="selectTab" />
<BluetoothPanel />
</GalaxySection> <template v-if="tab === 'bluetooth'">
<GalaxySection title="Bluetooth Devices" icon="bi-bluetooth" :collapsible="false">
<BluetoothPanel />
</GalaxySection>
</template>
<template v-else>
<WheelControls />
</template>
</div> </div>
`, `,
} }
@@ -4,11 +4,11 @@ import { PwaInstallSection, isFirestarOrigin } from "../components/PwaInstallSec
const isTunnel = () => isFirestarOrigin() const isTunnel = () => isFirestarOrigin()
function localDeviceUrl(ip) { function localDeviceUrl(ip, route = "/") {
const raw = String(ip || "").trim() const raw = String(ip || "").trim()
if (!raw || raw === "unknown") return "" if (!raw || raw === "unknown") return ""
const host = raw.includes(":") && !raw.startsWith("[") ? `[${raw}]` : raw const host = raw.includes(":") && !raw.startsWith("[") ? `[${raw}]` : raw
return `http://${host}:8082` return `http://${host}:8082/#${route}`
} }
export const Galaxy = { export const Galaxy = {
@@ -28,7 +28,7 @@ export const Galaxy = {
if (this.isTunnel) { if (this.isTunnel) {
try { try {
const status = await api.getDeviceStatus() const status = await api.getDeviceStatus()
this.localUrl = localDeviceUrl(status?.lanIp) this.localUrl = localDeviceUrl(status?.lanIp, "/galaxy")
} catch (e) {} } catch (e) {}
return return
} }
@@ -92,7 +92,10 @@ export const Galaxy = {
<strong>Galaxy Pairing Unavailable via Galaxy</strong> <strong>Galaxy Pairing Unavailable via Galaxy</strong>
<span> <span>
Galaxy pairing requires a direct connection. If you are on the same local network, connect here: Galaxy pairing requires a direct connection. If you are on the same local network, connect here:
<a v-if="localUrl" :href="localUrl" style="color:inherit; font-weight:var(--fw-bold); overflow-wrap:anywhere;">{{ localUrl }}</a> <br />
<a v-if="localUrl" class="gx-btn gx-btn--tonal" :href="localUrl" style="margin-top:var(--sp-3);">
<i class="bi bi-box-arrow-up-right"></i> Open Galaxy Locally
</a>
<span v-else>your device's local IP on port 8082.</span> <span v-else>your device's local IP on port 8082.</span>
</span> </span>
</div> </div>
@@ -52,11 +52,11 @@ function normalizeRoute(r) {
} }
} }
function localDeviceUrl(ip) { function localDeviceUrl(ip, route = "/") {
const raw = String(ip || "").trim() const raw = String(ip || "").trim()
if (!raw || raw === "unknown") return "" if (!raw || raw === "unknown") return ""
const host = raw.includes(":") && !raw.startsWith("[") ? `[${raw}]` : raw const host = raw.includes(":") && !raw.startsWith("[") ? `[${raw}]` : raw
return `http://${host}:8082` return `http://${host}:8082/#${route}`
} }
export const Recordings = { export const Recordings = {
@@ -346,7 +346,7 @@ export const Recordings = {
if (this.onFirestar) { if (this.onFirestar) {
try { try {
const status = await api.getDeviceStatus() const status = await api.getDeviceStatus()
this.localUrl = localDeviceUrl(status?.lanIp) this.localUrl = localDeviceUrl(status?.lanIp, "/recordings")
} catch (e) {} } catch (e) {}
return return
} }
@@ -520,7 +520,10 @@ export const Recordings = {
<GxNotice v-else tone="info" icon="bi-satellite" title="Recordings unavailable via Galaxy"> <GxNotice v-else tone="info" icon="bi-satellite" title="Recordings unavailable via Galaxy">
Recordings are unavailable via Galaxy for bandwidth reasons. If you are on the same local network, connect here: Recordings are unavailable via Galaxy for bandwidth reasons. If you are on the same local network, connect here:
<a v-if="localUrl" :href="localUrl" style="color:inherit; font-weight:var(--fw-bold); overflow-wrap:anywhere;">{{ localUrl }}</a> <br />
<a v-if="localUrl" class="gx-btn gx-btn--tonal" :href="localUrl" style="margin-top:var(--sp-3);">
<i class="bi bi-box-arrow-up-right"></i> Open Recordings Locally
</a>
<span v-else>your device's local IP on port 8082.</span> <span v-else>your device's local IP on port 8082.</span>
</GxNotice> </GxNotice>
</div> </div>
@@ -11,7 +11,7 @@ const TOOLS = [
{ name: "System Tools", link: "/system", icon: "bi-arrow-repeat", desc: "Backup, restore, updates" }, { name: "System Tools", link: "/system", icon: "bi-arrow-repeat", desc: "Backup, restore, updates" },
{ name: "Theme Maker", link: "/theme_maker", icon: "bi-palette-fill", desc: "Customize the look" }, { name: "Theme Maker", link: "/theme_maker", icon: "bi-palette-fill", desc: "Customize the look" },
{ name: "Tuning, Plots & Testing", link: "/tuning", icon: "bi-sign-turn-right", desc: "Steering & speed tuning, live plots, testing grounds" }, { name: "Tuning, Plots & Testing", link: "/tuning", icon: "bi-sign-turn-right", desc: "Steering & speed tuning, live plots, testing grounds" },
{ name: "Vehicle Controls", link: "/vehicle", icon: "bi-car-front", desc: "Controllers & vehicle features" }, { name: "Vehicle Controls", link: "/vehicle", icon: "bi-car-front", desc: "Vehicle features" },
].sort((a, b) => a.name.localeCompare(b.name)) ].sort((a, b) => a.name.localeCompare(b.name))
export const Tools = { export const Tools = {
@@ -1,34 +1,22 @@
import { api, showSnackbar } from "../api.js" import { api, showSnackbar } from "../api.js"
import { navigate, toolHref } from "../store.js" import { navigate, toolHref } from "../store.js"
import { WheelControls } from "../components/WheelControls.js"
import { GalaxySection } from "../components/GalaxySection.js" import { GalaxySection } from "../components/GalaxySection.js"
import { GalaxyTabs } from "../components/GalaxyTabs.js"
import { useTabRouting } from "../composables.js"
const FEATURES = [ const FEATURES = [
{ key: "doors", name: "Lock/Unlock Doors", icon: "bi-door-closed", desc: "Send lock or unlock commands remotely to your vehicle.", embed: "/manage_doors" }, { key: "doors", name: "Lock/Unlock Doors", icon: "bi-door-closed", desc: "Send lock or unlock commands remotely to your vehicle.", embed: "/manage_doors" },
{ key: "tsk", name: "Toyota Security Keys", icon: "bi-key-fill", desc: "Manage and apply security keys for secOC protected devices.", embed: "/manage_tsk" }, { key: "tsk", name: "Toyota Security Keys", icon: "bi-key-fill", desc: "Manage and apply security keys for secOC protected devices.", embed: "/manage_tsk" },
] ]
const TABS = {
controllers: "Controllers",
features: "Vehicle Features",
}
export const Vehicle = { export const Vehicle = {
name: "Vehicle", name: "Vehicle",
components: { WheelControls, GalaxySection, GalaxyTabs }, components: { GalaxySection },
data() { data() {
return { return {
TABS,
features: FEATURES, features: FEATURES,
featureStatus: {}, featureStatus: {},
busy: "", busy: "",
} }
}, },
setup() {
return useTabRouting("/vehicle", { controllers: "controllers", features: "features" })
},
computed: { computed: {
featureList() { return this.features }, featureList() { return this.features },
}, },
@@ -63,30 +51,22 @@ export const Vehicle = {
<div class="gx-view"> <div class="gx-view">
<h2 style="margin-top:0;">Vehicle Controls</h2> <h2 style="margin-top:0;">Vehicle Controls</h2>
<GalaxyTabs :items="TABS" :active="tab" @select="selectTab" /> <GalaxySection title="Vehicle Features" icon="bi-check2-square">
<div style="padding: var(--sp-3); display:grid; gap:8px;">
<template v-if="tab === 'controllers'"> <button v-for="f in featureList" :key="f.key" type="button"
<WheelControls /> class="gx-row" style="width:100%; border:none; background:transparent; color:inherit; cursor:pointer; text-align:left;"
</template> @click="openFeature(f)">
<div class="gx-row__info">
<template v-else> <span class="gx-row__label"><i class="bi" :class="f.icon" style="margin-right:6px; color:var(--primary);"></i>{{ f.name }}</span>
<GalaxySection title="Vehicle Features" icon="bi-check2-square"> <span class="gx-row__desc">{{ f.desc }}</span>
<div style="padding: var(--sp-3); display:grid; gap:8px;"> </div>
<button v-for="f in featureList" :key="f.key" type="button" <span v-if="busy === f.key" class="gx-chip" style="background:var(--surface-variant);">Checking...</span>
class="gx-row" style="width:100%; border:none; background:transparent; color:inherit; cursor:pointer; text-align:left;" <span v-else-if="statusOf(f.key) === 'denied'" class="gx-chip" style="background:var(--error);">Not supported</span>
@click="openFeature(f)"> <i v-else class="bi bi-chevron-right" style="color:var(--text-muted);"></i>
<div class="gx-row__info"> </button>
<span class="gx-row__label"><i class="bi" :class="f.icon" style="margin-right:6px; color:var(--primary);"></i>{{ f.name }}</span> <p style="color:var(--text-muted); margin:0;">These features verify vehicle compatibility when launched.</p>
<span class="gx-row__desc">{{ f.desc }}</span> </div>
</div> </GalaxySection>
<span v-if="busy === f.key" class="gx-chip" style="background:var(--surface-variant);">Checking...</span>
<span v-else-if="statusOf(f.key) === 'denied'" class="gx-chip" style="background:var(--error);">Not supported</span>
<i v-else class="bi bi-chevron-right" style="color:var(--text-muted);"></i>
</button>
<p style="color:var(--text-muted); margin:0;">These features verify vehicle compatibility when launched.</p>
</div>
</GalaxySection>
</template>
</div> </div>
`, `,
} }
@@ -126,8 +126,8 @@ def test_ui_ports_all_tool_views():
assert ep in src, f"{rel} should use api.{ep}" assert ep in src, f"{rel} should use api.{ep}"
vehicle = _read("js/views/Vehicle.js") vehicle = _read("js/views/Vehicle.js")
bluetooth = _read("js/views/Bluetooth.js") bluetooth = _read("js/views/Bluetooth.js")
assert "WheelControls" in vehicle and "BluetoothPanel" not in vehicle and "carFeaturesCheck" in vehicle assert "WheelControls" not in vehicle and "BluetoothPanel" not in vehicle and "carFeaturesCheck" in vehicle
assert "BluetoothPanel" in bluetooth assert "BluetoothPanel" in bluetooth and "WheelControls" in bluetooth
def test_ui_routes_ported_views_natively_no_classic_fallback(): def test_ui_routes_ported_views_natively_no_classic_fallback():
@@ -233,9 +233,13 @@ def test_ui_schema_driven_param_engine_reused():
assert "GalaxyEmbed" not in tuning and 'src="/tuning"' not in tuning, "Tuning must be native, not a classic embed" assert "GalaxyEmbed" not in tuning and 'src="/tuning"' not in tuning, "Tuning must be native, not a classic embed"
assert "LateralTuningPanel" in tuning and "LongitudinalManeuvers" not in tuning assert "LateralTuningPanel" in tuning and "LongitudinalManeuvers" not in tuning
vehicle = _read("js/views/Vehicle.js") vehicle = _read("js/views/Vehicle.js")
bluetooth = _read("js/views/Bluetooth.js")
assert "ParamSections" not in vehicle, "Vehicle must not render redundant toggles" assert "ParamSections" not in vehicle, "Vehicle must not render redundant toggles"
assert "WheelControls" in vehicle and "BluetoothPanel" not in vehicle assert "WheelControls" not in vehicle and "BluetoothPanel" not in vehicle
assert "GalaxySection" in vehicle assert "GalaxySection" in vehicle
assert "WheelControls" in bluetooth and "BluetoothPanel" in bluetooth
assert bluetooth.index('bluetooth: "Bluetooth"') < bluetooth.index('controllers: "Controllers"')
assert 'useTabRouting("/bluetooth"' in bluetooth
engine = _read("js/components/ParamSections.js") engine = _read("js/components/ParamSections.js")
assert "SettingTree" in engine assert "SettingTree" in engine
assert "isSettingVisible" in engine assert "isSettingVisible" in engine
@@ -517,6 +521,10 @@ def test_ui_mobile_polish_regressions():
assert "bandwidth reasons" in recordings and "status?.lanIp" in recordings assert "bandwidth reasons" in recordings and "status?.lanIp" in recordings
assert "status?.lanIp" in galaxy assert "status?.lanIp" in galaxy
assert ':href="localUrl"' in recordings and ':href="localUrl"' in galaxy assert ':href="localUrl"' in recordings and ':href="localUrl"' in galaxy
assert 'localDeviceUrl(status?.lanIp, "/recordings")' in recordings
assert 'localDeviceUrl(status?.lanIp, "/galaxy")' in galaxy
assert "gx-btn gx-btn--tonal" in recordings and "Open Recordings Locally" in recordings
assert "gx-btn gx-btn--tonal" in galaxy and "Open Galaxy Locally" in galaxy
home = _read("js/views/Home.js") home = _read("js/views/Home.js")
home_css = _read("css/home.css") home_css = _read("css/home.css")