diff --git a/app/src/examples/example3/Example3.tsx b/app/src/examples/example3/Example3.tsx index 7441c3c8..425b7bfc 100644 --- a/app/src/examples/example3/Example3.tsx +++ b/app/src/examples/example3/Example3.tsx @@ -7,8 +7,16 @@ export const Example3: React.FunctionComponent = () => ( Example 3 This is another example plugin that also demonstrates the addition of custom components to the main header - toolbar. Components should be added in the Plugin structure using the `headerItems` array. Toolbar components - should be created as single FunctionComponents and added to the array. + toolbar. + + + Components should be added in to the Plugin structure using the `headerItems` array. Toolbar components should + be created as single FunctionComponents and added to the array. + + + Header components will remain in the toolbar until the focus is changed to an alternative plugin. However, + should you wish to persist the components despite the UI focus then an alternative structure can be added to the + `headerItems` array in the form {component: 'MyComponent', universal: true}. diff --git a/app/src/examples/example3/ToolbarItemComp.tsx b/app/src/examples/example3/ToolbarItemComp.tsx index 70966fe9..6c7b7d78 100644 --- a/app/src/examples/example3/ToolbarItemComp.tsx +++ b/app/src/examples/example3/ToolbarItemComp.tsx @@ -27,7 +27,7 @@ export const ToolbarItemComp1: React.FunctionComponent = () => { , ]} > - Hello World! + Hello World! I am part of the Example3 plugin ) @@ -81,7 +81,7 @@ export const ToolbarItemComp2: React.FunctionComponent = () => { onSelect={onSelect} toggle={ - Dropdown + Plugin Example 3 Dropdown } isOpen={isOpen} diff --git a/app/src/examples/example3/index.ts b/app/src/examples/example3/index.ts index 10a56a2b..8938955e 100644 --- a/app/src/examples/example3/index.ts +++ b/app/src/examples/example3/index.ts @@ -8,7 +8,7 @@ export const registerExample3: HawtioPlugin = () => { title: 'Example 3', path: '/example3', component: Example3, - headerItems: [ToolbarItemComp1, ToolbarItemComp2], + headerItems: [ToolbarItemComp1, { component: ToolbarItemComp2, universal: true }], isActive: async () => true, }) } diff --git a/packages/hawtio/src/core/core.ts b/packages/hawtio/src/core/core.ts index e3accaa2..726697d9 100644 --- a/packages/hawtio/src/core/core.ts +++ b/packages/hawtio/src/core/core.ts @@ -3,6 +3,36 @@ import $ from 'jquery' import { eventService } from './event-service' import { log } from './globals' +/* + * Components to be added to the header navbar + * Can define either a single component type or + * a component with a universal property. + * + * By default, components will only be displayed + * if the plugin UI is also visible. However, setting + * universal to 'true' will ensure the component + * remains displayed regardless of which plugin is + * given focus. + */ +export interface UniversalHeaderItem { + // The components that should be populated as + // dropdown items on the header bar + // eslint-disable-next-line @typescript-eslint/no-explicit-any + component: React.ComponentType + + // Should components remain visible on header even when + // the plugin is not being displayed. + universal: boolean +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type HeaderItem = React.ComponentType | UniversalHeaderItem + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function isUniversalHeaderItem(obj: any): obj is UniversalHeaderItem { + return typeof obj.universal === 'boolean' +} + /** * Internal representation of a Hawtio plugin. */ @@ -13,8 +43,7 @@ export interface Plugin { // eslint-disable-next-line @typescript-eslint/no-explicit-any component: React.ComponentType - // eslint-disable-next-line @typescript-eslint/no-explicit-any - headerItems?: React.ComponentType[] + headerItems?: HeaderItem[] /** * Returns if this plugin should be activated. diff --git a/packages/hawtio/src/ui/page/HawtioHeader.tsx b/packages/hawtio/src/ui/page/HawtioHeader.tsx index 657a4068..09780ce7 100644 --- a/packages/hawtio/src/ui/page/HawtioHeader.tsx +++ b/packages/hawtio/src/ui/page/HawtioHeader.tsx @@ -1,5 +1,5 @@ import { PUBLIC_USER, userService } from '@hawtiosrc/auth' -import { DEFAULT_APP_NAME, useHawtconfig, Plugin } from '@hawtiosrc/core' +import { DEFAULT_APP_NAME, useHawtconfig, UniversalHeaderItem, isUniversalHeaderItem } from '@hawtiosrc/core' import { hawtioLogo, userAvatar } from '@hawtiosrc/img' import { preferencesService } from '@hawtiosrc/preferences/preferences-service' import { HawtioAbout } from '@hawtiosrc/ui/about' @@ -105,23 +105,50 @@ const HawtioHeaderToolbar: React.FunctionComponent = () => { userItems.pop() } - /* - * Determine which plugin is currently displaying - * based on the path of the current location - */ - const pluginFromLocation = (): Plugin | null => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const collectHeaderItems = (): React.ComponentType[] => { const path = location.pathname - return plugins.find(plugin => path.startsWith(plugin.path)) ?? null + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const components: React.ComponentType[] = [] + + // Iterate through the plugins ... + plugins.forEach(plugin => { + if (!plugin.headerItems || plugin.headerItems.length === 0) return // no header items in plugin + + // if plugin is currently visible in UI + if (path.startsWith(plugin.path)) { + components.push( + ...plugin.headerItems.map(headerItem => + isUniversalHeaderItem(headerItem) ? headerItem.component : headerItem, + ), + ) + return + } + + components.push( + ...plugin.headerItems + .filter( + headerItem => isUniversalHeaderItem(headerItem) && (headerItem as UniversalHeaderItem).universal === true, + ) + .map(headerItem => (headerItem as UniversalHeaderItem).component), + ) + }) + + return components } - const plugin = pluginFromLocation() + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const headerComponents: React.ComponentType[] = collectHeaderItems() return ( - {plugin?.headerItems?.map((comp, index) => ( - {React.createElement(comp)} + {headerComponents.map((component, index) => ( + + {React.createElement(component)} + ))}