From 5fcfe09bb61279dc3d57d24f9b5bf90fee7d83e0 Mon Sep 17 00:00:00 2001 From: vabene1111 Date: Thu, 17 Jul 2025 15:34:51 +0200 Subject: [PATCH] added basic plugin support --- vue3/src/apps/tandoor/main.ts | 12 ++++- .../components/dialogs/ModelEditDialog.vue | 4 +- vue3/src/pages/ModelEditPage.vue | 2 +- vue3/src/plugins/luxonPlugin.ts | 8 --- vue3/src/types/Models.ts | 49 ++++++++++++++++++- vue3/src/types/Plugins.ts | 7 +++ 6 files changed, 68 insertions(+), 14 deletions(-) delete mode 100644 vue3/src/plugins/luxonPlugin.ts create mode 100644 vue3/src/types/Plugins.ts diff --git a/vue3/src/apps/tandoor/main.ts b/vue3/src/apps/tandoor/main.ts index e73946761..7e2877acc 100644 --- a/vue3/src/apps/tandoor/main.ts +++ b/vue3/src/apps/tandoor/main.ts @@ -1,5 +1,5 @@ import {createApp} from "vue"; -import {createRouter, createWebHashHistory, createWebHistory} from 'vue-router' +import {createRouter, createWebHistory} from 'vue-router' import {createPinia} from 'pinia' // @ts-ignore import App from './Tandoor.vue' @@ -12,8 +12,9 @@ import { createRulesPlugin } from 'vuetify/labs/rules' import {setupI18n} from "@/i18n"; import MealPlanPage from "@/pages/MealPlanPage.vue"; +import {TandoorPlugin} from "@/types/Plugins.ts"; -const routes = [ +let routes = [ {path: '/', component: () => import("@/pages/StartPage.vue"), name: 'StartPage'}, {path: '/search', redirect: {name: 'StartPage'}}, {path: '/test', component: () => import("@/pages/TestPage.vue"), name: 'view_test'}, @@ -55,6 +56,13 @@ const routes = [ {path: '/space-setup', component: () => import("@/pages/SpaceSetupPage.vue"), name: 'SpaceSetupPage'}, ] +const pluginModules = import.meta.glob('@/plugins/*/plugin.ts', { eager: true }) +const tandoorPlugins = [] as TandoorPlugin[] +Object.values(pluginModules).forEach(module => { + tandoorPlugins.push(module.plugin) + routes = routes.concat(module.plugin.routes) +}) + const router = createRouter({ history: createWebHistory(), routes, diff --git a/vue3/src/components/dialogs/ModelEditDialog.vue b/vue3/src/components/dialogs/ModelEditDialog.vue index 90f46393f..3c7bdf2bd 100644 --- a/vue3/src/components/dialogs/ModelEditDialog.vue +++ b/vue3/src/components/dialogs/ModelEditDialog.vue @@ -28,7 +28,7 @@ const props = defineProps({ closeAfterDelete: {default: true}, }) -const editorComponent = shallowRef(defineAsyncComponent(() => import(`@/components/model_editors/${getGenericModelFromString(props.model, t).model.name}Editor.vue`))) +const editorComponent = shallowRef(getGenericModelFromString(props.model, t).model.editorComponent) const dialog = defineModel({default: undefined}) const dialogActivator = (dialog.value !== undefined) ? undefined : props.activator @@ -40,7 +40,7 @@ const editingObjChangedState = ref(false) * because of this watch prop changes and update manually if prop is changed */ watch(() => props.model, () => { - editorComponent.value = defineAsyncComponent(() => import(`@/components/model_editors/${getGenericModelFromString(props.model, t).model.name}Editor.vue`)) + editorComponent.value = getGenericModelFromString(props.model, t).model.editorComponent }) /** diff --git a/vue3/src/pages/ModelEditPage.vue b/vue3/src/pages/ModelEditPage.vue index c7f919cd9..a861eb75a 100644 --- a/vue3/src/pages/ModelEditPage.vue +++ b/vue3/src/pages/ModelEditPage.vue @@ -32,7 +32,7 @@ const props = defineProps({ id: {type: String, required: false, default: undefined}, }) -const editorComponent = shallowRef(defineAsyncComponent(() => import(`@/components/model_editors/${getGenericModelFromString(props.model, t).model.name}Editor.vue`))) +const editorComponent = shallowRef(getGenericModelFromString(props.model, t).model.editorComponent) const router = useRouter() diff --git a/vue3/src/plugins/luxonPlugin.ts b/vue3/src/plugins/luxonPlugin.ts deleted file mode 100644 index 7d4a36b64..000000000 --- a/vue3/src/plugins/luxonPlugin.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {DateTime} from "luxon"; - -export default { - install: (app: any) => { - // inject a globally available luxon DateTime - app.config.globalProperties.$luxon = DateTime - } -} \ No newline at end of file diff --git a/vue3/src/types/Models.ts b/vue3/src/types/Models.ts index bcce6f6a4..9a83278a8 100644 --- a/vue3/src/types/Models.ts +++ b/vue3/src/types/Models.ts @@ -18,6 +18,7 @@ import { import {VDataTable} from "vuetify/components"; import {getNestedProperty} from "@/utils/utils"; import {useUserPreferenceStore} from "@/stores/UserPreferenceStore"; +import {defineAsyncComponent, shallowRef} from "vue"; type VDataTableProps = InstanceType['$props'] @@ -40,7 +41,7 @@ export function getGenericModelFromString(modelName: EditorSupportedModels, t: a * register a given model instance in the supported models list * @param model model to register */ -function registerModel(model: Model) { +export function registerModel(model: Model) { SUPPORTED_MODELS.set(model.name.toLowerCase(), model) } @@ -90,6 +91,8 @@ export type Model = { icon: string, toStringKeys: Array, + editorComponent?: any, + itemValue: string | undefined, itemLabel: string | undefined, @@ -184,6 +187,8 @@ export const TFood = { localizationKeyDescription: 'FoodHelp', icon: 'fa-solid fa-carrot', + editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/FoodEditor.vue`)), + isPaginated: true, isMerge: true, mergeAutomation: 'FOOD_ALIAS', @@ -204,6 +209,8 @@ export const TUnit = { localizationKeyDescription: 'UnitHelp', icon: 'fa-solid fa-scale-balanced', + editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/UnitEditor.vue`)), + isPaginated: true, isMerge: true, mergeAutomation: 'UNIT_ALIAS', @@ -223,6 +230,8 @@ export const TKeyword = { localizationKeyDescription: 'KeywordHelp', icon: 'fa-solid fa-tags', + editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/KeywordEditor.vue`)), + isPaginated: true, isMerge: true, mergeAutomation: 'KEYWORD_ALIAS', @@ -241,6 +250,8 @@ export const TRecipe = { localizationKeyDescription: 'RecipeHelp', icon: 'fa-solid fa-book', + editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/RecipeEditor.vue`)), + isPaginated: true, toStringKeys: ['name'], @@ -295,6 +306,8 @@ export const TMealType = { localizationKeyDescription: 'MealTypeHelp', icon: 'fa-solid fa-utensils', + editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/MealTypeEditor.vue`)), + isPaginated: true, toStringKeys: ['name'], @@ -311,6 +324,8 @@ export const TMealPlan = { localizationKeyDescription: 'MealPlanHelp', icon: 'fa-solid fa-calendar-days', + editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/MealPlanEditor.vue`)), + isPaginated: true, toStringKeys: ['title', 'recipe.name'], @@ -330,6 +345,8 @@ export const TRecipeBook = { localizationKeyDescription: 'RecipeBookHelp', icon: 'fa-solid fa-book-bookmark', + editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/RecipeBookEditor.vue`)), + isPaginated: true, toStringKeys: ['name'], @@ -367,6 +384,8 @@ export const TCustomFilter = { localizationKeyDescription: 'SavedSearchHelp', icon: 'fa-solid fa-filter', + editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/CustomFilterEditor.vue`)), + isPaginated: true, toStringKeys: ['name'], @@ -405,6 +424,8 @@ export const TSupermarket = { localizationKeyDescription: 'SupermarketHelp', icon: 'fa-solid fa-store', + editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/SupermarketEditor.vue`)), + isPaginated: true, toStringKeys: ['name'], @@ -421,6 +442,8 @@ export const TSupermarketCategory = { localizationKeyDescription: 'SupermarketCategoryHelp', icon: 'fa-solid fa-boxes-stacked', + editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/SupermarketCategoryEditor.vue`)), + isPaginated: true, isMerge: true, toStringKeys: ['name'], @@ -438,6 +461,8 @@ export const TShoppingListEntry = { localizationKeyDescription: 'ShoppingListEntryHelp', icon: 'fa-solid fa-list-check', + editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/ShoppingListEntryEditor.vue`)), + disableListView: true, isPaginated: true, toStringKeys: ['amount', 'unit.name', 'food.name'], @@ -457,6 +482,8 @@ export const TPropertyType = { localizationKeyDescription: 'PropertyTypeHelp', icon: 'fa-solid fa-database', + editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/PropertyTypeEditor.vue`)), + isPaginated: true, toStringKeys: ['name'], @@ -473,6 +500,8 @@ export const TProperty = { localizationKeyDescription: 'PropertyHelp', icon: 'fa-solid fa-database', + editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/PropertyEditor.vue`)), + disableListView: true, isPaginated: true, toStringKeys: ['propertyAmount', 'propertyType.name'], @@ -491,6 +520,8 @@ export const TUnitConversion = { localizationKeyDescription: 'UnitConversionHelp', icon: 'fa-solid fa-exchange-alt', + editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/UnitConversionEditor.vue`)), + isPaginated: true, toStringKeys: ['food.name', 'baseUnit.name', 'convertedUnit.name'], @@ -511,6 +542,8 @@ export const TUserFile = { localizationKeyDescription: 'UserFileHelp', icon: 'fa-solid fa-file', + editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/UserFileEditor.vue`)), + isPaginated: true, toStringKeys: ['name'], @@ -527,6 +560,8 @@ export const TAutomation = { localizationKeyDescription: 'AutomationHelp', icon: 'fa-solid fa-robot', + editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/AutomationEditor.vue`)), + isPaginated: true, toStringKeys: ['name'], @@ -584,6 +619,8 @@ export const TAccessToken = { localizationKeyDescription: 'AccessTokenHelp', icon: 'fa-solid fa-key', + editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/AccessTokenEditor.vue`)), + disableListView: true, isPaginated: true, toStringKeys: ['token'], @@ -602,6 +639,8 @@ export const TUserSpace = { localizationKeyDescription: 'SpaceMembersHelp', icon: 'fa-solid fa-users', + editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/UserSpaceEditor.vue`)), + disableListView: true, isPaginated: true, toStringKeys: ['user.displayName'], @@ -621,6 +660,8 @@ export const TInviteLink = { localizationKeyDescription: 'InviteLinkHelp', icon: 'fa-solid fa-link', + editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/InviteLinkEditor.vue`)), + disableListView: true, isPaginated: true, toStringKeys: ['email', 'role'], @@ -640,6 +681,8 @@ export const TStorage = { localizationKeyDescription: 'StorageHelp', icon: 'fa-solid fa-cloud', + editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/StorageEditor.vue`)), + disableListView: false, toStringKeys: ['name'], isPaginated: true, @@ -657,6 +700,8 @@ export const TSync = { localizationKeyDescription: 'SyncedPathHelp', icon: 'fa-solid fa-folder-plus', + editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/SyncEditor.vue`)), + disableListView: false, toStringKeys: ['path'], isPaginated: true, @@ -722,6 +767,8 @@ export const TConnectorConfig = { localizationKeyDescription: 'ConnectorConfigHelp', icon: 'fa-solid fa-arrows-turn-to-dots', + editorComponent: defineAsyncComponent(() => import(`@/components/model_editors/ConnectorConfigEditor.vue`)), + disableListView: false, toStringKeys: ['name'], isPaginated: true, diff --git a/vue3/src/types/Plugins.ts b/vue3/src/types/Plugins.ts new file mode 100644 index 000000000..b9abda9ef --- /dev/null +++ b/vue3/src/types/Plugins.ts @@ -0,0 +1,7 @@ +import {RouteRecordRaw} from "vue-router"; +import {Model, registerModel} from "@/types/Models.ts"; + +export type TandoorPlugin = { + name: string, + routes: RouteRecordRaw[] +}