레이아웃 정리

This commit is contained in:
현성필
2025-01-25 20:20:58 +09:00
parent 73b4bb96f6
commit 05f00337df
8 changed files with 147 additions and 78 deletions
+33 -28
View File
@@ -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 */}