탭 정리

This commit is contained in:
현성필
2025-02-02 22:22:37 +09:00
parent b02596ed04
commit 40e6aac336
4 changed files with 367 additions and 314 deletions
@@ -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;