Skip to content

Commit 5485820

Browse files
authored
Refactor: Convert sidebar to Nuxt UI UNavigationMenu (betaflight#5033)
1 parent 6b5dbed commit 5485820

23 files changed

Lines changed: 545 additions & 426 deletions

src/App.vue

Lines changed: 111 additions & 205 deletions
Large diffs are not rendered by default.

src/components/betaflight-logo/BetaflightLogo.vue

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,14 @@ export default defineComponent({
5656
.tab_container .logo_image {
5757
width: 100%;
5858
height: 48px;
59-
background-image: url(../../images/dark-wide-2.svg);
59+
background-image: url(../../images/bf_logo_white.svg);
6060
background-repeat: no-repeat;
6161
background-position: left center;
6262
background-size: contain;
6363
}
6464
6565
.dark .tab_container .logo_image {
66-
background-image: url(../../images/light-wide-2.svg);
66+
background-image: url(../../images/bf_logo_black.svg);
6767
}
6868
6969
@media (max-width: 1055px) {
@@ -72,7 +72,18 @@ export default defineComponent({
7272
}
7373
.tab_container .logo_image {
7474
width: 48px;
75-
background-size: auto 100%;
75+
background-image: url(../../images/bf_logo_short_white.svg);
76+
background-position: center;
77+
background-size: contain;
78+
}
79+
.dark .tab_container .logo_image {
80+
background-image: url(../../images/bf_logo_short_black.svg);
81+
}
82+
}
83+
84+
@media all and (max-width: 575px), all and (max-width: 950px) and (max-height: 500px) and (orientation: landscape) {
85+
.tab_container .logo {
86+
display: none;
7687
}
7788
}
7889
</style>

src/components/port-picker/ConnectButton.vue

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
v-slot="{ open }"
3232
:items="menuItems"
3333
:content="{ align: 'end', side: 'top' }"
34-
:ui="{ content: 'max-h-96' }"
34+
:ui="{ content: 'max-h-96 z-[2100]' }"
3535
>
3636
<UButton
3737
color="success"
@@ -271,8 +271,35 @@ export default defineComponent({
271271
}
272272
273273
@media (max-width: 1055px) {
274+
.sidebar-connect {
275+
display: flex;
276+
justify-content: center;
277+
}
274278
.sidebar-connect__label {
275279
display: none;
276280
}
281+
.sidebar-connect__group {
282+
width: auto !important;
283+
}
284+
}
285+
286+
/* Default Nuxt UI `success` soft tint is too pale in light mode — lift the contrast. */
287+
html:not(.dark) .sidebar-connect :deep(button) {
288+
background-color: var(--success-400);
289+
border: 1px solid var(--success-600);
290+
color: var(--surface-900);
291+
}
292+
html:not(.dark) .sidebar-connect :deep(button:hover) {
293+
background-color: var(--success-500);
294+
}
295+
296+
.tab_container.reveal .sidebar-connect {
297+
display: block;
298+
}
299+
.tab_container.reveal .sidebar-connect__label {
300+
display: inline;
301+
}
302+
.tab_container.reveal .sidebar-connect__group {
303+
width: 100% !important;
277304
}
278305
</style>

src/components/sidebar/Sidebar.vue

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<template>
2+
<UNavigationMenu
3+
:items="visibleItems"
4+
orientation="vertical"
5+
:collapsed="isCompact"
6+
tooltip
7+
:ui="navMenuUi"
8+
class="sidebar-nav"
9+
/>
10+
</template>
11+
12+
<script setup>
13+
import { computed, inject, ref } from "vue";
14+
import { useTranslation } from "i18next-vue";
15+
import { sidebarItems, isItemVisible } from "./sidebar_items.js";
16+
import { useConnectionStore } from "@/stores/connection";
17+
import { useAuthStore } from "@/stores/auth";
18+
import { vueTabState } from "@/js/vue_tab_mounter.js";
19+
import { switchTab } from "@/js/tab_switch.js";
20+
import GUI from "@/js/gui.js";
21+
import FCModule from "@/js/fc.js";
22+
23+
const { t } = useTranslation();
24+
const connectionStore = useConnectionStore();
25+
const authStore = useAuthStore();
26+
const sidebarExpanded = inject("sidebarExpanded", ref(true));
27+
const isCompact = computed(() => !sidebarExpanded.value);
28+
const navMenuUi = computed(() => (isCompact.value ? { link: "justify-center" } : {}));
29+
const betaflightModel = inject("betaflightModel", null);
30+
31+
const isModeVisible = (mode) => {
32+
switch (mode) {
33+
case "disconnected":
34+
return !connectionStore.connectionValid;
35+
case "connected":
36+
case "cli":
37+
return !!connectionStore.connectionValid;
38+
case "shared":
39+
return true;
40+
case "loggedin":
41+
return authStore.isLoggedIn;
42+
default:
43+
return false;
44+
}
45+
};
46+
47+
const ctx = computed(() => {
48+
const model = betaflightModel ?? globalThis.vm;
49+
const fc = model?.FC ?? FCModule;
50+
return {
51+
expertMode: Boolean(model?.expertMode),
52+
buildOptions: fc?.CONFIG?.buildOptions,
53+
features: fc?.FEATURE_CONFIG?.features,
54+
};
55+
});
56+
57+
const isAllowed = (item) => {
58+
if (item.mode === "loggedin" || item.mode === "shared") {
59+
return true;
60+
}
61+
return GUI.allowedTabs.includes(item.tab ?? item.key);
62+
};
63+
64+
const activeItems = computed(() =>
65+
sidebarItems
66+
.filter((item) => isModeVisible(item.mode))
67+
.filter((item) => isAllowed(item))
68+
.filter((item) => isItemVisible(item, ctx.value)),
69+
);
70+
71+
const visibleItems = computed(() =>
72+
activeItems.value.map((item) => ({
73+
label: t(item.i18n),
74+
icon: item.icon,
75+
active: vueTabState.activeTabName === (item.tab ?? item.key),
76+
tooltip: { text: t(item.i18n) },
77+
onSelect: (event) => {
78+
event?.preventDefault?.();
79+
switchTab(item.tab ?? item.key, { mode: item.mode, label: t(item.i18n) });
80+
},
81+
})),
82+
);
83+
</script>
84+
85+
<style scoped>
86+
.sidebar-nav {
87+
width: 100%;
88+
}
89+
</style>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
export const sidebarItems = [
2+
{ key: "landing", mode: "disconnected", i18n: "tabLanding", icon: "i-lucide-home" },
3+
{ key: "help", mode: "disconnected", i18n: "tabHelp", icon: "i-lucide-help-circle" },
4+
{ key: "options", mode: "disconnected", i18n: "tabOptions", icon: "i-lucide-settings" },
5+
{ key: "firmware_flasher", mode: "disconnected", i18n: "tabFirmwareFlasher", icon: "i-lucide-zap" },
6+
{ key: "preflight", mode: "disconnected", i18n: "tabPreflight", icon: "i-lucide-clipboard-check" },
7+
{ key: "flight_plan", mode: "disconnected", i18n: "tabFlightPlan", icon: "i-lucide-route", expert: true },
8+
9+
{ key: "setup", mode: "connected", i18n: "tabSetup", icon: "i-lucide-sliders-horizontal" },
10+
{ key: "ports", mode: "connected", i18n: "tabPorts", icon: "i-lucide-cable" },
11+
{ key: "configuration", mode: "connected", i18n: "tabConfiguration", icon: "i-lucide-settings" },
12+
{ key: "power", mode: "connected", i18n: "tabPower", icon: "i-lucide-battery" },
13+
{ key: "failsafe", mode: "connected", i18n: "tabFailsafe", icon: "i-lucide-shield-alert", expert: true },
14+
{ key: "presets", mode: "connected", i18n: "tabPresets", icon: "i-lucide-wand-2" },
15+
{ key: "pid_tuning", mode: "connected", i18n: "tabPidTuning", icon: "i-lucide-gauge" },
16+
{ key: "receiver", mode: "connected", i18n: "tabReceiver", icon: "i-lucide-radio" },
17+
{ key: "auxiliary", mode: "connected", i18n: "tabAuxiliary", icon: "i-lucide-toggle-right" },
18+
{ key: "adjustments", mode: "connected", i18n: "tabAdjustments", icon: "i-lucide-sliders", expert: true },
19+
{
20+
key: "servos",
21+
mode: "connected",
22+
i18n: "tabServos",
23+
icon: "i-lucide-rotate-ccw",
24+
buildOptions: ["USE_SERVOS", "USE_WING"],
25+
},
26+
{ key: "gps", mode: "connected", i18n: "tabGPS", icon: "i-lucide-map-pin", buildOptions: ["USE_GPS"] },
27+
{ key: "motors", mode: "connected", i18n: "tabMotorTesting", icon: "i-lucide-fan" },
28+
{ key: "osd", mode: "connected", i18n: "tabOsd", icon: "i-lucide-monitor", feature: "OSD" },
29+
{ key: "vtx", mode: "connected", i18n: "tabVtx", icon: "i-lucide-radio-tower" },
30+
{ key: "led_strip", mode: "connected", i18n: "tabLedStrip", icon: "i-lucide-lightbulb", feature: "LED_STRIP" },
31+
{ key: "sensors", mode: "connected", i18n: "tabRawSensorData", icon: "i-lucide-activity", expert: true },
32+
{
33+
key: "flight_plan_connected",
34+
tab: "flight_plan",
35+
mode: "connected",
36+
i18n: "tabFlightPlan",
37+
icon: "i-lucide-route",
38+
buildOptions: ["USE_FLIGHT_PLAN"],
39+
},
40+
{ key: "logging", mode: "connected", i18n: "tabLogging", icon: "i-lucide-file-text", expert: true },
41+
{ key: "onboard_logging", mode: "connected", i18n: "tabOnboardLogging", icon: "i-lucide-database" },
42+
43+
{ key: "cli", mode: "cli", i18n: "tabCLI", icon: "i-lucide-terminal" },
44+
45+
{ key: "log", mode: "shared", i18n: "tabLog", icon: "i-lucide-file-text", expert: true },
46+
47+
{ key: "backups", mode: "loggedin", i18n: "tabBackups", icon: "i-lucide-database" },
48+
{ key: "user_profile", mode: "loggedin", i18n: "tabUserProfile", icon: "i-lucide-user" },
49+
];
50+
51+
export function isItemVisible(item, ctx) {
52+
if (item.expert && !ctx.expertMode) {
53+
return false;
54+
}
55+
if (item.buildOptions && !item.buildOptions.some((o) => ctx.buildOptions?.includes(o))) {
56+
return false;
57+
}
58+
if (item.feature && !ctx.features?.isEnabled?.(item.feature)) {
59+
return false;
60+
}
61+
return true;
62+
}

src/components/tabs/BackupsTab.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<template>
22
<BaseTab tab-name="backups">
33
<div class="content_wrapper grid-box col1">
4+
<div class="tab_title">{{ $t("tabBackups") }}</div>
45
<!-- Loading State -->
56
<div v-if="isLoading" class="flex items-center justify-center py-16">
67
<UIcon name="i-lucide-loader-circle" class="size-5 animate-spin text-[var(--color-primary-500)]" />

src/components/tabs/CliTab.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<template>
22
<BaseTab tab-name="cli" @mounted="onTabMounted" @cleanup="onTabCleanup">
33
<div class="content_wrapper">
4+
<div class="tab_title">{{ $t("tabCLI") }}</div>
45
<div class="note">
56
<p v-html="$t('cliInfo')"></p>
67
</div>

src/components/tabs/HelpTab.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<template>
22
<BaseTab tab-name="help">
33
<div class="content_wrapper">
4+
<div class="tab_title">{{ $t("tabHelp") }}</div>
45
<div class="grid-row grid-box col5">
56
<div class="col-span-3">
67
<UiBox :title="$t('defaultDocumentationHead')" class="sm:h-full">

src/components/tabs/LandingTab.vue

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,4 +317,14 @@ export default defineComponent({
317317
}
318318
}
319319
}
320+
321+
@media all and (max-width: 575px), all and (max-width: 950px) and (max-height: 500px) and (orientation: landscape) {
322+
.content_top {
323+
height: auto;
324+
padding: 10px 20px;
325+
}
326+
.logowrapper img {
327+
display: none;
328+
}
329+
}
320330
</style>

src/components/tabs/LogTab.vue

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,14 @@ onMounted(() => {
7272
display: flex;
7373
flex-direction: column;
7474
height: 100%;
75-
padding: 1rem;
76-
gap: 0.5rem;
7775
}
7876
7977
.log-toolbar {
8078
display: flex;
8179
align-items: center;
8280
gap: 1rem;
8381
flex-wrap: wrap;
82+
margin-bottom: 0.5rem;
8483
}
8584
8685
.log-autoscroll {

0 commit comments

Comments
 (0)