diff --git a/TestMasterUI/src/components/client-views/TCPClientView.jsx b/TestMasterUI/src/components/client-views/TCPClientView.jsx
index e9d50ae..7fdb017 100644
--- a/TestMasterUI/src/components/client-views/TCPClientView.jsx
+++ b/TestMasterUI/src/components/client-views/TCPClientView.jsx
@@ -9,6 +9,8 @@ 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';
export const TCPClientView = ({model, index}) => {
const apiClient = new APIClient();
@@ -119,170 +121,239 @@ export const TCPClientView = ({model, index}) => {
layout: updatedLayout
}));
- console.log({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
+ setCurrentModel(prev => ({
+ ...prev,
+ layout: updatedLayout,
+ requestBody: generateRequestBody(updatedLayout.computedLayoutItemRoot)
+ }));
+
+ // Update editor contents with new request body
+ setEditorContents(prev => ({
+ ...prev,
+ requestBody: generateRequestBody(updatedLayout.computedLayoutItemRoot)
+ }));
+ }, []);
+
+ 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('');
+ }
+
+ return '';
+ };
+
+ return processNodes(layoutRoot);
}, []);
return (
{/* Request Header Section */}
-
-
-
-
- {currentModel.collectionName}
- {' > '}
- {currentModel.name}
-
-
-
- {/* Request Controls */}
-
-
-
-
-
-
-
-
-
-
- {/* Request/Response Container */}
-
- {/* Request Section */}
-
-
-
- Layout
- Layout Tree
- Layout JSON
- Pre-Request
- Post-Request
-
-
-
- {/* Body Tab Content */}
-
-
-
-
-
-
-
- {
- setEditorContents(prev => ({...prev, layout: value}));
- setCurrentModel(prev => ({...prev, layout: JSON.parse(value)}));
- }}
- height="full"
- />
-
-
- {
- setEditorContents(prev => ({...prev, preRequestScript: value}));
- setCurrentModel(prev => ({...prev, preRequestScript: value}));
- }}
- height="full"
- />
-
-
- {/* Post-Request Tab Content */}
-
- {
- setEditorContents(prev => ({...prev, postRequestScript: value}));
- setCurrentModel(prev => ({...prev, postRequestScript: value}));
- }}
- height="full"
- />
-
-
-
-
-
-
{
- setCurrentModel(prev => ({
- ...prev,
- requestBody: newValue
- }));
- }}
- />
-
- {/* Response Section */}
-
-
-
Response
-
{responseData.status}
-
{responseData.time}
-
{responseData.size}
+
+
+
+
+
+ {currentModel.collectionName}
+ {' > '}
+ {currentModel.name}
-
-
-
- Body
- Console
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+ {/* 요청 영역 */}
+
+
+ Request
+
+
+
+
+ Layout
+ Layout Tree
+ Layout Data
+ Layout JSON
+ Pre-Request
+ Post-Request
+
+
+ {/* Body Tab Content */}
+
+
+
+
+
+
+
+
+
+
+ {
+ setEditorContents(prev => ({...prev, layout: value}));
+ setCurrentModel(prev => ({...prev, layout: JSON.parse(value)}));
+ }}
+ height="full"
+ />
+
+
+ {
+ setEditorContents(prev => ({...prev, preRequestScript: value}));
+ setCurrentModel(prev => ({...prev, preRequestScript: value}));
+ }}
+ height="full"
+ />
+
+
+ {/* Post-Request Tab Content */}
+
+ {
+ setEditorContents(prev => ({...prev, postRequestScript: value}));
+ setCurrentModel(prev => ({...prev, postRequestScript: value}));
+ }}
+ height="full"
+ />
+
+
+
+
+
+
+
+
Request Body
+
{
+ setCurrentModel(prev => ({
+ ...prev,
+ requestBody: newValue
+ }));
+ }}
+ height="200px"
+ />
+
+ 전체 메시지 길이: {currentModel.requestBody?.length || 0}
+
+
+
+
+
+ {/* 응답 영역 */}
+
+
+
+ Response
+ {responseData.status}
+ {responseData.time}
+ {responseData.size}
+
+
+
+
+
+ Body
+ Console
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/TestMasterUI/src/components/layout/LayoutGrid.jsx b/TestMasterUI/src/components/layout/LayoutGrid.jsx
index 5b777ca..61d39c0 100644
--- a/TestMasterUI/src/components/layout/LayoutGrid.jsx
+++ b/TestMasterUI/src/components/layout/LayoutGrid.jsx
@@ -135,14 +135,12 @@ const LayoutGrid = ({ layout, layoutItems, setLayoutItems, onChange }) => {
Parent
항목명(영문)
항목설명
-
깊이
+
깊이
아이템 유형
반복 횟수
반복 참조 필드
데이터 길이
-
- 값
-
+
기본 값
비고
);
};
-export default LayoutTreeGrid;
+export default LayoutTreeDataGrid;
diff --git a/TestMasterUI/src/components/layout/LayoutTreeGrid.jsx b/TestMasterUI/src/components/layout/LayoutTreeGrid.jsx
index 9a439f0..406aa4f 100644
--- a/TestMasterUI/src/components/layout/LayoutTreeGrid.jsx
+++ b/TestMasterUI/src/components/layout/LayoutTreeGrid.jsx
@@ -10,6 +10,7 @@ import {
TableRow
} from '@/components/ui/table';
import ApiLayoutItemTreeBuilder from '@/lib/ApiLayoutItemTreeBuilder.js';
+import EditableInput from '@/components/shared/EditableInput.jsx';
const LayoutTreeGrid = ({ layout, layoutItems }) => {
// 트리구조를 만들고, 각 노드의 펼침 여부를 관리하는 상태
@@ -74,11 +75,19 @@ const LayoutTreeGrid = ({ layout, layoutItems }) => {
{node.loutItemDesc}
{node.loutItemDepth}
{node.loutItemType}
- {node.loutItemOccCnt}
+ {node.loutItemOccCnt}
{node.loutItemOccRef}
- {node.loutItemLength}
+ {node.loutItemType === 'Field'?node.loutItemLength:''}
- {node.loutItemType === 'Field' ? node.value : ''}
+ {node.loutItemType === 'Field' && (
+
+ handleNodeChange(node.id, 'loutItemDefault', value)
+ }
+ />
+ )}
{hasChildren && isExpanded &&
@@ -105,7 +114,7 @@ const LayoutTreeGrid = ({ layout, layoutItems }) => {
반복 횟수
반복 참조 필드
데이터 길이
- 값
+ 기본 값
diff --git a/TestMasterUI/src/components/shared/MonacoEditor.jsx b/TestMasterUI/src/components/shared/MonacoEditor.jsx
index fccc673..6f0a3f8 100644
--- a/TestMasterUI/src/components/shared/MonacoEditor.jsx
+++ b/TestMasterUI/src/components/shared/MonacoEditor.jsx
@@ -12,6 +12,14 @@ loader.config({
languages: ['javascript', 'json', 'xml', 'plaintext']
});
+const debounce = (fn, ms) => {
+ let timer;
+ return (...args) => {
+ clearTimeout(timer);
+ timer = setTimeout(() => fn(...args), ms);
+ };
+};
+
const MonacoEditor = memo(({
id,
value = '',
@@ -29,7 +37,7 @@ const MonacoEditor = memo(({
const getEditorOptions = (lang) => {
const baseOptions = {
- automaticLayout: true,
+ automaticLayout: false, // Changed to false to handle layout manually
theme: 'vs-light',
minimap: { enabled: false },
readOnly,
@@ -40,7 +48,8 @@ const MonacoEditor = memo(({
tabSize: 2,
scrollbar: {
vertical: 'auto',
- horizontal: 'auto'
+ horizontal: 'auto',
+ alwaysConsumeMouseWheel: false // Prevent scroll issues
}
};
@@ -86,6 +95,26 @@ const MonacoEditor = memo(({
subscriptionRef.current = subscription;
+ // Debounced layout update function
+ const debouncedLayout = debounce(() => {
+ if (editorRef.current) {
+ editorRef.current.layout();
+ }
+ }, 100); // 100ms debounce
+
+ const resizeObserver = new ResizeObserver(() => {
+ requestAnimationFrame(debouncedLayout);
+ });
+
+ // Observe container size changes
+ if (containerRef.current) {
+ resizeObserver.observe(containerRef.current);
+ }
+
+ // Initial layout call
+ editor.layout();
+
+ // Format JSON if needed
if (language === 'json' && normalizedValue) {
try {
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
@@ -94,70 +123,49 @@ const MonacoEditor = memo(({
schemas: []
});
- // Use a ref to track if component is mounted
- const isMounted = { current: true };
-
- // Format after a short delay to ensure editor is ready
- const timeoutId = setTimeout(() => {
- if (isMounted.current && editorRef.current) {
+ // Delayed format action
+ setTimeout(() => {
+ if (editorRef.current) {
const action = editorRef.current.getAction('editor.action.formatDocument');
if (action) {
- try {
- action.run();
- } catch (e) {
- // Ignore formatting errors
- console.debug('Format action failed:', e);
- }
+ action.run().catch(console.debug);
}
}
}, 100);
-
- return () => {
- isMounted.current = false;
- clearTimeout(timeoutId);
- };
} catch (e) {
console.warn('Failed to format JSON:', e);
}
}
- const resizeObserver = new ResizeObserver(() => {
- editor.layout();
- });
-
- resizeObserver.observe(containerRef.current);
-
return () => {
+ debouncedLayout.clear?.();
resizeObserver.disconnect();
subscription.dispose();
if (editor) {
- try {
- const model = editor.getModel();
- if (model) {
- model.dispose();
- }
- editor.dispose();
- } catch (e) {
- console.warn('Editor disposal error:', e);
+ const model = editor.getModel();
+ if (model) {
+ model.dispose();
}
- }
- if (editorRef.current) {
- editorRef.current.dispose();
- editorRef.current = null;
+ editor.dispose();
}
};
- }, [language]); // Only recreate when language changes
+ }, [language]);
-
- // Handle value updates
+ // Handle value updates with debouncing
useEffect(() => {
- if (editorRef.current) {
- const currentValue = editorRef.current.getValue();
- if (currentValue !== normalizedValue) {
- editorRef.current.setValue(normalizedValue);
+ const updateValue = debounce(() => {
+ if (editorRef.current) {
+ const currentValue = editorRef.current.getValue();
+ if (currentValue !== normalizedValue) {
+ editorRef.current.setValue(normalizedValue);
+ }
}
- }
+ }, 50);
+
+ updateValue();
+
+ return () => updateValue.clear?.();
}, [normalizedValue]);
const getHeightStyle = (height) => {
@@ -178,7 +186,8 @@ const MonacoEditor = memo(({
minHeight: '100px',
height: getHeightStyle(height),
border: '1px solid #e2e8f0',
- marginTop: '0.5rem'
+ marginTop: '0.5rem',
+ position: 'relative' // Added for stable layout
}}
/>
);