409 lines
14 KiB
HTML
409 lines
14 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<body>
|
|
<script src="chrome://__addonRef__/content/lib/js/go.js"></script>
|
|
<style>
|
|
html,
|
|
body,
|
|
div {
|
|
height: 100%;
|
|
width: 100%;
|
|
padding: 0;
|
|
margin: 0;
|
|
}
|
|
#canvas {
|
|
position: absolute;
|
|
height: 100%;
|
|
width: 100%;
|
|
}
|
|
</style>
|
|
<div id="allSampleContent" class="p-4 w-full">
|
|
<script id="code">
|
|
var Diagram;
|
|
function init() {
|
|
window.addEventListener("message", handler, false);
|
|
|
|
// Since 2.2 you can also author concise templates with method chaining instead of GraphObject.make
|
|
// For details, see https://gojs.net/latest/intro/buildingObjects.html
|
|
const $ = go.GraphObject.make;
|
|
|
|
Diagram = $(go.Diagram, "DiagramDiv", {
|
|
"commandHandler.copiesTree": true,
|
|
"commandHandler.copiesParentKey": true,
|
|
"commandHandler.deletesTree": true,
|
|
"draggingTool.dragsTree": true,
|
|
"undoManager.isEnabled": true,
|
|
initialAutoScale: go.Diagram.UniformToFill,
|
|
layout: $(go.TreeLayout, {
|
|
comparer: go.LayoutVertex.smartComparer,
|
|
}),
|
|
});
|
|
|
|
// a node consists of some text with a line shape underneath
|
|
Diagram.nodeTemplate = $(
|
|
go.Node,
|
|
"Vertical",
|
|
{ selectionObjectName: "TEXT" },
|
|
$(
|
|
go.TextBlock,
|
|
{
|
|
name: "TEXT",
|
|
minSize: new go.Size(30, 15),
|
|
editable: true,
|
|
textEdited: editNode,
|
|
},
|
|
// remember not only the text string but the scale and the font in the node data
|
|
new go.Binding("text", "text").makeTwoWay(),
|
|
new go.Binding("scale", "scale").makeTwoWay(),
|
|
new go.Binding("font", "font").makeTwoWay(),
|
|
new go.Binding("stroke", "stroke").makeTwoWay()
|
|
),
|
|
$(
|
|
go.Shape,
|
|
"LineH",
|
|
{
|
|
stretch: go.GraphObject.Horizontal,
|
|
strokeWidth: 3,
|
|
height: 3,
|
|
// this line shape is the port -- what links connect with
|
|
portId: "",
|
|
fromSpot: go.Spot.LeftRightSides,
|
|
toSpot: go.Spot.LeftRightSides,
|
|
},
|
|
new go.Binding("stroke", "brush"),
|
|
// make sure links come in from the proper direction and go out appropriately
|
|
new go.Binding("fromSpot", "dir", (d) => spotConverter(d, true)),
|
|
new go.Binding("toSpot", "dir", (d) => spotConverter(d, false))
|
|
),
|
|
// remember the locations of each node in the node data
|
|
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(
|
|
go.Point.stringify
|
|
),
|
|
// make sure text "grows" in the desired direction
|
|
new go.Binding("locationSpot", "dir", (d) =>
|
|
spotConverter(d, false)
|
|
)
|
|
);
|
|
|
|
// selected nodes show a button for adding children
|
|
Diagram.nodeTemplate.selectionAdornmentTemplate = $(
|
|
go.Adornment,
|
|
"Spot",
|
|
$(
|
|
go.Panel,
|
|
"Auto",
|
|
// this Adornment has a rectangular blue Shape around the selected node
|
|
$(go.Shape, { fill: null, stroke: "dodgerblue", strokeWidth: 3 }),
|
|
$(go.Placeholder, { margin: new go.Margin(4, 4, 0, 4) })
|
|
),
|
|
// and this Adornment has a Button to the right of the selected node
|
|
$(
|
|
"Button",
|
|
{
|
|
alignment: go.Spot.Right,
|
|
alignmentFocus: go.Spot.Left,
|
|
click: jumpNode, // define click behavior for this Button in the Adornment
|
|
},
|
|
$(
|
|
go.TextBlock,
|
|
"↗️", // the Button content
|
|
{ font: "bold 8pt sans-serif" }
|
|
)
|
|
)
|
|
);
|
|
|
|
// a link is just a Bezier-curved line of the same color as the node to which it is connected
|
|
Diagram.linkTemplate = $(
|
|
go.Link,
|
|
{
|
|
curve: go.Link.Bezier,
|
|
fromShortLength: -2,
|
|
toShortLength: -2,
|
|
selectable: false,
|
|
},
|
|
$(
|
|
go.Shape,
|
|
{ strokeWidth: 3 },
|
|
new go.Binding("stroke", "toNode", (n) => {
|
|
if (n.data.brush) return n.data.brush;
|
|
return "black";
|
|
}).ofObject()
|
|
)
|
|
);
|
|
|
|
Diagram.addDiagramListener("SelectionMoved", (e) => {
|
|
var rootX = Diagram.findNodeForKey(0).location.x;
|
|
Diagram.selection.each((node) => {
|
|
if (node.data.parent !== 0) return; // Only consider nodes connected to the root
|
|
var nodeX = node.location.x;
|
|
if (rootX < nodeX && node.data.dir !== "right") {
|
|
updateNodeDirection(node, "right");
|
|
} else if (rootX > nodeX && node.data.dir !== "left") {
|
|
updateNodeDirection(node, "left");
|
|
}
|
|
layoutTree(node);
|
|
});
|
|
});
|
|
|
|
// read in the predefined graph using the JSON format data held in the "mySavedModel" textarea
|
|
window.parent.postMessage({ type: "ready" }, "*");
|
|
getData();
|
|
}
|
|
|
|
function spotConverter(dir, from) {
|
|
if (dir === "left") {
|
|
return from ? go.Spot.Left : go.Spot.Right;
|
|
} else {
|
|
return from ? go.Spot.Right : go.Spot.Left;
|
|
}
|
|
}
|
|
|
|
function changeTextSize(obj, factor) {
|
|
var adorn = obj.part;
|
|
adorn.diagram.startTransaction("Change Text Size");
|
|
var node = adorn.adornedPart;
|
|
var tb = node.findObject("TEXT");
|
|
tb.scale *= factor;
|
|
adorn.diagram.commitTransaction("Change Text Size");
|
|
}
|
|
|
|
function toggleTextWeight(obj) {
|
|
var adorn = obj.part;
|
|
adorn.diagram.startTransaction("Change Text Weight");
|
|
var node = adorn.adornedPart;
|
|
var tb = node.findObject("TEXT");
|
|
// assume "bold" is at the start of the font specifier
|
|
var idx = tb.font.indexOf("bold");
|
|
if (idx < 0) {
|
|
tb.font = "bold " + tb.font;
|
|
} else {
|
|
tb.font = tb.font.slice(idx + 5);
|
|
}
|
|
adorn.diagram.commitTransaction("Change Text Weight");
|
|
}
|
|
|
|
function updateNodeDirection(node, dir) {
|
|
Diagram.model.setDataProperty(node.data, "dir", dir);
|
|
// recursively update the direction of the child nodes
|
|
var chl = node.findTreeChildrenNodes(); // gives us an iterator of the child nodes related to this particular node
|
|
while (chl.next()) {
|
|
updateNodeDirection(chl.value, dir);
|
|
}
|
|
}
|
|
|
|
function layoutTree(node) {
|
|
if (node.data.key === 0) {
|
|
// adding to the root?
|
|
layoutAll(); // lay out everything
|
|
} else {
|
|
// otherwise lay out only the subtree starting at this parent node
|
|
var parts = node.findTreeParts();
|
|
layoutAngle(parts, node.data.dir === "left" ? 180 : 0);
|
|
}
|
|
}
|
|
|
|
function layoutAngle(parts, angle) {
|
|
var layout = go.GraphObject.make(go.TreeLayout, {
|
|
angle: angle,
|
|
arrangement: go.TreeLayout.ArrangementFixedRoots,
|
|
nodeSpacing: 5,
|
|
layerSpacing: 20,
|
|
setsPortSpot: false, // don't set port spots since we're managing them with our spotConverter function
|
|
setsChildPortSpot: false,
|
|
});
|
|
layout.doLayout(parts);
|
|
}
|
|
|
|
function layoutAll() {
|
|
var root = Diagram.findNodeForKey(0);
|
|
if (root === null) return;
|
|
Diagram.startTransaction("Layout");
|
|
// split the nodes and links into two collections
|
|
var rightward = new go.Set(/*go.Part*/);
|
|
var leftward = new go.Set(/*go.Part*/);
|
|
root.findLinksConnected().each((link) => {
|
|
var child = link.toNode;
|
|
if (child.data.dir === "left") {
|
|
leftward.add(root); // the root node is in both collections
|
|
leftward.add(link);
|
|
leftward.addAll(child.findTreeParts());
|
|
} else {
|
|
rightward.add(root); // the root node is in both collections
|
|
rightward.add(link);
|
|
rightward.addAll(child.findTreeParts());
|
|
}
|
|
});
|
|
// do one layout and then the other without moving the shared root node
|
|
layoutAngle(rightward, 0);
|
|
layoutAngle(leftward, 180);
|
|
Diagram.commitTransaction("Layout");
|
|
}
|
|
|
|
function getData() {
|
|
window.parent.postMessage({ type: "getMindMapData" }, "*");
|
|
}
|
|
|
|
function setData(nodes) {
|
|
const data = {
|
|
class: "go.TreeModel",
|
|
nodeDataArray: [{ key: 999, text: "📄", parent: undefined }],
|
|
};
|
|
const colors = [];
|
|
const wrapText = (text) => {
|
|
let wrappedText = "";
|
|
for (let i = 0; i < text.length / 50; i++) {
|
|
wrappedText += text.slice(50 * i, 50 * i + 50);
|
|
wrappedText += "\n";
|
|
}
|
|
return wrappedText;
|
|
};
|
|
for (const node of nodes) {
|
|
const parent =
|
|
node.parent.model.id === -1 ? 999 : node.parent.model.id;
|
|
data.nodeDataArray.push({
|
|
key: node.model.id,
|
|
text: `${node.model.level === 7 ? "🔗" : ""}${wrapText(
|
|
node.model.name
|
|
)}`,
|
|
stroke: "-moz-DialogText",
|
|
parent: parent,
|
|
lineIndex: node.model.lineIndex,
|
|
level: node.model.level,
|
|
noteLink: node.model.level === 7 ? node.model.link : "",
|
|
brush: go.Brush.randomColor(),
|
|
});
|
|
}
|
|
Diagram.model = go.Model.fromJson(data);
|
|
}
|
|
|
|
function jumpNode(e, obj) {
|
|
var adorn = obj.part;
|
|
var oldnode = adorn.adornedPart;
|
|
var olddata = oldnode.data;
|
|
if (olddata.noteLink) {
|
|
window.parent.postMessage(
|
|
{ type: "openNote", link: olddata.noteLink, id: olddata.key },
|
|
"*"
|
|
);
|
|
} else {
|
|
window.parent.postMessage(
|
|
{
|
|
type: "jumpNode",
|
|
lineIndex: olddata.lineIndex,
|
|
id: olddata.key,
|
|
workspaceType: window.workspaceType || "tab",
|
|
},
|
|
"*"
|
|
);
|
|
}
|
|
}
|
|
|
|
function editNode(textBlock, previousText, currentText) {
|
|
const adorn = textBlock.part;
|
|
const data = adorn.data;
|
|
if (data.level === 7) {
|
|
alert("Link cannot be edited in mind map");
|
|
return false;
|
|
}
|
|
window.parent.postMessage(
|
|
{
|
|
type: "editNode",
|
|
lineIndex: data.lineIndex,
|
|
text: data.text,
|
|
workspaceType: window.workspaceType || "tab",
|
|
},
|
|
"*"
|
|
);
|
|
}
|
|
|
|
function handler(e) {
|
|
console.log(e);
|
|
switch (e.data.type) {
|
|
case "setMindMapData":
|
|
setData(e.data.nodes);
|
|
window.workspaceType = e.data.workspaceType;
|
|
break;
|
|
case "saveImage":
|
|
const imgString = Diagram.makeImageData({
|
|
scale: 1,
|
|
});
|
|
window.parent.postMessage(
|
|
{
|
|
type: "saveImageReturn",
|
|
image: imgString,
|
|
},
|
|
"*"
|
|
);
|
|
case "saveSVG":
|
|
const svgElement = Diagram.makeSvg({
|
|
scale: 1,
|
|
});
|
|
window.parent.postMessage(
|
|
{
|
|
type: "saveSVGReturn",
|
|
image: svgElement.outerHTML,
|
|
},
|
|
"*"
|
|
);
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
window.addEventListener("DOMContentLoaded", () => {
|
|
try {
|
|
init();
|
|
} catch (e) {
|
|
window.parent.postMessage({ type: "error", event: e }, "*");
|
|
}
|
|
});
|
|
|
|
var resizeTime = new Date().getTime();
|
|
window.addEventListener("resize", (e) => {
|
|
const canvas = document.getElementsByTagName("canvas")[0];
|
|
const div = document.getElementById("DiagramDiv");
|
|
canvas.setAttribute("height", div.getAttribute("height"));
|
|
canvas.setAttribute("width", div.getAttribute("width"));
|
|
const currentResize = new Date().getTime();
|
|
resizeTime = currentResize;
|
|
// Delay update
|
|
setTimeout(() => {
|
|
if (resizeTime === currentResize) {
|
|
getData();
|
|
}
|
|
}, 500);
|
|
});
|
|
</script>
|
|
|
|
<div id="sample">
|
|
<div
|
|
id="DiagramDiv"
|
|
style="
|
|
width: 100%;
|
|
position: relative;
|
|
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
|
|
cursor: auto;
|
|
font: 13px sans-serif;
|
|
overflow: hidden;
|
|
"
|
|
>
|
|
<canvas
|
|
tabindex="0"
|
|
style="
|
|
position: absolute;
|
|
top: 0px;
|
|
left: 0px;
|
|
z-index: 2;
|
|
user-select: none;
|
|
touch-action: none;
|
|
cursor: auto;
|
|
"
|
|
>This text is displayed if your browser does not support the Canvas
|
|
HTML element.</canvas
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|