diff --git a/__version__.py b/__version__.py
index a9f7c69a73..b6b2f10e76 100644
--- a/__version__.py
+++ b/__version__.py
@@ -5,7 +5,7 @@
# @website https://github.com/stochss/stochss
# =============================================================================
-__version__ = '2.4.7'
+__version__ = '2.4.8'
__title__ = 'StochSS'
__description__ = 'StochSS is an integrated development environment (IDE) \
for simulation of biochemical networks.'
diff --git a/client/app.js b/client/app.js
index af22f9612a..786121e86f 100644
--- a/client/app.js
+++ b/client/app.js
@@ -100,31 +100,45 @@ let registerRenderSubview = (parent, view, hook) => {
let getXHR = (endpoint, {
always = function (err, response, body) {}, success = function (err, response, body) {},
error = function (err, response, body) {}}={}) => {
- xhr({uri: endpoint, json: true}, function (err, response, body) {
- if(response.statusCode < 400) {
- success(err, response, body);
- }else if(response.statusCode < 500) {
- error(err, response, body);
- }else{
- console.log("Critical Error Detected");
- }
- always(err, response, body);
- });
+ try {
+ xhr({uri: endpoint, json: true}, function (err, response, body) {
+ if(response.statusCode < 400) {
+ success(err, response, body);
+ }else if(response.statusCode < 500) {
+ error(err, response, body);
+ }else{
+ console.log("Critical Error Detected");
+ }
+ always(err, response, body);
+ });
+ }catch(exception){
+ console.log(exception);
+ let response = {Reason: "Network Error", Message: exception};
+ let body = {response: response, err: exception}
+ error(exception, response, body);
+ }
};
let postXHR = (endpoint, data, {
always = function (err, response, body) {}, success = function (err, response, body) {},
error = function (err, response, body) {}}={}, isJSON) => {
- xhr({uri: endpoint, json: isJSON !== undefined ? isJSON : true, method: "post", body: data}, function (err, response, body) {
- if(response.statusCode < 400) {
- success(err, response, body);
- }else if(response.statusCode < 500) {
- error(err, response, body);
- }else{
- console.log("Critical Error Detected");
- }
- always(err, response, body);
- });
+ try {
+ xhr({uri: endpoint, json: isJSON !== undefined ? isJSON : true, method: "post", body: data}, function (err, response, body) {
+ if(response.statusCode < 400) {
+ success(err, response, body);
+ }else if(response.statusCode < 500) {
+ error(err, response, body);
+ }else{
+ console.log("Critical Error Detected");
+ }
+ always(err, response, body);
+ });
+ }catch(exception){
+ console.log(exception);
+ let response = {Reason: "Network Error", Message: exception};
+ let body = {response: response, err: exception}
+ error(exception, response, body);
+ }
};
let getBrowser = () => {
diff --git a/client/domain-view/domain-view.js b/client/domain-view/domain-view.js
new file mode 100644
index 0000000000..6afabcda6f
--- /dev/null
+++ b/client/domain-view/domain-view.js
@@ -0,0 +1,544 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 StochSS developers.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+let $ = require('jquery');
+let path = require('path');
+let _ = require('underscore');
+//support files
+let app = require('../app');
+let Plotly = require('../lib/plotly');
+//models
+let Particle = require('../models/particle');
+//views
+let View = require('ampersand-view');
+let TypesView = require('./views/types-view');
+let LimitsView = require('./views/limits-view');
+let QuickviewType = require('./views/quickview-type');
+let PropertiesView = require('./views/properties-view');
+let EditParticleView = require('./views/particle-view');
+let ViewParticleView = require('./views/view-particle');
+let FillGeometryView = require('./views/fill-geometry-view');
+let ImportMeshView = require('./views/import-mesh-view');
+let Edit3DDomainView = require('./views/edit-3D-domain-view');
+let TypesDescriptionView = require('./views/types-description-view');
+//templates
+let template = require('./domainView.pug');
+
+module.exports = View.extend({
+ template: template,
+ events: {
+ 'click [data-hook=collapse-domain-particles]' : 'changeCollapseButtonText',
+ 'click [data-hook=add-new-particle]' : 'handleAddParticle',
+ 'click [data-hook=save-selected-particle]' : 'handleSaveParticle',
+ 'click [data-hook=remove-selected-particle]' : 'handleRemoveParticle',
+ 'click [data-hook=collapse-domain-figure]' : 'changeCollapseButtonText'
+ },
+ initialize: function (attrs, options) {
+ View.prototype.initialize.apply(this, arguments);
+ this.readOnly = attrs.readOnly ? attrs.readOnly : false;
+ this.plot = attrs.plot ? attrs.plot : null;
+ this.elements = attrs.elements ? attrs.elements : null;
+ this.queryStr = attrs.queryStr;
+ this.newPart = this.createNewParticle();
+ this.actPart = {"part":null, "tn":0, "pn":0};
+ this.model.updateValid();
+ },
+ render: function (attrs, options) {
+ View.prototype.render.apply(this, arguments);
+ this.renderPropertiesView();
+ this.renderLimitsView();
+ this.renderTypesView();
+ if(this.readOnly) {
+ $(this.queryByHook('domain-particles-editor')).css('display', 'none');
+ $(this.queryByHook('domain-figure-preview')).css('display', 'none');
+ this.renderTypesQuickview();
+ }else{
+ this.updateParticleViews({includeGeometry: true});
+ this.renderTypesDescriptionView();
+ }
+ if(!this.elements) {
+ this.elements = {
+ figure: this.queryByHook('domain-figure'),
+ figureEmpty: this.queryByHook('domain-figure-empty')
+ }
+ }
+ let endpoint = path.join(app.getApiPath(), "spatial-model/domain-plot") + this.queryStr;
+ if(this.plot) {
+ this.displayFigure();
+ }else{
+ app.getXHR(endpoint, {success: (err, response, body) => {
+ this.plot = body.fig;
+ this.traceTemp = body.trace_temp;
+ this.displayFigure();
+ }});
+ }
+ },
+ add3DDomain: function (limits, particles) {
+ let limitsChanged = this.changeDomainLimits(limits, false);
+ particles.forEach((particle) => {
+ particle = new Particle(particle);
+ this.model.particles.addParticle({particle: particle});
+ this.addParticle({particle: particle});
+ });
+ if(limitsChanged) {
+ this.renderLimitsView();
+ }
+ this.renderTypesView();
+ this.resetFigure();
+ },
+ addMeshDomain: function (limits, particles, types, reset) {
+ let limitsChanged = this.changeDomainLimits(limits, reset);
+ if(types) {
+ this.addMissingTypes(types);
+ }
+ particles.forEach((particle) => {
+ particle = new Particle(particle);
+ this.model.particles.addParticle({particle: particle});
+ this.addParticle({particle: particle});
+ });
+ if(limitsChanged) {
+ this.renderLimitsView();
+ }
+ this.renderTypesView();
+ if(types) {
+ this.updateParticleViews();
+ }
+ this.resetFigure();
+ },
+ addMissingTypes: function (typeIDs) {
+ typeIDs.forEach((typeID) => {
+ if(!this.model.types.get(typeID, 'typeID')) {
+ let name = this.model.types.addType();
+ this.addType(name, {update: false});
+ }
+ });
+ },
+ addParticle: function ({particle=this.newPart}={}) {
+ this.plot.data[particle.type].ids.push(particle.particle_id);
+ this.plot.data[particle.type].x.push(particle.point[0]);
+ this.plot.data[particle.type].y.push(particle.point[1]);
+ this.plot.data[particle.type].z.push(particle.point[2]);
+ },
+ addType: function (name, {update=true}={}) {
+ let newTrace = JSON.parse(JSON.stringify(this.traceTemp));
+ newTrace.name = name;
+ this.plot.data.push(newTrace);
+ if(update) {
+ this.updateParticleViews();
+ }
+ },
+ applyGeometry: function (ids, type) {
+ let actPart = JSON.parse(JSON.stringify(this.actPart));
+ ids.forEach((id) => {
+ let particle = this.model.particles.get(id, 'particle_id');
+ // Set active particle for updating particle in the plot
+ this.actPart = {
+ part: particle,
+ tn: particle.type,
+ pn: this.plot.data[particle.type].ids.indexOf(particle.particle_id)
+ }
+ // Update the particle attributes
+ particle.type = type.typeID;
+ particle.mass = type.mass;
+ particle.volume = type.volume;
+ particle.rho = type.rho;
+ particle.nu = type.nu;
+ particle.c = type.c;
+ particle.fixed = type.fixed;
+ // Update the particle in the figure
+ this.changeParticleType(type.typeID, {update: false});
+ });
+ if(actPart.part) {
+ this.actPart = {
+ part: this.model.particles.get(actPart.part.particle_id, "particle_id"),
+ tn: type.typeID,
+ pn: this.plot.data[type.typeID].ids.indexOf(actPart.part.particle_id)
+ }
+ if(ids.includes(actPart.part.particle_id)) {
+ this.renderEditParticleView();
+ }
+ }else{
+ this.actPart = actPart
+ }
+ this.renderTypesView();
+ this.resetFigure();
+ },
+ changeCollapseButtonText: function (e) {
+ app.changeCollapseButtonText(this, e)
+ },
+ changeDomainLimits: function (limits, reset) {
+ var limitsChanged = false;
+ if(reset) {
+ this.model.x_lim = limits.x_lim;
+ this.model.y_lim = limits.y_lim;
+ this.model.y_lim = limits.y_lim;
+ limitsChanged = true;
+ }else{
+ if(this.model.x_lim[0] > limits.x_lim[0]) {
+ this.model.x_lim[0] = limits.x_lim[0];
+ limitsChanged = true;
+ }
+ if(this.model.y_lim[0] > limits.y_lim[0]) {
+ this.model.y_lim[0] = limits.y_lim[0];
+ limitsChanged = true;
+ }
+ if(this.model.z_lim[0] > limits.z_lim[0]) {
+ this.model.z_lim[0] = limits.z_lim[0];
+ limitsChanged = true;
+ }
+ if(this.model.x_lim[1] < limits.x_lim[1]) {
+ this.model.x_lim[1] = limits.x_lim[1];
+ limitsChanged = true;
+ }
+ if(this.model.y_lim[1] < limits.y_lim[1]) {
+ this.model.y_lim[1] = limits.y_lim[1];
+ limitsChanged = true;
+ }
+ if(this.model.z_lim[1] < limits.z_lim[1]) {
+ this.model.z_lim[1] = limits.z_lim[1];
+ limitsChanged = true;
+ }
+ }
+ return limitsChanged;
+ },
+ changeParticleLocation: function () {
+ this.plot.data[this.actPart.tn].x[this.actPart.pn] = this.actPart.part.point[0];
+ this.plot.data[this.actPart.tn].y[this.actPart.pn] = this.actPart.part.point[1];
+ this.plot.data[this.actPart.tn].z[this.actPart.pn] = this.actPart.part.point[2];
+ },
+ changeParticleType: function (type, {update=true}={}) {
+ let id = this.plot.data[this.actPart.tn].ids.splice(this.actPart.pn, 1)[0];
+ let x = this.plot.data[this.actPart.tn].x.splice(this.actPart.pn, 1)[0];
+ let y = this.plot.data[this.actPart.tn].y.splice(this.actPart.pn, 1)[0];
+ let z = this.plot.data[this.actPart.tn].z.splice(this.actPart.pn, 1)[0];
+ this.plot.data[type].ids.push(id);
+ this.plot.data[type].x.push(x);
+ this.plot.data[type].y.push(y);
+ this.plot.data[type].z.push(z);
+ if(update) {
+ this.resetFigure();
+ }
+ },
+ createNewParticle: function () {
+ let type = this.model.types.get(0, 'typeID');
+ return new Particle({
+ c: type.c,
+ fixed: type.fixed,
+ mass: type.mass,
+ nu: type.nu,
+ point: [0, 0, 0],
+ rho: type.rho,
+ type: type.typeID,
+ volume: type.volume
+ });
+ },
+ completeAction: function (prefix) {
+ $(this.queryByHook(`${prefix}-in-progress`)).css("display", "none");
+ $(this.queryByHook(`${prefix}-complete`)).css("display", "inline-block");
+ setTimeout(() => {
+ $(this.queryByHook(`${prefix}-complete`)).css('display', 'none');
+ }, 5000);
+ },
+ deleteParticle: function () {
+ this.plot.data[this.actPart.tn].ids.splice(this.actPart.pn, 1);
+ this.plot.data[this.actPart.tn].x.splice(this.actPart.pn, 1);
+ this.plot.data[this.actPart.tn].y.splice(this.actPart.pn, 1);
+ this.plot.data[this.actPart.tn].z.splice(this.actPart.pn, 1);
+ this.resetFigure();
+ },
+ deleteType: function (type, {unassign=true}={}) {
+ if(unassign) {
+ this.unassignAllParticles(type, {update: false});
+ }else{
+ if(this.actPart.part && this.actPart.part.type === type) {
+ this.actPart = {"part":null, "tn":0, "pn":0};
+ }
+ if(this.newPart && this.newPart.type === type) {
+ this.newPart.type = 0
+ }
+ let particles = this.model.particles.filter((particle) => {
+ return particle.type === type;
+ });
+ this.model.particles.removeParticles(particles);
+ }
+ this.model.realignTypes(type);
+ this.plot.data.splice(type, 1);
+ this.renderTypesView();
+ this.updateParticleViews({includeGeometry: true});
+ this.resetFigure();
+ },
+ displayFigure: function () {
+ if(this.model.particles.length > 0) {
+ $(this.elements.figureEmpty).css('display', 'none');
+ $(this.elements.figure).css('display', 'block');
+ Plotly.newPlot(this.elements.figure, this.plot);
+ this.elements.figure.on('plotly_click', _.bind(this.selectParticle, this));
+ }else{
+ $(this.elements.figureEmpty).css('display', 'block');
+ $(this.elements.figure).css('display', 'none');
+ }
+ },
+ handleAddParticle: function () {
+ this.startAction("anp")
+ this.model.particles.addParticle({particle: this.newPart});
+ this.addParticle();
+ this.resetFigure();
+ this.renderTypesView();
+ this.newPart = this.createNewParticle();
+ this.renderNewParticleView();
+ this.completeAction("anp");
+ },
+ handleRemoveParticle: function () {
+ this.startAction("rsp")
+ this.model.particles.removeParticle(this.actPart.part);
+ this.deleteParticle();
+ this.actPart = {"part":null, "tn":0, "pn":0};
+ this.renderTypesView();
+ this.renderEditParticleView();
+ this.completeAction("rsp")
+ },
+ handleSaveParticle: function () {
+ this.startAction("esp");
+ if(this.editParticleView.origType !== this.actPart.part.type) {
+ this.changeParticleType(this.actPart.part.type, {update: false});
+ this.renderTypesView();
+ }
+ if(!this.actPart.part.comparePoint(this.editParticleView.origPoint)) {
+ this.changeParticleLocation();
+ }
+ this.resetFigure();
+ this.completeAction("esp");
+ },
+ removeFigure: function () {
+ try {
+ this.elements.figure.removeListener('plotly_click', this.selectParticle);
+ Plotly.purge(this.elements.figure);
+ }catch (err) {
+ return
+ }
+ },
+ renameType: function (index, name) {
+ this.plot.data[index].name = name;
+ this.resetFigure();
+ },
+ renderEdit3DDomainView: function () {
+ if(this.edit3DDomainView) {
+ this.edit3DDomainView.remove();
+ }
+ this.edit3DDomainView = new Edit3DDomainView();
+ app.registerRenderSubview(this, this.edit3DDomainView, "3d-domain-container");
+ },
+ renderEditParticleView: function () {
+ if(this.editParticleView) {
+ this.editParticleView.remove();
+ }
+ let disable = this.actPart.part == null
+ this.editParticleView = new EditParticleView({
+ model: this.actPart.part ? this.actPart.part : this.createNewParticle(),
+ defaultType: this.model.types.get(this.actPart.part ? this.actPart.part.type : 0, "typeID"),
+ viewIndex: 1,
+ disable: disable
+ });
+ app.registerRenderSubview(this, this.editParticleView, "edit-particle-container");
+ $(this.queryByHook("edit-select-message")).css('display', disable ? 'block' : 'none');
+ $(this.queryByHook("save-selected-particle")).prop('disabled', disable);
+ $(this.queryByHook("remove-selected-particle")).prop('disabled', disable);
+ },
+ renderFillGeometryView: function () {
+ if(this.fillGeometryView) {
+ this.fillGeometryView.remove();
+ }
+ this.fillGeometryView = new FillGeometryView();
+ app.registerRenderSubview(this, this.fillGeometryView, 'fill-geometry-container');
+ },
+ renderImportMeshView: function () {
+ if(this.importMeshView) {
+ this.importMeshView.remove();
+ }
+ this.importMeshView = new ImportMeshView();
+ app.registerRenderSubview(this, this.importMeshView, "import-particles-section");
+ },
+ renderLimitsView: function () {
+ if(this.limitsView) {
+ this.limitsView.remove();
+ }
+ this.limitsView = new LimitsView({
+ model: this.model,
+ readOnly: this.readOnly
+ });
+ app.registerRenderSubview(this, this.limitsView, "domain-limits-container");
+ },
+ renderNewParticleView: function () {
+ if(this.newParticleView) {
+ this.newParticleView.remove();
+ }
+ this.newParticleView = new EditParticleView({
+ model: this.newPart,
+ defaultType: this.model.types.get(0, "typeID"),
+ viewIndex: 0
+ });
+ app.registerRenderSubview(this, this.newParticleView, "new-particle-container");
+ },
+ renderPropertiesView: function () {
+ if(this.propertiesView) {
+ this.propertiesView.remove();
+ }
+ this.propertiesView = new PropertiesView({
+ model: this.model,
+ readOnly: this.readOnly
+ });
+ app.registerRenderSubview(this, this.propertiesView, "domain-properties-container");
+ },
+ renderTypesDescriptionView: function () {
+ if(this.typesDescriptionView) {
+ this.typesDescriptionView.remove();
+ }
+ this.typesDescriptionView = new TypesDescriptionView();
+ app.registerRenderSubview(this, this.typesDescriptionView, "particle-types-container");
+ },
+ renderTypesQuickview: function () {
+ if(this.typesQuickviewView) {
+ this.typesQuickviewView.remove();
+ }
+ this.elements.select.css('display', 'block');
+ this.typesQuickviewView = this.renderCollection(
+ this.model.types,
+ QuickviewType,
+ this.elements.type
+ );
+ },
+ renderTypesView: function () {
+ if(this.typesView) {
+ this.typesView.remove();
+ }
+ let particleCounts = {};
+ this.model.particles.forEach((particle) => {
+ if(particleCounts[particle.type]) {
+ particleCounts[particle.type] += 1;
+ }else{
+ particleCounts[particle.type] = 1;
+ }
+ });
+ this.model.types.forEach((dType) => {
+ if(particleCounts[dType.typeID]) {
+ dType.numParticles = particleCounts[dType.typeID];
+ }else{
+ dType.numParticles = 0;
+ }
+ });
+ this.typesView = new TypesView({
+ collection: this.model.types,
+ readOnly: this.readOnly
+ });
+ app.registerRenderSubview(this, this.typesView, "domain-types-container");
+ },
+ renderViewParticleView: function () {
+ if(this.viewParticleView) {
+ this.viewParticleView.remove();
+ }
+ this.elements.select.css('display', 'none');
+ this.viewParticleView = new ViewParticleView({
+ model: this.actPart.part
+ });
+ app.registerRenderSubview(this.elements.particle.view, this.viewParticleView, this.elements.particle.hook);
+ },
+ resetFigure: function () {
+ this.removeFigure();
+ this.displayFigure();
+ },
+ selectParticle: function (data) {
+ let point = data.points[0];
+ this.actPart.part = this.model.particles.get(point.id, 'particle_id');
+ this.actPart.tn = point.curveNumber;
+ this.actPart.pn = point.pointNumber;
+ if(this.readOnly) {
+ this.renderViewParticleView();
+ }else{
+ this.renderEditParticleView();
+ }
+ },
+ setParticleTypes: function (typeIDs, types) {
+ this.addMissingTypes(typeIDs);
+ let actPart = JSON.parse(JSON.stringify(this.actPart));
+ types.forEach((type) => {
+ let particle = this.model.particles.get(type.particle_id, 'particle_id');
+ this.actPart = {
+ part: particle,
+ tn: particle.type,
+ pn: this.plot.data[particle.type].ids.indexOf(particle.particle_id)
+ }
+ this.changeParticleType(type.typeID, {update: false});
+ particle.type = type.typeID;
+ });
+ if(actPart.part && actPart.part.type === type) {
+ this.actPart = {
+ part: this.model.particles.get(actPart.part.particle_id, "particle_id"),
+ tn: 0,
+ pn: this.plot.data[0].ids.indexOf(actPart.part.particle_id)
+ }
+ this.renderEditParticleView();
+ }else{
+ this.actPart = actPart
+ }
+ this.resetFigure();
+ this.updateParticleViews();
+ },
+ startAction: function (prefix) {
+ $(this.queryByHook(`${prefix}-complete`)).css('display', 'none');
+ $(this.queryByHook(`${prefix}-in-progress`)).css("display", "inline-block");
+ },
+ unassignAllParticles: function (type, {update=true}={}) {
+ let actPart = JSON.parse(JSON.stringify(this.actPart));
+ this.model.particles.forEach((particle) => {
+ if(particle.type === type) {
+ this.actPart = {
+ part: particle,
+ tn: type,
+ pn: this.plot.data[type].ids.indexOf(particle.particle_id)
+ }
+ this.changeParticleType(0, {update: false});
+ particle.type = 0;
+ }
+ });
+ if(actPart.part) {
+ this.actPart = {
+ part: this.model.particles.get(actPart.part.particle_id, "particle_id"),
+ tn: 0,
+ pn: this.plot.data[0].ids.indexOf(actPart.part.particle_id)
+ }
+ if(actPart.part.type === type) {
+ this.renderEditParticleView();
+ }
+ }else{
+ this.actPart = actPart
+ }
+ if(update) {
+ this.renderTypesView();
+ this.resetFigure();
+ }
+ },
+ updateParticleViews: function ({includeGeometry=false}={}) {
+ this.renderNewParticleView();
+ this.renderEditParticleView();
+ this.renderEdit3DDomainView();
+ this.renderImportMeshView();
+ if(includeGeometry) {
+ this.renderFillGeometryView();
+ }
+ }
+});
diff --git a/client/domain-view/domainView.pug b/client/domain-view/domainView.pug
new file mode 100644
index 0000000000..646a80baa5
--- /dev/null
+++ b/client/domain-view/domainView.pug
@@ -0,0 +1,126 @@
+div#domain-view
+
+ div(data-hook="domain-properties-container")
+
+ div(data-hook="domain-limits-container")
+
+ div(data-hook="domain-types-container")
+
+ div#domain-particles-editor.card(data-hook="domain-particles-editor")
+
+ div.card-header.pb-0
+
+ h3.inline.mr-3 Particles
+
+ div.inline.mr-3
+
+ ul.nav.nav-tabs.card-header-tabs(id="domain-particles-tabs")
+
+ li.nav-item
+
+ a.nav-link.tab.active(data-hook="new-particle-tab" data-toggle="tab" href="#create-new-particle") Create New
+
+ li.nav-item
+
+ a.nav-link.tab(data-hook="edit-particle-tab" data-toggle="tab" href="#edit-selected-particle") Edit Selected
+
+ li.nav-item
+
+ a.nav-link.tab(data-hook="particle-types-tab" data-toggle="tab" href="#set-particle-types") Set Types from File
+
+ li.nav-item
+
+ a.nav-link.tab(data-hook="3d-domain-tab" data-toggle="tab" href="#create-3d-domain") Create 3D Domain
+
+ li.nav-item
+
+ a.nav-link.tab(data-hook="import-particles-tab" data-toggle="tab" href="#import-particles") Import Mesh
+
+ li.nav-item
+
+ a.nav-link.tab(data-hook="fill-geometry-tab" data-toggle="tab" href="#fill-geometry") Fill Geometry
+
+ button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#domain-particles-section" data-hook="collapse-domain-particles") -
+
+ div.collapse.show(id="domain-particles-section" data-hook="domain-particles-section")
+
+ div.card-body.tab-content
+
+ div.tab-pane.active(id="create-new-particle" data-hook="new-particle-section")
+
+ div(data-hook="new-particle-container")
+
+ button.btn.btn-outline-primary.box-shadow(data-hook="add-new-particle") Add Particle
+
+ div.mdl-edit-btn.saving-status.inline(data-hook="anp-in-progress")
+
+ div.spinner-grow.mr-2
+
+ span Adding particle ...
+
+ div.mdl-edit-btn.saved-status.inline(data-hook="anp-complete")
+
+ span Particle Added
+
+ div.tab-pane(id="edit-selected-particle" data-hook="edit-particle-section")
+
+ div.text-info.mb-4(data-hook="edit-select-message" style="display: none")
+ | Click on a particle in the Domain section to begin editing.
+
+ div(data-hook="edit-particle-container")
+
+ button.btn.btn-outline-primary.box-shadow.ml-2(data-hook="save-selected-particle") Save Particle
+
+ div.mdl-edit-btn.saving-status.inline(data-hook="esp-in-progress")
+
+ div.spinner-grow.mr-2
+
+ span Saving particle ...
+
+ div.mdl-edit-btn.saved-status.inline(data-hook="esp-complete")
+
+ span Particle Saved
+
+ button.btn.btn-outline-primary.box-shadow.ml-2(data-hook="remove-selected-particle") Remove Particle
+
+ div.mdl-edit-btn.saving-status.inline(data-hook="rsp-in-progress")
+
+ div.spinner-grow.mr-2
+
+ span Removing particle ...
+
+ div.mdl-edit-btn.saved-status.inline(data-hook="rsp-complete")
+
+ span Particle Removed
+
+ div.tab-pane(id="set-particle-types" data-hook="set-particle-types-section")
+
+ div(data-hook="particle-types-container")
+
+ div.tab-pane(id="create-3d-domain" data-hook="3d-domain-section")
+
+ div(data-hook="3d-domain-container")
+
+ div.tab-pane(id="import-particles" data-hook="import-particles-section")
+
+ div(data-hook="import-particles-container")
+
+ div.tab-pane(id="fill-geometry" data-hook="fill-geometry-section")
+
+ div(data-hook="fill-geometry-container")
+
+ div#domain-figure-preview.card(data-hook="domain-figure-preview")
+
+ div.card-header.pb-0
+
+ h3.inline.mr-3 Domain
+
+ button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#domain-figure-container" data-hook="collapse-domain-figure") -
+
+ div.collapse.show(id="domain-figure-container" data-hook="domain-figure-container")
+
+ div.card-body
+
+ div(id="domain-figure" data-hook="domain-figure" style="height: 800px")
+
+ div.text-danger(data-hook="domain-figure-empty") The domain currently has no particles to display
\ No newline at end of file
diff --git a/client/domain-view/templates/edit3DDomainView.pug b/client/domain-view/templates/edit3DDomainView.pug
new file mode 100644
index 0000000000..7e3a60dd29
--- /dev/null
+++ b/client/domain-view/templates/edit3DDomainView.pug
@@ -0,0 +1,231 @@
+div.mx-1
+
+ h4.mt-3 Particle Distribution
+
+ div
+
+ hr
+
+ div.mb-3.mx-1.row.head.align-items-baseline
+
+ div.col-sm-3
+
+ div.col-sm-2
+
+ h6.inline X-Plane
+
+ div.col-sm-2
+
+ h6.inline Y-Plane
+
+ div.col-sm-2
+
+ h6.inline Z-Plane
+
+ div.col-sm-3
+
+ h6.inline Total
+
+ div.row
+
+ div.col-sm-3
+
+ h6.inline Number of Particles
+
+ div.col-sm-2
+
+ div(data-target="edit-3d-domain-n" data-hook="edit-3d-domain-nx" data-key="nx")
+
+ div.col-sm-2
+
+ div(data-target="edit-3d-domain-n" data-hook="edit-3d-domain-ny" data-key="ny")
+
+ div.col-sm-2
+
+ div(data-target="edit-3d-domain-n" data-hook="edit-3d-domain-nz" data-key="nz")
+
+ div.col-sm-3
+
+ div(data-target="3d-domain-n" data-hook="3d-domain-n")
+
+ h4.mt-3 Domain Limits
+
+ div
+
+ div.mx-1.row.head.align-items-baseline
+
+ div.col-sm-3
+
+ div.col-sm-3
+
+ h6.inline X-Axis
+
+ div.col-sm-3
+
+ h6.inline Y-Axis
+
+ div.col-sm-3
+
+ h6.inline Z-Axis
+
+ div.mt-3.mx-1.row
+
+ div.col-sm-3
+
+ h6.inline Minimum
+
+ div.col-sm-3
+
+ div(data-hook="edit-3d-domain-x-lim-min" data-target="edit-3d-domain-limitation" data-name="xLim" data-index="0")
+
+ div.col-sm-3
+
+ div(data-hook="edit-3d-domain-y-lim-min" data-target="edit-3d-domain-limitation" data-name="yLim" data-index="0")
+
+ div.col-sm-3
+
+ div(data-hook="edit-3d-domain-z-lim-min" data-target="edit-3d-domain-limitation" data-name="zLim" data-index="0")
+
+ hr
+
+ div.mx-1.row
+
+ div.col-sm-3
+
+ h6.inline Maximum
+
+ div.col-sm-3
+
+ div(data-hook="edit-3d-domain-x-lim-max" data-target="edit-3d-domain-limitation" data-name="xLim" data-index="1")
+
+ div.col-sm-3
+
+ div(data-hook="edit-3d-domain-y-lim-max" data-target="edit-3d-domain-limitation" data-name="yLim" data-index="1")
+
+ div.col-sm-3
+
+ div(data-hook="edit-3d-domain-z-lim-max" data-target="edit-3d-domain-limitation" data-name="zLim" data-index="1")
+
+ div.mt-3
+
+ h4.inline Advanced
+
+ button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#edit-3D-domain-advanced" data-hook="collapse-3D-domain-advanced") +
+
+ div.collapse(id="edit-3D-domain-advanced" data-hook="3D-domain-advanced-container")
+
+ div.mt-3
+
+ h5.inline.mr-2 Type:
+
+ div.inline(data-hook="edit-3d-domain-type-select")
+
+ div.mt-3
+
+ h5 Type Defaults
+
+ hr
+
+ div.mb-3.mx-1.row.head.align-items-baseline
+
+ div.col-sm-2
+
+ h6.inline Mass
+
+ div.col-sm-2
+
+ h6.inline Volume
+
+ div.col-sm-2
+
+ h6.inline Density
+
+ div.col-sm-2
+
+ h6.inline Viscosity
+
+ div.col-sm-2
+
+ h6.inline Speed of Sound
+
+ div.col-sm-2
+
+ h6.inline Fixed
+
+ div.row.pl-3
+
+ div.col-sm-2
+
+ div(data-hook="edit-3d-domain-td-mass")
+
+ div.col-sm-2
+
+ div(data-hook="edit-3d-domain-td-vol")
+
+ div.col-sm-2
+
+ div(data-hook="edit-3d-domain-td-rho")
+
+ div.col-sm-2
+
+ div(data-hook="edit-3d-domain-td-nu")
+
+ div.col-sm-2
+
+ div(data-hook="edit-3d-domain-td-c")
+
+ div.col-sm-2
+
+ input(type="checkbox" data-hook="edit-3d-domain-td-fixed" disabled)
+
+ div.my-3
+
+ h5 Particle Transformations
+
+ hr
+
+ div.mb-3.mx-1.row.head.align-items-baseline
+
+ div.col-sm-4
+
+ h6.inline X-Axis
+
+ div.col-sm-4
+
+ h6.inline Y-Axis
+
+ div.col-sm-4
+
+ h6.inline Z-Axis
+
+ div.row
+
+ div.col-sm-4
+
+ div(data-target="edit-3d-domain-trans" data-hook="edit-3d-domain-x-trans" data-index="0")
+
+ div.col-sm-4
+
+ div(data-target="edit-3d-domain-trans" data-hook="edit-3d-domain-y-trans" data-index="1")
+
+ div.col-sm-4
+
+ div(data-target="edit-3d-domain-trans" data-hook="edit-3d-domain-z-trans" data-index="2")
+
+ div.inline
+
+ button.btn.btn-outline-primary(data-hook="build-3d-domain") Build Domain
+
+ div.mdl-edit-btn.saving-status.inline(data-hook="c3dd-in-progress")
+
+ div.spinner-grow.mr-2
+
+ span Creating domain ...
+
+ div.mdl-edit-btn.saved-status.inline(data-hook="c3dd-complete")
+
+ span Domain successfully created
+
+ div.mdl-edit-btn.save-error-status(data-hook="c3dd-error")
+
+ span(data-hook="c3dd-action-error")
diff --git a/client/domain-view/templates/editParticleView.pug b/client/domain-view/templates/editParticleView.pug
new file mode 100644
index 0000000000..0ab7f02d4e
--- /dev/null
+++ b/client/domain-view/templates/editParticleView.pug
@@ -0,0 +1,83 @@
+div.mx-1
+
+ div
+
+ h4.inline.mr-2 Type:
+
+ div.inline(data-target="type" data-hook=`particle-type-${this.viewIndex}`)
+
+ div
+
+ h4.inline.mr-2 Location:
+
+ div.inline.mr-2 (x:
+
+ div.inline.ml2(data-target=`location-${this.viewIndex}` data-hook=`x-coord-${this.viewIndex}` data-index="0")
+
+ div.inline.mr-2 , y:
+
+ div.inline.ml2(data-target=`location-${this.viewIndex}` data-hook=`y-coord-${this.viewIndex}` data-index="1")
+
+ div.inline.mr-2 , z:
+
+ div.inline.ml2(data-target=`location-${this.viewIndex}` data-hook=`z-coord-${this.viewIndex}` data-index="2")
+
+ div.inline )
+
+ h4 Particle Properties
+
+ div.pl-2(data-hook="particle-details")
+
+ hr
+
+ div.mb-3.mx-1.row.head.align-items-baseline
+
+ div.col-sm-2
+
+ h6.inline Mass
+
+ div.col-sm-2
+
+ h6.inline Volume
+
+ div.col-sm-2
+
+ h6.inline Density
+
+ div.col-sm-2
+
+ h6.inline Viscosity
+
+ div.col-sm-2
+
+ h6.inline Speed of Sound
+
+ div.col-sm-2
+
+ h6.inline Fixed
+
+ div.row
+
+ div.col-sm-2
+
+ div(data-hook=`particle-mass-${this.viewIndex}`)
+
+ div.col-sm-2
+
+ div(data-hook=`particle-vol-${this.viewIndex}`)
+
+ div.col-sm-2
+
+ div(data-hook=`particle-rho-${this.viewIndex}`)
+
+ div.col-sm-2
+
+ div(data-hook=`particle-nu-${this.viewIndex}`)
+
+ div.col-sm-2
+
+ div(data-hook=`particle-c-${this.viewIndex}`)
+
+ div.col-sm-2
+
+ input(type="checkbox" data-hook=`particle-fixed-${this.viewIndex}`)
\ No newline at end of file
diff --git a/client/domain-view/templates/editType.pug b/client/domain-view/templates/editType.pug
new file mode 100644
index 0000000000..0c630182e8
--- /dev/null
+++ b/client/domain-view/templates/editType.pug
@@ -0,0 +1,128 @@
+div.mx-1
+
+ if(this.model.collection.indexOf(this.model) !== 1)
+ hr
+
+ div.row
+
+ div.col-sm-1
+
+ div.pl-3
+
+ input(type="checkbox" data-hook="select" data-toggle="collapse" data-target="#collapse-type-details" + this.model.typeID)
+
+ div.col-sm-3
+
+ div(data-hook="type-name" data-target=this.model.typeID)
+
+ div.col-sm-3
+
+ div=this.model.numParticles
+
+ div.col-sm-3
+
+ button.btn.btn-outline-secondary.box-shadow(data-type=this.model.typeID data-hook='unassign-all') Un-Assign Particles
+
+ div.col-sm-2
+
+ button.btn.btn-outline-secondary.box-shadow.dropdown-toggle(
+ id="delete-domain-type",
+ data-toggle="dropdown",
+ aria-haspopup="true",
+ aria-expanded="false",
+ type="button"
+ ) Delete
+
+ ul.dropdown-menu(aria-labelledby="delete-domain-type")
+ li.dropdown-item(data-hook="delete-type" data-type=this.model.typeID) Type
+ li.dropdown-item(data-hook="delete-all" data-type=this.model.typeID) Type and Particles
+
+ div-mx-1.pl-2(data-hook="type-details")
+
+ div.collapse(id="collapse-type-details" + this.model.typeID)
+
+ hr
+
+ div.mb-3.mx-1.row.head.align-items-baseline
+
+ div.col-sm-2
+
+ h6.inline Mass
+
+ div.col-sm-2
+
+ h6.inline Volume
+
+ div.col-sm-2
+
+ h6.inline Density
+
+ div.col-sm-2
+
+ h6.inline Viscosity
+
+ div.col-sm-2
+
+ h6.inline Speed of Sound
+
+ div.col-sm-2
+
+ h6.inline Fixed
+
+ div.row
+
+ div.col-sm-2
+
+ div(data-hook="td-mass" data-target="type-defaults")
+
+ div.col-sm-2
+
+ div(data-hook="td-vol" data-target="type-defaults")
+
+ div.col-sm-2
+
+ div(data-hook="td-rho" data-target="type-defaults")
+
+ div.col-sm-2
+
+ div(data-hook="td-nu" data-target="type-defaults")
+
+ div.col-sm-2
+
+ div(data-hook="td-c" data-target="type-defaults")
+
+ div.col-sm-2
+
+ input(type="checkbox" data-hook="td-fixed")
+
+ hr
+
+ div.row.align-items-baseline
+
+ div.col-sm-10.align-items-baseline
+
+ h6.inline Geometry:
+
+ div.tooltip-icon.mr-3(data-html="true" data-toggle="tooltip" title=this.tooltips.geometry)
+
+ div.inline(style="width: 80%")
+
+ div(id="type-geometry" data-hook="type-geometry")
+
+ div.col-sm-2
+
+ button.btn.btn-outline-secondary.box-shadow(data-hook="apply-geometry") Apply
+
+ div.mdl-edit-btn.saving-status.inline(data-hook="tg-in-progress-" + this.model.typeID)
+
+ div.spinner-grow.mr-2
+
+ span Applying type to particles ...
+
+ div.mdl-edit-btn.saved-status.inline(data-hook="tg-complete-" + this.model.typeID)
+
+ span Type successfully applied
+
+ div.mdl-edit-btn.save-error-status(data-hook="tg-error-" + this.model.typeID)
+
+ span(data-hook="tg-action-error-" + this.model.typeID)
diff --git a/client/domain-view/templates/fillGeometryView.pug b/client/domain-view/templates/fillGeometryView.pug
new file mode 100644
index 0000000000..e9f2400326
--- /dev/null
+++ b/client/domain-view/templates/fillGeometryView.pug
@@ -0,0 +1,167 @@
+div.mx-1
+
+ div
+
+ h4.inline.mr-2 Type:
+
+ div.inline(data-hook="fill-geometry-type-select")
+
+ div
+
+ h4.inline.mr-2 Geometry:
+
+ div.inline(data-hook="fill-geometry-type-geometry")
+
+ div.mt-3
+
+ h5 Type Defaults
+
+ hr
+
+ div.mb-3.mx-1.row.head.align-items-baseline
+
+ div.col-sm-2
+
+ h6.inline Mass
+
+ div.col-sm-2
+
+ h6.inline Volume
+
+ div.col-sm-2
+
+ h6.inline Density
+
+ div.col-sm-2
+
+ h6.inline Viscosity
+
+ div.col-sm-2
+
+ h6.inline Speed of Sound
+
+ div.col-sm-2
+
+ h6.inline Fixed
+
+ div.row.pl-3
+
+ div.col-sm-2
+
+ div(data-hook="fill-geometry-td-mass")
+
+ div.col-sm-2
+
+ div(data-hook="fill-geometry-td-vol")
+
+ div.col-sm-2
+
+ div(data-hook="fill-geometry-td-rho")
+
+ div.col-sm-2
+
+ div(data-hook="fill-geometry-td-nu")
+
+ div.col-sm-2
+
+ div(data-hook="fill-geometry-td-c")
+
+ div.col-sm-2
+
+ input(type="checkbox" data-hook="fill-geometry-td-fixed" disabled)
+
+ h4.mt-3 Domain Limits
+
+ div
+
+ div.mx-1.row.head.align-items-baseline
+
+ div.col-sm-3
+
+ div.col-sm-3
+
+ h6.inline X-Axis
+
+ div.col-sm-3
+
+ h6.inline Y-Axis
+
+ div.col-sm-3
+
+ h6.inline Z-Axis
+
+ div.mt-3.mx-1.row
+
+ div.col-sm-3
+
+ h6.inline Minimum
+
+ div.col-sm-3
+
+ div(data-hook="fill-geometry-x-lim-min" data-target="fill-geometry-limitation" data-name="xmin")
+
+ div.col-sm-3
+
+ div(data-hook="fill-geometry-y-lim-min" data-target="fill-geometry-limitation" data-name="ymin")
+
+ div.col-sm-3
+
+ div(data-hook="fill-geometry-z-lim-min" data-target="fill-geometry-limitation" data-name="zmin")
+
+ hr
+
+ div.mx-1.row
+
+ div.col-sm-3
+
+ h6.inline Maximum
+
+ div.col-sm-3
+
+ div(data-hook="fill-geometry-x-lim-max" data-target="fill-geometry-limitation" data-name="xmax")
+
+ div.col-sm-3
+
+ div(data-hook="fill-geometry-y-lim-max" data-target="fill-geometry-limitation" data-name="ymax")
+
+ div.col-sm-3
+
+ div(data-hook="fill-geometry-z-lim-max" data-target="fill-geometry-limitation" data-name="zmax")
+
+ hr
+
+ div.mx-1.row
+
+ div.col-sm-3
+
+ h6.inline Spacing
+
+ div.col-sm-3
+
+ div(data-hook="fill-geometry-deltax" data-target="fill-geometry-delta" data-name="deltax")
+
+ div.col-sm-3
+
+ div(data-hook="fill-geometry-deltay" data-target="fill-geometry-delta" data-name="deltay")
+
+ div.col-sm-3
+
+ div(data-hook="fill-geometry-deltaz" data-target="fill-geometry-delta" data-name="deltaz")
+
+ div.inline
+
+ button.btn.btn-outline-primary(data-hook="fill-geometry" disabled) Fill with Particles
+
+ div.mdl-edit-btn.saving-status.inline(data-hook="fg-in-progress")
+
+ div.spinner-grow.mr-2
+
+ span Creating particles ...
+
+ div.mdl-edit-btn.saved-status.inline(data-hook="fg-complete")
+
+ span Particles successfully created
+
+ div.mdl-edit-btn.save-error-status(data-hook="fg-error")
+
+ span(data-hook="fg-action-error")
diff --git a/client/domain-view/templates/importMeshView.pug b/client/domain-view/templates/importMeshView.pug
new file mode 100644
index 0000000000..000a38acc3
--- /dev/null
+++ b/client/domain-view/templates/importMeshView.pug
@@ -0,0 +1,137 @@
+div.mx-1
+
+ div.my-3
+
+ span.inline.mr-2(for="meshfile") Please specify a mesh to import:
+
+ input(id="meshfile" type="file" name="meshfile" size="30" accept=".xml" required)
+
+ div.mb-3
+
+ span.inline.mr-2(for=typefile) Type descriptions (optional):
+
+ input(id="typefile" type="file" name="typefile" size="30" accept=".txt")
+
+ div.mt-3
+
+ h4.inline Advanced
+
+ button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#import-mesh-advanced" data-hook="collapse-import-mesh-advanced") +
+
+ div.collapse(id="import-mesh-advanced" data-hook="import-mesh-container")
+
+ div.mt-3
+
+ h5.inline.mr-2 Type:
+
+ div.inline(data-hook="import-mesh-type-select")
+
+ div.mt-3
+
+ h5 Type Defaults
+
+ hr
+
+ div.mb-3.mx-1.row.head.align-items-baseline
+
+ div.col-sm-2
+
+ h6.inline Mass
+
+ div.col-sm-2
+
+ h6.inline Volume
+
+ div.col-sm-2
+
+ h6.inline Density
+
+ div.col-sm-2
+
+ h6.inline Viscosity
+
+ div.col-sm-2
+
+ h6.inline Speed of Sound
+
+ div.col-sm-2
+
+ h6.inline Fixed
+
+ div.row.pl-3
+
+ div.col-sm-2
+
+ div(data-hook="import-mesh-td-mass")
+
+ div.col-sm-2
+
+ div(data-hook="import-mesh-td-vol")
+
+ div.col-sm-2
+
+ div(data-hook="import-mesh-td-rho")
+
+ div.col-sm-2
+
+ div(data-hook="import-mesh-td-nu")
+
+ div.col-sm-2
+
+ div(data-hook="import-mesh-td-c")
+
+ div.col-sm-2
+
+ input(type="checkbox" data-hook="import-mesh-td-fixed" disabled)
+
+ div.my-3
+
+ h5 Particle Transformations
+
+ hr
+
+ div.mb-3.mx-1.row.head.align-items-baseline
+
+ div.col-sm-4
+
+ h6.inline X-Axis
+
+ div.col-sm-4
+
+ h6.inline Y-Axis
+
+ div.col-sm-4
+
+ h6.inline Z-Axis
+
+ div.row
+
+ div.col-sm-4
+
+ div(data-target="import-mesh-trans" data-hook="import-mesh-x-trans" data-index="0")
+
+ div.col-sm-4
+
+ div(data-target="import-mesh-trans" data-hook="import-mesh-y-trans" data-index="1")
+
+ div.col-sm-4
+
+ div(data-target="import-mesh-trans" data-hook="import-mesh-z-trans" data-index="2")
+
+ div.inline
+
+ button.btn.btn-outline-primary.box-shadow(data-hook="import-mesh-particles" disabled) Import Mesh
+
+ div.mdl-edit-btn.saving-status.inline(data-hook="imp-in-progress")
+
+ div.spinner-grow.mr-2
+
+ span Importing mesh ...
+
+ div.mdl-edit-btn.saved-status.inline(data-hook="imp-complete")
+
+ span
+
+ div.mdl-edit-btn.save-error-status(data-hook="imp-error")
+
+ span(data-hook="imp-action-error")
\ No newline at end of file
diff --git a/client/domain-view/templates/limitsView.pug b/client/domain-view/templates/limitsView.pug
new file mode 100644
index 0000000000..3fb1868a60
--- /dev/null
+++ b/client/domain-view/templates/limitsView.pug
@@ -0,0 +1,175 @@
+div#domain-limits-editor.card
+
+ div.card-header.pb-0
+
+ h3.inline.mr-3 Limits
+
+ div.inline.mr-3
+
+ ul.nav.nav-tabs.card-header-tabs(id="domain-limits-tabs")
+
+ li.nav-item
+
+ a.nav-link.tab.active(data-hook="domain-limits-edit-tab" data-toggle="tab" href="#edit-domain-limits") Edit
+
+ li.nav-item
+
+ a.nav-link.tab(data-hook="domain-limits-view-tab" data-toggle="tab" href="#view-domain-limits") View
+
+ button.btn.btn-ouline-collapse(data-toggle="collapse" data-target="#domain-limits-section" data-hook="collapse-domain-limits") -
+
+ div.collapse.show(id="domain-limits-section" data-hook="domain-limits-section")
+
+ div.card-body.tab-content
+
+ div.tab-pane.active(id="edit-domain-limits" data-hook="edit-domain-limits")
+
+ div.mx-1.row.head.align-items-baseline
+
+ div.col-sm-3
+
+ div.col-sm-3
+
+ h6.inline X-Axis
+
+ div.col-sm-3
+
+ h6.inline Y-Axis
+
+ div.col-sm-3
+
+ h6.inline Z-Axis
+
+ div.mt-3.mx-1.row
+
+ div.col-sm-3
+
+ h6.inline Minimum
+
+ div.col-sm-3
+
+ div(data-hook="x-lim-min" data-target="limitation" data-name="x_lim" data-index="0")
+
+ div.col-sm-3
+
+ div(data-hook="y-lim-min" data-target="limitation" data-name="y_lim" data-index="0")
+
+ div.col-sm-3
+
+ div(data-hook="z-lim-min" data-target="limitation" data-name="z_lim" data-index="0")
+
+ hr
+
+ div.mx-1.row
+
+ div.col-sm-3
+
+ h6.inline Maximum
+
+ div.col-sm-3
+
+ div(data-hook="x-lim-max" data-target="limitation" data-name="x_lim" data-index="1")
+
+ div.col-sm-3
+
+ div(data-hook="y-lim-max" data-target="limitation" data-name="y_lim" data-index="1")
+
+ div.col-sm-3
+
+ div(data-hook="z-lim-max" data-target="limitation" data-name="z_lim" data-index="1")
+
+ hr
+
+ div.mx-1.row
+
+ div.col-sm-3
+
+ h6.inline Reflect
+
+ div.col-sm-3
+
+ input(type="checkbox" id="x-reflect" data-hook="reflect_x" data-target="reflect" disabled)
+
+ div.col-sm-3
+
+ input(type="checkbox" id="y-reflect" data-hook="reflect_y" data-target="reflect" disabled)
+
+ div.col-sm-3
+
+ input(type="checkbox" id="z-reflect" data-hook="reflect_z" data-target="reflect" disabled)
+
+ div.tab-pane(id="view-domain-limits" data-hook="view-domain-limits")
+
+ div.mx-1.row.head.align-items-baseline
+
+ div.col-sm-3
+
+ div.col-sm-3
+
+ h6.inline X-Axis
+
+ div.col-sm-3
+
+ h6.inline Y-Axis
+
+ div.col-sm-3
+
+ h6.inline Z-Axis
+
+ div.mt-3.mx-1.row
+
+ div.col-sm-3
+
+ h6.inline Minimum
+
+ div.col-sm-3
+
+ div=this.model.x_lim[0]
+
+ div.col-sm-3
+
+ div=this.model.y_lim[0]
+
+ div.col-sm-3
+
+ div=this.model.z_lim[0]
+
+ hr
+
+ div.mx-1.row
+
+ div.col-sm-3
+
+ h6.inline Maximum
+
+ div.col-sm-3
+
+ div=this.model.x_lim[1]
+
+ div.col-sm-3
+
+ div=this.model.y_lim[1]
+
+ div.col-sm-3
+
+ div=this.model.z_lim[1]
+
+ hr
+
+ div.mx-1.row
+
+ div.col-sm-3
+
+ h6.inline Reflect
+
+ div.col-sm-3
+
+ input(type="checkbox" id="x-reflect" data-hook="view-reflect_x" data-target="reflect" disabled)
+
+ div.col-sm-3
+
+ input(type="checkbox" id="y-reflect" data-hook="view-reflect_y" data-target="reflect" disabled)
+
+ div.col-sm-3
+
+ input(type="checkbox" id="z-reflect" data-hook="view-reflect_z" data-target="reflect" disabled)
\ No newline at end of file
diff --git a/client/domain-view/templates/propertiesView.pug b/client/domain-view/templates/propertiesView.pug
new file mode 100644
index 0000000000..b6bc871a94
--- /dev/null
+++ b/client/domain-view/templates/propertiesView.pug
@@ -0,0 +1,123 @@
+div#domain-properties-editor.card
+
+ div.card-header.pb-0
+
+ h3.inline.mr-3 Properties
+
+ div.inline.mr-3
+
+ ul.nav.nav-tabs.card-header-tabs(id="domain-properties-tabs")
+
+ li.nav-item
+
+ a.nav-link.tab.active(data-hook="domain-properties-edit-tab" data-toggle="tab" href="#edit-domain-properties") Edit
+
+ li.nav-item
+
+ a.nav-link.tab(data-hook="domain-properties-view-tab" data-toggle="tab" href="#view-domain-properties") View
+
+ button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#domain-properties-section" data-hook="collapse-domain-properties") -
+
+ div.collapse.show(id="domain-properties-section" data-hook="domain-properties-section")
+
+ div.card-body.tab-content
+
+ div.tab-pane.active(id="edit-domain-properties" data-hook="edit-domain-properties")
+
+ div
+
+ span.mr-3(for="#static-domain") Static Domain:
+
+ input(type="checkbox" id="static-domain" data-hook="static-domain")
+
+ hr
+
+ div.mx-1.row.head.align-items-baseline
+
+ div.col-sm-3
+
+ h6.inline Density
+
+ div.col-sm-3
+
+ h6.inline Gravity
+
+ div.col-sm-3
+
+ h6.inline Pressure
+
+ div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.pressure)
+
+ div.col-sm-3
+
+ h6.inline Speed of Sound
+
+ div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.speed)
+
+ div.mt-3.mx-1.row
+
+ div.col-sm-3
+
+ div(data-hook="density")
+
+ div.col-sm-3
+
+ div(data-target="gravity" data-hook="gravity-x" data-index="0")
+
+ div(data-target="gravity" data-hook="gravity-y" data-index="1")
+
+ div(data-target="gravity" data-hook="gravity-z" data-index="2")
+
+ div.col-sm-3
+
+ div(data-hook="pressure")
+
+ div.col-sm-3
+
+ div(data-hook="speed")
+
+ div.tab-pane(id="view-domain-properties" data-hook="view-domain-properties")
+
+ div
+
+ span.mr-3(for="#view-static-domain") Static Domain:
+
+ input(type="checkbox" id="view-static-domain" data-hook="view-static-domain" disabled)
+
+ hr
+
+ div.mx-1.row.head.align-items-baseline
+
+ div.col-sm-3
+
+ h6.inline Density
+
+ div.col-sm-3
+
+ h6.inline Gravity
+
+ div.col-sm-3
+
+ h6.inline Pressure
+
+ div.col-sm-3
+
+ h6.inline Speed of Sound
+
+ div.mt-3.mx-1.row
+
+ div.col-sm-3
+
+ div=this.model.rho_0
+
+ div.col-sm-3
+
+ div=`(X: ${this.model.gravity[0]}, Y: ${this.model.gravity[1]}, Z: ${this.model.gravity[2]})`
+
+ div.col-sm-3
+
+ div=this.model.p_0
+
+ div.col-sm-3
+
+ div=this.model.c_0
\ No newline at end of file
diff --git a/client/domain-view/templates/quickviewType.pug b/client/domain-view/templates/quickviewType.pug
new file mode 100644
index 0000000000..391b93bbc7
--- /dev/null
+++ b/client/domain-view/templates/quickviewType.pug
@@ -0,0 +1,9 @@
+div
+
+ hr
+
+ div.row.mx-1
+
+ div.col-sm-6=this.model.name
+
+ div.col-sm-6=this.model.numParticles
diff --git a/client/domain-view/templates/typesDescriptionView.pug b/client/domain-view/templates/typesDescriptionView.pug
new file mode 100644
index 0000000000..f4f2ab190e
--- /dev/null
+++ b/client/domain-view/templates/typesDescriptionView.pug
@@ -0,0 +1,36 @@
+div.mx-1
+
+ div.text-info(data-hook="type-location-message" style="display: none")
+ | There are multiple type files with that name, please select a location
+
+ div
+
+ div.inline.mr-3
+
+ span.inline.mr-2(for="file-select") Select type description file:
+
+ div.inline(id="file-select" data-hook="file-select")
+
+ div.inline(data-hook="file-location-container" style="display: none")
+
+ span.inlinemr-2(for="file-location-select") Location:
+
+ div.inline(id="file-location-select" data-hook="file-location-select")
+
+ div.my-3
+
+ button.btn.btn-outline-primary.box-shadow(data-hook="set-particle-types-btn" disabled) Set Types
+
+ div.mdl-edit-btn.saving-status.inline(data-hook="st-in-progress")
+
+ div.spinner-grow.mr-2
+
+ span Setting types ...
+
+ div.mdl-edit-btn.saved-status.inline(data-hook="st-complete")
+
+ span
+
+ div.mdl-edit-btn.save-error-status(data-hook="st-error")
+
+ span(data-hook="st-error-action")
\ No newline at end of file
diff --git a/client/domain-view/templates/typesView.pug b/client/domain-view/templates/typesView.pug
new file mode 100644
index 0000000000..d51f270214
--- /dev/null
+++ b/client/domain-view/templates/typesView.pug
@@ -0,0 +1,75 @@
+div#domain-types-editor.card
+
+ div.card-header.pb-0
+
+ h3.inline.mr-3 Types
+
+ div.inline.mr-3
+
+ ul.nav.nav-tabs.card-header-tabs(id="domain-types-teabs")
+
+ li.nav-item
+
+ a.nav-link.tab.active(data-hook="domain-types-edit-tab" data-toggle="tab" href="#edit-domain-types") Edit
+
+ li.nav-item
+
+ a.nav-link.tab(data-hook="domain-types-view-tab" data-toggle="tab" href="#view-domain-types") View
+
+ button.btn.btn-outline-collapse(data-toggle="collapse" data-target="#domain-types-section" data-hook="collapse-domain-types") -
+
+ div.collapse.show(id="domain-types-section" data-hook="domain-types-section")
+
+ div.card-body.tab-content
+
+ div
+
+ span.mr-3.inline(for="unassigned-type-count") Number of Un-Assigned Particles:
+
+ div.inline(id="unassigned-type-count" data-hook="unassigned-type-count")=this.collection.models[0].numParticles
+
+ div(data-hook="domain-error")
+
+ p.text-danger A domain cannot have any un-assigned particles.
+
+ div.tab-pane.active(id="edit-domain-types" data-hook="edit-domain-types")
+
+ hr
+
+ div.mx-1.row.head.align-items-baseline
+
+ div.col-sm-1
+
+ h6.inline Edit
+
+ div.col-sm-3
+
+ h6.inline Name
+
+ div.col-sm-8
+
+ h6.inline Number of Particles
+
+ div.my-3(data-hook="edit-domain-types-list")
+
+ button.btn.btn-outline-primary.box-shadow(data-hook="add-domain-type") Add Type
+
+ div.tab-pane(id="view-domain-types" data-hook="view-domain-types")
+
+ hr
+
+ div.mx-1.row.head.align-items-baseline
+
+ div.col-sm-3
+
+ h6.inline Name
+
+ div.col-sm-3
+
+ h6.inline Numbe of Particles
+
+ div.col-sm-6
+
+ h6.inline Defaults
+
+ div.my-3(data-hook="view-domain-types-list")
\ No newline at end of file
diff --git a/client/domain-view/templates/viewParticle.pug b/client/domain-view/templates/viewParticle.pug
new file mode 100644
index 0000000000..fd290ef052
--- /dev/null
+++ b/client/domain-view/templates/viewParticle.pug
@@ -0,0 +1,117 @@
+div
+
+ h5 Properties
+
+ hr.mt-2.mb-1
+
+ div.row.mx-1
+
+ div.col-sm-6
+
+ h6 ID:
+
+ div.col-sm-6
+
+ div.inline=this.model.particle_id
+
+ hr.my-1
+
+ div.row.mx-1
+
+ div.col-sm-6
+
+ h6 Type:
+
+ div.col-sm-6
+
+ div.inline=this.type
+
+ hr.my-1
+
+ div.row.mx-1
+
+ div.col-sm-6
+
+ h6 Location:
+
+ div.col-sm-6
+
+ div.inline
+
+ div=`X: ${this.model.point[0]}`
+
+ div=`Y: ${this.model.point[1]}`
+
+ div=`Z: ${this.model.point[2]}`
+
+ hr.my-1
+
+ div.row.mx-1
+
+ div.col-sm-6
+
+ h6 Mass:
+
+ div.col-sm-6
+
+ div.inline=this.model.mass
+
+ hr.my-1
+
+ div.row.mx-1
+
+ div.col-sm-6
+
+ h6 Volume:
+
+ div.col-sm-6
+
+ div.inline=this.model.volume
+
+ hr.my-1
+
+ div.row.mx-1
+
+ div.col-sm-6
+
+ h6 Density:
+
+ div.col-sm-6
+
+ div.inline=this.model.rho
+
+ hr.my-1
+
+ div.row.mx-1
+
+ div.col-sm-6
+
+ h6 Viscosity:
+
+ div.col-sm-6
+
+ div.inline=this.model.nu
+
+ hr.my-1
+
+ div.row.mx-1
+
+ div.col-sm-6
+
+ h6 Speed of Sound:
+
+ div.col-sm-6
+
+ div.inline=this.model.c
+
+ hr.my-1
+
+ div.row.mx-1
+
+ div.col-sm-6
+
+ h6 Fixed:
+
+ div.col-sm-6
+
+ input.inline(type="checkbox" checked=this.model.fixed disabled)
diff --git a/client/domain-view/templates/viewType.pug b/client/domain-view/templates/viewType.pug
new file mode 100644
index 0000000000..5a0277bf4a
--- /dev/null
+++ b/client/domain-view/templates/viewType.pug
@@ -0,0 +1,48 @@
+div.mx-1
+
+ if(this.model.collection.indexOf(this.model) !== 1)
+ hr
+
+ div.row
+
+ div.col-sm-6
+
+ div.row
+
+ div.col-sm-6
+
+ div=this.model.name
+
+ div.col-sm-6
+
+ div=this.model.numParticles
+
+ hr
+
+ h6.inline.mr-2 Geometry:
+
+ div.inline=this.model.geometry
+
+ div.col-sm-6
+
+ div.row
+
+ div.col-sm-6
+
+ div="Mass: " + this.model.mass
+
+ div="Volume: " + this.model.volume
+
+ div="Density: " + this.model.rho
+
+ div.col-sm-6
+
+ div="Viscosity: " + this.model.nu
+
+ div="Speed of Sound: " + this.model.c
+
+ div
+
+ span.mr-3(for="view-td-fixed") Fixed:
+
+ input(type="checkbox" data-hook="view-td-fixed" disabled)
diff --git a/client/domain-view/views/edit-3D-domain-view.js b/client/domain-view/views/edit-3D-domain-view.js
new file mode 100644
index 0000000000..cfd4c503b4
--- /dev/null
+++ b/client/domain-view/views/edit-3D-domain-view.js
@@ -0,0 +1,302 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 StochSS developers.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+let $ = require('jquery');
+let path = require('path');
+//support files
+let app = require('../../app');
+let tests = require('../../views/tests');
+//views
+let View = require('ampersand-view');
+let InputView = require('../../views/input');
+let SelectView = require('ampersand-select-view');
+//templates
+let template = require('../templates/edit3DDomainView.pug');
+
+module.exports = View.extend({
+ template: template,
+ events: {
+ 'change [data-target=edit-3d-domain-n]' : 'handleNumParticlesUpdate',
+ 'change [data-target=edit-3d-domain-limitation]' : 'handleLimitsUpdate',
+ 'change [data-hook=edit-3d-domain-type-select]' : 'handleTypeUpdate',
+ 'change [data-target=edit-3d-domain-trans]' : 'handleTransformationupdate',
+ 'click [data-hook=collapse-3D-domain-advanced]' : 'changeCollapseButtonText',
+ 'click [data-hook=build-3d-domain]' : 'handleBuildDomain'
+ },
+ initialize: function (attrs, options) {
+ View.prototype.initialize.apply(this, arguments);
+ this.data = {
+ "nx":1, "ny":1, "nz":1,
+ "xLim":[0, 0], "yLim":[0, 0], "zLim":[0, 0],
+ "type": null, "transformation": null
+ }
+ this.transformation = [0, 0, 0];
+ },
+ render: function (attrs, options) {
+ View.prototype.render.apply(this, arguments);
+ this.type = this.parent.model.types.get(0, "typeID");
+ this.updateTotalParticles();
+ this.updateTypeDefaults();
+ },
+ changeCollapseButtonText: function (e) {
+ app.changeCollapseButtonText(this, e);
+ },
+ completeAction: function () {
+ $(this.queryByHook("c3dd-in-progress")).css("display", "none");
+ $(this.queryByHook("c3dd-complete")).css("display", "inline-block");
+ setTimeout(() => {
+ $(this.queryByHook("c3dd-complete")).css("display", "none");
+ }, 5000);
+ },
+ errorAction: function (action) {
+ $(this.queryByHook("c3dd-in-progress")).css("display", "none");
+ $(this.queryByHook("c3dd-action-error")).text(action);
+ $(this.queryByHook("c3dd-error")).css("display", "block");
+ },
+ handleBuildDomain: function () {
+ this.startAction();
+ this.transformation.every((value) => {
+ if(value !== 0) {
+ this.data.transformation = this.transformation;
+ return false;
+ }
+ return true;
+ });
+ this.data.domainExists = this.parent.model.particles.length > 0;
+ let endpoint = path.join(app.getApiPath(), "spatial-model/3d-domain");
+ app.postXHR(endpoint, this.data, {
+ success: (err, response, body) => {
+ this.parent.add3DDomain(body.limits, body.particles);
+ this.completeAction();
+ $('html, body').animate({
+ scrollTop: $("#domain-figure").offset().top
+ }, 20);
+ },
+ error: (err, response, body) => {
+ this.errorAction(body.Message);
+ }
+ });
+ },
+ handleLimitsUpdate: function (e) {
+ let key = e.target.parentElement.parentElement.dataset.name;
+ let index = Number(e.target.parentElement.parentElement.dataset.index);
+ this.data[key][index] = Number(e.target.value);
+ },
+ handleNumParticlesUpdate: function (e) {
+ let key = e.target.parentElement.parentElement.dataset.key;
+ this.data[key] = Number(e.target.value);
+ this.updateTotalParticles();
+ },
+ handleTransformationupdate: function (e) {
+ let index = e.target.parentElement.parentElement.dataset.index;
+ this.transformation[index] = Number(e.target.value);
+ },
+ handleTypeUpdate: function (e) {
+ let typeID = Number(e.target.value);
+ this.type = this.parent.model.types.get(typeID, "typeID");
+ this.updateTypeDefaults();
+ },
+ startAction: function () {
+ $(this.queryByHook("c3dd-complete")).css("display", "none");
+ $(this.queryByHook("c3dd-error")).css("display", "none");
+ $(this.queryByHook("c3dd-in-progress")).css("display", "inline-block");
+ },
+ update: function () {},
+ updateValid: function () {},
+ updateTotalParticles: function () {
+ let n = this.data.nx * this.data.ny * this.data.nz;
+ $(this.queryByHook("3d-domain-n")).text(n);
+ $(this.queryByHook("build-3d-domain")).prop("disabled", n <= 0);
+ },
+ updateTypeDefaults: function () {
+ this.data.typeID = this.type.typeID;
+ this.data.type = {
+ "type_id": this.type.name, "mass": this.type.mass, "rho": this.type.rho,
+ "nu": this.type.nu, "c": this.type.c, "fixed": this.type.fixed
+ }
+ $(this.queryByHook("edit-3d-domain-td-mass")).text(this.type.mass);
+ $(this.queryByHook("edit-3d-domain-td-vol")).text(this.type.volume);
+ $(this.queryByHook("edit-3d-domain-td-rho")).text(this.type.rho);
+ $(this.queryByHook("edit-3d-domain-td-nu")).text(this.type.nu);
+ $(this.queryByHook("edit-3d-domain-td-c")).text(this.type.c);
+ $(this.queryByHook("edit-3d-domain-td-fixed")).prop('checked', this.type.fixed);
+ },
+ subviews: {
+ nXInputView: {
+ hook: "edit-3d-domain-nx",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'nx',
+ valueType: 'number',
+ tests: tests.valueTests,
+ value: this.data.nx
+ });
+ }
+ },
+ nYInputView: {
+ hook: "edit-3d-domain-ny",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'ny',
+ valueType: 'number',
+ tests: tests.valueTests,
+ value: this.data.ny
+ });
+ }
+ },
+ nZInputView: {
+ hook: "edit-3d-domain-nz",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'nz',
+ valueType: 'number',
+ tests: tests.valueTests,
+ value: this.data.nz
+ });
+ }
+ },
+ xMinLimInputView: {
+ hook: "edit-3d-domain-x-lim-min",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'x-lim-min',
+ valueType: 'number',
+ value: this.data.xLim[0]
+ });
+ }
+ },
+ yMinLimInputView: {
+ hook: "edit-3d-domain-y-lim-min",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'y-lim-min',
+ valueType: 'number',
+ value: this.data.yLim[0]
+ });
+ }
+ },
+ zMinLimInputView: {
+ hook: "edit-3d-domain-z-lim-min",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'z-lim-min',
+ valueType: 'number',
+ value: this.data.zLim[0]
+ });
+ }
+ },
+ xMaxLimInputView: {
+ hook: "edit-3d-domain-x-lim-max",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'x-lim-max',
+ valueType: 'number',
+ value: this.data.xLim[1]
+ });
+ }
+ },
+ yMaxLimInputView: {
+ hook: "edit-3d-domain-y-lim-max",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'y-lim-max',
+ valueType: 'number',
+ value: this.data.yLim[1]
+ });
+ }
+ },
+ zMaxLimInputView: {
+ hook: "edit-3d-domain-z-lim-max",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'z-lim-max',
+ valueType: 'number',
+ value: this.data.zLim[1]
+ });
+ }
+ },
+ typeSelectView: {
+ hook: "edit-3d-domain-type-select",
+ prepareView: function (el) {
+ return new SelectView({
+ name: 'type',
+ required: true,
+ idAttribute: 'typeID',
+ textAttribute: 'name',
+ eagerValidate: true,
+ options: this.parent.model.types,
+ value: this.type
+ });
+ }
+ },
+ xTransInputView: {
+ hook: "edit-3d-domain-x-trans",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'x-transformation',
+ valueType: 'number',
+ value: this.transformation[0]
+ });
+ }
+ },
+ yTransInputView: {
+ hook: "edit-3d-domain-y-trans",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'y-transformation',
+ valueType: 'number',
+ value: this.transformation[1]
+ });
+ }
+ },
+ zTransInputView: {
+ hook: "edit-3d-domain-z-trans",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'z-transformation',
+ valueType: 'number',
+ value: this.transformation[2]
+ });
+ }
+ }
+ }
+});
\ No newline at end of file
diff --git a/client/domain-view/views/fill-geometry-view.js b/client/domain-view/views/fill-geometry-view.js
new file mode 100644
index 0000000000..f9f507f3a9
--- /dev/null
+++ b/client/domain-view/views/fill-geometry-view.js
@@ -0,0 +1,267 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 StochSS developers.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+let $ = require('jquery');
+let path = require('path');
+//support files
+let app = require('../../app');
+let tests = require('../../views/tests');
+//views
+let View = require('ampersand-view');
+let InputView = require('../../views/input');
+let SelectView = require('ampersand-select-view');
+//templates
+let template = require('../templates/fillGeometryView.pug');
+
+module.exports = View.extend({
+ template: template,
+ events: {
+ 'change [data-hook=fill-geometry-type-select]' : 'handleTypeUpdate',
+ 'change [data-target=fill-geometry-limitation]' : 'handleDataUpdate',
+ 'change [data-target=fill-geometry-delta]' : 'handleDataUpdate',
+ 'click [data-hook=fill-geometry]' : 'handleFillGeometry'
+ },
+ initialize: function (attrs, options) {
+ View.prototype.initialize.apply(this, arguments);
+ this.data = {
+ "xmin":0, "ymin":0, "zmin":0, "xmax":0, "ymax":0,
+ "zmax":0, "deltax": 0, "deltay": 0, "deltaz": 0
+ }
+ this.type = null;
+ },
+ render: function (attrs, options) {
+ View.prototype.render.apply(this, arguments);
+ this.getTypes();
+ this.renderTypeSelectView();
+ },
+ completeAction: function () {
+ $(this.queryByHook("fg-in-progress")).css("display", "none");
+ $(this.queryByHook("fg-complete")).css("display", "inline-block");
+ setTimeout(() => {
+ $(this.queryByHook("fg-complete")).css("display", "none");
+ }, 5000);
+ },
+ disable: function () {
+ if(this.data.xmin === this.data.xmax) { return true; }
+ if(this.data.ymin === this.data.ymax) { return true; }
+ if(this.data.zmin === this.data.zmax) { return true; }
+ if(this.data.deltax === 0) { return true; }
+ if(this.data.deltay === 0) { return true; }
+ if(this.data.deltaz === 0) { return true; }
+ if(!this.type) { return true; }
+ return false;
+ },
+ errorAction: function (action) {
+ $(this.queryByHook("fg-in-progress")).css("display", "none");
+ $(this.queryByHook("fg-action-error")).html(action);
+ $(this.queryByHook("fg-error")).css("display", "block");
+ },
+ getTypes: function () {
+ this.types = this.parent.model.types.filter((type) => {
+ return Boolean(type.geometry);
+ });
+ },
+ handleDataUpdate: function (e) {
+ let key = e.target.parentElement.parentElement.dataset.name;
+ this.data[key] = Number(e.target.value);
+ this.toggleFillGeometry();
+ },
+ handleFillGeometry: function () {
+ this.startAction();
+ let data = {kwargs: this.data, type: this.type}
+ let endpoint = path.join(app.getApiPath(), 'spatial-model/fill-geometry');
+ app.postXHR(endpoint, data, {
+ success: (err, response, body) => {
+ this.parent.add3DDomain(body.limits, body.particles);
+ this.completeAction();
+ },
+ error: (err, response, body) => {
+ if(body.Traceback.includes("SyntaxError")) {
+ var tracePart = body.Traceback.split('\n').slice(6)
+ tracePart.splice(2, 2)
+ tracePart[1] = tracePart[1].replace(new RegExp(' ', 'g'), ' ')
+ var errorBlock = `
${body.Message}
${tracePart.join('
')}
`
+ }else{
+ var errorBlock = body.Message
+ }
+ this.errorAction(errorBlock);
+ }
+ });
+ },
+ handleTypeUpdate: function (e) {
+ if(e.target.value) {
+ let typeID = Number(e.target.value);
+ this.type = this.parent.model.types.get(typeID, "typeID");
+ }else{
+ this.type = null;
+ }
+ this.updateTypeDefaults();
+ this.toggleFillGeometry();
+ },
+ renderTypeSelectView: function () {
+ if(this.typeSelectView) {
+ this.typeSelectView.remove();
+ }
+ if(this.types) {
+ var options = this.types.map((type) => {
+ return [type.typeID, type.name];
+ });
+ }else{
+ var options = [];
+ }
+ this.typeSelectView = new SelectView({
+ name: 'type',
+ required: false,
+ idAttribute: 'typeID',
+ textAttribute: 'name',
+ eagerValidate: true,
+ options: options,
+ unselectedText: "Select a Type"
+ });
+ app.registerRenderSubview(this, this.typeSelectView, "fill-geometry-type-select");
+ },
+ startAction: function () {
+ $(this.queryByHook("fg-complete")).css("display", "none");
+ $(this.queryByHook("fg-error")).css("display", "none");
+ $(this.queryByHook("fg-in-progress")).css("display", "inline-block");
+ },
+ toggleFillGeometry: function () {
+ $(this.queryByHook('fill-geometry')).prop('disabled', this.disable());
+ },
+ update: function() {},
+ updateTypeDefaults: function () {
+ $(this.queryByHook("fill-geometry-type-geometry")).text(this.type.geometry || "");
+ $(this.queryByHook("fill-geometry-td-mass")).text(this.type.mass || "");
+ $(this.queryByHook("fill-geometry-td-vol")).text(this.type.volume || "");
+ $(this.queryByHook("fill-geometry-td-rho")).text(this.type.rho || "");
+ $(this.queryByHook("fill-geometry-td-nu")).text(this.type.nu || "0");
+ $(this.queryByHook("fill-geometry-td-c")).text(this.type.c || "");
+ $(this.queryByHook("fill-geometry-td-fixed")).prop('checked', this.type.fixed || false);
+ },
+ updateValid: function () {},
+ subviews: {
+ xMinLimInputView: {
+ hook: "fill-geometry-x-lim-min",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'x-lim-min',
+ valueType: 'number',
+ value: this.data.xmin
+ });
+ }
+ },
+ yMinLimInputView: {
+ hook: "fill-geometry-y-lim-min",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'y-lim-min',
+ valueType: 'number',
+ value: this.data.ymin
+ });
+ }
+ },
+ zMinLimInputView: {
+ hook: "fill-geometry-z-lim-min",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'z-lim-min',
+ valueType: 'number',
+ value: this.data.zmin
+ });
+ }
+ },
+ xMaxLimInputView: {
+ hook: "fill-geometry-x-lim-max",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'x-lim-max',
+ valueType: 'number',
+ value: this.data.xmax
+ });
+ }
+ },
+ yMaxLimInputView: {
+ hook: "fill-geometry-y-lim-max",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'y-lim-max',
+ valueType: 'number',
+ value: this.data.ymax
+ });
+ }
+ },
+ zMaxLimInputView: {
+ hook: "fill-geometry-z-lim-max",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'z-lim-max',
+ valueType: 'number',
+ value: this.data.zmax
+ });
+ }
+ },
+ deltaxInputView: {
+ hook: "fill-geometry-deltax",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'deltax',
+ valueType: 'number',
+ value: this.data.deltax
+ });
+ }
+ },
+ deltayInputView: {
+ hook: "fill-geometry-deltay",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'deltay',
+ valueType: 'number',
+ value: this.data.deltay
+ });
+ }
+ },
+ deltazInputView: {
+ hook: "fill-geometry-deltaz",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'deltaz',
+ valueType: 'number',
+ value: this.data.deltaz
+ });
+ }
+ },
+ }
+});
\ No newline at end of file
diff --git a/client/domain-view/views/import-mesh-view.js b/client/domain-view/views/import-mesh-view.js
new file mode 100644
index 0000000000..29bf950f57
--- /dev/null
+++ b/client/domain-view/views/import-mesh-view.js
@@ -0,0 +1,187 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 StochSS developers.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+let $ = require('jquery');
+let path = require('path');
+//support files
+let app = require('../../app');
+let tests = require('../../views/tests');
+//views
+let View = require('ampersand-view');
+let InputView = require('../../views/input');
+let SelectView = require('ampersand-select-view');
+//templates
+let template = require('../templates/importMeshView.pug');
+
+module.exports = View.extend({
+ template: template,
+ events: {
+ 'change #meshfile' : 'setMeshFile',
+ 'change #typefile' : 'setTypeFile',
+ 'change [data-hook=import-mesh-type-select]' : 'handleTypeUpdate',
+ 'change [data-target=import-mesh-trans]' : 'handleTransformationUpdate',
+ 'click [data-hook=collapse-import-mesh-advanced]' : 'changeCollapseButtonText',
+ 'click [data-hook=import-mesh-particles]' : 'handleImportMesh'
+ },
+ initialize: function (attrs, options) {
+ View.prototype.initialize.apply(this, arguments);
+ this.meshFile = null;
+ this.typeFile = null;
+ this.data = {"type": null, "transformation": null};
+ this.transformation = [0, 0, 0];
+ },
+ render: function (attrs, options) {
+ View.prototype.render.apply(this, arguments);
+ this.type = this.parent.model.types.get(0, "typeID");
+ this.updateTypeDefaults();
+ },
+ changeCollapseButtonText: function (e) {
+ app.changeCollapseButtonText(this, e);
+ },
+ completeAction: function () {
+ $(this.queryByHook("imp-in-progress")).css("display", "none");
+ $(this.queryByHook("imp-complete")).css("display", "inline-block");
+ setTimeout(() => {
+ $(this.queryByHook("imp-complete")).css("display", "none");
+ }, 5000);
+ },
+ errorAction: function (action) {
+ $(this.queryByHook("imp-in-progress")).css("display", "none");
+ $(this.queryByHook("imp-action-error")).text(action);
+ $(this.queryByHook("imp-error")).css("display", "block");
+ },
+ handleImportMesh: function () {
+ this.startAction();
+ this.transformation.every((value) => {
+ if(value !== 0) {
+ this.data.transformation = this.transformation;
+ return false;
+ }
+ return true;
+ });
+ let formData = new FormData();
+ formData.append("datafile", this.meshFile);
+ formData.append("particleData", JSON.stringify(this.data));
+ if(this.typeFile) {
+ formData.append("typefile", this.typeFile);
+ }
+ let endpoint = path.join(app.getApiPath(), 'spatial-model/import-mesh');
+ app.postXHR(endpoint, formData, {
+ success: (err, response, body) => {
+ body = JSON.parse(body);
+ this.parent.addMeshDomain(body.limits, body.particles, body.types, this.parent.model.particles.length > 0);
+ this.completeAction();
+ $('html, body').animate({
+ scrollTop: $("#domain-figure").offset().top
+ }, 20);
+ },
+ error: (err, response, body) => {
+ body = JSON.parse(body);
+ this.errorAction(body.Message);
+ }
+ }, false);
+ },
+ handleTransformationUpdate: function (e) {
+ let index = e.target.parentElement.parentElement.dataset.index;
+ this.transformation[index] = Number(e.target.value);
+ },
+ handleTypeUpdate: function (e) {
+ let typeID = Number(e.target.value);
+ this.type = this.parent.model.types.get(typeID, "typeID");
+ this.updateTypeDefaults();
+ },
+ setMeshFile: function (e) {
+ this.meshFile = e.target.files[0];
+ $(this.queryByHook("import-mesh-particles")).prop('disabled', !this.meshFile);
+ },
+ setTypeFile: function (e) {
+ this.typeFile = e.target.files[0];
+ },
+ startAction: function () {
+ $(this.queryByHook("imp-complete")).css("display", "none");
+ $(this.queryByHook("imp-error")).css("display", "none");
+ $(this.queryByHook("imp-in-progress")).css("display", "inline-block");
+ },
+ update: function () {},
+ updateValid: function () {},
+ updateTypeDefaults: function () {
+ this.data.typeID = this.type.typeID;
+ this.data.type = {
+ "type_id": this.type.name, "mass": this.type.mass, "rho": this.type.rho,
+ "nu": this.type.nu, "c": this.type.c, "fixed": this.type.fixed
+ }
+ $(this.queryByHook("import-mesh-td-mass")).text(this.type.mass);
+ $(this.queryByHook("import-mesh-td-vol")).text(this.type.volume);
+ $(this.queryByHook("import-mesh-td-rho")).text(this.type.rho);
+ $(this.queryByHook("import-mesh-td-nu")).text(this.type.nu);
+ $(this.queryByHook("import-mesh-td-c")).text(this.type.c);
+ $(this.queryByHook("import-mesh-td-fixed")).prop('checked', this.type.fixed);
+ },
+ subviews: {
+ typeSelectView: {
+ hook: "import-mesh-type-select",
+ prepareView: function (el) {
+ return new SelectView({
+ name: 'type',
+ required: true,
+ idAttribute: 'typeID',
+ textAttribute: 'name',
+ eagerValidate: true,
+ options: this.parent.model.types,
+ value: this.type
+ });
+ }
+ },
+ xTransInputView: {
+ hook: "import-mesh-x-trans",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'x-transformation',
+ valueType: 'number',
+ value: this.transformation[0]
+ });
+ }
+ },
+ yTransInputView: {
+ hook: "import-mesh-y-trans",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'y-transformation',
+ valueType: 'number',
+ value: this.transformation[1]
+ });
+ }
+ },
+ zTransInputView: {
+ hook: "import-mesh-z-trans",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'z-transformation',
+ valueType: 'number',
+ value: this.transformation[2]
+ });
+ }
+ }
+ }
+});
\ No newline at end of file
diff --git a/client/domain-view/views/limits-view.js b/client/domain-view/views/limits-view.js
new file mode 100644
index 0000000000..09f740b160
--- /dev/null
+++ b/client/domain-view/views/limits-view.js
@@ -0,0 +1,162 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 StochSS developers.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+let $ = require('jquery');
+//support files
+let app = require('../../app');
+//views
+let View = require('ampersand-view');
+let InputView = require('../../views/input');
+//templates
+let template = require('../templates/limitsView.pug');
+
+module.exports = View.extend({
+ template: template,
+ events: {
+ 'click [data-hook=collapse-domain-limits]' : 'changeCollapseButtonText',
+ 'change [data-target=limitation]' : 'setLimitation',
+ 'change [data-target=reflect]' : 'setBoundaryCondition'
+ },
+ initialize: function (attrs, options) {
+ View.prototype.initialize.apply(this, arguments);
+ this.readOnly = attrs.readOnly ? attrs.readOnly : false;
+ },
+ render: function (attrs, options) {
+ View.prototype.render.apply(this, arguments);
+ if(this.readOnly) {
+ $(this.queryByHook('domain-limits-edit-tab')).addClass("disabled");
+ $(".nav .disabled>a").on("click", function(e) {
+ e.preventDefault();
+ return false;
+ });
+ $(this.queryByHook('domain-limits-view-tab')).tab('show');
+ $(this.queryByHook('edit-domain-limits')).removeClass('active');
+ $(this.queryByHook('view-domain-limits')).addClass('active');
+ }else{
+ this.renderXMinLimitInputView();
+ this.renderYMinLimitInputView();
+ this.renderZMinLimitInputView();
+ this.renderXMaxLimitInputView();
+ this.renderYMaxLimitInputView();
+ this.renderZMaxLimitInputView();
+ $(this.queryByHook("reflect_x")).prop("checked", this.model.boundary_condition.reflect_x);
+ $(this.queryByHook("reflect_y")).prop("checked", this.model.boundary_condition.reflect_y);
+ $(this.queryByHook("reflect_z")).prop("checked", this.model.boundary_condition.reflect_z);
+ }
+ $(this.queryByHook("view-reflect_x")).prop("checked", this.model.boundary_condition.reflect_x);
+ $(this.queryByHook("view-reflect_y")).prop("checked", this.model.boundary_condition.reflect_y);
+ $(this.queryByHook("view-reflect_z")).prop("checked", this.model.boundary_condition.reflect_z);
+ },
+ changeCollapseButtonText: function (e) {
+ app.changeCollapseButtonText(this, e);
+ },
+ renderXMinLimitInputView: function () {
+ if(this.xMinLimitInputView) {
+ this.xMinLimitInputView.remove();
+ }
+ this.xMinLimitInputView = new InputView({
+ parent: this,
+ required: true,
+ name: 'x-lim-min',
+ valueType: 'number',
+ value: this.model.x_lim[0] || 0
+ });
+ app.registerRenderSubview(this, this.xMinLimitInputView, "x-lim-min");
+ },
+ renderYMinLimitInputView: function () {
+ if(this.yMinLimitInputView) {
+ this.yMinLimitInputView.remove();
+ }
+ this.yMinLimitInputView = new InputView({
+ parent: this,
+ required: true,
+ name: 'y-lim-min',
+ valueType: 'number',
+ value: this.model.y_lim[0] || 0
+ });
+ app.registerRenderSubview(this, this.yMinLimitInputView, "y-lim-min");
+ },
+ renderZMinLimitInputView: function () {
+ if(this.zMinLimitInputView) {
+ this.zMinLimitInputView.remove();
+ }
+ this.zMinLimitInputView = new InputView({
+ parent: this,
+ required: true,
+ name: 'z-lim-min',
+ valueType: 'number',
+ value: this.model.z_lim[0] || 0
+ });
+ app.registerRenderSubview(this, this.zMinLimitInputView, "z-lim-min");
+ },
+ renderXMaxLimitInputView: function () {
+ if(this.xMaxLimitInputView) {
+ this.xMaxLimitInputView.remove();
+ }
+ this.xMaxLimitInputView = new InputView({
+ parent: this,
+ required: true,
+ name: 'x-lim-max',
+ valueType: 'number',
+ value: this.model.x_lim[1] || 0
+ });
+ app.registerRenderSubview(this, this.xMaxLimitInputView, "x-lim-max");
+ },
+ renderYMaxLimitInputView: function () {
+ if(this.yMaxLimitInputView) {
+ this.yMaxLimitInputView.remove();
+ }
+ this.yMaxLimitInputView = new InputView({
+ parent: this,
+ required: true,
+ name: 'y-lim-max',
+ valueType: 'number',
+ value: this.model.y_lim[1] || 0
+ });
+ app.registerRenderSubview(this, this.yMaxLimitInputView, "y-lim-max");
+ },
+ renderZMaxLimitInputView: function () {
+ if(this.zMaxLimitInputView) {
+ this.zMaxLimitInputView.remove();
+ }
+ this.zMaxLimitInputView = new InputView({
+ parent: this,
+ required: true,
+ name: 'z-lim-max',
+ valueType: 'number',
+ value: this.model.z_lim[1] || 0
+ });
+ app.registerRenderSubview(this, this.zMaxLimitInputView, "z-lim-max");
+ },
+ setBoundaryCondition: function (e) {
+ let key = e.target.dataset.hook;
+ this.model.boundary_condition[key] = e.target.checked;
+ this.updateView();
+ },
+ setLimitation: function (e) {
+ let key = e.target.parentElement.parentElement.dataset.name;
+ let index = Number(e.target.parentElement.parentElement.dataset.index);
+ this.model[key][index] = Number(e.target.value.trim());
+ this.updateView();
+ },
+ update: function () {},
+ updateValid: function () {},
+ updateView: function () {
+ this.parent.renderLimitsView();
+ }
+});
diff --git a/client/domain-view/views/particle-view.js b/client/domain-view/views/particle-view.js
new file mode 100644
index 0000000000..87f1a0b424
--- /dev/null
+++ b/client/domain-view/views/particle-view.js
@@ -0,0 +1,229 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 StochSS developers.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+let $ = require('jquery');
+//support files
+let app = require('../../app');
+//views
+let View = require('ampersand-view');
+let InputView = require('../../views/input');
+let SelectView = require('ampersand-select-view');
+//templates
+let template = require('../templates/editParticleView.pug');
+
+module.exports = View.extend({
+ template: template,
+ events: function () {
+ let events = {};
+ events[`change [data-hook=particle-type-${this.viewIndex}]`] = 'handleSelectType';
+ events[`change [data-target=location-${this.viewIndex}]`] = 'handleUpdateLocation';
+ events[`change [data-hook=particle-fixed=${this.viewIndex}]`] = 'handleUpdateFixed';
+ return events;
+ },
+ initialize: function (attrs, options) {
+ View.prototype.initialize.apply(this, arguments);
+ this.viewIndex = attrs.viewIndex;
+ this.defaultType = attrs.defaultType;
+ this.disable = attrs.disable ? attrs.disable : false;
+ this.origType = this.model.type;
+ this.origPoint = JSON.parse(JSON.stringify(this.model.point));
+ },
+ render: function (attrs, options) {
+ View.prototype.render.apply(this, arguments);
+ this.renderTypeSelectView();
+ this.renderParticleProperties();
+ this.renderXCoordView();
+ this.renderYCoordView();
+ this.renderZCoordView();
+ },
+ handleSelectType: function (e) {
+ this.defaultType = this.parent.model.types.get(this.model.type, "typeID")
+ this.model.type = Number(e.target.value);
+ let type = this.parent.model.types.get(this.model.type, "typeID")
+ if(this.model.mass === this.defaultType.mass) {
+ this.model.mass = type.mass;
+ }
+ if(this.model.volume === this.defaultType.volume) {
+ this.model.volume = type.volume;
+ }
+ if(this.model.rho === this.defaultType.rho) {
+ this.model.rho = type.rho;
+ }
+ if(this.model.nu === this.defaultType.nu) {
+ this.model.nu = type.nu;
+ }
+ if(this.model.c === this.defaultType.c) {
+ this.model.c = type.c;
+ }
+ if(this.model.fixed === this.defaultType.fixed) {
+ this.model.fixed = type.fixed;
+ }
+ this.renderParticleProperties();
+ },
+ handleUpdateFixed: function (e) {
+ this.model.fixed = e.target.checked;
+ },
+ handleUpdateLocation: function (e) {
+ let index = Number(e.target.parentElement.parentElement.dataset.index);
+ this.model.point[index] = Number(e.target.value);
+ },
+ renderCInputView: function () {
+ if(this.cInputView) {
+ this.cInputView.remove();
+ }
+ this.cInputView = new InputView({
+ parent: this,
+ required: true,
+ name: 'speed-of-sound',
+ disabled: this.disable,
+ modelKey: 'c',
+ valueType: 'number',
+ value: this.model.c
+ });
+ app.registerRenderSubview(this, this.cInputView, `particle-c-${this.viewIndex}`);
+ },
+ renderParticleProperties: function () {
+ this.renderCInputView();
+ this.renderMassInputView();
+ this.renderNuInputView();
+ this.renderRhoInputView();
+ this.renderVolumeInputView();
+ $(this.queryByHook(`particle-fixed-${this.viewIndex}`)).prop('checked', this.model.fixed);
+ $(this.queryByHook(`particle-fixed-${this.viewIndex}`)).prop('disabled', this.disable);
+ },
+ renderMassInputView: function () {
+ if(this.massInputView) {
+ this.massInputView.remove();
+ }
+ this.massInputView = new InputView({
+ parent: this,
+ required: true,
+ name: 'mass',
+ disabled: this.disable,
+ modelKey: 'mass',
+ valueType: 'number',
+ value: this.model.mass
+ });
+ app.registerRenderSubview(this, this.massInputView, `particle-mass-${this.viewIndex}`);
+ },
+ renderNuInputView: function () {
+ if(this.nuInputView) {
+ this.nuInputView.remove();
+ }
+ this.nuInputView = new InputView({
+ parent: this,
+ required: true,
+ name: 'viscosity',
+ disabled: this.disable,
+ modelKey: 'nu',
+ valueType: 'number',
+ value: this.model.nu
+ });
+ app.registerRenderSubview(this, this.nuInputView, `particle-nu-${this.viewIndex}`);
+ },
+ renderRhoInputView: function () {
+ if(this.rhoInputView) {
+ this.rhoInputView.remove();
+ }
+ this.rhoInputView = new InputView({
+ parent: this,
+ required: true,
+ name: 'density',
+ disabled: this.disable,
+ modelKey: 'rho',
+ valueType: 'number',
+ value: this.model.rho
+ });
+ app.registerRenderSubview(this, this.rhoInputView, `particle-rho-${this.viewIndex}`);
+ },
+ renderTypeSelectView: function () {
+ if(this.typeSelectView) {
+ this.typeSelectView.remove();
+ }
+ this.typeSelectView = new SelectView({
+ name: 'type',
+ required: true,
+ idAttribute: 'typeID',
+ textAttribute: 'name',
+ eagerValidate: true,
+ options: this.parent.model.types,
+ value: this.parent.model.types.get(this.model.type, "typeID")
+ });
+ app.registerRenderSubview(this, this.typeSelectView, `particle-type-${this.viewIndex}`)
+ $(this.queryByHook(`particle-type-${this.viewIndex}`)).find('select').prop('disabled', this.disable);
+ },
+ renderVolumeInputView: function () {
+ if(this.volumeInputView) {
+ this.volumeInputView.remove();
+ }
+ this.volumeInputView = new InputView({
+ parent: this,
+ required: true,
+ name: 'volume',
+ disabled: this.disable,
+ modelKey: 'volume',
+ valueType: 'number',
+ value: this.model.volume
+ });
+ app.registerRenderSubview(this, this.volumeInputView, `particle-vol-${this.viewIndex}`);
+ },
+ renderXCoordView: function () {
+ if(this.xCoordView) {
+ this.xCoordView.remove();
+ }
+ this.xCoordView = new InputView({
+ parent: this,
+ required: true,
+ name: 'x-coord',
+ disabled: this.disable,
+ valueType: 'number',
+ value: this.model.point[0]
+ });
+ app.registerRenderSubview(this, this.xCoordView, `x-coord-${this.viewIndex}`);
+ },
+ renderYCoordView: function () {
+ if(this.yCoordView) {
+ this.yCoordView.remove();
+ }
+ this.yCoordView = new InputView({
+ parent: this,
+ required: true,
+ name: 'y-coord',
+ disabled: this.disable,
+ valueType: 'number',
+ value: this.model.point[1]
+ });
+ app.registerRenderSubview(this, this.yCoordView, `y-coord-${this.viewIndex}`);
+ },
+ renderZCoordView: function () {
+ if(this.zCoordView) {
+ this.zCoordView.remove();
+ }
+ this.zCoordView = new InputView({
+ parent: this,
+ required: true,
+ name: 'z-coord',
+ disabled: this.disable,
+ valueType: 'number',
+ value: this.model.point[2]
+ });
+ app.registerRenderSubview(this, this.zCoordView, `z-coord-${this.viewIndex}`);
+ },
+ update: function (e) {},
+ updateValid: function (e) {}
+});
\ No newline at end of file
diff --git a/client/domain-view/views/properties-view.js b/client/domain-view/views/properties-view.js
new file mode 100644
index 0000000000..f45575c57c
--- /dev/null
+++ b/client/domain-view/views/properties-view.js
@@ -0,0 +1,176 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 StochSS developers.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+let $ = require('jquery');
+//support files
+let app = require('../../app');
+let Tooltips = require('../../tooltips');
+//views
+let View = require('ampersand-view');
+let InputView = require('../../views/input');
+//templates
+let template = require('../templates/propertiesView.pug');
+
+module.exports = View.extend({
+ events: {
+ 'click [data-hook=collapse-domain-properties]' : 'changeCollapseButtonText',
+ 'change [data-hook=static-domain]' : 'setStaticDomain',
+ 'change [data-hook=density]' : 'setDensity',
+ 'change [data-target=gravity]' : 'setGravity',
+ 'change [data-hook=pressure]' : 'setPressure',
+ 'change [data-hook=speed]' : 'setSpeed'
+ },
+ template: template,
+ initialize: function (attrs, options) {
+ View.prototype.initialize.apply(this, arguments);
+ this.readOnly = attrs.readOnly ? attrs.readOnly : false;
+ this.tooltips = Tooltips.domainEditor;
+ },
+ render: function (attrs, options) {
+ View.prototype.render.apply(this, arguments);
+ if(this.readOnly) {
+ $(this.queryByHook('domain-properties-edit-tab')).addClass("disabled");
+ $(".nav .disabled>a").on("click", function(e) {
+ e.preventDefault();
+ return false;
+ });
+ $(this.queryByHook('domain-properties-view-tab')).tab('show');
+ $(this.queryByHook('edit-domain-properties')).removeClass('active');
+ $(this.queryByHook('view-domain-properties')).addClass('active');
+ }else{
+ $(this.queryByHook("static-domain")).prop("checked", this.model.static);
+ this.renderDensityInputView();
+ this.renderXGravityInputView();
+ this.renderYGravityInputView();
+ this.renderZGravityInputView();
+ this.renderPressureInputView();
+ this.renderSpeedInputView();
+ }
+ $(this.queryByHook("view-static-domain")).prop("checked", this.model.static);
+ },
+ changeCollapseButtonText: function (e) {
+ app.changeCollapseButtonText(this, e);
+ },
+ renderDensityInputView: function () {
+ if(this.densityInputView) {
+ this.densityInputView.remove();
+ }
+ this.densityInputView = new InputView({
+ parent: this,
+ required: true,
+ name: 'density',
+ valueType: 'number',
+ value: this.model.rho_0 || 1
+ });
+ app.registerRenderSubview(this, this.densityInputView, "density");
+ },
+ renderPressureInputView: function () {
+ if(this.pressureInputView) {
+ this.pressureInputView.remove();
+ }
+ this.pressureInputView = new InputView({
+ parent: this,
+ required: true,
+ name: 'pressure',
+ valueType: 'number',
+ value: this.model.p_0 || 0
+ });
+ app.registerRenderSubview(this, this.pressureInputView, "pressure");
+ },
+ renderSpeedInputView: function () {
+ if(this.speedInputView) {
+ this.speedInputView.remove();
+ }
+ this.speedInputView = new InputView({
+ parent: this,
+ required: true,
+ name: 'speed',
+ valueType: 'number',
+ value: this.model.c_0 || 0
+ });
+ app.registerRenderSubview(this, this.speedInputView, "speed");
+ },
+ renderXGravityInputView: function () {
+ if(this.xGravityInputView) {
+ this.xGravityInputView.remove();
+ }
+ this.xGravityInputView = new InputView({
+ parent: this,
+ required: true,
+ name: 'gravity-x',
+ valueType: 'number',
+ value: this.model.gravity[0],
+ label: "X: "
+ });
+ app.registerRenderSubview(this, this.xGravityInputView, "gravity-x");
+ },
+ renderYGravityInputView: function () {
+ if(this.yGravityInputView) {
+ this.yGravityInputView.remove();
+ }
+ this.yGravityInputView = new InputView({
+ parent: this,
+ required: true,
+ name: 'gravity-y',
+ valueType: 'number',
+ value: this.model.gravity[1],
+ label: "Y: "
+ });
+ app.registerRenderSubview(this, this.yGravityInputView, "gravity-y");
+ },
+ renderZGravityInputView: function () {
+ if(this.zGravityInputView) {
+ this.zGravityInputView.remove();
+ }
+ this.zGravityInputView = new InputView({
+ parent: this,
+ required: true,
+ name: 'gravity-z',
+ valueType: 'number',
+ value: this.model.gravity[2],
+ label: "Z: "
+ });
+ app.registerRenderSubview(this, this.zGravityInputView, "gravity-z");
+ },
+ setDensity: function (e) {
+ this.model.rho_0 = Number(e.target.value);
+ this.updateView();
+ },
+ setGravity: function (e) {
+ let index = Number(e.target.parentElement.parentElement.dataset.index);
+ this.model.gravity[index] = Number(e.target.value);
+ this.updateView();
+ },
+ setPressure: function (e) {
+ this.model.p_0 = Number(e.target.value);
+ this.updateView();
+ },
+ setSpeed: function (e) {
+ this.model.c_0 = Number(e.target.value);
+ this.updateView();
+ },
+ setStaticDomain: function (e) {
+ this.model.static = e.target.checked;
+ this.updateView();
+ },
+ update: function (e) {},
+ updateValid: function (e) {},
+ updateView: function (e) {
+ this.parent.renderPropertiesView();
+ }
+});
\ No newline at end of file
diff --git a/client/views/quickview-domain-types.js b/client/domain-view/views/quickview-type.js
similarity index 87%
rename from client/views/quickview-domain-types.js
rename to client/domain-view/views/quickview-type.js
index ee7f17446f..1e0f17bc6c 100644
--- a/client/views/quickview-domain-types.js
+++ b/client/domain-view/views/quickview-type.js
@@ -17,9 +17,9 @@ along with this program. If not, see .
*/
//views
-var View = require('ampersand-view');
+let View = require('ampersand-view');
//templates
-var template = require('../templates/includes/quickviewDomainTypes.pug');
+let template = require('../templates/quickviewType.pug');
module.exports = View.extend({
template: template,
diff --git a/client/domain-view/views/type-view.js b/client/domain-view/views/type-view.js
new file mode 100644
index 0000000000..ea2ac60f3d
--- /dev/null
+++ b/client/domain-view/views/type-view.js
@@ -0,0 +1,251 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 StochSS developers.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+let $ = require('jquery');
+let path = require('path');
+let _ = require('underscore');
+//support files
+let app = require('../../app');
+let Tooltips = require('../../tooltips');
+let tests = require('../../views/tests');
+//views
+let View = require('ampersand-view');
+let InputView = require('../../views/input');
+//templates
+let editTemplate = require('../templates/editType.pug');
+let viewTemplate = require('../templates/viewType.pug');
+
+module.exports = View.extend({
+ bindings: {
+ 'model.selected' : {
+ type: function (el, value, previousValue) {
+ el.checked = value;
+ },
+ hook: 'select'
+ }
+ },
+ events: {
+ 'change [data-hook=type-name]' : 'handleRenameType',
+ 'change [data-target=type-defaults]' : 'updateView',
+ 'change [data-hook=td-fixed]' : 'setTDFixed',
+ 'change [data-hook=type-geometry]' : 'updateView',
+ 'click [data-hook=select]' : 'selectType',
+ 'click [data-hook=unassign-all]' : 'handleUnassignParticles',
+ 'click [data-hook=delete-type]' : 'handleDeleteType',
+ 'click [data-hook=delete-all]' : 'handleDeleteTypeAndParticle',
+ 'click [data-hook=apply-geometry]' : 'handleApplyGeometry'
+ },
+ initialize: function (attrs, options) {
+ View.prototype.initialize.apply(this, arguments);
+ this.viewMode = attrs.viewMode ? attrs.viewMode : false;
+ this.tooltips = Tooltips.domainType;
+ },
+ render: function (attrs, options) {
+ this.template = this.viewMode ? viewTemplate : editTemplate;
+ View.prototype.render.apply(this, arguments);
+ if(!this.viewMode) {
+ if(this.model.selected) {
+ setTimeout(_.bind(this.openTypeDetails, this), 1);
+ }
+ }
+ $(this.queryByHook('view-td-fixed')).prop('checked', this.model.fixed)
+ app.documentSetup();
+ },
+ completeAction: function () {
+ $(this.queryByHook(`tg-in-progress-${this.model.typeID}`)).css("display", "none");
+ $(this.queryByHook(`tg-complete-${this.model.typeID}`)).css("display", "inline-block");
+ setTimeout(() => {
+ $(this.queryByHook(`tg-complete-${this.model.typeID}`)).css("display", "none");
+ }, 5000);
+ },
+ errorAction: function (action) {
+ $(this.queryByHook(`tg-in-progress-${this.model.typeID}`)).css("display", "none");
+ $(this.queryByHook(`tg-action-error-${this.model.typeID}`)).html(action);
+ $(this.queryByHook(`tg-error-${this.model.typeID}`)).css("display", "block");
+ },
+ handleApplyGeometry: function (e) {
+ this.startAction();
+ let domain = this.model.collection.parent;
+ let particles = domain.particles.toJSON();
+ let center = [
+ (domain.x_lim[1] + domain.x_lim[0]) / 2,
+ (domain.y_lim[1] + domain.y_lim[0]) / 2,
+ (domain.z_lim[1] + domain.z_lim[0]) / 2
+ ];
+ let data = {particles: particles, type: this.model.toJSON(), center: center}
+ let endpoint = path.join(app.getApiPath(), 'spatial-model/apply-geometry');
+ app.postXHR(endpoint, data, {
+ success: (err, response, body) => {
+ this.parent.parent.applyGeometry(body.particles, this.model);
+ this.completeAction();
+ },
+ error: (err, response, body) => {
+ if(body.Traceback.includes("SyntaxError")) {
+ var tracePart = body.Traceback.split('\n').slice(6)
+ tracePart.splice(2, 2)
+ tracePart[1] = tracePart[1].replace(new RegExp(' ', 'g'), ' ')
+ var errorBlock = `${body.Message}
${tracePart.join('
')}
`
+ }else{
+ var errorBlock = body.Message
+ }
+ this.errorAction(errorBlock);
+ }
+ });
+ },
+ handleDeleteType: function (e) {
+ let type = Number(e.target.dataset.type);
+ this.model.collection.removeType(this.model);
+ this.parent.parent.deleteType(this.model.typeID);
+ },
+ handleDeleteTypeAndParticle: function (e) {
+ let type = Number(e.target.dataset.type);
+ this.model.collection.removeType(this.model);
+ this.parent.parent.deleteType(this.model.typeID, {unassign: false});
+ },
+ handleRenameType: function (e) {
+ this.updateView();
+ let type = Number(e.target.parentElement.parentElement.dataset.target);
+ let name = e.target.value;
+ this.parent.parent.renameType(type, name);
+ },
+ handleUnassignParticles: function () {
+ this.model.numParticles = 0;
+ this.parent.parent.unassignAllParticles(this.model.typeID);
+ },
+ openTypeDetails: function () {
+ $("#collapse-type-details" + this.model.typeID).collapse("show");
+ },
+ selectType: function () {
+ this.model.selected = !this.model.selected;
+ },
+ setTDFixed: function (e) {
+ this.model.fixed = e.target.checked;
+ this.updateView();
+ },
+ startAction: function () {
+ $(this.queryByHook(`tg-complete-${this.model.typeID}`)).css("display", "none");
+ $(this.queryByHook(`tg-error-${this.model.typeID}`)).css("display", "none");
+ $(this.queryByHook(`tg-in-progress-${this.model.typeID}`)).css("display", "inline-block");
+ },
+ update: function () {},
+ updateValid: function () {},
+ updateView: function () {
+ this.parent.renderViewTypeView();
+ this.parent.parent.updateParticleViews({includeGeometry: true});
+ },
+ subviews: {
+ inputTypeID: {
+ hook: "type-name",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'name',
+ modelKey: 'name',
+ tests: [tests.invalidChar],
+ valueType: 'string',
+ value: this.model.name
+ });
+ }
+ },
+ inputMass: {
+ hook: "td-mass",
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'mass',
+ modelKey: 'mass',
+ valueType: 'number',
+ tests: tests.valueTests,
+ value: this.model.mass
+ });
+ }
+ },
+ inputVolume: {
+ hook: 'td-vol',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'volume',
+ modelKey: 'volume',
+ valueType: 'number',
+ tests: tests.valueTests,
+ value: this.model.volume
+ });
+ }
+ },
+ inputDensity: {
+ hook: 'td-rho',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'density',
+ modelKey: 'rho',
+ valueType: 'number',
+ tests: tests.valueTests,
+ value: this.model.rho
+ });
+ }
+ },
+ inputViscosity: {
+ hook: 'td-nu',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'viscosity',
+ modelKey: 'nu',
+ valueType: 'number',
+ tests: tests.valueTests,
+ value: this.model.nu
+ });
+ }
+ },
+ inputSpeedOfSound: {
+ hook: 'td-c',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'speed-of-sound',
+ modelKey: 'c',
+ valueType: 'number',
+ tests: tests.valueTests,
+ value: this.model.c
+ });
+ }
+ },
+ inputGeometry: {
+ hook: 'type-geometry',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: false,
+ name: 'geometry',
+ modelKey: 'geometry',
+ valueType: 'string',
+ value: this.model.geometry,
+ placeholder: "--Expression in terms of 'x', 'y', 'z'--"
+ });
+ }
+ }
+ }
+});
\ No newline at end of file
diff --git a/client/domain-view/views/types-description-view.js b/client/domain-view/views/types-description-view.js
new file mode 100644
index 0000000000..2279ff68ee
--- /dev/null
+++ b/client/domain-view/views/types-description-view.js
@@ -0,0 +1,128 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 StochSS developers.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+let $ = require('jquery');
+let path = require('path');
+//support files
+let app = require('../../app');
+//views
+let View = require('ampersand-view');
+let SelectView = require('ampersand-select-view');
+//templates
+let template = require('../templates/typesDescriptionView.pug');
+
+module.exports = View.extend({
+ template: template,
+ events: {
+ 'change [data-hook=file-select]' : 'selectTypeFile',
+ 'change [data-hook=file-location-select]' : 'selectFileLocation',
+ 'click [data-hook=set-particle-types-btn]' : 'getTypesFromFile'
+ },
+ initialize: function (attrs, options) {
+ View.prototype.initialize.apply(this, arguments);
+ this.typeFile = null;
+ },
+ render: function(attrs, options) {
+ View.prototype.render.apply(this, arguments);
+ let endpoint = path.join(app.getApiPath(), 'spatial-model/types-list');
+ app.getXHR(endpoint, {success: (err, response, body) => {
+ this.typeFiles = body.files;
+ this.fileLocations = body.paths;
+ this.renderFileSelectView();
+ }});
+ },
+ completeAction: function () {
+ $(this.queryByHook('st-in-progress')).css('display', 'none');
+ $(this.queryByHook('st-complete')).css('display', 'inline-block');
+ },
+ errorAction: function (action) {
+ $(this.queryByHook('st-in-progress')).css('display', 'none');
+ $(this.queryByHook('st-action-error')).text(action);
+ $(this.queryByHook('st-error')).css('display', 'block');
+ },
+ getTypesFromFile: function () {
+ this.startAction();
+ let queryStr = `?path=${this.typeFile}`;
+ let endpoint = path.join(app.getApiPath(), 'spatial-model/particle-types') + queryStr;
+ app.getXHR(endpoint, {
+ success: (err, response, body) => {
+ this.parent.setParticleTypes(body.names, body.types);
+ this.completeAction();
+ },
+ error: (err, response, body) => {
+ this.errorAction(body.Message);
+ }
+ });
+ },
+ renderFileLocationSelectView: function (options) {
+ if(this.fileLocationSelectView) {
+ this.fileLocationSelectView.remove();
+ }
+ this.fileLocationSelectView = new SelectView({
+ name: 'type-locations',
+ required: false,
+ idAttributes: 'cid',
+ options: options,
+ unselectedText: "-- Select Location --"
+ });
+ app.registerRenderSubview(this, this.fileLocationSelectView, "file-location-select");
+ },
+ renderFileSelectView: function () {
+ if(this.fileSelectView) {
+ this.fileSelectView.remove();
+ }
+ this.fileSelectView = new SelectView({
+ name: 'type-files',
+ required: false,
+ idAttributes: 'cid',
+ options: this.typeFiles,
+ unselectedText: "-- Select Type File --",
+ });
+ app.registerRenderSubview(this, this.fileSelectView, "file-select");
+ },
+ selectFileLocation: function (e) {
+ let value = e.target.value;
+ this.typeFile = value ? value : null;
+ $(this.queryByHook("set-particle-types-btn")).prop("disabled", this.typeFile === null);
+ },
+ selectTypeFile: function (e) {
+ let value = e.target.value;
+ if(value) {
+ if(this.fileLocations[value].length > 1) {
+ $(this.queryByHook("type-location-message")).css('display', "block");
+ $(this.queryByHook("file-location-container")).css("display", "inline-block");
+ this.renderFileLocationSelectView(this.fileLocations[value]);
+ this.typeFile = null;
+ }else{
+ $(this.queryByHook("type-location-message")).css('display', "none");
+ $(this.queryByHook("file-location-container")).css("display", "none");
+ this.typeFile = this.fileLocations[value][0];
+ }
+ }else{
+ $(this.queryByHook("type-location-message")).css('display', "none");
+ $(this.queryByHook("file-location-container")).css("display", "none");
+ this.typeFile = null;
+ }
+ $(this.queryByHook("set-particle-types-btn")).prop("disabled", this.typeFile === null);
+ },
+ startAction: function () {
+ $(this.queryByHook("st-complete")).css('display', 'none');
+ $(this.queryByHook("st-error")).css('display', 'none');
+ $(this.queryByHook("st-in-progress")).css("display", "inline-block");
+ },
+});
\ No newline at end of file
diff --git a/client/domain-view/views/types-view.js b/client/domain-view/views/types-view.js
new file mode 100644
index 0000000000..b5351ed9e9
--- /dev/null
+++ b/client/domain-view/views/types-view.js
@@ -0,0 +1,105 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 StochSS developers.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+let $ = require('jquery');
+//support files
+let app = require('../../app');
+//views
+let View = require('ampersand-view');
+let TypeView = require('./type-view');
+//templates
+let template = require('../templates/typesView.pug');
+
+module.exports = View.extend({
+ template: template,
+ events: {
+ 'click [data-hook=collapse-domain-types]' : 'changeCollapseButtonText',
+ 'click [data-hook=add-domain-type]' : 'handleAddDomainType',
+ },
+ initialize: function (attrs, options) {
+ View.prototype.initialize.apply(this, arguments);
+ this.readOnly = attrs.readOnly ? attrs.readOnly : false;
+ },
+ render: function (attrs, options) {
+ View.prototype.render.apply(this, arguments);
+ if(this.readOnly) {
+ $(this.queryByHook('domain-types-edit-tab')).addClass("disabled");
+ $(".nav .disabled>a").on("click", function(e) {
+ e.preventDefault();
+ return false;
+ });
+ $(this.queryByHook('domain-types-view-tab')).tab('show');
+ $(this.queryByHook('edit-domain-types')).removeClass('active');
+ $(this.queryByHook('view-domain-types')).addClass('active');
+ }else{
+ this.renderEditTypeView();
+ }
+ this.toggleDomainError();
+ this.renderViewTypeView();
+ },
+ changeCollapseButtonText: function (e) {
+ app.changeCollapseButtonText(this, e);
+ },
+ handleAddDomainType: function () {
+ let name = this.collection.addType();
+ this.parent.addType(name);
+ },
+ renderEditTypeView: function () {
+ if(this.editTypeView) {
+ this.editTypeView.remove();
+ }
+ let options = {
+ filter: function (model) {
+ return model.typeID != 0;
+ }
+ }
+ this.editTypeView = this.renderCollection(
+ this.collection,
+ TypeView,
+ this.queryByHook("edit-domain-types-list"),
+ options
+ );
+ },
+ renderViewTypeView: function () {
+ if(this.viewTypeView) {
+ this.viewTypeView.remove();
+ }
+ let options = {
+ viewOptions: {viewMode: true},
+ filter: function (model) {
+ return model.typeID != 0;
+ }
+ }
+ this.viewTypeView = this.renderCollection(
+ this.collection,
+ TypeView,
+ this.queryByHook("view-domain-types-list"),
+ options
+ );
+ },
+ toggleDomainError: function () {
+ let errorMsg = $(this.queryByHook('domain-error'))
+ if(!this.collection.parent.valid) {
+ errorMsg.addClass('component-invalid');
+ errorMsg.removeClass('component-valid');
+ }else{
+ errorMsg.addClass('component-valid');
+ errorMsg.removeClass('component-invalid');
+ }
+ }
+});
\ No newline at end of file
diff --git a/client/views/view-particle.js b/client/domain-view/views/view-particle.js
similarity index 89%
rename from client/views/view-particle.js
rename to client/domain-view/views/view-particle.js
index d832472fda..68cd5c7d7a 100644
--- a/client/views/view-particle.js
+++ b/client/domain-view/views/view-particle.js
@@ -17,9 +17,9 @@ along with this program. If not, see .
*/
//views
-var View = require('ampersand-view');
+let View = require('ampersand-view');
//templates
-var template = require('../templates/includes/viewParticle.pug');
+let template = require('../templates/viewParticle.pug');
module.exports = View.extend({
template: template,
diff --git a/client/model-view/model-view.js b/client/model-view/model-view.js
index ee749e91a9..8cdd074563 100644
--- a/client/model-view/model-view.js
+++ b/client/model-view/model-view.js
@@ -46,6 +46,9 @@ module.exports = View.extend({
'change [data-hook=all-continuous]' : 'setDefaultMode',
'change [data-hook=all-discrete]' : 'setDefaultMode',
'change [data-hook=advanced]' : 'setDefaultMode',
+ 'change [data-hook=spatial-continuous]' : 'setDefaultMode',
+ 'change [data-hook=spatial-discrete]' : 'setDefaultMode',
+ 'change [data-hook=spatial-discrete-concentration]' : 'setDefaultMode',
'change [data-hook=edit-volume]' : 'updateVolumeViewer',
'click [data-hook=collapse-mv-advanced-section]' : 'changeCollapseButtonText',
'click [data-hook=collapse-system-volume]' : 'changeCollapseButtonText'
@@ -76,14 +79,27 @@ module.exports = View.extend({
this.setReadOnlyMode("model-mode");
this.setReadOnlyMode("system-volume");
}else {
- if(this.model.defaultMode === "" && !this.model.is_spatial){
- this.getInitialDefaultMode();
+ if(this.model.defaultMode === ""){
+ if(this.model.is_spatial) {
+ this.model.defaultMode = "discrete";
+ $(this.queryByHook("spatial-discrete")).prop('checked', true);
+ }else{
+ this.getInitialDefaultMode();
+ }
}else {
- let dataHooks = {'continuous':'all-continuous', 'discrete':'all-discrete', 'dynamic':'advanced'};
- $(this.queryByHook(dataHooks[this.model.defaultMode])).prop('checked', true);
if(this.model.is_spatial) {
- $(this.queryByHook("advanced-model-mode")).css("display", "none");
+ let dataHooks = {
+ 'continuous':'spatial-continuous',
+ 'discrete':'spatial-discrete',
+ 'discrete-concentration':'spatial-discrete-concentration'
+ };
+ $(this.queryByHook(dataHooks[this.model.defaultMode])).prop('checked', true);
+ $(this.queryByHook("model-mode-container")).css("display", "none");
$(this.queryByHook("system-volume-container")).css("display", "none");
+ }else{
+ let dataHooks = {'continuous':'all-continuous', 'discrete':'all-discrete', 'dynamic':'advanced'};
+ $(this.queryByHook(dataHooks[this.model.defaultMode])).prop('checked', true);
+ $(this.queryByHook("spatial-model-mode-container")).css("display", "none");
}
}
this.model.reactions.on("change", (reactions) => {
@@ -170,34 +186,14 @@ module.exports = View.extend({
if(this.domainViewer) {
this.domainViewer.remove();
}
- if(domainPath && domainPath !== "viewing") {
- let queryStr = `?path=${this.model.directory}&domain_path=${domainPath}`;
- let endpoint = path.join(app.getApiPath(), "spatial-model/load-domain") + queryStr;
- app.getXHR(endpoint, {
- always: (err, response, body) => {
- let domain = new Domain(body.domain);
- this.domainViewer = new DomainViewer({
- parent: this,
- model: domain,
- domainPath: domainPath,
- readOnly: this.readOnly,
- domainElements: this.domainElements,
- domainPlot: this.domainPlot
- });
- app.registerRenderSubview(this, this.domainViewer, 'domain-viewer-container');
- }
- });
- }else{
- this.domainViewer = new DomainViewer({
- parent: this,
- model: this.model.domain,
- domainPath: domainPath,
- readOnly: this.readOnly,
- domainElements: this.domainElements,
- domainPlot: this.domainPlot
- });
- app.registerRenderSubview(this, this.domainViewer, 'domain-viewer-container');
- }
+ this.domainViewer = new DomainViewer({
+ parent: this,
+ model: this.model,
+ readOnly: this.readOnly,
+ domainElements: this.domainElements,
+ domainPlot: this.domainPlot
+ });
+ app.registerRenderSubview(this, this.domainViewer, 'domain-viewer-container');
},
renderEventsView: function () {
if(this.model.is_spatial) { return };
@@ -319,8 +315,10 @@ module.exports = View.extend({
let prevMode = this.model.defaultMode;
this.model.defaultMode = e.target.dataset.name;
this.speciesView.defaultMode = e.target.dataset.name;
- this.setAllSpeciesModes(prevMode);
- this.toggleVolumeContainer();
+ if(!this.model.is_spatial) {
+ this.setAllSpeciesModes(prevMode);
+ this.toggleVolumeContainer();
+ }
},
setInitialDefaultMode: function (modal, mode) {
var dataHooks = {'continuous':'all-continuous', 'discrete':'all-discrete', 'dynamic':'advanced'};
diff --git a/client/model-view/modelView.pug b/client/model-view/modelView.pug
index 9284a249c5..8fea01cd20 100644
--- a/client/model-view/modelView.pug
+++ b/client/model-view/modelView.pug
@@ -32,17 +32,28 @@ div#model-view
hr
- div.row
+ div.row(data-hook="model-mode-container")
div.col-sm-4
input.mr-2(type="radio" name="default-mode" data-hook="all-continuous" data-name="continuous")
span Concentration
div.col-sm-4
input.mr-2(type="radio" name="default-mode" data-hook="all-discrete" data-name="discrete")
span Population
- div.col-sm-4(data-hook="advanced-model-mode")
+ div.col-sm-4
input.mr-2(type="radio" name="default-mode" data-hook="advanced" data-name="dynamic")
span Hybrid Concentration/Population
+ div.row(data-hook="spatial-model-mode-container")
+ div.col-sm-4
+ input.mr-2(type="radio" name="default-mode" data-hook="spatial-continuous" data-name="continuous")
+ span Concentration
+ div.col-sm-4
+ input.mr-2(type="radio" name="default-mode" data-hook="spatial-discrete" data-name="discrete")
+ span Population (outputs: absolute value)
+ div.col-sm-4
+ input.mr-2(type="radio" name="default-mode" data-hook="spatial-discrete-concentration" data-name="discrete-concentration")
+ span Population (outputs: scaled by volume)
+
div.tab-pane(id="view-model-mode" data-hook="view-model-mode")
hr
diff --git a/client/model-view/templates/domainViewer.pug b/client/model-view/templates/domainViewer.pug
index 41b57b714f..d675aa0174 100644
--- a/client/model-view/templates/domainViewer.pug
+++ b/client/model-view/templates/domainViewer.pug
@@ -41,101 +41,11 @@ div#domain-viewer.card
div.inline(data-hook="select-location")
- div.row
-
- div.col-md-4
-
- h4 Domain Properties
-
- hr.mt-2
- div.row
- div.col-sm-6: h6.pl-2 Static Domain
- div.col-sm-6: input(type="checkbox" id="static-domain" data-hook="static-domain" checked=this.model.static disabled)
-
- hr.mt-2
- div.row
- div.col-sm-6: h6.pl-2 Density
- div.col-sm-6=this.model.rho_0
-
- hr.mt-2
- div.row
- div.col-sm-6: h6.pl-2 Gravity
- div.col-sm-6=this.gravity
-
- hr.mt-2
- div.row
- div.col-sm-6: h6.pl-2 Pressure
- div.col-sm-6=this.model.p_0
-
- hr.mt-2
- div.row
- div.col-sm-6: h6.pl-2 Speed of Sound
- div.col-sm-6=this.model.c_0
-
- div.col-md-8
-
- h4 Domain Limits
-
- hr.mt-2
- div.row.head.mx-1
- div.col-sm-3
- div.col-sm-3.pb-1: h6 Minimum
- div.col-sm-3: h6 Minimum
- div.col-sm-3: h6 Reflect
-
- div.row.mt-3
- div.col-sm-3: h6.pl-2 X-Axis
- div.col-sm-3=this.model.x_lim[0]
- div.col-sm-3=this.model.x_lim[1]
- div.col-sm-3: input(type="checkbox" checked=this.model.boundary_condition.reflect_x disabled)
-
- hr.mt-2
- div.row
- div.col-sm-3: h6.pl-2 Y-Axis
- div.col-sm-3=this.model.y_lim[0]
- div.col-sm-3=this.model.y_lim[1]
- div.col-sm-3: input(type="checkbox" checked=this.model.boundary_condition.reflect_y disabled)
-
- hr.mt-2
- div.row
- div.col-sm-3: h6.pl-2 Z-Axis
- div.col-sm-3=this.model.z_lim[0]
- div.col-sm-3=this.model.z_lim[1]
- div.col-sm-3: input(type="checkbox" checked=this.model.boundary_condition.reflect_z disabled)
-
- div.mt-3
-
- h4 Types
-
- hr
-
- h6="Number of Un-Assigned Particles: " + this.model.types.get(0, "typeID").numParticles
-
- div.component-valid(data-hook="domain-error"): p.text-danger A domain cannot have any un-assigned particles.
-
- hr
-
- div.mx-1.row.head.align-items-baseline
-
- div.col-sm-2: h6 Name
-
- div.col-sm-2: h6 Number of Particles
-
- div.col-sm-2: h6 Mass
-
- div.col-sm-2: h6 Volume
-
- div.col-sm-2: h6 Viscosity
-
- div.col-sm-2: h6 Fixed
-
- div.my-3(data-hook="domain-types-list")
+ div(data-hook="domain-view-container")
div.tab-pane.active(id="edit-domain" data-hook="edit-domain")
- hr
-
- div
+ div.mt-3
button.btn.btn-outline-primary.box-shadow(data-hook="edit-domain-btn") Edit Domain
diff --git a/client/model-view/templates/editCustomStoichSpecie.pug b/client/model-view/templates/editCustomStoichSpecie.pug
deleted file mode 100644
index 5f0d1a6d7f..0000000000
--- a/client/model-view/templates/editCustomStoichSpecie.pug
+++ /dev/null
@@ -1,10 +0,0 @@
-div
- div.inline
- button.btn.btn-outline-secondary(class='btn-sm', data-hook='decrement') -
- span.custom(data-hook="ratio")
- div.inline
- button.btn.btn-outline-secondary(class='btn-sm', data-hook='increment') +
- select(data-hook="select-stoich-specie")
- span.message.message-below.message-error(data-hook="message-container")
- p(data-hook="message-text")
- button.btn.btn-outline-secondary.custom(class='btn-sm', data-hook='remove') X
\ No newline at end of file
diff --git a/client/model-view/templates/editReaction.pug b/client/model-view/templates/editReaction.pug
new file mode 100644
index 0000000000..455489de7d
--- /dev/null
+++ b/client/model-view/templates/editReaction.pug
@@ -0,0 +1,90 @@
+div.mx-1
+
+ if(this.model.collection.indexOf(this.model) !== 0)
+ hr
+
+ div.row.align-items-baseline
+
+ div.col-sm-1
+
+ div.pl-3: input(type="checkbox" data-hook="select" data-toggle="collapse" data-target="#collapse-reaction-details" + this.model.compID)
+
+ div.col-sm-3(data-hook="input-name-container")
+
+ div.col-sm-4.reaction-list-summary(data-hook="summary")
+
+ div.col-sm-2
+
+ div.tooltip-icon-large(data-hook="annotation-tooltip" data-html="true" data-toggle="tooltip" title=this.model.annotation || "Click 'Add' to add an annotation")
+
+ button.btn.btn-outline-secondary.btn-sm.box-shadow(data-hook="edit-annotation-btn") Edit
+
+ div.col-sm-2
+
+ button.btn.btn-outline-secondary.box-shadow(data-hook="remove") X
+
+ div.mx-1.pl-2(data-hook="reaction-details")
+
+ div.collapse(id="collapse-reaction-details" + this.model.compID)
+
+ hr
+
+ div.mx-1.row.align-items-baseline
+
+ div.col-sm-6
+
+ div(data-hook="select-reaction-type")
+
+ div
+
+ h5.inline
+
+ span(for="propensity-input") Stochastic Propensity Function:
+
+ div.tooltip-icon(data-html="true" data-hook="rate-parameter-tooltip" data-toggle="tooltip" title=this.parent.tooltips.propensity)
+
+ div
+
+ div(id="propensity-input" data-hook="propensity-input")
+
+ div
+
+ h5.inline
+
+ span(for="ode-propensity-input") ODE Propensity Function:
+
+ div.tooltip-icon(data-html="true" data-hook="rate-parameter-tooltip" data-toggle="tooltip" title=this.parent.tooltips.odePropensity)
+
+ div
+
+ div(id="ode-propensity-input" data-hook="ode-propensity-input")
+
+ div.align-self-center
+
+ input.inline.mr-3(type="checkbox" data-hook="mirror-propensities")
+
+ span.inline Same as Stochastic Propensity Function
+
+ div.col-sm-6
+
+ div
+
+ span(for="select-rate-parameter") Rate Parameter:
+
+ div.tooltip-icon(data-html="true" data-hook="rate-parameter-tooltip" data-toggle="tooltip" title=this.parent.tooltips.rate)
+
+ div.inline.horizontal-space(id="select-rate-parameter" data-hook="select-rate-parameter")
+
+ div.row
+
+ div.col-sm-6(data-hook="reactants-editor")
+
+ div.col-sm-6(data-hook="products-editor")
+
+ div.col-md-12(data-hook="domain-types-editor")
+
+ div.collapse(data-hook="conflicting-modes-message")
+
+ p.text-warning Warning: This reaction containes Variables with modes of 'Concentration' and 'Population' or 'Hybrid Concentration/Population' and will fire stochastically.
+
+ div(data-hook="custom-reaction-error"): p.text-danger A reaction must have at least one reactant or product
diff --git a/client/model-view/templates/editStoichSpecie.pug b/client/model-view/templates/editStoichSpecie.pug
deleted file mode 100644
index 34e754e846..0000000000
--- a/client/model-view/templates/editStoichSpecie.pug
+++ /dev/null
@@ -1,6 +0,0 @@
-div
- label.select
- span(data-hook="ratio")
- select(data-hook="select-stoich-specie")
- span.message.message-below.message-error(data-hook="message-container")
- p(data-hook="message-text")
\ No newline at end of file
diff --git a/client/model-view/templates/reactantProduct.pug b/client/model-view/templates/reactantProduct.pug
index 72578cf9f3..65d07ac51d 100644
--- a/client/model-view/templates/reactantProduct.pug
+++ b/client/model-view/templates/reactantProduct.pug
@@ -6,9 +6,12 @@ div(data-hook="reaction-details")
div.tooltip-icon(data-hook="field-title-tooltip" data-html="true" data-toggle="tooltip" title="")
- div(data-hook="reactants-editor")
+ div(data-hook=this.hookAnchor + "-editor")
- div(data-hook="collapse", class="collapse")
- div.row(data-hook="add-specie-container")
- div(data-hook="select-specie")
- button.btn.btn-outline-secondary(class="btn-sm", data-hook="add-selected-specie") add
\ No newline at end of file
+ div.hidden(data-hook="custom-" + this.hookAnchor)
+
+ div.row(data-hook=this.hookAnchor + "-add-specie-container")
+
+ div(data-hook=this.hookAnchor + "-select-specie")
+
+ button.btn.btn-outline-secondary(class="btn-sm", data-hook=this.hookAnchor + "-add-selected-specie") add
diff --git a/client/model-view/templates/reactionDetails.pug b/client/model-view/templates/reactionDetails.pug
deleted file mode 100644
index 90ad574574..0000000000
--- a/client/model-view/templates/reactionDetails.pug
+++ /dev/null
@@ -1,27 +0,0 @@
-div.row(data-hook="reaction-details")
-
- div.reaction-summary Summary:
-
- div(data-hook="summary-container", id="reaction-summary")
-
- div.col-md-12(data-hook="select-reaction-type")
-
- div.col-md-12.verticle-space
-
- span(for="select-rate-parameter" data-hook="rate-parameter-label")
-
- div.tooltip-icon(data-html="true" data-hook="rate-parameter-tooltip" data-toggle="tooltip" title="")
-
- div.inline.horizontal-space(id="select-rate-parameter" data-hook="select-rate-parameter")
-
- div.col-md-12(data-hook="domain-types-editor")
-
- div.col-md-6(data-hook="reactants-editor")
-
- div.col-md-6(data-hook="products-editor")
-
- div.collapse(data-hook="conflicting-modes-message")
-
- p.text-warning Warning: This reaction containes Variables with modes of 'Concentration' and 'Population' or 'Hybrid Concentration/Population' and will fire stochastically.
-
- div(data-hook="custom-reaction-error"): p.text-danger A reaction must have at least one reactant or product
diff --git a/client/model-view/templates/reactionListing.pug b/client/model-view/templates/reactionListing.pug
deleted file mode 100644
index 3b7ea5cae3..0000000000
--- a/client/model-view/templates/reactionListing.pug
+++ /dev/null
@@ -1,24 +0,0 @@
-div.mx-1
-
- if(this.model.collection.indexOf(this.model) !== 0)
- hr
-
- div.row.align-items-baseline
-
- div.col-md-2
-
- div.pl-3: input(type="radio" data-hook="select" name="reaction-select")
-
- div.col-md-2(data-hook="input-name-container")
-
- div.col-md-3.reaction-list-summary(data-hook="summary")
-
- div.col-md-3
-
- div.tooltip-icon-large(data-hook="annotation-tooltip" data-html="true" data-toggle="tooltip" title=this.model.annotation || "Click 'Add' to add an annotation")
-
- button.btn.btn-outline-secondary.btn-sm.box-shadow(data-hook="edit-annotation-btn") Edit
-
- div.col-md-2
-
- button.btn.btn-outline-secondary.box-shadow(data-hook="remove") X
diff --git a/client/model-view/templates/reactionTypes.pug b/client/model-view/templates/reactionRestrictTo.pug
similarity index 100%
rename from client/model-view/templates/reactionTypes.pug
rename to client/model-view/templates/reactionRestrictTo.pug
diff --git a/client/model-view/templates/reactionsView.pug b/client/model-view/templates/reactionsView.pug
index c3b6e5b7ef..e32bec78cd 100644
--- a/client/model-view/templates/reactionsView.pug
+++ b/client/model-view/templates/reactionsView.pug
@@ -27,37 +27,31 @@ div#reactions-editor.card
div.tab-pane.active(id="edit-reactions" data-hook="edit-reactions")
- div.row.mb-3
-
- div.col-md-7.container-part
-
- hr
+ hr
- div.mx-1.row.head.align-items-baseline
+ div.mx-1.row.head.align-items-baseline
+
+ div.col-sm-1: h6.align-bottom Edit
- div.col-md-2: h6.align-bottom Edit
-
- div.col-md-2
-
- h6.inline Name
+ div.col-sm-3
- div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.name)
+ h6.inline Name
- div.col-md-3: h6 Summary
+ div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.name)
- div.col-md-3
+ div.col-sm-4: h6 Summary
- div.inline
+ div.col-sm-2
- h6 Annotation
+ div.inline
- div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.annotation)
-
- div.col-md-2: h6 Remove
+ h6 Annotation
- div.mt-3(data-hook="edit-reaction-list")
+ div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.annotation)
+
+ div.col-sm-2: h6 Remove
- div.col-md-5.container-part(data-hook="reaction-details-container")
+ div.mt-3(data-hook="edit-reaction-list")
div(data-hook="massaction-message"): p.text-info To add a mass action reaction the model must have at least one parameter
@@ -66,7 +60,7 @@ div#reactions-editor.card
div.dropdown.inline
button.btn.btn-outline-primary.dropdown-toggle.box-shadow#addReactionBtn(
- data-hook="add-reaction-full",
+ data-hook="add-reaction",
data-toggle="dropdown",
aria-haspopup="true",
aria-expanded="false",
@@ -74,29 +68,41 @@ div#reactions-editor.card
) Add Reaction
ul.dropdown-menu(aria-labelledby="addReactionBtn")
- li.dropdown-item(data-hook="creation") Creation Reaction
- li.dropdown-item(data-hook="destruction") Destruction Reaction
- li.dropdown-item(data-hook="change") Transformation Reaction
- li.dropdown-item(data-hook="dimerization") Dimerization Reaction
- li.dropdown-item(data-hook="merge") Merge Reaction
- li.dropdown-item(data-hook="split") Split Reaction
- li.dropdown-item(data-hook="four") Four Reaction
+ li.dropdown-header Mass Action
li.dropdown-divider
- li.dropdown-item(data-hook="custom-massaction") Custom mass action
- li.dropdown-item(data-hook="custom-propensity") Custom propensity
-
- div.dropdown.inline
-
- button.btn.btn-outline-primary.dropdown-toggle.box-shadow#addReactionBtn(
- data-hook="add-reaction-partial",
- data-toggle="dropdown",
- aria-haspopup="true",
- aria-expanded="false",
- type="button"
- ) Add Reaction
-
- ul.dropdown-menu(aria-labelledby="addReactionBtn")
- li.dropdown-item(data-hook="custom-propensity") Custom propensity
+ li.dropdown-item.rtype(data-hook="creation")
+
+ span.reaction-lb2 Creation Reaction
+ span.reaction-lb3 Parameter Required
+ li.dropdown-item.rtype(data-hook="destruction")
+
+ span.reaction-lb2 Destruction Reaction
+ span.reaction-lb3 Parameter Required
+ li.dropdown-item.rtype(data-hook="change")
+
+ span.reaction-lb2 Transformation Reaction
+ span.reaction-lb3 Parameter Required
+ li.dropdown-item.rtype(data-hook="dimerization")
+
+ span.reaction-lb2 Dimerization Reaction
+ span.reaction-lb3 Parameter Required
+ li.dropdown-item.rtype(data-hook="merge")
+
+ span.reaction-lb2 Merge Reaction
+ span.reaction-lb3 Parameter Required
+ li.dropdown-item.rtype(data-hook="split")
+
+ span.reaction-lb2 Split Reaction
+ span.reaction-lb3 Parameter Required
+ li.dropdown-item.rtype(data-hook="four")
+
+ span.reaction-lb2 Four Reaction
+ span.reaction-lb3 Parameter Required
+ li.dropdown-item.rtype(data-hook="custom-massaction")
+ span Custom Mass Action
+ span.reaction-lb3 Parameter Required
+ li.dropdown-divider
+ li.dropdown-item(data-hook="custom-propensity") Custom Propensity
div.tab-pane(id="view-reactions" data-hook="view-reactions")
diff --git a/client/model-view/templates/stoichSpecieView.pug b/client/model-view/templates/stoichSpecieView.pug
new file mode 100644
index 0000000000..073d00ec0b
--- /dev/null
+++ b/client/model-view/templates/stoichSpecieView.pug
@@ -0,0 +1,21 @@
+div
+
+ label.select
+
+ div.inline(data-hook="custom-decrement")
+
+ button.btn.btn-outline-secondary.btn-sm(data-hook='decrement') -
+
+ span.custom(data-hook="ratio")
+
+ div.inline(data-hook="custom-increment")
+
+ button.btn.btn-outline-secondary.btn-sm(data-hook='increment') +
+
+ select(data-hook="select-stoich-specie")
+
+ span.message.message-below.message-error(data-hook="message-container")
+
+ p(data-hook="message-text")
+
+ button.btn.btn-outline-secondary.custom.btn-sm(data-hook='custom-remove') X
diff --git a/client/model-view/templates/viewReaction.pug b/client/model-view/templates/viewReaction.pug
index 21be6b3275..6c45bdc68a 100644
--- a/client/model-view/templates/viewReaction.pug
+++ b/client/model-view/templates/viewReaction.pug
@@ -5,20 +5,38 @@ div.mx-1
div.row.align-items-baseline
- div.col-md-2
+ div.col-sm-2
div.pl-2=this.model.name
- div.col-md-3(data-hook="summary")
+ div.col-sm-3(data-hook="summary")
- div.col-md-3=this.rate
+ if this.model.massaction
+ div.col-sm-3
+
+ div=this.rate
+ else
+ div.col-sm-3
+ h5
+
+ span Stochastic Propensity Function:
+
+ div.pl-3=this.model.propensity
+
+ if !this.model.mirrorPropensities
+ h5
+
+ span ODE Propensity Function:
+
+ div.pl-3=this.model.odePropensity
+
if this.model.collection.parent.is_spatial
- div.col-md-2(data-hook="reaction-types")
+ div.col-sm-2(data-hook="reaction-types")
div=this.types.join(', ')
- div.col-md-2(data-hook="reaction-annotation-header")
+ div.col-sm-2(data-hook="reaction-annotation-header")
if this.model.annotation
div.tooltip-icon-large(data-hook="annotation-tooltip" data-html="true" data-toggle="tooltip" title=this.model.annotation)
diff --git a/client/model-view/views/domain-viewer.js b/client/model-view/views/domain-viewer.js
index 04b83f3fdc..c2a7d99b18 100644
--- a/client/model-view/views/domain-viewer.js
+++ b/client/model-view/views/domain-viewer.js
@@ -18,48 +18,42 @@ along with this program. If not, see .
let $ = require('jquery');
let path = require('path');
-let _ = require('underscore');
//support files
let app = require('../../app');
-let Plotly = require('../../lib/plotly');
-let Tooltips = require('../../tooltips');
+//models
+let Domain = require('../../models/domain');
//views
let View = require('ampersand-view');
let SelectView = require('ampersand-select-view');
-let TypesViewer = require('../../views/edit-domain-type');
-let ParticleViewer = require('../../views/view-particle');
-let QuickviewDomainTypes = require('../../views/quickview-domain-types');
+let DomainView = require('../../domain-view/domain-view');
//templates
let template = require('../templates/domainViewer.pug');
module.exports = View.extend({
template: template,
events: {
- 'click [data-hook=domain-edit-tab]' : 'toggleViewExternalDomainBtn',
- 'click [data-hook=domain-view-tab]' : 'toggleViewExternalDomainBtn',
+ 'change [data-hook=select-domain]' : 'handleSelectDomain',
+ 'change [data-hook=select-location]' : 'handleSelectLocation',
+ 'click [data-hook=domain-edit-tab]' : 'handleModeSwitch',
+ 'click [data-hook=domain-view-tab]' : 'handleModeSwitch',
'click [data-hook=collapse]' : 'changeCollapseButtonText',
+ 'click [data-hook=select-external-domain]' : 'handleViewExternalDomains',
'click [data-hook=edit-domain-btn]' : 'editDomain',
'click [data-hook=create-domain]' : 'editDomain',
- 'click [data-hook=save-to-model]' : 'saveDomainToModel',
- 'click [data-hook=select-external-domain]' : 'handleLoadExternalDomain',
- 'change [data-hook=select-domain]' : 'handleSelectDomain',
- 'change [data-hook=select-location]' : 'handleSelectDomainLocation'
+ 'click [data-hook=save-to-model]' : 'saveDomainToModel'
},
initialize: function (attrs, options) {
View.prototype.initialize.apply(this, arguments);
this.readOnly = attrs.readOnly ? attrs.readOnly : false;
- this.tooltips = Tooltips.domainEditor;
- this.domainPath = attrs.domainPath;
- this.plot = Boolean(attrs.domainPlot) ? attrs.domainPlot : null;
- this.elements = Boolean(attrs.domainElements) ? attrs.domainElements : null;
- this.model.particles.forEach((particle) => {
- this.model.types.get(particle.type, "typeID").numParticles += 1;
- });
- this.gravity = this.getGravityString();
+ this.elements = attrs.domainElements ? attrs.domainElements : null;
+ this.plot = attrs.domainPlot ? attrs.domainPlot : null;
+ this.domainPath = null;
+ this.domain = null;
+ this.files = null;
+ this.locations = null;
},
render: function (attrs, options) {
View.prototype.render.apply(this, arguments);
- this.queryStr = `?path=${this.parent.model.directory}`;
if(this.readOnly) {
$(this.queryByHook('domain-edit-tab')).addClass("disabled");
$(".nav .disabled>a").on("click", (e) => {
@@ -69,33 +63,25 @@ module.exports = View.extend({
$(this.queryByHook('domain-view-tab')).tab('show');
$(this.queryByHook('edit-domain')).removeClass('active');
$(this.queryByHook('view-domain')).addClass('active');
- this.toggleViewExternalDomainBtn();
- }else {
- this.renderDomainSelectView();
- if(this.domainPath) {
- $(this.queryByHook("domain-container")).collapse("show");
- $(this.queryByHook("collapse")).text("-");
- if(this.domainPath !== "viewing") {
- $(this.queryByHook("save-to-model")).prop("disabled", false);
- $(this.queryByHook("external-domain-select")).css("display", "block");
- $(this.queryByHook("select-external-domain")).text("View Model's Domain");
- this.queryStr += `&domain_path=${this.domainPath}`;
- }
+ this.toggleViewExternalDomains(this.readOnly);
+ }
+ if(!this.elements) {
+ $(this.queryByHook("view-domain-plot-container")).css('display', 'block');
+ this.elements = {
+ select: $(this.queryByHook("domain-select-particle")),
+ particle: {view: this, hook: "domain-particle-viewer"},
+ figure: this.queryByHook("view-domain-plot"),
+ // figureEmpty: this.queryByHook("domain-plot-container-empty"),
+ type: this.queryByHook("domain-types-quick-view")
}
- this.toggleDomainError();
}
- this.renderTypesViewer();
- this.renderPlotParticleViewer();
+ this.renderDomainView();
},
changeCollapseButtonText: function (e) {
app.changeCollapseButtonText(this, e);
},
- displayDomain: function (domainPreview) {
- Plotly.newPlot(domainPreview, this.plot);
- domainPreview.on('plotly_click', _.bind(this.selectParticle, this));
- },
editDomain: function (e) {
- var queryStr = `?path=${this.parent.model.directory}`;
+ var queryStr = `?path=${this.model.directory}`;
if(e.target.dataset.hook === "create-domain") {
queryStr += "&new"
}else if(this.domainPath) {
@@ -104,45 +90,69 @@ module.exports = View.extend({
let endpoint = path.join(app.getBasePath(), "stochss/domain/edit") + queryStr;
window.location.href = endpoint;
},
- getDomainSelectValue: function (files) {
- if(!this.domainPath || this.domainPath === "viewing") {
- return null;
- }
- let domainFile = this.domainPath.split('/').pop();
- let value = files.filter((file) => {
- return file[1] === domainFile;
- })[0][0];
- return value;
- },
- getGravityString: function () {
- var gravity = `(X: ${this.model.gravity[0]}`;
- gravity += `, Y: ${this.model.gravity[1]}`;
- gravity += `, Z: ${this.model.gravity[2]})`;
- return gravity;
- },
- handleLoadExternalDomain: function (e) {
- let text = e.target.textContent;
- if(text === "View External Domain") {
- $(this.queryByHook("external-domain-select")).css("display", "block");
- $(this.queryByHook("select-external-domain")).text("View Model's Domain");
- }else{
- this.reloadDomain("viewing");
+ handleModeSwitch: function (e) {
+ if(!e.target.classList.contains('active')) {
+ this.toggleViewExternalDomains(e.target.text === "View");
}
},
handleSelectDomain: function (e) {
let value = e.srcElement.value;
if(value) {
- if(this.externalDomains[value].length <= 1) {
- this.reloadDomain(this.externalDomains[value][0]);
+ if(this.locations[value].length <= 1) {
+ $(this.queryByHook('select-location-message')).css('display', 'none');
+ $(this.queryByHook('select-domain-location')).css('display', 'none');
+ this.domainPath = this.locations[value][0];
+ this.loadExternalDomainModel();
+ }else{
+ this.renderDomainLocationSelectView(this.locations[value]);
+ }
+ }
+ },
+ handleSelectLocation: function (e) {
+ this.domainPath = e.srcElement.value;
+ this.loadExternalDomainModel();
+ },
+ handleViewExternalDomains: function (e) {
+ let display = e.target.textContent === "View External Domain";
+ if(display) {
+ $(this.queryByHook('select-external-domain')).text("View Model's Domain");
+ if(!this.files) {
+ this.loadExternalDomainFiles();
}else{
- $(this.queryByHook("select-location-message")).css('display', "block");
- $(this.queryByHook("select-domain-location")).css("display", "inline-block");
- this.renderDomainLocationSelectView(this.externalDomains[value]);
+ this.renderDomainFileSelectView();
}
+ }else{
+ $(this.queryByHook('select-external-domain')).text("View External Domain");
+ this.domain = null;
+ this.domainPath = null;
+ $(this.queryByHook('select-location-message')).css('display', 'none');
+ $(this.queryByHook('select-domain-location')).css('display', 'none');
+ $(this.queryByHook('external-domain-select')).css('display', 'none');
+ $(this.queryByHook('save-to-model')).prop('disabled', true);
+ this.renderDomainView();
}
},
- handleSelectDomainLocation: function (e) {
- this.reloadDomain(e.srcElement.value);
+ loadExternalDomainFiles: function () {
+ let endpoint = path.join(app.getApiPath(), "spatial-model/domain-list");
+ app.getXHR(endpoint, {
+ always: (err, response, body) => {
+ this.files = body.files;
+ this.locations = body.paths;
+ this.renderDomainFileSelectView();
+ }
+ });
+ },
+ loadExternalDomainModel: function () {
+ let queryStr = `?path=${this.model.directory}&domain_path=${this.domainPath}`;
+ let endpoint = path.join(app.getApiPath(), "spatial-model/load-domain") + queryStr;
+ app.getXHR(endpoint, {
+ always: (err, response, body) => {
+ console.log(body)
+ this.domain = new Domain(body.domain);
+ this.renderDomainView({modelsDomain: false});
+ $(this.queryByHook('save-to-model')).prop('disabled', false);
+ }
+ });
},
openSection: function () {
if(!$(this.queryByHook("domain-container")).hasClass("show")) {
@@ -152,156 +162,60 @@ module.exports = View.extend({
}
app.switchToEditTab(this, "domain");
},
- reloadDomain: function (domainPath) {
- if(this.domainPath !== domainPath || domainPath === "viewing") {
- let el = this.elements === null ? this.queryByHook("view-domain-plot") : this.elements.plot;
- el.removeListener('plotly_click', this.selectParticle);
- Plotly.purge(el);
- this.plot = null;
- this.model.types.forEach(function (type) {
- type.numParticles = 0;
- });
- this.parent.renderDomainViewer(domainPath);
+ renderDomainFileSelectView: function () {
+ if(this.domainFileSelectView) {
+ this.domainFileSelectView.remove();
}
+ this.domainFileSelectView = new SelectView({
+ name: 'domain-files',
+ required: false,
+ idAttributes: 'cid',
+ options: this.files,
+ unselectedText: '-- Select Domain --'
+ });
+ app.registerRenderSubview(this, this.domainFileSelectView, 'select-domain');
+ $(this.queryByHook('external-domain-select')).css('display', 'block');
},
renderDomainLocationSelectView: function (options) {
if(this.domainLocationSelectView) {
this.domainLocationSelectView.remove();
}
this.domainLocationSelectView = new SelectView({
- name: 'locations',
+ name: 'file-locations',
required: false,
idAttributes: 'cid',
options: options,
unselectedText: "-- Select Location --"
});
app.registerRenderSubview(this, this.domainLocationSelectView, "select-location");
- },
- renderDomainSelectView: function () {
- let endpoint = path.join(app.getApiPath(), "spatial-model/domain-list");
- app.getXHR(endpoint, {
- always: (err, response, body) => {
- this.externalDomains = body.paths;
- let domainSelectView = new SelectView({
- name: 'domains',
- required: false,
- idAttributes: 'cid',
- options: body.files,
- unselectedText: "-- Select Domain --",
- value: this.getDomainSelectValue(body.files)
- });
- app.registerRenderSubview(this, domainSelectView, "select-domain");
- }
+ $(this.queryByHook('select-location-message')).css('display', 'block');
+ $(this.queryByHook('select-domain-location')).css('display', 'inline-block');
+ },
+ renderDomainView: function ({modelsDomain=true}={}) {
+ if(this.domainView) {
+ this.domainView.remove();
+ }
+ let domain = modelsDomain ? this.model.domain : this.domain;
+ var queryStr = `?path=${this.model.directory}`;
+ if(!modelsDomain) {
+ queryStr += `&domain_path=${this.domainPath}`;
+ }
+ this.domainView = new DomainView({
+ model: domain,
+ readOnly: true,
+ elements: this.elements,
+ plot: this.plot,
+ queryStr: queryStr
});
- },
- renderLocalParticlesViewer: function ({particle=null}) {
- if(particle){
- $(this.queryByHook("domain-select-particle")).css("display", "none");
- this.particleViewer = new ParticleViewer({
- model: particle
- });
- app.registerRenderSubview(this, this.particleViewer, "domain-particle-viewer");
- }else{
- $(this.queryByHook("domain-select-particle")).css("display", "block");
- this.typeQuickViewer = this.renderCollection(
- this.model.types,
- QuickviewDomainTypes,
- this.queryByHook("domain-types-quick-view")
- );
- }
- },
- renderMEParticlesViewer: function ({particle=null}) {
- if(particle){
- this.elements.select.css("display", "none");
- this.particleViewer = new ParticleViewer({
- model: particle
- });
- app.registerRenderSubview(this.elements.particle.view, this.particleViewer, this.elements.particle.hook);
- }else{
- this.elements.select.css("display", "block");
- this.typeQuickViewer = this.renderCollection(
- this.model.types,
- QuickviewDomainTypes,
- this.elements.type
- );
- }
- },
- renderPlot: function (plotElement) {
- if(this.plot === null) {
- let endpoint = path.join(app.getApiPath(), "spatial-model/domain-plot") + this.queryStr;
- app.getXHR(endpoint, {
- always: (err, response, body) => {
- this.plot = body.fig;
- this.displayDomain(plotElement);
- }
- });
- }else{
- this.displayDomain(plotElement);
- }
- },
- renderPlotParticleViewer: function ({particle=null}={}) {
- if(this.particleViewer) {
- this.particleViewer.remove();
- }
- if(this.typeQuickViewer) {
- this.typeQuickViewer.remove();
- }
- if(this.elements === null) {
- $(this.queryByHook("view-domain-plot-container")).css('display', 'block');
- this.renderLocalParticlesViewer({particle: particle});
- if(particle === null) {
- this.renderPlot(this.queryByHook("view-domain-plot"));
- }
- }else{
- this.renderMEParticlesViewer({particle: particle});
- if(particle === null) {
- this.renderPlot(this.elements.plot);
- }
- }
- },
- renderTypesViewer: function () {
- if(this.typesViewer) {
- this.typesViewer.remove();
- }
- this.typesViewer = this.renderCollection(
- this.model.types,
- TypesViewer,
- this.queryByHook("domain-types-list"),
- {filter: (model) => {
- return model.typeID != 0;
- }, viewOptions: {viewMode: true}}
- );
+ app.registerRenderSubview(this, this.domainView, "domain-view-container");
},
saveDomainToModel: function (e) {
- this.parent.model.domain = this.model;
- this.parent.modelStateButtons.clickSaveHandler(e);
- this.reloadDomain();
- },
- selectParticle: function (data) {
- let point = data.points[0];
- let particle = this.model.particles.get(point.id, "particle_id");
- this.renderPlotParticleViewer({particle: particle});
+ this.model.domain = this.domain;
+ this.parent.parent.clickSaveHandler(e);
+ this.renderDomainView();
},
- toggleDomainError: function () {
- let errorMsg = $(this.queryByHook('domain-error'));
- this.model.updateValid();
- if(!this.model.valid) {
- errorMsg.addClass('component-invalid');
- errorMsg.removeClass('component-valid');
- }else{
- errorMsg.addClass('component-valid');
- errorMsg.removeClass('component-invalid');
- }
+ toggleViewExternalDomains: function (hide) {
+ let display = hide ? "none" : "block";
+ $(this.queryByHook('external-domains-container')).css('display', display);
},
- toggleViewExternalDomainBtn: function (e) {
- if(e) {
- if(!e.target.classList.contains("active")) {
- let display = e.target.text === "View" ? "none" : "block";
- $(this.queryByHook("external-domains-container")).css("display", display);
- }
- }else{
- let display = this.readOnly ? "none" : "block";
- $(this.queryByHook("external-domains-container")).css("display", display);
- }
- }
});
\ No newline at end of file
diff --git a/client/model-view/views/edit-stoich-specie.js b/client/model-view/views/edit-stoich-specie.js
deleted file mode 100644
index e4d15bdc24..0000000000
--- a/client/model-view/views/edit-stoich-specie.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
-StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see .
-*/
-
-var $ = require('jquery');
-//views
-var SelectView = require('ampersand-select-view');
-//templates
-var template = require('../templates/editStoichSpecie.pug');
-
-module.exports = SelectView.extend({
- // SelectView expects a string template, so pre-render it
- template: template(),
- bindings: {
- 'model.ratio' : {
- hook: 'ratio'
- }
- },
- events: {
- 'change select' : 'selectChangeHandler'
- },
- initialize: function () {
- SelectView.prototype.initialize.apply(this, arguments);
- this.value = this.model.specie || null;
- },
- render: function() {
- SelectView.prototype.render.apply(this, arguments);
- },
- update: function () {
- },
- selectChangeHandler: function (e) {
- var species = this.getSpeciesCollection();
- var reactions = this.getReactionsCollection();
- var specie = species.filter(function (m) {
- return m.name === e.target.selectedOptions.item(0).text;
- })[0];
- this.model.specie = specie;
- this.value = specie;
- reactions.trigger("change");
- this.model.collection.parent.trigger('change-reaction')
- },
- getReactionsCollection: function () {
- return this.model.collection.parent.collection;
- },
- getSpeciesCollection: function () {
- return this.model.collection.parent.collection.parent.species;
- },
-});
\ No newline at end of file
diff --git a/client/model-view/views/parameters-view.js b/client/model-view/views/parameters-view.js
index 4dc463bb68..c9c95b6e9b 100644
--- a/client/model-view/views/parameters-view.js
+++ b/client/model-view/views/parameters-view.js
@@ -41,6 +41,9 @@ module.exports = View.extend({
self.collection.parent.reactions.map(function (reaction) {
if(reaction.rate && reaction.rate.compID === compID){
reaction.rate = parameter;
+ if(reaction.reactionType !== 'custom-propensity') {
+ reaction.trigger('change-reaction');
+ }
}
});
self.collection.parent.eventsCollection.map(function (event) {
diff --git a/client/model-view/views/reactant-product.js b/client/model-view/views/reactant-product.js
index d10f54923a..f3b82d54b9 100644
--- a/client/model-view/views/reactant-product.js
+++ b/client/model-view/views/reactant-product.js
@@ -17,68 +17,96 @@ along with this program. If not, see .
*/
let $ = require('jquery');
-//models
-let StoichSpecie = require('../../models/stoich-specie');
+//support files
+let app = require('../../app');
//views
let View = require('ampersand-view');
let SelectView = require('ampersand-select-view');
-let EditStoichSpecieView = require('./edit-stoich-specie');
-let EditCustomStoichSpecieView = require('./edit-custom-stoich-specie');
+let StoichSpeciesView = require('./stoich-species-view');
//templates
let template = require('../templates/reactantProduct.pug');
module.exports = View.extend({
template: template,
- events: {
- 'change [data-hook=select-specie]' : 'selectSpecie',
- 'click [data-hook=add-selected-specie]' : 'addSelectedSpecie'
+ events: function () {
+ let events = {};
+ events[`change [data-hook=${this.hookAnchor}-select-specie]`] = 'selectSpecie';
+ events[`click [data-hook=${this.hookAnchor}-add-selected-specie]`] = 'addSelectedSpecie';
+ return events;
},
initialize: function (args) {
View.prototype.initialize.apply(this, arguments);
this.collection = args.collection;
this.species = args.species;
this.reactionType = args.reactionType;
- this.isReactants = args.isReactants
+ this.custom = args.reactionType.startsWith('custom');
+ this.isReactants = args.isReactants;
this.unselectedText = 'Pick a species';
- this.fieldTitle = args.fieldTitle;
+ if(this.isReactants) {
+ this.fieldTitle = 'Reactants';
+ this.hookAnchor = 'reactants';
+ }else{
+ this.fieldTitle = 'Products';
+ this.hookAnchor = 'products';
+ }
},
render: function () {
View.prototype.render.apply(this, arguments);
+ if(this.isReactants) {
+ var tooltip = this.parent.parent.tooltips.reactant;
+ }else{
+ var tooltip = this.parent.parent.tooltips.product;
+ }
+ $(this.queryByHook('field-title-tooltip')).prop('title', tooltip);
+ this.renderStoichSpecies();
+ if(this.custom) {
+ $(this.queryByHook(`custom-${this.hookAnchor}`)).css('display', 'block');
+ this.renderSelectSpeciesView();
+ this.toggleAddSpecieButton();
+ }
+ },
+ addSelectedSpecie: function () {
+ var specieName = this.specieName ? this.specieName : 'Pick a variable';
+ if(this.validateAddSpecie()) {
+ this.collection.addStoichSpecie(specieName);
+ this.toggleAddSpecieButton();
+ this.collection.parent.trigger('change-reaction');
+ }
+ },
+ renderSelectSpeciesView: function () {
+ if(this.selectSpeciesView) {
+ this.selectSpeciesView.remove();
+ }
+
+ this.selectSpeciesView = new SelectView({
+ name: 'stoich-specie',
+ required: false,
+ textAttribute: 'name',
+ eagerValidate: false,
+ idAttribute: 'compID',
+ options: this.species,
+ unselectedText: this.unselectedText
+ });
+ app.registerRenderSubview(this, this.selectSpeciesView, `${this.hookAnchor}-select-specie`);
+ },
+ renderStoichSpecies: function () {
let args = {
viewOptions: {
name: 'stoich-specie',
required: true,
textAttribute: 'name',
eagerValidate: true,
- idAttribute: 'name',
+ idAttribute: 'compID',
+ yieldModel: false,
options: this.species
}
};
- let type = this.reactionType;
- let StoichSpeciesView = (type.startsWith('custom')) ? EditCustomStoichSpecieView : EditStoichSpecieView;
this.renderCollection(
this.collection,
StoichSpeciesView,
- this.queryByHook('reactants-editor'),
+ this.queryByHook(`${this.hookAnchor}-editor`),
args
);
- if(this.reactionType.startsWith('custom')) {
- $(this.queryByHook('collapse')).collapse();
- }
- this.toggleAddSpecieButton();
- if(this.fieldTitle === "Reactants"){
- $(this.queryByHook('field-title-tooltip')).prop('title', this.parent.parent.tooltips.reactant);
- }else{
- $(this.queryByHook('field-title-tooltip')).prop('title', this.parent.parent.tooltips.product);
- }
- },
- addSelectedSpecie: function () {
- var specieName = this.specieName ? this.specieName : 'Pick a variable';
- if(this.validateAddSpecie()) {
- this.collection.addStoichSpecie(specieName);
- this.toggleAddSpecieButton();
- this.collection.parent.trigger('change-reaction');
- }
},
selectSpecie: function (e) {
if(this.unselectedText === e.target.selectedOptions.item(0).text){
@@ -90,37 +118,17 @@ module.exports = View.extend({
this.toggleAddSpecieButton();
},
toggleAddSpecieButton: function () {
- if(!this.validateAddSpecie()){
- $(this.queryByHook('add-selected-specie')).prop('disabled', true);
- }else{
- $(this.queryByHook('add-selected-specie')).prop('disabled', false);
- }
+ $(this.queryByHook(`${this.hookAnchor}-add-selected-specie`)).prop('disabled', !this.validateAddSpecie());
this.parent.toggleCustomReactionError();
},
validateAddSpecie: function () {
if(this.hasSelectedSpecie){
if(!this.collection.length) { return true; }
if(this.collection.length < 2 && this.collection.at(0).ratio < 2) { return true; }
- if(this.reactionType !== 'custom-massaction') { return true; }
+ if(this.reactionType === 'custom-propensity') { return true; }
if(!this.isReactants) { return true; }
return false;
}
return false;
},
- subviews: {
- selectSpecies: {
- hook: 'select-specie',
- prepareView: function (el) {
- return new SelectView({
- name: 'stoich-specie',
- required: false,
- textAttribute: 'name',
- eagerValidate: false,
- idAttribute: 'name',
- options: this.species,
- unselectedText: this.unselectedText
- });
- }
- }
- }
});
\ No newline at end of file
diff --git a/client/model-view/views/reaction-details.js b/client/model-view/views/reaction-details.js
deleted file mode 100644
index 981b699487..0000000000
--- a/client/model-view/views/reaction-details.js
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
-StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see .
-*/
-
-let $ = require('jquery');
-let katex = require('katex');
-let _ = require('underscore');
-//support files
-let app = require('../../app');
-let ReactionTypes = require('../../reaction-types');
-//models
-let StoichSpecie = require('../../models/stoich-specie');
-//views
-let InputView = require('../../views/input');
-let View = require('ampersand-view');
-let SelectView = require('ampersand-select-view');
-let ReactionTypesView = require('./reaction-types');
-let ReactantProductView = require('./reactant-product');
-//templates
-let template = require('../templates/reactionDetails.pug');
-
-module.exports = View.extend({
- template: template,
- bindings: {
- 'model.propensity': {
- type: 'value',
- hook: 'select-rate-parameter'
- },
- 'model.summary' : {
- type: function (el, value, previousValue) {
- katex.render(this.model.summary, this.queryByHook('summary-container'), {
- displayMode: true,
- output: 'html'
- });
- },
- hook: 'summary-container',
- },
- 'model.hasConflict': {
- type: function (el, value, previousValue) {
- if(value) {
- $(this.queryByHook('conflicting-modes-message')).collapse('show');
- }else{
- $(this.queryByHook('conflicting-modes-message')).collapse('hide');
- }
- },
- hook: 'conflicting-modes-message'
- }
- },
- events: {
- 'change [data-hook=select-rate-parameter]' : 'selectRateParam',
- 'change [data-hook=select-reaction-type]' : 'selectReactionType'
- },
- initialize: function (attrs, options) {
- View.prototype.initialize.apply(this, arguments);
- let self = this;
- this.model.on("change:reaction_type", function (model) {
- self.updateStoichSpeciesForReactionType(model.reactionType);
- });
- },
- render: function () {
- View.prototype.render.apply(this, arguments);
- let self = this;
- this.renderReactionTypesSelectView();
- if(this.model.reactionType === 'custom-propensity'){
- let propensityView = new InputView({
- parent: this,
- required: true,
- name: 'rate',
- modelKey:'propensity',
- valueType: 'string',
- value: this.model.propensity,
- placeholder: "--No Expression Entered--"
- });
- app.registerRenderSubview(this, propensityView, 'select-rate-parameter');
- $(this.queryByHook('rate-parameter-label')).text('Propensity:');
- $(this.queryByHook('rate-parameter-tooltip')).prop('title', this.parent.tooltips.propensity);
- }else{
- // make sure the reaction has a rate and that rate exists in the parameters collection
- let paramIDs = this.model.collection.parent.parameters.map(function (param) {
- return param.compID;
- });
- if(!this.model.rate.compID || !paramIDs.includes(this.model.rate.compID)) {
- this.model.rate = this.model.collection.getDefaultRate();
- }
- let rateParameterView = new SelectView({
- name: 'rate',
- required: true,
- idAttribute: 'cid',
- textAttribute: 'name',
- eagerValidate: true,
- options: this.model.collection.parent.parameters,
- // For new reactions (with no rate.name) just use the first parameter in the Parameters collection
- // Else fetch the right Parameter from Parameters based on existing rate
- value: this.model.rate.name ? this.getRateFromParameters(this.model.rate.name) : this.model.collection.parent.parameters.at(0)
- });
- app.registerRenderSubview(this, rateParameterView, 'select-rate-parameter');
- $(this.queryByHook('rate-parameter-label')).text('Rate Parameter:');
- $(this.queryByHook('rate-parameter-tooltip')).prop('title', this.parent.tooltips.rate);
- }
- let reactantsView = new ReactantProductView({
- collection: this.model.reactants,
- species: this.model.collection.parent.species,
- reactionType: this.model.reactionType,
- fieldTitle: 'Reactants',
- isReactants: true
- });
- app.registerRenderSubview(this, reactantsView, 'reactants-editor');
- let productsView = new ReactantProductView({
- collection: this.model.products,
- species: this.model.collection.parent.species,
- reactionType: this.model.reactionType,
- fieldTitle: 'Products',
- isReactants: false
- });
- app.registerRenderSubview(this, productsView, 'products-editor');
- if(this.model.collection.parent.is_spatial) {
- let typesView = new ReactionTypesView({
- model: this.model,
- parent: this
- });
- app.registerRenderSubview(this, typesView, 'domain-types-editor');
- }
- this.totalRatio = this.getTotalReactantRatio();
- app.tooltipSetup();
- this.toggleCustomReactionError();
- },
- getArrayOfDefaultStoichSpecies: function (arr) {
- return arr.map(function (params) {
- let stoichSpecie = new StoichSpecie(params);
- stoichSpecie.specie = this.parent.getDefaultSpecie();
- return stoichSpecie;
- }, this);
- },
- getRateFromParameters: function (name) {
- // Seems like model.rate is not actually part of the Parameters collection
- // Get the Parameter from Parameters that matches model.rate
- // TODO this is some garbagio, get model.rate into Parameters collection...?
- if (!name) { name = this.model.rate.name };
- let rate = this.model.collection.parent.parameters.filter(function (param) {
- return param.name === name;
- })[0];
- return rate;
- },
- getReactionTypeLabels: function () {
- return _.map(ReactionTypes, function (val, key) { return val.label; });
- },
- getTotalReactantRatio: function () {
- return this.model.reactants.length;
- },
- renderReactionTypes: function () {
- if(this.model.collection.parent.parameters.length < 1){ return };
- let options = {
- displayMode: true,
- output: 'html'
- }
- katex.render(ReactionTypes['creation'].label, this.queryByHook('select-reaction-type').firstChild.children[1]['0'], options);
- katex.render(ReactionTypes['destruction'].label, this.queryByHook('select-reaction-type').firstChild.children[1]['1'], options);
- katex.render(ReactionTypes['change'].label, this.queryByHook('select-reaction-type').firstChild.children[1]['2'], options);
- katex.render(ReactionTypes['dimerization'].label, this.queryByHook('select-reaction-type').firstChild.children[1]['3'], options);
- katex.render(ReactionTypes['merge'].label, this.queryByHook('select-reaction-type').firstChild.children[1]['4'], options);
- katex.render(ReactionTypes['split'].label, this.queryByHook('select-reaction-type').firstChild.children[1]['5'], options);
- katex.render(ReactionTypes['four'].label, this.queryByHook('select-reaction-type').firstChild.children[1]['6'], options);
- },
- renderReactionTypesSelectView: function () {
- if(this.reactionTypesSelectView) {
- this.reactionTypesSelectView.remove();
- }
- if(this.model.collection.parent.parameters.length <= 0){
- var options = ["Custom propensity"];
- }
- else{
- var options = this.getReactionTypeLabels();
- }
- this.reactionTypesSelectView = new SelectView({
- label: 'Reaction Type:',
- name: 'reaction-type',
- required: true,
- idAttribute: 'cid',
- options: options,
- value: ReactionTypes[this.model.reactionType].label
- });
- app.registerRenderSubview(this, this.reactionTypesSelectView, 'select-reaction-type');
- this.renderReactionTypes();
- },
- selectRateParam: function (e) {
- if(this.model.reactionType !== 'custom-propensity') {
- let val = e.target.selectedOptions.item(0).text;
- let param = this.getRateFromParameters(val);
- this.model.rate = param || this.model.rate;
- this.model.trigger("change");
- this.model.collection.trigger("change");
- }
- },
- selectReactionType: function (e) {
- let label = e.target.selectedOptions.item(0).value;
- let type = _.findKey(ReactionTypes, function (o) { return o.label === label; });
- this.model.reactionType = type;
- this.model.summary = label;
- this.updateStoichSpeciesForReactionType(type);
- this.model.collection.trigger("change");
- this.model.trigger('change-reaction');
- this.render();
- },
- toggleCustomReactionError: function () {
- let errorMsg = $(this.queryByHook("custom-reaction-error"));
- if(this.model.reactants.length <= 0 && this.model.products.length <= 0) {
- errorMsg.addClass('component-invalid');
- errorMsg.removeClass('component-valid');
- }else{
- errorMsg.addClass('component-valid');
- errorMsg.removeClass('component-invalid');
- }
- },
- update: function () {},
- updateStoichSpeciesForReactionType: function (type) {
- let args = this.parent.getStoichArgsForReactionType(type);
- let newReactants = this.getArrayOfDefaultStoichSpecies(args.reactants);
- let newProducts = this.getArrayOfDefaultStoichSpecies(args.products);
- this.model.reactants.reset(newReactants);
- this.model.products.reset(newProducts);
- if(type !== 'custom-propensity')
- this.model.rate = this.model.collection.getDefaultRate();
- },
- updateValid: function () {}
-});
\ No newline at end of file
diff --git a/client/model-view/views/reaction-listing.js b/client/model-view/views/reaction-listing.js
deleted file mode 100644
index f8f9104774..0000000000
--- a/client/model-view/views/reaction-listing.js
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
-StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see .
-*/
-
-let $ = require('jquery');
-let katex = require('katex');
-let _ = require('underscore');
-//support files
-let app = require('../../app');
-let tests = require('../../views/tests');
-let modals = require('../../modals');
-//views
-let InputView = require('../../views/input');
-let View = require('ampersand-view');
-//templates
-let viewTemplate = require('../templates/viewReaction.pug');
-let editTemplate = require('../templates/reactionListing.pug');
-
-module.exports = View.extend({
- bindings: {
- 'model.name' : {
- type: 'value',
- hook: 'input-name-container'
- },
- 'model.summary' : {
- type: function (el, value, previousValue) {
- katex.render(this.model.summary, this.queryByHook('summary'), {
- displayMode: true,
- output: 'html',
- maxSize: 5
- });
- },
- hook: 'summary'
- },
- 'model.selected' : {
- type: function (el, value, previousValue) {
- el.checked = value;
- },
- hook: 'select'
- }
- },
- events: {
- 'click [data-hook=edit-annotation-btn]' : 'editAnnotation',
- 'click [data-hook=select]' : 'selectReaction',
- 'click [data-hook=remove]' : 'removeReaction'
- },
- initialize: function (attrs, options) {
- View.prototype.initialize.apply(this, arguments);
- this.viewMode = attrs.viewMode ? attrs.viewMode : false;
- if(this.viewMode) {
- this.rate = this.model.reactionType === "custom-propensity" ? this.model.propensity : this.model.rate.name;
- this.types = [];
- let self = this;
- if(this.model.types) {
- this.model.types.forEach(function (index) {
- let type = self.model.collection.parent.domain.types.get(index, "typeID");
- self.types.push(type.name);
- });
- }
- }else{
- this.model.on('change', _.bind(this.updateViewer, this));
- }
- },
- render: function () {
- this.template = this.viewMode ? viewTemplate : editTemplate;
- View.prototype.render.apply(this, arguments);
- app.documentSetup();
- if(!this.model.annotation){
- $(this.queryByHook('edit-annotation-btn')).text('Add');
- }
- },
- editAnnotation: function () {
- if(document.querySelector('#reactionAnnotationModal')) {
- document.querySelector('#reactionAnnotationModal').remove();
- }
- let self = this;
- let name = this.model.name;
- let annotation = this.model.annotation;
- let modal = $(modals.annotationModalHtml("reaction", name, annotation)).modal();
- let okBtn = document.querySelector('#reactionAnnotationModal .ok-model-btn');
- let input = document.querySelector('#reactionAnnotationModal #reactionAnnotationInput');
- input.addEventListener("keyup", function (event) {
- if(event.keyCode === 13){
- event.preventDefault();
- okBtn.click();
- }
- });
- okBtn.addEventListener('click', function (e) {
- self.model.annotation = input.value.trim();
- self.parent.renderEditReactionListingView();
- modal.modal('hide');
- });
- },
- removeReaction: function (e) {
- this.collection.removeReaction(this.model);
- this.parent.collection.trigger("change");
- },
- selectReaction: function (e) {
- this.model.collection.trigger("select", this.model);
- },
- update: function () {},
- updateValid: function () {},
- updateViewer: function () {
- this.parent.renderViewReactionView();
- },
- subviews: {
- inputName: {
- hook: 'input-name-container',
- prepareView: function (el) {
- return new InputView({
- parent: this,
- required: true,
- name: 'name',
- tests: tests.nameTests,
- modelKey: 'name',
- valueType: 'string',
- value: this.model.name
- });
- }
- }
- }
-});
\ No newline at end of file
diff --git a/client/model-view/views/reaction-types.js b/client/model-view/views/reaction-restrict-to.js
similarity index 79%
rename from client/model-view/views/reaction-types.js
rename to client/model-view/views/reaction-restrict-to.js
index 6cf01751e9..7f48966fc8 100644
--- a/client/model-view/views/reaction-types.js
+++ b/client/model-view/views/reaction-restrict-to.js
@@ -18,9 +18,9 @@ along with this program. If not, see .
//views
let View = require('ampersand-view');
-let TypesView = require('./component-types');
+let DomainTypesView = require('./component-types');
//templates
-let template = require('../templates/reactionTypes.pug');
+let template = require('../templates/reactionRestrictTo.pug');
module.exports = View.extend({
template: template,
@@ -31,15 +31,15 @@ module.exports = View.extend({
},
render: function () {
View.prototype.render.apply(this, arguments);
- this.renderTypes();
+ this.renderDomainTypes();
},
- renderTypes: function () {
- if(this.typesView) {
- this.typesView.remove();
+ renderDomainTypes: function () {
+ if(this.domainTypesView) {
+ this.domainTypesView.remove();
}
- this.typesView = this.renderCollection(
+ this.domainTypesView = this.renderCollection(
this.baseModel.domain.types,
- TypesView,
+ DomainTypesView,
this.queryByHook("reaction-types-container"),
{"filter": function (model) {
return model.typeID != 0;
diff --git a/client/model-view/views/reaction-view.js b/client/model-view/views/reaction-view.js
new file mode 100644
index 0000000000..30f8042e32
--- /dev/null
+++ b/client/model-view/views/reaction-view.js
@@ -0,0 +1,486 @@
+/*
+StochSS is a platform for simulating biochemical systems
+Copyright (C) 2019-2022 StochSS developers.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+let $ = require('jquery');
+let katex = require('katex');
+let _ = require('underscore');
+//support files
+let app = require('../../app');
+let modals = require('../../modals');
+let tests = require('../../views/tests');
+let ReactionTypes = require('../../reaction-types');
+//collections
+let StoichSpecies = require('../../models/stoich-species');
+//views
+let View = require('ampersand-view');
+let InputView = require('../../views/input');
+let SelectView = require('ampersand-select-view');
+let RestrictToView = require('./reaction-restrict-to');
+let ReactantProductView = require('./reactant-product');
+//templates
+let viewTemplate = require('../templates/viewReaction.pug');
+let editTemplate = require('../templates/editReaction.pug');
+
+module.exports = View.extend({
+ bindings: {
+ 'model.name' : {
+ type: 'value',
+ hook: 'input-name-container'
+ },
+ 'model.summary' : {
+ type: function (el, value, previousValue) {
+ katex.render(this.model.summary, this.queryByHook('summary'), {
+ displayMode: true,
+ output: 'html',
+ maxSize: 5
+ });
+ },
+ hook: 'summary'
+ },
+ 'model.selected' : {
+ type: function (el, value, previousValue) {
+ el.checked = value;
+ },
+ hook: 'select'
+ },
+ 'model.hasConflict': {
+ type: function (el, value, previousValue) {
+ if(value) {
+ $(this.queryByHook('conflicting-modes-message')).collapse('show');
+ }else{
+ $(this.queryByHook('conflicting-modes-message')).collapse('hide');
+ }
+ },
+ hook: 'conflicting-modes-message'
+ },
+ 'model.mirrorPropensities': {
+ type: function (el, value, previousValue) {
+ el.checked = value;
+ },
+ hook: 'mirror-propensities'
+ }
+ },
+ events: {
+ 'click [data-hook=select]' : 'selectReaction',
+ 'click [data-hook=edit-annotation-btn]' : 'editAnnotation',
+ 'click [data-hook=remove]' : 'removeReaction',
+ 'click [data-hook=mirror-propensities]' : 'setMirrorPropensities',
+ 'change [data-hook=select-rate-parameter]' : 'selectRateParam',
+ 'change [data-hook=propensity-input]' : 'handlePropensityChange',
+ 'change [data-hook=select-reaction-type]' : 'selectReactionType'
+ },
+ initialize: function (attrs, options) {
+ View.prototype.initialize.apply(this, arguments);
+ this.viewMode = attrs.viewMode ? attrs.viewMode : false;
+ if(this.viewMode) {
+ this.rate = this.model.reactionType === "custom-propensity" ? this.model.propensity : this.model.rate.name;
+ this.types = [];
+ let self = this;
+ if(this.model.types) {
+ this.model.types.forEach(function (index) {
+ let type = self.model.collection.parent.domain.types.get(index, "typeID");
+ self.types.push(type.name);
+ });
+ }
+ }else{
+ this.model.on('change', _.bind(this.updateViewer, this));
+ this.model.on('change-reaction', () => {
+ if(this.model.massaction) {
+ this.renderODEPropensityInputView({override: true});
+ this.renderPropensityInputView({override: true});
+ }
+ });
+ }
+ },
+ render: function () {
+ this.template = this.viewMode ? viewTemplate : editTemplate;
+ View.prototype.render.apply(this, arguments);
+ if(!this.viewMode){
+ if(this.model.selected) {
+ setTimeout(_.bind(this.openReactionDetails, this), 1);
+ }
+ if(!this.model.annotation){
+ $(this.queryByHook('edit-annotation-btn')).text('Add');
+ }
+ }
+ app.documentSetup();
+ },
+ buildStoichSpecies: function (newSpecs, oldSpecs) {
+ let specIDs = [];
+ newSpecs.forEach((stoichSpecies) => {
+ let index = newSpecs.indexOf(stoichSpecies);
+ if(oldSpecs.at(index)) {
+ stoichSpecies.specie = oldSpecs.at(index).specie;
+ }else{
+ stoichSpecies.specie = this.getSpecies(specIDs);
+ }
+ specIDs.push(stoichSpecies.specie.compID);
+ });
+ return newSpecs;
+ },
+ editAnnotation: function () {
+ if(document.querySelector('#reactionAnnotationModal')) {
+ document.querySelector('#reactionAnnotationModal').remove();
+ }
+ let self = this;
+ let name = this.model.name;
+ let annotation = this.model.annotation;
+ let modal = $(modals.annotationModalHtml("reaction", name, annotation)).modal();
+ let okBtn = document.querySelector('#reactionAnnotationModal .ok-model-btn');
+ let input = document.querySelector('#reactionAnnotationModal #reactionAnnotationInput');
+ input.addEventListener("keyup", function (event) {
+ if(event.keyCode === 13){
+ event.preventDefault();
+ okBtn.click();
+ }
+ });
+ okBtn.addEventListener('click', function (e) {
+ modal.modal('hide');
+ self.model.annotation = input.value.trim();
+ self.parent.renderEditReactionView();
+ });
+ },
+ getReactionTypes: function () {
+ let disableTypes = this.model.collection.parent.parameters.length == 0;
+ let options = _.map(ReactionTypes, function (val, key) {
+ let disabled = disableTypes && key !== "custom-propensity"
+ return [key, val.label, disabled];
+ });
+ return options
+ },
+ getSpecies: function (speciesIDs) {
+ let species = this.model.collection.parent.species.filter((spec) => {
+ return !speciesIDs.includes(spec.compID);
+ });
+ if(species.length > 0) { return species[0]; }
+ return this.model.collection.parent.species.at(0);
+ },
+ handlePropensityChange: function () {
+ if(this.model.mirrorPropensities) {
+ this.model.odePropensity = this.model.propensity;
+ this.renderODEPropensityInputView({override: true});
+ }
+ },
+ openReactionDetails: function () {
+ $("#collapse-reaction-details" + this.model.compID).collapse("show");
+ this.renderDetailsSection();
+ },
+ removeReaction: function (e) {
+ this.collection.removeReaction(this.model);
+ this.parent.collection.trigger("change");
+ },
+ renderDetailsSection: function () {
+ this.renderRateSelectView();
+ this.renderPropensityInputView();
+ this.renderODEPropensityInputView();
+ this.renderReactionTypesSelectView();
+ this.toggleCustomReactionError();
+ this.renderReactantsView();
+ this.renderProductsView();
+ if(this.model.collection.parent.is_spatial) {
+ this.renderRestrictToView()
+ }
+ },
+ renderODEPropensityInputView: function ({override=false}={}) {
+ if(override && this.odePropensityInputView) {
+ this.odePropensityInputView.remove();
+ }
+
+ if(!this.odePropensityInputView || override) {
+ if(this.model.massaction) {
+ var required = false;
+ var propensity = this.model.maODEPropensity;
+ var modelKey = 'maODEPropensity';
+ $(this.queryByHook('mirror-propensities')).prop('disabled', true);
+ }else{
+ var required = !this.model.mirrorPropensities;
+ var propensity = this.model.odePropensity;
+ var modelKey = 'odePropensity';
+ $(this.queryByHook('mirror-propensities')).prop('disabled', false);
+ }
+
+ this.odePropensityInputView = new InputView({
+ parent: this,
+ required: required,
+ disabled: this.model.massaction || this.model.mirrorPropensities,
+ name: 'ode-propensity',
+ modelKey: modelKey,
+ valueType: 'string',
+ value: propensity,
+ placeholder: "--No Expression Entered--"
+ });
+ app.registerRenderSubview(this, this.odePropensityInputView, 'ode-propensity-input');
+ }
+ },
+ renderProductsView: function ({override=false}={}) {
+ if(override && this.productsView) {
+ this.productsView.remove();
+ }
+
+ if(!this.productsView || override) {
+ this.productsView = new ReactantProductView({
+ collection: this.model.products,
+ species: this.model.collection.parent.species,
+ reactionType: this.model.reactionType,
+ isReactants: false
+ });
+ app.registerRenderSubview(this, this.productsView, 'products-editor');
+ }
+ },
+ renderPropensityInputView: function ({override=false}={}) {
+ if(override && this.propensityInputView) {
+ this.propensityInputView.remove();
+ }
+
+ if(!this.propensityInputView || override) {
+ if(this.model.massaction) {
+ var required = false;
+ var propensity = this.model.maPropensity
+ var modelKey = 'maPropensity'
+ }else{
+ var required = !this.model.odePropensity;
+ var propensity = this.model.propensity
+ var modelKey = 'propensity'
+ }
+
+ this.propensityInputView = new InputView({
+ parent: this,
+ required: required,
+ disabled: this.model.massaction,
+ name: 'propensity',
+ modelKey: modelKey,
+ valueType: 'string',
+ value: propensity,
+ placeholder: "--No Expression Entered--"
+ });
+ app.registerRenderSubview(this, this.propensityInputView, 'propensity-input');
+ }
+ },
+ renderRateSelectView: function ({override=false}={}) {
+ if(override && this.rateParameterView) {
+ this.rateParameterView.remove();
+ }
+
+ if(!this.rateParameterView || override) {
+ let propensity = this.model.reactionType === 'custom-propensity';
+ viewOptions = {
+ name: 'rate',
+ required: !propensity,
+ idAttribute: 'compID',
+ textAttribute: 'name',
+ eagerValidate: !propensity
+ }
+ if(propensity) {
+ viewOptions['options'] = [];
+ viewOptions['unselectedText'] = "N/A";
+ }else{
+ // make sure the reaction has a rate and that rate exists in the parameters collection
+ let paramIDs = this.model.collection.parent.parameters.map(function (param) {
+ return param.compID;
+ });
+ if(!this.model.rate.compID || !paramIDs.includes(this.model.rate.compID)) {
+ this.model.rate = this.model.collection.getDefaultRate();
+ }
+ viewOptions['options'] = this.model.collection.parent.parameters;
+ viewOptions['value'] = this.model.rate.compID;
+ }
+
+ this.rateParameterView = new SelectView(viewOptions);
+ app.registerRenderSubview(this, this.rateParameterView, 'select-rate-parameter');
+ $(this.queryByHook('select-rate-parameter').firstChild.children[1]).prop('disabled', propensity);
+ }
+ },
+ renderReactantsView: function ({override=false}={}) {
+ if(override && this.reactantsView) {
+ this.reactantsView.remove();
+ }
+
+ if(!this.reactantsView || override) {
+ this.reactantsView = new ReactantProductView({
+ collection: this.model.reactants,
+ species: this.model.collection.parent.species,
+ reactionType: this.model.reactionType,
+ isReactants: true
+ });
+ app.registerRenderSubview(this, this.reactantsView, 'reactants-editor');
+ }
+ },
+ renderReactionTypes: function () {
+ let options = {
+ displayMode: true,
+ output: 'html'
+ }
+ katex.render(ReactionTypes['creation'].label, this.queryByHook('select-reaction-type').firstChild.children[1]['0'], options);
+ katex.render(ReactionTypes['destruction'].label, this.queryByHook('select-reaction-type').firstChild.children[1]['1'], options);
+ katex.render(ReactionTypes['change'].label, this.queryByHook('select-reaction-type').firstChild.children[1]['2'], options);
+ katex.render(ReactionTypes['dimerization'].label, this.queryByHook('select-reaction-type').firstChild.children[1]['3'], options);
+ katex.render(ReactionTypes['merge'].label, this.queryByHook('select-reaction-type').firstChild.children[1]['4'], options);
+ katex.render(ReactionTypes['split'].label, this.queryByHook('select-reaction-type').firstChild.children[1]['5'], options);
+ katex.render(ReactionTypes['four'].label, this.queryByHook('select-reaction-type').firstChild.children[1]['6'], options);
+ },
+ renderReactionTypesSelectView: function ({override=false}={}) {
+ if(override && this.reactionTypesSelectView) {
+ this.reactionTypesSelectView.remove();
+ }
+
+ if(!this.reactionTypesSelectView || override) {
+ let options = this.getReactionTypes();
+
+ this.reactionTypesSelectView = new SelectView({
+ label: 'Reaction Type:',
+ name: 'reaction-type',
+ required: true,
+ idAttribute: 'cid',
+ options: options,
+ value: this.model.reactionType
+ });
+ app.registerRenderSubview(this, this.reactionTypesSelectView, 'select-reaction-type');
+ this.renderReactionTypes();
+ }
+ },
+ renderRestrictToView: function ({override=true}={}) {
+ if(override && this.restrictToView) {
+ this.restrictToView.remove();
+ }
+
+ if(!this.restrictToView || override) {
+ this.restrictToView = new RestrictToView({
+ model: this.model,
+ parent: this
+ });
+ app.registerRenderSubview(this, this.restrictToView, 'domain-types-editor');
+ }
+ },
+ selectRateParam: function (e) {
+ let val = e.target.selectedOptions.item(0).value;
+ let param = this.model.collection.parent.parameters.get(val, 'compID');
+ if(param) {
+ this.model.rate = param;
+ this.updateViewer();
+ this.model.trigger('change-reaction');
+ this.model.collection.trigger("change");
+ }
+ },
+ selectReaction: function (e) {
+ this.model.selected = !this.model.selected;
+ this.renderDetailsSection();
+ },
+ selectReactionType: function (e) {
+ let oldReactionType = this.model.reactionType;
+ let newReactionType = e.target.selectedOptions.item(0).value;
+ if(newReactionType === 'custom-propensity') {
+ this.model.reactionType = newReactionType;
+ this.model.massaction = false;
+ }else if(newReactionType === 'custom-massaction') {
+ if(oldReactionType === 'custom-propensity') {
+ this.switchCustoms();
+ }else{
+ this.model.reactionType = newReactionType;
+ }
+ }else{
+ this.switchToFormula(newReactionType);
+ }
+ this.model.trigger('change-reaction');
+ if(newReactionType === 'custom-propensity' || oldReactionType === 'custom-propensity') {
+ this.renderRateSelectView({override: true});
+ this.renderPropensityInputView({override: true});
+ this.renderODEPropensityInputView({override: true});
+ }
+ this.renderReactantsView({override: true});
+ this.renderProductsView({override: true});
+ },
+ setMirrorPropensities: function () {
+ this.model.mirrorPropensities = !this.model.mirrorPropensities;
+ if(this.model.mirrorPropensities) {
+ this.prevODEProp = this.model.odePropensity;
+ this.model.odePropensity = this.model.propensity;
+ }else{
+ this.model.odePropensity = Boolean(this.prevODEProp) ? this.prevODEProp : ""
+ }
+ this.renderODEPropensityInputView({override: true});
+ },
+ switchCustoms: function () {
+ let reactants = new StoichSpecies([]);
+ reactants.parent = this.model;
+ var totalRatio = 0;
+ this.model.reactants.forEach((stoichSpecies) => {
+ if(totalRatio < 2) {
+ let ratio = totalRatio + stoichSpecies.ratio > 2 ? 2 - totalRatio : stoichSpecies.ratio;
+ reactants.addStoichSpecie(stoichSpecies.specie.name, {ratio: ratio});
+ totalRatio += ratio;
+ }
+ });
+ this.model.reactionType = 'custom-massaction';
+ this.model.massaction = true;
+ this.model.reactants = reactants;
+ if(!this.model.rate.compID) {
+ this.model.rate = this.model.collection.getDefaultRate();
+ }
+ },
+ switchToFormula: function (reactionType) {
+ let formula = ReactionTypes[reactionType];
+ let reactants = this.buildStoichSpecies(
+ (new StoichSpecies(formula.reactants)), this.model.reactants
+ );
+ reactants.parent = this.model;
+ let products = this.buildStoichSpecies(
+ (new StoichSpecies(formula.products)), this.model.products
+ );
+ products.parent = this.model;
+ this.model.reactionType = reactionType;
+ this.model.massaction = true;
+ this.model.reactants = reactants;
+ this.model.products = products;
+ if(!this.model.rate.compID) {
+ this.model.rate = this.model.collection.getDefaultRate();
+ }
+ },
+ toggleCustomReactionError: function () {
+ let errorMsg = $(this.queryByHook("custom-reaction-error"));
+ if(this.model.reactants.length <= 0 && this.model.products.length <= 0) {
+ errorMsg.addClass('component-invalid');
+ errorMsg.removeClass('component-valid');
+ }else{
+ errorMsg.addClass('component-valid');
+ errorMsg.removeClass('component-invalid');
+ }
+ },
+ update: function () {},
+ updateValid: function () {},
+ updateViewer: function (event) {
+ if(!event || !("selected" in event._changed)){
+ this.parent.renderViewReactionView();
+ }
+ },
+ subviews: {
+ inputName: {
+ hook: 'input-name-container',
+ prepareView: function (el) {
+ return new InputView({
+ parent: this,
+ required: true,
+ name: 'name',
+ tests: tests.nameTests,
+ modelKey: 'name',
+ valueType: 'string',
+ value: this.model.name
+ });
+ }
+ }
+ }
+});
\ No newline at end of file
diff --git a/client/model-view/views/reactions-view.js b/client/model-view/views/reactions-view.js
index 38a00ef305..f0ffd3c0c1 100644
--- a/client/model-view/views/reactions-view.js
+++ b/client/model-view/views/reactions-view.js
@@ -18,33 +18,28 @@ along with this program. If not, see .
let $ = require('jquery');
let katex = require('katex');
-let _ = require('underscore');
//support files
let app = require('../../app');
let Tooltips = require('../../tooltips');
let ReactionTypes = require('../../reaction-types');
-//models
-let StoichSpeciesCollection = require('../../models/stoich-species');
//views
let View = require('ampersand-view');
-let ViewSwitcher = require('ampersand-view-switcher');
-let ReactionListingView = require('./reaction-listing');
-let ReactionDetailsView = require('./reaction-details');
+let ReactionView = require('./reaction-view');
//templates
let template = require('../templates/reactionsView.pug');
module.exports = View.extend({
template: template,
events: {
- 'click [data-hook=creation]' : 'handleAddReactionClick',
- 'click [data-hook=destruction]' : 'handleAddReactionClick',
- 'click [data-hook=change]' : 'handleAddReactionClick',
- 'click [data-hook=dimerization]' : 'handleAddReactionClick',
- 'click [data-hook=merge]' : 'handleAddReactionClick',
- 'click [data-hook=split]' : 'handleAddReactionClick',
- 'click [data-hook=four]' : 'handleAddReactionClick',
- 'click [data-hook=custom-massaction]' : 'handleAddReactionClick',
- 'click [data-hook=custom-propensity]' : 'handleAddReactionClick',
+ 'click [data-hook=creation]' : 'handleAddReactionClick',
+ 'click [data-hook=destruction]' : 'handleAddReactionClick',
+ 'click [data-hook=change]' : 'handleAddReactionClick',
+ 'click [data-hook=dimerization]' : 'handleAddReactionClick',
+ 'click [data-hook=merge]' : 'handleAddReactionClick',
+ 'click [data-hook=split]' : 'handleAddReactionClick',
+ 'click [data-hook=four]' : 'handleAddReactionClick',
+ 'click [data-hook=custom-massaction]' : 'handleAddReactionClick',
+ 'click [data-hook=custom-propensity]' : 'handleAddReactionClick',
'click [data-hook=collapse]' : 'changeCollapseButtonText'
},
initialize: function (attrs, options) {
@@ -52,31 +47,14 @@ module.exports = View.extend({
this.tooltips = Tooltips.reactionsEditor;
this.readOnly = attrs.readOnly ? attrs.readOnly : false;
if(!this.readOnly) {
- this.collection.on("select", function (reaction) {
- this.setSelectedReaction(reaction);
- this.setDetailsView(reaction);
- }, this);
- this.collection.on("remove", function (reaction) {
- // Select the last reaction by default
- // But only if there are other reactions other than the one we're removing
- if (reaction.detailsView){
- reaction.detailsView.remove();
- }
- this.collection.removeReaction(reaction);
- if (this.collection.length) {
- let selected = this.collection.at(this.collection.length-1);
- this.collection.trigger("select", selected);
- }
- }, this);
this.collection.parent.species.on('add remove', this.toggleAddReactionButton, this);
- this.collection.parent.parameters.on('add remove', this.toggleReactionTypes, this);
+ this.collection.parent.parameters.on('add remove', this.updateMAState, this);
this.collection.parent.on('change', this.toggleProcessError, this);
}
},
render: function () {
View.prototype.render.apply(this, arguments);
if(this.readOnly) {
- this.renderViewReactionView();
$(this.queryByHook('reactions-edit-tab')).addClass("disabled");
$(".nav .disabled>a").on("click", function(e) {
e.preventDefault();
@@ -86,41 +64,34 @@ module.exports = View.extend({
$(this.queryByHook('edit-reactions')).removeClass('active');
$(this.queryByHook('view-reactions')).addClass('active');
}else{
- this.renderReactionListingViews();
- if (this.collection.length) {
- this.setSelectedReaction(this.collection.at(0));
- this.collection.trigger("select", this.selectedReaction);
- }
+ this.renderEditReactionView();
this.toggleAddReactionButton();
- if(this.collection.parent.parameters.length > 0){
- $(this.queryByHook('add-reaction-partial')).prop('hidden', true);
- }else {
- $(this.queryByHook('add-reaction-full')).prop('hidden', true);
- }
+ this.updateMAState();
this.renderReactionTypes();
- katex.render("\\emptyset", this.queryByHook('emptyset'), {
- displayMode: false,
- output: 'html',
- });
this.toggleProcessError();
- $(this.queryByHook('massaction-message')).prop('hidden', this.collection.parent.parameters.length > 0);
}
+ katex.render("\\emptyset", this.queryByHook('emptyset'), {
+ displayMode: false,
+ output: 'html',
+ });
+ this.renderViewReactionView();
},
changeCollapseButtonText: function (e) {
app.changeCollapseButtonText(this, e);
},
- getAnnotation: function (type) {
- return ReactionTypes[type].label;
- },
- getDefaultSpecie: function () {
- return this.collection.parent.species.models[0];
- },
getStoichArgsForReactionType: function(type) {
return ReactionTypes[type];
},
handleAddReactionClick: function (e) {
+ let disableTypes = this.collection.parent.parameters.length == 0;
+ let maTypes = [
+ "creation", "destruction", "change", "dimerization",
+ "merge", "split", "four", "custom-massaction"
+ ]
let reactionType = e.delegateTarget.dataset.hook;
- let stoichArgs = this.getStoichArgsForReactionType(reactionType);
+ if(disableTypes && maTypes.includes(reactionType)) {
+ return
+ }
if(this.parent.model.domain.types) {
var types = this.parent.model.domain.types.map(function (type) {
return type.typeID;
@@ -129,20 +100,10 @@ module.exports = View.extend({
}else{
var types = [];
}
+ let stoichArgs = this.getStoichArgsForReactionType(reactionType);
let reaction = this.collection.addReaction(reactionType, stoichArgs, types);
- reaction.detailsView = this.newDetailsView(reaction);
- this.collection.trigger("select", reaction);
app.tooltipSetup();
- },
- newDetailsView: function (reaction) {
- let detailsView = new ReactionDetailsView({ model: reaction });
- detailsView.parent = this;
- return detailsView;
- },
- openReactionsContainer: function () {
- $(this.queryByHook('reactions-list-container')).collapse('show');
- let collapseBtn = $(this.queryByHook('collapse'));
- collapseBtn.trigger('click');
+ this.collection.trigger('change');
},
openSection: function (error, {isCollection=false}={}) {
if(!$(this.queryByHook("reactions-list-container")).hasClass("show")) {
@@ -152,34 +113,24 @@ module.exports = View.extend({
}
app.switchToEditTab(this, "reactions");
if(error.type !== "process") {
- let reaction = this.collection.filter((react) => {
- return react.compID === error.id;
+ let reactionView = this.editReactionView.views.filter((reactView) => {
+ return reactView.model.compID === error.id;
})[0];
- this.collection.trigger("select", reaction);
+ reactionView.model.selected = true;
+ reactionView.openReactionDetails();
}
},
- renderEditReactionListingView: function () {
- if(this.editReactionListingView){
- this.editReactionListingView.remove();
+ renderEditReactionView: function () {
+ if(this.editReactionView){
+ this.editReactionView.remove();
}
- if(this.collection.parent.parameters.length <= 0) {
- for(var i = 0; i < this.collection.length; i++) {
- if(this.collection.models[i].reactionType !== "custom-propensity"){
- this.collection.models[i].reactionType = "custom-propensity";
- }
- }
- }
- this.editReactionListingView = this.renderCollection(
+ this.editReactionView = this.renderCollection(
this.collection,
- ReactionListingView,
+ ReactionView,
this.queryByHook('edit-reaction-list')
);
app.tooltipSetup();
},
- renderReactionListingViews: function () {
- this.renderEditReactionListingView();
- this.renderViewReactionView();
- },
renderReactionTypes: function () {
let options = {
displayMode: false,
@@ -194,8 +145,8 @@ module.exports = View.extend({
katex.render(ReactionTypes['four'].label, this.queryByHook('four-lb1'), options);
},
renderViewReactionView: function () {
- if(this.viewReactionListingView){
- this.viewReactionListingView.remove();
+ if(this.viewReactionView){
+ this.viewReactionView.remove();
}
if(!this.collection.parent.is_spatial){
$(this.queryByHook("reaction-types-header")).css("display", "none");
@@ -207,25 +158,15 @@ module.exports = View.extend({
$(this.queryByHook("reaction-annotation-header")).css("display", "block");
}
let options = {viewOptions: {viewMode: true, hasAnnotations: this.containsMdlWithAnn}}
- this.viewReactionListingView = this.renderCollection(
+ this.viewReactionView = this.renderCollection(
this.collection,
- ReactionListingView,
+ ReactionView,
this.queryByHook('view-reaction-list'),
options
);
},
- setDetailsView: function (reaction) {
- reaction.detailsView = this.newDetailsView(reaction);
- this.detailsViewSwitcher.set(reaction.detailsView);
- },
- setSelectedReaction: function (reaction) {
- this.collection.each(function (m) { m.selected = false; });
- reaction.selected = true;
- this.selectedReaction = reaction;
- },
toggleAddReactionButton: function () {
- $(this.queryByHook('add-reaction-full')).prop('disabled', (this.collection.parent.species.length <= 0));
- $(this.queryByHook('add-reaction-partial')).prop('disabled', (this.collection.parent.species.length <= 0));
+ $(this.queryByHook('add-reaction')).prop('disabled', (this.collection.parent.species.length <= 0));
},
toggleProcessError: function () {
let model = this.collection.parent;
@@ -239,30 +180,21 @@ module.exports = View.extend({
errorMsg.removeClass('component-invalid');
}
},
- toggleReactionTypes: function (e, prev, curr) {
- if(curr && curr.add && this.collection.parent.parameters.length === 1){
- $(this.queryByHook('massaction-message')).prop('hidden', true);
- $(this.queryByHook('add-reaction-full')).prop('hidden', false);
- $(this.queryByHook('add-reaction-partial')).prop('hidden', true);
- }else if(curr && !curr.add && this.collection.parent.parameters.length === 0){
- $(this.queryByHook('massaction-message')).prop('hidden', false);
- $(this.queryByHook('add-reaction-full')).prop('hidden', true);
- $(this.queryByHook('add-reaction-partial')).prop('hidden', false);
- }
- if(this.selectedReaction){
- this.selectedReaction.detailsView.renderReactionTypesSelectView();
- }
- },
update: function () {},
- updateValid: function () {},
- subviews: {
- detailsViewSwitcher: {
- hook: "reaction-details-container",
- prepareView: function (el) {
- return new ViewSwitcher({
- el: el
- });
+ updateMAState: function () {
+ let disableTypes = this.collection.parent.parameters.length == 0;
+ let maTypes = [
+ "creation", "destruction", "change", "dimerization",
+ "merge", "split", "four", "custom-massaction"
+ ]
+ for(var i = 0; i < maTypes.length; i++) {
+ if(disableTypes) {
+ $(this.queryByHook(maTypes[i])).addClass("disabled")
+ }else{
+ $(this.queryByHook(maTypes[i])).removeClass("disabled")
}
}
- }
+ $(this.queryByHook('massaction-message')).prop('hidden', !disableTypes);
+ },
+ updateValid: function () {},
});
\ No newline at end of file
diff --git a/client/model-view/views/species-view.js b/client/model-view/views/species-view.js
index f9fa717277..a00e004cdb 100644
--- a/client/model-view/views/species-view.js
+++ b/client/model-view/views/species-view.js
@@ -42,23 +42,21 @@ module.exports = View.extend({
let self = this;
this.collection.on('update-species', function (compID, specie, isNameUpdate, isDefaultMode) {
self.collection.parent.reactions.forEach(function (reaction) {
+ var changedReaction = false;
reaction.reactants.forEach(function (reactant) {
if(reactant.specie.compID === compID) {
reactant.specie = specie;
+ changedReaction = true;
}
});
reaction.products.forEach(function (product) {
if(product.specie.compID === compID) {
product.specie = specie;
+ changedReaction = true;
}
});
- if(isNameUpdate) {
- reaction.buildSummary();
- if(reaction.selected) {
- self.parent.reactionsView.setDetailsView(reaction);
- }
- }else if(!isDefaultMode || specie.compID === self.collection.models[self.collection.length-1].compID){
- reaction.checkModes();
+ if(changedReaction) {
+ reaction.trigger('change-reaction');
}
});
self.collection.parent.eventsCollection.forEach(function (event) {
diff --git a/client/model-view/views/edit-custom-stoich-specie.js b/client/model-view/views/stoich-species-view.js
similarity index 51%
rename from client/model-view/views/edit-custom-stoich-specie.js
rename to client/model-view/views/stoich-species-view.js
index dbab386e20..01df300036 100644
--- a/client/model-view/views/edit-custom-stoich-specie.js
+++ b/client/model-view/views/stoich-species-view.js
@@ -16,14 +16,13 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
-var $ = require('jquery');
+let $ = require('jquery');
//views
-var SelectView = require('ampersand-select-view');
+let SelectView = require('ampersand-select-view');
//templates
-var template = require('../templates/editCustomStoichSpecie.pug');
+let template = require('../templates/stoichSpecieView.pug');
module.exports = SelectView.extend({
- // SelectView expects a string template, so pre-render it
template: template(),
bindings: {
'model.ratio' : {
@@ -32,47 +31,82 @@ module.exports = SelectView.extend({
},
events: {
'change select' : 'selectChangeHandler',
- 'click [data-hook=increment]' : 'handleIncrement',
'click [data-hook=decrement]' : 'handleDecrement',
- 'click [data-hook=remove]' : 'deleteSpecie'
+ 'click [data-hook=increment]' : 'handleIncrement',
+ 'click [data-hook=custom-remove]' : 'deleteSpecie'
},
- initialize: function (args) {
- var self = this;
+ initialize: function () {
SelectView.prototype.initialize.apply(this, arguments);
- this.value = this.model.specie || null;
- this.isReactants = this.parent.parent.isReactants;
- this.reactionType = this.parent.parent.reactionType;
- this.stoichSpecies = this.parent.parent.collection;
- this.stoichSpecies.on('add', function () {
- self.toggleIncrementButton();
- });
- this.stoichSpecies.on('remove', function () {
- self.toggleIncrementButton();
+ this.value = this.model.specie.compID;
+ if(this.parent.parent.custom) {
+ this.isReactants = this.parent.parent.isReactants;
+ this.reactionType = this.parent.parent.reactionType;
+ this.stoichSpecies = this.parent.parent.collection;
+ this.stoichSpecies.on('add', () => {
+ this.toggleIncrementButton();
+ });
+ this.stoichSpecies.on('remove', () => {
+ this.toggleIncrementButton();
+ });
+ }
+ },
+ render: function() {
+ SelectView.prototype.render.apply(this, arguments);
+ if(this.parent.parent.custom) {
+ this.toggleDecrementButton();
+ this.toggleIncrementButton();
+ }else{
+ $(this.queryByHook('custom-decrement')).css('display', 'none');
+ $(this.queryByHook('custom-increment')).css('display', 'none');
+ $(this.queryByHook('custom-remove')).css('display', 'none');
+ }
+ this.model.collection.parent.on('change-reaction', () => {
+ var setValueFirstModel = () => {
+ if(!this.options.length) {
+ return;
+ }
+ if(this.yieldModel) {
+ this.setValue(this.options.models[0]);
+ }
+ else {
+ this.setValue(this.options.models[0][this.idAttribute]);
+ }
+ };
+
+ this.renderOptions();
+ if (this.hasOptionByValue(this.value)) {
+ this.updateSelectedOption();
+ }
+ else {
+ setValueFirstModel();
+ }
});
},
- render: function () {
- SelectView.prototype.render.apply(this);
- this.toggleIncrementButton();
- this.toggleDecrementButton();
+ deleteSpecie: function () {
+ if(!this.model.collection.parent.reactionType.startsWith('custom')) { return }
+ let reaction = this.model.collection.parent;
+ this.collection.removeStoichSpecie(this.model);
+ reaction.trigger('change-reaction');
+ this.parent.parent.toggleAddSpecieButton();
},
- selectChangeHandler: function (e) {
- var species = this.getSpeciesCollection();
- var reactions = this.getReactionsCollection();
- var specie = species.filter(function (m) {
- return m.name === e.target.selectedOptions.item(0).text;
- })[0];
- this.model.specie = specie;
- this.value = specie;
- reactions.trigger("change");
- this.model.collection.parent.trigger('change-reaction')
+ getReactionsCollection: function () {
+ return this.model.collection.parent.collection;
},
getSpeciesCollection: function () {
return this.model.collection.parent.collection.parent.species;
},
- getReactionsCollection: function () {
- return this.model.collection.parent.collection;
+ handleDecrement: function () {
+ if(!this.model.collection.parent.reactionType.startsWith('custom')) { return }
+ this.model.ratio--;
+ this.model.collection.parent.trigger('change-reaction')
+ this.toggleDecrementButton();
+ if(this.validateRatioIncrement()){
+ this.toggleIncrementButton();
+ this.parent.parent.toggleAddSpecieButton();
+ }
},
handleIncrement: function () {
+ if(!this.model.collection.parent.reactionType.startsWith('custom')) { return }
if(this.validateRatioIncrement()){
this.model.ratio++;
this.toggleIncrementButton();
@@ -81,41 +115,29 @@ module.exports = SelectView.extend({
}
this.toggleDecrementButton();
},
+ selectChangeHandler: function (e) {
+ let species = this.getSpeciesCollection();
+ let reactions = this.getReactionsCollection();
+ let specie = species.get(e.target.selectedOptions.item(0).value, 'compID');
+ this.model.specie = specie;
+ this.value = specie.compID;
+ reactions.trigger("change");
+ this.model.collection.parent.trigger('change-reaction')
+ },
+ toggleDecrementButton: function () {
+ $(this.queryByHook('decrement')).prop('disabled', this.model.ratio <= 1);
+ },
+ toggleIncrementButton: function () {
+ $(this.queryByHook('increment')).prop('disabled', !this.validateRatioIncrement());
+ },
+ update: function () {},
validateRatioIncrement: function () {
if(this.stoichSpecies.length < 2 && this.model.ratio < 2)
return true;
- if(this.reactionType !== 'custom-massaction')
+ if(this.reactionType === 'custom-propensity')
return true;
if(!this.isReactants)
return true;
return false;
- },
- toggleIncrementButton: function () {
- if(!this.validateRatioIncrement()){
- $(this.queryByHook('increment')).prop('disabled', true);
- }else{
- $(this.queryByHook('increment')).prop('disabled', false);
- }
- },
- toggleDecrementButton: function () {
- if(this.model.ratio <= 1)
- $(this.queryByHook('decrement')).prop('disabled', true);
- else
- $(this.queryByHook('decrement')).prop('disabled', false);
- },
- handleDecrement: function () {
- this.model.ratio--;
- this.model.collection.parent.trigger('change-reaction')
- this.toggleDecrementButton();
- if(this.validateRatioIncrement()){
- this.toggleIncrementButton();
- this.parent.parent.toggleAddSpecieButton();
- }
- },
- deleteSpecie: function () {
- var reaction = this.model.collection.parent
- this.collection.removeStoichSpecie(this.model);
- reaction.trigger('change-reaction')
- this.parent.parent.toggleAddSpecieButton();
- },
+ }
});
\ No newline at end of file
diff --git a/client/models/domain-type.js b/client/models/domain-type.js
index 930f6a197f..16584666e4 100644
--- a/client/models/domain-type.js
+++ b/client/models/domain-type.js
@@ -21,15 +21,22 @@ var State = require('ampersand-state');
module.exports = State.extend({
props: {
+ c: 'number',
fixed: 'boolean',
+ geometry: 'string',
mass: 'number',
name: 'string',
nu: 'number',
+ rho: 'number',
typeID: 'number',
volume: 'number'
},
session: {
- numParticles: 'number'
+ numParticles: 'number',
+ selected: {
+ type: 'boolean',
+ default: false
+ }
},
initialize: function (attrs, options) {
State.prototype.initialize.apply(this, arguments)
diff --git a/client/models/domain-types.js b/client/models/domain-types.js
index 3d557a1dde..9ec4e5ac59 100644
--- a/client/models/domain-types.js
+++ b/client/models/domain-types.js
@@ -24,19 +24,21 @@ var Collection = require('ampersand-collection');
module.exports = Collection.extend({
model: Type,
indexes: ['typeID'],
- addType: function (vol, mass, nu, fixed, id=null) {
- if(!id) {
- id = this.parent.getDefaultTypeID();
- }
+ addType: function () {
+ let id = this.parent.getDefaultTypeID();
let name = String(id);
- var type = new Type({
- fixed: fixed,
- mass: mass,
- nu: nu,
- typeID: id,
+ let type = new Type({
+ c: 10,
+ fixed: false,
+ geometry: "",
+ mass: 1.0,
name: name,
- volume: vol
+ nu: 0.0,
+ rho: 1.0,
+ typeID: id,
+ volume: 1.0
});
+ type.selected = true;
this.add(type);
return name;
},
diff --git a/client/models/domain.js b/client/models/domain.js
index 738ebbe28d..137e9c38f4 100644
--- a/client/models/domain.js
+++ b/client/models/domain.js
@@ -33,7 +33,8 @@ module.exports = State.extend({
x_lim: 'object',
y_lim: 'object',
z_lim: 'object',
- static: 'boolean'
+ static: 'boolean',
+ template_version: 'number'
},
collections: {
types: Types,
@@ -72,6 +73,23 @@ module.exports = State.extend({
this.def_type_id += 1;
return id;
},
+ realignTypes: function (oldType) {
+ this.def_type_id -= 1;
+ this.types.forEach((type) => {
+ if(type.typeID > oldType) {
+ let id = type.typeID - 1;
+ if(type.name === type.typeID.toString()) {
+ type.name = id.toString();
+ }
+ type.typeID = id;
+ }
+ });
+ this.particles.forEach((particle) => {
+ if(particle.type > oldType) {
+ particle.type -= 1;
+ }
+ });
+ },
validateModel: function () {
if(!this.particles.validateCollection()) return false;
return true;
diff --git a/client/models/model.js b/client/models/model.js
index 076612620c..c993e86c91 100644
--- a/client/models/model.js
+++ b/client/models/model.js
@@ -43,7 +43,8 @@ module.exports = Model.extend({
defaultID: 'number',
defaultMode: 'string',
annotation: 'string',
- volume: 'any'
+ volume: 'any',
+ template_version: 'number'
},
collections: {
species: Species,
diff --git a/client/models/parameters.js b/client/models/parameters.js
index 520418a72d..2279bc59ee 100644
--- a/client/models/parameters.js
+++ b/client/models/parameters.js
@@ -24,6 +24,7 @@ var Collection = require('ampersand-collection');
module.exports = Collection.extend({
model: Parameter,
+ indexes: ['compID'],
addParameter: function () {
var id = this.parent.getDefaultID();
var name = this.getDefaultName();
diff --git a/client/models/particle.js b/client/models/particle.js
index 1871d36f2c..0a685631bf 100644
--- a/client/models/particle.js
+++ b/client/models/particle.js
@@ -21,21 +21,26 @@ var State = require('ampersand-state');
module.exports = State.extend({
props: {
+ c: 'number',
fixed: 'boolean',
mass: 'number',
nu: 'number',
particle_id: 'number',
point: 'object',
+ rho: 'number',
type: 'number',
volume: 'number'
},
- session: {
- pointChanged: 'boolean',
- typeChanged: 'boolean'
- },
initialize: function (attrs, options) {
State.prototype.initialize.apply(this, arguments)
},
+ comparePoint(point) {
+ if(this.point.length !== point.length) { return false; }
+ for(var i = 0; i < this.point.length; i++) {
+ if(this.point[i] !== point[i]) { return false; }
+ }
+ return true;
+ },
validate: function () {
return true;
}
diff --git a/client/models/particles.js b/client/models/particles.js
index 937f0000a7..968f1f6ff5 100644
--- a/client/models/particles.js
+++ b/client/models/particles.js
@@ -24,17 +24,23 @@ var Collection = require('ampersand-collection');
module.exports = Collection.extend({
model: Particle,
indexes: ['particle_id'],
- addParticle: function (point, vol, mass, type, nu, fixed) {
+ addParticle: function ({particle=null, point=[0,0,0], vol=1, mass=1, type=0, nu=0, fixed=false, c=10, rho=1}={}) {
let id = this.parent.getDefaultID();
- var particle = new Particle({
- fixed: fixed,
- mass: mass,
- nu: nu,
- particle_id: id,
- point: point,
- type: type,
- volume: vol
- });
+ if(particle) {
+ particle.particle_id = id;
+ }else{
+ particle = new Particle({
+ c: c,
+ fixed: fixed,
+ mass: mass,
+ nu: nu,
+ particle_id: id,
+ point: point,
+ rho: rho,
+ type: type,
+ volume: vol
+ });
+ }
this.add(particle);
},
removeParticle: function (particle) {
diff --git a/client/models/reaction.js b/client/models/reaction.js
index 3edcbe8941..262103d508 100644
--- a/client/models/reaction.js
+++ b/client/models/reaction.js
@@ -27,9 +27,8 @@ module.exports = State.extend({
props: {
compID: 'number',
name: 'string',
- reactionType: 'string',
- summary: 'string',
massaction: 'boolean',
+ odePropensity: 'string',
propensity: 'string',
annotation: 'string',
types: 'object'
@@ -42,29 +41,58 @@ module.exports = State.extend({
products: StoichSpecies
},
session: {
- selected: {
+ hasConflict: {
type: 'boolean',
- default: true,
+ default: false,
},
- hasConflict: {
+ maODEPropensity: 'string',
+ maPropensity: 'string',
+ mirrorPropensities: 'boolean',
+ reactionType: 'string',
+ selected: {
type: 'boolean',
default: false,
},
+ summary: 'string'
},
initialize: function (attrs, options) {
var self = this;
State.prototype.initialize.apply(this, arguments);
- if(!this.reactionType.startsWith('custom')) {
- let reactionType = this.updateReactionType();
- if(this.reactionType !== reactionType){
- this.reactionType = reactionType
- this.buildSummary()
+ this.reactionType = this.updateReactionType();
+ this.mirrorPropensities = this.propensity === this.odePropensity;
+ this.buildSummary();
+ this.buildMAPropensities();
+ this.checkModes();
+ this.on('change-reaction', () => {
+ this.buildSummary();
+ this.buildMAPropensities();
+ this.checkModes();
+ });
+ },
+ buildMAPropensities: function () {
+ if(this.reactionType === "custom-propensity") { return }
+ var odePropensity = this.rate.name;
+ var propensity = this.rate.name;
+
+ this.reactants.forEach((stoichSpecies) => {
+ let name = stoichSpecies.specie.name;
+ if(stoichSpecies.ratio == 2) {
+ odePropensity += ` * ${name} * ${name}`;
+ propensity = `0.5 * ${propensity} * ${name} * (${name} - 1) / vol`;
+ }else{
+ odePropensity += ` * ${name}`;
+ propensity += ` * ${name}`;
}
+ })
+
+ let order = this.reactants.length;
+ if(order == 2) {
+ propensity += " / vol";
+ }else if(order == 0) {
+ propensity += " * vol"
}
- this.on('change-reaction', function () {
- self.buildSummary();
- self.checkModes();
- });
+ this.maODEPropensity = odePropensity;
+ this.maPropensity = propensity;
},
buildSummary: function () {
var summary = "";
@@ -137,6 +165,9 @@ module.exports = State.extend({
this.hasConflict = Boolean(hasContinuous && (hasDynamic || hasDiscrete))
},
updateReactionType: function () {
+ if(!this.massaction) {
+ return "custom-propensity"
+ }
let numReactants = this.reactants.length
let numProducts = this.products.length
let prodRatio1 = numProducts > 0 ? this.products.models[0].ratio : 0
diff --git a/client/models/reactions.js b/client/models/reactions.js
index eb4e1d6861..f609f339c5 100644
--- a/client/models/reactions.js
+++ b/client/models/reactions.js
@@ -31,13 +31,13 @@ Reactions = Collection.extend({
addReaction: function (reactionType, stoichArgs, types) {
var id = this.parent.getDefaultID();
var name = this.getDefaultName();
- var massaction = reactionType === 'custom-massaction';
+ var massaction = reactionType !== 'custom-propensity';
var reaction = new Reaction({
compID: id,
name: name,
- reactionType: reactionType,
massaction: massaction,
propensity: '',
+ odePropensity: '',
annotation: '',
types: types,
reactants: stoichArgs.reactants,
@@ -47,9 +47,12 @@ Reactions = Collection.extend({
this.setDefaultSpecieForStoichSpecies(reaction.products);
if(reactionType !== 'custom-propensity')
reaction.rate = this.getDefaultRate();
- reaction.buildSummary()
+ reaction.buildSummary();
+ reaction.buildMAPropensities();
+ reaction.reactionType = reactionType;
+ reaction.selected = true;
this.add(reaction);
- this.parent.updateValid()
+ this.parent.updateValid();
return reaction;
},
getDefaultName: function () {
@@ -64,11 +67,14 @@ Reactions = Collection.extend({
},
setDefaultSpecieForStoichSpecies: function (stoichSpecies) {
stoichSpecies.forEach(function (stoichSpecie) {
- stoichSpecie.specie = this.getDefaultSpecie();
+ stoichSpecie.specie = this.getDefaultSpecie(stoichSpecies.indexOf(stoichSpecie));
}, this);
},
- getDefaultSpecie: function () {
- var specie = this.parent.species.at(0);
+ getDefaultSpecie: function (index) {
+ if(this.parent.species.length <= index) {
+ index -= this.parent.species.length;
+ }
+ var specie = this.parent.species.at(index);
return specie;
},
getDefaultRate: function () {
diff --git a/client/models/species.js b/client/models/species.js
index 2e822cfc5b..dcba8eadc2 100644
--- a/client/models/species.js
+++ b/client/models/species.js
@@ -24,6 +24,7 @@ var Collection = require('ampersand-collection');
module.exports = Collection.extend({
model: Specie,
+ indexes: ['compID'],
addSpecie: function (types) {
var id = this.parent.getDefaultID();
var name = this.getDefaultName();
diff --git a/client/models/stoich-species.js b/client/models/stoich-species.js
index 70cff4ecb5..4fc9006f68 100644
--- a/client/models/stoich-species.js
+++ b/client/models/stoich-species.js
@@ -32,12 +32,12 @@ module.exports = Collection.extend({
this.baseModel = this.parent.collection.parent;
this.baseModel.species.trigger('stoich-species-change');
},
- addStoichSpecie: function (specieName) {
+ addStoichSpecie: function (specieName, {ratio=1}={}) {
var specie = this.parent.collection.parent.species.filter(function (specie) {
return specie.name === specieName;
})[0];
var stoichSpecie = new StoichSpecie({
- ratio: 1
+ ratio: ratio
});
stoichSpecie.specie = specie;
this.add(stoichSpecie);
diff --git a/client/pages/domain-editor.js b/client/pages/domain-editor.js
index acbe54f347..9a489a61e9 100644
--- a/client/pages/domain-editor.js
+++ b/client/pages/domain-editor.js
@@ -16,223 +16,35 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
-var $ = require('jquery');
+let $ = require('jquery');
let path = require('path');
-var _ = require('underscore');
//support files
-var app = require('../app');
-var tests = require('../views/tests');
-var Plotly = require('../lib/plotly');
-var Tooltips = require('../tooltips');
+let app = require('../app');
let modals = require('../modals');
-//views
-var PageView = require('../pages/base');
-var InputView = require('../views/input');
-var SelectView = require('ampersand-select-view');
-var ParticleView = require('../views/edit-particle');
-var EditDomainTypeView = require('../views/edit-domain-type');
-var Create3DDomainView = require('../views/edit-3D-domain');
-//collections
-var Collection = require('ampersand-collection');
//models
-var Model = require('../models/model');
-var Domain = require('../models/domain');
-var Particle = require('../models/particle');
-var TypeModel = require('../models/domain-type');
+let Model = require('../models/model');
+let Domain = require('../models/domain');
+//views
+let DomainView = require('../domain-view/domain-view');
+let PageView = require('../pages/base');
//templates
-var template = require('../templates/pages/domainEditor.pug');
+let template = require('../templates/pages/domainEditor.pug');
import initPage from './page.js';
let DomainEditor = PageView.extend({
template: template,
events: {
- 'click [data-toggle=collapse]' : 'changeCollapseButtonText',
- 'click [data-hook=add-domain-type]' : 'handleAddDomainType',
- 'click [data-hook=set-type-defaults]' : 'handleSetDefaults',
'click [data-hook=save-to-model]' : 'handleSaveToModel',
'click [data-hook=save-to-file]' : 'handleSaveToFile',
- 'click [data-hook=import-particles-btn]' : 'handleImportMesh',
- 'click [data-hook=set-particle-types-btn]' : 'getTypesFromFile',
- 'change [data-hook=static-domain]' : 'setStaticDomain',
- 'change [data-hook=density]' : 'setDensity',
- 'change [data-target=gravity]' : 'setGravity',
- 'change [data-hook=pressure]' : 'setPressure',
- 'change [data-hook=speed]' : 'setSpeed',
- 'change [data-name=limitation]' : 'setLimitation',
- 'change [data-target=reflect]' : 'setBoundaryCondition',
- 'change #meshfile' : 'updateImportBtn',
- 'change [data-hook=mesh-type-select]' : 'updateMeshTypeAndDefaults',
- 'change [data-hook=types-file-select]' : 'handleSelectTypeFile',
- 'change [data-hook=types-file-location-select]' : 'handleSelectTypeLocation'
- },
- handleAddDomainType: function () {
- this.selectedType = "new";
- this.renderEditTypeDefaults();
- },
- handleImportMesh: function (e) {
- this.startAction("Importing mesh ...", "im")
- let data = {"type":null, "transformation":null}
- let id = Number($(this.queryByHook("mesh-type-select")).find('select')[0].value)
- if(id > 0) {
- data.type = this.domain.types.get(id, "typeID").toJSON();
- }
- let xTrans = Number($(this.queryByHook("mesh-x-trans")).find('input')[0].value)
- let yTrans = Number($(this.queryByHook("mesh-y-trans")).find('input')[0].value)
- let zTrans = Number($(this.queryByHook("mesh-z-trans")).find('input')[0].value)
- if(xTrans !== 0 || yTrans !== 0 || zTrans !== 0) {
- data.transformation = [xTrans, yTrans, zTrans];
- }
- this.importMesh(data)
- },
- importMesh: function (data) {
- let self = this;
- let file = $("#meshfile").prop("files")[0];
- let typeFiles = $("#typefile").prop("files")
- let formData = new FormData();
- formData.append("datafile", file);
- formData.append("particleData", JSON.stringify(data));
- if(typeFiles.length) {
- formData.append("typefile", typeFiles[0]);
- }
- let endpoint = path.join(app.getApiPath(), 'spatial-model/import-mesh');
- app.postXHR(endpoint, formData, {
- success: function (err, response, body) {
- body = JSON.parse(body);
- if(body.types) {
- self.addMissingTypes(body.types)
- }
- self.addParticles(body.particles);
- if(self.domain.x_lim[0] > body.limits.x_lim[0]) {
- self.domain.x_lim[0] = body.limits.x_lim[0]
- }
- if(self.domain.y_lim[0] > body.limits.y_lim[0]) {
- self.domain.y_lim[0] = body.limits.y_lim[0]
- }
- if(self.domain.z_lim[0] > body.limits.z_lim[0]) {
- self.domain.z_lim[0] = body.limits.z_lim[0]
- }
- if(self.domain.x_lim[1] < body.limits.x_lim[1]) {
- self.domain.x_lim[1] = body.limits.x_lim[1]
- }
- if(self.domain.y_lim[1] < body.limits.y_lim[1]) {
- self.domain.y_lim[1] = body.limits.y_lim[1]
- }
- if(self.domain.z_lim[1] < body.limits.z_lim[1]) {
- self.domain.z_lim[1] = body.limits.z_lim[1]
- }
- self.renderDomainLimitations();
- self.completeAction("Mesh successfully imported", "im")
- $('html, body').animate({
- scrollTop: $("#domain-plot").offset().top
- }, 20);
- },
- error: function (err, response, body) {
- body = JSON.parse(body);
- self.errorAction(body.Message, "im")
- }
- }, false)
- },
- handleSetDefaults: function (e) {
- let mass = Number($(this.queryByHook("td-mass")).find('input')[0].value);
- let vol = Number($(this.queryByHook("td-vol")).find('input')[0].value);
- let nu = Number($(this.queryByHook("td-nu")).find('input')[0].value);
- let fixed = $(this.queryByHook("td-fixed")).prop("checked");
- if(this.selectedType === "new") {
- let name = this.domain.types.addType(vol, mass, nu, fixed)
- this.addType(name);
- }else{
- let type = this.domain.types.get(this.selectedType, "typeID")
- type.mass = mass;
- type.volume = vol;
- type.nu = nu;
- type.fixed = fixed;
- }
- this.renderDomainTypes();
- $(this.queryByHook("edit-defaults")).css("display", "none");
- },
- handleSaveToModel: function () {
- if(this.model) {
- this.startAction("Saving domain to model ...", "sd");
- this.model.domain = this.domain;
- this.model.saveModel(_.bind(function () {
- this.completeAction("Domain saved to model", "sd");
- }, this));
- window.location.replace(this.getBreadcrumbData().model.href);
- }
- },
- handleSaveToFile: function () {
- this.startAction("Saving the domain to file (.domn) ...", "sd")
- if(this.domain.directory && !this.domain.dirname) {
- this.saveDomain()
- }else{
- var self = this
- if(document.querySelector('#newDomainModal')) {
- document.querySelector('#newDomainModal').remove()
- }
- let modal = $(modals.createDomainHtml()).modal();
- let okBtn = document.querySelector('#newDomainModal .ok-model-btn');
- let input = document.querySelector('#newDomainModal #domainNameInput');
- input.addEventListener("keyup", function (event) {
- if(event.keyCode === 13){
- event.preventDefault();
- okBtn.click();
- }
- });
- input.addEventListener("input", function (e) {
- var endErrMsg = document.querySelector('#newDomainModal #domainNameInputEndCharError')
- var charErrMsg = document.querySelector('#newDomainModal #domainNameInputSpecCharError')
- let error = app.validateName(input.value)
- okBtn.disabled = error !== "" || input.value.trim() === ""
- charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none"
- endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none"
- });
- okBtn.addEventListener('click', function (e) {
- if (Boolean(input.value)) {
- modal.modal('hide')
- let name = input.value.trim()
- self.saveDomain(name);
- }
- });
- }
- },
- handleSelectTypeFile: function (e) {
- let value = e.srcElement.value;
- if(value) {
- if(this.typeDescriptions[value].length <= 1) {
- $(this.queryByHook("type-location-message")).css('display', "none");
- $(this.queryByHook("type-location-container")).css("display", "none");
- this.typeDescriptionsFile = this.typeDescriptions[value][0];
- var disabled = false;
- }else{
- $(this.queryByHook("type-location-message")).css('display', "block");
- $(this.queryByHook("type-location-container")).css("display", "inline-block");
- this.renderTypesLocationSelect(this.typeDescriptions[value]);
- var disabled = true;
- }
- }else{
- $(this.queryByHook("type-location-message")).css('display', "none");
- $(this.queryByHook("type-location-container")).css("display", "none");
- var disabled = true;
- }
- $(this.queryByHook("set-particle-types-btn")).prop("disabled", disabled);
- },
- handleSelectTypeLocation: function (e) {
- this.typeDescriptionsFile = e.srcElement.value;
- $(this.queryByHook("set-particle-types-btn")).prop("disabled", false);
- },
- updateImportBtn: function (e) {
- $(this.queryByHook("import-particles-btn")).prop("disabled", !Boolean(e.target.files.length))
},
initialize: function (attrs, options) {
PageView.prototype.initialize.apply(this, arguments);
- this.tooltips = Tooltips.domainEditor;
- var self = this;
let urlParams = new URLSearchParams(window.location.search)
let modelPath = urlParams.has("path") ? urlParams.get("path") : null;
let domainPath = urlParams.has("domainPath") ? urlParams.get("domainPath") : null;
let newDomain = urlParams.has("new") ? true : false;
- this.queryStr = modelPath ? "?path=" + modelPath : "?"
+ this.queryStr = modelPath ? `?path=${modelPath}` : "?"
if(newDomain) {
if(modelPath) {
this.queryStr += "&"
@@ -246,183 +58,40 @@ let DomainEditor = PageView.extend({
}
let endpoint = path.join(app.getApiPath(), "spatial-model/load-domain") + this.queryStr
app.getXHR(endpoint, {
- success: function (err, response, body) {
- self.domain = new Domain(body.domain);
- self.domain.directory = domainPath
- self.domain.dirname = newDomain && !modelPath ? domainPath : null
- self.model = self.buildModel(body.model, modelPath);
- self.actPart = {"part":null, "tn":0, "pn":0};
- self.renderSubviews();
+ success: (err, response, body) => {
+ this.domain = new Domain(body.domain);
+ this.domain.directory = domainPath
+ this.domain.dirname = newDomain && !modelPath ? domainPath : null
+ this.model = this.buildModel(body.model, modelPath);
+ this.renderSubviews();
}
});
- $(document).on('shown.bs.modal', function (e) {
- $('[autofocus]', e.target).focus();
- });
- },
- addParticle: function (newPart, {fromImport=false, cb=null}={}) {
- this.domain.particles.addParticle(newPart.point, newPart.volume,
- newPart.mass, newPart.type,
- newPart.nu, newPart.fixed);
- let numPart = this.domain.particles.models.length
- let particle = this.domain.particles.models[numPart-1]
- this.plot.data[particle.type].ids.push(particle.particle_id);
- this.plot.data[particle.type].x.push(particle.point[0]);
- this.plot.data[particle.type].y.push(particle.point[1]);
- this.plot.data[particle.type].z.push(particle.point[2]);
- if(!fromImport) {
- this.renderDomainTypes();
- this.updatePlot();
- }
- if(cb) {
- cb();
- }
- },
- addParticles: function (particles) {
- let self = this;
- particles.forEach(function (particle) {
- self.addParticle(particle, {fromImport: true});
- });
- this.renderDomainTypes();
- this.updatePlot();
- },
- addMissingTypes: function (types) {
- let self = this;
- let defaultType = self.domain.types.get(0, "typeID");
- types.forEach(function (type) {
- let domainType = self.domain.types.get(type, "typeID");
- if(!domainType) {
- self.domain.types.addType(defaultType.volume, defaultType.mass,
- defaultType.nu, defaultType.fixed, type);
- self.addType(String(type));
- }
- });
- },
- addType: function (name) {
- this.renderEditParticle();
- this.renderNewParticle();
- var trace = {"ids":[], "x":[], "y":[], "z":[], "name":name};
- trace['marker'] = this.plot.data[0].marker;
- trace['mode'] = this.plot.data[0].mode;
- trace['type'] = this.plot.data[0].type;
- this.plot.data.push(trace)
- this.updatePlot()
},
buildModel: function (modelData, modelPath) {
if(!modelPath) {
return null;
}
- var model = new Model(modelData);
+ let model = new Model(modelData);
model.for = "domain";
model.isPreview = false;
model.directory = modelPath;
return model;
},
- changeCollapseButtonText: function (e) {
- app.changeCollapseButtonText(this, e);
- },
- changeParticleLocation: function (x, y, z) {
- this.domain.particles.get(this.actPart.part.particle_id, "particle_id").point = [x, y, z];
- this.plot.data[this.actPart.tn].x[this.actPart.pn] = x;
- this.plot.data[this.actPart.tn].y[this.actPart.pn] = y;
- this.plot.data[this.actPart.tn].z[this.actPart.pn] = z;
- this.actPart.part.pointChanged = false;
- },
- changeParticleTypes: function (types) {
- let self = this;
- types.forEach(function (type) {
- let particle = self.domain.particles.get(type.particle_id, "particle_id");
- self.actPart.part = particle;
- for(var i = 0; i < self.plot.data.length; i++) {
- let trace = self.plot.data[i];
- if(trace.ids.includes(String(type.particle_id))){
- self.actPart.tn = self.plot.data.indexOf(trace);
- self.actPart.pn = trace.ids.indexOf(String(type.particle_id));
- break;
- }
- };
- self.changeParticleType(type.typeID)
- });
- this.renderDomainTypes();
- this.updatePlot();
- },
- changeParticleType: function (type) {
- this.domain.particles.get(this.actPart.part.particle_id, "particle_id").type = type
- let id = this.plot.data[this.actPart.tn].ids.splice(this.actPart.pn, 1)[0];
- let x = this.plot.data[this.actPart.tn].x.splice(this.actPart.pn, 1)[0];
- let y = this.plot.data[this.actPart.tn].y.splice(this.actPart.pn, 1)[0];
- let z = this.plot.data[this.actPart.tn].z.splice(this.actPart.pn, 1)[0];
- this.plot.data[type].ids.push(id);
- this.plot.data[type].x.push(x);
- this.plot.data[type].y.push(y);
- this.plot.data[type].z.push(z);
- this.actPart.part.typeChanged = false;
- },
- completeAction: function (action, src) {
- $(this.queryByHook(src + "-in-progress")).css("display", "none");
- $(this.queryByHook(src + "-action-complete")).text(action);
- $(this.queryByHook(src + "-complete")).css("display", "inline-block");
- console.log(action)
- let self = this
- setTimeout(function () {
- $(self.queryByHook(src + "-complete")).css("display", "none");
+ completeAction: function (action) {
+ $(this.queryByHook("sd-in-progress")).css("display", "none");
+ $(this.queryByHook("sd-action-complete")).text(action);
+ $(this.queryByHook("sd-complete")).css("display", "inline-block");
+ setTimeout(() => {
+ $(this.queryByHook("sd-complete")).css("display", "none");
}, 5000);
},
- deleteParticle: function () {
- this.domain.particles.removeParticle(this.actPart.part);
- this.plot.data[this.actPart.tn].ids.splice(this.actPart.pn, 1);
- this.plot.data[this.actPart.tn].x.splice(this.actPart.pn, 1);
- this.plot.data[this.actPart.tn].y.splice(this.actPart.pn, 1);
- this.plot.data[this.actPart.tn].z.splice(this.actPart.pn, 1);
- this.actPart.part = null;
- this.updatePlot();
- this.renderEditParticle();
- this.renderDomainTypes();
- $('html, body').animate({
- scrollTop: $("#domain-plot").offset().top
- }, 20);
- },
- deleteType: function (typeID) {
- if(this.actPart.part && this.actPart.part.type >= typeID) {
- this.actPart.part = null;
- }
- this.unassignAllParticles(typeID, false);
- let type = this.domain.types.get(typeID, "typeID");
- this.domain.types.removeType(type);
- this.renderEditParticle();
- this.renderNewParticle();
- this.plot.data.splice(typeID, 1);
- this.updatePlot();
- },
- deleteTypeAndParticles: function (typeID) {
- if(this.actPart.part && this.actPart.part.type >= typeID) {
- this.actPart.part = null;
- }
- let self = this;
- let particles = this.domain.particles.filter(function (particle) {
- return particle.type === typeID;
- });
- this.domain.particles.removeParticles(particles);
- let type = this.domain.types.get(typeID, "typeID");
- this.domain.types.removeType(type);
- this.renderEditParticle();
- this.renderNewParticle();
- this.plot.data.splice(typeID, 1);
- this.updatePlot();
- },
- displayDomain: function () {
- let self = this;
- var el = this.queryByHook("domain-plot");
- Plotly.newPlot(el, this.plot);
- el.on('plotly_click', _.bind(this.selectParticle, this));
- },
- errorAction: function (action, src) {
- $(this.queryByHook(src + "-in-progress")).css("display", "none");
- $(this.queryByHook(src + "-action-error")).text(action);
- $(this.queryByHook(src + "-error")).css("display", "block");
- console.log(action)
+ errorAction: function (action) {
+ $(this.queryByHook("sd-in-progress")).css("display", "none");
+ $(this.queryByHook("sd-action-error")).text(action);
+ $(this.queryByHook("sd-error")).css("display", "block");
},
getBreadcrumbData: function () {
- var data = {"project":null, "model":null};
+ let data = {"project":null, "model":null};
var projEP = "stochss/project/manager?path="
var mdlEP = "stochss/models/edit?path="
if(this.model) {
@@ -450,217 +119,58 @@ let DomainEditor = PageView.extend({
}
return data
},
- getNewParticle: function () {
- var particle = new Particle({
- point: [0, 0, 0],
- mass: 1.0,
- volume: 1.0,
- nu: 0.0,
- fixed: false,
- type: 0
- });
- return particle;
- },
- getTypesFromFile: function (typePath) {
- this.startAction("Setting types ...", "st")
- let self = this;
- let queryStr = "?path=" + this.typeDescriptionsFile;
- let endpoint = path.join(app.getApiPath(), "spatial-model/particle-types") + queryStr;
- app.getXHR(endpoint, {
- success: function (err, response, body) {
- self.addMissingTypes(body.names);
- self.changeParticleTypes(body.types);
- self.completeAction("Types set", "st");
- },
- error: function (err, response, body) {
- self.errorAction(body.Message, "st");
- console.log(err);
- }
- });
- },
- renameType: function (index, newName) {
- this.domain.types.get(index, "typeID").name = newName;
- this.renderEditParticle();
- this.renderNewParticle();
- this.renderTypeSelectView();
- this.renderCreate3DDomain();
- this.plot.data[index].name = newName;
- this.updatePlot();
- },
- renderCreate3DDomain: function () {
- if(this.create3DDomainView) {
- this.create3DDomainView.remove()
- }
- this.create3DDomainView = new Create3DDomainView({
- parent: this,
- model: this.domain
- });
- app.registerRenderSubview(this, this.create3DDomainView, "add-3d-domain");
- },
- renderDomainLimitations: function () {
- if(this.xLimMinView) {
- this.xLimMinView.remove();
- this.yLimMinView.remove();
- this.zLimMinView.remove();
- this.xLimMaxView.remove();
- this.yLimMaxView.remove();
- this.zLimMaxView.remove();
- }
- this.xLimMinView = new InputView({parent: this, required: true,
- name: 'x-lim-min', valueType: 'number',
- value: this.domain.x_lim[0] || 0});
- app.registerRenderSubview(this, this.xLimMinView, "x_lim-min");
- this.yLimMinView = new InputView({parent: this, required: true,
- name: 'y-lim-min', valueType: 'number',
- value: this.domain.y_lim[0] || 0});
- app.registerRenderSubview(this, this.yLimMinView, "y_lim-min");
- this.zLimMinView = new InputView({parent: this, required: true,
- name: 'z-lim-min', valueType: 'number',
- value: this.domain.z_lim[0] || 0});
- app.registerRenderSubview(this, this.zLimMinView, "z_lim-min");
- this.xLimMaxView = new InputView({parent: this, required: true,
- name: 'x-lim-max', valueType: 'number',
- value: this.domain.x_lim[1] || 0});
- app.registerRenderSubview(this, this.xLimMaxView, "x_lim-max");
- this.yLimMaxView = new InputView({parent: this, required: true,
- name: 'y-lim-max', valueType: 'number',
- value: this.domain.y_lim[1] || 0});
- app.registerRenderSubview(this, this.yLimMaxView, "y_lim-max");
- this.zLimMaxView = new InputView({parent: this, required: true,
- name: 'z-lim-max', valueType: 'number',
- value: this.domain.z_lim[1] || 0});
- app.registerRenderSubview(this, this.zLimMaxView, "z_lim-max");
- },
- renderDomainProperties: function () {
- $(this.queryByHook("static-domain")).prop("checked", this.domain.static);
- let densityView = new InputView({parent: this, required: true,
- name: 'density', valueType: 'number',
- value: this.domain.rho_0 || 1});
- app.registerRenderSubview(this, densityView, "density");
- let gravityXView = new InputView({parent: this, required: true,
- name: 'gravity-x', valueType: 'number',
- value: this.domain.gravity[0], label: "X: "});
- app.registerRenderSubview(this, gravityXView, "gravity-x");
- let gravityYView = new InputView({parent: this, required: true,
- name: 'gravity-y', valueType: 'number',
- value: this.domain.gravity[1], label: "Y: "});
- app.registerRenderSubview(this, gravityYView, "gravity-y");
- let gravityZView = new InputView({parent: this, required: true,
- name: 'gravity-z', valueType: 'number',
- value: this.domain.gravity[2], label: "Z: "});
- app.registerRenderSubview(this, gravityZView, "gravity-z");
- let pressureView = new InputView({parent: this, required: true,
- name: 'pressure', valueType: 'number',
- value: this.domain.p_0 || 0});
- app.registerRenderSubview(this, pressureView, "pressure");
- let speedView = new InputView({parent: this, required: true,
- name: 'speed', valueType: 'number',
- value: this.domain.c_0 || 0});
- app.registerRenderSubview(this, speedView, "speed");
- },
- renderDomainTypes: function () {
- if(this.domainTypesView) {
- this.domainTypesView.remove();
- this.domain.types.forEach(function (type) {
- type.numParticles = 0;
+ handleSaveToModel: function () {
+ if(this.model) {
+ this.startAction("Saving domain to model ...");
+ this.model.domain = this.domain;
+ this.model.saveModel(() => {
+ this.completeAction("Domain saved to model");
});
}
- let self = this;
- this.domain.particles.forEach(function (particle) {
- self.domain.types.get(particle.type, "typeID").numParticles += 1;
- });
- let unaPart = "Number of Un-Assigned Particles: " + this.domain.types.get(0, "typeID").numParticles;
- $(this.queryByHook("unassigned-particles")).text(unaPart);
- this.domainTypesView = this.renderCollection(
- this.domain.types,
- EditDomainTypeView,
- this.queryByHook("domain-types-list"),
- {"filter": function (model) {
- return model.typeID != 0;
- }}
- );
- this.toggleDomainError()
},
- renderEditTypeDefaults: function () {
- if(this.massView) {
- this.massView.remove();
- }
- if(this.volView) {
- this.volView.remove();
- }
- if(this.nuView) {
- this.nuView.remove();
- }
- var title = "Defaults for ";
- if(this.selectedType === "new"){
- var type = this.domain.types.get(0, "typeID")
- title += "New Type";
- }else{
- var type = this.domain.types.get(this.selectedType, "typeID")
- title += type.name
- }
- $(this.queryByHook("set-type-defaults-header")).text(title)
- this.massView = new InputView({parent: this, required: true,
- name: 'mass', valueType: 'number',
- value: type.mass});
- app.registerRenderSubview(this, this.massView, "td-mass");
- this.volView = new InputView({parent: this, required: true,
- name: 'volume', valueType: 'number',
- value: type.volume});
- app.registerRenderSubview(this, this.volView, "td-vol");
- this.nuView = new InputView({parent: this, required: true,
- name: 'viscosity', valueType: 'number',
- value: type.nu});
- app.registerRenderSubview(this, this.nuView, "td-nu");
- $(this.queryByHook("td-fixed")).prop("checked", type.fixed);
- $(this.queryByHook("edit-defaults")).css("display", "block");
- },
- renderEditParticle: function () {
- if(this.editParticleView) {
- this.editParticleView.remove();
- }
- if(this.actPart.part) {
- var particle = this.actPart.part;
+ handleSaveToFile: function () {
+ this.startAction("Saving the domain to file (.domn) ...")
+ if(this.domain.directory && !this.domain.dirname) {
+ this.saveDomain()
}else{
- var particle = this.getNewParticle();
+ if(document.querySelector('#newDomainModal')) {
+ document.querySelector('#newDomainModal').remove()
+ }
+ let modal = $(modals.createDomainHtml()).modal();
+ let okBtn = document.querySelector('#newDomainModal .ok-model-btn');
+ let input = document.querySelector('#newDomainModal #domainNameInput');
+ input.addEventListener("keyup", (event) => {
+ if(event.keyCode === 13){
+ event.preventDefault();
+ okBtn.click();
+ }
+ });
+ input.addEventListener("input", (e) => {
+ let endErrMsg = document.querySelector('#newDomainModal #domainNameInputEndCharError')
+ let charErrMsg = document.querySelector('#newDomainModal #domainNameInputSpecCharError')
+ let error = app.validateName(input.value)
+ okBtn.disabled = error !== "" || input.value.trim() === ""
+ charErrMsg.style.display = error === "both" || error === "special" ? "block" : "none"
+ endErrMsg.style.display = error === "both" || error === "forward" ? "block" : "none"
+ });
+ okBtn.addEventListener('click', (e) => {
+ if (Boolean(input.value)) {
+ modal.modal('hide')
+ let name = input.value.trim()
+ this.saveDomain({name: name});
+ }
+ });
}
- this.editParticleView = new ParticleView({
- model: particle,
- newParticle: false,
- });
- app.registerRenderSubview(this, this.editParticleView, "edit-particle");
- },
- renderMeshTransformations: function () {
- let xtrans = new InputView({parent: this, required: true,
- name: 'x-transformation', valueType: 'number',
- value: 0});
- app.registerRenderSubview(this, xtrans, "mesh-x-trans");
- let ytrans = new InputView({parent: this, required: true,
- name: 'y-transformation', valueType: 'number',
- value: 0});
- app.registerRenderSubview(this, ytrans, "mesh-y-trans");
- let ztrans = new InputView({parent: this, required: true,
- name: 'z-transformation', valueType: 'number',
- value: 0});
- app.registerRenderSubview(this, ztrans, "mesh-z-trans");
},
- renderMeshTypeDefaults: function (id) {
- let type = this.domain.types.get(id, "typeID")
- $(this.queryByHook("mesh-mass")).text(type.mass);
- $(this.queryByHook("mesh-volume")).text(type.volume);
- $(this.queryByHook("mesh-nu")).text(type.nu);
- $(this.queryByHook("mesh-fixed")).prop("checked", type.fixed);
- },
- renderNewParticle: function () {
- if(this.newParticleView) {
- this.newParticleView.remove();
+ renderDomainView: function () {
+ if(this.doaminView) {
+ this.domainView.remove();
}
- var particle = this.getNewParticle();
- this.newParticleView = new ParticleView({
- model: particle,
- newParticle: true,
+ this.domainView = new DomainView({
+ model: this.domain,
+ queryStr: this.queryStr
});
- app.registerRenderSubview(this, this.newParticleView, "new-particle");
+ app.registerRenderSubview(this, this.domainView, "domain-view-container");
},
renderSubviews: function () {
let breadData = this.getBreadcrumbData();
@@ -692,216 +202,37 @@ let DomainEditor = PageView.extend({
$(this.queryByHook("return-to-model")).css("display", "none");
$(this.queryByHook("return-to-project")).css("display", "none");
}
- this.renderDomainProperties();
- this.renderDomainLimitations();
- $(this.queryByHook("reflect_x")).prop("checked", this.domain.boundary_condition.reflect_x);
- $(this.queryByHook("reflect_y")).prop("checked", this.domain.boundary_condition.reflect_y);
- $(this.queryByHook("reflect_z")).prop("checked", this.domain.boundary_condition.reflect_z);
- this.renderDomainTypes();
- this.renderNewParticle();
- this.renderEditParticle();
- this.renderTypeSelectView();
- this.renderMeshTypeDefaults(0);
- this.renderMeshTransformations();
- this.renderTypesFileSelect();
- this.renderCreate3DDomain();
- let self = this;
- let endpoint = path.join(app.getApiPath(), "spatial-model/domain-plot") + this.queryStr;
- app.getXHR(endpoint, {
- success: function (err, response, body) {
- self.plot = body.fig;
- self.displayDomain();
- }
- });
- $(document).ready(function () {
- $('[data-toggle="tooltip"]').tooltip();
- $('[data-toggle="tooltip"]').click(function () {
- $('[data-toggle="tooltip"]').tooltip("hide");
- });
- });
- this.domain.updateValid();
- this.toggleDomainError();
+ this.renderDomainView();
+ app.documentSetup();
if(!this.model) {
$(this.queryByHook("save-to-model")).addClass("disabled")
}
},
- renderTypesFileSelect: function () {
- let self = this;
- let endpoint = path.join(app.getApiPath(), "spatial-model/types-list");
- app.getXHR(endpoint, {
- success: function (err, response, body) {
- self.typeDescriptions = body.paths;
- var typesSelectView = new SelectView({
- label: '',
- name: 'type-files',
- required: false,
- idAttributes: 'cid',
- options: body.files,
- unselectedText: "-- Select Type File --",
- });
- app.registerRenderSubview(self, typesSelectView, "types-file-select");
- }
- });
- },
- renderTypesLocationSelect: function (options) {
- if(this.typesLocationSelectView) {
- this.typesLocationSelectView.remove();
- }
- this.typesLocationSelectView = new SelectView({
- label: '',
- name: 'type-locations',
- required: false,
- idAttributes: 'cid',
- options: options,
- unselectedText: "-- Select Location --"
- });
- app.registerRenderSubview(this, this.typesLocationSelectView, "types-file-location-select")
- },
- renderTypeSelectView: function () {
- if(this.typeView) {
- this.typeView.remove()
- }
- this.typeView = new SelectView({
- label: 'Type: ',
- name: 'type',
- required: true,
- idAttribute: 'typeID',
- textAttribute: 'name',
- eagerValidate: true,
- options: this.domain.types,
- value: this.domain.types.get(0, "typeID")
- });
- app.registerRenderSubview(this, this.typeView, "mesh-type-select")
- },
- saveDomain: function (name=null) {
+ saveDomain: function ({name=null}={}) {
let domain = this.domain.toJSON();
if(name) {
- let file = name + ".domn";
+ let file = `${name}.domn`;
var dirname = this.model ? path.dirname(this.model.directory) : this.domain.dirname;
var domainPath = dirname === "/" ? file : path.join(dirname, file);
}else{
var domainPath = this.domain.directory;
}
- let self = this;
- let endpoint = path.join(app.getApiPath(), "file/json-data") + "?path=" + domainPath;
+ let endpoint = path.join(app.getApiPath(), "file/json-data") + `?path=${domainPath}`;
app.postXHR(endpoint, domain, {
- success: function (err, response, body) {
- self.completeAction("Domain save to file (.domn)", "sd");
+ success: (err, response, body) => {
+ this.completeAction("Domain save to file (.domn)");
},
- error: function (err, response, body) {
- self.errorAction(body.Message, "sd");
- console.log(body.message);
+ error: (err, response, body) => {
+ this.errorAction(body.Message);
}
});
},
- selectParticle: function (data) {
- let point = data.points[0];
- this.actPart.part = this.domain.particles.get(point.id, "particle_id");
- this.actPart.tn = point.curveNumber;
- this.actPart.pn = point.pointNumber;
- this.renderEditParticle();
- },
- setBoundaryCondition: function (e) {
- let key = e.target.dataset.hook;
- let value = e.target.checked;
- this.domain.boundary_condition[key] = value;
- },
- setLimitation: function (e) {
- let data = e.target.parentElement.parentElement.dataset.hook.split('-');
- let index = data[1] === "min" ? 0 : 1;
- let value = Number(e.target.value.trim())
- this.domain[data[0]][index] = value;
- },
- setDensity: function (e) {
- let value = Number(e.target.value)
- this.domain.rho_0 = value;
- },
- setGravity: function (e) {
- let value = Number(e.target.value)
- let hook = e.target.parentElement.parentElement.dataset.hook;
- if(hook.endsWith("x")){
- this.domain.gravity[0] = value;
- }else if(hook.endsWith("y")) {
- this.domain.gravity[1] = value;
- }else {
- this.domain.gravity[2] = value;
- }
- },
- setPressure: function (e) {
- let value = Number(e.target.value)
- this.domain.p_0 = value;
- },
- setSpeed: function (e) {
- let value = Number(e.target.value)
- this.domain.c_0 = value;
- },
- setStaticDomain: function (e) {
- let value = e.target.checked;
- this.domain.statis = value;
- },
- startAction: function (action, src) {
- $(this.queryByHook(src + "-complete")).css("display", "none");
- $(this.queryByHook(src + "-action-in-progress")).text(action);
- $(this.queryByHook(src + "-in-progress")).css("display", "inline-block");
- console.log(action)
- },
- unassignAllParticles: function (type, update=true) {
- let self = this;
- this.domain.particles.forEach(function (particle) {
- if(particle.type === type) {
- self.actPart.part = particle;
- self.actPart.tn = type;
- self.actPart.pn = self.plot.data[type].ids.indexOf(particle.particle_id)
- self.changeParticleType(0);
- }
- });
- if(this.actPart.part && this.actPart.part.type == type) {
- this.actPart.part = null;
- this.renderEditParticle();
- }
- if(update) {
- this.updatePlot();
- }
- },
- updatePlot: function () {
- var el = this.queryByHook("domain-plot");
- el.removeListener('plotly_click', this.selectParticle);
- Plotly.purge(el);
- this.displayDomain();
- },
- update: function () {},
- updateMeshTypeAndDefaults: function (e) {
- let id = Number(e.target.selectedOptions.item(0).value);
- this.renderMeshTypeDefaults(id);
- },
- updateParticle: function ({cb=null}={}) {
- if(this.actPart.part.pointChanged) {
- let x = this.actPart.part.point[0];
- let y = this.actPart.part.point[1];
- let z = this.actPart.part.point[2];
- this.changeParticleLocation(x, y, z)
- }
- if(this.actPart.part.typeChanged) {
- let type = this.actPart.part.type
- this.changeParticleType(type);
- this.renderDomainTypes();
- }
- this.updatePlot();
- if(cb) {
- cb()
- }
- },
- updateValid: function () {},
- toggleDomainError: function () {
- let errorMsg = $(this.queryByHook('domain-error'))
- if(!this.domain.valid) {
- errorMsg.addClass('component-invalid')
- errorMsg.removeClass('component-valid')
- }else{
- errorMsg.addClass('component-valid')
- errorMsg.removeClass('component-invalid')
- }
- },
+ startAction: function (action) {
+ $(this.queryByHook("sd-complete")).css("display", "none");
+ $(this.queryByHook("sd-error")).css("display", "none");
+ $(this.queryByHook("sd-action-in-progress")).text(action);
+ $(this.queryByHook("sd-in-progress")).css("display", "inline-block");
+ }
});
initPage(DomainEditor);
diff --git a/client/pages/model-editor.js b/client/pages/model-editor.js
index fe16d3b4dc..63e41c697b 100644
--- a/client/pages/model-editor.js
+++ b/client/pages/model-editor.js
@@ -330,7 +330,8 @@ let ModelEditor = PageView.extend({
let domainElements = {
select: $(this.queryByHook("me-select-particle")),
particle: {view: this, hook: "me-particle-viewer"},
- plot: this.queryByHook("domain-plot-container"),
+ figure: this.queryByHook("domain-plot-container"),
+ figureEmpty: this.queryByHook("domain-plot-container-empty"),
type: this.queryByHook("me-types-quick-view")
}
this.modelView = new ModelView({
diff --git a/client/reaction-types.js b/client/reaction-types.js
index 4ff9516b32..f8a2e3f8e4 100644
--- a/client/reaction-types.js
+++ b/client/reaction-types.js
@@ -55,11 +55,11 @@ module.exports = {
'custom-massaction': {
reactants: [ { ratio: 1 } ],
products: [ { ratio: 1 } ],
- label: 'Custom mass action'
+ label: 'Custom Mass Action'
},
'custom-propensity': {
reactants: [ { ratio: 1 } ],
products: [ { ratio: 1 } ],
- label: 'Custom propensity'
+ label: 'Custom Propensity'
}
}
diff --git a/client/styles/styles.css b/client/styles/styles.css
index e5c6744d2d..6ddb695ddc 100644
--- a/client/styles/styles.css
+++ b/client/styles/styles.css
@@ -130,8 +130,8 @@ label {
}
span.custom {
- padding-right: 0.25rem;
- padding-left: 0.25rem;
+ padding-right: 0.5rem;
+ padding-left: 0.5rem;
}
button.custom {
@@ -302,7 +302,6 @@ input[type="file"]::-ms-browse {
.reaction-list-summary {
width: auto !important;
- text-align: center;
}
.container-part {
@@ -649,13 +648,32 @@ span.checkbox {
height: auto;
}
+.dropdown-item.rtype.disabled {
+ pointer-events: auto !important;
+}
+
.reaction-lb2 {
display: none;
}
-.dropdown-item:hover .reaction-lb2 {
+.dropdown-item:not(.disabled):hover .reaction-lb2 {
position: absolute;
- left: 195px;
+ left: 196px;
+ display: inline-block;
+ background-color: black;
+ color: white;
+ box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
+ border: .5px solid rgb(228, 229, 230) !important;
+ padding: 0px 5px;
+}
+
+.reaction-lb3 {
+ display: none;
+}
+
+.dropdown-item.disabled:hover .reaction-lb3 {
+ position: absolute;
+ left: 196px;
display: inline-block;
background-color: black;
color: white;
@@ -727,3 +745,7 @@ span.checkbox {
margin-top: 64px;
padding: 20px 16px;
}
+
+.hidden {
+ display: none;
+}
diff --git a/client/templates/includes/edit3DDomain.pug b/client/templates/includes/edit3DDomain.pug
deleted file mode 100644
index 4a6e55b090..0000000000
--- a/client/templates/includes/edit3DDomain.pug
+++ /dev/null
@@ -1,103 +0,0 @@
-div.card.card-body
-
- div
-
- h4.mt-3 Particles Distribution
-
- table.table
- thead
- tr
- th(scope="col")
- th(scope="col") X-Plane
- th(scope="col") Y-Plane
- th(scope="col") Z-Plane
- th(scope="col") Total
- tbody
- tr
- th(scope="row") Number of Particles
- td: div(data-target="num-particles" data-hook="nx")
- td: div(data-target="num-particles" data-hook="ny")
- td: div(data-target="num-particles" data-hook="nz")
- td: div(data-hook="total")
-
- div
-
- h4 Domain Limits
-
- table.table
- thead
- tr
- th(scope="col")
- th(scope="col") X-Axis
- th(scope="col") Y-Axis
- th(scope="col") Z-Axis
- tbody
- tr
- th(scope="row") Minimum
- td: div(data-target="limits" data-hook="xLim-min")
- td: div(data-target="limits" data-hook="yLim-min")
- td: div(data-target="limits" data-hook="zLim-min")
- tr
- th(scope="row") Maximum
- td: div(data-target="limits" data-hook="xLim-max")
- td: div(data-target="limits" data-hook="yLim-max")
- td: div(data-target="limits" data-hook="zLim-max")
-
- div
-
- h4.inline Advanced
-
- button.btn.btn-outline-collapse(data-toggle="collapse", data-target="#create-3D-domain-advanced", data-hook="collapse") +
-
- div.collapse(id="create-3D-domain-advanced" data-hook="3D-domain-advanced-container")
-
- div(data-hook="type-select")
-
- h5 Type Defaults
-
- table.table
- thead
- tr
- th(scope="col") Mass
- th(scope="col") Volume
- th(scope="col") Viscosity
- th(scope="col") Fixed
-
- tbody
- tr
- td: div(data-hook="mass")
- td: div(data-hook="volume")
- td: div(data-hook="nu")
- td: input(type="checkbox" data-hook="fixed" disabled)
-
- h5 Particle Transformations
-
- table.table
- thead
- tr
- th(scope="col") X-Axis
- th(scope="col") Y-Axis
- th(scope="col") Z-Axis
- tbody
- tr
- td: div(data-hook="domain-x-trans")
- td: div(data-hook="domain-y-trans")
- td: div(data-hook="domain-z-trans")
-
- div.inline
-
- button.btn.btn-outline-primary(data-hook="build-domain") Build Domain
-
- div.mdl-edit-btn.saving-status.inline(data-hook="cd-in-progress")
-
- div.spinner-grow.mr-2
-
- span(data-hook="cd-action-in-progress")
-
- div.mdl-edit-btn.saved-status.inline(data-hook="cd-complete")
-
- span(data-hook="cd-action-complete")
-
- div.mdl-edit-btn.save-error-status(data-hook="cd-error")
-
- span(data-hook="cd-action-error")
diff --git a/client/templates/includes/editParticle.pug b/client/templates/includes/editParticle.pug
deleted file mode 100644
index c4b5d34153..0000000000
--- a/client/templates/includes/editParticle.pug
+++ /dev/null
@@ -1,63 +0,0 @@
-div.card.card-body
-
- div.text-info.mb-4(data-hook="select-message-" + this.viewIndex style="display: none")
- | Click on a particle in the Domain section to begin editing.
-
- div
-
- h4.inline.mr-2 Location:
-
- div.inline.mr-2 (x:
-
- div.inline.ml2(data-target="location" data-hook="x-coord-" + this.viewIndex)
-
- div.inline.mr-2 , y:
-
- div.inline.ml2(data-target="location" data-hook="y-coord-" + this.viewIndex)
-
- div.inline.mr-2 , z:
-
- div.inline.ml2(data-target="location" data-hook="z-coord-" + this.viewIndex)
-
- div.inline )
-
- h4 Particle Properties
-
- table.table
- thead
- tr
- th(scope="row") Type
- th(scope="row") Mass
- th(scope="row") Volume
- th(scope="row") Viscosity
- th(scope="row") Fixed
-
- tbody
- tr
- td: div(data-target="type" data-hook="type-" + this.viewIndex)
- td: div(data-target="mass" data-hook="mass-" + this.viewIndex)
- td: div(data-target="volume" data-hook="volume-" + this.viewIndex)
- td: div(data-target="nu" data-hook="nu-" + this.viewIndex)
- td: input(type="checkbox" data-target="fixed" data-hook="fixed-" + this.viewIndex)
-
- div
-
- div.inline(data-hook="new-particle-btns-" + this.viewIndex)
-
- button.btn.btn-outline-primary.box-shadow(data-hook="add-particle") Add Particle
-
- div.inline(data-hook="edit-particle-btns-" + this.viewIndex)
-
- button.btn.btn-outline-primary.box-shadow.ml-2(data-hook="save-particle") Save Particle
-
- button.btn.btn-outline-primary.box-shadow.ml-2(data-hook="remove-particle") Remove Particle
-
- div.mdl-edit-btn.saving-status.inline(data-hook="ep-in-progress")
-
- div.spinner-grow.mr-2
-
- span(data-hook="ep-action-in-progress")
-
- div.mdl-edit-btn.saved-status.inline(data-hook="ep-complete")
-
- span(data-hook="ep-action-complete")
diff --git a/client/templates/includes/jstreeView.pug b/client/templates/includes/jstreeView.pug
index 64c3109a8f..e2f0075e83 100644
--- a/client/templates/includes/jstreeView.pug
+++ b/client/templates/includes/jstreeView.pug
@@ -23,8 +23,8 @@ div
li.dropdown-item(id="fb-new-directory" data-hook="fb-new-directory") Create Directory
li.dropdown-item(id="fb-new-project" data-hook="fb-new-project") Create Project
li.dropdown-item(id="fb-new-model" data-hook="fb-new-model" data-type="model") Create Model
- li.dropdown-item(id="fb-new-model" data-hook="fb-new-model" data-type="spatial") Create Spatial Model (beta)
- li.dropdown-item(id="fb-new-model" data-hook="fb-new-domain") Create Domain (beta)
+ li.dropdown-item(id="fb-new-model" data-hook="fb-new-model" data-type="spatial") Create Spatial Model
+ li.dropdown-item(id="fb-new-model" data-hook="fb-new-domain") Create Domain
li.dropdown-divider(data-hook="fb-proj-seperator")
li.dropdown-item(id="fb-import-model" data-hook="fb-import-model") Add Existing Model
li.dropdown-divider
diff --git a/client/templates/includes/meshEditor.pug b/client/templates/includes/meshEditor.pug
deleted file mode 100644
index 4b478b473a..0000000000
--- a/client/templates/includes/meshEditor.pug
+++ /dev/null
@@ -1,13 +0,0 @@
-div#mesh-editor.card.card-body
-
- div
-
- h3.inline Mesh Editor
- button.btn.btn-outline-collapse(data-toggle="collapse", data-target="#collapse-mesh", data-hook="collapse") -
-
- div.collapse(class="show", id="collapse-mesh")
-
- table.table
- tbody
- tr
- td: div(data-hook="num-subdomains-container")
\ No newline at end of file
diff --git a/client/templates/includes/quickviewDomainTypes.pug b/client/templates/includes/quickviewDomainTypes.pug
deleted file mode 100644
index 80ad38e1f0..0000000000
--- a/client/templates/includes/quickviewDomainTypes.pug
+++ /dev/null
@@ -1,5 +0,0 @@
-tr
-
- td=this.model.name
-
- td=this.model.numParticles
\ No newline at end of file
diff --git a/client/templates/includes/viewParticle.pug b/client/templates/includes/viewParticle.pug
deleted file mode 100644
index 7049a0fb5d..0000000000
--- a/client/templates/includes/viewParticle.pug
+++ /dev/null
@@ -1,30 +0,0 @@
-div
-
- h5 Properties
-
- table.table
- tbody
- tr
- th(scope="row") ID
- td=this.model.particle_id
- tr
- th(scope="row") Type
- td=this.type
- tr
- th(scope="row") Location
- td
- div="x: " + this.model.point[0]
- div="y: " + this.model.point[1]
- div="z: " + this.model.point[2]
- tr
- th(scope="row") Mass
- td=this.model.mass
- tr
- th(scope="row") Volume
- td=this.model.volume
- tr
- th(scope="row") Viscosity
- td=this.model.nu
- tr
- th(scope="row") Fixed
- td: input(type="checkbox" checked=this.model.fixed disabled)
\ No newline at end of file
diff --git a/client/templates/pages/domainEditor.pug b/client/templates/pages/domainEditor.pug
index 578b0d59bb..d97d85b6c2 100644
--- a/client/templates/pages/domainEditor.pug
+++ b/client/templates/pages/domainEditor.pug
@@ -18,285 +18,7 @@ section.page
li.breadcrumb-item
a.active-link(data-hook="parent-one-breadcrumb") Parent
- div.card.card-body
-
- div
-
- h3.inline Properties
-
- button.btn.btn-outline-collapse(data-toggle="collapse", data-target="#domain-properties", data-hook="collapse-domain-properties") -
-
- div.collapse.show(id="domain-properties" data-hook="domain-properties")
-
- table.table
- thead
- tr
- th(scope="col") Static Domain
- th(scope="col") Density
- th(scope="col") Gravity
- th(scope="col")
- div
- div.inline Pressure
-
- div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.pressure)
-
- th(scope="col")
- div
- div.inline Speed of Sound
-
- div.tooltip-icon(data-html="true" data-toggle="tooltip" title=this.tooltips.speed)
-
- tbody
- tr
- td: input(type="checkbox", id="static-domain", data-hook="static-domain")
- td: div(data-hook="density")
- td
- div(data-target="gravity" data-hook="gravity-x")
- div(data-target="gravity" data-hook="gravity-y")
- div(data-target="gravity" data-hook="gravity-z")
- td: div(data-hook="pressure")
- td: div(data-hook="speed")
-
- h4 Domain Limits
-
- table.table
- thead
- tr
- th(scope="col")
- th(scope="col") X-Axis
- th(scope="col") Y-Axis
- th(scope="col") Z-Axis
-
- tbody
- tr
- th(scope="row") Minimum
- td: div(data-hook="x_lim-min" data-name="limitation")
- td: div(data-hook="y_lim-min" data-name="limitation")
- td: div(data-hook="z_lim-min" data-name="limitation")
- tr
- th(scope="row") Maximum
- td: div(data-hook="x_lim-max" data-name="limitation")
- td: div(data-hook="y_lim-max" data-name="limitation")
- td: div(data-hook="z_lim-max" data-name="limitation")
- tr
- th(scope="row") Reflect
- td: input(type="checkbox", id="x-reflect", data-hook="reflect_x" data-target="reflect" disabled)
- td: input(type="checkbox", id="y-reflect", data-hook="reflect_y" data-target="reflect" disabled)
- td: input(type="checkbox", id="z-reflect", data-hook="reflect_z" data-target="reflect" disabled)
-
- div.card.card-body
-
- div
-
- h3.inline Types
-
- button.btn.btn-outline-collapse(data-toggle="collapse", data-target="#domain-types", data-hook="collapse-domain-types") -
-
- div.collapse.show(id="domain-types" data-hook="domain-types")
-
- div
-
- h6(data-hook="unassigned-particles")
-
- div(data-hook="domain-error"): p.text-danger A domain cannot have any un-assigned particles.
-
- table.table
- thead
- tr
- th(scope="col") Name
- th(scope="col") Number of Particles
- th(scope="col" colspan="4") Defaults
-
- tbody(data-hook="domain-types-list")
-
- button.btn.btn-outline-primary.box-shadow(data-hook="add-domain-type") Add Type
-
- div.card.card-body(data-hook="edit-defaults" style="display: none")
-
- h4(data-hook="set-type-defaults-header")
-
- table.table
- thead
- tr
- th(scope="col") Mass
- th(scope="col") Volume
- th(scope="col") Viscosity
- th(scope="col") Fixed
-
- tbody
- tr
- td: div(data-hook="td-mass")
- td: div(data-hook="td-vol")
- td: div(data-hook="td-nu")
- td: input(type="checkbox" data-hook="td-fixed")
-
- button.btn.btn-outline-primary.box-shadow(data-hook="set-type-defaults") Submit
-
- div.card.card-body
-
- div
-
- h3.inline Particles
-
- button.btn.btn-outline-collapse(data-toggle="collapse", data-target="#domain-particles", data-hook="collapse-domain-particle") -
-
- div.collapse.show(id="domain-particles" data-hook="domain-particles")
-
- ul.nav.nav-tabs
-
- li.nav-item
-
- a.nav-link.tab.active(data-toggle="tab" href="#new-particle") Create New
-
- li.nav-item
-
- a.nav-link.tab(data-toggle="tab" href="#edit-particle") Edit Selected
-
- li.nav-item
-
- a.nav-link.tab(data-toggle="tab" href="#set-particle-types") Set Particle Types
-
- li.nav-item
-
- a.nav-link.tab(data-toggle="tab" href="#add-3d-domain") Create 3D Domain
-
- li.nav-item
-
- a.nav-link.tab(data-toggle="tab" href="#import-particles") Import Mesh
-
- div.tab-content
-
- div.tab-pane.active(id="new-particle" data-hook="new-particle")
-
- div.tab-pane(id="edit-particle" data-hook="edit-particle")
-
- div.tab-pane(id="add-3d-domain" data-hook="add-3d-domain")
-
- div.tab-pane(id="set-particle-types" data-hook="set-particle-types")
-
- div.card.card-body
-
- div.my-3
-
- div.text-info(data-hook="type-location-message" style="display: none")
- | There are multiple type files with that name, please select a location
-
- div.inline.mr-3
-
- span.inline.mr-2(for="types-file-select") Select type description file:
-
- div.inline(id="types-file-select" data-hook="types-file-select")
-
- div.inline(data-hook="type-location-container" style="display: none")
-
- span.inlinemr-2(for="types-file-location-select") Location:
-
- div.inline(id="types-file-location-select" data-hook="types-file-location-select")
-
- div.inline
-
- button.btn.btn-outline-primary.box-shadow(data-hook="set-particle-types-btn" disabled) Set Types
-
- div.mdl-edit-btn.saving-status.inline(data-hook="st-in-progress")
-
- div.spinner-grow.mr-2
-
- span(data-hook="st-action-in-progress")
-
- div.mdl-edit-btn.saved-status.inline(data-hook="st-complete")
-
- span(data-hook="st-action-complete")
-
- div.mdl-edit-btn.save-error-status(data-hook="st-error")
-
- span(data-hook="st-action-error")
-
- div.tab-pane(id="import-particles")
-
- div.card.card-body
-
- div.my-3
-
- span.inline.mr-2(for="meshfile") Please specify a mesh to import:
-
- input(id="meshfile" type="file" name="meshfile" size="30" accept=".xml" required)
-
- div.mb-3
-
- span.inline.mr-2(for=typefile) Type descriptions (optional):
-
- input(id="typefile" type="file" name="typefile" size="30" accept=".txt")
-
- div
-
- h4.inline Advanced
-
- button.btn.btn-outline-collapse(data-toggle="collapse", data-target="#mesh-advanced", data-hook="collapse-mesh-advanced") +
-
- div.collapse(id="mesh-advanced" data-hook="mesh-advanced-container")
-
- div(data-hook="mesh-type-select")
-
- h5 Type Defaults
-
- table.table
- thead
- tr
- th(scope="col") Mass
- th(scope="col") Volume
- th(scope="col") Viscosity
- th(scope="col") Fixed
-
- tbody
- tr
- td: div(data-hook="mesh-mass")
- td: div(data-hook="mesh-volume")
- td: div(data-hook="mesh-nu")
- td: input(type="checkbox" data-hook="mesh-fixed" disabled)
-
- h5 Particle Transformations
-
- table.table
- thead
- tr
- th(scope="col") X-Axis
- th(scope="col") Y-Axis
- th(scope="col") Z-Axis
- tbody
- tr
- td: div(data-hook="mesh-x-trans")
- td: div(data-hook="mesh-y-trans")
- td: div(data-hook="mesh-z-trans")
-
- div.inline
-
- button.btn.btn-outline-primary.box-shadow(data-hook="import-particles-btn" disabled) Import Mesh
-
- div.mdl-edit-btn.saving-status.inline(data-hook="im-in-progress")
-
- div.spinner-grow.mr-2
-
- span(data-hook="im-action-in-progress")
-
- div.mdl-edit-btn.saved-status.inline(data-hook="im-complete")
-
- span(data-hook="im-action-complete")
-
- div.mdl-edit-btn.save-error-status(data-hook="im-error")
-
- span(data-hook="im-action-error")
-
- div.card.card-body
-
- div
-
- h3.inline Domain
-
- button.btn.btn-outline-collapse(data-toggle="collapse", data-target="#domain-plot-container", data-hook="collapse-domain-plot") -
-
- div.collapse.show(id="domain-plot-container" data-hook="domain-plot-container")
-
- div(id="domain-plot" data-hook="domain-plot" style="height: 800px")
+ div(data-hook="domain-view-container")
div.inline
diff --git a/client/templates/pages/modelEditor.pug b/client/templates/pages/modelEditor.pug
index 5f5f5f9fc9..306bb6e24b 100644
--- a/client/templates/pages/modelEditor.pug
+++ b/client/templates/pages/modelEditor.pug
@@ -37,6 +37,8 @@ section.page
div(data-hook="domain-plot-container")
+ div.text-danger(data-hook="domain-plot-container-empty") The domain currently has no particles to display
+
div.col-sm-4.overflow-auto(style="max-height:450px;")
div.mt-2.particle-view(data-hook="me-particle-viewer")
@@ -50,8 +52,7 @@ section.page
h4 Particles per Type
- table.table
- tbody(data-hook="me-types-quick-view")
+ div(data-hook="me-types-quick-view")
div(data-hook="model-run-container")
diff --git a/client/templates/pages/projectManager.pug b/client/templates/pages/projectManager.pug
index 59bd4742bb..05476cf6c0 100644
--- a/client/templates/pages/projectManager.pug
+++ b/client/templates/pages/projectManager.pug
@@ -51,7 +51,7 @@ section.page
ul.dropdown-menu(aria-labelledby="project-manager-add-model-btn")
li.dropdown-item(id="new-model" data-hook="new-model" data-type="model") New Model
- li.dropdown-item(id="new-model" data-hook="new-model" data-type="spatial") New Spatial Model (beta)
+ li.dropdown-item(id="new-model" data-hook="new-model" data-type="spatial") New Spatial Model
li.dropdown-divider
li.dropdown-item(id="existing-model" data-hook="existing-model") Existing Model
li.dropdown-divider
diff --git a/client/tooltips.js b/client/tooltips.js
index 1049db9302..067224de3f 100644
--- a/client/tooltips.js
+++ b/client/tooltips.js
@@ -53,6 +53,8 @@ module.exports = {
rate: "The rate of the mass-action reaction.",
propensity: "The custom propensity expression for the reaction.",
+
+ odePropensity: "The custom ode propensity expression for the reaction.",
reactant: "The reactants that are consumed in the reaction, with stoichiometry.",
@@ -176,6 +178,11 @@ module.exports = {
speed: "Approximate or artificial speed of sound"
},
+ domainType: {
+ geometry: "The geometry expression can be any mathematical expression which evaluates to a boolean value in a python environment (i.e. x==5). This "+
+ "expression is evaluable within the a limited namespace, and only lower case variables (x, y, z), particles location, and (cx, cy, cz), "+
+ "center of the geometry, can be referenced in the expression."
+ },
boundaryConditionsEditor: {
annotation: "An optional note about a boundary condition."
}
diff --git a/client/views/edit-3D-domain.js b/client/views/edit-3D-domain.js
deleted file mode 100644
index 03c5afff1a..0000000000
--- a/client/views/edit-3D-domain.js
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
-StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see .
-*/
-
-var $ = require('jquery');
-var path = require('path');
-//support files
-var app = require('../app');
-var tests = require('./tests');
-//views
-var View = require('ampersand-view');
-var InputView = require('./input');
-var SelectView = require('ampersand-select-view');
-//templates
-var template = require('../templates/includes/edit3DDomain.pug');
-
-module.exports = View.extend({
- template: template,
- events: {
- 'change [data-target=num-particles]' : 'handleNumParticlesUpdate',
- 'change [data-target=limits]' : 'handleLimitsUpdate',
- 'change [data-hook=type-select]' : 'updateTypeAndDefaults',
- 'click [data-hook=build-domain]' : 'handleBuildDomain'
- },
- handleBuildDomain: function (e) {
- this.startAction("Creating domain ...")
- this.data.type = {"type_id":this.type.typeID, "mass":this.type.mass, "nu":this.type.nu, "fixed":this.type.fixed};
- this.data.volume = this.type.volume;
- let xtrans = Number($(this.queryByHook("domain-x-trans")).find('input')[0].value);
- let ytrans = Number($(this.queryByHook("domain-y-trans")).find('input')[0].value);
- let ztrans = Number($(this.queryByHook("domain-z-trans")).find('input')[0].value);
- if(xtrans !== 0 || ytrans !== 0 || ztrans !== 0) {
- this.data.transformation = [xtrans, ytrans, ztrans];
- }
- let self = this;
- let endpoint = path.join(app.getApiPath(), "spatial-model/3d-domain");
- app.postXHR(endpoint, this.data, {
- success: function (err, response, body) {
- self.parent.addParticles(body.particles);
- if(self.parent.domain.x_lim[0] > body.limits.x_lim[0]) {
- self.parent.domain.x_lim[0] = body.limits.x_lim[0];
- }
- if(self.parent.domain.y_lim[0] > body.limits.y_lim[0]) {
- self.parent.domain.y_lim[0] = body.limits.y_lim[0];
- }
- if(self.parent.domain.z_lim[0] > body.limits.z_lim[0]) {
- self.parent.domain.z_lim[0] = body.limits.z_lim[0];
- }
- if(self.parent.domain.x_lim[1] < body.limits.x_lim[1]) {
- self.parent.domain.x_lim[1] = body.limits.x_lim[1];
- }
- if(self.parent.domain.y_lim[1] < body.limits.y_lim[1]) {
- self.parent.domain.y_lim[1] = body.limits.y_lim[1];
- }
- if(self.parent.domain.z_lim[1] < body.limits.z_lim[1]) {
- self.parent.domain.z_lim[1] = body.limits.z_lim[1];
- }
- self.parent.renderDomainLimitations();
- self.completeAction("Domain successfully created");
- $('html, body').animate({
- scrollTop: $("#domain-plot").offset().top
- }, 20);
- },
- error: function (err, response, body) {
- self.errorAction(body.Message);
- }
- });
- },
- handleLimitsUpdate: function (e) {
- let data = e.target.parentElement.parentElement.dataset.hook.split("-");
- let index = data[1] === "min" ? 0 : 1;
- let value = Number(e.target.value);
- this.data[data[0]][index] = value;
- },
- handleNumParticlesUpdate: function (e) {
- let hook = e.target.parentElement.parentElement.dataset.hook;
- let value = Number(e.target.value);
- this.data[hook] = value;
- this.updateTotalParticles();
- },
- initialize: function (attrs, options) {
- View.prototype.initialize.apply(this, arguments);
- this.type = this.model.types.get(0, "typeID");
- this.data = {"nx":1, "ny":1, "nz":1, "xLim":[0, 0],
- "yLim":[0, 0], "zLim":[0, 0], "type":0, "volume":1,
- "transformation": null}
- },
- render: function (attrs, options) {
- View.prototype.render.apply(this, arguments);
- this.renderNumberOfParticles();
- this.updateTotalParticles();
- this.renderParticleLimits();
- this.renderType();
- this.renderTypeDefaults();
- this.renderDomainTransformations();
- },
- completeAction: function (action) {
- $(this.queryByHook("cd-in-progress")).css("display", "none");
- $(this.queryByHook("cd-action-complete")).text(action);
- $(this.queryByHook("cd-complete")).css("display", "inline-block");
- let self = this
- setTimeout(function () {
- $(self.queryByHook("cd-complete")).css("display", "none");
- }, 5000);
- },
- errorAction: function (action) {
- $(this.queryByHook("cd-in-progress")).css("display", "none");
- $(this.queryByHook("cd-action-error")).text(action);
- $(this.queryByHook("cd-error")).css("display", "block");
- console.log(action)
- },
- renderDomainTransformations: function () {
- let xtrans = new InputView({parent: this, required: true,
- name: 'x-transformation', valueType: 'number',
- value: 0});
- app.registerRenderSubview(this, xtrans, "domain-x-trans");
- let ytrans = new InputView({parent: this, required: true,
- name: 'y-transformation', valueType: 'number',
- value: 0});
- app.registerRenderSubview(this, ytrans, "domain-y-trans");
- let ztrans = new InputView({parent: this, required: true,
- name: 'z-transformation', valueType: 'number',
- value: 0});
- app.registerRenderSubview(this, ztrans, "domain-z-trans");
- },
- renderNumberOfParticles: function () {
- let nxView = new InputView({parent: this, required: true,
- name: 'nx', valueType: 'number',
- tests: tests.valueTests,
- value: this.data.nx});
- app.registerRenderSubview(this, nxView, "nx");
- let nyView = new InputView({parent: this, required: true,
- name: 'ny', valueType: 'number',
- tests: tests.valueTests,
- value: this.data.ny});
- app.registerRenderSubview(this, nyView, "ny");
- let nzView = new InputView({parent: this, required: true,
- name: 'nz', valueType: 'number',
- tests: tests.valueTests,
- value: this.data.nz});
- app.registerRenderSubview(this, nzView, "nz");
- },
- renderParticleLimits: function () {
- let xLimMinView = new InputView({parent: this, required: true,
- name: 'x-lim-min', valueType: 'number',
- value: this.data.xLim[0]});
- app.registerRenderSubview(this, xLimMinView, "xLim-min");
- let yLimMinView = new InputView({parent: this, required: true,
- name: 'y-lim-min', valueType: 'number',
- value: this.data.yLim[0]});
- app.registerRenderSubview(this, yLimMinView, "yLim-min");
- let zLimMinView = new InputView({parent: this, required: true,
- name: 'z-lim-min', valueType: 'number',
- value: this.data.zLim[0]});
- app.registerRenderSubview(this, zLimMinView, "zLim-min");
- let xLimMaxView = new InputView({parent: this, required: true,
- name: 'x-lim-max', valueType: 'number',
- value: this.data.xLim[1]});
- app.registerRenderSubview(this, xLimMaxView, "xLim-max");
- let yLimMaxView = new InputView({parent: this, required: true,
- name: 'y-lim-max', valueType: 'number',
- value: this.data.yLim[1]});
- app.registerRenderSubview(this, yLimMaxView, "yLim-max");
- let zLimMaxView = new InputView({parent: this, required: true,
- name: 'z-lim-max', valueType: 'number',
- value: this.data.zLim[1]});
- app.registerRenderSubview(this, zLimMaxView, "zLim-max");
- },
- renderType: function () {
- var typeView = new SelectView({
- label: 'Type: ',
- name: 'type',
- required: true,
- idAttribute: 'typeID',
- textAttribute: 'name',
- eagerValidate: true,
- options: this.model.types,
- value: this.type
- });
- app.registerRenderSubview(this, typeView, "type-select")
- },
- renderTypeDefaults: function () {
- $(this.queryByHook("mass")).text(this.type.mass)
- $(this.queryByHook("volume")).text(this.type.volume)
- $(this.queryByHook("nu")).text(this.type.nu)
- $(this.queryByHook("fixed")).prop("checked", this.type.fixed)
- },
- startAction: function (action) {
- $(this.queryByHook("cd-complete")).css("display", "none");
- $(this.queryByHook("cd-action-in-progress")).text(action);
- $(this.queryByHook("cd-in-progress")).css("display", "inline-block");
- },
- update: function (e) {},
- updateTotalParticles: function (e) {
- let total = this.data.nx * this.data.ny * this.data.nz;
- $(this.queryByHook("total")).text(total)
- $(this.queryByHook("build-domain")).prop("disabled", total <= 0)
- },
- updateTypeAndDefaults: function (e) {
- let id = Number(e.target.selectedOptions.item(0).value);
- this.type = this.model.types.get(id, "typeID");
- this.renderTypeDefaults();
- },
- updateValid: function (e) {}
-});
\ No newline at end of file
diff --git a/client/views/edit-particle.js b/client/views/edit-particle.js
deleted file mode 100644
index e28682ac34..0000000000
--- a/client/views/edit-particle.js
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
-StochSS is a platform for simulating biochemical systems
-Copyright (C) 2019-2022 StochSS developers.
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see .
-*/
-
-var $ = require('jquery');
-var _ = require('underscore');
-//support files
-let app = require('../app');
-var tests = require('../views/tests');
-//views
-var View = require('ampersand-view');
-var InputView = require('./input');
-var SelectView = require('ampersand-select-view');
-//templates
-var template = require('../templates/includes/editParticle.pug');
-
-module.exports = View.extend({
- template: template,
- events: {
- 'change [data-target=location]' : 'handleUpdateLocation',
- 'change [data-target=mass]' : 'handleUpdateMass',
- 'change [data-target=volume]' : 'handleUpdateVolume',
- 'change [data-target=nu]' : 'handleUpdateNu',
- 'change [data-target=fixed]' : 'handleUpdateFixed',
- 'change [data-target=type]' : 'handleUpdateType',
- 'click [data-hook=add-particle]' : 'handleAddParticle',
- 'click [data-hook=save-particle]' : 'handleSaveParticle',
- 'click [data-hook=remove-particle]' : 'handleRemoveParticle'
- },
- handleAddParticle: function (e) {
- this.startAction("Adding particle ...")
- this.parent.addParticle(this.model, {cb: _.bind(function () {
- this.completeAction("Particle Added")
- }, this)});
- },
- handleSaveParticle: function (e) {
- this.startAction("Saving particle ...")
- this.parent.updateParticle({cb: _.bind(function () {
- this.completeAction("Particle Saved")
- }, this)});
- },
- handleRemoveParticle: function (e) {
- this.parent.deleteParticle();
- },
- handleUpdateFixed: function (e) {
- let value = e.target.checked;
- this.model.fixed = value;
- },
- handleUpdateLocation: function (e) {
- let hook = e.target.parentElement.parentElement.dataset.hook;
- let value = Number(e.target.value);
- if(hook.startsWith('x')) {
- this.model.point[0] = value;
- }else if(hook.startsWith('y')) {
- this.model.point[1] = value;
- }else{
- this.model.point[2] = value;
- }
- this.model.pointChanged = true;
- },
- handleUpdateMass: function (e) {
- let value = Number(e.target.value);
- this.model.mass = value;
- },
- handleUpdateNu: function (e) {
- let value = Number(e.target.value);
- this.model.nu = value;
- },
- handleUpdateType: function (e) {
- let id = Number(e.target.selectedOptions.item(0).value);
- let oldType = this.parent.domain.types.get(this.model.type, "typeID");
- let newType = this.parent.domain.types.get(id, "typeID");
- if(this.model.mass === oldType.mass) {
- this.model.mass = newType.mass;
- }
- if(this.model.volume === oldType.volume) {
- this.model.volume = newType.volume;
- }
- if(this.model.nu === oldType.nu) {
- this.model.nu = newType.nu;
- }
- if(this.model.fixed === oldType.fixed) {
- this.model.fixed = newType.fixed;
- }
- this.renderProperties();
- this.model.type = id;
- this.model.typeChanged = true;
- },
- handleUpdateVolume: function (e) {
- let value = Number(e.target.value);
- this.model.volume = value;
- },
- initialize: function (attrs, options) {
- View.prototype.initialize.apply(this, arguments);
- this.newParticle = attrs.newParticle;
- this.viewIndex = this.newParticle ? 0 : 1;
- },
- completeAction: function (action) {
- $(this.queryByHook("ep-in-progress")).css("display", "none");
- $(this.queryByHook("ep-action-complete")).text(action);
- $(this.queryByHook("ep-complete")).css("display", "inline-block");
- console.log(action)
- let self = this
- setTimeout(function () {
- $(self.queryByHook("ep-complete")).css("display", "none");
- }, 5000);
- },
- disableAll: function () {
- $(this.queryByHook("x-coord-" + this.viewIndex)).find('input').prop('disabled', true);
- $(this.queryByHook("y-coord-" + this.viewIndex)).find('input').prop('disabled', true);
- $(this.queryByHook("z-coord-" + this.viewIndex)).find('input').prop('disabled', true);
- $(this.queryByHook("type-" + this.viewIndex)).find('select').prop('disabled', true);
- $(this.queryByHook("mass-" + this.viewIndex)).find('input').prop('disabled', true);
- $(this.queryByHook("volume-" + this.viewIndex)).find('input').prop('disabled', true);
- $(this.queryByHook("nu-" + this.viewIndex)).find('input').prop('disabled', true);
- $(this.queryByHook("fixed-" + this.viewIndex)).prop('disabled', true);
- $(this.queryByHook("save-particle")).prop('disabled', true);
- $(this.queryByHook("remove-particle")).prop('disabled', true);
- },
- render: function (attrs, options) {
- View.prototype.render.apply(this, arguments);
- this.type = this.parent.domain.types.get(this.model.type, "typeID");
- if(this.newParticle) {
- $(this.queryByHook("edit-particle-btns-"+this.viewIndex)).css("display", "none");
- }else{
- $(this.queryByHook("new-particle-btns-"+this.viewIndex)).css("display", "none");
- }
- this.renderLocation();
- this.renderProperties();
- this.renderType();
- if(!this.newParticle && !this.parent.actPart.part) {
- $(this.queryByHook("select-message-" + this.viewIndex)).css('display', 'block')
- this.disableAll();
- }
- },
- renderLocation: function () {
- var xCoord = new InputView({parent: this, required: true,
- name: 'x-coord', valueType: 'number',
- value: this.model.point[0] || 0});
- app.registerRenderSubview(this, xCoord, "x-coord-" + this.viewIndex);
- var yCoord = new InputView({parent: this, required: true,
- name: 'y-coord', valueType: 'number',
- value: this.model.point[1] || 0});
- app.registerRenderSubview(this, yCoord, "y-coord-" + this.viewIndex);
- var zCoord = new InputView({parent: this, required: true,
- name: 'z-coord', valueType: 'number',
- value: this.model.point[2] || 0});
- app.registerRenderSubview(this, zCoord, "z-coord-" + this.viewIndex);
- },
- renderProperties: function () {
- if(this.massView) {
- this.massView.remove();
- }
- if(this.volView) {
- this.volView.remove();
- }
- if(this.nuView) {
- this.nuView.remove();
- }
- this.massView = new InputView({parent: this, required: true,
- name: 'mass', valueType: 'number',
- value: this.model.mass || this.type.mass});
- app.registerRenderSubview(this, this.massView, "mass-" + this.viewIndex);
- this.volView = new InputView({parent: this, required: true,
- name: 'volume', valueType: 'number',
- value: this.model.volume || this.type.volume});
- app.registerRenderSubview(this, this.volView, "volume-" + this.viewIndex);
- this.nuView = new InputView({parent: this, required: true,
- name: 'viscosity', valueType: 'number',
- value: this.model.nu || this.type.nu});
- app.registerRenderSubview(this, this.nuView, "nu-" + this.viewIndex);
- let fixed = this.model.fixed || this.type.fixed;
- $(this.queryByHook("fixed-" + this.viewIndex)).prop("checked", fixed);
- },
- renderType: function () {
- var typeView = new SelectView({
- label: '',
- name: 'type',
- required: true,
- idAttribute: 'typeID',
- textAttribute: 'name',
- eagerValidate: true,
- options: this.parent.domain.types,
- value: this.parent.domain.types.get(this.model.type, "typeID")
- });
- app.registerRenderSubview(this, typeView, "type-" + this.viewIndex)
- },
- startAction: function (action) {
- $(this.queryByHook("ep-complete")).css("display", "none");
- $(this.queryByHook("ep-action-in-progress")).text(action);
- $(this.queryByHook("ep-in-progress")).css("display", "inline-block");
- console.log(action)
- },
- update: function (e) {},
- updateValid: function (e) {}
-});
\ No newline at end of file
diff --git a/client/views/input.js b/client/views/input.js
index e4531e0732..3e2ba786cc 100644
--- a/client/views/input.js
+++ b/client/views/input.js
@@ -24,6 +24,7 @@ module.exports = AmpersandInputView.extend({
label: 'string',
modelKey: 'string',
valueType: 'string',
+ disabled: 'boolean'
},
events: {
'input input' : 'changeInputHandler',
@@ -32,11 +33,12 @@ module.exports = AmpersandInputView.extend({
AmpersandInputView.prototype.initialize.apply(this, arguments);
},
render: function () {
+ let disabled = this.disabled ? "disabled" : "";
if(this.label) {
this.template = [
'