탭 정리
This commit is contained in:
@@ -5,6 +5,7 @@ import { TCPClientView } from '../client-views/TCPClientView';
|
||||
import { HTTPFlatClientView } from '../client-views/HTTPFlatClientView';
|
||||
import { X } from 'lucide-react';
|
||||
import {Button} from '@/components/ui/button.jsx';
|
||||
import {ScrollArea} from '@/components/ui/scroll-area.jsx';
|
||||
|
||||
const APITabs = ({
|
||||
apiRequests,
|
||||
|
||||
@@ -12,6 +12,7 @@ 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';
|
||||
import {ScrollArea} from '@/components/ui/scroll-area.jsx';
|
||||
|
||||
const HTTP_METHODS = [
|
||||
'POST', 'GET', 'PUT', 'DELETE', 'CONNECT',
|
||||
@@ -96,16 +97,15 @@ export const HTTPClientView = ({model, index}) => {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const buildUrlFromPathAndParams = (path, queryParams) => {
|
||||
const baseUrl = path.split('?')[0];
|
||||
const enabledParams = queryParams.filter(p => p.enabled && p.key);
|
||||
|
||||
if (!enabledParams.length) return baseUrl;
|
||||
if (!enabledParams.length) {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
const queryString = enabledParams
|
||||
.map(p => `${p.key}=${p.value || ''}`)
|
||||
.join('&');
|
||||
const queryString = enabledParams.map(p => `${p.key}=${p.value || ''}`).join('&');
|
||||
|
||||
return `${baseUrl}?${queryString}`;
|
||||
};
|
||||
@@ -119,7 +119,6 @@ export const HTTPClientView = ({model, index}) => {
|
||||
}));
|
||||
};
|
||||
|
||||
|
||||
const parseQueryParams = (path) => {
|
||||
const queryParams = [];
|
||||
const [, queryString] = path.split('?');
|
||||
@@ -176,7 +175,6 @@ export const HTTPClientView = ({model, index}) => {
|
||||
responseBody: response.body
|
||||
}));
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setEditorContents(prev => ({
|
||||
@@ -228,8 +226,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" />
|
||||
<SidebarTrigger className="-ml-1"/>
|
||||
<Separator orientation="vertical" className="mr-2 h-4"/>
|
||||
<div>
|
||||
<span>{currentModel.collectionName}</span>
|
||||
{' > '}
|
||||
@@ -275,9 +273,10 @@ export const HTTPClientView = ({model, index}) => {
|
||||
</div>
|
||||
|
||||
{/* Request/Response Container */}
|
||||
<div className="flex flex-col gap-4 flex-1 min-h-0">
|
||||
<ScrollArea className="flex-1">
|
||||
<div className="grid flex-1 gap-4 min-h-0">
|
||||
{/* Request Section */}
|
||||
<div className="flex-1 min-h-0">
|
||||
<div className="flex flex-col">
|
||||
<Tabs value={currentTab} onValueChange={setCurrentTab} className="flex flex-col h-full">
|
||||
<TabsList>
|
||||
<TabsTrigger value="body">Body</TabsTrigger>
|
||||
@@ -317,7 +316,7 @@ export const HTTPClientView = ({model, index}) => {
|
||||
requestBody: newValue
|
||||
}));
|
||||
}}
|
||||
height="full"
|
||||
height="300px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -346,7 +345,7 @@ export const HTTPClientView = ({model, index}) => {
|
||||
setEditorContents(prev => ({...prev, preRequestScript: value}));
|
||||
setCurrentModel(prev => ({...prev, preRequestScript: value}));
|
||||
}}
|
||||
height="full"
|
||||
height="300px"
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
@@ -360,7 +359,7 @@ export const HTTPClientView = ({model, index}) => {
|
||||
setEditorContents(prev => ({...prev, postRequestScript: value}));
|
||||
setCurrentModel(prev => ({...prev, postRequestScript: value}));
|
||||
}}
|
||||
height="full"
|
||||
height="300px"
|
||||
/>
|
||||
</TabsContent>
|
||||
</div>
|
||||
@@ -368,7 +367,7 @@ export const HTTPClientView = ({model, index}) => {
|
||||
</div>
|
||||
|
||||
{/* Response Section */}
|
||||
<div className="flex-1 min-h-0">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span>Response</span>
|
||||
<span className="text-sm text-gray-500">{responseData.status}</span>
|
||||
@@ -389,7 +388,7 @@ export const HTTPClientView = ({model, index}) => {
|
||||
id={`response-body-${model.id}`}
|
||||
value={editorContents.responseBody}
|
||||
readOnly={true}
|
||||
height="full"
|
||||
height="300px"
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
@@ -403,15 +402,14 @@ export const HTTPClientView = ({model, index}) => {
|
||||
value={editorContents.consoleLog}
|
||||
language="plaintext"
|
||||
readOnly={true}
|
||||
height="full"
|
||||
height="300px"
|
||||
/>
|
||||
</TabsContent>
|
||||
</div>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</ScrollArea>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,10 +7,9 @@ import MonacoEditor from '@/components/shared/MonacoEditor';
|
||||
import APIClient from '@/services/APIClient';
|
||||
import {SidebarTrigger} from '@/components/ui/sidebar.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 FlatLayoutManager from '@/components/layout/FlatLayoutManager.jsx';
|
||||
import {ScrollArea} from '@/components/ui/scroll-area.jsx';
|
||||
|
||||
export const TCPClientView = ({model, index}) => {
|
||||
const apiClient = new APIClient();
|
||||
@@ -19,7 +18,7 @@ export const TCPClientView = ({model, index}) => {
|
||||
const [currentModel, setCurrentModel] = useState(model);
|
||||
const [currentTab, setCurrentTab] = useState('body');
|
||||
const [currentResponseTab, setCurrentResponseTab] = useState('response-body');
|
||||
const [layoutItems, setLayoutItems] = useState(model.layout?.layoutItems || []);
|
||||
// const [layoutItems, setLayoutItems] = useState(model.layout?.layoutItems || []);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentModel(prev => ({
|
||||
@@ -48,7 +47,7 @@ export const TCPClientView = ({model, index}) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (currentModel.server) {
|
||||
const selectedServer = getServer(currentModel.server);
|
||||
getServer(currentModel.server);
|
||||
}
|
||||
}, [currentModel.server]);
|
||||
|
||||
@@ -115,58 +114,35 @@ export const TCPClientView = ({model, index}) => {
|
||||
};
|
||||
|
||||
const handleLayoutUpdate = useCallback((updatedLayout) => {
|
||||
setLayoutItems(updatedLayout.layoutItems); // Add this
|
||||
setCurrentModel(prev => ({ // Update this to use setState
|
||||
setCurrentModel(prev => ({
|
||||
...prev,
|
||||
layout: updatedLayout
|
||||
}));
|
||||
|
||||
setEditorContents(prev => ({
|
||||
...prev,
|
||||
layout: JSON.stringify(updatedLayout, null, 2)
|
||||
}));
|
||||
|
||||
}, []);
|
||||
|
||||
const handleLayoutDataUpdate = useCallback((updatedLayout) => {
|
||||
console.log(updatedLayout);
|
||||
|
||||
// Update layout and request body based on the data tree
|
||||
const requestBody = generateRequestBody(updatedLayout.computedLayoutItemRoot);
|
||||
setCurrentModel(prev => ({
|
||||
...prev,
|
||||
layout: updatedLayout,
|
||||
requestBody: generateRequestBody(updatedLayout.computedLayoutItemRoot)
|
||||
}));
|
||||
|
||||
// Update editor contents with new request body
|
||||
setEditorContents(prev => ({
|
||||
...prev,
|
||||
requestBody: generateRequestBody(updatedLayout.computedLayoutItemRoot)
|
||||
requestBody
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const generateRequestBody = useCallback((layoutRoot) => {
|
||||
console.log(layoutRoot);
|
||||
if (!layoutRoot) return '';
|
||||
|
||||
// Helper function to pad field values with spaces
|
||||
const padField = (value, length) => {
|
||||
// Convert to string and ensure it's not null/undefined
|
||||
const stringValue = String(value || '');
|
||||
// Left pad with spaces to match the specified length
|
||||
return stringValue.padStart(length, ' ');
|
||||
};
|
||||
|
||||
// Process nodes using recursive function
|
||||
const processNodes = (node) => {
|
||||
// If it's a Field type node, pad the value
|
||||
if (node.loutItemType === 'Field') {
|
||||
return padField(node.value, node.loutItemLength);
|
||||
}
|
||||
|
||||
// If node has children
|
||||
if (node.children && node.children.length > 0) {
|
||||
// For non-repeating nodes, just process children
|
||||
return node.children
|
||||
.map(child => processNodes(child))
|
||||
.join('');
|
||||
@@ -178,6 +154,7 @@ export const TCPClientView = ({model, index}) => {
|
||||
return processNodes(layoutRoot);
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full p-4 gap-4">
|
||||
{/* Request Header Section */}
|
||||
@@ -202,7 +179,7 @@ export const TCPClientView = ({model, index}) => {
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={handleSave}>Save</Button>
|
||||
</div>
|
||||
|
||||
<ScrollArea className="flex-1">
|
||||
<div className="grid flex-1 gap-4 min-h-0">
|
||||
{/* 요청 영역 */}
|
||||
<Card className="flex flex-col">
|
||||
@@ -213,49 +190,23 @@ export const TCPClientView = ({model, index}) => {
|
||||
<Tabs value={currentTab} onValueChange={setCurrentTab} className="flex flex-col flex-1">
|
||||
<TabsList>
|
||||
<TabsTrigger value="body">Layout</TabsTrigger>
|
||||
<TabsTrigger value="layout-tree">Layout Tree</TabsTrigger>
|
||||
<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>
|
||||
</TabsList>
|
||||
|
||||
<div className="flex-1 min-h-0 mt-2">
|
||||
{/* Body Tab Content */}
|
||||
<TabsContent value="body" className="h-full m-0">
|
||||
<LayoutGrid
|
||||
layout={currentModel.layout}
|
||||
layoutItems={layoutItems} // Add this
|
||||
setLayoutItems={setLayoutItems} // Add this
|
||||
onChange={handleLayoutUpdate}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="layout-tree" className="h-full m-0">
|
||||
<LayoutTreeGrid
|
||||
layout={currentModel.layout}
|
||||
layoutItems={layoutItems} // Add this
|
||||
setLayoutItems={setLayoutItems} // Add this
|
||||
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)}));
|
||||
<FlatLayoutManager
|
||||
initialLayout={currentModel.layout}
|
||||
modelId={model.id}
|
||||
onLayoutChange={handleLayoutUpdate}
|
||||
onLayoutDataChange={handleLayoutDataUpdate}
|
||||
onRequestBodyChange={(body) => {
|
||||
setCurrentModel(prev => ({
|
||||
...prev,
|
||||
requestBody: body
|
||||
}));
|
||||
}}
|
||||
height="full"
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="pre-request" className="h-full m-0">
|
||||
@@ -267,7 +218,7 @@ export const TCPClientView = ({model, index}) => {
|
||||
setEditorContents(prev => ({...prev, preRequestScript: value}));
|
||||
setCurrentModel(prev => ({...prev, preRequestScript: value}));
|
||||
}}
|
||||
height="full"
|
||||
height="300px"
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
@@ -281,7 +232,7 @@ export const TCPClientView = ({model, index}) => {
|
||||
setEditorContents(prev => ({...prev, postRequestScript: value}));
|
||||
setCurrentModel(prev => ({...prev, postRequestScript: value}));
|
||||
}}
|
||||
height="full"
|
||||
height="300px"
|
||||
/>
|
||||
</TabsContent>
|
||||
</div>
|
||||
@@ -301,7 +252,7 @@ export const TCPClientView = ({model, index}) => {
|
||||
requestBody: newValue
|
||||
}));
|
||||
}}
|
||||
height="200px"
|
||||
height="150px"
|
||||
/>
|
||||
<div className="mt-2 text-sm text-gray-600">
|
||||
<strong>전체 메시지 길이:</strong> {currentModel.requestBody?.length || 0}
|
||||
@@ -337,7 +288,7 @@ export const TCPClientView = ({model, index}) => {
|
||||
id={`response-body-${model.id}`}
|
||||
value={editorContents.responseBody}
|
||||
readOnly={true}
|
||||
height="full"
|
||||
height="250px"
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="console" className="h-full m-0">
|
||||
@@ -346,7 +297,7 @@ export const TCPClientView = ({model, index}) => {
|
||||
value={editorContents.consoleLog}
|
||||
language="plaintext"
|
||||
readOnly={true}
|
||||
height="full"
|
||||
height="250px"
|
||||
/>
|
||||
</TabsContent>
|
||||
</div>
|
||||
@@ -354,6 +305,7 @@ export const TCPClientView = ({model, index}) => {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</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