diff --git a/package-lock.json b/package-lock.json index f1b8bc1d..fbc65796 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "react-dom": "^19.1.0", "react-scripts": "5.0.1", "reactflow": "^11.11.4", + "sonarqube-scanner": "^4.3.0", "typescript": "^4.9.5", "web-vitals": "^2.1.4" } @@ -4825,6 +4826,15 @@ "node": ">=8.9" } }, + "node_modules/adm-zip": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.12.tgz", + "integrity": "sha512-6TVU49mK6KZb4qG6xWaaM4C7sA/sgUMLy/JYMOzkcp3BvVLpW0fXDFQiIzAuxFCt/2+xD7fNIiPFAoLZPhVNLQ==", + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -5311,6 +5321,33 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz", + "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -5320,6 +5357,12 @@ "node": ">= 0.4" } }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "license": "Apache-2.0" + }, "node_modules/babel-jest": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", @@ -5580,6 +5623,13 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/bare-events": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.6.1.tgz", + "integrity": "sha512-AuTJkq9XmE6Vk0FJVNq5QxETrSA/vKHarWVBG5l/JbdCL1prJemiyJqUS0jrlXO0MftuPq4m3YVYhoNc5+aE/g==", + "license": "Apache-2.0", + "optional": true + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -8449,6 +8499,12 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -9449,6 +9505,15 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/hpagent": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", + "integrity": "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/html-encoding-sniffer": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", @@ -14167,6 +14232,15 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/properties-file": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/properties-file/-/properties-file-3.5.4.tgz", + "integrity": "sha512-OGQPWZ4j9ENDKBl+wUHqNtzayGF5sLlVcmjcqEMUUHeCbUSggDndii+kjcBDPj3GQvqYB9sUEc4siX36wx4glw==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -14189,6 +14263,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", @@ -15630,6 +15710,15 @@ "node": ">=8" } }, + "node_modules/slugify": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", + "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -15641,6 +15730,85 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/sonarqube-scanner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/sonarqube-scanner/-/sonarqube-scanner-4.3.0.tgz", + "integrity": "sha512-dwQz+5SuZJVg2rSk3oRglAf+i49w/gebpGtNfdVtROk4PhYeMtg1oRr3H5cNxVGViip+4CMgLVUiM2BmtypdSg==", + "license": "LGPL-3.0-only", + "dependencies": { + "adm-zip": "0.5.12", + "axios": "1.8.2", + "commander": "12.0.0", + "fs-extra": "11.2.0", + "hpagent": "1.2.0", + "node-forge": "^1.3.1", + "properties-file": "3.5.4", + "proxy-from-env": "^1.1.0", + "semver": "7.6.0", + "slugify": "1.6.6", + "tar-stream": "3.1.7" + }, + "bin": { + "sonar": "bin/sonar-scanner.js", + "sonar-scanner": "bin/sonar-scanner.js" + } + }, + "node_modules/sonarqube-scanner/node_modules/commander": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", + "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/sonarqube-scanner/node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/sonarqube-scanner/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sonarqube-scanner/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sonarqube-scanner/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -15904,6 +16072,19 @@ "node": ">= 0.4" } }, + "node_modules/streamx": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", + "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -16542,6 +16723,17 @@ "node": ">=6" } }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/temp-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", @@ -16669,6 +16861,15 @@ "node": ">=8" } }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", diff --git a/package.json b/package.json index 94c2512a..6e5b3a80 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "react-dom": "^19.1.0", "react-scripts": "5.0.1", "reactflow": "^11.11.4", + "sonarqube-scanner": "^4.3.0", "typescript": "^4.9.5", "web-vitals": "^2.1.4" }, diff --git a/src/App.test.tsx b/src/App.test.tsx index 55b181bb..ada34b8d 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -1,8 +1,22 @@ import { render, screen } from '@testing-library/react'; -import App from './App'; +import { Header } from './components/layout/Header'; test('renders Vitruvius Modeler title', () => { - render(); - const titleElement = screen.getByText(/vitruvius modeler/i); + render(
); + const titleElement = screen.getByText(/Vitruvius Modeler/i); expect(titleElement).toBeInTheDocument(); -}); \ No newline at end of file +}); + +// test('renders Button components when handlers are provided', () => { +// const mockHandlers = { +// onNew: jest.fn(), +// onLoad: jest.fn(), +// onSave: jest.fn(), +// }; + +// render(
); + +// expect(screen.getByText('New')).toBeInTheDocument(); +// expect(screen.getByText('Load')).toBeInTheDocument(); +// expect(screen.getByText('Save')).toBeInTheDocument(); +// }); \ No newline at end of file diff --git a/src/components/flow/FlowCanvas.tsx b/src/components/flow/FlowCanvas.tsx index ba5902a3..f86588d4 100644 --- a/src/components/flow/FlowCanvas.tsx +++ b/src/components/flow/FlowCanvas.tsx @@ -16,9 +16,10 @@ const nodeTypes = { editable: EditableNode }; interface FlowCanvasProps { onDeploy?: (nodes: Node[], edges: Edge[]) => void; + sidebarOpen?: boolean; } -export function FlowCanvas({ onDeploy }: FlowCanvasProps) { +export function FlowCanvas({ onDeploy, sidebarOpen = true }: FlowCanvasProps) { const reactFlowWrapper = useRef(null); const [reactFlowInstance, setReactFlowInstance] = useState(null); @@ -51,10 +52,17 @@ export function FlowCanvas({ onDeploy }: FlowCanvasProps) { // } // }; + const containerStyle: React.CSSProperties = { + flexGrow: 1, + height: '100%', + width: '100%', + position: 'relative', + }; + return (
({ @@ -70,6 +78,7 @@ export function FlowCanvas({ onDeploy }: FlowCanvasProps) { onDragOver={onDragOver} onInit={setReactFlowInstance} nodeTypes={nodeTypes} + style={{ width: '100%', height: '100%' }} > diff --git a/src/components/index.ts b/src/components/index.ts index f79ea954..c26c062a 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -8,4 +8,5 @@ export { FlowCanvas } from './flow/FlowCanvas'; export { EditableNode } from './flow/EditableNode'; // UI components -export { Button } from './ui/Button'; \ No newline at end of file +export { Button } from './ui/Button'; +export { TreeMenu } from './ui/TreeMenu'; \ No newline at end of file diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 8fefeadb..fdd07401 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -1,33 +1,92 @@ import React from 'react'; +import { Button } from '../../components/ui/Button'; interface HeaderProps { onSave?: () => void; onLoad?: () => void; onNew?: () => void; + onToggleSidebar?: () => void; + sidebarOpen?: boolean; title?: string; } -export function Header({ onSave, onLoad, onNew, title = 'Vitruvius Modeler' }: HeaderProps) { +export function Header({ + onSave, + onLoad, + onNew, + onToggleSidebar, + sidebarOpen = true, + title = 'Vitruvius Modeler' +}: HeaderProps) { return (
-
-

{title}

+ minHeight: '60px', + }} className="header-responsive"> +
+ {onToggleSidebar && ( + + )} +

+ {title} +

+
+ {onNew && ( + + )} + {onLoad && ( + + )} + {onSave && ( + + )} +
); } \ No newline at end of file diff --git a/src/components/layout/MainLayout.tsx b/src/components/layout/MainLayout.tsx index 043de045..72052aef 100644 --- a/src/components/layout/MainLayout.tsx +++ b/src/components/layout/MainLayout.tsx @@ -13,15 +13,26 @@ interface MainLayoutProps { export function MainLayout({ onDeploy, onSave, onLoad, onNew }: MainLayoutProps) { return ( -
+
-
+
-
+
diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index d425c5ee..c3d561ca 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -1,41 +1,103 @@ import React from 'react'; +import { TreeMenu } from '../ui/TreeMenu'; +import { TreeNode } from '../../types/tree'; const sidebarStyle: React.CSSProperties = { - width: 180, - background: '#eee', - padding: 20, + width: '220px', + background: '#fff', + padding: '16px 8px', boxSizing: 'border-box', height: '100vh', - position: 'absolute', - left: 0, - top: 0, - zIndex: 10 + borderRight: '1px solid #e0e0e0', + overflowY: 'auto', }; -const itemStyle: React.CSSProperties = { - border: '1px solid #999', - padding: 8, - marginBottom: 12, - background: '#fff', - borderRadius: 4, - cursor: 'grab', - textAlign: 'center' +const titleStyle: React.CSSProperties = { + fontSize: '16px', + fontWeight: 'bold', + marginBottom: '16px', + color: '#222', + textAlign: 'left', + padding: 0, + borderBottom: '1px solid #e0e0e0', }; -const onDragStart = (event: React.DragEvent, nodeType: string) => { - event.dataTransfer.setData('application/reactflow', nodeType); - event.dataTransfer.effectAllowed = 'move'; +const onDragStart = (event: React.DragEvent, node: TreeNode) => { + if (node.nodeType) { + event.dataTransfer.setData('application/reactflow', node.nodeType); + event.dataTransfer.effectAllowed = 'move'; + } }; +const onNodeClick = (node: TreeNode) => { + // No-op for now +}; + +const sidebarTreeData: TreeNode[] = [ + { + id: 'uml-diagrams', + label: 'UML Diagrams', + children: [ + { + id: 'structure-diagrams', + label: 'Structure Diagrams', + children: [ + { id: 'class-diagram', label: 'Class Diagram', nodeType: 'class', draggable: true }, + { id: 'object-diagram', label: 'Object Diagram', nodeType: 'object', draggable: true }, + { id: 'component-diagram', label: 'Component Diagram', nodeType: 'component', draggable: true }, + { id: 'composite-structure-diagram', label: 'Composite Structure Diagram', nodeType: 'composite', draggable: true }, + { id: 'deployment-diagram', label: 'Deployment Diagram', nodeType: 'deployment', draggable: true }, + { id: 'package-diagram', label: 'Package Diagram', nodeType: 'package', draggable: true }, + { id: 'profile-diagram', label: 'Profile Diagram', nodeType: 'profile', draggable: true }, + ], + }, + { + id: 'behavior-diagrams', + label: 'Behavior Diagrams', + children: [ + { id: 'use-case-diagram', label: 'Use Case Diagram', nodeType: 'use-case', draggable: true }, + { id: 'activity-diagram', label: 'Activity Diagram', nodeType: 'activity', draggable: true }, + { id: 'state-machine-diagram', label: 'State Machine Diagram', nodeType: 'state-machine', draggable: true }, + { + id: 'interaction-diagrams', + label: 'Interaction Diagrams', + children: [ + { id: 'sequence-diagram', label: 'Sequence Diagram', nodeType: 'sequence', draggable: true }, + { id: 'communication-diagram', label: 'Communication Diagram', nodeType: 'communication', draggable: true }, + { id: 'timing-diagram', label: 'Timing Diagram', nodeType: 'timing', draggable: true }, + { id: 'interaction-overview-diagram', label: 'Interaction Overview Diagram', nodeType: 'interaction-overview', draggable: true }, + ], + }, + ], + }, + ], + }, + { + id: 'java', + label: 'JAVA', + children: [], // Ready for future children + }, + { + id: 'c++', + label: 'C++', + children: [], // Ready for future children + }, + { + id: 'sql', + label: 'SQL', + children: [], // Ready for future children + }, +]; + export function Sidebar() { return ( ); } \ No newline at end of file diff --git a/src/components/ui/TreeMenu.tsx b/src/components/ui/TreeMenu.tsx new file mode 100644 index 00000000..b68114ca --- /dev/null +++ b/src/components/ui/TreeMenu.tsx @@ -0,0 +1,93 @@ +import React, { useState } from 'react'; +import { TreeNode, TreeMenuProps } from '../../types/tree'; + +interface TreeMenuItemProps { + node: TreeNode; + level: number; + onNodeClick?: (node: TreeNode) => void; + onDragStart?: (event: React.DragEvent, node: TreeNode) => void; +} + +const TreeMenuItem: React.FC = ({ node, level, onNodeClick, onDragStart }) => { + const [isExpanded, setIsExpanded] = useState(false); + const hasChildren = node.children && node.children.length > 0; + + const handleToggle = (e: React.MouseEvent) => { + e.stopPropagation(); + if (hasChildren) { + setIsExpanded(!isExpanded); + } + if (onNodeClick) { + onNodeClick(node); + } + }; + + const handleDragStart = (event: React.DragEvent) => { + if (node.draggable && onDragStart) { + onDragStart(event, node); + } + }; + + const itemStyle: React.CSSProperties = { + padding: '4px 0 4px 0', + margin: 0, + cursor: node.draggable ? 'grab' : 'pointer', + background: 'none', + border: 'none', + fontSize: '14px', + color: '#222', + userSelect: 'none', + display: 'flex', + alignItems: 'center', + paddingLeft: `${level * 18 + 4}px`, + fontWeight: level === 0 ? 600 : (hasChildren ? 600 : 400), + }; + + return ( +
+
+ {hasChildren && ( + + {isExpanded ? '▼' : '▶'} + + )} + {!hasChildren && } + {node.label} +
+ {hasChildren && isExpanded && ( +
+ {node.children!.map((child) => ( + + ))} +
+ )} +
+ ); +}; + +export const TreeMenu: React.FC = ({ nodes, onNodeClick, onDragStart }) => { + return ( +
+ {nodes.map((node) => ( + + ))} +
+ ); +}; \ No newline at end of file diff --git a/src/setupTests.ts b/src/setupTests.ts index e56fbed5..0c4f5dac 100644 --- a/src/setupTests.ts +++ b/src/setupTests.ts @@ -3,4 +3,11 @@ // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom import '@testing-library/jest-dom'; -import './__mocks__/resizeObserverMock'; \ No newline at end of file + +// Mock ResizeObserver for ReactFlow compatibility +global.ResizeObserver = jest.fn().mockImplementation(() => ({ + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn(), +})); + diff --git a/src/styles/global.css b/src/styles/global.css index ae9aa568..e1db3059 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -11,6 +11,7 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; background-color: #f5f5f5; + overflow: hidden; } code { @@ -63,6 +64,37 @@ code { cursor: grabbing; } +/* Responsive breakpoints */ +@media (max-width: 768px) { + .sidebar-responsive { + width: 100% !important; + max-width: 280px; + } + + .header-responsive { + padding: 0 12px !important; + } + + .header-responsive h1 { + font-size: 16px !important; + } +} + +@media (max-width: 480px) { + .sidebar-responsive { + width: 100% !important; + max-width: 100%; + } + + .header-responsive { + padding: 0 8px !important; + } + + .header-responsive h1 { + font-size: 14px !important; + } +} + /* Scrollbar styles */ ::-webkit-scrollbar { width: 8px; @@ -70,17 +102,17 @@ code { } ::-webkit-scrollbar-track { - background: #f1f1f1; + background: rgba(255, 255, 255, 0.1); border-radius: 4px; } ::-webkit-scrollbar-thumb { - background: #c1c1c1; + background: rgba(255, 255, 255, 0.3); border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { - background: #a8a8a8; + background: rgba(255, 255, 255, 0.5); } /* React Flow custom styles */ @@ -96,4 +128,23 @@ code { .react-flow__minimap { border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +/* Modern scrollbar for sidebar */ +.sidebar-scrollbar::-webkit-scrollbar { + width: 6px; +} + +.sidebar-scrollbar::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.1); + border-radius: 3px; +} + +.sidebar-scrollbar::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.3); + border-radius: 3px; +} + +.sidebar-scrollbar::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.5); } \ No newline at end of file diff --git a/src/types/flow.ts b/src/types/flow.ts index c74f8b86..b5031933 100644 --- a/src/types/flow.ts +++ b/src/types/flow.ts @@ -16,7 +16,21 @@ export interface FlowData { edges: FlowEdge[]; } -export type NodeType = 'sequence' | 'object'; +export type NodeType = + | 'sequence' + | 'object' + | 'class' + | 'component' + | 'composite' + | 'deployment' + | 'package' + | 'profile' + | 'use-case' + | 'activity' + | 'state-machine' + | 'communication' + | 'timing' + | 'interaction-overview'; export interface DragItem { type: NodeType; diff --git a/src/types/index.ts b/src/types/index.ts index 68a641ab..c0e3cea4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1 +1,2 @@ -export * from './flow'; \ No newline at end of file +export * from './flow'; +export * from './tree'; \ No newline at end of file diff --git a/src/types/tree.ts b/src/types/tree.ts new file mode 100644 index 00000000..65719edf --- /dev/null +++ b/src/types/tree.ts @@ -0,0 +1,13 @@ +export interface TreeNode { + id: string; + label: string; + children?: TreeNode[]; + nodeType?: string; + draggable?: boolean; +} + +export interface TreeMenuProps { + nodes: TreeNode[]; + onNodeClick?: (node: TreeNode) => void; + onDragStart?: (event: React.DragEvent, node: TreeNode) => void; +} \ No newline at end of file