diff --git a/editor/js/editor.js b/editor/js/editor.js index cc29bcbe90d4298b708671076a02be2fafc8a0aa..27b51c4ed28ce753f4acd3c51953b0ebab64b1d3 100644 --- a/editor/js/editor.js +++ b/editor/js/editor.js @@ -1,6 +1,6 @@ import { State } from "./state"; import * as Graph from "./graph"; -import { loadGraphJson } from "../../datasets/datasets"; +import { loadSpaceJson } from "../../datasets/datasets"; import ForceGraph from "force-graph"; import * as Interactions from "./interactions"; import { setSpace, SPACE } from "../../config"; @@ -30,11 +30,18 @@ export function loadSpace(spaceId) { } setSpace(spaceId); - return loadGraphJson(SPACE).then((graphConfig) => { - jquery("#ks-editor #header-space-title").text(" - " + graphConfig.name + " [" + graphConfig.id + "]"); + return loadSpaceJson(SPACE).then((response) => { + if (response.spaces.length <= 0) { + return; + } + const space = Object.values(response.spaces)[0]; + + jquery("#ks-editor #header-space-title").text( + " - " + space.name + " [" + space.space_id + "]" + ); state = new State(); - graph = new Graph.Graph(graphConfig); + graph = new Graph.Graph(space); load(); graph.restartSimulation(); @@ -57,10 +64,15 @@ function load() { .width(width) .graphData(graph.data) .nodeLabel(Graph.NODE_LABEL) + .nodeId(Graph.NODE_ID) + .linkSource(Graph.LINK_SOURCE) + .linkTarget(Graph.LINK_TARGET) .linkColor((link) => state.linkColor(link)) .nodeColor((node) => state.nodeColor(node)) .onNodeClick((node) => state.onNodeClick(node)) - .onNodeDragEnd((node, translate) => state.onNodeDragEnd(node, translate)) + .onNodeDragEnd((node, translate) => + state.onNodeDragEnd(node, translate) + ) .autoPauseRedraw(false) // keep redrawing after engine has stopped .linkWidth((link) => state.linkWidth(link)) .linkDirectionalParticles(state.linkDirectionalParticles()) diff --git a/editor/js/graph.js b/editor/js/graph.js index c3839268257b431ca705419ff3d2f28b82a1f583..13507054ad3d2e471be24de34d35bf2a0b978d82 100644 --- a/editor/js/graph.js +++ b/editor/js/graph.js @@ -1,38 +1,41 @@ import ManagedData from "./manageddata"; -import { PLUGIN_PATH, COLOR_PALETTE } from "../../config"; +import { PLUGIN_PATH } from "../../config"; const LINK_NAME_CONNECTOR = " → "; -export const NODE_LABEL = "name"; -export const NODE_ID = "id"; +export const NODE_LABEL = "title"; +export const NODE_ID = "node_id"; export const NODE_TYPE = "type"; export const NODE_DESCRIPTION = "description"; -export const NODE_IMAGE = "image"; -export const NODE_REFERENCES = "infoLinks"; -export const NODE_VIDEO = "video"; -export const NODE_DETAIL_IMAGE = "infoImage"; - -export const LINK_SOURCE = "source"; -export const LINK_TARGET = "target"; -export const LINK_TYPE = "type"; +export const NODE_ICON_IMG = "icon_url"; +export const NODE_REFERENCES = "references"; +export const NODE_VIDEO = "video_url"; +export const NODE_HEADER_IMG = "header_url"; + +export const LINK_SOURCE_OBJ = "source"; +export const LINK_TARGET_OBJ = "target"; +export const LINK_SOURCE = "source_node_id"; +export const LINK_TARGET = "target_node_id"; +export const LINK_ID = "link_id"; export const LINK_PARTICLE_COUNT = 4; -export const GRAPH_NODES = "nodes"; -export const GRAPH_LINKS = "links"; +export const TAG_EDITED = "updated"; +export const TAG_DELETED = "deleted"; +export const TAG_CREATED = "created"; export const IMAGE_SIZE = 12; export const IMAGE_SRC = PLUGIN_PATH + "datasets/images/"; -export const LINK_PARAMS = []; +export const LINK_PARAMS = [LINK_ID]; export const NODE_PARAMS = [ NODE_ID, NODE_LABEL, - NODE_IMAGE, + NODE_ICON_IMG, NODE_DESCRIPTION, NODE_REFERENCES, NODE_VIDEO, NODE_TYPE, - NODE_DETAIL_IMAGE, + NODE_HEADER_IMG, ]; 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 @@ -56,7 +59,6 @@ export class Graph extends ManagedData { clearTimeout(this.physicsStopTimeoutId); } - this.data = Graph.addIdentifiers(this.getCleanData(this.data, false)); this.triggerOnChange(); @@ -84,44 +86,30 @@ export class Graph extends ManagedData { return this.getCleanData(data, true); } - /** - * Based on the function from the 3d-graph code from @JoschaRode - */ + getNodeColor(node) { + return node.type.color; + } + calculateNodeTypes() { + // TODO: Collect all types from database const nodeClasses = []; - this.data[GRAPH_NODES].forEach((node) => + this.data.nodes.forEach((node) => nodeClasses.push(node[NODE_TYPE]) ); this.nodeTypes = [...new Set(nodeClasses)]; } - getNodeColor(node) { - return this.getTypeColor(node[NODE_TYPE]); - } - - getTypeColor(typeClass) { - var classIndex = this.nodeTypes.indexOf(typeClass); - - if (classIndex <= -1) { - return "black"; - } - - return COLOR_PALETTE[classIndex % COLOR_PALETTE.length]; - } - deleteNode(nodeId) { // Delete node from nodes - this.data[GRAPH_NODES] = this.data[GRAPH_NODES].filter( - (n) => n[NODE_ID] !== nodeId - ); + this.data.nodes = this.data.nodes.filter((n) => n[NODE_ID] !== nodeId); // Delete links with node - this.data[GRAPH_LINKS] = this.data[GRAPH_LINKS].filter( + this.data.links = this.data.links.filter( (l) => - l[LINK_SOURCE][NODE_ID] !== nodeId && - l[LINK_TARGET][NODE_ID] !== nodeId + l[LINK_SOURCE_OBJ][NODE_ID] !== nodeId && + l[LINK_TARGET_OBJ][NODE_ID] !== nodeId ); this.storeCurrentData("Deleted node with id [" + nodeId + "]"); @@ -143,22 +131,24 @@ export class Graph extends ManagedData { this.enableStoring(); } - this.storeCurrentData("Deleted nodes with ids [" + nodeIds.join(",") + "]"); + this.storeCurrentData( + "Deleted nodes with ids [" + nodeIds.join(",") + "]" + ); } stopPhysics() { - this.data[GRAPH_NODES].forEach((n) => { + this.data.nodes.forEach((n) => { n.fx = n.x; n.fy = n.y; }); } static addIdentifiers(data) { - data[GRAPH_NODES].forEach((n) => { + data.nodes.forEach((n) => { n.node = true; n.link = false; }); - data[GRAPH_LINKS].forEach((l) => { + data.links.forEach((l) => { l.node = false; l.link = true; }); @@ -168,10 +158,10 @@ export class Graph extends ManagedData { deleteLink(sourceId, targetId) { // Only keep links, of one of the nodes is different - this.data[GRAPH_LINKS] = this.data[GRAPH_LINKS].filter( + this.data.links = this.data.links.filter( (l) => - l[LINK_SOURCE][NODE_ID] !== sourceId || - l[LINK_TARGET][NODE_ID] !== targetId + l[LINK_SOURCE_OBJ][NODE_ID] !== sourceId || + l[LINK_TARGET_OBJ][NODE_ID] !== targetId ); this.storeCurrentData( @@ -180,6 +170,7 @@ export class Graph extends ManagedData { } isLinkOnNode(link, node) { + // Parameters valid? if (link === undefined || node === undefined) { return false; } @@ -188,20 +179,21 @@ export class Graph extends ManagedData { return false; } + // Is node on at least one end of the link? return ( - link[LINK_SOURCE][NODE_ID] === node[NODE_ID] || - link[LINK_TARGET][NODE_ID] === node[NODE_ID] + link[LINK_SOURCE_OBJ][NODE_ID] === node[NODE_ID] || + link[LINK_TARGET_OBJ][NODE_ID] === node[NODE_ID] ); } existsLink(sourceId, targetId) { - const links = this.data[GRAPH_LINKS]; + const links = this.data.links; for (var i = 0; i < links.length; i++) { var link = links[i]; if ( - link[LINK_SOURCE][NODE_ID] === sourceId && - link[LINK_TARGET][NODE_ID] === targetId + link[LINK_SOURCE_OBJ][NODE_ID] === sourceId && + link[LINK_TARGET_OBJ][NODE_ID] === targetId ) { return true; } @@ -215,15 +207,15 @@ export class Graph extends ManagedData { this.changeNodeDetails(selectionDetails[NODE_ID], selectionDetails); } else if (selectionDetails.link === true) { this.changeLinkDetails( - selectionDetails[LINK_SOURCE][NODE_ID], - selectionDetails[LINK_TARGET][NODE_ID], + selectionDetails[LINK_SOURCE_OBJ][NODE_ID], + selectionDetails[LINK_TARGET_OBJ][NODE_ID], selectionDetails ); } } changeNodeDetails(nodeId, newDetails) { - var nodes = this.data[GRAPH_NODES]; + var nodes = this.data.nodes; for (var i = 0; i < nodes.length; i++) { // Is relevant node? if (nodes[i][NODE_ID] !== nodeId) { @@ -240,12 +232,12 @@ export class Graph extends ManagedData { } changeLinkDetails(sourceId, targetId, newDetails) { - var links = this.data[GRAPH_LINKS]; + var links = this.data.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 + links[i][LINK_SOURCE_OBJ][NODE_ID] !== sourceId || + links[i][LINK_TARGET_OBJ][NODE_ID] !== targetId ) { continue; // No } @@ -278,19 +270,15 @@ export class Graph extends ManagedData { } var cleanData = {}; - cleanData[GRAPH_LINKS] = []; - cleanData[GRAPH_NODES] = []; + cleanData.links = []; + cleanData.nodes = []; - data[GRAPH_LINKS].forEach((link) => - cleanData[GRAPH_LINKS].push( - this.getCleanLink(link, simulationParameters) - ) + data.links.forEach((link) => + cleanData.links.push(this.getCleanLink(link, simulationParameters)) ); - data[GRAPH_NODES].forEach((node) => - cleanData[GRAPH_NODES].push( - this.getCleanNode(node, simulationParameters) - ) + data.nodes.forEach((node) => + cleanData.nodes.push(this.getCleanNode(node, simulationParameters)) ); return cleanData; @@ -317,11 +305,11 @@ export class Graph extends ManagedData { // Assuming that all nodes are valid, there are two possible formats // 1. source and target are node objects - if (link[LINK_SOURCE][NODE_ID] !== undefined) { - // Source and target nodes + if (link[LINK_SOURCE_OBJ] !== undefined) { + // Source and target are nodes // Node ids will be converted to complete node objects on running graphs, gotta convert back - cleanLink[LINK_SOURCE] = link[LINK_SOURCE][NODE_ID]; - cleanLink[LINK_TARGET] = link[LINK_TARGET][NODE_ID]; + cleanLink[LINK_SOURCE] = link[LINK_SOURCE_OBJ][NODE_ID]; + cleanLink[LINK_TARGET] = link[LINK_TARGET_OBJ][NODE_ID]; } else { // 2. source and target are just node ids cleanLink[LINK_SOURCE] = link[LINK_SOURCE]; @@ -343,7 +331,7 @@ export class Graph extends ManagedData { } existsNodeId(nodeId) { - var nodes = this.data[GRAPH_NODES]; + var nodes = this.data.nodes; for (var i = 0; i < nodes.length; i++) { if (nodes[i][NODE_ID] === nodeId) { return true; @@ -403,7 +391,7 @@ export class Graph extends ManagedData { newLink.node = false; // Add node - this.data[GRAPH_LINKS].push(newLink); + this.data.links.push(newLink); this.triggerOnChange(); this.storeCurrentData( @@ -433,7 +421,7 @@ export class Graph extends ManagedData { newNode.link = false; // Add node - this.data[GRAPH_NODES].push(newNode); + this.data.nodes.push(newNode); this.triggerOnChange(); this.storeCurrentData( @@ -452,9 +440,9 @@ export class Graph extends ManagedData { return item[NODE_LABEL]; } else if (item.link) { return ( - Graph.toStr(item[LINK_SOURCE]) + + Graph.toStr(item[LINK_SOURCE_OBJ]) + LINK_NAME_CONNECTOR + - Graph.toStr(item[LINK_TARGET]) + Graph.toStr(item[LINK_TARGET_OBJ]) ); } else { return "UNDEFINED"; diff --git a/editor/js/state.js b/editor/js/state.js index cb2458abc4b245144a27dd561329f3888a694169..a581cae4b1c57d9b9a39b6e7d5d02afa091d95fd 100644 --- a/editor/js/state.js +++ b/editor/js/state.js @@ -95,11 +95,12 @@ export class State extends Tool { onNodeDragEnd(node, translate) { // Handle as click event, if drag distance under a certain threshold - var distanceDragged = Math.sqrt(Math.pow(translate.x, 2) + Math.pow(translate.y, 2)); + var distanceDragged = Math.sqrt( + Math.pow(translate.x, 2) + Math.pow(translate.y, 2) + ); if (distanceDragged < DRAG_THRESHOLD_2D) { this.onNodeClick(node); return; - } else { this.tool.onNodeDragEnd(node, translate); } @@ -161,8 +162,8 @@ export class State extends Tool { } // Draw image - if (node[Graph.NODE_IMAGE] !== undefined) { - var path = node[Graph.NODE_IMAGE]; + if (node[Graph.NODE_ICON_IMG] != null) { + var path = node[Graph.NODE_ICON_IMG]; if (!path.includes("/")) { path = Graph.IMAGE_SRC + path; @@ -250,34 +251,40 @@ export class State extends Tool { } // Links already initialized? - if (link.source.x === undefined) { + if (link[Graph.LINK_SOURCE_OBJ] === undefined) { return undefined; } // Draw gradient link var gradient = ctx.createLinearGradient( - link.source.x, - link.source.y, - link.target.x, - link.target.y + link[Graph.LINK_SOURCE_OBJ].x, + link[Graph.LINK_SOURCE_OBJ].y, + link[Graph.LINK_TARGET_OBJ].x, + link[Graph.LINK_TARGET_OBJ].y ); // Have reversed colors // Color at source node referencing the target node and vice versa - gradient.addColorStop("0", graph.getNodeColor(link.target)); - gradient.addColorStop("1", graph.getNodeColor(link.source)); + gradient.addColorStop( + "0", + "#" + graph.getNodeColor(link[Graph.LINK_TARGET_OBJ]) + ); + gradient.addColorStop( + "1", + "#" + graph.getNodeColor(link[Graph.LINK_SOURCE_OBJ]) + ); ctx.beginPath(); - ctx.moveTo(link.source.x, link.source.y); - ctx.lineTo(link.target.x, link.target.y); + ctx.moveTo( + link[Graph.LINK_SOURCE_OBJ].x, + link[Graph.LINK_SOURCE_OBJ].y + ); + ctx.lineTo( + link[Graph.LINK_TARGET_OBJ].x, + link[Graph.LINK_TARGET_OBJ].y + ); ctx.strokeStyle = gradient; ctx.stroke(); - // Only render strokes on last link - // var lastLink = graph.data[Graph.GRAPH_LINKS][graph.data[Graph.GRAPH_LINKS].length - 1]; - // if (link === lastLink) { - // ctx.stroke(); - // } - return undefined; } diff --git a/editor/js/tools/deletetool.js b/editor/js/tools/deletetool.js index ae347563a2a24abf0a1d72704b2bc81391db3769..f82dcbe7d6f8cceeb69bd50cb5421f30d4977ece 100644 --- a/editor/js/tools/deletetool.js +++ b/editor/js/tools/deletetool.js @@ -68,8 +68,8 @@ export default class DeleteTool extends Tool { onLinkClick(link) { graph.deleteLink( - link[Graph.LINK_SOURCE][Graph.NODE_ID], - link[Graph.LINK_TARGET][Graph.NODE_ID] + link[Graph.LINK_SOURCE_OBJ][Graph.NODE_ID], + link[Graph.LINK_TARGET_OBJ][Graph.NODE_ID] ); if (state.selectedItem == link) { diff --git a/editor/js/tools/menus/selectmenu.js b/editor/js/tools/menus/selectmenu.js index e5d9478420b813d62cb2cc19f979e9af83614fdf..f0628b1f1646cb16785877bdb2c22a62c4b6ec23 100644 --- a/editor/js/tools/menus/selectmenu.js +++ b/editor/js/tools/menus/selectmenu.js @@ -55,12 +55,12 @@ export class SelectMenu extends ToolMenu { this.context = undefined; this.map = [ { menu: NODE_NAME_ID, property: Graph.NODE_LABEL }, - { menu: NODE_IMG_ID, property: Graph.NODE_IMAGE }, + { menu: NODE_IMG_ID, property: Graph.NODE_ICON_IMG }, { 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_VIDEO_ID, property: Graph.NODE_VIDEO }, - { menu: NODE_DETAIL_IMG_ID, property: Graph.NODE_DETAIL_IMAGE }, + { menu: NODE_DETAIL_IMG_ID, property: Graph.NODE_HEADER_IMG }, ]; this.hooked = false; // Can only hook menu events once, but have to do it later, when they are loaded } @@ -158,11 +158,23 @@ export class SelectMenu extends ToolMenu { formatValue(propertyKey, rawValue) { var formattedValue = rawValue; + // Format references 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 + .filter((url) => url) // Remove empty entries + .map((url) => { + return { + node_id: this.values[SELECTION_KEY][Graph.NODE_ID], + url: url, + }; + }); // Map to reference object + + // Set created tag for every reference + formattedValue.forEach((reference) => { + reference[Graph.TAG_CREATED] = true; + }); } return formattedValue; @@ -239,7 +251,7 @@ export class SelectMenu extends ToolMenu { var formattedValue = undefined; if (propertyKey === Graph.NODE_REFERENCES && Array.isArray(value)) { - formattedValue = value.join("\n"); + formattedValue = value.map((ref) => ref.url).join("\n"); } else { formattedValue = value; }