@@ -173,67 +50,7 @@ export function IDELayout() {
-
-
-
-
- {tabs.map(({label, id}, i) => {
- return (
-
-
- {label}
-
- {i !== 0 && (
-
- )}
-
- )
- })}
-
-
-
-
-
- {tabs.map(({id}, i) => {
- return i === 0 ? (
-
-
-
- ) : (
-
-
-
- )
- })}
-
-
-
-
+
diff --git a/console/client/src/layout/Tabs.tsx b/console/client/src/layout/Tabs.tsx
new file mode 100644
index 0000000000..5c14b8eb66
--- /dev/null
+++ b/console/client/src/layout/Tabs.tsx
@@ -0,0 +1,134 @@
+import {Tab as TabComponent} from '@headlessui/react'
+import {XMarkIcon} from '@heroicons/react/24/outline'
+import React from 'react'
+import {useSearchParams} from 'react-router-dom'
+import {Timeline} from '../features/timeline/Timeline'
+import {VerbTab} from '../features/verbs/VerbTab'
+import {modulesContext} from '../providers/modules-provider'
+import {
+ NotificationType,
+ NotificationsContext,
+} from '../providers/notifications-provider'
+import {Tab, TabsContext} from '../providers/tabs-provider'
+import {headerColor, headerTextColor, panelColor} from '../utils'
+
+const selectedTabStyle = `${headerTextColor} ${headerColor}`
+const unselectedTabStyle = `text-gray-300 bg-slate-100 dark:bg-slate-600`
+
+export const Tabs = () => {
+ const [searchParams] = useSearchParams()
+ const {tabs, activeTabId, openTab, closeTab} = React.useContext(TabsContext)
+ const {showNotification} = React.useContext(NotificationsContext)
+ const {modules} = React.useContext(modulesContext)
+
+ React.useEffect(() => {
+ openTab({
+ id: 'timeline',
+ label: 'Timeline',
+ isClosable: false,
+ component: ,
+ } as Tab)
+ }, [])
+
+ React.useEffect(() => {
+ const verbRef = searchParams.get('verb')
+ if (!verbRef || modules.length === 0) return
+
+ const [moduleName, verbName] = verbRef.split('.')
+ const module = modules.find(module => module?.name === moduleName)
+ if (!module) {
+ showNotification(
+ {
+ title: 'Module not found',
+ message: `Module '${moduleName}' does not exist`,
+ type: NotificationType.Error,
+ },
+ 'indefinite'
+ )
+ return
+ }
+
+ const verb = module?.verbs.find(v => v.verb?.name === verbName)
+ if (!verb) {
+ showNotification(
+ {
+ title: 'Verb not found',
+ message: `Verb '${verbName}' does not exist on module '${moduleName}'`,
+ type: NotificationType.Error,
+ },
+ 'indefinite'
+ )
+ return
+ }
+
+ openTab({
+ id: verbRef,
+ label: verb.verb?.name ?? 'Verb',
+ isClosable: true,
+ component: ,
+ })
+ }, [modules])
+
+ const handleChangeTab = (index: number) => {
+ const tab = tabs[index]
+ openTab(tab)
+ }
+
+ return (
+
+
tab.id === activeTabId)}
+ onChange={handleChangeTab}
+ >
+
+
+ {tabs.map(tab => {
+ return (
+
+
+ {tab.label}
+
+ {tab.isClosable && (
+
+ )}
+
+ )
+ })}
+
+
+
+
+
+ {tabs.map(tab => (
+
+ {tab.component}
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/console/client/src/providers/tabs-provider.tsx b/console/client/src/providers/tabs-provider.tsx
index 89074717a5..b331da692a 100644
--- a/console/client/src/providers/tabs-provider.tsx
+++ b/console/client/src/providers/tabs-provider.tsx
@@ -1,56 +1,73 @@
import React from 'react'
-
-export const TabType = {
- Timeline: 'timeline',
- Verb: 'verb',
-} as const
+import {useSearchParams} from 'react-router-dom'
export type Tab = {
id: string
label: string
- type: (typeof TabType)[keyof typeof TabType]
-}
-
-export const TabSearchParams = {
- id: 'tab-id',
- type: 'tab-type',
-} as const
-
-export const timelineTab = {
- id: 'timeline',
- label: 'Timeline',
- type: TabType.Timeline,
+ isClosable: boolean
+ component: React.ReactNode
}
-export type ActiveTab =
- | {
- id: string
- type: string
- }
- | undefined
-
type TabsContextType = {
tabs: Tab[]
- activeTab?: ActiveTab
- setTabs: React.Dispatch>
- setActiveTab: React.Dispatch>
+ activeTabId?: string
+ openTab: (tab: Tab) => void
+ closeTab: (tabId: string) => void
}
export const TabsContext = React.createContext({
tabs: [],
- activeTab: undefined,
- setTabs: () => {},
- setActiveTab: () => {},
+ activeTabId: undefined,
+ openTab: () => {},
+ closeTab: () => {},
})
export const TabsProvider = (props: React.PropsWithChildren) => {
- const [tabs, setTabs] = React.useState([timelineTab])
- const [activeTab, setActiveTab] = React.useState<
- {id: string; type: string} | undefined
- >()
+ const [searchParams, setSearchParams] = useSearchParams()
+ const [tabs, setTabs] = React.useState([])
+ const [activeTabId, setActiveTabId] = React.useState()
+
+ const updateParams = (tab: Tab) => {
+ if (tab.id !== 'timeline') {
+ setSearchParams({
+ ...Object.fromEntries(searchParams),
+ verb: tab.id,
+ })
+ }
+ }
+
+ const openTab = (tab: Tab) => {
+ setTabs(prevTabs => {
+ // Add the tab if it doesn't exist
+ if (!prevTabs.some(existingTab => existingTab.id === tab.id)) {
+ return [...prevTabs, tab]
+ }
+ return prevTabs
+ })
+
+ setActiveTabId(tab.id)
+ updateParams(tab)
+ }
+
+ const closeTab = (tabId: string) => {
+ const newTabs = tabs.filter(tab => tab.id !== tabId)
+ const closedTabIndex = tabs.findIndex(tab => tab.id === tabId)
+
+ if (activeTabId === tabId) {
+ const activeTab = newTabs[closedTabIndex - 1]
+ setActiveTabId(activeTab?.id)
+ updateParams(activeTab)
+ }
+
+ if (newTabs.length === 1 && newTabs[0].id === 'timeline') {
+ searchParams.delete('verb')
+ setSearchParams(searchParams)
+ }
+ setTabs(newTabs)
+ }
return (
-
+
{props.children}
)
diff --git a/console/client/src/utils/index.ts b/console/client/src/utils/index.ts
index c8aa35d723..58879281be 100644
--- a/console/client/src/utils/index.ts
+++ b/console/client/src/utils/index.ts
@@ -1,4 +1,3 @@
export * from './date.utils'
-export * from './invalid-tab.utils'
export * from './react.utils'
export * from './style.utils'
diff --git a/console/client/src/utils/invalid-tab.utils.ts b/console/client/src/utils/invalid-tab.utils.ts
deleted file mode 100644
index 09b1a52a2b..0000000000
--- a/console/client/src/utils/invalid-tab.utils.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import {TabType, timelineTab, TabSearchParams} from '../providers/tabs-provider'
-export const invalidTab = ({
- id,
- type,
-}: {
- id?: string
- type?: string
-}): string | undefined => {
- // No ID or type
- if (!id || !type) {
- return `Required tab field undefined: ${JSON.stringify(
- {[TabSearchParams.type]: type, [TabSearchParams.id]: id},
- null,
- 2
- ).replace(/":/g, '" :')}`
- }
- // Invalid type
- const invalidType = Object.values(TabType).some(v => v === type)
- if (!invalidType) {
- return `Invalid tab type: ${type}`
- }
-
- // Type is timeline but id is wrong
- if (type === TabType.Timeline) {
- if (id !== timelineTab.id) {
- return `invalid timeline id: ${id}`
- }
- }
- // Type is verb but invalid type
- if (type === TabType.Verb) {
- const verbIdArray = id.split('.')
- if (type === TabType.Verb && verbIdArray.length !== 2) {
- return `Invalid verb ${id}`
- }
- }
-}