mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-15 12:04:00 +08:00
multi comma
This commit is contained in:
@@ -361,7 +361,7 @@ ul { list-style: none; margin: 0; padding: 0; }
|
||||
|
||||
.gx-appbar .gx-appbar__back,
|
||||
.gx-appbar .gx-theme-toggle,
|
||||
.gx-appbar .gx-appbar__pin {
|
||||
.gx-appbar .gx-appbar__menu {
|
||||
background: var(--glass-bg);
|
||||
backdrop-filter: var(--glass-blur);
|
||||
-webkit-backdrop-filter: var(--glass-blur);
|
||||
@@ -376,7 +376,8 @@ ul { list-style: none; margin: 0; padding: 0; }
|
||||
}
|
||||
|
||||
.gx-appbar .gx-appbar__back:active,
|
||||
.gx-appbar .gx-theme-toggle:active { transform: scale(0.92); }
|
||||
.gx-appbar .gx-theme-toggle:active,
|
||||
.gx-appbar .gx-appbar__menu:active { transform: scale(0.92); }
|
||||
|
||||
.gx-appbar__title {
|
||||
background: linear-gradient(135deg, #8b6cc5 0%, #5ec8c8 55%, #d4789c 100%);
|
||||
@@ -464,8 +465,7 @@ ul { list-style: none; margin: 0; padding: 0; }
|
||||
.gx-icon-btn:active { transform: scale(0.9); }
|
||||
.gx-icon-btn i { font-size: 1.3rem; }
|
||||
.gx-back-btn { display: none; }
|
||||
.gx-menu-btn { display: inline-flex; }
|
||||
.gx-appbar .gx-appbar__pin { display: none; }
|
||||
.gx-appbar__menu { display: inline-flex; }
|
||||
|
||||
.gx-searchwrap {
|
||||
align-items: center;
|
||||
@@ -1724,6 +1724,24 @@ button.gx-chip:hover {
|
||||
|
||||
.gx-nav-item i { font-size: 1.3rem; width: 24px; }
|
||||
|
||||
.gx-device-picker__name {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.gx-device-picker__current {
|
||||
background: var(--glass-active-bg);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: var(--radius-full);
|
||||
color: var(--primary);
|
||||
font-size: var(--fs-xs);
|
||||
margin-left: auto;
|
||||
padding: 3px 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.gx-scrim {
|
||||
align-items: center;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
@@ -2252,17 +2270,14 @@ button.gx-chip:hover {
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.liquid-glass-nav { display: flex; }
|
||||
.gx-menu-btn { display: none; }
|
||||
.gx-back-btn { display: inline-flex; }
|
||||
.gx-appbar__search { flex: 1; min-width: 0; }
|
||||
.gx-status-pill { display: none; }
|
||||
.gx-appbar .gx-appbar__pin { display: inline-flex; }
|
||||
.gx-content { padding-left: var(--sp-3); padding-right: var(--sp-3); }
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.liquid-glass-nav { display: none; }
|
||||
.gx-menu-btn { display: inline-flex; }
|
||||
.gx-back-btn { display: none; }
|
||||
.gx-content { padding-bottom: var(--sp-6); }
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { store, navigate, goBack, toolHref, toggleTheme, toggleNavPinned } from
|
||||
import { api } from "../api.js"
|
||||
import { usePolling } from "../composables.js"
|
||||
import { languageState, setLanguage, t } from "../i18n.js"
|
||||
import { DevicePicker } from "./DevicePicker.js"
|
||||
|
||||
const NAV = {
|
||||
recordings: [
|
||||
@@ -33,6 +34,7 @@ const BOTTOM_NAV = [
|
||||
|
||||
export const AppShell = {
|
||||
name: "AppShell",
|
||||
components: { DevicePicker },
|
||||
data() {
|
||||
return { store, BOTTOM_NAV, NAV }
|
||||
},
|
||||
@@ -115,9 +117,6 @@ export const AppShell = {
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
</button>
|
||||
<div class="gx-appbar__pill">
|
||||
<button type="button" class="gx-icon-btn gx-menu-btn" :aria-label="tr('Menu')" @click="store.drawerOpen = true">
|
||||
<i class="bi bi-list"></i>
|
||||
</button>
|
||||
<span class="gx-appbar__home" role="button" tabindex="0"
|
||||
:aria-label="tr('Galaxy home')" @click="goHome" @keydown.enter="goHome" @keydown.space.prevent="goHome">
|
||||
<span class="gx-appbar__title">Galaxy</span>
|
||||
@@ -140,10 +139,8 @@ export const AppShell = {
|
||||
:title="isLight ? tr('Dark mode') : tr('Light mode')" @click="themeToggle">
|
||||
<i class="bi" :class="isLight ? 'bi-moon-stars-fill' : 'bi-sun-fill'"></i>
|
||||
</button>
|
||||
<button type="button" class="gx-icon-btn gx-appbar__pin" :aria-pressed="navPinned"
|
||||
:aria-label="navPinned ? tr('Unpin navigation') : tr('Pin navigation')"
|
||||
:title="navPinned ? tr('Unpin navigation') : tr('Pin navigation')" @click="toggleNavPin">
|
||||
<i class="bi" :class="navPinned ? 'bi-pin-angle-fill' : 'bi-pin-angle'"></i>
|
||||
<button type="button" class="gx-icon-btn gx-appbar__menu" :aria-label="tr('Menu')" :title="tr('Menu')" @click="store.drawerOpen = true">
|
||||
<i class="bi bi-list"></i>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
@@ -160,6 +157,7 @@ export const AppShell = {
|
||||
<i class="bi" :class="navPinned ? 'bi-pin-angle-fill' : 'bi-pin-angle'"></i>
|
||||
</button>
|
||||
</div>
|
||||
<DevicePicker />
|
||||
<div class="gx-nav-section">
|
||||
<div class="gx-nav-section__title">{{ tr("Main") }}</div>
|
||||
<a class="gx-nav-item" :class="{ active: isActive('/') }" @click.prevent="navTo('/')">
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
const SLUG_RE = /^[A-Za-z0-9]{16}$/
|
||||
|
||||
export const DevicePicker = {
|
||||
name: "DevicePicker",
|
||||
data() {
|
||||
return {
|
||||
devices: [],
|
||||
activeSlug: "",
|
||||
loading: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasMultipleDevices() { return this.devices.length > 1 },
|
||||
},
|
||||
methods: {
|
||||
async loadDevices() {
|
||||
try {
|
||||
const response = await fetch("/_gateway/devices", { cache: "no-store" })
|
||||
if (!response.ok) return
|
||||
const data = await response.json()
|
||||
this.activeSlug = SLUG_RE.test(data?.activeSlug || "") ? data.activeSlug : ""
|
||||
this.devices = Array.isArray(data?.devices)
|
||||
? data.devices.filter((device) => SLUG_RE.test(device?.slug || "") && device?.path)
|
||||
: []
|
||||
} catch (error) {
|
||||
// Local Galaxy instances do not have the gateway directory endpoint.
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
selectDevice(device) {
|
||||
if (!device?.path || device.slug === this.activeSlug) return
|
||||
window.location.assign(device.path)
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadDevices()
|
||||
},
|
||||
template: `
|
||||
<div v-if="!loading && hasMultipleDevices" class="gx-nav-section gx-device-picker">
|
||||
<div class="gx-nav-section__title">Commas</div>
|
||||
<a v-for="device in devices" :key="device.slug" class="gx-nav-item gx-device-picker__item"
|
||||
:class="{ active: device.slug === activeSlug }" :href="device.path"
|
||||
:aria-current="device.slug === activeSlug ? 'page' : undefined"
|
||||
@click.prevent="selectDevice(device)">
|
||||
<i class="bi bi-cpu"></i>
|
||||
<span class="gx-device-picker__name">{{ device.name }}</span>
|
||||
<span v-if="device.slug === activeSlug" class="gx-device-picker__current">Current</span>
|
||||
</a>
|
||||
</div>
|
||||
`,
|
||||
}
|
||||
@@ -26,6 +26,7 @@ def test_ui_app_shell_files_exist():
|
||||
"js/params.js",
|
||||
"js/i18n.js",
|
||||
"js/components/AppShell.js",
|
||||
"js/components/DevicePicker.js",
|
||||
"js/components/GalaxyModal.js",
|
||||
"js/components/GalaxySection.js",
|
||||
"js/components/GalaxyEmbed.js",
|
||||
@@ -62,6 +63,17 @@ def test_ui_index_wires_vue_and_mount_point():
|
||||
assert 'apple-mobile-web-app-title" content="Galaxy"' in index
|
||||
|
||||
|
||||
def test_ui_device_picker_uses_gateway_directory_and_preserves_local_galaxy():
|
||||
picker = _read("js/components/DevicePicker.js")
|
||||
shell = _read("js/components/AppShell.js")
|
||||
|
||||
assert 'fetch("/_gateway/devices"' in picker
|
||||
assert "hasMultipleDevices" in picker
|
||||
assert "window.location.assign(device.path)" in picker
|
||||
assert '<DevicePicker />' in shell
|
||||
assert "Local Galaxy instances do not have the gateway directory endpoint." in picker
|
||||
|
||||
|
||||
def test_ui_uses_same_backend_endpoints():
|
||||
settings = _read("js/views/Settings.js")
|
||||
params = _read("js/params.js")
|
||||
|
||||
Reference in New Issue
Block a user