-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
For the moment, it's not possible to render the widget on the docs page directly. This PR adds a to_html method so that we can export the workgraph to an HTML file and then load the HTML file into jupyter-notebook; in this case, the GUI will rendered on the docs page. We convert the react code to vanilla js so that we can merge all codes into one HTML file.
- Loading branch information
1 parent
df22cf7
commit e232caf
Showing
29 changed files
with
6,867 additions
and
1,281 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -71,6 +71,7 @@ instance/ | |
|
||
# Sphinx documentation | ||
docs/_build/ | ||
docs/source/**/*.txt | ||
|
||
# PyBuilder | ||
target/ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,244 @@ | ||
html_template = """ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>Rete.js with React in Vanilla JS</title> | ||
<!-- Import React, ReactDOM, and Babel from CDN --> | ||
<script src="https://unpkg.com/react@18.2.0/umd/react.development.js" crossorigin></script> | ||
<script src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.development.js" crossorigin></script> | ||
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> | ||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-is/18.2.0/umd/react-is.production.min.js"></script> | ||
<script src="https://cdn.jsdelivr.net/npm/styled-components@5.3.6/dist/styled-components.min.js"></script> | ||
<script src="https://unpkg.com/elkjs@0.8.2/lib/elk.bundled.js"></script> | ||
<!-- Import Rete.js and its plugins from CDN --> | ||
<script src="https://cdn.jsdelivr.net/npm/rete@2.0.3/rete.min.js"></script> | ||
<script src="https://cdn.jsdelivr.net/npm/rete-area-plugin@2.0.3/rete-area-plugin.min.js"></script> | ||
<script src="https://cdn.jsdelivr.net/npm/rete-connection-plugin@2.0.2/rete-connection-plugin.min.js"></script> | ||
<script src="https://cdn.jsdelivr.net/npm/rete-render-utils@2.0.2/rete-render-utils.min.js"></script> | ||
<script src="https://cdn.jsdelivr.net/npm/rete-react-plugin@2.0.5/rete-react-plugin.min.js"></script> | ||
<script src="https://cdn.jsdelivr.net/npm/rete-auto-arrange-plugin@2.0.1/rete-auto-arrange-plugin.min.js"></script> | ||
<script src="https://cdn.jsdelivr.net/npm/rete-minimap-plugin@2.0.1/rete-minimap-plugin.min.js"></script> | ||
<style> | ||
.App { | ||
font-family: sans-serif; | ||
background: rgb(200, 190, 190); | ||
} | ||
.rete { | ||
position: relative; | ||
font-size: 1rem; | ||
margin: 1em; | ||
border-radius: 1em; | ||
text-align: left; | ||
} | ||
body { | ||
overflow: hidden; | ||
margin: 0; | ||
padding: 0; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<div id="root"></div> | ||
<script type="text/babel"> | ||
const { useState, useRef, useEffect } = React; | ||
const { createRoot } = ReactDOM; | ||
const { NodeEditor, ClassicPreset } = Rete; | ||
const { AreaPlugin, AreaExtensions } = ReteAreaPlugin; | ||
const { ConnectionPlugin, Presets: ConnectionPresets } = ReteConnectionPlugin; | ||
const { ReactPlugin, Presets } = ReteReactPlugin; | ||
const { AutoArrangePlugin, Presets: ArrangePresets, ArrangeAppliers} = ReteAutoArrangePlugin; | ||
const { MinimapExtra, MinimapPlugin } = ReteMinimapPlugin; | ||
const { RenderUtils } = ReteRenderUtils; | ||
const styled = window.styled; | ||
const workgraphData = __WORKGRAPH_DATA__ | ||
// Define Schemes to use in vanilla JS | ||
const Schemes = { | ||
Node: ClassicPreset.Node, | ||
Connection: ClassicPreset.Connection | ||
}; | ||
class Node extends ClassicPreset.Node { | ||
width = 180; | ||
height = 100; | ||
} | ||
class Connection extends ClassicPreset.Connection {} | ||
function createDynamicNode(nodeData) { | ||
const node = new Node(nodeData.label); | ||
// resize the node based on the max length of the input/output names | ||
let maxSocketNameLength = 0; | ||
nodeData.inputs.forEach((input) => { | ||
let socket = new ClassicPreset.Socket(input.name); | ||
if (!node.inputs.hasOwnProperty(input.name)) { | ||
node.addInput(input.name, new ClassicPreset.Input(socket, input.name)); | ||
maxSocketNameLength = Math.max(maxSocketNameLength, input.name.length); | ||
} | ||
}); | ||
nodeData.outputs.forEach((output) => { | ||
let socket = new ClassicPreset.Socket(output.name); | ||
if (!node.outputs.hasOwnProperty(output.name)) { | ||
node.addOutput(output.name, new ClassicPreset.Output(socket, output.name)); | ||
maxSocketNameLength = Math.max(maxSocketNameLength, output.name.length); | ||
} | ||
}); | ||
node.height = Math.max(140, node.height + (nodeData.inputs.length + nodeData.outputs.length) * 35) | ||
node.width += maxSocketNameLength * 5; | ||
return node; | ||
} | ||
async function addNode(editor, area, nodeData) { | ||
console.log("Adding node", nodeData); | ||
const node = createDynamicNode(nodeData); | ||
await editor.addNode(node); | ||
editor.nodeMap[nodeData.label] = node; // Assuming each nodeData has a unique ID | ||
await area.translate(node.id, { x: nodeData.position[0], y: nodeData.position[1] }); | ||
} | ||
async function addLink(editor, area, layout, linkData) { | ||
const fromNode = editor.nodeMap[linkData.from_node]; | ||
const toNode = editor.nodeMap[linkData.to_node]; | ||
console.log("fromNode", fromNode, "toNode", toNode); | ||
let socket; | ||
if (fromNode && toNode) { | ||
socket = new ClassicPreset.Socket(linkData.from_socket); | ||
if (!fromNode.outputs.hasOwnProperty(linkData.from_socket)) { | ||
fromNode.addOutput(linkData.from_socket, new ClassicPreset.Output(socket, linkData.from_socket)); | ||
fromNode.height += 25; // Increase height of node for each output | ||
area.update('node', fromNode.id); | ||
} | ||
socket = new ClassicPreset.Socket(linkData.to_socket); | ||
if (!toNode.inputs.hasOwnProperty(linkData.to_socket)) { | ||
toNode.addInput(linkData.to_socket, new ClassicPreset.Input(socket, linkData.to_socket)); | ||
toNode.height += 25; // Increase height of node for each input | ||
area.update('node', toNode.id); | ||
} | ||
await editor.addConnection(new Connection(fromNode, linkData.from_socket, toNode, linkData.to_socket)); | ||
// await layout(true); | ||
} | ||
} | ||
async function loadJSON(editor, area, layout, workgraphData) { | ||
for (const nodeId in workgraphData.nodes) { | ||
const nodeData = workgraphData.nodes[nodeId]; | ||
await addNode(editor, area, nodeData); | ||
} | ||
// Adding connections based on workgraphData | ||
workgraphData.links.forEach(async (link) => { // Specify the type of link here | ||
await addLink(editor, area, layout, link); | ||
}); | ||
} | ||
async function createEditor(container) { | ||
const socket = new ClassicPreset.Socket("socket"); | ||
const editor = new NodeEditor(Schemes); | ||
const area = new AreaPlugin(container); | ||
const connection = new ConnectionPlugin(); | ||
const render = new ReactPlugin({ createRoot }); | ||
const arrange = new AutoArrangePlugin(); | ||
const minimap = new MinimapPlugin({ | ||
boundViewport: true | ||
}); | ||
AreaExtensions.selectableNodes(area, AreaExtensions.selector(), { | ||
accumulating: AreaExtensions.accumulateOnCtrl(), | ||
}); | ||
render.addPreset(Presets.classic.setup()); | ||
render.addPreset(Presets.minimap.setup({ size: 200 })); | ||
connection.addPreset(ConnectionPresets.classic.setup()); | ||
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); | ||
area.use(minimap); | ||
AreaExtensions.simpleNodesOrder(area); | ||
async function layout(animate) { | ||
await arrange.layout({ applier: animate ? applier : undefined }); | ||
AreaExtensions.zoomAt(area, editor.getNodes()); | ||
} | ||
// Adding nodes based on workgraphData | ||
const nodeMap = {}; // To keep track of created nodes for linking | ||
editor.nodeMap = nodeMap; | ||
return { | ||
editor: editor, | ||
area: area, | ||
layout: layout, | ||
destroy: () => area.destroy() | ||
}; | ||
} | ||
function App() { | ||
const [editor, setEditor] = useState(null); | ||
const containerRef = useRef(null); | ||
useEffect(() => { | ||
if (containerRef.current && !editor) { | ||
createEditor(containerRef.current).then((editor) => { | ||
setEditor(editor); | ||
loadJSON(editor.editor, editor.area, editor.layout, workgraphData).then(() => { | ||
// aplly layout twice to ensure all nodes are arranged | ||
editor?.layout(false).then(() => editor?.layout(true)); | ||
}); | ||
}); | ||
} | ||
return () => { | ||
if (editor) { | ||
editor.destroy(); | ||
} | ||
}; | ||
}, [containerRef, editor]); | ||
return ( | ||
<div className="App"> | ||
<div> | ||
<button onClick={() => editor?.layout(true)}>Arrange</button> | ||
</div> | ||
<div ref={containerRef} className="rete" style={{ height: "100vh", width: "100%" }}></div> | ||
</div> | ||
); | ||
} | ||
const rootElement = document.getElementById("root"); | ||
const root = createRoot(rootElement); | ||
root.render( | ||
<App /> | ||
); | ||
</script> | ||
</body> | ||
</html> | ||
""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.