Skip to content

Commit 397e682

Browse files
authored
Merge pull request #49 from pkendall64/master
Save settings in local storage
2 parents 91e2dfb + dc034af commit 397e682

File tree

7 files changed

+769
-739
lines changed

7 files changed

+769
-739
lines changed

package-lock.json

Lines changed: 459 additions & 725 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,22 @@
1010
},
1111
"dependencies": {
1212
"@mdi/font": "7.4.47",
13-
"@zip.js/zip.js": "^2.8.15",
13+
"@zip.js/zip.js": "^2.8.22",
1414
"bluejay-rtttl-parse": "^2.0.2",
1515
"crypto-js": "^4.2.0",
1616
"esptool-js": "^0.5.7",
1717
"file-saver": "^2.0.5",
1818
"pako": "^2.1.0",
1919
"roboto-fontface": "*",
20-
"vue": "^3.5.27",
20+
"vue": "^3.5.29",
21+
"vue-localstorage": "^0.6.2",
2122
"vuetify": "^3.11.7"
2223
},
2324
"devDependencies": {
2425
"@types/file-saver": "^2.0.7",
2526
"@types/pako": "^2.0.4",
2627
"@types/w3c-web-serial": "^1.0.8",
27-
"@vitejs/plugin-vue": "^6.0.3",
28+
"@vitejs/plugin-vue": "^6.0.4",
2829
"sass": "1.97.3",
2930
"unplugin-fonts": "^1.4.0",
3031
"unplugin-vue-components": "^31.0.0",

src/components/BindPhraseInput.vue

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,58 @@
11
<script setup>
2-
import {ref} from "vue";
2+
import {ref, watch, onMounted} from "vue";
33
import {VTextField} from "vuetify/components";
44
import {uidBytesFromText} from "../js/phrase.js";
55
6+
const props = defineProps({
7+
bindPhraseText: {
8+
type: String,
9+
default: null
10+
}
11+
})
12+
13+
const emit = defineEmits(['update:bindPhraseText'])
14+
615
let model = defineModel()
716
817
let bindPhrase = ref(null)
918
let uid = ref('Bind Phrase')
1019
1120
function generateUID() {
12-
if (bindPhrase.value === '') uid.value = 'Bind Phrase'
13-
else {
21+
if (bindPhrase.value === '' || bindPhrase.value === null) {
22+
uid.value = 'Bind Phrase'
23+
model.value = null
24+
emit('update:bindPhraseText', null)
25+
} else {
1426
let val = Array.from(uidBytesFromText(bindPhrase.value))
1527
model.value = val
1628
uid.value = 'UID: ' + val
29+
emit('update:bindPhraseText', bindPhrase.value)
1730
}
1831
}
32+
33+
watch(() => model.value, (newVal) => {
34+
if (newVal && Array.isArray(newVal) && newVal.length > 0) {
35+
uid.value = 'UID: ' + newVal
36+
} else if (!newVal) {
37+
uid.value = 'Bind Phrase'
38+
}
39+
}, { immediate: true })
40+
41+
watch(() => props.bindPhraseText, (newVal) => {
42+
if (newVal && !bindPhrase.value) {
43+
bindPhrase.value = newVal
44+
generateUID()
45+
}
46+
}, { immediate: true })
47+
48+
onMounted(() => {
49+
if (props.bindPhraseText) {
50+
bindPhrase.value = props.bindPhraseText
51+
generateUID()
52+
} else if (model.value && Array.isArray(model.value) && model.value.length > 0) {
53+
uid.value = 'UID: ' + model.value
54+
}
55+
})
1956
</script>
2057

2158
<template>

src/js/storage.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const STORAGE_KEY = 'elrs-web-flasher_settings'
2+
3+
/**
4+
* Get all settings from localStorage
5+
* @returns {Object|null}
6+
*/
7+
export function getSettings() {
8+
try {
9+
const stored = localStorage.getItem(STORAGE_KEY)
10+
return stored ? JSON.parse(stored) : null
11+
} catch (e) {
12+
console.error('Error loading settings:', e)
13+
return null
14+
}
15+
}
16+
17+
/**
18+
* Save all settings to localStorage
19+
* @param {Object} settings - Settings object with shared properties and tx/rx sub-objects
20+
*/
21+
export function saveSettings(settings) {
22+
try {
23+
localStorage.setItem(STORAGE_KEY, JSON.stringify(settings))
24+
} catch (e) {
25+
console.error('Error saving settings:', e)
26+
}
27+
}
28+
29+
/**
30+
* Clear all stored settings
31+
*/
32+
export function clearSettings() {
33+
try {
34+
localStorage.removeItem(STORAGE_KEY)
35+
} catch (e) {
36+
console.error('Error clearing settings:', e)
37+
}
38+
}

src/pages/BackpackOptions.vue

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
<script setup>
2+
import {onMounted, watch, ref} from 'vue';
23
import {store} from "../js/state.js";
4+
import {getSettings, saveSettings, clearSettings as clearStoredSettings} from "../js/storage.js";
35
import {watchEffect} from "vue";
46
57
import BindPhraseInput from "../components/BindPhraseInput.vue";
68
import WiFiSettingsInput from "../components/WiFiSettingsInput.vue";
79
import FlashMethodSelect from "../components/FlashMethodSelect.vue";
810
import WiFiAutoOn from "../components/WiFiAutoOn.vue";
911
12+
const bindPhraseText = ref(null);
13+
1014
watchEffect(() => {
1115
if (store.targetType === 'txbp') {
1216
store.name = store.target?.config?.product_name + " Backpack"
@@ -19,19 +23,68 @@ watchEffect(() => {
1923
} else {
2024
store.name = store.target?.config?.product_name
2125
}
22-
})
26+
});
27+
28+
onMounted(() => {
29+
const savedSettings = getSettings();
30+
if (savedSettings) {
31+
if (savedSettings.uid !== undefined) store.options.uid = savedSettings.uid;
32+
if (savedSettings.bindPhraseText !== undefined) bindPhraseText.value = savedSettings.bindPhraseText;
33+
if (savedSettings.region !== undefined) store.options.region = savedSettings.region;
34+
if (savedSettings.domain !== undefined) store.options.domain = savedSettings.domain;
35+
if (savedSettings.ssid !== undefined) store.options.ssid = savedSettings.ssid;
36+
if (savedSettings.password !== undefined) store.options.password = savedSettings.password;
37+
if (savedSettings.wifiOnInternal !== undefined) store.options.wifiOnInternal = savedSettings.wifiOnInternal;
38+
}
39+
});
40+
41+
function saveAllSettings() {
42+
const settings = getSettings() || {};
43+
settings.uid = store.options.uid;
44+
settings.bindPhraseText = bindPhraseText.value;
45+
settings.region = store.options.region;
46+
settings.domain = store.options.domain;
47+
settings.ssid = store.options.ssid;
48+
settings.password = store.options.password;
49+
settings.wifiOnInternal = store.options.wifiOnInternal;
50+
saveSettings(settings);
51+
}
52+
53+
watch(() => store.options.uid, () => saveAllSettings(), { deep: false });
54+
watch(bindPhraseText, () => saveAllSettings());
55+
watch(() => store.options.ssid, () => saveAllSettings());
56+
watch(() => store.options.password, () => saveAllSettings());
57+
watch(() => store.options.wifiOnInternal, () => saveAllSettings());
58+
59+
function clearSettings() {
60+
clearStoredSettings();
61+
store.options.uid = null;
62+
bindPhraseText.value = null;
63+
store.options.region = 'FCC';
64+
store.options.domain = 1;
65+
store.options.ssid = null;
66+
store.options.password = null;
67+
store.options.wifiOnInternal = 60;
68+
store.options.flashMethod = null;
69+
}
2370
</script>
2471
2572
<template>
2673
<VContainer max-width="600px">
2774
<VCardTitle>Backpack Options</VCardTitle>
2875
<VCardText>Set the flashing options and method for your <b>{{ store.name }}</b></VCardText>
2976
<br>
30-
<BindPhraseInput v-model="store.options.uid"/>
31-
<WiFiSettingsInput v-model:ssid="store.options.ssid" v-model:password="store.options.password"
32-
v-if="store.target?.config?.platform!=='stm32'"/>
33-
<WiFiAutoOn v-model="store.options.wifiOnInternal"/>
77+
<VForm autocomplete="on" method="POST">
78+
<BindPhraseInput v-model="store.options.uid" :bind-phrase-text="bindPhraseText" @update:bindPhraseText="bindPhraseText = $event"/>
79+
<WiFiSettingsInput v-model:ssid="store.options.ssid" v-model:password="store.options.password"
80+
v-if="store.target?.config?.platform!=='stm32'"/>
81+
<WiFiAutoOn v-model="store.options.wifiOnInternal"/>
82+
83+
<FlashMethodSelect v-model="store.options.flashMethod" :methods="store.target?.config?.upload_methods"/>
3484
35-
<FlashMethodSelect v-model="store.options.flashMethod" :methods="store.target?.config?.upload_methods"/>
85+
<VBtn color="error" variant="outlined" size="small" @click="clearSettings" class="mt-4">
86+
Clear Stored Settings
87+
</VBtn>
88+
</VForm>
3689
</VContainer>
3790
</template>

src/pages/ReceiverOptions.vue

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<script setup>
2+
import {onMounted, watch, ref} from 'vue';
23
import {store} from "../js/state.js";
4+
import {getSettings, saveSettings, clearSettings as clearStoredSettings} from "../js/storage.js";
35
46
import BindPhraseInput from "../components/BindPhraseInput.vue";
57
import RFSelect from "../components/RFSelect.vue";
@@ -9,6 +11,84 @@ import WiFiAutoOn from "../components/WiFiAutoOn.vue";
911
import RXasTX from "../components/RXasTX.vue";
1012
import RXOptions from "../components/RXOptions.vue";
1113
import TXOptions from "../components/TXOptions.vue";
14+
15+
const bindPhraseText = ref(null);
16+
17+
onMounted(() => {
18+
const savedSettings = getSettings();
19+
if (savedSettings) {
20+
if (savedSettings.uid !== undefined) store.options.uid = savedSettings.uid;
21+
if (savedSettings.bindPhraseText !== undefined) bindPhraseText.value = savedSettings.bindPhraseText;
22+
if (savedSettings.region !== undefined) store.options.region = savedSettings.region;
23+
if (savedSettings.domain !== undefined) store.options.domain = savedSettings.domain;
24+
if (savedSettings.ssid !== undefined) store.options.ssid = savedSettings.ssid;
25+
if (savedSettings.password !== undefined) store.options.password = savedSettings.password;
26+
if (savedSettings.wifiOnInternal !== undefined) store.options.wifiOnInternal = savedSettings.wifiOnInternal;
27+
28+
if (savedSettings.rx) {
29+
if (savedSettings.rx.uartBaud !== undefined) store.options.rx.uartBaud = savedSettings.rx.uartBaud;
30+
if (savedSettings.rx.lockOnFirstConnect !== undefined) store.options.rx.lockOnFirstConnect = savedSettings.rx.lockOnFirstConnect;
31+
if (savedSettings.rx.r9mmMiniSBUS !== undefined) store.options.rx.r9mmMiniSBUS = savedSettings.rx.r9mmMiniSBUS;
32+
if (savedSettings.rx.fanMinRuntime !== undefined) store.options.rx.fanMinRuntime = savedSettings.rx.fanMinRuntime;
33+
if (savedSettings.rx.rxAsTx !== undefined) store.options.rx.rxAsTx = savedSettings.rx.rxAsTx;
34+
if (savedSettings.rx.rxAsTxType !== undefined) store.options.rx.rxAsTxType = savedSettings.rx.rxAsTxType;
35+
}
36+
}
37+
});
38+
39+
// Helper function to save all settings
40+
function saveAllSettings() {
41+
const settings = getSettings() || {};
42+
settings.uid = store.options.uid;
43+
settings.bindPhraseText = bindPhraseText.value;
44+
settings.region = store.options.region;
45+
settings.domain = store.options.domain;
46+
settings.ssid = store.options.ssid;
47+
settings.password = store.options.password;
48+
settings.wifiOnInternal = store.options.wifiOnInternal;
49+
50+
if (!settings.rx) settings.rx = {};
51+
settings.rx.uartBaud = store.options.rx.uartBaud;
52+
settings.rx.lockOnFirstConnect = store.options.rx.lockOnFirstConnect;
53+
settings.rx.r9mmMiniSBUS = store.options.rx.r9mmMiniSBUS;
54+
settings.rx.fanMinRuntime = store.options.rx.fanMinRuntime;
55+
settings.rx.rxAsTx = store.options.rx.rxAsTx;
56+
settings.rx.rxAsTxType = store.options.rx.rxAsTxType;
57+
58+
saveSettings(settings);
59+
}
60+
61+
watch(() => store.options.uid, () => saveAllSettings(), { deep: false });
62+
watch(bindPhraseText, () => saveAllSettings());
63+
watch(() => store.options.region, () => saveAllSettings());
64+
watch(() => store.options.domain, () => saveAllSettings());
65+
watch(() => store.options.ssid, () => saveAllSettings());
66+
watch(() => store.options.password, () => saveAllSettings());
67+
watch(() => store.options.wifiOnInternal, () => saveAllSettings());
68+
watch(() => store.options.rx.uartBaud, () => saveAllSettings());
69+
watch(() => store.options.rx.lockOnFirstConnect, () => saveAllSettings());
70+
watch(() => store.options.rx.r9mmMiniSBUS, () => saveAllSettings());
71+
watch(() => store.options.rx.fanMinRuntime, () => saveAllSettings());
72+
watch(() => store.options.rx.rxAsTx, () => saveAllSettings());
73+
watch(() => store.options.rx.rxAsTxType, () => saveAllSettings());
74+
75+
function clearSettings() {
76+
clearStoredSettings();
77+
store.options.uid = null;
78+
bindPhraseText.value = null;
79+
store.options.region = 'FCC';
80+
store.options.domain = 1;
81+
store.options.ssid = null;
82+
store.options.password = null;
83+
store.options.wifiOnInternal = 60;
84+
store.options.flashMethod = null;
85+
store.options.rx.uartBaud = 420000;
86+
store.options.rx.lockOnFirstConnect = true;
87+
store.options.rx.r9mmMiniSBUS = false;
88+
store.options.rx.fanMinRuntime = 30;
89+
store.options.rx.rxAsTx = false;
90+
store.options.rx.rxAsTxType = 0;
91+
}
1292
</script>
1393

1494
<template>
@@ -17,7 +97,7 @@ import TXOptions from "../components/TXOptions.vue";
1797
<VCardText>Set the flashing options and method for your <b>{{ store.target?.config?.product_name }}</b></VCardText>
1898
<br>
1999
<VForm autocomplete="on" method="POST">
20-
<BindPhraseInput v-model="store.options.uid"/>
100+
<BindPhraseInput v-model="store.options.uid" :bind-phrase-text="bindPhraseText" @update:bindPhraseText="bindPhraseText = $event"/>
21101
<RFSelect v-model:region="store.options.region" v-model:domain="store.options.domain" :radio="store.radio"/>
22102
<WiFiSettingsInput v-model:ssid="store.options.ssid" v-model:password="store.options.password"
23103
v-if="store.target?.config?.platform!=='stm32'"/>
@@ -34,6 +114,10 @@ import TXOptions from "../components/TXOptions.vue";
34114
</VExpansionPanelText>
35115
</VExpansionPanel>
36116
</VExpansionPanels>
117+
118+
<VBtn color="error" variant="outlined" size="small" @click="clearSettings" class="mt-4">
119+
Clear Stored Settings
120+
</VBtn>
37121
</VForm>
38122
</VContainer>
39123
</template>

0 commit comments

Comments
 (0)