Skip to content

Commit caebd08

Browse files
authored
feat: Migrate Help and Landing tabs from jQuery to Vue (#4723)
* feat: Migrate Help and Landing tabs from jQuery to Vue This PR begins the migration of jQuery-based tabs to Vue 3 Single File Components as part of the ongoing modernization effort. Changes: - Add BaseTab.vue component for common tab lifecycle management - Add HelpTab.vue - static help content using Vue i18n - Add LandingTab.vue - landing page with reactive language switcher - Add vue_tab_mounter.js utility for mounting Vue tabs into #content - Update vue_components.js with VueTabComponents registry - Update main.js to use mountVueTab() for help and landing cases This is Phase 1 of a multi-phase migration approach, starting with the simplest static tabs to establish patterns before migrating more complex configuration and visualization tabs. * fix: Use v-html for i18n translations containing HTML markup The i18n translations for the landing tab contain HTML tags (links, bold text). Using {{ $t() }} escapes HTML as text. Changed to v-html directive to render the HTML content properly. * chore: Delete replaced jQuery tab files Remove old HTML and JS files that are now replaced by Vue components: - src/tabs/help.html -> HelpTab.vue - src/js/tabs/help.js -> HelpTab.vue - src/tabs/landing.html -> LandingTab.vue - src/js/tabs/landing.js -> LandingTab.vue * refactor: Replace deprecated align attribute with CSS text-align Remove deprecated HTML align='center' attributes from .logowrapper and add scoped CSS rule for text-align: center instead. * fix: Use v-html for HelpTab i18n translations containing HTML links The i18n translations for documentation and support sections contain HTML links and formatting. Using v-html to render them properly.
1 parent def5dce commit caebd08

File tree

10 files changed

+376
-197
lines changed

10 files changed

+376
-197
lines changed

src/components/tabs/BaseTab.vue

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<template>
2+
<div :class="`tab-${tabName}`">
3+
<slot></slot>
4+
</div>
5+
</template>
6+
7+
<script>
8+
import { defineComponent, onMounted, onUnmounted, inject } from "vue";
9+
import GUI from "../../js/gui";
10+
11+
/**
12+
* BaseTab provides common tab lifecycle management for Vue tabs.
13+
*
14+
* Usage:
15+
* <BaseTab tab-name="help" @mounted="onTabMounted">
16+
* <template>...content...</template>
17+
* </BaseTab>
18+
*/
19+
export default defineComponent({
20+
name: "BaseTab",
21+
props: {
22+
tabName: {
23+
type: String,
24+
required: true,
25+
},
26+
},
27+
emits: ["mounted", "cleanup"],
28+
setup(props, { emit }) {
29+
// Access the global reactive model
30+
const model = inject("betaflightModel", null);
31+
32+
onMounted(() => {
33+
GUI.active_tab = props.tabName;
34+
emit("mounted");
35+
});
36+
37+
onUnmounted(() => {
38+
// Clean up any intervals/timeouts when tab is destroyed
39+
GUI.interval_kill_all();
40+
GUI.timeout_kill_all();
41+
emit("cleanup");
42+
});
43+
44+
return { model };
45+
},
46+
});
47+
</script>

src/components/tabs/HelpTab.vue

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<template>
2+
<BaseTab tab-name="help">
3+
<div class="content_wrapper grid-row">
4+
<div class="grid-col col8">
5+
<div class="gui_box">
6+
<div class="gui_box_titlebar">
7+
<div class="spacer_box_title" v-html="$t('defaultDocumentationHead')"></div>
8+
</div>
9+
<div class="spacer">
10+
<p v-html="$t('defaultDocumentation')"></p>
11+
<ul>
12+
<li>
13+
<span v-html="$t('defaultDocumentation1')"></span>
14+
</li>
15+
<li>
16+
<span v-html="$t('defaultDocumentation2')"></span>
17+
</li>
18+
</ul>
19+
</div>
20+
</div>
21+
</div>
22+
<div class="grid-col col4">
23+
<div class="gui_box">
24+
<div class="gui_box_titlebar">
25+
<div class="spacer_box_title" v-html="$t('defaultSupportHead')"></div>
26+
</div>
27+
<div class="spacer">
28+
<p v-html="$t('defaultSupport')"></p>
29+
<div class="subline">
30+
<strong v-html="$t('defaultSupportSubline1')"></strong>
31+
</div>
32+
<ul>
33+
<li>
34+
<span v-html="$t('defaultSupport1')"></span>
35+
</li>
36+
<li>
37+
<span v-html="$t('defaultSupport2')"></span>
38+
</li>
39+
<li>
40+
<span v-html="$t('defaultSupport3')"></span>
41+
</li>
42+
</ul>
43+
<div class="subline">
44+
<strong v-html="$t('defaultSupportSubline2')"></strong>
45+
</div>
46+
<ul>
47+
<li>
48+
<span v-html="$t('defaultSupport4')"></span>
49+
</li>
50+
<li>
51+
<span v-html="$t('defaultSupport5')"></span>
52+
</li>
53+
</ul>
54+
</div>
55+
</div>
56+
</div>
57+
</div>
58+
</BaseTab>
59+
</template>
60+
61+
<script>
62+
import { defineComponent } from "vue";
63+
import BaseTab from "./BaseTab.vue";
64+
import GUI from "../../js/gui";
65+
66+
export default defineComponent({
67+
name: "HelpTab",
68+
components: {
69+
BaseTab,
70+
},
71+
setup() {
72+
// Called after tab is ready - equivalent to old content_ready callback
73+
function onTabReady() {
74+
GUI.content_ready();
75+
}
76+
77+
return { onTabReady };
78+
},
79+
mounted() {
80+
this.onTabReady();
81+
},
82+
});
83+
</script>
84+
85+
<style scoped>
86+
/* Inherit styles from existing help.html via global CSS */
87+
</style>

src/components/tabs/LandingTab.vue

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
<template>
2+
<BaseTab tab-name="landing">
3+
<div class="content_wrapper">
4+
<div class="content_top">
5+
<div class="logowrapper">
6+
<img src="/images/bf_logo_white.svg" alt="" />
7+
<div v-html="$t('defaultWelcomeIntro')"></div>
8+
</div>
9+
</div>
10+
<div class="tab_sponsor" ref="sponsorContainer"></div>
11+
<div class="content_mid grid-row">
12+
<div class="column third_left text1 grid-col col4">
13+
<div class="wrap">
14+
<h2 v-html="$t('defaultWelcomeHead')"></h2>
15+
<div v-html="$t('defaultWelcomeText')"></div>
16+
</div>
17+
</div>
18+
<div class="column third_center text2 grid-col col5">
19+
<div class="wrap">
20+
<h2 v-html="$t('defaultContributingHead')"></h2>
21+
<div v-html="$t('defaultContributingText')"></div>
22+
</div>
23+
</div>
24+
<div class="column third_right text3 grid-col col3">
25+
<div class="wrap2">
26+
<h3 v-html="$t('defaultDonateHead')"></h3>
27+
<div v-html="$t('defaultDonateText')"></div>
28+
<div class="donate">
29+
<a
30+
href="https://paypal.me/betaflight"
31+
rel="noopener noreferrer"
32+
target="_blank"
33+
:title="$t('defaultDonate')"
34+
>
35+
<img src="/images/btn-donate.png" alt="Paypal" height="30" />
36+
</a>
37+
</div>
38+
<div v-html="$t('defaultDonateBottom')"></div>
39+
</div>
40+
</div>
41+
<div class="content_mid_bottom">
42+
<div class="socialMediaParagraph">
43+
<div class="logoSocialMedia">
44+
<img src="/images/flogo_RGB_HEX-1024.svg" alt="Facebook" class="facebookLogo" />
45+
</div>
46+
<div class="socialMediaText" v-html="$t('defaultFacebookText')"></div>
47+
</div>
48+
<div class="socialMediaParagraph">
49+
<div class="logoSocialMedia">
50+
<img src="/images/discord-logo-color.svg" alt="Discord" class="discordLogo" />
51+
</div>
52+
<div class="socialMediaText" v-html="$t('defaultDiscordText')"></div>
53+
</div>
54+
</div>
55+
<div class="content_bottom">
56+
<div class="statsCollection" v-html="$t('statisticsDisclaimer')"></div>
57+
</div>
58+
<div class="content_foot">
59+
<div class="languageSwitcher">
60+
<span>{{ $t("language_choice_message") }}</span>
61+
<a
62+
v-for="lang in availableLanguages"
63+
:key="lang"
64+
href="#"
65+
:lang="lang"
66+
:class="{ selected_language: lang === selectedLanguage }"
67+
@click.prevent="changeLanguage(lang)"
68+
>
69+
{{ $t(`language_${lang}`) }}
70+
</a>
71+
</div>
72+
</div>
73+
</div>
74+
</div>
75+
</BaseTab>
76+
</template>
77+
78+
<script>
79+
import { defineComponent, ref, onMounted } from "vue";
80+
import $ from "jquery";
81+
import BaseTab from "./BaseTab.vue";
82+
import GUI from "../../js/gui";
83+
import { i18n } from "../../js/localization";
84+
import Sponsor from "../../js/Sponsor";
85+
86+
export default defineComponent({
87+
name: "LandingTab",
88+
components: {
89+
BaseTab,
90+
},
91+
setup() {
92+
const sponsorContainer = ref(null);
93+
const sponsor = new Sponsor();
94+
95+
// Get available languages including DEFAULT
96+
const availableLanguages = ref(["DEFAULT", ...i18n.getLanguagesAvailables()]);
97+
const selectedLanguage = ref(i18n.selectedLanguage);
98+
99+
function changeLanguage(lang) {
100+
if (i18n.selectedLanguage !== lang) {
101+
i18n.changeLanguage(lang);
102+
selectedLanguage.value = lang;
103+
}
104+
}
105+
106+
onMounted(() => {
107+
// Load sponsor tile - wrap with jQuery for Sponsor.js compatibility
108+
if (sponsorContainer.value) {
109+
sponsor.loadSponsorTile("landing", $(sponsorContainer.value));
110+
}
111+
GUI.content_ready();
112+
});
113+
114+
return {
115+
sponsorContainer,
116+
availableLanguages,
117+
selectedLanguage,
118+
changeLanguage,
119+
};
120+
},
121+
});
122+
</script>
123+
124+
<style scoped>
125+
.logowrapper,
126+
.logowrapper > div {
127+
text-align: center;
128+
}
129+
.selected_language {
130+
font-weight: bold;
131+
}
132+
.languageSwitcher a {
133+
margin-left: 8px;
134+
}
135+
</style>

src/js/main.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import CliAutoComplete from "./CliAutoComplete.js";
1313
import DarkTheme, { setDarkTheme } from "./DarkTheme.js";
1414
import { isExpertModeEnabled } from "./utils/isExpertModeEnabled.js";
1515
import { updateTabList } from "./utils/updateTabList.js";
16+
import { mountVueTab } from "./vue_tab_mounter.js";
1617
import * as THREE from "three";
1718
import NotificationManager from "./utils/notifications.js";
1819

@@ -230,7 +231,8 @@ function startProcess() {
230231

231232
switch (tab) {
232233
case "landing":
233-
import("./tabs/landing").then(({ landing }) => landing.initialize(content_ready));
234+
// Vue tab - use mountVueTab instead of jQuery load
235+
mountVueTab("landing", content_ready);
234236
break;
235237
case "changelog":
236238
import("./tabs/static_tab").then(({ staticTab }) =>
@@ -251,7 +253,8 @@ function startProcess() {
251253
);
252254
break;
253255
case "help":
254-
import("./tabs/help").then(({ help }) => help.initialize(content_ready));
256+
// Vue tab - use mountVueTab instead of jQuery load
257+
mountVueTab("help", content_ready);
255258
break;
256259
case "auxiliary":
257260
import("./tabs/auxiliary").then(({ auxiliary }) => auxiliary.initialize(content_ready));

src/js/tabs/help.js

Lines changed: 0 additions & 24 deletions
This file was deleted.

src/js/tabs/landing.js

Lines changed: 0 additions & 72 deletions
This file was deleted.

0 commit comments

Comments
 (0)