[plugins] Cleanup, documentation and refactoring of plugin system
This commit is contained in:
		
							parent
							
								
									cd937f111c
								
							
						
					
					
						commit
						851ce3aa8b
					
				|  | @ -3,7 +3,7 @@ import { FG_Plugin } from 'src/plugins'; | ||||||
| import routes from 'src/router/routes'; | import routes from 'src/router/routes'; | ||||||
| import { api } from 'boot/axios'; | import { api } from 'boot/axios'; | ||||||
| import { AxiosResponse } from 'axios'; | import { AxiosResponse } from 'axios'; | ||||||
| import { Router, RouteRecordRaw } from 'vue-router'; | import { RouteRecordRaw } from 'vue-router'; | ||||||
| 
 | 
 | ||||||
| const config: { [key: string]: Array<string> } = { | const config: { [key: string]: Array<string> } = { | ||||||
|   // Do not change required Modules !!
 |   // Do not change required Modules !!
 | ||||||
|  | @ -12,9 +12,33 @@ const config: { [key: string]: Array<string> } = { | ||||||
|   loadModules: ['Balance', 'Schedule', 'Pricelist'], |   loadModules: ['Balance', 'Schedule', 'Pricelist'], | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | /* Stop! | ||||||
|  | 
 | ||||||
| // do not change anything here !!
 | // do not change anything here !!
 | ||||||
| 
 | 
 | ||||||
| // combine routes from source to target
 | // You can not even read? I said stop!
 | ||||||
|  | 
 | ||||||
|  | // Really are you stupid? Stop scrolling down here!
 | ||||||
|  | 
 | ||||||
|  | // Every line you scroll down, an unicorn will die painfully!
 | ||||||
|  | 
 | ||||||
|  | // Ok you must hate unicorns... But what if I say you I joked... Baby otters will die!
 | ||||||
|  | 
 | ||||||
|  |          .-"""-. | ||||||
|  |         /      o\ | ||||||
|  |        |    o   0).-. | ||||||
|  |        |       .-;(_/     .-. | ||||||
|  |         \     /  /)).---._|  `\   , | ||||||
|  |          '.  '  /((       `'-./ _/|
 | ||||||
|  |            \  .'  )        .-.;`  /
 | ||||||
|  |             '.             |  `\-' | ||||||
|  |               '._        -'    / | ||||||
|  |                  ``""--`------` | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | /**************************************************** | ||||||
|  |  ******** Internal area for some magic ************** | ||||||
|  |  ****************************************************/ | ||||||
| 
 | 
 | ||||||
| interface BackendPlugin { | interface BackendPlugin { | ||||||
|   permissions: string[]; |   permissions: string[]; | ||||||
|  | @ -22,26 +46,35 @@ interface BackendPlugin { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| interface BackendPlugins { | interface BackendPlugins { | ||||||
|   [key: string]: BackendPlugin | null; |   [key: string]: BackendPlugin; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| interface Backend { | interface Backend { | ||||||
|   plugins: BackendPlugins[]; |   plugins: BackendPlugins; | ||||||
|   version: string; |   version: string; | ||||||
| } | } | ||||||
| 
 |  | ||||||
| export { Backend }; | export { Backend }; | ||||||
| 
 | 
 | ||||||
| function setPermissions(object: FG_Plugin.PluginRouteConfig) { | // Combine routes, shortcuts and widgets from plugins
 | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Helper function, set permissions from MenuRoute to meta from RouteRecordRaw | ||||||
|  |  * @param object MenuRoute to set route meta | ||||||
|  |  */ | ||||||
|  | function setPermissions(object: FG_Plugin.MenuRoute) { | ||||||
|   if (object.permissions !== undefined) { |   if (object.permissions !== undefined) { | ||||||
|     if (object.route.meta === undefined) object.route.meta = {}; |     if (object.route.meta === undefined) object.route.meta = {}; | ||||||
|     object.route.meta['permissions'] = object.permissions; |     object.route.meta['permissions'] = object.permissions; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function convertRoutes(parent: RouteRecordRaw, children?: FG_Plugin.PluginRouteConfig[]) { | /** | ||||||
|   if (children === undefined) return; |  * Helper function to convert MenuRoute to the parents RouteRecordRaw | ||||||
| 
 |  * @param parent Parent RouteRecordRaw | ||||||
|  |  * @param children MenuRoute to convert | ||||||
|  |  */ | ||||||
|  | function convertRoutes(parent: RouteRecordRaw, children?: FG_Plugin.MenuRoute[]) { | ||||||
|  |   if (children !== undefined) { | ||||||
|     children.forEach((child) => { |     children.forEach((child) => { | ||||||
|       setPermissions(child); |       setPermissions(child); | ||||||
|       convertRoutes(child.route, child.children); |       convertRoutes(child.route, child.children); | ||||||
|  | @ -49,17 +82,24 @@ function convertRoutes(parent: RouteRecordRaw, children?: FG_Plugin.PluginRouteC | ||||||
|       parent.children.push(child.route); |       parent.children.push(child.route); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| function combineRoutes( | /** | ||||||
|  |  * Combines routes from plugin MenuRoute to Vue-Router RouteRecordRaw to get a clean route-tree | ||||||
|  |  * @param target | ||||||
|  |  * @param source | ||||||
|  |  * @param mainPath | ||||||
|  |  */ | ||||||
|  | function combineMenuRoutes( | ||||||
|   target: RouteRecordRaw[], |   target: RouteRecordRaw[], | ||||||
|   source: FG_Plugin.PluginRouteConfig[], |   source: FG_Plugin.MenuRoute[], | ||||||
|   mainPath: '/' | '/main' = '/' |   mainPath: '/' | '/in' = '/' | ||||||
| ): RouteRecordRaw[] { | ): RouteRecordRaw[] { | ||||||
|   // Search parent
 |   // Search parent
 | ||||||
|   target.forEach((target) => { |   target.forEach((target) => { | ||||||
|     if (target.path === mainPath) { |     if (target.path === mainPath) { | ||||||
|       // Parent found = target
 |       // Parent found = target
 | ||||||
|       source.forEach((sourceMainConfig: FG_Plugin.PluginRouteConfig) => { |       source.forEach((sourceMainConfig: FG_Plugin.MenuRoute) => { | ||||||
|         // Check if source is already in target
 |         // Check if source is already in target
 | ||||||
|         const targetMainConfig = target.children?.find((targetMainConfig: RouteRecordRaw) => { |         const targetMainConfig = target.children?.find((targetMainConfig: RouteRecordRaw) => { | ||||||
|           return sourceMainConfig.route.path === targetMainConfig.path; |           return sourceMainConfig.route.path === targetMainConfig.path; | ||||||
|  | @ -89,193 +129,229 @@ function combineRoutes( | ||||||
|   return target; |   return target; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // combine Links of Plugins from source to target
 | function combineRoutes( | ||||||
| function combineMainLinks( |   target: RouteRecordRaw[], | ||||||
|   target: FG_Plugin.PluginMainLink[], |   source: FG_Plugin.NamedRouteRecordRaw[], | ||||||
|   source: FG_Plugin.PluginRouteConfig |   mainPath: '/' | '/in' | ||||||
| ): FG_Plugin.PluginMainLink[] { | ) { | ||||||
|   const targetPluginMainLink: FG_Plugin.PluginMainLink | undefined = target.find( |   // Search parent
 | ||||||
|     (targetPluginMainLink: FG_Plugin.PluginMainLink) => { |   target.forEach((target) => { | ||||||
|       return targetPluginMainLink.title == source.title; |     if (target.path === mainPath) { | ||||||
|     } |       // Parent found = target
 | ||||||
|  |       source.forEach((sourceRoute) => { | ||||||
|  |         // Check if source is already in target
 | ||||||
|  |         const targetRoot = target.children?.find( | ||||||
|  |           (targetRoot) => sourceRoute.path === targetRoot.path | ||||||
|         ); |         ); | ||||||
|   if (targetPluginMainLink) { |         // Already in target routes, add only children
 | ||||||
|     source.children?.forEach((sourcePluginChildLink: FG_Plugin.PluginRouteConfig) => { |         if (targetRoot) { | ||||||
|       targetPluginMainLink.children.push(<FG_Plugin.PluginChildLink>{ |           if (targetRoot.children === undefined) targetRoot.children = []; | ||||||
|         title: sourcePluginChildLink.title, |           targetRoot.children.push(...(sourceRoute.children || [])); | ||||||
|         icon: sourcePluginChildLink.icon, |  | ||||||
|         link: sourcePluginChildLink.route.name, |  | ||||||
|         name: sourcePluginChildLink.route.name, |  | ||||||
|         permissions: sourcePluginChildLink.permissions, |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|         } else { |         } else { | ||||||
|     const mainLink: FG_Plugin.PluginMainLink = <FG_Plugin.PluginMainLink>{ |           // Append to target
 | ||||||
|  |           if (target.children === undefined) target.children = []; | ||||||
|  |           if ( | ||||||
|  |             sourceRoute.children && | ||||||
|  |             sourceRoute.children.length > 0 && | ||||||
|  |             sourceRoute.component === undefined | ||||||
|  |           ) | ||||||
|  |             Object.assign(sourceRoute, { | ||||||
|  |               component: () => import('src/components/navigation/EmptyParent.vue'), | ||||||
|  |             }); | ||||||
|  |           target.children.push(sourceRoute); | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Combine MenuRoutes into Flaschengeist MenuLinks for the main menu | ||||||
|  |  * @param target Flaschengeist list of menu links | ||||||
|  |  * @param source MenuRoutes to combine | ||||||
|  |  */ | ||||||
|  | function combineMenuLinks(target: FG_Plugin.MenuLink[], source: FG_Plugin.MenuRoute) { | ||||||
|  |   let idx = target.findIndex((link) => link.title == source.title); | ||||||
|  |   // Link not found, add new one
 | ||||||
|  |   if (idx === -1) { | ||||||
|  |     idx += target.push({ | ||||||
|       title: source.title, |       title: source.title, | ||||||
|       icon: source.icon, |       icon: source.icon, | ||||||
|       link: source.route.name, |       link: source.route.name, | ||||||
|       name: source.route.name, |  | ||||||
|       permissions: source.permissions, |       permissions: source.permissions, | ||||||
|     }; |     }); | ||||||
|     source.children?.forEach((child) => { |  | ||||||
|       if (mainLink.children === undefined) { |  | ||||||
|         mainLink.children = []; |  | ||||||
|   } |   } | ||||||
|       mainLink.children.push(<FG_Plugin.PluginChildLink>{ |   if (target[idx].children === undefined) { | ||||||
|         title: child.title, |     target[idx].children = []; | ||||||
|         icon: child.icon, |   } | ||||||
|         link: child.route.name, |   source.children?.forEach((sourceChild) => { | ||||||
|         name: child.route.name, |     target[idx].children?.push({ | ||||||
|         permissions: child.permissions, |       title: sourceChild.title, | ||||||
|  |       icon: sourceChild.icon, | ||||||
|  |       link: sourceChild.route.name, | ||||||
|  |       permissions: sourceChild.permissions, | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
|     target.push(mainLink); |  | ||||||
|   } |  | ||||||
|   return target; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function loadShortCuts( | /** | ||||||
|   target: FG_Plugin.ShortCutLink[], |  * Combine shortcuts from Plugin MenuRouts into the Flaschenbeist Shortcut list | ||||||
|   source: FG_Plugin.PluginRouteConfig[] |  * @param target Flaschengeist list of shortcuts | ||||||
| ): FG_Plugin.ShortCutLink[] { |  * @param source MenuRoutes to extract shortcuts from | ||||||
|  |  */ | ||||||
|  | function combineShortcuts(target: FG_Plugin.Shortcut[], source: FG_Plugin.MenuRoute[]) { | ||||||
|   source.forEach((route) => { |   source.forEach((route) => { | ||||||
|     if (route.shortcut) { |     if (route.shortcut) { | ||||||
|       target.push(<FG_Plugin.ShortCutLink>{ |       target.push(<FG_Plugin.Shortcut>{ | ||||||
|         link: route.route.name, |         link: route.route.name, | ||||||
|         icon: route.icon, |         icon: route.icon, | ||||||
|         permissions: route.permissions, |         permissions: route.permissions, | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|     if (route.children) { |     if (route.children) { | ||||||
|       target = loadShortCuts(target, route.children); |       combineShortcuts(target, route.children); | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
|   return target; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // loade plugins
 | /** | ||||||
|  |  * Load a Flaschengeist plugin | ||||||
|  |  * @param loadedPlugins Flaschgeist object | ||||||
|  |  * @param pluginName Plugin to load | ||||||
|  |  * @param context RequireContext of plugins | ||||||
|  |  * @param router VueRouter instance | ||||||
|  |  */ | ||||||
| function loadPlugin( | function loadPlugin( | ||||||
|   loadedPlugins: FG_Plugin.Flaschengeist, |   loadedPlugins: FG_Plugin.Flaschengeist, | ||||||
|   modules: string[], |   pluginName: string, | ||||||
|   backendpromise: Promise<Backend | null>, |   context: __WebpackModuleApi.RequireContext, | ||||||
|   plugins: FG_Plugin.Plugin[], |   backend: Backend | ||||||
|   router: Router | ) { | ||||||
| ): FG_Plugin.Flaschengeist { |   // Check if already loaded
 | ||||||
|   modules.forEach((requiredModule) => { |   if (loadedPlugins.plugins.findIndex((p) => p.name === pluginName) !== -1) return true; | ||||||
|     const plugin = plugins.find((plugin) => { | 
 | ||||||
|       return plugin.name == requiredModule; |   // Search if plugin is installed
 | ||||||
|     }); |   const available = context.keys(); | ||||||
|     if (plugin) { |   const plugin = available.includes(`./${pluginName.toLowerCase()}/plugin.ts`) | ||||||
|       if (plugin.mainRoutes) { |     ? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
 | ||||||
|         loadedPlugins.routes = combineRoutes(loadedPlugins.routes, plugin.mainRoutes, '/main'); |       <FG_Plugin.Plugin>context(`./${pluginName.toLowerCase()}/plugin.ts`).default | ||||||
|         plugin.mainRoutes.forEach((route) => { |     : undefined; | ||||||
|           loadedPlugins.mainLinks = combineMainLinks(loadedPlugins.mainLinks, route); | 
 | ||||||
|         }); |   if (!plugin) { | ||||||
|         loadedPlugins.shortcuts = loadShortCuts(loadedPlugins.shortcuts, plugin.mainRoutes); |     // Plugin is not found, results in an error
 | ||||||
|  |     console.exception(`Could not find required Plugin ${pluginName}`); | ||||||
|  |     return false; | ||||||
|  |   } else { | ||||||
|  |     // Plugin found. Check backend dependencies
 | ||||||
|  |     if ( | ||||||
|  |       !plugin.requiredBackendModules.every((required) => backend.plugins[required] !== undefined) | ||||||
|  |     ) { | ||||||
|  |       console.error(`Plugin ${pluginName}: Backend modules not satisfied`); | ||||||
|  |       return false; | ||||||
|     } |     } | ||||||
|       if (plugin.outRoutes) { | 
 | ||||||
|         loadedPlugins.routes = combineRoutes(loadedPlugins.routes, plugin.outRoutes); |     // Check frontend dependencies
 | ||||||
|         loadedPlugins.shortcutsOut = loadShortCuts(loadedPlugins.shortcutsOut, plugin.outRoutes); |     if ( | ||||||
|  |       !plugin.requiredModules.every((required) => | ||||||
|  |         loadPlugin(loadedPlugins, required, context, backend) | ||||||
|  |       ) | ||||||
|  |     ) { | ||||||
|  |       console.error(`Plugin ${pluginName}: Backend modules not satisfied`); | ||||||
|  |       return false; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     // Start combining and loading routes, shortcuts etc
 | ||||||
|  |     if (plugin.internalRoutes) { | ||||||
|  |       combineRoutes(loadedPlugins.routes, plugin.internalRoutes, '/in'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (plugin.innerRoutes) { | ||||||
|  |       // Routes for Vue Router
 | ||||||
|  |       combineMenuRoutes(loadedPlugins.routes, plugin.innerRoutes, '/in'); | ||||||
|  |       // Combine links for menu
 | ||||||
|  |       plugin.innerRoutes.forEach((route) => combineMenuLinks(loadedPlugins.menuLinks, route)); | ||||||
|  |       // Combine shortcuts
 | ||||||
|  |       combineShortcuts(loadedPlugins.shortcuts, plugin.innerRoutes); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (plugin.outerRoutes) { | ||||||
|  |       combineMenuRoutes(loadedPlugins.routes, plugin.outerRoutes); | ||||||
|  |       combineShortcuts(loadedPlugins.outerShortcuts, plugin.outerRoutes); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     if (plugin.widgets.length > 0) { |     if (plugin.widgets.length > 0) { | ||||||
|       plugin.widgets.forEach((widget) => (widget.name = plugin.name + '_' + widget.name)); |       plugin.widgets.forEach((widget) => (widget.name = plugin.name + '_' + widget.name)); | ||||||
|       Array.prototype.push.apply(loadedPlugins.widgets, plugin.widgets); |       Array.prototype.push.apply(loadedPlugins.widgets, plugin.widgets); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     loadedPlugins.plugins.push({ |     loadedPlugins.plugins.push({ | ||||||
|       name: plugin.name, |       name: plugin.name, | ||||||
|       version: plugin.version, |       version: plugin.version, | ||||||
|     }); |     }); | ||||||
|     } else { | 
 | ||||||
|       console.exception(`Could not find required Plugin ${requiredModule}`); |     return plugin; | ||||||
|       router.push({ name: 'error' }).catch((e) => { |  | ||||||
|         console.warn(e); |  | ||||||
|       }); |  | ||||||
|   } |   } | ||||||
|   }); |  | ||||||
|   return loadedPlugins; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async function getBackend(): Promise<Backend | null> { | /** | ||||||
|   let backend: Backend | null = null; |  * Loading backend information | ||||||
|  |  * @returns Backend object or null | ||||||
|  |  */ | ||||||
|  | async function getBackend() { | ||||||
|   try { |   try { | ||||||
|     const response: AxiosResponse<Backend> = await api.get('/'); |     const { data }: AxiosResponse<Backend> = await api.get('/'); | ||||||
|     backend = response.data; |     return data; | ||||||
|   } catch (e) { |   } catch (e) { | ||||||
|     console.warn(e); |     console.warn(e); | ||||||
|     return null; |     return null; | ||||||
|   } finally { |  | ||||||
|     return backend; |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default boot(({ router, app }) => { | /** | ||||||
|   const plugins: FG_Plugin.Plugin[] = []; |  * Boot file, load all required plugins, check for dependencies | ||||||
|  |  */ | ||||||
|  | export default boot(async ({ router, app }) => { | ||||||
|  |   const backend = await getBackend(); | ||||||
|  |   if (!backend) { | ||||||
|  |     void router.push({ name: 'error' }); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   const backendPromise = getBackend(); |   const loadedPlugins: FG_Plugin.Flaschengeist = { | ||||||
| 
 |  | ||||||
|   let loadedPlugins: FG_Plugin.Flaschengeist = { |  | ||||||
|     routes, |     routes, | ||||||
|     plugins: [], |     plugins: [], | ||||||
|     mainLinks: [], |     menuLinks: [], | ||||||
|     shortcuts: [], |     shortcuts: [], | ||||||
|     shortcutsOut: [], |     outerShortcuts: [], | ||||||
|     widgets: [], |     widgets: [], | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   // get all plugins
 |   // get all plugins
 | ||||||
|   const pluginsContext = require.context('src/plugins', true, /.+\/plugin.ts$/); |   const pluginsContext = require.context('src/plugins', true, /.+\/plugin.ts$/); | ||||||
|   pluginsContext.keys().forEach((fileName: string) => { |  | ||||||
|     // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
 |  | ||||||
|     plugins.push(pluginsContext(fileName).default); |  | ||||||
|   }); |  | ||||||
| 
 | 
 | ||||||
|   // check dependencies
 |   // Start loading plugins
 | ||||||
|   backendPromise |   // Load required modules:
 | ||||||
|     .then((backend) => { |   config.requiredModules.forEach((required) => { | ||||||
|       if (backend) { |     const plugin = loadPlugin(loadedPlugins, required, pluginsContext, backend); | ||||||
|         plugins.forEach((plugin: FG_Plugin.Plugin) => { |     if (!plugin) { | ||||||
|           plugin.requiredModules.forEach((requiredModule: string) => { |       void router.push({ name: 'error' }); | ||||||
|             if ( |       return; | ||||||
|               !( |  | ||||||
|                 config.requiredModules.includes(requiredModule) || |  | ||||||
|                 config.loadModules.includes(requiredModule) |  | ||||||
|               ) |  | ||||||
|             ) { |  | ||||||
|               console.error(`Plugin ${plugin.name} need Plugin ${requiredModule}`); |  | ||||||
|               router.push({ name: 'error' }).catch((e) => { |  | ||||||
|                 console.warn(e); |  | ||||||
|               }); |  | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
|           plugin.requiredBackendModules.forEach((requiredBackendModule: string) => { | 
 | ||||||
|             if (!(requiredBackendModule in backend.plugins)) { |   // Load user defined plugins
 | ||||||
|               console.error( |   config.loadModules.forEach((required) => { | ||||||
|                 `Plugin ${plugin.name} need Plugin ${requiredBackendModule} in backend.` |     const plugin = loadPlugin(loadedPlugins, required, pluginsContext, backend); | ||||||
|               ); |     if (!plugin) { | ||||||
|               router.push({ name: 'error' }).catch((err) => { |       void router.push({ name: 'error' }); | ||||||
|                 console.warn(err); |       return; | ||||||
|               }); |  | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
|         }); |  | ||||||
|       } |  | ||||||
|     }) |  | ||||||
|     .catch((e) => { |  | ||||||
|       console.error(e); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|   // load plugins
 |  | ||||||
|   loadedPlugins = loadPlugin( |  | ||||||
|     loadedPlugins, |  | ||||||
|     config.requiredModules, |  | ||||||
|     backendPromise, |  | ||||||
|     plugins, |  | ||||||
|     router |  | ||||||
|   ); |  | ||||||
|   loadedPlugins = loadPlugin(loadedPlugins, config.loadModules, backendPromise, plugins, router); |  | ||||||
| 
 | 
 | ||||||
|  |   // Sort widgets by priority
 | ||||||
|   loadedPlugins.widgets.sort((a, b) => b.priority - a.priority); |   loadedPlugins.widgets.sort((a, b) => b.priority - a.priority); | ||||||
| 
 | 
 | ||||||
|  |   // Add loaded routes to router
 | ||||||
|   loadedPlugins.routes.forEach((route) => router.addRoute(route)); |   loadedPlugins.routes.forEach((route) => router.addRoute(route)); | ||||||
| 
 | 
 | ||||||
|   // save plugins in VM-variable
 |   // save plugins in VM-variable
 | ||||||
|  |  | ||||||
|  | @ -1,50 +1,35 @@ | ||||||
| <template> | <template> | ||||||
|   <q-item v-if="isGranted" clickable tag="a" target="self" :to="{ name: link }"> |   <q-item v-if="isGranted" clickable tag="a" target="self" :to="{ name: entry.link }"> | ||||||
|     <q-item-section v-if="icon" avatar> |     <q-item-section v-if="entry.icon" avatar> | ||||||
|       <q-icon :name="icon" /> |       <q-icon :name="entry.icon" /> | ||||||
|     </q-item-section> |     </q-item-section> | ||||||
| 
 | 
 | ||||||
|     <q-item-section> |     <q-item-section> | ||||||
|       <q-item-label>{{ title }}</q-item-label> |       <q-item-label>{{ title }}</q-item-label> | ||||||
|       <!--<q-item-label caption> |  | ||||||
|         {{ caption }} |  | ||||||
|       </q-item-label>--> |  | ||||||
|     </q-item-section> |     </q-item-section> | ||||||
|   </q-item> |   </q-item> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { computed, defineComponent } from 'vue'; | import { computed, defineComponent, PropType } from 'vue'; | ||||||
| import { hasPermissions } from 'src/utils/permission'; | import { hasPermissions } from 'src/utils/permission'; | ||||||
|  | import { FG_Plugin } from 'src/plugins'; | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
|   name: 'EssentialLink', |   name: 'EssentialLink', | ||||||
|   props: { |   props: { | ||||||
|     title: { |     entry: { | ||||||
|       type: String, |       type: Object as PropType<FG_Plugin.MenuLink>, | ||||||
|       required: true, |       required: true, | ||||||
|     }, |     }, | ||||||
|     caption: { |  | ||||||
|       type: String, |  | ||||||
|       default: '', |  | ||||||
|     }, |  | ||||||
|     link: { |  | ||||||
|       type: String, |  | ||||||
|       default: 'dashboard', |  | ||||||
|     }, |  | ||||||
|     icon: { |  | ||||||
|       type: String, |  | ||||||
|       default: '', |  | ||||||
|     }, |  | ||||||
|     permissions: { |  | ||||||
|       default: () => Array<string>(), |  | ||||||
|       type: Array as () => Array<string>, |  | ||||||
|     }, |  | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   setup(props) { |   setup(props) { | ||||||
|     const isGranted = computed(() => hasPermissions(props.permissions)); |     const isGranted = computed(() => hasPermissions(props.entry.permissions || [])); | ||||||
|     return { isGranted }; |     const title = computed(() => | ||||||
|  |       typeof props.entry.title === 'object' ? props.entry.title.value : props.entry.title | ||||||
|  |     ); | ||||||
|  |     return { isGranted, title }; | ||||||
|   }, |   }, | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | @ -1,30 +0,0 @@ | ||||||
| <template> |  | ||||||
|   <q-btn v-if="isGranted" flat dense :icon="icon" :to="{ name: link }" /> |  | ||||||
| </template> |  | ||||||
| 
 |  | ||||||
| <script lang="ts"> |  | ||||||
| import { computed, defineComponent } from 'vue'; |  | ||||||
| import { hasPermissions } from 'src/utils/permission'; |  | ||||||
| 
 |  | ||||||
| export default defineComponent({ |  | ||||||
|   name: 'ShortCutLink', |  | ||||||
|   props: { |  | ||||||
|     link: { |  | ||||||
|       required: true, |  | ||||||
|       type: String, |  | ||||||
|     }, |  | ||||||
|     icon: { |  | ||||||
|       required: true, |  | ||||||
|       type: String, |  | ||||||
|     }, |  | ||||||
|     permissions: { |  | ||||||
|       default: undefined, |  | ||||||
|       type: Array as () => Array<string>, |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
|   setup(props) { |  | ||||||
|     const isGranted = computed(() => hasPermissions(props.permissions || [])); |  | ||||||
|     return { isGranted }; |  | ||||||
|   }, |  | ||||||
| }); |  | ||||||
| </script> |  | ||||||
|  | @ -0,0 +1,23 @@ | ||||||
|  | <template> | ||||||
|  |   <q-btn v-if="isGranted" flat dense :icon="shortcut.icon" :to="{ name: shortcut.link }" /> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts"> | ||||||
|  | import { computed, defineComponent, PropType } from 'vue'; | ||||||
|  | import { hasPermissions } from 'src/utils/permission'; | ||||||
|  | import { FG_Plugin } from 'src/plugins'; | ||||||
|  | 
 | ||||||
|  | export default defineComponent({ | ||||||
|  |   name: 'ShortcutLink', | ||||||
|  |   props: { | ||||||
|  |     shortcut: { | ||||||
|  |       required: true, | ||||||
|  |       type: Object as PropType<FG_Plugin.Shortcut>, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   setup(props) { | ||||||
|  |     const isGranted = computed(() => hasPermissions(props.shortcut.permissions || [])); | ||||||
|  |     return { isGranted }; | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | @ -39,7 +39,7 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { computed, defineComponent, PropType } from 'vue'; | import { computed, defineComponent, PropType } from 'vue'; | ||||||
| import { date as q_date } from 'quasar'; | import { date as q_date } from 'quasar'; | ||||||
| import { stringIsDate, stringIsTime, stringIsDateTime } from 'src/utils/validators'; | import { stringIsDate, stringIsTime, stringIsDateTime, Validator } from 'src/utils/validators'; | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
|   name: 'IsoDateInput', |   name: 'IsoDateInput', | ||||||
|  |  | ||||||
|  | @ -2,7 +2,6 @@ | ||||||
|   <q-layout view="hHh lpr lFf"> |   <q-layout view="hHh lpr lFf"> | ||||||
|     <q-header elevated class="bg-primary text-white"> |     <q-header elevated class="bg-primary text-white"> | ||||||
|       <q-toolbar> |       <q-toolbar> | ||||||
|         <!-- Button um Navigationsleiset ein und auszublenden. Nötig bei Desktop? --> |  | ||||||
|         <q-btn |         <q-btn | ||||||
|           v-if="!leftDrawerOpen" |           v-if="!leftDrawerOpen" | ||||||
|           dense |           dense | ||||||
|  | @ -22,15 +21,11 @@ | ||||||
|         </q-toolbar-title> |         </q-toolbar-title> | ||||||
| 
 | 
 | ||||||
|         <!-- Hier kommen die Shortlinks hin --> |         <!-- Hier kommen die Shortlinks hin --> | ||||||
|         <div> |         <shortcut-link | ||||||
|           <short-cut-link |           v-for="(shortcut, index) in shortcuts" | ||||||
|             v-for="(shortcut, index) in flaschengeist.shortcuts" |  | ||||||
|           :key="'shortcut' + index" |           :key="'shortcut' + index" | ||||||
|             :link="shortcut.link" |           :shortcut="shortcut" | ||||||
|             :icon="shortcut.icon" |  | ||||||
|             :permissions="shortcut.permissions" |  | ||||||
|         /> |         /> | ||||||
|         </div> |  | ||||||
|         <q-btn flat round dense icon="mdi-exit-to-app" @click="logout()" /> |         <q-btn flat round dense icon="mdi-exit-to-app" @click="logout()" /> | ||||||
|       </q-toolbar> |       </q-toolbar> | ||||||
|     </q-header> |     </q-header> | ||||||
|  | @ -46,26 +41,17 @@ | ||||||
|       <!-- Plugins --> |       <!-- Plugins --> | ||||||
|       <q-list> |       <q-list> | ||||||
|         <essential-link |         <essential-link | ||||||
|           v-for="(link, index) in flaschengeist.mainLinks" |           v-for="(entry, index) in mainLinks" | ||||||
|           :key="'plugin' + index" |           :key="'plugin' + index" | ||||||
|           :title="typeof link.title === 'object' ? link.title.value : link.title" |           :entry="entry" | ||||||
|           :link="link.link" |  | ||||||
|           :icon="link.icon" |  | ||||||
|           :permissions="link.permissions" |  | ||||||
|         /> |         /> | ||||||
|       </q-list> |  | ||||||
|         <q-separator /> |         <q-separator /> | ||||||
| 
 |  | ||||||
|         <!-- Plugin functions --> |         <!-- Plugin functions --> | ||||||
|       <!-- <router-view name="plugin-nav" /> --> | 
 | ||||||
|       <q-list> |  | ||||||
|         <essential-link |         <essential-link | ||||||
|           v-for="(link, index) in pluginChildLinks" |           v-for="(entry, index) in subLinks" | ||||||
|           :key="'childPlugin' + index" |           :key="'childPlugin' + index" | ||||||
|           :title="link.title" |           :entry="entry" | ||||||
|           :link="link.link" |  | ||||||
|           :icon="link.icon" |  | ||||||
|           :permissions="link.permissions" |  | ||||||
|         /> |         /> | ||||||
|       </q-list> |       </q-list> | ||||||
| 
 | 
 | ||||||
|  | @ -84,12 +70,9 @@ | ||||||
|       <q-separator /> |       <q-separator /> | ||||||
| 
 | 
 | ||||||
|       <essential-link |       <essential-link | ||||||
|         v-for="(link, index) in links" |         v-for="(entry, index) in essentials" | ||||||
|         :key="'main' + index" |         :key="'essential' + index" | ||||||
|         :title="link.title" |         :entry="entry" | ||||||
|         :link="link.link" |  | ||||||
|         :icon="link.icon" |  | ||||||
|         :permissions="link.permissions" |  | ||||||
|       /> |       /> | ||||||
|     </q-drawer> |     </q-drawer> | ||||||
|     <q-page-container> |     <q-page-container> | ||||||
|  | @ -99,54 +82,37 @@ | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import EssentialLink from 'components/navigation/EssentialLink.vue'; | import EssentialLink from 'src/components/navigation/EssentialLink.vue'; | ||||||
| import ShortCutLink from 'components/navigation/ShortCutLink.vue'; | import ShortcutLink from 'src/components/navigation/ShortcutLink.vue'; | ||||||
| import { Screen } from 'quasar'; | import { Screen } from 'quasar'; | ||||||
| import { defineComponent, ref, inject, computed } from 'vue'; | import { defineComponent, ref, inject, computed } from 'vue'; | ||||||
| import { useMainStore } from 'src/store'; | import { useMainStore } from 'src/store'; | ||||||
| import { FG_Plugin } from 'src/plugins'; | import { FG_Plugin } from 'src/plugins'; | ||||||
| import { useRoute, useRouter } from 'vue-router'; | import { useRouter } from 'vue-router'; | ||||||
| 
 | 
 | ||||||
| const links = [ | const essentials: FG_Plugin.MenuLink[] = [ | ||||||
|   { |   { | ||||||
|     name: 'about', |  | ||||||
|     title: 'Über Flaschengeist', |     title: 'Über Flaschengeist', | ||||||
|     link: 'about', |     link: 'about', | ||||||
|     icon: 'mdi-information', |     icon: 'mdi-information', | ||||||
|   }, |   }, | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
| const shortcuts = [ |  | ||||||
|   { |  | ||||||
|     link: 'about', |  | ||||||
|     icon: 'mdi-information', |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     link: 'user', |  | ||||||
|     icon: 'mdi-account', |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     link: 'user-plugin1', |  | ||||||
|     icon: 'mdi-account-plus', |  | ||||||
|   }, |  | ||||||
| ]; |  | ||||||
| 
 |  | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
|   name: 'MainLayout', |   name: 'MainLayout', | ||||||
|   components: { EssentialLink, ShortCutLink }, |   components: { EssentialLink, ShortcutLink }, | ||||||
|   setup() { |   setup() { | ||||||
|     const route = useRoute(); |  | ||||||
|     const router = useRouter(); |     const router = useRouter(); | ||||||
|     const mainStore = useMainStore(); |     const mainStore = useMainStore(); | ||||||
|     const flaschengeist = inject<FG_Plugin.Flaschengeist>('flaschengeist'); |     const flaschengeist = inject<FG_Plugin.Flaschengeist>('flaschengeist'); | ||||||
|     const leftDrawer = ref(false); |     const leftDrawer = ref(false); | ||||||
|  |     const shortcuts = flaschengeist?.shortcuts; | ||||||
|  |     const mainLinks = flaschengeist?.menuLinks; | ||||||
| 
 | 
 | ||||||
|     const leftDrawerOpen = ref( |     const leftDrawerOpen = computed({ | ||||||
|       computed({ |  | ||||||
|       get: () => (leftDrawer.value || Screen.gt.sm ? true : false), |       get: () => (leftDrawer.value || Screen.gt.sm ? true : false), | ||||||
|       set: (val: boolean) => (leftDrawer.value = val), |       set: (val: boolean) => (leftDrawer.value = val), | ||||||
|       }) |     }); | ||||||
|     ); |  | ||||||
|     const leftDrawerMini = ref(false); |     const leftDrawerMini = ref(false); | ||||||
| 
 | 
 | ||||||
|     function leftDrawerClicker() { |     function leftDrawerClicker() { | ||||||
|  | @ -155,20 +121,9 @@ export default defineComponent({ | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const pluginChildLinks = computed(() => { |     const subLinks = computed(() => { | ||||||
|       const link: FG_Plugin.PluginMainLink | undefined = flaschengeist?.mainLinks.find( |       const matched = router.currentRoute.value.matched[1]; | ||||||
|         (plugin: FG_Plugin.PluginMainLink) => { |       return flaschengeist?.menuLinks.find((link) => matched.name == link.link)?.children; | ||||||
|           if (route.matched.length > 1) { |  | ||||||
|             return plugin.name == route.matched[1].name; |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|       ); |  | ||||||
| 
 |  | ||||||
|       if (link == undefined) { |  | ||||||
|         return []; |  | ||||||
|       } else { |  | ||||||
|         return link.children; |  | ||||||
|       } |  | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     function logout() { |     function logout() { | ||||||
|  | @ -177,14 +132,14 @@ export default defineComponent({ | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return { |     return { | ||||||
|  |       essentials, | ||||||
|       leftDrawerOpen, |       leftDrawerOpen, | ||||||
|       leftDrawerMini, |       leftDrawerMini, | ||||||
|       leftDrawerClicker, |       leftDrawerClicker, | ||||||
|       links, |  | ||||||
|       pluginChildLinks, |  | ||||||
|       shortcuts, |  | ||||||
|       logout, |       logout, | ||||||
|       flaschengeist, |       mainLinks, | ||||||
|  |       shortcuts, | ||||||
|  |       subLinks, | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -8,30 +8,13 @@ | ||||||
|           </q-avatar> |           </q-avatar> | ||||||
|           <span class="gt-xs"> Flaschengeist </span> |           <span class="gt-xs"> Flaschengeist </span> | ||||||
|         </q-toolbar-title> |         </q-toolbar-title> | ||||||
|         <div> |         <shortcut-link | ||||||
|           <short-cut-link |  | ||||||
|           v-for="(shortcut, index) in shortcuts" |           v-for="(shortcut, index) in shortcuts" | ||||||
|           :key="'shortcut' + index" |           :key="'shortcut' + index" | ||||||
|             :link="shortcut.link" |           :shortcut="shortcut" | ||||||
|             :icon="shortcut.icon" |  | ||||||
|           /> |  | ||||||
|         </div> |  | ||||||
|         <q-btn |  | ||||||
|           v-if="$route.name != 'about_out'" |  | ||||||
|           flat |  | ||||||
|           round |  | ||||||
|           dense |  | ||||||
|           icon="mdi-information" |  | ||||||
|           @click="$router.push({ name: 'about_out' })" |  | ||||||
|         /> |  | ||||||
|         <q-btn |  | ||||||
|           v-if="$route.name != 'login'" |  | ||||||
|           flat |  | ||||||
|           round |  | ||||||
|           dense |  | ||||||
|           icon="mdi-login-variant" |  | ||||||
|           @click="$router.push({ name: 'login' })" |  | ||||||
|         /> |         /> | ||||||
|  |         <shortcut-link v-if="$route.name != 'about_out'" :shortcut="about" /> | ||||||
|  |         <shortcut-link v-if="$route.name != 'login'" :shortcut="login" /> | ||||||
|       </q-toolbar> |       </q-toolbar> | ||||||
|     </q-header> |     </q-header> | ||||||
| 
 | 
 | ||||||
|  | @ -44,14 +27,18 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import { FG_Plugin } from 'src/plugins'; | import { FG_Plugin } from 'src/plugins'; | ||||||
| import { defineComponent, inject } from 'vue'; | import { defineComponent, inject } from 'vue'; | ||||||
| import ShortCutLink from 'components/navigation/ShortCutLink.vue'; | import ShortcutLink from 'components/navigation/ShortcutLink.vue'; | ||||||
| 
 | 
 | ||||||
| export default defineComponent({ | export default defineComponent({ | ||||||
|   name: 'OutLayout', |   name: 'OutLayout', | ||||||
|   components: { ShortCutLink }, |   components: { ShortcutLink }, | ||||||
|   setup() { |   setup() { | ||||||
|     const shortcuts = inject<FG_Plugin.Flaschengeist>('flaschengeist')?.shortcutsOut || []; |     const flaschengeist = inject<FG_Plugin.Flaschengeist>('flaschengeist'); | ||||||
|     return { shortcuts }; |     const shortcuts = flaschengeist?.outerShortcuts || []; | ||||||
|  |     const about: FG_Plugin.Shortcut = { icon: 'mdi-information', link: 'about_out' }; | ||||||
|  |     const login: FG_Plugin.Shortcut = { icon: 'mdi-login-variant', link: 'login' }; | ||||||
|  | 
 | ||||||
|  |     return { about, login, shortcuts }; | ||||||
|   }, |   }, | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|  | @ -1,66 +1,100 @@ | ||||||
| import { RouteRecordRaw } from 'vue-router'; | import { RouteRecordRaw, RouteRecordName } from 'vue-router'; | ||||||
| import { Component, ComputedRef } from 'vue'; | import { Component, ComputedRef } from 'vue'; | ||||||
| 
 | 
 | ||||||
| declare global { | declare namespace FG_Plugin { | ||||||
|   type Validator = (value: unknown) => boolean | string; |   /** | ||||||
|  |    * Interface defining a Flaschengeist plugin | ||||||
|  |    */ | ||||||
|  |   interface Plugin { | ||||||
|  |     name: string; | ||||||
|  |     version: string; | ||||||
|  |     widgets: Widget[]; | ||||||
|  |     /** Pther frontend modules needed for this plugin to work correctly */ | ||||||
|  |     requiredModules: string[]; | ||||||
|  |     /** Backend modules needed for this plugin to work correctly */ | ||||||
|  |     requiredBackendModules: string[]; | ||||||
|  |     /** Menu entries for authenticated users */ | ||||||
|  |     innerRoutes?: MenuRoute[]; | ||||||
|  |     /** Public menu entries (without authentification) */ | ||||||
|  |     outerRoutes?: MenuRoute[]; | ||||||
|  |     /** Routes without menu links, for internal usage */ | ||||||
|  |     internalRoutes?: NamedRouteRecordRaw[]; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| declare namespace FG_Plugin { |   /** | ||||||
|   interface ShortCutLink { |    * Defines the loaded state of the Flaschengeist | ||||||
|     link: string; |    */ | ||||||
|  |   interface Flaschengeist { | ||||||
|  |     /** All loaded plugins */ | ||||||
|  |     plugins: LoadedPlugin[]; | ||||||
|  |     /** All routes, combined from all plugins */ | ||||||
|  |     routes: RouteRecordRaw[]; | ||||||
|  |     /** All menu entries */ | ||||||
|  |     menuLinks: MenuLink[]; | ||||||
|  |     /** All inner shortcuts */ | ||||||
|  |     shortcuts: Shortcut[]; | ||||||
|  |     /** All outer shortcuts */ | ||||||
|  |     outerShortcuts: Shortcut[]; | ||||||
|  |     /** All widgets */ | ||||||
|  |     widgets: Widget[]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Loaded Flaschengeist plugin | ||||||
|  |    */ | ||||||
|  |   interface LoadedPlugin { | ||||||
|  |     name: string; | ||||||
|  |     version: string; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Defines a shortcut link | ||||||
|  |    */ | ||||||
|  |   interface Shortcut { | ||||||
|  |     link: RouteRecordName; | ||||||
|     icon: string; |     icon: string; | ||||||
|     permissions?: string[]; |     permissions?: string[]; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   interface PluginRouteConfig { |   /** | ||||||
|  |    * Defines a main menu entry along with the route | ||||||
|  |    * Used when defining a plugin | ||||||
|  |    */ | ||||||
|  |   interface MenuRoute extends MenuEntry { | ||||||
|  |     route: NamedRouteRecordRaw; | ||||||
|  |     shortcut?: boolean; | ||||||
|  |     children?: this[]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   type NamedRouteRecordRaw = RouteRecordRaw & { | ||||||
|  |     name: RouteRecordName; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Defines a menu entry in the main menu | ||||||
|  |    */ | ||||||
|  |   interface MenuLink extends MenuEntry { | ||||||
|  |     /** Name of the target route */ | ||||||
|  |     link: RouteRecordName; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Base interface for internal use | ||||||
|  |    */ | ||||||
|  |   interface MenuEntry { | ||||||
|     title: string | ComputedRef<string>; |     title: string | ComputedRef<string>; | ||||||
|     icon: string; |     icon: string; | ||||||
|     route: RouteRecordRaw; |  | ||||||
|     shortcut?: boolean; |  | ||||||
|     children?: PluginRouteConfig[]; |  | ||||||
|     permissions?: string[]; |     permissions?: string[]; | ||||||
|  |     children?: this[]; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /** | ||||||
|  |    * Widget object for the dashboard | ||||||
|  |    */ | ||||||
|   interface Widget { |   interface Widget { | ||||||
|     name: string; |     name: string; | ||||||
|     priority: number; |     priority: number; | ||||||
|     permissions: FG.Permission[]; |     permissions: FG.Permission[]; | ||||||
|     widget: Component; |     widget: Component; | ||||||
|   } |   } | ||||||
| 
 |  | ||||||
|   interface Plugin { |  | ||||||
|     name: string; |  | ||||||
|     version: string; |  | ||||||
|     widgets: Widget[]; |  | ||||||
|     requiredModules: string[]; |  | ||||||
|     requiredBackendModules: string[]; |  | ||||||
|     mainRoutes?: PluginRouteConfig[]; |  | ||||||
|     outRoutes?: PluginRouteConfig[]; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   interface PluginMainLink extends PluginChildLink { |  | ||||||
|     children: PluginChildLink[]; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   interface PluginChildLink { |  | ||||||
|     name: string; |  | ||||||
|     title: string; |  | ||||||
|     link: string; |  | ||||||
|     icon: string; |  | ||||||
|     permissions?: string[]; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   interface LoadedPlugin { |  | ||||||
|     name: string; |  | ||||||
|     version: string; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   interface Flaschengeist { |  | ||||||
|     plugins: LoadedPlugin[]; |  | ||||||
|     routes: RouteRecordRaw[]; |  | ||||||
|     mainLinks: PluginMainLink[]; |  | ||||||
|     shortcuts: ShortCutLink[]; |  | ||||||
|     shortcutsOut: ShortCutLink[]; |  | ||||||
|     widgets: Widget[]; |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import { defineAsyncComponent } from 'vue'; | ||||||
| 
 | 
 | ||||||
| const plugin: FG_Plugin.Plugin = { | const plugin: FG_Plugin.Plugin = { | ||||||
|   name: 'Balance', |   name: 'Balance', | ||||||
|   mainRoutes: routes, |   innerRoutes: routes, | ||||||
|   requiredModules: ['User'], |   requiredModules: ['User'], | ||||||
|   requiredBackendModules: ['balance'], |   requiredBackendModules: ['balance'], | ||||||
|   version: '0.0.2', |   version: '0.0.2', | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import { FG_Plugin } from 'src/plugins'; | import { FG_Plugin } from 'src/plugins'; | ||||||
| import permissions from '../permissions'; | import permissions from '../permissions'; | ||||||
| 
 | 
 | ||||||
| const mainRoutes: FG_Plugin.PluginRouteConfig[] = [ | const mainRoutes: FG_Plugin.MenuRoute[] = [ | ||||||
|   { |   { | ||||||
|     title: 'Gerücht', |     title: 'Gerücht', | ||||||
|     icon: 'mdi-cash-100', |     icon: 'mdi-cash-100', | ||||||
|  |  | ||||||
|  | @ -1,21 +1,13 @@ | ||||||
| import routes from './routes'; | import { innerRoutes } from './routes'; | ||||||
| import { FG_Plugin } from 'src/plugins'; | import { FG_Plugin } from 'src/plugins'; | ||||||
| 
 | 
 | ||||||
| const plugin: FG_Plugin.Plugin = { | const plugin: FG_Plugin.Plugin = { | ||||||
|   name: 'Pricelist', |   name: 'Pricelist', | ||||||
|   mainRoutes: routes, |   innerRoutes, | ||||||
|   requiredModules: [], |   requiredModules: [], | ||||||
|   requiredBackendModules: ['pricelist'], |   requiredBackendModules: ['pricelist'], | ||||||
|   version: '0.0.1', |   version: '0.0.1', | ||||||
|   widgets: [], |   widgets: [], | ||||||
|   // widgets: [
 |  | ||||||
|   //   {
 |  | ||||||
|   //     priority: 1,
 |  | ||||||
|   //     name: 'greeting',
 |  | ||||||
|   //     permissions: []
 |  | ||||||
|   //     widget: () => import('./components/Widget.vue')
 |  | ||||||
|   //   }
 |  | ||||||
|   // ]
 |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default plugin; | export default plugin; | ||||||
|  |  | ||||||
|  | @ -1,12 +1,13 @@ | ||||||
| import { FG_Plugin } from 'src/plugins'; | import { FG_Plugin } from 'src/plugins'; | ||||||
| const mainRoutes: FG_Plugin.PluginRouteConfig[] = [ | 
 | ||||||
|  | export const innerRoutes: FG_Plugin.MenuRoute[] = [ | ||||||
|   { |   { | ||||||
|     title: 'Getränke', |     title: 'Getränke', | ||||||
|     icon: 'mdi-glass-mug-variant', |     icon: 'mdi-glass-mug-variant', | ||||||
|     route: { |     route: { | ||||||
|       path: 'drinks', |       path: 'drinks', | ||||||
|       name: 'drinks', |       name: 'drinks', | ||||||
|       redirect: { name: 'drinks-pricelist' } |       redirect: { name: 'drinks-pricelist' }, | ||||||
|     }, |     }, | ||||||
|     permissions: ['user'], |     permissions: ['user'], | ||||||
|     children: [ |     children: [ | ||||||
|  | @ -18,8 +19,8 @@ const mainRoutes: FG_Plugin.PluginRouteConfig[] = [ | ||||||
|         route: { |         route: { | ||||||
|           path: 'pricelist', |           path: 'pricelist', | ||||||
|           name: 'drinks-pricelist', |           name: 'drinks-pricelist', | ||||||
|           component: () => import('../pages/PricelistP.vue') |           component: () => import('../pages/PricelistP.vue'), | ||||||
|         } |         }, | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         title: 'Einstellungen', |         title: 'Einstellungen', | ||||||
|  | @ -29,11 +30,9 @@ const mainRoutes: FG_Plugin.PluginRouteConfig[] = [ | ||||||
|         route: { |         route: { | ||||||
|           path: 'settings', |           path: 'settings', | ||||||
|           name: 'drinks-settings', |           name: 'drinks-settings', | ||||||
|           component: () => import('../pages/Settings.vue') |           component: () => import('../pages/Settings.vue'), | ||||||
|         } |         }, | ||||||
|       } |       }, | ||||||
|     ] |     ], | ||||||
|   } |   }, | ||||||
| ]; | ]; | ||||||
| 
 |  | ||||||
| export default mainRoutes; |  | ||||||
|  |  | ||||||
|  | @ -1,10 +1,11 @@ | ||||||
| import { defineAsyncComponent } from 'vue'; | import { defineAsyncComponent } from 'vue'; | ||||||
| import mainRoutes from './routes'; | import { innerRoutes, privateRoutes } from './routes'; | ||||||
| import { FG_Plugin } from 'src/plugins'; | import { FG_Plugin } from 'src/plugins'; | ||||||
| 
 | 
 | ||||||
| const plugin: FG_Plugin.Plugin = { | const plugin: FG_Plugin.Plugin = { | ||||||
|   name: 'Schedule', |   name: 'Schedule', | ||||||
|   mainRoutes, |   innerRoutes, | ||||||
|  |   internalRoutes: privateRoutes, | ||||||
|   requiredModules: ['User'], |   requiredModules: ['User'], | ||||||
|   requiredBackendModules: ['events'], |   requiredBackendModules: ['events'], | ||||||
|   version: '0.0.1', |   version: '0.0.1', | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import { FG_Plugin } from 'src/plugins'; | import { FG_Plugin } from 'src/plugins'; | ||||||
| import { PERMISSIONS } from '../permissions'; | import { PERMISSIONS } from '../permissions'; | ||||||
| 
 | 
 | ||||||
| const mainRoutes: FG_Plugin.PluginRouteConfig[] = [ | export const innerRoutes: FG_Plugin.MenuRoute[] = [ | ||||||
|   { |   { | ||||||
|     title: 'Dienste', |     title: 'Dienste', | ||||||
|     icon: 'mdi-briefcase', |     icon: 'mdi-briefcase', | ||||||
|  | @ -47,4 +47,10 @@ const mainRoutes: FG_Plugin.PluginRouteConfig[] = [ | ||||||
|   }, |   }, | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
| export default mainRoutes; | export const privateRoutes: FG_Plugin.NamedRouteRecordRaw[] = [ | ||||||
|  |   { | ||||||
|  |     name: 'events-edit', | ||||||
|  |     path: 'schedule/edit/:id', | ||||||
|  |     redirect: { name: 'schedule-overview' }, | ||||||
|  |   }, | ||||||
|  | ]; | ||||||
|  |  | ||||||
|  | @ -1,41 +0,0 @@ | ||||||
| <template> |  | ||||||
|   <div> |  | ||||||
|     <q-page v-if="checkMain" padding> |  | ||||||
|       <q-card> |  | ||||||
|         <q-card-section> |  | ||||||
|           <q-list v-for="(mainRoute, index) in mainRoutes" :key="'mainRoute' + index"> |  | ||||||
|             <essential-link |  | ||||||
|               v-for="(route, index2) in mainRoute.children" |  | ||||||
|               :key="'route' + index2" |  | ||||||
|               :title="route.title" |  | ||||||
|               :icon="route.icon" |  | ||||||
|               :link="route.name" |  | ||||||
|               :permissions="route.permissions" |  | ||||||
|             /> |  | ||||||
|           </q-list> |  | ||||||
|         </q-card-section> |  | ||||||
|       </q-card> |  | ||||||
|     </q-page> |  | ||||||
|     <router-view /> |  | ||||||
|   </div> |  | ||||||
| </template> |  | ||||||
| 
 |  | ||||||
| <script lang="ts"> |  | ||||||
| import { useRoute } from 'vue-router'; |  | ||||||
| import { computed, defineComponent } from 'vue'; |  | ||||||
| import mainRoutes from 'src/plugins/user/routes'; |  | ||||||
| import EssentialLink from 'src/components/navigation/EssentialLink.vue'; |  | ||||||
| 
 |  | ||||||
| export default defineComponent({ |  | ||||||
|   // name: 'PageName' |  | ||||||
|   components: { EssentialLink }, |  | ||||||
|   setup() { |  | ||||||
|     const route = useRoute(); |  | ||||||
| 
 |  | ||||||
|     const checkMain = computed(() => { |  | ||||||
|       return route.matched.length == 2; |  | ||||||
|     }); |  | ||||||
|     return { checkMain, mainRoutes }; |  | ||||||
|   }, |  | ||||||
| }); |  | ||||||
| </script> |  | ||||||
|  | @ -4,7 +4,7 @@ import { defineAsyncComponent } from 'vue'; | ||||||
| 
 | 
 | ||||||
| const plugin: FG_Plugin.Plugin = { | const plugin: FG_Plugin.Plugin = { | ||||||
|   name: 'User', |   name: 'User', | ||||||
|   mainRoutes: routes, |   innerRoutes: routes, | ||||||
|   requiredModules: [], |   requiredModules: [], | ||||||
|   requiredBackendModules: ['auth'], |   requiredBackendModules: ['auth'], | ||||||
|   version: '0.0.1', |   version: '0.0.1', | ||||||
|  |  | ||||||
|  | @ -2,14 +2,14 @@ import { FG_Plugin } from 'src/plugins'; | ||||||
| import { useMainStore } from 'src/store'; | import { useMainStore } from 'src/store'; | ||||||
| import { computed } from 'vue'; | import { computed } from 'vue'; | ||||||
| 
 | 
 | ||||||
| const mainRoutes: FG_Plugin.PluginRouteConfig[] = [ | const mainRoutes: FG_Plugin.MenuRoute[] = [ | ||||||
|   { |   { | ||||||
|     get title() { |     get title() { | ||||||
|       return computed(() => useMainStore().user?.display_name || 'Not loaded'); |       return computed(() => useMainStore().currentUser.display_name); | ||||||
|     }, |     }, | ||||||
|     icon: 'mdi-account', |     icon: 'mdi-account', | ||||||
|     permissions: ['user'], |     permissions: ['user'], | ||||||
|     route: { path: 'user', name: 'user', component: () => import('../pages/MainPage.vue') }, |     route: { path: 'user', name: 'user', redirect: { name: 'user-settings' } }, | ||||||
|     children: [ |     children: [ | ||||||
|       { |       { | ||||||
|         title: 'Einstellungen', |         title: 'Einstellungen', | ||||||
|  |  | ||||||
|  | @ -24,7 +24,7 @@ const routes: RouteRecordRaw[] = [ | ||||||
|     ], |     ], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     path: '/main', |     path: '/in', | ||||||
|     redirect: 'dashboard', |     redirect: 'dashboard', | ||||||
|     component: () => import('layouts/MainLayout.vue'), |     component: () => import('layouts/MainLayout.vue'), | ||||||
|     meta: { permissions: ['user'] }, |     meta: { permissions: ['user'] }, | ||||||
|  |  | ||||||
|  | @ -4,11 +4,3 @@ declare module '*.vue' { | ||||||
|   const component: ComponentOptions; |   const component: ComponentOptions; | ||||||
|   export default component; |   export default component; | ||||||
| } | } | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| // Mocks all files ending in `.vue` showing them as plain Vue instances
 |  | ||||||
| declare module '*.vue' { |  | ||||||
|   import Vue from 'vue'; |  | ||||||
|   export default Vue; |  | ||||||
| } |  | ||||||
| */ |  | ||||||
|  |  | ||||||
|  | @ -1,3 +1,5 @@ | ||||||
|  | export type Validator = (value: unknown) => boolean | string; | ||||||
|  | 
 | ||||||
| export function notEmpty(val: unknown) { | export function notEmpty(val: unknown) { | ||||||
|   return !!val || 'Feld darf nicht leer sein!'; |   return !!val || 'Feld darf nicht leer sein!'; | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue