diff --git a/console/client/src/App.test.tsx b/console/client/src/App.test.tsx index cd9ec8ecad..5a9159a90d 100644 --- a/console/client/src/App.test.tsx +++ b/console/client/src/App.test.tsx @@ -5,6 +5,8 @@ import { App } from './App' describe('App', () => { it('renders the app', () => { + window.history.pushState({}, 'Modules', '/modules') + render( diff --git a/console/client/src/App.tsx b/console/client/src/App.tsx index 0631612d9d..98379e0345 100644 --- a/console/client/src/App.tsx +++ b/console/client/src/App.tsx @@ -1,14 +1,20 @@ -import { Route, Routes } from 'react-router-dom' +import { Navigate, Route, Routes } from 'react-router-dom' import { GraphPage } from './features/graph/GraphPage.tsx' -import { IDELayout } from './layout/IDELayout.tsx' +import { ModulesList } from './features/modules/ModulesList.tsx' +import { Timeline } from './features/timeline/Timeline.tsx' +import { Layout } from './layout/Layout.tsx' import { bgColor, textColor } from './utils/style.utils.ts' export const App = () => { return (
- } /> - } /> + }> + } /> + } /> + } /> + } /> +
) diff --git a/console/client/src/features/graph/GraphPage.tsx b/console/client/src/features/graph/GraphPage.tsx index eed52dbdec..ded19c419d 100644 --- a/console/client/src/features/graph/GraphPage.tsx +++ b/console/client/src/features/graph/GraphPage.tsx @@ -1,7 +1,6 @@ import { useContext, useEffect } from 'react' import ReactFlow, { Controls, MiniMap, useEdgesState, useNodesState } from 'reactflow' import 'reactflow/dist/style.css' -import { Navigation } from '../../layout/Navigation' import { modulesContext } from '../../providers/modules-provider' import { GroupNode } from './GroupNode' import { VerbNode } from './VerbNode' @@ -22,7 +21,6 @@ export const GraphPage = () => { return ( <> -
{ const modules = React.useContext(modulesContext) const { selectedModule, setSelectedModule } = React.useContext(SelectedModuleContext) - const { openTab } = React.useContext(TabsContext) const [searchParams] = useSearchParams() const moduleId = searchParams.get('module') // When mounting with a valid module in query params set selected module @@ -29,16 +25,6 @@ export const ModuleDetails = () => { ) } - const handleVerbClicked = (verb: Verb) => { - const verbId = [selectedModule.name, verb.verb?.name].join('.') - openTab({ - id: verbId, - label: verb.verb?.name ?? 'Verb', - isClosable: true, - component: , - }) - } - return (
@@ -60,9 +46,6 @@ export const ModuleDetails = () => { {selectedModule.verbs.map((verb, index) => (
{ - handleVerbClicked(verb) - }} className='rounded bg-indigo-600 px-2 py-1 text-xs font-semibold text-white text-center cursor-pointer shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600' > diff --git a/console/client/src/features/verbs/VerbForm.tsx b/console/client/src/features/verbs/VerbForm.tsx index ceee2dd027..8f4c2fdaf3 100644 --- a/console/client/src/features/verbs/VerbForm.tsx +++ b/console/client/src/features/verbs/VerbForm.tsx @@ -10,7 +10,6 @@ import { Module, Verb } from '../../protos/xyz/block/ftl/v1/console/console_pb' import { VerbService } from '../../protos/xyz/block/ftl/v1/ftl_connect' import { VerbRef } from '../../protos/xyz/block/ftl/v1/schema/schema_pb' import { useDarkMode } from '../../providers/dark-mode-provider' -import { TabsContext } from '../../providers/tabs-provider' export type Schema = JSONSchema4 | JSONSchema6 | JSONSchema7 @@ -22,7 +21,6 @@ interface Props { export const VerbForm = ({ module, verb }: Props) => { const client = useClient(VerbService) const { isDarkMode } = useDarkMode() - const { activeTabId } = React.useContext(TabsContext) const [editorText, setEditorText] = React.useState('') const [response, setResponse] = React.useState(null) const [error, setError] = React.useState(null) @@ -82,7 +80,7 @@ export const VerbForm = ({ module, verb }: Props) => { validate: true, schemas: [{ schema, uri: 'http://myserver/foo-schema.json', fileMatch: ['*'] }], }) - }, [monaco, schema, activeTabId]) + }, [monaco, schema]) return ( <> diff --git a/console/client/src/layout/IDELayout.tsx b/console/client/src/layout/IDELayout.tsx deleted file mode 100644 index 197462c884..0000000000 --- a/console/client/src/layout/IDELayout.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { ModuleDetails } from '../features/modules/ModuleDetails' -import { ModulesList } from '../features/modules/ModulesList' -import { bgColor, headerColor, headerTextColor, panelColor, textColor } from '../utils' -import { Navigation } from './Navigation' -import { Notification } from './Notification' -import { SidePanel } from './SidePanel' -import { Tabs } from './Tabs' - -export const IDELayout = () => { - return ( - <> -
- - -
- {/* Left Column */} - - - {/* Main Content */} -
-
-
- - -
-
-
-
-
- - - ) -} diff --git a/console/client/src/layout/Layout.tsx b/console/client/src/layout/Layout.tsx new file mode 100644 index 0000000000..ec31c9eab8 --- /dev/null +++ b/console/client/src/layout/Layout.tsx @@ -0,0 +1,21 @@ +import { Outlet } from 'react-router-dom' +import { Navigation } from './Navigation' +import { Notification } from './Notification' +import { SidePanel } from './SidePanel' + +export const Layout = () => { + return ( +
+ + +
+
+ +
+
+ + + +
+ ) +} diff --git a/console/client/src/layout/Navigation.tsx b/console/client/src/layout/Navigation.tsx index a986441914..54ab8c345b 100644 --- a/console/client/src/layout/Navigation.tsx +++ b/console/client/src/layout/Navigation.tsx @@ -1,36 +1,82 @@ -import { NavLink } from 'react-router-dom' +import { Schema, Timeline, ViewModuleSharp } from '@mui/icons-material' +import { useContext } from 'react' +import { Link, NavLink } from 'react-router-dom' import { DarkModeSwitch } from '../components/DarkModeSwitch' -import { classNames } from '../utils/react.utils' -import { navColor } from '../utils/style.utils' +import { modulesContext } from '../providers/modules-provider' +import { classNames } from '../utils' + +const navigation = [ + { name: 'Events', href: '/events', icon: Timeline }, + { name: 'Modules', href: '/modules', icon: ViewModuleSharp }, + { name: 'Graph', href: '/graph', icon: Schema }, +] export const Navigation = () => { + const modules = useContext(modulesContext) + return ( -
-
- -
- FTL - -
-
-
-
- - classNames( - isActive ? 'bg-indigo-700 text-white' : 'text-white hover:bg-indigo-500 hover:bg-opacity-75', - 'rounded-md px-3 py-2 text-sm font-medium', - ) - } - > - Graph - +
+
- +
) } diff --git a/console/client/src/layout/SidePanel.tsx b/console/client/src/layout/SidePanel.tsx index ca88b98c43..e8fdb3309e 100644 --- a/console/client/src/layout/SidePanel.tsx +++ b/console/client/src/layout/SidePanel.tsx @@ -20,9 +20,7 @@ export const SidePanel = () => { leaveTo='translate-x-full' >
-
+
Event Details - )} - - ) - })} - -
-
-
- - {tabs.map((tab) => ( - {tab.component} - ))} - -
- -
- ) -} diff --git a/console/client/src/providers/AppProviders.tsx b/console/client/src/providers/AppProviders.tsx index e40f8c5007..e2ffcbe00c 100644 --- a/console/client/src/providers/AppProviders.tsx +++ b/console/client/src/providers/AppProviders.tsx @@ -6,7 +6,6 @@ import { SchemaProvider } from './schema-provider' import { SelectedModuleProvider } from './selected-module-provider' import { SelectedTimelineEntryProvider } from './selected-timeline-entry-provider' import { SidePanelProvider } from './side-panel-provider' -import { TabsProvider } from './tabs-provider' export const AppProviders = () => { return ( @@ -15,13 +14,11 @@ export const AppProviders = () => { - - - - - - - + + + + + diff --git a/console/client/src/providers/tabs-provider.tsx b/console/client/src/providers/tabs-provider.tsx deleted file mode 100644 index 3db89b44af..0000000000 --- a/console/client/src/providers/tabs-provider.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React from 'react' -import { useSearchParams } from 'react-router-dom' - -export interface Tab { - id: string - label: string - isClosable: boolean - component: React.ReactNode -} - -interface TabsContextType { - tabs: Tab[] - activeTabId?: string - openTab: (tab: Tab) => void - closeTab: (tabId: string) => void -} - -export const TabsContext = React.createContext({ - tabs: [], - activeTabId: undefined, - openTab: () => {}, - closeTab: () => {}, -}) - -export const TabsProvider = (props: React.PropsWithChildren) => { - 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} -}