Skip to content

Commit

Permalink
fully generic merge dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
vabene1111 committed Jan 2, 2025
1 parent 9e99edf commit cab1641
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 33 deletions.
53 changes: 41 additions & 12 deletions vue3/src/components/dialogs/ModelMergeDialog.vue
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
<template>
<v-dialog max-width="600px" :activator="props.activator" v-model="dialog">
<v-card :loading="loading">
<v-card-title>{{ $t('merge_title', {type: getGenericModelFromString(props.model).model.name}) }}</v-card-title>
<!-- TODO localize model name -->
<v-closable-card-title
:title="$t('merge_title', {type: $t(genericModel.model.localizationKey)})"
:sub-title="genericModel.getLabel(props.source)"
:icon="genericModel.model.icon"
v-model="dialog"
></v-closable-card-title>
<v-divider></v-divider>

<v-card-text>
{{ $t('merge_selection', {source: '', type: getGenericModelFromString(props.model).model.name}) }}
<model-select :model="props.model" v-model="target"></model-select>
<model-select append-to-body :model="props.model" v-model="target"></model-select>
{{ $t('merge_selection', {source: genericModel.getLabel(props.source), type: $t(genericModel.model.localizationKey)}) }}
<model-select :model="props.model" v-model="target" allow-create></model-select>

<v-row>
<v-col class="text-center">
<v-card color="warning" variant="tonal">
<v-card-title>{{ genericModel.getLabel(props.source) }}</v-card-title>
</v-card>
<v-icon icon="fa-solid fa-arrow-down" class="mt-4 mb-4"></v-icon>
<v-card color="success" variant="tonal">
<v-card-title v-if="!target">?</v-card-title>
<v-card-title v-else>{{ genericModel.getLabel(target) }}</v-card-title>
</v-card>
</v-col>
</v-row>


</v-card-text>
<v-card-actions>
<v-btn>{{ $t('Cancel') }}</v-btn>
<v-btn color="warning" @click="mergeModel()">{{ $t('Merge') }}</v-btn>
<v-btn :disabled="loading">{{ $t('Cancel') }}</v-btn>
<v-btn color="warning" @click="mergeModel()" :loading="loading">{{ $t('Merge') }}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
Expand All @@ -20,31 +40,40 @@
import ModelSelect from "@/components/inputs/ModelSelect.vue";
import {PropType, ref} from "vue";
import {EditorSupportedModels, getGenericModelFromString} from "@/types/Models";
import {EditorSupportedModels, EditorSupportedTypes, getGenericModelFromString} from "@/types/Models";
import {ApiApi, Food} from "@/openapi";
import {ErrorMessageType, PreparedMessage, useMessageStore} from "@/stores/MessageStore";
import {useI18n} from "vue-i18n";
import VClosableCardTitle from "@/components/dialogs/VClosableCardTitle.vue";
const props = defineProps({
model: {type: String as PropType<EditorSupportedModels>, required: true},
sourceId: {type: Number},
source: {type: {} as PropType<EditorSupportedTypes>, required: true},
activator: {type: String, default: 'parent'},
})
const {t} = useI18n()
const dialog = defineModel<boolean>({default: false})
const loading = ref(false)
const target = ref({} as Food)
const genericModel = getGenericModelFromString(props.model, t)
const target = ref<null | EditorSupportedTypes>(null)
/**
* merge source into selected target
*/
function mergeModel() {
let api = new ApiApi()
if (target.value != null) {
loading.value = true
api.apiFoodMergeUpdate({id: props.sourceId!, food: {} as Food, target: target.value.id!}).then(r => {
genericModel.merge(props.source, target.value).then(r => {
useMessageStore().addPreparedMessage(PreparedMessage.UPDATE_SUCCESS)
}).catch(err => {
useMessageStore().addError(ErrorMessageType.UPDATE_ERROR, err)
}).finally(() => {
loading.value = false
dialog.value = false
})
}
Expand Down
8 changes: 4 additions & 4 deletions vue3/src/components/inputs/StepEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@
:min="0"></v-number-input>
</v-col>
<v-col cols="3">
<model-select model="Unit" v-model="ingredient.unit" hide-details></model-select>
<model-select model="Unit" v-model="ingredient.unit" allow-create hide-details></model-select>
</v-col>
<v-col cols="3">
<model-select model="Food" v-model="ingredient.food" hide-details></model-select>
<model-select model="Food" v-model="ingredient.food" allow-create hide-details></model-select>
</v-col>
<v-col cols="3" @keydown.tab="event => handleIngredientNoteTab(event, index)">
<v-text-field :label="$t('Note')" v-model="ingredient.note" hide-details></v-text-field>
Expand Down Expand Up @@ -153,8 +153,8 @@
<v-form>
<v-number-input v-model="step.ingredients[editingIngredientIndex].amount" inset control-variant="stacked" autofocus :label="$t('Amount')"
:min="0"></v-number-input>
<model-select model="Unit" v-model="step.ingredients[editingIngredientIndex].unit" :label="$t('Unit')"></model-select>
<model-select model="Food" v-model="step.ingredients[editingIngredientIndex].food" :label="$t('Food')"></model-select>
<model-select model="Unit" v-model="step.ingredients[editingIngredientIndex].unit" :label="$t('Unit')" allow-create></model-select>
<model-select model="Food" v-model="step.ingredients[editingIngredientIndex].food" :label="$t('Food')" allow-create></model-select>
<v-text-field :label="$t('Note')" v-model="step.ingredients[editingIngredientIndex].note"></v-text-field>
</v-form>
</v-card-text>
Expand Down
9 changes: 1 addition & 8 deletions vue3/src/composables/useModelEditorFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,7 @@ export function useModelEditorFunctions<T>(modelName: EditorSupportedModels, emi


if (editingObj.value.id) {
if (useUserPreferenceStore().serverSettings.debug) {
name += '#' + editingObj.value.id
}

modelClass.value.model.toStringKeys.forEach(key => {
let value = getNestedProperty(editingObj.value, key)
name += ' ' + ((value != null) ? value : '')
})
name = modelClass.value.getLabel(editingObj.value, useUserPreferenceStore().serverSettings.debug)
}

if (name == '') {
Expand Down
6 changes: 3 additions & 3 deletions vue3/src/pages/ModelListPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@
<template v-slot:item.action="{ item }">
<v-btn class="float-right" icon="$menu" variant="plain">
<v-icon icon="$menu"></v-icon>
<v-menu activator="parent">
<v-list>
<v-menu activator="parent" close-on-content-click>
<v-list density="compact">
<v-list-item prepend-icon="$edit" :to="{name: 'ModelEditPage', params: {model: model, id: item.id}}">
{{ $t('Edit') }}
</v-list-item>
<v-list-item prepend-icon="fa-solid fa-arrows-to-dot" link>
{{ $t('Merge') }}
<model-merge-dialog :model="model" :source-id="item.id" activator="parent"></model-merge-dialog>
<model-merge-dialog :model="model" :source-id="item.id" :source="item" activator="parent"></model-merge-dialog>
</v-list-item>
</v-list>
</v-menu>
Expand Down
8 changes: 8 additions & 0 deletions vue3/src/stores/MessageStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export enum PreparedMessage {
UPDATE_SUCCESS = 'UPDATE_SUCCESS',
CREATE_SUCCESS = 'CREATE_SUCCESS',
DELETE_SUCCESS = 'DELETE_SUCCESS',
MERGE_SUCCESS = 'MERGE_SUCCESS',
MOVE_SUCCESS = 'MOVE_SUCCESS',
NOT_FOUND = 'NOT_FOUND',
}

Expand Down Expand Up @@ -144,6 +146,12 @@ export const useMessageStore = defineStore('message_store', () => {
if (preparedMessage == PreparedMessage.CREATE_SUCCESS) {
addMessage(MessageType.SUCCESS, {title: t('Created'), text: ''} as StructuredMessage, 6000, data)
}
if (preparedMessage == PreparedMessage.MERGE_SUCCESS) {
addMessage(MessageType.SUCCESS, {title: t('Merge'), text: ''} as StructuredMessage, 6000, data)
}
if (preparedMessage == PreparedMessage.MOVE_SUCCESS) {
addMessage(MessageType.SUCCESS, {title: t('Move'), text: ''} as StructuredMessage, 6000, data)
}
if (preparedMessage == PreparedMessage.NOT_FOUND) {
addMessage(MessageType.WARNING, {title: t('NotFound'), text: t('NotFoundHelp')} as StructuredMessage, 6000, data)
}
Expand Down
94 changes: 88 additions & 6 deletions vue3/src/types/Models.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
import {ApiApi} from "@/openapi";
import {
AccessToken,
ApiApi, Automation,
Food,
Ingredient,
InviteLink, Keyword,
MealPlan,
MealType,
Property, PropertyType,
Recipe, ShoppingListEntry,
Step,
Supermarket,
SupermarketCategory,
Unit,
UnitConversion, User, UserFile,
UserSpace
} from "@/openapi";
import {VDataTable} from "vuetify/components";
import {getNestedProperty} from "@/utils/utils";
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";

type VDataTableProps = InstanceType<typeof VDataTable>['$props']

Expand Down Expand Up @@ -32,7 +50,7 @@ function registerModel(model: Model) {
export function getListModels() {
let modelList: Model[] = []
SUPPORTED_MODELS.forEach((model) => {
if(!model.disableListView){
if (!model.disableListView) {
modelList.push(model)
}
})
Expand Down Expand Up @@ -71,8 +89,8 @@ export type Model = {
icon: string,
toStringKeys: Array<string>,

itemValue: string|undefined,
itemLabel: string|undefined,
itemValue: string | undefined,
itemLabel: string | undefined,

disableList?: boolean | undefined,
disableRetrieve?: boolean | undefined,
Expand All @@ -90,6 +108,7 @@ export type Model = {
}
export let SUPPORTED_MODELS = new Map<string, Model>()

// used for (string) name based passing of models (to configure model selects, editor, ...)
export type EditorSupportedModels =
'UnitConversion'
| 'AccessToken'
Expand All @@ -112,6 +131,29 @@ export type EditorSupportedModels =
| 'ShoppingListEntry'
| 'User'

// used to type methods/parameters in conjunction with configuration type
export type EditorSupportedTypes =
UnitConversion
| AccessToken
| InviteLink
| UserSpace
| MealType
| MealPlan
| Property
| Recipe
| Step
| Ingredient
| Food
| Unit
| Supermarket
| SupermarketCategory
| PropertyType
| Automation
| Keyword
| UserFile
| ShoppingListEntry
| User

export const TFood = {
name: 'Food',
localizationKey: 'Food',
Expand All @@ -136,6 +178,7 @@ export const TUnit = {
icon: 'fa-solid fa-scale-balanced',

isPaginated: true,
isMerge: true,
toStringKeys: ['name'],

tableHeaders: [
Expand All @@ -152,6 +195,7 @@ export const TKeyword = {
icon: 'fa-solid fa-tags',

isPaginated: true,
isMerge: true,
toStringKeys: ['name'],

tableHeaders: [
Expand Down Expand Up @@ -287,6 +331,7 @@ export const TSupermarketCategory = {
icon: 'fa-solid fa-boxes-stacked',

isPaginated: true,
isMerge: true,
toStringKeys: ['name'],

tableHeaders: [
Expand Down Expand Up @@ -551,7 +596,7 @@ export class GenericModel {
* @param obj object to create
* @return promise of request
*/
create(obj: any) {
create(obj: EditorSupportedTypes) {
if (this.model.disableCreate) {
throw new Error('Cannot create on this model!')
} else {
Expand All @@ -568,7 +613,7 @@ export class GenericModel {
* @param obj object to update
* @return promise of request
*/
update(id: number, obj: any) {
update(id: number, obj: EditorSupportedTypes) {
if (this.model.disableUpdate) {
throw new Error('Cannot update on this model!')
} else {
Expand Down Expand Up @@ -611,4 +656,41 @@ export class GenericModel {
}
}

/**
* merge the given source into the target by updating all entries using source to use target instead and deleting source
* @param source object to be replaced by target
* @param target object replacing source
*/
merge(source: EditorSupportedTypes, target: EditorSupportedTypes) {
if (!this.model.isMerge) {
throw new Error('Cannot merge on this model!')
} else {
let mergeRequestParams: any = {id: source.id, target: target.id}
mergeRequestParams[this.model.name.toLowerCase()] = {}

return this.api[`api${this.model.name}MergeUpdate`](mergeRequestParams)
}
}

/**
* gets a label for a specific object instance using the model toStringKeys property
* @param obj obj to get label for
* @param includeId debug function to include the ID as part of the object label
*/
getLabel(obj: EditorSupportedTypes, includeId?: boolean) {
let name = ''

if (obj) {
if (includeId) {
name += '#' + obj.id
}

this.model.toStringKeys.forEach(key => {
let value = getNestedProperty(obj, key)
name += ' ' + ((value != null) ? value : '')
})
}
return name
}

}

0 comments on commit cab1641

Please sign in to comment.