mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-03 13:19:16 -05:00
stubbed out all Food GenericModalForms
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
<template>
|
||||
<div id="app" style="margin-bottom: 4vh">
|
||||
<generic-modal-form
|
||||
:model="this_model.name"
|
||||
:action="this_action"/>
|
||||
:model="this_model"
|
||||
:action="this_action"
|
||||
:item1="foods[5]"
|
||||
:item2="undefined"
|
||||
:show="true"/> <!-- TODO make this based on method -->
|
||||
<generic-split-lists
|
||||
:list_name="this_model.name"
|
||||
@reset="resetList"
|
||||
@@ -59,9 +62,8 @@
|
||||
<!-- TODO initial selection isn't working and I don't know why -->
|
||||
<generic-multiselect
|
||||
@change="this_item.recipe=$event.val"
|
||||
label="name"
|
||||
:initial_selection="[this_item.recipe]"
|
||||
search_function="listRecipes"
|
||||
:model="recipe"
|
||||
:multiple="false"
|
||||
:sticky_options="[{'id': null,'name': $t('None')}]"
|
||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||
@@ -74,7 +76,7 @@
|
||||
<label for="id_food_category_edit">{{ this.$t('Shopping_Category') }}</label>
|
||||
<generic-multiselect
|
||||
@change="this_item.supermarket_category=$event.val"
|
||||
label="name"
|
||||
:model="models.SHOPPING_CATEGORY"
|
||||
:initial_selection="[this_item.supermarket_category]"
|
||||
search_function="listSupermarketCategorys"
|
||||
:multiple="false"
|
||||
@@ -103,29 +105,25 @@
|
||||
{{ this.$t("move_selection", {'child': this_item.name}) }}
|
||||
<generic-multiselect
|
||||
@change="this_item.target=$event.val"
|
||||
label="name"
|
||||
search_function="listFoods"
|
||||
:model="this_model"
|
||||
:multiple="false"
|
||||
:sticky_options="[{'id': 0,'name': $t('Root')}]"
|
||||
:tree_api="true"
|
||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||
:placeholder="this.$t('Search')">
|
||||
>
|
||||
</generic-multiselect>
|
||||
</b-modal>
|
||||
<!-- merge modal -->
|
||||
<b-modal class="modal"
|
||||
:id="'id_modal_food_merge'"
|
||||
:title="this.$t('Merge_Food')"
|
||||
:title="this.$t('merge_title', {'type': 'Food'})"
|
||||
:ok-title="this.$t('Merge')"
|
||||
:cancel-title="this.$t('Cancel')"
|
||||
@ok="mergeFood(this_item.id, this_item.target.id)">
|
||||
{{ this.$t("merge_selection", {'source': this_item.name, 'type': this.$t('food')}) }}
|
||||
<generic-multiselect
|
||||
@change="this_item.target=$event.val"
|
||||
label="name"
|
||||
search_function="listFoods"
|
||||
:model="this_model"
|
||||
:multiple="false"
|
||||
:tree_api="true"
|
||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||
:placeholder="this.$t('Search')">
|
||||
</generic-multiselect>
|
||||
@@ -141,7 +139,7 @@ import {BootstrapVue} from 'bootstrap-vue'
|
||||
|
||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||
|
||||
import {ApiMixin, CardMixin, ToastMixin} from "@/utils/utils";
|
||||
import {CardMixin, ToastMixin, genericAPI} from "@/utils/utils";
|
||||
import {Models, Actions} from "@/utils/models";
|
||||
import {StandardToasts} from "@/utils/utils";
|
||||
|
||||
@@ -154,12 +152,13 @@ Vue.use(BootstrapVue)
|
||||
|
||||
export default {
|
||||
name: 'FoodListView',
|
||||
mixins: [ApiMixin, CardMixin, ToastMixin],
|
||||
mixins: [CardMixin, ToastMixin],
|
||||
components: {GenericHorizontalCard, GenericMultiselect, GenericSplitLists, GenericModalForm},
|
||||
data() {
|
||||
return {
|
||||
this_model: Models.FOOD,
|
||||
this_action:'',
|
||||
this_model: Models.FOOD, //TODO: mounted method to calcuate
|
||||
this_action: Actions.UPDATE, //TODO: based on what we are doing
|
||||
models: Models,
|
||||
foods: [],
|
||||
foods2: [],
|
||||
load_more_left: true,
|
||||
@@ -251,7 +250,8 @@ export default {
|
||||
getFoods: function(params, callback) {
|
||||
let column = params?.column ?? 'left'
|
||||
|
||||
this.genericAPI(this.this_model, Actions.LIST, params).then((result) => {
|
||||
// TODO: does this need to be a callback?
|
||||
genericAPI(this.this_model, Actions.LIST, params).then((result) => {
|
||||
if (result.data.results.length){
|
||||
if (column ==='left') {
|
||||
this.foods = this.foods.concat(result.data.results)
|
||||
@@ -266,20 +266,21 @@ export default {
|
||||
}
|
||||
// return true if total objects are still less than the length of the list
|
||||
callback(result.data.count < (column==="left" ? this.foods.length : this.foods2.length))
|
||||
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_FETCH)
|
||||
})
|
||||
},
|
||||
getThis: function(id, callback){
|
||||
return this.genericAPI(this.this_model, Actions.FETCH, {'id': id})
|
||||
return genericAPI(this.this_model, Actions.FETCH, {'id': id})
|
||||
},
|
||||
saveFood: function () {
|
||||
let food = {...this.this_item}
|
||||
food.supermarket_category = this.this_item.supermarket_category?.id ?? null
|
||||
food.recipe = this.this_item.recipe?.id ?? null
|
||||
if (!food?.id) { // if there is no item id assume it's a new item
|
||||
this.genericAPI(this.this_model, Actions.CREATE, food).then((result) => {
|
||||
genericAPI(this.this_model, Actions.CREATE, food).then((result) => {
|
||||
// place all new foods at the top of the list - could sort instead
|
||||
this.foods = [result.data].concat(this.foods)
|
||||
// this creates a deep copy to make sure that columns stay independent
|
||||
@@ -294,7 +295,7 @@ export default {
|
||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_CREATE)
|
||||
})
|
||||
} else {
|
||||
this.genericAPI(this.this_model, Actions.UPDATE, food).then((result) => {
|
||||
genericAPI(this.this_model, Actions.UPDATE, food).then((result) => {
|
||||
this.refreshObject(food.id)
|
||||
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_UPDATE)
|
||||
}).catch((err) => {
|
||||
@@ -305,7 +306,7 @@ export default {
|
||||
this.this_item = {...this.blank_item}
|
||||
},
|
||||
moveFood: function (source_id, target_id) {
|
||||
this.genericAPI(this.this_model, Actions.MOVE, {'source': source_id, 'target': target_id}).then((result) => {
|
||||
genericAPI(this.this_model, Actions.MOVE, {'source': source_id, 'target': target_id}).then((result) => {
|
||||
if (target_id === 0) {
|
||||
let food = this.findCard(source_id, this.foods) || this.findCard(source_id, this.foods2)
|
||||
this.foods = [food].concat(this.destroyCard(source_id, this.foods)) // order matters, destroy old card before adding it back in at root
|
||||
@@ -326,7 +327,7 @@ export default {
|
||||
})
|
||||
},
|
||||
mergeFood: function (source_id, target_id) {
|
||||
this.genericAPI(this.this_model, Actions.MERGE, {'source': source_id, 'target': target_id}).then((result) => {
|
||||
genericAPI(this.this_model, Actions.MERGE, {'source': source_id, 'target': target_id}).then((result) => {
|
||||
this.foods = this.destroyCard(source_id, this.foods)
|
||||
this.foods2 = this.destroyCard(source_id, this.foods2)
|
||||
this.refreshObject(target_id)
|
||||
@@ -343,7 +344,7 @@ export default {
|
||||
'root': food.id,
|
||||
'pageSize': 200
|
||||
}
|
||||
this.genericAPI(this.this_model, Actions.LIST, options).then((result) => {
|
||||
genericAPI(this.this_model, Actions.LIST, options).then((result) => {
|
||||
parent = this.findCard(food.id, col === 'left' ? this.foods : this.foods2)
|
||||
if (parent) {
|
||||
Vue.set(parent, 'children', result.data.results)
|
||||
@@ -362,7 +363,7 @@ export default {
|
||||
'pageSize': 200
|
||||
}
|
||||
|
||||
this.genericAPI(Models.RECIPE, Actions.LIST, options).then((result) => {
|
||||
genericAPI(Models.RECIPE, Actions.LIST, options).then((result) => {
|
||||
parent = this.findCard(food.id, col === 'left' ? this.foods : this.foods2)
|
||||
if (parent) {
|
||||
Vue.set(parent, 'recipes', result.data.results)
|
||||
@@ -376,7 +377,6 @@ export default {
|
||||
})
|
||||
},
|
||||
refreshObject: function(id){
|
||||
console.log('refresh object', id)
|
||||
this.getThis(id).then(result => {
|
||||
this.refreshCard(result.data, this.foods)
|
||||
this.refreshCard({...result.data}, this.foods2)
|
||||
@@ -393,7 +393,7 @@ export default {
|
||||
this.this_item.icon = icon
|
||||
},
|
||||
deleteThis: function(id, model) {
|
||||
this.genericAPI(this.this_model, Actions.DELETE, {'id': id}).then((result) => {
|
||||
genericAPI(this.this_model, Actions.DELETE, {'id': id}).then((result) => {
|
||||
this.foods = this.destroyCard(id, this.foods)
|
||||
this.foods2 = this.destroyCard(id, this.foods2)
|
||||
StandardToasts.makeStandardToast(StandardToasts.SUCCESS_DELETE)
|
||||
|
||||
@@ -157,7 +157,7 @@
|
||||
<generic-multiselect
|
||||
@change="this_item.target=$event.val"
|
||||
label="name"
|
||||
search_function="listKeywords"
|
||||
:models="models.KEYWORD"
|
||||
:multiple="false"
|
||||
:sticky_options="[{'id': 0,'name': $t('Root')}]"
|
||||
:tree_api="true"
|
||||
@@ -175,8 +175,7 @@
|
||||
{{ this.$t("merge_selection", {'source': this_item.name, 'type': this.$t('keyword')}) }}
|
||||
<generic-multiselect
|
||||
@change="this_item.target=$event.val"
|
||||
label="name"
|
||||
search_function="listKeywords"
|
||||
:model="models.KEYWORD"
|
||||
:multiple="false"
|
||||
:tree_api="true"
|
||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||
@@ -213,6 +212,7 @@ import EmojiGroups from '@kevinfaguiar/vue-twemoji-picker/emoji-data/emoji-group
|
||||
// end move with generic modals
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
import {Models} from "@/utils/models";
|
||||
|
||||
export default {
|
||||
name: 'KeywordListView',
|
||||
@@ -232,6 +232,7 @@ export default {
|
||||
return {
|
||||
keywords: [],
|
||||
keywords2: [],
|
||||
models: Models,
|
||||
show_split: false,
|
||||
search_input: '',
|
||||
search_input2: '',
|
||||
|
||||
@@ -195,7 +195,7 @@
|
||||
<b-input-group class="mt-2">
|
||||
<generic-multiselect @change="genericSelectChanged" parent_variable="search_books"
|
||||
:initial_selection="settings.search_books"
|
||||
search_function="listRecipeBooks" label="name"
|
||||
:model="models.RECIPE_BOOK"
|
||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||
v-bind:placeholder="$t('Books')" :limit="50"></generic-multiselect>
|
||||
<b-input-group-append>
|
||||
@@ -282,6 +282,7 @@ import VueCookies from 'vue-cookies'
|
||||
Vue.use(VueCookies)
|
||||
|
||||
import {ResolveUrlMixin} from "@/utils/utils";
|
||||
import {Models} from "@/utils/models";
|
||||
|
||||
import LoadingSpinner from "@/components/LoadingSpinner"; // is this deprecated?
|
||||
|
||||
@@ -328,6 +329,7 @@ export default {
|
||||
|
||||
pagination_count: 0,
|
||||
random_search: false,
|
||||
models: Models
|
||||
}
|
||||
|
||||
},
|
||||
@@ -353,7 +355,7 @@ export default {
|
||||
|
||||
this.loadMealPlan()
|
||||
// this.loadRecentlyViewed()
|
||||
// this.refreshData(false) // this gets triggered when the cookies get loaded
|
||||
this.refreshData(false) // this gets triggered when the cookies get loaded
|
||||
})
|
||||
|
||||
this.$i18n.locale = window.CUSTOM_LOCALE
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
:clear-on-select="true"
|
||||
:hide-selected="true"
|
||||
:preserve-search="true"
|
||||
:placeholder="placeholder"
|
||||
:placeholder="lookupPlaceholder"
|
||||
:label="label"
|
||||
track-by="id"
|
||||
:multiple="multiple"
|
||||
@@ -19,7 +19,8 @@
|
||||
<script>
|
||||
|
||||
import Multiselect from 'vue-multiselect'
|
||||
import {ApiApiFactory} from "@/utils/openapi/api";
|
||||
import {genericAPI} from "@/utils/utils";
|
||||
import {Actions} from "@/utils/models";
|
||||
|
||||
export default {
|
||||
name: "GenericMultiselect",
|
||||
@@ -32,15 +33,14 @@ export default {
|
||||
}
|
||||
},
|
||||
props: {
|
||||
placeholder: String,
|
||||
search_function: String,
|
||||
label: String,
|
||||
placeholder: {type: String, default: undefined},
|
||||
model: {type: Object, default () {return {}}},
|
||||
label: {type: String, default: 'name'},
|
||||
parent_variable: {type: String, default: undefined},
|
||||
limit: {type: Number, default: 10,},
|
||||
sticky_options: {type:Array, default(){return []}},
|
||||
initial_selection: {type:Array, default(){return []}},
|
||||
multiple: {type: Boolean, default: true},
|
||||
tree_api: {type: Boolean, default: false} // api requires params that are unique to TreeMixin
|
||||
multiple: {type: Boolean, default: true}
|
||||
},
|
||||
watch: {
|
||||
initial_selection: function (newVal, oldVal) { // watch it
|
||||
@@ -59,31 +59,21 @@ export default {
|
||||
this.selected_objects = this.initial_selection?.[0] ?? null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
lookupPlaceholder() {
|
||||
return this.placeholder || this.model.name || this.$t('Search')
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
search: function (query) {
|
||||
let apiClient = new ApiApiFactory()
|
||||
if (this.tree_api) {
|
||||
let page = 1
|
||||
let root = undefined
|
||||
let tree = undefined
|
||||
let pageSize = 10
|
||||
|
||||
if (query === '') {
|
||||
query = undefined
|
||||
}
|
||||
apiClient[this.search_function](query, root, tree, page, pageSize).then(result => {
|
||||
this.objects = this.sticky_options.concat(result.data.results)
|
||||
})
|
||||
} else if (this.search_function === 'listRecipes') {
|
||||
apiClient[this.search_function](query, undefined, undefined, undefined, undefined, undefined,
|
||||
undefined, undefined, undefined, undefined, undefined, 25, undefined).then(result => {
|
||||
this.objects = this.sticky_options.concat(result.data.results)
|
||||
})
|
||||
} else {
|
||||
apiClient[this.search_function]({query: {query: query, limit: this.limit}}).then(result => {
|
||||
this.objects = this.sticky_options.concat(result.data)
|
||||
})
|
||||
let options = {
|
||||
'page': 1,
|
||||
'pageSize': 10,
|
||||
'query': query
|
||||
}
|
||||
genericAPI(this.model, Actions.LIST, options).then((result) => {
|
||||
this.objects = this.sticky_options.concat(result.data?.results ?? result.data)
|
||||
})
|
||||
},
|
||||
selectionChanged: function () {
|
||||
this.$emit('change', {var: this.parent_variable, val: this.selected_objects})
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-modal class="modal" id="modal" >
|
||||
<template v-slot:modal-title><h4>{{model}} {{action}}</h4></template>
|
||||
<div v-for="(f, i) in fields" v-bind:key=i>
|
||||
<template v-slot:modal-title><h4>{{form.title}}</h4></template>
|
||||
<div v-for="(f, i) in form.fields" v-bind:key=i>
|
||||
<p v-if="f.type=='instruction'">{{f.label}}</p>
|
||||
<lookup-input v-if="f.type=='lookup'"
|
||||
:label="f.label"
|
||||
:value="f.value"
|
||||
:field="f.field"
|
||||
:model="listModel(f.list)"
|
||||
:sticky_options="f.sticky_options || undefined"
|
||||
@change="changeValue"/> <!-- TODO add ability to create new items associated with lookup -->
|
||||
<checkbox-input v-if="f.type=='checkbox'"
|
||||
:label="f.label"
|
||||
@@ -22,7 +24,7 @@
|
||||
|
||||
<template v-slot:modal-footer>
|
||||
<b-button class="float-right mx-1" variant="secondary" v-on:click="$bvModal.hide('modal')">{{$t('Cancel')}}</b-button>
|
||||
<b-button class="float-right mx-1" variant="primary" v-on:click="doAction">{{buttonLabel}}</b-button>
|
||||
<b-button class="float-right mx-1" variant="primary" v-on:click="doAction">{{form.ok_label}}</b-button>
|
||||
</template>
|
||||
</b-modal>
|
||||
<b-button v-on:click="Button">ok</b-button>
|
||||
@@ -32,7 +34,10 @@
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
import {BootstrapVue} from 'bootstrap-vue'
|
||||
import {getForm} from "@/utils/utils";
|
||||
Vue.use(BootstrapVue)
|
||||
|
||||
import {Models} from "@/utils/models";
|
||||
import CheckboxInput from "@/components/Modals/CheckboxInput";
|
||||
import LookupInput from "@/components/Modals/LookupInput";
|
||||
import TextInput from "@/components/Modals/TextInput";
|
||||
@@ -41,46 +46,17 @@ export default {
|
||||
name: 'GenericModalForm',
|
||||
components: {CheckboxInput, LookupInput, TextInput},
|
||||
props: {
|
||||
model: {type: String, default: ''},
|
||||
model: {type: Object, default: function() {}},
|
||||
action: {type: Object, default: function() {}},
|
||||
item1: {type: Object, default: function() {}},
|
||||
item2: {type: Object, default: function() {}},
|
||||
// action: {type: String, default: ''},
|
||||
show: {type: Boolean, default: false},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
new_item: {},
|
||||
action: '',
|
||||
fields: [
|
||||
{'label': 'This is a long set of instructions that tell you to be careful with what you do next or really bad things are likely to happen.',
|
||||
'type': 'instruction',
|
||||
'value': undefined},
|
||||
{'field': 'name',
|
||||
'label': 'first',
|
||||
'type': 'text',
|
||||
'value': undefined,
|
||||
'placeholder': 'do the thing'},
|
||||
{'field': 'shopping',
|
||||
'label': 'second',
|
||||
'type': 'lookup',
|
||||
'value': undefined},
|
||||
{'field': 'isreal',
|
||||
'label': 'third',
|
||||
'type': 'checkbox',
|
||||
'value': undefined},
|
||||
{'field': 'ignore',
|
||||
'label': 'fourth',
|
||||
'type': 'checkbox',
|
||||
'value': undefined},
|
||||
{'field': 'another_category',
|
||||
'label': 'fifth',
|
||||
'type': 'lookup',
|
||||
'value': undefined},
|
||||
{'field': 'description',
|
||||
'label': 'sixth',
|
||||
'type': 'text',
|
||||
'value': undefined,
|
||||
'placeholder': 'also, do the thing'
|
||||
}
|
||||
],
|
||||
form: {},
|
||||
buttons: {
|
||||
'new':{'label': this.$t('Save')},
|
||||
'delete':{'label': this.$t('Delete')},
|
||||
@@ -101,20 +77,20 @@ export default {
|
||||
watch: {
|
||||
'show': function () {
|
||||
if (this.show) {
|
||||
this.form = getForm(this.model, this.action, this.item1, this.item2)
|
||||
this.$bvModal.show('modal')
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
Button: function(e) {
|
||||
console.log(typeof({}), typeof([]), typeof('this'), typeof(1))
|
||||
this.action='new'
|
||||
this.form = getForm(this.model, this.action, this.item1, this.item2)
|
||||
console.log(this.form)
|
||||
this.$bvModal.show('modal')
|
||||
},
|
||||
doAction: function(){
|
||||
console.log(this.new_item)
|
||||
let alert_text = ''
|
||||
for (const [k, v] of Object.entries(this.fields)) {
|
||||
for (const [k, v] of Object.entries(this.form.fields)) {
|
||||
if (v.type !== 'instruction'){
|
||||
alert_text = alert_text + v.field + ": " + this.new_item[v.field] + "\n"
|
||||
}
|
||||
@@ -122,11 +98,17 @@ export default {
|
||||
this.$nextTick(function() {this.$bvModal.hide('modal')})
|
||||
setTimeout(() => {}, 0) // confirm that the
|
||||
alert(alert_text)
|
||||
console.log(this.model_values)
|
||||
},
|
||||
changeValue: function(field, value) {
|
||||
// console.log('catch change', field, value)
|
||||
this.new_item[field] = value
|
||||
console.log('catch value', field, value)
|
||||
},
|
||||
listModel: function(m) {
|
||||
if (m === 'self') {
|
||||
return this.model
|
||||
} else {
|
||||
return Models[m]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,19 +4,19 @@
|
||||
v-bind:label="label"
|
||||
class="mb-3">
|
||||
<generic-multiselect
|
||||
@change="new_value=$event.val"
|
||||
label="name"
|
||||
@change="new_value=$event.val['id']"
|
||||
:initial_selection="[]"
|
||||
search_function="listSupermarketCategorys"
|
||||
:model="model"
|
||||
:multiple="false"
|
||||
:sticky_options="[{'id': null,'name': $t('None')}]"
|
||||
:sticky_options="sticky_options"
|
||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||
:placeholder="$t('Shopping_Category')">
|
||||
:placeholder="modelName">
|
||||
</generic-multiselect>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Vue from "vue";
|
||||
import GenericMultiselect from "@/components/GenericMultiselect";
|
||||
|
||||
export default {
|
||||
@@ -24,10 +24,10 @@ export default {
|
||||
components: {GenericMultiselect},
|
||||
props: {
|
||||
field: {type: String, default: 'You Forgot To Set Field Name'},
|
||||
label: {type: String, default: 'Lookup Field'},
|
||||
label: {type: String, default: ''},
|
||||
value: {type: Object, default () {return {}}},
|
||||
show_move: {type: Boolean, default: false},
|
||||
show_merge: {type: Boolean, default: false},
|
||||
model: {type: Object, default () {return {}}},
|
||||
sticky_options: {type:Array, default(){return []}},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -37,6 +37,11 @@ export default {
|
||||
mounted() {
|
||||
this.new_value = this.value.id
|
||||
},
|
||||
computed: {
|
||||
modelName() {
|
||||
return this?.model?.name ?? this.$t('Search')
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'new_value': function () {
|
||||
this.$root.$emit('change', this.field, this.new_value)
|
||||
|
||||
@@ -95,11 +95,11 @@
|
||||
"Move": "Move",
|
||||
"Merge": "Merge",
|
||||
"Parent": "Parent",
|
||||
"delete_confimation": "Are you sure that you want to delete {kw} and all of it's children?",
|
||||
"delete_confirmation": "Are you sure that you want to delete {source}?",
|
||||
"move_confirmation": "Move {child} to parent {parent}",
|
||||
"merge_confirmation": "Replace {source} with {target}",
|
||||
"move_selection": "Select a parent to move {child} to.",
|
||||
"merge_selection": "Replace all occurences of {source} with the selected {type}.",
|
||||
"move_selection": "Select a parent {type} to move {source} to.",
|
||||
"merge_selection": "Replace all occurrences of {source} with the selected {type}.",
|
||||
"Root": "Root",
|
||||
"Ignore_Shopping": "Ignore Shopping",
|
||||
"Shopping_Category": "Shopping Category",
|
||||
@@ -109,5 +109,16 @@
|
||||
"Hide_Food": "Hide Food",
|
||||
"Delete_Food": "Delete Food",
|
||||
"No_ID": "ID not found, cannot delete.",
|
||||
"Meal_Plan_Days": "Future meal plans"
|
||||
"Meal_Plan_Days": "Future meal plans",
|
||||
"merge_title": "Merge {type}",
|
||||
"move_title": "Move {type}",
|
||||
"Food": "Food",
|
||||
"Recipe_Book": "Recipe Book",
|
||||
"del_confirmation_tree": "Are you sure that you want to delete {source} and all of it's children?",
|
||||
"delete_title": "Delete {type}",
|
||||
"create_title": "New {type}",
|
||||
"edit_title": "Edit {type}",
|
||||
"Name": "Name",
|
||||
"Description": "Description",
|
||||
"Recipe": "Recipe"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Utility CLASS to define model configurations
|
||||
* */
|
||||
import i18n from "@/i18n";
|
||||
|
||||
// TODO this needs rethought and simplified
|
||||
// maybe a function that returns a single dictionary based on action?
|
||||
@@ -25,26 +26,94 @@ export class Models {
|
||||
},
|
||||
'tree': {'default': undefined},
|
||||
},
|
||||
},
|
||||
'delete': {
|
||||
"form": {
|
||||
'instruction': {
|
||||
'form_field': true,
|
||||
'type': 'instruction',
|
||||
'function': 'translate',
|
||||
'phrase': "del_confimation_tree",
|
||||
'params':[
|
||||
{
|
||||
'token': 'source',
|
||||
'from':'item1',
|
||||
'attribute': "name"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MODELS - inherits and takes precedence over MODEL_TYPES and ACTIONS
|
||||
static FOOD = {
|
||||
'name': 'Food', // *OPTIONAL: parameters will be built model -> model_type -> default
|
||||
'model_type': this.TREE, // *OPTIONAL* model specific params for api, if not present will attempt modeltype_create then default_create
|
||||
'name': i18n.t('Food'), // *OPTIONAL* : parameters will be built model -> model_type -> default
|
||||
'apiName': 'Food', // *REQUIRED* : the name that is used in api.ts for this model
|
||||
'model_type': this.TREE, // *OPTIONAL* : model specific params for api, if not present will attempt modeltype_create then default_create
|
||||
// REQUIRED: unordered array of fields that can be set during create
|
||||
'create': {
|
||||
// if not defined partialUpdate will use the same parameters, prepending 'id'
|
||||
'params': [['name', 'description', 'recipe', 'ignore_shopping', 'supermarket_category']]
|
||||
'params': [['name', 'description', 'recipe', 'ignore_shopping', 'supermarket_category']],
|
||||
'form': {
|
||||
'name': {
|
||||
'form_field': true,
|
||||
'type': 'text',
|
||||
'field': 'name',
|
||||
'label': i18n.t('Name'),
|
||||
'placeholder': ''
|
||||
},
|
||||
'description': {
|
||||
'form_field': true,
|
||||
'type': 'text',
|
||||
'field': 'description',
|
||||
'label': i18n.t('Description'),
|
||||
'placeholder': ''
|
||||
},
|
||||
'recipe': {
|
||||
'form_field': true,
|
||||
'type': 'lookup',
|
||||
'field': 'recipe',
|
||||
'list': 'RECIPE',
|
||||
'label': i18n.t('Recipe')
|
||||
},
|
||||
'shopping': {
|
||||
'form_field': true,
|
||||
'type': 'checkbox',
|
||||
'field': 'ignore_shopping',
|
||||
'label': i18n.t('Ignore_Shopping')
|
||||
},
|
||||
'shopping_category': {
|
||||
'form_field': true,
|
||||
'type': 'lookup',
|
||||
'field': 'supermarket_category',
|
||||
'list': 'SHOPPING_CATEGORY',
|
||||
'label': i18n.t('Shopping_Category')
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
static KEYWORD = {}
|
||||
static KEYWORD = {
|
||||
'name': i18n.t('Keyword'), // *OPTIONAL: parameters will be built model -> model_type -> default
|
||||
'apiName': 'Keyword',
|
||||
'model_type': this.TREE
|
||||
}
|
||||
static UNIT = {}
|
||||
static RECIPE = {}
|
||||
static SHOPPING_LIST = {}
|
||||
static RECIPE_BOOK = {
|
||||
'name': i18n.t('Recipe_Book'),
|
||||
'apiName': 'RecipeBook',
|
||||
}
|
||||
static SHOPPING_CATEGORY = {
|
||||
'name': i18n.t('Shopping_Category'),
|
||||
'apiName': 'SupermarketCategory',
|
||||
}
|
||||
|
||||
static RECIPE = {
|
||||
'name': 'Recipe',
|
||||
'name': i18n.t('Recipe'),
|
||||
'apiName': 'Recipe',
|
||||
'list': {
|
||||
'params': ['query', 'keywords', 'foods', 'books', 'keywordsOr', 'foodsOr', 'booksOr', 'internal', 'random', '_new', 'page', 'pageSize', 'options'],
|
||||
'config': {
|
||||
@@ -60,14 +129,69 @@ export class Models {
|
||||
|
||||
export class Actions {
|
||||
static CREATE = {
|
||||
"function": "create"
|
||||
"function": "create",
|
||||
'form': {
|
||||
'title': {
|
||||
'function': 'translate',
|
||||
'phrase': 'create_title',
|
||||
'params' : [
|
||||
{
|
||||
'token': 'type',
|
||||
'from': 'model',
|
||||
'attribute':'name'
|
||||
}
|
||||
],
|
||||
},
|
||||
'ok_label': i18n.t('Save'),
|
||||
}
|
||||
}
|
||||
static UPDATE = {
|
||||
"function": "partialUpdate",
|
||||
// special case for update only - updated assumes create form is sufficient, but a different title is required.
|
||||
"form_title": {
|
||||
'function': 'translate',
|
||||
'phrase': 'edit_title',
|
||||
'params' : [
|
||||
{
|
||||
'token': 'type',
|
||||
'from': 'model',
|
||||
'attribute':'name'
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
static DELETE = {
|
||||
"function": "destroy",
|
||||
'params': ['id']
|
||||
'params': ['id'],
|
||||
'form': {
|
||||
'title': {
|
||||
'function': 'translate',
|
||||
'phrase': 'delete_title',
|
||||
'params' : [
|
||||
{
|
||||
'token': 'type',
|
||||
'from': 'model',
|
||||
'attribute':'name'
|
||||
}
|
||||
],
|
||||
},
|
||||
'ok_label': i18n.t('Delete'),
|
||||
'instruction': {
|
||||
'form_field': true,
|
||||
'type': 'instruction',
|
||||
'label': {
|
||||
'function': 'translate',
|
||||
'phrase': "delete_confirmation",
|
||||
'params':[
|
||||
{
|
||||
'token': 'source',
|
||||
'from':'item1',
|
||||
'attribute': "name"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
static FETCH = {
|
||||
"function": "retrieve",
|
||||
@@ -78,26 +202,107 @@ export class Actions {
|
||||
"suffix": "s",
|
||||
"params": ['query', 'page', 'pageSize'],
|
||||
"config": {
|
||||
'query': {'default':undefined},
|
||||
'query': {'default': undefined},
|
||||
'page': {'default': 1},
|
||||
'pageSize': {'default': 25}
|
||||
}
|
||||
|
||||
}
|
||||
static MERGE = {
|
||||
"function": "merge",
|
||||
'params': ['source', 'target'],
|
||||
"config": {
|
||||
'source': {'type':'string'},
|
||||
'source': {'type': 'string'},
|
||||
'target': {'type': 'string'}
|
||||
},
|
||||
'form': {
|
||||
'title': {
|
||||
'function': 'translate',
|
||||
'phrase': 'merge_title',
|
||||
'params' : [
|
||||
{
|
||||
'token': 'type',
|
||||
'from': 'model',
|
||||
'attribute':'name'
|
||||
}
|
||||
],
|
||||
},
|
||||
'ok_label': i18n.t('Merge'),
|
||||
'instruction': {
|
||||
'form_field': true,
|
||||
'type': 'instruction',
|
||||
'label': {
|
||||
'function': 'translate',
|
||||
'phrase': "merge_selection",
|
||||
'params':[
|
||||
{
|
||||
'token': 'source',
|
||||
'from':'item1',
|
||||
'attribute': "name"
|
||||
},
|
||||
{
|
||||
'token': 'type',
|
||||
'from':'model',
|
||||
'attribute': "name"
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
'target': {
|
||||
'form_field': true,
|
||||
'type': 'lookup',
|
||||
'field': 'target',
|
||||
'list': 'self'
|
||||
}
|
||||
}
|
||||
}
|
||||
static MOVE = {
|
||||
"function": "move",
|
||||
'params': ['source', 'target'],
|
||||
"config": {
|
||||
'source': {'type':'string'},
|
||||
'source': {'type': 'string'},
|
||||
'target': {'type': 'string'}
|
||||
},
|
||||
'form': {
|
||||
'title': {
|
||||
'function': 'translate',
|
||||
'phrase': 'move_title',
|
||||
'params' : [
|
||||
{
|
||||
'token': 'type',
|
||||
'from': 'model',
|
||||
'attribute':'name'
|
||||
}
|
||||
],
|
||||
},
|
||||
'ok_label': i18n.t('Move'),
|
||||
'instruction': {
|
||||
'form_field': true,
|
||||
'type': 'instruction',
|
||||
'label': {
|
||||
'function': 'translate',
|
||||
'phrase': "move_selection",
|
||||
'params':[
|
||||
{
|
||||
'token': 'source',
|
||||
'from':'item1',
|
||||
'attribute': "name"
|
||||
},
|
||||
{
|
||||
'token': 'type',
|
||||
'from':'model',
|
||||
'attribute': "name"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
},
|
||||
'target': {
|
||||
'form_field': true,
|
||||
'type': 'lookup',
|
||||
'field': 'target',
|
||||
'list': 'self'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,50 +161,37 @@ import {ApiApiFactory} from "@/utils/openapi/api.ts"; // TODO: is it possible t
|
||||
import axios from "axios";
|
||||
axios.defaults.xsrfCookieName = 'csrftoken'
|
||||
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
|
||||
export const ApiMixin = {
|
||||
methods: {
|
||||
/**
|
||||
* constructs OpenAPI Generator function using named parameters
|
||||
* @param {string} model string to define which model API to use
|
||||
* @param {string} api string to define which of the API functions to use
|
||||
* @param {object} options dictionary to define all of the parameters necessary to use API
|
||||
*/
|
||||
genericAPI: function(model, action, options) {
|
||||
let setup = getConfig(model, action)
|
||||
let func = setup.function
|
||||
let config = setup?.config ?? {}
|
||||
let params = setup?.params ?? []
|
||||
console.log('config', config, 'params', params)
|
||||
let parameters = []
|
||||
export function genericAPI(model, action, options) {
|
||||
let setup = getConfig(model, action)
|
||||
let func = setup.function
|
||||
let config = setup?.config ?? {}
|
||||
let params = setup?.params ?? []
|
||||
let parameters = []
|
||||
|
||||
let this_value = undefined
|
||||
params.forEach(function (item, index) {
|
||||
if (Array.isArray(item)) {
|
||||
this_value = {}
|
||||
// if the value is an array, convert it to a dictionary of key:value
|
||||
// filtered based on OPTIONS passed
|
||||
// maybe map/reduce is better?
|
||||
for (const [k, v] of Object.entries(options)) {
|
||||
if (item.includes(k)) {
|
||||
this_value[k] = formatParam(config?.[k], v)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this_value = options?.[item] ?? undefined
|
||||
if (this_value) {this_value = formatParam(config?.[item], this_value)}
|
||||
let this_value = undefined
|
||||
params.forEach(function (item, index) {
|
||||
if (Array.isArray(item)) {
|
||||
this_value = {}
|
||||
// if the value is an array, convert it to a dictionary of key:value
|
||||
// filtered based on OPTIONS passed
|
||||
// maybe map/reduce is better?
|
||||
for (const [k, v] of Object.entries(options)) {
|
||||
if (item.includes(k)) {
|
||||
this_value[k] = formatParam(config?.[k], v)
|
||||
}
|
||||
// if no value is found so far, get the default if it exists
|
||||
if (!this_value) {
|
||||
this_value = getDefault(config?.[item], options)
|
||||
}
|
||||
parameters.push(this_value)
|
||||
});
|
||||
|
||||
console.log(func, 'parameters', parameters, 'passed options', options)
|
||||
let apiClient = new ApiApiFactory()
|
||||
return apiClient[func](...parameters)
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this_value = options?.[item] ?? undefined
|
||||
if (this_value) {this_value = formatParam(config?.[item], this_value)}
|
||||
}
|
||||
// if no value is found so far, get the default if it exists
|
||||
if (!this_value) {
|
||||
this_value = getDefault(config?.[item], options)
|
||||
}
|
||||
parameters.push(this_value)
|
||||
});
|
||||
let apiClient = new ApiApiFactory()
|
||||
return apiClient[func](...parameters)
|
||||
}
|
||||
|
||||
// /*
|
||||
@@ -258,7 +245,6 @@ function getDefault(config, options) {
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
function getConfig(model, action) {
|
||||
let f = action.function
|
||||
// if not defined partialUpdate will use params from create
|
||||
@@ -268,21 +254,84 @@ function getConfig(model, action) {
|
||||
|
||||
let config = {
|
||||
'name': model.name,
|
||||
'function': f + model.name + action?.suffix
|
||||
'apiName': model.apiName,
|
||||
}
|
||||
// spread operator merges dictionaries - last item in list takes precedence
|
||||
config = {...config, ...action, ...model.model_type?.[f], ...model?.[f]}
|
||||
// nested dictionaries are not merged - so merge again on any nested keys
|
||||
config.config = {...action?.config, ...model.model_type?.[f]?.config, ...model?.[f]?.config}
|
||||
config.function = config.function + config.name + (config?.suffix ?? '') // parens are required to force optional chaining to evaluate before concat
|
||||
config['function'] = f + config.apiName + (config?.suffix ?? '') // parens are required to force optional chaining to evaluate before concat
|
||||
return config
|
||||
}
|
||||
|
||||
// /*
|
||||
// * functions for Generic Modal Forms
|
||||
// * */
|
||||
export function getForm(model, action, item1, item2) {
|
||||
let f = action.function
|
||||
let config = {...action?.form, ...model.model_type?.[f]?.form, ...model?.[f]?.form}
|
||||
// if not defined partialUpdate will use form from create
|
||||
if (f === 'partialUpdate' && Object.keys(config).length == 0) {
|
||||
console.log('create form',Actions.CREATE?.form)
|
||||
config = {...Actions.CREATE?.form, ...model.model_type?.['create']?.form, ...model?.['create']?.form}
|
||||
config['title'] = {...action?.form_title, ...model.model_type?.[f]?.form_title, ...model?.[f]?.form_title}
|
||||
}
|
||||
let form = {'fields': []}
|
||||
let value = ''
|
||||
for (const [k, v] of Object.entries(config)) {
|
||||
if (v?.function){
|
||||
switch(v.function) {
|
||||
case 'translate':
|
||||
value = formTranslate(v, model, item1, item2)
|
||||
}
|
||||
} else {
|
||||
value = v
|
||||
}
|
||||
if (value?.form_field) {
|
||||
form.fields.push(
|
||||
{
|
||||
...value,
|
||||
...{
|
||||
'label': formTranslate(value?.label, model, item1, item2),
|
||||
'placeholder': formTranslate(value?.placeholder, model, item1, item2)
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
form[k] = value
|
||||
}
|
||||
}
|
||||
console.log('utils form', form)
|
||||
return form
|
||||
|
||||
}
|
||||
function formTranslate(translate, model, item1, item2) {
|
||||
if (typeof(translate) !== 'object') {return translate}
|
||||
let phrase = translate.phrase
|
||||
let options = {}
|
||||
let obj = undefined
|
||||
translate?.params.forEach(function (x, index) {
|
||||
switch(x.from){
|
||||
case 'item1':
|
||||
obj = item1
|
||||
break;
|
||||
case 'item2':
|
||||
obj = item2
|
||||
break;
|
||||
case 'model':
|
||||
obj = model
|
||||
}
|
||||
options[x.token] = obj[x.attribute]
|
||||
})
|
||||
return i18n.t(phrase, options)
|
||||
|
||||
}
|
||||
|
||||
// /*
|
||||
// * Utility functions to use manipulate nested components
|
||||
// * */
|
||||
import Vue from 'vue'
|
||||
import { Actions } from './models';
|
||||
export const CardMixin = {
|
||||
methods: {
|
||||
findCard: function(id, card_list){
|
||||
@@ -335,7 +384,6 @@ export const CardMixin = {
|
||||
}
|
||||
} else {
|
||||
idx = card_list.indexOf(card_list.find(x => x.id === target.id))
|
||||
console.log(card_list, idx, obj)
|
||||
Vue.set(card_list, idx, obj)
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user