improved generic type

This commit is contained in:
vabene1111
2024-09-24 16:00:31 +02:00
parent a44f61b507
commit 91f2f34cd3
33 changed files with 219 additions and 214 deletions

View File

@@ -205,6 +205,7 @@
"Print": "",
"Profile": "",
"PropertiesFoodHelp": "",
"PropertyType": "",
"Protected": "",
"Proteins": "",
"Quick actions": "",

View File

@@ -198,6 +198,7 @@
"Print": "Печат",
"Profile": "",
"PropertiesFoodHelp": "",
"PropertyType": "",
"Protected": "Защитен",
"Proteins": "Протеини (белтъчини)",
"Quick actions": "Бързи действия",

View File

@@ -274,6 +274,7 @@
"Properties_Food_Amount": "",
"Properties_Food_Unit": "",
"Property": "",
"PropertyType": "",
"Property_Editor": "",
"Protected": "",
"Proteins": "",

View File

@@ -272,6 +272,7 @@
"Properties_Food_Amount": "Množství nutriční vlastnosti",
"Properties_Food_Unit": "Jednotka nutriční vlastnosti",
"Property": "Nutriční vlastnost",
"PropertyType": "",
"Property_Editor": "Editovat nutriční vlastnosti",
"Protected": "Chráněný",
"Proteins": "Proteiny",

View File

@@ -255,6 +255,7 @@
"Properties": "Egenskaber",
"PropertiesFoodHelp": "",
"Property": "Egenskab",
"PropertyType": "",
"Protected": "Beskyttet",
"Proteins": "Proteiner",
"Quick actions": "Hurtige handlinger",

View File

@@ -276,6 +276,7 @@
"Properties_Food_Amount": "Eigenschaften: Lebensmittelmenge",
"Properties_Food_Unit": "Nährwert Einheit",
"Property": "Eigenschaft",
"PropertyType": "Eigenschafts Typ",
"Property_Editor": "Nährwerte bearbeiten",
"Protected": "Geschützt",
"Proteins": "Proteine",

View File

@@ -247,6 +247,7 @@
"Properties": "Ιδιότητες",
"PropertiesFoodHelp": "",
"Property": "Ιδιότητα",
"PropertyType": "",
"Protected": "Προστατευμένο",
"Proteins": "Πρωτεΐνες",
"Quick actions": "Γρήγηορες δράσεις",

View File

@@ -277,6 +277,7 @@
"Properties_Food_Amount": "Properties Food Amount",
"Properties_Food_Unit": "Properties Food Unit",
"Property": "Property",
"PropertyType": "Property Type",
"Property_Editor": "Property Editor",
"Protected": "Protected",
"Proteins": "Proteins",

View File

@@ -273,6 +273,7 @@
"Properties": "Propiedades",
"PropertiesFoodHelp": "",
"Property": "Propiedad",
"PropertyType": "",
"Property_Editor": "Editor de Propiedades",
"Protected": "Protegido",
"Proteins": "Proteinas",

View File

@@ -139,6 +139,7 @@
"Print": "Tulosta",
"Profile": "",
"PropertiesFoodHelp": "",
"PropertyType": "",
"Proteins": "Proteiinit",
"Rating": "Luokitus",
"Recently_Viewed": "Äskettäin katsotut",

View File

@@ -274,6 +274,7 @@
"Properties_Food_Amount": "Propriété Quantité de nourriture",
"Properties_Food_Unit": "Propriété Unité de nourriture",
"Property": "Propriété",
"PropertyType": "",
"Property_Editor": "Editeur de propriétés",
"Protected": "Protégé",
"Proteins": "Protéines",

View File

@@ -275,6 +275,7 @@
"Properties_Food_Amount": "הגדרות כמות אוכל",
"Properties_Food_Unit": "הגדרות יחידת אוכל",
"Property": "נכס",
"PropertyType": "",
"Property_Editor": "עורך ערכים",
"Protected": "מוגן",
"Proteins": "פרוטאינים",

View File

@@ -249,6 +249,7 @@
"Properties": "Tulajdonságok",
"PropertiesFoodHelp": "",
"Property": "Tulajdonság",
"PropertyType": "",
"Protected": "Védett",
"Proteins": "Fehérjék",
"Quick actions": "Gyors parancsok",

View File

@@ -85,6 +85,7 @@
"Print": "Տպել",
"Profile": "",
"PropertiesFoodHelp": "",
"PropertyType": "",
"Proteins": "",
"Rating": "",
"Recently_Viewed": "Վերջերս դիտած",

View File

@@ -225,6 +225,7 @@
"Private_Recipe_Help": "Resep hanya diperlihatkan kepada Anda dan orang-orang yang dibagikan resep tersebut.",
"Profile": "",
"PropertiesFoodHelp": "",
"PropertyType": "",
"Protected": "Terlindung",
"Proteins": "Protein",
"Quick actions": "",

View File

@@ -274,6 +274,7 @@
"Properties_Food_Amount": "",
"Properties_Food_Unit": "",
"Property": "",
"PropertyType": "",
"Property_Editor": "",
"Protected": "",
"Proteins": "",

View File

@@ -233,6 +233,7 @@
"Private_Recipe_Help": "La ricetta viene mostrata solo a te e a chi l'hai condivisa.",
"Profile": "",
"PropertiesFoodHelp": "",
"PropertyType": "",
"Protected": "Protetto",
"Proteins": "Proteine",
"Quick actions": "Azioni rapide",

View File

@@ -253,6 +253,7 @@
"Properties": "",
"PropertiesFoodHelp": "",
"Property": "",
"PropertyType": "",
"Protected": "",
"Proteins": "",
"Quick actions": "",

View File

@@ -245,6 +245,7 @@
"Properties": "Egenskaper",
"PropertiesFoodHelp": "",
"Property": "Egenskap",
"PropertyType": "",
"Protected": "Beskyttet",
"Proteins": "Protein",
"Quick actions": "",

View File

@@ -249,6 +249,7 @@
"Properties": "Eigenschappen",
"PropertiesFoodHelp": "",
"Property": "Eigenschap",
"PropertyType": "",
"Protected": "Beschermd",
"Proteins": "Eiwitten",
"Quick actions": "Snelle acties",

View File

@@ -276,6 +276,7 @@
"Properties_Food_Amount": "Właściwości ilości żywności",
"Properties_Food_Unit": "Właściwości jednostek żywności",
"Property": "Właściwość",
"PropertyType": "",
"Property_Editor": "Edytor właściwości",
"Protected": "Chroniony",
"Proteins": "Białka",

View File

@@ -195,6 +195,7 @@
"Private_Recipe_Help": "A receita só é mostrada ás pessoas com que foi partilhada.",
"Profile": "",
"PropertiesFoodHelp": "",
"PropertyType": "",
"Protected": "Protegido",
"Proteins": "Proteínas",
"Quick actions": "Acções Rápidas",

View File

@@ -263,6 +263,7 @@
"Properties_Food_Amount": "Quantidade de Alimento das Propriedades",
"Properties_Food_Unit": "Unidade de Alimento das Propriedades",
"Property": "Propriedade",
"PropertyType": "",
"Property_Editor": "Editor de Propriedades",
"Protected": "Protegido",
"Proteins": "Proteínas",

View File

@@ -237,6 +237,7 @@
"Private_Recipe_Help": "Rețeta este arătată doar ție și oamenilor cu care este împărtășită.",
"Profile": "",
"PropertiesFoodHelp": "",
"PropertyType": "",
"Protected": "Protejat",
"Proteins": "Proteine",
"Quick actions": "Acțiuni rapide",

View File

@@ -181,6 +181,7 @@
"Print": "Распечатать",
"Profile": "",
"PropertiesFoodHelp": "",
"PropertyType": "",
"Protected": "Защищено",
"Proteins": "Белки",
"Quick actions": "Быстрые действия",

View File

@@ -176,6 +176,7 @@
"Private_Recipe_Help": "Recept je prikazan samo vam in osebam, s katerimi ga delite.",
"Profile": "",
"PropertiesFoodHelp": "",
"PropertyType": "",
"Proteins": "Beljakovine",
"QuickEntry": "Hitri vnos",
"Rating": "Ocena",

View File

@@ -276,6 +276,7 @@
"Properties_Food_Amount": "Egenskaper Livsmedel Mängd",
"Properties_Food_Unit": "Egenskaper Livsmedel Enhet",
"Property": "Egendom",
"PropertyType": "",
"Property_Editor": "Egendom redigerare",
"Protected": "Skyddad",
"Proteins": "Protein",

View File

@@ -275,6 +275,7 @@
"Properties_Food_Amount": "Özellikler Yiyecek Miktar",
"Properties_Food_Unit": "Özellikler Yiyecek Birim",
"Property": "Özellik",
"PropertyType": "",
"Property_Editor": "Özellik Editörü",
"Protected": "Korumalı",
"Proteins": "Proteinler",

View File

@@ -213,6 +213,7 @@
"Private_Recipe_Help": "Рецепт показаний тільки Вам і тими з ким ви поділилися їм.",
"Profile": "",
"PropertiesFoodHelp": "",
"PropertyType": "",
"Protected": "Захищено",
"Proteins": "Білки",
"Quick actions": "",

View File

@@ -270,6 +270,7 @@
"Properties_Food_Amount": "食物数量属性",
"Properties_Food_Unit": "食品单位属性",
"Property": "属性",
"PropertyType": "",
"Property_Editor": "属性编辑器",
"Protected": "受保护的",
"Proteins": "蛋白质",

View File

@@ -60,6 +60,7 @@
"Print": "",
"Profile": "",
"PropertiesFoodHelp": "",
"PropertyType": "",
"Proteins": "",
"Rating": "",
"Recently_Viewed": "",

View File

@@ -8,17 +8,17 @@
<v-menu activator="parent">
<v-list>
<v-list-item v-for="model in supportedModels"
:to="{name: 'ModelListPage', params: {model: model}}"
<v-list-item v-for="model in [TFood, TUnit, TKeyword, TSupermarketCategory, TPropertyType]"
:to="{name: 'ModelListPage', params: {model: model.name}}"
>
<template #prepend><v-icon :icon="model.icon"></v-icon> </template>
{{ $t(model.localizedName) }}
{{ $t(model.localizationKey) }}
</v-list-item>
</v-list>
</v-menu>
</v-btn>
<v-icon :icon="modelClass.icon"></v-icon>
{{ $t(modelClass.localizedName) }}</span>
<v-icon :icon="genericModel.model.icon"></v-icon>
{{ $t(genericModel.model.localizationKey) }}</span>
</v-col>
</v-row>
<v-row>
@@ -57,8 +57,9 @@
import {onBeforeMount, onMounted, ref, watch} from "vue";
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore";
import {useI18n} from "vue-i18n";
import {Food, GenericModel, getModelFromStr, SUPPORTED_MODELS, Unit} from "@/types/Models";
import {TFood, TUnit, GenericModel, getModelFromStr, TKeyword, TSupermarketCategory, TPropertyType} from "@/types/Models";
import {ca} from "vuetify/locale";
import {ApiApi} from "@/openapi";
const {t} = useI18n()
@@ -87,15 +88,13 @@ const items = ref([] as Array<any>)
const itemCount = ref(0)
const searchQuery = ref('')
const modelClass = ref({} as GenericModel<any>)
const supportedModels = ref([
new Food, new Unit
])
const genericModel = ref({} as GenericModel)
// watch for changes to the prop in case its changed
watch(() => props.model, () => {
console.log('loading model ', props.model)
modelClass.value = getModelFromStr(props.model)
genericModel.value = getModelFromStr(props.model)
loadItems({page: 1, itemsPerPage: 10})
})
@@ -103,18 +102,19 @@ watch(() => props.model, () => {
* select model class before mount because template renders (and requests item load) before onMounted is called
*/
onBeforeMount(() => {
try {
modelClass.value = getModelFromStr(props.model)
genericModel.value = getModelFromStr(props.model)
} catch (Error) {
console.error('Invalid model passed to ModelListPage, loading Food instead')
modelClass.value = getModelFromStr('Food')
genericModel.value = getModelFromStr('Food')
}
})
function loadItems({page, itemsPerPage, search, sortBy, groupBy}) {
loading.value = true
modelClass.value.list({page: page, pageSize: itemsPerPage, query: search}).then(r => {
genericModel.value.list({page: page, pageSize: itemsPerPage, query: search}).then(r => {
items.value = r.results
itemCount.value = r.count
}).catch(err => {

View File

@@ -12,227 +12,201 @@ import {
PropertyType as IPropertyType, ApiFoodListRequest, ApiUnitListRequest,
} from "@/openapi";
export const SUPPORTED_MODELS = ["Food", "Unit"]
export function getModelFromStr(model_name: String) {
switch (model_name.toLowerCase()) {
case 'food': {
return new Food
}
case 'unit': {
return new Unit
}
case 'keyword': {
return new Keyword
}
case 'recipe': {
return new Recipe
}
case 'mealtype': {
return new MealType
}
case 'user': {
return new User
}
case 'foodinheritfield': {
return new FoodInheritField
}
case 'supermarketcategory': {
return new SupermarketCategory
}
case 'propertytype': {
return new PropertyType
}
default: {
throw Error(`Invalid Model ${model_name}, did you forget to register it in Models.ts?`)
}
/**
* returns a GenericModel instance with the given model type
* throws and error if no model with the given name exist
* @param modelName name of the model
* @return instance of GenericModel
*/
export function getModelFromStr(modelName: String) {
if (SUPPORTED_MODELS.has(modelName)) {
return new GenericModel(SUPPORTED_MODELS.get(modelName))
} else {
throw Error(`Model ${modelName} not in SUPPORTED_MODELS`)
}
}
/**
* Generic model used for generic model selects/requests
* TODO should be somehow automatically created in the model but for now this works
* common list parameters shared by all generic models
*/
export abstract class GenericModel<T> {
type GenericListRequestParameter = {
page: number,
pageSize: number,
query: string,
}
localizedName: string
icon: string
/**
* custom type containing all attributes needed by the generic model system to properly handle all functions
*/
type Model = {
name: string,
localizationKey: string,
icon: string,
/**
* override and set to false if model is read only
*/
canCreate(): boolean {
return true
disableList: boolean | undefined,
disableRetrieve: boolean | undefined,
disableCreate: boolean | undefined,
disableDelete: boolean | undefined,
// table headers
// canCreate
// canDelete
}
export let SUPPORTED_MODELS = new Map<string, Model>()
export const TFood = {
name: 'Food',
localizationKey: 'Food',
icon: 'fa-solid fa-carrot'
} as Model
SUPPORTED_MODELS.set(TFood.name, TFood)
export const TUnit = {
name: 'Unit',
localizationKey: 'Unit',
icon: 'fa-solid fa-scale-balanced',
} as Model
SUPPORTED_MODELS.set(TUnit.name, TUnit)
export const TKeyword = {
name: 'Keyword',
localizationKey: 'Keyword',
icon: 'fa-solid fa-tags',
} as Model
SUPPORTED_MODELS.set(TKeyword.name, TKeyword)
export const TRecipe = {
name: 'Recipe',
localizationKey: 'Recipe',
icon: 'fa-solid fa-book',
} as Model
SUPPORTED_MODELS.set(TRecipe.name, TRecipe)
export const TMealType = {
name: 'MealType',
localizationKey: 'Meal_Type',
icon: 'fa-solid fa-utensils',
} as Model
SUPPORTED_MODELS.set(TMealType.name, TMealType)
export const TUser = {
name: 'User',
localizationKey: 'User',
icon: 'fa-solid fa-users',
disableCreate: true,
disableDelete: true,
} as Model
SUPPORTED_MODELS.set(TUser.name, TUser)
export const TSupermarketCategory = {
name: 'SupermarketCategory',
localizationKey: 'Category',
icon: 'fa-solid fa-boxes-stacked',
} as Model
SUPPORTED_MODELS.set(TSupermarketCategory.name, TSupermarketCategory)
export const TPropertyType = {
name: 'PropertyType',
localizationKey: 'Property',
icon: 'fa-solid fa-database',
} as Model
SUPPORTED_MODELS.set(TPropertyType.name, TPropertyType)
export const TFoodInheritField = {
name: 'FoodInheritField',
localizationKey: 'FoodInherit',
icon: 'fa-solid fa-list',
disableCreate: true,
disableDelete: true,
disableRetrieve: true,
} as Model
SUPPORTED_MODELS.set(TFoodInheritField.name, TFoodInheritField)
/**
* Many of Tandoors models and model API endpoints share the same interfaces
* The GenericModel class allows interaction with these models in a standardized manner
*/
export class GenericModel {
api: Object
model: Model
constructor(model: Model) {
this.model = model
this.api = new ApiApi()
}
/**
* create a new instance of a given model
* do not override on models that cannot create
* @param name value for field name
* query the models list endpoint using the given generic parameters
* @param genericListRequestParameter parameters
* @return promise of request
*/
create(name: string): Promise<T> {
if (!this.canCreate()) {
throw new Error('Cannot create on this model!')
list(genericListRequestParameter: GenericListRequestParameter) {
if (this.model.disableList) {
throw new Error('Cannot list on this model!')
} else {
return this.api[`api${this.model.name}List`](genericListRequestParameter)
}
return new Promise(() => {
return undefined
})
};
/**
* retrieves instances of given model with given query from DB
* @param query value for standard query parameter of endpoint
* create a new instance of the given model
* throws error if creation is not supported for given model
* @param obj object to create
* @return promise of request
*/
abstract list(query: string): Promise<Array<T>>
create(obj: any) {
if (this.model.disableCreate) {
throw new Error('Cannot create on this model!')
} else {
let createRequestParams = {}
createRequestParams[this.model.name.toLowerCase()] = obj
return this.api[`api${this.model.name}Create`](createRequestParams)
}
}
/**
* update a model instance with the given value
* throws error if updating is not supported for given model
* @param id id of object to update
* @param obj object to update
* @return promise of request
*/
update(id: number, obj: any) {
if (this.model.disableCreate) {
throw new Error('Cannot update on this model!')
} else {
let updateRequestParams = {}
updateRequestParams['id'] = id
updateRequestParams[this.model.name.toLowerCase()] = obj
return this.api[`api${this.model.name}Update`](updateRequestParams)
}
}
/**
* deletes the given model instance
* throws error if creation is not supported for given model
* @param id object id to delete
* @return promise of request
*/
destroy(id: number) {
if (this.model.disableDelete) {
throw new Error('Cannot delete on this model!')
} else {
let destroyRequestParams = {}
destroyRequestParams['id'] = id
return this.api[`api${this.model.name}Destroy`](createRequestParams)
}
}
}
export class Keyword extends GenericModel<IKeyword> {
create(name: string) {
const api = new ApiApi()
return api.apiKeywordCreate({keyword: {name: name} as IKeyword})
}
list(query: string) {
const api = new ApiApi()
return api.apiKeywordList({query: query}).then(r => {
if (r.results) {
return r.results
} else {
return []
}
})
}
}
// TODO review this whole file and its usages
export class Food extends GenericModel<IFood> {
localizedName = 'Food'
icon = 'fa-solid fa-carrot'
create(name: string) {
const api = new ApiApi()
return api.apiFoodCreate({food: {name: name} as IFood})
}
list(requestParameters: ApiFoodListRequest = {}) {
const api = new ApiApi()
return api.apiFoodList(requestParameters)
}
}
export class Unit extends GenericModel<IUnit> {
localizedName = 'Unit'
icon = 'fa-solid fa-scale-balanced'
create(name: string) {
const api = new ApiApi()
return api.apiUnitCreate({unit: {name: name} as IUnit})
}
list(requestParameters: ApiUnitListRequest = {}) {
const api = new ApiApi()
return api.apiUnitList(requestParameters)
}
}
export class Recipe extends GenericModel<IRecipeOverview> {
create(name: string) {
const api = new ApiApi()
return api.apiRecipeCreate({recipe: {name: name} as IRecipe}).then(r => {
return r as unknown as IRecipeOverview
})
}
list(query: string) {
const api = new ApiApi()
return api.apiRecipeList({query: query}).then(r => {
if (r.results) {
return r.results
} else {
return []
}
})
}
}
export class MealType extends GenericModel<IMealType> {
create(name: string) {
const api = new ApiApi()
return api.apiMealTypeCreate({mealType: {name: name} as IMealType}).then(r => {
return r as unknown as IMealType
})
}
list(query: string) {
const api = new ApiApi()
return api.apiMealTypeList({}).then(r => {
if (r.results) {
return r.results
} else {
return []
}
})
}
}
export class User extends GenericModel<IUser> {
canCreate(): boolean {
return false
}
list(query: string) {
const api = new ApiApi()
return api.apiUserList({}).then(r => {
if (r) {
return r
} else {
return []
}
})
}
}
export class FoodInheritField extends GenericModel<IFoodInheritField> {
canCreate(): boolean {
return false
}
list(query: string) {
const api = new ApiApi()
return api.apiFoodInheritFieldList({}).then(r => {
if (r) {
return r
} else {
return []
}
})
}
}
export class SupermarketCategory extends GenericModel<ISupermarketCategory> {
create(name: string) {
const api = new ApiApi()
return api.apiSupermarketCategoryCreate({supermarketCategory: {name: name} as ISupermarketCategory}).then(r => {
return r as unknown as ISupermarketCategory
})
}
list(query: string) {
const api = new ApiApi()
return api.apiSupermarketCategoryList({query: query}).then(r => {
if (r.results) {
return r.results
} else {
return []
}
})
}
}
export class PropertyType extends GenericModel<IPropertyType> {