first draft of a hirarchy editor

This commit is contained in:
vabene1111
2025-08-22 16:58:12 +02:00
parent 76c2e144fc
commit 6b1217ec35
48 changed files with 913 additions and 94 deletions

View File

@@ -592,7 +592,7 @@ class KeywordSerializer(UniqueFieldsMixin, ExtendedRecipeMixin):
fields = (
'id', 'name', 'label', 'description', 'image', 'parent', 'numchild', 'numrecipe', 'created_at',
'updated_at', 'full_name')
read_only_fields = ('id', 'label', 'numchild', 'parent', 'image')
read_only_fields = ('id', 'label', 'numchild', 'numrecipe', 'parent', 'image')
class UnitSerializer(UniqueFieldsMixin, ExtendedRecipeMixin, OpenDataModelMixin):

View File

@@ -411,6 +411,7 @@ class MergeMixin(ViewSetMixin):
description='Return first level children of {obj} with ID [int]. Integer 0 will return root {obj}s.',
type=int),
OpenApiParameter(name='tree', description='Return all self and children of {obj} with ID [int].', type=int),
OpenApiParameter(name='root_tree', description='Return all items belonging to the tree of the given {obj} id', type=int),
]),
move=extend_schema(parameters=[
OpenApiParameter(name="parent", description='The ID of the desired parent of the {obj}.', type=OpenApiTypes.INT,
@@ -423,6 +424,7 @@ class TreeMixin(MergeMixin, FuzzyFilterMixin):
def get_queryset(self):
root = self.request.query_params.get('root', None)
tree = self.request.query_params.get('tree', None)
root_tree = self.request.query_params.get('root_tree', None)
if root:
if root.isnumeric():
@@ -441,10 +443,23 @@ class TreeMixin(MergeMixin, FuzzyFilterMixin):
self.queryset = self.model.objects.get(id=int(tree)).get_descendants_and_self()
except self.model.DoesNotExist:
self.queryset = self.model.objects.none()
elif root_tree:
if root_tree.isnumeric():
try:
self.queryset = self.model.objects.get(id=int(root_tree)).get_root().get_descendants_and_self()
except self.model.DoesNotExist:
self.queryset = self.model.objects.none()
else:
return self.annotate_recipe(queryset=super().get_queryset(), request=self.request,
serializer=self.serializer_class, tree=True)
self.queryset = self.queryset.filter(space=self.request.space).order_by(Lower('name').asc())
self.queryset = self.queryset.filter(space=self.request.space)
# only order if not root_tree or tree mde because in these modes the sorting is relevant for the client
if not root_tree and not tree:
self.queryset = self.queryset.order_by(Lower('name').asc())
return self.annotate_recipe(queryset=self.queryset, request=self.request, serializer=self.serializer_class,
tree=True)

View File

@@ -15,8 +15,8 @@
<i class="fas fa-check text-success fa-fw" v-if="a.checked"></i>
<i class="fas fa-clock-rotate-left text-info fa-fw" v-if="a.delayed"></i> <b>
<span :class="{'text-disabled': a.checked || a.delayed}" class="text-no-wrap">
{{ $n(a.amount) }}
<span v-if="a.unit">{{ a.unit.name }}</span>
<span v-if="amounts.length > 1 || (amounts.length == 1 && a.amount != 1)">{{ $n(a.amount) }}</span>
<span class="ms-1" v-if="a.unit">{{ a.unit.name }}</span>
</span>
</b>

View File

@@ -123,6 +123,8 @@
</template>
</v-list>
<!-- TODO remove once append to body for model select is working properly -->
<v-spacer style="margin-top: 120px;"></v-spacer>
</v-col>
</v-row>
@@ -180,7 +182,8 @@
<template #append>
<v-btn icon="$create" color="create" :disabled="manualAddRecipe == undefined">
<v-icon icon="$create"></v-icon>
<add-to-shopping-dialog :recipe="manualAddRecipe" v-if="manualAddRecipe != undefined" @created="useShoppingStore().refreshFromAPI(); manualAddRecipe = undefined"></add-to-shopping-dialog>
<add-to-shopping-dialog :recipe="manualAddRecipe" v-if="manualAddRecipe != undefined"
@created="useShoppingStore().refreshFromAPI(); manualAddRecipe = undefined"></add-to-shopping-dialog>
</v-btn>
</template>
</ModelSelect>

View File

@@ -0,0 +1,178 @@
<template>
<v-row justify="space-between" dense>
<v-col cols="6">
<v-card :loading="loading" variant="outlined">
<v-card-text>
<v-treeview
v-model:activated="activeObjs"
return-object
activatable
rounded
indent-lines
hide-actions
density="compact"
open-all
item-title="name"
:items="objTree"
:disabled="loading">
<template v-slot:append="{ item, depth, isFirst, isLast }">
<v-icon icon="fa-solid fa-location-crosshairs" v-if="item.id == editingObj.id!"></v-icon>
</template>
</v-treeview>
</v-card-text>
</v-card>
</v-col>
<v-col cols="6">
<v-card v-if="activeObjs.length == 1" :title="activeObjs[0].name" :prepend-icon="genericModel.model.icon" variant="outlined">
<v-card-text>
<v-label>{{$t('AddChild')}}</v-label>
<model-select :model="genericModel.model.name" v-model="addChildObj" allow-create>
<template #append>
<v-btn color="save" icon="$save" :disabled="addChildObj == undefined" @click="moveObject(addChildObj,activeObjs[0].id!); addChildObj = undefined"></v-btn>
</template>
</model-select>
<v-label>{{$t('Parent')}}</v-label>
<model-select :model="genericModel.model.name" v-model="setParentObj" allow-create>
<template #append>
<v-btn color="save" icon="$save" :disabled="setParentObj == undefined" @click="moveObject(activeObjs[0], setParentObj.id!); setParentObj = undefined"></v-btn>
</template>
</model-select>
<v-btn @click="moveObject(activeObjs[0],0)" class="mt-2" color="warning" prepend-icon="fa-solid fa-link-slash" block>
{{$t('RemoveParent')}}
</v-btn>
<v-btn block prepend-icon="$edit" color="info" class="mt-4"
:to="{name: 'ModelEditPage' , params: {model: genericModel.model.name, id: activeObjs[0].id }}"
v-if="activeObjs[0].id != editingObj.id!">
{{ $t('Edit') }}
</v-btn>
<v-btn block prepend-icon="$search" color="success" class="mt-4 mb-10"
:to="searchLink" target="_blank"
v-if="searchLink">
{{ $t('Recipes') }}
</v-btn>
</v-card-text>
</v-card>
</v-col>
</v-row>
</template>
<script setup lang="ts">
import {computed, onMounted, PropType, ref} from "vue";
import ModelSelect from "@/components/inputs/ModelSelect.vue";
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore.ts";
import {EditorSupportedModels, EditorSupportedTypes, getGenericModelFromString} from "@/types/Models.ts";
import {useI18n} from "vue-i18n";
type Tree<T> = T & { children: Tree<T>[] };
const props = defineProps({
model: {type: String as PropType<EditorSupportedModels>, required: true},
})
const editingObj = defineModel<EditorSupportedTypes>({required: true})
const t = useI18n()
/**
* compute tree structure based on object list
*/
const objTree = computed(() => {
return buildTreeDFS(objList.value)
})
/**
* link to search for recipes using the selected object
*/
const searchLink = computed(() => {
if (activeObjs.value.length == 1) {
if (props.model == 'Keyword') {
return {name: 'SearchPage', query: {keywords: activeObjs.value[0].id!}}
} else if (props.model == 'Food') {
return {name: 'SearchPage', query: {keywords: activeObjs.value[0].id!}}
}
}
return undefined
})
const loading = ref(false)
const objList = ref([] as EditorSupportedTypes[])
const activeObjs = ref([] as EditorSupportedTypes[])
const addChildObj = ref<undefined | EditorSupportedTypes>(undefined)
const setParentObj = ref<undefined | EditorSupportedTypes>(undefined)
const genericModel = ref(getGenericModelFromString(props.model, t))
onMounted(() => {
recLoadObjectTree(editingObj.value.id!, 1)
})
/**
* recursively load all objects belonging to the selected objects tree
* @param objId base object id to look for tree
* @param page page to load
*/
function recLoadObjectTree(objId: number, page: number) {
loading.value = true
genericModel.value.list({rootTree: objId, pageSize: 100}).then(response => {
objList.value = objList.value.concat(response.results)
if (response.next) {
recLoadObjectTree(objId, page + 1)
} else {
if (activeObjs.value.length == 0) {
activeObjs.value = [objTree.value.find(x => x.id! == editingObj.value.id!)]
}
loading.value = false
}
})
}
/**
* move the given obj to the desired parent and update in local data cache
* @param obj object to change parent for
* @param parentId parent id to change the object to or 0 to remove parent
*/
function moveObject(obj: EditorSupportedTypes, parentId: number) {
loading.value = true
genericModel.value.move(obj, parentId).then((r: any) => {
objList.value = []
recLoadObjectTree(editingObj.value.id!, 1)
}).catch((err: any) => {
loading.value = false
useMessageStore().addError(ErrorMessageType.UPDATE_ERROR, err)
})
}
/**
* Recursively build a tree datastructures from a DFS ordered list of objects
*/
function buildTreeDFS<T extends { id: number; parent: number | null }>(items: T[]): Tree<T>[] {
let index = 0;
function buildSubtree(parentId: number | null): Tree<T>[] {
const children: Tree<T>[] = [];
while (index < items.length && items[index].parent === parentId) {
const item = items[index++];
const node: Tree<T> = {
...item,
children: buildSubtree(item.id) // recurse immediately, consumes children in DFS order
};
children.push(node);
}
return children;
}
return buildSubtree(null); // start from roots (parent === null)
}
</script>
<style scoped>
</style>

View File

@@ -15,6 +15,7 @@
<v-tab value="food">{{ $t('Food') }}</v-tab>
<v-tab value="properties">{{ $t('Properties') }}</v-tab>
<v-tab value="conversions">{{ $t('Conversion') }}</v-tab>
<v-tab value="hierarchy">{{ $t('Hierarchy') }}</v-tab>
<v-tab value="misc">{{ $t('Miscellaneous') }}</v-tab>
</v-tabs>
</v-card-text>
@@ -83,7 +84,7 @@
</v-col>
<v-col md="6">
<!-- TODO fix card overflow invisible, overflow-visible class is not working -->
<model-select v-model="uc.baseUnit" model="Unit" hide-details></model-select>
<model-select v-model="uc.baseUnit" model="Unit" hide-details></model-select>
</v-col>
</v-row>
<v-row dense>
@@ -97,7 +98,7 @@
</v-col>
<v-col md="6">
<!-- TODO fix card overflow invisible, overflow-visible class is not working -->
<model-select v-model="uc.convertedUnit" model="Unit"></model-select>
<model-select v-model="uc.convertedUnit" model="Unit"></model-select>
</v-col>
</v-row>
</v-card-text>
@@ -105,7 +106,19 @@
</v-card>
</v-form>
<!-- TODO remove once append to body for model select is working properly -->
<v-spacer style="margin-top: 60px;"></v-spacer>
<v-spacer style="margin-top: 60px;"></v-spacer>
</v-tabs-window-item>
<v-tabs-window-item value="hierarchy">
<hierarchy-editor v-model="editingObj" :model="modelClass.model.name"></hierarchy-editor>
<v-checkbox :label="$t('substitute_siblings')" :hint="$t('substitute_siblings_help')" v-model="editingObj.substituteSiblings" persistent-hint></v-checkbox>
<v-checkbox :label="$t('substitute_children')" :hint="$t('substitute_children_help')" v-model="editingObj.substituteChildren" persistent-hint></v-checkbox>
<ModelSelect model="FoodInheritField" v-model="editingObj.inheritFields" :label="$t('InheritFields')" :hint="$t('InheritFields_help')"
mode="tags"></ModelSelect>
<ModelSelect model="FoodInheritField" v-model="editingObj.childInheritFields" :label="$t('ChildInheritFields')" :hint="$t('ChildInheritFields_help')"
mode="tags"></ModelSelect>
</v-tabs-window-item>
<v-tabs-window-item value="misc">
@@ -117,14 +130,6 @@
<v-divider class="mt-2 mb-2"></v-divider>
<ModelSelect model="Food" v-model="editingObj.substitute" :label="$t('Substitutes')" :hint="$t('substitute_help')" mode="tags"></ModelSelect>
<v-checkbox :label="$t('substitute_siblings')" :hint="$t('substitute_siblings_help')" v-model="editingObj.substituteSiblings" persistent-hint></v-checkbox>
<v-checkbox :label="$t('substitute_children')" :hint="$t('substitute_children_help')" v-model="editingObj.substituteChildren" persistent-hint></v-checkbox>
<ModelSelect model="FoodInheritField" v-model="editingObj.inheritFields" :label="$t('InheritFields')" :hint="$t('InheritFields_help')"
mode="tags"></ModelSelect>
<ModelSelect model="FoodInheritField" v-model="editingObj.childInheritFields" :label="$t('ChildInheritFields')" :hint="$t('ChildInheritFields_help')"
mode="tags"></ModelSelect>
<!-- TODO re-add reset inheritance button/api call /function (previously annotated field on food -->
<v-text-field :label="$t('Open_Data_Slug')" :hint="$t('open_data_help_text')" persistent-hint v-model="editingObj.openDataSlug" disabled></v-text-field>
</v-form>
@@ -154,6 +159,7 @@ import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
import FdcSearchDialog from "@/components/dialogs/FdcSearchDialog.vue";
import {openFdcPage} from "@/utils/fdc.ts";
import {DateTime} from "luxon";
import HierarchyEditor from "@/components/inputs/HierarchyEditor.vue";
const props = defineProps({
@@ -212,7 +218,7 @@ onMounted(() => {
/**
* component specific state setup logic
*/
function initializeEditor(){
function initializeEditor() {
setupState(props.item, props.itemId, {
newItemFunction: () => {
editingObj.value.propertiesFoodAmount = 100

View File

@@ -9,13 +9,29 @@
:is-changed="editingObjChanged"
:model-class="modelClass"
:object-name="editingObjName()">
<v-card-text class="pa-0">
<v-tabs v-model="tab" :disabled="loading" grow>
<v-tab value="keyword">{{ $t('Keyword') }}</v-tab>
<v-tab value="hierarchy">{{ $t('Hierarchy') }}</v-tab>
</v-tabs>
</v-card-text>
<v-card-text>
<v-form :disabled="loading">
<v-tabs-window v-model="tab">
<v-tabs-window-item value="keyword">
<v-form :disabled="loading">
<v-text-field :label="$t('Name')" v-model="editingObj.name"></v-text-field>
<v-textarea :label="$t('Description')" v-model="editingObj.description"></v-textarea>
<v-text-field :label="$t('Name')" v-model="editingObj.name"></v-text-field>
<v-textarea :label="$t('Description')" v-model="editingObj.description"></v-textarea>
</v-form>
</v-form>
</v-tabs-window-item>
<v-tabs-window-item value="hierarchy">
<hierarchy-editor v-model="editingObj" :model="modelClass.model.name"></hierarchy-editor>
</v-tabs-window-item>
</v-tabs-window>
</v-card-text>
</model-editor-base>
@@ -23,10 +39,11 @@
<script setup lang="ts">
import {onMounted, PropType, watch} from "vue";
import {onMounted, PropType, ref, watch} from "vue";
import {Keyword} from "@/openapi";
import ModelEditorBase from "@/components/model_editors/ModelEditorBase.vue";
import {useModelEditorFunctions} from "@/composables/useModelEditorFunctions";
import HierarchyEditor from "@/components/inputs/HierarchyEditor.vue";
const props = defineProps({
item: {type: {} as PropType<Keyword>, required: false, default: null},
@@ -38,6 +55,8 @@ const props = defineProps({
const emit = defineEmits(['create', 'save', 'delete', 'close', 'changedState'])
const {setupState, deleteObject, saveObject, isUpdate, editingObjName, loading, editingObj, editingObjChanged, modelClass} = useModelEditorFunctions<Keyword>('Keyword', emit)
const tab = ref("keyword")
/**
* watch prop changes and re-initialize editor
* required to embed editor directly into pages and be able to change item from the outside
@@ -55,7 +74,7 @@ onMounted(() => {
/**
* component specific state setup logic
*/
function initializeEditor(){
function initializeEditor() {
setupState(props.item, props.itemId, {itemDefaults: props.itemDefaults})
}

View File

@@ -2,6 +2,7 @@
"API_Browser": "",
"API_Documentation": "",
"Add": "",
"AddChild": "",
"AddFoodToShopping": "",
"AddToShopping": "",
"Add_Servings_to_Shopping": "",
@@ -111,6 +112,7 @@
"Hide_Keywords": "",
"Hide_Recipes": "",
"Hide_as_header": "",
"Hierarchy": "",
"Icon": "",
"IgnoreAccents": "",
"IgnoreAccentsHelp": "",
@@ -234,6 +236,7 @@
"Recipes_per_page": "",
"RemoveAllType": "",
"RemoveFoodFromShopping": "",
"RemoveParent": "",
"Remove_nutrition_recipe": "",
"Reset": "",
"Reset_Search": "",

View File

@@ -2,6 +2,7 @@
"API_Browser": "",
"API_Documentation": "",
"Add": "Добави",
"AddChild": "",
"AddFoodToShopping": "Добавете {food} към списъка си за пазаруване",
"AddToShopping": "Добавяне към списъка за пазаруване",
"Add_Servings_to_Shopping": "Добавете {servings} порции към Пазаруване",
@@ -108,6 +109,7 @@
"Hide_Keywords": "Скриване на ключова дума",
"Hide_Recipes": "Скриване на рецепти",
"Hide_as_header": "Скриване като заглавка",
"Hierarchy": "",
"Icon": "Икона",
"IgnoreAccents": "",
"IgnoreAccentsHelp": "",
@@ -227,6 +229,7 @@
"Recipes_per_page": "Рецепти на страница",
"RemoveAllType": "",
"RemoveFoodFromShopping": "Премахнете {food} от списъка си за пазаруване",
"RemoveParent": "",
"Remove_nutrition_recipe": "Изтрийте хранителните стойности от рецептата",
"Reset": "Нулиране",
"Reset_Search": "Нулиране на търсенето",

View File

@@ -4,6 +4,7 @@
"API_Documentation": "",
"Account": "Compte",
"Add": "Afegir",
"AddChild": "",
"AddFoodToShopping": "Afegeix {food} a la llista de la compra",
"AddToShopping": "Afegir a la llista de la compra",
"Add_Servings_to_Shopping": "Afegir {servings} racions a la compra",
@@ -152,6 +153,7 @@
"Hide_Keywords": "Amagueu paraula clau",
"Hide_Recipes": "Amagueu receptes",
"Hide_as_header": "Amagueu com a títol",
"Hierarchy": "",
"Hour": "Hora",
"Hours": "Hores",
"Icon": "Icona",
@@ -304,6 +306,7 @@
"Recipes_per_page": "Receptes per pàgina",
"RemoveAllType": "",
"RemoveFoodFromShopping": "Elimina {food} de la llista de la compra",
"RemoveParent": "",
"Remove_nutrition_recipe": "Esborreu nutrició de la recepta",
"Reset": "Restablir",
"Reset_Search": "Reinicieu la cerca",

View File

@@ -4,6 +4,7 @@
"API_Documentation": "",
"Account": "Účet",
"Add": "Přidat",
"AddChild": "",
"AddFoodToShopping": "Přidat {food} na váš nákupní seznam",
"AddToShopping": "Přidat do nákupního seznamu",
"Add_Servings_to_Shopping": "Přidat {servings} porce na nákupní seznam",
@@ -151,6 +152,7 @@
"Hide_Keywords": "Skrýt štítek",
"Hide_Recipes": "Skrýt recept",
"Hide_as_header": "Skryj jako nadpis",
"Hierarchy": "",
"Hour": "Hodina",
"Hours": "Hodiny",
"Icon": "Ikona",
@@ -301,6 +303,7 @@
"Recipes_per_page": "Receptů na stránku",
"RemoveAllType": "",
"RemoveFoodFromShopping": "Odstranit {food} z nákupního seznamu",
"RemoveParent": "",
"Remove_nutrition_recipe": "Smazat nutriční hodnoty",
"Reset": "Resetovat",
"Reset_Search": "Zrušit filtry vyhledávání",

View File

@@ -4,6 +4,7 @@
"API_Documentation": "",
"Account": "Bruger",
"Add": "Tilføj",
"AddChild": "",
"AddFoodToShopping": "Tilføj {food} til indkøbsliste",
"AddToShopping": "Tilføj til indkøbsliste",
"Add_Servings_to_Shopping": "Tilføj {servings} serveringer til indkøb",
@@ -152,6 +153,7 @@
"Hide_Keywords": "Skjul nøgleord",
"Hide_Recipes": "Skjul opskrifter",
"Hide_as_header": "Skjul som rubrik",
"Hierarchy": "",
"Hour": "Time",
"Hours": "Timer",
"Icon": "Ikon",
@@ -304,6 +306,7 @@
"Recipes_per_page": "Opskrifter pr. side",
"RemoveAllType": "",
"RemoveFoodFromShopping": "Fjern {food} fra indkøbsliste",
"RemoveParent": "",
"Remove_nutrition_recipe": "Fjern næringsindhold fra opskrift",
"Reset": "Nulstil",
"Reset_Search": "Nulstil søgning",

View File

@@ -11,6 +11,7 @@
"Activity": "Aktivität",
"Add": "Hinzufügen",
"AddAll": "Alle Hinzufügen",
"AddChild": "Kind hinzufügen",
"AddFilter": "Filter Hinzufügen",
"AddFoodToShopping": "Fügen Sie {food} zur Einkaufsliste hinzu",
"AddMany": "Mehrere Hinzufügen",
@@ -218,6 +219,7 @@
"Hide_Keywords": "Schlagwort verstecken",
"Hide_Recipes": "Rezepte verstecken",
"Hide_as_header": "Keine Überschrift",
"Hierarchy": "Hierarchie",
"History": "Verlauf",
"HostedFreeVersion": "Sie verwenden die kostenlose Testversion von Tandoor",
"Hour": "Stunde",
@@ -421,6 +423,7 @@
"Remove": "Entfernen",
"RemoveAllType": "Alle {type} entfernen",
"RemoveFoodFromShopping": "{food} von der Einkaufsliste löschen",
"RemoveParent": "Eltern entfernen",
"Remove_nutrition_recipe": "Nährwerte aus Rezept löschen",
"Reset": "Zurücksetzen",
"ResetHelp": "Hilfe Zurücksetzen",

View File

@@ -4,6 +4,7 @@
"API_Documentation": "",
"Account": "Λογαριασμός",
"Add": "Προσθήκη",
"AddChild": "",
"AddFoodToShopping": "Προσθήκη του φαγητού {food} στη λίστα αγορών σας",
"AddToShopping": "Προσθήκη στη λίστα αγορών",
"Add_Servings_to_Shopping": "Προσθήκη {servings} μερίδων στις αγορές",
@@ -152,6 +153,7 @@
"Hide_Keywords": "Απόκρυψη λέξης-κλειδί",
"Hide_Recipes": "Απόκρυψη συνταγών",
"Hide_as_header": "Απόκρυψη ως κεφαλίδα",
"Hierarchy": "",
"Hour": "Ώρα",
"Hours": "Ώρες",
"Icon": "Εικονίδιο",
@@ -304,6 +306,7 @@
"Recipes_per_page": "Συνταγές ανά σελίδα",
"RemoveAllType": "",
"RemoveFoodFromShopping": "Αφαίρεση του φαγητού {food} από τη λίστα αγορών σας",
"RemoveParent": "",
"Remove_nutrition_recipe": "Αφαίρεση διατροφικής αξίας από τη συνταγή",
"Reset": "Επαναφορά",
"Reset_Search": "Επαναφορά αναζήτησης",

View File

@@ -11,6 +11,7 @@
"Activity": "Activity",
"Add": "Add",
"AddAll": "Add all",
"AddChild": "Add child",
"AddFilter": "Add Filter",
"AddFoodToShopping": "Add {food} to your shopping list",
"AddMany": "Add Many",
@@ -216,6 +217,7 @@
"Hide_Keywords": "Hide Keyword",
"Hide_Recipes": "Hide Recipes",
"Hide_as_header": "Hide as header",
"Hierarchy": "Hierarchy",
"History": "History",
"HostedFreeVersion": "You are using the free version of Tandoor",
"Hour": "Hour",
@@ -419,6 +421,7 @@
"Remove": "Remove",
"RemoveAllType": "Remove all {type}",
"RemoveFoodFromShopping": "Remove {food} from your shopping list",
"RemoveParent": "Remove parent",
"Remove_nutrition_recipe": "Delete nutrition from recipe",
"Reset": "Reset",
"ResetHelp": "Reset Help",

View File

@@ -11,6 +11,7 @@
"Activity": "Actividad",
"Add": "Añadir",
"AddAll": "Agregar todo",
"AddChild": "",
"AddFilter": "Agregar filtro",
"AddFoodToShopping": "Añadir {food} a la lista de compras",
"AddMany": "Agregar muchos",
@@ -210,6 +211,7 @@
"Hide_Keywords": "Esconder palabra clave",
"Hide_Recipes": "Esconder recetas",
"Hide_as_header": "Esconder como titulo",
"Hierarchy": "",
"History": "Historial",
"HostedFreeVersion": "Estás usando la versión gratuita de Tandoor",
"Hour": "Hora",
@@ -408,6 +410,7 @@
"Remove": "Remover",
"RemoveAllType": "",
"RemoveFoodFromShopping": "Eliminar {food} de la lista de la compra",
"RemoveParent": "",
"Remove_nutrition_recipe": "Borrar nutrición de la canasta",
"Reset": "Restablecer",
"ResetHelp": "Reiniciar ayuda",

View File

@@ -4,6 +4,7 @@
"API_Documentation": "",
"Account": "Tili",
"Add": "Lisää",
"AddChild": "",
"AddFoodToShopping": "Lisää {food} ostoslistaan",
"AddToShopping": "Lisää ostoslistalle",
"Add_Servings_to_Shopping": "Lisää {servings} Annoksia Ostoksiin",
@@ -149,6 +150,7 @@
"Hide_Keywords": "Piilota Avainsana",
"Hide_Recipes": "Piilota Reseptit",
"Hide_as_header": "Piilota otsikko",
"Hierarchy": "",
"Hour": "Tunti",
"Hours": "Tuntia",
"Icon": "Kuvake",
@@ -293,6 +295,7 @@
"Recipes_per_page": "Reseptejä sivulla",
"RemoveAllType": "",
"RemoveFoodFromShopping": "Poista {food} ostoslistalta",
"RemoveParent": "",
"Remove_nutrition_recipe": "Poista ravintoaine reseptistä",
"Reset": "Nollaa",
"Reset_Search": "Nollaa haku",

View File

@@ -11,6 +11,7 @@
"Activity": "Activité",
"Add": "Ajouter",
"AddAll": "Tout ajouter",
"AddChild": "",
"AddFilter": "Ajouter un filtre",
"AddFoodToShopping": "Ajouter laliment {food} à votre liste de courses",
"AddMany": "Ajouter plusieurs",
@@ -217,6 +218,7 @@
"Hide_Keywords": "Cacher le mot-clé",
"Hide_Recipes": "Cacher les recettes",
"Hide_as_header": "Cacher comme en-tête",
"Hierarchy": "",
"History": "Historique",
"HostedFreeVersion": "Vous utilisez la version gratuite de Tandoor",
"Hour": "Heure",
@@ -418,6 +420,7 @@
"Remove": "Enlever",
"RemoveAllType": "",
"RemoveFoodFromShopping": "Supprimer laliment {food} de votre liste de courses",
"RemoveParent": "",
"Remove_nutrition_recipe": "Supprimer les valeurs nutritionelles de la recette",
"Reset": "Réinitialiser",
"ResetHelp": "Aide à la réinitialisation",

View File

@@ -4,6 +4,7 @@
"API_Documentation": "",
"Account": "חשבון",
"Add": "הוספה",
"AddChild": "",
"AddFoodToShopping": "הוסף {מזון} לרשימת הקניות",
"AddToShopping": "הוסף לרשימת קניות",
"Add_Servings_to_Shopping": "הוסף{מנה}מנות לקנייה",
@@ -152,6 +153,7 @@
"Hide_Keywords": "הסתרת מילת מפתח",
"Hide_Recipes": "הסתרת מתכונים",
"Hide_as_header": "הסתר בתור כותרת",
"Hierarchy": "",
"Hour": "שעה",
"Hours": "שעות",
"Icon": "צלמית",
@@ -304,6 +306,7 @@
"Recipes_per_page": "מתכונים בכל דף",
"RemoveAllType": "",
"RemoveFoodFromShopping": "הסר {מזון} מרשימת הקניות",
"RemoveParent": "",
"Remove_nutrition_recipe": "מחר ערכים תזונתיים מהמתכון",
"Reset": "אפס",
"Reset_Search": "אפס חיפוש",

View File

@@ -4,6 +4,7 @@
"API_Documentation": "",
"Account": "Korisnički račun",
"Add": "Dodaj",
"AddChild": "",
"AddFoodToShopping": "Dodaj {food} na svoj popis za kupovinu",
"AddToShopping": "Dodaj na popis za kupovinu",
"Add_Servings_to_Shopping": "Dodaj {servings} obroka u Kupovinu",
@@ -152,6 +153,7 @@
"Hide_Keywords": "Sakrij ključnu riječ",
"Hide_Recipes": "Sakrij Recepte",
"Hide_as_header": "Sakrij kao zaglavlje",
"Hierarchy": "",
"Hour": "Sat",
"Hours": "Sati",
"Icon": "Ikona",
@@ -304,6 +306,7 @@
"Recipes_per_page": "Recepata po stranici",
"RemoveAllType": "",
"RemoveFoodFromShopping": "Ukloni {food} sa svog popisa za kupovinu",
"RemoveParent": "",
"Remove_nutrition_recipe": "Izbrišite hranjive sastojke iz recepta",
"Reset": "Ponovo postavi",
"Reset_Search": "Poništi pretragu",

View File

@@ -4,6 +4,7 @@
"API_Documentation": "",
"Account": "Fiók",
"Add": "Hozzáadás",
"AddChild": "",
"AddFoodToShopping": "{food} hozzáadása bevásárlólistához",
"AddToShopping": "Hozzáadás a bevásárlólistához",
"Add_Servings_to_Shopping": "",
@@ -135,6 +136,7 @@
"Hide_Keywords": "Kulcsszó elrejtése",
"Hide_Recipes": "Receptek elrejtése",
"Hide_as_header": "Fejlécként elrejtve",
"Hierarchy": "",
"Hour": "Óra",
"Hours": "Óra",
"Icon": "Ikon",
@@ -277,6 +279,7 @@
"Recipes_per_page": "Receptek oldalanként",
"RemoveAllType": "",
"RemoveFoodFromShopping": "{food} eltávolítása bevásárlólistáról",
"RemoveParent": "",
"Remove_nutrition_recipe": "Tápértékadatok törlése a receptből",
"Reset": "Visszaállítás",
"Reset_Search": "Keresés alaphelyzetbe állítása",

View File

@@ -2,6 +2,7 @@
"API_Browser": "",
"API_Documentation": "",
"Add": "",
"AddChild": "",
"Add_nutrition_recipe": "Ավելացնել սննդայնություն բաղադրատոմսին",
"Add_to_Book": "",
"Add_to_Plan": "Ավելացնել պլանին",
@@ -58,6 +59,7 @@
"Hide_Keywords": "Թաքցնել բանալի բառը",
"Hide_Recipes": "Թաքցնել բաղադրատոմսերը",
"Hide_as_header": "Թաքցնել որպես խորագիր",
"Hierarchy": "",
"IgnoreAccents": "",
"IgnoreAccentsHelp": "",
"Import": "Ներմուծել",
@@ -107,6 +109,7 @@
"Recipes": "Բաղադրատոմսեր",
"Recipes_per_page": "Բաղադրատոմս էջում",
"RemoveAllType": "",
"RemoveParent": "",
"Remove_nutrition_recipe": "Հեռացնել բաղադրատոմսի սննդայնությունը",
"Reset_Search": "Զրոյացնել որոնումը",
"Save": "",

View File

@@ -4,6 +4,7 @@
"API_Documentation": "",
"Account": "",
"Add": "Tambahkan",
"AddChild": "",
"AddFoodToShopping": "",
"AddToShopping": "",
"Add_Servings_to_Shopping": "",
@@ -123,6 +124,7 @@
"Hide_Keywords": "Sembunyikan Kata Kunci",
"Hide_Recipes": "Sembunyikan Resep",
"Hide_as_header": "Sembunyikan sebagai tajuk",
"Hierarchy": "",
"Hour": "",
"Hours": "",
"Icon": "",
@@ -253,6 +255,7 @@
"Recipes_per_page": "Resep per Halaman",
"RemoveAllType": "",
"RemoveFoodFromShopping": "",
"RemoveParent": "",
"Remove_nutrition_recipe": "Hapus nutrisi dari resep",
"Reset": "",
"Reset_Search": "Setel Ulang Pencarian",

View File

@@ -4,6 +4,7 @@
"API_Documentation": "",
"Account": "",
"Add": "",
"AddChild": "",
"AddFoodToShopping": "",
"AddToShopping": "",
"Add_Servings_to_Shopping": "",
@@ -151,6 +152,7 @@
"Hide_Keywords": "",
"Hide_Recipes": "",
"Hide_as_header": "",
"Hierarchy": "",
"Hour": "",
"Hours": "",
"Icon": "",
@@ -303,6 +305,7 @@
"Recipes_per_page": "",
"RemoveAllType": "",
"RemoveFoodFromShopping": "",
"RemoveParent": "",
"Remove_nutrition_recipe": "",
"Reset": "",
"Reset_Search": "",

View File

@@ -11,6 +11,7 @@
"Activity": "Attività",
"Add": "Aggiungi",
"AddAll": "Aggiungi tutto",
"AddChild": "",
"AddFilter": "Aggiungi filtro",
"AddFoodToShopping": "Aggiungi {food} alla tua lista della spesa",
"AddMany": "Aggiungi molti",
@@ -217,6 +218,7 @@
"Hide_Keywords": "Nascondi parola chiave",
"Hide_Recipes": "Nascondi ricette",
"Hide_as_header": "Nascondi come intestazione",
"Hierarchy": "",
"History": "Cronologia",
"HostedFreeVersion": "Stai utilizzando la versione gratuita di Tandoor",
"Hour": "Ora",
@@ -420,6 +422,7 @@
"Remove": "Rimuovi",
"RemoveAllType": "",
"RemoveFoodFromShopping": "Rimuovi {food} dalla tua lista della spesa",
"RemoveParent": "",
"Remove_nutrition_recipe": "Elimina nutrienti dalla ricetta",
"Reset": "Azzera",
"ResetHelp": "Ripristina aiuto",

View File

@@ -4,6 +4,7 @@
"API_Documentation": "",
"Account": "",
"Add": "",
"AddChild": "",
"AddFoodToShopping": "",
"AddToShopping": "",
"Add_Servings_to_Shopping": "",
@@ -137,6 +138,7 @@
"Hide_Keywords": "Paslėpti raktažodį",
"Hide_Recipes": "Paslėpti receptus",
"Hide_as_header": "Slėpti kaip antraštę",
"Hierarchy": "",
"Hour": "",
"Hours": "",
"Icon": "",
@@ -281,6 +283,7 @@
"Recipes_per_page": "Receptų skaičius per puslapį",
"RemoveAllType": "",
"RemoveFoodFromShopping": "",
"RemoveParent": "",
"Remove_nutrition_recipe": "Ištrinti mitybos informaciją iš recepto",
"Reset": "",
"Reset_Search": "Iš naujo nustatyti paiešką",

View File

@@ -4,6 +4,7 @@
"API_Documentation": "",
"Account": "",
"Add": "",
"AddChild": "",
"AddFoodToShopping": "",
"AddToShopping": "",
"Add_Servings_to_Shopping": "",
@@ -152,6 +153,7 @@
"Hide_Keywords": "",
"Hide_Recipes": "",
"Hide_as_header": "",
"Hierarchy": "",
"Hour": "",
"Hours": "",
"Icon": "",
@@ -304,6 +306,7 @@
"Recipes_per_page": "",
"RemoveAllType": "",
"RemoveFoodFromShopping": "",
"RemoveParent": "",
"Remove_nutrition_recipe": "",
"Reset": "",
"Reset_Search": "",

View File

@@ -4,6 +4,7 @@
"API_Documentation": "",
"Account": "",
"Add": "Legg til",
"AddChild": "",
"AddFoodToShopping": "Legg til {food] i handlelisten din",
"AddToShopping": "Legg til i handleliste",
"Add_Servings_to_Shopping": "Legg til {servings} serveringer i handlelisten",
@@ -143,6 +144,7 @@
"Hide_Keywords": "Skjul nøkkelord",
"Hide_Recipes": "Skjul oppskrifter",
"Hide_as_header": "Skjul overskrift",
"Hierarchy": "",
"Hour": "Time",
"Hours": "Timer",
"Icon": "Ikon",
@@ -288,6 +290,7 @@
"Recipes_per_page": "Oppskrifter per side",
"RemoveAllType": "",
"RemoveFoodFromShopping": "Fjern {food} fra handelisten din",
"RemoveParent": "",
"Remove_nutrition_recipe": "Fjern næringsinnhold fra oppskrift",
"Reset": "",
"Reset_Search": "Nullstill søk",

View File

@@ -11,6 +11,7 @@
"Activity": "Activiteit",
"Add": "Voeg toe",
"AddAll": "Voeg alles toe",
"AddChild": "",
"AddFilter": "Voeg filter toe",
"AddFoodToShopping": "Voeg {food} toe aan je boodschappenlijst",
"AddMany": "Voeg meerdere toe",
@@ -218,6 +219,7 @@
"Hide_Keywords": "Verberg trefwoord",
"Hide_Recipes": "Verberg recepten",
"Hide_as_header": "Verberg als koptekst",
"Hierarchy": "",
"History": "Geschiedenis",
"HostedFreeVersion": "Je gebruikt de gratis versie van Tandoor",
"Hour": "Uur",
@@ -421,6 +423,7 @@
"Remove": "Verwijder",
"RemoveAllType": "",
"RemoveFoodFromShopping": "Verwijder {food} van je boodschappenlijst",
"RemoveParent": "",
"Remove_nutrition_recipe": "Verwijder voedingswaarde van recept",
"Reset": "Herstel",
"ResetHelp": "Hulp herstellen",

View File

@@ -9,6 +9,7 @@
"Activity": "Aktywność",
"Add": "Dodaj",
"AddAll": "Dodaj wszystkie",
"AddChild": "",
"AddFilter": "Dodaj filtr",
"AddFoodToShopping": "Dodaj {food} do swojej listy zakupów",
"AddMany": "Dodaj wiele",
@@ -178,6 +179,7 @@
"Hide_Keywords": "Ukryj słowo kluczowe",
"Hide_Recipes": "Ukryj przepisy",
"Hide_as_header": "Ukryj jako nagłówek",
"Hierarchy": "",
"Hour": "Godzina",
"Hours": "Godziny",
"Icon": "Ikona",
@@ -330,6 +332,7 @@
"Recipes_per_page": "Przepisy na stronę",
"RemoveAllType": "",
"RemoveFoodFromShopping": "Usuń {food} z listy zakupów",
"RemoveParent": "",
"Remove_nutrition_recipe": "Usuń wartości odżywcze z przepisu",
"Reset": "Resetowanie",
"Reset_Search": "Resetuj wyszukiwanie",

View File

@@ -2,6 +2,7 @@
"API_Browser": "",
"API_Documentation": "",
"Add": "Adicionar",
"AddChild": "",
"AddFoodToShopping": "Adicionar {food} à sua lista de compras",
"AddToShopping": "Adicionar á lista de compras",
"Add_Servings_to_Shopping": "Adicionar {servings} doses ás compras",
@@ -123,6 +124,7 @@
"Hide_Keywords": "Esconder palavra-chave",
"Hide_Recipes": "Esconder Receitas",
"Hide_as_header": "Esconder como cabeçalho",
"Hierarchy": "",
"Icon": "Ícone",
"IgnoreAccents": "",
"IgnoreAccentsHelp": "",
@@ -246,6 +248,7 @@
"Recipes_per_page": "Receitas por página",
"RemoveAllType": "",
"RemoveFoodFromShopping": "Remover {food} da sua lista de compras",
"RemoveParent": "",
"Remove_nutrition_recipe": "Remover valor nutricional da receita",
"Reset": "Reiniciar",
"Reset_Search": "Repor Pesquisa",

View File

@@ -11,6 +11,7 @@
"Activity": "Atividade",
"Add": "Adicionar",
"AddAll": "Adicionar todos",
"AddChild": "",
"AddFilter": "Adicionar Filtro",
"AddFoodToShopping": "Incluir {food} na sua lista de compras",
"AddMany": "Adicionar muitos",
@@ -216,6 +217,7 @@
"Hide_Keywords": "Esconder palavra-chave",
"Hide_Recipes": "Esconder Receitas",
"Hide_as_header": "Esconder cabeçalho",
"Hierarchy": "",
"History": "Histórico",
"HostedFreeVersion": "Você está utilizando a versão gratuita do Tandoor",
"Hour": "Hora",
@@ -382,6 +384,7 @@
"Recipes_per_page": "Receitas por página",
"RemoveAllType": "",
"RemoveFoodFromShopping": "Remover {food} da sua lista de compras",
"RemoveParent": "",
"Remove_nutrition_recipe": "Deletar dados nutricionais da receita",
"Reset": "Reiniciar",
"Reset_Search": "Resetar Busca",

View File

@@ -4,6 +4,7 @@
"API_Documentation": "",
"Account": "Cont",
"Add": "Adaugă",
"AddChild": "",
"AddFoodToShopping": "Adăugă {food} în lista de cumpărături",
"AddToShopping": "Adaugă la lista de cumpărături",
"Add_Servings_to_Shopping": "Adăugă {servings} porții la cumpărături",
@@ -130,6 +131,7 @@
"Hide_Keywords": "Ascunde cuvânt cheie",
"Hide_Recipes": "Ascunde rețetele",
"Hide_as_header": "Ascunderea ca antet",
"Hierarchy": "",
"Hour": "Oră",
"Hours": "Ore",
"Icon": "Iconiță",
@@ -265,6 +267,7 @@
"Recipes_per_page": "Rețete pe pagină",
"RemoveAllType": "",
"RemoveFoodFromShopping": "Șterge {food} din lista de cumpărături",
"RemoveParent": "",
"Remove_nutrition_recipe": "Ștergere a nutriției din rețetă",
"Reset": "Resetare",
"Reset_Search": "Resetarea căutării",

View File

@@ -11,6 +11,7 @@
"Activity": "Активность",
"Add": "Добавить",
"AddAll": "Добавить все",
"AddChild": "",
"AddFilter": "Добавить фильтр",
"AddFoodToShopping": "Добавить {food} в ваш список покупок",
"AddMany": "Добавить несколько",
@@ -217,6 +218,7 @@
"Hide_Keywords": "Скрыть ключевое слово",
"Hide_Recipes": "Скрыть рецепт",
"Hide_as_header": "Скрыть заголовок",
"Hierarchy": "",
"History": "История",
"HostedFreeVersion": "Текущая версия: бесплатная",
"Hour": "Час",
@@ -418,6 +420,7 @@
"Remove": "Удалить",
"RemoveAllType": "",
"RemoveFoodFromShopping": "Удалить {food} из вашего списка покупок",
"RemoveParent": "",
"Remove_nutrition_recipe": "Уберите питательные вещества из рецепта",
"Reset": "Сбросить",
"ResetHelp": "Сбросить подсказки",

View File

@@ -11,6 +11,7 @@
"Activity": "Aktivnost",
"Add": "Dodaj",
"AddAll": "Dodaj vse",
"AddChild": "",
"AddFilter": "Dodaj filter",
"AddFoodToShopping": "Dodaj {food} v nakupovalni listek",
"AddMany": "Dodaj veliko",
@@ -217,6 +218,7 @@
"Hide_Keywords": "Skrij ključno besedo",
"Hide_Recipes": "Skrij recept",
"Hide_as_header": "Skrij kot glavo",
"Hierarchy": "",
"History": "Zgodovina",
"HostedFreeVersion": "Uporabljate brezplačno različico Tandoorja",
"Hour": "Ura",
@@ -420,6 +422,7 @@
"Remove": "Odstrani",
"RemoveAllType": "",
"RemoveFoodFromShopping": "Odstrani {food} iz nakupovalnega listka",
"RemoveParent": "",
"Remove_nutrition_recipe": "Receptu izbriši hranilno vrednost",
"Reset": "Ponastavi",
"ResetHelp": "Pomoč pri ponastavitvi",

View File

@@ -10,6 +10,7 @@
"Activity": "Aktivitet",
"Add": "Lägg till",
"AddAll": "Lägg till alla",
"AddChild": "",
"AddFilter": "Lägg till filter",
"AddFoodToShopping": "Lägg till {food} på din inköpslista",
"AddMany": "Lägg till flera",
@@ -189,6 +190,7 @@
"Hide_Keywords": "Dölj nyckelord",
"Hide_Recipes": "Dölj recept",
"Hide_as_header": "Göm som rubrik",
"Hierarchy": "",
"Hour": "Timme",
"Hours": "Timmar",
"Icon": "Ikon",
@@ -341,6 +343,7 @@
"Recipes_per_page": "Recept per sida",
"RemoveAllType": "",
"RemoveFoodFromShopping": "Ta bort {mat} från din inköpslista",
"RemoveParent": "",
"Remove_nutrition_recipe": "Ta bort näring från receptet",
"Reset": "Återställ",
"Reset_Search": "Rensa sök",

View File

@@ -4,6 +4,7 @@
"API_Documentation": "",
"Account": "Hesap",
"Add": "Ekle",
"AddChild": "",
"AddFoodToShopping": "{food}'ı alışveriş listenize ekleyin",
"AddToShopping": "Alışveriş listesine ekle",
"Add_Servings_to_Shopping": "Alışverişe {servings} Porsiyon Ekle",
@@ -152,6 +153,7 @@
"Hide_Keywords": "Anahtar Kelimeyi Gizle",
"Hide_Recipes": "Tarifleri Gizle",
"Hide_as_header": "Başlık olarak gizle",
"Hierarchy": "",
"Hour": "Saat",
"Hours": "Saatler",
"Icon": "Simge",
@@ -304,6 +306,7 @@
"Recipes_per_page": "Sayfa Başına Tarif",
"RemoveAllType": "",
"RemoveFoodFromShopping": "{food}'ı alışveriş listenizden çıkarın",
"RemoveParent": "",
"Remove_nutrition_recipe": "Tariften besin değeri sil",
"Reset": "Sıfırla",
"Reset_Search": "Aramayı Sıfırla",

View File

@@ -2,6 +2,7 @@
"API_Browser": "",
"API_Documentation": "",
"Add": "Додати",
"AddChild": "",
"AddFoodToShopping": "Додати {food} до вашого списку покупок",
"AddToShopping": "Додати до списку покупок",
"Add_Servings_to_Shopping": "Додати {servings} Порції до Покупок",
@@ -133,6 +134,7 @@
"Hide_Keywords": "Сховати Ключове слово",
"Hide_Recipes": "Сховати Рецепти",
"Hide_as_header": "Приховати як заголовок",
"Hierarchy": "",
"Icon": "Іконка",
"IgnoreAccents": "",
"IgnoreAccentsHelp": "",
@@ -268,6 +270,7 @@
"Recipes_per_page": "Кількість Рецептів на Сторінку",
"RemoveAllType": "",
"RemoveFoodFromShopping": "Видалити {food} з вашого списку покупок",
"RemoveParent": "",
"Remove_nutrition_recipe": "Видалити харчову цінність з рецепта",
"Reset": "",
"Reset_Search": "Скинути Пошук",

View File

@@ -4,6 +4,7 @@
"API_Documentation": "",
"Account": "账户",
"Add": "添加",
"AddChild": "",
"AddFoodToShopping": "添加 {food} 到购物清单",
"AddToShopping": "添加到购物清单",
"Add_Servings_to_Shopping": "添加 {servings} 份到购物",
@@ -152,6 +153,7 @@
"Hide_Keywords": "隐藏关键词",
"Hide_Recipes": "隐藏食谱",
"Hide_as_header": "隐藏标题",
"Hierarchy": "",
"Hour": "小数",
"Hours": "小时",
"Icon": "图标",
@@ -304,6 +306,7 @@
"Recipes_per_page": "每页食谱数量",
"RemoveAllType": "",
"RemoveFoodFromShopping": "从购物清单中移除 {food}",
"RemoveParent": "",
"Remove_nutrition_recipe": "从食谱中删除营养信息",
"Reset": "重置",
"Reset_Search": "重置搜索",

View File

@@ -11,6 +11,7 @@
"Activity": "活動",
"Add": "新增",
"AddAll": "增加全部",
"AddChild": "",
"AddFilter": "增加過濾器",
"AddFoodToShopping": "添加食物到購物",
"AddMany": "增加多個",
@@ -216,6 +217,7 @@
"Hide_Keywords": "隱藏關鍵字",
"Hide_Recipes": "隱藏食譜",
"Hide_as_header": "隱藏為標題",
"Hierarchy": "",
"History": "歷史記錄",
"HostedFreeVersion": "您正在使用 Tandoor 的免費版本",
"Hour": "小時",
@@ -419,6 +421,7 @@
"Remove": "移除",
"RemoveAllType": "",
"RemoveFoodFromShopping": "從購物清單中移除 {food}",
"RemoveParent": "",
"Remove_nutrition_recipe": "從食譜中刪除營養資訊",
"Reset": "重置",
"ResetHelp": "重置說明",

View File

@@ -21,6 +21,7 @@ models/EnterpriseKeyword.ts
models/EnterpriseSocialEmbed.ts
models/EnterpriseSocialEmbedTypeEnum.ts
models/EnterpriseSocialRecipeSearch.ts
models/EnterpriseSpace.ts
models/ExportLog.ts
models/ExportRequest.ts
models/FdcQuery.ts
@@ -63,6 +64,7 @@ models/PaginatedCookLogList.ts
models/PaginatedCustomFilterList.ts
models/PaginatedEnterpriseSocialEmbedList.ts
models/PaginatedEnterpriseSocialRecipeSearchList.ts
models/PaginatedEnterpriseSpaceList.ts
models/PaginatedExportLogList.ts
models/PaginatedFoodList.ts
models/PaginatedImportLogList.ts
@@ -107,6 +109,7 @@ models/PatchedConnectorConfig.ts
models/PatchedCookLog.ts
models/PatchedCustomFilter.ts
models/PatchedEnterpriseSocialEmbed.ts
models/PatchedEnterpriseSpace.ts
models/PatchedExportLog.ts
models/PatchedFood.ts
models/PatchedImportLog.ts

View File

@@ -23,6 +23,7 @@ import type {
CookLog,
CustomFilter,
EnterpriseSocialEmbed,
EnterpriseSpace,
ExportLog,
ExportRequest,
FdcQuery,
@@ -55,6 +56,7 @@ import type {
PaginatedCustomFilterList,
PaginatedEnterpriseSocialEmbedList,
PaginatedEnterpriseSocialRecipeSearchList,
PaginatedEnterpriseSpaceList,
PaginatedExportLogList,
PaginatedFoodList,
PaginatedImportLogList,
@@ -99,6 +101,7 @@ import type {
PatchedCookLog,
PatchedCustomFilter,
PatchedEnterpriseSocialEmbed,
PatchedEnterpriseSpace,
PatchedExportLog,
PatchedFood,
PatchedImportLog,
@@ -190,6 +193,8 @@ import {
CustomFilterToJSON,
EnterpriseSocialEmbedFromJSON,
EnterpriseSocialEmbedToJSON,
EnterpriseSpaceFromJSON,
EnterpriseSpaceToJSON,
ExportLogFromJSON,
ExportLogToJSON,
ExportRequestFromJSON,
@@ -254,6 +259,8 @@ import {
PaginatedEnterpriseSocialEmbedListToJSON,
PaginatedEnterpriseSocialRecipeSearchListFromJSON,
PaginatedEnterpriseSocialRecipeSearchListToJSON,
PaginatedEnterpriseSpaceListFromJSON,
PaginatedEnterpriseSpaceListToJSON,
PaginatedExportLogListFromJSON,
PaginatedExportLogListToJSON,
PaginatedFoodListFromJSON,
@@ -342,6 +349,8 @@ import {
PatchedCustomFilterToJSON,
PatchedEnterpriseSocialEmbedFromJSON,
PatchedEnterpriseSocialEmbedToJSON,
PatchedEnterpriseSpaceFromJSON,
PatchedEnterpriseSpaceToJSON,
PatchedExportLogFromJSON,
PatchedExportLogToJSON,
PatchedFoodFromJSON,
@@ -711,6 +720,7 @@ export interface ApiEnterpriseSocialKeywordListRequest {
query?: string;
random?: string;
root?: number;
rootTree?: number;
tree?: number;
updatedAt?: string;
}
@@ -832,6 +842,33 @@ export interface ApiEnterpriseSocialRecipeUpdateRequest {
recipe: Omit<Recipe, 'image'|'createdBy'|'createdAt'|'updatedAt'|'foodProperties'|'rating'|'lastCooked'>;
}
export interface ApiEnterpriseSpaceCreateRequest {
enterpriseSpace: EnterpriseSpace;
}
export interface ApiEnterpriseSpaceDestroyRequest {
space: number;
}
export interface ApiEnterpriseSpaceListRequest {
page?: number;
pageSize?: number;
}
export interface ApiEnterpriseSpacePartialUpdateRequest {
space: number;
patchedEnterpriseSpace?: PatchedEnterpriseSpace;
}
export interface ApiEnterpriseSpaceRetrieveRequest {
space: number;
}
export interface ApiEnterpriseSpaceUpdateRequest {
space: number;
enterpriseSpace: EnterpriseSpace;
}
export interface ApiExportCreateRequest {
exportRequest: ExportRequest;
}
@@ -892,6 +929,7 @@ export interface ApiFoodListRequest {
query?: string;
random?: string;
root?: number;
rootTree?: number;
tree?: number;
updatedAt?: string;
}
@@ -1057,6 +1095,7 @@ export interface ApiKeywordListRequest {
query?: string;
random?: string;
root?: number;
rootTree?: number;
tree?: number;
updatedAt?: string;
}
@@ -4023,6 +4062,10 @@ export class ApiApi extends runtime.BaseAPI {
queryParameters['root'] = requestParameters['root'];
}
if (requestParameters['rootTree'] != null) {
queryParameters['root_tree'] = requestParameters['rootTree'];
}
if (requestParameters['tree'] != null) {
queryParameters['tree'] = requestParameters['tree'];
}
@@ -4919,6 +4962,260 @@ export class ApiApi extends runtime.BaseAPI {
return await response.value();
}
/**
*/
async apiEnterpriseSpaceCreateRaw(requestParameters: ApiEnterpriseSpaceCreateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<EnterpriseSpace>> {
if (requestParameters['enterpriseSpace'] == null) {
throw new runtime.RequiredError(
'enterpriseSpace',
'Required parameter "enterpriseSpace" was null or undefined when calling apiEnterpriseSpaceCreate().'
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json';
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/enterprise-space/`,
method: 'POST',
headers: headerParameters,
query: queryParameters,
body: EnterpriseSpaceToJSON(requestParameters['enterpriseSpace']),
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => EnterpriseSpaceFromJSON(jsonValue));
}
/**
*/
async apiEnterpriseSpaceCreate(requestParameters: ApiEnterpriseSpaceCreateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<EnterpriseSpace> {
const response = await this.apiEnterpriseSpaceCreateRaw(requestParameters, initOverrides);
return await response.value();
}
/**
*/
async apiEnterpriseSpaceCurrentRetrieveRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<EnterpriseSpace>> {
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/enterprise-space/current/`,
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => EnterpriseSpaceFromJSON(jsonValue));
}
/**
*/
async apiEnterpriseSpaceCurrentRetrieve(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<EnterpriseSpace> {
const response = await this.apiEnterpriseSpaceCurrentRetrieveRaw(initOverrides);
return await response.value();
}
/**
*/
async apiEnterpriseSpaceDestroyRaw(requestParameters: ApiEnterpriseSpaceDestroyRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<void>> {
if (requestParameters['space'] == null) {
throw new runtime.RequiredError(
'space',
'Required parameter "space" was null or undefined when calling apiEnterpriseSpaceDestroy().'
);
}
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/enterprise-space/{space}/`.replace(`{${"space"}}`, encodeURIComponent(String(requestParameters['space']))),
method: 'DELETE',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.VoidApiResponse(response);
}
/**
*/
async apiEnterpriseSpaceDestroy(requestParameters: ApiEnterpriseSpaceDestroyRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<void> {
await this.apiEnterpriseSpaceDestroyRaw(requestParameters, initOverrides);
}
/**
*/
async apiEnterpriseSpaceListRaw(requestParameters: ApiEnterpriseSpaceListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<PaginatedEnterpriseSpaceList>> {
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) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/enterprise-space/`,
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => PaginatedEnterpriseSpaceListFromJSON(jsonValue));
}
/**
*/
async apiEnterpriseSpaceList(requestParameters: ApiEnterpriseSpaceListRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<PaginatedEnterpriseSpaceList> {
const response = await this.apiEnterpriseSpaceListRaw(requestParameters, initOverrides);
return await response.value();
}
/**
*/
async apiEnterpriseSpacePartialUpdateRaw(requestParameters: ApiEnterpriseSpacePartialUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<EnterpriseSpace>> {
if (requestParameters['space'] == null) {
throw new runtime.RequiredError(
'space',
'Required parameter "space" was null or undefined when calling apiEnterpriseSpacePartialUpdate().'
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json';
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/enterprise-space/{space}/`.replace(`{${"space"}}`, encodeURIComponent(String(requestParameters['space']))),
method: 'PATCH',
headers: headerParameters,
query: queryParameters,
body: PatchedEnterpriseSpaceToJSON(requestParameters['patchedEnterpriseSpace']),
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => EnterpriseSpaceFromJSON(jsonValue));
}
/**
*/
async apiEnterpriseSpacePartialUpdate(requestParameters: ApiEnterpriseSpacePartialUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<EnterpriseSpace> {
const response = await this.apiEnterpriseSpacePartialUpdateRaw(requestParameters, initOverrides);
return await response.value();
}
/**
*/
async apiEnterpriseSpaceRetrieveRaw(requestParameters: ApiEnterpriseSpaceRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<EnterpriseSpace>> {
if (requestParameters['space'] == null) {
throw new runtime.RequiredError(
'space',
'Required parameter "space" was null or undefined when calling apiEnterpriseSpaceRetrieve().'
);
}
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/enterprise-space/{space}/`.replace(`{${"space"}}`, encodeURIComponent(String(requestParameters['space']))),
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => EnterpriseSpaceFromJSON(jsonValue));
}
/**
*/
async apiEnterpriseSpaceRetrieve(requestParameters: ApiEnterpriseSpaceRetrieveRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<EnterpriseSpace> {
const response = await this.apiEnterpriseSpaceRetrieveRaw(requestParameters, initOverrides);
return await response.value();
}
/**
*/
async apiEnterpriseSpaceUpdateRaw(requestParameters: ApiEnterpriseSpaceUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<EnterpriseSpace>> {
if (requestParameters['space'] == null) {
throw new runtime.RequiredError(
'space',
'Required parameter "space" was null or undefined when calling apiEnterpriseSpaceUpdate().'
);
}
if (requestParameters['enterpriseSpace'] == null) {
throw new runtime.RequiredError(
'enterpriseSpace',
'Required parameter "enterpriseSpace" was null or undefined when calling apiEnterpriseSpaceUpdate().'
);
}
const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json';
if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
}
const response = await this.request({
path: `/api/enterprise-space/{space}/`.replace(`{${"space"}}`, encodeURIComponent(String(requestParameters['space']))),
method: 'PUT',
headers: headerParameters,
query: queryParameters,
body: EnterpriseSpaceToJSON(requestParameters['enterpriseSpace']),
}, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => EnterpriseSpaceFromJSON(jsonValue));
}
/**
*/
async apiEnterpriseSpaceUpdate(requestParameters: ApiEnterpriseSpaceUpdateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<EnterpriseSpace> {
const response = await this.apiEnterpriseSpaceUpdateRaw(requestParameters, initOverrides);
return await response.value();
}
/**
*/
async apiExportCreateRaw(requestParameters: ApiExportCreateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<ExportLog>> {
@@ -5451,6 +5748,10 @@ export class ApiApi extends runtime.BaseAPI {
queryParameters['root'] = requestParameters['root'];
}
if (requestParameters['rootTree'] != null) {
queryParameters['root_tree'] = requestParameters['rootTree'];
}
if (requestParameters['tree'] != null) {
queryParameters['tree'] = requestParameters['tree'];
}
@@ -6929,6 +7230,10 @@ export class ApiApi extends runtime.BaseAPI {
queryParameters['root'] = requestParameters['root'];
}
if (requestParameters['rootTree'] != null) {
queryParameters['root_tree'] = requestParameters['rootTree'];
}
if (requestParameters['tree'] != null) {
queryParameters['tree'] = requestParameters['tree'];
}

View File

@@ -0,0 +1,70 @@
/* 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 EnterpriseSpace
*/
export interface EnterpriseSpace {
/**
*
* @type {number}
* @memberof EnterpriseSpace
*/
space: number;
/**
*
* @type {string}
* @memberof EnterpriseSpace
*/
licensedModules: string;
}
/**
* Check if a given object implements the EnterpriseSpace interface.
*/
export function instanceOfEnterpriseSpace(value: object): value is EnterpriseSpace {
if (!('space' in value) || value['space'] === undefined) return false;
if (!('licensedModules' in value) || value['licensedModules'] === undefined) return false;
return true;
}
export function EnterpriseSpaceFromJSON(json: any): EnterpriseSpace {
return EnterpriseSpaceFromJSONTyped(json, false);
}
export function EnterpriseSpaceFromJSONTyped(json: any, ignoreDiscriminator: boolean): EnterpriseSpace {
if (json == null) {
return json;
}
return {
'space': json['space'],
'licensedModules': json['licensed_modules'],
};
}
export function EnterpriseSpaceToJSON(value?: EnterpriseSpace | null): any {
if (value == null) {
return value;
}
return {
'space': value['space'],
'licensed_modules': value['licensedModules'],
};
}

View File

@@ -0,0 +1,101 @@
/* 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 { EnterpriseSpace } from './EnterpriseSpace';
import {
EnterpriseSpaceFromJSON,
EnterpriseSpaceFromJSONTyped,
EnterpriseSpaceToJSON,
} from './EnterpriseSpace';
/**
*
* @export
* @interface PaginatedEnterpriseSpaceList
*/
export interface PaginatedEnterpriseSpaceList {
/**
*
* @type {number}
* @memberof PaginatedEnterpriseSpaceList
*/
count: number;
/**
*
* @type {string}
* @memberof PaginatedEnterpriseSpaceList
*/
next?: string;
/**
*
* @type {string}
* @memberof PaginatedEnterpriseSpaceList
*/
previous?: string;
/**
*
* @type {Array<EnterpriseSpace>}
* @memberof PaginatedEnterpriseSpaceList
*/
results: Array<EnterpriseSpace>;
/**
*
* @type {Date}
* @memberof PaginatedEnterpriseSpaceList
*/
timestamp?: Date;
}
/**
* Check if a given object implements the PaginatedEnterpriseSpaceList interface.
*/
export function instanceOfPaginatedEnterpriseSpaceList(value: object): value is PaginatedEnterpriseSpaceList {
if (!('count' in value) || value['count'] === undefined) return false;
if (!('results' in value) || value['results'] === undefined) return false;
return true;
}
export function PaginatedEnterpriseSpaceListFromJSON(json: any): PaginatedEnterpriseSpaceList {
return PaginatedEnterpriseSpaceListFromJSONTyped(json, false);
}
export function PaginatedEnterpriseSpaceListFromJSONTyped(json: any, ignoreDiscriminator: boolean): PaginatedEnterpriseSpaceList {
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(EnterpriseSpaceFromJSON)),
'timestamp': json['timestamp'] == null ? undefined : (new Date(json['timestamp'])),
};
}
export function PaginatedEnterpriseSpaceListToJSON(value?: PaginatedEnterpriseSpaceList | null): any {
if (value == null) {
return value;
}
return {
'count': value['count'],
'next': value['next'],
'previous': value['previous'],
'results': ((value['results'] as Array<any>).map(EnterpriseSpaceToJSON)),
'timestamp': value['timestamp'] == null ? undefined : ((value['timestamp']).toISOString()),
};
}

View File

@@ -0,0 +1,68 @@
/* 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 PatchedEnterpriseSpace
*/
export interface PatchedEnterpriseSpace {
/**
*
* @type {number}
* @memberof PatchedEnterpriseSpace
*/
space?: number;
/**
*
* @type {string}
* @memberof PatchedEnterpriseSpace
*/
licensedModules?: string;
}
/**
* Check if a given object implements the PatchedEnterpriseSpace interface.
*/
export function instanceOfPatchedEnterpriseSpace(value: object): value is PatchedEnterpriseSpace {
return true;
}
export function PatchedEnterpriseSpaceFromJSON(json: any): PatchedEnterpriseSpace {
return PatchedEnterpriseSpaceFromJSONTyped(json, false);
}
export function PatchedEnterpriseSpaceFromJSONTyped(json: any, ignoreDiscriminator: boolean): PatchedEnterpriseSpace {
if (json == null) {
return json;
}
return {
'space': json['space'] == null ? undefined : json['space'],
'licensedModules': json['licensed_modules'] == null ? undefined : json['licensed_modules'],
};
}
export function PatchedEnterpriseSpaceToJSON(value?: PatchedEnterpriseSpace | null): any {
if (value == null) {
return value;
}
return {
'space': value['space'],
'licensed_modules': value['licensedModules'],
};
}

View File

@@ -19,6 +19,7 @@ export * from './EnterpriseKeyword';
export * from './EnterpriseSocialEmbed';
export * from './EnterpriseSocialEmbedTypeEnum';
export * from './EnterpriseSocialRecipeSearch';
export * from './EnterpriseSpace';
export * from './ExportLog';
export * from './ExportRequest';
export * from './FdcQuery';
@@ -61,6 +62,7 @@ export * from './PaginatedCookLogList';
export * from './PaginatedCustomFilterList';
export * from './PaginatedEnterpriseSocialEmbedList';
export * from './PaginatedEnterpriseSocialRecipeSearchList';
export * from './PaginatedEnterpriseSpaceList';
export * from './PaginatedExportLogList';
export * from './PaginatedFoodList';
export * from './PaginatedImportLogList';
@@ -105,6 +107,7 @@ export * from './PatchedConnectorConfig';
export * from './PatchedCookLog';
export * from './PatchedCustomFilter';
export * from './PatchedEnterpriseSocialEmbed';
export * from './PatchedEnterpriseSpace';
export * from './PatchedExportLog';
export * from './PatchedFood';
export * from './PatchedImportLog';

View File

@@ -1,80 +1,11 @@
<template>
<v-container>
<v-btn @click="loadRecipes()" :loading="loading">Load</v-btn>
<v-row>
<v-col>
Recipe 1 - {{ recipe1.name }}
<keywords-bar :keywords="recipe1.keywords"></keywords-bar>
</v-col>
<v-col>
Recipe 2 - {{ recipe2.name }}
<keywords-bar :keywords="recipe2.keywords"></keywords-bar>
</v-col>
</v-row>
<model-select model="Keyword" allow-create mode="tags" v-model="keywords"></model-select>
<v-btn @click="batchUpdate()" :loading="loading">Add to recipes</v-btn>
<v-btn @click="batchRemove()" :loading="loading">Remove to recipes</v-btn>
</v-container>
</template>
<script setup lang="ts">
import {onMounted, ref} from "vue";
import {ApiApi, Keyword, Recipe} from "@/openapi";
import KeywordsBar from "@/components/display/KeywordsBar.vue";
import ModelSelect from "@/components/inputs/ModelSelect.vue";
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore.ts";
const loading = ref(false)
const recipe1 = ref({} as Recipe)
const recipe2 = ref({} as Recipe)
const keywords = ref([] as Keyword[])
onMounted(() => {
loadRecipes()
})
function loadRecipes() {
let api = new ApiApi()
loading.value = true
api.apiRecipeRetrieve({id: 231}).then(r => {
recipe1.value = r
})
api.apiRecipeRetrieve({id: 232}).then(r => {
recipe2.value = r
}).finally(() => {
loading.value = false
})
}
function batchUpdate() {
let api = new ApiApi()
loading.value = true
api.apiRecipeBatchUpdateUpdate({recipeBatchUpdate: {recipes: [recipe1.value.id!, recipe2.value.id!], keywordsAdd: keywords.value.flatMap(x => x.id!)}}).then(r => {
}).catch(err => {
useMessageStore().addError(ErrorMessageType.UPDATE_ERROR, err)
}).finally(() => {
loadRecipes()
})
}
function batchRemove() {
let api = new ApiApi()
loading.value = true
api.apiRecipeBatchUpdateUpdate({recipeBatchUpdate: {recipes: [recipe1.value.id!, recipe2.value.id!], keywordsRemove: keywords.value.flatMap(x => x.id!)}}).then(r => {
}).catch(err => {
useMessageStore().addError(ErrorMessageType.UPDATE_ERROR, err)
}).finally(() => {
loadRecipes()
})
}
</script>

View File

@@ -1,6 +1,6 @@
import {
AccessToken,
ApiApi, Automation, type AutomationTypeEnum, ConnectorConfig, CookLog, CustomFilter,
ApiApi, ApiKeywordMoveUpdateRequest, Automation, type AutomationTypeEnum, ConnectorConfig, CookLog, CustomFilter,
Food,
Ingredient,
InviteLink, Keyword,
@@ -85,7 +85,7 @@ type ModelTableHeaders = {
* custom type containing all attributes needed by the generic model system to properly handle all functions
*/
export type Model = {
name: string,
name: EditorSupportedModels,
localizationKey: string,
localizationKeyDescription: string,
icon: string,
@@ -191,6 +191,7 @@ export const TFood = {
isPaginated: true,
isMerge: true,
isTree: true,
mergeAutomation: 'FOOD_ALIAS',
toStringKeys: ['name'],
@@ -234,6 +235,7 @@ export const TKeyword = {
isPaginated: true,
isMerge: true,
isTree: true,
mergeAutomation: 'KEYWORD_ALIAS',
toStringKeys: ['name'],
@@ -949,6 +951,22 @@ export class GenericModel {
}
}
/**
* move the given source object so that its parent is the given parentId.
* @param source object to change parent for
* @param parentId parent id to change the object to or 0 to remove parent
*/
move(source: EditorSupportedTypes, parentId: number) {
if (!this.model.isTree) {
throw new Error('This model does not support trees!')
} else {
let moveRequestParams: any = {id: source.id, parent: parentId}
moveRequestParams[this.model.name.charAt(0).toLowerCase() + this.model.name.slice(1)] = source
return this.api[`api${this.model.name}MoveUpdate`](moveRequestParams)
}
}
/**
* gets a label for a specific object instance using the model toStringKeys property
* @param obj obj to get label for