레이아웃 트리
This commit is contained in:
@@ -8,8 +8,7 @@ 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 LayoutGrid from '@/components/layout/LayoutGrid.jsx';
|
||||||
import ApiLayoutItemTreeBuilder from '@/lib/ApiLayoutItemTreeBuilder.js';
|
import LayoutTreeGrid from '@/components/layout/LayoutTreeGrid.jsx';
|
||||||
import ApiLayoutItemTreeDataBuilder from '@/lib/ApiLayoutItemTreeDataBuilder.js';
|
|
||||||
|
|
||||||
export const TCPClientView = ({model, index}) => {
|
export const TCPClientView = ({model, index}) => {
|
||||||
const apiClient = new APIClient();
|
const apiClient = new APIClient();
|
||||||
@@ -18,6 +17,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 || []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentModel(prev => ({
|
setCurrentModel(prev => ({
|
||||||
@@ -28,13 +28,6 @@ export const TCPClientView = ({model, index}) => {
|
|||||||
}));
|
}));
|
||||||
}, [model]);
|
}, [model]);
|
||||||
|
|
||||||
|
|
||||||
const [layoutRoot, setLayoutRoot] = useState(() => {
|
|
||||||
const root = ApiLayoutItemTreeBuilder.createTree(model.layout.layoutItems);
|
|
||||||
const replicator = new ApiLayoutItemTreeDataBuilder(10000);
|
|
||||||
return replicator.createReplicatedTree(root);
|
|
||||||
});
|
|
||||||
|
|
||||||
const [responseData, setResponseData] = useState({
|
const [responseData, setResponseData] = useState({
|
||||||
headers: [],
|
headers: [],
|
||||||
status: '',
|
status: '',
|
||||||
@@ -120,12 +113,14 @@ export const TCPClientView = ({model, index}) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleLayoutUpdate = useCallback((updatedLayout) => {
|
const handleLayoutUpdate = useCallback((updatedLayout) => {
|
||||||
const updatedRoot = ApiLayoutItemTreeBuilder.createTree(updatedLayout.layoutItems);
|
setLayoutItems(updatedLayout.layoutItems); // Add this
|
||||||
const replicator = new ApiLayoutItemTreeDataBuilder(10000);
|
setCurrentModel(prev => ({ // Update this to use setState
|
||||||
const newRoot = replicator.createReplicatedTree(updatedRoot);
|
...prev,
|
||||||
setLayoutRoot(newRoot);
|
layout: updatedLayout
|
||||||
currentModel.layout = updatedLayout;
|
}));
|
||||||
}, [currentModel]);
|
|
||||||
|
console.log({updatedLayout});
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full p-4 gap-4">
|
<div className="flex flex-col h-full p-4 gap-4">
|
||||||
@@ -163,6 +158,7 @@ export const TCPClientView = ({model, index}) => {
|
|||||||
<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">Layout</TabsTrigger>
|
<TabsTrigger value="body">Layout</TabsTrigger>
|
||||||
|
<TabsTrigger value="layout-tree">Layout Tree</TabsTrigger>
|
||||||
<TabsTrigger value="layout-json">Layout JSON</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>
|
||||||
@@ -175,18 +171,21 @@ export const TCPClientView = ({model, index}) => {
|
|||||||
<div className="h-[calc(100%-40px)]">
|
<div className="h-[calc(100%-40px)]">
|
||||||
<LayoutGrid
|
<LayoutGrid
|
||||||
layout={currentModel.layout}
|
layout={currentModel.layout}
|
||||||
|
layoutItems={layoutItems} // Add this
|
||||||
|
setLayoutItems={setLayoutItems} // Add this
|
||||||
onChange={handleLayoutUpdate}
|
onChange={handleLayoutUpdate}
|
||||||
/>
|
/>
|
||||||
<MonacoEditor
|
</div>
|
||||||
id={`request-body-${model.id}`}
|
</div>
|
||||||
value={currentModel.requestBody}
|
</TabsContent>
|
||||||
language="plaintext"
|
<TabsContent value="layout-tree" className="h-full m-0">
|
||||||
onChange={(newValue) => {
|
<div className="flex flex-col h-full gap-2">
|
||||||
setCurrentModel(prev => ({
|
<div className="h-[calc(100%-40px)]">
|
||||||
...prev,
|
<LayoutTreeGrid
|
||||||
requestBody: newValue
|
layout={currentModel.layout}
|
||||||
}));
|
layoutItems={layoutItems} // Add this
|
||||||
}}
|
setLayoutItems={setLayoutItems} // Add this
|
||||||
|
onChange={handleLayoutUpdate}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -233,6 +232,17 @@ export const TCPClientView = ({model, index}) => {
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<MonacoEditor
|
||||||
|
id={`request-body-${model.id}`}
|
||||||
|
value={currentModel.requestBody}
|
||||||
|
language="plaintext"
|
||||||
|
onChange={(newValue) => {
|
||||||
|
setCurrentModel(prev => ({
|
||||||
|
...prev,
|
||||||
|
requestBody: newValue
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Separator/>
|
<Separator/>
|
||||||
{/* Response Section */}
|
{/* Response Section */}
|
||||||
<div className="flex-1 min-h-0">
|
<div className="flex-1 min-h-0">
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
|
|
||||||
const LayoutGrid = ({ layout, onChange }) => {
|
const LayoutGrid = ({ layout, layoutItems, setLayoutItems, onChange }) => {
|
||||||
const [items, setItems] = useState(layout?.layoutItems || []);
|
const [items, setItems] = useState(layout?.layoutItems || []);
|
||||||
|
|
||||||
const getLastId = useCallback(() => {
|
const getLastId = useCallback(() => {
|
||||||
@@ -111,18 +111,18 @@ const LayoutGrid = ({ layout, onChange }) => {
|
|||||||
|
|
||||||
const handleFieldChange = useCallback(
|
const handleFieldChange = useCallback(
|
||||||
(index, field, value) => {
|
(index, field, value) => {
|
||||||
const newItems = [...items];
|
const newItems = [...layoutItems]; // Use layoutItems from props
|
||||||
newItems[index][field] = value;
|
newItems[index][field] = value;
|
||||||
|
|
||||||
if (field === "value" && newItems[index].value === undefined) {
|
if (field === "value" && newItems[index].value === undefined) {
|
||||||
newItems[index].value = "";
|
newItems[index].value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
setItems(newItems);
|
setLayoutItems(newItems); // Use setLayoutItems from props
|
||||||
layout.layoutItems = newItems;
|
layout.layoutItems = newItems;
|
||||||
onChange?.(layout);
|
onChange?.(layout);
|
||||||
},
|
},
|
||||||
[items, layout, onChange]
|
[layoutItems, layout, onChange, setLayoutItems] // Update dependencies
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -157,7 +157,7 @@ const LayoutGrid = ({ layout, onChange }) => {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{items.map((item, index) => (
|
{layoutItems.map((item, index) => (
|
||||||
<TableRow key={item.id} className="layout-grid-row">
|
<TableRow key={item.id} className="layout-grid-row">
|
||||||
<TableCell className="text-center">
|
<TableCell className="text-center">
|
||||||
{item.loutItemSerno}
|
{item.loutItemSerno}
|
||||||
|
|||||||
@@ -1,214 +1,391 @@
|
|||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useCallback, useEffect } from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { ChevronRight, ChevronDown, Trash2, Plus } from "lucide-react";
|
||||||
|
import EditableInput from "../shared/EditableInput";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import EditableInput from '../shared/EditableInput';
|
import ApiLayoutItemTreeBuilder from "@/lib/ApiLayoutItemTreeBuilder";
|
||||||
import _ from 'lodash';
|
|
||||||
import APIClient from '@/services/APIClient.js';
|
|
||||||
|
|
||||||
// Helper function to get indentation for tree visualization
|
import {
|
||||||
const getIndent = (depth) => {
|
Table,
|
||||||
return Array(depth + 1).fill().map((_, i) => (
|
TableBody,
|
||||||
<span key={i} className="treegrid-indent" />
|
TableCell,
|
||||||
));
|
TableHead,
|
||||||
};
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/components/ui/table";
|
||||||
|
|
||||||
const TreeNode = ({
|
const LayoutTreeGrid = ({ layout, onChange }) => {
|
||||||
node,
|
const [expandedNodes, setExpandedNodes] = useState(new Set());
|
||||||
depth,
|
const [items, setItems] = useState(layout?.layoutItems || []);
|
||||||
options,
|
const [treeRoot, setTreeRoot] = useState(() => ApiLayoutItemTreeBuilder.createTree(items));
|
||||||
isFirst,
|
|
||||||
onExpand,
|
|
||||||
expanded,
|
|
||||||
onValueChange,
|
|
||||||
getNodePath
|
|
||||||
}) => {
|
|
||||||
const hasChildren = node.children && node.children.length > 0;
|
|
||||||
|
|
||||||
const renderCell = (fieldInfo) => {
|
useEffect(() => {
|
||||||
const padding = fieldInfo.isFirst ? getIndent(depth) : null;
|
// Update tree structure when items change
|
||||||
const nodePath = getNodePath(node.id) + '.value';
|
const newRoot = ApiLayoutItemTreeBuilder.createTree(items);
|
||||||
|
setTreeRoot(newRoot);
|
||||||
|
}, [items]);
|
||||||
|
|
||||||
if (node.loutItemType === 'Field' && fieldInfo.type === 'textfield') {
|
const toggleNode = (nodeId) => {
|
||||||
return (
|
setExpandedNodes(prev => {
|
||||||
<EditableInput
|
const newSet = new Set(prev);
|
||||||
name={fieldInfo.name}
|
if (newSet.has(nodeId)) {
|
||||||
value={node[fieldInfo.name] || ''}
|
newSet.delete(nodeId);
|
||||||
onChange={(value) => onValueChange(nodePath, value)}
|
// Also collapse all children
|
||||||
/>
|
const collapseChildren = (parentId) => {
|
||||||
);
|
items.forEach(item => {
|
||||||
|
if (item.parent === parentId) {
|
||||||
|
newSet.delete(item.id);
|
||||||
|
collapseChildren(item.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
collapseChildren(nodeId);
|
||||||
|
} else {
|
||||||
|
newSet.add(nodeId);
|
||||||
|
}
|
||||||
|
return newSet;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLastId = useCallback(() => {
|
||||||
|
let lastId = 0;
|
||||||
|
items.forEach((item) => {
|
||||||
|
if (item.id > lastId) {
|
||||||
|
lastId = item.id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return lastId + 1;
|
||||||
|
}, [items]);
|
||||||
|
|
||||||
|
const handleAddItem = useCallback(() => {
|
||||||
|
const newItem = {
|
||||||
|
id: getLastId(),
|
||||||
|
parent: 0,
|
||||||
|
level: 1,
|
||||||
|
loutName: "",
|
||||||
|
loutItemName: "",
|
||||||
|
loutItemDesc: "",
|
||||||
|
loutItemDepth: 1,
|
||||||
|
loutItemType: "Field",
|
||||||
|
loutItemOccCnt: "",
|
||||||
|
loutItemOccRef: "",
|
||||||
|
loutItemDataType: "String",
|
||||||
|
loutItemLength: 1,
|
||||||
|
loutItemDecimal: 0,
|
||||||
|
loutItemDefault: "",
|
||||||
|
loutItemMaskYn: "N",
|
||||||
|
loutItemMaskOffset: 0,
|
||||||
|
loutItemMaskLength: 0,
|
||||||
|
parentLoutItemIndex: 0,
|
||||||
|
expanded: true,
|
||||||
|
isLeaf: true,
|
||||||
|
LOUTITEMPATH: "",
|
||||||
|
value: "",
|
||||||
|
loutItemSerno: items.length + 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const newItems = [...items, newItem];
|
||||||
|
setItems(newItems);
|
||||||
|
layout.layoutItems = newItems;
|
||||||
|
onChange?.(layout);
|
||||||
|
}, [items, layout, onChange, getLastId]);
|
||||||
|
|
||||||
|
const handleFieldChange = useCallback(
|
||||||
|
(index, field, value) => {
|
||||||
|
const newItems = [...items];
|
||||||
|
newItems[index][field] = value;
|
||||||
|
|
||||||
|
if (field === "value" && newItems[index].value === undefined) {
|
||||||
|
newItems[index].value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle special cases for Grid and Group types
|
||||||
|
if (field === "loutItemType" && ["Grid", "Group"].includes(value)) {
|
||||||
|
const item = newItems[index];
|
||||||
|
if (item.loutItemOccCnt === "*" && item.loutItemOccRef) {
|
||||||
|
const refItem = newItems.find(i => i.loutItemName === item.loutItemOccRef);
|
||||||
|
if (refItem) {
|
||||||
|
item.childOccCnt = parseInt(refItem.value, 10);
|
||||||
|
}
|
||||||
|
} else if (item.loutItemOccCnt) {
|
||||||
|
item.childOccCnt = parseInt(item.loutItemOccCnt, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setItems(newItems);
|
||||||
|
layout.layoutItems = newItems;
|
||||||
|
onChange?.(layout);
|
||||||
|
},
|
||||||
|
[items, layout, onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDelete = useCallback(
|
||||||
|
(index) => {
|
||||||
|
const deleteItemAndChildren = (itemId) => {
|
||||||
|
const idsToDelete = new Set();
|
||||||
|
|
||||||
|
const collectIds = (id) => {
|
||||||
|
idsToDelete.add(id);
|
||||||
|
items.forEach(item => {
|
||||||
|
if (item.parent === id) {
|
||||||
|
collectIds(item.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
collectIds(itemId);
|
||||||
|
return items.filter(item => !idsToDelete.has(item.id));
|
||||||
|
};
|
||||||
|
|
||||||
|
const itemToDelete = items[index];
|
||||||
|
const newItems = deleteItemAndChildren(itemToDelete.id);
|
||||||
|
|
||||||
|
// Update loutItemSerno values
|
||||||
|
newItems.forEach((item, idx) => {
|
||||||
|
item.loutItemSerno = idx + 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
setItems(newItems);
|
||||||
|
layout.layoutItems = newItems;
|
||||||
|
onChange?.(layout);
|
||||||
|
},
|
||||||
|
[items, layout, onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const isVisible = useCallback((item) => {
|
||||||
|
if (item.parent === 0 || item.parent === treeRoot.id) return true;
|
||||||
|
|
||||||
|
let current = item;
|
||||||
|
while (current && current.parent !== 0 && current.parent !== treeRoot.id) {
|
||||||
|
if (!expandedNodes.has(current.parent)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
current = items.find(i => i.id === current.parent);
|
||||||
|
if (!current) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}, [expandedNodes, items, treeRoot.id]);
|
||||||
|
|
||||||
|
const handleMoveNode = useCallback((item, direction) => {
|
||||||
|
const parent = ApiLayoutItemTreeBuilder.findParent(treeRoot, item);
|
||||||
|
if (!parent) return;
|
||||||
|
|
||||||
|
const siblings = parent.children;
|
||||||
|
const currentIndex = siblings.indexOf(item);
|
||||||
|
if (currentIndex === -1) return;
|
||||||
|
|
||||||
|
const newIndex = direction === "up" ? currentIndex - 1 : currentIndex + 1;
|
||||||
|
if (newIndex >= 0 && newIndex < siblings.length) {
|
||||||
|
// Swap nodes in the tree
|
||||||
|
[siblings[currentIndex], siblings[newIndex]] = [siblings[newIndex], siblings[currentIndex]];
|
||||||
|
|
||||||
|
// Update the layout items accordingly
|
||||||
|
const newItems = [...items];
|
||||||
|
newItems.forEach((item, idx) => {
|
||||||
|
item.loutItemSerno = idx + 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
setItems(newItems);
|
||||||
|
layout.layoutItems = newItems;
|
||||||
|
onChange?.(layout);
|
||||||
|
}
|
||||||
|
}, [items, layout, onChange, treeRoot]);
|
||||||
|
|
||||||
|
const renderTreeRow = (item, index) => {
|
||||||
|
const hasChildren = items.some(i => i.parent === item.id);
|
||||||
|
const isExpanded = expandedNodes.has(item.id);
|
||||||
|
const indentLevel = item.loutItemDepth || 1;
|
||||||
|
const paddingLeft = (indentLevel - 1) * 20;
|
||||||
|
|
||||||
|
if (!isVisible(item)) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center">
|
<TableRow key={item.id} className="layout-grid-row" data-node-id={item.id}>
|
||||||
{padding}
|
<TableCell className="text-center">
|
||||||
{fieldInfo.isFirst && hasChildren && (
|
{item.loutItemSerno}
|
||||||
<button
|
</TableCell>
|
||||||
onClick={() => onExpand(node.id)}
|
<TableCell>
|
||||||
className="treegrid-expander w-4 h-4 flex items-center justify-center"
|
<div className="flex items-center gap-2" style={{ paddingLeft: `${paddingLeft}px` }}>
|
||||||
|
{hasChildren ? (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-4 w-4 p-0"
|
||||||
|
onClick={() => toggleNode(item.id)}
|
||||||
|
>
|
||||||
|
{isExpanded ?
|
||||||
|
<ChevronDown className="h-4 w-4" /> :
|
||||||
|
<ChevronRight className="h-4 w-4" />
|
||||||
|
}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<div className="w-4" />
|
||||||
|
)}
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={item.id}
|
||||||
|
onChange={(e) => handleFieldChange(index, "id", e.target.value)}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={item.parent}
|
||||||
|
onChange={(e) => handleFieldChange(index, "parent", e.target.value)}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={item.loutItemName}
|
||||||
|
onChange={(e) => handleFieldChange(index, "loutItemName", e.target.value)}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={item.loutItemDesc}
|
||||||
|
onChange={(e) => handleFieldChange(index, "loutItemDesc", e.target.value)}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={item.loutItemDepth}
|
||||||
|
onChange={(e) => handleFieldChange(index, "loutItemDepth", parseInt(e.target.value))}
|
||||||
|
min={1}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Select
|
||||||
|
value={item.loutItemType}
|
||||||
|
onValueChange={(value) => handleFieldChange(index, "loutItemType", value)}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-full">
|
||||||
|
<SelectValue placeholder="Select type" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="Field">Field</SelectItem>
|
||||||
|
<SelectItem value="Grid">Grid</SelectItem>
|
||||||
|
<SelectItem value="Group">Group</SelectItem>
|
||||||
|
<SelectItem value="Attr">Attr</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={item.loutItemOccCnt}
|
||||||
|
onChange={(e) => handleFieldChange(index, "loutItemOccCnt", e.target.value)}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={item.loutItemOccRef}
|
||||||
|
onChange={(e) => handleFieldChange(index, "loutItemOccRef", e.target.value)}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={item.loutItemLength}
|
||||||
|
onChange={(e) => handleFieldChange(index, "loutItemLength", parseInt(e.target.value))}
|
||||||
|
min={1}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{item.loutItemType === "Field" && (
|
||||||
|
<EditableInput
|
||||||
|
name={`items[${index}].value`}
|
||||||
|
value={item.value || ""}
|
||||||
|
onChange={(value) => handleFieldChange(index, "value", value)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-center">
|
||||||
|
<div className="flex justify-center space-x-1">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleMoveNode(item, "up")}
|
||||||
>
|
>
|
||||||
{expanded ? '-' : '+'}
|
<ChevronUp className="h-4 w-4" />
|
||||||
</button>
|
</Button>
|
||||||
)}
|
<Button
|
||||||
{node[fieldInfo.name]}
|
variant="outline"
|
||||||
</div>
|
size="sm"
|
||||||
|
onClick={() => handleMoveNode(item, "down")}
|
||||||
|
>
|
||||||
|
<ChevronDown className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleDelete(index)}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr className={`treegrid-${node.id} ${node.parent ? `treegrid-parent-${node.parent}` : ''}`}>
|
<div className="overflow-x-auto">
|
||||||
{options.map((fieldInfo, index) => (
|
<Table>
|
||||||
<td
|
<TableHeader>
|
||||||
key={fieldInfo.name}
|
<TableRow>
|
||||||
className={cn(
|
<TableHead className="text-center w-[50px]">#</TableHead>
|
||||||
"border p-2",
|
<TableHead className="text-center w-[80px]">ID</TableHead>
|
||||||
fieldInfo.align === 'center' && "text-center",
|
<TableHead className="text-center w-[80px]">Parent</TableHead>
|
||||||
fieldInfo.align === 'right' && "text-right"
|
<TableHead className="text-center w-[120px]">항목명(영문)</TableHead>
|
||||||
)}
|
<TableHead className="text-center w-[150px]">항목설명</TableHead>
|
||||||
style={{ width: fieldInfo.width }}
|
<TableHead className="text-center w-[60px]">깊이</TableHead>
|
||||||
>
|
<TableHead className="text-center w-[110px]">아이템 유형</TableHead>
|
||||||
{renderCell(fieldInfo)}
|
<TableHead className="text-center w-[80px]">반복 횟수</TableHead>
|
||||||
</td>
|
<TableHead className="text-center w-[100px]">반복 참조 필드</TableHead>
|
||||||
))}
|
<TableHead className="text-center w-[80px]">데이터 길이</TableHead>
|
||||||
</tr>
|
<TableHead className="text-center w-[120px]">값</TableHead>
|
||||||
);
|
<TableHead className="text-center w-[120px]">
|
||||||
};
|
비고
|
||||||
|
<Button
|
||||||
const LayoutTreeDataGrid = ({
|
variant="outline"
|
||||||
root,
|
size="sm"
|
||||||
apiRequest,
|
onClick={handleAddItem}
|
||||||
options,
|
className="ml-2"
|
||||||
totalMessageLengthSelector,
|
|
||||||
onUpdate
|
|
||||||
}) => {
|
|
||||||
const [expandedNodes, setExpandedNodes] = useState(new Set());
|
|
||||||
const [apiClient] = useState(() => new APIClient());
|
|
||||||
|
|
||||||
const getNodePath = useCallback((targetId, node = root, path = '') => {
|
|
||||||
if (node.id === targetId) return path;
|
|
||||||
|
|
||||||
if (node.children) {
|
|
||||||
for (let i = 0; i < node.children.length; i++) {
|
|
||||||
const currentPath = path ? `${path}.children[${i}]` : `children[${i}]`;
|
|
||||||
const resultPath = getNodePath(targetId, node.children[i], currentPath);
|
|
||||||
if (resultPath) return resultPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}, [root]);
|
|
||||||
|
|
||||||
const handleExpand = useCallback((nodeId) => {
|
|
||||||
setExpandedNodes(prev => {
|
|
||||||
const next = new Set(prev);
|
|
||||||
if (next.has(nodeId)) {
|
|
||||||
next.delete(nodeId);
|
|
||||||
} else {
|
|
||||||
next.add(nodeId);
|
|
||||||
}
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const fillPadding = useCallback(async (value, dataLength) => {
|
|
||||||
let paddedValue = await apiClient.processPreScriptAndVariables(apiRequest, value.toString());
|
|
||||||
while (paddedValue.length < dataLength) {
|
|
||||||
paddedValue = ' ' + paddedValue;
|
|
||||||
}
|
|
||||||
return paddedValue;
|
|
||||||
}, [apiRequest]);
|
|
||||||
|
|
||||||
const concatenateFieldValues = useCallback(async (node) => {
|
|
||||||
let concatenatedValues = '';
|
|
||||||
|
|
||||||
if (node.loutItemType === 'Field') {
|
|
||||||
concatenatedValues += await fillPadding(node.value || '', node.loutItemLength) || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.children?.length > 0) {
|
|
||||||
for (const child of node.children) {
|
|
||||||
concatenatedValues += await concatenateFieldValues(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return concatenatedValues;
|
|
||||||
}, [fillPadding]);
|
|
||||||
|
|
||||||
const sumFieldLoutItemLengths = useCallback(() => {
|
|
||||||
const queue = [root];
|
|
||||||
let sum = 0;
|
|
||||||
|
|
||||||
while (queue.length > 0) {
|
|
||||||
const node = queue.shift();
|
|
||||||
if (node.loutItemType === 'Field') {
|
|
||||||
sum += parseInt(node.loutItemLength) || 0;
|
|
||||||
}
|
|
||||||
if (node.children?.length > 0) {
|
|
||||||
queue.push(...node.children);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sum;
|
|
||||||
}, [root]);
|
|
||||||
|
|
||||||
const handleValueChange = useCallback(async (path, value) => {
|
|
||||||
_.set(root, path, value);
|
|
||||||
const finalMessage = await concatenateFieldValues(root);
|
|
||||||
onUpdate?.(finalMessage, root);
|
|
||||||
}, [root, concatenateFieldValues, onUpdate]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (totalMessageLengthSelector) {
|
|
||||||
totalMessageLengthSelector.textContent = sumFieldLoutItemLengths();
|
|
||||||
}
|
|
||||||
}, [root, sumFieldLoutItemLengths, totalMessageLengthSelector]);
|
|
||||||
|
|
||||||
const renderNodes = useCallback((node, depth = 0) => {
|
|
||||||
const isExpanded = expandedNodes.has(node.id);
|
|
||||||
const result = [];
|
|
||||||
|
|
||||||
result.push(
|
|
||||||
<TreeNode
|
|
||||||
key={node.id}
|
|
||||||
node={node}
|
|
||||||
depth={depth}
|
|
||||||
options={options}
|
|
||||||
isFirst={true}
|
|
||||||
onExpand={handleExpand}
|
|
||||||
expanded={isExpanded}
|
|
||||||
onValueChange={handleValueChange}
|
|
||||||
getNodePath={getNodePath}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isExpanded && node.children?.length > 0) {
|
|
||||||
node.children.forEach(child => {
|
|
||||||
result.push(...renderNodes(child, depth + 1));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}, [expandedNodes, options, handleExpand, handleValueChange, getNodePath]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="overflow-auto">
|
|
||||||
<table className="w-full border-collapse border">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
{options.map(field => (
|
|
||||||
<th
|
|
||||||
key={field.name}
|
|
||||||
className="border p-2 text-center"
|
|
||||||
style={{ width: field.width }}
|
|
||||||
>
|
>
|
||||||
{field.label}
|
<Plus className="h-4 w-4" />
|
||||||
</th>
|
</Button>
|
||||||
))}
|
</TableHead>
|
||||||
</tr>
|
</TableRow>
|
||||||
</thead>
|
</TableHeader>
|
||||||
<tbody>
|
<TableBody>
|
||||||
{renderNodes(root)}
|
{items.map((item, index) => renderTreeRow(item, index))}
|
||||||
</tbody>
|
</TableBody>
|
||||||
</table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LayoutTreeDataGrid;
|
export default LayoutTreeGrid;
|
||||||
|
|||||||
@@ -1,36 +1,31 @@
|
|||||||
import React, { useState, useCallback } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { cn } from "@/lib/utils";
|
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||||
import EditableInput from '../shared/EditableInput';
|
import { Button } from '@/components/ui/button';
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow
|
||||||
|
} from '@/components/ui/table';
|
||||||
|
import ApiLayoutItemTreeBuilder from '@/lib/ApiLayoutItemTreeBuilder.js';
|
||||||
|
|
||||||
const LayoutTreeGrid = ({ root, options = [], onUpdate }) => {
|
const LayoutTreeGrid = ({ layout, layoutItems }) => {
|
||||||
|
// 트리구조를 만들고, 각 노드의 펼침 여부를 관리하는 상태
|
||||||
const [expandedNodes, setExpandedNodes] = useState(new Set());
|
const [expandedNodes, setExpandedNodes] = useState(new Set());
|
||||||
|
const [treeRoot, setTreeRoot] = useState(() =>
|
||||||
|
ApiLayoutItemTreeBuilder.createTree(layoutItems || [])
|
||||||
|
);
|
||||||
|
|
||||||
// Helper function to get node path
|
// layoutItems가 변경되면 트리 구조 재생성
|
||||||
const getNodePath = useCallback((node, targetId, path = '') => {
|
useEffect(() => {
|
||||||
if (node.id === targetId) {
|
setTreeRoot(ApiLayoutItemTreeBuilder.createTree(layoutItems || []));
|
||||||
return path;
|
}, [layoutItems]);
|
||||||
}
|
|
||||||
|
|
||||||
if (node.children) {
|
// 노드 토글 함수: 해당 노드의 펼침 여부를 토글합니다.
|
||||||
for (let i = 0; i < node.children.length; i++) {
|
const toggleNode = (nodeId) => {
|
||||||
const currentPath = path ? `${path}.children[${i}]` : `children[${i}]`;
|
setExpandedNodes((prev) => {
|
||||||
const resultPath = getNodePath(node.children[i], targetId, currentPath);
|
|
||||||
if (resultPath) {
|
|
||||||
return resultPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Get indentation for tree structure
|
|
||||||
const getIndent = useCallback((depth) => {
|
|
||||||
return Array(depth + 1).join(' ');
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Handle expand/collapse
|
|
||||||
const handleExpanderClick = useCallback((nodeId) => {
|
|
||||||
setExpandedNodes(prev => {
|
|
||||||
const newSet = new Set(prev);
|
const newSet = new Set(prev);
|
||||||
if (newSet.has(nodeId)) {
|
if (newSet.has(nodeId)) {
|
||||||
newSet.delete(nodeId);
|
newSet.delete(nodeId);
|
||||||
@@ -39,118 +34,86 @@ const LayoutTreeGrid = ({ root, options = [], onUpdate }) => {
|
|||||||
}
|
}
|
||||||
return newSet;
|
return newSet;
|
||||||
});
|
});
|
||||||
}, []);
|
};
|
||||||
|
|
||||||
// Render cell content
|
// 재귀적으로 트리 노드를 순회하며 테이블 행으로 렌더링하는 함수
|
||||||
const renderCell = useCallback((node, field, depth) => {
|
const renderTreeRows = (node, indentLevel = 0, index) => {
|
||||||
const nodePath = getNodePath(root, node.id) + '.value';
|
// 부모 노드의 id나 인덱스를 함께 결합하여 키를 만듭니다.
|
||||||
|
const key = `${node.id}-${indentLevel}-${index}`;
|
||||||
|
|
||||||
if (node.loutItemType === 'Field' && field.type === 'textfield') {
|
const hasChildren = node.children && node.children.length > 0;
|
||||||
return (
|
const isExpanded = expandedNodes.has(node.id);
|
||||||
<EditableInput
|
|
||||||
name={field.name}
|
|
||||||
value={node[field.name]}
|
|
||||||
path={nodePath}
|
|
||||||
onChange={(value) => {
|
|
||||||
node[field.name] = value;
|
|
||||||
onUpdate?.(root);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (field.isFirst) {
|
return (
|
||||||
const hasChildren = node.children?.length > 0;
|
<React.Fragment key={key}>
|
||||||
return (
|
<TableRow className="layout-grid-row">
|
||||||
<div className="flex items-center">
|
<TableCell className="text-center">{node.loutItemSerno}</TableCell>
|
||||||
<span className="inline-block" style={{ width: `${depth * 20}px` }} />
|
<TableCell>
|
||||||
{hasChildren && (
|
<div className="flex items-center" style={{ paddingLeft: `${indentLevel * 10}px` }}>
|
||||||
<button
|
{hasChildren ? (
|
||||||
onClick={() => handleExpanderClick(node.id)}
|
<Button
|
||||||
className="w-4 h-4 flex items-center justify-center mr-1"
|
variant="ghost"
|
||||||
>
|
size="sm"
|
||||||
{expandedNodes.has(node.id) ? '−' : '+'}
|
className="h-4 w-4 p-0"
|
||||||
</button>
|
onClick={() => toggleNode(node.id)}
|
||||||
)}
|
>
|
||||||
<span>{node[field.name]}</span>
|
{isExpanded ? (
|
||||||
</div>
|
<ChevronDown className="h-4 w-4" />
|
||||||
);
|
) : (
|
||||||
}
|
<ChevronRight className="h-4 w-4" />
|
||||||
|
)}
|
||||||
return node[field.name];
|
</Button>
|
||||||
}, [root, expandedNodes, handleExpanderClick, getNodePath, onUpdate]);
|
) : (
|
||||||
|
<div className="w-4" />
|
||||||
// Recursive function to render tree rows
|
)}
|
||||||
const renderTreeRows = useCallback((node, depth = 0, parentId = null) => {
|
<span>{node.id}</span>
|
||||||
const rows = [];
|
</div>
|
||||||
|
</TableCell>
|
||||||
const isVisible = depth === 0 || !parentId || expandedNodes.has(parentId);
|
<TableCell className="text-center">{node.parent}</TableCell>
|
||||||
|
<TableCell>{node.loutItemName}</TableCell>
|
||||||
if (!isVisible) {
|
<TableCell>{node.loutItemDesc}</TableCell>
|
||||||
return rows;
|
<TableCell className="text-center">{node.loutItemDepth}</TableCell>
|
||||||
}
|
<TableCell className="text-center">{node.loutItemType}</TableCell>
|
||||||
|
<TableCell>{node.loutItemOccCnt}</TableCell>
|
||||||
const row = (
|
<TableCell>{node.loutItemOccRef}</TableCell>
|
||||||
<tr
|
<TableCell className="text-center">{node.loutItemLength}</TableCell>
|
||||||
key={node.id}
|
<TableCell>
|
||||||
className={cn(
|
{node.loutItemType === 'Field' ? node.value : ''}
|
||||||
"layout-grid-row",
|
</TableCell>
|
||||||
depth === 0 && "hidden",
|
</TableRow>
|
||||||
parentId && `treegrid-parent-${parentId}`
|
{hasChildren && isExpanded &&
|
||||||
)}
|
node.children.map((child, childIndex) =>
|
||||||
data-node-id={node.id}
|
renderTreeRows(child, indentLevel + 1, childIndex)
|
||||||
>
|
)}
|
||||||
{options.map((field, fieldIndex) => (
|
</React.Fragment>
|
||||||
<td
|
|
||||||
key={fieldIndex}
|
|
||||||
className={cn(
|
|
||||||
"border p-2",
|
|
||||||
field.align && `text-${field.align}`,
|
|
||||||
"whitespace-nowrap"
|
|
||||||
)}
|
|
||||||
style={{ width: field.width }}
|
|
||||||
>
|
|
||||||
{renderCell(node, field, depth)}
|
|
||||||
</td>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
rows.push(row);
|
|
||||||
|
|
||||||
if (node.children?.length > 0) {
|
|
||||||
node.children.forEach(child => {
|
|
||||||
rows.push(...renderTreeRows(child, depth + 1, node.id));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return rows;
|
|
||||||
}, [options, expandedNodes, renderCell]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-auto">
|
<div className="overflow-x-auto">
|
||||||
<table className="w-full border-collapse border">
|
<Table>
|
||||||
<thead>
|
<TableHeader>
|
||||||
<tr>
|
<TableRow>
|
||||||
{options.map((field, index) => (
|
<TableHead className="text-center w-[50px]">#</TableHead>
|
||||||
<th
|
<TableHead className="text-center w-[80px]">ID</TableHead>
|
||||||
key={index}
|
<TableHead className="text-center w-[80px]">Parent</TableHead>
|
||||||
className={cn(
|
<TableHead className="text-center w-[120px]">항목명(영문)</TableHead>
|
||||||
"border p-2",
|
<TableHead className="text-center w-[150px]">항목설명</TableHead>
|
||||||
field.align && `text-${field.align}`,
|
<TableHead className="text-center w-[60px]">깊이</TableHead>
|
||||||
"whitespace-nowrap"
|
<TableHead className="text-center w-[110px]">아이템 유형</TableHead>
|
||||||
)}
|
<TableHead className="text-center w-[80px]">반복 횟수</TableHead>
|
||||||
style={{ width: field.width }}
|
<TableHead className="text-center w-[100px]">반복 참조 필드</TableHead>
|
||||||
>
|
<TableHead className="text-center w-[80px]">데이터 길이</TableHead>
|
||||||
{field.label}
|
<TableHead className="text-center w-[120px]">값</TableHead>
|
||||||
</th>
|
</TableRow>
|
||||||
))}
|
</TableHeader>
|
||||||
</tr>
|
<TableBody>
|
||||||
</thead>
|
{/* treeRoot는 일반적으로 Root 노드(예: id가 0 또는 첫번째 parent)로 생성되므로,
|
||||||
<tbody>
|
실제 렌더링은 Root의 자식부터 시작 */}
|
||||||
{renderTreeRows(root)}
|
{treeRoot && treeRoot.children.map((child) => renderTreeRows(child))}
|
||||||
</tbody>
|
</TableBody>
|
||||||
</table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useRef, memo } from 'react';
|
import React, { useEffect, useRef, memo } from 'react';
|
||||||
import * as monaco from 'monaco-editor';
|
import * as monaco from 'monaco-editor';
|
||||||
import {loader} from '@monaco-editor/react';
|
import { loader } from '@monaco-editor/react';
|
||||||
|
|
||||||
loader.config({
|
loader.config({
|
||||||
paths: {
|
paths: {
|
||||||
@@ -9,8 +9,7 @@ loader.config({
|
|||||||
'vs/nls': {
|
'vs/nls': {
|
||||||
availableLanguages: {},
|
availableLanguages: {},
|
||||||
},
|
},
|
||||||
// Specify which languages to load
|
languages: ['javascript', 'json', 'xml', 'plaintext']
|
||||||
languages: ['javascript', 'json', 'xml','plaintext']
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const MonacoEditor = memo(({
|
const MonacoEditor = memo(({
|
||||||
@@ -95,12 +94,28 @@ const MonacoEditor = memo(({
|
|||||||
schemas: []
|
schemas: []
|
||||||
});
|
});
|
||||||
|
|
||||||
const action = editor.getAction('editor.action.formatDocument');
|
// Use a ref to track if component is mounted
|
||||||
if (action) {
|
const isMounted = { current: true };
|
||||||
setTimeout(() => {
|
|
||||||
action.run();
|
// Format after a short delay to ensure editor is ready
|
||||||
}, 100);
|
const timeoutId = setTimeout(() => {
|
||||||
}
|
if (isMounted.current && 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
isMounted.current = false;
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Failed to format JSON:', e);
|
console.warn('Failed to format JSON:', e);
|
||||||
}
|
}
|
||||||
@@ -116,7 +131,6 @@ const MonacoEditor = memo(({
|
|||||||
resizeObserver.disconnect();
|
resizeObserver.disconnect();
|
||||||
subscription.dispose();
|
subscription.dispose();
|
||||||
|
|
||||||
// Ensure proper cleanup
|
|
||||||
if (editor) {
|
if (editor) {
|
||||||
try {
|
try {
|
||||||
const model = editor.getModel();
|
const model = editor.getModel();
|
||||||
@@ -128,9 +142,14 @@ const MonacoEditor = memo(({
|
|||||||
console.warn('Editor disposal error:', e);
|
console.warn('Editor disposal error:', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (editorRef.current) {
|
||||||
|
editorRef.current.dispose();
|
||||||
|
editorRef.current = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, [language]); // Only recreate when language changes
|
}, [language]); // Only recreate when language changes
|
||||||
|
|
||||||
|
|
||||||
// Handle value updates
|
// Handle value updates
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editorRef.current) {
|
if (editorRef.current) {
|
||||||
@@ -141,11 +160,6 @@ const MonacoEditor = memo(({
|
|||||||
}
|
}
|
||||||
}, [normalizedValue]);
|
}, [normalizedValue]);
|
||||||
|
|
||||||
// Convert height to pixel value if it's a number
|
|
||||||
// const containerHeight = typeof height === 'number'
|
|
||||||
// ? `${height}px`
|
|
||||||
// : height.endsWith('px') ? height : `${height}px`;
|
|
||||||
|
|
||||||
const getHeightStyle = (height) => {
|
const getHeightStyle = (height) => {
|
||||||
if (height === 'full') {
|
if (height === 'full') {
|
||||||
return '100%';
|
return '100%';
|
||||||
|
|||||||
Reference in New Issue
Block a user