new settings page finished

This commit is contained in:
vabene1111
2022-08-05 16:54:53 +02:00
parent 64688ca5e1
commit 6605b87c5c
17 changed files with 446 additions and 654 deletions

View File

@ -1,41 +1,77 @@
<template>
<div id="app" class="row">
<div class="col-md-3 col-12">
<b-nav vertical>
<b-nav-item :active="visible_settings === 'cosmetic'" @click="visible_settings = 'cosmetic'"><i
class="fas fa-fw fa-eye"></i> Cosmetic
</b-nav-item>
<b-nav-item :active="visible_settings === 'account'" @click="visible_settings = 'account'"><i
class="fas fa-fw fa-user"></i> Account
</b-nav-item>
<b-nav-item :active="visible_settings === 'search'" @click="visible_settings = 'search'"><i
class="fas fa-fw fa-search"></i> Search
</b-nav-item>
<b-nav-item :active="visible_settings === 'shopping'" @click="visible_settings = 'shopping'"><i
class="fas fa-fw fa-shopping-cart"></i> Shopping
</b-nav-item>
<b-nav-item :active="visible_settings === 'meal_plan'" @click="visible_settings = 'meal_plan'"><i
class="fas fa-fw fa-calendar"></i> Meal Plan
</b-nav-item>
<b-nav-item :active="visible_settings === 'api'" @click="visible_settings = 'api'"><i
class="fas fa-fw fa-code"></i> API
</b-nav-item>
</b-nav>
</div>
<div class="col-md-9 col-12">
<cosmetic-settings-component v-if="visible_settings === 'cosmetic'"
:user_id="user_id"></cosmetic-settings-component>
<account-settings-component v-if="visible_settings === 'account'"
:user_id="user_id"></account-settings-component>
<search-settings-component v-if="visible_settings === 'search'"
:user_id="user_id"></search-settings-component>
<shopping-settings-component v-if="visible_settings === 'shopping'"
:user_id="user_id"></shopping-settings-component>
<meal-plan-settings-component v-if="visible_settings === 'meal_plan'"
:user_id="user_id"></meal-plan-settings-component>
<a-p-i-settings-component v-if="visible_settings === 'api'" :user_id="user_id"></a-p-i-settings-component>
<div id="app">
<div class="row">
<div class="col-12">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a :href="resolveDjangoUrl('view_settings')">{{
$t('Settings')
}}</a></li>
<li class="breadcrumb-item" v-if="visible_settings === 'cosmetic'"
@click="visible_settings = 'cosmetic'">{{ $t('Cosmetic') }}
</li>
<li class="breadcrumb-item" v-if="visible_settings === 'account'"
@click="visible_settings = 'account'"> {{ $t('Account') }}
</li>
<li class="breadcrumb-item" v-if="visible_settings === 'search'"
@click="visible_settings = 'search'">{{ $t('Search') }}
</li>
<li class="breadcrumb-item" v-if="visible_settings === 'shopping'"
@click="visible_settings = 'shopping'">{{ $t('Shopping_list') }}
</li>
<li class="breadcrumb-item" v-if="visible_settings === 'meal_plan'"
@click="visible_settings = 'meal_plan'">
{{ $t('Meal_Plan') }}
</li>
<li class="breadcrumb-item" v-if="visible_settings === 'api'" @click="visible_settings = 'api'">
{{ $t('API') }}
</li>
</ol>
</nav>
</div>
</div>
<div class="row">
<div class="col-md-3 col-12">
<b-nav vertical>
<b-nav-item :active="visible_settings === 'cosmetic'" @click="visible_settings = 'cosmetic'"><i
class="fas fa-fw fa-eye"></i> {{ $t('Cosmetic') }}
</b-nav-item>
<b-nav-item :active="visible_settings === 'account'" @click="visible_settings = 'account'"><i
class="fas fa-fw fa-user"></i> {{ $t('Account') }}
</b-nav-item>
<b-nav-item :active="visible_settings === 'search'" @click="visible_settings = 'search'"><i
class="fas fa-fw fa-search"></i> {{ $t('Search') }}
</b-nav-item>
<b-nav-item :active="visible_settings === 'shopping'" @click="visible_settings = 'shopping'"><i
class="fas fa-fw fa-shopping-cart"></i> {{ $t('Shopping_list') }}
</b-nav-item>
<b-nav-item :active="visible_settings === 'meal_plan'" @click="visible_settings = 'meal_plan'"><i
class="fas fa-fw fa-calendar"></i> {{ $t('Meal_Plan') }}
</b-nav-item>
<b-nav-item :active="visible_settings === 'api'" @click="visible_settings = 'api'"><i
class="fas fa-fw fa-code"></i> {{ $t('API') }}
</b-nav-item>
</b-nav>
</div>
<div class="col-md-9 col-12">
<cosmetic-settings-component v-if="visible_settings === 'cosmetic'"
:user_id="user_id"></cosmetic-settings-component>
<account-settings-component v-if="visible_settings === 'account'"
:user_id="user_id"></account-settings-component>
<search-settings-component v-if="visible_settings === 'search'"
:user_id="user_id"></search-settings-component>
<shopping-settings-component v-if="visible_settings === 'shopping'"
:user_id="user_id"></shopping-settings-component>
<meal-plan-settings-component v-if="visible_settings === 'meal_plan'"
:user_id="user_id"></meal-plan-settings-component>
<a-p-i-settings-component v-if="visible_settings === 'api'"
:user_id="user_id"></a-p-i-settings-component>
</div>
</div>
</div>
</template>
@ -50,12 +86,13 @@ import SearchSettingsComponent from "@/components/Settings/SearchSettingsCompone
import ShoppingSettingsComponent from "@/components/Settings/ShoppingSettingsComponent";
import MealPlanSettingsComponent from "@/components/Settings/MealPlanSettingsComponent";
import APISettingsComponent from "@/components/Settings/APISettingsComponent";
import {ResolveUrlMixin} from "@/utils/utils";
Vue.use(BootstrapVue)
export default {
name: "ProfileView",
mixins: [],
mixins: [ResolveUrlMixin],
components: {
CosmeticSettingsComponent,
AccountSettingsComponent,

View File

@ -33,7 +33,8 @@
<template #title>
<b-spinner v-if="loading" type="border" small class="d-inline-block"></b-spinner>
<i v-if="!loading" class="fas fa-shopping-cart fa-fw d-inline-block d-md-none"></i>
<span class="d-none d-md-inline-block">{{ $t('Shopping_list') + ` (${items.filter(x => x.checked === false).length})`}}</span>
<span
class="d-none d-md-inline-block">{{ $t('Shopping_list') + ` (${items.filter(x => x.checked === false).length})` }}</span>
</template>
<div class="container p-0 p-md-3" id="shoppinglist">
<div class="row">
@ -177,7 +178,7 @@
<b-tab :title="$t('Recipes')">
<template #title>
<i class="fas fa-book fa-fw d-block d-md-none"></i>
<span class="d-none d-md-block">{{ $t('Recipes') + ` (${Recipes.length})`}}</span>
<span class="d-none d-md-block">{{ $t('Recipes') + ` (${Recipes.length})` }}</span>
</template>
<div class="container p-0">
<div class="row">
@ -234,7 +235,9 @@
</thead>
<tr v-for="r in Recipes" :key="r.list_recipe">
<td>{{ r.recipe_mealplan.name }}</td>
<td><a :href="resolveDjangoUrl('view_recipe', r.recipe_mealplan.recipe)">{{ r.recipe_mealplan.recipe_name }}</a></td>
<td><a :href="resolveDjangoUrl('view_recipe', r.recipe_mealplan.recipe)">{{
r.recipe_mealplan.recipe_name
}}</a></td>
<td class="block-inline">
<b-form-input min="1" type="number" :debounce="300"
:value="r.recipe_mealplan.servings"
@ -258,7 +261,7 @@
<b-tab>
<template #title>
<i class="fas fa-store-alt fa-fw d-block d-md-none"></i>
<span class="d-none d-md-block">{{ $t('Supermarkets') + ` (${supermarkets.length})`}}</span>
<span class="d-none d-md-block">{{ $t('Supermarkets') + ` (${supermarkets.length})` }}</span>
</template>
<div class="container p-0">
<div class="row">
@ -460,183 +463,7 @@
</template>
<div class="row justify-content-center">
<div class="col-12 col-md-8">
<b-card class="no-body">
<div class="row">
<div class="col col-md-6">{{ $t("mealplan_autoadd_shopping") }}</div>
<div class="col col-md-6 text-right">
<input type="checkbox" class="form-control settings-checkbox"
v-model="settings.mealplan_autoadd_shopping" @change="saveSettings"/>
</div>
</div>
<div class="row sm mb-3">
<div class="col">
<em class="small text-muted">{{ $t("mealplan_autoadd_shopping_desc") }}</em>
</div>
</div>
<div v-if="settings.mealplan_autoadd_shopping">
<div class="row">
<div class="col col-md-6">{{ $t("mealplan_autoexclude_onhand") }}</div>
<div class="col col-md-6 text-right">
<input type="checkbox" class="form-control settings-checkbox"
v-model="settings.mealplan_autoexclude_onhand" @change="saveSettings"/>
</div>
</div>
<div class="row sm mb-3">
<div class="col">
<em class="small text-muted">{{ $t("mealplan_autoexclude_onhand_desc") }}</em>
</div>
</div>
</div>
<div v-if="settings.mealplan_autoadd_shopping">
<div class="row">
<div class="col col-md-6">{{ $t("mealplan_autoinclude_related") }}</div>
<div class="col col-md-6 text-right">
<input type="checkbox" class="form-control settings-checkbox"
v-model="settings.mealplan_autoinclude_related" @change="saveSettings"/>
</div>
</div>
<div class="row sm mb-3">
<div class="col">
<em class="small text-muted">
{{ $t("mealplan_autoinclude_related_desc") }}
</em>
</div>
</div>
</div>
<div class="row">
<div class="col col-md-6">{{ $t("shopping_share") }}</div>
<div class="col col-md-6 text-right">
<generic-multiselect
size="sm"
@change="
settings.shopping_share = $event.val
saveSettings()
"
:model="Models.USER"
:initial_selection="settings.shopping_share"
label="display_name"
:multiple="true"
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
:placeholder="$t('User')"
/>
</div>
</div>
<div class="row sm mb-3">
<div class="col">
<em class="small text-muted">{{ $t("shopping_share_desc") }}</em>
</div>
</div>
<div class="row">
<div class="col col-md-6">{{ $t("shopping_auto_sync") }}</div>
<div class="col col-md-6 text-right">
<input type="number" class="form-control" v-model="settings.shopping_auto_sync"
@change="saveSettings"/>
</div>
</div>
<div class="row sm mb-3">
<div class="col">
<em class="small text-muted">
{{ $t("shopping_auto_sync_desc") }}
</em>
</div>
</div>
<div class="row">
<div class="col col-md-6">{{ $t("shopping_add_onhand") }}</div>
<div class="col col-md-6 text-right">
<input type="checkbox" class="form-control settings-checkbox"
v-model="settings.shopping_add_onhand" @change="saveSettings"/>
</div>
</div>
<div class="row sm mb-3">
<div class="col">
<em class="small text-muted">
{{ $t("shopping_add_onhand_desc") }}
</em>
</div>
</div>
<div class="row">
<div class="col col-md-6">{{ $t("shopping_recent_days") }}</div>
<div class="col col-md-6 text-right">
<input type="number" class="form-control" v-model="settings.shopping_recent_days"
@change="saveSettings"/>
</div>
</div>
<div class="row sm mb-3">
<div class="col">
<em class="small text-muted">
{{ $t("shopping_recent_days_desc") }}
</em>
</div>
</div>
<div class="row">
<div class="col col-md-6">{{ $t("filter_to_supermarket") }}</div>
<div class="col col-md-6 text-right">
<input type="checkbox" class="form-control settings-checkbox"
v-model="settings.filter_to_supermarket" @change="saveSettings"/>
</div>
</div>
<div class="row sm mb-3">
<div class="col">
<em class="small text-muted">
{{ $t("filter_to_supermarket_desc") }}
</em>
</div>
</div>
<div class="row">
<div class="col col-md-6">{{ $t("default_delay") }}</div>
<div class="col col-md-6 text-right">
<input type="number" class="form-control" min="1" v-model="settings.default_delay"
@change="saveSettings"/>
</div>
</div>
<div class="row sm mb-3">
<div class="col">
<em class="small text-muted">
{{ $t("default_delay_desc") }}
</em>
</div>
</div>
<div class="row">
<div class="col col-md-6">{{ $t("csv_delim_label") }}</div>
<div class="col col-md-6 text-right">
<input class="form-control" v-model="settings.csv_delim" @change="saveSettings"/>
</div>
</div>
<div class="row sm mb-3">
<div class="col">
<em class="small text-muted">
{{ $t("csv_delim_help") }}
</em>
</div>
</div>
<div class="row">
<div class="col col-md-6">{{ $t("csv_prefix_label") }}</div>
<div class="col col-md-6 text-right">
<input class="form-control" v-model="settings.csv_prefix" @change="saveSettings"/>
</div>
</div>
<div class="row sm mb-3">
<div class="col">
<em class="small text-muted">
{{ $t("csv_prefix_help") }}
</em>
</div>
</div>
<div class="row">
<div class="col col-md-6">{{ $t("left_handed") }}</div>
<div class="col col-md-6">
<input type="checkbox" class="form-control settings-checkbox"
v-model="settings.left_handed" @change="saveSettings"/>
</div>
</div>
<div class="row sm mb-3">
<div class="col">
<em class="small text-muted">
{{ $t("left_handed_help") }}
</em>
</div>
</div>
</b-card>
<shopping-settings-component @updated="settings = $event" :user_id="user_id"></shopping-settings-component>
</div>
</div>
</b-tab>
@ -783,6 +610,7 @@ import ShoppingModal from "@/components/Modals/ShoppingModal"
import {ApiMixin, getUserPreference, StandardToasts, makeToast, ResolveUrlMixin} from "@/utils/utils"
import {ApiApiFactory} from "@/utils/openapi/api"
import ShoppingSettingsComponent from "@/components/Settings/ShoppingSettingsComponent";
Vue.use(BootstrapVue)
Vue.use(VueCookies)
@ -790,7 +618,7 @@ let SETTINGS_COOKIE_NAME = "shopping_settings"
export default {
name: "ShoppingListView",
mixins: [ApiMixin,ResolveUrlMixin],
mixins: [ApiMixin, ResolveUrlMixin],
components: {
ContextMenu,
ContextMenuItem,
@ -801,7 +629,8 @@ export default {
DownloadCSV,
CopyToClipboard,
ShoppingModal,
draggable
draggable,
ShoppingSettingsComponent
},
data() {
@ -837,6 +666,7 @@ export default {
shopping_add_onhand: true,
left_handed: false,
},
user_id: parseInt(localStorage.getItem('USER_ID')),
editing_supermarket_categories: [],
editing_supermarket: null,
new_supermarket: {entrymode: false, value: undefined, editmode: undefined},
@ -1315,16 +1145,6 @@ export default {
this.$refs.menu.open(e, value)
},
saveSettings: function () {
let api = ApiApiFactory()
api.partialUpdateUserPreference(this.settings.user, this.settings)
.then((result) => {
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
})
.catch((err) => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
},
saveThis: function (thisItem, toast = true) {
let api = new ApiApiFactory()
if (!thisItem?.id) {

View File

@ -1,20 +1,67 @@
<template>
<div>
<code>Authorization: Bearer TOKEN</code> or<br/>
<b-alert show variant="danger">
The API is made for developers to interact with the application.
It is possible to break things using the API so be careful and create a backup first.
The API definition can and will change in the future, make sure to read the changelog to spot changes early
on.
<b-button-toolbar>
<b-button-group class="mx-1">
<a :href="resolveDjangoUrl('docs_api')" class="btn btn-info" target="_blank"
rel="noreferrer nofollow">Docs</a>
</b-button-group>
<b-button-group class="mx-1">
<a :href="resolveDjangoUrl('api:api-root')" class="btn btn-success" target="_blank"
rel="noreferrer nofollow">Interactive API Browser</a>
</b-button-group>
</b-button-toolbar>
</b-alert>
Authentication works by proving the word <code>Bearer</code> followed by an API Token as a request Authorization
header as shown below. <br/>
<code>Authorization: Bearer TOKEN</code> -or-<br/>
<code>curl -X GET http://your.domain.com/api/recipes/ -H 'Authorization:
Bearer TOKEN'</code>
<br/>
<br/>
You can have multiple tokens and each token can have its own scope. Currently there is <code>read</code>, <code>write</code>
and <code>bookmarklet</code>.
Read and write do what the name says, the bookmarklet scope is only used for the bookmarklet to limit access to
it.
<b-alert show variant="info">Make sure to save your token after creation as they cannot be viewed afterwards.
</b-alert>
<b-list-group class="mt-3">
<b-list-group-item v-for="t in access_tokens" v-bind:key="t.id">{{ t.token }}<br/><small class="text-muted">{{
t.scope
}}
- {{ t.expires }}</small>
<b-button @click="active_token=t; generic_action = Actions.UPDATE;">Edit</b-button>
<b-button @click="active_token=t; generic_action = Actions.DELETE;">Delete</b-button>
<b-list-group-item v-for="t in access_tokens" v-bind:key="t.id">
<div class="row">
<div class="col-9">
{{ t.token }}<br/>
<small>
<span class="text-muted">Scope:</span> <code>{{ t.scope }}</code> <span class="text-muted">Expires:</span>
{{ formatDate(t.expires) }}
</small>
</div>
<div class="col-3">
<b-button-group>
<b-button variant="primary" @click="active_token=t; generic_action = Actions.UPDATE;"><i
class="far fa-edit"></i></b-button>
<b-button variant="danger" @click="active_token=t; generic_action = Actions.DELETE;"><i
class="fas fa-trash-alt"></i></b-button>
</b-button-group>
</div>
</div>
</b-list-group-item>
</b-list-group>
<b-button @click="generic_action=Actions.CREATE">NEW</b-button>
<b-button class="mt-1" variant="success" @click="generic_action=Actions.CREATE">{{ $t('New') }}</b-button>
<generic-modal-form :model="Models.ACCESS_TOKEN" :action="generic_action" :show="generic_action !== null"
:item1="active_token"
@ -24,10 +71,11 @@
<script>
import {ApiApiFactory} from "@/utils/openapi/api";
import {ApiMixin, StandardToasts} from "@/utils/utils";
import {ApiMixin, ResolveUrlMixin, StandardToasts} from "@/utils/utils";
import axios from "axios";
import GenericModalForm from "@/components/Modals/GenericModalForm";
import moment from "moment";
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
@ -35,7 +83,7 @@ axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
export default {
name: "APISettingsComponent",
components: {GenericModalForm},
mixins: [ApiMixin,],
mixins: [ApiMixin, ResolveUrlMixin],
props: {
user_id: Number,
},
@ -52,6 +100,10 @@ export default {
this.loadTokens()
},
methods: {
formatDate: function (datetime) {
moment.locale(window.navigator.language);
return moment(datetime).format('L')
},
loadTokens: function () {
let apiFactory = new ApiApiFactory()
apiFactory.listAccessTokens().then(result => {

View File

@ -1,15 +1,32 @@
<template>
<div>
<div v-if="user !== undefined">
<b-form-input v-model="user.username" @change="updateUser(false)" disabled></b-form-input>
<b-form-input v-model="user.first_name" @change="updateUser(false)"></b-form-input>
<b-form-input v-model="user.last_name" @change="updateUser(false)"></b-form-input>
<div v-if="user !== undefined">
<b-form-group :label="$t('Username')">
<b-form-input v-model="user.username" @change="updateUser(false)" disabled></b-form-input>
</b-form-group>
<b-form-group :label="$t('First_name')">
<b-form-input v-model="user.first_name" @change="updateUser(false)" :placeholder="$t('First_name')"></b-form-input>
</b-form-group>
<b-form-group :label="$t('Last_name')">
<b-form-input v-model="user.last_name" @change="updateUser(false)" :placeholder="$t('Last_name')"></b-form-input>
</b-form-group>
</div>
<a :href="resolveDjangoUrl('account_email')" class="btn btn-primary">Emails</a>
<a :href="resolveDjangoUrl('account_change_password')" class="btn btn-primary">Password</a>
<a :href="resolveDjangoUrl('socialaccount_connections')" class="btn btn-primary">Social</a>
<b-button-toolbar>
<b-button-group class="mx-1">
<a :href="resolveDjangoUrl('account_email')" class="btn btn-primary">{{ $t('Manage_Emails') }}</a>
</b-button-group>
<b-button-group class="mx-1">
<a :href="resolveDjangoUrl('account_change_password')" class="btn btn-primary">{{ $t('Change_Password') }}</a>
</b-button-group>
<b-button-group class="mx-1">
<a :href="resolveDjangoUrl('socialaccount_connections')" class="btn btn-primary">{{ $t('Social_Authentication') }}</a>
</b-button-group>
</b-button-toolbar>
</div>
</template>

View File

@ -1,54 +1,83 @@
<template>
<div v-if="user_preferences !== undefined">
<b-form-input v-model="user_preferences.default_unit" @change="updateSettings"></b-form-input>
<b-form-group :label="$t('Default_Unit')">
<b-form-input v-model="user_preferences.default_unit" @change="updateSettings(false)"></b-form-input>
</b-form-group>
{{ user_preferences.ingredient_decimals }}
<b-form-input type="range" min="0" max="4" step="1" v-model="user_preferences.ingredient_decimals"
@change="updateSettings"></b-form-input>
<b-form-group :label="$t('Decimals')">
<b-form-input type="number" min="0" max="4" step="1" v-model="user_preferences.ingredient_decimals"
@change="updateSettings(false)"></b-form-input>
</b-form-group>
<b-form-checkbox v-model="user_preferences.use_fractions" @change="updateSettings"></b-form-checkbox>
<b-form-group :description="$t('Use_Fractions_Help')">
<b-form-checkbox v-model="user_preferences.use_fractions" @change="updateSettings(false)">
{{ $t('Use_Fractions') }}
</b-form-checkbox>
</b-form-group>
<hr/>
Language
<b-form-select v-model="$i18n.locale" @change="updateLanguage">
<b-form-select-option v-bind:key="l[0]" v-for="l in languages" :value="l[1]">{{ l[0] }} ({{
l[1]
}})
</b-form-select-option>
</b-form-select>
<b-form-group>
<b-form-checkbox v-model="user_preferences.use_kj" @change="updateSettings(false);">{{ $t('Use_Kj') }}
</b-form-checkbox>
</b-form-group>
<b-form-group>
<b-form-checkbox v-model="user_preferences.comments" @change="updateSettings(false);">
{{ $t('Comments_setting') }}
</b-form-checkbox>
</b-form-group>
<b-form-group :description="$t('left_handed_help')">
<b-form-checkbox v-model="user_preferences.left_handed" @change="updateSettings(false);">
{{ $t('left_handed') }}
</b-form-checkbox>
</b-form-group>
<b-form-select v-model="user_preferences.theme" @change="updateSettings(true);">
<b-form-select-option value="TANDOOR">Tandoor</b-form-select-option>
<b-form-select-option value="BOOTSTRAP">Bootstrap</b-form-select-option>
<b-form-select-option value="DARKLY">Darkly</b-form-select-option>
<b-form-select-option value="FLATLY">Flatly</b-form-select-option>
<b-form-select-option value="SUPERHERO">Superhero</b-form-select-option>
</b-form-select>
<b-form-checkbox v-model="user_preferences.sticky_navbar" @change="updateSettings(true);"></b-form-checkbox>
<b-form-select v-model="user_preferences.nav_color" @change="updateSettings(true);">
<b-form-select-option value="PRIMARY">Primary</b-form-select-option>
<b-form-select-option value="SECONDARY">Secondary</b-form-select-option>
<b-form-select-option value="SUCCESS">Success</b-form-select-option>
<b-form-select-option value="INFO">Info</b-form-select-option>
<b-form-select-option value="WARNING">Warning</b-form-select-option>
<b-form-select-option value="DANGER">Danger</b-form-select-option>
<b-form-select-option value="LIGHT">Light</b-form-select-option>
<b-form-select-option value="DARK">Dark</b-form-select-option>
</b-form-select>
<hr/>
<b-form-checkbox v-model="user_preferences.use_kj" @change="updateSettings();"></b-form-checkbox>
<b-form-checkbox v-model="user_preferences.comments" @change="updateSettings();"></b-form-checkbox>
<b-form-checkbox v-model="user_preferences.left_handed" @change="updateSettings();"></b-form-checkbox>
<b-form-group :label="$t('Language')">
<b-form-select v-model="$i18n.locale" @change="updateLanguage">
<b-form-select-option v-bind:key="l[0]" v-for="l in languages" :value="l[1]">{{ l[0] }} ({{
l[1]
}})
</b-form-select-option>
</b-form-select>
</b-form-group>
<b-form-group :label="$t('Theme')">
<b-form-select v-model="user_preferences.theme" @change="updateSettings(true);">
<b-form-select-option value="TANDOOR">Tandoor</b-form-select-option>
<b-form-select-option value="BOOTSTRAP">Bootstrap</b-form-select-option>
<b-form-select-option value="DARKLY">Darkly</b-form-select-option>
<b-form-select-option value="FLATLY">Flatly</b-form-select-option>
<b-form-select-option value="SUPERHERO">Superhero</b-form-select-option>
</b-form-select>
</b-form-group>
<b-form-group :description="$t('Sticky_Nav_Help')">
<b-form-checkbox v-model="user_preferences.sticky_navbar" @change="updateSettings(true);">
{{ $t('Sticky_Nav') }}
</b-form-checkbox>
</b-form-group>
<b-form-group :label="$t('Nav_Color')" :description="$t('Nav_Color_Help')">
<b-form-select v-model="user_preferences.nav_color" @change="updateSettings(true);">
<b-form-select-option value="PRIMARY">Primary</b-form-select-option>
<b-form-select-option value="SECONDARY">Secondary</b-form-select-option>
<b-form-select-option value="SUCCESS">Success</b-form-select-option>
<b-form-select-option value="INFO">Info</b-form-select-option>
<b-form-select-option value="WARNING">Warning</b-form-select-option>
<b-form-select-option value="DANGER">Danger</b-form-select-option>
<b-form-select-option value="LIGHT">Light</b-form-select-option>
<b-form-select-option value="DARK">Dark</b-form-select-option>
</b-form-select>
</b-form-group>
</div>
</template>
<script>
import {ApiApiFactory} from "@/utils/openapi/api";
import {resolveDjangoUrl, StandardToasts} from "@/utils/utils";
@ -69,6 +98,8 @@ export default {
}
},
mounted() {
this.$i18n.locale = window.CUSTOM_LOCALE
this.user_preferences = this.preferences
this.languages = window.AVAILABLE_LANGUAGES
this.loadSettings()
@ -86,7 +117,7 @@ export default {
let apiFactory = new ApiApiFactory()
apiFactory.partialUpdateUserPreference(this.user_id.toString(), this.user_preferences).then(result => {
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
if (reload !== undefined) {
if (reload) {
location.reload()
}
}).catch(err => {

View File

@ -1,15 +1,16 @@
<template>
<div v-if="user_preferences !== undefined">
<generic-multiselect
@change="updateSettings(false)"
:model="Models.USER"
:initial_selection="user_preferences.plan_share"
label="display_name"
:multiple="true"
:placeholder="$t('User')"
></generic-multiselect>
<b-form-group :label="$t('Share')" :description="$t('plan_share_desc')">
<generic-multiselect
@change="updateSettings(false)"
:model="Models.USER"
:initial_selection="user_preferences.plan_share"
label="display_name"
:multiple="true"
:placeholder="$t('User')"
></generic-multiselect>
</b-form-group>
</div>
</template>

View File

@ -1,13 +1,32 @@
<template>
<div v-if="user_preferences !== undefined">
<div>
<a :href="resolveDjangoUrl('view_shopping_settings')" class="btn btn-primary">Search Settings</a>
<div v-if="false">
<!--TODO search must fundamentally be reworked, thus i was to lazy to implement the settings -->
method
unpercice
accent
partial
start with
fuzzy
full text
trigram
</div>
</div>
</template>
<script>
import {ApiApiFactory} from "@/utils/openapi/api";
import {StandardToasts} from "@/utils/utils";
import {ResolveUrlMixin, StandardToasts} from "@/utils/utils";
import axios from "axios";
@ -16,40 +35,20 @@ axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
export default {
name: "SearchSettingsComponent",
mixins: [ResolveUrlMixin],
props: {
user_id: Number,
},
data() {
return {
user_preferences: undefined,
languages: [],
}
},
mounted() {
this.user_preferences = this.preferences
this.languages = window.AVAILABLE_LANGUAGES
this.loadSettings()
},
methods: {
loadSettings: function () {
let apiFactory = new ApiApiFactory()
apiFactory.retrieveUserPreference(this.user_id.toString()).then(result => {
this.user_preferences = result.data
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err)
})
},
updateSettings: function (reload) {
let apiFactory = new ApiApiFactory()
apiFactory.partialUpdateUserPreference(this.user_id.toString(), this.user_preferences).then(result => {
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
if (reload !== undefined) {
location.reload()
}
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
},
}
}
</script>

View File

@ -1,32 +1,92 @@
<template>
<div v-if="user_preferences !== undefined">
<b-form-group :label="$t('shopping_share')" :description="$t('shopping_share_desc')">
<generic-multiselect
@change="updateSettings(false)"
:model="Models.USER"
:initial_selection="user_preferences.shopping_share"
label="display_name"
:multiple="true"
:placeholder="$t('User')"
></generic-multiselect>
</b-form-group>
<generic-multiselect
@change="updateSettings(false)"
:model="Models.USER"
:initial_selection="user_preferences.shopping_share"
label="display_name"
:multiple="true"
:placeholder="$t('User')"
></generic-multiselect>
<b-form-group :label="$t('shopping_auto_sync')" :description="$t('shopping_auto_sync_desc')">
<b-form-input type="range" :min="SHOPPING_MIN_AUTOSYNC_INTERVAL" max="60" step="1" v-model="user_preferences.shopping_auto_sync"
@change="updateSettings(false)"></b-form-input>
<div class="text-center">
<span v-if="user_preferences.shopping_auto_sync > 0">
{{ Math.round(user_preferences.shopping_auto_sync) }}
<span v-if="user_preferences.shopping_auto_sync === 1">{{ $t('Second') }}</span>
<span v-else> {{ $t('Seconds') }}</span>
</span>
<!--TODO load min autosync time from env -->
<b-form-input type="range" min="0" max="60" step="1" v-model="user_preferences.shopping_auto_sync"
@change="updateSettings(false)"></b-form-input>
<span v-if="user_preferences.shopping_auto_sync < 1">{{ $t('Disable') }}</span>
</div>
<br/>
<b-button class="btn btn-sm" @click="user_preferences.shopping_auto_sync = 0">{{ $t('Disabled') }}</b-button>
</b-form-group>
<b-form-checkbox v-model="user_preferences.mealplan_autoadd_shopping" @change="updateSettings(false)"></b-form-checkbox>
<b-form-checkbox v-model="user_preferences.mealplan_autoexclude_onhand" @change="updateSettings(false)"></b-form-checkbox>
<b-form-checkbox v-model="user_preferences.mealplan_autoinclude_related" @change="updateSettings(false)"></b-form-checkbox>
<b-form-checkbox v-model="user_preferences.shopping_add_onhand" @change="updateSettings(false)"></b-form-checkbox>
<b-form-group :description="$t('mealplan_autoadd_shopping_desc')">
<b-form-checkbox v-model="user_preferences.mealplan_autoadd_shopping"
@change="updateSettings(false)">{{ $t('mealplan_autoadd_shopping') }}
</b-form-checkbox>
</b-form-group>
<b-form-input type="number" v-model="user_preferences.default_delay" @change="updateSettings(false)"></b-form-input>
<b-form-checkbox v-model="user_preferences.filter_to_supermarket" @change="updateSettings(false)"></b-form-checkbox>
<b-form-input type="range" min="0" max="14" step="1" v-model="user_preferences.shopping_recent_days"
@change="updateSettings(false)"></b-form-input>
<b-form-group :description="$t('mealplan_autoexclude_onhand_desc')">
<b-form-checkbox v-model="user_preferences.mealplan_autoexclude_onhand"
@change="updateSettings(false)">{{ $t('mealplan_autoexclude_onhand') }}
</b-form-checkbox>
</b-form-group>
<b-form-group :description="$t('mealplan_autoinclude_related_desc')">
<b-form-checkbox v-model="user_preferences.mealplan_autoinclude_related"
@change="updateSettings(false)">{{ $t('mealplan_autoinclude_related') }}
</b-form-checkbox>
</b-form-group>
<b-form-group :description="$t('shopping_add_onhand_desc')">
<b-form-checkbox v-model="user_preferences.shopping_add_onhand"
@change="updateSettings(false)">{{ $t('shopping_add_onhand') }}
</b-form-checkbox>
</b-form-group>
<b-form-group :label="$t('default_delay')" :description="$t('default_delay_desc')">
<b-form-input type="range" min="1" max="72" step="1" v-model="user_preferences.default_delay"
@change="updateSettings(false)"></b-form-input>
<div class="text-center">
<span>{{ Math.round(user_preferences.default_delay) }}
<span v-if="user_preferences.default_delay === 1">{{ $t('Hour') }}</span>
<span v-else> {{ $t('Hours') }}</span>
</span>
</div>
</b-form-group>
<b-form-group :description="$t('filter_to_supermarket_desc')">
<b-form-checkbox v-model="user_preferences.filter_to_supermarket"
@change="updateSettings(false)">{{ $t('filter_to_supermarket') }}
</b-form-checkbox>
</b-form-group>
<b-form-group :label="$t('shopping_recent_days')" :description="$t('shopping_recent_days_desc')">
<b-form-input type="range" min="0" max="14" step="1" v-model="user_preferences.shopping_recent_days"
@change="updateSettings(false)"></b-form-input>
<div class="text-center">
<span>{{ Math.round(user_preferences.shopping_recent_days) }}
<span v-if="user_preferences.shopping_recent_days === 1">{{ $t('Day') }}</span>
<span v-else> {{ $t('Days') }}</span>
</span>
</div>
</b-form-group>
<b-form-group :label="$t('csv_delim_label')" :description="$t('csv_delim_help')">
<b-form-input v-model="user_preferences.csv_delim" @change="updateSettings(false)"></b-form-input>
</b-form-group>
<b-form-group :label="$t('csv_prefix_label')" :description="$t('csv_prefix_help')">
<b-form-input v-model="user_preferences.csv_prefix" @change="updateSettings(false)"></b-form-input>
</b-form-group>
<b-form-input v-model="user_preferences.csv_delim" @change="updateSettings(false)"></b-form-input>
<b-form-input v-model="user_preferences.csv_prefix" @change="updateSettings(false)"></b-form-input>
</div>
</template>
@ -50,6 +110,7 @@ export default {
data() {
return {
user_preferences: undefined,
SHOPPING_MIN_AUTOSYNC_INTERVAL: window.SHOPPING_MIN_AUTOSYNC_INTERVAL,
languages: [],
}
},
@ -69,9 +130,10 @@ export default {
},
updateSettings: function (reload) {
let apiFactory = new ApiApiFactory()
this.$emit('updated', this.user_preferences)
apiFactory.partialUpdateUserPreference(this.user_id.toString(), this.user_preferences).then(result => {
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
if (reload !== undefined) {
if (reload) {
location.reload()
}
}).catch(err => {

View File

@ -169,6 +169,8 @@
"tree_root": "Root of Tree",
"Icon": "Icon",
"Unit": "Unit",
"Decimals": "Decimals",
"Default_Unit": "Default Unit",
"No_Results": "No Results",
"New_Unit": "New Unit",
"Create_New_Shopping Category": "Create New Shopping Category",
@ -219,6 +221,8 @@
"Title_or_Recipe_Required": "Title or recipe selection required",
"Color": "Color",
"New_Meal_Type": "New Meal type",
"Use_Fractions": "Use Fractions",
"Use_Fractions_Help": "Automatically convert decimals to fractions when viewing a recipe.",
"AddFoodToShopping": "Add {food} to your shopping list",
"RemoveFoodFromShopping": "Remove {food} from your shopping list",
"DeleteShoppingConfirm": "Are you sure that you want to remove all {food} from the shopping list?",
@ -241,6 +245,8 @@
"FoodInherit": "Food Inheritable Fields",
"ShowUncategorizedFood": "Show Undefined",
"GroupBy": "Group By",
"Language": "Language",
"Theme": "Theme",
"SupermarketCategoriesOnly": "Supermarket Categories Only",
"MoveCategory": "Move To: ",
"CountMore": "...+{count} more",
@ -259,6 +265,7 @@
"mealplan_autoexclude_onhand": "Exclude Food On Hand",
"mealplan_autoinclude_related": "Add Related Recipes",
"default_delay": "Default Delay Hours",
"plan_share_desc": "New Meal Plan entries will automatically be shared with selected users.",
"shopping_share_desc": "Users will see all items you add to your shopping list. They must add you to see items on their list.",
"shopping_auto_sync_desc": "Setting to 0 will disable auto sync. When viewing a shopping list the list is updated every set seconds to sync changes someone else might have made. Useful when shopping with multiple people but will use mobile data.",
"mealplan_autoadd_shopping_desc": "Automatically add meal plan ingredients to shopping list.",
@ -270,6 +277,12 @@
"Auto_Planner": "Auto-Planner",
"New_Cookbook": "New cookbook",
"Hide_Keyword": "Hide keywords",
"Hour": "Hour",
"Hours": "Hours",
"Day": "Day",
"Days": "Days",
"Second": "Second",
"Seconds": "Seconds",
"Clear": "Clear",
"Users": "Users",
"Invites": "Invites",
@ -310,6 +323,9 @@
"shopping_category_help": "Supermarkets can be ordered and filtered by Shopping Category according to the layout of the aisles.",
"food_recipe_help": "Linking a recipe here will include the linked recipe in any other recipe that use this food",
"Foods": "Foods",
"Account": "Account",
"Cosmetic": "Cosmetic",
"API": "API",
"enable_expert": "Enable Expert Mode",
"expert_mode": "Expert Mode",
"simple_mode": "Simple Mode",
@ -353,6 +369,12 @@
"App": "App",
"Message": "Message",
"Bookmarklet": "Bookmarklet",
"Sticky_Nav": "Sticky Navigation",
"Sticky_Nav_Help": "Always show the navigation menu at the top of the screen.",
"Nav_Color": "Navigation Color",
"Nav_Color_Help": "Change navigation color.",
"Use_Kj": "Use kJ instead of kcal",
"Comments_setting": "Show Comments",
"click_image_import": "Click the image you want to import for this recipe",
"no_more_images_found": "No additional images found on Website.",
"import_duplicates": "To prevent duplicates recipes with the same name as existing ones are ignored. Check this box to import everything.",
@ -391,6 +413,9 @@
"Ratings": "Ratings",
"Internal": "Internal",
"Units": "Units",
"Manage_Emails": "Manage Emails",
"Change_Password": "Change Password",
"Social_Authentication": "Social Authentication",
"Random Recipes": "Random Recipes",
"parameter_count": "Parameter {count}",
"select_keyword": "Select Keyword",
@ -404,12 +429,17 @@
"Select": "Select",
"Supermarkets": "Supermarkets",
"User": "User",
"Username": "Username",
"First_name": "First Name",
"Last_name": "Last Name",
"Keyword": "Keyword",
"Advanced": "Advanced",
"Page": "Page",
"Single": "Single",
"Multiple": "Multiple",
"Reset": "Reset",
"Disabled": "Disabled",
"Disable": "Disable",
"Options": "Options",
"Create Food": "Create Food",
"create_food_desc": "Create a food and link it to this recipe.",