Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,184 changes: 459 additions & 725 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,22 @@
},
"dependencies": {
"@mdi/font": "7.4.47",
"@zip.js/zip.js": "^2.8.15",
"@zip.js/zip.js": "^2.8.22",
"bluejay-rtttl-parse": "^2.0.2",
"crypto-js": "^4.2.0",
"esptool-js": "^0.5.7",
"file-saver": "^2.0.5",
"pako": "^2.1.0",
"roboto-fontface": "*",
"vue": "^3.5.27",
"vue": "^3.5.29",
"vue-localstorage": "^0.6.2",
"vuetify": "^3.11.7"
},
"devDependencies": {
"@types/file-saver": "^2.0.7",
"@types/pako": "^2.0.4",
"@types/w3c-web-serial": "^1.0.8",
"@vitejs/plugin-vue": "^6.0.3",
"@vitejs/plugin-vue": "^6.0.4",
"sass": "1.97.3",
"unplugin-fonts": "^1.4.0",
"unplugin-vue-components": "^31.0.0",
Expand Down
43 changes: 40 additions & 3 deletions src/components/BindPhraseInput.vue
Original file line number Diff line number Diff line change
@@ -1,21 +1,58 @@
<script setup>
import {ref} from "vue";
import {ref, watch, onMounted} from "vue";
import {VTextField} from "vuetify/components";
import {uidBytesFromText} from "../js/phrase.js";

const props = defineProps({
bindPhraseText: {
type: String,
default: null
}
})

const emit = defineEmits(['update:bindPhraseText'])

let model = defineModel()

let bindPhrase = ref(null)
let uid = ref('Bind Phrase')

function generateUID() {
if (bindPhrase.value === '') uid.value = 'Bind Phrase'
else {
if (bindPhrase.value === '' || bindPhrase.value === null) {
uid.value = 'Bind Phrase'
model.value = null
emit('update:bindPhraseText', null)
} else {
let val = Array.from(uidBytesFromText(bindPhrase.value))
model.value = val
uid.value = 'UID: ' + val
emit('update:bindPhraseText', bindPhrase.value)
}
}

watch(() => model.value, (newVal) => {
if (newVal && Array.isArray(newVal) && newVal.length > 0) {
uid.value = 'UID: ' + newVal
} else if (!newVal) {
uid.value = 'Bind Phrase'
}
}, { immediate: true })

watch(() => props.bindPhraseText, (newVal) => {
if (newVal && !bindPhrase.value) {
bindPhrase.value = newVal
generateUID()
}
}, { immediate: true })

onMounted(() => {
if (props.bindPhraseText) {
bindPhrase.value = props.bindPhraseText
generateUID()
} else if (model.value && Array.isArray(model.value) && model.value.length > 0) {
uid.value = 'UID: ' + model.value
}
})
</script>

<template>
Expand Down
38 changes: 38 additions & 0 deletions src/js/storage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const STORAGE_KEY = 'elrs-web-flasher_settings'

/**
* Get all settings from localStorage
* @returns {Object|null}
*/
export function getSettings() {
try {
const stored = localStorage.getItem(STORAGE_KEY)
return stored ? JSON.parse(stored) : null
} catch (e) {
console.error('Error loading settings:', e)
return null
}
}

/**
* Save all settings to localStorage
* @param {Object} settings - Settings object with shared properties and tx/rx sub-objects
*/
export function saveSettings(settings) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(settings))
} catch (e) {
console.error('Error saving settings:', e)
}
}

/**
* Clear all stored settings
*/
export function clearSettings() {
try {
localStorage.removeItem(STORAGE_KEY)
} catch (e) {
console.error('Error clearing settings:', e)
}
}
65 changes: 59 additions & 6 deletions src/pages/BackpackOptions.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
<script setup>
import {onMounted, watch, ref} from 'vue';
import {store} from "../js/state.js";
import {getSettings, saveSettings, clearSettings as clearStoredSettings} from "../js/storage.js";
import {watchEffect} from "vue";

import BindPhraseInput from "../components/BindPhraseInput.vue";
import WiFiSettingsInput from "../components/WiFiSettingsInput.vue";
import FlashMethodSelect from "../components/FlashMethodSelect.vue";
import WiFiAutoOn from "../components/WiFiAutoOn.vue";

const bindPhraseText = ref(null);

watchEffect(() => {
if (store.targetType === 'txbp') {
store.name = store.target?.config?.product_name + " Backpack"
Expand All @@ -19,19 +23,68 @@ watchEffect(() => {
} else {
store.name = store.target?.config?.product_name
}
})
});

onMounted(() => {
const savedSettings = getSettings();
if (savedSettings) {
if (savedSettings.uid !== undefined) store.options.uid = savedSettings.uid;
if (savedSettings.bindPhraseText !== undefined) bindPhraseText.value = savedSettings.bindPhraseText;
if (savedSettings.region !== undefined) store.options.region = savedSettings.region;
if (savedSettings.domain !== undefined) store.options.domain = savedSettings.domain;
if (savedSettings.ssid !== undefined) store.options.ssid = savedSettings.ssid;
if (savedSettings.password !== undefined) store.options.password = savedSettings.password;
if (savedSettings.wifiOnInternal !== undefined) store.options.wifiOnInternal = savedSettings.wifiOnInternal;
}
});

function saveAllSettings() {
const settings = getSettings() || {};
settings.uid = store.options.uid;
settings.bindPhraseText = bindPhraseText.value;
settings.region = store.options.region;
settings.domain = store.options.domain;
settings.ssid = store.options.ssid;
settings.password = store.options.password;
settings.wifiOnInternal = store.options.wifiOnInternal;
saveSettings(settings);
}

watch(() => store.options.uid, () => saveAllSettings(), { deep: false });
watch(bindPhraseText, () => saveAllSettings());
watch(() => store.options.ssid, () => saveAllSettings());
watch(() => store.options.password, () => saveAllSettings());
watch(() => store.options.wifiOnInternal, () => saveAllSettings());

function clearSettings() {
clearStoredSettings();
store.options.uid = null;
bindPhraseText.value = null;
store.options.region = 'FCC';
store.options.domain = 1;
store.options.ssid = null;
store.options.password = null;
store.options.wifiOnInternal = 60;
store.options.flashMethod = null;
}
</script>

<template>
<VContainer max-width="600px">
<VCardTitle>Backpack Options</VCardTitle>
<VCardText>Set the flashing options and method for your <b>{{ store.name }}</b></VCardText>
<br>
<BindPhraseInput v-model="store.options.uid"/>
<WiFiSettingsInput v-model:ssid="store.options.ssid" v-model:password="store.options.password"
v-if="store.target?.config?.platform!=='stm32'"/>
<WiFiAutoOn v-model="store.options.wifiOnInternal"/>
<VForm autocomplete="on" method="POST">
<BindPhraseInput v-model="store.options.uid" :bind-phrase-text="bindPhraseText" @update:bindPhraseText="bindPhraseText = $event"/>
<WiFiSettingsInput v-model:ssid="store.options.ssid" v-model:password="store.options.password"
v-if="store.target?.config?.platform!=='stm32'"/>
<WiFiAutoOn v-model="store.options.wifiOnInternal"/>

<FlashMethodSelect v-model="store.options.flashMethod" :methods="store.target?.config?.upload_methods"/>

<FlashMethodSelect v-model="store.options.flashMethod" :methods="store.target?.config?.upload_methods"/>
<VBtn color="error" variant="outlined" size="small" @click="clearSettings" class="mt-4">
Clear Stored Settings
</VBtn>
</VForm>
</VContainer>
</template>
86 changes: 85 additions & 1 deletion src/pages/ReceiverOptions.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<script setup>
import {onMounted, watch, ref} from 'vue';
import {store} from "../js/state.js";
import {getSettings, saveSettings, clearSettings as clearStoredSettings} from "../js/storage.js";

import BindPhraseInput from "../components/BindPhraseInput.vue";
import RFSelect from "../components/RFSelect.vue";
Expand All @@ -9,6 +11,84 @@ import WiFiAutoOn from "../components/WiFiAutoOn.vue";
import RXasTX from "../components/RXasTX.vue";
import RXOptions from "../components/RXOptions.vue";
import TXOptions from "../components/TXOptions.vue";

const bindPhraseText = ref(null);

onMounted(() => {
const savedSettings = getSettings();
if (savedSettings) {
if (savedSettings.uid !== undefined) store.options.uid = savedSettings.uid;
if (savedSettings.bindPhraseText !== undefined) bindPhraseText.value = savedSettings.bindPhraseText;
if (savedSettings.region !== undefined) store.options.region = savedSettings.region;
if (savedSettings.domain !== undefined) store.options.domain = savedSettings.domain;
if (savedSettings.ssid !== undefined) store.options.ssid = savedSettings.ssid;
if (savedSettings.password !== undefined) store.options.password = savedSettings.password;
if (savedSettings.wifiOnInternal !== undefined) store.options.wifiOnInternal = savedSettings.wifiOnInternal;

if (savedSettings.rx) {
if (savedSettings.rx.uartBaud !== undefined) store.options.rx.uartBaud = savedSettings.rx.uartBaud;
if (savedSettings.rx.lockOnFirstConnect !== undefined) store.options.rx.lockOnFirstConnect = savedSettings.rx.lockOnFirstConnect;
if (savedSettings.rx.r9mmMiniSBUS !== undefined) store.options.rx.r9mmMiniSBUS = savedSettings.rx.r9mmMiniSBUS;
if (savedSettings.rx.fanMinRuntime !== undefined) store.options.rx.fanMinRuntime = savedSettings.rx.fanMinRuntime;
if (savedSettings.rx.rxAsTx !== undefined) store.options.rx.rxAsTx = savedSettings.rx.rxAsTx;
if (savedSettings.rx.rxAsTxType !== undefined) store.options.rx.rxAsTxType = savedSettings.rx.rxAsTxType;
}
}
});

// Helper function to save all settings
function saveAllSettings() {
const settings = getSettings() || {};
settings.uid = store.options.uid;
settings.bindPhraseText = bindPhraseText.value;
settings.region = store.options.region;
settings.domain = store.options.domain;
settings.ssid = store.options.ssid;
settings.password = store.options.password;
settings.wifiOnInternal = store.options.wifiOnInternal;

if (!settings.rx) settings.rx = {};
settings.rx.uartBaud = store.options.rx.uartBaud;
settings.rx.lockOnFirstConnect = store.options.rx.lockOnFirstConnect;
settings.rx.r9mmMiniSBUS = store.options.rx.r9mmMiniSBUS;
settings.rx.fanMinRuntime = store.options.rx.fanMinRuntime;
settings.rx.rxAsTx = store.options.rx.rxAsTx;
settings.rx.rxAsTxType = store.options.rx.rxAsTxType;

saveSettings(settings);
}

watch(() => store.options.uid, () => saveAllSettings(), { deep: false });
watch(bindPhraseText, () => saveAllSettings());
watch(() => store.options.region, () => saveAllSettings());
watch(() => store.options.domain, () => saveAllSettings());
watch(() => store.options.ssid, () => saveAllSettings());
watch(() => store.options.password, () => saveAllSettings());
watch(() => store.options.wifiOnInternal, () => saveAllSettings());
watch(() => store.options.rx.uartBaud, () => saveAllSettings());
watch(() => store.options.rx.lockOnFirstConnect, () => saveAllSettings());
watch(() => store.options.rx.r9mmMiniSBUS, () => saveAllSettings());
watch(() => store.options.rx.fanMinRuntime, () => saveAllSettings());
watch(() => store.options.rx.rxAsTx, () => saveAllSettings());
watch(() => store.options.rx.rxAsTxType, () => saveAllSettings());

function clearSettings() {
clearStoredSettings();
store.options.uid = null;
bindPhraseText.value = null;
store.options.region = 'FCC';
store.options.domain = 1;
store.options.ssid = null;
store.options.password = null;
store.options.wifiOnInternal = 60;
store.options.flashMethod = null;
store.options.rx.uartBaud = 420000;
store.options.rx.lockOnFirstConnect = true;
store.options.rx.r9mmMiniSBUS = false;
store.options.rx.fanMinRuntime = 30;
store.options.rx.rxAsTx = false;
store.options.rx.rxAsTxType = 0;
}
</script>

<template>
Expand All @@ -17,7 +97,7 @@ import TXOptions from "../components/TXOptions.vue";
<VCardText>Set the flashing options and method for your <b>{{ store.target?.config?.product_name }}</b></VCardText>
<br>
<VForm autocomplete="on" method="POST">
<BindPhraseInput v-model="store.options.uid"/>
<BindPhraseInput v-model="store.options.uid" :bind-phrase-text="bindPhraseText" @update:bindPhraseText="bindPhraseText = $event"/>
<RFSelect v-model:region="store.options.region" v-model:domain="store.options.domain" :radio="store.radio"/>
<WiFiSettingsInput v-model:ssid="store.options.ssid" v-model:password="store.options.password"
v-if="store.target?.config?.platform!=='stm32'"/>
Expand All @@ -34,6 +114,10 @@ import TXOptions from "../components/TXOptions.vue";
</VExpansionPanelText>
</VExpansionPanel>
</VExpansionPanels>

<VBtn color="error" variant="outlined" size="small" @click="clearSettings" class="mt-4">
Clear Stored Settings
</VBtn>
</VForm>
</VContainer>
</template>
Loading
Loading