diff --git a/src/assets/defaults.ts b/src/assets/defaults.ts index aa2f8bff8..54583aa29 100644 --- a/src/assets/defaults.ts +++ b/src/assets/defaults.ts @@ -13,7 +13,7 @@ export const defaultProfileVehicleCorrespondency = { } export const defaultWidgetManagerVars = { - timesMounted: 0, + everMounted: false, configMenuOpen: false, allowMoving: false, lastNonMaximizedX: 0.4, @@ -24,7 +24,7 @@ export const defaultWidgetManagerVars = { } export const defaultMiniWidgetManagerVars = { - timesMounted: 0, + everMounted: false, configMenuOpen: false, highlighted: false, } diff --git a/src/components/WidgetHugger.vue b/src/components/WidgetHugger.vue index b75c1ae00..4ed4f7cff 100644 --- a/src/components/WidgetHugger.vue +++ b/src/components/WidgetHugger.vue @@ -211,10 +211,10 @@ const resizeWidgetToMinimalSize = (): void => { } onMounted(async () => { - if (managerVars.value.timesMounted === 0) { + if (managerVars.value.everMounted === false) { resizeWidgetToMinimalSize() } - managerVars.value.timesMounted += 1 + managerVars.value.everMounted = true if (widgetResizeHandles.value) { for (let i = 0; i < widgetResizeHandles.value.length; i++) { diff --git a/src/composables/settingsSyncer.ts b/src/composables/settingsSyncer.ts new file mode 100644 index 000000000..9afd3c74e --- /dev/null +++ b/src/composables/settingsSyncer.ts @@ -0,0 +1,193 @@ +import { type RemovableRef, useStorage, watchThrottled } from '@vueuse/core' +import { type MaybeRef, onMounted, ref, unref } from 'vue' + +import { + getKeyDataFromCockpitVehicleStorage, + NoPathInBlueOsErrorName, + setKeyDataOnCockpitVehicleStorage, +} from '@/libs/blueos' +import { isEqual } from '@/libs/utils' +import { useAlertStore } from '@/stores/alert' +import { useMainVehicleStore } from '@/stores/mainVehicle' + +import { useInteractionDialog } from './interactionDialog' + +/** + * This composable will keep a setting in sync between the browser's local storage and BlueOS. + * + * When initialized, it will try to get the value from BlueOS. While BlueOS does not connect, it will use the local + * stored value and keep trying to communicate with BlueOS to get it's value. + * + * Once the connection is stablished, if BlueOS doesn't have a value, it will use the local stored one and update + * BlueOS with it. On the other hand, if BlueOS has a value, it will ask the user if they want to use the value from + * BlueOS or the local one. Depending on the user's choice, it will update the local value or BlueOS. + * + * Once everything is in sync, if the local value changes, it will update the value on BlueOS. + * In resume, the initial source of truth is decided by the user, and once everything is in sync, the source of truth + * is the local value. + * @param { string } key + * @param { T } defaultValue + * @returns { RemovableRef } + */ +export function useBlueOsStorage(key: string, defaultValue: MaybeRef): RemovableRef { + const { showDialog, closeDialog } = useInteractionDialog() + + const primitiveDefaultValue = unref(defaultValue) + const currentValue = useStorage(key, primitiveDefaultValue) + const finishedInitialFetch = ref(false) + let initialSyncTimeout: ReturnType | undefined = undefined + let blueOsUpdateTimeout: ReturnType | undefined = undefined + + const getVehicleAddress = async (): Promise => { + const vehicleStore = useMainVehicleStore() + + while (vehicleStore.globalAddress === undefined) { + console.debug('Waiting for vehicle global address on BlueOS sync routine.') + await new Promise((r) => setTimeout(r, 1000)) + // Wait until we have a global address + } + + return vehicleStore.globalAddress + } + + const askIfUserWantsToUseBlueOsValue = async (): Promise => { + let useBlueOsValue = true + + const preferBlueOs = (): void => { + useBlueOsValue = true + } + + const preferCockpit = (): void => { + useBlueOsValue = false + } + + await showDialog({ + title: 'Conflict with BlueOS', + message: ` + The value for '${key}' that is currently used in Cockpit differs from the one stored in BlueOS. What do you + want to do? + `, + variant: 'warning', + actions: [ + { text: 'Use the value from BlueOS', action: preferBlueOs }, + { text: "Keep Cockpit's value", action: preferCockpit }, + ], + }) + + closeDialog() + + return useBlueOsValue + } + + const updateValueOnBlueOS = async (newValue: T): Promise => { + const vehicleAddress = await getVehicleAddress() + const alertStore = useAlertStore() + + alertStore.pushInfoAlert(`Updating '${key}' on BlueOS.`) + + let timesTriedBlueOsUpdate = 0 + const tryToUpdateBlueOsValue = async (): Promise => { + // Clear update routine if there's one left, as we are going to start a new one + clearTimeout(blueOsUpdateTimeout) + + timesTriedBlueOsUpdate++ + try { + await setKeyDataOnCockpitVehicleStorage(vehicleAddress, key, newValue) + alertStore.pushSuccessAlert(`Success updating '${key}' on BlueOS.`) + } catch (fetchError) { + const errorMessage = `Failed updating '${key}' on BlueOS. Will keep trying.` + if (timesTriedBlueOsUpdate > 1) { + alertStore.pushErrorAlert(errorMessage) + } else { + console.error(errorMessage) + } + console.error(fetchError) + + // If we can't update the value on BlueOS, try again in 10 seconds + blueOsUpdateTimeout = setTimeout(tryToUpdateBlueOsValue, 10000) + } + } + + // Start BlueOS value update routine + tryToUpdateBlueOsValue() + } + + onMounted(async () => { + const vehicleAddress = await getVehicleAddress() + const alertStore = useAlertStore() + + alertStore.pushInfoAlert(`Started syncing '${key}' with BlueOS.`) + + let timesTriedInitialSync = 0 + const tryToDoInitialSync = async (): Promise => { + // Clear initial sync routine if there's one left, as we are going to start a new one + clearTimeout(initialSyncTimeout) + + timesTriedInitialSync++ + + try { + const valueOnBlueOS = await getKeyDataFromCockpitVehicleStorage(vehicleAddress, key) + console.log(`Success getting value of '${key}' from BlueOS:`, valueOnBlueOS) + + // If the value on BlueOS is the same as the one we have locally, we don't need to bother the user + if (isEqual(currentValue.value, valueOnBlueOS)) { + console.debug(`Value for '${key}' on BlueOS is the same as the local one. No need to update.`) + finishedInitialFetch.value = true + return + } + + // If Cockpit has a different value than BlueOS, ask the user if they want to use the value from BlueOS or + // if they want to update BlueOS with the value from Cockpit. + + const useBlueOsValue = await askIfUserWantsToUseBlueOsValue() + + if (useBlueOsValue) { + currentValue.value = valueOnBlueOS as T + } else { + updateValueOnBlueOS(currentValue.value) + } + + alertStore.pushSuccessAlert(`Success syncing '${key}' with BlueOS.`) + + finishedInitialFetch.value = true + } catch (initialSyncError) { + // If the initial sync fails because there's no value for the key on BlueOS, we can just use the current value + if ((initialSyncError as Error).name === NoPathInBlueOsErrorName) { + console.debug(`No value for '${key}' on BlueOS. Using current value.`) + updateValueOnBlueOS(currentValue.value) + finishedInitialFetch.value = true + return + } + + // If the initial sync fails because we can't connect to BlueOS, try again in 10 seconds + initialSyncTimeout = setTimeout(tryToDoInitialSync, 10000) + + const errorMessage = `Failed syncing '${key}' with BlueOS. Will keep trying.` + if (timesTriedInitialSync > 1) { + alertStore.pushErrorAlert(errorMessage) + } else { + console.error(errorMessage) + } + console.error(`Not able to get current value of '${key}' on BlueOS. ${initialSyncError}`) + } + } + + // Start initial sync routine + tryToDoInitialSync() + }) + + // Update BlueOS value when local value changes. + // Throttle to avoid spamming BlueOS with requests while the user is updating the value. + watchThrottled( + currentValue, + async (newValue) => { + // Don't update the value on BlueOS if we haven't finished the initial fetch, so we don't overwrite the value there without user consent + if (!finishedInitialFetch.value) return + + updateValueOnBlueOS(newValue) + }, + { throttle: 3000, deep: true } + ) + + return currentValue +} diff --git a/src/libs/blueos.ts b/src/libs/blueos.ts index 0b2a1a36a..0eaba093a 100644 --- a/src/libs/blueos.ts +++ b/src/libs/blueos.ts @@ -1,24 +1,25 @@ -import ky from 'ky' +import ky, { HTTPError } from 'ky' + +export const NoPathInBlueOsErrorName = 'NoPathInBlueOS' const defaultTimeout = 10000 /* eslint-disable @typescript-eslint/no-explicit-any */ export const getBagOfHoldingFromVehicle = async ( vehicleAddress: string, - bagName: string -): Promise> => { + bagPath: string +): Promise | any> => { try { - return await ky.get(`http://${vehicleAddress}/bag/v1.0/get/${bagName}`, { timeout: defaultTimeout }).json() + const options = { timeout: defaultTimeout, retry: 0 } + return await ky.get(`http://${vehicleAddress}/bag/v1.0/get/${bagPath}`, options).json() } catch (error) { - throw new Error(`Could not get bag of holdings for ${bagName}. ${error}`) - } -} - -export const getCockpitStorageFromVehicle = async (vehicleAddress: string): Promise> => { - try { - return await getBagOfHoldingFromVehicle(vehicleAddress, 'cockpit') - } catch (error) { - throw new Error(`Could not get Cockpit's storage data from vehicle. ${error}`) + const errorBody = await (error as HTTPError).response.json() + if (errorBody.detail === 'Invalid path') { + const noPathError = new Error(`No data available in BlueOS storage for path '${bagPath}'.`) + noPathError.name = NoPathInBlueOsErrorName + throw noPathError + } + throw new Error(`Could not get bag of holdings for ${bagPath}. ${error}`) } } @@ -26,8 +27,7 @@ export const getKeyDataFromCockpitVehicleStorage = async ( vehicleAddress: string, storageKey: string ): Promise | undefined> => { - const cockpitVehicleStorage = await getCockpitStorageFromVehicle(vehicleAddress) - return cockpitVehicleStorage[storageKey] + return await getBagOfHoldingFromVehicle(vehicleAddress, `cockpit/${storageKey}`) } export const setBagOfHoldingOnVehicle = async ( @@ -42,32 +42,12 @@ export const setBagOfHoldingOnVehicle = async ( } } -export const setCockpitStorageOnVehicle = async ( - vehicleAddress: string, - storageData: Record | any -): Promise => { - try { - await setBagOfHoldingOnVehicle(vehicleAddress, 'cockpit', storageData) - } catch (error) { - throw new Error(`Could not set Cockpit's storage data on vehicle. ${error}`) - } -} - export const setKeyDataOnCockpitVehicleStorage = async ( vehicleAddress: string, storageKey: string, storageData: Record | any ): Promise => { - let previousVehicleStorage: Record = {} - try { - previousVehicleStorage = await getCockpitStorageFromVehicle(vehicleAddress) - } catch (error) { - console.error(error) - } - const newVehicleStorage = previousVehicleStorage - newVehicleStorage[storageKey] = storageData - - await setCockpitStorageOnVehicle(vehicleAddress, newVehicleStorage) + await setBagOfHoldingOnVehicle(vehicleAddress, `cockpit/${storageKey}`, storageData) } /* eslint-disable jsdoc/require-jsdoc */ diff --git a/src/libs/sensors-logging.ts b/src/libs/sensors-logging.ts index 9b3e65090..cd295f892 100644 --- a/src/libs/sensors-logging.ts +++ b/src/libs/sensors-logging.ts @@ -1,8 +1,8 @@ -import { useStorage } from '@vueuse/core' import { differenceInMilliseconds, differenceInSeconds, format, intervalToDuration } from 'date-fns' import localforage from 'localforage' import Swal from 'sweetalert2' +import { useBlueOsStorage } from '@/composables/settingsSyncer' import { useMainVehicleStore } from '@/stores/mainVehicle' import { useMissionStore } from '@/stores/mission' @@ -199,7 +199,7 @@ class DataLogger { currentCockpitLog: CockpitStandardLog = [] variablesBeingUsed: DatalogVariable[] = [] veryGenericIndicators: VeryGenericData[] = [] - telemetryDisplayData = useStorage('cockpit-datalogger-overlay-grid', { + telemetryDisplayData = useBlueOsStorage('cockpit-datalogger-overlay-grid', { LeftTop: [], CenterTop: [], RightTop: [], @@ -210,7 +210,7 @@ class DataLogger { CenterBottom: [], RightBottom: [], }) - telemetryDisplayOptions = useStorage('cockpit-datalogger-overlay-options', { + telemetryDisplayOptions = useBlueOsStorage('cockpit-datalogger-overlay-options', { fontSize: 30, fontColor: '#FFFFFFFF', backgroundColor: '#000000FF', @@ -223,7 +223,7 @@ class DataLogger { fontUnderline: false, fontStrikeout: false, }) - logInterval = useStorage('cockpit-datalogger-log-interval', 1000) + logInterval = useBlueOsStorage('cockpit-datalogger-log-interval', 1000) cockpitLogsDB = localforage.createInstance({ driver: localforage.INDEXEDDB, name: 'Cockpit - Sensor Logs', @@ -476,7 +476,7 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text` return `&H${invertedAlpha}${blue}${green}${red}` } - + log.forEach((logPoint, index) => { // Don't deal with the last log point, as it has no next point to compare to if (index === log.length - 1) return @@ -521,7 +521,7 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text` const millisNextPoint = differenceInMilliseconds(new Date(log[index + 1].epoch), new Date(videoStartEpoch)) const remainingMillisNextPoint = millisNextPoint - roundedMillisNextPoint const remainingCentisNextPoint = Math.floor(remainingMillisNextPoint / 10).toString().padStart(2, '0') - + const timeThis = `${durationHoursThisPoint}:${durationMinutesThisPoint}:${durationSecondsThisPoint}.${remainingCentisThisPoint}` const timeNext = `${durationHoursNextPoint}:${durationMinutesNextPoint}:${durationSecondsNextPoint}.${remainingCentisNextPoint}` diff --git a/src/stores/alert.ts b/src/stores/alert.ts index 737ad28f9..ff3a332e0 100644 --- a/src/stores/alert.ts +++ b/src/stores/alert.ts @@ -1,16 +1,20 @@ -import { useStorage } from '@vueuse/core' import { defineStore } from 'pinia' import { computed, reactive, watch } from 'vue' +import { useBlueOsStorage } from '@/composables/settingsSyncer' + import { Alert, AlertLevel } from '../types/alert' export const useAlertStore = defineStore('alert', () => { const alerts = reactive([new Alert(AlertLevel.Success, 'Cockpit started')]) - const enableVoiceAlerts = useStorage('cockpit-enable-voice-alerts', true) + const enableVoiceAlerts = useBlueOsStorage('cockpit-enable-voice-alerts', true) // eslint-disable-next-line jsdoc/require-jsdoc const availableAlertSpeechVoices = reactive([]) - const selectedAlertSpeechVoiceName = useStorage('cockpit-selected-alert-speech-voice', undefined) - const enabledAlertLevels = useStorage('cockpit-enabled-alert-levels', [ + const selectedAlertSpeechVoiceName = useBlueOsStorage( + 'cockpit-selected-alert-speech-voice', + undefined + ) + const enabledAlertLevels = useBlueOsStorage('cockpit-enabled-alert-levels', [ { level: AlertLevel.Success, enabled: true }, { level: AlertLevel.Error, enabled: true }, { level: AlertLevel.Info, enabled: false }, @@ -48,6 +52,22 @@ export const useAlertStore = defineStore('alert', () => { } } + const pushSuccessAlert = (message: string, time_created: Date = new Date()): void => { + pushAlert(new Alert(AlertLevel.Success, message, time_created)) + } + const pushErrorAlert = (message: string, time_created: Date = new Date()): void => { + pushAlert(new Alert(AlertLevel.Error, message, time_created)) + } + const pushInfoAlert = (message: string, time_created: Date = new Date()): void => { + pushAlert(new Alert(AlertLevel.Info, message, time_created)) + } + const pushWarningAlert = (message: string, time_created: Date = new Date()): void => { + pushAlert(new Alert(AlertLevel.Warning, message, time_created)) + } + const pushCriticalAlert = (message: string, time_created: Date = new Date()): void => { + pushAlert(new Alert(AlertLevel.Critical, message, time_created)) + } + // Alert speech syntesis routine const synth = window.speechSynthesis @@ -135,5 +155,10 @@ export const useAlertStore = defineStore('alert', () => { availableAlertSpeechVoiceNames, sortedAlerts, pushAlert, + pushSuccessAlert, + pushErrorAlert, + pushInfoAlert, + pushWarningAlert, + pushCriticalAlert, } }) diff --git a/src/stores/controller.ts b/src/stores/controller.ts index 5e408cb6d..57a80858f 100644 --- a/src/stores/controller.ts +++ b/src/stores/controller.ts @@ -1,4 +1,4 @@ -import { useDocumentVisibility, useStorage } from '@vueuse/core' +import { useDocumentVisibility } from '@vueuse/core' import { saveAs } from 'file-saver' import { defineStore } from 'pinia' import Swal from 'sweetalert2' @@ -10,6 +10,7 @@ import { cockpitStandardToProtocols, defaultProtocolMappingVehicleCorrespondency, } from '@/assets/joystick-profiles' +import { useBlueOsStorage } from '@/composables/settingsSyncer' import { getKeyDataFromCockpitVehicleStorage, setKeyDataOnCockpitVehicleStorage } from '@/libs/blueos' import { MavType } from '@/libs/connection/m2r/messages/mavlink2rest-enum' import { type JoystickEvent, EventType, joystickManager, JoystickModel } from '@/libs/joystick/manager' @@ -17,7 +18,6 @@ import { allAvailableAxes, allAvailableButtons } from '@/libs/joystick/protocols import { modifierKeyActions, otherAvailableActions } from '@/libs/joystick/protocols/other' import { Alert, AlertLevel } from '@/types/alert' import { - type GamepadToCockpitStdMapping, type JoystickProtocolActionsMapping, type JoystickState, type ProtocolAction, @@ -45,19 +45,19 @@ export const useControllerStore = defineStore('controller', () => { const alertStore = useAlertStore() const joysticks = ref>(new Map()) const updateCallbacks = ref([]) - const protocolMappings = useStorage(protocolMappingsKey, cockpitStandardToProtocols) - const protocolMappingIndex = useStorage(protocolMappingIndexKey, 0) - const cockpitStdMappings = useStorage(cockpitStdMappingsKey, availableGamepadToCockpitMaps) + const protocolMappings = useBlueOsStorage(protocolMappingsKey, cockpitStandardToProtocols) + const protocolMappingIndex = useBlueOsStorage(protocolMappingIndexKey, 0) + const cockpitStdMappings = useBlueOsStorage(cockpitStdMappingsKey, availableGamepadToCockpitMaps) const availableAxesActions = allAvailableAxes const availableButtonActions = allAvailableButtons const enableForwarding = ref(false) - const holdLastInputWhenWindowHidden = useStorage('cockpit-hold-last-joystick-input-when-window-hidden', false) - const vehicleTypeProtocolMappingCorrespondency = useStorage( + const holdLastInputWhenWindowHidden = useBlueOsStorage('cockpit-hold-last-joystick-input-when-window-hidden', false) + const vehicleTypeProtocolMappingCorrespondency = useBlueOsStorage( 'cockpit-default-vehicle-type-protocol-mappings', defaultProtocolMappingVehicleCorrespondency ) // Confirmation per joystick action required currently is only available for cockpit actions - const actionsJoystickConfirmRequired = useStorage( + const actionsJoystickConfirmRequired = useBlueOsStorage( 'cockpit-actions-joystick-confirm-required', {} as Record ) @@ -268,36 +268,6 @@ export const useControllerStore = defineStore('controller', () => { reader.readAsText(e.target.files[0]) } - const exportJoysticksMappingsToVehicle = async ( - vehicleAddress: string, - joystickMappings: { [key in JoystickModel]: GamepadToCockpitStdMapping } - ): Promise => { - await setKeyDataOnCockpitVehicleStorage(vehicleAddress, cockpitStdMappingsKey, joystickMappings) - Swal.fire({ icon: 'success', text: 'Joystick mapping exported to vehicle.', timer: 3000 }) - } - - const importJoysticksMappingsFromVehicle = async (vehicleAddress: string): Promise => { - const newMapping = await getKeyDataFromCockpitVehicleStorage(vehicleAddress, cockpitStdMappingsKey) - if (!newMapping) { - Swal.fire({ icon: 'error', text: 'No joystick mappings to import from vehicle.', timer: 3000 }) - return - } - try { - Object.values(newMapping).forEach((mapping) => { - if (!mapping['name'] || !mapping['axes'] || !mapping['buttons']) { - throw Error('Invalid joystick mapping inside vehicle.') - } - }) - } catch (error) { - Swal.fire({ icon: 'error', text: `Could not import joystick mapping from vehicle. ${error}`, timer: 3000 }) - return - } - - // @ts-ignore: We check for the necessary fields in the if before - cockpitStdMappings.value = newMapping - Swal.fire({ icon: 'success', text: 'Joystick mapping imported from vehicle.', timer: 3000 }) - } - const exportFunctionsMapping = (protocolActionsMapping: JoystickProtocolActionsMapping): void => { const blob = new Blob([JSON.stringify(protocolActionsMapping)], { type: 'text/plain;charset=utf-8' }) saveAs(blob, `cockpit-std-profile-joystick-${protocolActionsMapping.name}.json`) @@ -393,8 +363,6 @@ export const useControllerStore = defineStore('controller', () => { loadProtocolMapping, exportJoystickMapping, importJoystickMapping, - exportJoysticksMappingsToVehicle, - importJoysticksMappingsFromVehicle, exportFunctionsMapping, importFunctionsMapping, exportFunctionsMappingToVehicle, diff --git a/src/stores/development.ts b/src/stores/development.ts index 278c47b79..92ee3aef7 100644 --- a/src/stores/development.ts +++ b/src/stores/development.ts @@ -1,12 +1,13 @@ -import { useStorage } from '@vueuse/core' import { defineStore } from 'pinia' import { ref } from 'vue' +import { useBlueOsStorage } from '@/composables/settingsSyncer' + export const systemLoggingEnablingKey = 'cockpit-enable-system-logging' export const useDevelopmentStore = defineStore('development', () => { const developmentMode = ref(false) const widgetDevInfoBlurLevel = ref(3) - const enableSystemLogging = useStorage(systemLoggingEnablingKey, true) + const enableSystemLogging = useBlueOsStorage(systemLoggingEnablingKey, true) return { developmentMode, widgetDevInfoBlurLevel, enableSystemLogging } }) diff --git a/src/stores/mainVehicle.ts b/src/stores/mainVehicle.ts index cf8b72b43..6b5f3f134 100644 --- a/src/stores/mainVehicle.ts +++ b/src/stores/mainVehicle.ts @@ -1,8 +1,9 @@ -import { useStorage, useTimestamp, watchThrottled } from '@vueuse/core' +import { useTimestamp, watchThrottled } from '@vueuse/core' import { defineStore } from 'pinia' import { computed, reactive, ref, watch } from 'vue' import { defaultGlobalAddress } from '@/assets/defaults' +import { useBlueOsStorage } from '@/composables/settingsSyncer' import { altitude_setpoint } from '@/libs/altitude-slider' import { getCpuTempCelsius, getStatus } from '@/libs/blueos' import * as Connection from '@/libs/connection/connection' @@ -63,19 +64,19 @@ export const useMainVehicleStore = defineStore('main-vehicle', () => { const ws_protocol = location?.protocol === 'https:' ? 'wss' : 'ws' const cpuLoad = ref() - const globalAddress = useStorage('cockpit-vehicle-address', defaultGlobalAddress) + const globalAddress = useBlueOsStorage('cockpit-vehicle-address', defaultGlobalAddress) const defaultMainConnectionURI = ref(`${ws_protocol}://${globalAddress.value}/mavlink2rest/ws/mavlink`) const defaultWebRTCSignallingURI = ref(`${ws_protocol}://${globalAddress.value}:6021/`) - const customMainConnectionURI = useStorage('cockpit-vehicle-custom-main-connection-uri', { + const customMainConnectionURI = useBlueOsStorage('cockpit-vehicle-custom-main-connection-uri', { data: defaultMainConnectionURI.value, enabled: false, } as CustomParameter) - const customWebRTCSignallingURI = useStorage('cockpit-vehicle-custom-webrtc-signalling-uri', { + const customWebRTCSignallingURI = useBlueOsStorage('cockpit-vehicle-custom-webrtc-signalling-uri', { data: defaultWebRTCSignallingURI.value, enabled: false, } as CustomParameter) - const customWebRTCConfiguration = useStorage('cockpit-custom-rtc-config', { + const customWebRTCConfiguration = useBlueOsStorage('cockpit-custom-rtc-config', { data: defaultRtcConfiguration, enabled: false, }) diff --git a/src/stores/mission.ts b/src/stores/mission.ts index aab1f174d..7401faab2 100644 --- a/src/stores/mission.ts +++ b/src/stores/mission.ts @@ -1,21 +1,19 @@ -import { useStorage } from '@vueuse/core' import { defineStore } from 'pinia' import { reactive, ref, watch } from 'vue' +import { useBlueOsStorage } from '@/composables/settingsSyncer' import { eventCategoriesDefaultMapping } from '@/libs/slide-to-confirm' import type { Waypoint, WaypointCoordinates } from '@/types/mission' export const useMissionStore = defineStore('mission', () => { const missionName = ref('') - const lastMissionName = useStorage('cockpit-last-mission-name', '') - const missionStartTime = useStorage('cockpit-mission-start-time', new Date()) - const slideEventsEnabled = useStorage('cockpit-slide-events-enabled', true) - const slideEventsCategoriesRequired = useStorage( + const slideEventsEnabled = useBlueOsStorage('cockpit-slide-events-enabled', true) + const slideEventsCategoriesRequired = useBlueOsStorage( 'cockpit-slide-events-categories-required', - eventCategoriesDefaultMapping, - localStorage, - { mergeDefaults: true } + eventCategoriesDefaultMapping ) + const lastMissionName = useBlueOsStorage('cockpit-last-mission-name', '') + const missionStartTime = useBlueOsStorage('cockpit-mission-start-time', new Date()) watch(missionName, () => (lastMissionName.value = missionName.value)) diff --git a/src/stores/video.ts b/src/stores/video.ts index a1f733678..754ec93a2 100644 --- a/src/stores/video.ts +++ b/src/stores/video.ts @@ -1,4 +1,4 @@ -import { useDebounceFn, useStorage, useThrottleFn, useTimestamp } from '@vueuse/core' +import { useDebounceFn, useThrottleFn, useTimestamp } from '@vueuse/core' import { BlobReader, BlobWriter, ZipWriter } from '@zip.js/zip.js' import { differenceInSeconds, format } from 'date-fns' import { saveAs } from 'file-saver' @@ -11,6 +11,7 @@ import fixWebmDuration from 'webm-duration-fix' import adapter from 'webrtc-adapter' import { useInteractionDialog } from '@/composables/interactionDialog' +import { useBlueOsStorage } from '@/composables/settingsSyncer' import { WebRTCManager } from '@/composables/webRTC' import { getIpsInformationFromVehicle } from '@/libs/blueos' import { availableCockpitActions, registerActionCallback } from '@/libs/joystick/protocols/cockpit-actions' @@ -40,13 +41,16 @@ export const useVideoStore = defineStore('video', () => { const { globalAddress, rtcConfiguration, webRTCSignallingURI } = useMainVehicleStore() console.debug('[WebRTC] Using webrtc-adapter for', adapter.browserDetails) - const allowedIceIps = useStorage('cockpit-allowed-stream-ips', []) - const allowedIceProtocols = useStorage('cockpit-allowed-stream-protocols', []) - const jitterBufferTarget = useStorage('cockpit-jitter-buffer-target', 0) + const allowedIceIps = useBlueOsStorage('cockpit-allowed-stream-ips', []) + const allowedIceProtocols = useBlueOsStorage('cockpit-allowed-stream-protocols', []) + const jitterBufferTarget = useBlueOsStorage('cockpit-jitter-buffer-target', 0) const activeStreams = ref<{ [key in string]: StreamData | undefined }>({}) const mainWebRTCManager = new WebRTCManager(webRTCSignallingURI, rtcConfiguration) const availableIceIps = ref([]) - const unprocessedVideos = useStorage<{ [key in string]: UnprocessedVideoInfo }>('cockpit-unprocessed-video-info', {}) + const unprocessedVideos = useBlueOsStorage<{ [key in string]: UnprocessedVideoInfo }>( + 'cockpit-unprocessed-video-info', + {} + ) const timeNow = useTimestamp({ interval: 500 }) const namesAvailableStreams = computed(() => mainWebRTCManager.availableStreams.value.map((stream) => stream.name)) diff --git a/src/stores/widgetManager.ts b/src/stores/widgetManager.ts index a4ab95979..0acafea53 100644 --- a/src/stores/widgetManager.ts +++ b/src/stores/widgetManager.ts @@ -1,6 +1,6 @@ import '@/libs/cosmos' -import { useDebounceFn, useStorage, useWindowSize } from '@vueuse/core' +import { useDebounceFn, useWindowSize } from '@vueuse/core' import { saveAs } from 'file-saver' import { defineStore } from 'pinia' import Swal from 'sweetalert2' @@ -9,6 +9,7 @@ import { computed, onBeforeMount, onBeforeUnmount, ref, watch } from 'vue' import { defaultProfileVehicleCorrespondency, defaultWidgetManagerVars, widgetProfiles } from '@/assets/defaults' import { miniWidgetsProfile } from '@/assets/defaults' +import { useBlueOsStorage } from '@/composables/settingsSyncer' import { getKeyDataFromCockpitVehicleStorage, setKeyDataOnCockpitVehicleStorage } from '@/libs/blueos' import { MavType } from '@/libs/connection/m2r/messages/mavlink2rest-enum' import * as Words from '@/libs/funny-name/words' @@ -32,14 +33,14 @@ export const useWidgetManagerStore = defineStore('widget-manager', () => { const editingMode = ref(false) const snapToGrid = ref(true) const gridInterval = ref(0.01) - const currentMiniWidgetsProfile = useStorage('cockpit-mini-widgets-profile-v4', miniWidgetsProfile) - const savedProfiles = useStorage(savedProfilesKey, []) - const currentViewIndex = useStorage('cockpit-current-view-index', 0) - const currentProfileIndex = useStorage('cockpit-current-profile-index', 0) + const currentMiniWidgetsProfile = useBlueOsStorage('cockpit-mini-widgets-profile-v4', miniWidgetsProfile) + const savedProfiles = useBlueOsStorage(savedProfilesKey, []) + const currentViewIndex = useBlueOsStorage('cockpit-current-view-index', 0) + const currentProfileIndex = useBlueOsStorage('cockpit-current-profile-index', 0) const desiredTopBarHeightPixels = ref(48) const desiredBottomBarHeightPixels = ref(48) const visibleAreaMinClearancePixels = ref(20) - const vehicleTypeProfileCorrespondency = useStorage( + const vehicleTypeProfileCorrespondency = useBlueOsStorage( 'cockpit-default-vehicle-type-profiles', defaultProfileVehicleCorrespondency ) @@ -592,9 +593,29 @@ export const useWidgetManagerStore = defineStore('widget-manager', () => { v.visible = v.visible ?? true // If there's any configuration menu open, close it - v.widgets.forEach((w) => (w.managerVars.configMenuOpen = false)) - v.miniWidgetContainers.forEach((c) => c.widgets.forEach((w) => (w.managerVars.configMenuOpen = false))) + v.widgets.forEach((w) => { + w.managerVars.configMenuOpen = false + w.managerVars.everMounted = true + // @ts-ignore: This is an old value that we are removing on those that still hold it + w.managerVars.timesMounted = undefined + }) + v.miniWidgetContainers.forEach((c) => + c.widgets.forEach((w) => { + w.managerVars.configMenuOpen = false + w.managerVars.everMounted = true + // @ts-ignore: This is an old value that we are removing on those that still hold it + w.managerVars.timesMounted = undefined + }) + ) }) + + currentMiniWidgetsProfile.value.containers.forEach((c) => + c.widgets.forEach((w) => { + w.managerVars.everMounted = true + // @ts-ignore: This is an old value that we are removing on those that still hold it + w.managerVars.timesMounted = undefined + }) + ) }) }) diff --git a/src/types/miniWidgets.ts b/src/types/miniWidgets.ts index cad5039c7..3f7ac8a84 100644 --- a/src/types/miniWidgets.ts +++ b/src/types/miniWidgets.ts @@ -42,7 +42,7 @@ export type MiniWidget = { /** * Number of times the mini-widget was mounted */ - timesMounted: number + everMounted: boolean /** * If the configuration menu is open or not */ diff --git a/src/types/widgets.ts b/src/types/widgets.ts index 3331b8b7e..372e3986d 100644 --- a/src/types/widgets.ts +++ b/src/types/widgets.ts @@ -51,7 +51,7 @@ export type Widget = { /** * Number of times the widget was mounted */ - timesMounted: number + everMounted: boolean /** * If the configuration menu is open or not */ @@ -128,7 +128,7 @@ export type Profile = { export const isWidget = (maybeWidget: Widget): maybeWidget is Widget => { const widgetProps = ['hash', 'component', 'position', 'size', 'name', 'options', 'managerVars'] - const managetVarsProps = ['timesMounted'] + const managetVarsProps = ['everMounted'] let realWidget = true widgetProps.forEach((p) => { // @ts-ignore diff --git a/src/views/ConfigurationJoystickView.vue b/src/views/ConfigurationJoystickView.vue index b20fc8dbe..f96b718cd 100644 --- a/src/views/ConfigurationJoystickView.vue +++ b/src/views/ConfigurationJoystickView.vue @@ -145,20 +145,6 @@ /> Import from computer - -
@@ -179,20 +165,6 @@ /> Import from computer - -
@@ -338,7 +310,6 @@