/**
* $3Dmol.UI - UI creates panels in the viewer to assist control of the viewport
* @constructor
* @param {$3Dmol.StateManager} stateManager StateManager is required to have interaction between glviewer and the ui.
* @param {Object} config Loads the user defined parameters to generate the ui
* @param {Object} parentElement Refers the parent division used to hold the canvas for 3Dmol.js
*/
$3Dmol.UI = (function () {
/*
The dictionaries are for dropdown menus and validation of the viewer
*/
// prop : It is used to add the option for property in context menu in the 3dmol ui
// the code for prop can be found under /ui/ui.js -> UI -> ContextMenu -> setProperties -> submit.ui.on
// gui : It is used to generate forms for different features in the 3dmol ui
// the code for gui can be found under /ui/form.js -> Form (Form defination)
// floatType : separates integer from float since these are used in
// input validation of the 3dmol ui
var validAtomSpecs = {
"resn": { type: "string", valid: true, prop: true, gui: true }, // Parent residue name
"x": { type: "number", floatType: true, valid: false, step: 0.1, prop: true }, // Atom's x coordinate
"y": { type: "number", floatType: true, valid: false, step: 0.1, prop: true }, // Atom's y coordinate
"z": { type: "number", floatType: true, valid: false, step: 0.1, prop: true }, // Atom's z coordinate
"color": { type: "color", gui: false }, // Atom's color, as hex code
"surfaceColor": { type: "color", gui: false }, // Hex code for color to be used for surface patch over this atom
"elem": { type: "element", gui: true, prop: true }, // Element abbreviation (e.g. 'H', 'Ca', etc)
"hetflag": { type: "boolean", valid: false, gui: true }, // Set to true if atom is a heteroatom
"chain": { type: "string", gui: true, prop: true }, // Chain this atom belongs to, if specified in input file (e.g 'A' for chain A)
"resi": { type: "array_range", gui: true }, // Residue number
"icode": { type: "number", valid: false, step: 0.1 },
"rescode": { type: "number", valid: false, step: 0.1, prop: true },
"serial": { type: "number", valid: false, step: 0.1 }, // Atom's serial id numbermodels
"atom": { type: "string", valid: false, gui: true, prop: true }, // Atom name; may be more specific than 'elem' (e.g 'CA' for alpha carbon)
"bonds": { type: "array", valid: false }, // Array of atom ids this atom is bonded to
"ss": { type: "string", valid: false }, // Secondary structure identifier (for cartoon render; e.g. 'h' for helix)
"singleBonds": { type: "boolean", valid: false }, // true if this atom forms only single bonds or no bonds at all
"bondOrder": { type: "array", valid: false }, // Array of this atom's bond orders, corresponding to bonds identfied by 'bonds'
"properties": { type: "properties", valid: false }, // Optional mapping of additional properties
"b": { type: "number", floatType: true, valid: false, step: 0.1, prop: true }, // Atom b factor data
"pdbline": { type: "string", valid: false }, // If applicable, this atom's record entry from the input PDB file (used to output new PDB from models)
"clickable": { type: "boolean", valid: false, gui: false }, // Set this flag to true to enable click selection handling for this atom
"contextMenuEnabled": { type: "boolean", valid: false, gui: false }, // Set this flag to true to enable click selection handling for this atom
"callback": { type: "function", valid: false }, // Callback click handler function to be executed on this atom and its parent viewer
"invert": { type: "boolean", valid: false }, // for selection, inverts the meaning of the selection
//unsure about this
"reflectivity": { type: "number", floatType: true, gui: false, step: 0.1 }, //for describing the reflectivity of a model
"altLoc": { type: "invalid", valid: false }, //alternative location, e.g. in PDB
"sym": { type: 'number', gui: false }, //which symmetry
};
//type is irrelivent here becuase htey are are invalid
var validExtras = { // valid atom specs are ok too
"model": { type: "string", valid: false }, // a single model or list of models from which atoms should be selected
"bonds": { type: "number", valid: false, gui: true }, // overloaded to select number of bonds, e.g. {bonds: 0} will select all nonbonded atoms
"predicate": { type: "string", valid: false }, // user supplied function that gets passed an {AtomSpec} and should return true if the atom should be selected
"invert": { type: "boolean", valid: false, gui: true }, // if set, inverts the meaning of the selection
"byres": { type: "boolean", valid: false, gui: true }, // if set, expands the selection to include all atoms of any residue that has any atom selected
"expand": { type: "number", valid: false, gui: false }, // expands the selection to include all atoms within a given distance from the selection
"within": { type: "string", valid: false }, // intersects the selection with the set of atoms within a given distance from another selection
"and": { type: "string", valid: false }, // and boolean logic
"or": { type: "string", valid: false }, // or boolean logic
"not": { type: "string", valid: false }, // not boolean logic
};
var validAtomSelectionSpecs = $3Dmol.extend({}, validAtomSpecs);
validAtomSelectionSpecs = $3Dmol.extend(validAtomSelectionSpecs, validExtras);
var validLineSpec = {
"hidden": { type: "boolean", gui: true },
"linewidth": { type: "number", floatType: true, gui: true, step: 0.1, default: 1.0 },
"colorscheme": { type: "colorscheme", gui: true },
"color": { type: "color", gui: true },
"opacity": { type: "number", floatType: true, gui: true, step: 0.1, default: 1, min: 0, max: 1 },
};
var validCrossSpec = {
"hidden": { type: "boolean", gui: true },
"linewidth": { type: "number", floatType: true, gui: false, step: 0.1, default: 1.0, min: 0 },//deprecated
"colorscheme": { type: "colorscheme", gui: true },
"color": { type: "color", gui: true },
"radius": { type: "number", floatType: true, gui: true, step: 0.1, default: 1, min: 0.1 },
"scale": { type: "number", floatType: true, gui: true, step: 0.1, default: 1, min: 0 },
"opacity": { type: "number", floatType: true, gui: true, step: 0.1, default: 1, min: 0, max: 1 },
};
var validStickSpec = {
"hidden": { type: "boolean", gui: true },
"colorscheme": { type: "colorscheme", gui: true },
"color": { type: "color", gui: true },
"radius": { type: "number", floatType: true, gui: true, step: 0.1, default: 0.25, min: 0.1 },
"singleBonds": { type: "boolean", gui: true },
"opacity": { type: "number", floatType: true, gui: true, step: 0.1, default: 1, min: 0, max: 1 },
};
var validSphereSpec = {
"hidden": { type: "boolean", gui: false }, // needed in the new gui it has separate function to hide the spheres
"singleBonds": { type: "boolean", gui: true },
"colorscheme": { type: "colorscheme", gui: true },
"color": { type: "color", gui: true },
"radius": { type: "number", floatType: true, gui: true, step: 0.1, default: 1.5, min: 0 },
"scale": { type: "number", floatType: true, gui: true, step: 0.1, default: 1.0, min: 0.1 },
"opacity": { type: "number", floatType: true, gui: true, step: 0.1, default: 1, min: 0, max: 1 },
};
var validCartoonSpec = {
"style": { validItems: ["trace", "oval", "rectangle", "parabola", "edged"], gui: true },
"color": { type: "color", gui: true, spectrum: true },
"arrows": { type: "boolean", gui: true },
"ribbon": { type: "boolean", gui: true },
"hidden": { type: "boolean", gui: true },
"tubes": { type: "boolean", gui: true },
"thickness": { type: "number", floatType: true, gui: true, step: 0.1, default: 1, min: 0 },
"width": { type: "number", floatType: true, gui: true, step: 0.1, default: 1, min: 0 },
"opacity": { type: "number", floatType: true, gui: true, step: 0.1, default: 1, min: 0, max: 1 },
};
var validAtomStyleSpecs = {
"line": { validItems: validLineSpec, type: "form", gui: true }, // draw bonds as lines
"cross": { validItems: validCrossSpec, type: "form", gui: true }, // draw atoms as crossed lines (aka stars)
"stick": { validItems: validStickSpec, type: "form", gui: true }, // draw bonds as capped cylinders
"sphere": { validItems: validSphereSpec, type: "form", gui: true }, // draw atoms as spheres
"cartoon": { validItems: validCartoonSpec, type: "form", gui: true }, // draw cartoon representation of secondary structure
"colorfunc": { validItems: null, type: "js", valid: false },
"clicksphere": { validItems: validSphereSpec, type: "form" } //invisible style for click handling
};
var validSurfaceSpecs = {
"opacity": { type: "number", floatType: true, gui: true, step: 0.01, default: 1, min: 0, max: 1 },
"colorscheme": { type: "colorscheme", gui: true },
"color": { type: "color", gui: true },
"voldata": { type: "number", floatType: true, gui: false },
"volscheme": { type: "number", floatType: true, gui: false },
"map": { type: "number", gui: false }
};
function UI(stateManager, config, parentElement) {
config = config || {}
// Extract the viewer and then render it
var icons = new $3Dmol.UI.Icons();
var _editingForm = null;
var mainParent = $(parentElement[0]);
// Generates the necessary UI elements
var HEIGHT = config.height;
this.tools = generateUI(config);
/**
* Creates all the jquery object of different UI features
*/
function generateUI() {
var modelToolBar = new ModelToolbar();
mainParent.append(modelToolBar.ui);
setLocation(mainParent, modelToolBar.ui, 'left', 'top');
// modelToolBar.updateInputLength();
var contextMenu = new ContextMenu();
mainParent.append(contextMenu.ui);
setPosition(contextMenu.ui, 100, 100)
var surfaceMenu = new SurfaceMenu();
mainParent.append(surfaceMenu.ui);
setLocation(mainParent, surfaceMenu.ui, 'right', 'top', 0, modelToolBar.ui.height() + 5);
var selectionBox = new SelectionBox(icons.select);
mainParent.append(selectionBox.ui);
setLocation(mainParent, selectionBox.ui, 'left', 'top', 0, modelToolBar.ui.height() + 5);
// Fixing Context Menu Behaviour
selectionBox.ui.on('mousedown', () => {
stateManager.exitContextMenu();
});
surfaceMenu.ui.on('mousedown', () => {
stateManager.exitContextMenu();
});
return {
modelToolBar: modelToolBar,
selectionBox: selectionBox,
contextMenu: contextMenu,
surfaceMenu: surfaceMenu
}
}
/**
* Resize the panel with respect to the new viewport
*
* @function $3Dmol.UI#resize
*/
this.resize = function () {
var selectionBox = this.tools.selectionBox;
var surfaceMenu = this.tools.surfaceMenu;
var modelToolBar = this.tools.modelToolBar;
var HEIGHT = mainParent.height();
setLocation(mainParent, modelToolBar.ui, 'left', 'top');
// modelToolBar.updateInputLength();
setLocation(mainParent, selectionBox.ui, 'left', 'top', 0, modelToolBar.ui.height() + 5);
selectionBox.updateScrollBox(HEIGHT);
setLocation(mainParent, surfaceMenu.ui, 'right', 'top', 0, modelToolBar.ui.height() + 5);
surfaceMenu.updateScrollBox(HEIGHT);
}
/*
* ModelToolbar is part of $3Dmol.UI to edit or change the model loaded into the viewer
*
* @function ModelToolbar
*/
function ModelToolbar() {
var boundingBox = this.ui = $('<div></div>');
boundingBox.css({
'position': 'relative',
'min-width': '150px'
});
var modelButton = new button(icons.molecule, 20, { tooltip: 'Toggle Model Selection Bar' });
boundingBox.append(modelButton.ui);
modelButton.ui.css({
'display': 'inline-block',
'top': '3px',
});
var control = {
urlType: {
active: true,
value: null,
key: 'Model type'
},
url: {
active: true,
value: null,
key: 'Url'
},
};
var surroundingBox = $('<div></div>');
surroundingBox.css({
'display': 'inline-block',
'background': '#e4e4e4',
'padding': '2px',
'border-radius': '3px',
// 'width' : '90%'
});
boundingBox.append(surroundingBox);
var currentModelBox = $('<div></div>');
currentModelBox.css({
});
var currentModel = $('<div></div>');
currentModel.css({
'display': 'inline-block',
'font-family': 'Arial',
'font-size': '12px',
'font-weight': 'bold',
// 'padding' : '3px'
});
currentModelBox.append(currentModel);
var changeButton = new button(icons.change, 16, { tooltip: 'Change Model', backgroundColor: 'white', bfr: 0.5 });
changeButton.ui.css({
'display': 'inline-block',
'margin-left': '4px',
});
currentModelBox.append(changeButton.ui);
currentModelBox.hide();
surroundingBox.append(currentModelBox);
var formBox = $('<div></div>');
surroundingBox.append(formBox);
var dbs = 'pdb,mmtf,cid'.split(',');
var list = this.list = new $3Dmol.UI.Form.ListInput(control.urlType, dbs);
list.showAlertBox = false;
list.ui.css({
'display': 'inline-block',
})
formBox.append(list.ui);
var input = this.url = new $3Dmol.UI.Form.Input(control.url);
formBox.append(input.ui);
input.ui.css({
'display': 'inline-block',
'width': '125px'
});
// input.setWidth(125);
var submitButton = new button(icons.tick, 16, { bfr: 0.5, backgroundColor: 'lightgreen', tooltip: 'Add Model' });
submitButton.ui.css({
'margin': '0px'
})
formBox.append(submitButton.ui);
this.updateInputLength = function () {
// var width = parentElement.width()*0.3;
// boundingBox.width(width);
// input.setWidth(width - 12);
}
modelButton.ui.on('click', () => {
surroundingBox.toggle();
});
submitButton.ui.on('click', function () {
var validateDb = list.validate();
var validateId = input.validate();
if (validateId && validateDb) {
stateManager.addModel(control);
}
});
/*
* Sets the title in the ui with specified value
*
* @function ModelToolbar#setModel
* @param {String} heading Name of the molecule that is to be displayed on the title
*/
this.setModel = function (heading) {
currentModel.text(heading);
currentModelBox.show();
formBox.hide();
}
changeButton.ui.on('click', function () {
currentModelBox.hide();
formBox.show();
input.setValue('');
});
boundingBox.on('keypress', function (e) {
if (e.key == 'Enter' || e.key == 'Return') {
submitButton.ui.trigger('click')
}
});
}
/*
* Selection box creates the UI panel to manipulate selections and style that are drawn
* on the viewport
*
* @function SelectionBox
* @param {$3Dmol.UI.Icons} icon takes the svg code for the icon that is to be used to display
* selection box
* @return {Object} Jquery element of div
*/
function SelectionBox(icon, side = 'left') {
var selectionBox = this.ui = $('<div></div>');
_editingForm = false;
var selectionObjects = [];
var selections = $('<div></div>');
var scrollBox = $('<div></div>');
selections.css('opacity', '0.9');
var showArea = $('<div></div>');
var addArea = $('<div></div>');
var plusButton = new button(icons.plus, 20, { tooltip: 'Add New Selection' });
plusButton.ui.css('margin', '0px');
var hideButton = new button(icon, 20, { tooltip: 'Toggle Selection Menu' });
this.selectionObjects = [];
// Content
selectionBox.append(hideButton.ui);
selectionBox.append(showArea);
selectionBox.css('position', 'absolute');
scrollBox.append(selections);
showArea.append(scrollBox);
addArea.append(plusButton.ui);
var alertBox = new AlertBox();
showArea.append(alertBox.ui);
showArea.append(addArea);
alertBox.ui.css('width', 162);
// CSS
if (side == 'left') {
selectionBox.css('text-align', 'left');
}
else if (side == 'right') {
selectionBox.css('text-align', 'right');
}
else {
// Add alert box code
selectionBox.css('text-align', 'right');
}
showArea.css('box-sizing', 'border-box');
showArea.css('padding', '3px');
// showArea.css('width', '162px');
scrollBox.css('max-height', HEIGHT * 0.8);
scrollBox.css('overflow-y', 'auto');
scrollBox.css('overflow-x', 'visible');
selections.css('box-sizing', 'content-box');
this.updateScrollBox = function (height) {
scrollBox.css('max-height', height * 0.8);
}
// Action
var hidden = true;
showArea.hide();
hideButton.ui.click(toggleHide);
function toggleHide() {
if (hidden) {
showArea.show(100);
}
else {
showArea.hide(100);
}
hidden = !hidden;
}
/*
* Card for manipulation of a selection form and related styles
*
* @function Selection
*/
function Selection() {
var boundingBox = this.ui = $('<div></div>');
var sid = this.id = null;
selectionObjects.push(this);
boundingBox.css({
'background': '#e8e8e8',
'padding': '4px 4px 2px 4px',
'border-radius': '6px',
'margin-bottom': '3px',
'position': 'relative',
'width': '156px'
});
var header = $('<div></div>');
boundingBox.append(header);
var heading = $('<div></div>');
var controls = $('<div></div>');
header.append(heading, controls);
heading.css({
'font-family': 'Arial',
'font-weight': 'bold',
'font-size': '12px',
'display': 'inline-block',
'width': '60px'
});
controls.css({
'display': 'inline-block'
});
header.hide();
controls.editMode = false;
var removeButton = new button(icons.minus, 16, { bfr: 0.5, backgroundColor: '#f06f6f', tooltip: 'Remove Selection' });
var editButton = new button(icons.pencil, 16, { tooltip: 'Edit Selection' });
var visibleButton = new button(icons.visible, 16, { tooltip: 'Show / Hide Selection' });
controls.append(removeButton.ui)
controls.append(editButton.ui);
controls.append(visibleButton.ui);
var parameters = $('<div></div>');
boundingBox.append(parameters);
var styleHolder = $('<div></div>');
removeButton.ui.on('click', function () {
stateManager.removeSelection(sid);
boundingBox.detach();
//delete this;
});
editButton.ui.on('click', function () {
parameters.toggle();
});
var hidden = false;
visibleButton.ui.on('click', () => {
stateManager.toggleHide(sid);
if (hidden) {
hidden = false;
visibleButton.setSVG(icons.visible);
}
else {
hidden = true;
visibleButton.setSVG(icons.invisible);
}
});
var styleBox = new StyleBox();
styleHolder.append(styleBox.ui);
styleBox.ui.css({
'position': 'static',
// 'left' : '0',
'width': 'px',
'border-radius': '4px'
});
styleBox.ui.hide();
var allControl = this.allSelector = {
key: 'Select All Atom',
value: null,
active: true
}
var allCheckBox = new $3Dmol.UI.Form.Checkbox(allControl);
parameters.append(allCheckBox.ui);
var selectionFormControl = this.selectionValue = {
key: 'Selection Spec',
value: null,
active: true
}
var selectionSpecForm = new $3Dmol.UI.Form(validAtomSelectionSpecs, selectionFormControl);
parameters.append(selectionSpecForm.ui);
var submitControls = $('<div></div>');
var submit = new button(icons.tick, 16, { backgroundColor: 'lightgreen', tooltip: 'Submit' });
var cancel = new button(icons.cross, 16, { backgroundColor: 'lightcoral', tooltip: 'Cancel' });
submitControls.append(submit.ui, cancel.ui);
var alertBox = new AlertBox();
parameters.append(alertBox.ui);
parameters.append(submitControls);
boundingBox.append(styleHolder);
allCheckBox.update = function () {
selectionSpecForm.ui.toggle();
}
function finalizeSelection(id) {
header.show();
controls.editMode = true;
sid = this.id = id;
heading.text('Sel#' + id);
boundingBox.attr('data-id', id);
parameters.hide();
styleBox.setSid(id);
styleBox.ui.show();
}
function checkAndAddSelection(sid = null) {
var validate = selectionSpecForm.validate();
if (validate) {
selectionSpecForm.getValue();
var checkAtoms = stateManager.checkAtoms(selectionFormControl.value);
if (Object.keys(selectionFormControl.value).length == 0) {
alertBox.error('Please enter some input');
}
else {
if (checkAtoms) {
var id = stateManager.addSelection(selectionFormControl.value, sid);
finalizeSelection(id);
if (sid == null) _editingForm = false;
}
else {
alertBox.error('No atom selected');
}
}
}
else {
alertBox.error('Invalid Input');
}
}
function removeSelf() {
// delete selectionToRemove;
}
submit.ui.on('click', () => {
if (controls.editMode == false) {
if (allControl.value) {
let id = stateManager.addSelection({});
finalizeSelection(id);
_editingForm = false;
}
else {
checkAndAddSelection();
}
}
else {
if (allControl.value) {
let id = sid;
stateManager.addSelection({}, id);
finalizeSelection(id);
}
else {
let id = sid;
checkAndAddSelection(id);
}
}
});
var self = this;
cancel.ui.on('click', () => {
if (controls.editMode) {
parameters.hide();
}
else {
boundingBox.detach();
removeSelf(self);
_editingForm = false;
}
});
boundingBox.on('keyup', (e) => {
if (e.key == 'Enter') {
submit.ui.trigger('click');
}
});
/*
* @function Selection#setProperty
* @param {string} id Id of the selection created in StateManager
* @param {Object} specs Defination of the selection that will be used to set default
* values in the form
*/
this.setProperty = function (id, specs) {
// check for all selection
if (Object.keys(specs).length == 0) {
allCheckBox.setValue(true)
} else {
selectionSpecForm.setValue(specs);
}
// finalize the selection
finalizeSelection(id);
}
/*
* Adds style to the given selection
*
* @function Selection#addStyle
* @param {String} selId Id of the selection to inititate the StyleBox
* @param {String} styleId Id of the style that is created through StateManager
* @param {AtomStyleSpecs} styleSpecs
*/
this.addStyle = function (selId, styleId, styleSpecs) {
styleBox.addStyle(selId, styleId, styleSpecs);
}
}
plusButton.ui.on('click', () => {
if (!_editingForm) {
var newSelection = new Selection();
selections.append(newSelection.ui);
_editingForm = true;
} else {
alertBox.warning('Please complete the previous form');
}
});
/*
* Remove all the selection card from the ui
*/
this.empty = function () {
selections.empty();
_editingForm = false;
}
/*
* Adds or create new selection card
*
* @function SelectionBox#editSelection
* @param {String} id Id created in StateManager and passed down to this function during call
* @param {AtomSelectionSpec} selSpec Selection spec that is used to generate the selection form
* @param {String} styleId Id of style created in StateManager
* @param {AtomStyleSpecs} styleSpec Style spec if specified add the selection to the current selection
*/
this.editSelection = function (id, selSpec, styleId, styleSpec) {
// if selection does not exist create new
// This thing works but I am not sure how!
// Search selection with id
var selectionUI = selections.children('[data-id=' + id + ']');
if (selectionUI.length == 0) {
var selection = new Selection();
selection.setProperty(id, selSpec);
selections.append(selection.ui);
if (styleId != null) {
selection.addStyle(id, styleId, styleSpec);
}
}
}
}
/*
* Creates StyleBox for listing out different styles inside the selection
*
* @function StyleBox
* @param {String} selId Id of the selection for which the style box is created
* @param {String} side Alignment of text inside the box
*/
function StyleBox(selId, side = 'left') {
var styleBox = this.ui = $('<div></div>');
_editingForm = false;
var sid = this.sid = selId; // selection id
this.setSid = function (id) {
sid = this.sid = id;
}
var styles = $('<div></div>');
var scrollBox = $('<div></div>');
styles.css('opacity', '0.9');
var showArea = $('<div></div>');
var addArea = $('<div></div>');
addArea.css('text-align', 'center');
var plusButton = new button(icons.plus, 20, { tooltip: 'Add New Style' });
plusButton.ui.css('margin', '0px');
this.selectionObjects = [];
// Content
styleBox.append(showArea);
styleBox.css('position', 'absolute');
scrollBox.append(styles);
showArea.append(scrollBox);
var alertBox = new AlertBox();
showArea.append(alertBox.ui);
addArea.append(plusButton.ui);
showArea.append(addArea);
// CSS
if (side == 'left') {
styleBox.css('text-align', 'left');
}
else if (side == 'right') {
styleBox.css('text-align', 'right');
}
else {
// Add alert box code
styleBox.css('text-align', 'right');
}
showArea.css('box-sizing', 'border-box');
showArea.css('padding', '3px');
// showArea.css('width', '162px');
showArea.css('background-color', '#a4a4a4')
showArea.css('border-radius', '4px');
// scrollBox.css('max-height', HEIGHT*0.8);
scrollBox.css('overflow', 'hidden');
// styles.css('max-height', HEIGHT*0.8);
// styles.css('overflow', 'auto');
styles.css('box-sizing', 'content-box');
/*
* Style card to define the value of the style
*
* @param {string} sid Id of the selction for which the style box is created
* and this stye will be added under that selection
*/
function Style(sid) {
var boundingBox = this.ui = $('<div></div>');
var stid = this.id = null; // style id
boundingBox.css({
'background': '#e8e8e8',
'padding': '4px 4px 2px 4px',
'border-radius': '6px',
'margin-bottom': '3px',
'position': 'relative'
});
var header = $('<div></div>');
boundingBox.append(header);
var heading = $('<div></div>');
var controls = $('<div></div>');
header.append(heading, controls);
heading.css({
'font-family': 'Arial',
'font-weight': 'bold',
'font-size': '12px',
'display': 'inline-block',
'width': '60px'
});
controls.css({
'display': 'inline-block'
});
header.hide();
controls.editMode = false;
var removeButton = new button(icons.minus, 16, { bfr: 0.5, backgroundColor: '#f06f6f', tooltip: 'Remove Style' });
var editButton = new button(icons.pencil, 16, { tooltip: 'Edit Style' });
var visibleButton = new button(icons.visible, 16, { tooltip: 'Show / Hide Style' });
controls.append(removeButton.ui)
controls.append(editButton.ui);
controls.append(visibleButton.ui);
var parameters = $('<div></div>');
boundingBox.append(parameters);
removeButton.ui.on('click', { parent: this, stid: stid }, function () {
stateManager.removeStyle(sid, stid);
boundingBox.detach();
//delete this;
});
editButton.ui.on('click', function () {
parameters.toggle();
});
var hidden = false;
visibleButton.ui.on('click', () => {
stateManager.toggleHideStyle(sid, stid);
if (hidden) {
hidden = false;
visibleButton.setSVG(icons.visible);
}
else {
hidden = true;
visibleButton.setSVG(icons.invisible);
}
});
var styleFormControl = this.selectionValue = {
key: 'Style Spec',
value: null,
active: true
}
var styleSpecForm = new $3Dmol.UI.Form(validAtomStyleSpecs, styleFormControl);
parameters.append(styleSpecForm.ui);
var submitControls = $('<div></div>');
var submit = new button(icons.tick, 16, { backgroundColor: 'lightgreen', tooltip: 'Submit' });
var cancel = new button(icons.cross, 16, { backgroundColor: 'lightcoral', tooltip: 'Cancel' });
submitControls.append(submit.ui, cancel.ui);
var alertBox = new AlertBox();
parameters.append(alertBox.ui);
parameters.append(submitControls);
function finalizeStyle(id) {
header.show();
controls.editMode = true;
stid = id;
heading.text('Sty#' + id);
parameters.hide();
}
function checkAndAddStyle(stid = null) {
var validate = styleSpecForm.validate();
if (validate) {
styleSpecForm.getValue();
if (Object.keys(styleFormControl.value).length == 0) {
alertBox.error('Please enter some value');
}
else {
var id = stateManager.addStyle(styleFormControl.value, sid, stid);
finalizeStyle(id);
if (stid == null) _editingForm = false;
}
}
else {
alertBox.error('Invalid Input');
}
}
submit.ui.on('click', () => {
if (controls.editMode == false) {
checkAndAddStyle();
}
else {
var id = stid
styleSpecForm.getValue();
if (Object.keys(styleFormControl.value).length == 0) {
alertBox.error('Please enter some value');
}
else {
checkAndAddStyle(id);
}
}
});
cancel.ui.on('click', () => {
if (controls.editMode) {
parameters.hide();
}
else {
boundingBox.detach();
//delete this;
}
});
boundingBox.on('keyup', (e) => {
if (e.key == 'Enter') {
submit.ui.trigger('click');
}
});
/**
* @function Style#updateStyle
* @param {String} styleId Id of the style created by StateManager
* @param {AtomStyleSpecs} styleSpec Specs for defining the style and setting default values
*/
this.updateStyle = function (styleId, styleSpec) {
styleSpecForm.setValue(styleSpec);
finalizeStyle(styleId);
}
}
plusButton.ui.on('click', () => {
if (!_editingForm) {
var newStyle = new Style(sid);
styles.append(newStyle.ui);
_editingForm = true;
}
else {
alertBox.warning('Please complete editing the current form');
}
});
/**
* @function StyleBox#addStyle
* @param {String} selectionId Id of the selection for which styles will be created
* @param {String} styleId Id of the style part of the selection
* @param {AtomStyleSpecs} styleSpecs Style specs that will be used to create
* style for the specified selection and set default values in the Style card
*/
this.addStyle = function (selectionId, styleId, styleSpecs) {
var style = new Style(selectionId);
styles.append(style.ui);
style.updateStyle(styleId, styleSpecs);
}
}
/*
* Add alert messages to different panels
*
* @function AlertBox
* @param {Object} config Configuraiton for alert box display
*/
function AlertBox(config) {
var boundingBox = this.ui = $('<div></div>');
config = config || {}
var delay = config.delay || 5000;
var autohide = (config.autohide == undefined) ? true : config.autohide;
boundingBox.css({
'font-family': 'Arial',
'font-size': '14px',
'padding': '3px',
'border-radius': '4px',
'margin-top': '2px',
'margin-bottm': '2px',
'font-weight': 'bold',
'text-align': 'center',
});
boundingBox.hide();
function hide() {
if (autohide) {
setTimeout(() => {
boundingBox.hide();
}, delay);
}
}
/**
* Generate Internal alert message
* @param {String} msg Error Message
*/
this.error = function (msg) {
boundingBox.css({
'background': 'lightcoral',
'color': 'darkred',
'border': '1px solid darkred'
});
boundingBox.text(msg);
boundingBox.show();
hide();
}
/**
* Generates Internal warning message
* @param {String} msg Warming message
*/
this.warning = function (msg) {
boundingBox.css({
'background': '#fff3cd',
'color': '#856409',
'border': '1px solid #856409'
});
boundingBox.text(msg);
boundingBox.show();
hide();
}
/**
* Generates Internal Info message
* @param {String} msg Info message
*/
this.message = function (msg) {
boundingBox.css({
'background': 'lightgreen',
'color': 'green',
'border': '1px solid green'
});
boundingBox.text(msg);
boundingBox.show();
hide();
}
}
/*
* Creates the panel for manipulation of labels on the viewport
*
* @function ContextMenu
*/
function ContextMenu() {
var boundingBox = this.ui = $('<div></div>');
boundingBox.css('position', 'absolute');
// boundingBox.css('border', '1px solid black');
boundingBox.css('border-radius', '3px');
boundingBox.css('background', '#f1f1f1');
boundingBox.css('z-index', 99);
var contentBox = $('<div></div>');
contentBox.css('position', 'relative');
boundingBox.css('opacity', '0.85');
boundingBox.append(contentBox);
contentBox.css({
'background': '#f1f1f1',
'border-radius': '4px',
'padding': '4px',
'width': '140px'
});
// Context Box
// Remove Label Button
var labelMenuStyle = {
'background': '#d3e2ee',
'padding': '2px',
'font-family': 'Arial',
'font-weight': 'bold',
'font-size': '12px',
'border-radius': '2px',
// 'margin-top':'3px'
}
var removeLabelMenu = $('<div></div>');
removeLabelMenu.text('Remove Label');
removeLabelMenu.css(labelMenuStyle);
removeLabelMenu.css('margin-bottom', '3px');
contentBox.append(removeLabelMenu);
removeLabelMenu.hide();
// Label Property List
var propertyKeys = Object.keys(validAtomSpecs);
var propertyList = [];
var propertyObjectList = [];
propertyKeys.forEach((prop) => {
var propObj = validAtomSpecs;
if (propObj[prop].prop === true) {
propertyList.push(prop);
}
});
// Property Menu
var propertyMenu = $('<div></div>');
contentBox.append(propertyMenu);
/*
* Property object used in property menu
*
* @function Property
* @param {String} key Name of the atom property
* @param {*} value Value of the property
*/
function Property(key, value) {
this.row = $('<tr></tr>');
var propLabelValue = this.control = {
key: '',
value: null,
active: true,
name: key,
}
this.key = key;
this.value = value;
var checkbox = new $3Dmol.UI.Form.Checkbox(propLabelValue);
var checkboxHolder = $('<td></td>');
checkboxHolder.append(checkbox.ui);
var keyHolder = $('<td></td>');
var separatorHolder = $('<td></td>').text(':');
var valueHolder = $('<td></td>');
this.row.append(checkboxHolder, keyHolder, separatorHolder, valueHolder);
keyHolder.text(key);
if (typeof (value) == "number") {
valueHolder.text(value.toFixed(2));
} else {
valueHolder.text(value.replace(/\^/g, ''));
}
}
/*
* @param {AtomSpec} atom Value of different property of the atom, if the atom has prop : true
* then that option is made visible in the context menu
*/
function setProperties(atom) {
propertyMenu.empty();
propertyObjectList = [];
var propertyTable = $('<table></table>');
propertyList.forEach((prop) => {
var propObj = new Property(prop, atom[prop]);
propertyTable.append(propObj.row);
propertyObjectList.push(propObj);
});
propertyMenu.append(propertyTable);
var labelStyleHolder = $('<div><div>');
var labelStyle = $('<div><div>');
labelStyle.text('Style');
labelStyle.css({
'display': 'inline-block',
'font-family': 'Arial',
'font-size': '14px',
'margin-right': '6px',
'margin-left': '6px'
});
var stylesForLabel = new $3Dmol.UI.Form.ListInput(labelStyle, Object.keys($3Dmol.labelStyles));
stylesForLabel.ui.css({
'display': 'inline-block'
});
stylesForLabel.setValue('milk');
labelStyleHolder.append(labelStyle, stylesForLabel.ui);
propertyMenu.append(labelStyleHolder);
var submit = new button(icons.tick, 18, { backgroundColor: 'lightgreen', tooltip: 'Submit' });
var cancel = new button(icons.cross, 18, { backgroundColor: 'lightcoral', tooltip: 'Cancel' });
var controlButtons = $('<div></div>');
controlButtons.append(submit.ui, cancel.ui);
// controlButtons.css('text-align', 'center');
var alertBox = new AlertBox();
propertyMenu.append(alertBox.ui);
propertyMenu.append(controlButtons);
submit.ui.on('click', () => {
var props = processPropertyList();
var labelStyleValidation = stylesForLabel.validate();
if (props != null) {
if (labelStyleValidation) {
stateManager.addAtomLabel(props, atom, stylesForLabel.getValue().value);
stateManager.exitContextMenu(false);
}
else {
alertBox.error('Select style for label');
}
}
else {
alertBox.error('No value selected for label');
}
});
cancel.ui.on('click', () => {
stateManager.exitContextMenu();
});
}
// Previous Labels
var labelHolder = $('<div></div>');
contentBox.append(labelHolder);
// Add Menu
var addMenu = $('<div></div>');
contentBox.append(addMenu);
addMenu.css('width', '100%');
var addLabelMenu = $('<div></div>');
addMenu.append(addLabelMenu);
addLabelMenu.text('Add Label');
addLabelMenu.css(labelMenuStyle);
addLabelMenu.css('margin-bottom', '3px');
addLabelMenu.hide();
// Edit Menu
var editMenu = $('<div></div>');
contentBox.append(editMenu);
contentBox.css({
'position': 'relative',
});
editMenu.css({
'background': '#dfdfdf',
'border-radius': '3px',
'font-family': 'Arial',
'font-weight': 'bold',
'font-size': '12px',
// 'position': 'absolute',
// 'left' : '105%',
// 'top' : '0',,
'box-sizing': 'border-box',
'width': '100%',
});
editMenu.hide();
var alertBox = new AlertBox({ autohide: false });
contentBox.append(alertBox.ui);
// Add Label Inputs
/*
* Generate input elements that are used as form values in the context menu under addLabelForm
* @returns {Object} that holds different input elements
*/
function generateAddLabelForm() {
var addLabelForm = $('<div></div>');
var addLabelValue = {
text: {
key: 'Label Text',
value: null,
active: true,
},
style: {
key: 'Style',
value: null,
active: true,
},
sel: {
key: 'Selection',
value: null,
active: true,
}
}
var formModifierControl = $('<div></div>');
var removeButton = new button(icons.minus, 16);
var tick = new button(icons.tick, 16, { backgroundColor: 'lightgreen', tooltip: 'Submit' });
var cross = new button(icons.cross, 16, { backgroundColor: 'lightcoral', tooltip: 'Cancel' });
formModifierControl.append(removeButton.ui, tick.ui, cross.ui);
removeButton.ui.hide();
addLabelForm.append(formModifierControl);
var addLabelTextBox = $('<div></div>');
var lt = $('<div></div>').text('Label Text');
var addLabelTextInput = new $3Dmol.UI.Form.Input(addLabelValue.text);
addLabelTextBox.append(lt, addLabelTextInput.ui);
var width = 126//editMenu.innerWidth()*0.8;
addLabelTextInput.setWidth(width);
addLabelForm.append(addLabelTextBox);
var addLabelStyleBox = $('<div></div>');
var ls = $('<div></div>').text('Label Style');
var addLabelStyleInput = new $3Dmol.UI.Form.ListInput(addLabelValue.style, Object.keys($3Dmol.labelStyles));
addLabelStyleInput.setValue('milk');
addLabelStyleBox.append(ls, addLabelStyleInput.ui);
addLabelForm.append(addLabelStyleBox);
var selectionList = stateManager.getSelectionList();
var addLabelSelectionBox = $('<div></div>');
var lsl = $('<div></div>').text('Label Selection');
var addLabelSelectionInput = new $3Dmol.UI.Form.ListInput(addLabelValue.sel, selectionList);
addLabelSelectionBox.append(lsl, addLabelSelectionInput.ui);
addLabelForm.append(addLabelSelectionBox);
// CSS
addLabelForm.css({
'padding': '2px',
});
tick.ui.on('click', () => {
var validate = true;
if (!addLabelStyleInput.validate())
validate = false;
if (!addLabelTextInput.validate())
validate = false;
if (!addLabelSelectionInput.validate())
validate = false;
if (validate) {
stateManager.addLabel(addLabelValue);
}
});
cross.ui.on('click', () => {
stateManager.exitContextMenu();
});
removeButton.ui.on('click', () => {
stateManager.removeLabel()
});
addLabelForm.on('keyup', (e) => {
if (e.key == 'Enter') {
tick.ui.trigger('click');
}
});
return {
boundingBox: addLabelForm,
text: addLabelTextInput,
style: addLabelStyleInput,
selection: addLabelSelectionInput,
editMode: function () {
removeButton.ui.show();
}
}
}
function processPropertyList() {
var propsForLabel = {};
propertyObjectList.forEach((propObj) => {
if (propObj.control.value === true) {
propsForLabel[propObj.key] = propObj.value;
}
});
if (Object.keys(propsForLabel).length != 0) {
return propsForLabel
}
else {
return null;
}
}
// Context Menu UI Funciton
boundingBox.hide();
this.hidden = true;
this.atom = null;
removeLabelMenu.on('click', { atom: this.atom }, function () {
stateManager.removeAtomLabel(removeLabelMenu.atom);
});
/**
* Shows the context menu
*
* @function ContextMenu#show
*
* @param {Number} x x coordinate of the mouse
* @param {Number} y y coordinate of the mouse in the viewport in pixels
* @param {AtomSpec} atom Value of the atoms that is selected
* @param {Boolean} atomExist if atom label is previously added it is set true else false
*/
this.show = function (x, y, atom, atomExist) {
if (atomExist) {
removeLabelMenu.show();
removeLabelMenu.atom = atom;
}
else {
removeLabelMenu.hide();
removeLabelMenu.atom = null;
}
alertBox.ui.hide();
addLabelMenu.hide();
if (stateManager.getSelectionList().length == 0) {
alertBox.message('Please create selections before adding label');
} else {
addLabelMenu.show();
}
unsetForm();
setPosition(boundingBox, x, y);
boundingBox.show();
this.hidden = false;
if (atom) {
setProperties(atom);
this.atom = atom;
}
else {
propertyMenu.empty();
}
}
/**
* Hides the context menu and if needed process the propertyMenu
*
* @function ContextMenu#hide
* @param {Boolean} processContextMenu If true then submission of the property to add label is executed
*/
this.hide = function (processContextMenu) {
if (processContextMenu) {
var propsForLabel = processPropertyList();
if (propsForLabel != null) {
stateManager.addAtomLabel(propsForLabel, this.atom);
}
}
boundingBox.hide();
this.hidden = true;
unsetForm();
}
addLabelMenu.on('click', function () {
var addLabelMenuForm = generateAddLabelForm();
setForm(addLabelMenuForm);
});
function setForm(form) {
editMenu.children().detach();
editMenu.append(form.boundingBox);
editMenu.show();
}
function unsetForm() {
editMenu.children().detach();
editMenu.hide();
}
}
/*
* Creates UI panel for surface manipulations
*
* @function SurfaceMenu
*/
function SurfaceMenu() {
var boundingBox = this.ui = $('<div></div>');
var _editingForm = false;
// Selection Layout
boundingBox.css({
'position': 'absolute',
'width': '140px',
'text-align': 'right'
});
var surfaceButton = new button(icons.surface, 20, { tooltip: 'Toggle Surface Menu' });
boundingBox.append(surfaceButton.ui);
var displayBox = $('<div></div>');
boundingBox.append(displayBox);
// Overflow fix
boundingBox.css({
'overflow': 'visible',
});
var newSurfaceSpace = $('<div></div>');
newSurfaceSpace.css({
'max-height': HEIGHT * 0.8,
'overflow-y': 'auto',
'overflow-x': 'hidden'
});
this.updateScrollBox = function (height) {
newSurfaceSpace.css('max-height', height * 0.8);
}
// newSurfaceSpace.append(controlButton);
// controlButton.hide();
displayBox.append(newSurfaceSpace);
var alertBox = new AlertBox();
displayBox.append(alertBox.ui);
var addArea = $('<div></div>');
var addButton = new button(icons.plus, 20, { tooltip: 'Add New Surface' });
addArea.append(addButton.ui);
displayBox.append(addArea);
displayBox.hide();
var surfaces = this.surfaces = [];
/*
* Creates cards for manipulation of surface
*
* @function Surface
*/
function Surface() {
var control = {
surfaceType: {
key: 'Surface Type',
value: null
},
surfaceStyle: {
key: 'Surface Style',
value: null
},
surfaceFor: {
key: 'Selection Atoms',
value: null
},
surfaceOf: {
key: 'Surface Generating Atoms',
value: null,
},
};
var surfaceBox = this.ui = $('<div></div>');
surfaceBox.css({
'margin-top': '3px',
'padding': '6px',
'border-radius': '3px',
'background-color': '#e8e8e8',
// 'position':'relative',
'width': '100%',
'box-sizing': 'border-box',
// 'left': "-100%",
'opacity': 0.9,
'text-align': 'left'
});
var heading = this.heading = $('<div></div>');
var header = $('<div></div>');
header.css({
'text-align': 'right'
})
// Control Buttons
var toolButtons = $('<div></div>');
var editButton = new button(icons.pencil, 16, { tooltip: 'Edit Surface' });
var removeButton = new button(icons.minus, 16, { bfr: 0.5, backgroundColor: '#f06f6f' });
toolButtons.append(removeButton.ui);
toolButtons.append(editButton.ui);
toolButtons.editButton = editButton;
toolButtons.removeButton = removeButton;
toolButtons.editMode = false;
var defaultTextStyle = {
'font-weight': 'bold',
'font-family': 'Arial',
'font-size': '12px'
}
heading.css('display', 'inline-block');
heading.css(defaultTextStyle);
toolButtons.css('display', 'inline-block');
header.hide();
header.append(heading, toolButtons);
surfaceBox.append(header);
// toolButtons.hide();
var surfacePropertyBox = $('<div></div>');
surfaceBox.append(surfacePropertyBox);
// Surface Type
var surfaceType = $('<div></div>');
var labelSurfaceType = $('<div></div>');
labelSurfaceType.text('Surface Type');
labelSurfaceType.css(defaultTextStyle);
var listSurfaceType = new $3Dmol.UI.Form.ListInput(control.surfaceType, Object.keys($3Dmol.SurfaceType));
surfaceType.append(labelSurfaceType, listSurfaceType.ui);
surfacePropertyBox.append(surfaceType);
listSurfaceType.setValue(Object.keys($3Dmol.SurfaceType)[0]);
// Surface Style
var surfaceStyle = $('<div></div>');
var labelSurfaceStyle = $('<div></div>');
// labelSurfaceStyle.text('Surface Style');
var formSurfaceStyle = new $3Dmol.UI.Form(validSurfaceSpecs, control.surfaceStyle);
surfaceStyle.append(labelSurfaceStyle, formSurfaceStyle.ui);
surfacePropertyBox.append(surfaceStyle);
// Surface Of
var surfaceOf = $('<div></div>');
var labelSurfaceOf = $('<div></div>');
labelSurfaceOf.text('Surface Atoms');
labelSurfaceOf.css(defaultTextStyle);
var surfaceGeneratorAtomType = ['self', 'all'];
var surfaceGeneratorDesc = {
'self': 'Atoms in the selections will be used to generate the surface',
'all': 'All the atoms will be used to generate the surface'
}
var listSurfaceOf = new $3Dmol.UI.Form.ListInput(control.surfaceOf, surfaceGeneratorAtomType);
var hintbox = $('<div></div>');
hintbox.css({
'background-color': '#e4e4e4',
'border': '1px solid grey',
'color': 'grey',
'padding': '2px',
'border-radius': '3px',
'font-family': 'Arial',
'font-size': '12px',
'font-weight': 'bold',
'margin-top': '3px'
});
hintbox.hide();
listSurfaceOf.update = function (control) {
if (control.value == 'self') {
hintbox.show();
hintbox.text(surfaceGeneratorDesc['self']);
}
else if (control.value == 'all') {
hintbox.show();
hintbox.text(surfaceGeneratorDesc['all']);
}
else {
hintbox.hide();
}
}
listSurfaceOf.setValue('all');
surfaceOf.append(labelSurfaceOf, listSurfaceOf.ui, hintbox);
surfacePropertyBox.append(surfaceOf);
// Surface For
var selectionListElement = ['all'].concat(stateManager.getSelectionList());
var surfaceFor = $('<div></div>');
var labelSurfaceFor = $('<div></div>');
labelSurfaceFor.text('Show Atoms');
labelSurfaceFor.css(defaultTextStyle);
var listSurfaceFor = new $3Dmol.UI.Form.ListInput(control.surfaceFor, selectionListElement);
listSurfaceFor.setValue('all');
surfaceFor.append(labelSurfaceFor, listSurfaceFor.ui);
surfacePropertyBox.append(surfaceFor);
var alertBox = new AlertBox();
surfacePropertyBox.append(alertBox.ui);
// Control Button
var controlButton = $('<div></div>');
var submit = new button(icons.tick, 16, { backgroundColor: 'lightgreen', tooltip: 'Submit' });
var cancel = new button(icons.cross, 16, { backgroundColor: 'lightcoral', tooltip: 'Cancel' });
controlButton.append(submit.ui);
controlButton.append(cancel.ui);
surfacePropertyBox.append(controlButton);
// Functionality
removeButton.ui.on('click', { surfaceBox: surfaceBox }, function (e) {
var id = e.data.surfaceBox.data('surf-id');
surfaceBox.remove();
stateManager.removeSurface(id);
});
editButton.ui.on('click', function () {
surfacePropertyBox.toggle();
// After creation of the surface box all the changes will be edit to the surfaces so on first submit toolButtons.editMode == true;
});
// Form Validation
var validateInput = this.validateInput = function () {
var validated = true;
if (!listSurfaceFor.validate()) {
validated = false;
}
if (!listSurfaceOf.validate()) {
validated = false;
}
if (!listSurfaceType.validate()) {
validated = false;
}
if (!formSurfaceStyle.validate()) {
validated = false;
}
return validated;
}
// edit this code to add on edit selection option to work
// boundingBox.on('mouseenter', function(){
// selections = stateManager.getSelectionList();
// selectionListElement = selections.map( (m)=>{
// return m.id;
// });
// listSurfaceFor.updateList(selectionListElement);
// listSurfaceOf.updateList(selectionListElement);
// });
function finalize(id) {
// element properties
surfaceBox.data('surf-id', id);
heading.text('surf#' + id);
header.show();
toolButtons.editMode = true;
surfacePropertyBox.hide();
}
// Submit
submit.ui.on('click', {}, function () {
listSurfaceFor.getValue();
listSurfaceOf.getValue();
listSurfaceType.getValue();
formSurfaceStyle.getValue();
if (validateInput()) {
if (toolButtons.editMode === false) {
var id = stateManager.addSurface(control);
control.id = id;
finalize(id);
surfaces.push(this);
_editingForm = false;
}
else {
formSurfaceStyle.getValue();
control.id = surfaceBox.data('surf-id');
stateManager.editSurface(control); // -> add updateSurface funciton to surfaceMenu
surfacePropertyBox.hide();
}
}
else {
alertBox.error('Invalid Input');
}
});
// Cancel Edit
cancel.ui.on('click', {}, function () {
if (toolButtons.editMode == false) {
surfaceBox.detach();
surfaceBox.remove();
_editingForm = false;
}
else {
surfacePropertyBox.hide();
toolButtons.editMode = false;
}
});
surfaceBox.on('keyup', (e) => {
if (e.key == 'Enter') {
submit.ui.trigger('click');
}
});
/**
* Finalizes the surface card with value specified in the surfaceSpec
*
* @function Surface#editSurface
* @param {String} id Id of the surface generated by StateManager
* @param {Object} surfaceSpec Different value of the surface menu
*/
this.editSurface = function (id, surfaceSpec) {
finalize(id);
listSurfaceType.setValue(surfaceSpec.surfaceType.value);
formSurfaceStyle.setValue(surfaceSpec.surfaceStyle.value);
listSurfaceOf.setValue(surfaceSpec.surfaceOf.value);
listSurfaceFor.setValue(surfaceSpec.surfaceFor.value);
listSurfaceFor.getValue();
listSurfaceOf.getValue();
listSurfaceType.getValue();
formSurfaceStyle.getValue();
}
}
// Functionality
// Surface addition
addButton.ui.on('click', { surfaces: this }, function () {
if (!_editingForm) {
var newSurface = new Surface();
newSurfaceSpace.append(newSurface.ui);
_editingForm = true;
} else {
alertBox.warning('Please complete the previous form first');
}
});
surfaceButton.ui.on('click', () => {
displayBox.toggle();
});
/**
* Clear all the surface cards
* @function SurfaceMenu#empty
*/
this.empty = function () {
newSurfaceSpace.empty();
_editingForm = false;
}
/**
* Add Surface in the Surface Menu
*
* @function SurfaceMenu#addSurface
* @param {String} id Id of the surface generated in the StateManager
* @param {Object} surfaceSpec Values of different property required for setting values in surface menu
*/
this.addSurface = function (id, surfaceSpec) {
var newSurface = new Surface();
newSurfaceSpace.append(newSurface.ui);
newSurface.editSurface(id, surfaceSpec);
}
}
/*
* Sets the css position property left and top for the element
*
* @function setPosition
*
* @param {object} jquery html element
* @param {number} left : css left property
* @param {number} top : css top peroperty
*/
function setPosition(ele, left, top) {
ele.css('left', left);
ele.css('top', top);
}
/*
* Sets the location of the element relative to the parseInt
* as per position types
* @function setLocation
*
* @param {Object} parent jquery object
* @param {Object} child jquery object
* @param {String} x_type 'left|right'
* @param {String} y_type 'top|bottom'
* @param {Number} x_offset Offset x values in pixels
* @param {Number} y_offset Offset y values in pixels
*/
function setLocation(parent, child, x_type = 'left', y_type = 'top', x_offset = 0, y_offset = 0) {
// p_ stands for parent
child.css('z-index', 99);
var p_width = getWidth(parent);
var p_height = getHeight(parent);
// c_ stand for child
var c_width = child.outerWidth(); // includes padding and margin
var c_height = child.outerHeight(); // includes padding and margin
var padding = parseInt(parent.css('padding').replace('px', ''));
padding = (padding) ? padding : 0;
// Setting position
var c_position = {
left: 0,
top: 0
};
if (x_type == 'left') {
c_position.left = padding + x_offset;
}
else if (x_type == 'center') {
c_position.left = p_width / 2 - c_width / 2 + x_offset;
}
else if (x_type == 'right') {
c_position.left = p_width - c_width - padding + x_offset;
}
else {
c_position.left = x_offset + padding;
}
if (y_type == 'top') {
c_position.top = y_offset + padding;
}
else if (y_type == 'center') {
c_position.top = p_height / 2 - c_height / 2 + y_offset;
}
else if (y_type == 'bottom') {
c_position.top = p_height - c_height - y_offset - padding;
}
else {
c_position.top = y_offset + padding;
}
setPosition(child, c_position.left, c_position.top);
}
// Copied from glviewer.js
function getRect(container) {
let div = container[0];
let rect = div.getBoundingClientRect();
if (rect.width == 0 && rect.height == 0 && div.style.display === 'none') {
let oldpos = div.style.position;
let oldvis = div.style.visibility;
div.style.display = 'block';
div.style.visibility = 'hidden';
div.style.position = 'absolute';
rect = div.getBoundingClientRect();
div.style.display = 'none';
div.style.visibility = oldvis;
div.style.position = oldpos;
}
return rect;
}
function getHeight(container) {
return getRect(container).height;
}
function getWidth(container) {
return getRect(container).width;
}
/*
* button - generates button with the given markup as contents
* @param {String} svg SVG markup string that contains the content of the button
* @param {Number} height Height of the content
* @param {Object} config Various properties to define the button
*
*/
function button(svg, height, config) {
config = config || {};
var borderRadius = config.bfr * height || (height / 4); // body radius factor
var bgColor = config.backgroundColor || 'rgb(177, 194, 203)';
var color = config.color || 'black';
var hoverable = config.hoverable || 'true';
var tooltipText = config.tooltip || null;
// Button instance
var button = this.ui = $('<div></div>');
var innerButton = $('<div></div>');
button.append(innerButton);
// CSS
button.css('box-sizing', 'border-box');
button.css('display', 'inline-block');
button.css('margin', '3px');
button.css('height', height);
button.css('width', height);
button.css('border-radius', borderRadius + 'px');
// button.css('padding', '3px');
button.css('color', color);
button.css('background', bgColor);
innerButton.css('display', 'flex');
innerButton.css('justify-content', 'center');
innerButton.css('align-items', 'center');
innerButton.css('padding', '2px');
// content
this.setSVG = function (svg) {
innerButton.empty();
var formatted_content = $(svg);
innerButton.append(formatted_content);
}
this.setSVG(svg);
// Hover
// Setting up tool tip
button.css({
'position': 'relative'
});
// setting up tool tip
if (tooltipText != null) {
button.attr('title', tooltipText);
}
if (hoverable == 'true') {
button.on('mouseenter',
() => {
button.css('box-shadow', '0px 0px 3px black');
}).on('mouseleave',
() => {
button.css('box-shadow', 'none');
}
);
// click
button.on('mousedown', () => {
button.css('box-shadow', '0px 0px 1px black');
});
button.on('mouseup', () => {
button.css('box-shadow', '0px 0px 3px black');
});
button.on('mousemove', () => {
// mouseX = e.clientX;
// mouseY = e.clientY;
});
}
}
}
return UI;
})();