├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── DEVELOPMENT.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── apache.config.sample ├── app.cfg.sample ├── appveyor.yml ├── data ├── ala3.dcd ├── ala3.pdb ├── md.gro ├── md.pdb ├── md.xtc ├── md_1u19.gro ├── md_1u19.part0001.xtc ├── md_1u19.part0002.xtc ├── md_1u19.xtc └── script.ngl ├── develop ├── appveyor-ci │ └── run_with_env.cmd ├── conda-recipe │ └── meta.yaml └── test.py ├── mdsrv.wsgi.sample ├── mdsrv ├── __init__.py ├── _version.py ├── mdsrv.py ├── trajectory.py └── webapp │ ├── css │ ├── dark.css │ ├── font-awesome.min.css │ ├── light.css │ └── main.css │ ├── embedded.html │ ├── favicon.ico │ ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ └── fontawesome-webfont.woff │ ├── index.html │ ├── js │ ├── gui.js │ ├── lib │ │ ├── colorpicker.min.js │ │ ├── signals.min.js │ │ └── tether.min.js │ ├── ngl.js │ └── ui │ │ ├── ui.extra.js │ │ ├── ui.helper.js │ │ ├── ui.js │ │ └── ui.ngl.js │ └── mobile.html ├── setup.cfg ├── setup.py └── versioneer.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.sublime-project 3 | *.sublime-workspace 4 | 5 | *.pyc 6 | app.cfg 7 | 8 | *.offsets 9 | .eggs/* 10 | 11 | build/* 12 | dist/* 13 | *.egg-info 14 | *.so 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # blocklist 2 | branches: 3 | except: 4 | - gh-pages 5 | 6 | matrix: 7 | include: 8 | - os: osx 9 | language: generic 10 | python: "2.7" 11 | env: PYTHON_VERSION=2.7 12 | - os: osx 13 | language: generic 14 | python: "3.4" 15 | env: PYTHON_VERSION=3.4 16 | - os: osx 17 | language: generic 18 | python: "3.5" 19 | env: PYTHON_VERSION=3.5 20 | - os: osx 21 | language: generic 22 | python: "3.6" 23 | env: PYTHON_VERSION=3.6 24 | - os: linux 25 | language: python 26 | python: "2.7" 27 | env: PYTHON_VERSION=2.7 28 | - os: linux 29 | language: python 30 | python: "3.4" 31 | env: PYTHON_VERSION=3.4 32 | - os: linux 33 | language: python 34 | python: "3.5" 35 | env: PYTHON_VERSION=3.5 36 | - os: linux 37 | language: python 38 | python: "3.6" 39 | env: PYTHON_VERSION=3.6 40 | 41 | 42 | install: 43 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then 44 | if [[ $PYTHON_VERSION == 2* ]]; then 45 | wget https://repo.continuum.io/miniconda/Miniconda2-latest-MacOSX-x86_64.sh -O miniconda.sh; 46 | elif [[ $PYTHON_VERSION == 3* ]]; then 47 | wget https://repo.continuum.io/miniconda/Miniconda3-latest-MacOSX-x86_64.sh -O miniconda.sh; 48 | fi 49 | fi 50 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then 51 | if [[ $PYTHON_VERSION == 2* ]]; then 52 | wget https://repo.continuum.io/miniconda/Miniconda2-latest-Linux-x86_64.sh -O miniconda.sh; 53 | elif [[ $PYTHON_VERSION == 3* ]]; then 54 | wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; 55 | fi 56 | fi 57 | - bash miniconda.sh -b -p $HOME/miniconda 58 | - export PATH="$HOME/miniconda/bin:$PATH" 59 | - hash -r 60 | - conda update -qy conda 61 | #- conda info -a 62 | - conda install python=$PYTHON_VERSION -y 63 | - conda install conda-build pip -y 64 | - conda install anaconda-client -y 65 | #- conda list 66 | #- python -E -V 67 | - conda config --add channels conda-forge 68 | - conda build develop/conda-recipe 69 | #- python -E -V 70 | - conda install mdsrv --use-local -y 71 | 72 | 73 | script: 74 | - mdsrv -h 75 | - python develop/test.py data/md.xtc data/md.gro 76 | 77 | 78 | after_success: 79 | - tags: true 80 | - branch: master 81 | - echo $TRAVIS_TAG 82 | - echo $CONDA_USER 83 | - export CONDA_PACKAGE=`conda build --output develop/conda-recipe | grep bz2` 84 | - echo $CONDA_PACKAGE 85 | # Only for initial release (or special issues) 86 | # - anaconda -t $CONDA_UPLOAD_TOKEN upload -u $CONDA_USER $CONDA_PACKAGE 87 | # If this build is because of a tag, upload the build if it succeeds. 88 | - if [ "$TRAVIS_TAG" ]; then anaconda -t $CONDA_UPLOAD_TOKEN upload -u $CONDA_USER $CONDA_PACKAGE; fi 89 | 90 | 91 | notifications: 92 | email: false -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file, following the suggestions of [Keep a CHANGELOG](http://keepachangelog.com/). This project adheres to [Semantic Versioning](http://semver.org/). 3 | 4 | 5 | ## [v0.3.5] - 2017-08-13 6 | ### Added 7 | - WINDOWS support (tested for conda) 8 | ### Changed 9 | - trajectory support switched from simpletraj to MDTraj with MDAnalysis as extra requirement 10 | - NGL update to version 0.10.5-18 including bugfixes related to superpositioning, updated remove trajectory formats, IE11 workaround 11 | 12 | 13 | ## [v0.3.4] - 2017-07-31 14 | ### Added 15 | - conda support via ngl channel 16 | - delta time and time offset as CMD variables 17 | 18 | 19 | ## [v0.3.3] - 2017-07-27 20 | ### Changed 21 | - NGL update to version 0.10.5-15 incl. bugfixes related to interpolation, change superpositioning on initial frame, add bounce direction for trajectoy player, animations 22 | - script examples according to new NGL version 23 | ### Added 24 | - conda support 25 | - netcdf, gro, lammpstrj, hdf5, dtr, arc tng trajectory support from mdtraj 26 | 27 | 28 | ## [v0.3.2] - 2017-07-03 29 | ### Changed 30 | - ngl update to version 0.10.5-2 incl. prmtop parser, traj time (delta time) settings, debug 31 | 32 | 33 | ## [v0.3.1] - 2017-07-02 34 | ### Changed 35 | - major ngl update to version 0.10.5-1 incl. psf, netcdf, xtc (speedup), movable gui, ... 36 | 37 | 38 | ## [v0.3] - 2016-01-11 39 | ### Added 40 | - --script arguments 41 | - versioneer 42 | - DOC: installation & deployment, usage, scripting 43 | 44 | ### Changed 45 | - major ngl update to version 0.10.0-dev5 46 | 47 | 48 | ## [v0.2] - 2016-02-12 49 | ### Added 50 | - --host and --port arguments 51 | - DOC: described arguments of the comand line tool 52 | [![DOI](https://zenodo.org/badge/doi/10.5281/zenodo.45961.svg)](http://dx.doi.org/10.5281/zenodo.45961) 53 | 54 | 55 | ## [v0.1.1] - 2016-01-02 56 | ### Added 57 | - Initial release 58 | [![DOI](https://zenodo.org/badge/doi/10.5281/zenodo.44286.svg)](http://dx.doi.org/10.5281/zenodo.44286) 59 | -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | 2 | Making a Release 3 | ================ 4 | 5 | Follow semantic versioning and make sure the changelog is up-to-date. 6 | 7 | For non prerelease level also update [README.md](README.md) and [CHANGELOG.md](CHANGELOG.md) and make a release on github including copying the relevant info from the changelog file there. 8 | 9 | 10 | We use [Versioneer](https://github.com/warner/python-versioneer) to automatically update the version string (of a release but also in development). This means for a release a new git tag should be created. The tag should be of the form vX.Y or vX.Y.Z and generally follow [pep440](https://www.python.org/dev/peps/pep-0440/) with a prefixed "v". 11 | 12 | ```bash 13 | git tag -a vX.Y -m "version X.Y" 14 | git push 15 | git push origin --tags 16 | python setup.py sdist upload -r pypi # better use twine for uploading, see below 17 | ``` 18 | 19 | To ensure a secure upload use `twine`: 20 | ```bash 21 | # Create some distributions in the normal way: 22 | python setup.py sdist 23 | # Upload with twine: 24 | twine upload dist/* 25 | ``` 26 | 27 | 28 | Development 29 | =========== 30 | 31 | Development of the MDsrv is coordinated through the repository on [github](http://github.com/arose/mdsrv). Please use the [issue tracker](https://github.com/arose/mdsrv/issues) there to report bugs or suggest improvements. 32 | 33 | To participate in developing for the MDsrv you need a local copy of the source code, which you can obtain by forking the [repository](https://github.com/arose/mdsrv) on github. Read about how to [fork a repository](https://help.github.com/articles/fork-a-repo/), [sync a fork](https://help.github.com/articles/syncing-a-fork/) and [start a pull request](https://help.github.com/articles/using-pull-requests/). 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | For license texts see at the end of this document. 3 | 4 | 5 | ////////// 6 | // MDsrv 7 | 8 | The MIT License, Copyright © 2013-2017 Alexander S. Rose 9 | 10 | applies to all files unless otherwise noted 11 | 12 | 13 | ////////// 14 | // NGL 15 | 16 | The MIT License, Copyright © 2013-2017 Alexander S. Rose 17 | 18 | applies to all files unless otherwise noted (excluding files in tmp/*). 19 | 20 | For further license information see 21 | 22 | 23 | 24 | 25 | ////////////////// 26 | // License texts 27 | 28 | 29 | The MIT License 30 | 31 | Copyright (c) , 32 | 33 | Permission is hereby granted, free of charge, to any person obtaining a copy 34 | of this software and associated documentation files (the "Software"), to deal 35 | in the Software without restriction, including without limitation the rights 36 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 37 | copies of the Software, and to permit persons to whom the Software is 38 | furnished to do so, subject to the following conditions: 39 | 40 | The above copyright notice and this permission notice shall be included in 41 | all copies or substantial portions of the Software. 42 | 43 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 44 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 45 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 46 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 47 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 48 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 49 | THE SOFTWARE. 50 | 51 | 52 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft mdsrv/webapp 2 | include LICENSE README.md DEVELOPMENT.md CHANGELOG.md 3 | include setup.py 4 | include mdsrv/_version.py versioneer.py 5 | global-exclude *.pyc 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE) 3 | [![Version](http://img.shields.io/badge/version-0.3.5-blue.svg?style=flat)](https://github.com/arose/mdsrv/releases/tag/v0.3.5) 4 | [![Changelog](https://img.shields.io/badge/changelog--lightgrey.svg?style=flat)](CHANGELOG) 5 | [![Travis Build Status](https://travis-ci.org/arose/mdsrv.svg?branch=master)](https://travis-ci.org/arose/mdsrv) 6 | 7 | 8 | MDsrv is a simple server that enables remote access to coordinate trajectories from molecular dynamics simulations. It can be used together with the NGL Viewer (http://github.com/arose/ngl) to interactively view trajectories of molecular complexes in a web-browser, either within a local network or from anywhere over the internet. 9 | 10 | See it in action: 11 | * [Web application](http://nglviewer.org/mdsrv/examples) 12 | * [Documentation](http://nglviewer.org/mdsrv/) 13 | 14 | 15 | Features 16 | -------- 17 | 18 | * Coordinate trajectories (animation, remote access) 19 | * Trajectory formats supported (xtc/trr, nc/netcdf, dcd, lammpstrj, xyz, binpos, hdf5, dtr, arc, tng) 20 | * Additional trajectory formats supported - only unix with py2 (mdcrd/crd, dms, 21 | trj, ent ncdf) 22 | * [NGL Viewer](https://github.com/arose/ngl/) (Molecular structures, Density volumes, User interaction, Embeddable) 23 | * Lightweight coordinate-only trajecotry reader (via [MDTraj](http://mdtraj.org/) and [MDAnalysis](http://www.mdanalysis.org/) ) 24 | 25 | 26 | 27 | Table of contents 28 | ================= 29 | 30 | * [Documentation](#documentation) 31 | * [RESTful API](#restful-api) 32 | * [NGL browser support](#ngl browser-support) 33 | * [Acknowledgments](#acknowledgments) 34 | * [Cite](#cite) 35 | 36 | 37 | Documentation 38 | ============ 39 | 40 | Detailed information concerning the installation, deployment, usage and scripting examples can be found at the [documentation](http://nglviewer.org/mdsrv/). 41 | 42 | 43 | Installation 44 | ============ 45 | 46 | From PyPI: 47 | 48 | pip install mdsrv 49 | 50 | From conda: 51 | conda config --add channels conda-forge 52 | conda install -c ngl mdsrv 53 | 54 | 55 | MDsrv depends on MDtraj. Please ensure that this is installed correctly and functional. 56 | 57 | 58 | Running 59 | ======= 60 | 61 | The `mdsrv` command starts a local server and opens a browser window with the web application. 62 | 63 | To use a custom configuration file 64 | 65 | mdsrv --cfg my_conf.cfg 66 | 67 | 68 | Load a topology and trajectory at startup 69 | 70 | mdsrv struc.gro traj.xtc 71 | 72 | For more options, please consult [the documentation](http://nglviewer.org/mdsrv) 73 | 74 | Configuration file 75 | ------------------ 76 | 77 | Optional. Copy/rename the sample [app.cfg](app.cfg.sample) file. It allows e.g. setting `data_dir` data directories that will be accessible through the web server and to define access restrictions. 78 | 79 | 80 | Deployment 81 | ========== 82 | 83 | The Apache Webserver can used to run the server via `mod_wsgi`. First make sure you have everything required installed: 84 | 85 | sudo apt-get install apache2 libapache2-mod-wsgi 86 | 87 | 88 | Then you need to create a wsgi configuration file to be referenced in the Apache configuration. There is an example named [mdsrv.wsgi.sample](mdsrv.wsgi.sample) in the root directory of this package. Also, a snippet showing how the configuration for Apache should look like can be found in the [apache.config.sample](apache.config.sample) file. 89 | 90 | Finally, to restart apache issue 91 | 92 | sudo /etc/init.d/apache2 restart 93 | 94 | More information can be found at the [documentation](http://nglviewer.org/mdsrv/). 95 | 96 | RESTful API 97 | =========== 98 | 99 | The RESTful API is the interface through which the web application gets all data but it may be also used to access the served trajectory data from other applications. 100 | 101 | You can retrieve information about directory content (e.g. name of sub-directory, file name, file size), number of frames and frame coordinates. 102 | 103 | For more information, please visit the [documentation](http://nglviewer.org/mdsrv/). 104 | 105 | NGL browser support 106 | =============== 107 | 108 | The NGL Viewer requires your browser to support WebGL. To see if your browser supports WebGL and what you might need to do to activate it, visit the [Get WebGL](https://get.webgl.org/) page. 109 | 110 | Generally, WebGL is available in recent browser versions of Mozilla Firefox (>29) or Google Chrome (>27). The Internet Explorer supports WebGL only since version 11. The Safari Browser since version 8 (though WebGL can be activated in earlier version: first enable the Develop menu in Safari’s Advanced preferences, then secondly in the now visible Develop menu enable WebGL). 111 | 112 | See also [this page](https://www.khronos.org/webgl/wiki/BlacklistsAndWhitelists) for details on which graphics card drivers are supported by the browsers. 113 | 114 | __WebGL draft extensions__: For a smoother appearance of cylinders and spheres your browser needs to have the `EXT_frag_depth` extension available. The [WebGL Report](http://webglreport.com/) should list the extension if active. If not, you can enable WebGL draft extensions in your browser following these instructions: 115 | 116 | * Chrome: browse to `about:flags`, enable the `Enable WebGL Draft Extensions` option, then relaunch. 117 | * Firefox: browse to `about:config` and set `webgl.enable-draft-extensions` to `true`. 118 | * Safari: Currently, the `EXT_frag_depth` extension is not supported. 119 | * Internet Explorer: Currently, the `EXT_frag_depth` extension is not supported. 120 | 121 | 122 | 123 | 124 | Acknowledgments 125 | =============== 126 | 127 | Thanks to code from MDAnalysis (http://www.mdanalysis.org/) there is random access to xtc/trr trajectory files via indexing and seeking capabilities added to the libxdrfile2 library. 128 | 129 | 130 | Funding sources: 131 | 132 | * NCI/NIH award number U01 CA198942 133 | * DFG project HI 1502 134 | * HLRN project bec00085 135 | 136 | 137 | Cite 138 | ==== 139 | 140 | When using MGsrv please cite: 141 | 142 | * A. S. Rose, and MDsrv Contributors. MDsrv v0.2 Zenodo (2016), doi:10.5281/zenodo.45961. [doi:10.5281/zenodo.45961](http://dx.doi.org/10.5281/zenodo.45961) 143 | * AS Rose, AR Bradley, Y Valasatava, JM Duarte, A Prlić and PW Rose. _Web-based molecular graphics for large complexes._ ACM Proceedings of the 21st International Conference on Web3D Technology (Web3D '16): 185-186, 2016. [doi:10.1145/2945292.2945324](http://dx.doi.org/10.1145/2945292.2945324) 144 | * AS Rose and PW Hildebrand. _NGL Viewer: a web application for molecular visualization._ Nucl Acids Res (1 July 2015) 43 (W1): W576-W579 first published online April 29, 2015. [doi:10.1093/nar/gkv402](https://doi.org/10.1093/nar/gkv402) 145 | * RT McGibbon, KA Beauchamp, MP Harrigan, C Klein, JM Swails, CX Hernández, CR Schwantes, LP Wang, TJ Lane, VS Pande. _MDTraj: A Modern Open Library for the Analysis of Molecular Dynamics Trajectories._ Biophys J. (20 October 2015) 109(8):1528-32. [doi: 10.1016/j.bpj.2015.08.015](http:/dx.doi.org/10.1016/j.bpj.2015.08.015) 146 | -------------------------------------------------------------------------------- /apache.config.sample: -------------------------------------------------------------------------------- 1 | 2 | ServerName localhost 3 | 4 | # the wsgi process will run with the user & group specified below, 5 | # so make sure that the files and directories you want to serve 6 | # are accessible with that user & group combination 7 | WSGIDaemonProcess mdsrv user=www-data group=www-data threads=5 8 | WSGIScriptAlias /mdsrv /var/www/mdsrv/mdsrv.wsgi 9 | 10 | 11 | WSGIProcessGroup mdsrv 12 | WSGIApplicationGroup %{GLOBAL} 13 | WSGIScriptReloading On 14 | WSGIPassAuthorization On 15 | Order deny,allow 16 | Allow from all 17 | 18 | -------------------------------------------------------------------------------- /app.cfg.sample: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | DEBUG = False 4 | HOST = "127.0.0.1" 5 | PORT = 8010 6 | 7 | MAX_CONTENT_LENGTH = 64 * 1024 * 1024 8 | SEND_FILE_MAX_AGE_DEFAULT = 0 9 | 10 | DATA_DIRS = { 11 | # "examplepath": os.path.abspath("/path/unprotected/available"), 12 | # "_hidden": os.path.abspath("/path/hidden/from/dir/listing"), 13 | # "protected": os.path.abspath("/path/protected"), 14 | } 15 | 16 | # Note that only one of REQUIRE_AUTH and REQUIRE_DATA_AUTH 17 | # can be true with the former taken precedence 18 | 19 | REQUIRE_AUTH = False 20 | USERNAME = "user" 21 | PASSWORD = "pass" 22 | 23 | REQUIRE_DATA_AUTH = True 24 | DATA_AUTH = { 25 | "protected": [ "user", "test123" ] 26 | } 27 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # modified from MDTraj appveyor 2 | environment: 3 | global: 4 | # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the 5 | # /E:ON and /V:ON options are not enabled in the batch script intepreter 6 | # See: http://stackoverflow.com/a/13751649/163740 7 | CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\develop\\appveyor-ci\\run_with_env.cmd" 8 | PYTHONUNBUFFERED: 1 9 | # BINSTAR_TOKEN: 10 | # secure: LJUoqxkUbcJR0UnL2Ap7/dLquM6cX8i1JC0mXNvwXEbTTVW3C98qQ/JlJWzAYpoy 11 | # BINSTAR_USER: "j0kaso" 12 | BINSTAR_TOKEN: 13 | secure: iG/R6JSE4NP4kGAKws0IDTnhB70bYlUI2FHnmPpH9FCOlEta6TXinmu76MSSW03W 14 | BINSTAR_USER: "ngl" 15 | 16 | matrix: 17 | - PYTHON: "C:\\Miniconda" 18 | CONDA_PY: "27" 19 | - PYTHON: "C:\\Miniconda-x64" 20 | CONDA_PY: "27" 21 | ARCH: "64" 22 | - PYTHON: "C:\\Miniconda3" 23 | CONDA_PY: "34" 24 | - PYTHON: "C:\\Miniconda3-x64" 25 | CONDA_PY: "34" 26 | ARCH: "64" 27 | - PYTHON: "C:\\Miniconda3" 28 | CONDA_PY: "35" 29 | - PYTHON: "C:\\Miniconda3-x64" 30 | CONDA_PY: "35" 31 | ARCH: "64" 32 | - PYTHON: "C:\\Miniconda3" 33 | CONDA_PY: "36" 34 | - PYTHON: "C:\\Miniconda3-x64" 35 | CONDA_PY: "36" 36 | ARCH: "64" 37 | 38 | 39 | install: 40 | - set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH% 41 | - conda config --add channels conda-forge 42 | - conda update -yq --all 43 | - conda install -yq anaconda-client conda-build jinja2 44 | - conda install python=%CONDA_PY:~0,1%.%CONDA_PY:~1,2% -y 45 | # - python -E -V 46 | - conda build --quiet develop\\conda-recipe 47 | - conda install --use-local mdsrv -y 48 | 49 | 50 | build: false 51 | 52 | 53 | test_script: 54 | - "%CMD_IN_ENV% mdsrv -h" 55 | - "%CMD_IN_ENV% python develop\\test.py data\\md.xtc data\\md.gro" 56 | 57 | 58 | deploy_script: 59 | - echo "Starting Deployment" 60 | # upload conda builds to conda cloud 61 | - cmd: set PATH=%PYTHON%;%PYTHON%\Scripts;%PYTHON%\Library\bin;%PATH% 62 | - echo %APPVEYOR_REPO_TAG% 63 | - ps: If ($env:APPVEYOR_REPO_TAG -eq "true") { $env:conda_upload = 'true' } Else { write-output "Not on a tag, won't deploy to anaconda" } 64 | - cmd: IF "%conda_upload%"=="true" anaconda -t %BINSTAR_TOKEN% upload %PYTHON%\\conda\\win-*\\*.tar.bz2 -u %BINSTAR_USER% --no-progress --force 65 | 66 | 67 | notifications: 68 | email: false -------------------------------------------------------------------------------- /data/ala3.dcd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nglviewer/mdsrv/51302bd369be447b5d01ee74b4f8a3c0653760e8/data/ala3.dcd -------------------------------------------------------------------------------- /data/ala3.pdb: -------------------------------------------------------------------------------- 1 | CRYST1 1.000 1.000 1.000 90.00 90.00 90.00 P 1 1 2 | ATOM 1 CAY ALA A 1 5.970 6.492 1.019 0.00 0.00 ALA3 3 | ATOM 2 HY1 ALA A 1 4.860 6.444 1.024 0.00 0.00 ALA3 4 | ATOM 3 HY2 ALA A 1 6.240 7.187 0.196 0.00 0.00 ALA3 5 | ATOM 4 HY3 ALA A 1 6.602 6.764 1.892 0.00 0.00 ALA3 6 | ATOM 5 CY ALA A 1 6.330 5.043 0.603 0.00 0.00 ALA3 7 | ATOM 6 OY ALA A 1 5.783 4.117 1.223 0.00 0.00 ALA3 8 | ATOM 7 N ALA A 1 7.324 4.934 -0.245 0.00 0.00 ALA3 9 | ATOM 8 HN ALA A 1 7.527 5.760 -0.766 0.00 0.00 ALA3 10 | ATOM 9 CA ALA A 1 8.178 3.827 -0.406 0.00 0.00 ALA3 11 | ATOM 10 HA ALA A 1 8.104 3.082 0.372 0.00 0.00 ALA3 12 | ATOM 11 CB ALA A 1 7.947 3.037 -1.699 0.00 0.00 ALA3 13 | ATOM 12 HB1 ALA A 1 8.770 2.372 -2.039 0.00 0.00 ALA3 14 | ATOM 13 HB2 ALA A 1 7.003 2.461 -1.806 0.00 0.00 ALA3 15 | ATOM 14 HB3 ALA A 1 8.089 3.822 -2.472 0.00 0.00 ALA3 16 | ATOM 15 C ALA A 1 9.604 4.361 -0.473 0.00 0.00 ALA3 17 | ATOM 16 O ALA A 1 9.868 5.574 -0.554 0.00 0.00 ALA3 18 | ATOM 17 N ALA A 2 10.512 3.338 -0.396 0.00 0.00 ALA3 19 | ATOM 18 HN ALA A 2 10.077 2.443 -0.445 0.00 0.00 ALA3 20 | ATOM 19 CA ALA A 2 11.947 3.411 -0.279 0.00 0.00 ALA3 21 | ATOM 20 HA ALA A 2 12.364 2.415 -0.262 0.00 0.00 ALA3 22 | ATOM 21 CB ALA A 2 12.534 4.353 -1.374 0.00 0.00 ALA3 23 | ATOM 22 HB1 ALA A 2 13.627 4.173 -1.463 0.00 0.00 ALA3 24 | ATOM 23 HB2 ALA A 2 12.110 4.136 -2.378 0.00 0.00 ALA3 25 | ATOM 24 HB3 ALA A 2 12.329 5.431 -1.203 0.00 0.00 ALA3 26 | ATOM 25 C ALA A 2 12.240 3.865 1.115 0.00 0.00 ALA3 27 | ATOM 26 O ALA A 2 12.440 2.973 1.982 0.00 0.00 ALA3 28 | ATOM 27 N ALA A 3 12.366 5.182 1.390 0.00 0.00 ALA3 29 | ATOM 28 HN ALA A 3 12.205 5.750 0.586 0.00 0.00 ALA3 30 | ATOM 29 CA ALA A 3 12.914 5.631 2.606 0.00 0.00 ALA3 31 | ATOM 30 HA ALA A 3 13.699 4.959 2.918 0.00 0.00 ALA3 32 | ATOM 31 CB ALA A 3 13.456 7.019 2.256 0.00 0.00 ALA3 33 | ATOM 32 HB1 ALA A 3 14.373 7.226 2.848 0.00 0.00 ALA3 34 | ATOM 33 HB2 ALA A 3 13.692 7.170 1.181 0.00 0.00 ALA3 35 | ATOM 34 HB3 ALA A 3 12.676 7.790 2.433 0.00 0.00 ALA3 36 | ATOM 35 C ALA A 3 11.769 5.679 3.617 0.00 0.00 ALA3 37 | ATOM 36 O ALA A 3 10.645 5.644 3.125 0.00 0.00 ALA3 38 | ATOM 37 NT ALA A 3 11.923 5.593 4.992 0.00 0.00 ALA3 39 | ATOM 38 HNT ALA A 3 11.160 5.497 5.625 0.00 0.00 ALA3 40 | ATOM 39 CAT ALA A 3 13.240 5.427 5.505 0.00 0.00 ALA3 41 | ATOM 40 HT1 ALA A 3 13.976 4.860 4.896 0.00 0.00 ALA3 42 | ATOM 41 HT2 ALA A 3 13.685 6.444 5.533 0.00 0.00 ALA3 43 | ATOM 42 HT3 ALA A 3 13.269 5.116 6.571 0.00 0.00 ALA3 44 | END 45 | -------------------------------------------------------------------------------- /data/md.xtc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nglviewer/mdsrv/51302bd369be447b5d01ee74b4f8a3c0653760e8/data/md.xtc -------------------------------------------------------------------------------- /data/md_1u19.part0001.xtc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nglviewer/mdsrv/51302bd369be447b5d01ee74b4f8a3c0653760e8/data/md_1u19.part0001.xtc -------------------------------------------------------------------------------- /data/md_1u19.part0002.xtc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nglviewer/mdsrv/51302bd369be447b5d01ee74b4f8a3c0653760e8/data/md_1u19.part0002.xtc -------------------------------------------------------------------------------- /data/md_1u19.xtc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nglviewer/mdsrv/51302bd369be447b5d01ee74b4f8a3c0653760e8/data/md_1u19.xtc -------------------------------------------------------------------------------- /data/script.ngl: -------------------------------------------------------------------------------- 1 | panel.setName( "Controls" ); 2 | stage.setParameters( { theme: "light" } ); 3 | 4 | var h = scriptHelperFunctions(stage, panel); 5 | 6 | h.uiButton( 7 | "show/hide all", 8 | function(){ 9 | var hidden; 10 | var compX = h.components("md_1u19").list[0]; 11 | if (compX.visible === false){ 12 | hidden = true; 13 | }else{ 14 | hidden = false; 15 | } 16 | stage.eachComponent( function( comp ){ 17 | if (hidden === true){ 18 | h.visible(true, comp); 19 | }else{ 20 | h.visible(false, comp); 21 | } 22 | } ); 23 | } 24 | ); 25 | 26 | h.uiButton( 27 | "reverse view", 28 | function(){ 29 | stage.eachComponent( function( comp ){ 30 | if (comp.visible === true){ 31 | h.visible( false, comp ); 32 | }else{ 33 | h.visible( true, comp ); 34 | } 35 | } ); 36 | } 37 | ); 38 | 39 | h.uiButton( 40 | "sidechains on/off", 41 | function(){ 42 | h.representations( "licorice" ).list.forEach( function( repre ){ 43 | h.visible(!repre.visible, repre); 44 | } ); 45 | } 46 | ); 47 | 48 | h.uiButton( 49 | "center and show R135", 50 | function(){ 51 | h.representations( "R135" ).list.forEach( function( repre ){ 52 | h.visible(true, repre); 53 | stage.animationControls.orient([-48.34,5.16,1.25,0,4.23,44.34,-19.53,0,-3.21,-19.3,-44.53,0,-35.74,-47.65,-63.7,1], 3000); 54 | } ); 55 | } 56 | ); 57 | 58 | h.uiButton( 59 | "show complete system", 60 | function(){ 61 | stage.autoView(1000); 62 | } 63 | ); 64 | 65 | var trajSele = "backbone and not hydrogen"; 66 | 67 | var basePath = "cwd/data/"; 68 | var name = "md_1u19"; 69 | var sysPath = "file://" + basePath + name + ".gro"; 70 | stage.loadFile( sysPath ).then( function( comp ){ 71 | 72 | comp.setName( name ); 73 | comp.setSelection( "protein and not _h" ); 74 | 75 | comp.addRepresentation( "cartoon", { color: "#55eb86", sele: "*" } ); 76 | comp.addRepresentation( "licorice", { colorScheme: "element", colorValue: "#55eb86", visible: false, sele: "protein" } ); 77 | comp.addRepresentation("licorice", { name: "R135", colorScheme: "element", colorValue: "#55eb86", visible: false, sele: "135" } ); 78 | comp.addTrajectory( basePath + name + ".xtc" ); 79 | // comp.addTrajectory( basePath + "/@md_1u19.xtc", { sele: trajSele, deltaTime: 200, centerPbc: true, removePbc: true, superpose: true } ); 80 | comp.autoView(); 81 | } ); 82 | 83 | -------------------------------------------------------------------------------- /develop/appveyor-ci/run_with_env.cmd: -------------------------------------------------------------------------------- 1 | :: To build extensions for 64 bit Python 3, we need to configure environment 2 | :: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of: 3 | :: MS Windows SDK for Windows 7 and .NET Framework 4 (SDK v7.1) 4 | :: 5 | :: To build extensions for 64 bit Python 2, we need to configure environment 6 | :: variables to use the MSVC 2008 C++ compilers from GRMSDKX_EN_DVD.iso of: 7 | :: MS Windows SDK for Windows 7 and .NET Framework 3.5 (SDK v7.0) 8 | :: 9 | :: 32 bit builds do not require specific environment configurations. 10 | :: 11 | :: Note: this script needs to be run with the /E:ON and /V:ON flags for the 12 | :: cmd interpreter, at least for (SDK v7.0) 13 | :: 14 | :: More details at: 15 | :: https://github.com/cython/cython/wiki/64BitCythonExtensionsOnWindows 16 | :: http://stackoverflow.com/a/13751649/163740 17 | :: 18 | :: Author: Olivier Grisel 19 | :: License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/ 20 | 21 | :: Modified for use with conda environment variables 22 | 23 | @ECHO OFF 24 | 25 | SET COMMAND_TO_RUN=%* 26 | SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows 27 | 28 | SET MAJOR_PYTHON_VERSION="%CONDA_PY:~0,1%" 29 | IF %MAJOR_PYTHON_VERSION% == "2" ( 30 | SET WINDOWS_SDK_VERSION="v7.0" 31 | ) ELSE IF %MAJOR_PYTHON_VERSION% == "3" ( 32 | SET WINDOWS_SDK_VERSION="v7.1" 33 | ) ELSE ( 34 | ECHO Unsupported Python version: "%MAJOR_PYTHON_VERSION%" 35 | EXIT 1 36 | ) 37 | 38 | IF "%ARCH%"=="64" ( 39 | ECHO Configuring Windows SDK %WINDOWS_SDK_VERSION% for Python %MAJOR_PYTHON_VERSION% on a 64 bit architecture 40 | SET DISTUTILS_USE_SDK=1 41 | SET MSSdk=1 42 | "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION% 43 | "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release 44 | ECHO Executing: %COMMAND_TO_RUN% 45 | call %COMMAND_TO_RUN% || EXIT 1 46 | ) ELSE ( 47 | ECHO Using default MSVC build environment for 32 bit architecture 48 | ECHO Executing: %COMMAND_TO_RUN% 49 | call %COMMAND_TO_RUN% || EXIT 1 50 | ) 51 | -------------------------------------------------------------------------------- /develop/conda-recipe/meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: mdsrv 3 | version: {{ environ.get('GIT_DESCRIBE_TAG', 'v0.3.5')}} 4 | 5 | source: 6 | path: ../../ 7 | 8 | build: 9 | number: 1 10 | script: python setup.py install 11 | 12 | requirements: 13 | build: 14 | - python 15 | - cython 16 | - numpy 17 | - msinttypes # [win and py2k] 18 | - setuptools 19 | - flask 20 | - scipy 21 | - zlib 22 | - mdtraj 23 | - mdanalysis # [not win and py2k] 24 | 25 | run: 26 | - python 27 | - setuptools 28 | - numpy 29 | - flask 30 | - mdtraj 31 | - mdanalysis # [not win and py2k] 32 | 33 | test: 34 | imports: 35 | - mdsrv 36 | 37 | about: 38 | home: https://github.com/arose/mdsrv 39 | license: MIT License 40 | summary: "Simple server to visualize remote trajectories." 41 | description: | 42 | MDsrv is a simple server that enables remote access to coordinate trajectories from molecular dynamics simulations. It can be used together with the NGL Viewer (http://github.com/arose/ngl) to interactively view trajectories of molecular complexes in a web-browser, either within a local network or from anywhere over the internet. 43 | dev_url: https://nglviewer.org/mdsrv -------------------------------------------------------------------------------- /develop/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | from mdsrv import trajectory 4 | 5 | 6 | def main(argv): 7 | 8 | print('Importarray: ') 9 | print(trajectory.importarray) 10 | 11 | print('test for loading via gui:') 12 | print(trajectory.get_trajectory(argv[0], '')) 13 | print(trajectory.importarray) 14 | 15 | print('test for loading with command + struc&traj:') 16 | print(trajectory.get_trajectory(argv[0], argv[1])) 17 | print(trajectory.importarray) 18 | 19 | print('Extratest: Import mdtraj') 20 | try: 21 | import mdtraj 22 | print('success') 23 | except ImportError as e: 24 | print('error',e) 25 | 26 | print('Extratest: Import mdanalysis') 27 | try: 28 | import mdanalysis 29 | print('success') 30 | except ImportError as e: 31 | print('error',e) 32 | 33 | print('Extratest: mdtraj functionality') 34 | try: 35 | from mdtraj.formats import XTCTrajectoryFile 36 | xtc = XTCTrajectoryFile( argv[0], 'r' ) 37 | xtc_traj = xtc.read(n_frames=1) 38 | print('success:', xtc_traj) 39 | except Exception as ex: 40 | template = "An exception of type {0} occurred. Arguments:\n{1!r}" 41 | message = template.format(type(ex).__name__, ex.args) 42 | print(message, ex) 43 | 44 | if __name__ == "__main__": 45 | main(argv=sys.argv[1:]) -------------------------------------------------------------------------------- /mdsrv.wsgi.sample: -------------------------------------------------------------------------------- 1 | ##### 2 | # configuration for the mdsrv wsgi app 3 | 4 | # leave empty if no virtualenv is needed 5 | APP_ENV = '' 6 | 7 | # leave empty to use default config 8 | APP_CFG = '' 9 | 10 | 11 | ##### 12 | # do not change anything below unless you are sure 13 | # see http://flask.pocoo.org/docs/deploying/mod_wsgi/ 14 | 15 | 16 | import os, sys 17 | 18 | if APP_ENV: 19 | if sys.version_info > (3,): 20 | activate_this = os.path.join( APP_ENV, 'activate_this.py' ) 21 | with open( activate_this ) as f: 22 | code = compile( f.read(), activate_this, 'exec' ) 23 | exec( code, dict( __file__=activate_this ) ) 24 | else: 25 | activate_this = os.path.join( APP_ENV, 'activate_this.py' ) 26 | execfile( activate_this, dict( __file__=activate_this ) ) 27 | 28 | from mdsrv import app as application 29 | if APP_CFG: 30 | application.config.from_pyfile( APP_CFG ) 31 | -------------------------------------------------------------------------------- /mdsrv/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from ._version import get_versions 3 | __version__ = get_versions()['version'] 4 | del get_versions 5 | 6 | 7 | 8 | from .mdsrv import * 9 | from .trajectory import * 10 | 11 | 12 | -------------------------------------------------------------------------------- /mdsrv/_version.py: -------------------------------------------------------------------------------- 1 | 2 | # This file helps to compute a version number in source trees obtained from 3 | # git-archive tarball (such as those provided by githubs download-from-tag 4 | # feature). Distribution tarballs (built by setup.py sdist) and build 5 | # directories (produced by setup.py build) will contain a much shorter file 6 | # that just contains the computed version number. 7 | 8 | # This file is released into the public domain. Generated by 9 | # versioneer-0.18 (https://github.com/warner/python-versioneer) 10 | 11 | """Git implementation of _version.py.""" 12 | 13 | import errno 14 | import os 15 | import re 16 | import subprocess 17 | import sys 18 | 19 | 20 | def get_keywords(): 21 | """Get the keywords needed to look up the version information.""" 22 | # these strings will be replaced by git during git-archive. 23 | # setup.py/versioneer.py will grep for the variable names, so they must 24 | # each be defined on a line of their own. _version.py will just call 25 | # get_keywords(). 26 | git_refnames = "$Format:%d$" 27 | git_full = "$Format:%H$" 28 | git_date = "$Format:%ci$" 29 | keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} 30 | return keywords 31 | 32 | 33 | class VersioneerConfig: 34 | """Container for Versioneer configuration parameters.""" 35 | 36 | 37 | def get_config(): 38 | """Create, populate and return the VersioneerConfig() object.""" 39 | # these strings are filled in when 'setup.py versioneer' creates 40 | # _version.py 41 | cfg = VersioneerConfig() 42 | cfg.VCS = "git" 43 | cfg.style = "pep440" 44 | cfg.tag_prefix = "v" 45 | cfg.parentdir_prefix = "None" 46 | cfg.versionfile_source = "mdsrv/_version.py" 47 | cfg.verbose = False 48 | return cfg 49 | 50 | 51 | class NotThisMethod(Exception): 52 | """Exception raised if a method is not valid for the current scenario.""" 53 | 54 | 55 | LONG_VERSION_PY = {} 56 | HANDLERS = {} 57 | 58 | 59 | def register_vcs_handler(vcs, method): # decorator 60 | """Decorator to mark a method as the handler for a particular VCS.""" 61 | def decorate(f): 62 | """Store f in HANDLERS[vcs][method].""" 63 | if vcs not in HANDLERS: 64 | HANDLERS[vcs] = {} 65 | HANDLERS[vcs][method] = f 66 | return f 67 | return decorate 68 | 69 | 70 | def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, 71 | env=None): 72 | """Call the given command(s).""" 73 | assert isinstance(commands, list) 74 | p = None 75 | for c in commands: 76 | try: 77 | dispcmd = str([c] + args) 78 | # remember shell=False, so use git.cmd on windows, not just git 79 | p = subprocess.Popen([c] + args, cwd=cwd, env=env, 80 | stdout=subprocess.PIPE, 81 | stderr=(subprocess.PIPE if hide_stderr 82 | else None)) 83 | break 84 | except EnvironmentError: 85 | e = sys.exc_info()[1] 86 | if e.errno == errno.ENOENT: 87 | continue 88 | if verbose: 89 | print("unable to run %s" % dispcmd) 90 | print(e) 91 | return None, None 92 | else: 93 | if verbose: 94 | print("unable to find command, tried %s" % (commands,)) 95 | return None, None 96 | stdout = p.communicate()[0].strip() 97 | if sys.version_info[0] >= 3: 98 | stdout = stdout.decode() 99 | if p.returncode != 0: 100 | if verbose: 101 | print("unable to run %s (error)" % dispcmd) 102 | print("stdout was %s" % stdout) 103 | return None, p.returncode 104 | return stdout, p.returncode 105 | 106 | 107 | def versions_from_parentdir(parentdir_prefix, root, verbose): 108 | """Try to determine the version from the parent directory name. 109 | 110 | Source tarballs conventionally unpack into a directory that includes both 111 | the project name and a version string. We will also support searching up 112 | two directory levels for an appropriately named parent directory 113 | """ 114 | rootdirs = [] 115 | 116 | for i in range(3): 117 | dirname = os.path.basename(root) 118 | if dirname.startswith(parentdir_prefix): 119 | return {"version": dirname[len(parentdir_prefix):], 120 | "full-revisionid": None, 121 | "dirty": False, "error": None, "date": None} 122 | else: 123 | rootdirs.append(root) 124 | root = os.path.dirname(root) # up a level 125 | 126 | if verbose: 127 | print("Tried directories %s but none started with prefix %s" % 128 | (str(rootdirs), parentdir_prefix)) 129 | raise NotThisMethod("rootdir doesn't start with parentdir_prefix") 130 | 131 | 132 | @register_vcs_handler("git", "get_keywords") 133 | def git_get_keywords(versionfile_abs): 134 | """Extract version information from the given file.""" 135 | # the code embedded in _version.py can just fetch the value of these 136 | # keywords. When used from setup.py, we don't want to import _version.py, 137 | # so we do it with a regexp instead. This function is not used from 138 | # _version.py. 139 | keywords = {} 140 | try: 141 | f = open(versionfile_abs, "r") 142 | for line in f.readlines(): 143 | if line.strip().startswith("git_refnames ="): 144 | mo = re.search(r'=\s*"(.*)"', line) 145 | if mo: 146 | keywords["refnames"] = mo.group(1) 147 | if line.strip().startswith("git_full ="): 148 | mo = re.search(r'=\s*"(.*)"', line) 149 | if mo: 150 | keywords["full"] = mo.group(1) 151 | if line.strip().startswith("git_date ="): 152 | mo = re.search(r'=\s*"(.*)"', line) 153 | if mo: 154 | keywords["date"] = mo.group(1) 155 | f.close() 156 | except EnvironmentError: 157 | pass 158 | return keywords 159 | 160 | 161 | @register_vcs_handler("git", "keywords") 162 | def git_versions_from_keywords(keywords, tag_prefix, verbose): 163 | """Get version information from git keywords.""" 164 | if not keywords: 165 | raise NotThisMethod("no keywords at all, weird") 166 | date = keywords.get("date") 167 | if date is not None: 168 | # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant 169 | # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 170 | # -like" string, which we must then edit to make compliant), because 171 | # it's been around since git-1.5.3, and it's too difficult to 172 | # discover which version we're using, or to work around using an 173 | # older one. 174 | date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) 175 | refnames = keywords["refnames"].strip() 176 | if refnames.startswith("$Format"): 177 | if verbose: 178 | print("keywords are unexpanded, not using") 179 | raise NotThisMethod("unexpanded keywords, not a git-archive tarball") 180 | refs = set([r.strip() for r in refnames.strip("()").split(",")]) 181 | # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of 182 | # just "foo-1.0". If we see a "tag: " prefix, prefer those. 183 | TAG = "tag: " 184 | tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) 185 | if not tags: 186 | # Either we're using git < 1.8.3, or there really are no tags. We use 187 | # a heuristic: assume all version tags have a digit. The old git %d 188 | # expansion behaves like git log --decorate=short and strips out the 189 | # refs/heads/ and refs/tags/ prefixes that would let us distinguish 190 | # between branches and tags. By ignoring refnames without digits, we 191 | # filter out many common branch names like "release" and 192 | # "stabilization", as well as "HEAD" and "master". 193 | tags = set([r for r in refs if re.search(r'\d', r)]) 194 | if verbose: 195 | print("discarding '%s', no digits" % ",".join(refs - tags)) 196 | if verbose: 197 | print("likely tags: %s" % ",".join(sorted(tags))) 198 | for ref in sorted(tags): 199 | # sorting will prefer e.g. "2.0" over "2.0rc1" 200 | if ref.startswith(tag_prefix): 201 | r = ref[len(tag_prefix):] 202 | if verbose: 203 | print("picking %s" % r) 204 | return {"version": r, 205 | "full-revisionid": keywords["full"].strip(), 206 | "dirty": False, "error": None, 207 | "date": date} 208 | # no suitable tags, so version is "0+unknown", but full hex is still there 209 | if verbose: 210 | print("no suitable tags, using unknown + full revision id") 211 | return {"version": "0+unknown", 212 | "full-revisionid": keywords["full"].strip(), 213 | "dirty": False, "error": "no suitable tags", "date": None} 214 | 215 | 216 | @register_vcs_handler("git", "pieces_from_vcs") 217 | def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): 218 | """Get version from 'git describe' in the root of the source tree. 219 | 220 | This only gets called if the git-archive 'subst' keywords were *not* 221 | expanded, and _version.py hasn't already been rewritten with a short 222 | version string, meaning we're inside a checked out source tree. 223 | """ 224 | GITS = ["git"] 225 | if sys.platform == "win32": 226 | GITS = ["git.cmd", "git.exe"] 227 | 228 | out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, 229 | hide_stderr=True) 230 | if rc != 0: 231 | if verbose: 232 | print("Directory %s not under git control" % root) 233 | raise NotThisMethod("'git rev-parse --git-dir' returned error") 234 | 235 | # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] 236 | # if there isn't one, this yields HEX[-dirty] (no NUM) 237 | describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", 238 | "--always", "--long", 239 | "--match", "%s*" % tag_prefix], 240 | cwd=root) 241 | # --long was added in git-1.5.5 242 | if describe_out is None: 243 | raise NotThisMethod("'git describe' failed") 244 | describe_out = describe_out.strip() 245 | full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) 246 | if full_out is None: 247 | raise NotThisMethod("'git rev-parse' failed") 248 | full_out = full_out.strip() 249 | 250 | pieces = {} 251 | pieces["long"] = full_out 252 | pieces["short"] = full_out[:7] # maybe improved later 253 | pieces["error"] = None 254 | 255 | # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] 256 | # TAG might have hyphens. 257 | git_describe = describe_out 258 | 259 | # look for -dirty suffix 260 | dirty = git_describe.endswith("-dirty") 261 | pieces["dirty"] = dirty 262 | if dirty: 263 | git_describe = git_describe[:git_describe.rindex("-dirty")] 264 | 265 | # now we have TAG-NUM-gHEX or HEX 266 | 267 | if "-" in git_describe: 268 | # TAG-NUM-gHEX 269 | mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) 270 | if not mo: 271 | # unparseable. Maybe git-describe is misbehaving? 272 | pieces["error"] = ("unable to parse git-describe output: '%s'" 273 | % describe_out) 274 | return pieces 275 | 276 | # tag 277 | full_tag = mo.group(1) 278 | if not full_tag.startswith(tag_prefix): 279 | if verbose: 280 | fmt = "tag '%s' doesn't start with prefix '%s'" 281 | print(fmt % (full_tag, tag_prefix)) 282 | pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" 283 | % (full_tag, tag_prefix)) 284 | return pieces 285 | pieces["closest-tag"] = full_tag[len(tag_prefix):] 286 | 287 | # distance: number of commits since tag 288 | pieces["distance"] = int(mo.group(2)) 289 | 290 | # commit: short hex revision ID 291 | pieces["short"] = mo.group(3) 292 | 293 | else: 294 | # HEX: no tags 295 | pieces["closest-tag"] = None 296 | count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], 297 | cwd=root) 298 | pieces["distance"] = int(count_out) # total number of commits 299 | 300 | # commit date: see ISO-8601 comment in git_versions_from_keywords() 301 | date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], 302 | cwd=root)[0].strip() 303 | pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) 304 | 305 | return pieces 306 | 307 | 308 | def plus_or_dot(pieces): 309 | """Return a + if we don't already have one, else return a .""" 310 | if "+" in pieces.get("closest-tag", ""): 311 | return "." 312 | return "+" 313 | 314 | 315 | def render_pep440(pieces): 316 | """Build up version string, with post-release "local version identifier". 317 | 318 | Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you 319 | get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty 320 | 321 | Exceptions: 322 | 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] 323 | """ 324 | if pieces["closest-tag"]: 325 | rendered = pieces["closest-tag"] 326 | if pieces["distance"] or pieces["dirty"]: 327 | rendered += plus_or_dot(pieces) 328 | rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) 329 | if pieces["dirty"]: 330 | rendered += ".dirty" 331 | else: 332 | # exception #1 333 | rendered = "0+untagged.%d.g%s" % (pieces["distance"], 334 | pieces["short"]) 335 | if pieces["dirty"]: 336 | rendered += ".dirty" 337 | return rendered 338 | 339 | 340 | def render_pep440_pre(pieces): 341 | """TAG[.post.devDISTANCE] -- No -dirty. 342 | 343 | Exceptions: 344 | 1: no tags. 0.post.devDISTANCE 345 | """ 346 | if pieces["closest-tag"]: 347 | rendered = pieces["closest-tag"] 348 | if pieces["distance"]: 349 | rendered += ".post.dev%d" % pieces["distance"] 350 | else: 351 | # exception #1 352 | rendered = "0.post.dev%d" % pieces["distance"] 353 | return rendered 354 | 355 | 356 | def render_pep440_post(pieces): 357 | """TAG[.postDISTANCE[.dev0]+gHEX] . 358 | 359 | The ".dev0" means dirty. Note that .dev0 sorts backwards 360 | (a dirty tree will appear "older" than the corresponding clean one), 361 | but you shouldn't be releasing software with -dirty anyways. 362 | 363 | Exceptions: 364 | 1: no tags. 0.postDISTANCE[.dev0] 365 | """ 366 | if pieces["closest-tag"]: 367 | rendered = pieces["closest-tag"] 368 | if pieces["distance"] or pieces["dirty"]: 369 | rendered += ".post%d" % pieces["distance"] 370 | if pieces["dirty"]: 371 | rendered += ".dev0" 372 | rendered += plus_or_dot(pieces) 373 | rendered += "g%s" % pieces["short"] 374 | else: 375 | # exception #1 376 | rendered = "0.post%d" % pieces["distance"] 377 | if pieces["dirty"]: 378 | rendered += ".dev0" 379 | rendered += "+g%s" % pieces["short"] 380 | return rendered 381 | 382 | 383 | def render_pep440_old(pieces): 384 | """TAG[.postDISTANCE[.dev0]] . 385 | 386 | The ".dev0" means dirty. 387 | 388 | Eexceptions: 389 | 1: no tags. 0.postDISTANCE[.dev0] 390 | """ 391 | if pieces["closest-tag"]: 392 | rendered = pieces["closest-tag"] 393 | if pieces["distance"] or pieces["dirty"]: 394 | rendered += ".post%d" % pieces["distance"] 395 | if pieces["dirty"]: 396 | rendered += ".dev0" 397 | else: 398 | # exception #1 399 | rendered = "0.post%d" % pieces["distance"] 400 | if pieces["dirty"]: 401 | rendered += ".dev0" 402 | return rendered 403 | 404 | 405 | def render_git_describe(pieces): 406 | """TAG[-DISTANCE-gHEX][-dirty]. 407 | 408 | Like 'git describe --tags --dirty --always'. 409 | 410 | Exceptions: 411 | 1: no tags. HEX[-dirty] (note: no 'g' prefix) 412 | """ 413 | if pieces["closest-tag"]: 414 | rendered = pieces["closest-tag"] 415 | if pieces["distance"]: 416 | rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) 417 | else: 418 | # exception #1 419 | rendered = pieces["short"] 420 | if pieces["dirty"]: 421 | rendered += "-dirty" 422 | return rendered 423 | 424 | 425 | def render_git_describe_long(pieces): 426 | """TAG-DISTANCE-gHEX[-dirty]. 427 | 428 | Like 'git describe --tags --dirty --always -long'. 429 | The distance/hash is unconditional. 430 | 431 | Exceptions: 432 | 1: no tags. HEX[-dirty] (note: no 'g' prefix) 433 | """ 434 | if pieces["closest-tag"]: 435 | rendered = pieces["closest-tag"] 436 | rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) 437 | else: 438 | # exception #1 439 | rendered = pieces["short"] 440 | if pieces["dirty"]: 441 | rendered += "-dirty" 442 | return rendered 443 | 444 | 445 | def render(pieces, style): 446 | """Render the given version pieces into the requested style.""" 447 | if pieces["error"]: 448 | return {"version": "unknown", 449 | "full-revisionid": pieces.get("long"), 450 | "dirty": None, 451 | "error": pieces["error"], 452 | "date": None} 453 | 454 | if not style or style == "default": 455 | style = "pep440" # the default 456 | 457 | if style == "pep440": 458 | rendered = render_pep440(pieces) 459 | elif style == "pep440-pre": 460 | rendered = render_pep440_pre(pieces) 461 | elif style == "pep440-post": 462 | rendered = render_pep440_post(pieces) 463 | elif style == "pep440-old": 464 | rendered = render_pep440_old(pieces) 465 | elif style == "git-describe": 466 | rendered = render_git_describe(pieces) 467 | elif style == "git-describe-long": 468 | rendered = render_git_describe_long(pieces) 469 | else: 470 | raise ValueError("unknown style '%s'" % style) 471 | 472 | return {"version": rendered, "full-revisionid": pieces["long"], 473 | "dirty": pieces["dirty"], "error": None, 474 | "date": pieces.get("date")} 475 | 476 | 477 | def get_versions(): 478 | """Get version information or return default if unable to do so.""" 479 | # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have 480 | # __file__, we can work backwards from there to the root. Some 481 | # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which 482 | # case we can only use expanded keywords. 483 | 484 | cfg = get_config() 485 | verbose = cfg.verbose 486 | 487 | try: 488 | return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, 489 | verbose) 490 | except NotThisMethod: 491 | pass 492 | 493 | try: 494 | root = os.path.realpath(__file__) 495 | # versionfile_source is the relative path from the top of the source 496 | # tree (where the .git directory might live) to this file. Invert 497 | # this to find the root from __file__. 498 | for i in cfg.versionfile_source.split('/'): 499 | root = os.path.dirname(root) 500 | except NameError: 501 | return {"version": "0+unknown", "full-revisionid": None, 502 | "dirty": None, 503 | "error": "unable to find root of source tree", 504 | "date": None} 505 | 506 | try: 507 | pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) 508 | return render(pieces, cfg.style) 509 | except NotThisMethod: 510 | pass 511 | 512 | try: 513 | if cfg.parentdir_prefix: 514 | return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) 515 | except NotThisMethod: 516 | pass 517 | 518 | return {"version": "0+unknown", "full-revisionid": None, 519 | "dirty": None, 520 | "error": "unable to compute version", "date": None} 521 | -------------------------------------------------------------------------------- /mdsrv/mdsrv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import with_statement 4 | from __future__ import absolute_import 5 | from __future__ import print_function 6 | 7 | import os 8 | import sys 9 | import json 10 | import logging 11 | import datetime 12 | import functools 13 | 14 | # python 2 and 3 compatibility 15 | try: 16 | basestring # attempt to evaluate basestring 17 | def isstr(s): 18 | return isinstance(s, basestring) 19 | except NameError: 20 | def isstr(s): 21 | return isinstance(s, str) 22 | 23 | from .trajectory import * 24 | 25 | from flask import Flask 26 | from flask import send_from_directory 27 | from flask import request 28 | from flask import make_response, Response 29 | from flask import current_app 30 | 31 | logging.basicConfig( level=logging.DEBUG ) 32 | LOG = logging.getLogger('ngl') 33 | LOG.setLevel( logging.DEBUG ) 34 | 35 | MODULE_DIR = os.path.split( os.path.abspath( __file__ ) )[0] 36 | 37 | app = Flask(__name__) 38 | 39 | struct = [] 40 | 41 | ############################ 42 | # basic auth 43 | ############################ 44 | 45 | def check_auth( auth ): 46 | """This function is called to check if a username / 47 | password combination is valid. 48 | """ 49 | return ( 50 | auth.username == app.config.get( 'USERNAME', '' ) and 51 | auth.password == app.config.get( 'PASSWORD', '' ) 52 | ) 53 | 54 | 55 | def check_data_auth( auth, root ): 56 | DATA_AUTH = app.config.get( 'DATA_AUTH', {} ) 57 | if root in DATA_AUTH: 58 | return ( 59 | auth.username == DATA_AUTH[ root ][ 0 ] and 60 | auth.password == DATA_AUTH[ root ][ 1 ] 61 | ) 62 | else: 63 | return True 64 | 65 | 66 | def authenticate(): 67 | """Sends a 401 response that enables basic auth""" 68 | return Response( 69 | 'Could not verify your access level for that URL.\n' 70 | 'You have to login with proper credentials', 401, 71 | { 'WWW-Authenticate': 'Basic realm="Login Required"' } 72 | ) 73 | 74 | 75 | # use as decorator *after* a route decorator 76 | def requires_auth( f ): 77 | @functools.wraps( f ) 78 | def decorated( *args, **kwargs ): 79 | REQUIRE_AUTH = app.config.get( 'REQUIRE_AUTH', False ) 80 | REQUIRE_DATA_AUTH = \ 81 | app.config.get( 'REQUIRE_DATA_AUTH', False ) and not REQUIRE_AUTH 82 | DATA_AUTH = app.config.get( 'DATA_AUTH', {} ) 83 | auth = request.authorization 84 | root = kwargs.get( "root", None ) 85 | if REQUIRE_AUTH: 86 | if not auth or not check_auth( auth ): 87 | return authenticate() 88 | elif REQUIRE_DATA_AUTH and root and root in DATA_AUTH: 89 | if not auth or not check_data_auth( auth, root ): 90 | return authenticate() 91 | return f( *args, **kwargs ) 92 | return decorated 93 | 94 | 95 | #################### 96 | # helper functions 97 | #################### 98 | 99 | def get_directory( root ): 100 | DATA_DIRS = app.config.get( "DATA_DIRS", {} ) 101 | if root in DATA_DIRS: 102 | directory = os.path.join( DATA_DIRS[ root ] ) 103 | return os.path.abspath( directory ) 104 | else: 105 | return "" 106 | 107 | 108 | def get_struc_directoy( struc ): 109 | structurefile = struc.split('file/')[1] 110 | path = structurefile.split('/')[0] 111 | name = '/'.join(structurefile.split('/')[1:]) 112 | return [path, name] 113 | 114 | def crossdomain( 115 | origin=None, methods=None, headers=None, 116 | max_age=21600, attach_to_all=True, automatic_options=True 117 | ): 118 | if methods is not None: 119 | methods = ', '.join( sorted( x.upper() for x in methods ) ) 120 | if headers is not None and not isstr( headers ): 121 | headers = ', '.join( x.upper() for x in headers ) 122 | if not isstr( origin ): 123 | origin = ', '.join(origin) 124 | if isinstance( max_age, datetime.timedelta ): 125 | max_age = max_age.total_seconds() 126 | 127 | def get_methods(): 128 | if methods is not None: 129 | return methods 130 | 131 | options_resp = current_app.make_default_options_response() 132 | return options_resp.headers[ 'allow' ] 133 | 134 | def decorator(f): 135 | def wrapped_function(*args, **kwargs): 136 | if automatic_options and request.method == 'OPTIONS': 137 | resp = current_app.make_default_options_response() 138 | else: 139 | resp = make_response( f( *args, **kwargs ) ) 140 | if not attach_to_all and request.method != 'OPTIONS': 141 | return resp 142 | h = resp.headers 143 | h['Access-Control-Allow-Origin'] = origin 144 | h['Access-Control-Allow-Methods'] = get_methods() 145 | h['Access-Control-Max-Age'] = str( max_age ) 146 | if headers is not None: 147 | h['Access-Control-Allow-Headers'] = headers 148 | return resp 149 | f.provide_automatic_options = False 150 | return functools.update_wrapper( wrapped_function, f ) 151 | return decorator 152 | 153 | 154 | ############### 155 | # web app 156 | ############### 157 | 158 | @app.route( '/webapp/' ) 159 | @app.route( '/webapp/' ) 160 | @requires_auth 161 | @crossdomain( origin='*' ) 162 | def webapp( filename="index.html" ): 163 | if request.args.get('struc'): 164 | struc = request.args.get('struc') 165 | global struct 166 | structurefile = struc.split('file://')[1] 167 | path = structurefile.split('/')[0] 168 | name = '/'.join(structurefile.split('/')[1:]) 169 | struct = [path, name] 170 | directory = os.path.join( MODULE_DIR, "webapp" ) 171 | return send_from_directory( directory, filename ) 172 | 173 | 174 | ############### 175 | # file server 176 | ############### 177 | 178 | @app.route( '/file//' ) 179 | @requires_auth 180 | @crossdomain( origin='*' ) 181 | def file( root, filename ): 182 | global struct 183 | if request.args.get('struc') and struct==[]: 184 | struc = request.args.get('struc') 185 | struct = get_struc_directoy(struc) 186 | directory = get_directory( root ) 187 | if directory: 188 | return send_from_directory( directory, filename ) 189 | 190 | 191 | @app.route( '/dir/' ) 192 | @app.route( '/dir//' ) 193 | @app.route( '/dir//' ) 194 | @requires_auth 195 | @crossdomain( origin='*' ) 196 | def dir( root="", path="" ): 197 | DATA_DIRS = app.config.get( "DATA_DIRS", {} ) 198 | REQUIRE_AUTH = app.config.get( 'REQUIRE_AUTH', False ) 199 | REQUIRE_DATA_AUTH = \ 200 | app.config.get( 'REQUIRE_DATA_AUTH', False ) and not REQUIRE_AUTH 201 | DATA_AUTH = app.config.get( 'DATA_AUTH', {} ) 202 | if sys.version_info < (3,): 203 | root = root.encode( "utf-8" ) 204 | path = path.encode( "utf-8" ) 205 | dir_content = [] 206 | if root == "": 207 | for fname in DATA_DIRS.keys(): 208 | if sys.version_info < (3,): 209 | fname = unicode( fname ) 210 | if fname.startswith( '_' ): 211 | continue 212 | dir_content.append({ 213 | 'name': fname, 214 | 'path': fname, 215 | 'dir': True, 216 | 'restricted': REQUIRE_DATA_AUTH and fname in DATA_AUTH 217 | }) 218 | return json.dumps( dir_content ) 219 | directory = get_directory( root ) 220 | if sys.version_info < (3,): 221 | directory = directory.encode( "utf-8" ) 222 | if not directory: 223 | return json.dumps( dir_content ) 224 | dir_path = os.path.join( directory, path ) 225 | if path == "": 226 | dir_content.append({ 227 | 'name': '..', 228 | 'path': "", 229 | 'dir': True 230 | }) 231 | else: 232 | dir_content.append({ 233 | 'name': '..', 234 | 'path': os.path.split( os.path.join( root, path ) )[0], 235 | 'dir': True 236 | }) 237 | for fname in sorted( os.listdir( dir_path ) ): 238 | if sys.version_info < (3,): 239 | fname = fname.decode( "utf-8" ).encode( "utf-8" ) 240 | if( not fname.startswith('.') and 241 | not ( fname.startswith('#') and fname.endswith('#') ) ): 242 | fpath = os.path.join( dir_path, fname ) 243 | if os.path.isfile( fpath ): 244 | dir_content.append({ 245 | 'name': fname, 246 | 'path': os.path.join( root, path, fname ), 247 | 'size': os.path.getsize( fpath ) 248 | }) 249 | else: 250 | dir_content.append({ 251 | 'name': fname, 252 | 'path': os.path.join( root, path, fname ), 253 | 'dir': True 254 | }) 255 | for fname in get_split_xtc( dir_path ): 256 | if sys.version_info < (3,): 257 | fname = fname.decode( "utf-8" ).encode( "utf-8" ) 258 | dir_content.append({ 259 | 'name': fname, 260 | 'path': os.path.join( root, path, fname ), 261 | 'size': sum([ 262 | os.path.getsize( x ) for x in 263 | get_xtc_parts( fname, dir_path ) 264 | ]) 265 | }) 266 | return json.dumps( dir_content ) 267 | 268 | 269 | ##################### 270 | # trajectory server 271 | ##################### 272 | 273 | TRAJ_CACHE = TrajectoryCache() 274 | 275 | @app.route( '/traj/frame///', methods=['POST'] ) 276 | @requires_auth 277 | @crossdomain( origin='*' ) 278 | def traj_frame( frame, root, filename ): 279 | global struct 280 | if request.args.get('struc') and struct==[]: 281 | struc = request.args.get('struc') 282 | struct = get_struc_directoy(struc) 283 | directory = get_directory( root ) 284 | if directory: 285 | path = os.path.join( directory, filename ) 286 | else: 287 | return 288 | try: 289 | directory_struc = get_directory( struct[0] ) 290 | struc_path = os.path.join( directory_struc, struct[1] ) 291 | except: 292 | struc_path="" 293 | atom_indices = request.form.get( "atomIndices" ) 294 | if atom_indices: 295 | atom_indices = [ 296 | [ int( y ) for y in x.split( "," ) ] 297 | for x in atom_indices.split( ";" ) 298 | ] 299 | return TRAJ_CACHE.get( path, struc_path ).get_frame_string( 300 | frame, atom_indices=atom_indices 301 | ) 302 | 303 | 304 | @app.route( '/traj/numframes//' ) 305 | @requires_auth 306 | @crossdomain( origin='*' ) 307 | def traj_numframes( root, filename ): 308 | global struct 309 | if request.args.get('struc') and struct==[]: 310 | struc = request.args.get('struc') 311 | struct=get_struc_directoy(struc) 312 | directory = get_directory( root ) 313 | if directory: 314 | path = os.path.join( directory, filename ) 315 | else: 316 | return 317 | try: 318 | directory_struc = get_directory( struct[0] ) 319 | struc_path = os.path.join( directory_struc, struct[1] ) 320 | except: 321 | struc_path="" 322 | return str( TRAJ_CACHE.get( path, struc_path ).numframes ) 323 | 324 | 325 | @app.route( '/traj/path///', methods=['POST'] ) 326 | @requires_auth 327 | @crossdomain( origin='*' ) 328 | def traj_path( index, root, filename, structure ): 329 | global struct 330 | if request.args.get('struc') and struct==[]: 331 | struc = request.args.get('struc') 332 | struct=get_struc_directoy(struc) 333 | directory = get_directory( root ) 334 | if directory: 335 | path = os.path.join( directory, filename ) 336 | else: 337 | return 338 | try: 339 | directory_struc = get_directory( struct[0] ) 340 | struc_path = os.path.join( directory_struc, struct[1] ) 341 | except: 342 | struc_path="" 343 | frame_indices = request.form.get( "frameIndices" ) 344 | if frame_indices: 345 | frame_indices = None 346 | return TRAJ_CACHE.get( path, struc_path ).get_path_string( 347 | index, frame_indices=frame_indices 348 | ) 349 | 350 | ############################ 351 | # main 352 | ############################ 353 | 354 | def open_browser( app, host, port, structure=None, trajectory=None, deltaTime=None, timeOffset=None, script=None ): 355 | if not app.config.get( "BROWSER_OPENED", False ): 356 | import webbrowser 357 | url = "http://" + host + ":" + str(port) + "/webapp" 358 | if structure: 359 | url += "?struc=file://cwd/" + structure 360 | if trajectory: 361 | url += "&traj=file://cwd/" + trajectory 362 | if deltaTime != "0.00": 363 | url += "&dt=" + deltaTime 364 | if timeOffset != "0.00": 365 | url += "&to=" + timeOffset 366 | if script: 367 | url += "?load=file://cwd/" + script 368 | 369 | webbrowser.open( url, new=2, autoraise=True ) 370 | app.config.BROWSER_OPENED = True 371 | 372 | 373 | # based on http://stackoverflow.com/a/27598916 374 | def patch_socket_bind( on_bind ): 375 | try: 376 | import socketserver 377 | except ImportError: 378 | import SocketServer as socketserver 379 | original_socket_bind = socketserver.TCPServer.server_bind 380 | def socket_bind_wrapper(self): 381 | ret = original_socket_bind(self) 382 | if on_bind: 383 | host, port = self.socket.getsockname() 384 | on_bind( host, port ) 385 | # Recover original implementation 386 | socketserver.TCPServer.server_bind = original_socket_bind 387 | return ret 388 | socketserver.TCPServer.server_bind = socket_bind_wrapper 389 | 390 | 391 | def parse_args(): 392 | from argparse import ArgumentParser 393 | parser = ArgumentParser( description="" ) 394 | parser.add_argument( 'structure', type=str, nargs='?', default="", 395 | help="Path to a structure/topology file. Supported are pdb, gro and "+\ 396 | "cif files. The file must be included within the current working "+\ 397 | "directory (cwd) or a sub directory." ) 398 | parser.add_argument( 'trajectory', type=str, nargs='?', default="", 399 | help="Path to a trajectory file. Supported are xtc/trr, nc/netcdf, "+\ 400 | "lammpstrj, h5, dtr, arc, tng, and dcd "+\ 401 | "files. The file must be included within the current working "+\ 402 | "directory (cwd) or a sub directory." ) 403 | parser.add_argument( '--deltaTime', '-dt', type=str, default="0.00", 404 | help="Delta time of the trajectory (to convert frame steps into ns time scale)." ) 405 | parser.add_argument( '--timeOffset', '-to', type=str, default="0.00", 406 | help="Time offset of the trajectory (to convert frame steps into ns time scale)." ) 407 | parser.add_argument( '--script', '-sc', type=str, default="", 408 | help="Path to an ngl/js script file. The file must be included within "+\ 409 | "the current working directory (cwd) or a sub directory. See "+\ 410 | "https://github.com/arose/mdsrv/blob/master/script.ngl or the "+\ 411 | "documentation for an example." ) 412 | parser.add_argument( '--configure', '-cfg', type=str, help="Path to a "+\ 413 | "configuration file. "+\ 414 | "See https://github.com/arose/mdsrv/blob/master/app.cfg.sample "+\ 415 | "or the documentation for an example." ) 416 | parser.add_argument( '--host', '-ho', type=str, default="127.0.0.1", 417 | help="Host for the server. The default is 127.0.0.1 or localhost. "+\ 418 | "To make the server available to other clients set to your IP "+\ 419 | "address or to 0.0.0.0 for automatic host determination. This is "+\ 420 | "overwritten by the PORT in a configuration file." ) 421 | parser.add_argument( '--port', '-po', type=int, default=0, help="Port to bind "+\ 422 | "the server to. The default is 0 for an automatic choose of a "+\ 423 | "free port. Fails when the given port is already in use on "+\ 424 | "your machine. This is overwritten by the PORT in a configuration file." ) 425 | args = parser.parse_args() 426 | return args 427 | 428 | 429 | def app_config( path ): 430 | if path: 431 | if not path.startswith( "/" ): 432 | path = os.path.join( os.getcwd(), path ) 433 | app.config.from_pyfile( os.path.abspath( path ) ) 434 | 435 | 436 | def entry_point(): 437 | main() 438 | 439 | 440 | def main(): 441 | args = parse_args() 442 | app_config( args.configure ) 443 | DATA_DIRS = app.config.get( "DATA_DIRS", {} ) 444 | DATA_DIRS.update( { 445 | "cwd": os.path.abspath( os.getcwd() ) 446 | } ) 447 | app.config[ "DATA_DIRS" ] = DATA_DIRS 448 | def on_bind( host, port ): 449 | open_browser( app, host, port, args.structure, args.trajectory, args.deltaTime, args.timeOffset, args.script ) 450 | patch_socket_bind( on_bind ) 451 | app.run( 452 | debug=app.config.get( 'DEBUG', False ), 453 | host=app.config.get( 'HOST', args.host ), 454 | port=app.config.get( 'PORT', args.port ), 455 | threaded=True, 456 | processes=1 457 | ) 458 | 459 | 460 | if __name__ == '__main__': 461 | main() 462 | -------------------------------------------------------------------------------- /mdsrv/trajectory.py: -------------------------------------------------------------------------------- 1 | # cython: c_string_type=str, c_string_encoding=ascii 2 | from __future__ import absolute_import 3 | from __future__ import print_function 4 | 5 | import os 6 | import re 7 | import sys 8 | import array 9 | import collections 10 | import numpy as np 11 | import warnings 12 | 13 | importarray = [ False, False, False ]#, False ] 14 | 15 | try: 16 | import MDAnalysis 17 | importarray[1] = True 18 | except ImportError: 19 | pass 20 | 21 | try: 22 | import mdtraj as md 23 | from mdtraj.formats import * 24 | importarray[0] = True 25 | importarray[2] = True 26 | except ImportError: 27 | pass 28 | 29 | try: 30 | import cPickle as pickle 31 | except ImportError: 32 | import pickle 33 | 34 | if sys.version_info > (3,): 35 | long = int 36 | 37 | 38 | def get_xtc_parts( name, directory ): 39 | pattern = re.escape( name[1:-4] ) + "\.part[0-9]{4,4}\.(xtc|trr)$" 40 | parts = [] 41 | for f in os.listdir( directory ): 42 | m = re.match( pattern, f ) 43 | if m and os.path.isfile( os.path.join( directory, f ) ): 44 | parts.append( os.path.join( directory, f ) ) 45 | return sorted( parts ) 46 | 47 | 48 | def get_split_xtc( directory ): 49 | pattern = "(.*)\.part[0-9]{4,4}\.(xtc|trr)$" 50 | split = collections.defaultdict( int ) 51 | for f in os.listdir( directory ): 52 | m = re.match( pattern, f ) 53 | if( m ): 54 | split[ "@" + m.group(1) + "." + m.group(2) ] += 1 55 | if sys.version_info > (3,): 56 | return sorted( [ k for k, v in split.items() if v > 1 ] ) 57 | else: 58 | return sorted( [ k for k, v in split.iteritems() if v > 1 ] ) 59 | 60 | 61 | def get_trajectory( file_name, struc_path ): 62 | ext = os.path.splitext( file_name )[1].lower() 63 | types = { 64 | ".xtc": [ XtcTrajectory, MDAnalysisTrajectory, MDTrajTrajectory ], 65 | ".trr": [ TrrTrajectory, MDAnalysisTrajectory, MDTrajTrajectory ], 66 | ".netcdf": [ NetcdfTrajectory, MDAnalysisTrajectory, MDTrajTrajectory ], 67 | ".nc": [ NetcdfTrajectory, MDAnalysisTrajectory, MDTrajTrajectory ], 68 | ".dcd": [ DcdTrajectory, MDAnalysisTrajectory, DcdTrajectory ], 69 | ".gro": [ GroTrajectory, MDAnalysisTrajectory, GroTrajectory ], 70 | ".pdb": [ None, MDAnalysisTrajectory, MDTrajTrajectory ], 71 | ".lammpstrj": [ LammpsTrajectory, None, LammpsTrajectory ], 72 | ".gz": [ None, MDAnalysisTrajectory, MDTrajTrajectory ], 73 | ".xyz": [ XyzTrajectory, MDAnalysisTrajectory, MDTrajTrajectory ], 74 | ".mdcrd": [ None, MDAnalysisTrajectory, MDTrajTrajectory ], 75 | ".binpos": [ BinposTrajectory, None, MDTrajTrajectory ], 76 | ".h5": [ Hdf5Trajectory, None, MDTrajTrajectory ], 77 | ".dtr": [ DtrTrajectory, None, DtrTrajectory ], 78 | ".arc": [ ArcTrajectory, None, ArcTrajectory ], 79 | ".tng": [ TngTrajectory, None, MDTrajTrajectory ], 80 | ".dms": [ None, MDAnalysisTrajectory, None ], 81 | ".crd": [ None, MDAnalysisTrajectory, None ], 82 | '.trj': [ None, MDAnalysisTrajectory, None ], 83 | '.trz': [ None, MDAnalysisTrajectory, None ], 84 | '.ent': [ None, MDAnalysisTrajectory, None ], 85 | '.ncdf': [ None, MDAnalysisTrajectory, None ], 86 | } 87 | if ext in types: 88 | if struc_path!="" and importarray[1]: 89 | importarray[0] = False 90 | for index, elem in enumerate(importarray): 91 | if elem & (types[ext][index] != None ): 92 | return types[ ext ][index]( file_name, struc_path ) 93 | else: 94 | raise Exception( "extension '%s' not supported" % ext ) 95 | 96 | 97 | class TrajectoryCache( object ): 98 | def __init__( self ): 99 | self.cache = {} 100 | self.mtime = {} 101 | self.parts = {} 102 | 103 | def add( self, path, pathList, struc_path ): 104 | self.cache[ path ] = TrajectoryCollection( pathList, struc_path ) 105 | # initial mtimes 106 | mtime = {} 107 | for partPath in pathList: 108 | mtime[ partPath ] = os.path.getmtime( partPath ) 109 | self.mtime[ path ] = mtime 110 | # initial pathList 111 | self.parts[ path ] = pathList 112 | 113 | def get( self, path, struc_path ): 114 | stem = os.path.basename( path ) 115 | if stem.startswith( "@" ): 116 | pathList = frozenset( 117 | get_xtc_parts( stem, os.path.dirname( path ) ) 118 | ) 119 | else: 120 | pathList = frozenset( [ path ] ) 121 | if path not in self.cache: 122 | self.add( path, pathList, struc_path ) 123 | elif pathList != self.parts[ path ]: 124 | # print( "pathList changed, rebuilding" ) 125 | del self.cache[ path ] 126 | self.add( path, pathList ) 127 | else: 128 | updateRequired = False 129 | mtime = self.mtime[ path ] 130 | for partPath in pathList: 131 | if mtime[ partPath ] < os.path.getmtime( partPath ): 132 | updateRequired = True 133 | if updateRequired: 134 | # print( "file modified, updating" ) 135 | self.cache[ path ].update( True ) 136 | return self.cache[ path ] 137 | 138 | 139 | class Trajectory( object ): 140 | def __init__( self, file_name, struc_name ): 141 | pass 142 | 143 | def update( self, force=False ): 144 | pass 145 | 146 | def _get_frame( self, index ): 147 | # return box, coords, time 148 | # box, coords in angstrom 149 | # time in ??? 150 | pass 151 | 152 | def get_frame( self, index, atom_indices=None ): 153 | box, coords, time = self._get_frame( int( index ) ) 154 | if atom_indices: 155 | coords = np.concatenate([ 156 | coords[ i:j ].ravel() for i, j in atom_indices 157 | ]) 158 | return { 159 | "numframes": self.numframes, 160 | "time": time, 161 | "box": box, 162 | "coords": coords 163 | } 164 | 165 | def get_frame_string( self, index, atom_indices=None ): 166 | frame = self.get_frame( index, atom_indices=atom_indices ) 167 | return ( 168 | array.array( "i", [ frame[ "numframes" ] ] ).tostring() + 169 | array.array( "f", [ frame[ "time" ] ] ).tostring() + 170 | array.array( "f", frame[ "box" ].flatten() ).tostring() + 171 | array.array( "f", frame[ "coords" ].flatten() ).tostring() 172 | ) 173 | 174 | def get_path( self, atom_index, frame_indices=None ): 175 | if( frame_indices ): 176 | size = len( frame_indices ) 177 | frames = map( int, frame_indices ) 178 | else: 179 | size = self.numframes 180 | frames = range( size ) 181 | path = np.zeros( ( size, 3 ), dtype=np.float32 ) 182 | for i in frames: 183 | box, coords, time = self._get_frame( i ) 184 | path[ i ] = coords[ atom_index ] 185 | return path 186 | 187 | def get_path_string( self, atom_index, frame_indices=None ): 188 | path = self.get_path( atom_index, frame_indices=frame_indices ) 189 | return array.array( "f", path.flatten() ).tostring() 190 | 191 | def __del__( self ): 192 | pass 193 | 194 | 195 | class TrajectoryCollection( Trajectory ): 196 | def __init__( self, parts, struc_path ): 197 | self.parts = [] 198 | for file_name in sorted( parts ): 199 | self.parts.append( get_trajectory( file_name, struc_path ) ) 200 | self.box = self.parts[ 0 ].box 201 | self._update_numframes() 202 | 203 | def _update_numframes( self ): 204 | self.numframes = 0 205 | for trajectory in self.parts: 206 | self.numframes += trajectory.numframes 207 | 208 | def update( self, force=False ): 209 | for trajectory in self.parts: 210 | trajectory.update( force=force ) 211 | self._update_numframes() 212 | 213 | def _get_frame( self, index ): 214 | i = 0 215 | for trajectory in self.parts: 216 | if index < i + trajectory.numframes: 217 | break 218 | i += trajectory.numframes 219 | return trajectory._get_frame( index - i ) 220 | 221 | def __del__( self ): 222 | for trajectory in self.parts: 223 | trajectory.__del__() 224 | 225 | 226 | class TngTrajectory( Trajectory ): 227 | def __init__( self, file_name, struc_name ): 228 | self.file_name = file_name 229 | self.tng = TNGTrajectoryFile( self.file_name, 'r' ) 230 | self.tng_traj = self.tng.read(n_frames=1) 231 | self.tng.seek(0, whence=2) 232 | 233 | self.numatoms = len(self.tng_traj[0][0]) 234 | self.numframes = self.tng.tell() 235 | 236 | self.x = None 237 | self.box = np.zeros( ( 3, 3 ), dtype=np.float32 ) 238 | self.time = 0.0 239 | 240 | def _get_frame( self, index ): 241 | self.tng.seek(index) 242 | xyz, time, boxlength = self.tng.read(n_frames=1) 243 | self.box[ 0, 0 ] = boxlength[ 0 ][ 0 ][ 0 ] * 10 244 | self.box[ 1, 1 ] = boxlength[ 0 ][ 1 ][ 1 ] * 10 245 | self.box[ 2, 2 ] = boxlength[ 0 ][ 2 ][ 2 ] * 10 246 | self.x = xyz[ 0 ] * 10 247 | self.time = time[ 0 ] 248 | return self.box, self.x, self.time 249 | 250 | def __del__( self ): 251 | try: 252 | self.tng.close() 253 | except: 254 | pass 255 | 256 | 257 | class ArcTrajectory( Trajectory ): 258 | def __init__( self, file_name, struc_name ): 259 | self.file_name = file_name 260 | self.arc = ArcTrajectoryFile( self.file_name, 'r' ) 261 | self.arc_traj = self.arc.read() 262 | 263 | self.numatoms = len(self.arc_traj[0][0]) 264 | self.numframes = len(self.arc_traj[0]) 265 | 266 | self.x = None 267 | self.box = np.zeros( ( 3, 3 ), dtype=np.float32 ) 268 | self.time = 0.0 269 | 270 | def _get_frame( self, index ): 271 | xyz, boxlength, boxangles = self.arc_traj 272 | try: 273 | self.box[ 0, 0 ] = boxlength[ index ][ 0 ] 274 | self.box[ 1, 1 ] = boxlength[ index ][ 1 ] 275 | self.box[ 2, 2 ] = boxlength[ index ][ 2 ] 276 | except: pass 277 | self.x = xyz[ index ] 278 | return self.box, self.x, self.time 279 | 280 | def __del__( self ): 281 | try: 282 | self.arc.close() 283 | except: 284 | pass 285 | 286 | 287 | class DtrTrajectory( Trajectory ): 288 | def __init__( self, file_name, struc_name ): 289 | self.file_name = file_name 290 | self.dtr = DTRTrajectoryFile( self.file_name, 'r' ) 291 | self.dtr_traj = self.dtr.read(n_frames=1) 292 | self.dtr.seek(0, whence=2) 293 | 294 | self.numatoms = len(self.dtr_traj[0][0]) 295 | self.numframes = self.dtr.tell() 296 | 297 | self.x = None 298 | self.box = np.zeros( ( 3, 3 ), dtype=np.float32 ) 299 | self.time = 0.0 300 | 301 | def _get_frame( self, index ): 302 | self.dtr.seek(index) 303 | xyz, times, boxlength, boxangles = self.dtr.read(n_frames=1) 304 | self.box[ 0, 0 ] = boxlength[ 0 ][ 0 ] 305 | self.box[ 1, 1 ] = boxlength[ 0 ][ 1 ] 306 | self.box[ 2, 2 ] = boxlength[ 0 ][ 2 ] 307 | self.x = xyz[ 0 ] 308 | self.time = times[ 0 ] 309 | return self.box, self.x, self.time 310 | 311 | def __del__( self ): 312 | try: 313 | self.dtr.close() 314 | except: 315 | pass 316 | 317 | 318 | class Hdf5Trajectory( Trajectory ): 319 | def __init__( self, file_name, struc_name ): 320 | self.file_name = file_name 321 | self.hdf5 = HDF5TrajectoryFile( self.file_name, 'r' ) 322 | self.hdf5_traj = self.hdf5.read(n_frames=1) 323 | self.hdf5.seek(0, whence=2) 324 | 325 | self.numatoms = len(self.hdf5_traj[0][0]) 326 | self.numframes = self.hdf5.tell() 327 | 328 | self.x = None 329 | self.box = np.zeros( ( 3, 3 ), dtype=np.float32 ) 330 | self.time = 0.0 331 | 332 | def _get_frame( self, index ): 333 | self.hdf5.seek(index) 334 | self.hdf5_traj = self.hdf5.read(n_frames=1) 335 | xyz = self.hdf5_traj.coordinates 336 | boxlength = self.hdf5_traj.cell_lengths 337 | cell_lengths = self.hdf5_traj.cell_angles 338 | time = self.hdf5_traj.time 339 | self.time = time[ 0 ] 340 | self.box[ 0, 0 ] = cell_lengths[ 0 ][ 0 ] * 10 341 | self.box[ 1, 1 ] = cell_lengths[ 0 ][ 1 ] * 10 342 | self.box[ 2, 2 ] = cell_lengths[ 0 ][ 2 ] * 10 343 | self.x = xyz[ 0 ] * 10 344 | return self.box, self.x, self.time 345 | 346 | def __del__( self ): 347 | try: 348 | self.hdf5.close() 349 | except: 350 | pass 351 | 352 | 353 | class LammpsTrajectory( Trajectory ): 354 | def __init__( self, file_name, struc_name ): 355 | self.file_name = file_name 356 | self.lammps = LAMMPSTrajectoryFile( self.file_name, 'r' ) 357 | self.lammps_traj = self.lammps.read(n_frames=1) 358 | looping(self.lammps, self.lammps_traj[0]) 359 | 360 | self.numatoms = len(self.lammps_traj[0][0]) 361 | self.numframes = self.lammps.tell() 362 | 363 | self.x = None 364 | self.box = np.zeros( ( 3, 3 ), dtype=np.float32 ) 365 | self.time = 0.0 366 | 367 | def _get_frame( self, index ): 368 | self.lammps.seek(index) 369 | xyz, cell_lengths, boxangles = self.lammps.read(n_frames=1) 370 | self.box[ 0, 0 ] = cell_lengths[ 0 ][ 0 ] 371 | self.box[ 1, 1 ] = cell_lengths[ 0 ][ 1 ] 372 | self.box[ 2, 2 ] = cell_lengths[ 0 ][ 2 ] 373 | self.x = xyz[0] 374 | return self.box, self.x, self.time 375 | 376 | def __del__( self ): 377 | try: 378 | self.lammps.close() 379 | except: 380 | pass 381 | 382 | 383 | class GroTrajectory( Trajectory ): 384 | def __init__( self, file_name, struc_name ): 385 | self.file_name = file_name 386 | self.gro = GroTrajectoryFile( self.file_name, 'r' ) 387 | self.gro_traj = self.gro.read() 388 | self.numatoms = len(self.gro_traj[0][0]) 389 | self.numframes = len(self.gro_traj[0]) 390 | 391 | self.x = None 392 | self.box = np.zeros( ( 3, 3 ), dtype=np.float32 ) 393 | self.time = 0.0 394 | 395 | def _get_frame( self, index ): 396 | xyz, time, unitcell = self.gro_traj 397 | self.box = unitcell[ index ] * 10 398 | self.x = xyz[ index ] * 10 399 | try: 400 | self.time = time[ index ] 401 | except: 402 | self.time = index 403 | return self.box, self.x, self.time 404 | 405 | def __del__( self ): 406 | try: 407 | self.gro.close() 408 | except: 409 | pass 410 | 411 | 412 | class TrrTrajectory( Trajectory ): 413 | def __init__( self, file_name, struc_name ): 414 | self.file_name = file_name 415 | self.trr = TRRTrajectoryFile( self.file_name, 'r' ) 416 | self.trr_traj = self.trr.read(n_frames=1) 417 | looping2(self.trr, self.trr_traj[0]) 418 | 419 | self.numatoms = len(self.trr_traj[0][0]) 420 | self.numframes = self.trr.tell()+1 421 | 422 | self.x = None 423 | self.box = np.zeros( ( 3, 3 ), dtype=np.float32 ) 424 | self.time = 0.0 425 | 426 | def _get_frame( self, index ): 427 | self.trr.seek(index) 428 | xyz, time, step, box, lambd = self.trr.read(n_frames=1) 429 | self.box = box[ 0 ] * 10 430 | self.x = xyz[ 0 ] * 10 431 | self.time = time[ 0 ] 432 | return self.box, self.x, self.time 433 | 434 | def __del__( self ): 435 | try: 436 | self.trr.close() 437 | except: 438 | pass 439 | 440 | 441 | def looping2(traj, r_traj): 442 | stepsizes = [100000, 50000, 10000, 5000, 1000, 500, 100, 10, 1] 443 | for index, stepsize in enumerate(stepsizes): 444 | r = r_traj 445 | while r.size!=0: 446 | try: 447 | traj.seek(stepsize, whence=1) 448 | r = traj.read(n_frames=1)[0] 449 | stepsize += stepsizes[index] 450 | stepsize -= 1 451 | except: 452 | traj.seek(-1, whence=1) 453 | break 454 | try: 455 | stepsize -=stepsizes[index] 456 | traj.seek(-stepsize, whence=1) 457 | r = traj.read(n_frames=1)[0] 458 | except: break 459 | 460 | 461 | class XtcTrajectory( Trajectory ): 462 | def __init__( self, file_name, struc_name ): 463 | self.file_name = file_name 464 | self.xtc = XTCTrajectoryFile( self.file_name, 'r' ) 465 | self.xtc_traj = self.xtc.read(n_frames=1) 466 | looping2(self.xtc, self.xtc_traj[0]) 467 | 468 | self.numatoms = len(self.xtc_traj[0][0]) 469 | self.numframes = self.xtc.tell() 470 | 471 | self.x = None 472 | self.box = np.zeros( ( 3, 3 ), dtype=np.float32 ) 473 | self.time = 0.0 474 | 475 | def _get_frame( self, index ): 476 | self.xtc.seek(index) 477 | xyz, time, step, box = self.xtc.read(n_frames=1) 478 | self.box = box[0] * 10 479 | self.x = xyz[0] * 10 480 | self.time = time[0] 481 | return self.box, self.x, self.time 482 | 483 | def __del__( self ): 484 | try: 485 | self.xtc.close() 486 | except: 487 | pass 488 | 489 | 490 | class NetcdfTrajectory( Trajectory ): 491 | def __init__( self, file_name, struc_name ): 492 | # http://ambermd.org/netcdf/nctraj.pdf 493 | self.file_name = file_name 494 | self.netcdf = NetCDFTrajectoryFile( self.file_name ) 495 | self.numatoms = self.netcdf.n_atoms 496 | self.numframes = self.netcdf.n_frames 497 | 498 | self.x = None 499 | self.box = np.zeros( ( 3, 3 ), dtype=np.float32 ) 500 | self.time = 0.0 501 | 502 | def _get_frame( self, index ): 503 | self.netcdf.seek(index) 504 | xyz, time, cell_lengths, boxangles = self.netcdf.read(n_frames=1) 505 | try: 506 | self.box[ 0, 0 ] = cell_lengths[ 0 ][ 0 ] 507 | self.box[ 1, 1 ] = cell_lengths[ 0 ][ 1 ] 508 | self.box[ 2, 2 ] = cell_lengths[ 0 ][ 2 ] 509 | except: pass 510 | self.x = xyz[0] 511 | try: 512 | self.time = time[0] 513 | except: 514 | self.time = index 515 | return self.box, self.x, self.time 516 | 517 | def __del__( self ): 518 | try: 519 | self.netcdf.close() 520 | except: 521 | pass 522 | 523 | 524 | def looping(traj, r_traj): 525 | stepsizes = [100000, 50000, 10000, 5000, 1000, 500, 100, 10, 1] 526 | for index, stepsize in enumerate(stepsizes): 527 | r = r_traj 528 | while r.size!=0: 529 | try: 530 | traj.seek(stepsize, whence=1) 531 | r = traj.read(n_frames=1)[0] 532 | stepsize += stepsizes[index] 533 | except: break 534 | try: 535 | stepsize -=stepsizes[index] 536 | traj.seek(-stepsize, whence=1) 537 | r = traj.read(n_frames=1)[0] 538 | except: break 539 | 540 | 541 | class DcdTrajectory( Trajectory ): 542 | def __init__( self, file_name, struc_name ): 543 | self.file_name = file_name 544 | self.dcd = DCDTrajectoryFile( self.file_name, 'r' ) 545 | self.dcd_traj = self.dcd.read(n_frames=1) 546 | looping(self.dcd, self.dcd_traj[0]) 547 | 548 | self.numatoms = len(self.dcd_traj[0][0]) 549 | self.numframes = self.dcd.tell()-2 550 | 551 | self.x = None 552 | self.box = np.zeros( ( 3, 3 ), dtype=np.float32 ) 553 | self.time = 0.0 554 | 555 | def _get_frame( self, index ): 556 | self.dcd.seek(index) 557 | xyz, cell_lengths, boxangles = self.dcd.read(n_frames=1) 558 | try: 559 | self.box[ 0, 0 ] = cell_lengths[ 0 ][ 0 ] 560 | self.box[ 1, 1 ] = cell_lengths[ 0 ][ 1 ] 561 | self.box[ 2, 2 ] = cell_lengths[ 0 ][ 2 ] 562 | except: pass 563 | self.x = xyz[ 0 ] 564 | return self.box, self.x, self.time 565 | 566 | def __del__( self ): 567 | try: 568 | self.dcd.close() 569 | except: 570 | pass 571 | 572 | 573 | class MdcrdTrajectory( Trajectory ): 574 | def __init__( self, file_name, struc_name ): 575 | self.file_name = file_name 576 | self.struc_name = struc_name 577 | topology = md.load( self.struc_name ).topology 578 | self.numatoms = topology.n_atoms 579 | self.mdcrd = MDCRDTrajectoryFile( self.file_name, self.numatoms ) 580 | self.mdcrd_traj = self.mdcrd.read() 581 | self.numframes = len(self.mdcrd_traj[0]) 582 | self.x = None 583 | self.box = np.zeros( ( 3, 3 ), dtype=np.float32 ) 584 | self.time = 0.0 585 | 586 | def _get_frame( self, index ): 587 | xyz, cell_lengths = self.mdcrd_traj 588 | try: 589 | self.box[ 0, 0 ] = cell_lengths[ index ][ 0 ] 590 | self.box[ 1, 1 ] = cell_lengths[ index ][ 1 ] 591 | self.box[ 2, 2 ] = cell_lengths[ index ][ 2 ] 592 | except: pass 593 | self.x = xyz[ index ] 594 | return self.box, self.x, self.time 595 | 596 | def __del__( self ): 597 | try: 598 | self.mdcrd.close() 599 | except: 600 | pass 601 | 602 | 603 | class BinposTrajectory( Trajectory ): 604 | def __init__( self, file_name, struc_name ): 605 | self.file_name = file_name 606 | self.binpos = BINPOSTrajectoryFile( self.file_name, 'r' ) 607 | self.binpos_traj = self.binpos.read(n_frames=1) 608 | self.binpos.seek(-1, whence=2) 609 | 610 | self.numatoms = len(self.binpos_traj[0][0]) 611 | self.numframes = self.binpos.tell() 612 | 613 | self.x = None 614 | self.box = np.zeros( ( 3, 3 ), dtype=np.float32 ) 615 | self.time = 0.0 616 | 617 | def _get_frame( self, index ): 618 | self.binpos.seek(index) 619 | xyz = self.binpos.read(n_frames=1) 620 | self.x = xyz[0] 621 | return self.box, self.x, self.time 622 | 623 | def __del__( self ): 624 | try: 625 | self.binpos.close() 626 | except: 627 | pass 628 | 629 | 630 | class XyzTrajectory( Trajectory ): 631 | def __init__( self, file_name, struc_name ): 632 | self.file_name = file_name 633 | self.xyz = XYZTrajectoryFile( self.file_name, 'r' ) 634 | self.xyz_traj = self.xyz.read(n_frames=1) 635 | looping(self.xyz, self.xyz_traj) 636 | 637 | self.numframes = self.xyz.tell() 638 | self.numatoms = len(self.xyz_traj[0][0]) 639 | 640 | self.x = None 641 | self.box = np.zeros( ( 3, 3 ), dtype=np.float32 ) 642 | self.time = 0.0 643 | 644 | def _get_frame( self, index ): 645 | self.xyz.seek(index) 646 | xyz = self.xyz.read(n_frames=1) 647 | self.x = xyz[0] 648 | return self.box, self.x, self.time 649 | 650 | def __del__( self ): 651 | try: 652 | self.xyz.close() 653 | except: 654 | pass 655 | 656 | 657 | class MDTrajTrajectory( Trajectory ): 658 | def __init__( self, file_name, struc_path ): 659 | self.file_name = file_name 660 | self.struc_name = struc_path 661 | filesize = os.path.getsize(self.file_name) 662 | if filesize <= 1500000000: 663 | self.frame = md.load(self.file_name, top=self.struc_name) 664 | self.numframes = self.frame.n_frames 665 | self.numatoms = self.frame.n_atoms 666 | else: 667 | first_frame = md.load_frame(self.file_name, 0, self.struc_name) 668 | times = 0 669 | for chunk in md.iterload(self.file_name, top=self.struc_name, chunk=100, atom_indices=[1]): 670 | times += chunk.n_frames 671 | self.numframes = times 672 | self.numatoms = first_frame.n_atoms 673 | self.frame = None 674 | self.x = None 675 | self.box = np.zeros( ( 3, 3 ), dtype=np.float32 ) 676 | self.time = 0.0 677 | 678 | def _get_frame( self, index ): 679 | if self.frame: 680 | frame = self.frame[index] 681 | else: 682 | frame = md.load_frame(self.file_name, index, self.struc_name) 683 | try: 684 | self.box = frame.unitcell_vectors * 10 685 | except: 686 | self.box = np.zeros( ( 3, 3 ), dtype=np.float32 ) 687 | pass 688 | self.x = frame.xyz * 10 689 | self.time = frame.time 690 | return self.box, self.x, self.time 691 | 692 | 693 | class MDAnalysisTrajectory( Trajectory ): 694 | def __init__( self, file_name, struc_path ): 695 | self.file_name = file_name 696 | self.struc_name = struc_path 697 | self.universe = MDAnalysis.Universe(self.struc_name, self.file_name) 698 | self.numatoms = self.universe.trajectory.n_atoms 699 | self.numframes = self.universe.trajectory.n_frames 700 | self.x = None 701 | self.box = np.zeros( ( 3, 3 ), dtype=np.float32 ) 702 | self.time = 0.0 703 | 704 | def _get_frame( self, index ): 705 | frame = self.universe.trajectory[index] 706 | try: 707 | self.box[ 0, 0 ] = frame.dimensions[ 0 ] 708 | self.box[ 1, 1 ] = frame.dimensions[ 1 ] 709 | self.box[ 2, 2 ] = frame.dimensions[ 2 ] 710 | except: 711 | self.box = np.zeros( ( 3, 3 ), dtype=np.float32 ) 712 | pass 713 | self.x = frame.positions 714 | self.time = frame.frame 715 | return self.box, self.x, self.time 716 | -------------------------------------------------------------------------------- /mdsrv/webapp/css/dark.css: -------------------------------------------------------------------------------- 1 | 2 | /* text color */ 3 | 4 | * { 5 | color: #B8B8B8; 6 | } 7 | 8 | input, 9 | textarea, 10 | button, 11 | select, 12 | option { 13 | color: #111111; 14 | } 15 | 16 | input[type=range]:focus:after { 17 | color: #B8B8B8; 18 | } 19 | 20 | a{ 21 | color: #719eff; 22 | } 23 | 24 | input.Number, input.File { 25 | color: #A1B56C; 26 | } 27 | 28 | 29 | /* background color */ 30 | 31 | .OverlayPanel, 32 | #menubar, 33 | #menubar .menu .options, 34 | #sidebar, 35 | #toolbar 36 | { 37 | background-color: #1C1F26; 38 | } 39 | 40 | 41 | /* border color */ 42 | 43 | .OverlayPanel, 44 | #menubar .menu .options hr, 45 | #sidebar > .Panel, 46 | #sidebar > .Content > .Panel { 47 | border-color: #444444; 48 | } 49 | 50 | 51 | /* special */ 52 | 53 | #menubar .menu .options .option:hover, 54 | .option:hover > .Icon, 55 | .option:hover > .Text { 56 | color: #181818; 57 | background-color: #7CAFC2; 58 | } 59 | 60 | .highlight, 61 | .highlight > .Icon, 62 | .highlight > .Text { 63 | color: #181818; 64 | background-color: #7CAFC2; 65 | } 66 | 67 | .EllipsisMultilineText:after { 68 | background: linear-gradient(to right, rgba(0, 0, 0, 0), #1C1F26 50%, #1C1F26); 69 | } 70 | -------------------------------------------------------------------------------- /mdsrv/webapp/css/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.1.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.1.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff?v=4.1.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.1.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1)}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-square:before,.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"} -------------------------------------------------------------------------------- /mdsrv/webapp/css/light.css: -------------------------------------------------------------------------------- 1 |  2 | /* text color */ 3 | 4 | * { 5 | color: #555555; 6 | } 7 | 8 | input, 9 | textarea, 10 | button, 11 | select, 12 | option { 13 | color: #111111; 14 | } 15 | 16 | input[type=range]:focus:after { 17 | color: #555555; 18 | } 19 | 20 | a{ 21 | color: #1956d8; 22 | } 23 | 24 | input.Number, input.File { 25 | color: #79952e; 26 | } 27 | 28 | 29 | /* background color */ 30 | 31 | .OverlayPanel, 32 | #menubar, 33 | #menubar .menu .options, 34 | #sidebar, 35 | #toolbar 36 | { 37 | background-color: #e1e4eb; 38 | } 39 | 40 | 41 | /* border color */ 42 | 43 | .OverlayPanel, 44 | #menubar .menu .options hr, 45 | #sidebar > .Panel, 46 | #sidebar > .Content > .Panel { 47 | border-color: #cccccc; 48 | } 49 | 50 | 51 | /* special */ 52 | 53 | #menubar .menu .options .option:hover, 54 | .option:hover > .Icon, 55 | .option:hover > .Text { 56 | color: #D8D8D8; 57 | background-color: #345a69; 58 | } 59 | 60 | .highlight, 61 | .highlight > .Icon, 62 | .highlight > .Text { 63 | color: #D8D8D8; 64 | background-color: #345a69; 65 | } 66 | 67 | .EllipsisMultilineText:after { 68 | background: linear-gradient(to right, rgba(255, 255, 255, 0), #e1e4eb 50%, #e1e4eb); 69 | } 70 | -------------------------------------------------------------------------------- /mdsrv/webapp/css/main.css: -------------------------------------------------------------------------------- 1 | 2 | * { 3 | vertical-align: middle; 4 | } 5 | 6 | /* Webkit micro scrollbars */ 7 | 8 | ::-webkit-scrollbar { 9 | width:9px; 10 | height:9px; 11 | } 12 | 13 | ::-webkit-scrollbar-track { 14 | -webkit-border-radius:5px; 15 | border-radius:5px; 16 | background:rgba(140,140,140,0.1); 17 | } 18 | 19 | ::-webkit-scrollbar-thumb { 20 | -webkit-border-radius:5px; 21 | border-radius:5px; 22 | background:rgba(140,140,140,0.2); 23 | } 24 | 25 | ::-webkit-scrollbar-thumb:hover { 26 | background:rgba(140,140,140,0.4); 27 | } 28 | 29 | ::-webkit-scrollbar-thumb:window-inactive { 30 | background:rgba(140,140,140,0.5); 31 | } 32 | 33 | /* elmement */ 34 | 35 | body { 36 | font-family: Arial, sans-serif; 37 | font-size: 14px; 38 | margin: 0; 39 | overflow: hidden; 40 | } 41 | 42 | hr { 43 | border: 0px; 44 | border-top: 1px solid #ccc; 45 | } 46 | 47 | button { 48 | position: relative; 49 | margin-left: 0px; 50 | } 51 | 52 | select { 53 | margin-left: 1px; 54 | } 55 | 56 | textarea { 57 | white-space: pre; 58 | word-wrap: normal; 59 | } 60 | 61 | textarea.success { 62 | border-color: #8b8 !important; 63 | } 64 | 65 | textarea.fail { 66 | border-color: #f00 !important; 67 | background-color: rgba(255,0,0,0.05); 68 | } 69 | 70 | textarea, input { outline: none; } /* osx */ 71 | 72 | input.Number { 73 | font-size: 12px; /** TODO: Use of !imporant is not ideal **/ 74 | background-color: transparent!important; /* For now this is a quick fix a rendering issue due to inherited background */ 75 | border: 1px solid transparent; 76 | padding: 2px; 77 | cursor: col-resize; 78 | } 79 | 80 | input.File { 81 | border: 0px solid !important; 82 | padding: 0px !important; 83 | } 84 | 85 | input[type=range]:focus:after { 86 | position: absolute; 87 | transform: translate(-100%,-50% ); 88 | content: attr(value); 89 | font-size: 12px; 90 | } 91 | 92 | /* class */ 93 | 94 | .deleteInfo:after { 95 | position: absolute; 96 | transform: translate(-50%,-250% ); 97 | content: "double-click to delete"; 98 | font-size: 10px; 99 | display: block; 100 | } 101 | 102 | .Panel { 103 | 104 | -moz-user-select: none; 105 | -webkit-user-select: none; 106 | -ms-user-select: none; 107 | 108 | /* No support for these yet */ 109 | -o-user-select: none; 110 | user-select: none; 111 | } 112 | 113 | .OverlayPanel { 114 | z-index: 10; 115 | position: absolute; 116 | padding: 10px; 117 | overflow: auto; 118 | border: 1px solid; 119 | } 120 | 121 | .OverlayPanel:focus { 122 | z-index: 20; 123 | } 124 | 125 | .Text { 126 | cursor: default; 127 | } 128 | 129 | .FancySelect { 130 | padding: 0; 131 | cursor: default; 132 | overflow: auto; 133 | outline: none; 134 | } 135 | 136 | .FancySelect .option { 137 | padding: 4px; 138 | white-space: nowrap; 139 | } 140 | 141 | .CollapsiblePanel .CollapsiblePanelButton { 142 | float: left; 143 | margin-right: 6px; 144 | width: 0px; 145 | height: 0px; 146 | border: 6px solid transparent; 147 | } 148 | 149 | .CollapsiblePanel.collapsed > .CollapsiblePanelButton { 150 | margin-top: 2px; 151 | border-left-color: #555; 152 | } 153 | 154 | .CollapsiblePanel:not(.collapsed) > .CollapsiblePanelButton { 155 | margin-top: 6px; 156 | border-top-color: #555; 157 | } 158 | 159 | .CollapsiblePanel.collapsed .CollapsibleContent { 160 | display: none; 161 | } 162 | 163 | .CollapsiblePanel:not(.collapsed) > .CollapsibleContent { 164 | clear: both; 165 | } 166 | 167 | /* http://www.brianchu.com/blog/2013/11/02/creating-an-auto-growing-text-input/ */ 168 | 169 | .AdaptiveTextAreaContainer { 170 | position: relative; 171 | display: inline-block; 172 | margin-top: 2px; 173 | margin-bottom: 2px; 174 | } 175 | 176 | .AdaptiveTextArea, .AdaptiveTextAreaSize { 177 | min-height: 21px; 178 | /* need to manually set font and font size */ 179 | font-family: Arial, sans-serif; 180 | font-size: 13px; 181 | box-sizing: border-box; 182 | padding: 2px; 183 | border: 1px solid #ccc; 184 | 185 | overflow: hidden; 186 | width: 100%; 187 | } 188 | 189 | .AdaptiveTextArea { 190 | height: 100%; 191 | position: absolute; 192 | resize: none; 193 | 194 | /* 195 | "pre" or "preline" or "normal" fixes Chrome issue where 196 | whitespace at end of lines does not trigger a line break. 197 | However, it causes the text to exhibit the behavior seen with 198 | "pre" that is described below. 199 | */ 200 | white-space: normal; 201 | word-wrap: break-word; 202 | overflow-wrap: break-word; 203 | } 204 | 205 | .AdaptiveTextAreaSize { 206 | visibility: hidden; 207 | 208 | /* 209 | Pre-wrap: preserve spacing and newlines, but wrap text. 210 | Pre: preserve spacing and newlines but don't wrap text. 211 | 212 | "pre" does not wrap well on Firefox, even with word-wrap:break-word. 213 | "pre" on Chrome works with word-wrap, but exhibits different behavior: 214 | Instead of entire words being moved to the next line for wrapping, 215 | the browser will cut words in the middle for wrapping. 216 | "pre-line" has Firefox issues 217 | */ 218 | white-space: pre-wrap; 219 | /* Required for wrapping lines in Webkit, 220 | but not necessary in Firefox if you have white-space wrapping 221 | (pre-wrap, normal, pre-line) already set */ 222 | word-wrap: break-word; 223 | overflow-wrap: break-word; 224 | } 225 | 226 | /* FlexiColorPicker */ 227 | 228 | .picker-wrapper, 229 | .slide-wrapper { 230 | position: relative; 231 | float: left; 232 | } 233 | 234 | .picker-indicator, 235 | .slide-indicator { 236 | position: absolute; 237 | left: 0; 238 | top: 0; 239 | pointer-events: none; 240 | } 241 | 242 | .picker, 243 | .slide { 244 | cursor: crosshair; 245 | float: left; 246 | } 247 | 248 | .slide-wrapper { 249 | margin-left: 10px; 250 | } 251 | 252 | .picker-indicator { 253 | width: 5px; 254 | height: 5px; 255 | border: 2px solid darkblue; 256 | -moz-border-radius: 4px; 257 | -o-border-radius: 4px; 258 | -webkit-border-radius: 4px; 259 | border-radius: 4px; 260 | opacity: .5; 261 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; 262 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50); 263 | filter: alpha(opacity=50); 264 | background-color: white; 265 | } 266 | 267 | .slide-indicator { 268 | width: 100%; 269 | height: 10px; 270 | left: -4px; 271 | opacity: .6; 272 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)"; 273 | filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=60); 274 | filter: alpha(opacity=60); 275 | border: 4px solid lightblue; 276 | -moz-border-radius: 4px; 277 | -o-border-radius: 4px; 278 | -webkit-border-radius: 4px; 279 | border-radius: 4px; 280 | background-color: white; 281 | } 282 | 283 | /* http://www.mobify.com/blog/multiline-ellipsis-in-pure-css/ */ 284 | 285 | .EllipsisMultilineText { 286 | overflow: hidden; 287 | max-height: 2.6em; 288 | line-height: 1.3em; 289 | } 290 | 291 | .EllipsisMultilineText:before { 292 | content:""; 293 | float: left; 294 | width: 5px; 295 | max-height: 2.6em; 296 | } 297 | 298 | .EllipsisMultilineText > *:first-child { 299 | float: right; 300 | width: 100%; 301 | margin-top: 0px; 302 | margin-bottom: 0px; 303 | margin-left: -5px; 304 | word-wrap: break-word; 305 | } 306 | 307 | .EllipsisMultilineText:after { 308 | content: "\02026"; 309 | 310 | box-sizing: content-box; 311 | -webkit-box-sizing: content-box; 312 | -moz-box-sizing: content-box; 313 | 314 | float: right; position: relative; 315 | top: 1.3em; left: 100%; 316 | width: 3em; margin-left: -3em; 317 | padding-right: 5px; 318 | 319 | text-align: right; 320 | } 321 | 322 | /* resize */ 323 | 324 | .ResizeLeft, .ResizeLeft:hover { 325 | position: relative; 326 | float: left; 327 | height: 100%; 328 | width: 10px; 329 | } 330 | 331 | .ResizeLeft:hover{ 332 | cursor: col-resize; 333 | } 334 | 335 | /* virtual list */ 336 | 337 | .VirtualListRow { 338 | position: absolute; 339 | overflow: hidden; 340 | white-space: nowrap; 341 | display: inline-block; 342 | } 343 | 344 | .VirtualListRow > .Text { 345 | cursor: inherit; 346 | } 347 | 348 | /* id */ 349 | 350 | #viewport { 351 | position: absolute; 352 | top: 32px; 353 | left: 0px; 354 | right: 300px; 355 | bottom: 32px; 356 | } 357 | 358 | #menubar { 359 | position: absolute; 360 | width: 100%; 361 | height: 32px; 362 | padding: 0px; 363 | margin: 0px; 364 | z-index: 100; 365 | top: 0px; 366 | left: 0px; 367 | } 368 | 369 | #menubar .menu { 370 | float: left; 371 | cursor: pointer; 372 | position: relative; 373 | } 374 | 375 | #menubar .menu .title { 376 | margin: 0px; 377 | padding: 8px; 378 | } 379 | 380 | #menubar .menu .options { 381 | display: none; 382 | padding: 5px 0px; 383 | width: 140px; 384 | position: absolute; 385 | top: 32px; 386 | } 387 | 388 | #menubar .menu:hover .options { 389 | display: block; 390 | max-height: 600px; 391 | overflow: auto; 392 | } 393 | 394 | #menubar .menu .options .option { 395 | background-color: transparent; 396 | padding: 5px 10px; 397 | margin: 0px !important; 398 | } 399 | 400 | #sidebar { 401 | position: absolute; 402 | right: 0px; 403 | top: 32px; 404 | bottom: 0px; 405 | width: 300px; 406 | overflow: hidden; 407 | } 408 | 409 | #sidebar .Panel { 410 | margin-bottom: 10px; 411 | } 412 | 413 | #sidebar .Panel.collapsed { 414 | margin-bottom: 0px; 415 | } 416 | 417 | #sidebar .CollapsibleContent { 418 | margin-top: 10px; 419 | } 420 | 421 | #sidebar > .Panel { 422 | padding: 10px; 423 | border-top: 1px solid; 424 | } 425 | 426 | #sidebar > .Content > .Panel { 427 | padding: 10px; 428 | border-top: 1px solid; 429 | } 430 | 431 | #sidebar .Content { 432 | top: 40px; 433 | bottom: 0px; 434 | position: absolute; 435 | overflow: auto; 436 | right: 0px; 437 | width: 100%; 438 | } 439 | 440 | #toolbar { 441 | position: absolute; 442 | left: 0px; 443 | right: 300px; 444 | bottom: 0px; 445 | height: 32px; 446 | } 447 | 448 | #toolbar .Panel { 449 | padding: 6px; 450 | } 451 | 452 | #toolbar button { 453 | margin-right: 6px; 454 | } 455 | -------------------------------------------------------------------------------- /mdsrv/webapp/embedded.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | NGL - embedded 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 93 |
94 |
95 | 96 | 97 | 98 | 99 |
100 | 101 | 102 | -------------------------------------------------------------------------------- /mdsrv/webapp/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nglviewer/mdsrv/51302bd369be447b5d01ee74b4f8a3c0653760e8/mdsrv/webapp/favicon.ico -------------------------------------------------------------------------------- /mdsrv/webapp/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nglviewer/mdsrv/51302bd369be447b5d01ee74b4f8a3c0653760e8/mdsrv/webapp/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /mdsrv/webapp/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nglviewer/mdsrv/51302bd369be447b5d01ee74b4f8a3c0653760e8/mdsrv/webapp/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /mdsrv/webapp/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nglviewer/mdsrv/51302bd369be447b5d01ee74b4f8a3c0653760e8/mdsrv/webapp/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /mdsrv/webapp/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nglviewer/mdsrv/51302bd369be447b5d01ee74b4f8a3c0653760e8/mdsrv/webapp/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /mdsrv/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | NGL/MDsrv 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /mdsrv/webapp/js/lib/colorpicker.min.js: -------------------------------------------------------------------------------- 1 | if( typeof importScripts !== 'function' ){ 2 | (function(w,l,r){function s(a,c){var d=c.getBoundingClientRect();return{x:Math.max(Math.min(a.clientX-d.left,c.clientWidth),0),y:Math.max(Math.min(a.clientY-d.top,c.clientHeight),0)}}function f(a,c,d){a=l.createElementNS("http://www.w3.org/2000/svg",a);for(var b in c)a.setAttribute(b,c[b]);Array.isArray(d)||(d=[d]);c=0;for(b=d[0]&&d.length||0;c
', 5 | this.slideElement=a.getElementsByClassName("slide")[0],this.pickerElement=a.getElementsByClassName("picker")[0],this.slideIndicator=a.getElementsByClassName("slide-indicator")[0],this.pickerIndicator=a.getElementsByClassName("picker-indicator")[0],g.positionIndicators(this.slideIndicator,this.pickerIndicator,{x:0,y:0},{x:0,y:0}),this.callback=function(a,b,d,e,f){g.positionIndicators(this.slideIndicator,this.pickerIndicator,f,e);c(a,b,d)});this.slide_listener=x(this,this.slideElement,this.pickerElement); 6 | this.picker_listener=y(this,this.pickerElement);d||this.fixIndicators(this.slideIndicator,this.pickerIndicator);a=t.cloneNode(!0);d=u.cloneNode(!0);var b=a.getElementsByTagName("defs")[0].firstChild,e=a.getElementsByTagName("rect")[0];b.id="gradient-hsv-"+n;e.setAttribute("fill","url(#"+b.id+")");b=d.getElementsByTagName("defs")[0];b=[b.firstChild,b.lastChild];e=d.getElementsByTagName("rect");b[0].id="gradient-black-"+n;b[1].id="gradient-white-"+n;e[0].setAttribute("fill","url(#"+b[1].id+")");e[1].setAttribute("fill", 7 | "url(#"+b[0].id+")");this.slideElement.appendChild(a);this.pickerElement.appendChild(d);n++;p(this.slideElement,this.slide_listener);p(this.pickerElement,this.picker_listener)}function p(a,c){function d(a){e&&c(a)}function b(a){d(a);e=!1;l.removeEventListener("mouseup",b,!1);l.removeEventListener("mousemove",d,!1)}var e=!1;a.addEventListener("mousedown",function(a){e=!0;a.preventDefault();l.addEventListener("mouseup",b,!0);l.addEventListener("mousemove",d,!1)},!1)}function q(a,c,d,b){a.h=c.h%360; 8 | a.s=c.s;a.v=c.v;c=k(a);var e={y:a.h*a.slideElement.offsetHeight/360,x:0},f=a.pickerElement.offsetHeight,f={x:a.s*a.pickerElement.offsetWidth,y:f-a.v*f};a.pickerElement.style.backgroundColor=k({h:a.h,s:1,v:1}).hex;a.callback&&a.callback(b||c.hex,{h:a.h,s:a.s,v:a.v},d||{r:c.r,g:c.g,b:c.b},f,e);return a}var u,t,v={xmlns:"http://www.w3.org/2000/svg",version:"1.1",width:"100%",height:"100%"};t=f("svg",v,[f("defs",{},f("linearGradient",{id:"gradient-hsv",x1:"0%",y1:"100%",x2:"0%",y2:"0%"},[f("stop",{offset:"0%", 9 | "stop-color":"#FF0000","stop-opacity":"1"}),f("stop",{offset:"13%","stop-color":"#FF00FF","stop-opacity":"1"}),f("stop",{offset:"25%","stop-color":"#8000FF","stop-opacity":"1"}),f("stop",{offset:"38%","stop-color":"#0040FF","stop-opacity":"1"}),f("stop",{offset:"50%","stop-color":"#00FFFF","stop-opacity":"1"}),f("stop",{offset:"63%","stop-color":"#00FF40","stop-opacity":"1"}),f("stop",{offset:"75%","stop-color":"#0BED00","stop-opacity":"1"}),f("stop",{offset:"88%","stop-color":"#FFFF00","stop-opacity":"1"}), 10 | f("stop",{offset:"100%","stop-color":"#FF0000","stop-opacity":"1"})])),f("rect",{x:"0",y:"0",width:"100%",height:"100%",fill:"url(#gradient-hsv)"})]);u=f("svg",v,[f("defs",{},[f("linearGradient",{id:"gradient-black",x1:"0%",y1:"100%",x2:"0%",y2:"0%"},[f("stop",{offset:"0%","stop-color":"#000000","stop-opacity":"1"}),f("stop",{offset:"100%","stop-color":"#CC9A81","stop-opacity":"0"})]),f("linearGradient",{id:"gradient-white",x1:"0%",y1:"100%",x2:"100%",y2:"100%"},[f("stop",{offset:"0%","stop-color":"#FFFFFF", 11 | "stop-opacity":"1"}),f("stop",{offset:"100%","stop-color":"#CC9A81","stop-opacity":"0"})])]),f("rect",{x:"0",y:"0",width:"100%",height:"100%",fill:"url(#gradient-white)"}),f("rect",{x:"0",y:"0",width:"100%",height:"100%",fill:"url(#gradient-black)"})]);var n=0;g.hsv2rgb=function(a){a=k(a);delete a.hex;return a};g.hsv2hex=function(a){return k(a).hex};g.rgb2hsv=m;g.rgb2hex=function(a){return k(m(a)).hex};g.hex2hsv=function(a){return m(g.hex2rgb(a))};g.hex2rgb=function(a){return{r:parseInt(a.substr(1, 12 | 2),16),g:parseInt(a.substr(3,2),16),b:parseInt(a.substr(5,2),16)}};g.prototype.setHsv=function(a){return q(this,a)};g.prototype.setRgb=function(a){return q(this,m(a),a)};g.prototype.setHex=function(a){return q(this,g.hex2hsv(a),r,a)};g.positionIndicators=function(a,c,d,b){d&&(a.style.top=d.y-a.offsetHeight/2+"px");b&&(c.style.top=b.y-c.offsetHeight/2+"px",c.style.left=b.x-c.offsetWidth/2+"px")};g.prototype.fixIndicators=function(a,c){p(a,this.slide_listener);p(c,this.picker_listener)};w.ColorPicker= 13 | g})(window,window.document); 14 | } -------------------------------------------------------------------------------- /mdsrv/webapp/js/lib/signals.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | JS Signals 4 | Released under the MIT license 5 | Author: Miller Medeiros 6 | Version: 1.0.0 - Build: 268 (2012/11/29 05:48 PM) 7 | */ 8 | (function(i){function h(a,b,c,d,e){this._listener=b;this._isOnce=c;this.context=d;this._signal=a;this._priority=e||0}function g(a,b){if(typeof a!=="function")throw Error("listener is a required param of {fn}() and should be a Function.".replace("{fn}",b));}function e(){this._bindings=[];this._prevParams=null;var a=this;this.dispatch=function(){e.prototype.dispatch.apply(a,arguments)}}h.prototype={active:!0,params:null,execute:function(a){var b;this.active&&this._listener&&(a=this.params?this.params.concat(a): 9 | a,b=this._listener.apply(this.context,a),this._isOnce&&this.detach());return b},detach:function(){return this.isBound()?this._signal.remove(this._listener,this.context):null},isBound:function(){return!!this._signal&&!!this._listener},isOnce:function(){return this._isOnce},getListener:function(){return this._listener},getSignal:function(){return this._signal},_destroy:function(){delete this._signal;delete this._listener;delete this.context},toString:function(){return"[SignalBinding isOnce:"+this._isOnce+ 10 | ", isBound:"+this.isBound()+", active:"+this.active+"]"}};e.prototype={VERSION:"1.0.0",memorize:!1,_shouldPropagate:!0,active:!0,_registerListener:function(a,b,c,d){var e=this._indexOfListener(a,c);if(e!==-1){if(a=this._bindings[e],a.isOnce()!==b)throw Error("You cannot add"+(b?"":"Once")+"() then add"+(!b?"":"Once")+"() the same listener without removing the relationship first.");}else a=new h(this,a,b,c,d),this._addBinding(a);this.memorize&&this._prevParams&&a.execute(this._prevParams);return a}, 11 | _addBinding:function(a){var b=this._bindings.length;do--b;while(this._bindings[b]&&a._priority<=this._bindings[b]._priority);this._bindings.splice(b+1,0,a)},_indexOfListener:function(a,b){for(var c=this._bindings.length,d;c--;)if(d=this._bindings[c],d._listener===a&&d.context===b)return c;return-1},has:function(a,b){return this._indexOfListener(a,b)!==-1},add:function(a,b,c){g(a,"add");return this._registerListener(a,!1,b,c)},addOnce:function(a,b,c){g(a,"addOnce");return this._registerListener(a, 12 | !0,b,c)},remove:function(a,b){g(a,"remove");var c=this._indexOfListener(a,b);c!==-1&&(this._bindings[c]._destroy(),this._bindings.splice(c,1));return a},removeAll:function(){for(var a=this._bindings.length;a--;)this._bindings[a]._destroy();this._bindings.length=0},getNumListeners:function(){return this._bindings.length},halt:function(){this._shouldPropagate=!1},dispatch:function(a){if(this.active){var b=Array.prototype.slice.call(arguments),c=this._bindings.length,d;if(this.memorize)this._prevParams= 13 | b;if(c){d=this._bindings.slice();this._shouldPropagate=!0;do c--;while(d[c]&&this._shouldPropagate&&d[c].execute(b)!==!1)}}},forget:function(){this._prevParams=null},dispose:function(){this.removeAll();delete this._bindings;delete this._prevParams},toString:function(){return"[Signal active:"+this.active+" numListeners:"+this.getNumListeners()+"]"}};var f=e;f.Signal=e;typeof define==="function"&&define.amd?define(function(){return f}):typeof module!=="undefined"&&module.exports?module.exports=f:i.signals= 14 | f})(this); -------------------------------------------------------------------------------- /mdsrv/webapp/js/lib/tether.min.js: -------------------------------------------------------------------------------- 1 | if( typeof importScripts !== 'function' ){ 2 | !function(t,e){"function"==typeof define&&define.amd?define(e):"object"==typeof exports?module.exports=e(require,exports,module):t.Tether=e()}(this,function(t,e,o){"use strict";function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function n(t){var e=getComputedStyle(t),o=e.position;if("fixed"===o)return t;for(var i=t;i=i.parentNode;){var n=void 0;try{n=getComputedStyle(i)}catch(r){}if("undefined"==typeof n||null===n)return i;var s=n.overflow,a=n.overflowX,f=n.overflowY;if(/(auto|scroll)/.test(s+f+a)&&("absolute"!==o||["relative","absolute","fixed"].indexOf(n.position)>=0))return i}return document.body}function r(t){var e=void 0;t===document?(e=document,t=document.documentElement):e=t.ownerDocument;var o=e.documentElement,i={},n=t.getBoundingClientRect();for(var r in n)i[r]=n[r];var s=x(e);return i.top-=s.top,i.left-=s.left,"undefined"==typeof i.width&&(i.width=document.body.scrollWidth-i.left-i.right),"undefined"==typeof i.height&&(i.height=document.body.scrollHeight-i.top-i.bottom),i.top=i.top-o.clientTop,i.left=i.left-o.clientLeft,i.right=e.body.clientWidth-i.width-i.left,i.bottom=e.body.clientHeight-i.height-i.top,i}function s(t){return t.offsetParent||document.documentElement}function a(){var t=document.createElement("div");t.style.width="100%",t.style.height="200px";var e=document.createElement("div");f(e.style,{position:"absolute",top:0,left:0,pointerEvents:"none",visibility:"hidden",width:"200px",height:"150px",overflow:"hidden"}),e.appendChild(t),document.body.appendChild(e);var o=t.offsetWidth;e.style.overflow="scroll";var i=t.offsetWidth;o===i&&(i=e.clientWidth),document.body.removeChild(e);var n=o-i;return{width:n,height:n}}function f(){var t=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],e=[];return Array.prototype.push.apply(e,arguments),e.slice(1).forEach(function(e){if(e)for(var o in e)({}).hasOwnProperty.call(e,o)&&(t[o]=e[o])}),t}function h(t,e){if("undefined"!=typeof t.classList)e.split(" ").forEach(function(e){e.trim()&&t.classList.remove(e)});else{var o=new RegExp("(^| )"+e.split(" ").join("|")+"( |$)","gi"),i=u(t).replace(o," ");p(t,i)}}function l(t,e){if("undefined"!=typeof t.classList)e.split(" ").forEach(function(e){e.trim()&&t.classList.add(e)});else{h(t,e);var o=u(t)+(" "+e);p(t,o)}}function d(t,e){if("undefined"!=typeof t.classList)return t.classList.contains(e);var o=u(t);return new RegExp("(^| )"+e+"( |$)","gi").test(o)}function u(t){return t.className instanceof SVGAnimatedString?t.className.baseVal:t.className}function p(t,e){t.setAttribute("class",e)}function c(t,e,o){o.forEach(function(o){-1===e.indexOf(o)&&d(t,o)&&h(t,o)}),e.forEach(function(e){d(t,e)||l(t,e)})}function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function g(t,e){var o=arguments.length<=2||void 0===arguments[2]?1:arguments[2];return t+o>=e&&e>=t-o}function m(){return"undefined"!=typeof performance&&"undefined"!=typeof performance.now?performance.now():+new Date}function v(){for(var t={top:0,left:0},e=arguments.length,o=Array(e),i=0;e>i;i++)o[i]=arguments[i];return o.forEach(function(e){var o=e.top,i=e.left;"string"==typeof o&&(o=parseFloat(o,10)),"string"==typeof i&&(i=parseFloat(i,10)),t.top+=o,t.left+=i}),t}function y(t,e){return"string"==typeof t.left&&-1!==t.left.indexOf("%")&&(t.left=parseFloat(t.left,10)/100*e.width),"string"==typeof t.top&&-1!==t.top.indexOf("%")&&(t.top=parseFloat(t.top,10)/100*e.height),t}function b(t,e){return"scrollParent"===e?e=t.scrollParent:"window"===e&&(e=[pageXOffset,pageYOffset,innerWidth+pageXOffset,innerHeight+pageYOffset]),e===document&&(e=e.documentElement),"undefined"!=typeof e.nodeType&&!function(){var t=r(e),o=t,i=getComputedStyle(e);e=[o.left,o.top,t.width+o.left,t.height+o.top],U.forEach(function(t,o){t=t[0].toUpperCase()+t.substr(1),"Top"===t||"Left"===t?e[o]+=parseFloat(i["border"+t+"Width"]):e[o]-=parseFloat(i["border"+t+"Width"])})}(),e}var w=function(){function t(t,e){for(var o=0;o1?a-1:0),h=1;a>h;h++)f[h-1]=arguments[h];i.apply(s,f),r?this.bindings[t].splice(e,1):++e}}}]),t}();C.Utils={getScrollParent:n,getBounds:r,getOffsetParent:s,extend:f,addClass:l,removeClass:h,hasClass:d,updateClasses:c,defer:T,flush:S,uniqueId:O,Evented:W,getScrollBarSize:a};var M=function(){function t(t,e){var o=[],i=!0,n=!1,r=void 0;try{for(var s,a=t[Symbol.iterator]();!(i=(s=a.next()).done)&&(o.push(s.value),!e||o.length!==e);i=!0);}catch(f){n=!0,r=f}finally{try{!i&&a["return"]&&a["return"]()}finally{if(n)throw r}}return o}return function(e,o){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return t(e,o);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),w=function(){function t(t,e){for(var o=0;o16?(e=Math.min(e-16,250),void(o=setTimeout(n,250))):void("undefined"!=typeof t&&m()-t<10||("undefined"!=typeof o&&(clearTimeout(o),o=null),t=m(),_(),e=m()-t))};["resize","scroll","touchmove"].forEach(function(t){window.addEventListener(t,i)})}();var z={center:"center",left:"right",right:"left"},F={middle:"middle",top:"bottom",bottom:"top"},L={top:0,left:0,middle:"50%",center:"50%",bottom:"100%",right:"100%"},Y=function(t,e){var o=t.left,i=t.top;return"auto"===o&&(o=z[e.left]),"auto"===i&&(i=F[e.top]),{left:o,top:i}},H=function(t){var e=t.left,o=t.top;return"undefined"!=typeof L[t.left]&&(e=L[t.left]),"undefined"!=typeof L[t.top]&&(o=L[t.top]),{left:e,top:o}},X=function(t){var e=t.split(" "),o=M(e,2),i=o[0],n=o[1];return{top:i,left:n}},j=X,N=function(){function t(e){var o=this;i(this,t),this.position=this.position.bind(this),B.push(this),this.history=[],this.setOptions(e,!1),C.modules.forEach(function(t){"undefined"!=typeof t.initialize&&t.initialize.call(o)}),this.position()}return w(t,[{key:"getClass",value:function(){var t=arguments.length<=0||void 0===arguments[0]?"":arguments[0],e=this.options.classes;return"undefined"!=typeof e&&e[t]?this.options.classes[t]:this.options.classPrefix?this.options.classPrefix+"-"+t:t}},{key:"setOptions",value:function(t){var e=this,o=arguments.length<=1||void 0===arguments[1]?!0:arguments[1],i={offset:"0 0",targetOffset:"0 0",targetAttachment:"auto auto",classPrefix:"tether"};this.options=f(i,t);var r=this.options,s=r.element,a=r.target,h=r.targetModifier;if(this.element=s,this.target=a,this.targetModifier=h,"viewport"===this.target?(this.target=document.body,this.targetModifier="visible"):"scroll-handle"===this.target&&(this.target=document.body,this.targetModifier="scroll-handle"),["element","target"].forEach(function(t){if("undefined"==typeof e[t])throw new Error("Tether Error: Both element and target must be defined");"undefined"!=typeof e[t].jquery?e[t]=e[t][0]:"string"==typeof e[t]&&(e[t]=document.querySelector(e[t]))}),l(this.element,this.getClass("element")),this.options.addTargetClasses!==!1&&l(this.target,this.getClass("target")),!this.options.attachment)throw new Error("Tether Error: You must provide an attachment");this.targetAttachment=j(this.options.targetAttachment),this.attachment=j(this.options.attachment),this.offset=X(this.options.offset),this.targetOffset=X(this.options.targetOffset),"undefined"!=typeof this.scrollParent&&this.disable(),this.scrollParent="scroll-handle"===this.targetModifier?this.target:n(this.target),this.options.enabled!==!1&&this.enable(o)}},{key:"getTargetBounds",value:function(){if("undefined"==typeof this.targetModifier)return r(this.target);if("visible"===this.targetModifier){if(this.target===document.body)return{top:pageYOffset,left:pageXOffset,height:innerHeight,width:innerWidth};var t=r(this.target),e={height:t.height,width:t.width,top:t.top,left:t.left};return e.height=Math.min(e.height,t.height-(pageYOffset-t.top)),e.height=Math.min(e.height,t.height-(t.top+t.height-(pageYOffset+innerHeight))),e.height=Math.min(innerHeight,e.height),e.height-=2,e.width=Math.min(e.width,t.width-(pageXOffset-t.left)),e.width=Math.min(e.width,t.width-(t.left+t.width-(pageXOffset+innerWidth))),e.width=Math.min(innerWidth,e.width),e.width-=2,e.topo.clientWidth||[i.overflow,i.overflowX].indexOf("scroll")>=0||this.target!==document.body,s=0;n&&(s=15);var a=t.height-parseFloat(i.borderTopWidth)-parseFloat(i.borderBottomWidth)-s,e={width:15,height:.975*a*(a/o.scrollHeight),left:t.left+t.width-parseFloat(i.borderLeftWidth)-15},f=0;408>a&&this.target===document.body&&(f=-11e-5*Math.pow(a,2)-.00727*a+22.58),this.target!==document.body&&(e.height=Math.max(e.height,24));var h=this.target.scrollTop/(o.scrollHeight-a);return e.top=h*(a-e.height-f)+t.top+parseFloat(i.borderTopWidth),this.target===document.body&&(e.height=Math.max(e.height,24)),e}}},{key:"clearCache",value:function(){this._cache={}}},{key:"cache",value:function(t,e){return"undefined"==typeof this._cache&&(this._cache={}),"undefined"==typeof this._cache[t]&&(this._cache[t]=e.call(this)),this._cache[t]}},{key:"enable",value:function(){var t=arguments.length<=0||void 0===arguments[0]?!0:arguments[0];this.options.addTargetClasses!==!1&&l(this.target,this.getClass("enabled")),l(this.element,this.getClass("enabled")),this.enabled=!0,this.scrollParent!==document&&this.scrollParent.addEventListener("scroll",this.position),t&&this.position()}},{key:"disable",value:function(){h(this.target,this.getClass("enabled")),h(this.element,this.getClass("enabled")),this.enabled=!1,"undefined"!=typeof this.scrollParent&&this.scrollParent.removeEventListener("scroll",this.position)}},{key:"destroy",value:function(){var t=this;this.disable(),B.forEach(function(e,o){return e===t?void B.splice(o,1):void 0})}},{key:"updateAttachClasses",value:function(t,e){var o=this;t=t||this.attachment,e=e||this.targetAttachment;var i=["left","top","bottom","right","middle","center"];"undefined"!=typeof this._addAttachClasses&&this._addAttachClasses.length&&this._addAttachClasses.splice(0,this._addAttachClasses.length),"undefined"==typeof this._addAttachClasses&&(this._addAttachClasses=[]);var n=this._addAttachClasses;t.top&&n.push(this.getClass("element-attached")+"-"+t.top),t.left&&n.push(this.getClass("element-attached")+"-"+t.left),e.top&&n.push(this.getClass("target-attached")+"-"+e.top),e.left&&n.push(this.getClass("target-attached")+"-"+e.left);var r=[];i.forEach(function(t){r.push(o.getClass("element-attached")+"-"+t),r.push(o.getClass("target-attached")+"-"+t)}),T(function(){"undefined"!=typeof o._addAttachClasses&&(c(o.element,o._addAttachClasses,r),o.options.addTargetClasses!==!1&&c(o.target,o._addAttachClasses,r),delete o._addAttachClasses)})}},{key:"position",value:function(){var t=this,e=arguments.length<=0||void 0===arguments[0]?!0:arguments[0];if(this.enabled){this.clearCache();var o=Y(this.targetAttachment,this.attachment);this.updateAttachClasses(this.attachment,o);var i=this.cache("element-bounds",function(){return r(t.element)}),n=i.width,f=i.height;if(0===n&&0===f&&"undefined"!=typeof this.lastSize){var h=this.lastSize;n=h.width,f=h.height}else this.lastSize={width:n,height:f};var l=this.cache("target-bounds",function(){return t.getTargetBounds()}),d=l,u=y(H(this.attachment),{width:n,height:f}),p=y(H(o),d),c=y(this.offset,{width:n,height:f}),g=y(this.targetOffset,d);u=v(u,c),p=v(p,g);for(var m=l.left+p.left-u.left,b=l.top+p.top-u.top,w=0;wwindow.innerWidth&&(A=this.cache("scrollbar-size",a),x.viewport.bottom-=A.height),document.body.scrollHeight>window.innerHeight&&(A=this.cache("scrollbar-size",a),x.viewport.right-=A.width),(-1===["","static"].indexOf(document.body.style.position)||-1===["","static"].indexOf(document.body.parentElement.style.position))&&(x.page.bottom=document.body.scrollHeight-b-f,x.page.right=document.body.scrollWidth-m-n),"undefined"!=typeof this.options.optimizations&&this.options.optimizations.moveElement!==!1&&"undefined"==typeof this.targetModifier&&!function(){var e=t.cache("target-offsetparent",function(){return s(t.target)}),o=t.cache("target-offsetparent-bounds",function(){return r(e)}),i=getComputedStyle(e),n=o,a={};if(["Top","Left","Bottom","Right"].forEach(function(t){a[t.toLowerCase()]=parseFloat(i["border"+t+"Width"])}),o.right=document.body.scrollWidth-o.left-n.width+a.right,o.bottom=document.body.scrollHeight-o.top-n.height+a.bottom,x.page.top>=o.top+a.top&&x.page.bottom>=o.bottom&&x.page.left>=o.left+a.left&&x.page.right>=o.right){var f=e.scrollTop,h=e.scrollLeft;x.offset={top:x.page.top-o.top+f-a.top,left:x.page.left-o.left+h-a.left}}}(),this.move(x),this.history.unshift(x),this.history.length>3&&this.history.pop(),e&&S(),!0}}},{key:"move",value:function(t){var e=this;if("undefined"!=typeof this.element.parentNode){var o={};for(var i in t){o[i]={};for(var n in t[i]){for(var r=!1,a=0;a=0&&(b=parseFloat(b),y=parseFloat(y)),b!==y&&(v=!0,m[n]=y)}v&&T(function(){f(e.element.style,m)})}}}]),t}();N.modules=[],C.position=_;var R=f(N,C),M=function(){function t(t,e){var o=[],i=!0,n=!1,r=void 0;try{for(var s,a=t[Symbol.iterator]();!(i=(s=a.next()).done)&&(o.push(s.value),!e||o.length!==e);i=!0);}catch(f){n=!0,r=f}finally{try{!i&&a["return"]&&a["return"]()}finally{if(n)throw r}}return o}return function(e,o){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return t(e,o);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),P=C.Utils,r=P.getBounds,f=P.extend,c=P.updateClasses,T=P.defer,U=["left","top","right","bottom"];C.modules.push({position:function(t){var e=this,o=t.top,i=t.left,n=t.targetAttachment;if(!this.options.constraints)return!0;var s=this.cache("element-bounds",function(){return r(e.element)}),a=s.height,h=s.width;if(0===h&&0===a&&"undefined"!=typeof this.lastSize){var l=this.lastSize;h=l.width,a=l.height}var d=this.cache("target-bounds",function(){return e.getTargetBounds()}),u=d.height,p=d.width,g=[this.getClass("pinned"),this.getClass("out-of-bounds")];this.options.constraints.forEach(function(t){var e=t.outOfBoundsClass,o=t.pinnedClass;e&&g.push(e),o&&g.push(o)}),g.forEach(function(t){["left","top","right","bottom"].forEach(function(e){g.push(t+"-"+e)})});var m=[],v=f({},n),y=f({},this.attachment);return this.options.constraints.forEach(function(t){var r=t.to,s=t.attachment,f=t.pin;"undefined"==typeof s&&(s="");var l=void 0,d=void 0;if(s.indexOf(" ")>=0){var c=s.split(" "),g=M(c,2);d=g[0],l=g[1]}else l=d=s;var w=b(e,r);("target"===d||"both"===d)&&(ow[3]&&"bottom"===v.top&&(o-=u,v.top="top")),"together"===d&&(ow[3]&&"bottom"===v.top&&("top"===y.top?(o-=u,v.top="top",o-=a,y.top="bottom"):"bottom"===y.top&&(o-=u,v.top="top",o+=a,y.top="top")),"middle"===v.top&&(o+a>w[3]&&"top"===y.top?(o-=a,y.top="bottom"):ow[2]&&"right"===v.left&&(i-=p,v.left="left")),"together"===l&&(iw[2]&&"right"===v.left?"left"===y.left?(i-=p,v.left="left",i-=h,y.left="right"):"right"===y.left&&(i-=p,v.left="left",i+=h,y.left="left"):"center"===v.left&&(i+h>w[2]&&"left"===y.left?(i-=h,y.left="right"):iw[3]&&"top"===y.top&&(o-=a,y.top="bottom")),("element"===l||"both"===l)&&(iw[2]&&"left"===y.left&&(i-=h,y.left="right")),"string"==typeof f?f=f.split(",").map(function(t){return t.trim()}):f===!0&&(f=["top","left","right","bottom"]),f=f||[];var C=[],O=[];o=0?(o=w[1],C.push("top")):O.push("top")),o+a>w[3]&&(f.indexOf("bottom")>=0?(o=w[3]-a,C.push("bottom")):O.push("bottom")),i=0?(i=w[0],C.push("left")):O.push("left")),i+h>w[2]&&(f.indexOf("right")>=0?(i=w[2]-h,C.push("right")):O.push("right")),C.length&&!function(){var t=void 0;t="undefined"!=typeof e.options.pinnedClass?e.options.pinnedClass:e.getClass("pinned"),m.push(t),C.forEach(function(e){m.push(t+"-"+e)})}(),O.length&&!function(){var t=void 0;t="undefined"!=typeof e.options.outOfBoundsClass?e.options.outOfBoundsClass:e.getClass("out-of-bounds"),m.push(t),O.forEach(function(e){m.push(t+"-"+e)})}(),(C.indexOf("left")>=0||C.indexOf("right")>=0)&&(y.left=v.left=!1),(C.indexOf("top")>=0||C.indexOf("bottom")>=0)&&(y.top=v.top=!1),(v.top!==n.top||v.left!==n.left||y.top!==e.attachment.top||y.left!==e.attachment.left)&&e.updateAttachClasses(y,v)}),T(function(){e.options.addTargetClasses!==!1&&c(e.target,m,g),c(e.element,m,g)}),{top:o,left:i}}});var P=C.Utils,r=P.getBounds,c=P.updateClasses,T=P.defer;C.modules.push({position:function(t){var e=this,o=t.top,i=t.left,n=this.cache("element-bounds",function(){return r(e.element)}),s=n.height,a=n.width,f=this.getTargetBounds(),h=o+s,l=i+a,d=[];o<=f.bottom&&h>=f.top&&["left","right"].forEach(function(t){var e=f[t];(e===i||e===l)&&d.push(t)}),i<=f.right&&l>=f.left&&["top","bottom"].forEach(function(t){var e=f[t];(e===o||e===h)&&d.push(t)});var u=[],p=[],g=["left","top","right","bottom"];return u.push(this.getClass("abutted")),g.forEach(function(t){u.push(e.getClass("abutted")+"-"+t)}),d.length&&p.push(this.getClass("abutted")),d.forEach(function(t){p.push(e.getClass("abutted")+"-"+t)}),T(function(){e.options.addTargetClasses!==!1&&c(e.target,p,u),c(e.element,p,u)}),!0}});var M=function(){function t(t,e){var o=[],i=!0,n=!1,r=void 0;try{for(var s,a=t[Symbol.iterator]();!(i=(s=a.next()).done)&&(o.push(s.value),!e||o.length!==e);i=!0);}catch(f){n=!0,r=f}finally{try{!i&&a["return"]&&a["return"]()}finally{if(n)throw r}}return o}return function(e,o){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return t(e,o);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}();return C.modules.push({position:function(t){var e=t.top,o=t.left;if(this.options.shift){var i=this.options.shift;"function"==typeof this.options.shift&&(i=this.options.shift.call(this,{top:e,left:o}));var n=void 0,r=void 0;if("string"==typeof i){i=i.split(" "),i[1]=i[1]||i[0];var s=M(i,2);n=s[0],r=s[1],n=parseFloat(n,10),r=parseFloat(r,10)}else n=i.top,r=i.left;return e+=n,o+=r,{top:e,left:o}}}}),R}); 3 | } -------------------------------------------------------------------------------- /mdsrv/webapp/js/ui/ui.extra.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Alexander Rose 3 | */ 4 | 5 | // Html 6 | 7 | UI.Html = function (html) { 8 | UI.Element.call(this) 9 | 10 | var dom = document.createElement('span') 11 | dom.className = 'Html' 12 | dom.style.cursor = 'default' 13 | dom.style.display = 'inline-block' 14 | dom.style.verticalAlign = 'middle' 15 | 16 | this.dom = dom 17 | this.setValue(html) 18 | 19 | return this 20 | } 21 | 22 | UI.Html.prototype = Object.create(UI.Element.prototype) 23 | 24 | UI.Html.prototype.setValue = function (value) { 25 | if (value !== undefined) { 26 | this.dom.innerHTML = value 27 | } 28 | 29 | return this 30 | } 31 | 32 | // Form 33 | 34 | UI.Form = function () { 35 | UI.Panel.call(this) 36 | 37 | var dom = document.createElement('form') 38 | dom.className = 'Form' 39 | dom.method = 'post' 40 | dom.action = '' 41 | dom.target = '_blank' 42 | dom.enctype = 'multipart/form-data' 43 | 44 | this.dom = dom 45 | 46 | return this 47 | } 48 | 49 | UI.Form.prototype = Object.create(UI.Panel.prototype) 50 | 51 | UI.Form.prototype.setMethod = function (value) { 52 | this.dom.method = value 53 | 54 | return this 55 | } 56 | 57 | UI.Form.prototype.setAction = function (value) { 58 | this.dom.action = value 59 | 60 | return this 61 | } 62 | 63 | UI.Form.prototype.setTarget = function (value) { 64 | this.dom.target = value 65 | 66 | return this 67 | } 68 | 69 | UI.Form.prototype.setEnctype = function (value) { 70 | this.dom.enctype = value 71 | 72 | return this 73 | } 74 | 75 | // File 76 | 77 | UI.File = function () { 78 | UI.Input.call(this) 79 | 80 | this.dom.className = 'File' 81 | this.dom.type = 'file' 82 | this.dom.multiple = false 83 | 84 | return this 85 | } 86 | 87 | UI.File.prototype = Object.create(UI.Input.prototype) 88 | 89 | UI.File.prototype.setMultiple = function (value) { 90 | this.dom.multiple = value 91 | 92 | return this 93 | } 94 | 95 | UI.File.prototype.getFiles = function (value) { 96 | return this.dom.files 97 | } 98 | 99 | // Hidden 100 | 101 | UI.Hidden = function () { 102 | UI.Input.call(this) 103 | 104 | this.dom.type = 'hidden' 105 | 106 | return this 107 | } 108 | 109 | UI.Hidden.prototype = Object.create(UI.Input.prototype) 110 | 111 | // Toggle Button 112 | 113 | UI.ToggleButton = function (labelA, labelB, callbackA, callbackB) { 114 | UI.Button.call(this, labelB) 115 | 116 | var flag = true 117 | 118 | this.onClick(function () { 119 | if (flag) { 120 | flag = false 121 | this.setLabel(labelA) 122 | callbackB() 123 | } else { 124 | flag = true 125 | this.setLabel(labelB) 126 | callbackA() 127 | } 128 | }.bind(this)) 129 | 130 | return this 131 | } 132 | 133 | UI.ToggleButton.prototype = Object.create(UI.Button.prototype) 134 | 135 | // Ellipsis Text 136 | 137 | UI.EllipsisText = function (text) { 138 | UI.Text.call(this, text) 139 | 140 | this.setWhiteSpace('nowrap') 141 | this.setOverflow('hidden') 142 | this.setTextOverflow('ellipsis') 143 | 144 | return this 145 | } 146 | 147 | UI.EllipsisText.prototype = Object.create(UI.Text.prototype) 148 | 149 | UI.EllipsisText.prototype.setValue = function (value) { 150 | if (value !== undefined) { 151 | this.dom.textContent = value 152 | this.setTitle(value) 153 | } 154 | 155 | return this 156 | } 157 | 158 | // Ellipsis Multiline Text 159 | 160 | UI.EllipsisMultilineText = function (text) { 161 | // http://www.mobify.com/blog/multiline-ellipsis-in-pure-css/ 162 | 163 | UI.Element.call(this) 164 | 165 | var dom = document.createElement('span') 166 | dom.className = 'EllipsisMultilineText' 167 | dom.style.cursor = 'default' 168 | dom.style.display = 'inline-block' 169 | dom.style.verticalAlign = 'middle' 170 | 171 | var content = document.createElement('p') 172 | dom.appendChild(content) 173 | 174 | this.dom = dom 175 | this.content = content 176 | 177 | this.setValue(text) 178 | 179 | return this 180 | } 181 | 182 | UI.EllipsisMultilineText.prototype = Object.create(UI.Element.prototype) 183 | 184 | UI.EllipsisMultilineText.prototype.setValue = function (value) { 185 | if (value !== undefined) { 186 | this.content.textContent = value 187 | this.setTitle(value) 188 | } 189 | 190 | return this 191 | } 192 | 193 | // Overlay Panel 194 | 195 | UI.OverlayPanel = function () { 196 | UI.Panel.call(this) 197 | 198 | this.dom.className = 'Panel OverlayPanel' 199 | this.dom.tabIndex = '-1' 200 | this.dom.style.outline = 'none' 201 | 202 | return this 203 | } 204 | 205 | UI.OverlayPanel.prototype = Object.create(UI.Panel.prototype) 206 | 207 | UI.OverlayPanel.prototype.attach = function (node) { 208 | node = node || document.body 209 | 210 | node.appendChild(this.dom) 211 | 212 | return this 213 | } 214 | 215 | // Icon (requires font awesome) 216 | 217 | UI.Icon = function (value) { 218 | UI.Panel.call(this) 219 | 220 | var dom = document.createElement('span') 221 | dom.className = 'Icon fa' 222 | 223 | this.dom = dom 224 | 225 | if (value) this.addClass.apply(this, arguments) 226 | 227 | return this 228 | } 229 | 230 | UI.Icon.prototype = Object.create(UI.Panel.prototype) 231 | 232 | UI.Icon.prototype.hasClass = function (value) { 233 | return this.dom.classList.contains('fa-' + value) 234 | } 235 | 236 | UI.Icon.prototype.addClass = function (value) { 237 | for (var i = 0; i < arguments.length; i++) { 238 | this.dom.classList.add('fa-' + arguments[ i ]) 239 | } 240 | 241 | return this 242 | } 243 | 244 | UI.Icon.prototype.setClass = function (value) { 245 | this.dom.className = 'Icon fa' 246 | 247 | for (var i = 0; i < arguments.length; i++) { 248 | this.dom.classList.add('fa-' + arguments[ i ]) 249 | } 250 | 251 | return this 252 | } 253 | 254 | UI.Icon.prototype.removeClass = function (value) { 255 | for (var i = 0; i < arguments.length; i++) { 256 | this.dom.classList.remove('fa-' + arguments[ i ]) 257 | } 258 | 259 | return this 260 | } 261 | 262 | UI.Icon.prototype.switchClass = function (newValue, oldValue) { 263 | this.removeClass(oldValue, newValue) 264 | this.addClass(newValue) 265 | 266 | return this 267 | } 268 | 269 | // Toggle Icon 270 | 271 | UI.ToggleIcon = function (value, classTrue, classFalse) { 272 | UI.Icon.call(this, value ? classTrue : classFalse) 273 | 274 | this.value = value 275 | this.classTrue = classTrue 276 | this.classFalse = classFalse 277 | 278 | return this 279 | } 280 | 281 | UI.ToggleIcon.prototype = Object.create(UI.Icon.prototype) 282 | 283 | UI.ToggleIcon.prototype.setValue = function (value) { 284 | this.value = value 285 | 286 | if (value) { 287 | this.switchClass(this.classTrue, this.classFalse) 288 | } else { 289 | this.switchClass(this.classFalse, this.classTrue) 290 | } 291 | 292 | return this 293 | } 294 | 295 | UI.ToggleIcon.prototype.getValue = function () { 296 | return this.value 297 | } 298 | 299 | // Dispose Icon 300 | 301 | UI.DisposeIcon = function () { 302 | UI.Icon.call(this, 'trash-o') 303 | 304 | var flag = false 305 | var scope = this 306 | 307 | this.setTitle('delete') 308 | this.setCursor('pointer') 309 | 310 | this.onClick(function () { 311 | if (flag === true) { 312 | if (typeof scope.disposeFunction === 'function') { 313 | scope.disposeFunction() 314 | } 315 | } else { 316 | scope.setColor('rgb(178, 34, 34)') 317 | scope.dom.classList.add('deleteInfo') 318 | flag = true 319 | 320 | setTimeout(function () { 321 | scope.setColor('#888') 322 | scope.dom.classList.remove('deleteInfo') 323 | flag = false 324 | }, 1500) 325 | } 326 | }) 327 | 328 | return this 329 | } 330 | 331 | UI.DisposeIcon.prototype = Object.create(UI.Icon.prototype) 332 | 333 | UI.DisposeIcon.prototype.setDisposeFunction = function (fn) { 334 | this.disposeFunction = fn 335 | 336 | return this 337 | } 338 | 339 | // Progress 340 | 341 | UI.Progress = function (max, value) { 342 | UI.Element.call(this) 343 | 344 | var dom = document.createElement('progress') 345 | dom.className = 'Progress' 346 | 347 | dom.max = max || 1.0 348 | if (value !== undefined) dom.value = value 349 | 350 | this.dom = dom 351 | 352 | return this 353 | } 354 | 355 | UI.Progress.prototype = Object.create(UI.Element.prototype) 356 | 357 | UI.Progress.prototype.getValue = function () { 358 | return this.dom.value 359 | } 360 | 361 | UI.Progress.prototype.setValue = function (value) { 362 | this.dom.value = value 363 | 364 | return this 365 | } 366 | 367 | UI.Progress.prototype.setMax = function (value) { 368 | this.dom.max = value 369 | 370 | return this 371 | } 372 | 373 | UI.Progress.prototype.setIndeterminate = function () { 374 | this.dom.removeAttribute('value') 375 | 376 | return this 377 | } 378 | 379 | // Range 380 | 381 | UI.Range = function (min, max, value, step) { 382 | UI.Element.call(this) 383 | 384 | var dom = document.createElement('input') 385 | dom.className = 'Range' 386 | dom.type = 'range' 387 | 388 | dom.min = min.toPrecision(3) 389 | dom.max = max.toPrecision(3) 390 | dom.value = value.toPrecision(3) 391 | dom.step = step.toPrecision(3) 392 | 393 | this.dom = dom 394 | 395 | return this 396 | } 397 | 398 | UI.Range.prototype = Object.create(UI.Element.prototype) 399 | 400 | UI.Range.prototype.getValue = function () { 401 | return parseFloat(this.dom.value) 402 | } 403 | 404 | UI.Range.prototype.setRange = function (min, max) { 405 | this.dom.min = min 406 | this.dom.max = max 407 | 408 | return this 409 | } 410 | 411 | UI.Range.prototype.setValue = function (value) { 412 | this.dom.value = value 413 | 414 | return this 415 | } 416 | 417 | UI.Range.prototype.setStep = function (value) { 418 | this.dom.step = value 419 | 420 | return this 421 | } 422 | 423 | // AdaptiveTextArea 424 | 425 | UI.AdaptiveTextArea = function () { 426 | // http://www.brianchu.com/blog/2013/11/02/creating-an-auto-growing-text-input/ 427 | 428 | UI.Element.call(this) 429 | 430 | var container = document.createElement('div') 431 | container.className = 'AdaptiveTextAreaContainer' 432 | 433 | var textarea = document.createElement('textarea') 434 | textarea.className = 'AdaptiveTextArea' 435 | 436 | var size = document.createElement('div') 437 | size.className = 'AdaptiveTextAreaSize' 438 | 439 | container.appendChild(textarea) 440 | container.appendChild(size) 441 | 442 | textarea.addEventListener('input', function (event) { 443 | size.innerHTML = textarea.value + '\n' 444 | }, false) 445 | 446 | this.textarea = textarea 447 | this.size = size 448 | this.dom = container 449 | 450 | return this 451 | } 452 | 453 | UI.AdaptiveTextArea.prototype = Object.create(UI.Element.prototype) 454 | 455 | UI.AdaptiveTextArea.prototype.getValue = function () { 456 | return this.textarea.value 457 | } 458 | 459 | UI.AdaptiveTextArea.prototype.setValue = function (value) { 460 | this.textarea.value = value 461 | this.size.innerHTML = value + '\n' 462 | 463 | return this 464 | } 465 | 466 | UI.AdaptiveTextArea.prototype.setSpellcheck = function (value) { 467 | this.textarea.spellcheck = value 468 | 469 | return this 470 | } 471 | 472 | UI.AdaptiveTextArea.prototype.setBackgroundColor = function (value) { 473 | this.textarea.style.backgroundColor = value 474 | 475 | return this 476 | } 477 | 478 | // Virtual List 479 | 480 | UI.VirtualList = function (items, itemHeight, height, generatorFn) { 481 | // based on Virtual DOM List 482 | // https://github.com/sergi/virtual-list 483 | // The MIT License (MIT) 484 | // Copyright (C) 2013 Sergi Mansilla 485 | 486 | UI.Element.call(this) 487 | 488 | items = items || [] 489 | itemHeight = itemHeight || 20 490 | height = height || 300 491 | generatorFn = generatorFn || function () {} 492 | 493 | var dom = document.createElement('div') 494 | dom.className = 'VirtualList' 495 | dom.style.height = height + 'px' 496 | 497 | var totalRows = items.length 498 | var screenItemsCount = Math.ceil(height / itemHeight) 499 | var cachedItemsCount = screenItemsCount * 3 500 | var lastRepaintY 501 | var maxBuffer = screenItemsCount * itemHeight 502 | var lastScrolled = 0 503 | var renderChunkCallback = function () {} 504 | 505 | var list = document.createElement('div') 506 | list.style.width = '100%' 507 | list.style.height = height + 'px' 508 | list.style[ 'overflow-y' ] = 'auto' 509 | list.style.position = 'relative' 510 | list.style.padding = 0 511 | 512 | var scroller = document.createElement('div') 513 | scroller.style.opacity = 0 514 | scroller.style.position = 'absolute' 515 | scroller.style.top = 0 516 | scroller.style.left = 0 517 | scroller.style.width = '1px' 518 | scroller.style.height = (itemHeight * totalRows) + 'px' 519 | 520 | function createRow (i) { 521 | var item = generatorFn(i) 522 | item.classList.add('VirtualListRow') 523 | item.style.height = itemHeight + 'px' 524 | item.style.top = (i * itemHeight) + 'px' 525 | return item 526 | } 527 | 528 | function renderChunk (from) { 529 | var finalItem = Math.min(totalRows, from + cachedItemsCount) 530 | renderChunkCallback(from, finalItem) 531 | // Append all the new rows in a document fragment 532 | // that we will later append to the parent node 533 | var fragment = document.createDocumentFragment() 534 | for (var i = from; i < finalItem; i++) { 535 | fragment.appendChild(createRow(i)) 536 | } 537 | // Hide and mark obsolete nodes for deletion. 538 | for (var j = 1, l = list.childNodes.length; j < l; j++) { 539 | list.childNodes[ j ].style.display = 'none' 540 | list.childNodes[ j ].setAttribute('data-rm', '1') 541 | } 542 | list.appendChild(fragment) 543 | }; 544 | 545 | // As soon as scrolling has stopped, this interval asynchronously 546 | // removes all the nodes that are not used anymore 547 | var rmNodeInterval = setInterval(function () { 548 | // check if list is still attached to dom 549 | var element = dom 550 | while (element !== document && element.parentNode) { 551 | element = element.parentNode 552 | } 553 | // if list not attached to dom, clear interval 554 | if (element !== document) { 555 | clearInterval(rmNodeInterval) 556 | } 557 | // remove tagged nodes 558 | if (Date.now() - lastScrolled > 100) { 559 | var badNodes = list.querySelectorAll('[data-rm="1"]') 560 | for (var i = 0, l = badNodes.length; i < l; i++) { 561 | list.removeChild(badNodes[ i ]) 562 | } 563 | } 564 | }, 500) 565 | 566 | function onScroll (e) { 567 | var scrollTop = e.target.scrollTop // Triggers reflow 568 | if (!lastRepaintY || Math.abs(scrollTop - lastRepaintY) > maxBuffer) { 569 | var first = Math.floor(scrollTop / itemHeight) 570 | renderChunk(Math.max(0, first - screenItemsCount)) 571 | lastRepaintY = scrollTop 572 | } 573 | lastScrolled = Date.now() 574 | e.preventDefault && e.preventDefault() 575 | } 576 | 577 | // API 578 | 579 | this.setItems = function (value) { 580 | items = value 581 | totalRows = items.length 582 | scroller.style.height = (itemHeight * totalRows) + 'px' 583 | renderChunk(0) 584 | return this 585 | } 586 | 587 | this.setItemHeight = function (value) { 588 | itemHeight = value 589 | screenItemsCount = Math.ceil(height / itemHeight) 590 | cachedItemsCount = screenItemsCount * 3 591 | maxBuffer = screenItemsCount * itemHeight 592 | scroller.style.height = (itemHeight * totalRows) + 'px' 593 | renderChunk(0) 594 | return this 595 | } 596 | 597 | this.setHeight = function (value) { 598 | UI.Element.prototype.setHeight.call(this, value + 'px') 599 | height = value 600 | screenItemsCount = Math.ceil(height / itemHeight) 601 | cachedItemsCount = screenItemsCount * 3 602 | maxBuffer = screenItemsCount * itemHeight 603 | list.style.height = height + 'px' 604 | scroller.style.height = height + 'px' 605 | renderChunk(0) 606 | return this 607 | } 608 | 609 | this.setGeneratorFn = function (value) { 610 | generatorFn = value 611 | renderChunk(0) 612 | return this 613 | } 614 | 615 | this.setRenderChunkCallback = function (value) { 616 | renderChunkCallback = value 617 | } 618 | 619 | this.redraw = function () { 620 | var first = Math.floor(list.scrollTop / itemHeight) 621 | renderChunk(Math.max(0, first - screenItemsCount)) 622 | lastRepaintY = list.scrollTop 623 | return this 624 | } 625 | 626 | // 627 | 628 | list.appendChild(scroller) 629 | dom.appendChild(list) 630 | list.addEventListener('scroll', onScroll) 631 | renderChunk(0) 632 | 633 | this.dom = dom 634 | 635 | return this 636 | } 637 | 638 | UI.VirtualList.prototype = Object.create(UI.Element.prototype) 639 | 640 | // Virtual Table 641 | 642 | UI.VirtualTable = function (items, itemHeight, height, columns, params) { 643 | var p = params || {} 644 | 645 | UI.Panel.call(this) 646 | 647 | // this.setOverflow( "scroll" ); 648 | 649 | var defaultWidth = p.defaultWidth !== undefined ? p.defaultWidth : 30 650 | var defaultMargin = p.defaultMargin !== undefined ? p.defaultMargin : 5 651 | var defaultAlign = p.defaultAlign !== undefined ? p.defaultAlign : 'left' 652 | var onRowSelect = p.onRowSelect 653 | 654 | // header 655 | 656 | var header = new UI.Panel() 657 | .setWhiteSpace('nowrap') 658 | .setDisplay('inline-block') 659 | .setOverflow('') 660 | .setWidth('100%') 661 | 662 | var fullWidth = 0 663 | 664 | var selected = [] 665 | 666 | var numericalSort = function (a, b) { 667 | return a - b 668 | } 669 | 670 | var lexicalSort = function (a, b) { 671 | return a.localeCompare(b) 672 | } 673 | 674 | var sortColumn = function (idx, flag) { 675 | var sort 676 | if (typeof items[ 0 ][ idx ] === 'string') { 677 | sort = lexicalSort 678 | } else { 679 | sort = numericalSort 680 | } 681 | items.sort(function (a, b) { 682 | if (flag) { 683 | return sort(b[ idx ], a[ idx ]) 684 | } else { 685 | return sort(a[ idx ], b[ idx ]) 686 | } 687 | }) 688 | virtualList.redraw() 689 | return this 690 | } 691 | 692 | var selectRow = function (event, idx) { 693 | selected.length = 0 694 | if (onRowSelect) onRowSelect(event, idx) 695 | if (idx !== undefined) { 696 | selected.push(items[ idx ][ 0 ]) 697 | } 698 | virtualList.redraw() 699 | return this 700 | } 701 | 702 | columns.forEach(function (col) { 703 | var width = col.width || defaultWidth 704 | var margin = col.margin || defaultMargin 705 | 706 | var text = new UI.EllipsisText() 707 | .setValue(col.name) 708 | .setWidth(width + 'px') 709 | .setTextAlign(col.align || defaultAlign) 710 | .setMarginLeft(margin + 'px') 711 | .setMarginRight(margin + 'px') 712 | .setCursor('pointer') 713 | .onClick(function (e) { 714 | var flag = col.__sortFlag === 'ASC' 715 | sortColumn(col.index, flag) 716 | if (flag) { 717 | col.__sortFlag = 'DESC' 718 | } else { 719 | col.__sortFlag = 'ASC' 720 | } 721 | }) 722 | 723 | header.add(text) 724 | 725 | fullWidth += width + 2 * margin 726 | }) 727 | 728 | // list 729 | 730 | var generatorFn = function (index) { 731 | var panel = new UI.Panel() 732 | 733 | columns.forEach(function (col) { 734 | var value = items[ index ][ col.index ] 735 | if (col.format) value = col.format(value) 736 | 737 | var width = col.width || defaultWidth 738 | var margin = col.margin || defaultMargin 739 | 740 | var element 741 | if (typeof value === 'object') { 742 | element = value 743 | } else { 744 | element = new UI.Text() 745 | .setValue(value) 746 | } 747 | 748 | element 749 | .setWidth(width + 'px') 750 | .setTextAlign(col.align || defaultAlign) 751 | .setMarginLeft(margin + 'px') 752 | .setMarginRight(margin + 'px') 753 | .onClick(function (event) { 754 | if (typeof col.onClick === 'function') { 755 | col.onClick(event, index, value) 756 | } 757 | }) 758 | .onMouseOver(function (event) { 759 | if (typeof col.onMouseOver === 'function') { 760 | col.onMouseOver(event, index, value) 761 | } 762 | }) 763 | .onMouseOut(function (event) { 764 | if (typeof col.onMouseOut === 'function') { 765 | col.onMouseOut(event, index, value) 766 | } 767 | }) 768 | 769 | panel.add(element) 770 | }) 771 | 772 | panel 773 | .setCursor('pointer') 774 | .onClick(function (event) { 775 | selectRow(event, index) 776 | }) 777 | 778 | if (selected.indexOf(items[ index ][ 0 ]) !== -1) { 779 | panel.dom.classList.add('highlight') 780 | } 781 | 782 | return panel.dom 783 | } 784 | 785 | var virtualList = new UI.VirtualList( 786 | items, itemHeight, height, generatorFn 787 | ).setWidth((fullWidth + 20) + 'px') 788 | 789 | // 790 | 791 | this.add( header, virtualList ) 792 | 793 | // API 794 | 795 | this.header = header 796 | this.list = virtualList 797 | this.sortColumn = sortColumn 798 | this.selectRow = function (idx) { 799 | selectRow(undefined, idx) 800 | } 801 | 802 | return this 803 | } 804 | 805 | UI.VirtualTable.prototype = Object.create(UI.Panel.prototype) 806 | 807 | // Popup Menu (requires Tether) 808 | 809 | UI.PopupMenu = function (iconClass, heading, constraintTo) { 810 | constraintTo = constraintTo || 'scrollParent' 811 | 812 | UI.Panel.call(this) 813 | 814 | var entryLabelWidth = '100px' 815 | 816 | var icon = new UI.Icon(iconClass || 'bars') 817 | 818 | var panel = new UI.OverlayPanel() 819 | .setDisplay('none') 820 | .attach(this.dom) 821 | 822 | var xOffset = 0 823 | var yOffset = 0 824 | 825 | var prevX = 0 826 | var prevY = 0 827 | 828 | function onMousemove (e) { 829 | if (prevX === 0) { 830 | prevX = e.clientX 831 | prevY = e.clientY 832 | } 833 | xOffset += prevX - e.clientX 834 | yOffset += prevY - e.clientY 835 | prevX = e.clientX 836 | prevY = e.clientY 837 | tether.setOptions({ 838 | element: panel.dom, 839 | target: icon.dom, 840 | attachment: 'top right', 841 | targetAttachment: 'top left', 842 | offset: yOffset + 'px ' + xOffset + 'px', 843 | constraints: [{ 844 | to: constraintTo, 845 | pin: ['top', 'bottom'] 846 | }] 847 | }) 848 | tether.position() 849 | } 850 | 851 | var headingPanel = new UI.Panel() 852 | .setBorderBottom('1px solid #555') 853 | .setMarginBottom('10px') 854 | .setHeight('25px') 855 | .setCursor('move') 856 | .onMouseDown(function (e) { 857 | if (e.which === 1) { 858 | document.addEventListener('mousemove', onMousemove) 859 | } 860 | document.addEventListener('mouseup', function (e) { 861 | document.removeEventListener('mousemove', onMousemove) 862 | }) 863 | }) 864 | 865 | headingPanel 866 | .add( 867 | new UI.Icon('times') 868 | .setFloat('right') 869 | .setCursor('pointer') 870 | .onClick(function () { 871 | this.setMenuDisplay('none') 872 | }.bind(this)) 873 | ) 874 | .add( 875 | new UI.Text(heading) 876 | ) 877 | 878 | panel.add(headingPanel) 879 | 880 | var tether 881 | 882 | icon.setTitle('menu') 883 | icon.setCursor('pointer') 884 | icon.onClick(function (e) { 885 | if (panel.getDisplay() === 'block') { 886 | this.setMenuDisplay('none') 887 | tether.destroy() 888 | return 889 | } 890 | 891 | panel.setMaxHeight((window.innerHeight / 1.2) + 'px') 892 | this.setMenuDisplay('block') 893 | 894 | xOffset = 5; 895 | yOffset = 0; 896 | 897 | tether = new Tether({ 898 | element: panel.dom, 899 | target: icon.dom, 900 | attachment: 'top right', 901 | targetAttachment: 'top left', 902 | offset: '0px 5px', 903 | constraints: [{ 904 | to: constraintTo, 905 | attachment: 'element', 906 | pin: ['top', 'bottom'] 907 | }] 908 | }) 909 | 910 | tether.position() 911 | }.bind(this)) 912 | 913 | this.add(icon) 914 | 915 | this.setClass('') 916 | .setDisplay('inline') 917 | 918 | this.icon = icon 919 | this.panel = panel 920 | this.entryLabelWidth = entryLabelWidth 921 | 922 | return this 923 | } 924 | 925 | UI.PopupMenu.prototype = Object.create(UI.Panel.prototype) 926 | 927 | UI.PopupMenu.prototype.addEntry = function (label, entry) { 928 | this.panel 929 | .add(new UI.Text(label) 930 | // .setWhiteSpace( "nowrap" ) 931 | .setWidth(this.entryLabelWidth)) 932 | .add(entry || new UI.Panel()) 933 | .add(new UI.Break()) 934 | 935 | return this 936 | } 937 | 938 | UI.PopupMenu.prototype.setEntryLabelWidth = function (value) { 939 | this.entryLabelWidth = value 940 | 941 | return this 942 | } 943 | 944 | UI.PopupMenu.prototype.setMenuDisplay = function (value) { 945 | this.panel.setDisplay(value) 946 | 947 | if (value !== 'none') this.panel.dom.focus() 948 | 949 | return this 950 | } 951 | 952 | UI.PopupMenu.prototype.setIconTitle = function (value) { 953 | this.icon.setTitle(value) 954 | 955 | return this 956 | } 957 | 958 | UI.PopupMenu.prototype.dispose = function () { 959 | this.panel.dispose() 960 | 961 | UI.Element.prototype.dispose.call(this) 962 | } 963 | 964 | // Collapsible Icon Panel 965 | 966 | UI.CollapsibleIconPanel = function (iconClass1, iconClass2) { 967 | UI.Panel.call(this) 968 | 969 | this.dom.className = 'Panel CollapsiblePanel' 970 | 971 | if (iconClass1 === undefined) { 972 | // iconClass1 = iconClass1 || "plus-square"; 973 | // iconClass2 = iconClass2 || "minus-square"; 974 | 975 | iconClass1 = iconClass1 || 'chevron-down' 976 | iconClass2 = iconClass2 || 'chevron-right' 977 | } 978 | 979 | this.button = new UI.Icon(iconClass1) 980 | .setTitle('expand/collapse') 981 | .setCursor('pointer') 982 | .setWidth('12px') 983 | .setMarginRight('6px') 984 | this.addStatic(this.button) 985 | 986 | var scope = this 987 | this.button.dom.addEventListener('click', function (event) { 988 | scope.toggle() 989 | }, false) 990 | 991 | this.content = document.createElement('div') 992 | this.content.className = 'CollapsibleContent' 993 | this.dom.appendChild(this.content) 994 | 995 | this.isCollapsed = false 996 | 997 | this.iconClass1 = iconClass1 998 | this.iconClass2 = iconClass2 999 | 1000 | return this 1001 | } 1002 | 1003 | UI.CollapsibleIconPanel.prototype = Object.create(UI.CollapsiblePanel.prototype) 1004 | 1005 | UI.CollapsibleIconPanel.prototype.setCollapsed = function (setCollapsed) { 1006 | if (setCollapsed) { 1007 | this.dom.classList.add('collapsed') 1008 | 1009 | if (this.iconClass2) { 1010 | this.button.switchClass(this.iconClass2, this.iconClass1) 1011 | } else { 1012 | this.button.addClass('rotate-90') 1013 | } 1014 | } else { 1015 | this.dom.classList.remove('collapsed') 1016 | 1017 | if (this.iconClass2) { 1018 | this.button.switchClass(this.iconClass1, this.iconClass2) 1019 | } else { 1020 | this.button.removeClass('rotate-90') 1021 | } 1022 | } 1023 | 1024 | this.isCollapsed = setCollapsed 1025 | } 1026 | 1027 | // Color picker (requires FlexiColorPicker) 1028 | // https://github.com/DavidDurman/FlexiColorPicker 1029 | // https://github.com/zvin/FlexiColorPicker 1030 | 1031 | UI.ColorPicker = function () { 1032 | var scope = this 1033 | 1034 | UI.Panel.call(this) 1035 | 1036 | // slider 1037 | 1038 | this.slideWrapper = new UI.Panel() 1039 | .setClass('slide-wrapper') 1040 | 1041 | this.sliderIndicator = new UI.Panel() 1042 | .setClass('slide-indicator') 1043 | 1044 | this.slider = new UI.Panel() 1045 | .setClass('slide') 1046 | .setWidth('25px') 1047 | .setHeight('80px') 1048 | 1049 | this.slideWrapper.add( 1050 | this.slider, 1051 | this.sliderIndicator 1052 | ) 1053 | 1054 | // picker 1055 | 1056 | this.pickerWrapper = new UI.Panel() 1057 | .setClass('picker-wrapper') 1058 | 1059 | this.pickerIndicator = new UI.Panel() 1060 | .setClass('picker-indicator') 1061 | 1062 | this.picker = new UI.Panel() 1063 | .setClass('picker') 1064 | .setWidth('130px') 1065 | .setHeight('80px') 1066 | 1067 | this.pickerWrapper.add( 1068 | this.picker, 1069 | this.pickerIndicator 1070 | ) 1071 | 1072 | // event 1073 | 1074 | var changeEvent = document.createEvent('Event') 1075 | changeEvent.initEvent('change', true, true) 1076 | 1077 | // finalize 1078 | 1079 | this.add( 1080 | this.pickerWrapper, 1081 | this.slideWrapper 1082 | ) 1083 | 1084 | this.colorPicker = ColorPicker( 1085 | 1086 | this.slider.dom, 1087 | this.picker.dom, 1088 | 1089 | function (hex, hsv, rgb, pickerCoordinate, sliderCoordinate) { 1090 | if (!pickerCoordinate && sliderCoordinate && hsv.s < 0.05) { 1091 | hsv.s = 0.5 1092 | hsv.v = 0.7 1093 | scope.colorPicker.setHsv(hsv) 1094 | 1095 | return 1096 | } 1097 | 1098 | ColorPicker.positionIndicators( 1099 | scope.sliderIndicator.dom, scope.pickerIndicator.dom, 1100 | sliderCoordinate, pickerCoordinate 1101 | ) 1102 | 1103 | scope.hex = hex 1104 | scope.hsv = hsv 1105 | scope.rgb = rgb 1106 | 1107 | if (!scope._settingValue) { 1108 | scope.dom.dispatchEvent(changeEvent) 1109 | } 1110 | } 1111 | 1112 | ) 1113 | 1114 | this.colorPicker.fixIndicators( 1115 | this.sliderIndicator.dom, 1116 | this.pickerIndicator.dom 1117 | ) 1118 | 1119 | return this 1120 | } 1121 | 1122 | UI.ColorPicker.prototype = Object.create(UI.Panel.prototype) 1123 | 1124 | UI.ColorPicker.prototype.setValue = function (value) { 1125 | if (value !== this.hex) { 1126 | this._settingValue = true 1127 | this.colorPicker.setHex(value) 1128 | this._settingValue = false 1129 | } 1130 | 1131 | return this 1132 | } 1133 | 1134 | UI.ColorPicker.prototype.getValue = function () { 1135 | return this.hex 1136 | } 1137 | -------------------------------------------------------------------------------- /mdsrv/webapp/js/ui/ui.helper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file UI HELPER 3 | * @author Alexander Rose 4 | */ 5 | 6 | var unicodeHelper = function(){ 7 | 8 | var replace_map = { 9 | "{alpha}": "\u03B1", 10 | "{beta}": "\u03B2", 11 | "{gamma}": "\u03B3", 12 | "{dot}": "\u00B7", 13 | "{bullet}": "\u2022", 14 | } 15 | 16 | var keys = Object.keys( replace_map ).join('|'); 17 | 18 | var rg = new RegExp( '(' + keys + ')', 'gi' ); 19 | 20 | return function( str ){ 21 | 22 | return str.replace( 23 | rg, function( s, p1, p2, offset, sx ){ 24 | return replace_map[ String( s ) ]; 25 | } 26 | ); 27 | 28 | }; 29 | 30 | }(); 31 | 32 | 33 | function scriptHelperFunctions( stage, panel ){ 34 | 35 | var U = unicodeHelper; 36 | 37 | // 38 | 39 | function components( name ){ 40 | 41 | return stage.getComponentsByName( name ); 42 | 43 | } 44 | 45 | function representations( name ){ 46 | 47 | return stage.getRepresentationsByName( name ); 48 | 49 | } 50 | 51 | function structures( name ){ 52 | 53 | return stage.getComponentsByName( name, NGL.StructureComponent ); 54 | 55 | } 56 | 57 | // 58 | 59 | function color( value, collection ){ 60 | 61 | collection.setColor( value ); 62 | 63 | } 64 | 65 | function visibility( value, collection ){ 66 | 67 | collection.setVisibility( value ); 68 | 69 | } 70 | 71 | function hide( collection ){ 72 | 73 | visibility( false, collection ); 74 | 75 | } 76 | 77 | function show( collection, only ){ 78 | 79 | if( only ) hide(); 80 | 81 | visibility( true, collection ); 82 | 83 | } 84 | 85 | function superpose( comp1, comp2, align, sele1, sele2, xsele1, xsele2 ){ 86 | 87 | comp1.superpose( comp2, align, sele1, sele2, xsele1, xsele2 ); 88 | 89 | } 90 | 91 | // 92 | 93 | function uiText( text, newline ){ 94 | 95 | var elm = new UI.Text( U( text ) ); 96 | 97 | panel.add( elm ); 98 | 99 | if( newline ) uiBreak( 1 ); 100 | 101 | return elm; 102 | 103 | } 104 | 105 | function uiHtml( html, newline ){ 106 | 107 | var elm = new UI.Html( U( html ) ); 108 | 109 | panel.add( elm ); 110 | 111 | if( newline ) uiBreak( 1 ); 112 | 113 | return elm; 114 | 115 | } 116 | 117 | function uiBreak( n ){ 118 | 119 | n = n === undefined ? 1 : n; 120 | 121 | for( var i = 0; i < n; ++i ){ 122 | 123 | panel.add( new UI.Break() ); 124 | 125 | } 126 | 127 | } 128 | 129 | function uiButton( label, callback ){ 130 | 131 | var btn = new UI.Button( U( label ) ).onClick( function(){ 132 | callback( btn ); 133 | } ); 134 | 135 | panel.add( btn ); 136 | 137 | return btn; 138 | 139 | } 140 | 141 | function uiSelect( options, callback ){ 142 | 143 | if( Array.isArray( options ) ){ 144 | var newOptions = {}; 145 | options.forEach( function( name ){ 146 | newOptions[ name ] = name; 147 | } ); 148 | options = newOptions; 149 | } 150 | 151 | var select = new UI.Select() 152 | .setOptions( options ) 153 | .onChange( function(){ 154 | callback( select ); 155 | } ); 156 | 157 | panel.add( select ); 158 | 159 | return select; 160 | 161 | } 162 | 163 | /* 164 | function uiOpenButton( label, callback, extensionList ){ 165 | 166 | var btn = new UI.Button( U( label ) ).onClick( function(){ 167 | 168 | NGL.open( callback, extensionList ); 169 | 170 | } ); 171 | 172 | panel.add( btn ); 173 | 174 | return btn; 175 | 176 | } 177 | */ 178 | 179 | function uiDownloadButton( label, callback, downloadName ){ 180 | 181 | var btn = new UI.Button( U( label ) ).onClick( function(){ 182 | 183 | NGL.download( callback, downloadName ); 184 | 185 | } ); 186 | 187 | panel.add( btn ); 188 | 189 | return btn; 190 | 191 | } 192 | 193 | function uiVisibilitySelect( collection ){ 194 | 195 | var list = collection.list; 196 | 197 | function getVisible(){ 198 | 199 | var nameList = []; 200 | 201 | list.forEach( function( o ){ 202 | 203 | if( o.visible ) nameList.push( o.name ); 204 | 205 | } ); 206 | 207 | return nameList; 208 | 209 | } 210 | 211 | var options = { "": "[show]" }; 212 | 213 | list.forEach( function( o ){ 214 | 215 | options[ o.name ] = o.name; 216 | 217 | o.signals.visibilityChanged.add( function(){ 218 | 219 | var nameList = getVisible(); 220 | 221 | if( nameList.length === list.length ){ 222 | select.setValue( "" ); 223 | }else if( o.visible ){ 224 | select.setValue( o.name ); 225 | }else{ 226 | select.setValue( nameList[ 0 ] ); 227 | } 228 | 229 | } ); 230 | 231 | } ); 232 | 233 | var select = new UI.Select() 234 | .setOptions( options ) 235 | .onChange( function(){ 236 | 237 | var name = select.getValue(); 238 | 239 | if( name === "" ){ 240 | show( collection ); 241 | }else{ 242 | hide( collection ); 243 | show( stage.getAnythingByName( name ) ); 244 | } 245 | 246 | } ); 247 | 248 | panel.add( select ); 249 | 250 | return select; 251 | 252 | } 253 | 254 | function uiVisibilityButton( label, collection ){ 255 | 256 | label = U( label ? label : "all" ); 257 | collection = collection || new NGL.Collection(); 258 | 259 | if( !( collection instanceof NGL.Collection ) && 260 | !( collection instanceof NGL.ComponentCollection ) && 261 | !( collection instanceof NGL.RepresentationCollection ) 262 | ){ 263 | collection = new NGL.Collection( [ collection ] ); 264 | } 265 | 266 | var list = collection.list; 267 | 268 | function isVisible(){ 269 | 270 | var visible = false; 271 | 272 | list.forEach( function( o ){ 273 | 274 | if( o.visible ) visible = true; 275 | 276 | } ); 277 | 278 | return visible; 279 | 280 | } 281 | 282 | function getLabel( value ){ 283 | 284 | return ( isVisible() ? "hide " : "show " ) + label; 285 | 286 | } 287 | 288 | list.forEach( function( o ){ 289 | 290 | o.signals.visibilityChanged.add( function(){ 291 | 292 | btn.setLabel( getLabel() ); 293 | 294 | } ); 295 | 296 | } ); 297 | 298 | var btn = new UI.Button( getLabel() ).onClick( function(){ 299 | 300 | visibility( !isVisible(), collection ); 301 | 302 | } ); 303 | 304 | // panel.add( btn ); 305 | 306 | return btn; 307 | 308 | } 309 | 310 | function uiPlayButton( label, trajComp, step, timeout, start, end ){ 311 | 312 | var traj = trajComp.trajectory; 313 | label = U( label ); 314 | 315 | var player = new NGL.TrajectoryPlayer( traj, step, timeout, start, end ); 316 | player.mode = "once"; 317 | 318 | var btn = new UI.Button( "play " + label ) 319 | .onClick( function(){ 320 | player.toggle(); 321 | } ); 322 | 323 | player.signals.startedRunning.add( function(){ 324 | btn.setLabel( "pause " + label ); 325 | } ); 326 | 327 | player.signals.haltedRunning.add( function(){ 328 | btn.setLabel( "play " + label ); 329 | } ); 330 | 331 | panel.add( btn ); 332 | 333 | return btn; 334 | 335 | } 336 | 337 | // 338 | 339 | return { 340 | 341 | 'components': components, 342 | 'representations': representations, 343 | 'structures': structures, 344 | 345 | 'color': color, 346 | 'visible': visibility, 347 | 'hide': hide, 348 | 'show': show, 349 | 'superpose': superpose, 350 | 351 | 'uiText': uiText, 352 | 'uiHtml': uiHtml, 353 | 'uiBreak': uiBreak, 354 | 'uiSelect': uiSelect, 355 | 'uiButton': uiButton, 356 | //'uiOpenButton': uiOpenButton, 357 | 'uiDownloadButton': uiDownloadButton, 358 | 'uiVisibilitySelect': uiVisibilitySelect, 359 | 'uiVisibilityButton': uiVisibilityButton, 360 | 'uiPlayButton': uiPlayButton, 361 | 362 | }; 363 | 364 | }; 365 | -------------------------------------------------------------------------------- /mdsrv/webapp/js/ui/ui.ngl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file UI NGL 3 | * @author Alexander Rose 4 | */ 5 | 6 | 7 | // Color 8 | 9 | UI.ColorPopupMenu = function(){ 10 | 11 | var scope = this; 12 | 13 | UI.Panel.call( this ); 14 | 15 | this.iconText = new UI.Text( "" ) 16 | .setCursor( "pointer" ) 17 | .setClass( "fa-stack-1x" ) 18 | .setFontFamily( "Arial, sans-serif" ) 19 | .setColor( "#111" ); 20 | 21 | this.iconSquare = new UI.Icon( "square", "stack-1x" ) 22 | //.setMarginTop( "0.05em" ); 23 | 24 | this.menu = new UI.PopupMenu( "stack", "Color" ); 25 | 26 | this.menu.icon 27 | .setTitle( "color" ) 28 | .setWidth( "1em" ).setHeight( "1em" ).setLineHeight( "1em" ) 29 | .add( this.iconSquare ) 30 | .add( this.iconText ) 31 | 32 | var changeEvent = document.createEvent( 'Event' ); 33 | changeEvent.initEvent( 'change', true, true ); 34 | 35 | this.colorInput = new UI.Input() 36 | .onChange( function(){ 37 | 38 | scope.setColor( scope.colorInput.getValue() ); 39 | scope.dom.dispatchEvent( changeEvent ); 40 | 41 | } ); 42 | 43 | this.colorPicker = new UI.ColorPicker() 44 | .setDisplay( "inline-block" ) 45 | .onChange( function( e ){ 46 | 47 | scope.setColor( scope.colorPicker.getValue() ); 48 | scope.dom.dispatchEvent( changeEvent ); 49 | 50 | } ); 51 | 52 | this.menu 53 | .addEntry( "Input", this.colorInput ) 54 | .addEntry( "Picker", this.colorPicker ); 55 | 56 | this.add( this.menu ); 57 | 58 | this.setClass( "" ) 59 | .setDisplay( "inline" ); 60 | 61 | return this; 62 | 63 | }; 64 | 65 | UI.ColorPopupMenu.prototype = Object.create( UI.Panel.prototype ); 66 | 67 | UI.ColorPopupMenu.prototype.setColor = function(){ 68 | 69 | var c = new NGL.Color(); 70 | 71 | return function( value ){ 72 | 73 | c.set( value ); 74 | value = "#" + c.getHexString(); 75 | 76 | this.colorInput 77 | .setBackgroundColor( value ) 78 | .setValue( value ); 79 | 80 | this.colorPicker.setValue( value ); 81 | 82 | this.iconSquare.setColor( value ); 83 | 84 | // perceived brightness (http://alienryderflex.com/hsp.html) 85 | var brightness = Math.sqrt( 86 | c.r*255 * c.r*255 * 0.241 + 87 | c.g*255 * c.g*255 * 0.691 + 88 | c.b*255 * c.b*255 * 0.068 89 | ); 90 | 91 | if( brightness > 130 ){ 92 | this.iconText.setColor( "#000000" ); 93 | this.colorInput.setColor( "#000000" ); 94 | }else{ 95 | this.iconText.setColor( "#FFFFFF" ); 96 | this.colorInput.setColor( "#FFFFFF" ); 97 | } 98 | 99 | return this; 100 | 101 | } 102 | 103 | }(); 104 | 105 | UI.ColorPopupMenu.prototype.getColor = function(){ 106 | 107 | return this.colorInput.getValue(); 108 | 109 | }; 110 | 111 | UI.ColorPopupMenu.prototype.getValue = function(){ 112 | 113 | return this.getColor(); 114 | 115 | }; 116 | 117 | UI.ColorPopupMenu.prototype.setValue = function( value ){ 118 | 119 | this.setColor( value ); 120 | 121 | return this; 122 | 123 | }; 124 | 125 | UI.ColorPopupMenu.prototype.dispose = function(){ 126 | 127 | this.menu.dispose(); 128 | 129 | UI.Panel.prototype.dispose.call( this ); 130 | 131 | }; 132 | 133 | 134 | // Vector3 135 | 136 | UI.Vector3 = function( value ){ 137 | 138 | UI.Panel.call( this ).setDisplay( "inline-block" ); 139 | 140 | this.xNumber = new UI.Number( 0 ).setWidth( "40px" ); 141 | this.yNumber = new UI.Number( 0 ).setWidth( "40px" ); 142 | this.zNumber = new UI.Number( 0 ).setWidth( "40px" ); 143 | 144 | this.add( this.xNumber, this.yNumber, this.zNumber ); 145 | this.setValue( value ); 146 | 147 | var changeEvent = document.createEvent( 'Event' ); 148 | changeEvent.initEvent( 'change', true, true ); 149 | 150 | this.xNumber.onChange( function(){ 151 | this.dom.dispatchEvent( changeEvent ); 152 | }.bind( this ) ); 153 | this.yNumber.onChange( function(){ 154 | this.dom.dispatchEvent( changeEvent ); 155 | }.bind( this ) ); 156 | this.zNumber.onChange( function(){ 157 | this.dom.dispatchEvent( changeEvent ); 158 | }.bind( this ) ); 159 | 160 | return this; 161 | 162 | }; 163 | 164 | UI.Vector3.prototype = Object.create( UI.Panel.prototype ); 165 | 166 | UI.Vector3.prototype.getValue = function(){ 167 | 168 | return { 169 | x: this.xNumber.getValue(), 170 | y: this.yNumber.getValue(), 171 | z: this.zNumber.getValue() 172 | }; 173 | 174 | }; 175 | 176 | UI.Vector3.prototype.setValue = function( value ){ 177 | 178 | if( value ){ 179 | this.xNumber.setValue( value.x ); 180 | this.yNumber.setValue( value.y ); 181 | this.zNumber.setValue( value.z ); 182 | } 183 | 184 | return this; 185 | 186 | }; 187 | 188 | UI.Vector3.prototype.setPrecision = function ( precision ) { 189 | 190 | this.xNumber.setPrecision( precision ); 191 | this.yNumber.setPrecision( precision ); 192 | this.zNumber.setPrecision( precision ); 193 | 194 | return this; 195 | 196 | }; 197 | 198 | UI.Vector3.prototype.setRange = function( min, max ){ 199 | 200 | this.xNumber.setRange( min, max ); 201 | this.yNumber.setRange( min, max ); 202 | this.zNumber.setRange( min, max ); 203 | 204 | return this; 205 | 206 | }; 207 | 208 | 209 | // Selection 210 | 211 | UI.SelectionInput = function( selection ){ 212 | 213 | UI.AdaptiveTextArea.call( this ); 214 | 215 | this.setSpellcheck( false ); 216 | 217 | if( ! ( selection.type === "selection" ) ){ 218 | 219 | NGL.error( "UI.SelectionInput: not a selection", selection ); 220 | 221 | return this; 222 | 223 | } 224 | 225 | this.setValue( selection.string ); 226 | 227 | this.selection = selection; 228 | 229 | var scope = this; 230 | 231 | var signals = selection.signals; 232 | 233 | signals.stringChanged.add( function( string ){ 234 | 235 | scope.setValue( string ); 236 | 237 | } ); 238 | 239 | this.onEnter(); 240 | 241 | return this; 242 | 243 | }; 244 | 245 | UI.SelectionInput.prototype = Object.create( UI.AdaptiveTextArea.prototype ); 246 | 247 | UI.SelectionInput.prototype.setValue = function( value ){ 248 | 249 | UI.AdaptiveTextArea.prototype.setValue.call( this, value ); 250 | 251 | return this; 252 | 253 | }; 254 | 255 | UI.SelectionInput.prototype.onEnter = function( callback ){ 256 | 257 | // TODO more a private method 258 | 259 | var scope = this; 260 | 261 | var check = function( string ){ 262 | 263 | var selection = new NGL.Selection( string ); 264 | 265 | return !selection.selection[ "error" ]; 266 | 267 | } 268 | 269 | this.onKeyPress( function( e ){ 270 | 271 | var value = scope.getValue(); 272 | var character = String.fromCharCode( e.which ); 273 | 274 | if( e.keyCode === 13 ){ 275 | 276 | e.preventDefault(); 277 | 278 | if( check( value ) ){ 279 | 280 | if( typeof callback === "function" ){ 281 | 282 | callback( value ); 283 | 284 | }else{ 285 | 286 | scope.selection.setString( value ); 287 | 288 | } 289 | 290 | scope.setBackgroundColor( "white" ); 291 | 292 | }else{ 293 | 294 | scope.setBackgroundColor( "tomato" ); 295 | 296 | } 297 | 298 | }else if( scope.selection.string !== value + character ){ 299 | 300 | scope.setBackgroundColor( "skyblue" ); 301 | 302 | }else{ 303 | 304 | scope.setBackgroundColor( "white" ); 305 | 306 | } 307 | 308 | } ); 309 | 310 | this.onKeyUp( function( e ){ 311 | 312 | var value = scope.getValue(); 313 | 314 | if( !check( value ) ){ 315 | 316 | scope.setBackgroundColor( "tomato" ); 317 | 318 | }else if( scope.selection.string === scope.getValue() ){ 319 | 320 | scope.setBackgroundColor( "white" ); 321 | 322 | }else{ 323 | 324 | scope.setBackgroundColor( "skyblue" ); 325 | 326 | } 327 | 328 | } ); 329 | 330 | return this; 331 | 332 | }; 333 | 334 | 335 | UI.SelectionPanel = function( selection ){ 336 | 337 | UI.Panel.call( this ); 338 | 339 | this.icon = new UI.Icon( 'filter' ) 340 | .setTitle( "filter selection" ) 341 | .addClass( 'lg' ) 342 | .setMarginRight( "10px" ); 343 | 344 | this.input = new UI.SelectionInput( selection ); 345 | 346 | this.add( this.icon, this.input ); 347 | 348 | return this; 349 | 350 | }; 351 | 352 | UI.SelectionPanel.prototype = Object.create( UI.Panel.prototype ); 353 | 354 | UI.SelectionPanel.prototype.setInputWidth = function( value ){ 355 | 356 | this.input.setWidth( value ); 357 | 358 | return this; 359 | 360 | }; 361 | 362 | 363 | // Component 364 | 365 | UI.ComponentPanel = function( component ){ 366 | 367 | UI.Panel.call( this ); 368 | 369 | var stage = component.stage; 370 | var signals = component.signals; 371 | 372 | signals.nameChanged.add( function( value ){ 373 | 374 | name.setValue( value ); 375 | 376 | } ); 377 | 378 | signals.visibilityChanged.add( function( value ){ 379 | 380 | toggle.setValue( value ); 381 | 382 | } ); 383 | 384 | signals.disposed.add( function(){ 385 | 386 | menu.dispose(); 387 | 388 | } ); 389 | 390 | // Name 391 | 392 | var name = new UI.EllipsisText( component.name ) 393 | .setWidth( "100px" ); 394 | 395 | // Actions 396 | 397 | var toggle = new UI.ToggleIcon( component.visible, "eye", "eye-slash" ) 398 | .setTitle( "hide/show" ) 399 | .setCursor( "pointer" ) 400 | .setMarginLeft( "25px" ) 401 | .onClick( function(){ 402 | 403 | component.setVisibility( !component.visible ); 404 | 405 | } ); 406 | 407 | var center = new UI.Icon( "bullseye" ) 408 | .setTitle( "center" ) 409 | .setCursor( "pointer" ) 410 | .setMarginLeft( "10px" ) 411 | .onClick( function(){ 412 | 413 | component.autoView( 1000 ); 414 | 415 | } ); 416 | 417 | var dispose = new UI.DisposeIcon() 418 | .setMarginLeft( "10px" ) 419 | .setDisposeFunction( function(){ 420 | 421 | stage.removeComponent( component ); 422 | 423 | } ); 424 | 425 | // Menu 426 | 427 | var menu = new UI.PopupMenu( "bars", component.type ) 428 | .setMarginLeft( "46px" ) 429 | .setEntryLabelWidth( "110px" ); 430 | 431 | // 432 | 433 | this.add( name, toggle, center, dispose, menu ); 434 | 435 | // 436 | 437 | this.menu = menu; 438 | 439 | return this; 440 | 441 | }; 442 | 443 | UI.ComponentPanel.prototype = Object.create( UI.Panel.prototype ); 444 | 445 | UI.ComponentPanel.prototype.addMenuEntry = function( label, entry ){ 446 | 447 | this.menu.addEntry( label, entry ); 448 | 449 | return this; 450 | 451 | }; 452 | 453 | UI.ComponentPanel.prototype.setMenuDisplay = function( value ){ 454 | 455 | this.menu.setMenuDisplay( value ); 456 | 457 | return this; 458 | 459 | }; 460 | -------------------------------------------------------------------------------- /mdsrv/webapp/mobile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | NGL - mobile 5 | 6 | 7 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 111 |
112 |
113 |
114 | 115 | 116 | 117 | 118 |
119 | 120 | 121 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | # See the docstring in versioneer.py for instructions. Note that you must 4 | # re-run 'versioneer.py setup' after changing this section, and commit the 5 | # resulting files. 6 | 7 | [versioneer] 8 | VCS = git 9 | style = pep440 10 | versionfile_source = mdsrv/_version.py 11 | versionfile_build = mdsrv/_version.py 12 | tag_prefix = v 13 | #parentdir_prefix = 14 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | 5 | import os 6 | 7 | import versioneer 8 | from versioneer import get_cmdclass 9 | sdist = get_cmdclass()['sdist'] 10 | build_py = get_cmdclass()['build_py'] 11 | 12 | here = os.path.dirname(os.path.abspath(__file__)) 13 | node_root = os.path.join(here, 'js') 14 | is_repo = os.path.exists(os.path.join(here, '.git')) 15 | 16 | 17 | 18 | 19 | CLASSIFIERS = [ 20 | "Development Status :: 3 - Alpha", 21 | "Intended Audience :: Science/Research", 22 | "Intended Audience :: Developers", 23 | "License :: OSI Approved :: MIT License", 24 | "Programming Language :: JavaScript", 25 | "Programming Language :: Python", 26 | "Programming Language :: Python :: 2", 27 | "Programming Language :: Python :: 3", 28 | "Topic :: Scientific/Engineering :: Bio-Informatics", 29 | "Topic :: Scientific/Engineering :: Chemistry", 30 | "Topic :: Scientific/Engineering :: Visualization", 31 | "Operating System :: POSIX", 32 | "Operating System :: Unix", 33 | "Operating System :: MacOS", 34 | ] 35 | 36 | 37 | 38 | setup_args = { 39 | 'name': 'MDsrv', 40 | 'version': versioneer.get_version(), 41 | 'description': 'Server for coordinate trajectories from molecular dynamics simulations.', 42 | 'include_package_data': True, 43 | 'package_data': { 44 | "mdsrv.data": ["*"] 45 | }, 46 | 'license': "MIT", 47 | 'entry_points': {'console_scripts': 48 | ['mdsrv = mdsrv:entry_point',] 49 | }, 50 | 'setup_requires': [ 51 | "cython", 52 | "numpy", 53 | "scipy", 54 | "setuptools", 55 | 56 | ], 57 | 'install_requires': [ 58 | "flask", 59 | "mdtraj", 60 | 61 | ], 62 | 'extra_requires': { 63 | "mdanalysis;platform_system!='Windows' and python_version<'3.4'": ["mdanalysis"], 64 | }, 65 | 'packages': set(find_packages() + 66 | ['mdsrv']), 67 | 'zip_safe': False, 68 | 'cmdclass': versioneer.get_cmdclass(), 69 | 'author': 'Alexander S. Rose, Johanna K. S. Tiemann', 70 | 'author_email': 'alexander.rose@weirdbyte.de, johanna.tiemann@gmail.com', 71 | 'url': 'https://github.com/arose/mdsrv', 72 | 'keywords': [ 73 | 'Molecular Dynamics simulation', 74 | ], 75 | 'classifiers': CLASSIFIERS, 76 | } 77 | 78 | 79 | setup(**setup_args) 80 | --------------------------------------------------------------------------------