탭 정리
This commit is contained in:
@@ -5,6 +5,7 @@ import { TCPClientView } from '../client-views/TCPClientView';
|
|||||||
import { HTTPFlatClientView } from '../client-views/HTTPFlatClientView';
|
import { HTTPFlatClientView } from '../client-views/HTTPFlatClientView';
|
||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
import {Button} from '@/components/ui/button.jsx';
|
import {Button} from '@/components/ui/button.jsx';
|
||||||
|
import {ScrollArea} from '@/components/ui/scroll-area.jsx';
|
||||||
|
|
||||||
const APITabs = ({
|
const APITabs = ({
|
||||||
apiRequests,
|
apiRequests,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ 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 {SidebarTrigger} from '@/components/ui/sidebar.jsx';
|
||||||
import {Separator} from '@/components/ui/separator.jsx';
|
import {Separator} from '@/components/ui/separator.jsx';
|
||||||
|
import {ScrollArea} from '@/components/ui/scroll-area.jsx';
|
||||||
|
|
||||||
const HTTP_METHODS = [
|
const HTTP_METHODS = [
|
||||||
'POST', 'GET', 'PUT', 'DELETE', 'CONNECT',
|
'POST', 'GET', 'PUT', 'DELETE', 'CONNECT',
|
||||||
@@ -96,16 +97,15 @@ export const HTTPClientView = ({model, index}) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const buildUrlFromPathAndParams = (path, queryParams) => {
|
const buildUrlFromPathAndParams = (path, queryParams) => {
|
||||||
const baseUrl = path.split('?')[0];
|
const baseUrl = path.split('?')[0];
|
||||||
const enabledParams = queryParams.filter(p => p.enabled && p.key);
|
const enabledParams = queryParams.filter(p => p.enabled && p.key);
|
||||||
|
|
||||||
if (!enabledParams.length) return baseUrl;
|
if (!enabledParams.length) {
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
const queryString = enabledParams
|
const queryString = enabledParams.map(p => `${p.key}=${p.value || ''}`).join('&');
|
||||||
.map(p => `${p.key}=${p.value || ''}`)
|
|
||||||
.join('&');
|
|
||||||
|
|
||||||
return `${baseUrl}?${queryString}`;
|
return `${baseUrl}?${queryString}`;
|
||||||
};
|
};
|
||||||
@@ -119,7 +119,6 @@ export const HTTPClientView = ({model, index}) => {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const parseQueryParams = (path) => {
|
const parseQueryParams = (path) => {
|
||||||
const queryParams = [];
|
const queryParams = [];
|
||||||
const [, queryString] = path.split('?');
|
const [, queryString] = path.split('?');
|
||||||
@@ -176,7 +175,6 @@ export const HTTPClientView = ({model, index}) => {
|
|||||||
responseBody: response.body
|
responseBody: response.body
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
setEditorContents(prev => ({
|
setEditorContents(prev => ({
|
||||||
@@ -228,8 +226,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" />
|
<SidebarTrigger className="-ml-1"/>
|
||||||
<Separator orientation="vertical" className="mr-2 h-4" />
|
<Separator orientation="vertical" className="mr-2 h-4"/>
|
||||||
<div>
|
<div>
|
||||||
<span>{currentModel.collectionName}</span>
|
<span>{currentModel.collectionName}</span>
|
||||||
{' > '}
|
{' > '}
|
||||||
@@ -275,143 +273,143 @@ export const HTTPClientView = ({model, index}) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Request/Response Container */}
|
{/* Request/Response Container */}
|
||||||
<div className="flex flex-col gap-4 flex-1 min-h-0">
|
<ScrollArea className="flex-1">
|
||||||
{/* Request Section */}
|
<div className="grid flex-1 gap-4 min-h-0">
|
||||||
<div className="flex-1 min-h-0">
|
{/* Request Section */}
|
||||||
<Tabs value={currentTab} onValueChange={setCurrentTab} className="flex flex-col h-full">
|
<div className="flex flex-col">
|
||||||
<TabsList>
|
<Tabs value={currentTab} onValueChange={setCurrentTab} className="flex flex-col h-full">
|
||||||
<TabsTrigger value="body">Body</TabsTrigger>
|
<TabsList>
|
||||||
<TabsTrigger value="params">Params</TabsTrigger>
|
<TabsTrigger value="body">Body</TabsTrigger>
|
||||||
<TabsTrigger value="headers">Headers</TabsTrigger>
|
<TabsTrigger value="params">Params</TabsTrigger>
|
||||||
<TabsTrigger value="pre-request">Pre-Request</TabsTrigger>
|
<TabsTrigger value="headers">Headers</TabsTrigger>
|
||||||
<TabsTrigger value="post-request">Post-Request</TabsTrigger>
|
<TabsTrigger value="pre-request">Pre-Request</TabsTrigger>
|
||||||
</TabsList>
|
<TabsTrigger value="post-request">Post-Request</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
<div className="flex-1 min-h-0 mt-2">
|
<div className="flex-1 min-h-0 mt-2">
|
||||||
{/* Body Tab Content */}
|
{/* Body Tab Content */}
|
||||||
<TabsContent value="body" className="h-full m-0">
|
<TabsContent value="body" className="h-full m-0">
|
||||||
<div className="flex flex-col h-full gap-2">
|
<div className="flex flex-col h-full gap-2">
|
||||||
<Select
|
<Select
|
||||||
value={currentModel.contentType}
|
value={currentModel.contentType}
|
||||||
onValueChange={(value) => setCurrentModel(prev => ({...prev, contentType: value}))}
|
onValueChange={(value) => setCurrentModel(prev => ({...prev, contentType: value}))}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Content Type"/>
|
<SelectValue placeholder="Content Type"/>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="json">JSON</SelectItem>
|
<SelectItem value="json">JSON</SelectItem>
|
||||||
<SelectItem value="xml">XML</SelectItem>
|
<SelectItem value="xml">XML</SelectItem>
|
||||||
<SelectItem value="plaintext">Text/Plain</SelectItem>
|
<SelectItem value="plaintext">Text/Plain</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<div className="h-[calc(100%-40px)]">
|
<div className="h-[calc(100%-40px)]">
|
||||||
<MonacoEditor
|
<MonacoEditor
|
||||||
id={`request-body-${model.id}`}
|
id={`request-body-${model.id}`}
|
||||||
value={currentModel.requestBody}
|
value={currentModel.requestBody}
|
||||||
language={
|
language={
|
||||||
currentModel.contentType
|
currentModel.contentType
|
||||||
}
|
}
|
||||||
onChange={(newValue) => {
|
onChange={(newValue) => {
|
||||||
setCurrentModel(prev => ({
|
setCurrentModel(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
requestBody: newValue
|
requestBody: newValue
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
height="full"
|
height="300px"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</TabsContent>
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="params" className="h-full m-0">
|
<TabsContent value="params" className="h-full m-0">
|
||||||
<PropertyTable
|
<PropertyTable
|
||||||
properties={currentModel.queryParams || []}
|
properties={currentModel.queryParams || []}
|
||||||
onChange={handleQueryParamsChange}
|
onChange={handleQueryParamsChange}
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* Headers Tab Content */}
|
{/* Headers Tab Content */}
|
||||||
<TabsContent value="headers" className="h-full m-0">
|
<TabsContent value="headers" className="h-full m-0">
|
||||||
<PropertyTable
|
<PropertyTable
|
||||||
properties={currentModel.headers || []}
|
properties={currentModel.headers || []}
|
||||||
onChange={handleHeadersChange}
|
onChange={handleHeadersChange}
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="pre-request" className="h-full m-0">
|
<TabsContent value="pre-request" className="h-full m-0">
|
||||||
<MonacoEditor
|
<MonacoEditor
|
||||||
id={`pre-request-${model.id}`}
|
id={`pre-request-${model.id}`}
|
||||||
value={editorContents.preRequestScript}
|
value={editorContents.preRequestScript}
|
||||||
language="javascript"
|
language="javascript"
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setEditorContents(prev => ({...prev, preRequestScript: value}));
|
setEditorContents(prev => ({...prev, preRequestScript: value}));
|
||||||
setCurrentModel(prev => ({...prev, preRequestScript: value}));
|
setCurrentModel(prev => ({...prev, preRequestScript: value}));
|
||||||
}}
|
}}
|
||||||
height="full"
|
height="300px"
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* Post-Request Tab Content */}
|
{/* Post-Request Tab Content */}
|
||||||
<TabsContent value="post-request" className="h-full m-0">
|
<TabsContent value="post-request" className="h-full m-0">
|
||||||
<MonacoEditor
|
<MonacoEditor
|
||||||
id={`post-request-${model.id}`}
|
id={`post-request-${model.id}`}
|
||||||
value={editorContents.postRequestScript}
|
value={editorContents.postRequestScript}
|
||||||
language="javascript"
|
language="javascript"
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setEditorContents(prev => ({...prev, postRequestScript: value}));
|
setEditorContents(prev => ({...prev, postRequestScript: value}));
|
||||||
setCurrentModel(prev => ({...prev, postRequestScript: value}));
|
setCurrentModel(prev => ({...prev, postRequestScript: value}));
|
||||||
}}
|
}}
|
||||||
height="full"
|
height="300px"
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</div>
|
</div>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Response Section */}
|
|
||||||
<div className="flex-1 min-h-0">
|
|
||||||
<div className="flex items-center gap-2 mb-2">
|
|
||||||
<span>Response</span>
|
|
||||||
<span className="text-sm text-gray-500">{responseData.status}</span>
|
|
||||||
<span className="text-sm text-gray-500">{responseData.time}</span>
|
|
||||||
<span className="text-sm text-gray-500">{responseData.size}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tabs value={currentResponseTab} onValueChange={setCurrentResponseTab} className="flex flex-col h-[calc(100%-2rem)]">
|
{/* Response Section */}
|
||||||
<TabsList>
|
<div className="flex flex-col">
|
||||||
<TabsTrigger value="response-body">Body</TabsTrigger>
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<TabsTrigger value="response-headers">Headers</TabsTrigger>
|
<span>Response</span>
|
||||||
<TabsTrigger value="console">Console</TabsTrigger>
|
<span className="text-sm text-gray-500">{responseData.status}</span>
|
||||||
</TabsList>
|
<span className="text-sm text-gray-500">{responseData.time}</span>
|
||||||
|
<span className="text-sm text-gray-500">{responseData.size}</span>
|
||||||
<div className="flex-1 min-h-0 mt-2">
|
|
||||||
<TabsContent value="response-body" className="h-full m-0">
|
|
||||||
<MonacoEditor
|
|
||||||
id={`response-body-${model.id}`}
|
|
||||||
value={editorContents.responseBody}
|
|
||||||
readOnly={true}
|
|
||||||
height="full"
|
|
||||||
/>
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="response-headers" className="h-full m-0">
|
|
||||||
{renderResponseHeaders()}
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="console" className="h-full m-0">
|
|
||||||
<MonacoEditor
|
|
||||||
id={`console-${model.id}`}
|
|
||||||
value={editorContents.consoleLog}
|
|
||||||
language="plaintext"
|
|
||||||
readOnly={true}
|
|
||||||
height="full"
|
|
||||||
/>
|
|
||||||
</TabsContent>
|
|
||||||
</div>
|
</div>
|
||||||
</Tabs>
|
|
||||||
|
<Tabs value={currentResponseTab} onValueChange={setCurrentResponseTab} className="flex flex-col h-[calc(100%-2rem)]">
|
||||||
|
<TabsList>
|
||||||
|
<TabsTrigger value="response-body">Body</TabsTrigger>
|
||||||
|
<TabsTrigger value="response-headers">Headers</TabsTrigger>
|
||||||
|
<TabsTrigger value="console">Console</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<div className="flex-1 min-h-0 mt-2">
|
||||||
|
<TabsContent value="response-body" className="h-full m-0">
|
||||||
|
<MonacoEditor
|
||||||
|
id={`response-body-${model.id}`}
|
||||||
|
value={editorContents.responseBody}
|
||||||
|
readOnly={true}
|
||||||
|
height="300px"
|
||||||
|
/>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="response-headers" className="h-full m-0">
|
||||||
|
{renderResponseHeaders()}
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="console" className="h-full m-0">
|
||||||
|
<MonacoEditor
|
||||||
|
id={`console-${model.id}`}
|
||||||
|
value={editorContents.consoleLog}
|
||||||
|
language="plaintext"
|
||||||
|
readOnly={true}
|
||||||
|
height="300px"
|
||||||
|
/>
|
||||||
|
</TabsContent>
|
||||||
|
</div>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ScrollArea>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,10 +7,9 @@ import MonacoEditor from '@/components/shared/MonacoEditor';
|
|||||||
import APIClient from '@/services/APIClient';
|
import APIClient from '@/services/APIClient';
|
||||||
import {SidebarTrigger} from '@/components/ui/sidebar.jsx';
|
import {SidebarTrigger} from '@/components/ui/sidebar.jsx';
|
||||||
import {Separator} from '@/components/ui/separator.jsx';
|
import {Separator} from '@/components/ui/separator.jsx';
|
||||||
import LayoutGrid from '@/components/layout/LayoutGrid.jsx';
|
|
||||||
import LayoutTreeGrid from '@/components/layout/LayoutTreeGrid.jsx';
|
|
||||||
import LayoutTreeDataGrid from '@/components/layout/LayoutTreeDataGrid.jsx';
|
|
||||||
import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card.jsx';
|
import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card.jsx';
|
||||||
|
import FlatLayoutManager from '@/components/layout/FlatLayoutManager.jsx';
|
||||||
|
import {ScrollArea} from '@/components/ui/scroll-area.jsx';
|
||||||
|
|
||||||
export const TCPClientView = ({model, index}) => {
|
export const TCPClientView = ({model, index}) => {
|
||||||
const apiClient = new APIClient();
|
const apiClient = new APIClient();
|
||||||
@@ -19,7 +18,7 @@ export const TCPClientView = ({model, index}) => {
|
|||||||
const [currentModel, setCurrentModel] = useState(model);
|
const [currentModel, setCurrentModel] = useState(model);
|
||||||
const [currentTab, setCurrentTab] = useState('body');
|
const [currentTab, setCurrentTab] = useState('body');
|
||||||
const [currentResponseTab, setCurrentResponseTab] = useState('response-body');
|
const [currentResponseTab, setCurrentResponseTab] = useState('response-body');
|
||||||
const [layoutItems, setLayoutItems] = useState(model.layout?.layoutItems || []);
|
// const [layoutItems, setLayoutItems] = useState(model.layout?.layoutItems || []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentModel(prev => ({
|
setCurrentModel(prev => ({
|
||||||
@@ -48,7 +47,7 @@ export const TCPClientView = ({model, index}) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentModel.server) {
|
if (currentModel.server) {
|
||||||
const selectedServer = getServer(currentModel.server);
|
getServer(currentModel.server);
|
||||||
}
|
}
|
||||||
}, [currentModel.server]);
|
}, [currentModel.server]);
|
||||||
|
|
||||||
@@ -115,58 +114,35 @@ export const TCPClientView = ({model, index}) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleLayoutUpdate = useCallback((updatedLayout) => {
|
const handleLayoutUpdate = useCallback((updatedLayout) => {
|
||||||
setLayoutItems(updatedLayout.layoutItems); // Add this
|
setCurrentModel(prev => ({
|
||||||
setCurrentModel(prev => ({ // Update this to use setState
|
|
||||||
...prev,
|
...prev,
|
||||||
layout: updatedLayout
|
layout: updatedLayout
|
||||||
}));
|
}));
|
||||||
|
|
||||||
setEditorContents(prev => ({
|
|
||||||
...prev,
|
|
||||||
layout: JSON.stringify(updatedLayout, null, 2)
|
|
||||||
}));
|
|
||||||
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleLayoutDataUpdate = useCallback((updatedLayout) => {
|
const handleLayoutDataUpdate = useCallback((updatedLayout) => {
|
||||||
console.log(updatedLayout);
|
const requestBody = generateRequestBody(updatedLayout.computedLayoutItemRoot);
|
||||||
|
|
||||||
// Update layout and request body based on the data tree
|
|
||||||
setCurrentModel(prev => ({
|
setCurrentModel(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
layout: updatedLayout,
|
layout: updatedLayout,
|
||||||
requestBody: generateRequestBody(updatedLayout.computedLayoutItemRoot)
|
requestBody
|
||||||
}));
|
|
||||||
|
|
||||||
// Update editor contents with new request body
|
|
||||||
setEditorContents(prev => ({
|
|
||||||
...prev,
|
|
||||||
requestBody: generateRequestBody(updatedLayout.computedLayoutItemRoot)
|
|
||||||
}));
|
}));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const generateRequestBody = useCallback((layoutRoot) => {
|
const generateRequestBody = useCallback((layoutRoot) => {
|
||||||
console.log(layoutRoot);
|
|
||||||
if (!layoutRoot) return '';
|
if (!layoutRoot) return '';
|
||||||
|
|
||||||
// Helper function to pad field values with spaces
|
|
||||||
const padField = (value, length) => {
|
const padField = (value, length) => {
|
||||||
// Convert to string and ensure it's not null/undefined
|
|
||||||
const stringValue = String(value || '');
|
const stringValue = String(value || '');
|
||||||
// Left pad with spaces to match the specified length
|
|
||||||
return stringValue.padStart(length, ' ');
|
return stringValue.padStart(length, ' ');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Process nodes using recursive function
|
|
||||||
const processNodes = (node) => {
|
const processNodes = (node) => {
|
||||||
// If it's a Field type node, pad the value
|
|
||||||
if (node.loutItemType === 'Field') {
|
if (node.loutItemType === 'Field') {
|
||||||
return padField(node.value, node.loutItemLength);
|
return padField(node.value, node.loutItemLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If node has children
|
|
||||||
if (node.children && node.children.length > 0) {
|
if (node.children && node.children.length > 0) {
|
||||||
// For non-repeating nodes, just process children
|
|
||||||
return node.children
|
return node.children
|
||||||
.map(child => processNodes(child))
|
.map(child => processNodes(child))
|
||||||
.join('');
|
.join('');
|
||||||
@@ -178,6 +154,7 @@ export const TCPClientView = ({model, index}) => {
|
|||||||
return processNodes(layoutRoot);
|
return processNodes(layoutRoot);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<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 */}
|
||||||
@@ -202,158 +179,133 @@ export const TCPClientView = ({model, index}) => {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button variant="secondary" onClick={handleSave}>Save</Button>
|
<Button variant="secondary" onClick={handleSave}>Save</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<ScrollArea className="flex-1">
|
||||||
|
<div className="grid flex-1 gap-4 min-h-0">
|
||||||
|
{/* 요청 영역 */}
|
||||||
|
<Card className="flex flex-col">
|
||||||
|
<CardHeader className="p-4 pb-2">
|
||||||
|
<CardTitle>Request</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-col flex-1 p-4 pt-2 min-h-0">
|
||||||
|
<Tabs value={currentTab} onValueChange={setCurrentTab} className="flex flex-col flex-1">
|
||||||
|
<TabsList>
|
||||||
|
<TabsTrigger value="body">Layout</TabsTrigger>
|
||||||
|
<TabsTrigger value="pre-request">Pre-Request</TabsTrigger>
|
||||||
|
<TabsTrigger value="post-request">Post-Request</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
<div className="grid flex-1 gap-4 min-h-0">
|
<div className="flex-1 min-h-0 mt-2">
|
||||||
{/* 요청 영역 */}
|
<TabsContent value="body" className="h-full m-0">
|
||||||
<Card className="flex flex-col">
|
<FlatLayoutManager
|
||||||
<CardHeader className="p-4 pb-2">
|
initialLayout={currentModel.layout}
|
||||||
<CardTitle>Request</CardTitle>
|
modelId={model.id}
|
||||||
</CardHeader>
|
onLayoutChange={handleLayoutUpdate}
|
||||||
<CardContent className="flex flex-col flex-1 p-4 pt-2 min-h-0">
|
onLayoutDataChange={handleLayoutDataUpdate}
|
||||||
<Tabs value={currentTab} onValueChange={setCurrentTab} className="flex flex-col flex-1">
|
onRequestBodyChange={(body) => {
|
||||||
<TabsList>
|
setCurrentModel(prev => ({
|
||||||
<TabsTrigger value="body">Layout</TabsTrigger>
|
...prev,
|
||||||
<TabsTrigger value="layout-tree">Layout Tree</TabsTrigger>
|
requestBody: body
|
||||||
<TabsTrigger value="layout-data">Layout Data</TabsTrigger>
|
}));
|
||||||
<TabsTrigger value="layout-json">Layout JSON</TabsTrigger>
|
}}
|
||||||
<TabsTrigger value="pre-request">Pre-Request</TabsTrigger>
|
/>
|
||||||
<TabsTrigger value="post-request">Post-Request</TabsTrigger>
|
</TabsContent>
|
||||||
</TabsList>
|
<TabsContent value="pre-request" className="h-full m-0">
|
||||||
|
<MonacoEditor
|
||||||
|
id={`pre-request-${model.id}`}
|
||||||
|
value={editorContents.preRequestScript}
|
||||||
|
language="javascript"
|
||||||
|
onChange={(value) => {
|
||||||
|
setEditorContents(prev => ({...prev, preRequestScript: value}));
|
||||||
|
setCurrentModel(prev => ({...prev, preRequestScript: value}));
|
||||||
|
}}
|
||||||
|
height="300px"
|
||||||
|
/>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
<div className="flex-1 min-h-0 mt-2">
|
{/* Post-Request Tab Content */}
|
||||||
{/* Body Tab Content */}
|
<TabsContent value="post-request" className="h-full m-0">
|
||||||
<TabsContent value="body" className="h-full m-0">
|
<MonacoEditor
|
||||||
<LayoutGrid
|
id={`post-request-${model.id}`}
|
||||||
layout={currentModel.layout}
|
value={editorContents.postRequestScript}
|
||||||
layoutItems={layoutItems} // Add this
|
language="javascript"
|
||||||
setLayoutItems={setLayoutItems} // Add this
|
onChange={(value) => {
|
||||||
onChange={handleLayoutUpdate}
|
setEditorContents(prev => ({...prev, postRequestScript: value}));
|
||||||
/>
|
setCurrentModel(prev => ({...prev, postRequestScript: value}));
|
||||||
</TabsContent>
|
}}
|
||||||
<TabsContent value="layout-tree" className="h-full m-0">
|
height="300px"
|
||||||
<LayoutTreeGrid
|
/>
|
||||||
layout={currentModel.layout}
|
</TabsContent>
|
||||||
layoutItems={layoutItems} // Add this
|
</div>
|
||||||
setLayoutItems={setLayoutItems} // Add this
|
</Tabs>
|
||||||
onChange={handleLayoutUpdate}
|
|
||||||
/>
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="layout-data" className="h-full m-0">
|
|
||||||
<LayoutTreeDataGrid
|
|
||||||
layout={currentModel.layout}
|
|
||||||
layoutItems={layoutItems} // Add this
|
|
||||||
setLayoutItems={setLayoutItems} // Add this
|
|
||||||
onChange={handleLayoutDataUpdate}
|
|
||||||
/>
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="layout-json" className="h-full m-0">
|
|
||||||
<MonacoEditor
|
|
||||||
id={`layout-json-${model.id}`}
|
|
||||||
value={editorContents.layout}
|
|
||||||
language="json"
|
|
||||||
onChange={(value) => {
|
|
||||||
setEditorContents(prev => ({...prev, layout: value}));
|
|
||||||
setCurrentModel(prev => ({...prev, layout: JSON.parse(value)}));
|
|
||||||
}}
|
|
||||||
height="full"
|
|
||||||
/>
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="pre-request" className="h-full m-0">
|
|
||||||
<MonacoEditor
|
|
||||||
id={`pre-request-${model.id}`}
|
|
||||||
value={editorContents.preRequestScript}
|
|
||||||
language="javascript"
|
|
||||||
onChange={(value) => {
|
|
||||||
setEditorContents(prev => ({...prev, preRequestScript: value}));
|
|
||||||
setCurrentModel(prev => ({...prev, preRequestScript: value}));
|
|
||||||
}}
|
|
||||||
height="full"
|
|
||||||
/>
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
{/* Post-Request Tab Content */}
|
<Separator className="my-4" />
|
||||||
<TabsContent value="post-request" className="h-full m-0">
|
|
||||||
<MonacoEditor
|
<div className="flex-none">
|
||||||
id={`post-request-${model.id}`}
|
<div className="font-medium mb-2">Request Body</div>
|
||||||
value={editorContents.postRequestScript}
|
<MonacoEditor
|
||||||
language="javascript"
|
id={`request-body-${model.id}`}
|
||||||
onChange={(value) => {
|
value={currentModel.requestBody}
|
||||||
setEditorContents(prev => ({...prev, postRequestScript: value}));
|
language="plaintext"
|
||||||
setCurrentModel(prev => ({...prev, postRequestScript: value}));
|
onChange={(newValue) => {
|
||||||
}}
|
setCurrentModel(prev => ({
|
||||||
height="full"
|
...prev,
|
||||||
/>
|
requestBody: newValue
|
||||||
</TabsContent>
|
}));
|
||||||
|
}}
|
||||||
|
height="150px"
|
||||||
|
/>
|
||||||
|
<div className="mt-2 text-sm text-gray-600">
|
||||||
|
<strong>전체 메시지 길이:</strong> {currentModel.requestBody?.length || 0}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Tabs>
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Separator className="my-4" />
|
{/* 응답 영역 */}
|
||||||
|
<Card className="flex flex-col">
|
||||||
|
<CardHeader className="p-4 pb-2">
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<span>Response</span>
|
||||||
|
<span className="text-sm font-normal text-gray-500">{responseData.status}</span>
|
||||||
|
<span className="text-sm font-normal text-gray-500">{responseData.time}</span>
|
||||||
|
<span className="text-sm font-normal text-gray-500">{responseData.size}</span>
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex flex-col flex-1 p-4 pt-2 min-h-0">
|
||||||
|
<Tabs
|
||||||
|
value={currentResponseTab}
|
||||||
|
onValueChange={setCurrentResponseTab}
|
||||||
|
className="flex flex-col flex-1"
|
||||||
|
>
|
||||||
|
<TabsList>
|
||||||
|
<TabsTrigger value="response-body">Body</TabsTrigger>
|
||||||
|
<TabsTrigger value="console">Console</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
<div className="flex-none">
|
<div className="flex-1 min-h-0 mt-2">
|
||||||
<div className="font-medium mb-2">Request Body</div>
|
<TabsContent value="response-body" className="h-full m-0">
|
||||||
<MonacoEditor
|
<MonacoEditor
|
||||||
id={`request-body-${model.id}`}
|
id={`response-body-${model.id}`}
|
||||||
value={currentModel.requestBody}
|
value={editorContents.responseBody}
|
||||||
language="plaintext"
|
readOnly={true}
|
||||||
onChange={(newValue) => {
|
height="250px"
|
||||||
setCurrentModel(prev => ({
|
/>
|
||||||
...prev,
|
</TabsContent>
|
||||||
requestBody: newValue
|
<TabsContent value="console" className="h-full m-0">
|
||||||
}));
|
<MonacoEditor
|
||||||
}}
|
id={`console-${model.id}`}
|
||||||
height="200px"
|
value={editorContents.consoleLog}
|
||||||
/>
|
language="plaintext"
|
||||||
<div className="mt-2 text-sm text-gray-600">
|
readOnly={true}
|
||||||
<strong>전체 메시지 길이:</strong> {currentModel.requestBody?.length || 0}
|
height="250px"
|
||||||
</div>
|
/>
|
||||||
</div>
|
</TabsContent>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</Tabs>
|
||||||
|
</CardContent>
|
||||||
{/* 응답 영역 */}
|
</Card>
|
||||||
<Card className="flex flex-col">
|
</div>
|
||||||
<CardHeader className="p-4 pb-2">
|
</ScrollArea>
|
||||||
<CardTitle className="flex items-center gap-2">
|
|
||||||
<span>Response</span>
|
|
||||||
<span className="text-sm font-normal text-gray-500">{responseData.status}</span>
|
|
||||||
<span className="text-sm font-normal text-gray-500">{responseData.time}</span>
|
|
||||||
<span className="text-sm font-normal text-gray-500">{responseData.size}</span>
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="flex flex-col flex-1 p-4 pt-2 min-h-0">
|
|
||||||
<Tabs
|
|
||||||
value={currentResponseTab}
|
|
||||||
onValueChange={setCurrentResponseTab}
|
|
||||||
className="flex flex-col flex-1"
|
|
||||||
>
|
|
||||||
<TabsList>
|
|
||||||
<TabsTrigger value="response-body">Body</TabsTrigger>
|
|
||||||
<TabsTrigger value="console">Console</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
|
|
||||||
<div className="flex-1 min-h-0 mt-2">
|
|
||||||
<TabsContent value="response-body" className="h-full m-0">
|
|
||||||
<MonacoEditor
|
|
||||||
id={`response-body-${model.id}`}
|
|
||||||
value={editorContents.responseBody}
|
|
||||||
readOnly={true}
|
|
||||||
height="full"
|
|
||||||
/>
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="console" className="h-full m-0">
|
|
||||||
<MonacoEditor
|
|
||||||
id={`console-${model.id}`}
|
|
||||||
value={editorContents.consoleLog}
|
|
||||||
language="plaintext"
|
|
||||||
readOnly={true}
|
|
||||||
height="full"
|
|
||||||
/>
|
|
||||||
</TabsContent>
|
|
||||||
</div>
|
|
||||||
</Tabs>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
import React, { useState, useCallback } from 'react';
|
||||||
|
import LayoutGrid from '@/components/layout/LayoutGrid';
|
||||||
|
import LayoutTreeGrid from '@/components/layout/LayoutTreeGrid';
|
||||||
|
import LayoutTreeDataGrid from '@/components/layout/LayoutTreeDataGrid';
|
||||||
|
import MonacoEditor from '@/components/shared/MonacoEditor';
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
|
|
||||||
|
const FlatLayoutManager = ({
|
||||||
|
initialLayout,
|
||||||
|
onLayoutChange,
|
||||||
|
onLayoutDataChange,
|
||||||
|
onRequestBodyChange,
|
||||||
|
modelId
|
||||||
|
}) => {
|
||||||
|
const [currentLayout, setCurrentLayout] = useState(initialLayout);
|
||||||
|
const [layoutItems, setLayoutItems] = useState(initialLayout?.layoutItems || []);
|
||||||
|
const [layoutJson, setLayoutJson] = useState(
|
||||||
|
JSON.stringify(initialLayout, null, 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Shared layout update handler
|
||||||
|
const handleLayoutUpdate = useCallback((updatedLayout) => {
|
||||||
|
// Clear the computed tree when layout changes
|
||||||
|
const layoutToUpdate = {
|
||||||
|
...updatedLayout,
|
||||||
|
computedLayoutItemRoot: null // Clear the computed tree
|
||||||
|
};
|
||||||
|
setCurrentLayout(layoutToUpdate);
|
||||||
|
setLayoutItems(layoutToUpdate.layoutItems);
|
||||||
|
setLayoutJson(JSON.stringify(layoutToUpdate, null, 2));
|
||||||
|
onLayoutChange?.(layoutToUpdate);
|
||||||
|
}, [onLayoutChange]);
|
||||||
|
|
||||||
|
// Layout data update handler (for LayoutTreeDataGrid)
|
||||||
|
const handleLayoutDataUpdate = useCallback((updatedLayout) => {
|
||||||
|
setCurrentLayout(updatedLayout);
|
||||||
|
setLayoutItems(updatedLayout.layoutItems);
|
||||||
|
setLayoutJson(JSON.stringify(updatedLayout, null, 2));
|
||||||
|
onLayoutDataChange?.(updatedLayout);
|
||||||
|
}, [onLayoutDataChange]);
|
||||||
|
|
||||||
|
// JSON editor change handler
|
||||||
|
const handleJsonChange = useCallback((value) => {
|
||||||
|
try {
|
||||||
|
const parsedLayout = JSON.parse(value);
|
||||||
|
setLayoutJson(value);
|
||||||
|
setCurrentLayout(parsedLayout);
|
||||||
|
setLayoutItems(parsedLayout.layoutItems || []);
|
||||||
|
onLayoutChange?.(parsedLayout);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Invalid JSON:', error);
|
||||||
|
}
|
||||||
|
}, [onLayoutChange]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tabs defaultValue="grid">
|
||||||
|
<TabsList>
|
||||||
|
<TabsTrigger value="grid">Layout</TabsTrigger>
|
||||||
|
<TabsTrigger value="tree">Layout Tree</TabsTrigger>
|
||||||
|
<TabsTrigger value="data">Layout Data</TabsTrigger>
|
||||||
|
<TabsTrigger value="json">Layout JSON</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<div className="flex-1 min-h-0 mt-2">
|
||||||
|
<TabsContent value="grid" className="h-full m-0">
|
||||||
|
<LayoutGrid
|
||||||
|
layout={currentLayout}
|
||||||
|
layoutItems={layoutItems}
|
||||||
|
setLayoutItems={setLayoutItems}
|
||||||
|
onChange={handleLayoutUpdate}
|
||||||
|
/>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="tree" className="h-full m-0">
|
||||||
|
<LayoutTreeGrid
|
||||||
|
layout={currentLayout}
|
||||||
|
layoutItems={layoutItems}
|
||||||
|
/>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="data" className="h-full m-0">
|
||||||
|
<LayoutTreeDataGrid
|
||||||
|
layout={currentLayout}
|
||||||
|
onChange={handleLayoutDataUpdate}
|
||||||
|
/>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="json" className="h-full m-0">
|
||||||
|
<MonacoEditor
|
||||||
|
id={`layout-json-${modelId}`}
|
||||||
|
value={layoutJson}
|
||||||
|
language="json"
|
||||||
|
onChange={handleJsonChange}
|
||||||
|
height="300px"
|
||||||
|
/>
|
||||||
|
</TabsContent>
|
||||||
|
</div>
|
||||||
|
</Tabs>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FlatLayoutManager;
|
||||||
Reference in New Issue
Block a user