refactor: plugin routes

This commit is contained in:
Haileyesus
2026-04-08 11:31:20 +03:00
parent bdf24092ff
commit 7c8819cf34
3 changed files with 42 additions and 11 deletions

View File

@@ -4,6 +4,7 @@ import {
Outlet, Outlet,
RouterProvider, RouterProvider,
createBrowserRouter, createBrowserRouter,
useLocation,
useParams, useParams,
} from 'react-router-dom'; } from 'react-router-dom';
import { AuthProvider, ProtectedRoute } from './components/auth'; import { AuthProvider, ProtectedRoute } from './components/auth';
@@ -28,6 +29,7 @@ const isValidRouteTab = (value: string | undefined): boolean => {
normalizedValue === 'files' || normalizedValue === 'files' ||
normalizedValue === 'git' || normalizedValue === 'git' ||
normalizedValue === 'tasks' || normalizedValue === 'tasks' ||
normalizedValue === 'plugins' ||
normalizedValue === 'preview' || normalizedValue === 'preview' ||
normalizedValue.startsWith('plugin:') normalizedValue.startsWith('plugin:')
); );
@@ -60,6 +62,7 @@ function WorkspaceLayout() {
} }
function WorkspaceTabRoute() { function WorkspaceTabRoute() {
const location = useLocation();
const { workspaceId, sessionId, tab } = useParams<{ const { workspaceId, sessionId, tab } = useParams<{
workspaceId: string; workspaceId: string;
sessionId?: string; sessionId?: string;
@@ -77,11 +80,13 @@ function WorkspaceTabRoute() {
const decodedWorkspaceId = workspaceId ? decodeURIComponent(workspaceId) : null; const decodedWorkspaceId = workspaceId ? decodeURIComponent(workspaceId) : null;
const decodedSessionId = sessionId ? decodeURIComponent(sessionId) : null; const decodedSessionId = sessionId ? decodeURIComponent(sessionId) : null;
const decodedTab = tab ? decodeURIComponent(tab) : 'chat'; const decodedTab = tab ? decodeURIComponent(tab) : 'chat';
const pluginName = decodeURIComponent(new URLSearchParams(location.search).get('name') || '');
const tabLabel = decodedTab === 'plugins' && pluginName ? `plugin:${pluginName}` : decodedTab;
return ( return (
<div className="h-full p-6"> <div className="h-full p-6">
<div className="rounded-xl border border-border/70 bg-card/30 p-5"> <div className="rounded-xl border border-border/70 bg-card/30 p-5">
<h2 className="text-lg font-semibold">{decodedTab} view</h2> <h2 className="text-lg font-semibold">{tabLabel} view</h2>
<p className="mt-2 text-sm text-muted-foreground"> <p className="mt-2 text-sm text-muted-foreground">
Workspace:{' '} Workspace:{' '}
<span className="font-medium text-foreground"> <span className="font-medium text-foreground">

View File

@@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { AppTab } from '@/types/app'; import type { AppTab } from '@/types/app';
import { usePlugins } from '@/contexts/PluginsContext'; import { usePlugins } from '@/contexts/PluginsContext';
@@ -56,6 +56,7 @@ export function MainHeading() {
const { isMobile } = useDeviceSettings({ trackPWA: false }); const { isMobile } = useDeviceSettings({ trackPWA: false });
const { sidebarIsCollapsed, setSidebarIsCollapsed } = useSystemUI(); const { sidebarIsCollapsed, setSidebarIsCollapsed } = useSystemUI();
const { workspaceId, sessionId, tab } = useParams<MainHeadingRouteParams>(); const { workspaceId, sessionId, tab } = useParams<MainHeadingRouteParams>();
const location = useLocation();
const scrollRef = useRef<HTMLDivElement>(null); const scrollRef = useRef<HTMLDivElement>(null);
const [canScrollLeft, setCanScrollLeft] = useState(false); const [canScrollLeft, setCanScrollLeft] = useState(false);
@@ -63,10 +64,18 @@ export function MainHeading() {
const decodedWorkspaceId = useMemo(() => decodeValue(workspaceId), [workspaceId]); const decodedWorkspaceId = useMemo(() => decodeValue(workspaceId), [workspaceId]);
const decodedSessionId = useMemo(() => decodeValue(sessionId), [sessionId]); const decodedSessionId = useMemo(() => decodeValue(sessionId), [sessionId]);
const pluginName = useMemo(() => {
const params = new URLSearchParams(location.search);
return decodeValue(params.get('name') ?? undefined);
}, [location.search]);
const activeTab = useMemo<AppTab>(() => { const activeTab = useMemo<AppTab>(() => {
const routeTab = decodeValue(tab); const routeTab = decodeValue(tab);
if (routeTab === 'plugins' && pluginName) {
return `plugin:${pluginName}` as AppTab;
}
return (routeTab || 'chat') as AppTab; return (routeTab || 'chat') as AppTab;
}, [tab]); }, [pluginName, tab]);
const pluginDisplayName = useMemo( const pluginDisplayName = useMemo(
() => () =>
@@ -117,15 +126,19 @@ export function MainHeading() {
const handleTabSelect = (nextTab: AppTab) => { const handleTabSelect = (nextTab: AppTab) => {
// Preserve route context while switching only the active tab path segment. // Preserve route context while switching only the active tab path segment.
const encodedTab = encodeURIComponent(nextTab); const isPluginTab = nextTab.startsWith('plugin:');
const pluginTabName = isPluginTab ? nextTab.replace('plugin:', '') : '';
const targetTab = isPluginTab ? 'plugins' : nextTab;
const encodedTargetTab = encodeURIComponent(targetTab);
const pluginQuery = isPluginTab ? `?name=${encodeURIComponent(pluginTabName)}` : '';
if (decodedSessionId) { if (decodedSessionId) {
navigate(`/sessions/${encodeURIComponent(decodedSessionId)}/${encodedTab}`); navigate(`/sessions/${encodeURIComponent(decodedSessionId)}/${encodedTargetTab}${pluginQuery}`);
return; return;
} }
const encodedWorkspaceId = encodeURIComponent(decodedWorkspaceId); const encodedWorkspaceId = encodeURIComponent(decodedWorkspaceId);
navigate(`/workspaces/${encodedWorkspaceId}/${encodedTab}`); navigate(`/workspaces/${encodedWorkspaceId}/${encodedTargetTab}${pluginQuery}`);
}; };
return ( return (

View File

@@ -1,5 +1,5 @@
import { useState, useRef, useEffect, useMemo } from 'react'; import { useState, useRef, useEffect, useMemo } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
MessageSquare, MessageSquare,
@@ -58,6 +58,7 @@ export function MobileNav() {
const { isMobile } = useDeviceSettings({ trackPWA: false }); const { isMobile } = useDeviceSettings({ trackPWA: false });
const { isChatInputFocused } = useSystemUI(); const { isChatInputFocused } = useSystemUI();
const { workspaceId, sessionId, tab } = useParams<MobileNavRouteParams>(); const { workspaceId, sessionId, tab } = useParams<MobileNavRouteParams>();
const location = useLocation();
const { tasksEnabled, isTaskMasterInstalled } = useTasksSettings(); const { tasksEnabled, isTaskMasterInstalled } = useTasksSettings();
const shouldShowTasksTab = Boolean(tasksEnabled && isTaskMasterInstalled); const shouldShowTasksTab = Boolean(tasksEnabled && isTaskMasterInstalled);
const { plugins } = usePlugins(); const { plugins } = usePlugins();
@@ -66,10 +67,18 @@ export function MobileNav() {
const enabledPlugins = plugins.filter((plugin) => plugin.enabled); const enabledPlugins = plugins.filter((plugin) => plugin.enabled);
const hasPlugins = enabledPlugins.length > 0; const hasPlugins = enabledPlugins.length > 0;
const pluginName = useMemo(() => {
const params = new URLSearchParams(location.search);
return decodeValue(params.get('name') ?? undefined);
}, [location.search]);
const activeTab = useMemo<AppTab>(() => { const activeTab = useMemo<AppTab>(() => {
const routeTab = decodeValue(tab); const routeTab = decodeValue(tab);
if (routeTab === 'plugins' && pluginName) {
return `plugin:${pluginName}` as AppTab;
}
return (routeTab || 'chat') as AppTab; return (routeTab || 'chat') as AppTab;
}, [tab]); }, [pluginName, tab]);
const isPluginActive = activeTab.startsWith('plugin:'); const isPluginActive = activeTab.startsWith('plugin:');
useEffect(() => { useEffect(() => {
@@ -93,16 +102,20 @@ export function MobileNav() {
return; return;
} }
const encodedTab = encodeURIComponent(nextTab); const isPluginTab = nextTab.startsWith('plugin:');
const pluginTabName = isPluginTab ? nextTab.replace('plugin:', '') : '';
const targetTab = isPluginTab ? 'plugins' : nextTab;
const encodedTargetTab = encodeURIComponent(targetTab);
const pluginQuery = isPluginTab ? `?name=${encodeURIComponent(pluginTabName)}` : '';
const decodedSessionId = decodeValue(sessionId); const decodedSessionId = decodeValue(sessionId);
if (decodedSessionId) { if (decodedSessionId) {
navigate(`/sessions/${encodeURIComponent(decodedSessionId)}/${encodedTab}`); navigate(`/sessions/${encodeURIComponent(decodedSessionId)}/${encodedTargetTab}${pluginQuery}`);
return; return;
} }
const encodedWorkspaceId = encodeURIComponent(decodeValue(workspaceId)); const encodedWorkspaceId = encodeURIComponent(decodeValue(workspaceId));
navigate(`/workspaces/${encodedWorkspaceId}/${encodedTab}`); navigate(`/workspaces/${encodedWorkspaceId}/${encodedTargetTab}${pluginQuery}`);
}; };
const selectPlugin = (name: string) => { const selectPlugin = (name: string) => {