탭 정리
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 => ({
|
||||||
@@ -275,9 +273,10 @@ 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">
|
||||||
|
<div className="grid flex-1 gap-4 min-h-0">
|
||||||
{/* Request Section */}
|
{/* 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">
|
<Tabs value={currentTab} onValueChange={setCurrentTab} className="flex flex-col h-full">
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="body">Body</TabsTrigger>
|
<TabsTrigger value="body">Body</TabsTrigger>
|
||||||
@@ -317,7 +316,7 @@ export const HTTPClientView = ({model, index}) => {
|
|||||||
requestBody: newValue
|
requestBody: newValue
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
height="full"
|
height="300px"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -346,7 +345,7 @@ export const HTTPClientView = ({model, index}) => {
|
|||||||
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>
|
||||||
|
|
||||||
@@ -360,7 +359,7 @@ export const HTTPClientView = ({model, index}) => {
|
|||||||
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>
|
||||||
@@ -368,7 +367,7 @@ export const HTTPClientView = ({model, index}) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Response Section */}
|
{/* Response Section */}
|
||||||
<div className="flex-1 min-h-0">
|
<div className="flex flex-col">
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<span>Response</span>
|
<span>Response</span>
|
||||||
<span className="text-sm text-gray-500">{responseData.status}</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}`}
|
id={`response-body-${model.id}`}
|
||||||
value={editorContents.responseBody}
|
value={editorContents.responseBody}
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
height="full"
|
height="300px"
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
@@ -403,15 +402,14 @@ export const HTTPClientView = ({model, index}) => {
|
|||||||
value={editorContents.consoleLog}
|
value={editorContents.consoleLog}
|
||||||
language="plaintext"
|
language="plaintext"
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
height="full"
|
height="300px"
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</div>
|
</div>
|
||||||
</Tabs>
|
</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,7 +179,7 @@ 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">
|
<div className="grid flex-1 gap-4 min-h-0">
|
||||||
{/* 요청 영역 */}
|
{/* 요청 영역 */}
|
||||||
<Card className="flex flex-col">
|
<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">
|
<Tabs value={currentTab} onValueChange={setCurrentTab} className="flex flex-col flex-1">
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="body">Layout</TabsTrigger>
|
<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="pre-request">Pre-Request</TabsTrigger>
|
||||||
<TabsTrigger value="post-request">Post-Request</TabsTrigger>
|
<TabsTrigger value="post-request">Post-Request</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<div className="flex-1 min-h-0 mt-2">
|
<div className="flex-1 min-h-0 mt-2">
|
||||||
{/* Body Tab Content */}
|
|
||||||
<TabsContent value="body" className="h-full m-0">
|
<TabsContent value="body" className="h-full m-0">
|
||||||
<LayoutGrid
|
<FlatLayoutManager
|
||||||
layout={currentModel.layout}
|
initialLayout={currentModel.layout}
|
||||||
layoutItems={layoutItems} // Add this
|
modelId={model.id}
|
||||||
setLayoutItems={setLayoutItems} // Add this
|
onLayoutChange={handleLayoutUpdate}
|
||||||
onChange={handleLayoutUpdate}
|
onLayoutDataChange={handleLayoutDataUpdate}
|
||||||
/>
|
onRequestBodyChange={(body) => {
|
||||||
</TabsContent>
|
setCurrentModel(prev => ({
|
||||||
<TabsContent value="layout-tree" className="h-full m-0">
|
...prev,
|
||||||
<LayoutTreeGrid
|
requestBody: body
|
||||||
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)}));
|
|
||||||
}}
|
}}
|
||||||
height="full"
|
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="pre-request" className="h-full m-0">
|
<TabsContent value="pre-request" className="h-full m-0">
|
||||||
@@ -267,7 +218,7 @@ export const TCPClientView = ({model, index}) => {
|
|||||||
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>
|
||||||
|
|
||||||
@@ -281,7 +232,7 @@ export const TCPClientView = ({model, index}) => {
|
|||||||
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>
|
||||||
@@ -301,7 +252,7 @@ export const TCPClientView = ({model, index}) => {
|
|||||||
requestBody: newValue
|
requestBody: newValue
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
height="200px"
|
height="150px"
|
||||||
/>
|
/>
|
||||||
<div className="mt-2 text-sm text-gray-600">
|
<div className="mt-2 text-sm text-gray-600">
|
||||||
<strong>전체 메시지 길이:</strong> {currentModel.requestBody?.length || 0}
|
<strong>전체 메시지 길이:</strong> {currentModel.requestBody?.length || 0}
|
||||||
@@ -337,7 +288,7 @@ export const TCPClientView = ({model, index}) => {
|
|||||||
id={`response-body-${model.id}`}
|
id={`response-body-${model.id}`}
|
||||||
value={editorContents.responseBody}
|
value={editorContents.responseBody}
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
height="full"
|
height="250px"
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="console" className="h-full m-0">
|
<TabsContent value="console" className="h-full m-0">
|
||||||
@@ -346,7 +297,7 @@ export const TCPClientView = ({model, index}) => {
|
|||||||
value={editorContents.consoleLog}
|
value={editorContents.consoleLog}
|
||||||
language="plaintext"
|
language="plaintext"
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
height="full"
|
height="250px"
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</div>
|
</div>
|
||||||
@@ -354,6 +305,7 @@ export const TCPClientView = ({model, index}) => {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
</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