diff --git a/editor/css/editor.css b/editor/css/editor.css index 0a0b43ffec384b1419853df4a83af7f19caac78f..722b6ff086c0aa18039f71914619647307f8daa7 100644 --- a/editor/css/editor.css +++ b/editor/css/editor.css @@ -29,6 +29,10 @@ div#ks-editor .medium-width { max-width: 20rem; } +div#ks-editor .small-width { + max-width: 5rem; +} + div#ks-editor input#node-name { font-size: large; font-weight: bolder; diff --git a/editor/editor.php b/editor/editor.php index 1a3ca47b2476fa20737d77fc42aa578ae3ff1814..7127c9a35d91979fab20070c2da4e7e81dd7dcea 100644 --- a/editor/editor.php +++ b/editor/editor.php @@ -35,13 +35,30 @@ <label for="node-references">References</label> <small>One URL per line</small> <textarea id="node-references" name="node-references" class="bottom-space"></textarea> - <label for="node-videos">Videos</label> <small>One video URL per line</small> + <label>Videos</label> <small>One video URL per line</small> <textarea id="node-videos" name="node-videos"></textarea> </div> <div id="link-selected" class="hidden"> <h3 id="link-name"></h3> </div> </div> + <div id="settings-menu" class="hidden" checked> + <input type="checkbox" checked id="label-toggle" name="label-toggle" class="bottom-space"> + <label for="label-toggle">Show labels in graph</label> + </input> + + </br> + + <label>Simulate physics from beginning</label> + </br> + <button id="reanimate-button" name="reanimate-button" class="bottom-space">Re-simulate</button> + + </br> + + <label for="stop-physics-delay">Amount of time [in seconds] after which the physics simulation is stopped</label> + <input type="number" value="5" id="stop-physics-delay" name="stop-physics-delay" class="small-width"> + </input> + </div> </section> <button id="save" type="submit" class="primary">Save and publish</button> </div> \ No newline at end of file diff --git a/editor/images/tools/settings.png b/editor/images/tools/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..eaae7fdec7dc02e78b280b8d66a80490dd08bf30 Binary files /dev/null and b/editor/images/tools/settings.png differ diff --git a/editor/js/editor.js b/editor/js/editor.js index b0ecfed15f2b2172feb8d41c622e551bbfd0b375..e6a0dc0815b5ae5670b4603053513f0108cce2eb 100644 --- a/editor/js/editor.js +++ b/editor/js/editor.js @@ -27,11 +27,7 @@ window.onload = function () { graph = new Graph.Graph(graphConfig); load(); - // Deactivate physics after a short delay - setTimeout(() => { - graph.stopPhysics(); - graph.storeCurrentData("Physics stopped"); - }, Graph.STOP_PHYSICS_DELAY); + graph.restartSimulation(); }); }; @@ -74,4 +70,6 @@ function load() { graphObj.cooldownTicks(0); graphObj.graphData(data); }); + + graph.setRenderer(graphObj); } diff --git a/editor/js/graph.js b/editor/js/graph.js index 22ba7035c81bfed979cc43edeb8b2ee4d62385ee..d1949d2217b9cad4f0519d3256ab20531e1e9226 100644 --- a/editor/js/graph.js +++ b/editor/js/graph.js @@ -45,6 +45,37 @@ export class Graph extends ManagedData { this.calculateLinkTypes(); this.onChangeCallbacks = []; + this.physicsDelay = STOP_PHYSICS_DELAY; + this.physicsStopTimeoutId = undefined; + this.graphRenderer = undefined; + } + + setRenderer(graphRenderer) { + this.graphRenderer = graphRenderer; + } + + restartSimulation() { + if (this.physicsStopTimeoutId !== undefined) { + clearTimeout(this.physicsStopTimeoutId); + } + + if (this.graphRenderer !== undefined) { + this.data[GRAPH_NODES].forEach((n) => { + n.x /= 10; + n.y /= 10; + n.fx = undefined; + n.fy = undefined; + }); + + this.graphRenderer.d3ReheatSimulation(); + } + + // Deactivate physics after a short delay + this.physicsStopTimeoutId = setTimeout(() => { + this.stopPhysics(); + this.storeCurrentData("Physics stopped"); + this.physicsStopTimeoutId = undefined; + }, this.physicsDelay); } triggerOnChange() { diff --git a/editor/js/state.js b/editor/js/state.js index 2066357f58906911bf2e33ec7845b8061e9ff0cd..5c41d79b3a6e78b30fc7a16808277b25273489cd 100644 --- a/editor/js/state.js +++ b/editor/js/state.js @@ -6,6 +6,7 @@ import CollectTool from "./tools/collecttool"; import DeleteTool from "./tools/deletetool"; import AddNodeTool from "./tools/addnodetool"; import ConnectTool from "./tools/connecttool"; +import SettingsTool from "./tools/settingstool"; import { graph } from "./editor"; import Toolbar from "./toolbar"; import * as Graph from "./graph"; @@ -18,6 +19,7 @@ export const TOOLS = { delete: new DeleteTool("delete"), addnode: new AddNodeTool("addnode"), connect: new ConnectTool("connect"), + settings: new SettingsTool("settings"), }; export const CONTEXT = { @@ -40,6 +42,7 @@ export class State extends Tool { this.selectedItem = undefined; this.selectedItems = new Set(); this.itemsContext = CONTEXT.nothing; + this.labelVisible = true; this.keyStates = {}; } @@ -154,20 +157,22 @@ export class State extends Tool { } // Draw label - const label = node[Graph.NODE_LABEL]; - const fontSize = 11/globalScale; - ctx.font = `${fontSize}px Sans-Serif`; - const textWidth = ctx.measureText(label).width; - const bckgDimensions = [textWidth, fontSize].map(n => n + fontSize * 0.2); // some padding - - const nodeHeightOffset = Graph.IMAGE_SIZE / 3 + bckgDimensions[1]; - ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; - ctx.fillRect(node.x - bckgDimensions[0] / 2, node.y - bckgDimensions[1] / 2 + nodeHeightOffset, ...bckgDimensions); - - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.fillStyle = 'white'; - ctx.fillText(label, node.x, node.y + nodeHeightOffset); + if (this.labelVisible) { + const label = node[Graph.NODE_LABEL]; + const fontSize = 11/globalScale; + ctx.font = `${fontSize}px Sans-Serif`; + const textWidth = ctx.measureText(label).width; + const bckgDimensions = [textWidth, fontSize].map(n => n + fontSize * 0.2); // some padding + + const nodeHeightOffset = Graph.IMAGE_SIZE / 3 + bckgDimensions[1]; + ctx.fillStyle = 'rgba(0, 0, 0, 0.5)'; + ctx.fillRect(node.x - bckgDimensions[0] / 2, node.y - bckgDimensions[1] / 2 + nodeHeightOffset, ...bckgDimensions); + + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillStyle = 'white'; + ctx.fillText(label, node.x, node.y + nodeHeightOffset); + } // TODO: Render label as always visible } @@ -262,4 +267,8 @@ export class State extends Tool { graph.isLinkOnNode(link, this.selectedItem) ); } + + setLabelVisibility(visibility) { + this.labelVisible = visibility; + } } diff --git a/editor/js/tools/menus/settingsmenu.js b/editor/js/tools/menus/settingsmenu.js new file mode 100644 index 0000000000000000000000000000000000000000..bb785076940fb0b9834ce31534b112a4012f8159 --- /dev/null +++ b/editor/js/tools/menus/settingsmenu.js @@ -0,0 +1,32 @@ +import ToolMenu from "./toolmenu"; + +const LABEL_TOGGLE_ID = "#label-toggle"; +const RESIMULATE_BUTTON_ID = "#reanimate-button"; +const PHYSICS_DELAY_ID = "#stop-physics-delay"; + +export const PHYSICS_DELAY_KEY = "delay"; +export const RESIMULATE_KEY = "resimulate"; +export const LABELS_KEY = "labels"; + +export class SettingsMenu extends ToolMenu { + constructor() { + super(); + } + + onMenuShow(initial) { + if (initial === false) { + return; + } + + // Initial interface hooks + this.find(LABEL_TOGGLE_ID).on("change", (e) => { + this.notifyTool(LABELS_KEY, e.target.checked); + }); + this.find(PHYSICS_DELAY_ID).on("change", (e) => { + this.notifyTool(PHYSICS_DELAY_KEY, e.target.value); + }); + this.find(RESIMULATE_BUTTON_ID).on("click", () => { + this.notifyTool(RESIMULATE_KEY); + }); + } +} diff --git a/editor/js/tools/settingstool.js b/editor/js/tools/settingstool.js new file mode 100644 index 0000000000000000000000000000000000000000..0812a270d12f404165dd54ad86ef1284a5aaa343 --- /dev/null +++ b/editor/js/tools/settingstool.js @@ -0,0 +1,24 @@ +import { graph, state } from "../editor"; +import { + SettingsMenu, + LABELS_KEY, + RESIMULATE_KEY, + PHYSICS_DELAY_KEY, +} from "./menus/settingsmenu"; +import Tool from "./tool"; + +export default class SettingsTool extends Tool { + constructor(key) { + super("Settings", "settings", key, new SettingsMenu()); + } + + onMenuChange(key, value) { + if (key === LABELS_KEY) { + state.setLabelVisibility(value); + } else if (key === RESIMULATE_KEY) { + graph.restartSimulation(); + } else if (key === PHYSICS_DELAY_KEY) { + graph.physicsDelay = Number(value) * 1000; // Convert seconds to ms + } + } +}