From ee841ba5253c104dd7887b454c109096a6382fa7 Mon Sep 17 00:00:00 2001 From: Maximilian Giller <m.giller@tu-bs.de> Date: Wed, 28 Sep 2022 02:21:55 +0200 Subject: [PATCH] Implemented all actions for graph manager --- src/common/datasets.js | 21 ++++++- src/datasets.php | 21 ++++++- src/editor/components/spacemanager.tsx | 11 +++- src/editor/components/spaceselect.tsx | 28 ++++----- src/editor/editor.tsx | 84 +++++++++++++++++--------- src/ks-datasets-database.php | 10 ++- 6 files changed, 126 insertions(+), 49 deletions(-) diff --git a/src/common/datasets.js b/src/common/datasets.js index 2f30ef7..4081153 100644 --- a/src/common/datasets.js +++ b/src/common/datasets.js @@ -3,9 +3,10 @@ /** * Uses the fetch API to create an ajax call for the WordPress API to fetch/post data from/to the server. * @param data {FormData} Data for the ajax call. Must contain an entry which specifies the ajax `action` + * @param parseJsonResult If true, tries to parse response as JSON. Should be disabled if backend does not return JSON data. * @returns {Promise<any>} The response from the server */ -function ajaxCall(data) { +function ajaxCall(data, parseJsonResult = true) { let opts = { method: "POST", body: data, @@ -13,7 +14,7 @@ function ajaxCall(data) { return fetch(ks_global.ajax_url, opts).then( function (response) { - return response.json(); + return parseJsonResult ? response.json() : response; }, function (reason) { console.log(reason); @@ -23,6 +24,7 @@ function ajaxCall(data) { /** * Returns the json object from the stored graph as promise. + * Creates new graph if spaceId does not exist yet. * * @param {String} spaceId Identification of graph to load. * @@ -36,6 +38,21 @@ export function loadGraphJson(spaceId) { return ajaxCall(data); } +/** + * Removes a graph entry from the database. + * + * @param {String} spaceId Identification of graph to remove. + * + * @returns Promise returning state of query. + */ +export function deleteGraphJson(spaceId) { + const data = new FormData(); + data.append("action", "delete_space"); + data.append("space", spaceId); + + return ajaxCall(data, false); +} + /** * Takes the graph json object and stores it in the backend. * diff --git a/src/datasets.php b/src/datasets.php index 92d2a4d..d2acace 100644 --- a/src/datasets.php +++ b/src/datasets.php @@ -53,6 +53,22 @@ function ks_update_space() { } } +add_action("wp_ajax_delete_space", "ks_delete_space"); // Fires only for logged-in-users +//add_action("wp_ajax_nopriv_delete_space", 'delete_space' ); // Fires for everyone +function ks_delete_space() +{ + // Check user capabilities + if (current_user_can("edit_posts")) { + $name = ks_escape_space_name($_POST["space"]); + + ks_delete_graph($name); + + wp_die(); + } else { + echo "Insufficient permissions!"; + } +} + function ks_escape_space_name($space_name) { // Cleaning up the space id $space_name = str_replace("/", "-", $space_name); @@ -61,5 +77,6 @@ function ks_escape_space_name($space_name) { $space_name = str_replace(";", "-", $space_name); $space_name = str_replace(":", "-", $space_name); $space_name = str_replace(",", "-", $space_name); - return strtolower($space_name); -} + return $space_name; + // return strtolower($space_name); Not used, since it reduces readability, but is not really required +} \ No newline at end of file diff --git a/src/editor/components/spacemanager.tsx b/src/editor/components/spacemanager.tsx index 937f89e..76ed708 100644 --- a/src/editor/components/spacemanager.tsx +++ b/src/editor/components/spacemanager.tsx @@ -2,6 +2,7 @@ import React, { useState } from "react"; import "./spacemanager.css"; interface SpaceManagerProps { + spaces: string[]; spaceId: string; onDeleteSpace: (spaceId: string) => void; onRenameSpace: (newId: string) => void; @@ -10,6 +11,7 @@ interface SpaceManagerProps { } function SpaceManager({ + spaces, spaceId, onDeleteSpace, onRenameSpace, @@ -25,6 +27,9 @@ function SpaceManager({ onDeleteSpace(spaceId); }; + const isSpaceCreationAllowed = () => + newSpaceName.length === 0 || spaces.includes(newSpaceName.trim()); + return ( <div id="space-manager"> <label htmlFor="space-name">New graph name</label> @@ -39,7 +44,7 @@ function SpaceManager({ <ul> <li> <button - disabled={newSpaceName.length === 0} + disabled={isSpaceCreationAllowed()} onClick={() => onRenameSpace(newSpaceName)} > Rename to {'"' + newSpaceName + '"'} @@ -47,7 +52,7 @@ function SpaceManager({ </li> <li> <button - disabled={newSpaceName.length === 0} + disabled={isSpaceCreationAllowed()} onClick={() => onDuplicateSpace(newSpaceName)} > Duplicate as {'"' + newSpaceName + '"'} @@ -55,7 +60,7 @@ function SpaceManager({ </li> <li> <button - disabled={newSpaceName.length === 0} + disabled={isSpaceCreationAllowed()} onClick={() => onCreateSpace(newSpaceName)} > Create empty graph {'"' + newSpaceName + '"'} diff --git a/src/editor/components/spaceselect.tsx b/src/editor/components/spaceselect.tsx index 86503b2..6a1915f 100644 --- a/src/editor/components/spaceselect.tsx +++ b/src/editor/components/spaceselect.tsx @@ -3,7 +3,7 @@ import SpaceManager from "./spacemanager"; import "./spaceselect.css"; interface SpaceSelectProps { - onLoadSpace: (spaceId: string) => boolean; + onLoadSpace: (spaceId: string) => Promise<boolean>; onDeleteSpace: (spaceId: string) => void; onRenameSpace: (newId: string) => void; onDuplicateSpace: (newId: string) => void; @@ -21,22 +21,21 @@ function SpaceSelect({ onCreateSpace, onDeleteSpace, }: SpaceSelectProps) { - const [selected, setSelected] = useState(spaceId); const [managerVisible, setMangerVisible] = useState(false); - const handleDeleteSpace = (spaceId: string) => { - setMangerVisible(false); - onDeleteSpace(spaceId); + const hideManagerAndCall = (call: (id: string) => void) => { + return (spaceId: string) => { + setMangerVisible(false); + call(spaceId); + }; }; return ( <div id="spaceselect"> <select - onChange={(event) => { - setSelected(event.target.value); - onLoadSpace(event.target.value); - }} - value={selected} + onChange={(event) => onLoadSpace(event.target.value)} + value={spaceId} + onFocus={() => setMangerVisible(false)} > {spaces.map((spaceName: string) => ( <option key={spaceName} value={spaceName}> @@ -51,11 +50,12 @@ function SpaceSelect({ </button> {managerVisible && ( <SpaceManager + spaces={spaces} spaceId={spaceId} - onCreateSpace={onCreateSpace} - onRenameSpace={onRenameSpace} - onDeleteSpace={handleDeleteSpace} - onDuplicateSpace={onDuplicateSpace} + onCreateSpace={hideManagerAndCall(onCreateSpace)} + onRenameSpace={hideManagerAndCall(onRenameSpace)} + onDeleteSpace={hideManagerAndCall(onDeleteSpace)} + onDuplicateSpace={hideManagerAndCall(onDuplicateSpace)} /> )} </div> diff --git a/src/editor/editor.tsx b/src/editor/editor.tsx index 8879c15..e04cd7a 100644 --- a/src/editor/editor.tsx +++ b/src/editor/editor.tsx @@ -1,6 +1,7 @@ import React from "react"; import { DynamicGraph } from "./graph"; import { + deleteGraphJson, listAllSpaces, loadGraphJson, saveGraphJson, @@ -10,7 +11,6 @@ import "./editor.css"; import * as Helpers from "../common/helpers"; import { Node, NodeProperties } from "../common/graph/node"; -import { SpaceManager } from "./components/spacemanager"; import SelectLayer from "./components/selectlayer"; import { Coordinate2D, GraphData, SimGraphData } from "../common/graph/graph"; import { NodeType, NodeTypeData } from "../common/graph/nodetype"; @@ -70,6 +70,7 @@ type stateTypes = { */ export class Editor extends React.PureComponent<any, stateTypes> { private rendererRef: React.RefObject<GraphRenderer2D>; + private defaultSpaceId = "Graph"; constructor(props: any) { super(props); @@ -77,6 +78,10 @@ export class Editor extends React.PureComponent<any, stateTypes> { // Making sure, all functions retain the proper this-bind this.loadGraph = this.loadGraph.bind(this); this.loadSpace = this.loadSpace.bind(this); + this.renameSpace = this.renameSpace.bind(this); + this.createSpace = this.createSpace.bind(this); + this.deleteSpace = this.deleteSpace.bind(this); + this.duplicateSpace = this.duplicateSpace.bind(this); this.saveSpace = this.saveSpace.bind(this); this.forceUpdate = this.forceUpdate.bind(this); this.handleNodeTypeSelect = this.handleNodeTypeSelect.bind(this); @@ -107,7 +112,7 @@ export class Editor extends React.PureComponent<any, stateTypes> { this.rendererRef = React.createRef(); - listAllSpaces().then((spaces) => this.setState({ spaces: spaces })); + this.loadListOfSpaces(); // Set as new state this.state = { @@ -140,10 +145,22 @@ export class Editor extends React.PureComponent<any, stateTypes> { * Tries to load initial graph after webpage finished loading. */ componentDidMount() { - if (this.state.spaceId !== undefined) { - // Load initial space - this.loadSpace(this.state.spaceId); - } + // Try to load given spaceId from Config + // If not available, just load default space + const initialSpaceId = this.state.spaceId + ? this.state.spaceId + : this.defaultSpaceId; + this.loadSpace(initialSpaceId); + } + + /** + * Fetches the most current list of available spaces from the server and updates the state accordingly. + */ + private loadListOfSpaces(): Promise<string[]> { + return listAllSpaces().then((spaces) => { + this.setState({ spaces: spaces }); + return spaces; + }); } /** @@ -151,13 +168,18 @@ export class Editor extends React.PureComponent<any, stateTypes> { * @param spaceId Id of space to load. * @returns Promise with boolean value that is true, if successful. */ - public loadSpace(spaceId: string): any { - return loadGraphJson(spaceId).then((data: GraphData) => - this.loadGraph(data, spaceId) - ); + public loadSpace(spaceId: string): Promise<boolean> { + return loadGraphJson(spaceId) + .then((data: GraphData) => { + // Loading space might have created a new space, if requested space was not available + // Just in case, reload list of spaces + this.loadListOfSpaces(); + return data; + }) + .then((data: GraphData) => this.loadGraph(data, spaceId)); } - public saveSpace() { + public saveSpace(): Promise<void> { return saveGraphJson( this.state.spaceId, this.state.graph.toJSONSerializableObject() @@ -429,37 +451,45 @@ export class Editor extends React.PureComponent<any, stateTypes> { /** * @param newId Explicit id of space that should be deleted. */ - private deleteSpace(spaceId: string) { - throw new Error( - 'Function "deleteSpace(spaceId)" has not been implemented.' - ); + private deleteSpace(spaceId: string): Promise<void> { + return deleteGraphJson(spaceId).then(() => { + // Select first space in list if available, otherwise select defaul space (which will be created) + const selectSpaceId: string = + this.state.spaces.length > 0 + ? this.state.spaces[0] + : this.defaultSpaceId; + + this.loadSpace(selectSpaceId); + }); } /** * @param newId New id for currently selected space. + * @returns Promise is true, if new, renamed graph could be loaded successfully. */ - private renameSpace(newId: string) { - throw new Error( - 'Function "renameSpace(newId)" has not been implemented.' - ); + private renameSpace(newId: string): Promise<boolean> { + return saveGraphJson(newId, this.state.graph.toJSONSerializableObject()) + .then(() => deleteGraphJson(this.state.spaceId)) + .then(() => this.loadSpace(newId)); } /** * @param newId Id for the newly created space with the data of the currently selected space copied over. + * @returns Promise is true, if newly created graph could be loaded successfully. */ - private duplicateSpace(newId: string) { - throw new Error( - 'Function "duplicateSpace(newId)" has not been implemented.' - ); + private duplicateSpace(newId: string): Promise<boolean> { + return saveGraphJson( + newId, + this.state.graph.toJSONSerializableObject() + ).then(() => this.loadSpace(newId)); } /** * @param newSpaceId Id for newly created space with the default empty space data. + * @returns Promise is true, if newly created graph could be loaded successfully. */ - private createSpace(newSpaceId: string) { - throw new Error( - 'Function "createSpace(newSpaceId)" has not been implemented.' - ); + private createSpace(newSpaceId: string): Promise<boolean> { + return this.loadSpace(newSpaceId); } render(): React.ReactNode { diff --git a/src/ks-datasets-database.php b/src/ks-datasets-database.php index 39d9e69..4a7cd98 100644 --- a/src/ks-datasets-database.php +++ b/src/ks-datasets-database.php @@ -11,6 +11,14 @@ function ks_insert_or_update_graph($name, $graph) return ks_insert_space($name, $graph); } +function ks_delete_graph($name) +{ + // Delete graph + global $SPACES_TABLE; + global $wpdb; + $wpdb->delete($SPACES_TABLE, array("name" => $name), array("%s")); +} + function ks_select_all_spaces() { global $SPACES_TABLE; @@ -69,4 +77,4 @@ function ks_insert($table, $data) $wpdb->insert($table, $data); return $wpdb->insert_id; -} +} \ No newline at end of file -- GitLab