레이아웃 정리
This commit is contained in:
@@ -4,6 +4,7 @@ import APITabs from '@/components/api-tester/APITabs';
|
||||
import {useAPI} from '@/providers/APIProvider.jsx';
|
||||
import ScenarioNavigation from '@/components/api-tester/ScenarioNavigation.jsx';
|
||||
import ScenarioEditor from '@/components/api-tester/ScenarioEditor.jsx';
|
||||
import {SidebarProvider} from '@/components/ui/sidebar.jsx';
|
||||
|
||||
const MainContentArea = ({isApiMode, toggleMode}) => {
|
||||
// Lift tab state up
|
||||
@@ -117,9 +118,9 @@ const APIMode = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="api-mode flex h-full w-full">
|
||||
<div className="navigation w-[250px] flex-shrink-0 border-r bg-background">
|
||||
{/* APINavigation 고정 */}
|
||||
<SidebarProvider>
|
||||
<div className="api-mode flex flex-1 h-[calc(100vh-57px)]">
|
||||
<div className="navigation-sidebar min-w-[0px] border-r bg-gray-100">
|
||||
<APINavigation
|
||||
contextPath="/"
|
||||
collections={collections} // Pass collections to APINavigation
|
||||
@@ -132,7 +133,7 @@ const APIMode = ({
|
||||
onToggleMode={onToggleMode}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 h-full w-full min-w-0 overflow-hidden">
|
||||
<div className="editor flex-1 h-[calc(100vh-57px)] overflow-hidden">
|
||||
{apiRequests.length === 0 ? (
|
||||
renderFallback() // Render fallback when no tabs exist
|
||||
) : (
|
||||
@@ -145,40 +146,44 @@ const APIMode = ({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
);
|
||||
};
|
||||
|
||||
// 시나리오 모드
|
||||
const ScenarioMode = ({
|
||||
isApiMode,
|
||||
onToggleMode,
|
||||
onToggleMode
|
||||
}) => {
|
||||
|
||||
let handleApiSelect = () => {};
|
||||
let handleApiSelect = () => {
|
||||
};
|
||||
return (
|
||||
<div className="scenario-mode flex flex-1 h-[calc(100vh-57px)]">
|
||||
<div className="navigation-sidebar min-w-[200px] border-r bg-gray-100">
|
||||
<ScenarioNavigation
|
||||
contextPath="/"
|
||||
side="left"
|
||||
isApiMode={isApiMode}
|
||||
onToggleMode={onToggleMode}
|
||||
/>
|
||||
<SidebarProvider>
|
||||
<div className="scenario-mode flex flex-1 h-[calc(100vh-57px)]">
|
||||
<div className="navigation-sidebar min-w-[0px] border-r bg-gray-100">
|
||||
<ScenarioNavigation
|
||||
contextPath="/"
|
||||
side="left"
|
||||
isApiMode={isApiMode}
|
||||
onToggleMode={onToggleMode}
|
||||
/>
|
||||
</div>
|
||||
<div className="editor flex-1 h-[calc(100vh-57px)] overflow-hidden">
|
||||
<ScenarioEditor/>
|
||||
</div>
|
||||
<div className="navigation-sidebar min-w-[200px] border-l bg-gray-100">
|
||||
<APINavigation
|
||||
contextPath="/"
|
||||
side="right"
|
||||
onApiSelect={handleApiSelect}
|
||||
isApiMode={isApiMode}
|
||||
onToggleMode={onToggleMode}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="editor flex-1 h-[calc(100vh-57px)] overflow-hidden">
|
||||
<ScenarioEditor />
|
||||
</div>
|
||||
<div className="navigation-sidebar min-w-[200px] border-l bg-gray-100">
|
||||
<APINavigation
|
||||
contextPath="/"
|
||||
side="right"
|
||||
onApiSelect={handleApiSelect}
|
||||
isApiMode={isApiMode}
|
||||
onToggleMode={onToggleMode}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default MainContentArea;
|
||||
export default MainContentArea;
|
||||
|
||||
@@ -250,7 +250,7 @@ const APINavigation = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<>
|
||||
<Sidebar side={side} className="py-16">
|
||||
{side === 'left' && (
|
||||
<SidebarHeader>
|
||||
@@ -300,7 +300,7 @@ const APINavigation = ({
|
||||
onSubmit={handleRenameApi}
|
||||
api={apiToRename}
|
||||
/>
|
||||
</SidebarProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -69,28 +69,58 @@ const DraggableApiItem = ({ api, collectionId, onApiAction }) => {
|
||||
draggable
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
className={`cursor-move transition-colors duration-200 ${
|
||||
isDragging ? 'opacity-50 bg-accent' : ''
|
||||
}`}
|
||||
className={`cursor-move transition-colors duration-200 ${isDragging ? 'opacity-50 bg-accent' : ''}`}
|
||||
>
|
||||
<SidebarMenuSubButton
|
||||
onClick={(e) => {
|
||||
// Prevent click when dragging ends
|
||||
if (!isDragging) {
|
||||
onApiAction.open(collectionId, api.id);
|
||||
}
|
||||
}}
|
||||
className="w-full justify-start gap-2"
|
||||
asChild
|
||||
>
|
||||
<div className="w-full flex items-center gap-2 min-w-0 pr-2">
|
||||
{/*<SidebarMenuSubButton*/}
|
||||
{/* onClick={(e) => {*/}
|
||||
{/* // Prevent click when dragging ends*/}
|
||||
{/* if (!isDragging) {*/}
|
||||
{/* onApiAction.open(collectionId, api.id);*/}
|
||||
{/* }*/}
|
||||
{/* }}*/}
|
||||
{/* className="w-full justify-start gap-2"*/}
|
||||
{/* asChild*/}
|
||||
{/*>*/}
|
||||
{/* <div className="w-full flex items-center gap-2 min-w-0 pr-2">*/}
|
||||
{/* <FileText className="h-4 w-4 flex-shrink-0"/>*/}
|
||||
{/* <Tooltip>*/}
|
||||
{/* <TooltipTrigger asChild>*/}
|
||||
{/* <span className="truncate flex-1">*/}
|
||||
{/* {api.method && <span className="font-medium text-primary">{api.method} </span>}*/}
|
||||
{/* {api.name}*/}
|
||||
{/* </span>*/}
|
||||
{/* </TooltipTrigger>*/}
|
||||
{/* <TooltipContent>*/}
|
||||
{/* <div className="flex flex-col gap-1">*/}
|
||||
{/* <p className="font-medium">{api.name}</p>*/}
|
||||
{/* {api.path && <p className="text-xs text-muted-foreground">{api.path}</p>}*/}
|
||||
{/* </div>*/}
|
||||
{/* </TooltipContent>*/}
|
||||
{/* </Tooltip>*/}
|
||||
{/* <SidebarMenuAction*/}
|
||||
{/* className="p-0 m-0 flex-shrink-0"*/}
|
||||
{/* onClick={(e) => e.stopPropagation()}*/}
|
||||
{/* >*/}
|
||||
{/* <ApiDropdownMenu*/}
|
||||
{/* onOpen={() => onApiAction.open(collectionId, api.id)}*/}
|
||||
{/* onRename={() => onApiAction.rename(collectionId, api.id)}*/}
|
||||
{/* onDelete={() => onApiAction.delete(collectionId, api.id)}*/}
|
||||
{/* />*/}
|
||||
{/* </SidebarMenuAction>*/}
|
||||
{/* </div>*/}
|
||||
{/*</SidebarMenuSubButton>*/}
|
||||
<div className="w-full flex items-center gap-2 min-w-0 pr-2">
|
||||
<div
|
||||
onClick={() => !isDragging && onApiAction.open(collectionId, api.id)}
|
||||
className="flex items-center gap-2 flex-1 min-w-0 cursor-pointer"
|
||||
>
|
||||
<FileText className="h-4 w-4 flex-shrink-0"/>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="truncate flex-1">
|
||||
{api.method && <span className="font-medium text-primary">{api.method} </span>}
|
||||
{api.name}
|
||||
</span>
|
||||
<span className="truncate">
|
||||
{api.method && <span className="font-medium text-primary">{api.method} </span>}
|
||||
{api.name}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div className="flex flex-col gap-1">
|
||||
@@ -99,18 +129,28 @@ const DraggableApiItem = ({ api, collectionId, onApiAction }) => {
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<SidebarMenuAction
|
||||
className="p-0 m-0 flex-shrink-0"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<ApiDropdownMenu
|
||||
onOpen={() => onApiAction.open(collectionId, api.id)}
|
||||
onRename={() => onApiAction.rename(collectionId, api.id)}
|
||||
onDelete={() => onApiAction.delete(collectionId, api.id)}
|
||||
/>
|
||||
</SidebarMenuAction>
|
||||
</div>
|
||||
</SidebarMenuSubButton>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger data-dropdown-trigger="true" className="p-0 h-auto">
|
||||
<div className="hover:bg-accent rounded-sm">
|
||||
<MoreHorizontal className="h-4 w-4"/>
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => onApiAction.open(collectionId, api.id)}>
|
||||
Open
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator/>
|
||||
<DropdownMenuItem onClick={() => onApiAction.rename(collectionId, api.id)}>
|
||||
Rename
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="text-destructive" onClick={() => onApiAction.delete(collectionId, api.id)}>
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</SidebarMenuSubItem>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import {Button} from '@/components/ui/button.jsx';
|
||||
import {Pause, Play, RotateCcw, Save, SkipForward} from 'lucide-react';
|
||||
import React from 'react';
|
||||
import {SidebarTrigger} from '@/components/ui/sidebar.jsx';
|
||||
import {Separator} from '@/components/ui/separator.jsx';
|
||||
|
||||
const ScenarioMenuBar = ({ onSave, onStepRun, onFullRun, onPause, onReset }) => {
|
||||
return (
|
||||
<div className="flex items-center gap-2 p-4 border-b">
|
||||
<SidebarTrigger className="-ml-1" />
|
||||
<Separator orientation="vertical" className="mr-2 h-4" />
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
SidebarMenu,
|
||||
SidebarMenuItem,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuAction, SidebarHeader
|
||||
SidebarMenuAction, SidebarHeader,
|
||||
} from '@/components/ui/sidebar';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
@@ -167,7 +167,6 @@ const ScenarioNavigation = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<Sidebar side={side} className="py-16">
|
||||
<SidebarHeader>
|
||||
<Button
|
||||
@@ -186,7 +185,6 @@ const ScenarioNavigation = ({
|
||||
scenarioName={scenarioToDelete?.name || ''}
|
||||
/>
|
||||
</Sidebar>
|
||||
</SidebarProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {useState} from 'react';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Button} from '@/components/ui/button';
|
||||
import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from '@/components/ui/select';
|
||||
import {Input} from '@/components/ui/input';
|
||||
@@ -11,6 +11,8 @@ import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from '@/c
|
||||
import {useAPI} from '@/providers/APIProvider';
|
||||
import MonacoEditor from '@/components/shared/MonacoEditor';
|
||||
import APIClient from '@/services/APIClient.js';
|
||||
import {SidebarTrigger} from '@/components/ui/sidebar.jsx';
|
||||
import {Separator} from '@/components/ui/separator.jsx';
|
||||
|
||||
const HTTP_METHODS = [
|
||||
'POST', 'GET', 'PUT', 'DELETE', 'CONNECT',
|
||||
@@ -40,17 +42,26 @@ export const HTTPClientView = ({model, index}) => {
|
||||
postRequestScript: currentModel.postRequestScript || ''
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (currentModel.server) {
|
||||
const selectedServer = getServer(currentModel.server);
|
||||
if (selectedServer) {
|
||||
setBasePath(selectedServer.basePath || '');
|
||||
}
|
||||
}
|
||||
}, [currentModel.server]);
|
||||
|
||||
const handleServerChange = (serverId) => {
|
||||
const selectedServer = getServer(serverId);
|
||||
// const selectedServer = getServer(serverId);
|
||||
|
||||
setCurrentModel(prev => ({
|
||||
...prev,
|
||||
server: serverId
|
||||
}));
|
||||
|
||||
if (selectedServer) {
|
||||
setBasePath(selectedServer.basePath || '');
|
||||
}
|
||||
// if (selectedServer) {
|
||||
// setBasePath(selectedServer.basePath || '');
|
||||
// }
|
||||
};
|
||||
|
||||
const handleHeadersChange = (headers) => {
|
||||
@@ -191,6 +202,8 @@ export const HTTPClientView = ({model, index}) => {
|
||||
<div className="flex flex-col h-full p-4 gap-4">
|
||||
{/* Request Header Section */}
|
||||
<div className="flex items-center gap-2">
|
||||
<SidebarTrigger className="-ml-1" />
|
||||
<Separator orientation="vertical" className="mr-2 h-4" />
|
||||
<div>
|
||||
<span>{currentModel.collectionName}</span>
|
||||
{' > '}
|
||||
@@ -212,17 +225,26 @@ export const HTTPClientView = ({model, index}) => {
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<ServerManager
|
||||
onServerChange={handleServerChange}
|
||||
defaultValue={currentModel.server}
|
||||
/>
|
||||
<Input value={basePath} readOnly className="w-40"/>
|
||||
<EditableInput value={currentModel.path} onChange={handlePathChange} className="flex-1" />
|
||||
|
||||
<Button disabled={isLoading} onClick={handleSendRequest}>
|
||||
{isLoading ? 'Sending...' : 'Send'}
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={handleSave}>Save</Button>
|
||||
<div className="shrink-0">
|
||||
<ServerManager
|
||||
onServerChange={handleServerChange}
|
||||
defaultValue={currentModel.server}
|
||||
/>
|
||||
</div>
|
||||
<Input value={basePath} readOnly className="w-40 shrink-0"/>
|
||||
<div className="min-w-0 flex-1">
|
||||
<EditableInput
|
||||
value={currentModel.path}
|
||||
onChange={handlePathChange}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-2 shrink-0">
|
||||
<Button disabled={isLoading} onClick={handleSendRequest}>
|
||||
{isLoading ? 'Sending...' : 'Send'}
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={handleSave}>Save</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Request/Response Container */}
|
||||
|
||||
Reference in New Issue
Block a user