diff --git a/editor/css/editor.css b/editor/css/editor.css index ea32a7da121b4c0a4c3c191a96f17a4562220278..7bb94545375f3010e6f63b535329ef15e1de226a 100644 --- a/editor/css/editor.css +++ b/editor/css/editor.css @@ -13,3 +13,38 @@ div#ks-editor section > * { div#ks-editor *.selected { background-color: lightblue; } + +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 96866b5b68ddb17b7c521ec049b969d853b2aa76..8c901ce5c67d2b210d941d9371dbb567a8d95e78 100644 --- a/editor/editor.php +++ b/editor/editor.php @@ -1,23 +1,65 @@ -<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> - <section> - <h3 id="selected-item">Nothing selected</h3> - <ul id="selected-params"></ul> - <section> - <h4>Sources</h4> - <ul id="selected-sources"></ul> - </section> - <section> - <h4>Targets</h4> - <ul id="selected-targets"></ul> - </section> + <section id="tool-menu"> + <div id="connect-menu" class="hidden"> + <label for="link-type">Link Type</label> + <select id="link-type" name="link-type"> + <option value="Vorlesung">Lecture</option> + <option value="Algorithmus">Algorithm</option> + <option value="Definition">Definition</option> + </select> + </div> + <div id="collect-menu" class="hidden"> + <h3>Collected items</h3> + <button id="clear-collection">Clear</button> + <ul id="selected-items"></ul> + </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"> + <h3 id="link-name" class="bottom-space">aud1 ↔ Sortieren</h3> + + <label for="link-type">Type</label> + <select id="link-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> + </div> + </div> </section> <button id="save" type="submit" class="primary">Save and publish</button> - <section> - <h3>Collected items</h3> - <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/display.js b/editor/js/display.js deleted file mode 100644 index da979d1db8b0ddac2292946bb25f4b6c267eee8d..0000000000000000000000000000000000000000 --- a/editor/js/display.js +++ /dev/null @@ -1,178 +0,0 @@ -import jQuery from "jquery"; -import { PLUGIN_PATH } from "../../config"; -import * as Graph from "./graph"; -import { graph, state } from "./editor"; - -const ID_TOOLBAR = "#toolbar"; -const ID_SELECTEDITEM = "#selected-item"; -const ID_SELECTED_PARAMS = "#selected-params"; -const ID_SELECTED_SOURCES = "#selected-sources"; -const ID_SELECTED_TARGETS = "#selected-targets"; -const ID_SELECTEDITEMS = "#selected-items"; - -const DOM_LIST_ITEM = "li"; - -const TOOL_ICON_SRC = PLUGIN_PATH + "editor/images/tools/"; -const TOOL_ICON_FORMAT = ".png"; -const TOOL_SELECTED_CLASS = "selected"; - -export default class Display { - constructor(tools) { - this.tools = Object.values(tools); - this.previousTool = undefined; - - this.renderToolbar(this.tools); - } - - setSelectedTool(tool) { - var selectedTool = jQuery(Display.getToolIdTag(tool)); - selectedTool.addClass(TOOL_SELECTED_CLASS); - - if (this.previousTool !== undefined) { - var previousTool = jQuery(Display.getToolIdTag(this.previousTool)); - previousTool.removeClass(TOOL_SELECTED_CLASS); - } - - this.previousTool = tool; - } - - renderToolbar(tools) { - this.fillDomList(ID_TOOLBAR, tools, this.toolRenderer); - - tools.forEach((tool) => { - this.toolClickEvent(tool); - }); - } - - static getToolIdTag(tool) { - return ID_TOOLBAR + "-" + tool.getKey(); - } - - static getToolId(tool) { - return Display.getToolIdTag(tool).substr(1); - } - - toolRenderer(tool) { - return ( - '<button id="' + - Display.getToolId(tool) + - '" title="' + - tool.getName() + - '"><img src="' + - TOOL_ICON_SRC + - tool.getIcon() + - TOOL_ICON_FORMAT + - '"></button>' - ); - } - - toolClickEvent(tool) { - jQuery("button" + Display.getToolIdTag(tool)).on( - "click", - "", - tool, - (e) => { - state.setTool(e.data); - } - ); - } - - setSelectedItem(item) { - jQuery(ID_SELECTEDITEM).html(Display.toStr(item)); - - var paramsDOM = jQuery(ID_SELECTED_PARAMS); - paramsDOM.empty(); - - var params = Graph.NODE_PARAMS; - if (item === undefined) { - params = []; - } else if (item.link) { - params = Graph.LINK_PARAMS; - } - - params.forEach((param) => { - paramsDOM.append( - "<" + - DOM_LIST_ITEM + - ">" + - param + - " <textarea>" + - (item[param] === undefined ? "" : item[param]) + - "</textarea></" + - DOM_LIST_ITEM + - ">" - ); - }); - - // Render Source and Target list - var sources = []; - var targets = []; - if (item !== undefined && item.node) { - var nodes = graph.data[Graph.GRAPH_NODES]; - for (var i = 0; i < nodes.length; i++) { - if ( - graph.existsLink( - nodes[i][Graph.NODE_ID], - item[Graph.NODE_ID] - ) - ) { - sources.push(nodes[i]); - } else if ( - graph.existsLink( - item[Graph.NODE_ID], - nodes[i][Graph.NODE_ID] - ) - ) { - targets.push(nodes[i]); - } - } - } else if (item !== undefined && item.link) { - sources.push(item[Graph.LINK_SOURCE]); - targets.push(item[Graph.LINK_TARGET]); - } - - this.fillDomList(ID_SELECTED_SOURCES, sources, this.graphItemRenderer); - this.fillDomList(ID_SELECTED_TARGETS, targets, this.graphItemRenderer); - } - - setSelectedItems(items, itemsContext) { - this.fillDomList(ID_SELECTEDITEMS, items, this.graphItemRenderer); - } - - graphItemRenderer(item) { - return ( - "<" + - DOM_LIST_ITEM + - ">" + - Display.toStr(item) + - "</" + - DOM_LIST_ITEM + - ">" - ); - } - - fillDomList(listId, items, itemRenderer) { - var listCont = jQuery(listId); - listCont.empty(); - - items.forEach((i) => listCont.append(itemRenderer(i))); - } - - static toStr(item) { - if (item === undefined) { - return "UNDEFINED"; - } - - if (item.node) { - return item[Graph.NODE_LABEL] + " [" + item[Graph.NODE_ID] + "]"; - } else if (item.link) { - return ( - Display.toStr(item[Graph.LINK_SOURCE]) + - " <-> " + - Display.toStr(item[Graph.LINK_TARGET]) - ); - } else { - return "UNDEFINED"; - } - } -} diff --git a/editor/js/graph.js b/editor/js/graph.js index 29badadc8014ef00fc9cd4667e52203f7e15c574..16aaef23718de17e48a05ff74df2cae09fce819a 100644 --- a/editor/js/graph.js +++ b/editor/js/graph.js @@ -1,11 +1,15 @@ import ManagedData from "./manageddata"; import { PLUGIN_PATH, COLOR_PALETTE } from "../../config"; +const LINK_NAME_CONNECTOR = " → "; + 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 +23,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 +166,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 ( @@ -163,21 +222,8 @@ export class Graph extends ManagedData { return; } - var link = {}; - - link[LINK_SOURCE] = sourceId; - link[LINK_TARGET] = targetId; - - this.data[GRAPH_LINKS].push(link); + this.addLink(sourceId, targetId); }); - - this.storeCurrentData( - "Created link connecting [" + - sourceId + - "] with [" + - targetIds.join() + - "]" - ); } getCleanData(data = undefined, simulationParameters = false) { @@ -350,4 +396,22 @@ export class Graph extends ManagedData { return newNode; } + + static toStr(item) { + if (item === undefined) { + return "UNDEFINED"; + } + + if (item.node) { + return item[Graph.NODE_LABEL]; + } else if (item.link) { + return ( + Graph.toStr(item[Graph.LINK_SOURCE]) + + LINK_NAME_CONNECTOR + + Graph.toStr(item[Graph.LINK_TARGET]) + ); + } else { + return "UNDEFINED"; + } + } } diff --git a/editor/js/state.js b/editor/js/state.js index d779f3dd07b89a439c9c0289176fc3990b5d19e5..cbf44c4cf38a0b1822215103f512744f78992e3d 100644 --- a/editor/js/state.js +++ b/editor/js/state.js @@ -7,7 +7,7 @@ import DeleteTool from "./tools/deletetool"; import AddNodeTool from "./tools/addnodetool"; import ConnectTool from "./tools/connecttool"; import { graph } from "./editor"; -import Display from "./display"; +import Toolbar from "./toolbar"; import * as Graph from "./graph"; export const TOOLS = { @@ -24,13 +24,14 @@ export const CONTEXT = { node: "node", link: "link", mixed: "mixed", + nothing: "nothing" }; export class State extends Tool { constructor() { super("State"); - this.display = new Display(TOOLS); + this.display = new Toolbar(TOOLS); this.tool = undefined; this.setTool(TOOLS.select); @@ -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 = {}; } @@ -49,7 +50,7 @@ export class State extends Tool { } if (this.tool !== undefined) { - this.tool.onToolDeactivate(tool); + this.tool.deactivateTool(tool); } this.previousTool = this.tool; @@ -57,29 +58,25 @@ export class State extends Tool { this.display.setSelectedTool(tool); if (this.tool !== undefined) { - this.tool.onToolActivate(); + this.tool.activateTool(); } } setSelectedItem(item) { this.selectedItem = item; - this.display.setSelectedItem(item); } addSelectedItem(item) { this.selectedItems.add(item); - this.display.setSelectedItems(this.selectedItems, this.itemsContext); } removeSelectedItem(item) { this.selectedItems.delete(item); - this.display.setSelectedItems(this.selectedItems, this.itemsContext); } clearSelectedItems() { this.selectedItems.clear(); - this.itemsContext = undefined; - this.display.setSelectedItems(this.selectedItems, this.itemsContext); + this.itemsContext = CONTEXT.nothing; } onNodeClick(node) { @@ -237,8 +234,6 @@ export class State extends Tool { redraw() { this.display.setSelectedTool(this.tool); - this.display.setSelectedItem(this.selectedItem); - this.display.setSelectedItems(this.selectedItems, this.itemsContext); } isLinkHighlighted(link) { diff --git a/editor/js/toolbar.js b/editor/js/toolbar.js new file mode 100644 index 0000000000000000000000000000000000000000..a4b93c25e70aab9386f1c69b7d5ea40d900003dc --- /dev/null +++ b/editor/js/toolbar.js @@ -0,0 +1,78 @@ +import jQuery from "jquery"; +import { PLUGIN_PATH } from "../../config"; +import { state } from "./editor"; + +const ID_TOOLBAR = "#toolbar"; + +const TOOL_ICON_SRC = PLUGIN_PATH + "editor/images/tools/"; +const TOOL_ICON_FORMAT = ".png"; +const TOOL_SELECTED_CLASS = "selected"; + +export default class Toolbar { + constructor(tools) { + this.tools = Object.values(tools); + this.previousTool = undefined; + + this.renderToolbar(this.tools); + } + + setSelectedTool(tool) { + var selectedTool = jQuery(Toolbar.getToolIdTag(tool)); + selectedTool.addClass(TOOL_SELECTED_CLASS); + + if (this.previousTool !== undefined) { + var previousTool = jQuery(Toolbar.getToolIdTag(this.previousTool)); + previousTool.removeClass(TOOL_SELECTED_CLASS); + } + + this.previousTool = tool; + } + + renderToolbar(tools) { + this.fillDomList(ID_TOOLBAR, tools, this.toolRenderer); + + tools.forEach((tool) => { + this.toolClickEvent(tool); + }); + } + + static getToolIdTag(tool) { + return ID_TOOLBAR + "-" + tool.getKey(); + } + + static getToolId(tool) { + return Toolbar.getToolIdTag(tool).substr(1); + } + + toolRenderer(tool) { + return ( + '<button id="' + + Toolbar.getToolId(tool) + + '" title="' + + tool.getName() + + '"><img src="' + + TOOL_ICON_SRC + + tool.getIcon() + + TOOL_ICON_FORMAT + + '"></button>' + ); + } + + toolClickEvent(tool) { + jQuery("button" + Toolbar.getToolIdTag(tool)).on( + "click", + "", + tool, + (e) => { + state.setTool(e.data); + } + ); + } + + fillDomList(listId, items, itemRenderer) { + var listCont = jQuery(listId); + listCont.empty(); + + items.forEach((i) => listCont.append(itemRenderer(i))); + } +} diff --git a/editor/js/tools/collecttool.js b/editor/js/tools/collecttool.js index 472163a61dafbdb1ba6f34ab6aeddde8bdb6dd5a..ffd3f71db598a33f7613e741fb93dfd8f0947daf 100644 --- a/editor/js/tools/collecttool.js +++ b/editor/js/tools/collecttool.js @@ -1,10 +1,11 @@ import Tool from "./tool"; import { state } from "../editor"; import { CONTEXT } from "../state"; +import { CollectMenu, COLLECTION_KEY } from "./menus/collectmenu"; export default class CollectTool extends Tool { constructor(key) { - super("Collect", "collect", key); + super("Collect", "collect", key, new CollectMenu()); } onNodeClick(node) { @@ -18,6 +19,8 @@ export default class CollectTool extends Tool { } else { state.addSelectedItem(node); } + + this.menu.value(COLLECTION_KEY, state.selectedItems); } onLinkClick(link) { @@ -31,11 +34,14 @@ export default class CollectTool extends Tool { } else { state.addSelectedItem(link); } + + this.menu.value(COLLECTION_KEY, state.selectedItems); } - onKeyUp(key) { - if (key.keyCode === 17) { - state.setTool(state.previousTool); + onMenuChange(key, value) { + if (key === COLLECTION_KEY && value === undefined) { + state.clearSelectedItems(); + this.menu.value(COLLECTION_KEY, []); } } } diff --git a/editor/js/tools/connecttool.js b/editor/js/tools/connecttool.js index 5fb9de144545ec2e6d2940a7833e832ad2884243..d0e8efc4cefea2a6db54541cd0b969ac4ac53250 100644 --- a/editor/js/tools/connecttool.js +++ b/editor/js/tools/connecttool.js @@ -1,12 +1,13 @@ import Tool from "./tool"; import { graph, state } from "../editor"; import * as Graph from "../graph"; +import ConnectMenu from "./menus/connectmenu"; 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; } @@ -21,9 +22,13 @@ export default class ConnectTool extends Tool { } // Add new link + var details = {}; + details[Graph.LINK_TYPE] = this.menu.value("link-type"); + var link = graph.addLink( state.selectedItem[Graph.NODE_ID], - node[Graph.NODE_ID] + node[Graph.NODE_ID], + details ); if (link === undefined) { diff --git a/editor/js/tools/menus/collectmenu.js b/editor/js/tools/menus/collectmenu.js new file mode 100644 index 0000000000000000000000000000000000000000..ca181963b1954e700b82932ab76c84d401942b73 --- /dev/null +++ b/editor/js/tools/menus/collectmenu.js @@ -0,0 +1,54 @@ +import jQuery from "jquery"; +import { Graph } from "../../graph"; +import ToolMenu from "./toolmenu"; + +export const COLLECTION_KEY = "collection"; + +const SELECTED_ITEMS_ID = "#selected-items"; +const CLEAR_BUTTON_ID = "#clear-collection"; + +const DOM_LIST_ITEM = "li"; + +export class CollectMenu extends ToolMenu { + constructor() { + super(); + this.hooked = false; + } + + hookClearButton() { + // On button press: Notify tool about clearing and empty list + jQuery(CLEAR_BUTTON_ID).on("click", (e) => { + this.notifyTool(COLLECTION_KEY, undefined); + }); + } + + afterSet(key, value) { + if (this.hooked === false) { + this.hookClearButton(); + this.hooked = true; + } + + if (key === COLLECTION_KEY) { + this.fillDomList(SELECTED_ITEMS_ID, value, this.itemListRenderer); + } + } + + itemListRenderer(item) { + return ( + "<" + + DOM_LIST_ITEM + + ">" + + Graph.toStr(item) + + "</" + + DOM_LIST_ITEM + + ">" + ); + } + + fillDomList(listId, items, itemRenderer) { + var listCont = this.find(listId); + listCont.empty(); + + items.forEach((i) => listCont.append(itemRenderer(i))); + } +} diff --git a/editor/js/tools/menus/connectmenu.js b/editor/js/tools/menus/connectmenu.js new file mode 100644 index 0000000000000000000000000000000000000000..4f148b0b8e815e1c1dad3212365b0f5578bfb780 --- /dev/null +++ b/editor/js/tools/menus/connectmenu.js @@ -0,0 +1,15 @@ +import ToolMenu from "./toolmenu"; + +const LINK_TYPE_SEL = "#link-type"; + +export default class ConnectMenu extends ToolMenu { + constructor() { + super(); + } + + beforeGet(key) { + if (key === "link-type") { + 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 0000000000000000000000000000000000000000..40602f1d8e402cd9265299bfd60c476f8491eb2f --- /dev/null +++ b/editor/js/tools/menus/selectmenu.js @@ -0,0 +1,147 @@ +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, +]; + +const LINK_NAME_ID = "#link-name"; +const LINK_TYPE_ID = "#link-type"; +const LINK_MENU = [LINK_TYPE_ID]; + +const MENU = [...NODE_MENU, ...LINK_MENU]; + +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 }, + { menu: LINK_TYPE_ID, property: Graph.LINK_TYPE }, + ]; + this.hooked = false; // Can only hook menu events once, but have to do it later, when they are loaded + } + + hookMenu() { + 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.fillLink(value); + this.setContext(CONTEXT.link); + } else { + this.setContext(CONTEXT.nothing); + } + } + + fillNode(node) { + NODE_MENU.forEach((menu) => { + this.find(menu).val(node[this.toProperty(menu)]); + }); + } + + fillLink(link) { + this.find(LINK_NAME_ID).text(Graph.Graph.toStr(link)); + + LINK_MENU.forEach((menu) => { + this.find(menu).val(link[this.toProperty(menu)]); + }); + } +} diff --git a/editor/js/tools/menus/toolmenu.js b/editor/js/tools/menus/toolmenu.js new file mode 100644 index 0000000000000000000000000000000000000000..73c2ac7a3073662ebc293bbe0aaa9530301ff90a --- /dev/null +++ b/editor/js/tools/menus/toolmenu.js @@ -0,0 +1,77 @@ +import jQuery from "jquery"; + +export default class ToolMenu { + constructor() { + this.warnings = false; + this.values = {}; + } + + loadTool(tool) { + this.tool = tool; + this.menuId = this.tool.getKey() + "-menu"; + } + + show() { + this.getMenu().removeClass("hidden"); + } + + hide() { + this.getMenu().addClass("hidden"); + } + + beforeGet(key) { + if (this.warnings) { + console.warn('Method "beforeGet" not implemented.'); + } + } + + afterSet(key, value) { + if (this.warnings) { + console.warn('Method "afterSet" not implemented.'); + } + } + + 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) + // Return all values as dict. + if (key === undefined) { + return this.values; + } + + // Is key valid? + // If not, create undefined entry + if (key in this.values == false) { + this.values[key] = undefined; + } + + // If value not defined, returned specified value. + if (newValue === undefined) { + newValue = this.beforeGet(key); + + if (newValue !== undefined) { + this.values[key] = newValue; + } + + return this.values[key]; + } + // If bot defined, store specified value. + else { + this.values[key] = newValue; + newValue = this.afterSet(key, newValue); + } + } + + find(selector) { + return this.getMenu().find(selector); + } + + getMenu() { + return jQuery("#tool-menu #" + this.menuId); + } +} diff --git a/editor/js/tools/selecttool.js b/editor/js/tools/selecttool.js index 921263e667f5530c35c5501645a0fe6345de31bf..4af6892e8b3996bafe8707b7cbff1b45f8eff27f 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 e3fade0276387f02f241a2fa77797eef2f48392e..0c7b3ad8fca720d14580e416fc1283a690790da7 100644 --- a/editor/js/tools/tool.js +++ b/editor/js/tools/tool.js @@ -1,9 +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 = menu; + + if (this.menu !== undefined) { + this.menu.loadTool(this); + } } getName() { @@ -18,6 +23,28 @@ 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(); + } + + this.onToolActivate(); + } + + deactivateTool(nextTool) { + this.onToolDeactivate(nextTool); + + if (this.menu !== undefined) { + this.menu.hide(); + } + } + onToolActivate() { if (this.warnings) { console.warn('Method "onToolActivate" not implemented.');