Skip to content

Commit fece39d

Browse files
authored
feat: Migrate Options tab from jQuery to Vue (#4724)
* feat: Migrate Options tab from jQuery to Vue - Add OptionsTab.vue with reactive settings using Vue watchers - All 15 settings bound with two-way data binding - Settings persisted to ConfigStorage on change - Integrates with PortHandler, CliAutoComplete, DarkTheme, Analytics - Notifications permission flow handling preserved Part of Phase 2 of the Vue tab migration initiative. * chore: Delete replaced jQuery tab files Remove old HTML and JS files that are now replaced by Vue components: - src/tabs/options.html -> OptionsTab.vue - src/js/tabs/options.js -> OptionsTab.vue * fix: Use v-html for OptionsTab i18n translations for consistency Apply v-html to all 18 labels in OptionsTab for consistency with HelpTab and LandingTab. Ensures any future translations with HTML will render correctly.
1 parent caebd08 commit fece39d

File tree

5 files changed

+358
-408
lines changed

5 files changed

+358
-408
lines changed

src/components/tabs/OptionsTab.vue

Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
<template>
2+
<BaseTab tab-name="options">
3+
<div class="content_wrapper grid-box col1">
4+
<!-- Main Options Box -->
5+
<div class="gui_box">
6+
<div class="gui_box_titlebar">
7+
<div class="spacer_box_title" v-html="$t('tabOptions')"></div>
8+
</div>
9+
<div class="spacer">
10+
<!-- Remember Last Tab -->
11+
<div class="rememberLastTab margin-bottom">
12+
<div>
13+
<input type="checkbox" class="toggle" v-model="settings.rememberLastTab" />
14+
</div>
15+
<span class="freelabel" v-html="$t('rememberLastTab')"></span>
16+
</div>
17+
18+
<!-- Metered Connection -->
19+
<div class="meteredConnection margin-bottom">
20+
<div>
21+
<input type="checkbox" class="toggle" v-model="settings.meteredConnection" />
22+
</div>
23+
<span class="freelabel" v-html="$t('meteredConnection')"></span>
24+
</div>
25+
26+
<!-- Analytics Opt Out -->
27+
<div class="analyticsOptOut margin-bottom">
28+
<div>
29+
<input type="checkbox" class="toggle" v-model="settings.analyticsOptOut" />
30+
</div>
31+
<span class="freelabel" v-html="$t('analyticsOptOut')"></span>
32+
</div>
33+
34+
<!-- CLI Auto Complete -->
35+
<div class="cliAutoComplete margin-bottom">
36+
<div>
37+
<input type="checkbox" class="toggle" v-model="settings.cliAutoComplete" />
38+
</div>
39+
<span class="freelabel" v-html="$t('cliAutoComplete')"></span>
40+
</div>
41+
42+
<!-- Show Manual Mode -->
43+
<div class="showManualMode margin-bottom">
44+
<div>
45+
<input type="checkbox" class="toggle" v-model="settings.showManualMode" />
46+
</div>
47+
<span class="freelabel" v-html="$t('showManualMode')"></span>
48+
</div>
49+
50+
<!-- Show Virtual Mode -->
51+
<div class="showVirtualMode margin-bottom">
52+
<div>
53+
<input type="checkbox" class="toggle" v-model="settings.showVirtualMode" />
54+
</div>
55+
<span class="freelabel" v-html="$t('showVirtualMode')"></span>
56+
</div>
57+
58+
<!-- Use Legacy Rendering Model -->
59+
<div class="useLegacyRenderingModel margin-bottom">
60+
<div>
61+
<input type="checkbox" class="toggle" v-model="settings.useLegacyRenderingModel" />
62+
</div>
63+
<span class="freelabel" v-html="$t('useLegacyRenderingModel')"></span>
64+
</div>
65+
66+
<!-- Dark Theme -->
67+
<div class="darkTheme margin-bottom">
68+
<select id="darkThemeSelect" v-model.number="settings.darkTheme">
69+
<option value="0">{{ $t("on") }}</option>
70+
<option value="1">{{ $t("off") }}</option>
71+
<option value="2">{{ $t("auto") }}</option>
72+
</select>
73+
<span class="freelabel" v-html="$t('darkTheme')"></span>
74+
</div>
75+
76+
<!-- Show Dev Tools On Startup -->
77+
<div class="showDevToolsOnStartup margin-bottom">
78+
<div>
79+
<input type="checkbox" class="toggle" v-model="settings.showDevToolsOnStartup" />
80+
</div>
81+
<span class="freelabel" v-html="$t('showDevToolsOnStartup')"></span>
82+
</div>
83+
84+
<!-- Show Notifications -->
85+
<div class="showNotifications margin-bottom">
86+
<div>
87+
<input
88+
type="checkbox"
89+
class="toggle"
90+
v-model="settings.showNotifications"
91+
@change="handleNotificationsChange"
92+
/>
93+
</div>
94+
<span class="freelabel" v-html="$t('showNotifications')"></span>
95+
</div>
96+
97+
<!-- Backup On Flash -->
98+
<div class="backupOnFlash margin-bottom">
99+
<select id="backupOnFlashSelect" v-model.number="settings.backupOnFlash">
100+
<option value="0">{{ $t("firmwareBackupDisabled") }}</option>
101+
<option value="1">{{ $t("firmwareBackupEnabled") }}</option>
102+
<option value="2">{{ $t("firmwareBackupAsk") }}</option>
103+
</select>
104+
<span class="freelabel" v-html="$t('firmwareBackupOnFlash')"></span>
105+
</div>
106+
107+
<!-- User Language -->
108+
<div class="userLanguage">
109+
<span class="dropdown">
110+
<select class="dropdown-select" id="userLanguage" v-model="settings.userLanguage">
111+
<option value="DEFAULT">{{ $t("language_default") }}</option>
112+
<option disabled>------</option>
113+
<option v-for="lang in availableLanguages" :key="lang" :value="lang">
114+
{{ $t(`language_${lang}`) }}
115+
</option>
116+
</select>
117+
</span>
118+
<span v-html="$t('userLanguageSelect')"></span>
119+
</div>
120+
</div>
121+
</div>
122+
123+
<!-- Development Settings Box -->
124+
<div class="gui_box">
125+
<div class="gui_box_titlebar">
126+
<div class="spacer_box_title" v-html="$t('developmentSettings')"></div>
127+
</div>
128+
<div class="spacer">
129+
<div class="showAllSerialDevices margin-bottom">
130+
<div>
131+
<input type="checkbox" class="toggle" v-model="settings.showAllSerialDevices" />
132+
</div>
133+
<span class="freelabel" v-html="$t('showAllSerialDevices')"></span>
134+
</div>
135+
136+
<div class="developmentSettings margin-bottom">
137+
<div class="cliOnlyMode margin-bottom">
138+
<div>
139+
<input type="checkbox" class="toggle" v-model="settings.cliOnlyMode" />
140+
</div>
141+
<span class="freelabel" v-html="$t('cliOnlyMode')"></span>
142+
</div>
143+
</div>
144+
</div>
145+
</div>
146+
147+
<!-- Warning Settings Box -->
148+
<div class="gui_box">
149+
<div class="gui_box_titlebar">
150+
<div class="spacer_box_title" v-html="$t('warningSettings')"></div>
151+
</div>
152+
<div class="spacer">
153+
<div class="presetsWarningBackup margin-bottom">
154+
<div>
155+
<input type="checkbox" class="toggle" v-model="settings.showPresetsWarningBackup" />
156+
</div>
157+
<span class="freelabel" v-html="$t('presetsWarningBackup')"></span>
158+
</div>
159+
</div>
160+
</div>
161+
</div>
162+
</BaseTab>
163+
</template>
164+
165+
<script>
166+
import { defineComponent, reactive, watch, onMounted } from "vue";
167+
import BaseTab from "./BaseTab.vue";
168+
import GUI from "../../js/gui";
169+
import { get as getConfig, set as setConfig } from "../../js/ConfigStorage";
170+
import { i18n } from "../../js/localization";
171+
import PortHandler from "../../js/port_handler";
172+
import CliAutoComplete from "../../js/CliAutoComplete";
173+
import DarkTheme, { setDarkTheme } from "../../js/DarkTheme";
174+
import { checkSetupAnalytics } from "../../js/Analytics";
175+
import NotificationManager from "../../js/utils/notifications";
176+
import { ispConnected } from "../../js/utils/connection";
177+
178+
export default defineComponent({
179+
name: "OptionsTab",
180+
components: {
181+
BaseTab,
182+
},
183+
setup() {
184+
// Load initial settings from config storage
185+
const settings = reactive({
186+
rememberLastTab: !!getConfig("rememberLastTab").rememberLastTab,
187+
meteredConnection: !!getConfig("meteredConnection").meteredConnection,
188+
analyticsOptOut: !!getConfig("analyticsOptOut").analyticsOptOut,
189+
cliAutoComplete: CliAutoComplete.configEnabled,
190+
showManualMode: !!getConfig("showManualMode").showManualMode,
191+
showVirtualMode: !!getConfig("showVirtualMode").showVirtualMode,
192+
useLegacyRenderingModel: !!getConfig("useLegacyRenderingModel").useLegacyRenderingModel,
193+
darkTheme: DarkTheme.configSetting,
194+
showDevToolsOnStartup: !!getConfig("showDevToolsOnStartup").showDevToolsOnStartup,
195+
showNotifications: !!getConfig("showNotifications").showNotifications,
196+
backupOnFlash: getConfig("backupOnFlash", 1).backupOnFlash ?? 1,
197+
userLanguage: i18n.selectedLanguage,
198+
showAllSerialDevices: !!getConfig("showAllSerialDevices").showAllSerialDevices,
199+
cliOnlyMode: !!getConfig("cliOnlyMode", false).cliOnlyMode,
200+
showPresetsWarningBackup: !!getConfig("showPresetsWarningBackup").showPresetsWarningBackup,
201+
});
202+
203+
const availableLanguages = i18n.getLanguagesAvailables();
204+
205+
// Watch each setting and persist changes
206+
watch(
207+
() => settings.rememberLastTab,
208+
(value) => setConfig({ rememberLastTab: value }),
209+
);
210+
211+
watch(
212+
() => settings.meteredConnection,
213+
(value) => {
214+
setConfig({ meteredConnection: value });
215+
ispConnected(); // Update network status
216+
},
217+
);
218+
219+
watch(
220+
() => settings.analyticsOptOut,
221+
(value) => {
222+
setConfig({ analyticsOptOut: value });
223+
checkSetupAnalytics((analyticsService) => {
224+
analyticsService.setOptOut(value);
225+
});
226+
},
227+
);
228+
229+
watch(
230+
() => settings.cliAutoComplete,
231+
(value) => {
232+
setConfig({ cliAutoComplete: value });
233+
CliAutoComplete.setEnabled(value);
234+
},
235+
);
236+
237+
watch(
238+
() => settings.showManualMode,
239+
(value) => {
240+
setConfig({ showManualMode: value });
241+
PortHandler.setShowManualMode(value);
242+
},
243+
);
244+
245+
watch(
246+
() => settings.showVirtualMode,
247+
(value) => {
248+
setConfig({ showVirtualMode: value });
249+
PortHandler.setShowVirtualMode(value);
250+
},
251+
);
252+
253+
watch(
254+
() => settings.useLegacyRenderingModel,
255+
(value) => setConfig({ useLegacyRenderingModel: value }),
256+
);
257+
258+
watch(
259+
() => settings.darkTheme,
260+
(value) => {
261+
setConfig({ darkTheme: value });
262+
setDarkTheme(value);
263+
},
264+
);
265+
266+
watch(
267+
() => settings.showDevToolsOnStartup,
268+
(value) => setConfig({ showDevToolsOnStartup: value }),
269+
);
270+
271+
watch(
272+
() => settings.backupOnFlash,
273+
(value) => setConfig({ backupOnFlash: value }),
274+
);
275+
276+
watch(
277+
() => settings.userLanguage,
278+
(value) => {
279+
i18n.changeLanguage(value);
280+
i18n.localizePage();
281+
},
282+
);
283+
284+
watch(
285+
() => settings.showAllSerialDevices,
286+
(value) => {
287+
setConfig({ showAllSerialDevices: value });
288+
PortHandler.setShowAllSerialDevices(value);
289+
},
290+
);
291+
292+
watch(
293+
() => settings.cliOnlyMode,
294+
(value) => setConfig({ cliOnlyMode: value }),
295+
);
296+
297+
watch(
298+
() => settings.showPresetsWarningBackup,
299+
(value) => setConfig({ showPresetsWarningBackup: value }),
300+
);
301+
302+
// Handle notifications permission flow
303+
function handleNotificationsChange() {
304+
const enabled = settings.showNotifications;
305+
306+
if (enabled) {
307+
const informationDialog = {
308+
title: i18n.getMessage("notificationsDeniedTitle"),
309+
text: i18n.getMessage("notificationsDenied"),
310+
buttonConfirmText: i18n.getMessage("OK"),
311+
};
312+
313+
switch (NotificationManager.checkPermission()) {
314+
case "granted":
315+
setConfig({ showNotifications: enabled });
316+
break;
317+
case "denied":
318+
GUI.showInformationDialog(informationDialog);
319+
settings.showNotifications = false;
320+
break;
321+
case "default":
322+
settings.showNotifications = false;
323+
NotificationManager.requestPermission().then((permission) => {
324+
if (permission === "granted") {
325+
setConfig({ showNotifications: true });
326+
settings.showNotifications = true;
327+
} else {
328+
GUI.showInformationDialog(informationDialog);
329+
}
330+
});
331+
break;
332+
}
333+
} else {
334+
setConfig({ showNotifications: false });
335+
}
336+
}
337+
338+
onMounted(() => {
339+
GUI.content_ready();
340+
});
341+
342+
return {
343+
settings,
344+
availableLanguages,
345+
handleNotificationsChange,
346+
};
347+
},
348+
});
349+
</script>
350+
351+
<style scoped>
352+
/* Inherit styles from existing options.html via global CSS */
353+
</style>

src/js/main.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,8 @@ function startProcess() {
245245
);
246246
break;
247247
case "options":
248-
import("./tabs/options").then(({ options }) => options.initialize(content_ready));
248+
// Vue tab - use mountVueTab instead of jQuery load
249+
mountVueTab("options", content_ready);
249250
break;
250251
case "firmware_flasher":
251252
import("./tabs/firmware_flasher").then(({ firmware_flasher }) =>

0 commit comments

Comments
 (0)