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