work on settings component

This commit is contained in:
vabene1111
2024-09-09 18:41:25 +02:00
parent abc2dc8437
commit 252a7207f6
14 changed files with 674 additions and 143 deletions

View File

@@ -27,7 +27,7 @@ import UserSpaceSettings from "@/components/settings/UserSpaceSettings.vue";
const routes = [
{path: '/', component: StartPage, name: 'view_home'},
{path: '/test', component: TestPage, name: 'view_test'},
{path: '/settings', component: SettingsPage, name: 'view_settings',
{path: '/settings', component: SettingsPage, name: 'view_settings', redirect: 'settings/account',
children: [
{path: 'account', component: AccountSettings, name: 'view_settings_account'},
{path: 'cosmetic', component: CosmeticSettings, name: 'view_settings_cosmetic'},

View File

@@ -1,8 +1,9 @@
<template>
<v-input>
<v-input :hint="hint" persistent-hint :label="label">
<!--TODO resolve-on-load false for now, race condition with model class, make prop once better solution is found -->
<!-- TODO strange behavior/layering issues with appendTo body, find soltion to make it work -->
<!-- TODO resolve-on-load false for now, race condition with model class, make prop once better solution is found -->
<!-- TODO strange behavior/layering issues with appendTo body, find solution to make it work -->
<!-- TODO label is not showing for some reason -->
<Multiselect
:id="id"
@@ -23,9 +24,9 @@
:can-clear="canClear"
:can-deselect="canClear"
:limit="limit"
placeholder="TODO ADD LOCALIZED PLACEHOLDER"
noOptionsText="TODO ADD LOCALIZED NO-OPTIONS"
noResultsText="TODO ADD LOCALIZED NO-RESULTS"
:placeholder="$t('Search')"
:noOptionsText="$t('No_Results')"
:noResultsText="$t('No_Results')"
/>
</v-input>
@@ -59,6 +60,9 @@ const props = defineProps({
noOptionsText: {type: String, default: undefined},
noResultsText: {type: String, default: undefined},
label: {type: String, default: ''},
hint: {type: String, default: ''},
// not verified
search_on_load: {type: Boolean, default: false},

View File

@@ -1,10 +1,59 @@
<template>
<v-form>
<p class="text-h6">{{ $t('Cosmetic') }}</p>
<p class="text-h6">{{ $t('Shopping_list') }}</p>
<v-divider class="mb-3"></v-divider>
<ModelSelect :hint="$t('shopping_share_desc')" :label="$t('shopping_share')" model="User" :allow-create="false"
v-model="useUserPreferenceStore().userSettings.shoppingShare" item-label="displayName"
mode="tags"></ModelSelect>
<v-btn class="mt-3" color="success" @click="useUserPreferenceStore().updateUserSettings()" prepend-icon="$save">{{$t('Save')}}</v-btn>
<v-number-input
class="mt-2"
:label="$t('shopping_auto_sync')"
:hint="$t('shopping_auto_sync_desc')"
persistent-hint
controlVariant="split"
v-model="useUserPreferenceStore().userSettings.shoppingAutoSync"
:step="useUserPreferenceStore().serverSettings.shoppingMinAutosyncInterval"
min="0"
>
<template #append>
<v-btn @click="useUserPreferenceStore().userSettings.shoppingAutoSync = 0">{{$t('Disable')}}</v-btn>
</template>
</v-number-input>
<v-checkbox :label="$t('mealplan_autoadd_shopping')" :hint="$t('mealplan_autoadd_shopping_desc')" persistent-hint v-model="useUserPreferenceStore().userSettings.mealplanAutoaddShopping"></v-checkbox>
<v-checkbox :label="$t('mealplan_autoexclude_onhand')" :hint="$t('mealplan_autoexclude_onhand_desc')" persistent-hint v-model="useUserPreferenceStore().userSettings.mealplanAutoexcludeOnhand"></v-checkbox>
<v-checkbox :label="$t('mealplan_autoinclude_related')" :hint="$t('mealplan_autoinclude_related_desc')" persistent-hint v-model="useUserPreferenceStore().userSettings.mealplanAutoincludeRelated"></v-checkbox>
<v-checkbox :label="$t('shopping_add_onhand')" :hint="$t('shopping_add_onhand_desc')" persistent-hint v-model="useUserPreferenceStore().userSettings.shoppingAddOnhand"></v-checkbox>
<v-checkbox :label="$t('filter_to_supermarket')" :hint="$t('filter_to_supermarket_desc')" persistent-hint v-model="useUserPreferenceStore().userSettings.filterToSupermarket"></v-checkbox>
<v-number-input
class="mt-2"
:label="$t('default_delay')"
:hint="$t('default_delay_desc')"
persistent-hint
controlVariant="split"
v-model="useUserPreferenceStore().userSettings.defaultDelay"
min="1"
></v-number-input>
<v-number-input
class="mt-2"
:label="$t('shopping_recent_days')"
:hint="$t('shopping_recent_days_desc')"
persistent-hint
controlVariant="split"
v-model="useUserPreferenceStore().userSettings.shoppingRecentDays"
min="0"
></v-number-input>
<v-text-field :label="$t('csv_delim_label')" :hint="$t('csv_delim_help')" persistent-hint v-model="useUserPreferenceStore().userSettings.csvDelim"></v-text-field>
<v-text-field :label="$t('csv_prefix_label')" :hint="$t('csv_prefix_help')" persistent-hint v-model="useUserPreferenceStore().userSettings.csvPrefix"></v-text-field>
<v-btn class="mt-3" color="success" @click="useUserPreferenceStore().updateUserSettings()" prepend-icon="$save">
{{ $t('Save') }}
</v-btn>
</v-form>
</template>
@@ -12,8 +61,9 @@
<script setup lang="ts">
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
import ModelSelect from "@/components/inputs/ModelSelect.vue";
import {VNumberInput} from 'vuetify/labs/VNumberInput' //TODO remove once component is out of labs
</script>
<style scoped>

View File

@@ -31,7 +31,7 @@ onMounted(() => {
const api = new ApiApi()
api.apiSpaceList().then(r => {
spaces.value = r
spaces.value = r.results
}).catch(err => {
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
})

View File

@@ -63,6 +63,7 @@ models/PaginatedRecipeBookList.ts
models/PaginatedRecipeOverviewList.ts
models/PaginatedShoppingListEntryList.ts
models/PaginatedShoppingListRecipeList.ts
models/PaginatedSpaceList.ts
models/PaginatedStepList.ts
models/PaginatedSupermarketCategoryList.ts
models/PaginatedSupermarketCategoryRelationList.ts
@@ -126,6 +127,7 @@ models/RecipeImage.ts
models/RecipeOverview.ts
models/RecipeShoppingUpdate.ts
models/RecipeSimple.ts
models/ServerSettings.ts
models/ShareLink.ts
models/ShoppingListEntry.ts
models/ShoppingListEntryBulk.ts

View File

@@ -62,6 +62,7 @@ import type {
PaginatedRecipeOverviewList,
PaginatedShoppingListEntryList,
PaginatedShoppingListRecipeList,
PaginatedSpaceList,
PaginatedStepList,
PaginatedSupermarketCategoryList,
PaginatedSupermarketCategoryRelationList,
@@ -124,6 +125,7 @@ import type {
RecipeImage,
RecipeShoppingUpdate,
RecipeSimple,
ServerSettings,
ShareLink,
ShoppingListEntry,
ShoppingListEntryBulk,
@@ -239,6 +241,8 @@ import {
PaginatedShoppingListEntryListToJSON,
PaginatedShoppingListRecipeListFromJSON,
PaginatedShoppingListRecipeListToJSON,
PaginatedSpaceListFromJSON,
PaginatedSpaceListToJSON,
PaginatedStepListFromJSON,
PaginatedStepListToJSON,
PaginatedSupermarketCategoryListFromJSON,
@@ -363,6 +367,8 @@ import {
RecipeShoppingUpdateToJSON,
RecipeSimpleFromJSON,
RecipeSimpleToJSON,
ServerSettingsFromJSON,
ServerSettingsToJSON,
ShareLinkFromJSON,
ShareLinkToJSON,
ShoppingListEntryFromJSON,
@@ -1281,6 +1287,11 @@ export interface ApiShoppingListRecipeUpdateRequest {
shoppingListRecipe: ShoppingListRecipe;
}
export interface ApiSpaceListRequest {
page?: number;
pageSize?: number;
}
export interface ApiSpacePartialUpdateRequest {
id: number;
patchedSpace?: PatchedSpace;
@@ -8769,6 +8780,34 @@ export class ApiApi extends runtime.BaseAPI {
await this.apiResetFoodInheritanceCreateRaw(initOverrides);
}
/**
*/
async apiServerSettingsCurrentRetrieveRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<ServerSettings>> {
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/server-settings/current/`,
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => ServerSettingsFromJSON(jsonValue));
}
/**
*/
async apiServerSettingsCurrentRetrieve(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<ServerSettings> {
const response = await this.apiServerSettingsCurrentRetrieveRaw(initOverrides);
return await response.value();
}
/**
*/
async apiShareLinkRetrieveRaw(requestParameters: ApiShareLinkRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<ShareLink>> {
@@ -9336,9 +9375,17 @@ export class ApiApi extends runtime.BaseAPI {
/**
*/
async apiSpaceListRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Array<Space>>> {
async apiSpaceListRaw(requestParameters: ApiSpaceListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<PaginatedSpaceList>> {
const queryParameters: any = {};
if (requestParameters['page'] != null) {
queryParameters['page'] = requestParameters['page'];
}
if (requestParameters['pageSize'] != null) {
queryParameters['page_size'] = requestParameters['pageSize'];
}
const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && this.configuration.apiKey) {
@@ -9352,13 +9399,13 @@ export class ApiApi extends runtime.BaseAPI {
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => jsonValue.map(SpaceFromJSON));
return new runtime.JSONApiResponse(response, (jsonValue) => PaginatedSpaceListFromJSON(jsonValue));
}
/**
*/
async apiSpaceList(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Array<Space>> {
const response = await this.apiSpaceListRaw(initOverrides);
async apiSpaceList(requestParameters: ApiSpaceListRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<PaginatedSpaceList> {
const response = await this.apiSpaceListRaw(requestParameters, initOverrides);
return await response.value();
}

View File

@@ -0,0 +1,93 @@
/* tslint:disable */
/* eslint-disable */
/**
* Tandoor
* Tandoor API Docs
*
* The version of the OpenAPI document: 0.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime';
import type { Space } from './Space';
import {
SpaceFromJSON,
SpaceFromJSONTyped,
SpaceToJSON,
} from './Space';
/**
*
* @export
* @interface PaginatedSpaceList
*/
export interface PaginatedSpaceList {
/**
*
* @type {number}
* @memberof PaginatedSpaceList
*/
count: number;
/**
*
* @type {string}
* @memberof PaginatedSpaceList
*/
next?: string;
/**
*
* @type {string}
* @memberof PaginatedSpaceList
*/
previous?: string;
/**
*
* @type {Array<Space>}
* @memberof PaginatedSpaceList
*/
results: Array<Space>;
}
/**
* Check if a given object implements the PaginatedSpaceList interface.
*/
export function instanceOfPaginatedSpaceList(value: object): boolean {
if (!('count' in value)) return false;
if (!('results' in value)) return false;
return true;
}
export function PaginatedSpaceListFromJSON(json: any): PaginatedSpaceList {
return PaginatedSpaceListFromJSONTyped(json, false);
}
export function PaginatedSpaceListFromJSONTyped(json: any, ignoreDiscriminator: boolean): PaginatedSpaceList {
if (json == null) {
return json;
}
return {
'count': json['count'],
'next': json['next'] == null ? undefined : json['next'],
'previous': json['previous'] == null ? undefined : json['previous'],
'results': ((json['results'] as Array<any>).map(SpaceFromJSON)),
};
}
export function PaginatedSpaceListToJSON(value?: PaginatedSpaceList | null): any {
if (value == null) {
return value;
}
return {
'count': value['count'],
'next': value['next'],
'previous': value['previous'],
'results': ((value['results'] as Array<any>).map(SpaceToJSON)),
};
}

View File

@@ -0,0 +1,115 @@
/* tslint:disable */
/* eslint-disable */
/**
* Tandoor
* Tandoor API Docs
*
* The version of the OpenAPI document: 0.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime';
/**
*
* @export
* @interface ServerSettings
*/
export interface ServerSettings {
/**
*
* @type {string}
* @memberof ServerSettings
*/
shoppingMinAutosyncInterval: string;
/**
*
* @type {boolean}
* @memberof ServerSettings
*/
enablePdfExport: boolean;
/**
*
* @type {boolean}
* @memberof ServerSettings
*/
disableExternalConnectors: boolean;
/**
*
* @type {string}
* @memberof ServerSettings
*/
termsUrl: string;
/**
*
* @type {string}
* @memberof ServerSettings
*/
privacyUrl: string;
/**
*
* @type {string}
* @memberof ServerSettings
*/
imprintUrl: string;
/**
*
* @type {boolean}
* @memberof ServerSettings
*/
hosted: boolean;
}
/**
* Check if a given object implements the ServerSettings interface.
*/
export function instanceOfServerSettings(value: object): boolean {
if (!('shoppingMinAutosyncInterval' in value)) return false;
if (!('enablePdfExport' in value)) return false;
if (!('disableExternalConnectors' in value)) return false;
if (!('termsUrl' in value)) return false;
if (!('privacyUrl' in value)) return false;
if (!('imprintUrl' in value)) return false;
if (!('hosted' in value)) return false;
return true;
}
export function ServerSettingsFromJSON(json: any): ServerSettings {
return ServerSettingsFromJSONTyped(json, false);
}
export function ServerSettingsFromJSONTyped(json: any, ignoreDiscriminator: boolean): ServerSettings {
if (json == null) {
return json;
}
return {
'shoppingMinAutosyncInterval': json['shopping_min_autosync_interval'],
'enablePdfExport': json['enable_pdf_export'],
'disableExternalConnectors': json['disable_external_connectors'],
'termsUrl': json['terms_url'],
'privacyUrl': json['privacy_url'],
'imprintUrl': json['imprint_url'],
'hosted': json['hosted'],
};
}
export function ServerSettingsToJSON(value?: ServerSettings | null): any {
if (value == null) {
return value;
}
return {
'shopping_min_autosync_interval': value['shoppingMinAutosyncInterval'],
'enable_pdf_export': value['enablePdfExport'],
'disable_external_connectors': value['disableExternalConnectors'],
'terms_url': value['termsUrl'],
'privacy_url': value['privacyUrl'],
'imprint_url': value['imprintUrl'],
'hosted': value['hosted'],
};
}

View File

@@ -60,6 +60,7 @@ export * from './PaginatedRecipeBookList';
export * from './PaginatedRecipeOverviewList';
export * from './PaginatedShoppingListEntryList';
export * from './PaginatedShoppingListRecipeList';
export * from './PaginatedSpaceList';
export * from './PaginatedStepList';
export * from './PaginatedSupermarketCategoryList';
export * from './PaginatedSupermarketCategoryRelationList';
@@ -123,6 +124,7 @@ export * from './RecipeImage';
export * from './RecipeOverview';
export * from './RecipeShoppingUpdate';
export * from './RecipeSimple';
export * from './ServerSettings';
export * from './ShareLink';
export * from './ShoppingListEntry';
export * from './ShoppingListEntryBulk';

View File

@@ -93,7 +93,11 @@ export const useMessageStore = defineStore('message_store', () => {
function addPreparedMessage(preparedMessage: PreparedMessage) {
if (preparedMessage == PreparedMessage.UPDATE_SUCCESS) {
addMessage(MessageType.SUCCESS, 'Updated Successfully', 7000, {}) // TODO localize and make more useful ?
}
if (preparedMessage == PreparedMessage.DELETE_SUCCESS) {
addMessage(MessageType.SUCCESS, 'Created Successfully', 7000, {})
}
if (preparedMessage == PreparedMessage.CREATE_SUCCESS) {
addMessage(MessageType.SUCCESS, 'Deleted Successfully', 7000, {})
}
}

View File

@@ -1,10 +1,11 @@
import {acceptHMRUpdate, defineStore} from 'pinia'
import {useStorage} from "@vueuse/core";
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore";
import {ApiApi, Space, UserPreference} from "@/openapi";
import {ErrorMessageType, PreparedMessage, useMessageStore} from "@/stores/MessageStore";
import {ApiApi, ServerSettings, Space, UserPreference} from "@/openapi";
const DEVICE_SETTINGS_KEY = 'TANDOOR_DEVICE_SETTINGS'
const USER_PREFERENCE_KEY = 'TANDOOR_USER_PREFERENCE'
const SERVER_SETTINGS_KEY = 'TANDOOR_SERVER_SETTINGS'
const ACTIVE_SPACE_KEY = 'TANDOOR_ACTIVE_SPACE'
class DeviceSettings {
@@ -29,6 +30,10 @@ export const useUserPreferenceStore = defineStore('user_preference_store', () =>
* database user settings, cache in local storage in case application is started offline
*/
let userSettings = useStorage(USER_PREFERENCE_KEY, {} as UserPreference)
/**
* some defaults and values returned by server
*/
let serverSettings = useStorage(SERVER_SETTINGS_KEY, {} as ServerSettings)
/**
* database user settings, cache in local storage in case application is started offline
*/
@@ -59,11 +64,24 @@ export const useUserPreferenceStore = defineStore('user_preference_store', () =>
api.apiUserPreferencePartialUpdate({user: userSettings.value.user, patchedUserPreference: userSettings.value}).then(r => {
userSettings.value = r
useMessageStore().addPreparedMessage(PreparedMessage.UPDATE_SUCCESS)
}).catch(err => {
useMessageStore().addError(ErrorMessageType.UPDATE_ERROR, err)
})
}
/**
* retrieves server settings from API
*/
function loadServerSettings() {
let api = new ApiApi()
api.apiServerSettingsCurrentRetrieve().then(r => {
serverSettings.value = r
}).catch(err => {
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
})
}
/**
* load data for currently active space
*/
@@ -91,10 +109,12 @@ export const useUserPreferenceStore = defineStore('user_preference_store', () =>
// always load user settings on first initialization of store
loadUserSettings()
// always load server settings on first initialization of store
loadServerSettings()
// always load active space on first initialization of store
loadActiveSpace()
return {deviceSettings, userSettings, activeSpace, loadUserSettings, updateUserSettings, switchSpace}
return {deviceSettings, userSettings, serverSettings, activeSpace, loadUserSettings, loadServerSettings,updateUserSettings, switchSpace}
})
// enable hot reload for store