From c8a34221d570110c7706b22905159128de899da9 Mon Sep 17 00:00:00 2001 From: Maximilian Giller <m.giller@tu-bs.de> Date: Thu, 6 Jan 2022 12:48:37 +0100 Subject: [PATCH] Connected node interface --- editor/editor.php | 3 +- editor/js/tools/collecttool.js | 7 ++ editor/js/tools/menus/collectmenu.js | 182 +++++++++++++++++++++++++++ 3 files changed, 191 insertions(+), 1 deletion(-) diff --git a/editor/editor.php b/editor/editor.php index 689cbd9..59d5475 100644 --- a/editor/editor.php +++ b/editor/editor.php @@ -32,7 +32,6 @@ <label for="nodes-type">* Type</label> </br> <select id="nodes-type" name="nodes-type" class="bottom-space"> - <option value=""></option> <option value="Vorlesung">Vorlesung</option> <option value="Algorithmus">Algorithmus</option> <option value="Definition">Definition</option> @@ -51,6 +50,8 @@ </br> <textarea id="nodes-references" name="nodes-references" class="bottom-space"></textarea> </br> + <button id="delete-selected-nodes">Delete all selected nodes</button> + <br/> </div> <h3>Collected items</h3> diff --git a/editor/js/tools/collecttool.js b/editor/js/tools/collecttool.js index 0d257bd..2f8775e 100644 --- a/editor/js/tools/collecttool.js +++ b/editor/js/tools/collecttool.js @@ -19,6 +19,12 @@ export default class CollectTool extends Tool { } } + onMenuChange(key, value) { + if (key === COLLECTION_KEY) { + graph.changeDetails(value); + } + } + onBoxSelect(left, bottom, top, right) { // Filter out selected nodes const hitNodes = []; @@ -80,6 +86,7 @@ export default class CollectTool extends Tool { onBackgroundClick() { state.clearSelectedItems(); + this.menu.value(COLLECTION_KEY, state.selectedItems); } onMenuChange(key, value) { diff --git a/editor/js/tools/menus/collectmenu.js b/editor/js/tools/menus/collectmenu.js index df6ddaf..85a683d 100644 --- a/editor/js/tools/menus/collectmenu.js +++ b/editor/js/tools/menus/collectmenu.js @@ -3,6 +3,7 @@ import { Graph } from "../../graph"; import { CONTEXT } from "../../state"; import { state } from "../../editor"; import ToolMenu from "./toolmenu"; +import { PLUGIN_PATH } from "../../../../config"; export const COLLECTION_KEY = "collection"; @@ -15,10 +16,51 @@ const HIDDEN_CLASS = "hidden"; const DOM_LIST_ITEM = "li"; +const NODES_IMG_PREVIEW = "#nodes-image-preview"; +const NODES_DETAIL_IMG_PREVIEW = "#nodes-detail-image-preview"; +const NODES_IMG_ID = "#nodes-image"; +const NODES_DETAIL_IMG_ID = "#nodes-detail-image"; +const NODES_DESC_ID = "#nodes-description"; +const NODES_TYPE_ID = "#nodes-type"; +const NODES_REF_ID = "#nodes-references"; +const NODES_VIDEO_ID = "#nodes-video"; +const NODES_MENU = [ + NODES_IMG_ID, + NODES_DESC_ID, + NODES_TYPE_ID, + NODES_REF_ID, + NODES_VIDEO_ID, + NODES_DETAIL_IMG_ID, +]; + +const IMAGE_MENU = [NODES_IMG_ID, NODES_DETAIL_IMG_ID]; +const IMAGE_FIELDS = [ + { + uri: NODES_IMG_ID, + preview: NODES_IMG_PREVIEW, + }, + { + uri: NODES_DETAIL_IMG_ID, + preview: NODES_DETAIL_IMG_PREVIEW, + }, +]; + +const MENU = [...NODES_MENU]; + +const ERROR_IMG_PATH = PLUGIN_PATH + "editor/images/onerror.png"; + export class CollectMenu extends ToolMenu { constructor() { super(); this.context = undefined; + this.map = [ + { menu: NODES_IMG_ID, property: Graph.NODE_IMAGE }, + { menu: NODES_DESC_ID, property: Graph.NODE_DESCRIPTION }, + { menu: NODES_TYPE_ID, property: Graph.NODE_TYPE }, + { menu: NODES_REF_ID, property: Graph.NODE_REFERENCES }, + { menu: NODES_VIDEO_ID, property: Graph.NODE_VIDEO }, + { menu: NODES_DETAIL_IMG_ID, property: Graph.NODE_DETAIL_IMAGE }, + ]; } hookClearButton() { @@ -31,6 +73,7 @@ export class CollectMenu extends ToolMenu { onMenuShow(initial) { if (initial) { this.hookClearButton(); + this.hookNodesInterface(); } } @@ -40,6 +83,7 @@ export class CollectMenu extends ToolMenu { // Set corresponding context this.setContext(state.itemsContext); + this.clearNodeInterface(); } } @@ -62,6 +106,9 @@ export class CollectMenu extends ToolMenu { items.forEach((i) => listCont.append(itemRenderer(i))); } + + // a LOT of duplicate code down here with selectmenu.js, should be improved + setContext(context) { if (context === this.context) { return; // Only do something if it changes @@ -92,4 +139,139 @@ export class CollectMenu extends ToolMenu { return undefined; } } + + clearNodeInterface() { + NODES_MENU.forEach((menu) => { + this.find(menu).val(undefined); + }); + this.updateImagePreviews(); + } + + updateImagePreviews() { + IMAGE_FIELDS.forEach((imageField) => { + var uri = this.find(imageField.uri).val(); + this.setImagePreview(uri, imageField.preview); + }); + } + + getFullImageSource(uri) { + if (uri === "") { + // Show default empty image + return ""; + } else if (uri.includes("/")) { + // Is absolute URL + return uri; + } else { + // Is file name + return Graph.IMAGE_SRC + uri; + } + } + + setImagePreview(uri, previewId) { + var previewImage = this.find(previewId); + previewImage.attr("src", this.getFullImageSource(uri)); + } + + 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; + } + + updateValue(menu, newValue) { + // Interface only available for nodes, so check before trying to work on data + if (this.context !== CONTEXT.node) { + return; + } + + var propertyKey = this.toProperty(menu); + var formatedValue = this.formatValue(propertyKey, newValue); + + // Update all nodes with the new value + this.values[COLLECTION_KEY].forEach((node) => { + node[propertyKey] = formatedValue; + }); + + // Notify tool + this.tool.onMenuChange(COLLECTION_KEY, this.values[COLLECTION_KEY]); + } + + formatValue(propertyKey, rawValue) { + var formattedValue = rawValue; + + if (propertyKey === Graph.NODE_REFERENCES) { + // Explode to list of url-lines + formattedValue = rawValue + .split("\n") // Every line is it's own url + .filter((url) => url); // Remove empty entries + } + + return formattedValue; + } + + hookNodesInterface() { + MENU.forEach((menu) => { + if (IMAGE_MENU.includes(menu)) { + return; + } + + // Subscribes to change event for each menu element + this.find(menu).on("change", (e) => { + this.updateValue(menu, e.target.value); + }); + }); + + // Special hooks for image, to update the shown image with every change + IMAGE_FIELDS.forEach((imageField) => { + // In case image can't be loaded, show message + this.find(imageField.preview).on("error", (e) => { + var img = this.find(e.target); + + // Is source even set? + if (img.attr("src") === undefined || img.attr("src") === "") { + return; + } + + // Show error message + this.setImagePreview(ERROR_IMG_PATH, imageField.preview); + // Maybe graph image should also be updated, but we might not want to overwrite previously saved images. + }); + + // Test images before updating them + this.find(imageField.uri).on("change", (e) => { + var imageSource = e.target.value; + + // If source is empty, always apply it + if (imageSource === undefined || imageSource === "") { + this.updateValue(imageField.uri, imageSource); + this.setImagePreview(imageSource, imageField.preview); + return; + } + + // Try loading the image and only apply it on success + var img = new Image(); + img.addEventListener("load", () => { + this.updateValue(imageField.uri, imageSource); + this.setImagePreview(imageSource, imageField.preview); + }); + img.addEventListener("error", () => { + this.setImagePreview(ERROR_IMG_PATH, imageField.preview); + }); + + img.src = this.getFullImageSource(imageSource); + }); + }); + } } -- GitLab