feat: implement layout store management and add API documentation modal
This commit is contained in:
@@ -1,3 +1,8 @@
|
||||
import { QueryRouterServer } from '@kevisual/router/browser'
|
||||
import { use } from '@kevisual/context'
|
||||
export const app = use('app', new QueryRouterServer())
|
||||
export const app = use('app', new QueryRouterServer())
|
||||
|
||||
import { useLayoutStore } from '@/pages/auth/store'
|
||||
|
||||
const layoutStore = useLayoutStore.getState()
|
||||
layoutStore.setShowBaseHeader(false)
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { AppProvider } from '../../../studio/index.tsx';
|
||||
|
||||
interface PageProps {
|
||||
params: Promise<{
|
||||
root: string
|
||||
appId: string
|
||||
}>
|
||||
}
|
||||
|
||||
export default async function Page({ params }: PageProps) {
|
||||
const { root, appId } = await params;
|
||||
console.log('root', root, 'appId', appId);
|
||||
return <AppProvider />;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { filterRouteInfo, useStudioStore } from './store.ts';
|
||||
import { use, useEffect, useState } from 'react';
|
||||
import { MonitorPlay, Play, PanelLeft, PanelLeftClose, PanelRight, PanelRightClose, Filter, FilterX, Search, X, MoreHorizontal, Info, Code, RotateCcw } from 'lucide-react';
|
||||
import { MonitorPlay, Play, PanelLeft, PanelLeftClose, PanelRight, PanelRightClose, PanelTop, PanelTopClose, Filter, FilterX, Search, X, MoreHorizontal, Info, Code, RotateCcw, Book } from 'lucide-react';
|
||||
import { Panel, Group } from 'react-resizable-panels'
|
||||
import { ViewList } from '../view/list.tsx';
|
||||
import { useShallow } from 'zustand/shallow';
|
||||
@@ -12,6 +12,7 @@ import { ExportDialog } from './components/ExportDialog';
|
||||
import { useQueryViewStore } from '../query-view/store/index.ts';
|
||||
import { toast } from 'sonner';
|
||||
import { DetailsDialog } from '../query-view/components/DetailsDialog.tsx';
|
||||
import { useLayoutStore } from '../auth/store.ts';
|
||||
export const AppProvider = () => {
|
||||
const { showLeftPanel, showRightPanel } = useStudioStore(useShallow((state) => ({
|
||||
showLeftPanel: state.showLeftPanel,
|
||||
@@ -49,12 +50,26 @@ export const WrapperHeader = (props: { children: React.ReactNode }) => {
|
||||
showRightPanel: state.showRightPanel,
|
||||
setShowRightPanel: state.setShowRightPanel,
|
||||
})));
|
||||
const layoutStore = useLayoutStore(useShallow((state) => ({
|
||||
showBaseHeader: state.showBaseHeader,
|
||||
setShowBaseHeader: state.setShowBaseHeader,
|
||||
})));
|
||||
return <div className='h-full'>
|
||||
<div className="w-full h-12 flex items-center justify-between px-4 border-b border-gray-200 bg-white">
|
||||
<div className="cursor-pointer text-gray-600 hover:text-gray-900 transition-colors" title="Kevisual Router Studio" onClick={() => {
|
||||
store.setShowLeftPanel(!store.showLeftPanel);
|
||||
}}>
|
||||
{showLeftPanel ? <PanelLeftClose size={16} /> : <PanelLeft size={16} />}
|
||||
<div className='flex gap-2'>
|
||||
|
||||
<div className="cursor-pointer text-gray-600 hover:text-gray-900 transition-colors" title="Kevisual Router Studio" onClick={() => {
|
||||
store.setShowLeftPanel(!store.showLeftPanel);
|
||||
}}>
|
||||
{showLeftPanel ? <PanelLeftClose size={16} /> : <PanelLeft size={16} />}
|
||||
</div>
|
||||
<div className='cursor-pointer text-gray-600 hover:text-gray-900 transition-colors" title={layoutStore.showBaseHeader ? "隐藏BaseHeader" : "显示BaseHeader"}' onClick={
|
||||
() => {
|
||||
layoutStore.setShowBaseHeader(!layoutStore.showBaseHeader)
|
||||
}
|
||||
}>
|
||||
{layoutStore.showBaseHeader ? <PanelTopClose size={16} /> : <PanelTop size={16} />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
|
||||
@@ -70,6 +70,8 @@ interface StudioState {
|
||||
setShowExportDialog: (show: boolean) => void;
|
||||
exportRoutes?: RouteItem[];
|
||||
setExportRoutes: (routes?: RouteItem[]) => void;
|
||||
showApiDocs: boolean;
|
||||
setShowApiDocs: (show: boolean) => void;
|
||||
}
|
||||
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
export const filterRouteInfo = (viewData: RouterViewItem) => {
|
||||
@@ -332,7 +334,9 @@ export const useStudioStore = create<StudioState>()(
|
||||
showExportDialog: false,
|
||||
setShowExportDialog: (show: boolean) => set({ showExportDialog: show }),
|
||||
exportRoutes: undefined,
|
||||
setExportRoutes: (routes?: RouteItem[]) => set({ exportRoutes: routes })
|
||||
setExportRoutes: (routes?: RouteItem[]) => set({ exportRoutes: routes }),
|
||||
showApiDocs: false,
|
||||
setShowApiDocs: (show: boolean) => set({ showApiDocs: show }),
|
||||
}),
|
||||
{
|
||||
name: 'studio-storage',
|
||||
|
||||
34
src/pages/view/components/DocsModal.tsx
Normal file
34
src/pages/view/components/DocsModal.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { useStudioStore } from "@/pages/studio/store";
|
||||
import { useShallow } from "zustand/shallow";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export const DocsModal = () => {
|
||||
const store = useStudioStore(useShallow((state) => ({
|
||||
showApiDocs: state.showApiDocs,
|
||||
setShowApiDocs: state.setShowApiDocs,
|
||||
})));
|
||||
|
||||
return (
|
||||
<Dialog open={store.showApiDocs} onOpenChange={store.setShowApiDocs}>
|
||||
<DialogContent className="max-w-3xl! max-h-[80vh] overflow-hidden">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-xl">API 文档</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex-1 overflow-auto">
|
||||
<p>这里是 API 文档的内容...</p>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button onClick={() => store.setShowApiDocs(false)}>关闭</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useStudioStore } from '../studio/store.ts';
|
||||
import { Search, RotateCw, Plus, MoreHorizontal, Layout, Edit2, Trash2, MousePointer2 } from "lucide-react";
|
||||
import { Search, RotateCw, Plus, MoreHorizontal, Layout, Edit2, Trash2, MousePointer2, Book } from "lucide-react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -14,7 +14,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover
|
||||
import { ViewEditor } from "@/pages/view/components/ViewEditor.tsx";
|
||||
import { toast } from "sonner";
|
||||
import { useShallow } from "zustand/shallow";
|
||||
|
||||
import { DocsModal } from './components/DocsModal.tsx'
|
||||
const ViewItem = ({ view, onEdit, onDelete, onDeleteViewItem }: { view: any; onEdit: (view: any) => void; onDelete: (id: string) => void; onDeleteViewItem: (id: string, viewId: string) => void }) => {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const studioStore = useStudioStore(useShallow((state) => ({
|
||||
@@ -158,22 +158,29 @@ const ViewItem = ({ view, onEdit, onDelete, onDeleteViewItem }: { view: any; onE
|
||||
</div>
|
||||
}
|
||||
export const ViewList = () => {
|
||||
const { routeViewList, updateRouteView, deleteRouteView, deleteRouteViewItem, getViewList } = useStudioStore();
|
||||
const store = useStudioStore(useShallow((state) => ({
|
||||
routeViewList: state.routeViewList,
|
||||
updateRouteView: state.updateRouteView,
|
||||
deleteRouteView: state.deleteRouteView,
|
||||
deleteRouteViewItem: state.deleteRouteViewItem,
|
||||
getViewList: state.getViewList,
|
||||
setShowApiDocs: state.setShowApiDocs,
|
||||
})));
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [editorOpen, setEditorOpen] = useState(false);
|
||||
const [editingView, setEditingView] = useState<any>(null);
|
||||
|
||||
const filteredViews = routeViewList.filter(view =>
|
||||
const filteredViews = store.routeViewList.filter(view =>
|
||||
(view.title || '未命名视图').toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
(view.description || '').toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
useEffect(() => {
|
||||
getViewList();
|
||||
store.getViewList();
|
||||
}, [])
|
||||
|
||||
const handleRefresh = async () => {
|
||||
const toastId = toast.loading('正在刷新视图列表...');
|
||||
await getViewList();
|
||||
await store.getViewList();
|
||||
// toast.update(toastId, { render: '视图列表已刷新', type: 'success', id: false, autoClose: 1000 });
|
||||
toast.success('视图列表已刷新', { duration: 1000 });
|
||||
toast.dismiss(toastId);
|
||||
@@ -190,17 +197,20 @@ export const ViewList = () => {
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
if (confirm('确定要删除这个视图吗?')) {
|
||||
deleteRouteView(id);
|
||||
store.deleteRouteView(id);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveView = (viewData: any) => {
|
||||
updateRouteView(viewData);
|
||||
store.updateRouteView(viewData);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full h-full max-w-4xl p-4 border border-gray-200 rounded-md shadow-sm">
|
||||
<div className="flex items-center space-x-2 mb-4">
|
||||
<Button onClick={() => store.setShowApiDocs(true)} title='文档' variant="outline" size="icon" className="h-8 w-8 cursor-pointer border-gray-300">
|
||||
<Book size={16} />
|
||||
</Button>
|
||||
<div className="relative flex-1">
|
||||
<Input
|
||||
placeholder="搜索视图..."
|
||||
@@ -230,7 +240,7 @@ export const ViewList = () => {
|
||||
view={view}
|
||||
onEdit={handleEdit}
|
||||
onDelete={handleDelete}
|
||||
onDeleteViewItem={deleteRouteViewItem}
|
||||
onDeleteViewItem={store.deleteRouteViewItem}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
@@ -242,6 +252,7 @@ export const ViewList = () => {
|
||||
data={editingView}
|
||||
onSave={handleSaveView}
|
||||
/>
|
||||
<DocsModal />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4,18 +4,27 @@ import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
|
||||
import { Toaster } from '@/components/ui/sonner'
|
||||
import { AuthProvider } from '@/pages/auth'
|
||||
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||
import { useLayoutStore } from '@/pages/auth/store';
|
||||
import { useShallow } from 'zustand/shallow';
|
||||
import clsx from 'clsx';
|
||||
export const Route = createRootRoute({
|
||||
component: RootComponent,
|
||||
})
|
||||
|
||||
|
||||
function RootComponent() {
|
||||
const store = useLayoutStore(useShallow(state => ({
|
||||
showBaseHeader: state.showBaseHeader,
|
||||
})));
|
||||
return (
|
||||
<div className='h-full overflow-hidden'>
|
||||
<LayoutMain />
|
||||
<AuthProvider mustLogin={true}>
|
||||
<TooltipProvider>
|
||||
<main className='h-[calc(100%-3rem)] overflow-auto scrollbar'>
|
||||
<main className={clsx('overflow-auto scrollbar', {
|
||||
'h-[calc(100%-3rem)]': store.showBaseHeader,
|
||||
'h-full': !store.showBaseHeader,
|
||||
})}>
|
||||
<Outlet />
|
||||
</main>
|
||||
</TooltipProvider>
|
||||
|
||||
Reference in New Issue
Block a user