From fd179b1e86eaad683d8adbb6c0c5f93288ee31bf Mon Sep 17 00:00:00 2001 From: superstar54 Date: Sun, 10 Dec 2023 06:38:15 +0000 Subject: [PATCH] add sidebar, worktree table, example rete editor --- aiida_worktree/web/backend/app/api.py | 31 ++- aiida_worktree/web/frontend/package-lock.json | 159 +++++++++++++++ aiida_worktree/web/frontend/package.json | 4 + aiida_worktree/web/frontend/src/App.css | 150 +++++++++++++- aiida_worktree/web/frontend/src/App.js | 40 ++++ aiida_worktree/web/frontend/src/App.tsx | 29 --- .../web/frontend/src/components/Home.js | 7 + .../web/frontend/src/components/Node.js | 7 + .../web/frontend/src/components/Settings.js | 12 ++ .../web/frontend/src/components/WorkTree.js | 71 +++++++ .../frontend/src/components/WorkTreeDetail.js | 31 +++ .../frontend/src/components/WorkTreeGraph.tsx | 45 ++++ aiida_worktree/web/frontend/src/logo.svg | 7 +- .../web/frontend/src/rete/default.ts | 192 ++++++------------ 14 files changed, 626 insertions(+), 159 deletions(-) create mode 100644 aiida_worktree/web/frontend/src/App.js delete mode 100644 aiida_worktree/web/frontend/src/App.tsx create mode 100644 aiida_worktree/web/frontend/src/components/Home.js create mode 100644 aiida_worktree/web/frontend/src/components/Node.js create mode 100644 aiida_worktree/web/frontend/src/components/Settings.js create mode 100644 aiida_worktree/web/frontend/src/components/WorkTree.js create mode 100644 aiida_worktree/web/frontend/src/components/WorkTreeDetail.js create mode 100644 aiida_worktree/web/frontend/src/components/WorkTreeGraph.tsx diff --git a/aiida_worktree/web/backend/app/api.py b/aiida_worktree/web/backend/app/api.py index b1b5c6d6..5dc2f8aa 100644 --- a/aiida_worktree/web/backend/app/api.py +++ b/aiida_worktree/web/backend/app/api.py @@ -23,8 +23,35 @@ async def read_root() -> dict: return {"message": "Welcome to your todo list."} +@app.get("/worktree-data") +async def read_worktree_data(): + from fastapi import HTTPException + from aiida_worktree.cli.query_worktree import WorkTreeQueryBuilder + + try: + relationships = {} + builder = WorkTreeQueryBuilder() + query_set = builder.get_query_set( + relationships=relationships, + # filters=filters, + # order_by={order_by: order_dir}, + # past_days=past_days, + # limit=limit, + ) + project = ["pk", "uuid", "state", "ctime", "mtime", "process_label"] + projected = builder.get_projected(query_set, projections=project) + # pop headers + projected.pop(0) + data = [] + for p in projected: + data.append({project[i]: p[i] for i in range(len(project))}) + return data + except KeyError: + raise HTTPException(status_code=404, detail=f"Worktree {id} not found") + + @app.get("/worktree/{id}") -async def read_item(id: str): +async def read_worktree_item(id: int): from fastapi import HTTPException from .utils import worktree_to_json @@ -38,6 +65,6 @@ async def read_item(id: str): return wtdata = deserialize_unsafe(wtdata) content = worktree_to_json(wtdata) - return {"id": id, "content": content} + return content except KeyError: raise HTTPException(status_code=404, detail=f"Worktree {id} not found") diff --git a/aiida_worktree/web/frontend/package-lock.json b/aiida_worktree/web/frontend/package-lock.json index 25aabf24..d069dec5 100644 --- a/aiida_worktree/web/frontend/package-lock.json +++ b/aiida_worktree/web/frontend/package-lock.json @@ -8,6 +8,8 @@ "name": "worktree", "version": "0.1.0", "dependencies": { + "@fortawesome/free-solid-svg-icons": "^6.5.1", + "@fortawesome/react-fontawesome": "^0.2.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -20,6 +22,8 @@ "elkjs": "^0.8.2", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-paginate": "^8.2.0", + "react-router-dom": "^6.20.1", "react-scripts": "5.0.1", "rete": "^2.0.2", "rete-area-3d-plugin": "^2.0.3", @@ -2424,6 +2428,52 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.1.tgz", + "integrity": "sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.1.tgz", + "integrity": "sha512-MfRCYlQPXoLlpem+egxjfkEuP9UQswTrlCOsknus/NcMoblTH2g0jPrapbcIb04KGA7E2GZxbAccGZfWoYgsrQ==", + "hasInstallScript": true, + "peer": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.5.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.1.tgz", + "integrity": "sha512-S1PPfU3mIJa59biTtXJz1oI0+KAXW6bkAb31XKhxdxtuXDiUIFsih4JR1v5BbxY7hVHsD1RKq+jRkVRaf773NQ==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.5.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz", + "integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.3" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", @@ -3288,6 +3338,14 @@ } } }, + "node_modules/@remix-run/router": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.13.1.tgz", + "integrity": "sha512-so+DHzZKsoOcoXrILB4rqDkMDy7NLMErRdOxvzvOKb507YINKUP4Di+shbTZDhSE/pBZ+vr7XGIpcOO0VLSA+Q==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -14639,6 +14697,17 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/react-paginate": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/react-paginate/-/react-paginate-8.2.0.tgz", + "integrity": "sha512-sJCz1PW+9PNIjUSn919nlcRVuleN2YPoFBOvL+6TPgrH/3lwphqiSOgdrLafLdyLDxsgK+oSgviqacF4hxsDIw==", + "dependencies": { + "prop-types": "^15" + }, + "peerDependencies": { + "react": "^16 || ^17 || ^18" + } + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -14647,6 +14716,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.20.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.20.1.tgz", + "integrity": "sha512-ccvLrB4QeT5DlaxSFFYi/KR8UMQ4fcD8zBcR71Zp1kaYTC5oJKYAp1cbavzGrogwxca+ubjkd7XjFZKBW8CxPA==", + "dependencies": { + "@remix-run/router": "1.13.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.20.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.20.1.tgz", + "integrity": "sha512-npzfPWcxfQN35psS7rJgi/EW0Gx6EsNjfdJSAk73U/HqMEJZ2k/8puxfwHFgDQhBGmS3+sjnGbMdMSV45axPQw==", + "dependencies": { + "@remix-run/router": "1.13.1", + "react-router": "6.20.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -19556,6 +19655,36 @@ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz", "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==" }, + "@fortawesome/fontawesome-common-types": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.1.tgz", + "integrity": "sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==" + }, + "@fortawesome/fontawesome-svg-core": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.1.tgz", + "integrity": "sha512-MfRCYlQPXoLlpem+egxjfkEuP9UQswTrlCOsknus/NcMoblTH2g0jPrapbcIb04KGA7E2GZxbAccGZfWoYgsrQ==", + "peer": true, + "requires": { + "@fortawesome/fontawesome-common-types": "6.5.1" + } + }, + "@fortawesome/free-solid-svg-icons": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.1.tgz", + "integrity": "sha512-S1PPfU3mIJa59biTtXJz1oI0+KAXW6bkAb31XKhxdxtuXDiUIFsih4JR1v5BbxY7hVHsD1RKq+jRkVRaf773NQ==", + "requires": { + "@fortawesome/fontawesome-common-types": "6.5.1" + } + }, + "@fortawesome/react-fontawesome": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz", + "integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==", + "requires": { + "prop-types": "^15.8.1" + } + }, "@humanwhocodes/config-array": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", @@ -20179,6 +20308,11 @@ "source-map": "^0.7.3" } }, + "@remix-run/router": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.13.1.tgz", + "integrity": "sha512-so+DHzZKsoOcoXrILB4rqDkMDy7NLMErRdOxvzvOKb507YINKUP4Di+shbTZDhSE/pBZ+vr7XGIpcOO0VLSA+Q==" + }, "@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -28282,11 +28416,36 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "react-paginate": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/react-paginate/-/react-paginate-8.2.0.tgz", + "integrity": "sha512-sJCz1PW+9PNIjUSn919nlcRVuleN2YPoFBOvL+6TPgrH/3lwphqiSOgdrLafLdyLDxsgK+oSgviqacF4hxsDIw==", + "requires": { + "prop-types": "^15" + } + }, "react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" }, + "react-router": { + "version": "6.20.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.20.1.tgz", + "integrity": "sha512-ccvLrB4QeT5DlaxSFFYi/KR8UMQ4fcD8zBcR71Zp1kaYTC5oJKYAp1cbavzGrogwxca+ubjkd7XjFZKBW8CxPA==", + "requires": { + "@remix-run/router": "1.13.1" + } + }, + "react-router-dom": { + "version": "6.20.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.20.1.tgz", + "integrity": "sha512-npzfPWcxfQN35psS7rJgi/EW0Gx6EsNjfdJSAk73U/HqMEJZ2k/8puxfwHFgDQhBGmS3+sjnGbMdMSV45axPQw==", + "requires": { + "@remix-run/router": "1.13.1", + "react-router": "6.20.1" + } + }, "react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", diff --git a/aiida_worktree/web/frontend/package.json b/aiida_worktree/web/frontend/package.json index d66fd9e8..ddbe0733 100644 --- a/aiida_worktree/web/frontend/package.json +++ b/aiida_worktree/web/frontend/package.json @@ -3,6 +3,8 @@ "version": "0.1.0", "private": true, "dependencies": { + "@fortawesome/free-solid-svg-icons": "^6.5.1", + "@fortawesome/react-fontawesome": "^0.2.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -15,6 +17,8 @@ "elkjs": "^0.8.2", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-paginate": "^8.2.0", + "react-router-dom": "^6.20.1", "react-scripts": "5.0.1", "rete": "^2.0.2", "rete-area-3d-plugin": "^2.0.3", diff --git a/aiida_worktree/web/frontend/src/App.css b/aiida_worktree/web/frontend/src/App.css index 74b5e053..aad786db 100644 --- a/aiida_worktree/web/frontend/src/App.css +++ b/aiida_worktree/web/frontend/src/App.css @@ -1,9 +1,10 @@ .App { text-align: center; + display: flex; } .App-logo { - height: 40vmin; + height: 5vmin; pointer-events: none; } @@ -36,3 +37,150 @@ transform: rotate(360deg); } } + +/* Home page sidebar Menu */ + +.sidebar { + width: 50px; /* Initial width */ + height: 100vh; + background-color: #333; + padding-top: 20px; + position: fixed; + transition: width 0.3s; + overflow: hidden; +} + +.sidebar:hover { + width: 200px; /* Expanded width */ +} + +.logo-container { + width: 50px; /* Logo container width */ + text-align: center; +} + +.sidebar img { + width: 30px; /* Adjust as per your logo's dimensions */ + cursor: pointer; +} + +.sidebar nav ul { + list-style-type: none; + padding: 0; + margin-top: 20px; +} + +.sidebar nav ul li { + padding: 10px; +} + +.sidebar:hover nav ul li { + opacity: 1; +} + +.sidebar nav ul li a { + color: white; + text-decoration: none; +} + +.content { + margin-left: 50px; /* Match the initial width of the sidebar */ + padding: 20px; + transition: margin-left 0.3s; +} + +.sidebar:hover ~ .content { + margin-left: 200px; /* Match the expanded width of the sidebar */ +} + +/* ... existing styles ... */ + +.sidebar ul li a { + display: flex; + align-items: center; + color: white; + text-decoration: none; +} + +.sidebar ul li a span { + margin-left: 10px; + display: none; /* Hide text labels by default */ +} + +.sidebar:hover ul li a span { + display: inline; /* Show text labels on hover */ +} + +/* Icon styles */ +.sidebar nav ul li a .fa-icon { + min-width: 20px; /* Ensure icons have space */ +} + +/* Pagination styles */ +.pagination { + display: flex; + list-style-type: none; + justify-content: center; + padding: 0; +} + +.pagination li { + margin: 0 5px; +} + +.pagination li a { + border: 1px solid #007bff; + padding: 5px 10px; + text-decoration: none; + color: #007bff; + border-radius: 5px; +} + +.pagination li a:hover { + background-color: #007bff; + color: white; +} + +.pagination .active a { + background-color: #007bff; + color: white; +} + +/* Table styles */ +.table { + width: 100%; + border-collapse: collapse; + margin: 25px 0; + font-size: 0.9em; + font-family: sans-serif; + min-width: 400px; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.15); +} + +.table thead tr { + background-color: #009879; + color: #ffffff; + text-align: left; +} + +.table th, +.table td { + padding: 12px 15px; +} + +.table tbody tr { + border-bottom: 1px solid #dddddd; +} + +.table tbody tr:nth-of-type(even) { + background-color: #f3f3f3; +} + +.table tbody tr:last-of-type { + border-bottom: 2px solid #009879; +} + +.table tbody tr.active-row { + font-weight: bold; + color: #009879; +} diff --git a/aiida_worktree/web/frontend/src/App.js b/aiida_worktree/web/frontend/src/App.js new file mode 100644 index 00000000..15f575d0 --- /dev/null +++ b/aiida_worktree/web/frontend/src/App.js @@ -0,0 +1,40 @@ +import React from 'react'; +import { BrowserRouter as Router, Route, Link, Routes } from 'react-router-dom'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faHome, faTree, faDotCircle, faCog } from '@fortawesome/free-solid-svg-icons'; +import Home from './components/Home'; +import WorkTree from './components/WorkTree'; +import Node from './components/Node'; +import WorkTreeGraph from './components/WorkTreeGraph'; // Import the component for the detail page +import Settings from './components/Settings'; // Import your Settings component +import './App.css'; + +function App() { + return ( + +
+
+ +
+
+ + } /> + } /> + } /> + } /> + } /> + +
+
+
+ ); +} + +export default App; diff --git a/aiida_worktree/web/frontend/src/App.tsx b/aiida_worktree/web/frontend/src/App.tsx deleted file mode 100644 index 51d221a0..00000000 --- a/aiida_worktree/web/frontend/src/App.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import { useRete } from 'rete-react-plugin'; -import logo from './logo.svg'; -import './App.css'; -import './rete.css'; -import { createEditor } from './rete'; - -function App() { - const [ref] = useRete(createEditor) - - return ( -
-
- logo - - Learn Rete.js - -
-
-
- ); -} - -export default App diff --git a/aiida_worktree/web/frontend/src/components/Home.js b/aiida_worktree/web/frontend/src/components/Home.js new file mode 100644 index 00000000..c03ca10b --- /dev/null +++ b/aiida_worktree/web/frontend/src/components/Home.js @@ -0,0 +1,7 @@ +import React from 'react'; + +function Home() { + return

Welcome to AiiDA-WorkTree

; +} + +export default Home; diff --git a/aiida_worktree/web/frontend/src/components/Node.js b/aiida_worktree/web/frontend/src/components/Node.js new file mode 100644 index 00000000..cd52c0c9 --- /dev/null +++ b/aiida_worktree/web/frontend/src/components/Node.js @@ -0,0 +1,7 @@ +import React from 'react'; + +function Node() { + return

Node

; +} + +export default Node; diff --git a/aiida_worktree/web/frontend/src/components/Settings.js b/aiida_worktree/web/frontend/src/components/Settings.js new file mode 100644 index 00000000..b1be66b2 --- /dev/null +++ b/aiida_worktree/web/frontend/src/components/Settings.js @@ -0,0 +1,12 @@ +import React from 'react'; + +function Settings() { + return ( +
+

Settings

+ {/* Add your settings content here */} +
+ ); +} + +export default Settings; diff --git a/aiida_worktree/web/frontend/src/components/WorkTree.js b/aiida_worktree/web/frontend/src/components/WorkTree.js new file mode 100644 index 00000000..f09f0dbd --- /dev/null +++ b/aiida_worktree/web/frontend/src/components/WorkTree.js @@ -0,0 +1,71 @@ +import React, { useState, useEffect } from 'react'; +import ReactPaginate from 'react-paginate'; +import { Link } from 'react-router-dom'; // Import Link + + +function WorkTree() { + const [data, setData] = useState([]); + const [currentPage, setCurrentPage] = useState(0); // `react-paginate` uses zero-based indexing + const [itemsPerPage] = useState(10); + + useEffect(() => { + // Replace with your actual endpoint + fetch('http://localhost:8000/worktree-data') + .then(response => response.json()) + .then(data => setData(data)) + .catch(error => console.error('Error fetching data: ', error)); + }, []); + + const indexOfLastItem = (currentPage + 1) * itemsPerPage; + const indexOfFirstItem = indexOfLastItem - itemsPerPage; + const currentItems = data.slice(indexOfFirstItem, indexOfLastItem); + + const handlePageClick = (event) => { + setCurrentPage(event.selected); + }; + + return ( +
+

WorkTree

+ + + + + + + + + + + {currentItems.map(item => ( + + + + + + + ))} + +
PKCreatedProcess LabelState
+ {item.pk} + {item.ctime}{item.process_label}{item.state}
+ +
+ ); +} + +export default WorkTree; diff --git a/aiida_worktree/web/frontend/src/components/WorkTreeDetail.js b/aiida_worktree/web/frontend/src/components/WorkTreeDetail.js new file mode 100644 index 00000000..8511a557 --- /dev/null +++ b/aiida_worktree/web/frontend/src/components/WorkTreeDetail.js @@ -0,0 +1,31 @@ +import React, { useState, useEffect } from 'react'; +import { useParams } from 'react-router-dom'; + +function WorkTreeDetail() { + let { pk } = useParams(); + const [worktreeData, setWorktreeData] = useState(null); + + useEffect(() => { + fetch(`http://localhost:8000/worktree/${pk}`) + .then(response => response.json()) + .then(data => setWorktreeData(data)) + .catch(error => console.error('Error fetching data:', error)); + }, [pk]); // useEffect will re-run if pk changes + + return ( +
+

WorkTree Detail - PK: {pk}

+ {/* Render worktreeData here */} + {worktreeData && ( +
+ {/* Display the data. For example: */} +

State: {worktreeData.state}

+

Create Time: {worktreeData.ctime}

+ {/* Render more data as needed */} +
+ )} +
+ ); +} + +export default WorkTreeDetail; diff --git a/aiida_worktree/web/frontend/src/components/WorkTreeGraph.tsx b/aiida_worktree/web/frontend/src/components/WorkTreeGraph.tsx new file mode 100644 index 00000000..cc000d96 --- /dev/null +++ b/aiida_worktree/web/frontend/src/components/WorkTreeGraph.tsx @@ -0,0 +1,45 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { useParams } from 'react-router-dom'; +import { useRete } from 'rete-react-plugin'; +import logo from '../logo.svg'; +import '../App.css'; +import '../rete.css'; +import { createEditor } from '../rete/default'; + +function WorkTreeGraph() { + let { pk } = useParams(); + const [worktreeData, setWorktreeData] = useState(null); + const reteContainerRef = useRef(null); + + useEffect(() => { + fetch(`http://localhost:8000/worktree/${pk}`) + .then(response => response.json()) + .then(data => setWorktreeData(data)) + .catch(error => console.error('Error fetching data:', error)); + }, [pk]); + + useEffect(() => { + if (worktreeData && reteContainerRef.current) { + createEditor(reteContainerRef.current, worktreeData); + } + }, [worktreeData]); // Run this effect when worktreeData changes + + return ( +
+
+ logo + + Learn AiiDA-WorkTree + +
+
+
+ ); +} + +export default WorkTreeGraph; diff --git a/aiida_worktree/web/frontend/src/logo.svg b/aiida_worktree/web/frontend/src/logo.svg index 71694760..f93712d7 100644 --- a/aiida_worktree/web/frontend/src/logo.svg +++ b/aiida_worktree/web/frontend/src/logo.svg @@ -1 +1,6 @@ - + + + + + + diff --git a/aiida_worktree/web/frontend/src/rete/default.ts b/aiida_worktree/web/frontend/src/rete/default.ts index ed5c7239..c31c1dd4 100644 --- a/aiida_worktree/web/frontend/src/rete/default.ts +++ b/aiida_worktree/web/frontend/src/rete/default.ts @@ -1,159 +1,99 @@ -import { ClassicPreset as Classic, GetSchemes, NodeEditor } from 'rete'; - -import { Area2D, AreaExtensions, AreaPlugin } from 'rete-area-plugin'; - +import { createRoot } from "react-dom/client"; +import { NodeEditor, GetSchemes, ClassicPreset } from "rete"; +import { AreaPlugin, AreaExtensions } from "rete-area-plugin"; import { - ReactPlugin, - ReactArea2D, - Presets as ReactPresets, -} from 'rete-react-plugin'; -import { createRoot } from 'react-dom/client'; - + ConnectionPlugin, + Presets as ConnectionPresets +} from "rete-connection-plugin"; +import { ReactPlugin, Presets, ReactArea2D } from "rete-react-plugin"; import { AutoArrangePlugin, Presets as ArrangePresets, -} from 'rete-auto-arrange-plugin'; -import { ReadonlyPlugin } from 'rete-readonly-plugin'; -import { - ContextMenuPlugin, - ContextMenuExtra, - Presets as ContextMenuPresets, -} from 'rete-context-menu-plugin'; -import { MinimapExtra, MinimapPlugin } from 'rete-minimap-plugin'; -import { - ReroutePlugin, - RerouteExtra, - RerouteExtensions, -} from 'rete-connection-reroute-plugin'; - -type Node = NumberNode | AddNode; -type Conn = - | Connection - | Connection - | Connection; -type Schemes = GetSchemes; - -class Connection extends Classic.Connection< - A, - B -> {} - -class NumberNode extends Classic.Node { + ArrangeAppliers +} from "rete-auto-arrange-plugin"; + +class Node extends ClassicPreset.Node { width = 180; height = 120; +} +class Connection extends ClassicPreset.Connection {} - constructor(initial: number, change?: (value: number) => void) { - super('Number'); +type Schemes = GetSchemes>; +type AreaExtra = ReactArea2D; - this.addOutput('value', new Classic.Output(socket, 'Number')); - this.addControl( - 'value', - new Classic.InputControl('number', { initial, change }) - ); - } -} +function createNode(label: string, socket: ClassicPreset.Socket) { + const node = new Node(label); -class AddNode extends Classic.Node { - width = 180; - height = 195; - - constructor() { - super('Add'); - - this.addInput('a', new Classic.Input(socket, 'A')); - this.addInput('b', new Classic.Input(socket, 'B')); - this.addOutput('value', new Classic.Output(socket, 'Number')); - this.addControl( - 'result', - new Classic.InputControl('number', { initial: 0, readonly: true }) - ); - } -} + node.addInput("port", new ClassicPreset.Input(socket)); + node.addOutput("port", new ClassicPreset.Output(socket)); -type AreaExtra = - | Area2D - | ReactArea2D - | ContextMenuExtra - | MinimapExtra - | RerouteExtra; + return node; +} -const socket = new Classic.Socket('socket'); +export async function createEditor(container: HTMLElement, worktreeData: any) { + const socket = new ClassicPreset.Socket("socket"); -export async function createEditor(container: HTMLElement) { const editor = new NodeEditor(); const area = new AreaPlugin(container); + const connection = new ConnectionPlugin(); + const render = new ReactPlugin({ createRoot }); + const arrange = new AutoArrangePlugin(); - const reactRender = new ReactPlugin({ createRoot }); - - const readonly = new ReadonlyPlugin(); - const contextMenu = new ContextMenuPlugin({ - items: ContextMenuPresets.classic.setup([ - ['Number', () => new NumberNode(1)], - ['Add', () => new AddNode()], - ]), + AreaExtensions.selectableNodes(area, AreaExtensions.selector(), { + accumulating: AreaExtensions.accumulateOnCtrl() }); - const minimap = new MinimapPlugin(); - const reroutePlugin = new ReroutePlugin(); - editor.use(readonly.root); - editor.use(area); - area.use(readonly.area); - area.use(reactRender); - - area.use(contextMenu); - area.use(minimap); - reactRender.use(reroutePlugin); - - reactRender.addPreset(ReactPresets.classic.setup()); - reactRender.addPreset(ReactPresets.contextMenu.setup()); - reactRender.addPreset(ReactPresets.minimap.setup()); - reactRender.addPreset( - ReactPresets.reroute.setup({ - contextMenu(id) { - reroutePlugin.remove(id); - }, - translate(id, dx, dy) { - reroutePlugin.translate(id, dx, dy); - }, - pointerdown(id) { - reroutePlugin.unselect(id); - reroutePlugin.select(id); - }, - }) - ); - - const a = new NumberNode(1); - const b = new NumberNode(1); - const add = new AddNode(); - - await editor.addNode(a); - await editor.addNode(b); - await editor.addNode(add); + render.addPreset(Presets.classic.setup()); - await editor.addConnection(new Connection(a, 'value', add, 'a')); - await editor.addConnection(new Connection(b, 'value', add, 'b')); + connection.addPreset(ConnectionPresets.classic.setup()); - const arrange = new AutoArrangePlugin(); + const applier = new ArrangeAppliers.TransitionApplier({ + duration: 500, + timingFunction: (t) => t, + async onTick() { + await AreaExtensions.zoomAt(area, editor.getNodes()); + } + }); arrange.addPreset(ArrangePresets.classic.setup()); + editor.use(area); + area.use(connection); + area.use(render); area.use(arrange); - await arrange.layout(); + AreaExtensions.simpleNodesOrder(area); - AreaExtensions.zoomAt(area, editor.getNodes()); + const a = createNode("A", socket); + const b = createNode("B", socket); + const c = createNode("C", socket); + const d = createNode("D", socket); + const e = createNode("E", socket); + const f = createNode("F", socket); - AreaExtensions.simpleNodesOrder(area); + await editor.addNode(a); + await editor.addNode(b); + await editor.addNode(c); + await editor.addNode(d); + await editor.addNode(e); + await editor.addNode(f); - const selector = AreaExtensions.selector(); - const accumulating = AreaExtensions.accumulateOnCtrl(); + await editor.addConnection(new Connection(a, "port", b, "port")); + await editor.addConnection(new Connection(b, "port", c, "port")); + await editor.addConnection(new Connection(a, "port", d, "port")); + await editor.addConnection(new Connection(d, "port", e, "port")); + await editor.addConnection(new Connection(e, "port", f, "port")); + await editor.addConnection(new Connection(c, "port", e, "port")); - AreaExtensions.selectableNodes(area, selector, { accumulating }); - RerouteExtensions.selectablePins(reroutePlugin, selector, accumulating); + await arrange.layout({ applier }); - readonly.enable(); + AreaExtensions.zoomAt(area, editor.getNodes()); return { - destroy: () => area.destroy(), + layout: async (animate: boolean) => { + await arrange.layout({ applier: animate ? applier : undefined }); + AreaExtensions.zoomAt(area, editor.getNodes()); + }, + destroy: () => area.destroy() }; }