From 2b4bb5ea64f64fd97031507d509e88ee5f05d395 Mon Sep 17 00:00:00 2001 From: PotatoMaxwell Date: Wed, 27 May 2026 11:40:32 +0800 Subject: [PATCH] System updated with many bug revised --- backend/__pycache__/database.cpython-39.pyc | Bin 0 -> 1392 bytes backend/server.py | 13 +- .../mxpic_project_1/mxpic_project_1.yml | 56 ------- .../layout/mxpic_project_1_2/.project.json | 4 - .../layout/mxpic_project_1_3/.project.json | 4 - .../layout/mxpic_project_1_4/.project.json | 4 - frontend/canvas.html | 137 +++++++++++++++--- frontend/dashboard.html | 80 ++++++++-- 8 files changed, 199 insertions(+), 99 deletions(-) create mode 100644 backend/__pycache__/database.cpython-39.pyc delete mode 100644 database/admin/layout/mxpic_project_1/mxpic_project_1.yml delete mode 100644 database/admin/layout/mxpic_project_1_2/.project.json delete mode 100644 database/admin/layout/mxpic_project_1_3/.project.json delete mode 100644 database/admin/layout/mxpic_project_1_4/.project.json diff --git a/backend/__pycache__/database.cpython-39.pyc b/backend/__pycache__/database.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2745305e26722fa9060ca4ce1eb95cbd6a3c1b66 GIT binary patch literal 1392 zcmaJ>PjA~c6elHFlASo~hIP9Vy|fw(qg&en3$~?hqIAX_rL*NNH3A5-NXJTS$s=jB z5u8)@LE_^+#ejVbU3c17*lCaSA2d6R0w3Qa-(S8T71!%E1moeK4=;Nag#J>(hr8fI&7U#?%~`7%_Zl-BmQTHFcP~#MGn~tt_o29#pAKo!`))MqOHk zb%*WJ+Lgg6tKA~7uhX4dj3Gh~dAol@G!%Ha{Op2djK`8ivsj3CIj7M$7URNbHcOYr zG9Jc)74_-+S(-#jZqi|*j5oss1`0!mFvSpvM0Z#kOMHVC=+0cC8@#}G7R5^lt~of< zTxE$wAZ=>i;u}MsKq(7jWGoGgq(g0$F8FYlkUp$cwNeD>J3;YBjjYFE*KdVB30to^ zK4~A5;4~!uS-ThZ$XqZk$dj$AvPdau2ciGk?~*s&_DQSzmi*|yC9QsV+77_ii64ZG zdlt$|7Ec)o{j-qtgZ58-Uq=c0oz8vLdw00fSLrCltxHdHMWg3;{KJrZL5{npC+k#y z`N8k{n%4opBadS`O|!=X`^&GMJ%90a@l8AE`Q1>JaJngPlV5|}3-6JiTb;h&BTo(* z(5?MM& zucG62$1m+<&PC2khrMUXT(Z&~F`0~WK$ZcBC@V9bW^!OB`E;7fvXV@4!926Hlqc?h z%AhJCEe*Zj5wF9}KTqlY`Ri0hoXv8P%A8-HuTbb8wa(6madO2ndcJ{Zb9P-iX$Jaq zSOs(ODG-Ef*fncapzuAwuHj<-5$q>eovw!ktC||Y&w<$@SRq#-*jN|}!3#9P3Y#DS zc5h5LwKXTbCDM{rdX-vQ!TrMqc4 z-&MOxskGFdzPT0IQ=9GulaV?gAPJ?cuLSSu3*n!DT)I&-jngcOibqH5_fFJBrtu^# z7$st!But3Wd@{LidV4y<(#S>m@jJ$^3O2uJ3g|jd<@KuD)-0Ce(us#cGuCCEW~D<@ z{fJb!Q95B!v?@?_W1EKpJshY%<_DE1H1Jh9-%fKnpRn&0s>A~zs;|4|Bg6gNbzN-Z Fe*p)BV_X0L literal 0 HcmV?d00001 diff --git a/backend/server.py b/backend/server.py index 8db4504..66f2d53 100644 --- a/backend/server.py +++ b/backend/server.py @@ -340,9 +340,20 @@ def delete_project(project_name): return jsonify({"message": "deleted", "project": safe_name(project_name, 'project_1')}) -@app.route('/api/projects//cells/', methods=['PATCH']) +@app.route('/api/projects//cells/', methods=['PATCH', 'DELETE']) @login_required_json def rename_cell(project_name, cell_name): + if request.method == 'DELETE': + cell = safe_name(cell_name, 'canvas_1') + target = os.path.abspath(cell_file_path(project_name, cell)) + project_dir = os.path.abspath(project_root(project_name)) + if not target.startswith(project_dir + os.sep): + return jsonify({"error": "Invalid cell path"}), 400 + if not os.path.exists(target): + return jsonify({"message": "already deleted", "cell": cell}) + os.remove(target) + return jsonify({"message": "deleted", "cell": cell}) + data = request.get_json(silent=True) or {} old_cell = safe_name(cell_name, 'canvas_1') new_cell = safe_name(data.get('name'), old_cell) diff --git a/database/admin/layout/mxpic_project_1/mxpic_project_1.yml b/database/admin/layout/mxpic_project_1/mxpic_project_1.yml deleted file mode 100644 index 53497d7..0000000 --- a/database/admin/layout/mxpic_project_1/mxpic_project_1.yml +++ /dev/null @@ -1,56 +0,0 @@ -# ============================================= -# mxPIC Cell/Project Definition File -# ============================================= -schema_version: "2.0.0" -kind: cell -project: mxpic_project_1 -name: mxpic_project_1 -type: project -version: "1.0.0" - -# 1. External Ports (How this cell connects to the outside world) -ports: -- name: in0 - layer: WG_CORE - x: 0.0 - y: 0.0 - angle: 180.0 - width: 0.5 -- name: out0 - layer: WG_CORE - x: 100.0 - y: 10.0 - angle: 0.0 - width: 0.5 -- name: out1 - layer: WG_CORE - x: 100.0 - y: -10.0 - angle: 0.0 - width: 0.5 - -# 2. Instances (The sub-components dropped onto this canvas) -instances: - canvas_1: - component: canvas_1 - x: 250.0 - y: 200.0 - rotation: 0.0 - mirror: false - settings: - length: - - component_2: - component: canvas_1 - x: 250.0 - y: 280.0 - rotation: 0.0 - mirror: false - settings: - length: - -# 3. Bundles (Grouped links for multi-bus/parallel routing) -bundles: - output_bus: - routing_type: euler_bend - links: diff --git a/database/admin/layout/mxpic_project_1_2/.project.json b/database/admin/layout/mxpic_project_1_2/.project.json deleted file mode 100644 index 4a24707..0000000 --- a/database/admin/layout/mxpic_project_1_2/.project.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "mxpic_project_1_2", - "technology": "Silterra/EMO1_2ML_CU_Al_RDL" -} \ No newline at end of file diff --git a/database/admin/layout/mxpic_project_1_3/.project.json b/database/admin/layout/mxpic_project_1_3/.project.json deleted file mode 100644 index ccec53e..0000000 --- a/database/admin/layout/mxpic_project_1_3/.project.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "mxpic_project_1_3", - "technology": "Silterra/EMO1_2ML_CU_Al_RDL" -} \ No newline at end of file diff --git a/database/admin/layout/mxpic_project_1_4/.project.json b/database/admin/layout/mxpic_project_1_4/.project.json deleted file mode 100644 index 929a8c6..0000000 --- a/database/admin/layout/mxpic_project_1_4/.project.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "mxpic_project_1_4", - "technology": "Silterra/EMO1_2ML_CU_Al_RDL" -} \ No newline at end of file diff --git a/frontend/canvas.html b/frontend/canvas.html index b21e426..9c81447 100644 --- a/frontend/canvas.html +++ b/frontend/canvas.html @@ -144,9 +144,16 @@ color: var(--text-main); } - .tree-folder summary { + summary.tree-folder { font-weight: 500; color: var(--accent); + line-height: 18px; + } + + summary.tree-folder::marker { + color: var(--text-muted); + font-size: 0.74rem; + line-height: 1; } .component-leaf { @@ -484,10 +491,11 @@ } .tree-summary-row { - display: flex; + display: inline-flex; align-items: center; gap: 8px; - width: 100%; + width: calc(100% - 18px); + vertical-align: middle; } .folder-icon { @@ -563,6 +571,33 @@ transition: transform 0.15s ease; } + .tree-delete-btn { + width: 20px; + height: 20px; + border: 1px solid rgba(239, 68, 68, 0.45); + border-radius: 6px; + background: rgba(239, 68, 68, 0.08); + color: #fca5a5; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + font-size: 0.78rem; + line-height: 1; + padding: 0; + flex: 0 0 auto; + } + + .tree-delete-btn:hover { + background: rgba(239, 68, 68, 0.18); + border-color: var(--danger); + color: #fecaca; + } + + body.light-mode .tree-delete-btn { + color: #b91c1c; + } + details[open] > summary .tree-expander { transform: rotate(90deg); } @@ -1076,7 +1111,7 @@ ); }; - const ProjectTreeNode = ({ name, children, onOpenComposite, onOpenProject, onSelectInstance, onRenameCanvas }) => { + const ProjectTreeNode = ({ name, children, onOpenComposite, onOpenProject, onSelectInstance, onRenameCanvas, onDeleteCanvas }) => { if (children && children.__type__ === 'project') { const projectName = children.__name__ || name; const composites = children.composites || []; @@ -1088,12 +1123,12 @@ - Project - {name} + {name} > {composites.map(comp => ( - + ))} ); @@ -1125,8 +1160,14 @@ }; const handleOpen = (event) => { if (event.target.closest('.tree-expander')) return; + if (event.target.closest('.tree-delete-btn')) return; if (onOpenComposite) onOpenComposite(cellName); }; + const handleDeleteCanvas = (event) => { + event.preventDefault(); + event.stopPropagation(); + if (onDeleteCanvas) onDeleteCanvas(cellName); + }; return (
@@ -1139,6 +1180,7 @@ onRename={onRenameCanvas} onOpen={() => onOpenComposite && onOpenComposite(cellName)} /> + > @@ -1186,7 +1228,7 @@ {Object.entries(children).map(([childName, childData]) => ( - + ))}
); @@ -1229,7 +1271,7 @@ return null; }; - const LeftPanel = ({ projectTreeItems, library, treeKey, expanded, onToggle, treeRef, width, onOpenComposite, onOpenProject, onSelectInstance, onRenameCanvas, projectExpanded, onProjectToggle, projectTreeRef, projectTreeKey }) => { + const LeftPanel = ({ projectTreeItems, library, treeKey, expanded, onToggle, treeRef, width, onOpenComposite, onOpenProject, onSelectInstance, onRenameCanvas, onDeleteCanvas, projectExpanded, onProjectToggle, projectTreeRef, projectTreeKey }) => { const [projectPanelHeight, setProjectPanelHeight] = useState(270); const [resizingProjectPanel, setResizingProjectPanel] = useState(false); const leftPanelRef = useRef(null); @@ -1278,11 +1320,11 @@ projectTreeItems.map(item => { if (item.type === 'project') { return ( - + ); } else { return ( - + ); } }) @@ -2266,6 +2308,7 @@ }, [pages, library, syncAllCompositeTrees]); const selectedNode = useMemo(() => currentNodes.find(n => n.selected), [currentNodes]); + const openTabs = useMemo(() => pages.filter(page => !page.isClosed), [pages]); const selectInstanceInPage = useCallback((pageName, instanceName) => { if (!pageName || !instanceName) return; @@ -2294,7 +2337,7 @@ const existing = prev.find(p => p.name === name && p.type === 'project'); if (existing) { setActivePageId(existing.id); - return prev; + return prev.map(p => p.id === existing.id ? { ...p, isClosed: false } : p); } const newProjectPage = { id: Date.now().toString() + Math.random().toString(36).substr(2, 5), @@ -2319,12 +2362,13 @@ const existing = prev.find(p => p.name === name && p.type === 'composite'); if (existing) { setActivePageId(existing.id); - return prev; + return prev.map(p => p.id === existing.id ? { ...p, isClosed: false } : p); } const newComposite = { id: Date.now().toString() + Math.random().toString(36).substr(2, 5), name: name, type: 'composite', + isClosed: false, nodes: [ { id: 'page-port', @@ -2413,6 +2457,7 @@ id: Date.now().toString() + Math.random().toString(36).substr(2, 5), name: cellName, type: 'composite', + isClosed: false, nodes: [ { id: 'page-port', @@ -2438,17 +2483,62 @@ const closePage = useCallback((pageId) => { setPages(prev => { - const pageToClose = prev.find(p => p.id === pageId); - const filtered = prev.filter(p => p.id !== pageId); + const closed = prev.map(p => p.id === pageId ? { ...p, isClosed: true } : p); if (activePageId === pageId) { const idx = prev.findIndex(p => p.id === pageId); - const nextActive = filtered[Math.min(idx, filtered.length - 1)] || null; + const openPages = closed.filter(p => !p.isClosed); + const nextActive = openPages[Math.min(idx, openPages.length - 1)] || openPages[openPages.length - 1] || null; setActivePageId(nextActive ? nextActive.id : null); } - return filtered; + return closed; }); }, [activePageId]); + const deleteCanvas = useCallback((cellName) => { + if (!cellName) return; + if (!window.confirm(`Delete canvas "${cellName}" from this project?`)) return; + const pageToDelete = pages.find(p => p.type === 'composite' && p.name === cellName); + setPages(prev => { + const withoutCell = prev + .filter(p => !(p.type === 'composite' && p.name === cellName)) + .map(p => { + if (p.type !== 'project') return p; + return { + ...p, + nodes: p.nodes.filter(node => node.data?.componentName !== cellName), + edges: p.edges.filter(edge => { + const removedNodeIds = new Set(p.nodes.filter(node => node.data?.componentName === cellName).map(node => node.id)); + return !removedNodeIds.has(edge.source) && !removedNodeIds.has(edge.target); + }) + }; + }); + if (activePageId === pageToDelete?.id) { + const nextActive = withoutCell.find(p => !p.isClosed) || withoutCell[0] || null; + setActivePageId(nextActive ? nextActive.id : null); + } + return withoutCell; + }); + setProjectCompositeMap(prev => { + const next = {}; + Object.entries(prev).forEach(([project, cells]) => { + next[project] = cells.filter(name => name !== cellName); + }); + return next; + }); + setStandaloneComposites(prev => prev.filter(name => name !== cellName)); + setCompositeTrees(prev => { + const next = { ...prev }; + delete next[cellName]; + return next; + }); + fetch(`/api/projects/${encodeURIComponent(currentProjectName)}/cells/${encodeURIComponent(cellName)}`, { + method: 'DELETE' + }).then(response => { + if (response.ok) addLog(`Deleted canvas "${cellName}".`); + else addLog(`Canvas "${cellName}" was removed locally, but file delete failed.`); + }).catch(() => addLog(`Canvas "${cellName}" was removed locally, but file delete failed.`)); + }, [pages, activePageId, currentProjectName, addLog]); + const switchPage = useCallback((pageId) => { setActivePageId(pageId); }, []); @@ -2680,7 +2770,15 @@ const projectTreeItems = useMemo(() => { const items = []; - const projectPages = pages.filter(p => p.type === 'project'); + const projectPagesByName = new Map(); + pages + .filter(p => p.type === 'project') + .forEach(project => { + if (!projectPagesByName.has(project.name) || project.id === activePageId) { + projectPagesByName.set(project.name, project); + } + }); + const projectPages = Array.from(projectPagesByName.values()); projectPages.forEach(project => { const projectNodeItems = project.nodes .filter(node => node.id !== 'page-port' && node.data?.componentName) @@ -2732,7 +2830,7 @@ }); }); return items; - }, [pages, library, projectCompositeMap, standaloneComposites, compositeTrees]); + }, [pages, library, projectCompositeMap, standaloneComposites, compositeTrees, activePageId]); const libraryWithCells = useMemo(() => { const cellEntries = {}; @@ -2904,6 +3002,7 @@ ${bundlesBlock}`; onOpenProject={openProject} onSelectInstance={selectInstanceInPage} onRenameCanvas={renameCanvas} + onDeleteCanvas={deleteCanvas} projectExpanded={projectExpanded} onProjectToggle={handleProjectToggle} projectTreeRef={projectTreeContainerRef} @@ -2919,7 +3018,7 @@ ${bundlesBlock}`;
+ Cell
- {pages.map(page => ( + {openTabs.map(page => (
switchPage(page.id)}> diff --git a/frontend/dashboard.html b/frontend/dashboard.html index 39ff643..1fad8ca 100644 --- a/frontend/dashboard.html +++ b/frontend/dashboard.html @@ -183,7 +183,9 @@ min-height: 76px; padding: 16px; border-radius: 8px; - display: flex; + display: grid; + grid-template-columns: 30px minmax(0, 1fr) minmax(64px, auto) auto; + gap: 12px; align-items: center; transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease; cursor: pointer; @@ -211,15 +213,37 @@ box-shadow: 0 20px 44px rgba(37, 99, 235, 0.18); } + .project-card.empty-project-card { + display: flex; + align-items: center; + grid-column: 1 / -1; + cursor: default; + color: var(--text-muted); + white-space: normal; + } + + .project-card.empty-project-card:hover { + transform: none; + border-color: var(--border); + box-shadow: 0 16px 34px var(--shadow); + } + .project-meta { - margin-left: auto; color: var(--text-muted); font-family: 'IBM Plex Mono', Consolas, monospace; font-size: 0.76rem; + margin-top: 4px; + overflow: hidden; + text-overflow: ellipsis; white-space: nowrap; } + .project-details { + min-width: 0; + } + .project-name { + display: block; font-weight: 600; letter-spacing: 0; min-width: 0; @@ -229,7 +253,6 @@ } .delete-project-btn { - margin-left: 14px; border: 1px solid rgba(239, 68, 68, 0.45); background: rgba(239, 68, 68, 0.08); color: #fecaca; @@ -345,6 +368,21 @@ font-size: 1rem; } + .modal-field { + margin-bottom: 14px; + } + + .modal-field label { + display: block; + margin-bottom: 6px; + color: var(--text-muted); + font-size: 0.82rem; + font-weight: 700; + letter-spacing: 0.02em; + text-transform: uppercase; + } + + .modal-panel input, .modal-panel select { width: 100%; background: var(--panel-soft); @@ -354,8 +392,10 @@ padding: 11px; font-family: inherit; outline: none; + box-sizing: border-box; } + .modal-panel input:focus, .modal-panel select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(110, 231, 255, 0.12); @@ -418,13 +458,19 @@ .project-card { align-items: flex-start; - flex-wrap: wrap; + grid-template-columns: 30px minmax(0, 1fr) auto; gap: 8px; } .project-meta { - margin-left: 45px; - width: calc(100% - 45px); + grid-column: 2 / 4; + margin-top: 0; + width: 100%; + } + + .delete-project-btn { + grid-column: 3; + grid-row: 1; } } @@ -462,8 +508,15 @@