mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-04 05:39:00 -05:00
basic user preference store
This commit is contained in:
@@ -90,9 +90,12 @@ import GlobalSearchDialog from "@/components/inputs/GlobalSearchDialog.vue"
|
|||||||
import {useDisplay} from "vuetify"
|
import {useDisplay} from "vuetify"
|
||||||
import VSnackbarQueued from "@/components/display/VSnackbarQueued.vue";
|
import VSnackbarQueued from "@/components/display/VSnackbarQueued.vue";
|
||||||
import MessageListDialog from "@/components/dialogs/MessageListDialog.vue";
|
import MessageListDialog from "@/components/dialogs/MessageListDialog.vue";
|
||||||
|
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
|
||||||
|
|
||||||
const {lgAndUp} = useDisplay()
|
const {lgAndUp} = useDisplay()
|
||||||
|
|
||||||
|
useUserPreferenceStore()
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ import {VDateInput} from 'vuetify/labs/VDateInput' //TODO remove once component
|
|||||||
import ModelSelect from "@/components/inputs/ModelSelect.vue";
|
import ModelSelect from "@/components/inputs/ModelSelect.vue";
|
||||||
import {useMessageStore} from "@/stores/MessageStore";
|
import {useMessageStore} from "@/stores/MessageStore";
|
||||||
import {adjustDateRangeLength, shiftDateRange} from "@/utils/date_utils";
|
import {adjustDateRangeLength, shiftDateRange} from "@/utils/date_utils";
|
||||||
|
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
|
||||||
|
|
||||||
const props = defineProps(
|
const props = defineProps(
|
||||||
{
|
{
|
||||||
@@ -133,11 +134,12 @@ function saveMealPlan() {
|
|||||||
* create new meal plan on current date
|
* create new meal plan on current date
|
||||||
*/
|
*/
|
||||||
function newMealPlan() {
|
function newMealPlan() {
|
||||||
// TODO load default meal type and shared users
|
// TODO load default meal type
|
||||||
return {
|
return {
|
||||||
fromDate: DateTime.now().toJSDate(),
|
fromDate: DateTime.now().toJSDate(),
|
||||||
toDate: DateTime.now().toJSDate(),
|
toDate: DateTime.now().toJSDate(),
|
||||||
servings: 1,
|
servings: 1,
|
||||||
|
shared: useUserPreferenceStore().userSettings.planShare
|
||||||
} as MealPlan
|
} as MealPlan
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,205 +0,0 @@
|
|||||||
import {defineStore} from 'pinia'
|
|
||||||
|
|
||||||
|
|
||||||
import {ApiApiFactory, UserPreference} from "@/utils/openapi/api";
|
|
||||||
import Vue from "vue";
|
|
||||||
import {StandardToasts} from "@/utils/utils";
|
|
||||||
|
|
||||||
const _STALE_TIME_IN_MS = 1000 * 30
|
|
||||||
const _STORE_ID = 'user_preference_store'
|
|
||||||
|
|
||||||
const _LS_DEVICE_SETTINGS = 'TANDOOR_LOCAL_SETTINGS'
|
|
||||||
const _LS_USER_SETTINGS = 'TANDOOR_USER_SETTINGS'
|
|
||||||
const _USER_ID = localStorage.getItem('USER_ID')
|
|
||||||
|
|
||||||
export const useUserPreferenceStore = defineStore(_STORE_ID, {
|
|
||||||
state: () => ({
|
|
||||||
data: null,
|
|
||||||
updated_at: null,
|
|
||||||
currently_updating: false,
|
|
||||||
|
|
||||||
user_settings_loaded_at: new Date(0),
|
|
||||||
user_settings: {
|
|
||||||
image: null,
|
|
||||||
theme: "TANDOOR",
|
|
||||||
nav_bg_color: "#ddbf86",
|
|
||||||
nav_text_color: "DARK",
|
|
||||||
nav_show_logo: true,
|
|
||||||
default_unit: "g",
|
|
||||||
default_page: "SEARCH",
|
|
||||||
use_fractions: false,
|
|
||||||
use_kj: false,
|
|
||||||
plan_share: [],
|
|
||||||
nav_sticky: true,
|
|
||||||
ingredient_decimals: 2,
|
|
||||||
comments: true,
|
|
||||||
shopping_auto_sync: 5,
|
|
||||||
mealplan_autoadd_shopping: false,
|
|
||||||
food_inherit_default: [],
|
|
||||||
default_delay: "4.0000",
|
|
||||||
mealplan_autoinclude_related: true,
|
|
||||||
mealplan_autoexclude_onhand: true,
|
|
||||||
shopping_share: [],
|
|
||||||
shopping_recent_days: 7,
|
|
||||||
csv_delim: ",",
|
|
||||||
csv_prefix: "",
|
|
||||||
filter_to_supermarket: false,
|
|
||||||
shopping_add_onhand: false,
|
|
||||||
left_handed: false,
|
|
||||||
show_step_ingredients: true,
|
|
||||||
food_children_exist: false,
|
|
||||||
locally_updated_at: new Date(0),
|
|
||||||
},
|
|
||||||
|
|
||||||
device_settings_initialized: false,
|
|
||||||
device_settings_loaded_at: new Date(0),
|
|
||||||
device_settings: {
|
|
||||||
// shopping
|
|
||||||
shopping_show_checked_entries: false,
|
|
||||||
shopping_show_delayed_entries: false,
|
|
||||||
shopping_show_selected_supermarket_only: false,
|
|
||||||
shopping_selected_grouping: 'food.supermarket_category.name',
|
|
||||||
shopping_selected_supermarket: null,
|
|
||||||
shopping_item_info_created_by: false,
|
|
||||||
shopping_item_info_mealplan: false,
|
|
||||||
shopping_item_info_recipe: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
getters: {},
|
|
||||||
actions: {
|
|
||||||
// Device settings (on device settings stored in local storage)
|
|
||||||
/**
|
|
||||||
* Load device settings from local storage and update state device_settings
|
|
||||||
*/
|
|
||||||
loadDeviceSettings() {
|
|
||||||
let s = localStorage.getItem(_LS_DEVICE_SETTINGS)
|
|
||||||
if (s !== null) {
|
|
||||||
let settings = JSON.parse(s)
|
|
||||||
for (s in settings) {
|
|
||||||
Vue.set(this.device_settings, s, settings[s])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.device_settings_initialized = true
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* persist changes to device settings into local storage
|
|
||||||
*/
|
|
||||||
updateDeviceSettings: function () {
|
|
||||||
localStorage.setItem(_LS_DEVICE_SETTINGS, JSON.stringify(this.device_settings))
|
|
||||||
},
|
|
||||||
// ---------------- new methods for user settings
|
|
||||||
loadUserSettings: function (allow_cached_results) {
|
|
||||||
let s = localStorage.getItem(_LS_USER_SETTINGS)
|
|
||||||
if (s !== null) {
|
|
||||||
let settings = JSON.parse(s)
|
|
||||||
for (s in settings) {
|
|
||||||
Vue.set(this.user_settings, s, settings[s])
|
|
||||||
}
|
|
||||||
console.log(`loaded local user settings age ${((new Date().getTime()) - this.user_settings.locally_updated_at) / 1000} `)
|
|
||||||
}
|
|
||||||
if (((new Date().getTime()) - this.user_settings.locally_updated_at) > _STALE_TIME_IN_MS || !allow_cached_results) {
|
|
||||||
console.log('refreshing user settings from API')
|
|
||||||
let apiClient = new ApiApiFactory()
|
|
||||||
apiClient.retrieveUserPreference(localStorage.getItem('USER_ID')).then(r => {
|
|
||||||
for (s in r.data) {
|
|
||||||
if (!(s in this.user_settings) && s !== 'user') {
|
|
||||||
// dont load new keys if no default exists (to prevent forgetting to add defaults)
|
|
||||||
console.error(`API returned UserPreference key "${s}" which has no default in UserPreferenceStore.user_settings.`)
|
|
||||||
} else {
|
|
||||||
Vue.set(this.user_settings, s, r.data[s])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vue.set(this.user_settings, 'locally_updated_at', new Date().getTime())
|
|
||||||
localStorage.setItem(_LS_USER_SETTINGS, JSON.stringify(this.user_settings))
|
|
||||||
}).catch(err => {
|
|
||||||
this.currently_updating = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
updateUserSettings: function () {
|
|
||||||
let apiClient = new ApiApiFactory()
|
|
||||||
apiClient.partialUpdateUserPreference(_USER_ID, this.user_settings).then(r => {
|
|
||||||
this.user_settings = r.data
|
|
||||||
Vue.set(this.user_settings, 'locally_updated_at', new Date().getTime())
|
|
||||||
localStorage.setItem(_LS_USER_SETTINGS, JSON.stringify(this.user_settings))
|
|
||||||
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
|
|
||||||
}).catch(err => {
|
|
||||||
this.currently_updating = false
|
|
||||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
// ----------------
|
|
||||||
// User Preferences (database settings stored in user preference model)
|
|
||||||
/**
|
|
||||||
* gets data from the store either directly or refreshes from API if data is considered stale
|
|
||||||
* @returns {UserPreference|*|Promise<axios.AxiosResponse<UserPreference>>}
|
|
||||||
*/
|
|
||||||
getData: function () {
|
|
||||||
if (this.isStaleOrEmpty) {
|
|
||||||
return this.refreshFromAPI()
|
|
||||||
} else {
|
|
||||||
return this.data
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* get data from store. Does not use API, if store is not initialized returns null.
|
|
||||||
* @returns {null|UserPreference|*}
|
|
||||||
*/
|
|
||||||
getStaleData: function () {
|
|
||||||
return this.data
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* checks if update timestamp is older than configured stale time interval
|
|
||||||
* @returns {boolean} true if data is considered stale and should be updated
|
|
||||||
*/
|
|
||||||
isStale() {
|
|
||||||
return this.updated_at === null || ((new Date()) - this.updated_at) > _STALE_TIME_IN_MS;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* checks if data of store is empty/not initialized
|
|
||||||
* @returns {boolean} true if store is empty
|
|
||||||
*/
|
|
||||||
isEmpty() {
|
|
||||||
return this.data === null
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* checks if store is empty or data is considered stale, see isStale() and isEmpty()
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isStaleOrEmpty() {
|
|
||||||
return this.isStale() || this.isEmpty()
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* refreshes store data if isStaleOrEmpty() is true
|
|
||||||
* @returns {Promise<axios.AxiosResponse<UserPreference>>} returns promise with data
|
|
||||||
*/
|
|
||||||
updateIfStaleOrEmpty() {
|
|
||||||
if (this.isStaleOrEmpty) {
|
|
||||||
return this.refreshFromAPI()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* refreshes store data from API
|
|
||||||
* @returns {Promise<axios.AxiosResponse<UserPreference>>} returns promise with data
|
|
||||||
*/
|
|
||||||
refreshFromAPI() {
|
|
||||||
let apiClient = new ApiApiFactory()
|
|
||||||
if (!this.currently_updating) {
|
|
||||||
this.currently_updating = true
|
|
||||||
return apiClient.retrieveUserPreference(localStorage.getItem('USER_ID')).then(r => {
|
|
||||||
this.data = r.data
|
|
||||||
this.updated_at = new Date()
|
|
||||||
this.currently_updating = false
|
|
||||||
|
|
||||||
this.user_settings = r.data
|
|
||||||
this.user_settings_loaded_at = new Date()
|
|
||||||
|
|
||||||
return this.data
|
|
||||||
}).catch(err => {
|
|
||||||
this.currently_updating = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
48
vue3/src/stores/UserPreferenceStore.ts
Normal file
48
vue3/src/stores/UserPreferenceStore.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import {acceptHMRUpdate, defineStore} from 'pinia'
|
||||||
|
import {useStorage} from "@vueuse/core";
|
||||||
|
import {ErrorMessageType, Message, useMessageStore} from "@/stores/MessageStore";
|
||||||
|
import {ApiApi, UserPreference} from "@/openapi";
|
||||||
|
import {ref} from "vue";
|
||||||
|
|
||||||
|
const DEVICE_SETTINGS_KEY = 'TANDOOR_DEVICE_SETTINGS'
|
||||||
|
|
||||||
|
class DeviceSettings {
|
||||||
|
|
||||||
|
shopping_show_checked_entries = false
|
||||||
|
shopping_show_delayed_entries = false
|
||||||
|
shopping_show_selected_supermarket_only = false
|
||||||
|
shopping_selected_grouping = 'food.supermarket_category.name'
|
||||||
|
shopping_selected_supermarket = null
|
||||||
|
shopping_item_info_created_by = false
|
||||||
|
shopping_item_info_mealplan = false
|
||||||
|
shopping_item_info_recipe = true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUserPreferenceStore = defineStore('user_preference_store', () => {
|
||||||
|
let deviceSettings = useStorage(DEVICE_SETTINGS_KEY, {} as DeviceSettings)
|
||||||
|
let userSettings = ref({} as UserPreference)
|
||||||
|
|
||||||
|
function loadUserSettings() {
|
||||||
|
console.log('loading user settings from DB')
|
||||||
|
let api = new ApiApi()
|
||||||
|
api.apiUserPreferenceList().then(r => {
|
||||||
|
if (r.length == 1) {
|
||||||
|
userSettings.value = r[0]
|
||||||
|
} else {
|
||||||
|
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, r)
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
loadUserSettings()
|
||||||
|
|
||||||
|
return {deviceSettings, userSettings}
|
||||||
|
})
|
||||||
|
|
||||||
|
// enable hot reload for store
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useUserPreferenceStore, import.meta.hot))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user