diff --git a/backend/vcdat/GraphicsMethods.py b/backend/vcdat/GraphicsMethods.py
index 280a21b..530b79b 100644
--- a/backend/vcdat/GraphicsMethods.py
+++ b/backend/vcdat/GraphicsMethods.py
@@ -3,29 +3,10 @@
import numpy
_ = vcs.init()
-_methods = {}
_2d_methods = ('scatter', 'vector', 'xvsy', 'stream', 'glyph', '3d_vector', '3d_dual_scalar')
_primitives = ('line', 'marker', 'fillarea', 'text')
-def get_gm():
- for t in vcs.graphicsmethodlist():
- _methods[t] = {}
- for m in vcs.elements[t].keys():
- gm = vcs.elements[t][m]
- _methods[t][m] = vcs.dumpToDict(gm)[0]
- if hasattr(gm, "levels"):
- arr = numpy.array(gm.levels)
- if numpy.allclose(arr, 1e20) and arr.shape[-1] == 2:
- _methods[t][m]["levels"] = [1e20, 1e20]
- return _methods
-
-def get_default_gms():
- _defaults = {}
- for t in vcs.graphicsmethodlist():
- _defaults[t] = vcs.elements[t].keys()
- return _defaults
-
def detect_nvars(g_type, g_method, g_obj):
"""Try to return the number of variables required for the plot method.
Returns the number of variables required by the plot type.
diff --git a/backend/vcdat/app.py b/backend/vcdat/app.py
index ac3cf58..9c85b25 100644
--- a/backend/vcdat/app.py
+++ b/backend/vcdat/app.py
@@ -5,7 +5,6 @@
import cdms2
import json
from flask import Flask, send_from_directory, request, send_file, Response, jsonify
-from GraphicsMethods import get_gm, get_default_gms
from Templates import templ_from_json
from Files import getFilesObject
from Colormaps import get_cmaps
@@ -120,13 +119,6 @@ def plot_template():
return resp
-@app.route("/getGraphicsMethods")
-@jsonresp
-def get_graphics_methods():
- graphics_methods = get_gm()
- return json.dumps(graphics_methods)
-
-
@app.route("/getDefaultMethods")
@jsonresp
def get_default_methods():
diff --git a/frontend/package.json b/frontend/package.json
index 88009a8..83de331 100755
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -4,6 +4,10 @@
"description": "Front-end GUI for CDAT",
"main": "src/js/app.js",
"private": true,
+ "prettier": {
+ "printWidth": 150,
+ "tabWidth": 4
+ },
"dependencies": {
"babel-polyfill": "^6.26.0",
"bootstrap-slider": "git://github.com/matthewma7/bootstrap-slider",
diff --git a/frontend/src/js/Store.js b/frontend/src/js/Store.js
index e37ff8f..bcadedc 100644
--- a/frontend/src/js/Store.js
+++ b/frontend/src/js/Store.js
@@ -6,6 +6,7 @@ import GraphicsMethodModel from './models/GraphicsMethods.js';
import TemplateModel from './models/Templates.js';
import VariableModel from './models/Variables.js';
import SpreadsheetModel from './models/Spreadsheet.js';
+import UIModel from './models/UI.js';
/* global $ */
@@ -16,7 +17,8 @@ const reducers = combineReducers({
graphics_methods: GraphicsMethodModel.reduce,
templates: TemplateModel.reduce,
sheets_model: SpreadsheetModel.reduce,
- colormaps: ColormapModel.reduce
+ colormaps: ColormapModel.reduce,
+ ui: UIModel.reduce,
});
const undoableReducer = undoable(reducers,{
@@ -31,12 +33,12 @@ var store = null;
const configureStore = (initialState = {}) => {
let state = Promise.resolve(initialState);
if (Object.keys(initialState).length === 0) {
- const models = [CachedFileModel, VariableModel, GraphicsMethodModel, TemplateModel, SpreadsheetModel, ColormapModel]
+ const models = [CachedFileModel, VariableModel, GraphicsMethodModel, TemplateModel, SpreadsheetModel, ColormapModel, UIModel]
const initialStates = models.map((m) => {
return m.getInitialState();
});
state = Promise.all(initialStates).then((values) => {
- const cached_files = values[0], variables = values[1], graphics_methods = values[2], templates = values[3], sheets_model = values[4], colormaps = values[5];
+ const cached_files = values[0], variables = values[1], graphics_methods = values[2], templates = values[3], sheets_model = values[4], colormaps = values[5], ui = values[6];
return {
present: {
cached_files,
@@ -44,7 +46,8 @@ const configureStore = (initialState = {}) => {
graphics_methods,
templates,
sheets_model,
- colormaps
+ colormaps,
+ ui,
},
};
});
diff --git a/frontend/src/js/components/GMList.jsx b/frontend/src/js/components/GMList.jsx
index 180d19b..baf980f 100644
--- a/frontend/src/js/components/GMList.jsx
+++ b/frontend/src/js/components/GMList.jsx
@@ -1,6 +1,10 @@
import React, { Component } from 'react'
+import { connect } from 'react-redux'
+import Actions from '../constants/Actions.js'
import PropTypes from 'prop-types'
+import Dialog from 'react-bootstrap-dialog'
import AddEditRemoveNav from './AddEditRemoveNav/AddEditRemoveNav.jsx'
+import GraphicsMethodCreator from './modals/GraphicsMethodCreator.jsx'
import GraphicsMethodEditor from './modals/GraphicsMethodEditor.jsx'
import Tree from './Tree.jsx'
import DragAndDropTypes from '../constants/DragAndDropTypes.js'
@@ -29,43 +33,97 @@ class GMList extends Component {
constructor(props){
super(props)
this.state = {
- activeGM: false,
- activeGMParent: false,
- showModal: false
+ show_edit_modal: false,
+ show_create_modal: false,
}
+ this.clickedAdd = this.clickedAdd.bind(this)
this.clickedEdit = this.clickedEdit.bind(this)
- this.closedModal = this.closedModal.bind(this)
+ this.confirmRemove = this.confirmRemove.bind(this)
+ this.removeGM = this.removeGM.bind(this)
+ this.closeEditModal = this.closeEditModal.bind(this)
this.selectedChild = this.selectedChild.bind(this)
}
+ clickedAdd() {
+ this.setState({show_create_modal: true})
+ }
+
clickedEdit() {
- const gm = this.props.graphicsMethods[this.state.activeGMParent][this.state.activeGM]
- if (SUPPORTED_GM_EDITORS && !SUPPORTED_GM_EDITORS.includes(gm.g_name)) {
+ if(!this.props.selected_graphics_type || !this.props.selected_graphics_method) {
+ toast.info("A Graphics Method must be selected to edit", { position: toast.POSITION.BOTTOM_CENTER })
+ return
+ }
+
+ const gm = this.props.graphics_methods[this.props.selected_graphics_type][this.props.selected_graphics_method]
+ if(SUPPORTED_GM_EDITORS && !SUPPORTED_GM_EDITORS.includes(gm.g_name)) {
toast.warn("This graphics method does not have an editor yet.", { position: toast.POSITION.BOTTOM_CENTER })
}
else {
- this.setState({showModal: true})
+ this.setState({show_edit_modal: true})
+ }
+ }
+
+ confirmRemove() {
+ const type = this.props.selected_graphics_type
+ const name = this.props.selected_graphics_method
+ if( type && name ) {
+ this.dialog.show({
+ body: `Are you sure you want to delete "${name}"?`,
+ actions: [
+ Dialog.DefaultAction(
+ 'Delete',
+ () => {
+ this.removeGM(type, name)
+ },
+ 'btn-danger'
+ ),
+ Dialog.CancelAction()
+ ]
+ })
+ }
+ else {
+ toast.info("A Graphics Method must be selected to delete", { position: toast.POSITION.BOTTOM_CENTER })
+ }
+ }
+
+ removeGM(type, name) {
+ try {
+ vcs.removegraphicsmethod(type, name).then(() => {
+ this.props.removeGraphicsMethod(type, name)
+ },
+ (error) => {
+ console.warn(error)
+ try {
+ toast.error(error.data.exception, { position: toast.POSITION.BOTTOM_CENTER })
+ }
+ catch(e){
+ toast.error("An error occurred while attempting to delete a graphics method.", { position: toast.POSITION.BOTTOM_CENTER })
+ }
+ })
+ }
+ catch(e){
+ console.warn(e)
+ if(e instanceof ReferenceError) {
+ toast.error("VCS is not loaded. Try restarting vCDAT", { position: toast.POSITION.BOTTOM_CENTER })
+ }
}
}
- closedModal() {
- this.setState({showModal: false})
+ closeEditModal() {
+ this.setState({show_edit_modal: false})
}
selectedChild(path) {
if (path.length === 2) {
let gm = path[1]
let gm_parent = path[0]
- this.setState({
- activeGM: gm,
- activeGMParent: gm_parent,
- })
+ this.props.selectGraphicsMethod(gm_parent, gm)
}
}
render() {
- const gmModel = Object.keys(this.props.graphicsMethods).sort().map((gmType) => {
- const gms = Object.keys(this.props.graphicsMethods[gmType]).sort().map((gmname) => {
+ const gmModel = Object.keys(this.props.graphics_methods).sort().map((gmType) => {
+ const gms = Object.keys(this.props.graphics_methods[gmType]).sort().map((gmname) => {
return {
'title': gmname,
'gmType': gmType
@@ -82,27 +140,30 @@ class GMList extends Component {
- {
- (this.state && this.state.activeGM) ?
+ {(this.props.selected_graphics_type &&
+ this.props.selected_graphics_method &&
+ this.props.graphics_methods[this.props.selected_graphics_type][this.props.selected_graphics_method]) ?
:
""
}
+ { this.state.show_create_modal &&
+
{this.setState({show_create_modal: false})}}
+ graphics_methods={this.props.graphics_methods}
+ selectGM={this.props.selectGraphicsMethod}
+ />
+ }
+
)
}
}
GMList.propTypes = {
- graphicsMethods: PropTypes.object,
- updateGraphicsMethod: PropTypes.func,
+ graphics_methods: PropTypes.object,
colormaps: PropTypes.object,
+ updateGraphicsMethod: PropTypes.func,
+ selectGraphicsMethod: PropTypes.func,
+ removeGraphicsMethod: PropTypes.func,
+ selected_graphics_method: PropTypes.string,
+ selected_graphics_type: PropTypes.string,
+}
+
+const mapStateToProps = (state) => {
+ return {
+ graphics_methods: state.present.graphics_methods,
+ selected_graphics_method: state.present.ui.selected_graphics_method,
+ selected_graphics_type: state.present.ui.selected_graphics_type,
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ updateGraphicsMethod: (graphics_method) => {
+ dispatch(Actions.updateGraphicsMethod(graphics_method))
+ },
+ selectGraphicsMethod: (type, name) => {
+ dispatch(Actions.selectGraphicsMethod(type, name))
+ },
+ removeGraphicsMethod: (type, name) => {
+ dispatch(Actions.removeGraphicsMethod(type, name))
+ }
+ }
}
-export default GMList
+export default connect(mapStateToProps, mapDispatchToProps)(GMList)
diff --git a/frontend/src/js/components/TemplateList.jsx b/frontend/src/js/components/TemplateList.jsx
index 01eef43..662cceb 100644
--- a/frontend/src/js/components/TemplateList.jsx
+++ b/frontend/src/js/components/TemplateList.jsx
@@ -62,7 +62,15 @@ class TemplateList extends Component {
else{
this.setState({showTemplateEditor: true, template_data: "loading"})
return vcs.gettemplate(this.props.selected_template).then((data)=>{ // return promise for testing purposes
- this.setState({template_data: data})
+ if(data) {
+ this.setState({template_data: data})
+ }
+ else { // data will be null if the selected template doesnt exist
+ // We can probably do more error handling here. Refresh the names, and deselect the selected template
+ toast.error(`${this.props.selected_template} doesn't exist on the server. Try refreshing the browser window.`,
+ { position: toast.POSITION.BOTTOM_CENTER }
+ )
+ }
},
(error) => {
console.warn(error)
diff --git a/frontend/src/js/components/modals/GraphicsMethodCreator.jsx b/frontend/src/js/components/modals/GraphicsMethodCreator.jsx
new file mode 100644
index 0000000..093fdc1
--- /dev/null
+++ b/frontend/src/js/components/modals/GraphicsMethodCreator.jsx
@@ -0,0 +1,198 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+import Actions from "../../constants/Actions.js";
+import { Modal, Button, FormGroup, FormControl, ControlLabel, HelpBlock, Row, Col } from "react-bootstrap";
+import { toast } from "react-toastify";
+
+class GraphicsMethodCreator extends Component {
+ constructor(props) {
+ super(props);
+ let default_gm_type = "";
+ let default_gm_method = "";
+ const gm_types = Object.keys(props.graphics_methods);
+
+ if (gm_types.length > 0) {
+ default_gm_type = Object.keys(props.graphics_methods)[0]; // just grab the first one each time since there isnt really a default
+ }
+
+ if (default_gm_type) {
+ // can't select a graphics method if the types weren't defined
+ const gm_methods = Object.keys(props.graphics_methods[default_gm_type]);
+ if (gm_methods.indexOf("default") >= 0) {
+ default_gm_method = "default";
+ } else if (gm_methods.length > 0) {
+ default_gm_method = gm_methods[0];
+ }
+ }
+
+ this.state = {
+ new_gm_name: "",
+ validation_state: null,
+ selected_gm_type: default_gm_type,
+ selected_gm_method: default_gm_method,
+ error_message: ""
+ };
+
+ this.createGraphicsMethod = this.createGraphicsMethod.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleKeyPress = this.handleKeyPress.bind(this)
+ }
+
+ handleChange(event) {
+ const name = event.target.value;
+ const { status: validation_state, message } = this.getValidationState(name, this.state.selected_gm_type);
+ this.setState({ new_gm_name: name, validation_state: validation_state, error_message: message });
+ }
+
+ handleKeyPress(event) {
+ if(event.charCode === 13 && this.state.validation_state === "success") {
+ this.createGraphicsMethod()
+ }
+ }
+
+ getValidationState(name, gm_type) {
+ // Checks if the given input is a valid name for a new GM
+ // returns an object with the following keys:
+ // status: A string or null that indicates the validity of the input
+ // message: A string that contains the error message to display when the status is "error"
+ if (name.length === 0 || gm_type === "") {
+ return { status: null, message: "" };
+ } else if (Object.keys(this.props.graphics_methods[gm_type]).indexOf(name) > -1) {
+ return { status: "error", message: "A Graphics Method with that name already exists" };
+ } else if (name.startsWith("__")) {
+ return { status: "error", message: "Graphics Method names should not start with two underscores" };
+ }
+ return { status: "success", message: "" };
+ }
+
+ createGraphicsMethod() {
+ try {
+ return vcs.creategraphicsmethod(this.state.selected_gm_type, this.state.new_gm_name, this.state.selected_gm_method).then(
+ (/* success */) => {
+ this.props.createGraphicsMethod(this.state.new_gm_name, this.state.selected_gm_type, this.state.selected_gm_method);
+ toast.success("Graphics Method created successfully!", { position: toast.POSITION.BOTTOM_CENTER });
+ this.props.close();
+ },
+ /* istanbul ignore next */
+ error => {
+ console.warn("Error while creating Graphics Method: ", error);
+ try {
+ toast.error(error.data.exception, { position: toast.POSITION.BOTTOM_CENTER });
+ } catch (e) {
+ toast.error("Failed to create Graphics Method", { position: toast.POSITION.BOTTOM_CENTER });
+ }
+ }
+ );
+ } catch (e) {
+ /* istanbul ignore next */
+ console.warn(e);
+ /* istanbul ignore next */
+ toast.error("An error occurred while creating the Graphics Method. Try restarting vCDAT.");
+ }
+ }
+
+ render() {
+ return (
+
+
+ Create a Graphics Method
+
+
+
+
+
+ Base Graphics Type
+ this.setState({ selected_gm_type: e.target.value })}
+ >
+ {Object.keys(this.props.graphics_methods)
+ .sort(function(a, b) {
+ return a.toLowerCase().localeCompare(b.toLowerCase());
+ })
+ .map(name => {
+ return (
+
+ );
+ })}
+
+ Select a type for your new graphics method
+
+
+
+
+ Base Graphics Method
+ this.setState({ selected_gm_method: e.target.value })}
+ >
+ {this.props.graphics_methods[this.state.selected_gm_type] ? (
+ Object.keys(this.props.graphics_methods[this.state.selected_gm_type])
+ .sort(function(a, b) {
+ return a.toLowerCase().localeCompare(b.toLowerCase());
+ })
+ .map(name => {
+ return (
+
+ );
+ })
+ ) : (
+
+ )}
+
+ Select an existing graphics method to copy and use as a base.
+
+
+
+
+ New Graphics Method Name
+
+
+ {this.state.validation_state === "error" ? this.state.error_message : null}
+
+
+
+
+
+
+
+ );
+ }
+}
+
+GraphicsMethodCreator.propTypes = {
+ show: PropTypes.bool,
+ close: PropTypes.func,
+ graphics_methods: PropTypes.objectOf(PropTypes.objectOf(PropTypes.object)),
+ createGraphicsMethod: PropTypes.func,
+ selectGraphicsMethod: PropTypes.func
+};
+
+/* istanbul ignore next */
+const mapStateToProps = state => {
+ return {
+ gms: state.present.graphics_methods
+ };
+};
+
+/* istanbul ignore next */
+const mapDispatchToProps = dispatch => {
+ return {
+ createGraphicsMethod: (name, type, base) => {
+ dispatch(Actions.createGraphicsMethod(name, type, base));
+ }
+ };
+};
+export { GraphicsMethodCreator as PureGraphicsMethodCreator };
+export default connect(mapStateToProps, mapDispatchToProps)(GraphicsMethodCreator);
diff --git a/frontend/src/js/components/modals/TemplateCreator.jsx b/frontend/src/js/components/modals/TemplateCreator.jsx
index c33d169..cf8204b 100644
--- a/frontend/src/js/components/modals/TemplateCreator.jsx
+++ b/frontend/src/js/components/modals/TemplateCreator.jsx
@@ -19,6 +19,7 @@ class TemplateCreator extends Component {
this.state = {
new_template_name: "",
validation_state: null,
+ error_message: "",
selected_base_template: default_base_template
}
@@ -29,8 +30,8 @@ class TemplateCreator extends Component {
handleChange(event) {
const name = event.target.value
- const validation_state = this.getValidationState(name)
- this.setState({new_template_name: name, validation_state: validation_state})
+ const {status: validation_state, message} = this.getValidationState(name)
+ this.setState({new_template_name: name, validation_state: validation_state, error_message: message})
}
handleKeyPress(event) {
@@ -41,12 +42,15 @@ class TemplateCreator extends Component {
getValidationState(name){
if(name.length === 0){
- return null
+ return {status: null, message: ""}
}
else if(this.props.templates.indexOf(name) > -1) {
- return "error"
+ return {status: "error", message: "A Template with that name already exists"}
}
- return "success"
+ else if(name.startsWith("__")) {
+ return {status: "error", message: "Template names should not start with two underscores"}
+ }
+ return {status: "success", message: ""}
}
createTemplate() {
@@ -111,7 +115,7 @@ class TemplateCreator extends Component {
/>
- { this.state.validation_state === "error" ? "A template with that name already exists." : null }
+ { this.state.validation_state === "error" ? this.state.error_message : null }
diff --git a/frontend/src/js/constants/Actions.js b/frontend/src/js/constants/Actions.js
index 0c4b50c..cfccc43 100644
--- a/frontend/src/js/constants/Actions.js
+++ b/frontend/src/js/constants/Actions.js
@@ -191,12 +191,34 @@ var Actions = {
transforms: transforms,
}
},
+ createGraphicsMethod(name, gm_type, base_method) {
+ return {
+ type: "CREATE_GRAPHICS_METHOD",
+ name: name,
+ gm_type: gm_type,
+ base_method: base_method
+ }
+ },
+ selectGraphicsMethod(type, method) {
+ return {
+ type: "SELECT_GRAPHICS_METHOD",
+ gm_type: type,
+ method: method
+ }
+ },
updateGraphicsMethod(graphics_method) {
return {
type: 'UPDATE_GRAPHICS_METHOD',
graphics_method
}
},
+ removeGraphicsMethod(gm_type, name) {
+ return {
+ type: 'REMOVE_GRAPHICS_METHOD',
+ gm_type: gm_type,
+ name: name
+ }
+ },
initializeColormaps(colormaps) {
return {
type: 'INITIALIZE_COLORMAPS',
diff --git a/frontend/src/js/containers/LeftSideBar.jsx b/frontend/src/js/containers/LeftSideBar.jsx
index a67af8d..0a153cf 100644
--- a/frontend/src/js/containers/LeftSideBar.jsx
+++ b/frontend/src/js/containers/LeftSideBar.jsx
@@ -19,10 +19,13 @@ class LeftSideBar extends Component {
selectVariable={this.props.selectVariable}
selected_variable={this.props.selected_variable}
/>
-
{
return {
variables: state.present.variables,
- graphics_methods: state.present.graphics_methods,
- templates: state.present.templates.names,
- selected_template: state.present.templates.selected_template,
+ templates: state.present.templates,
+ selected_template: state.present.ui.selected_template,
cached_files: state.present.cached_files,
sheets_model: state.present.sheets_model,
colormaps: state.present.colormaps,
@@ -82,9 +83,7 @@ const mapDispatchToProps = (dispatch) => {
// error handling here. No variable selected when delete was pressed
}
},
- updateGraphicsMethod: (graphics_method) => {
- dispatch(Actions.updateGraphicsMethod(graphics_method))
- },
+
selectTemplate: (name) => dispatch(Actions.selectTemplate(name)),
updateTemplate: (template) => dispatch(Actions.updateTemplate(template)),
removeTemplate: (name) => dispatch(Actions.removeTemplate(name)),
diff --git a/frontend/src/js/models/GraphicsMethods.js b/frontend/src/js/models/GraphicsMethods.js
index ac90175..8fb649e 100644
--- a/frontend/src/js/models/GraphicsMethods.js
+++ b/frontend/src/js/models/GraphicsMethods.js
@@ -42,6 +42,20 @@ class GraphicsMethodModel extends BaseModel {
break;
}
return new_graphics_methods;
+ case "CREATE_GRAPHICS_METHOD":
+ new_graphics_methods = $.extend(true, {}, state)
+ new_graphics_methods[action.gm_type][action.name] = $.extend(true, {}, new_graphics_methods[action.gm_type][action.base_method])
+ new_graphics_methods[action.gm_type][action.name].name = action.name
+ return new_graphics_methods
+ case "REMOVE_GRAPHICS_METHOD":
+ new_graphics_methods = $.extend(true, {}, state)
+ try {
+ delete new_graphics_methods[action.gm_type][action.name]
+ }
+ catch(e) {
+ console.warn(e)
+ }
+ return new_graphics_methods
case "DELETE_COLORMAP":
new_graphics_methods = Object.assign({}, state)
for(let type of Object.keys(new_graphics_methods)){
@@ -58,7 +72,24 @@ class GraphicsMethodModel extends BaseModel {
}
static getInitialState() {
- return $.get("getGraphicsMethods");
+ try {
+ return vcs.getallgraphicsmethods().then((methods) => {
+ // search through each gm type and check if any names start with "__"
+ // If any are found, they are removed from being displayed since they are temporary names
+ for(let type of Object.keys(methods)){
+ for(let name of Object.keys(methods[type])){
+ if(name.startsWith("__")){
+ delete methods[type][name]
+ }
+ }
+ }
+ return methods
+ })
+ }
+ catch(e){
+ console.warn(e)
+ return {}
+ }
}
}
diff --git a/frontend/src/js/models/Templates.js b/frontend/src/js/models/Templates.js
index 2ae47e9..f54a851 100644
--- a/frontend/src/js/models/Templates.js
+++ b/frontend/src/js/models/Templates.js
@@ -1,5 +1,6 @@
-import BaseModel from './BaseModel.js';
+import BaseModel from './BaseModel.js'
import { toast } from 'react-toastify'
+import $ from 'jquery'
class TemplateModel extends BaseModel {
static reduce(state={}, action) {
@@ -7,29 +8,21 @@ class TemplateModel extends BaseModel {
switch (action.type) {
case 'INITIALIZE_TEMPLATE_VALUES':
return action.templates;
- case 'SELECT_TEMPLATE':
- new_state = $.extend(true, {}, state);
- new_state.selected_template = action.selected_template
- return new_state
case 'CREATE_TEMPLATE':
- new_state = $.extend(true, {}, state);
- for(let i=0; i < new_state.names.length; i++){
- if(new_state.names[i].toLocaleLowerCase() > action.name.toLocaleLowerCase()){
- new_state.names.splice(i, 0, action.name) // inserts name into alphabetical index
- new_state.selected_template = action.name
+ new_state = $.extend(true, [], state);
+ for(let i=0; i < new_state.length; i++){
+ if(new_state[i].toLocaleLowerCase() > action.name.toLocaleLowerCase()){
+ new_state.splice(i, 0, action.name) // inserts name into alphabetical index
break
}
}
return new_state
case 'REMOVE_TEMPLATE':
- new_state = $.extend(true, {}, state);
- new_state.names.splice(new_state.names.indexOf(action.name), 1)
- if(new_state.names.indexOf(new_state.selected_template) === -1){
- new_state.selected_template = ""
- }
+ new_state = $.extend(true, [], state);
+ new_state.splice(new_state.indexOf(action.name), 1)
return new_state
case 'UPDATE_TEMPLATE':
- new_state = $.extend(true, {}, state);
+ new_state = $.extend(true, [], state);
new_state[action.template.name] = action.template;
return new_state;
default:
@@ -40,10 +33,7 @@ class TemplateModel extends BaseModel {
static getInitialState() {
try{
return vcs.getalltemplatenames().then((names) => {
- return {
- names: names,
- selected_template: ""
- }
+ return names.filter((name) => {return !name.startsWith("__")}) // ["ASD", "default"] etc. filter out temp names like "__boxfill_12345"
})
}
catch(e){
@@ -54,7 +44,7 @@ class TemplateModel extends BaseModel {
else{
console.warn(e)
}
- return {names: [], selected_template: ""}
+ return []
}
}
diff --git a/frontend/src/js/models/UI.js b/frontend/src/js/models/UI.js
new file mode 100644
index 0000000..3d1e0fe
--- /dev/null
+++ b/frontend/src/js/models/UI.js
@@ -0,0 +1,46 @@
+import BaseModel from './BaseModel.js';
+import $ from 'jquery'
+
+class UIModel extends BaseModel {
+ static reduce(state={}, action) {
+ let new_state;
+ switch (action.type) {
+ case 'CREATE_TEMPLATE':
+ new_state = $.extend(true, {}, state);
+ new_state.selected_template = action.name
+ return new_state
+ case 'SELECT_TEMPLATE':
+ new_state = $.extend(true, {}, state);
+ new_state.selected_template = action.selected_template
+ return new_state
+ case 'REMOVE_TEMPLATE':
+ new_state = $.extend(true, {}, state);
+ if(new_state.selected_template === action.name){
+ new_state.selected_template = ""
+ }
+ return new_state
+ case "CREATE_GRAPHICS_METHOD":
+ new_state = $.extend(true, {}, state);
+ new_state.selected_graphics_method = action.name
+ new_state.selected_graphics_type= action.gm_type
+ return new_state
+ case "SELECT_GRAPHICS_METHOD":
+ new_state = $.extend(true, {}, state);
+ new_state.selected_graphics_method = action.method
+ new_state.selected_graphics_type= action.gm_type
+ return new_state
+ default:
+ return state;
+ }
+ }
+
+ static getInitialState() {
+ return {
+ selected_template: "",
+ selected_graphics_type: "",
+ selected_graphics_method: "",
+ }
+ }
+}
+
+export default UIModel
diff --git a/frontend/test/mocha/components/Modals/GraphicsMethodCreatorTest.jsx b/frontend/test/mocha/components/Modals/GraphicsMethodCreatorTest.jsx
new file mode 100644
index 0000000..22016ea
--- /dev/null
+++ b/frontend/test/mocha/components/Modals/GraphicsMethodCreatorTest.jsx
@@ -0,0 +1,175 @@
+/* globals it, describe, before, beforeEach, */
+let chai = require("chai");
+let expect = chai.expect;
+let React = require("react");
+
+import { PureGraphicsMethodCreator as GraphicsMethodCreator } from "../../../../src/js/components/modals/GraphicsMethodCreator.jsx";
+import Enzyme from "enzyme";
+import Adapter from "enzyme-adapter-react-16";
+Enzyme.configure({ adapter: new Adapter() });
+import { shallow } from "enzyme";
+import sinon from "sinon";
+
+const getProps = function() {
+ return {
+ show: true,
+ close: sinon.spy(),
+ graphics_methods: {
+ boxfill: {
+ default: { name: "default" },
+ polar: { name: "polar" },
+ quick: { name: "quick" }
+ },
+ isofill: {
+ default: { name: "default" },
+ polar: { name: "polar" },
+ quick: { name: "quick" }
+ }
+ },
+ createGraphicsMethod: sinon.spy(),
+ selectGraphicsMethod: sinon.spy()
+ };
+};
+
+describe("GraphicsMethodCreatorTest.jsx", function() {
+ it("renders without exploding", () => {
+ let props = getProps();
+ let gm_creator = shallow();
+ expect(gm_creator).to.have.lengthOf(1);
+ });
+
+ it("Sets default gm type and method when default is present", () => {
+ let props = getProps();
+ let gm_creator = shallow();
+ expect(gm_creator.state().selected_gm_type).to.equal("boxfill");
+ expect(gm_creator.state().selected_gm_method).to.equal("default");
+ });
+
+ it("Sets default gm type and method when default is not present", () => {
+ let props = getProps();
+ props.graphics_methods = {
+ boxfill: {
+ polar: { name: "polar" },
+ quick: { name: "quick" }
+ },
+ isofill: {
+ polar: { name: "polar" },
+ quick: { name: "quick" }
+ }
+ };
+ let gm_creator = shallow();
+ expect(gm_creator.state().selected_gm_type).to.equal("boxfill");
+ expect(gm_creator.state().selected_gm_method).to.equal("polar");
+ });
+
+ it("getValidationState works", () => {
+ let props = getProps();
+ let gm_creator = shallow();
+
+ expect(gm_creator.instance().getValidationState("", "")).to.deep.equal({ status: null, message: "" }); // returns null if no name length
+ expect(gm_creator.instance().getValidationState("default", "boxfill")).to.deep.equal({
+ status: "error",
+ message: "A Graphics Method with that name already exists"
+ });
+ expect(gm_creator.instance().getValidationState("polar", "boxfill")).to.deep.equal({
+ status: "error",
+ message: "A Graphics Method with that name already exists"
+ });
+ expect(gm_creator.instance().getValidationState("quick", "boxfill")).to.deep.equal({
+ status: "error",
+ message: "A Graphics Method with that name already exists"
+ });
+ expect(gm_creator.instance().getValidationState("default", "isofill")).to.deep.equal({
+ status: "error",
+ message: "A Graphics Method with that name already exists"
+ });
+ expect(gm_creator.instance().getValidationState("polar", "isofill")).to.deep.equal({
+ status: "error",
+ message: "A Graphics Method with that name already exists"
+ });
+ expect(gm_creator.instance().getValidationState("quick", "isofill")).to.deep.equal({
+ status: "error",
+ message: "A Graphics Method with that name already exists"
+ });
+ expect(gm_creator.instance().getValidationState("__temp", "boxfill")).to.deep.equal({
+ status: "error",
+ message: "Graphics Method names should not start with two underscores"
+ });
+ expect(gm_creator.instance().getValidationState("test", "boxfill")).to.deep.equal({ status: "success", message: "" });
+ expect(gm_creator.instance().getValidationState("valid", "boxfill")).to.deep.equal({ status: "success", message: "" });
+ });
+
+ it("handleChange function sets state", () => {
+ let props = getProps();
+ let gm_creator = shallow();
+ const event_with_null_validation = {
+ target: {
+ value: ""
+ }
+ };
+ const event_with_error_validation = {
+ target: {
+ value: "default"
+ }
+ };
+ const event_with_success_validation = {
+ target: {
+ value: "valid"
+ }
+ };
+ gm_creator.setState({ selected_gm_type: "boxfill" });
+ gm_creator.instance().handleChange(event_with_null_validation);
+ expect(gm_creator.state().new_gm_name).to.equal("");
+ expect(gm_creator.state().validation_state).to.equal(null);
+
+ gm_creator.instance().handleChange(event_with_error_validation);
+ expect(gm_creator.state().new_gm_name).to.equal("default");
+ expect(gm_creator.state().validation_state).to.equal("error");
+
+ gm_creator.instance().handleChange(event_with_success_validation);
+ expect(gm_creator.state().new_gm_name).to.equal("valid");
+ expect(gm_creator.state().validation_state).to.equal("success");
+ });
+
+ it("createGraphicsMethod works", () => {
+ global.vcs = {
+ creategraphicsmethod: sinon.stub().resolves()
+ };
+ const state = {
+ selected_gm_type: "boxfill",
+ new_gm_name: "test_name",
+ selected_gm_method: "default"
+ };
+ let props = getProps();
+ props.createGraphicsMethod = sinon.spy();
+ let gm_creator = shallow();
+ gm_creator.setState(state);
+ return gm_creator
+ .instance()
+ .createGraphicsMethod()
+ .then(() => {
+ expect(vcs.creategraphicsmethod.callCount).to.equal(1);
+ expect(vcs.creategraphicsmethod.getCall(0).args[0]).to.equal(state.selected_gm_type);
+ expect(vcs.creategraphicsmethod.getCall(0).args[1]).to.equal(state.new_gm_name);
+ expect(vcs.creategraphicsmethod.getCall(0).args[2]).to.equal(state.selected_gm_method);
+ expect(props.close.callCount).to.equal(1);
+ });
+ });
+
+ it("handleKeyPress only causes a save when valid", () => {
+ let props = getProps()
+ let gm_creator = shallow()
+ let stub = sinon.stub(gm_creator.instance(), "createGraphicsMethod").callsFake(() => {});
+ gm_creator.setState({ validation_state: "error" });
+ gm_creator.instance().handleKeyPress({ charCode: 100 });
+ expect(stub.callCount).to.equal(0); // should not be called with keys besides enter
+ gm_creator.instance().handleKeyPress({ charCode: 13 }); // 13 is the 'enter' key
+ expect(stub.callCount).to.equal(0); // should not be called with inwvalid name
+
+ gm_creator.setState({ validation_state: "success" });
+ gm_creator.instance().handleKeyPress({ charCode: 100 });
+ expect(stub.callCount).to.equal(0); // should not be called with keys besides enter
+ gm_creator.instance().handleKeyPress({ charCode: 13 });
+ expect(stub.callCount).to.equal(1); // should be called now that validation is 'success'
+ });
+});
diff --git a/frontend/test/mocha/components/Modals/TemplateCreatorTest.jsx b/frontend/test/mocha/components/Modals/TemplateCreatorTest.jsx
index f228547..1917bc7 100644
--- a/frontend/test/mocha/components/Modals/TemplateCreatorTest.jsx
+++ b/frontend/test/mocha/components/Modals/TemplateCreatorTest.jsx
@@ -1,127 +1,141 @@
/* globals it, describe, before, beforeEach, */
-var chai = require('chai')
+var chai = require("chai");
var expect = chai.expect;
-var React = require('react')
+var React = require("react");
-import TemplateCreator from '../../../../src/js/components/modals/TemplateCreator.jsx'
-import Enzyme from 'enzyme'
-import Adapter from 'enzyme-adapter-react-16'
-Enzyme.configure({ adapter: new Adapter() })
-import { shallow, mount } from 'enzyme'
-import sinon from 'sinon'
-import { createMockStore } from 'redux-test-utils'
+import TemplateCreator from "../../../../src/js/components/modals/TemplateCreator.jsx";
+import Enzyme from "enzyme";
+import Adapter from "enzyme-adapter-react-16";
+Enzyme.configure({ adapter: new Adapter() });
+import { shallow } from "enzyme";
+import sinon from "sinon";
+import { createMockStore } from "redux-test-utils";
-const getProps = function(){
+const getProps = function() {
return {
show: true,
close: sinon.spy(),
templates: ["ASD", "default", "quick"],
createTemplate: sinon.spy(),
store: createMockStore({})
- }
-}
+ };
+};
-describe('TemplateCreatorTest.jsx', function() {
- it('renders without exploding', () => {
- let props = getProps()
- var template_creator = shallow()
- expect(template_creator).to.have.lengthOf(1)
+describe("TemplateCreatorTest.jsx", function() {
+ it("renders without exploding", () => {
+ let props = getProps();
+ var template_creator = shallow();
+ expect(template_creator).to.have.lengthOf(1);
});
it('Sets default base template if no template named "default" exists', () => {
- let props = getProps()
- props.templates = ["ASD", "quick"]
- var template_creator = shallow().dive()
- expect(template_creator.state().selected_base_template).to.equal("ASD")
+ let props = getProps();
+ props.templates = ["ASD", "quick"];
+ var template_creator = shallow().dive();
+ expect(template_creator.state().selected_base_template).to.equal("ASD");
});
-
- it('getValidationState works', () => {
- let props = getProps()
- var template_creator = shallow().dive()
+ it("getValidationState works", () => {
+ let props = getProps();
+ var template_creator = shallow().dive();
- expect(template_creator.instance().getValidationState("")).to.equal(null) // returns null if no name length
- expect(template_creator.instance().getValidationState("ASD")).to.equal("error")
- expect(template_creator.instance().getValidationState("default")).to.equal("error")
- expect(template_creator.instance().getValidationState("quick")).to.equal("error")
- expect(template_creator.instance().getValidationState("test")).to.equal("success")
- expect(template_creator.instance().getValidationState("valid")).to.equal("success")
+ expect(template_creator.instance().getValidationState("")).to.deep.equal({ status: null, message: "" }); // returns null if no name length
+ expect(template_creator.instance().getValidationState("ASD")).to.deep.equal({
+ status: "error",
+ message: "A Template with that name already exists"
+ });
+ expect(template_creator.instance().getValidationState("default")).to.deep.equal({
+ status: "error",
+ message: "A Template with that name already exists"
+ });
+ expect(template_creator.instance().getValidationState("quick")).to.deep.equal({
+ status: "error",
+ message: "A Template with that name already exists"
+ });
+ expect(template_creator.instance().getValidationState("__temp")).to.deep.equal({
+ status: "error",
+ message: "Template names should not start with two underscores"
+ });
+ expect(template_creator.instance().getValidationState("test")).to.deep.equal({ status: "success", message: "" });
+ expect(template_creator.instance().getValidationState("valid")).to.deep.equal({ status: "success", message: "" });
});
- it('handleChange function works', () => {
- let props = getProps()
- var template_creator = shallow().dive()
+ it("handleChange function works", () => {
+ let props = getProps();
+ var template_creator = shallow().dive();
const event_with_null_validation = {
target: {
value: ""
}
- }
+ };
const event_with_error_validation = {
target: {
value: "ASD"
}
- }
+ };
const event_with_success_validation = {
target: {
value: "valid"
}
- }
+ };
- template_creator.instance().handleChange(event_with_null_validation)
- expect(template_creator.state().new_template_name).to.equal("")
- expect(template_creator.state().validation_state).to.equal(null)
+ template_creator.instance().handleChange(event_with_null_validation);
+ expect(template_creator.state().new_template_name).to.equal("");
+ expect(template_creator.state().validation_state).to.equal(null);
- template_creator.instance().handleChange(event_with_error_validation)
- expect(template_creator.state().new_template_name).to.equal("ASD")
- expect(template_creator.state().validation_state).to.equal("error")
+ template_creator.instance().handleChange(event_with_error_validation);
+ expect(template_creator.state().new_template_name).to.equal("ASD");
+ expect(template_creator.state().validation_state).to.equal("error");
- template_creator.instance().handleChange(event_with_success_validation)
- expect(template_creator.state().new_template_name).to.equal("valid")
- expect(template_creator.state().validation_state).to.equal("success")
+ template_creator.instance().handleChange(event_with_success_validation);
+ expect(template_creator.state().new_template_name).to.equal("valid");
+ expect(template_creator.state().validation_state).to.equal("success");
});
- it('createTemplate works', () => {
+ it("createTemplate works", () => {
global.vcs = {
createtemplate: sinon.stub().resolves()
- }
+ };
let props = {
show: true,
close: sinon.spy(),
templates: ["ASD", "default", "quick"],
createTemplate: sinon.spy(),
store: createMockStore({})
- }
- var template_creator = shallow().dive()
- template_creator.setState({new_template_name: "test_name"})
- return template_creator.instance().createTemplate().then(() => {
- expect(vcs.createtemplate.callCount).to.equal(1)
- expect(vcs.createtemplate.getCall(0).args[0]).to.equal("test_name")
- expect(vcs.createtemplate.getCall(0).args[1]).to.equal("default")
- expect(props.close.callCount).to.equal(1)
- })
+ };
+ var template_creator = shallow().dive();
+ template_creator.setState({ new_template_name: "test_name" });
+ return template_creator
+ .instance()
+ .createTemplate()
+ .then(() => {
+ expect(vcs.createtemplate.callCount).to.equal(1);
+ expect(vcs.createtemplate.getCall(0).args[0]).to.equal("test_name");
+ expect(vcs.createtemplate.getCall(0).args[1]).to.equal("default");
+ expect(props.close.callCount).to.equal(1);
+ });
});
- it('handleKeyPress only causes a save when valid', () => {
+ it("handleKeyPress only causes a save when valid", () => {
let props = {
show: true,
close: sinon.spy(),
templates: ["ASD", "default", "quick"],
createTemplate: sinon.spy(),
store: createMockStore({})
- }
- var template_creator = shallow().dive() // fuuuuuu
- let stub = sinon.stub(template_creator.instance(), 'createTemplate').callsFake(() => {})
- template_creator.instance().forceUpdate()
- template_creator.setState({validation_state: "error"})
- template_creator.instance().handleKeyPress({charCode: 13}) // 13 is the 'enter' key
- expect(stub.callCount).to.equal(0) // should not be called with invalid name
- template_creator.instance().handleKeyPress({charCode: 100})
- expect(stub.callCount).to.equal(0) // should not be called with keys besides enter
+ };
+ var template_creator = shallow().dive();
+ let stub = sinon.stub(template_creator.instance(), "createTemplate").callsFake(() => {});
+ template_creator.setState({ validation_state: "error" });
+ template_creator.instance().handleKeyPress({ charCode: 100 });
+ expect(stub.callCount).to.equal(0); // should not be called with keys besides enter
+ template_creator.instance().handleKeyPress({ charCode: 13 }); // 13 is the 'enter' key
+ expect(stub.callCount).to.equal(0); // should not be called with invalid name
- template_creator.setState({validation_state: "success"})
- template_creator.instance().handleKeyPress({charCode: 100})
- expect(stub.callCount).to.equal(0) // should not be called with keys besides enter
- template_creator.instance().handleKeyPress({charCode: 13})
- expect(stub.callCount).to.equal(1) // should be called now that validation is 'success'
+ template_creator.setState({ validation_state: "success" });
+ template_creator.instance().handleKeyPress({ charCode: 100 });
+ expect(stub.callCount).to.equal(0); // should not be called with keys besides enter
+ template_creator.instance().handleKeyPress({ charCode: 13 });
+ expect(stub.callCount).to.equal(1); // should be called now that validation is 'success'
});
-});
\ No newline at end of file
+});