From bf4c67b68a4dc7448f96d08ae92b48ba11fca7c9 Mon Sep 17 00:00:00 2001
From: phantomjinx
Date: Wed, 6 Sep 2023 17:33:38 +0100
Subject: [PATCH] feature: Ability to add custom toolbar items to main page nav
bar
* core.ts
* Adds optional toolbarItems property to plugin interface
* HawtioHeader.tsx
* Picks up the plugins from the context and find the plugin that is
currently being displayed. If this plugin has a toolbar item property
then pick up the components into a custom toolbar group
---
app/src/examples/example3/Example3.tsx | 7 +-
app/src/examples/example3/ToolbarItemComp.tsx | 91 +++++++++++++++++++
app/src/examples/example3/index.ts | 2 +
packages/hawtio/src/core/core.ts | 3 +
packages/hawtio/src/ui/page/HawtioHeader.tsx | 23 ++++-
5 files changed, 122 insertions(+), 4 deletions(-)
create mode 100644 app/src/examples/example3/ToolbarItemComp.tsx
diff --git a/app/src/examples/example3/Example3.tsx b/app/src/examples/example3/Example3.tsx
index 475dfd94a..450088681 100644
--- a/app/src/examples/example3/Example3.tsx
+++ b/app/src/examples/example3/Example3.tsx
@@ -5,7 +5,12 @@ export const Example3: React.FunctionComponent = () => (
Example 3
- This is yet another example plugin.
+
+ 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.
+
)
diff --git a/app/src/examples/example3/ToolbarItemComp.tsx b/app/src/examples/example3/ToolbarItemComp.tsx
new file mode 100644
index 000000000..70966fe9f
--- /dev/null
+++ b/app/src/examples/example3/ToolbarItemComp.tsx
@@ -0,0 +1,91 @@
+import React from 'react'
+import { Button, Dropdown, DropdownItem, DropdownSeparator, DropdownToggle, Modal } from '@patternfly/react-core'
+
+export const ToolbarItemComp1: React.FunctionComponent = () => {
+ const [isModalOpen, setIsModalOpen] = React.useState(false)
+
+ const handleModalToggle = () => {
+ setIsModalOpen(!isModalOpen)
+ }
+
+ return (
+
+
+
+
+ Confirm
+ ,
+ ,
+ ]}
+ >
+ Hello World!
+
+
+ )
+}
+
+export const ToolbarItemComp2: React.FunctionComponent = () => {
+ const [isOpen, setIsOpen] = React.useState(false)
+
+ const onToggle = (isOpen: boolean) => {
+ setIsOpen(isOpen)
+ }
+
+ const onFocus = () => {
+ const element = document.getElementById('toggle-basic')
+ element?.focus()
+ }
+
+ const onSelect = () => {
+ setIsOpen(false)
+ onFocus()
+ }
+
+ const dropdownItems = [
+
+ Link
+ ,
+
+ Action
+ ,
+
+ Disabled link
+ ,
+
+ Disabled action
+ ,
+ ,
+ Separated link,
+
+ Separated action
+ ,
+ ]
+
+ return (
+
+ Dropdown
+
+ }
+ isOpen={isOpen}
+ dropdownItems={dropdownItems}
+ />
+ )
+}
diff --git a/app/src/examples/example3/index.ts b/app/src/examples/example3/index.ts
index c154a57dc..10a56a2b6 100644
--- a/app/src/examples/example3/index.ts
+++ b/app/src/examples/example3/index.ts
@@ -1,5 +1,6 @@
import { hawtio, HawtioPlugin } from '@hawtio/react'
import { Example3 } from './Example3'
+import { ToolbarItemComp1, ToolbarItemComp2 } from './ToolbarItemComp'
export const registerExample3: HawtioPlugin = () => {
hawtio.addPlugin({
@@ -7,6 +8,7 @@ export const registerExample3: HawtioPlugin = () => {
title: 'Example 3',
path: '/example3',
component: Example3,
+ headerItems: [ToolbarItemComp1, ToolbarItemComp2],
isActive: async () => true,
})
}
diff --git a/packages/hawtio/src/core/core.ts b/packages/hawtio/src/core/core.ts
index 025588e5e..e3accaa2e 100644
--- a/packages/hawtio/src/core/core.ts
+++ b/packages/hawtio/src/core/core.ts
@@ -13,6 +13,9 @@ 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[]
+
/**
* Returns if this plugin should be activated.
* This method needs to return a promise as the process of resolving if a plugin
diff --git a/packages/hawtio/src/ui/page/HawtioHeader.tsx b/packages/hawtio/src/ui/page/HawtioHeader.tsx
index e96a2d4aa..657a4068d 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 } from '@hawtiosrc/core'
+import { DEFAULT_APP_NAME, useHawtconfig, Plugin } from '@hawtiosrc/core'
import { hawtioLogo, userAvatar } from '@hawtiosrc/img'
import { preferencesService } from '@hawtiosrc/preferences/preferences-service'
import { HawtioAbout } from '@hawtiosrc/ui/about'
@@ -22,7 +22,7 @@ import {
} from '@patternfly/react-core'
import { BarsIcon, HelpIcon } from '@patternfly/react-icons'
import React, { useContext, useState } from 'react'
-import { Link } from 'react-router-dom'
+import { Link, useLocation } from 'react-router-dom'
import './HawtioHeader.css'
import { PageContext } from './context'
@@ -72,7 +72,8 @@ const HawtioBrand: React.FunctionComponent = () => {
}
const HawtioHeaderToolbar: React.FunctionComponent = () => {
- const { username } = useContext(PageContext)
+ const { username, plugins } = useContext(PageContext)
+ const location = useLocation()
const isPublic = username === PUBLIC_USER
@@ -104,9 +105,25 @@ const HawtioHeaderToolbar: React.FunctionComponent = () => {
userItems.pop()
}
+ /*
+ * Determine which plugin is currently displaying
+ * based on the path of the current location
+ */
+ const pluginFromLocation = (): Plugin | null => {
+ const path = location.pathname
+ return plugins.find(plugin => path.startsWith(plugin.path)) ?? null
+ }
+
+ const plugin = pluginFromLocation()
+
return (