mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-02 18:45:34 +08:00
refactor: plugin routes
This commit is contained in:
@@ -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">
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user