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 (