From a920bb748cec09bf971931a2173a0d771b0c8027 Mon Sep 17 00:00:00 2001 From: Max <m.giller.dev@gmail.com> Date: Thu, 30 Sep 2021 23:44:23 +0200 Subject: [PATCH] Implemented select node menu and made a bunch of smaller changes --- editor/css/editor.css | 31 +++++++ editor/editor.php | 37 +++++++- editor/js/graph.js | 61 ++++++++++++- editor/js/state.js | 5 +- editor/js/tools/collecttool.js | 6 -- editor/js/tools/connecttool.js | 3 +- editor/js/tools/menus/connectmenu.js | 6 +- editor/js/tools/menus/selectmenu.js | 131 +++++++++++++++++++++++++++ editor/js/tools/menus/toolmenu.js | 18 +++- editor/js/tools/selecttool.js | 14 ++- editor/js/tools/tool.js | 20 +++- 11 files changed, 303 insertions(+), 29 deletions(-) create mode 100644 editor/js/tools/menus/selectmenu.js diff --git a/editor/css/editor.css b/editor/css/editor.css index e5b2048..7bb9454 100644 --- a/editor/css/editor.css +++ b/editor/css/editor.css @@ -17,3 +17,34 @@ div#ks-editor *.selected { div#ks-editor *.hidden { display: none; } + +div#ks-editor input[type="text"] { + border-top-style: none; + border-left-style: none; + border-right-style: none; + letter-spacing: normal; +} + +div#ks-editor .medium-width { + max-width: 20rem; +} + +div#ks-editor input#node-name { + font-size: large; + font-weight: bolder; +} + +div#ks-editor .bottom-space { + margin-bottom: 1rem; +} + +div#ks-editor textarea { + max-width: 100%; + letter-spacing: normal; +} + +div#ks-editor img#node-image-preview { + max-width: 5rem; + height: auto; + border-radius: 50%; +} diff --git a/editor/editor.php b/editor/editor.php index acdbb04..5c8877b 100644 --- a/editor/editor.php +++ b/editor/editor.php @@ -1,4 +1,5 @@ -<div id="ks-editor"> <!--The id "ks-editor" indicates, that the javascript associated with this should automatically be executed--> +<div id="ks-editor"> + <!--The id "ks-editor" indicates, that the javascript associated with this should automatically be executed--> <h1>Interface</h1> <div id="2d-graph"></div> <section id="toolbar"></section> @@ -11,6 +12,38 @@ <option value="Definition">Definition</option> </select> </div> + <div id="select-menu" class=""> + <p id="nothing-selected">Nothing selected</p> + <div id="node-selected" class="hidden"> + <label for="node-name" hidden>Name</label> + <input type="text" id="node-name" name="node-name" placeholder="Enter name" class="bottom-space"></input> + + <label for="node-image">Image</label> + <img id="node-image-preview" src="https://via.placeholder.com/150" /> + <input type="text" id="node-image" name="node-image" placeholder="Enter file name" class="medium-width bottom-space" /> + + <label for="node-description">Description</label> + <textarea id="node-description" name="node-description" class="bottom-space"></textarea> + + <label for="node-type">Type</label> + <select id="node-type" name="node-type" class="bottom-space medium-width"> + <option value="Vorlesung">Vorlesung</option> + <option value="Algorithmus">Algorithmus</option> + <option value="Definition">Definition</option> + <option value="Beispiel">Beispiel</option> + <option value="Ãœbung">Ãœbung</option> + <option value="Kapitel">Kapitel</option> + </select> + + <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> + <textarea id="node-videos" name="node-videos"></textarea> + </div> + <div id="link-selected" class="hidden"> + </div> + </div> </section> <section> <h3 id="selected-item">Nothing selected</h3> @@ -30,4 +63,4 @@ <ul id="selected-items"></ul> <button id="clear-collection">Clear</button> </section> -</div> +</div> \ No newline at end of file diff --git a/editor/js/graph.js b/editor/js/graph.js index fd8e932..d6eb1d9 100644 --- a/editor/js/graph.js +++ b/editor/js/graph.js @@ -3,9 +3,11 @@ import { PLUGIN_PATH, COLOR_PALETTE } from "../../config"; export const NODE_LABEL = "name"; export const NODE_ID = "id"; -export const NODE_GROUP = "group"; +export const NODE_TYPE = "type"; export const NODE_DESCRIPTION = "description"; export const NODE_IMAGE = "image"; +export const NODE_REFERENCES = "references"; +export const NODE_VIDEOS = "videos"; export const LINK_SOURCE = "source"; export const LINK_TARGET = "target"; @@ -19,7 +21,15 @@ export const IMAGE_SIZE = 12; export const IMAGE_SRC = PLUGIN_PATH + "datasets/images/"; export const LINK_PARAMS = [LINK_TYPE]; -export const NODE_PARAMS = [NODE_ID, NODE_LABEL, NODE_IMAGE, NODE_DESCRIPTION]; +export const NODE_PARAMS = [ + NODE_ID, + NODE_LABEL, + NODE_IMAGE, + NODE_DESCRIPTION, + NODE_REFERENCES, + NODE_VIDEOS, + NODE_TYPE, +]; export const LINK_SIM_PARAMS = ["index"]; export const NODE_SIM_PARAMS = ["index", "x", "y", "vx", "vy", "fx", "fy"]; // Based on https://github.com/d3/d3-force#simulation_nodes @@ -154,6 +164,53 @@ export class Graph extends ManagedData { return false; } + changeDetails(selectionDetails) { + if (selectionDetails.node === true) { + this.changeNodeDetails(selectionDetails[NODE_ID].newDetails); + } else if (selectionDetails.link === true) { + this.changeLinkDetails( + selectionDetails[LINK_SOURCE][NODE_ID], + selectionDetails[LINK_TARGET][NODE_ID], + selectionDetails + ); + } + } + + changeNodeDetails(nodeId, newDetails) { + var nodes = this.data[GRAPH_NODES]; + for (var i = 0; i < nodes.length; i++) { + // Is relevant node? + if (nodes[i][NODE_ID] !== nodeId) { + continue; // No + } + + // Changed details + nodes[i] = Object.assign(nodes[i], newDetails); + + // All done + return; + } + } + + changeLinkDetails(sourceId, targetId, newDetails) { + var links = this.data[GRAPH_LINKS]; + for (var i = 0; i < links.length; i++) { + // Is relevant link? + if ( + links[i][LINK_SOURCE][NODE_ID] !== sourceId || + links[i][LINK_TARGET][NODE_ID] !== targetId + ) { + continue; // No + } + + // Changed details + links[i] = Object.assign(links[i], newDetails); + + // All done + return; + } + } + connectNodes(sourceId, targetIds) { targetIds.forEach((targetId) => { if ( diff --git a/editor/js/state.js b/editor/js/state.js index f160dea..1c4a8f1 100644 --- a/editor/js/state.js +++ b/editor/js/state.js @@ -24,6 +24,7 @@ export const CONTEXT = { node: "node", link: "link", mixed: "mixed", + nothing: "nothing" }; export class State extends Tool { @@ -38,7 +39,7 @@ export class State extends Tool { // Shared variables this.selectedItem = undefined; this.selectedItems = new Set(); - this.itemsContext = undefined; + this.itemsContext = CONTEXT.nothing; this.keyStates = {}; } @@ -78,7 +79,7 @@ export class State extends Tool { clearSelectedItems() { this.selectedItems.clear(); - this.itemsContext = undefined; + this.itemsContext = CONTEXT.nothing; this.display.setSelectedItems(this.selectedItems, this.itemsContext); } diff --git a/editor/js/tools/collecttool.js b/editor/js/tools/collecttool.js index 472163a..b4b3ce8 100644 --- a/editor/js/tools/collecttool.js +++ b/editor/js/tools/collecttool.js @@ -32,10 +32,4 @@ export default class CollectTool extends Tool { state.addSelectedItem(link); } } - - onKeyUp(key) { - if (key.keyCode === 17) { - state.setTool(state.previousTool); - } - } } diff --git a/editor/js/tools/connecttool.js b/editor/js/tools/connecttool.js index 1253886..d0e8efc 100644 --- a/editor/js/tools/connecttool.js +++ b/editor/js/tools/connecttool.js @@ -7,9 +7,8 @@ const KEEP_SOURCE_KEY_ID = 17; export default class ConnectTool extends Tool { constructor(key) { - super("Connect two nodes", "connect", key); + super("Connect two nodes", "connect", key, new ConnectMenu()); this.keepSource = false; - this.menu = new ConnectMenu(this); } onNodeClick(node) { diff --git a/editor/js/tools/menus/connectmenu.js b/editor/js/tools/menus/connectmenu.js index d901fff..4f148b0 100644 --- a/editor/js/tools/menus/connectmenu.js +++ b/editor/js/tools/menus/connectmenu.js @@ -3,13 +3,13 @@ import ToolMenu from "./toolmenu"; const LINK_TYPE_SEL = "#link-type"; export default class ConnectMenu extends ToolMenu { - constructor(tool) { - super(tool); + constructor() { + super(); } beforeGet(key) { if (key === "link-type") { - return this.getChildren(LINK_TYPE_SEL).val(); + return this.find(LINK_TYPE_SEL).val(); } } } \ No newline at end of file diff --git a/editor/js/tools/menus/selectmenu.js b/editor/js/tools/menus/selectmenu.js new file mode 100644 index 0000000..80dba6c --- /dev/null +++ b/editor/js/tools/menus/selectmenu.js @@ -0,0 +1,131 @@ +import * as Graph from "../../graph"; +import { CONTEXT } from "../../state"; +import ToolMenu from "./toolmenu"; + +const HIDDEN_CLASS = "hidden"; + +export const SELECTION_KEY = "selection"; + +const CONTEXT_NOTHING = "#nothing-selected"; +const CONTEXT_NODE = "#node-selected"; +const CONTEXT_LINK = "#link-selected"; + +const NODE_NAME_ID = "#node-name"; +const NODE_IMG_ID = "#node-image"; +const NODE_DESC_ID = "#node-description"; +const NODE_TYPE_ID = "#node-type"; +const NODE_REF_ID = "#node-references"; +const NODE_VIDEOS_ID = "#node-videos"; +const NODE_MENU = [ + NODE_NAME_ID, + NODE_IMG_ID, + NODE_DESC_ID, + NODE_TYPE_ID, + NODE_REF_ID, + NODE_VIDEOS_ID, +]; + +export class SelectMenu extends ToolMenu { + constructor() { + super(); + this.context = undefined; + this.map = [ + { menu: NODE_NAME_ID, property: Graph.NODE_LABEL }, + { menu: NODE_IMG_ID, property: Graph.NODE_IMAGE }, + { menu: NODE_DESC_ID, property: Graph.NODE_DESCRIPTION }, + { menu: NODE_TYPE_ID, property: Graph.NODE_TYPE }, + { menu: NODE_REF_ID, property: Graph.NODE_REFERENCES }, + { menu: NODE_VIDEOS_ID, property: Graph.NODE_VIDEOS }, + ]; + this.hooked = false; // Can only hook menu events once, but have to do it later, when they are loaded + } + + hookMenu() { + NODE_MENU.forEach((menu) => { + console.log(this.find(menu)); + // Subscribes to change event for each menu element + this.find(menu).on("change", (e) => { + var newValue = e.target.value; + console.log(newValue); + + // Modify stored selection + this.values[SELECTION_KEY][this.toProperty(menu)] = newValue; + + // Notify tool + this.tool.onMenuChange( + SELECTION_KEY, + this.values[SELECTION_KEY] + ); + }); + }); + } + + toProperty(menu) { + for (var i = 0; i < this.map.length; i++) { + if (this.map[i].menu === menu) { + return this.map[i].property; + } + } + return undefined; + } + + toMenu(property) { + for (var i = 0; i < this.map.length; i++) { + if (this.map[i].property === property) { + return this.map[i].menu; + } + } + return undefined; + } + + setContext(context) { + if (context === this.context) { + return; // Only do something if it changes + } + + // Disable previous context + this.getDomToContext(this.context).addClass(HIDDEN_CLASS); + + // Store and activate new context + this.context = context; + this.getDomToContext(this.context).removeClass(HIDDEN_CLASS); + } + + getDomToContext(context) { + var id = CONTEXT_NOTHING; + + if (context === CONTEXT.link) { + id = CONTEXT_LINK; + } else if (context === CONTEXT.node) { + id = CONTEXT_NODE; + } + + return this.find(id); + } + + afterSet(key, value) { + if (this.hooked !== true) { + this.hookMenu(); + this.hooked = true; + } + + if (key !== SELECTION_KEY) { + return; + } + + if (value.node) { + this.fillNode(value); + this.setContext(CONTEXT.node); + } else if (value.link) { + this.setContext(CONTEXT.link); + } else { + this.setContext(CONTEXT.nothing); + } + } + + fillNode(node) { + NODE_MENU.forEach((menu) => { + this.find(menu).val(node[this.toProperty(menu)]); + }); + } +} diff --git a/editor/js/tools/menus/toolmenu.js b/editor/js/tools/menus/toolmenu.js index 97d7f15..73c2ac7 100644 --- a/editor/js/tools/menus/toolmenu.js +++ b/editor/js/tools/menus/toolmenu.js @@ -1,13 +1,16 @@ import jQuery from "jquery"; export default class ToolMenu { - constructor(tool) { - this.tool = tool; - this.menuId = this.tool.getKey() + "-menu"; + constructor() { this.warnings = false; this.values = {}; } + loadTool(tool) { + this.tool = tool; + this.menuId = this.tool.getKey() + "-menu"; + } + show() { this.getMenu().removeClass("hidden"); } @@ -28,6 +31,11 @@ export default class ToolMenu { } } + notifyTool(key, value) { + this.values[key] = value; + this.tool.onMenuChange(key, value); + } + value(key, newValue) { // If key not defined // (be it by giving no arguments, or giving it an undefined value) @@ -59,8 +67,8 @@ export default class ToolMenu { } } - getChildren(selector) { - return this.getMenu().children(selector); + find(selector) { + return this.getMenu().find(selector); } getMenu() { diff --git a/editor/js/tools/selecttool.js b/editor/js/tools/selecttool.js index 921263e..4af6892 100644 --- a/editor/js/tools/selecttool.js +++ b/editor/js/tools/selecttool.js @@ -1,27 +1,31 @@ import Tool from "./tool"; -import { state } from "../editor"; +import { graph, state } from "../editor"; import { TOOLS } from "../state"; +import * as SelectMenu from "./menus/selectmenu"; export default class SelectTool extends Tool { constructor(key) { - super("Select", "select", key); + super("Select", "select", key, new SelectMenu.SelectMenu()); } onNodeClick(node) { state.setSelectedItem(node); + this.menu.value(SelectMenu.SELECTION_KEY, node); } onLinkClick(link) { state.setSelectedItem(link); + this.menu.value(SelectMenu.SELECTION_KEY, link); } onBackgroundClick(event, positions) { state.setSelectedItem(undefined); + this.menu.value(SelectMenu.SELECTION_KEY, {}); } - onKeyDown(key) { - if (key.keyCode === 17) { - state.setTool(TOOLS.collect); + onMenuChange(key, value) { + if (key === SelectMenu.SELECTION_KEY) { + graph.changeDetails(value); } } } diff --git a/editor/js/tools/tool.js b/editor/js/tools/tool.js index e2470d8..0c7b3ad 100644 --- a/editor/js/tools/tool.js +++ b/editor/js/tools/tool.js @@ -1,10 +1,14 @@ export default class Tool { - constructor(name, icon, key) { + constructor(name, icon, key, menu) { this.name = name; this.icon = icon; this.key = key; this.warnings = false; - this.menu = undefined; + this.menu = menu; + + if (this.menu !== undefined) { + this.menu.loadTool(this); + } } getName() { @@ -19,6 +23,12 @@ export default class Tool { return this.icon; } + onMenuChange(key, value) { + if (this.warnings) { + console.warn('Method "onMenuChange" not implemented.'); + } + } + activateTool() { if (this.menu !== undefined) { this.menu.show(); @@ -36,9 +46,15 @@ export default class Tool { } onToolActivate() { + if (this.warnings) { + console.warn('Method "onToolActivate" not implemented.'); + } } onToolDeactivate(nextTool) { + if (this.warnings) { + console.warn('Method "onToolDeactivate" not implemented.'); + } } onNodeClick(node) { -- GitLab