├── .editorconfig ├── .gitignore ├── .jshintrc ├── .project ├── .settings ├── .jsdtscope ├── com.eclipsesource.jshint.ui.prefs ├── org.eclipse.wst.jsdt.core.prefs ├── org.eclipse.wst.jsdt.ui.superType.container └── org.eclipse.wst.jsdt.ui.superType.name ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.md ├── client-tests └── .placeholder ├── client-v2-jsx ├── actionpalettecomponent.js ├── actions │ └── mapactions.js ├── constants │ └── mapconstants.js ├── dialogs │ ├── NodeEditDialog.js │ └── SharingDialog.js ├── dispatcher │ └── mapdispatcher.js ├── download.js ├── draggablepalettecomponent.js ├── editableshorttext.js ├── main.js ├── mapcanvas.js ├── mapcomponent.js ├── mapeditor.js ├── mapeditorpage.js ├── mapstatus.js ├── maptitledescription.js ├── palette.js ├── relatedmaps.js └── store │ ├── ShareDialogStore.js │ └── mapstore.js ├── client ├── 3rd │ ├── bootstrap-checkbox.css │ ├── bootstrap-checkbox.js │ ├── dom.jsPlumb-1.7.10.js │ ├── jqBootstrapValidation.js │ ├── jquery.form.js │ ├── raphael.min.js │ └── wheelnav.js ├── additional licences ├── analysis.jade ├── analytics.js ├── android-icon-192x192.png ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon-96x96.png ├── favicon.ico ├── favicon.svg ├── help │ ├── assistevolution.jade │ ├── assistuserneeds.jade │ └── assistvaluechain.jade ├── img │ ├── 1x1_transparent.png │ ├── icons.png │ └── logo.png ├── index.css ├── index.jade ├── index.js ├── logo.png ├── logout.html ├── mapeditor.css ├── mapeditor.jade ├── mapeditor.js ├── mapeditor │ ├── 1x1_transparent.png │ ├── js │ │ ├── jsPlumb-2.0.6-min.js │ │ ├── main.js │ │ ├── specs.js │ │ └── vendors.js │ ├── logo.png │ └── mapeditor.html ├── mapeditor2.jade ├── mapeditor_includes │ ├── actionmenu.jade │ ├── mapanalysis.jade │ ├── mapcreationassist.jade │ ├── nodedialog.jade │ └── preferences.jade ├── progresshelper.js ├── related.js └── views │ ├── base.jade │ ├── change-password.jade │ ├── facebook_login_failed.jade │ ├── facebook_login_form.jade │ ├── forgot-password.jade │ ├── google_login_failed.jade │ ├── google_login_form.jade │ ├── id_site_verification_failed.jade │ ├── linkedin_login_form.jade │ ├── login.jade │ ├── register.jade │ ├── unauthorized.jade │ ├── verification_complete.jade │ ├── verification_email_sent.jade │ ├── verification_failed.jade │ └── verify.jade ├── gulpfile.js ├── package.json ├── server-tests ├── it_tests_mailchimp.js └── tests_maps.js ├── server ├── analyzer.js ├── config │ ├── googleauth.js │ ├── mailchimpconfig.js │ ├── mongodbdata.js │ └── stormpathconfig.js ├── db.js ├── export.js ├── mailchimp.js ├── maps.js ├── router │ ├── apirouter.js │ ├── mainrouter.js │ ├── profilerouter.js │ └── share.js ├── server.js ├── svgtemplate.html ├── user-provider.js ├── user.js └── util │ ├── Access.js │ └── log.js ├── supervisord.conf └── ui-sketches └── componentrepository.ep /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = tab 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | 12 | [{package.json,.travis.yml,config.json}] 13 | indent_style = space 14 | indent_size = 2 15 | 16 | #[*.{js,py}] 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | config.json 3 | *.tgz 4 | npm-debug.log 5 | logs/ 6 | *.pem 7 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // -------------------------------------------------------------------- 3 | // JSHint Nodeclipse Configuration v0.15.1 4 | // Strict Edition with some relaxations and switch to Node.js, no `use strict` 5 | // by Ory Band, Michael Haschke, Paul Verest 6 | // https://github.com/Nodeclipse/nodeclipse-1/blob/master/org.nodeclipse.ui/templates/common-templates/.jshintrc 7 | // JSHint Documentation is at http://www.jshint.com/docs/options/ 8 | // JSHint Integration v0.9.9 comes with JSHInt 2.1.10 , see https://github.com/eclipsesource/jshint-eclipse 9 | // Known issues: 10 | // newer JSHint can't be used https://github.com/eclipsesource/jshint-eclipse/issues/75 that depends on JSHint issues. 11 | // -------------------------------------------------------------------- 12 | // from https://gist.github.com/haschek/2595796 13 | // 14 | // @author http://michael.haschke.biz/ 15 | // @license http://unlicense.org/ 16 | // 17 | // This is a options template for [JSHint][1], using [JSHint example][2] 18 | // and [Ory Band's example][3] as basis and setting config values to 19 | // be most strict: 20 | // 21 | // * set all enforcing options to true 22 | // * set all relaxing options to false 23 | // * set all environment options to false, except the node value 24 | // * set all JSLint legacy options to false 25 | // 26 | // [1]: http://www.jshint.com/ 27 | // [2]: https://github.com/jshint/node-jshint/blob/master/example/config.json //404 28 | // [3]: https://github.com/oryband/dotfiles/blob/master/jshintrc //404 29 | // [4]: http://www.jshint.com/options/ 30 | 31 | // == Enforcing Options =============================================== 32 | // 33 | // These options tell JSHint to be more strict towards your code. Use 34 | // them if you want to allow only a safe subset of JavaScript, very 35 | // useful when your codebase is shared with a big number of developers 36 | // with different skill levels. Was all true. 37 | 38 | "bitwise" : true, // Prohibit bitwise operators (&, |, ^, etc.). 39 | "curly" : true, // Require {} for every new block or scope. 40 | "eqeqeq" : true, // Require triple equals i.e. `===`. 41 | "forin" : true, // Tolerate `for in` loops without `hasOwnPrototype`. 42 | "immed" : true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );` 43 | "latedef" : true, // Prohibit variable use before definition. 44 | "newcap" : true, // Require capitalization of all constructor functions e.g. `new F()`. 45 | "noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`. 46 | "noempty" : true, // Prohibit use of empty blocks. 47 | "nonew" : true, // Prohibit use of constructors for side-effects. 48 | "plusplus" : false, // Prohibit use of `++` & `--`. //coding style related only 49 | "regexp" : true, // Prohibit `.` and `[^...]` in regular expressions. 50 | "undef" : true, // Require all non-global variables be declared before they are used. 51 | "strict" : false, // Require `use strict` pragma in every file. 52 | "trailing" : true, // Prohibit trailing whitespaces. 53 | 54 | // == Relaxing Options ================================================ 55 | // 56 | // These options allow you to suppress certain types of warnings. Use 57 | // them only if you are absolutely positive that you know what you are 58 | // doing. Was all false. 59 | "asi" : false, // Tolerate Automatic Semicolon Insertion (no semicolons). 60 | "boss" : false, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments. 61 | "debug" : false, // Allow debugger statements e.g. browser breakpoints. 62 | "eqnull" : false, // Tolerate use of `== null`. 63 | "esnext" : false, // Allow ES.next (ECMAScript 6) specific features such as `const` and `let`. 64 | "evil" : false, // Tolerate use of `eval`. 65 | "expr" : false, // Tolerate `ExpressionStatement` as Programs. 66 | "funcscope" : false, // Tolerate declarations of variables inside of control structures while accessing them later from the outside. 67 | "globalstrict" : false, // Allow global "use strict" (also enables 'strict'). 68 | "iterator" : false, // Allow usage of __iterator__ property. 69 | "lastsemic" : false, // Tolerat missing semicolons when the it is omitted for the last statement in a one-line block. 70 | "laxbreak" : false, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons. 71 | "laxcomma" : true, // Suppress warnings about comma-first coding style. 72 | "loopfunc" : false, // Allow functions to be defined within loops. 73 | "maxerr" : 100, // This options allows you to set the maximum amount of warnings JSHint will produce before giving up. Default is 50. 74 | "moz" : false, // This options tells JSHint that your code uses Mozilla JavaScript extensions. Unless you develop specifically for the Firefox web browser you don't need this option. 75 | "multistr" : false, // Tolerate multi-line strings. 76 | "onecase" : false, // Tolerate switches with just one case. 77 | "proto" : false, // Tolerate __proto__ property. This property is deprecated. 78 | "regexdash" : false, // Tolerate unescaped last dash i.e. `[-...]`. 79 | "scripturl" : false, // Tolerate script-targeted URLs. 80 | "smarttabs" : false, // Tolerate mixed tabs and spaces when the latter are used for alignmnent only. 81 | "shadow" : false, // Allows re-define variables later in code e.g. `var x=1; x=2;`. 82 | "sub" : false, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`. 83 | "supernew" : false, // Tolerate `new function () { ... };` and `new Object;`. 84 | "validthis" : false, // Tolerate strict violations when the code is running in strict mode and you use this in a non-constructor function. 85 | 86 | // == Environments ==================================================== 87 | // 88 | // These options pre-define global variables that are exposed by 89 | // popular JavaScript libraries and runtime environments—such as 90 | // browser or node.js. TODO JSHint Documentation has more, but it is not clear since what JSHint version they appeared 91 | "browser" : false, // Standard browser globals e.g. `window`, `document`. 92 | "couch" : false, // Enable globals exposed by CouchDB. 93 | "devel" : false, // Allow development statements e.g. `console.log();`. 94 | "dojo" : false, // Enable globals exposed by Dojo Toolkit. 95 | "jquery" : false, // Enable globals exposed by jQuery JavaScript library. 96 | "mootools" : false, // Enable globals exposed by MooTools JavaScript framework. 97 | "node" : true, // Enable globals available when code is running inside of the NodeJS runtime environment. 98 | "nonstandard" : false, // Define non-standard but widely adopted globals such as escape and unescape. 99 | "phantom" : false, //?since version? This option defines globals available when your core is running inside of the PhantomJS runtime environment. 100 | "prototypejs" : false, // Enable globals exposed by Prototype JavaScript framework. 101 | "rhino" : false, // Enable globals available when your code is running inside of the Rhino runtime environment. 102 | "worker" : false, //?since version? This option defines globals available when your code is running inside of a Web Worker. 103 | "wsh" : false, // Enable globals available when your code is running as a script for the Windows Script Host. 104 | "yui" : false, //?since version? This option defines globals exposed by the YUI JavaScript framework. 105 | 106 | // == JSLint Legacy =================================================== 107 | // 108 | // These options are legacy from JSLint. Aside from bug fixes they will 109 | // not be improved in any way and might be removed at any point. 110 | "nomen" : false, // Prohibit use of initial or trailing underbars in names. 111 | "onevar" : false, // Allow only one `var` statement per function. 112 | "passfail" : false, // Stop on first error. 113 | "white" : false, // Check against strict whitespace and indentation rules. 114 | 115 | // == Undocumented Options ============================================ 116 | // 117 | // While Michael have found these options in [example1][2] and [example2][3] (already gone 404) 118 | // they are not described in the [JSHint Options documentation][4]. 119 | 120 | "predef" : [ // Extra globals. 121 | "jsPlumb", 122 | "window", 123 | "document", 124 | "$", 125 | "it", "describe", "before", "after", "afterEach" 126 | "Java", "JavaFX", "$ARG" //no effect 127 | ] 128 | , "indent" : 4 // Specify indentation spacing 129 | } 130 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | wardleymapstool 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.ui.externaltools.ExternalToolBuilder 10 | full,incremental, 11 | 12 | 13 | LaunchConfigHandle 14 | <project>/.externalToolBuilders/com.eclipsesource.jshint.ui.builder.launch 15 | 16 | 17 | 18 | 19 | 20 | org.nodeclipse.ui.NodeNature 21 | org.eclipse.wst.jsdt.core.jsNature 22 | 23 | 24 | -------------------------------------------------------------------------------- /.settings/.jsdtscope: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.settings/com.eclipsesource.jshint.ui.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | excluded=//*.json\:bower_components//*\:node_lib//*\:node_modules//* 3 | included=//*.jjs\://*.js\://*.jshintrc\://*.mjs\://*.njs\://*.pjs\://*.vjs 4 | projectSpecificOptions=true 5 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | semanticValidation=disabled 3 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.ui.superType.container: -------------------------------------------------------------------------------- 1 | org.eclipse.wst.jsdt.launching.JRE_CONTAINER -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.ui.superType.name: -------------------------------------------------------------------------------- 1 | Global -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | services: 5 | - mongodb -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:vivid 2 | 3 | # Setup NodeSource Official PPA 4 | RUN apt-get update && \ 5 | apt-get install -y --force-yes \ 6 | curl \ 7 | apt-transport-https \ 8 | lsb-release \ 9 | build-essential \ 10 | python-all 11 | RUN curl -sL https://deb.nodesource.com/setup | bash - 12 | 13 | RUN apt-get install nodejs -y --force-yes 14 | 15 | #mongo 16 | RUN apt-get install -y --force-yes mongodb 17 | RUN mkdir -p /data/db 18 | 19 | #supervisor 20 | RUN apt-get install -y --force-yes supervisor 21 | RUN mkdir -p /var/log/supervisor 22 | COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf 23 | 24 | #phantomjs 25 | RUN apt-get install -y --force-yes phantomjs 26 | 27 | ADD . /srv 28 | WORKDIR /srv 29 | # install the dependencies 30 | RUN npm install 31 | 32 | 33 | EXPOSE 8080 34 | CMD ["/usr/bin/supervisord"] 35 | 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Atlas, the Wardley Mapping Tool 2 | =============== 3 | 4 | [![Build Status](https://travis-ci.org/cdaniel/wardleymapstool.svg?branch=master)](https://travis-ci.org/cdaniel/wardleymapstool) 5 | 6 | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Important Notice 7 | ---------------- 8 | 9 | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) **I have no plans to maintain this tool anymore.** 10 | 11 | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) **New version is being created at https://github.com/cdaniel/atlas2.** 12 | 13 | Preface 14 | ------------- 15 | 16 | There are people who understand the difference between a professional and a "professional" strategy. 17 | 18 | ![Dilbert strategy picture](https://pbs.twimg.com/media/CFOKU-7WEAEl6WZ.jpg:large) 19 | 20 | If the table below does not ring a bell with you, go and check [my mapping portal](http://wardleymaps.com/learn.html) in order to gather the 21 | minimal required knowledge (for free). 22 | 23 | 24 | 25 | | Strategy | Professional | "Professional" | 26 | | ------------ | ------------ | ------------------- | 27 | | Reasoning | Visual | Verbal | 28 | | Common tools | maps | SWOT/story telling | 29 | | Prepared by | you | consultants | 30 | | Price | cheap | very expensive | 31 | 32 | 33 | 34 | However, if you know what a Wardley Map is, I have a good news for you - I wrote a tool that makes map creation much easier. 35 | You can now easily create map, share it with someone, download as png, or create variants and evaluate them. 36 | 37 | If you still are not sure whether this tool is for you, check [the Atlas live instance](https://atlas.wardleymaps.com). 38 | 39 | Running your own instance 40 | ------------------------- 41 | There is a common bias that novel & business critical tool must have big brands guarding them, because it warrants that those tools will not dissappear over night leaving users in the lurch. That is just plain wrong. If you would like to have your own instance of Atlas, you can get it running in nearly no time. Instructions are available on a [project wiki] (https://github.com/cdaniel/wardleymapstool/wiki/Running-your-own-instance). Do not worry, it is not complicated. 42 | 43 | 44 | Support, questions and similar: 45 | ----------------- 46 | Via twitter [@wardleymaps](https://twitter.com/wardleymaps). Don't hesistate to ask, I will do what I can to help you. 47 | 48 | **Logo usage guide:** 49 | 50 | Logos visually identifying Atlas and wardleymaps.com are protected and should not be used if Atlas is used to offer commercial services. 51 | -------------------------------------------------------------------------------- /client-tests/.placeholder: -------------------------------------------------------------------------------- 1 | /* Copyright 2014 Krzysztof Daniel 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License.*/ -------------------------------------------------------------------------------- /client-v2-jsx/actionpalettecomponent.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | var React = require('react'); 3 | var Button = require('react-bootstrap').Button; 4 | var MapComponent = require('./mapcomponent'); 5 | var MapActions = require('./actions/mapactions'); 6 | var jquery = require('jquery'); 7 | 8 | var outerStyle = { 9 | position: 'relative' 10 | }; 11 | 12 | var ActionPaletteComponent = React.createClass({ 13 | componentDidMount: function() { 14 | this.props.store.addChangeListener(this._onChange); 15 | }, 16 | componentWillUnmount: function() { 17 | this.props.store.removeChangeListener(this._onChange); 18 | }, 19 | _onChange: function() { 20 | this.setState(this.props.store.getAll()); 21 | }, 22 | getInitialState : function() { 23 | return { 24 | mapMode : null 25 | }; 26 | }, 27 | handleClickMove : function(event){ 28 | event.preventDefault(); 29 | MapActions.toggleMode(this.props.toggle); 30 | }, 31 | render: function() { 32 | var target_function = this.handleClickMove; 33 | var store = this.props.store; 34 | var active = this.state.mapMode === this.props.toggle; 35 | var bsStyle = this.state.mapMode === this.props.toggle ? 'info' : 'default'; 36 | return ( 37 | 42 | ); 43 | } 44 | 45 | }); 46 | 47 | module.exports = ActionPaletteComponent; 48 | -------------------------------------------------------------------------------- /client-v2-jsx/actions/mapactions.js: -------------------------------------------------------------------------------- 1 | var MapDispatcher = require('../dispatcher/mapdispatcher'); 2 | var MapConstants = require('../constants/mapconstants'); 3 | 4 | var MapActions = { 5 | 6 | createNodeFromDrop: function(drop) { 7 | MapDispatcher.dispatch({ 8 | actionType: MapConstants.MAP_CREATE_NODE_FROM_DROP, 9 | drop: drop 10 | }); 11 | }, 12 | 13 | normalize: function(params){ 14 | MapDispatcher.dispatch({ 15 | actionType: MapConstants.MAP_NEW_NODE, 16 | params : params 17 | }); 18 | }, 19 | 20 | toggleMode : function(targetAction) { 21 | MapDispatcher.dispatch({ 22 | actionType: MapConstants.MAP_EDITOR_DRAG_MODE, 23 | targetAction : targetAction 24 | }); 25 | }, 26 | 27 | deleteNode: function(id){ 28 | MapDispatcher.dispatch({ 29 | actionType: MapConstants.MAP_DELETE_NODE, 30 | id : id 31 | }); 32 | }, 33 | 34 | recordConnection : function(connection){ 35 | MapDispatcher.dispatch({ 36 | actionType: MapConstants.MAP_RECORD_CONNECTION, 37 | connection : connection 38 | }); 39 | }, 40 | 41 | deleteConnection : function(connection){ 42 | MapDispatcher.dispatch({ 43 | actionType: MapConstants.MAP_DELETE_CONNECTION, 44 | connection : connection 45 | }); 46 | }, 47 | 48 | nodeDragged : function(drag){ 49 | MapDispatcher.dispatch({ 50 | actionType: MapConstants.MAP_NODE_DRAGSTOP, 51 | drag : drag 52 | }); 53 | }, 54 | 55 | mapRetrieved : function(map){ 56 | MapDispatcher.dispatch({ 57 | actionType: MapConstants.MAP_RETRIEVED, 58 | map : map 59 | }); 60 | }, 61 | 62 | changeName : function(name){ 63 | MapDispatcher.dispatch({ 64 | actionType: MapConstants.MAP_CHANGE_NAME, 65 | name : name 66 | }); 67 | }, 68 | 69 | changeDescription : function(description){ 70 | MapDispatcher.dispatch({ 71 | actionType: MapConstants.MAP_CHANGE_DESCRIPTION, 72 | description : description 73 | }); 74 | }, 75 | 76 | toggleSharingDialog : function(){ 77 | MapDispatcher.dispatch({ 78 | actionType: MapConstants.MAP_SHARING_DIALOG 79 | }); 80 | }, 81 | 82 | toggleAnonymousSharing : function(url){ 83 | MapDispatcher.dispatch({ 84 | actionType: MapConstants.MAP_TOGGLE_ANONYMOUS_SHARING, 85 | url : url 86 | }); 87 | }, 88 | 89 | loadSharingState : function(data){ 90 | MapDispatcher.dispatch({ 91 | actionType: MapConstants.LOAD_MAP_SHARING_STATE, 92 | data : data 93 | }); 94 | }, 95 | 96 | editNode : function(nodeId, newState){ 97 | MapDispatcher.dispatch({ 98 | actionType: MapConstants.MAP_EDIT_NODE, 99 | nodeId : nodeId, 100 | newState : newState 101 | }); 102 | }, 103 | 104 | errorReported : function(error){ 105 | MapDispatcher.dispatch({ 106 | actionType: MapConstants.ERROR, 107 | error : error 108 | }); 109 | } 110 | 111 | }; 112 | 113 | module.exports = MapActions; 114 | -------------------------------------------------------------------------------- /client-v2-jsx/constants/mapconstants.js: -------------------------------------------------------------------------------- 1 | var keyMirror = require('keymirror'); 2 | 3 | module.exports = keyMirror({ 4 | MAP_CREATE_NODE_FROM_DROP: null, 5 | MAP_NEW_NODE: null, 6 | MAP_DELETE_NODE: null, 7 | 8 | 9 | MAP_EDITOR_DRAG_MODE : null, 10 | MAP_EDITOR_CONNECT_MODE : null, 11 | MAP_EDITOR_DELETE_MODE : null, 12 | MAP_EDITOR_EDIT_MODE : null, 13 | 14 | MAP_RECORD_CONNECTION : null, 15 | MAP_DELETE_CONNECTION : null, 16 | 17 | MAP_CHANGE_NAME : null, 18 | MAP_CHANGE_DESCRIPTION : null, 19 | MAP_EDIT_NODE : null, 20 | 21 | MAP_NODE_DRAGSTOP : null, 22 | 23 | DROP_EVENT : null, 24 | CHANGE_EVENT : null, 25 | 26 | MAP_RETRIEVED : null, 27 | MAP_SHARING_DIALOG : null, 28 | 29 | MAP_TOGGLE_ANONYMOUS_SHARING : null, 30 | LOAD_MAP_SHARING_STATE : null, 31 | 32 | ERROR : null 33 | }); 34 | -------------------------------------------------------------------------------- /client-v2-jsx/dialogs/NodeEditDialog.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | var React = require('react'); 3 | var Input = require('react-bootstrap').Input; 4 | var Modal = require('react-bootstrap').Modal; 5 | var Button = require('react-bootstrap').Button; 6 | var Glyphicon = require('react-bootstrap').Glyphicon; 7 | var MapConstants = require('./../constants/mapconstants'); 8 | var MapActions = require('./../actions/mapactions.js'); 9 | var $ = require('jquery'); 10 | var EditableShortText = require('./../editableshorttext'); 11 | 12 | var NodeEditDialog = React.createClass({ 13 | getInitialState : function(){ 14 | return {newName : null, newType : null}; 15 | }, 16 | componentDidMount: function() { 17 | this.props.store.addChangeListener(this._onChange); 18 | }, 19 | componentWillUnmount: function() { 20 | this.props.store.removeChangeListener(this._onChange); 21 | }, 22 | close : function(){ 23 | MapActions.editNode(null, this.state); 24 | this.setState({newName : null, newType : null}); 25 | }, 26 | hide : function(){ 27 | MapActions.editNode(null, null); 28 | this.setState({newName : null, newType : null}); 29 | }, 30 | onNameUpdate : function(value){ 31 | this.setState({newName:value}); 32 | }, 33 | onSubmit : function(key){ 34 | key.preventDefault(); 35 | key.stopPropagation(); 36 | return false; 37 | }, 38 | render: function() { 39 | var editedNode = this.props.store.getNodeBeingEdited(); 40 | var show = editedNode !== null; 41 | var name = editedNode !== null ? editedNode.name : ""; 42 | if(this.state.newName){ 43 | name = this.state.newName; 44 | } 45 | 46 | var type = null; 47 | if(editedNode){ 48 | console.log(editedNode); 49 | if(editedNode.userneed){ 50 | type = 'userneed'; 51 | } else if (editedNode.external){ 52 | type = 'external'; 53 | } else { 54 | type = 'internal'; 55 | } 56 | } 57 | if(this.state.newType){ 58 | type = this.state.newType; 59 | } 60 | return ( 61 | 62 | 63 | 64 | Edit node 65 | 66 | 67 | 68 |
69 | 76 | 84 | 87 | 90 | 93 | 94 | 95 |
96 | 97 | 98 | 99 |
100 | ); 101 | }, 102 | _typeEdit : function(i){ 103 | this.setState({newType : i.target.value}); 104 | }, 105 | _onChange : function() { 106 | this.setState({newName : null, newType : null}); 107 | } 108 | }); 109 | 110 | module.exports = NodeEditDialog; 111 | -------------------------------------------------------------------------------- /client-v2-jsx/dialogs/SharingDialog.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | var React = require('react'); 3 | var Input = require('react-bootstrap').Input; 4 | var Modal = require('react-bootstrap').Modal; 5 | var Button = require('react-bootstrap').Button; 6 | var Glyphicon = require('react-bootstrap').Glyphicon; 7 | var MapConstants = require('./../constants/mapconstants'); 8 | var MapActions = require('./../actions/mapactions.js'); 9 | var $ = require('jquery'); 10 | 11 | var MapSharingDialog = React.createClass({ 12 | 13 | getInitialState : function(){ 14 | return { 15 | sharingDialog:false, 16 | anonymousShare:false, 17 | anonymousShareLink:'' 18 | }; 19 | }, 20 | 21 | componentDidMount: function() { 22 | this.props.store.addChangeListener(this._onChange); 23 | $.ajax({ 24 | type : 'GET', 25 | url : '/share/map/' + this.props.mapId, 26 | dataType : 'json', 27 | success : function(data) { 28 | MapActions.loadSharingState(data); 29 | } 30 | }); 31 | }, 32 | 33 | componentWillUnmount: function() { 34 | this.props.store.removeChangeListener(this._onChange); 35 | }, 36 | 37 | close : function(){ 38 | MapActions.toggleSharingDialog(); 39 | }, 40 | 41 | onSubmit : function(key){ 42 | key.preventDefault(); 43 | key.stopPropagation(); 44 | return false; 45 | }, 46 | 47 | toggleAnonymousSharing : function(){ 48 | if (!this.state.anonymousShare) { 49 | $.ajax({ 50 | type : 'PUT', 51 | url : '/share/map/' + this.props.mapId + '/anonymous', 52 | dataType : 'json', 53 | success : function(data) { 54 | MapActions.toggleAnonymousSharing(data.url); 55 | } 56 | }); 57 | } else { 58 | $.ajax({ 59 | type : 'PUT', 60 | url : '/share/map/' + this.props.mapId + '/unshareanonymous', 61 | dataType : 'json', 62 | success : function(data) { 63 | MapActions.toggleAnonymousSharing(null); 64 | } 65 | }); 66 | } 67 | }, 68 | 69 | render: function() { 70 | var show = this.state.sharingDialog; 71 | 72 | var isNotSharedAnonymously = ! this.state.anonymousShare; 73 | var anonymousShareLink = this.state.anonymousShareLink; 74 | var glyph = ; 75 | var anonymousShareBlock = function(){ 76 | if(isNotSharedAnonymously === true){ 77 | return null; 78 | } else { 79 | return ( ); 80 | } 81 | }; 82 | return ( 83 | 84 | 85 | 86 | Share your map 87 | 88 | 89 | 90 |

Anonymously

91 |
92 | 97 | { anonymousShareBlock() } 98 |
99 |
100 | 101 | 102 | 103 |
104 | ); 105 | }, 106 | _onChange : function() { 107 | this.setState(this.props.store.getSharedDialogState()); 108 | } 109 | }); 110 | 111 | module.exports = MapSharingDialog; 112 | -------------------------------------------------------------------------------- /client-v2-jsx/dispatcher/mapdispatcher.js: -------------------------------------------------------------------------------- 1 | var Dispatcher = require('flux').Dispatcher; 2 | //singleton 3 | module.exports = new Dispatcher(); 4 | -------------------------------------------------------------------------------- /client-v2-jsx/download.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | var React = require('react'); 3 | var MapActions = require('./actions/mapactions'); 4 | var MapConstants = require('./constants/mapconstants'); 5 | var Glyphicon = require('react-bootstrap').Glyphicon; 6 | var Dropdown = require('react-bootstrap').Dropdown; 7 | var MenuItem = require('react-bootstrap').MenuItem; 8 | var Badge = require('react-bootstrap').Badge; 9 | 10 | var Download = React.createClass({ 11 | getDownloadName : function(type){ 12 | if(!this.props.store.getNameAndDescription()){ 13 | return "notinitialized"; 14 | } 15 | return this.props.store.getNameAndDescription().name + "." + type; 16 | }, 17 | getDownloadLink : function(type){ 18 | return "/api/" + type+"forcedownload/" + this.props.mapId + "/" + this.props.mapId + "." + type; 19 | }, 20 | componentDidMount: function() { 21 | this.props.store.addChangeListener(this._onChange); 22 | }, 23 | componentWillUnmount: function() { 24 | this.props.store.removeChangeListener(this._onChange); 25 | }, 26 | _onChange : function() { 27 | this.forceUpdate(); 28 | }, 29 | render: function() { 30 | var _this = this; 31 | return ( 32 | 33 | 34 |  Download 35 | 36 | 37 | 40 | As SVG 41 | 42 | 45 | As PNG beta 46 | 47 | 48 | 49 | ); 50 | } 51 | }); 52 | 53 | module.exports = Download; 54 | -------------------------------------------------------------------------------- /client-v2-jsx/draggablepalettecomponent.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | var React = require('react'); 3 | var Button = require('react-bootstrap').Button; 4 | var MapComponent = require('./mapcomponent'); 5 | var MapActions = require('./actions/mapactions'); 6 | 7 | var outerStyle = { 8 | position: 'relative' 9 | }; 10 | 11 | var DraggablePaletteComponent = React.createClass({ 12 | render: function() { 13 | var store = this.props.store; 14 | var props = this.props; 15 | return ( 16 | 35 | 36 | ); 37 | } 38 | 39 | }); 40 | 41 | module.exports = DraggablePaletteComponent; 42 | -------------------------------------------------------------------------------- /client-v2-jsx/editableshorttext.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | var React = require('react'); 3 | var ButtonGroup = require('react-bootstrap').ButtonGroup; 4 | var Well = require('react-bootstrap').Well; 5 | var Button = require('react-bootstrap').Button; 6 | var Input = require('react-bootstrap').Input; 7 | var FormControls= require('react-bootstrap').FormControls; 8 | var Glyphicon = require('react-bootstrap').Glyphicon; 9 | var MapComponent = require('./mapcomponent'); 10 | var ActionPaletteComponent = require('./actionpalettecomponent'); 11 | var DraggablePaletteComponent = require('./draggablepalettecomponent'); 12 | var MapActions = require('./actions/mapactions'); 13 | var MapConstants = require('./constants/mapconstants'); 14 | var InlineEdit = require('react-edit-inline'); 15 | var ReactDOM = require('react-dom'); 16 | 17 | var styleToEdit = { 18 | borderBottom: '1px dashed #999', 19 | display: 'inline' 20 | }; 21 | 22 | var defaultLabelClassName = "col-xs-2"; 23 | var defaultWrapperClassName = "col-xs-5"; 24 | 25 | /** 26 | * params: 27 | * text to display 28 | placeholder 29 | label 30 | * onChange - method to call 31 | */ 32 | var EditableShortText = React.createClass({ 33 | getInitialState: function() { 34 | return {state:'text', newValue:null}; //also availabe 'hover' & 'edit' 35 | }, 36 | componendDidMount : function(){ 37 | if(this.refs.input){ 38 | var domnode = ReactDOM.findDOMNode(this.refs.input); 39 | if(domnode){ 40 | domnode.onkeyup=this._keyListener; 41 | domnode.focus(); 42 | } 43 | } 44 | }, 45 | componentDidUpdate : function(){ 46 | if(this.refs.input){ 47 | var domnode = this.refs.input.getInputDOMNode(); 48 | if(domnode){ 49 | domnode.onkeyup=this._keyListener; 50 | var _this=this; 51 | domnode.onblur=function(){ 52 | if(_this.props.updateOnBlur === 'true'){ 53 | var _newValue = _this.state.newValue; 54 | _this.setState({state:'text', newValue: null}); 55 | _this.props.onChange(_newValue); 56 | } else { 57 | _this.setState({state:'text', newValue : null}); 58 | } 59 | }; 60 | domnode.focus(); 61 | } 62 | } 63 | }, 64 | render: function() { 65 | var textToDisplay = this.state.newValue !== null ? this.state.newValue : this.props.text; 66 | var labelClassName = this.props.labelClassNameOverride ? this.props.labelClassNameOverride : defaultLabelClassName; 67 | var wrapperClassName = this.props.wrapperClassNameOverride ? this.props.wrapperClassNameOverride : defaultWrapperClassName; 68 | 69 | if(this.state.state === 'text'){ 70 | return ( 71 | 80 | ); 81 | } 82 | if(this.state.state === 'hover'){ 83 | var glyph = ( 84 | 85 | 86 | ); 87 | return ; 100 | } 101 | if(this.state.state === 'edit'){ 102 | var _this = this; 103 | return ; 114 | } 115 | }, 116 | _onChange : function() { 117 | this.setState(this.props.store.getStateInfo()); 118 | }, 119 | _onHover : function(){ 120 | this.setState({state:'hover'}); 121 | }, 122 | _onMouseOut : function(){ 123 | this.setState({state:'text'}); 124 | }, 125 | _startEdit : function(){ 126 | this.setState({state:'edit'}); 127 | }, 128 | _onEdit : function(i){ 129 | this.setState({newValue : i.target.value}); 130 | }, 131 | _keyListener : function(key){ 132 | if(key.keyCode === 27){ //esc 133 | this.setState({state:'text', newValue : null}); 134 | } 135 | if(key.keyCode === 13){ //enter 136 | var _newValue = this.state.newValue; 137 | this.setState({state:'text', newValue: null}); 138 | this.props.onChange(_newValue); 139 | } 140 | key.preventDefault(); 141 | key.stopPropagation(); 142 | } 143 | }); 144 | 145 | module.exports = EditableShortText; 146 | -------------------------------------------------------------------------------- /client-v2-jsx/main.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var ReactDom = require('react-dom'); 3 | var MapEditorPage = require('./mapeditorpage'); 4 | ReactDom.render(, document.getElementById('editorplaceholder')); 5 | -------------------------------------------------------------------------------- /client-v2-jsx/mapcanvas.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | var React = require('react'); 3 | var MapStore = require('./store/mapstore'); 4 | var MapComponent = require('./mapcomponent'); 5 | var MapConstants = require('./constants/mapconstants'); 6 | var MapActions = require('./actions/mapactions'); 7 | var jquery = require('jquery'); 8 | var _ = require('underscore'); 9 | 10 | var mapCanvasStyle = { 11 | position: 'absolute', 12 | top: 0, 13 | left : '2%', 14 | bottom : '2%', 15 | right : 0, 16 | backgroundImage: 'transparent url(1x1_transparent.png) repeat center top' 17 | }; 18 | 19 | var outerStyle = { 20 | width: 'auto', 21 | left: 150, 22 | right: 0, 23 | height: '100%', 24 | float: 'left', 25 | position: 'absolute' 26 | }; 27 | 28 | 29 | var axisSupport = { 30 | position: 'absolute', 31 | borderWidth: 1, 32 | top: '3%', 33 | bottom: '2%', 34 | border: '1px dashed silver', 35 | zIndex: '1' 36 | }; 37 | 38 | //TODO: align this properly (relative to the canvas) 39 | var axisSupport1 = _.extend(_.clone(axisSupport), {left: '25.5%'}); 40 | var axisSupport2 = _.extend(_.clone(axisSupport), {left: '51%'}); 41 | var axisSupport3 = _.extend(_.clone(axisSupport), {left: '76.5%'}); 42 | 43 | 44 | var axisX = { 45 | position: 'absolute', 46 | bottom: '2%', 47 | height: 1, 48 | left: '2%', 49 | right: '2%', 50 | border: '1px solid gray', 51 | marginRight: 0, 52 | marginLeft: 0, 53 | }; 54 | 55 | var arrowX = { 56 | position: 'absolute', 57 | width: 0, 58 | height: 0, 59 | borderTop: '4px solid transparent', 60 | borderBottom: '2px solid transparent', 61 | borderLeft: '10px solid gray', 62 | right: '2%', 63 | bottom: '2%' 64 | }; 65 | 66 | var axisY = { 67 | position: 'absolute', 68 | width: 1, 69 | borderWidth: 1, 70 | top: '2%', 71 | bottom: '2%', 72 | left: '2%', 73 | border: '1px solid gray' 74 | }; 75 | 76 | var arrowY = { 77 | position: 'absolute', 78 | width: 0, 79 | height: 0, 80 | left: '2%', 81 | top: '2%', 82 | borderLeft: '2px solid transparent', 83 | borderRight: '4px solid transparent', 84 | borderBottom: '10px solid gray' 85 | }; 86 | 87 | var valueCaption = { 88 | position: 'absolute', 89 | zIndex: 1, 90 | fontSize: 'smaller', 91 | top: '3%', 92 | left: '3%' 93 | }; 94 | 95 | var evolutionCaption = { 96 | position: 'absolute', 97 | zIndex: 1, 98 | fontSize: 'smaller', 99 | bottom: '3%', 100 | right: '3%' 101 | }; 102 | 103 | var genesisStyle = {fontSize: 'smaller', position:'absolute', left : '5%'}; 104 | var customBuiltStyle = {fontSize: 'smaller', position:'absolute', left : '31%'}; 105 | var productStyle = {fontSize: 'smaller', position:'absolute', left : '57%'}; 106 | var commodityStyle = {fontSize: 'smaller', position:'absolute', left : '81%'}; 107 | 108 | var MapCanvas = React.createClass({ 109 | 110 | id : (+new Date() + Math.floor(Math.random() * 999999)).toString(36), 111 | 112 | _this:this, 113 | 114 | handleResize : function () { 115 | var newState = { 116 | offset : { 117 | top : jquery(this.refs.root).offset().top, 118 | left : jquery(this.refs.root).offset().left 119 | }, 120 | size : { 121 | width : jquery(this.refs.root).width(), 122 | height: jquery(this.refs.root).height() 123 | } 124 | }; 125 | this.setState(newState); 126 | //anti pattern but jsPlumb does not want to work otherwise with react 127 | jsPlumb.repaintEverything(); 128 | }, 129 | 130 | componentDidMount: function() { 131 | this.props.store.addChangeListener(this._onChange); 132 | this.props.store.addChangeListener(this._normalizeDrop, MapConstants.DROP_EVENT); 133 | //intial size and react to resize 134 | this.handleResize(); 135 | jquery(window).resize(this.handleResize); 136 | 137 | jsPlumb.setContainer(this.id); 138 | jsPlumb.bind("beforeDrop", function(connection) { 139 | 140 | var scope = connection.connection.getData().scope; 141 | 142 | // no connection to self 143 | if (connection.sourceId === connection.targetId) { 144 | return false; 145 | } 146 | 147 | // no duplicate connections - TODO: check that in app state 148 | if(jsPlumb.getConnections({ 149 | scope:scope, 150 | source : connection.sourceId, 151 | target : connection.targetId 152 | }, true).length > 0){ 153 | //connection already exists 154 | return false; 155 | } 156 | 157 | MapActions.recordConnection(connection); 158 | 159 | return true; 160 | }); 161 | }, 162 | 163 | componentWillUnmount: function() { 164 | this.props.store.removeChangeListener(this._onChange); 165 | this.props.store.removeChangeListener(this._normalizeDrop, MapConstants.DROP_EVENT); 166 | jquery(window).off('resize', this.handleResize); 167 | }, 168 | 169 | getInitialState : function(){ 170 | return {nodes:[], connections:[], offset : {left : 0, top : 0}}; 171 | }, 172 | 173 | render: function() { 174 | var nodes = this.state.nodes; 175 | var offset = this.state.offset; 176 | var size = this.state.size; 177 | var props = this.props; 178 | var mapMode = this.state.mapMode; 179 | var store = this.props.store; 180 | var id = this.id; 181 | 182 | var components = this.state.nodes.map( 183 | function(component){ 184 | return ; 194 | } 195 | ); 196 | return ( 197 |
198 |
199 | {components} 200 |
201 |
202 |
Genesis
203 |
Custom Built
204 |
Product or Rental
205 |
Commodity/Utility
206 |
207 |
208 |
Value
209 |
210 |
211 |
Evolution
212 |
213 |
214 |
215 |
216 | ); 217 | }, 218 | 219 | _onChange: function() { 220 | this.setState(this.props.store.getAll()); 221 | }, 222 | 223 | _normalizeDrop : function(){ 224 | var top = this.state.offset.top; 225 | var left = this.state.offset.left; 226 | var width = jquery(this.refs.root).width(); 227 | var height = jquery(this.refs.root).height(); 228 | var data = { 229 | left : left, 230 | top : top, 231 | width : width, 232 | height : height 233 | }; 234 | setTimeout(function(){ 235 | MapActions.normalize(data); 236 | }); 237 | } 238 | }); 239 | 240 | module.exports = MapCanvas; 241 | -------------------------------------------------------------------------------- /client-v2-jsx/mapcomponent.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | /** globals jsPlumb: false */ 3 | 4 | var React = require('react'); 5 | var _ = require('underscore'); 6 | var MapConstants = require('./constants/mapconstants'); 7 | var MapActions= require('./actions/mapactions'); 8 | 9 | var mapComponentStyle = { 10 | minHeight: 20, 11 | minWidth: 20, 12 | maxWidth: 20, 13 | maxHeight: 20, 14 | borderRadius: 10, 15 | zIndex: 2 16 | }; 17 | 18 | var inlinedStyle = { 19 | display: 'inline-block', 20 | verticalAlign : 'middle' 21 | }; 22 | 23 | var endpointOptions = { 24 | paintStyle:{ fillStyle:"transparent", outlineColor:'transparent' }, 25 | allowLoopback:false, 26 | connector : "Straight", 27 | connectorStyle : { 28 | lineWidth : 2, 29 | strokeStyle : 'silver', 30 | outlineColor:"transparent", 31 | outlineWidth:10 32 | }, 33 | endpoint : [ "Dot", { 34 | radius : 1 35 | } ], 36 | deleteEndpointsOnDetach : false, 37 | uniqueEndpoints : true 38 | }; 39 | 40 | var actionEndpointOptions = { 41 | paintStyle:{ fillStyle:"transparent", outlineColor:'transparent' }, 42 | allowLoopback:false, 43 | connector : "Straight", 44 | connectorStyle : { 45 | lineWidth : 2, 46 | strokeStyle : '#339933', 47 | outlineColor:"transparent", 48 | outlineWidth:10 49 | }, 50 | endpoint : [ "Dot", { 51 | radius : 1 52 | } ], 53 | connectorOverlays : [[ "Arrow", { 54 | location : 1.0, 55 | width : 8, 56 | height : 3 57 | } ]], 58 | deleteEndpointsOnDetach : false, 59 | uniqueEndpoints : true 60 | }; 61 | 62 | var nonInlinedStyle = { 63 | position: 'absolute' 64 | }; 65 | 66 | 67 | var itemCaptionStyle = { 68 | top: -10, 69 | left: 23, 70 | position: 'absolute', 71 | zIndex: 2, 72 | textShadow: '2px 2px white', 73 | width: 100, 74 | height: 22, 75 | maxWidth: 100, 76 | maxHeight: 22, 77 | marginBottom: -20, 78 | fontSize: 10, 79 | lineHeight: '11px' 80 | }; 81 | 82 | var MapComponent = React.createClass({ 83 | id : null, 84 | left : 0, 85 | top : 0, 86 | positioned : false, 87 | relatedConnections : [], 88 | relatedConnectionListeners : [], 89 | 90 | getAnchors : function(scope){ 91 | if(scope === jsPlumb.getDefaultScope()){ 92 | return ["BottomCenter", "TopCenter"]; 93 | } 94 | if(scope === 'Actions'){ 95 | return ["Right", "Left"]; 96 | } 97 | }, 98 | 99 | getConnector : function(scope){ 100 | if(scope === jsPlumb.getDefaultScope()){ 101 | return endpointOptions.connector; 102 | } 103 | if(scope === 'Actions'){ 104 | return actionEndpointOptions.connector; 105 | } 106 | }, 107 | 108 | getConnectorStyle : function(scope){ 109 | if(scope === jsPlumb.getDefaultScope()){ 110 | return endpointOptions.connectorStyle; 111 | } 112 | if(scope === 'Actions'){ 113 | return actionEndpointOptions.connectorStyle; 114 | } 115 | }, 116 | 117 | getOverlays : function(scope){ 118 | if(scope === jsPlumb.getDefaultScope()){ 119 | return []; 120 | } 121 | if(scope === 'Actions'){ 122 | return actionEndpointOptions.connectorOverlays; 123 | } 124 | }, 125 | 126 | sortOutDeps : function(){ 127 | var _this = this; 128 | var connections = _this.props.store.getAll().connections; 129 | _this.relatedConnections = []; 130 | for(var i = 0; i < connections.length; i++){ 131 | if(connections[i].sourceId === _this.id){ 132 | _this.relatedConnections.push(connections[i]); 133 | } 134 | } 135 | 136 | //TODO: check if all connections exist and reconcile them 137 | for(var k = 0; k < this.relatedConnections.length; k++){ 138 | if(this.relatedConnections[k].conn) { continue; } //the connection exists 139 | var _conn = this.relatedConnections[k]; 140 | var connectionData = { 141 | source : _conn.sourceId, 142 | target : _conn.targetId, 143 | scope : _conn.scope, 144 | anchors: this.getAnchors(_conn.scope), 145 | paintStyle : this.getConnectorStyle(_conn.scope), 146 | endpoint : endpointOptions.endpoint, 147 | connector : this.getConnector(_conn.scope), 148 | endpointStyles : [endpointOptions.paintStyle, endpointOptions.paintStyle], 149 | overlays : this.getOverlays(_conn.scope), 150 | }; 151 | this.relatedConnections[k].conn = jsPlumb.connect(connectionData); 152 | } 153 | }, 154 | connectionDelete : function(_conn){ 155 | jsPlumb.detach(_conn); //remove from jsPlumb 156 | MapActions.deleteConnection(_conn); //update the state 157 | }, 158 | 159 | componentDidMount : function(){ 160 | this.sortOutDeps(); 161 | }, 162 | 163 | componentDidUpdate : function ( prevProps, prevState){ 164 | this.sortOutDeps(); 165 | if(this.props.mapMode !== MapConstants.MAP_EDITOR_DRAG_MODE){ 166 | jsPlumb.setDraggable(this.id, false); 167 | } 168 | if(this.props.mapMode !== MapConstants.MAP_EDITOR_CONNECT_MODE){ 169 | jsPlumb.unmakeSource(this.id); 170 | jsPlumb.unmakeTarget(this.id); 171 | } 172 | if(this.props.mapMode !== MapConstants.MAP_EDITOR_DELETE_MODE){ 173 | //connections. nodes are handled by a direct listener 174 | for(var j = 0; j < this.relatedConnections.length; j++){ 175 | if(this.relatedConnections[j].conn){ 176 | this.relatedConnections[j].conn.unbind('click'); 177 | } 178 | } 179 | } 180 | 181 | 182 | if(this.props.mapMode === MapConstants.MAP_EDITOR_DRAG_MODE){ 183 | var _this = this; 184 | jsPlumb.draggable(this.id, { 185 | ignoreZoom:true, 186 | containment:true, 187 | grid: [50,50], 188 | stop : function(params){ 189 | MapActions.nodeDragged( 190 | { 191 | id:_this.id, 192 | pos: { 193 | left : params.pos[0] / _this.props.canvasSize.width, 194 | top : params.pos[1] / _this.props.canvasSize.height, 195 | 196 | } 197 | }); 198 | } 199 | }); 200 | jsPlumb.setDraggable(this.id, true); 201 | } 202 | 203 | 204 | if(this.props.mapMode === MapConstants.MAP_EDITOR_CONNECT_MODE){ 205 | jsPlumb.makeTarget(this.id, endpointOptions, {anchor: "TopCenter"}); 206 | jsPlumb.makeSource(this.id, endpointOptions, {anchor : "BottomCenter"}); 207 | } 208 | 209 | if(this.props.mapMode === MapConstants.MAP_EDITOR_DELETE_MODE){ 210 | for(var i = 0; i < this.relatedConnections.length; i++){ 211 | var conn = this.relatedConnections[i].conn; 212 | conn.bind('click', this.connectionDelete); 213 | } 214 | } 215 | }, 216 | 217 | delete : function(){ 218 | jsPlumb.detachAllConnections(this.id); 219 | jsPlumb.removeAllEndpoints(this.id); 220 | jsPlumb.detach(this.id); 221 | MapActions.deleteNode(this.id); 222 | }, 223 | 224 | editNode : function(){ 225 | MapActions.editNode(this.id); 226 | }, 227 | 228 | render: function() { 229 | var that = this; 230 | 231 | var styleToSet = _.clone(mapComponentStyle); 232 | if(that.props.inline){ 233 | styleToSet = _.extend(styleToSet, inlinedStyle); 234 | } else { 235 | styleToSet = _.extend(styleToSet, nonInlinedStyle); 236 | } 237 | styleToSet = _.extend(styleToSet, that.props.styleOverride); 238 | 239 | if(that.props.position){ 240 | this.left = this.props.position.positionX * this.props.canvasSize.width; 241 | styleToSet.left = this.left; 242 | this.top = this.props.position.positionY * this.props.canvasSize.height; 243 | styleToSet.top = this.top; 244 | this.positioned = true; 245 | } 246 | 247 | that.id = this.props.id; 248 | var name = this.props.name; 249 | var onClickHandler = null; 250 | onClickHandler = this.props.mapMode === MapConstants.MAP_EDITOR_DELETE_MODE ? this.delete : null; 251 | if(this.props.mapMode === MapConstants.MAP_EDITOR_EDIT_MODE){ 252 | onClickHandler = this.editNode; 253 | } 254 | return ( 255 |
260 | { 261 | (function() { 262 | if(that.props.inline){ 263 | return null; 264 | } else { 265 | return ( 266 |
267 | {name} 268 |
); 269 | } 270 | })() 271 | } 272 |
273 | ); 274 | } 275 | }); 276 | 277 | module.exports = MapComponent; 278 | -------------------------------------------------------------------------------- /client-v2-jsx/mapeditor.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | var React = require('react'); 3 | var Palette = require('./palette'); 4 | var MapCanvas = require('./mapcanvas'); 5 | var MapStore = require('./store/mapstore'); 6 | var MapActions = require('./actions/mapactions'); 7 | var jquery = require('jquery'); 8 | 9 | var mapEditorStyle = { 10 | minWidth : 600, 11 | minHeight : 500, 12 | height: 'auto', 13 | position: 'relative' 14 | }; 15 | 16 | 17 | 18 | 19 | var MapEditor = React.createClass({ 20 | componentDidMount : function(){ 21 | var url = this.props.origin + '/api/map/' + this.props.mapid; 22 | jquery.get(url, function(result) { 23 | MapActions.mapRetrieved(result); 24 | }).error(function(e) { 25 | MapActions.errorReported(e.responseText + ' ' + e.status); 26 | }.bind(this)); 27 | }, 28 | // componentDidUpdate : function(oldProps, newProps){ 29 | // jsPlumb.repaintEverything(); 30 | // }, 31 | render: function() { 32 | return ( 33 |
34 | 35 | 52 |
53 | ); 54 | } 55 | }); 56 | 57 | module.exports = MapEditor; 58 | -------------------------------------------------------------------------------- /client-v2-jsx/mapeditorpage.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | var React = require('react'); 3 | var ReactDOM = require('react-dom'); 4 | 5 | var Grid = require('react-bootstrap').Grid; 6 | var Row = require('react-bootstrap').Row; 7 | var Col = require('react-bootstrap').Col; 8 | var Navbar = require('react-bootstrap').Navbar; 9 | var NavbarHeader = require('react-bootstrap').Navbar.Header; 10 | var NavbarCollapse = require('react-bootstrap').Navbar.Collapse; 11 | var NavbarBrand= require('react-bootstrap').NavbarBrand; 12 | var PageHeader= require('react-bootstrap').PageHeader; 13 | var NavItem = require('react-bootstrap').NavItem; 14 | var Glyphicon = require('react-bootstrap').Glyphicon; 15 | var Nav = require('react-bootstrap').Nav; 16 | var MapEditor = require('./mapeditor'); 17 | var RouterMixin = require('react-mini-router').RouterMixin; 18 | var MapStore = require('./store/mapstore'); 19 | var ShareDialogStore = require('./store/ShareDialogStore'); 20 | var MapTitleDescription = require('./maptitledescription'); 21 | var Button = require('react-bootstrap').Button; 22 | var MapStatus = require('./mapstatus'); 23 | var EditableShortText = require('./editableshorttext'); 24 | var Download = require('./download'); 25 | var MapSharingDialog = require('./dialogs/SharingDialog'); 26 | var NodeEditDialog = require('./dialogs/NodeEditDialog'); 27 | var MapActions = require('./actions/mapactions.js'); 28 | var RelatedMaps = require('./relatedmaps.js'); 29 | 30 | var logoStyle = { 31 | height : 30, 32 | marginTop : -5 33 | }; 34 | 35 | var urllite = require('urllite'); 36 | var url = urllite(window.location); 37 | var mapId = url.hash; 38 | 39 | //remove # at the beginning 40 | if(mapId) { 41 | mapId = mapId.substring(1); 42 | } 43 | 44 | var origin = url.origin; 45 | 46 | var MapEditorPage = React.createClass({ 47 | MapStore : MapStore, 48 | ShareDialogStore : ShareDialogStore, 49 | toggleSharingDialog : function(){ 50 | MapActions.toggleSharingDialog(); 51 | }, 52 | render: function() { 53 | return ( 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | Home 62 | 63 | 64 | 65 | 66 | 76 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | ); 104 | } 105 | }); 106 | 107 | module.exports = MapEditorPage; 108 | -------------------------------------------------------------------------------- /client-v2-jsx/mapstatus.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | var React = require('react'); 3 | var ButtonGroup = require('react-bootstrap').ButtonGroup; 4 | var Well = require('react-bootstrap').Well; 5 | var Button = require('react-bootstrap').Button; 6 | var MapComponent = require('./mapcomponent'); 7 | var ActionPaletteComponent = require('./actionpalettecomponent'); 8 | var DraggablePaletteComponent = require('./draggablepalettecomponent'); 9 | var MapActions = require('./actions/mapactions'); 10 | var MapConstants = require('./constants/mapconstants'); 11 | var InlineEdit = require('react-edit-inline'); 12 | 13 | 14 | var styleToEdit = { 15 | borderBottom: '1px dashed #999', 16 | display: 'inline' 17 | }; 18 | 19 | var MapStatus = React.createClass({ 20 | getInitialState: function() { 21 | return {state:'Loading...'}; 22 | }, 23 | componentDidMount: function() { 24 | this.props.store.addChangeListener(this._onChange); 25 | }, 26 | componentWillUnmount: function() { 27 | this.props.store.removeChangeListener(this._onChange); 28 | }, 29 | render: function() { 30 | if(!this.state.state){ 31 | return null; 32 | } 33 | return ; 34 | }, 35 | _onChange : function() { 36 | this.setState(this.props.store.getStateInfo()); 37 | } 38 | }); 39 | 40 | module.exports = MapStatus; 41 | -------------------------------------------------------------------------------- /client-v2-jsx/maptitledescription.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | var React = require('react'); 3 | var ButtonGroup = require('react-bootstrap').ButtonGroup; 4 | var Well = require('react-bootstrap').Well; 5 | var Button = require('react-bootstrap').Button; 6 | var form = require('react-bootstrap').Form; 7 | var MapComponent = require('./mapcomponent'); 8 | var ActionPaletteComponent = require('./actionpalettecomponent'); 9 | var DraggablePaletteComponent = require('./draggablepalettecomponent'); 10 | var MapActions = require('./actions/mapactions'); 11 | var MapConstants = require('./constants/mapconstants'); 12 | var EditableShortText = require('./editableshorttext'); 13 | 14 | var styleToEdit = { 15 | borderBottom: '1px dashed #999', 16 | display: 'inline' 17 | }; 18 | 19 | var MapTitleDescription = React.createClass({ 20 | editablename : null, 21 | editabledescription : null, 22 | componentDidMount: function() { 23 | this.props.store.addChangeListener(this._onChange); 24 | 25 | }, 26 | componentWillUnmount: function() { 27 | this.props.store.removeChangeListener(this._onChange); 28 | }, 29 | componentDidUpdate : function() { 30 | if(this.editabledescription){ 31 | $(this.editabledescription).editable(); 32 | } 33 | }, 34 | onSubmit : function(key){ 35 | key.preventDefault(); 36 | key.stopPropagation(); 37 | return false; 38 | }, 39 | render: function() { 40 | if(this.state){ 41 | var name = this.state.name; 42 | var description = this.state.description; 43 | var _this = this; 44 | return ( 45 |
46 |
47 | 56 |
57 |
58 | 59 | 68 | 69 |
70 |
71 | ); 72 | } else { 73 | return ( 74 |

Loading title & description

75 | ); 76 | } 77 | }, 78 | _onChange : function() { 79 | this.setState(this.props.store.getNameAndDescription()); 80 | }, 81 | _customValidateText : function(){ 82 | return true; 83 | } 84 | }); 85 | 86 | module.exports = MapTitleDescription; 87 | -------------------------------------------------------------------------------- /client-v2-jsx/palette.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | var React = require('react'); 3 | var ButtonGroup = require('react-bootstrap').ButtonGroup; 4 | var Well = require('react-bootstrap').Well; 5 | var Button = require('react-bootstrap').Button; 6 | var MapComponent = require('./mapcomponent'); 7 | var ActionPaletteComponent = require('./actionpalettecomponent'); 8 | var DraggablePaletteComponent = require('./draggablepalettecomponent'); 9 | var MapActions = require('./actions/mapactions'); 10 | var MapConstants = require('./constants/mapconstants'); 11 | 12 | var paletteStyle = { 13 | minWidth : 150, 14 | maxWidth : 150, 15 | height: '100%', 16 | float: 'left' 17 | }; 18 | 19 | var Palette = React.createClass({ 20 | getInitialState : function(){ 21 | return {items: this.props.store.getComponentTypes()}; 22 | }, 23 | render: function() { 24 | var store = this.props.store; 25 | var components = this.state.items.map( 26 | function(component){ 27 | var _key = component.key; 28 | console.log(_key); 29 | return ; 34 | } 35 | ); 36 | return ( 37 |
38 |
Components:
39 | 40 | {components} 41 | 42 |
Actions:
43 | 44 | 48 | 52 | 56 | 60 | 61 |
62 | ); 63 | } 64 | }); 65 | 66 | module.exports = Palette; 67 | -------------------------------------------------------------------------------- /client-v2-jsx/relatedmaps.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | var React = require('react'); 3 | var MapActions = require('./actions/mapactions'); 4 | var MapConstants = require('./constants/mapconstants'); 5 | var Glyphicon = require('react-bootstrap').Glyphicon; 6 | var Dropdown = require('react-bootstrap').Dropdown; 7 | var MenuItem = require('react-bootstrap').MenuItem; 8 | var Badge = require('react-bootstrap').Badge; 9 | var jquery = require('jquery'); 10 | var _async = require('async'); 11 | 12 | var RelatedMaps = React.createClass({ 13 | _tempRelated : [], 14 | getInitialState : function(){ 15 | return {related:[]}; 16 | }, 17 | componentDidMount: function() { 18 | var url = '/api/map/related/' + this.props.mapId; 19 | jquery.get(url, function(result) { 20 | this._tempRelated = result; 21 | this.fetchNames(); 22 | }.bind(this)); 23 | }, 24 | fetchNames : function (){ 25 | var _this = this; 26 | // for each prefetched map relation 27 | _async.map(_this._tempRelated, function(arg, clbck){ 28 | // find out which map we need to fetch 29 | var interestingMapId = arg.clonedSource; 30 | if(arg.clonedSource === _this.props.mapId){ 31 | interestingMapId = arg.clonedTarget; 32 | } 33 | // store a link to the 'other map' 34 | arg.link = "/map/" + interestingMapId; 35 | //fetch the 'other' map and get the name 36 | var url = "/api/map/" + interestingMapId; 37 | jquery.get(url, function(result) { 38 | arg.name = result.history[0].name; 39 | clbck(null, arg); 40 | }); 41 | }, function(err){ 42 | _this.setState({related:_this._tempRelated}); 43 | }); 44 | }, 45 | render: function() { 46 | if(this.state.related.length === 0) { 47 | return null; 48 | } 49 | var _this = this; 50 | return ( 51 | 52 | 53 |  Map Variants 54 | 55 | 56 | {(function(){ 57 | return _this.state.related.map(function(arg){ 58 | return ( 59 | 60 | {arg.name} 61 | 62 | ); 63 | }); 64 | })()} 65 | 66 | 67 | ); 68 | } 69 | }); 70 | 71 | module.exports = RelatedMaps; 72 | -------------------------------------------------------------------------------- /client-v2-jsx/store/ShareDialogStore.js: -------------------------------------------------------------------------------- 1 | var MapDispatcher = require('../dispatcher/mapdispatcher'); 2 | var EventEmitter = require('events').EventEmitter; 3 | var MapConstants = require('../constants/mapconstants'); 4 | var assign = require('object-assign'); 5 | 6 | var sharingDialog = false; 7 | var anonymousShare = false; 8 | var anonymousShareLink = ''; 9 | 10 | function toggleSharingDialog(){ 11 | sharingDialog = !sharingDialog; 12 | } 13 | 14 | var ShareDialogStore = assign({}, EventEmitter.prototype, { 15 | 16 | getSharedDialogState : function(){ 17 | return { 18 | sharingDialog:sharingDialog, 19 | anonymousShare:anonymousShare, 20 | anonymousShareLink:anonymousShareLink 21 | }; 22 | }, 23 | 24 | emitChange: function() { 25 | this.emit(MapConstants.CHANGE_EVENT); 26 | }, 27 | 28 | addChangeListener: function(callback, event) { 29 | if(!event) { 30 | event = MapConstants.CHANGE_EVENT; 31 | } 32 | this.on(event, callback); 33 | }, 34 | 35 | removeChangeListener: function(callback, event) { 36 | if(!event) { 37 | event = MapConstants.CHANGE_EVENT; 38 | } 39 | this.removeListener(event, callback); 40 | } 41 | }); 42 | 43 | MapDispatcher.register(function(action) { 44 | 45 | switch(action.actionType) { 46 | case MapConstants.MAP_SHARING_DIALOG: 47 | toggleSharingDialog(); 48 | ShareDialogStore.emitChange(); 49 | break; 50 | case MapConstants.MAP_TOGGLE_ANONYMOUS_SHARING: 51 | if(action.url){ 52 | anonymousShare = true; 53 | anonymousShareLink = action.url; 54 | } else { 55 | anonymousShare = false; 56 | } 57 | ShareDialogStore.emitChange(); 58 | break; 59 | case MapConstants.LOAD_MAP_SHARING_STATE: 60 | if(action.data && action.data.anonymousShare){ 61 | anonymousShare = true; 62 | anonymousShareLink = action.data.url; 63 | } 64 | ShareDialogStore.emitChange(); 65 | break; 66 | default: 67 | // no op 68 | } 69 | }); 70 | 71 | module.exports = ShareDialogStore; 72 | -------------------------------------------------------------------------------- /client/3rd/bootstrap-checkbox.css: -------------------------------------------------------------------------------- 1 | /* =========================================================== 2 | * bootstrap-checkbox - v.1.0.1 3 | * =========================================================== 4 | * Copyright 2014 Roberto Montresor 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * ========================================================== */ 18 | 19 | @CHARSET "ISO-8859-1"; 20 | 21 | .bootstrap-checkbox > button.btn{ 22 | padding:7px 0 4px 2px; 23 | width:28px; 24 | } 25 | .bootstrap-checkbox > button.btn.displayAsButton{ 26 | width:auto; 27 | } 28 | .bootstrap-checkbox > button.btn.displayAsButton > span.label-checkbox{ 29 | padding:0 8px 0 4px; 30 | } 31 | .bootstrap-checkbox > button.btn.displayAsButton > span.icon{ 32 | margin: 2px 4px; 33 | } 34 | .bootstrap-checkbox > button.btn.displayAsButton > span.label-prepend-checkbox{ 35 | padding:0 4px 0 8px; 36 | } 37 | .bootstrap-checkbox > button.btn.btn-large{ 38 | padding:10px 0 6px 2px; 39 | width:36px; 40 | } 41 | .bootstrap-checkbox > button.btn.btn-small{ 42 | padding:5px 0 3px 1px; 43 | width:24px; 44 | } 45 | .bootstrap-checkbox > button.btn.btn-mini{ 46 | padding:4px 0 1px 1px; 47 | width:20px; 48 | } 49 | 50 | .bootstrap-checkbox > .btn.btn-link{ 51 | text-decoration:none; 52 | } 53 | 54 | label.bootstrap-checkbox.disabled{ 55 | cursor:default; 56 | color:#666; 57 | } 58 | 59 | .bootstrap-checkbox > .label-prepend-checkbox{ 60 | padding-right:4px; 61 | } 62 | 63 | .bootstrap-checkbox > .label-checkbox{ 64 | padding-left:4px; 65 | font-weight : 300; 66 | } 67 | 68 | .bootstrap-checkbox.disabled > [class^="label-"], 69 | .bootstrap-checkbox > .btn[disabled]{ 70 | cursor:not-allowed; 71 | } 72 | 73 | [class^="cb-icon-"], 74 | [class*=" cb-icon-"] { 75 | display: inline-block; 76 | width: 14px; 77 | height: 14px; 78 | margin-top: 1px; 79 | *margin-right: .3em; 80 | line-height: 14px; 81 | vertical-align: text-top; 82 | background-image: url("../img/icons.png"); 83 | background-position: 14px 14px; 84 | background-repeat: no-repeat; 85 | } 86 | 87 | .bootstrap-checkbox button.btn > span.cb-icon-check, 88 | .bootstrap-checkbox.disabled button.btn:hover > span.cb-icon-check, 89 | .bootstrap-checkbox.disabled button.btn:active > span.cb-icon-check{ 90 | background-position: 0 -1px; 91 | } 92 | .bootstrap-checkbox button.btn:hover > span.cb-icon-check, 93 | .bootstrap-checkbox button.btn:focus > span.cb-icon-check, 94 | .bootstrap-checkbox button.btn:active > span.cb-icon-check{ 95 | background-position: 0 -25px; 96 | } 97 | .bootstrap-checkbox button.btn > span.cb-icon-check-empty, 98 | .bootstrap-checkbox.disabled button.btn:hover > span.cb-icon-check-empty, 99 | .bootstrap-checkbox.disabled button.btn:active > span.cb-icon-check-empty{ 100 | background-position: -24px -1px; 101 | } 102 | .bootstrap-checkbox button.btn:hover > span.cb-icon-check-empty, 103 | .bootstrap-checkbox button.btn:focus > span.cb-icon-check-empty, 104 | .bootstrap-checkbox button.btn:active > span.cb-icon-check-empty{ 105 | background-position: -24px -25px; 106 | } 107 | 108 | .bootstrap-checkbox button.btn > span.cb-icon-check-indeterminate, 109 | .bootstrap-checkbox.disabled button.btn:hover > span.cb-icon-check-indeterminate, 110 | .bootstrap-checkbox.disabled button.btn:active > span.cb-icon-check-indeterminate{ 111 | background-position: -48px -1px; 112 | } 113 | .bootstrap-checkbox button.btn:hover > span.cb-icon-check-indeterminate, 114 | .bootstrap-checkbox button.btn:focus > span.cb-icon-check-indeterminate, 115 | .bootstrap-checkbox button.btn:active > span.cb-icon-check-indeterminate{ 116 | background-position: -48px -25px; 117 | } 118 | -------------------------------------------------------------------------------- /client/additional licences: -------------------------------------------------------------------------------- 1 | this needs to be replaced. 2 | jqBootstrapValidation is MIT 3 | Please note that favicon.ico is a logo identifying wardleymaps.com and therefore should not be used if the tool is used commercially outside of wardleymaps.com. -------------------------------------------------------------------------------- /client/analysis.jade: -------------------------------------------------------------------------------- 1 | .modal-header 2 | button.close(type="button" data-dismiss="modal" aria-hidden="true") × 3 | h4.modal-title Automated Analysis 4 | .modal-body#analysisModalBody 5 | each feedback in result 6 | .panel(class=feedback.type) 7 | .panel-heading 8 | h3.panel-title=feedback.title 9 | .panel-body=feedback.message 10 | .modal-footer 11 | button.btn.btn-primary.btn-default(type="button" data-dismiss="modal") Close 12 | -------------------------------------------------------------------------------- /client/analytics.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdaniel/wardleymapstool/4de82586494bdb4e8752622e04b3a7bd39978b21/client/analytics.js -------------------------------------------------------------------------------- /client/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdaniel/wardleymapstool/4de82586494bdb4e8752622e04b3a7bd39978b21/client/android-icon-192x192.png -------------------------------------------------------------------------------- /client/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdaniel/wardleymapstool/4de82586494bdb4e8752622e04b3a7bd39978b21/client/favicon-16x16.png -------------------------------------------------------------------------------- /client/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdaniel/wardleymapstool/4de82586494bdb4e8752622e04b3a7bd39978b21/client/favicon-32x32.png -------------------------------------------------------------------------------- /client/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdaniel/wardleymapstool/4de82586494bdb4e8752622e04b3a7bd39978b21/client/favicon-96x96.png -------------------------------------------------------------------------------- /client/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdaniel/wardleymapstool/4de82586494bdb4e8752622e04b3a7bd39978b21/client/favicon.ico -------------------------------------------------------------------------------- /client/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /client/help/assistevolution.jade: -------------------------------------------------------------------------------- 1 | .modal-header 2 | button.close(type="button" data-dismiss="modal" aria-hidden="true") × 3 | h4.modal-title Evolution 4 | .modal-body(style="overflow-y: auto;max-height: calc(100vh - 212px);") 5 | p Value chains on their own are practically useless for understanding an environment because they lack any form of context on how it is changing. 6 | p If you think of a company like Nokia, it started as a paper mill became a plastics manufacturer and then eventually a telecommunications company. During this time its value chains have radically altered. 7 | p In order to understand an environment we therefore need to somehow capture this aspect of change and combine it with our value chain. 8 | p The biggest problem with creating an understanding of the context in which something operates is that the process of change and how things evolve can't be measured over time. As uncomfortable as it is, you have to simply accept that you don't have a crystal ball and hence you have to embrace the uncertainty of future change. 9 | p Fortunately, there’s a neat trick because whilst evolution can’t be measured over time, it can be measured over certainty. 10 | p So, this is exactly what you need to do. Take your value chain and map it with an evolution axis. 11 | p
Figure 3 – A Map 12 | img.img-thumbnail(src="http://2.bp.blogspot.com/-kob2Zj4i0sQ/VM_dozfTvSI/AAAAAAAAGwk/em5ZZaffNLU/s1600/Screen%2BShot%2B2015-02-02%2Bat%2B20.25.46.png") 13 | p
14 | p All the components in the above map are evolving from left to right due to supply and demand competition. 15 | p As they evolve their characteristics change from an uncharted domain (the uncertain, rare and constantly changing) becoming more industrialised (the known, the common, the stable). 16 | p For example, power supply is something commonplace and well understood. We all know how to use a plug and switch on a socket. However, creative studios and the art of creating a TV show is less common and less well understood, it is more a source of wonder and amazement for many. It should be remembered that power supply was itself a source of wonder and amazement back in the 1890s. 17 | p
18 | p.pull-right 19 | | Courtesy of Simon Wardley, http://blog.gardeviance.org/ 20 | p
21 | .modal-footer 22 | button.btn.btn-primary.btn-default(type="button" data-dismiss="modal") Close -------------------------------------------------------------------------------- /client/help/assistuserneeds.jade: -------------------------------------------------------------------------------- 1 | .modal-header 2 | button.close(type="button" data-dismiss="modal" aria-hidden="true") × 3 | h4.modal-title User Needs 4 | .modal-body 5 | p Critical to mapping is to first focus on the user need. 6 | p It’s important not to think of your needs i.e. your desire to make a profit or be successful. You need to think careful about what the user actually wants. 7 | p There are various techniques for this from writing the press release to creating a user journey. 8 | p In the case of the TV Company, the users wanted to be entertained during their leisure time. 9 | p There were two routes to satisfying this need, either through a branded online service that delivered the company’s programs or through a content aggregator such as NetFlix or Amazon where the output from many TV companies is combined. Figure 1 provides a simple needs diagram which shows the user need. 10 | p
Figure 1 – User Needs 11 | img.img-thumbnail(src="http://4.bp.blogspot.com/-RkoHImqNPNY/VM_dnV3PXsI/AAAAAAAAGwY/9GxsDGN_w2M/s1600/Screen%2BShot%2B2015-02-02%2Bat%2B20.25.32.png") 12 | p
13 | p.pull-right 14 | | Courtesy of Simon Wardley, http://blog.gardeviance.org/ 15 | p
16 | .modal-footer 17 | button.btn.btn-primary.btn-default(type="button" data-dismiss="modal") Close -------------------------------------------------------------------------------- /client/help/assistvaluechain.jade: -------------------------------------------------------------------------------- 1 | .modal-header 2 | button.close(type="button" data-dismiss="modal" aria-hidden="true") × 3 | h4.modal-title Value Chain 4 | .modal-body(style="overflow-y: auto;max-height: calc(100vh - 212px);") 5 | p Once you’ve determined the high level needs then the next step is to flesh this out with the components required to meet those needs. 6 | p You do this by creating a chain of needs. 7 | p It is called this a value chain, as the focus should always be that by meeting the needs of others you hope to create value. 8 | p If someone had a need for a cup of tea, then by mixing the components of a kettle, cold water, cup, electricity and tea then you can meet his/her needs. That has value to him/her. 9 | p At the top of any value chain should be the visible user need that you are trying to serve and below this are the increasingly invisible (to the user) components that are necessary to serve those needs. Figure 2 provides a value chain for the TV Company. 10 | p
Figure 2 – A Value Chain 11 | img.img-thumbnail(src="http://1.bp.blogspot.com/-IhJYIQIYTp0/VM_doW9YqFI/AAAAAAAAGwg/1aFnWy_lBtA/s1600/Screen%2BShot%2B2015-02-02%2Bat%2B20.25.38.png") 12 | p
13 | p In the above diagram then both branded and aggregator sites need content. That content needs a distribution mechanism (e.g. internet broadcast or traditional media) but it also has to be created which requires artistic direction. 14 | | Naturally, artistic direction needs a creative studio to make the show but it also needs market analysis i.e. what sort of programs do people want to watch? And so the chain continues down to components such as compute and power. 15 | p
16 | p.pull-right 17 | | Courtesy of Simon Wardley, http://blog.gardeviance.org/ 18 | p
19 | .modal-footer 20 | button.btn.btn-primary.btn-default(type="button" data-dismiss="modal") Close -------------------------------------------------------------------------------- /client/img/1x1_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdaniel/wardleymapstool/4de82586494bdb4e8752622e04b3a7bd39978b21/client/img/1x1_transparent.png -------------------------------------------------------------------------------- /client/img/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdaniel/wardleymapstool/4de82586494bdb4e8752622e04b3a7bd39978b21/client/img/icons.png -------------------------------------------------------------------------------- /client/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdaniel/wardleymapstool/4de82586494bdb4e8752622e04b3a7bd39978b21/client/img/logo.png -------------------------------------------------------------------------------- /client/index.css: -------------------------------------------------------------------------------- 1 | #newMapFormDialog { 2 | display: none; 3 | } 4 | 5 | .no_top_margin { 6 | margin-top: 0px; 7 | } 8 | 9 | .img-thumbnail { 10 | min-height: 200px; 11 | min-width: 200px; 12 | 13 | } -------------------------------------------------------------------------------- /client/index.jade: -------------------------------------------------------------------------------- 1 | //-Copyright 2014, 2015 Krzysztof Daniel and Scott Weinstein. 2 | //- 3 | //-Licensed under the Apache License, Version 2.0 (the "License"); 4 | //-you may not use this file except in compliance with the License. 5 | //-You may obtain a copy of the License at 6 | //- 7 | //- http://www.apache.org/licenses/LICENSE-2.0 8 | //- 9 | //-Unless required by applicable law or agreed to in writing, software 10 | //-distributed under the License is distributed on an "AS IS" BASIS, 11 | //-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | //-See the License for the specific language governing permissions and 13 | //-limitations under the License. 14 | doctype html 15 | html 16 | head 17 | meta(charset='utf-8') 18 | title Atlas, the mapping tool 19 | script(src='//cdnjs.cloudflare.com/ajax/libs/jquery/1.11.2/jquery.min.js') 20 | script(src='//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.2/jquery-ui.min.js') 21 | script(src='/3rd/jquery.form.js') 22 | script(src='//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js') 23 | script(src='/3rd/jqBootstrapValidation.js') 24 | link(rel='stylesheet', href='//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.2/jquery-ui.min.css') 25 | link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css') 26 | link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css') 27 | link(rel='stylesheet', href='index.css') 28 | link(rel='shortcut icon', href='/favicon.ico', type="favicon.ico") 29 | link(rel="icon" type="image/png" sizes="192x192" href="/android-icon-192x192.png") 30 | link(rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png") 31 | link(rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png") 32 | link(rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png") 33 | body 34 | //- actual content 35 | #content 36 | .container-fluid 37 | nav.navbar.navbar-default(role="navigation") 38 | .navbar-header 39 | button.navbar-toggle(type="button" data-target="#navbarCollapse" data-toggle="collapse") 40 | span.sr-only Toggle navigation 41 | span.icon-bar 42 | span.icon-bar 43 | span.icon-bar 44 | a.navbar-brand(href="/") 45 | img(src="/logo.png" alt="Home" style="height: 30px;margin-top: -5px;") 46 | #navbarCollapse.collapse.navbar-collapse 47 | ul.nav.navbar-nav 48 | li.active 49 | a(href="/") Your lines 50 | li 51 | a.twitter-mention-button(href="https://twitter.com/intent/tweet?screen_name=wardleymaps" 52 | data-size="large" data-related="wardleymaps" target="_blank") Support & Complaints 53 | ul.nav.navbar-nav.navbar-right 54 | li 55 | a(href="/profile") 56 | span.glyphicon.glyphicon-user=user.givenName 57 | li 58 | a(href="/logout") 59 | span.glyphicon.glyphicon-log-out Logout 60 | #selectors.row 61 | //- new map button 62 | .col-xs-12.col-sm-6.col-md-3.col-lg-2 63 | .thumbnail.text-center 64 | a(href="#" onclick="showAddMapDialog()").text-center 65 | .glyphicon.glyphicon-plus.img-thumbnail(style="font-size:140px;color:silver") 66 | .caption 67 | h3 Map a new business line... 68 | //- this part actually renders maps 69 | each map in response 70 | .col-xs-12.col-sm-6.col-md-3.col-lg-2(id=map._id) 71 | .thumbnail 72 | a(href="/map/" + map._id) 73 | img.img-thumbnail(src='api/thumbnail/' + map._id title=map.description) 74 | .caption 75 | div.col-xs-9 76 | h3.no_top_margin= map.name 77 | p= map.description 78 | .col-xs-1 79 | .dropdown 80 | button.btn.btn-default.dropdown-toggle(type="button" data-toggle="dropdown" aria-expanded="true" id="ddm"+map._id) 81 | span.glyphicon.glyphicon-cog   82 | span.caret 83 | ul.dropdown-menu.dropdown-menu-right(role="menu" aria-labelledby="ddm"+map._id) 84 | li(role="presentation") 85 | a(role="menuitem" tabindex="-1" href="/api/map/clone/" + map._id) 86 | span.glyphicon.glyphicon-duplicate 87 | |  Clone 88 | li(role="presentation") 89 | a(role="menuitem" tabindex="-1" href="/api/map/delete/" + map._id) 90 | span.glyphicon.glyphicon-remove 91 | |  Delete! 92 | li(role="presentation") 93 | a(role="menuitem" tabindex="-1" href="/map/#" + map._id) 94 | span.glyphicon.glyphicon-flash 95 | |  Open with an EXPERIMENTAL editor. 96 | //- new business line dialog 97 | #basicModal(class="modal fade" tabindex="-1" role="dialog" aria-labelledby="basicModal" aria-hidden="true") 98 | div(class="modal-dialog") 99 | div(class="modal-content") 100 | div(class="modal-header") 101 | button(type="button" 102 | class="close" 103 | data-dismiss="modal" 104 | aria-hidden="true") × 105 | h4#myModalLabel(class="modal-title") Map a new business line... 106 | div(class="modal-body") 107 | form#newMapCreationForm(action="/api/map/" 108 | method='post' class="form-horizontal") 109 | .form-group 110 | label(for="name" class="col-sm-3 control-label") Line's name: 111 | .col-sm-8 112 | input#name.form-control(type="text" name="name" value="" required) 113 | .form-group 114 | label(for="description" class="col-sm-3 control-label") Description: 115 | .col-sm-8 116 | textarea#description.form-control(rows="#" type="text" name="description" value="" required) 117 | .form-group 118 | .col-sm-5.col-sm-offset-8 119 | button.btn.btn-default(type="button" data-dismiss="modal") Cancel 120 | span    121 | button.btn.btn-primary(type="submit") Map! 122 | script(src='index.js') 123 | script(src='analytics.js') 124 | -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2014 Krzysztof Daniel 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License.*/ 14 | 15 | function showAddMapDialog() { 16 | var options = { 17 | "backdrop" : "static" 18 | }; 19 | $('#basicModal').modal(options); 20 | } 21 | 22 | 23 | function init() { 24 | $(function() { 25 | $("input,select,textarea").not("[type=submit]").jqBootstrapValidation({ 26 | submitSuccess: function ($form, event) { 27 | window.location.reload(); 28 | } 29 | }); 30 | 31 | }); 32 | } 33 | 34 | $(document).ready(init); 35 | 36 | -------------------------------------------------------------------------------- /client/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdaniel/wardleymapstool/4de82586494bdb4e8752622e04b3a7bd39978b21/client/logo.png -------------------------------------------------------------------------------- /client/logout.html: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 18 | 19 | Please log in again. 20 | 21 | -------------------------------------------------------------------------------- /client/mapeditor.css: -------------------------------------------------------------------------------- 1 | #map-container { 2 | border: 1px solid gray; 3 | min-height: 800px; 4 | max-height: 800px; 5 | min-width: 800px; 6 | height: auto; 7 | position: relative; 8 | z-index: 1; 9 | padding-left : 1%; 10 | padding-right: 1%; 11 | } 12 | 13 | .errormessage { 14 | display : none; 15 | } 16 | 17 | .item { 18 | border: 1px solid black; 19 | background-color: silver; 20 | min-height: 20px; 21 | min-width: 20px; 22 | max-width: 20px; 23 | max-height: 20px; 24 | position: absolute; 25 | border-radius: 10px; 26 | position: absolute; 27 | z-index: 2; 28 | } 29 | 30 | .wheelnav { 31 | top: -94px; 32 | left: -98px; 33 | position: relative; 34 | z-index: 23; 35 | text-shadow: 1px 1px white; 36 | width: 200px; 37 | height: 200px; 38 | } 39 | 40 | .itemSelected { 41 | background-color: #FFFFCC !important ; 42 | z-index : 9; 43 | } 44 | 45 | .itemCaption { 46 | top: -10px; 47 | left: 23px; 48 | position: absolute; 49 | z-index: 2; 50 | text-shadow: 2px 2px white; 51 | width: 80px; 52 | height: 20px; 53 | max-width: 80px; 54 | max-height: 20px; 55 | margin-bottom: -20px; 56 | font-size: 10px; 57 | line-height: 11px; 58 | } 59 | 60 | .itemMenu { 61 | width: 130px; 62 | z-index: 10; 63 | position: relative; 64 | left: -35px; 65 | top: -10px; 66 | } 67 | 68 | .mapUserNeed { 69 | border-width: 4px; 70 | } 71 | 72 | .mapExternal { 73 | background-color: #f2f2f2; 74 | } 75 | 76 | #value { 77 | z-index: 1; 78 | font-size: smaller; 79 | top: 3%; 80 | left: 3%; 81 | position: absolute; 82 | } 83 | 84 | #evolution { 85 | z-index: 1; 86 | bottom: 3%; 87 | right: 3%; 88 | position: absolute; 89 | font-size: smaller; 90 | } 91 | 92 | #axis-x { 93 | position: absolute; 94 | bottom: 2%; 95 | height: 1px; 96 | left: 2%; 97 | right: 2%; 98 | border: 1px solid gray; 99 | margin-right: 0; 100 | margin-left: 0; 101 | } 102 | 103 | #arrow-x { 104 | position: absolute; 105 | width: 0; 106 | height: 0; 107 | border-top: 4px solid transparent; 108 | border-bottom: 2px solid transparent; 109 | border-left: 10px solid gray; 110 | right: 2%; 111 | bottom: 2%; 112 | } 113 | 114 | #axis-y { 115 | width: 1px; 116 | position: absolute; 117 | border-width: 1px; 118 | top: 2%; 119 | bottom: 2%; 120 | left: 2%; 121 | border: 1px solid gray; 122 | } 123 | 124 | .axis-support { 125 | position: absolute; 126 | border-width: 1px; 127 | top: 3%; 128 | bottom: 2%; 129 | border: 1px dashed silver; 130 | z-index: 1; 131 | } 132 | 133 | #support-1 { 134 | left: 25%; 135 | } 136 | 137 | #support-2 { 138 | left: 50%; 139 | } 140 | 141 | #support-3 { 142 | left: 75%; 143 | } 144 | 145 | #arrow-y { 146 | position: absolute; 147 | width: 0; 148 | height: 0; 149 | border-left: 2px solid transparent; 150 | border-right: 4px solid transparent; 151 | border-bottom: 10px solid gray; 152 | left: 2%; 153 | top: 2%; 154 | } 155 | 156 | .connectionlabel { 157 | background-color: rgba(255,255,255,0.8); 158 | } 159 | 160 | ._jsPlumb_endpoint { z-index: 10; } 161 | -------------------------------------------------------------------------------- /client/mapeditor.jade: -------------------------------------------------------------------------------- 1 | //-Copyright 2015 Krzysztof Daniel and Scott Weinstein. 2 | //- 3 | //-Licensed under the Apache License, Version 2.0 (the "License"); 4 | //-you may not use this file except in compliance with the License. 5 | //-You may obtain a copy of the License at 6 | //- 7 | //- http://www.apache.org/licenses/LICENSE-2.0 8 | //- 9 | //-Unless required by applicable law or agreed to in writing, software 10 | //-distributed under the License is distributed on an "AS IS" BASIS, 11 | //-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | //-See the License for the specific language governing permissions and 13 | //-limitations under the License. 14 | doctype html 15 | html 16 | head 17 | meta(charset='utf-8') 18 | title Atlas, the mapping tool - #{map.history[0].name} 19 | script(src='//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js') 20 | script(src='//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js') 21 | script(src='/3rd/jquery.form.js') 22 | script(src='//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js') 23 | script(src='//cdnjs.cloudflare.com/ajax/libs/x-editable/1.5.0/bootstrap3-editable/js/bootstrap-editable.min.js') 24 | script(src='/3rd/jqBootstrapValidation.js') 25 | script(src='/3rd/dom.jsPlumb-1.7.10.js') 26 | script(src='/3rd/bootstrap-checkbox.js') 27 | script(src='/3rd/raphael.min.js') 28 | script(src='/3rd/wheelnav.js') 29 | script(src='https://cdnjs.cloudflare.com/ajax/libs/jqueryui-touch-punch/0.2.3/jquery.ui.touch-punch.min.js') 30 | link(rel='stylesheet', href='//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/jquery-ui.theme.min.css') 31 | link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css') 32 | link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css') 33 | link(rel='stylesheet', href='//cdnjs.cloudflare.com/ajax/libs/x-editable/1.5.0/bootstrap3-editable/css/bootstrap-editable.css') 34 | link(rel='stylesheet', href='/3rd/bootstrap-checkbox.css') 35 | link(rel='stylesheet', href='/mapeditor.css') 36 | link(rel='shortcut icon', href='/favicon.ico', type="favicon.ico") 37 | link(rel="icon" type="image/png" sizes="192x192" href="/android-icon-192x192.png") 38 | link(rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png") 39 | link(rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png") 40 | link(rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png") 41 | script(src='/progresshelper.js') 42 | script. 43 | var mapURL = '/api/map/#{map._id}'; 44 | var partialUpdateURL = '/api/map/partial/#{map._id}'; 45 | var progressURL = '/api/map/#{map._id}/progressstate'; 46 | var shareURL = '/share/map/#{map._id}/'; 47 | var map = { 48 | name : !{JSON.stringify(map.history[0].name)}, 49 | description : !{JSON.stringify(map.history[0].description)}, 50 | userDate: "#{map.history[0].userDate}", 51 | serverDate: "#{map.history[0].serverDate}", 52 | nodes : !{JSON.stringify(map.history[0].nodes)}, 53 | connections: !{JSON.stringify(map.history[0].connections)} 54 | }; 55 | var progressHelper = new ProgressHelper(); 56 | body 57 | .container-fluid 58 | nav.navbar.navbar-default(role="navigation") 59 | .navbar-header 60 | button.navbar-toggle(type="button" data-target="#navbarCollapse" data-toggle="collapse") 61 | span.sr-only Toggle navigation 62 | span.icon-bar 63 | span.icon-bar 64 | span.icon-bar 65 | a.navbar-brand(href="/") 66 | img(src="/logo.png" alt="Home" style="height: 30px;margin-top: -5px;") 67 | #navbarCollapse.collapse.navbar-collapse 68 | ul.nav.navbar-nav 69 | li 70 | a#name(href="#" data-type="text" data-title="Map name" value=map.history[0].name data-pk="name" data-url='/api/map/partial/#{map._id}')=map.history[0].name 71 | include ./mapeditor_includes/preferences.jade 72 | li.dropdown 73 | a.dropdown-toggle(data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false") 74 | span.glyphicon.glyphicon-download-alt  Download 75 | span.caret 76 | ul.dropdown-menu(aria-labelledby="dropdownMenu1") 77 | li 78 | a#download(download="#{map.history[0].name}.svg" href="/api/svgforcedownload/#{map._id}/#{map._id}.svg") As SVG 79 | li 80 | a#download(download="#{map.history[0].name}.png" href="/api/pngforcedownload/#{map._id}/#{map._id}.png") As PNG  81 | span.badge beta 82 | if related.length > 0 83 | li.dropdown 84 | a.dropdown-toggle(data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false") 85 | span Related maps... 86 | span.caret 87 | ul.dropdown-menu(aria-labelledby="dropdownMenu2") 88 | each val in related 89 | if map._id == val.clonedSource 90 | li 91 | a.mapidtoupdate(href="/map/#{val.clonedTarget}" id="#{val.clonedTarget}") Child map 92 | else 93 | li 94 | a(href="/map/#{val.clonedSource}") Parent map 95 | li 96 | a.twitter-mention-button(href="https://twitter.com/intent/tweet?screen_name=wardleymaps" 97 | data-size="large" data-related="wardleymaps" target="_blank") Support & Complaints 98 | ul.nav.navbar-nav.navbar-right 99 | li 100 | a(href="/profile") 101 | span.glyphicon.glyphicon-user=user.givenName 102 | li 103 | a(href="/logout") 104 | span.glyphicon.glyphicon-log-out Logout 105 | p Description: 106 | a#description(href="#" data-type="text" data-title="Description" value=map.history[0].description data-pk="description" data-url='/api/map/partial/#{map._id}')=map.history[0].description 107 | #lostsession.errormessage.alert.alert-warning 108 | strong Session lost!  109 | | The server requires authorization. You will be redirected in 5 seconds. 110 | #servernotresponding.errormessage.alert.alert-warning 111 | strong Server is not responding correctly!  112 | | You will be redirected in 5 seconds. 113 | //- canvas 114 | #map-container.col-lg-10 115 | .axis-support#support-1 116 | .axis-support#support-2 117 | .axis-support#support-3 118 | #value Value 119 | #evolution Evolution 120 | #axis-x.row 121 | .description.col-md-3.hidden-xs.hidden-sm Genesis 122 | .description.col-md-3.hidden-xs.hidden-sm Custom built 123 | .description.col-md-3.hidden-xs.hidden-sm Product(or rental) 124 | .description.col-md-3.hidden-xs.hidden-sm Commodity/Utility 125 | #axis-y 126 | #arrow-x 127 | #arrow-y 128 | 129 | .col-lg-2#mapcreationassist(style="display:none") 130 | include ./mapeditor_includes/mapcreationassist.jade 131 | #menu.col-lg-2 132 | include ./mapeditor_includes/actionmenu.jade 133 | //- pure none sense, but this is proper formatting 134 | include mapeditor_includes/mapanalysis.jade 135 | include ./mapeditor_includes/nodedialog.jade 136 | script(src='/mapeditor.js') 137 | script(src='/related.js') 138 | -------------------------------------------------------------------------------- /client/mapeditor/1x1_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdaniel/wardleymapstool/4de82586494bdb4e8752622e04b3a7bd39978b21/client/mapeditor/1x1_transparent.png -------------------------------------------------------------------------------- /client/mapeditor/js/specs.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 2 | 3 | 4 | 5 | Insert title here 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /client/mapeditor2.jade: -------------------------------------------------------------------------------- 1 | //-Copyright 2015 Krzysztof Daniel and Scott Weinstein. 2 | //- 3 | //-Licensed under the Apache License, Version 2.0 (the "License"); 4 | //-you may not use this file except in compliance with the License. 5 | //-You may obtain a copy of the License at 6 | //- 7 | //- http://www.apache.org/licenses/LICENSE-2.0 8 | //- 9 | //-Unless required by applicable law or agreed to in writing, software 10 | //-distributed under the License is distributed on an "AS IS" BASIS, 11 | //-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | //-See the License for the specific language governing permissions and 13 | //-limitations under the License. 14 | doctype html 15 | html 16 | head 17 | link(rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" integrity="sha512-dTfge/zgoMYpP7QbHy4gWMEGsbsdZeCXz7irItjcC3sPUFtf0kuFbDz/ixG7ArTxmDjLXDmezHubeNikyKGVyQ==" crossorigin="anonymous") 18 | link(rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css" integrity="sha384-aUGj/X2zp5rLCbBxumKTCw2Z50WgIr1vs/PFN4praOTvYXWlVyh2UtNUU0KAUhAX" crossorigin="anonymous") 19 | 20 | script(src="/mapeditor/js/jsPlumb-2.0.6-min.js") 21 | link(rel='shortcut icon', href='/favicon.ico', type="favicon.ico") 22 | link(rel="icon" type="image/png" sizes="192x192" href="/android-icon-192x192.png") 23 | link(rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png") 24 | link(rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png") 25 | link(rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png") 26 | body 27 | #editorplaceholder 28 | script(src="/mapeditor/js/vendors.js") 29 | script(src="/mapeditor/js/main.js") 30 | -------------------------------------------------------------------------------- /client/mapeditor_includes/actionmenu.jade: -------------------------------------------------------------------------------- 1 | #actionMenu(style="display:none") 2 | p Label: 3 | a#actionlabel(href="#" data-type="text" data-title="Action label") 4 | p Note: 5 | a#actionnote(href="#" data-type="textarea" data-title="Action Note") -------------------------------------------------------------------------------- /client/mapeditor_includes/mapanalysis.jade: -------------------------------------------------------------------------------- 1 | .modal.fade#analysisModal(tabindex="-1" role="dialog" aria-labelledby="analysisModal" aria-hidden="true") 2 | .modal-dialog 3 | .modal-content 4 | .modal-header 5 | button.close(type="button" data-dismiss="modal" aria-hidden="true") × 6 | h4.modal-title Automated Analysis 7 | .modal-body#analysisModalBody Please wait... 8 | .modal-footer 9 | button.btn.btn-primary.btn-default(type="button" data-dismiss="modal") Close 10 | script. 11 | $('body').on('hidden.bs.modal', '#analysisModal', function () { $(this).removeData('bs.modal'); $('#analysisModalBody)').empty();}); -------------------------------------------------------------------------------- /client/mapeditor_includes/mapcreationassist.jade: -------------------------------------------------------------------------------- 1 | ul.list-group 2 | a.list-group-item.disabled Work progress: 3 | .progress 4 | .progress-bar#progressprogressbar(role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="min-width: 2em;") 5 | li#mapping-progress-0.list-group-item 6 | p.progress-title Define user needs. 7 | a(href="/help/assistuserneeds" data-toggle="modal" data-target="#mapcreationassist0") 8 | span.glyphicon.glyphicon-question-sign 9 | p#mapping-progress-group-0 10 | | Just click anywhere on the map. 11 | | Each component will be marked as a user need automatically in this step. 12 | |
13 | | Click on the label to change component name. 14 | |
15 | button.btn.btn-default.btn-xs(type="button" onclick="progressHelper.advance()" disabled="true") Mark complete 16 | span.glyphicon.glyphicon-check 17 | li#mapping-progress-1.list-group-item 18 | p.progress-title Define value chain. 19 | a(href="/help/assistvaluechain" data-toggle="modal" data-target="#mapcreationassist1") 20 | span.glyphicon.glyphicon-question-sign 21 | p#mapping-progress-group-1 22 | | Click on a component to focus it. Click on a map to create a new component. 23 | |
24 | | Drag a dependency from the dark circle. 25 | |
26 | | Drop the dependency on any component to establish a relationship. 27 | |
28 | | Remember about marking external components (those that you buy). 29 | |
30 | button.btn.btn-default.btn-xs(type="button" onclick="progressHelper.advance()") Mark complete 31 | span.glyphicon.glyphicon-check 32 | li#mapping-progress-2.list-group-item 33 | p.progress-title Determine evolution phases. 34 | a(href="/help/assistevolution" data-toggle="modal" data-target="#mapcreationassist2") 35 | span.glyphicon.glyphicon-question-sign 36 | p#mapping-progress-group-2 37 | | Drag to the left components that are new, novel, and you believe in them, but actually nobody is using them yet. 38 | |
39 | | Drag to the right components that are cost of doing business. 40 | |
41 | | Align transitional components. 42 | |
43 | button.btn.btn-default.btn-xs(type="button" onclick="progressHelper.advance()") Mark complete 44 | span.glyphicon.glyphicon-check 45 | li#mapping-progress-3.list-group-item 46 | p.progress-title More to come here soon... 47 | p#mapping-progress-group-3 This is just being implemented. Stay tuned! 48 | 49 | .modal.fade#mapcreationassist0(tabindex="-1" role="dialog" aria-labelledby="mapcreationassist0" aria-hidden="true") 50 | .modal-dialog 51 | .modal-content 52 | .modal-header 53 | button.close(type="button" data-dismiss="modal" aria-hidden="true") × 54 | h4.modal-title Help 55 | .modal-body User needs help coming soon. 56 | .modal-footer 57 | button.btn.btn-primary.btn-default(type="button" data-dismiss="modal") Close 58 | 59 | .modal.fade#mapcreationassist1(tabindex="-1" role="dialog" aria-labelledby="mapcreationassist1" aria-hidden="true") 60 | .modal-dialog 61 | .modal-content 62 | .modal-header 63 | button.close(type="button" data-dismiss="modal" aria-hidden="true") × 64 | h4.modal-title Help 65 | .modal-body Value chain help coming soon. 66 | .modal-footer 67 | button.btn.btn-primary.btn-default(type="button" data-dismiss="modal") Close 68 | 69 | .modal.fade#mapcreationassist2(tabindex="-1" role="dialog" aria-labelledby="mapcreationassist2" aria-hidden="true") 70 | .modal-dialog 71 | .modal-content 72 | .modal-header 73 | button.close(type="button" data-dismiss="modal" aria-hidden="true") × 74 | h4.modal-title Help 75 | .modal-body Evolution stages help coming soon. 76 | .modal-footer 77 | button.btn.btn-primary.btn-default(type="button" data-dismiss="modal") Close -------------------------------------------------------------------------------- /client/mapeditor_includes/nodedialog.jade: -------------------------------------------------------------------------------- 1 | .modal.fade#nodemenudialog(tabindex="-1" role="dialog" aria-labelledby="nodemenudialog" aria-hidden="true") 2 | .modal-dialog 3 | .modal-content 4 | .modal-header 5 | button.close(type="button" data-dismiss="modal" aria-hidden="true") × 6 | h4.modal-title Component data 7 | .modal-body 8 | #nodeMenu(style="display:none").form-horizontal 9 | .form-group 10 | label.col-sm-4.control-label(for="node_title") Component name 11 | .col-sm-8 12 | a#node_title(data-type='text' href="#" data-title='Component Name') 13 | .form-group 14 | label.col-sm-4.control-label(for="node_userneed") User need 15 | .col-sm-8 16 | a#node_userneed(data-type='select' href="#" data-title='User need') 17 | .form-group 18 | label.col-sm-4.control-label(for="node_external") Control 19 | .col-sm-8 20 | a#node_external(data-type='select' href="#" data-title='Outsourcing') 21 | .modal-footer 22 | button.btn.btn-primary.btn-default(type="button" data-dismiss="modal") Close 23 | -------------------------------------------------------------------------------- /client/mapeditor_includes/preferences.jade: -------------------------------------------------------------------------------- 1 | li 2 | .checkbox.navbar-btn 3 | label.btn#mapeditor-preference-clickcreatetooltip( 4 | data-placement="bottom" 5 | data-toggle="tooltip" 6 | title="Every click on a map creates a new node by default. You may disable this functionality if your map is mature and you do not expect many new nodes." 7 | ) 8 | input#mapeditor-preference-clickcreate( 9 | aria-pressed="false" 10 | type="checkbox" 11 | checked 12 | ) 13 | |Prevent accidental node creation 14 | -------------------------------------------------------------------------------- /client/progresshelper.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2015 Krzysztof Daniel 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License.*/ 14 | 15 | var ProgressHelper = function(){ 16 | var self = this; 17 | self.maxnumberofsteps = 4; 18 | self.maxstate = 100; 19 | self.progress = -1; 20 | self.refresh = function() { 21 | $.ajax({ 22 | type : 'GET', 23 | url : progressURL /* defined in mapeditor.jade */, 24 | dataType : 'json', 25 | success : function(data) { 26 | self.progress = data.progress; 27 | self.updateUI(data.progress, false); 28 | } 29 | }); 30 | }; 31 | 32 | self.advance = function(){ 33 | $.ajax({ 34 | type : 'PUT', 35 | url : progressURL /* defined in mapeditor.jade */, 36 | dataType : 'json', 37 | success : function(data) { 38 | self.progress = data.progress; 39 | self.updateUI(data.progress, true); 40 | } 41 | }); 42 | }; 43 | 44 | self.updateUI = function(progress,progressadvanced){ 45 | /*progress not tracked for this map*/ 46 | if(progress == -1) return; 47 | 48 | for(var i = 0; i < self.maxnumberofsteps - 1; i++){ 49 | var title = $('#mapping-progress-' + i +'> p.progress-title'); 50 | var group = $('#mapping-progress-group-' + i); 51 | if(i == progress){ 52 | title.css('text-decoration', ''); 53 | title.css('color', 'black'); 54 | title.css('font-weight', 'bold'); 55 | group.show(); 56 | } else { 57 | if(i < progress){ 58 | title.css('text-decoration', 'line-through'); 59 | title.css('color', 'silver'); 60 | } else { 61 | title.css('text-decoration', ''); 62 | title.css('color', 'black'); 63 | } 64 | title.css('font-weight', 'normal'); 65 | group.hide(); 66 | } 67 | } 68 | var progressvalue = progress *10; 69 | $('#progressprogressbar').attr('aria-valuenow', progressvalue).css('width', '' + progressvalue + '%'); 70 | // there is progress, show the assistant box 71 | if(progress > -1 && progress < self.maxnumberofsteps - 1){ 72 | $('#mapcreationassist').show(100); 73 | } 74 | if(progress >= self.maxnumberofsteps - 1 && progressadvanced){ 75 | $('#progressprogressbar').attr('aria-valuenow', progressvalue).css('width', '' + 100 + '%'); 76 | $('#mapcreationassist').hide(500); 77 | } 78 | // progress has not been changed, so the mapcreation assist is invisible, and even if it is, it should not be shown to the user. 79 | if(progress >= self.maxnumberofsteps - 1 && !progressadvanced){ 80 | $('#mapcreationassist').hide(); 81 | } 82 | // fr=irst two steps involve a lot of node creation, so enable create by click 83 | if(progress < 2){ 84 | $('#mapeditor-preference-clickcreate')[0].checked = false; 85 | } 86 | // disable create by click if progress was changed 87 | if(progress === 2 && progressadvanced){ 88 | $('#mapeditor-preference-clickcreate')[0].checked = true; 89 | } 90 | 91 | //defined in the mapeditor.jade 92 | if(map.nodes.length > 0){ 93 | self.makeAdvanceFromUserNeedAvailable(); 94 | } 95 | }; 96 | 97 | self.updateNodeAccordingToProgressState = function(node){ 98 | if(self.progress == 0){ 99 | node.setUserNeed(true); 100 | node.setText('user need'); 101 | self.makeAdvanceFromUserNeedAvailable(); 102 | } 103 | }; 104 | 105 | self.makeAdvanceFromUserNeedAvailable = function(){ 106 | $('#mapping-progress-group-' + self.progress +'> button').removeAttr('disabled'); 107 | }; 108 | 109 | self.limitEndpointsToShow = function(endpoints){ 110 | if(self.progress > 2 || self.progress == -1){ 111 | return endpoints; 112 | } 113 | return [endpoints[0]]; 114 | }; 115 | }; -------------------------------------------------------------------------------- /client/related.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2015 Krzysztof Daniel 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License.*/ 14 | 15 | var idsToUpdate = $('a.mapidtoupdate'); 16 | 17 | $.each(idsToUpdate, function(index, value){ 18 | var id = value.id; 19 | $.ajax({ 20 | type : 'GET', 21 | url : '/api/map/'+id, 22 | dataType : 'json', 23 | success : function(data) { 24 | var map = data; 25 | var mapname = data.history[data.history.length-1].name; 26 | $('#'+id).text(mapname); 27 | } 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /client/views/change-password.jade: -------------------------------------------------------------------------------- 1 | extends base 2 | 3 | block vars 4 | - var title = 'Change Your Password' 5 | - var description = 'Change your password here.' 6 | - var bodytag = 'login' 7 | 8 | block body 9 | .container.custom-container 10 | .va-wrapper 11 | .view.login-view.container 12 | .box.row 13 | .email-password-area.col-xs-12.large.col-sm-12 14 | .header 15 | span. 16 | Change Your Password 17 | p. 18 | Enter your new account password below. Once confirmed, 19 | you'll be logged into your account and your new password will be 20 | active. 21 | 22 | if error 23 | .alert.alert-danger.bad-login 24 | p #{error} 25 | 26 | if formErrors 27 | .alert.alert-danger.bad-login 28 | each error in formErrors 29 | p #{error.error} 30 | 31 | form.login-form.form-horizontal(method='post', role='form') 32 | input(name='_csrf', type='hidden', value=csrfToken) 33 | 34 | .form-group.group-password 35 | label.col-sm-4 Password 36 | 37 | div.col-sm-8 38 | input.form-control(placeholder='Password', required=true, name='password', type='password') 39 | 40 | .form-group.group-password 41 | label.col-sm-4 Password (again) 42 | 43 | div.col-sm-8 44 | input.form-control(placeholder='Password (again)', required=true, name='passwordAgain', type='password') 45 | 46 | div 47 | button.login.btn.btn-login.btn-sp-green(type='submit') Submit 48 | -------------------------------------------------------------------------------- /client/views/facebook_login_failed.jade: -------------------------------------------------------------------------------- 1 | extends base 2 | 3 | block vars 4 | - var title = 'Facebook Login Failed' 5 | - var description = "Your Facebook login attempt has failed!" 6 | - var bodytag = 'login' 7 | 8 | block body 9 | .container.custom-container 10 | .va-wrapper 11 | .view.login-view.container 12 | .box.row 13 | .email-password-area.col-xs-12.large.col-sm-12 14 | .header 15 | span. 16 | Facebook Login Failed 17 | p. 18 | Your Facebook login attempt has failed. This might happen for 19 | several reasons: your verification token might be expired, it 20 | might be invalid, or we may just be having issues right now! 21 | Please try again, and if you're still having problems, contact 22 | the site administrator for help! 23 | -------------------------------------------------------------------------------- /client/views/facebook_login_form.jade: -------------------------------------------------------------------------------- 1 | button.btn.btn-social.btn-facebook(onclick='facebookLogin()') Facebook 2 | script(type='text/javascript'). 3 | function facebookLogin() { 4 | var FB = window.FB; 5 | var facebookScopes = '#{stormpathConfig.socialProviders.facebook.scopes}'; 6 | var scopes = []; 7 | 8 | if (facebookScopes.length) { 9 | scopes = facebookScopes.join(','); 10 | } else { 11 | scopes = ''; 12 | } 13 | 14 | FB.login(function (response) { 15 | if (response.status === 'connected') { 16 | var queryStr = window.location.search.replace('?', ''); 17 | // TODO make dynamic 18 | if (queryStr) { 19 | // Don't include any access_token parameters in 20 | // the query string as it will be added by us. 21 | queryStr = queryStr.replace(/(&?)access_token=([^&]*)/, ''); 22 | 23 | window.location.replace('/callbacks/facebook?' + queryStr + '&access_token=' + response.authResponse.accessToken); 24 | } else { 25 | window.location.replace('/callbacks/facebook?access_token=' + response.authResponse.accessToken); 26 | } 27 | } 28 | }, {scope: 'email' + (scopes ? ',' + scopes : '')}); 29 | } 30 | 31 | window.fbAsyncInit = function () { 32 | FB.init({ 33 | appId : '#{stormpathConfig.socialProviders.facebook.clientId}', 34 | cookie : true, 35 | xfbml : true, 36 | version : 'v2.3' 37 | }); 38 | }; 39 | 40 | (function (d, s, id){ 41 | var js, fjs = d.getElementsByTagName(s)[0]; 42 | if (d.getElementById(id)) {return;} 43 | js = d.createElement(s); js.id = id; 44 | js.src = "//connect.facebook.net/en_US/sdk.js"; 45 | fjs.parentNode.insertBefore(js, fjs); 46 | }(document, 'script', 'facebook-jssdk')); 47 | -------------------------------------------------------------------------------- /client/views/forgot-password.jade: -------------------------------------------------------------------------------- 1 | extends base 2 | 3 | block vars 4 | - var title = 'Forgot Your Password?' 5 | - var description = 'Forgot your password? No worries!' 6 | - var bodytag = 'login' 7 | 8 | block body 9 | .container.custom-container 10 | .va-wrapper 11 | .view.login-view.container 12 | if status==='invalid_sptoken' 13 | .row 14 | .alert.alert-warning.invalid-sp-token-warning 15 | p. 16 | The password reset link you tried to use is no longer valid. 17 | Please request a new link from the form below. 18 | .box.row 19 | .email-password-area.col-xs-12.large.col-sm-12 20 | .header 21 | 22 | span. 23 | Forgot your password? 24 | 25 | p. 26 | Enter your email address below to reset your password. You will 27 | be sent an email which you will need to open to continue. You may 28 | need to check your spam folder. 29 | 30 | if error 31 | .alert.alert-danger.bad-login 32 | p #{error} 33 | 34 | if formErrors 35 | .alert.alert-danger.bad-login 36 | each error in formErrors 37 | p #{error.error} 38 | 39 | form.login-form.form-horizontal(method='post', role='form', action=stormpathConfig.web.forgotPassword.uri) 40 | input(name='_csrf', type='hidden', value=csrfToken) 41 | 42 | .form-group.group-email 43 | label.col-sm-4 Email 44 | 45 | div.col-sm-8 46 | input.form-control(placeholder='Email', required=true, name='email', type='text') 47 | 48 | div 49 | button.login.btn.btn-login.btn-sp-green(type='submit') Send Email 50 | 51 | if stormpathConfig.web.login.enabled 52 | a.forgot(href="#{stormpathConfig.web.login.uri}") Back to Log In 53 | -------------------------------------------------------------------------------- /client/views/google_login_failed.jade: -------------------------------------------------------------------------------- 1 | extends base 2 | 3 | block vars 4 | - var title = 'Google Login Failed' 5 | - var description = "Your Google login attempt has failed!" 6 | - var bodytag = 'login' 7 | 8 | block body 9 | .container.custom-container 10 | .va-wrapper 11 | .view.login-view.container 12 | .box.row 13 | .email-password-area.col-xs-12.large.col-sm-12 14 | .header 15 | span. 16 | Google Login Failed 17 | p. 18 | Your Google login attempt has failed. This might happen for 19 | several reasons: your verification token might be expired, it 20 | might be invalid, or we may just be having issues right now! 21 | Please try again, and if you're still having problems, contact 22 | the site administrator for help! 23 | -------------------------------------------------------------------------------- /client/views/google_login_form.jade: -------------------------------------------------------------------------------- 1 | button.btn.btn-social.btn-google(onclick='googleLogin()') Google 2 | script(type='text/javascript'). 3 | /** 4 | * Get the value of a querystring 5 | * @param {String} field The field to get the value of 6 | * @param {String} url The URL to get the value from (optional) 7 | * @return {String} The field value 8 | */ 9 | 10 | function googleLogin() { 11 | var clientId = '#{stormpathConfig.socialProviders.google.clientId}'; 12 | var googleScopes = '#{stormpathConfig.socialProviders.google.scopes}'; 13 | var hd = '#{stormpathConfig.socialProviders.google.hd}'; 14 | var scopes = ''; 15 | 16 | if (googleScopes.length) { 17 | scopes = '+' + googleScopes.split(',').join('+'); 18 | } 19 | 20 | var finalUrl = 'https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=' + 21 | clientId + 22 | '&scope=email' + scopes + 23 | '&include_granted_scopes=true&redirect_uri=' + 24 | '#{url}#{stormpathConfig.socialProviders.google.callbackUri}'; 25 | 26 | if (hd) { 27 | finalUrl = finalUrl + '&hd=' + hd; 28 | } 29 | 30 | window.location = finalUrl; 31 | } 32 | -------------------------------------------------------------------------------- /client/views/id_site_verification_failed.jade: -------------------------------------------------------------------------------- 1 | extends base 2 | 3 | block vars 4 | - var title = 'Verification Failed' 5 | - var description = "Your verification has failed!" 6 | - var bodytag = 'login' 7 | 8 | block body 9 | .container.custom-container 10 | .va-wrapper 11 | .view.login-view.container 12 | .box.row 13 | .email-password-area.col-xs-12.large.col-sm-12 14 | .header 15 | span. 16 | Verification Failed 17 | p. 18 | Your account verification has failed. This might happen for 19 | several reasons: your verification token might be expired, it 20 | might have already been used, or we may just be having issues 21 | right now! Please try again, and if you're still having 22 | problems, contact the site administrator for help! 23 | -------------------------------------------------------------------------------- /client/views/linkedin_login_form.jade: -------------------------------------------------------------------------------- 1 | - var linkedInProvider = stormpathConfig.socialProviders.linkedin 2 | 3 | button.btn.btn-social.btn-linkedin(onclick='linkedinLogin()') LinkedIn 4 | script(type='text/javascript'). 5 | function buildUrl(baseUrl, queryString) { 6 | var result = baseUrl; 7 | 8 | if (queryString) { 9 | var serializedQueryString = ''; 10 | 11 | for (var key in queryString) { 12 | var value = queryString[key]; 13 | 14 | if (serializedQueryString.length) { 15 | serializedQueryString += '&'; 16 | } 17 | 18 | // Don't include any access_token parameters in 19 | // the query string as it will be added by LinkedIn. 20 | if (key === 'access_token') { 21 | continue; 22 | } 23 | 24 | serializedQueryString += key + '=' + encodeURIComponent(value); 25 | } 26 | 27 | result += '?' + serializedQueryString; 28 | } 29 | 30 | return result; 31 | } 32 | 33 | function linkedinLogin() { 34 | var oauthStateToken = '#{oauthStateToken}'; 35 | var authorizationUrl = 'https://www.linkedin.com/uas/oauth2/authorization'; 36 | 37 | var clientId = '#{linkedInProvider.clientId}'; 38 | var redirectUri = '#{url}#{linkedInProvider.callbackUri}'; 39 | 40 | var scopes = ['r_basicprofile', 'r_emailaddress']; 41 | var providerScopes = '#{linkedInProvider.scopes}'.split(','); 42 | 43 | providerScopes.forEach(function (scope) { 44 | if (scope.length && scopes.indexOf(scope) !== -1) { 45 | scopes.push(scope); 46 | } 47 | }); 48 | 49 | window.location = buildUrl(authorizationUrl, { 50 | response_type: 'code', 51 | client_id: clientId, 52 | scope: scopes.join(' '), 53 | redirect_uri: redirectUri, 54 | state: oauthStateToken 55 | }); 56 | } 57 | -------------------------------------------------------------------------------- /client/views/login.jade: -------------------------------------------------------------------------------- 1 | extends base 2 | 3 | block vars 4 | - var title = 'Log In' 5 | - var description = 'Log into your account!' 6 | - var bodytag = 'login' 7 | - var socialProviders = stormpathConfig.socialProviders 8 | - var registerFields = stormpathConfig.web.register.fields 9 | 10 | block body 11 | 12 | .container.custom-container 13 | .va-wrapper 14 | .view.login-view.container 15 | if status 16 | .box.row 17 | .email-password-area.col-xs-12.large.col-sm-12 18 | .header 19 | if status === 'unverified' 20 | span. 21 | Your account verification email has been sent! 22 | p. 23 | Before you can log into your account, you need to activate your 24 | account by clicking the link we sent to your inbox. 25 | p. 26 | Didn't get the email? Click Here. 27 | br 28 | if status === 'verified' 29 | span. 30 | Your Account Has Been Verified 31 | p. 32 | You may now login. 33 | if status === 'created' 34 | span. 35 | Your Account Has Been Created 36 | p. 37 | You may now login. 38 | if status === 'reset' 39 | span. 40 | Password Reset Successfuly 41 | p. 42 | You can now login with your new password. 43 | br 44 | if status === 'forgot' 45 | span. 46 | Password Reset Requested 47 | p. 48 | If an account exists for the email provided, you will 49 | receive an email shortly. 50 | br 51 | .box.row 52 | if hasSocialProviders 53 | - var cls = 'small col-sm-8' 54 | else 55 | - var cls = 'large col-sm-12' 56 | 57 | div(class='email-password-area col-xs-12 #{cls}') 58 | .header 59 | if stormpathConfig.web.register.enabled 60 | span. 61 | Log In or Create Account 62 | else 63 | span. 64 | Log In 65 | 66 | if error 67 | .alert.alert-danger.bad-login 68 | p #{error} 69 | 70 | if formErrors 71 | .alert.alert-danger.bad-login 72 | each error in formErrors 73 | p #{error.error} 74 | 75 | form.login-form.form-horizontal(method='post', role='form',action=formActionUri) 76 | input(name='_csrf', type='hidden', value=csrfToken) 77 | 78 | .form-group.group-email 79 | if hasSocialProviders 80 | - var cls = 'col-sm-12' 81 | else 82 | - var cls = 'col-sm-4' 83 | 84 | if registerFields.username && registerFields.username.enabled 85 | label(class='#{cls}') Username or Email 86 | else 87 | label(class='#{cls}') Email 88 | 89 | if hasSocialProviders 90 | - var cls = 'col-sm-12' 91 | else 92 | - var cls = 'col-sm-8' 93 | 94 | div(class='#{cls}') 95 | - var value = form.data ? form.data.login : ''; 96 | if registerFields.username && registerFields.username.enabled 97 | input.form-control(autofocus='true', placeholder='Username or Email', required=true, name='login', type='text', value=value) 98 | else 99 | input.form-control(autofocus='true', placeholder='Email', required=true, name='login', type='text', value=value) 100 | 101 | if hasSocialProviders 102 | - var cls = 'col-sm-12' 103 | else 104 | - var cls = 'col-sm-4' 105 | 106 | .form-group.group-password 107 | label(class='#{cls}') Password 108 | 109 | if hasSocialProviders 110 | - var cls = 'col-sm-12' 111 | else 112 | - var cls = 'col-sm-8' 113 | 114 | div(class='#{cls}') 115 | input.form-control(placeholder='Password', required=true, type='password', name='password') 116 | 117 | div 118 | button.login.btn.btn-login.btn-sp-green(type='submit') Log In 119 | 120 | if hasSocialProviders 121 | .social-area.col-xs-12.col-sm-4 122 | .header   123 | label Easy 1-click login: 124 | if socialProviders.facebook && socialProviders.facebook.enabled 125 | include facebook_login_form.jade 126 | if socialProviders.google && socialProviders.google.enabled 127 | include google_login_form.jade 128 | if socialProviders.linkedin && socialProviders.linkedin.enabled 129 | include linkedin_login_form.jade 130 | 131 | if toc 132 | .row 133 | .col-sm-12.small(style="margin: 0 auto;") By logging on and using this service, you accept  134 | a.small(href="#{toc}" target="_blank") Terms & Conditions 135 | span  (last update #{tocupdate}). 136 | 137 | if stormpathConfig.web.verifyEmail.enabled 138 | a.forgot(style="float:left", href="#{stormpathConfig.web.verifyEmail.uri}") Resend Verification Email? 139 | if stormpathConfig.web.forgotPassword.enabled 140 | a.forgot(style="float:right", href="#{stormpathConfig.web.forgotPassword.uri}") Forgot Password? 141 | -------------------------------------------------------------------------------- /client/views/register.jade: -------------------------------------------------------------------------------- 1 | extends base 2 | 3 | block vars 4 | - var title = 'Create an Account' 5 | - var description = 'Create a new account.' 6 | - var bodytag = 'register' 7 | 8 | block body 9 | 10 | .container.custom-container 11 | .va-wrapper 12 | .view.registration-view.container 13 | .box.row 14 | .col-sm-12 15 | .header 16 | span Create Account 17 | 18 | if errors 19 | .alert.alert-danger 20 | each error in errors 21 | p #{error.message} 22 | 23 | form.registration-form.form-horizontal.sp-form(method='post', role='form') 24 | each field in formModel.fields 25 | div(form-group='true', class='form-group group-#{field.name}') 26 | label.col-sm-4 #{field.label} 27 | .col-sm-8 28 | - var value = form ? form[field.name] : ''; 29 | input.form-control(placeholder=field.placeholder, name=field.name, value=value, required=field.required, type=field.type) 30 | div(form-group='true', class='form-group group-tc') 31 | label.col-sm-8 I hereby accept  32 | a(href="#{toc}" target="_blank") Terms & Conditions 33 | .col-sm-4 34 | input.form-control(placeholder="I hereby accept Terms & Conditions", name='tc', value='false', required='true', type='checkbox') 35 | button.btn.btn-register.btn-sp-green(type='submit') Create Account 36 | 37 | a.to-login(href=stormpathConfig.web.login.uri) Back to Log In 38 | -------------------------------------------------------------------------------- /client/views/unauthorized.jade: -------------------------------------------------------------------------------- 1 | extends base 2 | 3 | block vars 4 | - var title = 'Unauthorized' 5 | - var description = "You aren't authorized to view this page!" 6 | - var bodytag = 'login' 7 | 8 | block body 9 | .container.custom-container 10 | .va-wrapper 11 | .view.login-view.container 12 | .box.row 13 | .email-password-area.col-xs-12.large.col-sm-12 14 | .header 15 | span. 16 | Unauthorized 17 | p. 18 | You are not authorized to view this page. Please check with the 19 | site administrator for help! 20 | -------------------------------------------------------------------------------- /client/views/verification_complete.jade: -------------------------------------------------------------------------------- 1 | extends base 2 | 3 | block vars 4 | - var title = 'Account Verification Complete' 5 | - var description = 'You have successfully verified your account!' 6 | - var bodytag = 'login' 7 | 8 | block head 9 | meta(http-equiv='refresh' content="5; url=#{app.get('stormpathRedirectUrl')}") 10 | 11 | block body 12 | .container.custom-container 13 | .va-wrapper 14 | .view.login-view.container 15 | .box.row 16 | .email-password-area.col-xs-12.large.col-sm-12 17 | .header 18 | span. 19 | Account Verification Complete! 20 | p. 21 | Your account has been verified, and you have been logged into 22 | your account. You will be redirected back to the site in 5 23 | seconds. 24 | -------------------------------------------------------------------------------- /client/views/verification_email_sent.jade: -------------------------------------------------------------------------------- 1 | extends base 2 | 3 | block vars 4 | - var title = 'Account Verification Email Sent' 5 | - var description = 'Your account verification email has been sent!' 6 | - var bodytag = 'login' 7 | 8 | block body 9 | .container.custom-container 10 | .va-wrapper 11 | .view.login-view.container 12 | .box.row 13 | .email-password-area.col-xs-12.large.col-sm-12 14 | .header 15 | span. 16 | Your account verification email has been sent! 17 | p. 18 | Before you can log into your account, you need to activate your 19 | account by clicking the link we sent to you at your email 20 | address: #{email}. 21 | p. 22 | Please check your inbox to continue. 23 | -------------------------------------------------------------------------------- /client/views/verification_failed.jade: -------------------------------------------------------------------------------- 1 | extends base 2 | 3 | block vars 4 | - var title = 'Verification Failed' 5 | - var description = "Your verification token is invalid!" 6 | - var bodytag = 'login' 7 | 8 | block body 9 | .container.custom-container 10 | .va-wrapper 11 | .view.login-view.container 12 | .box.row 13 | .email-password-area.col-xs-12.large.col-sm-12 14 | .header 15 | span. 16 | Verification Failed 17 | p. 18 | Your account verification has failed. This might happen for 19 | several reasons: your verification token might be expired, it 20 | might have already been used, or we may just be having issues 21 | right now! Please try again, and if you're still having 22 | problems, contact the site administrator for help! 23 | -------------------------------------------------------------------------------- /client/views/verify.jade: -------------------------------------------------------------------------------- 1 | extends base 2 | 3 | block vars 4 | - var title = 'Resend Account Verification Email?' 5 | - var description = 'Didn\'t receive your account verification email? No worries!' 6 | - var bodytag = 'login' 7 | 8 | block body 9 | .container.custom-container 10 | .va-wrapper 11 | .view.login-view.container 12 | if invalid_sp_token 13 | .row 14 | .alert.alert-warning.invalid-sp-token-warning 15 | p. 16 | This verification link is no longer valid. Please request 17 | a new link from the form below. 18 | 19 | .box.row 20 | 21 | if !sptoken 22 | .email-password-area.col-xs-12.large.col-sm-12 23 | if invalid_sp_token 24 | p   25 | else 26 | .header 27 | span. 28 | Didn't receive your account verification email? 29 | p. 30 | Enter your email address below and we'll resend your account 31 | verification email. You will be sent an email which you will 32 | need to open to continue. You may need to check your spam 33 | folder. 34 | 35 | if error 36 | .alert.alert-danger.bad-login.form-error 37 | p #{error} 38 | 39 | if formErrors 40 | .alert.alert-danger.bad-login 41 | each error in formErrors 42 | p #{error.error} 43 | 44 | p   45 | 46 | form.login-form.form-horizontal(method='post', role='form') 47 | input(name='_csrf', type='hidden', value=csrfToken) 48 | 49 | .form-group.group-email 50 | label.col-sm-4 Email 51 | 52 | div.col-sm-8 53 | input.form-control(placeholder='Email', required='true', name='email', type='text') 54 | 55 | div 56 | button.login.btn.btn-login.btn-sp-green(type='submit') Submit 57 | 58 | if stormpathConfig.web.login.enabled 59 | a.forgot(href="#{stormpathConfig.web.login.uri}") Back to Log In 60 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var source = require('vinyl-source-stream'); // Used to stream bundle for further handling 3 | var browserify = require('browserify'); 4 | var watchify = require('watchify'); 5 | var reactify = require('reactify'); 6 | var gulpif = require('gulp-if'); 7 | var uglify = require('gulp-uglify'); 8 | var streamify = require('gulp-streamify'); 9 | var notify = require('gulp-notify'); 10 | var concat = require('gulp-concat'); 11 | var cssmin = require('gulp-cssmin'); 12 | var gutil = require('gulp-util'); 13 | var shell = require('gulp-shell'); 14 | var glob = require('glob'); 15 | var livereload = require('gulp-livereload'); 16 | var jasminePhantomJs = require('gulp-jasmine2-phantomjs'); 17 | var connect = require('gulp-connect'); 18 | 19 | // External dependencies you do not want to rebundle while developing, 20 | // but include in your application deployment 21 | var dependencies = [ 22 | 'react', 23 | 'react-dom', 24 | 'react/addons', 25 | 'react-bootstrap' 26 | ]; 27 | 28 | var browserifyTask = function (options) { 29 | 30 | // Our app bundler 31 | var appBundler = browserify({ 32 | entries: [options.src], // Only need initial file, browserify finds the rest 33 | transform: [reactify], // We want to convert JSX to normal javascript 34 | debug: options.development, // Gives us sourcemapping 35 | cache: {}, packageCache: {}, fullPaths: options.development // Requirement of watchify 36 | }); 37 | 38 | // We set our dependencies as externals on our app bundler when developing 39 | (options.development ? dependencies : []).forEach(function (dep) { 40 | appBundler.external(dep); 41 | }); 42 | 43 | // The rebundle process 44 | var rebundle = function () { 45 | var start = Date.now(); 46 | console.log('Building APP bundle'); 47 | appBundler.bundle() 48 | .on('error', gutil.log) 49 | .pipe(source('main.js')) 50 | .pipe(gulpif(!options.development, streamify(uglify()))) 51 | .pipe(gulp.dest(options.dest)) 52 | .pipe(gulpif(options.development, livereload())) 53 | .pipe(notify(function () { 54 | console.log('APP bundle built in ' + (Date.now() - start) + 'ms'); 55 | })); 56 | }; 57 | 58 | // Fire up Watchify when developing 59 | if (options.development) { 60 | appBundler = watchify(appBundler); 61 | appBundler.on('update', rebundle); 62 | } 63 | 64 | rebundle(); 65 | 66 | // We create a separate bundle for our dependencies as they 67 | // should not rebundle on file changes. This only happens when 68 | // we develop. When deploying the dependencies will be included 69 | // in the application bundle 70 | if (options.development) { 71 | // 72 | // var testFiles = glob.sync('./specs/**/*-spec.js'); 73 | // var testBundler = browserify({ 74 | // entries: testFiles, 75 | // debug: true, // Gives us sourcemapping 76 | // transform: [reactify], 77 | // cache: {}, packageCache: {}, fullPaths: true // Requirement of watchify 78 | // }); 79 | // 80 | // dependencies.forEach(function (dep) { 81 | // testBundler.external(dep); 82 | // }); 83 | // 84 | // var rebundleTests = function () { 85 | // var start = Date.now(); 86 | // console.log('Building TEST bundle'); 87 | // testBundler.bundle() 88 | // .on('error', gutil.log) 89 | // .pipe(source('specs.js')) 90 | // .pipe(gulp.dest(options.dest)) 91 | // .pipe(livereload()) 92 | // .pipe(notify(function () { 93 | // console.log('TEST bundle built in ' + (Date.now() - start) + 'ms'); 94 | // })); 95 | // }; 96 | // 97 | // testBundler = watchify(testBundler); 98 | // testBundler.on('update', rebundleTests); 99 | // rebundleTests(); 100 | 101 | // Remove react-addons when deploying, as it is only for 102 | // testing 103 | if (!options.development) { 104 | dependencies.splice(dependencies.indexOf('react-addons'), 1); 105 | } 106 | 107 | var vendorsBundler = browserify({ 108 | debug: true, 109 | require: dependencies 110 | }); 111 | 112 | // Run the vendor bundle 113 | var start = new Date(); 114 | console.log('Building VENDORS bundle'); 115 | vendorsBundler.bundle() 116 | .on('error', gutil.log) 117 | .pipe(source('vendors.js')) 118 | .pipe(gulpif(!options.development, streamify(uglify()))) 119 | .pipe(gulp.dest(options.dest)) 120 | .pipe(notify(function () { 121 | console.log('VENDORS bundle built in ' + (Date.now() - start) + 'ms'); 122 | })); 123 | 124 | } 125 | 126 | }; 127 | 128 | var cssTask = function (options) { 129 | if (options.development) { 130 | var run = function () { 131 | console.log(arguments); 132 | var start = new Date(); 133 | console.log('Building CSS bundle'); 134 | gulp.src(options.src) 135 | .pipe(concat('main.css')) 136 | .pipe(gulp.dest(options.dest)) 137 | .pipe(notify(function () { 138 | console.log('CSS bundle built in ' + (Date.now() - start) + 'ms'); 139 | })); 140 | }; 141 | run(); 142 | gulp.watch(options.src, run); 143 | } else { 144 | gulp.src(options.src) 145 | .pipe(concat('main.css')) 146 | .pipe(cssmin()) 147 | .pipe(gulp.dest(options.dest)); 148 | } 149 | }; 150 | 151 | // Starts our development workflow 152 | gulp.task('default', function () { 153 | // livereload.listen(); 154 | 155 | browserifyTask({ 156 | development: true, 157 | src: './client-v2-jsx/main.js', 158 | dest: './client/mapeditor/js/' 159 | }); 160 | 161 | // cssTask({ 162 | // development: true, 163 | // src: './styles/**/*.css', 164 | // dest: './build' 165 | // }); 166 | // 167 | // connect.server({ 168 | // root: 'build/', 169 | // port: 8889 170 | // }); 171 | 172 | }); 173 | 174 | //gulp.task('deploy', function () { 175 | // 176 | // browserifyTask({ 177 | // development: false, 178 | // src: './app/main.js', 179 | // dest: './dist' 180 | // }); 181 | // 182 | // cssTask({ 183 | // development: false, 184 | // src: './styles/**/*.css', 185 | // dest: './dist' 186 | // }); 187 | // 188 | //}); 189 | // 190 | //gulp.task('test', function () { 191 | // return gulp.src('./build/testrunner-phantomjs.html').pipe(jasminePhantomJs()); 192 | //}); 193 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wardleymaptool", 3 | "version": "1.1.0", 4 | "description": "A tool for creating Wardley Maps", 5 | "keywords": [ 6 | "OpenShift", 7 | "Node.js", 8 | "application", 9 | "openshift" 10 | ], 11 | "author": { 12 | "name": "Krzysztof Daniel", 13 | "email": "krzysztof.daniel@gmail.com", 14 | "url": "http://www.openshift.com/" 15 | }, 16 | "homepage": "http://wardleymaps.com", 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/cdaniel/wardleymapstool" 20 | }, 21 | "engines": { 22 | "node": ">= 0.6.0", 23 | "npm": ">= 1.0.0" 24 | }, 25 | "dependencies": { 26 | "d3": "3.5.16", 27 | "express": "4.13.4", 28 | "jade": "^1.9.2", 29 | "jsdom": "3.1.2", 30 | "log4js": "^0.6.31", 31 | "mongojs": "^2.1.0", 32 | "underscore": "^1.8.2", 33 | "xmldom": "0.1.19", 34 | "cookie-parser": "~1.4.1", 35 | "express-session": "~1.13.0", 36 | "body-parser": "~1.15.0", 37 | "multer": "~1.1.0", 38 | "morgan": "~1.7.0", 39 | "errorhandler": "~1.4.3", 40 | "phantom": "~0.8.0", 41 | "atob": "~2.0.0", 42 | "temporary": "0.0.8", 43 | "phantomjs": "~2.1.3", 44 | "q": "~1.4.1", 45 | "jsplumb": "2.0.6", 46 | "glob": "~7.0.0", 47 | "vinyl-source-stream": "~1.1.0", 48 | "gulp-streamify": "~1.0.2", 49 | "gulp-concat": "~2.6.0", 50 | "dev": "~0.1.3", 51 | "gulp-shell": "~0.5.1", 52 | "gulp-util": "~3.0.7", 53 | "gulp-jasmine2-phantomjs": "~0.2.0", 54 | "gulp-livereload": "~3.8.1", 55 | "gulp-cssmin": "~0.1.7", 56 | "gulp": "~3.9.0", 57 | "gulp-uglify": "~1.5.1", 58 | "gulp-if": "~2.0.0", 59 | "browserify": "~13.0.0", 60 | "watchify": "~3.7.0", 61 | "gulp-connect": "~3.1.0", 62 | "reactify": "~1.1.1", 63 | "gulp-notify": "~2.2.0", 64 | "react-dom": "~0.14.4", 65 | "react": "~0.14.7", 66 | "jquery": "~2.2.0", 67 | "keymirror": "~0.1.1", 68 | "flux": "~2.1.1", 69 | "object-assign": "~4.0.1", 70 | "events": "~1.1.0", 71 | "react-router": "~2.0.0-rc6", 72 | "urllite": "~0.5.0", 73 | "clipboard": "~1.5.5" 74 | }, 75 | "optionalDependencies": { 76 | "express-stormpath": "3.0.1", 77 | "mailchimp-api": "^2.0.7", 78 | "request": "^2.53.0" 79 | }, 80 | "devDependencies": { 81 | "mocha": "*", 82 | "should": "8.2.2", 83 | "sinon": "*", 84 | "cucumber": "~0.9.4", 85 | "zombie": "~2.5.1" 86 | }, 87 | "main": "server/server.js", 88 | "files": [ 89 | "client", 90 | "server", 91 | "Dockerfile", 92 | "LICENSE", 93 | "supervisord.conf" 94 | ], 95 | "license": "Apache-2.0", 96 | "scripts": { 97 | "build": "gulp build", 98 | "test": "mocha server-tests/tests_*", 99 | "start": "node server/server.js" 100 | }, 101 | "bin": "server/server.js" 102 | } 103 | -------------------------------------------------------------------------------- /server-tests/it_tests_mailchimp.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2015 by Krzysztof Daniel 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License.*/ 14 | 15 | var mailchimp = require('../server/mailchimp'); 16 | var should = require('should'); 17 | var sinon = require('sinon'); 18 | 19 | describe('Mailchimp', function() { 20 | 21 | describe('check integration', function() { 22 | 23 | it('subscribe', sinon.test(function(done) { 24 | mailchimp.exportToMailChimp('a', 'b', 'aaa@aa.a', done); 25 | })); 26 | 27 | it('check present', sinon.test(function(done) { 28 | mailchimp.isPresent('aaa@aa.a', function(found, error) { 29 | if (found) { 30 | done(); 31 | } else { 32 | done(error); 33 | } 34 | }); 35 | })); 36 | 37 | it('unsubscribe', sinon.test(function(done) { 38 | mailchimp.removeFromMailchimp('aaa@aa.a', done); 39 | })); 40 | 41 | it('check not present', sinon.test(function(done) { 42 | mailchimp.isPresent('aaa@aa.a', function(found, error) { 43 | if (!found && !error) { 44 | done(); 45 | } else { 46 | done(error); 47 | } 48 | }); 49 | })); 50 | }); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /server/analyzer.js: -------------------------------------------------------------------------------- 1 | //#!/bin/env node 2 | /* Copyright 2014 Krzysztof Daniel 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License.*/ 15 | 16 | 17 | function composeMissingComponentsMessage() { 18 | return { 19 | type : 'panel-warning', 20 | title : 'Missing components', 21 | message : 'Your map appears to be empty. I have nothing to analyze.' 22 | }; 23 | } 24 | function composeMissingNamesMessage() { 25 | return { 26 | type : 'panel-danger', 27 | title : 'Missing names', 28 | message : 'Fill all the component names first. I cannot give you feedback about anonymous components.' 29 | }; 30 | } 31 | function composePotentialDisruption(componentName) { 32 | var message = 'Your component \'' 33 | + componentName 34 | + '\' appears to be in a sweet spot. Consider delivering it as a utility product'; 35 | return { 36 | type : 'panel-success', 37 | title : 'Potential disruption', 38 | message : message 39 | }; 40 | } 41 | 42 | 43 | function composeBewareDisruption(componentName) { 44 | var message = 'Your component \'' 45 | + componentName 46 | + '\' appears to be in a sweet spot where delivering it as a utility product is possible, but you may suffer from inertia, and it is likely that YOU will be disrupted.'; 47 | return { 48 | type : 'panel-warning', 49 | title : 'Potential disruption', 50 | message : message 51 | }; 52 | } 53 | 54 | function composeLostChance(componentName) { 55 | var message = 'Your component \'' 56 | + componentName 57 | + '\' seems to be in a pretty advanced state but it was not offered to anyone although it should be. Consider abandoning it and adopting one of the market alternatives.' 58 | + 'There is a small chance your competition made a mistake and did not provide this as a utility product. If that is the case, consider going into that business right now.'; 59 | return { 60 | type : 'panel-info', 61 | title : 'Lost chance', 62 | message : message 63 | }; 64 | } 65 | 66 | function composeCreatePlayingField(componentName){ 67 | var message = 'Your rely on \'' 68 | + componentName 69 | + '\' which is pretty mature in evolution and should be delivered as a utility product soon.' 70 | + 'Consider looking for new suppliers as it may save you a lot of bucks.'; 71 | return { 72 | type : 'panel-success', 73 | title : 'Playing field creation opportunity', 74 | message : message 75 | }; 76 | } 77 | 78 | function analyse(map) { 79 | var result = []; 80 | if(map.history[0].nodes.length == 0){ 81 | result.push(composeMissingComponentsMessage()); 82 | console.error(result); 83 | return result; 84 | } 85 | 86 | var allnames = true; 87 | for(var i = 0; i < map.history[0].nodes.length; i++){ 88 | var currentNode = map.history[0].nodes[i]; 89 | currentNode.positionX = parseFloat(currentNode.positionX); 90 | currentNode.external = 'true' == currentNode.external ? true : false; 91 | currentNode.userneed = 'true' == currentNode.userneed ? true : false; 92 | 93 | if(currentNode.name == ""){ 94 | allnames = false; 95 | break; 96 | } 97 | // potential disruption 98 | if ((currentNode.positionX > 0.5) && (currentNode.positionX < 0.75) 99 | && !currentNode.external && !currentNode.userneed) { 100 | result.push(composePotentialDisruption(currentNode.name)); 101 | } 102 | 103 | if ((currentNode.positionX > 0.5) && (currentNode.positionX < 0.8) 104 | && !currentNode.external && currentNode.userneed) { 105 | result.push(composeBewareDisruption(currentNode.name)); 106 | } 107 | // reinvented wheel 108 | if ((currentNode.positionX > 0.75) 109 | && !currentNode.external && !currentNode.userneed) { 110 | result.push(composeLostChance(currentNode.name)); 111 | } 112 | 113 | // create playing field 114 | if ((currentNode.positionX > 0.5) && (currentNode.positionX < 0.75) 115 | && currentNode.external) { 116 | result.push(composeCreatePlayingField(currentNode.name)); 117 | } 118 | 119 | } 120 | if(!allnames){ 121 | result.push(composeMissingNamesMessage()); 122 | console.error(result); 123 | return result; 124 | } 125 | 126 | 127 | return result; 128 | } 129 | 130 | exports.analyse = analyse; -------------------------------------------------------------------------------- /server/config/googleauth.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2014 Krzysztof Daniel 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License.*/ 14 | 15 | /** 16 | * This module holds Google API Keys useful required to obtain permissions 17 | * from Google+. 18 | */ 19 | function googleauth() { 20 | 21 | this.getClientID = function (){ 22 | var result = process.env.WM_GOOGLE_CLIENT_ID || ""; 23 | if(result === ''){ 24 | console.log("please consider creating your own google keys!"); 25 | } 26 | return result; 27 | }; 28 | 29 | this.getClientSecret = function () { 30 | var result = process.env.WM_GOOGLE_CLIENT_SECRET || ""; 31 | if(result === ''){ 32 | console.log("please consider creating your own google keys!"); 33 | } 34 | return result; 35 | }; 36 | 37 | } 38 | 39 | var googleauth = new googleauth(); 40 | 41 | exports.googleauth = googleauth; -------------------------------------------------------------------------------- /server/config/mailchimpconfig.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2015 Krzysztof Daniel 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License.*/ 14 | 15 | var apiKey = process.env.WM_MAILCHIMP_API_KEY || ""; 16 | var listId = process.env.WM_MAILCHIMP_LIST_ID || ""; 17 | 18 | var enabled = (apiKey !== "") && (listId !== ""); 19 | 20 | console.log('mailchimp integration enabled:', enabled); 21 | 22 | exports.mailchimpconfig = { 23 | enabled : enabled, 24 | apiKey : apiKey, 25 | listId : listId 26 | }; -------------------------------------------------------------------------------- /server/config/mongodbdata.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2014 Krzysztof Daniel 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License.*/ 14 | 15 | 16 | 17 | /** 18 | * This module holds information about connection to mongo 19 | */ 20 | function mongodbdata() { 21 | 22 | this.getConnectionString = function (){ 23 | var result = "127.0.0.1:27017/wardleymaps"; 24 | 25 | // if OPENSHIFT env variables are present, use the available connection info: 26 | if(process.env.OPENSHIFT_MONGODB_DB_PASSWORD){ 27 | result = process.env.OPENSHIFT_MONGODB_DB_USERNAME + ":" + 28 | process.env.OPENSHIFT_MONGODB_DB_PASSWORD + "@" + 29 | process.env.OPENSHIFT_MONGODB_DB_HOST + ':' + 30 | process.env.OPENSHIFT_MONGODB_DB_PORT + '/' + 31 | process.env.OPENSHIFT_APP_NAME; 32 | } 33 | 34 | return result; 35 | }; 36 | 37 | } 38 | 39 | var mongodbdata = new mongodbdata(); 40 | 41 | exports.dbdata = mongodbdata; -------------------------------------------------------------------------------- /server/config/stormpathconfig.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2015 Krzysztof Daniel 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License.*/ 14 | 15 | /** 16 | * This module obtains key necessary to store user data in stormpath. 17 | * Listed variables should be set in the bash environment. 18 | * 19 | * TODO: make it configurable through config.json 20 | */ 21 | function stormpathconfig() { 22 | 23 | this.getApiKeyId = function (){ 24 | var result = process.env.WM_STORMPATH_API_KEY_ID || ""; 25 | if(result === ""){ 26 | console.error("please obtain stormpath account"); 27 | } 28 | return result; 29 | }; 30 | 31 | this.getApiKeySecret = function (){ 32 | var result = process.env.WM_STORMPATH_API_KEY_SECRET || ""; 33 | if(result === ""){ 34 | console.error("please obtain stormpath account"); 35 | } 36 | return result; 37 | }; 38 | 39 | this.getSecretKey = function (){ 40 | var result = process.env.WM_STORMPATH_SECRET_KEY || ""; 41 | if(result === ""){ 42 | console.error("please obtain stormpath account"); 43 | } 44 | return result; 45 | }; 46 | 47 | this.getApplication = function (){ 48 | var result = process.env.WM_STORMPATH_APPLICATION || ""; 49 | if(result === ""){ 50 | console.error("please obtain stormpath account"); 51 | } 52 | return result; 53 | }; 54 | } 55 | 56 | var stormpathconfig = new stormpathconfig(); 57 | 58 | exports.stormpathconfig = stormpathconfig; -------------------------------------------------------------------------------- /server/db.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2014, 2015 Krzysztof Daniel 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License.*/ 14 | 15 | 16 | module.exports = function (connectionString) { 17 | var logger = require('./util/log.js').getLogger("db"); 18 | logger.setLevel('ALL'); 19 | logger.debug('connecting to ', connectionString); 20 | 21 | var COLLECTIONS = ['users', 'maps', 'progress', 'maprelations', 'sharing']; 22 | var db = require('mongojs')(connectionString, COLLECTIONS); 23 | 24 | return db; 25 | }; 26 | -------------------------------------------------------------------------------- /server/mailchimp.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2015 Krzysztof Daniel 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License.*/ 14 | var config = require('./config/mailchimpconfig').mailchimpconfig; 15 | 16 | var api = require('mailchimp-api'); 17 | var mc_implementation = new api.Mailchimp(config.apiKey); 18 | var logger = require('./util/log.js').getLogger('mailchimp'); 19 | logger.setLevel('ALL'); 20 | 21 | function exportToMailChimp(firstName, lastName, email, next) { 22 | 23 | if (!config.enabled) { 24 | console.log('mailchimp account not configured'); 25 | return; 26 | } 27 | 28 | var merge_vars = { 29 | FNAME : firstName, 30 | LNAME : lastName 31 | }; 32 | /* 33 | * mc_implementation.lists.list({},function(data) { console.log(data); if 34 | * (next) next(); }, function(error) { console.log(error) if (next) next(); 35 | * }); 36 | */ 37 | mc_implementation.lists.subscribe({ 38 | id : config.listId, 39 | email : { 40 | email : email 41 | }, 42 | merge_vars : merge_vars, 43 | double_optin : 'false' 44 | }, function(data) { 45 | // gently ignore 46 | logger.debug(data); 47 | if (next) { 48 | next(); 49 | } 50 | }, function(error) { 51 | logger.error(error); 52 | if (next) { 53 | next(); 54 | } 55 | }); 56 | 57 | } 58 | 59 | function removeFromMailchimp(email, next) { 60 | 61 | if (!config.enabled) { 62 | console.log('mailchimp account not configured'); 63 | return; 64 | } 65 | 66 | mc_implementation.lists.unsubscribe({ 67 | id : config.listId, 68 | email : { 69 | email : email 70 | }, 71 | send_goodbye : 'false' 72 | }, function(data) { 73 | // gently ignore 74 | logger.debug(data); 75 | if (next) { 76 | next(); 77 | } 78 | }, function(error) { 79 | logger.error(error); 80 | if (next) { 81 | next(); 82 | } 83 | }); 84 | } 85 | 86 | function isPresent(email, callback){ 87 | 88 | if (!config.enabled) { 89 | console.log('mailchimp account not configured'); 90 | return; 91 | } 92 | 93 | mc_implementation.lists.memberInfo({ 94 | id : config.listId, 95 | emails : [ 96 | {email : email} 97 | ], 98 | }, function(data) { 99 | // gently ignore 100 | callback(data.success_count === 1); 101 | }, function(error) { 102 | logger.error(error); 103 | callback(false,error); 104 | }); 105 | } 106 | 107 | exports.exportToMailChimp = exportToMailChimp; 108 | exports.removeFromMailchimp = removeFromMailchimp; 109 | exports.isPresent = isPresent; -------------------------------------------------------------------------------- /server/router/apirouter.js: -------------------------------------------------------------------------------- 1 | //#!/bin/env node 2 | /* Copyright 2015 Krzysztof Daniel 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License.*/ 15 | 16 | module.exports = function(maps, exportmap){ 17 | var module = {}; 18 | 19 | 20 | module.router = require('express').Router(); 21 | 22 | //create a map 23 | module.router.post('/map/' , function(req, res) { 24 | maps.createNewMap(req, res); 25 | }); 26 | 27 | //create a map 28 | module.router.post('/map/partial/:mapid' , function(req, res) { 29 | maps.partialMapUpdate(req, res, req.params.mapid); 30 | }); 31 | 32 | //clone a map 33 | module.router.get('/map/clone/:mapid' , function(req, res) { 34 | maps.cloneMap(req, res, req.params.mapid); 35 | }); 36 | 37 | //get related maps 38 | module.router.get('/map/related/:mapid' , function(req, res) { 39 | maps.findRelatedMaps(req, res); 40 | }); 41 | 42 | // 2b. update a map 43 | module.router.post('/map/:mapid', function(req, res) { 44 | maps.updateMap(req, res, req.params.mapid); 45 | }); 46 | 47 | // 4. delete a map 48 | module.router.delete('/map/:mapid', function(req, res) { 49 | maps.deleteMap(req, res, req.params.mapid); 50 | }); 51 | 52 | // 4a. delete a map 53 | // workaround for the lack of get from the link 54 | // we operate with this as with a regular delete, and we perform 55 | // redirection to home. 56 | module.router.get('/map/delete/:mapid', function(req, res) { 57 | maps.deleteMap(req, res, req.params.mapid, "/"); 58 | }); 59 | 60 | module.router.get('/map/:mapid', function(req, res) { 61 | maps.getMap(req, req.params.mapid, function(arg){ 62 | if(arg instanceof Error){ 63 | if(arg.code === 404){ 64 | res.sendStatus(404); 65 | return; 66 | } else { 67 | res.sendStatus(500); 68 | return; 69 | } 70 | } 71 | res.send.bind(res)(arg); 72 | }); 73 | }); 74 | 75 | 76 | // progress 77 | module.router.get('/map/:mapid/progressstate', function(req, res) { 78 | maps.getProgressState(req, req.params.mapid, function(progress,err) { 79 | if(err){ 80 | res.json({progress:-1}); 81 | } else { 82 | res.json(progress); 83 | } 84 | }); 85 | }); 86 | 87 | // progress 88 | module.router.put('/map/:mapid/progressstate', function(req, res) { 89 | maps.advanceProgressState(req, req.params.mapid, function(progress, err) { 90 | if(err){ 91 | res.json({progress:-1}); 92 | } else { 93 | res.json(progress); 94 | } 95 | }); 96 | }); 97 | 98 | // 6. export 99 | module.router.get('/svg/:mapid/:name', function(req, 100 | res) { 101 | exportmap.createSVG(req, res, req.params.mapid, req.params.name, {format:'svg'}); 102 | }); 103 | 104 | module.router.get('/svgforcedownload/:mapid/:name', function(req, 105 | res) { 106 | exportmap.createSVG(req, res, req.params.mapid, req.params.name, {forcedownload:true, format:'svg'}); 107 | }); 108 | 109 | module.router.get('/pngforcedownload/:mapid/:name', function(req, 110 | res) { 111 | exportmap.createSVG(req, res, req.params.mapid, req.params.name, {forcedownload:true, format:'png'}); 112 | }); 113 | 114 | module.router.get('/thumbnail/:mapid', function(req, 115 | res) { 116 | exportmap.createThumbnail(req, res, req.params.mapid); 117 | }); 118 | 119 | return module; 120 | }; 121 | -------------------------------------------------------------------------------- /server/router/mainrouter.js: -------------------------------------------------------------------------------- 1 | //#!/bin/env node 2 | /* Copyright 2015 Krzysztof Daniel 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License.*/ 15 | 16 | module.exports = function(maps){ 17 | var module = {}; 18 | 19 | 20 | module.router = require('express').Router(); 21 | 22 | module.router.get('/' , function(req, res) { 23 | maps.getMaps(req, function(response) { 24 | res.render('index', {response : response, user : req.user}); 25 | }); 26 | }); 27 | 28 | //deserves own module 29 | module.router.get('/map/:mapid', function(req, res) { 30 | maps.getMap(req, req.params.mapid, function(map) { 31 | var res2 = { 32 | json : function(arg){ 33 | //TODO: this is a hack, as related maps should be loaded async (lazy) from the client 34 | res.render('mapeditor', { 35 | map : map, 36 | user : req.user, 37 | related: arg 38 | }); 39 | } 40 | }; 41 | maps.findRelatedMaps(req, res2); 42 | }); 43 | }); 44 | 45 | module.router.get('/map', function(req, res) { 46 | res.render('mapeditor2', {}); 47 | }); 48 | //help 49 | module.router.get('/help/:filename', function(req, res) { 50 | res.render('help/'+req.params.filename); 51 | }); 52 | 53 | return module; 54 | }; 55 | -------------------------------------------------------------------------------- /server/router/profilerouter.js: -------------------------------------------------------------------------------- 1 | //#!/bin/env node 2 | /* Copyright 2015 Krzysztof Daniel 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License.*/ 15 | 16 | module.exports = function(){ 17 | var module = {}; 18 | 19 | 20 | module.router = require('express').Router(); 21 | 22 | module.router.get('/' , function(req, res) { 23 | res.redirect('/'); 24 | }); 25 | 26 | return module; 27 | }; -------------------------------------------------------------------------------- /server/router/share.js: -------------------------------------------------------------------------------- 1 | //#!/bin/env node 2 | /* Copyright 2015 Krzysztof Daniel 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License.*/ 15 | 16 | var logger = require('./../util/log.js').getLogger('share'); 17 | var Q = require('q'); 18 | Q.longStackSupport = true; 19 | 20 | module.exports = function(context, db, authmiddleware, mapsmodule, exporter) { 21 | var module = {}; 22 | 23 | module.PRECISE = 'precise'; 24 | module.ANONYMOUS = 'anonymous'; 25 | 26 | /** 27 | * This function creates an url that can be used to share a map. 28 | * @param req - a request that is used to extract the host of the application running. 29 | * @param mapId - id of map to share 30 | * @param precise - true/false value indicating whether the map should be available to selected persons only 31 | * 32 | * @return url to share. 33 | * 34 | * It is actually necessary to rely on req, because otherwise we would not know where the map is located. 35 | */ 36 | module.constructSharingURL = function(req, mapId, precise) { 37 | var mode = precise ? module.PRECISE : module.ANONYMOUS; 38 | 39 | var protocol = req.headers.referer ? req.headers.referer.split(':')[0] 40 | : 'http'; 41 | 42 | var url_mainpart = protocol + '://' + req.headers.host; 43 | return url_mainpart + context + '/' + mode + '/' + mapId + '/map.svg'; 44 | }; 45 | 46 | /** 47 | * input {mapId, progress} 48 | * output {mapId, progress} 49 | */ 50 | var _anonymousShare = function(params, callback){ 51 | var deferred = Q.defer(); 52 | var mapId = '' + params.mapId; //ensure mapid is a string 53 | var updateInstruction = {}; 54 | if(params.anonymousShare){ 55 | updateInstruction = {$set:{anonymousShare:true,mapId:mapId}}; 56 | } 57 | if(params.anonymousUnshare){ 58 | updateInstruction = {$set:{anonymousShare:false,mapId:mapId}}; 59 | } 60 | db.sharing.update( 61 | {mapId : mapId}, //query 62 | updateInstruction, //set value 63 | {upsert: true}, 64 | function(err, object) { 65 | if (err) { 66 | deferred.reject(err); 67 | return; 68 | } 69 | if (object) { 70 | params.shareResult = {url : module.constructSharingURL(params.req, params.mapId, false)}; 71 | deferred.resolve(params); 72 | } 73 | }); 74 | return deferred.promise.nodeify(callback); 75 | }; 76 | 77 | module.share = function(req, res, userId, mapId, mode, callback){ 78 | logger.debug('sharing map ', mapId, ' as ', mode); 79 | var anonymousShare = mode === 'anonymous'; 80 | 81 | var params = { 82 | req : req, 83 | res : res, 84 | mapId : mapId, 85 | userId : userId 86 | }; 87 | 88 | if (anonymousShare) { 89 | params.anonymousShare = true; 90 | require('./../util/Access')(db).writeAccessCheck(params) 91 | .then(_anonymousShare) 92 | .then(callback) 93 | .catch(function(err){ 94 | if(err){ 95 | logger.err(err); 96 | } 97 | callback(null,err); 98 | }).done(); 99 | return; 100 | } 101 | 102 | var unshareanonymousShare = mode === 'unshareanonymous'; 103 | if (unshareanonymousShare) { 104 | params.anonymousUnshare = true; 105 | require('./../util/Access')(db).writeAccessCheck(params) 106 | .then(_anonymousShare) 107 | .then(callback) 108 | .catch(function(err){ 109 | callback(null,err); 110 | }).done(); 111 | return; 112 | } 113 | }; 114 | 115 | module.getInfo = function(req, res, userId, mapId){ 116 | //TODO: do not forget to check user id 117 | db.sharing.findOne( 118 | {mapId : '' + req.params.mapid}, 119 | function(err, object) { 120 | res.setHeader('Content-Type', 'application/json'); 121 | if (err) { 122 | logger.error(err); 123 | res.statusCode = 500; 124 | res.send(JSON.stringify(err)); 125 | } 126 | if (object && object.anonymousShare === true) { 127 | res.json({anonymousShare:true,url : module.constructSharingURL(req, mapId, false)}); 128 | } else { 129 | res.json({anonymousShare:false}); 130 | } 131 | }); 132 | }; 133 | 134 | module.router = require('express').Router(); 135 | 136 | // GET /share/anonymous/:mapid/:filename <- export 137 | module.router.get('/' + module.ANONYMOUS + '/:mapid/:filename', function( 138 | req, res) { 139 | db.sharing.findOne( 140 | {mapId : '' + req.params.mapid}, 141 | function(err, object) { 142 | console.log('found matching', err, object); 143 | if (err) { 144 | res.setHeader('Content-Type', 'application/json'); 145 | logger.error(err); 146 | res.statusCode = 500; 147 | res.send(JSON.stringify(err)); 148 | } 149 | if (object && object.anonymousShare === true) { 150 | exporter.createSharedSVG(req, res, req.params.mapid, req.params.name); 151 | } else { 152 | res.setHeader('Content-Type', 'image/png'); 153 | res.redirect('/android-icon-192x192.png'); 154 | } 155 | }); 156 | }); 157 | 158 | // GET /share/precise/:mapid/:filename <- export 159 | module.router.get('/' + module.PRECISE + '/:mapid/:filename', 160 | authmiddleware, function(req, res) { 161 | exporter.createSharedSVG(req, res, req.params.mapid, 162 | req.params.name); 163 | }); 164 | 165 | // /share/map/:mapid/:mode <- set mode 166 | module.router.put('/map/:mapid/:mode', authmiddleware, function(req, res) { 167 | 168 | var userId = req.user.href; 169 | //TODO: validation of params 170 | var mapId = db.ObjectId(req.params.mapid); 171 | var mode = req.params.mode; 172 | 173 | // this is how we communicate the result 174 | var callback = function(result, err) { 175 | //TODO: handle error 176 | res.json(result.shareResult); 177 | }; 178 | 179 | logger.debug("sharing ", mapId, " for user ", userId, " mode ", mode); 180 | 181 | module.share(req, res, userId, mapId, mode, callback); 182 | }); 183 | 184 | // /share/map/:mapid/:mode <- get mode 185 | module.router.get('/map/:mapid', authmiddleware, function(req, res) { 186 | 187 | var userId = req.user.href; 188 | //TODO: validation of params 189 | var mapId = db.ObjectId(req.params.mapid); 190 | 191 | module.getInfo(req, res, userId, mapId); 192 | }); 193 | 194 | return module; 195 | }; 196 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* Copyright 2014, 2015 Krzysztof Daniel and Scott Weinstein. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License.*/ 15 | 16 | 'use strict'; 17 | var express = require('express'); 18 | var fs = require('fs'); 19 | var path = require('path'); 20 | var logger = require('./util/log').getLogger('server'); 21 | 22 | 23 | function endsWith(str, suffix) { 24 | return str.indexOf(suffix, str.length - suffix.length) !== -1; 25 | } 26 | 27 | var WardleyMapsApp = function(configOptions) { 28 | 29 | // Scope. 30 | var self = this; 31 | 32 | /* ================================================================ */ 33 | /* Helper functions. */ 34 | /* ================================================================ */ 35 | 36 | 37 | /** 38 | * terminator === the termination handler Terminate server on receipt of the 39 | * specified signal. 40 | * 41 | * @param {string} 42 | * sig Signal to terminate on. 43 | */ 44 | self.terminator = function(sig) { 45 | if (typeof sig === "string") { 46 | console.log('%s: Received %s - terminating Wardley Maps Tool ...', 47 | Date(Date.now()), sig); 48 | process.exit(1); 49 | } 50 | console.log('%s: Node server stopped.', Date(Date.now())); 51 | }; 52 | 53 | /** 54 | * Setup termination handlers (for exit and a list of signals). 55 | */ 56 | self.setupTerminationHandlers = function() { 57 | // Process on exit and signals. 58 | process.on('exit', function() { 59 | self.terminator(); 60 | }); 61 | 62 | // Removed 'SIGPIPE' from the list - bugz 852598. 63 | [ 'SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGILL', 'SIGTRAP', 'SIGABRT', 64 | 'SIGBUS', 'SIGFPE', 'SIGUSR1', 'SIGSEGV', 'SIGUSR2', 'SIGTERM' ] 65 | .forEach(function(element, index, array) { 66 | process.on(element, function() { 67 | self.terminator(element); 68 | }); 69 | }); 70 | }; 71 | 72 | 73 | self.start = function() { 74 | 75 | var app = express(); 76 | 77 | var clientDir = path.join(__dirname, '..', 'client'); 78 | app.use(require('cookie-parser')()); 79 | app.use(require('body-parser').json()); 80 | app.use(require('body-parser').urlencoded({ extended: false })); 81 | app.use(express.static(clientDir)); 82 | app.use(require('express-session')({ 83 | secret : 'modecommcd90le', 84 | resave : false, 85 | saveUninitialized : false 86 | })); 87 | app.use(require('morgan')('combined')); 88 | app.use(require('errorhandler')()); 89 | 90 | var userProvider = require('./user-provider')(app); 91 | 92 | self.db = require('./db')(configOptions.databaseConnectionString); 93 | self.exportmap = new require('./export')(self.db); 94 | self.maps = new require('./maps')(self.db); 95 | 96 | app.use('/share', require('./router/share.js')('/share', self.db, userProvider.loginRequired, self.maps, self.exportmap).router); 97 | app.use('/profile', userProvider.loginRequired, require('./router/profilerouter.js')().router); 98 | app.use('/api', userProvider.authenticationRequired, require('./router/apirouter.js')(self.maps, self.exportmap).router); 99 | app.use('/', userProvider.loginRequired, require('./router/mainrouter.js')(self.maps).router); 100 | 101 | 102 | // jade configuration 103 | app.set('views', clientDir); 104 | app.set('view engine', 'jade'); 105 | 106 | var _ = require('underscore'); 107 | self.ipaddress = configOptions.ipaddress || '0.0.0.0'; 108 | self.port = configOptions.port || configOptions.ssl ? 8443 : 8080; 109 | self.localmode = true; 110 | 111 | self.setupTerminationHandlers(); 112 | 113 | var onStart = _.partial(console.log, '%s: Node server started on %s:%d ...', Date(Date.now()), self.ipaddress, self.port); 114 | var server; 115 | if (configOptions.ssl) { 116 | var https = require('https'); 117 | server = https.createServer(configOptions.ssl, app); 118 | } else { 119 | var http = require('http'); 120 | server = http.createServer(app); 121 | } 122 | server.listen(self.port, self.ipaddress, onStart); 123 | }; 124 | }; 125 | 126 | 127 | 128 | function getConfig() { 129 | var config = {}; 130 | try { 131 | var configFile = (process.argv.length > 2) ? path.join(process.cwd() ,process.argv[2]) : path.join(__dirname, '../config.json'); 132 | config = require(configFile); 133 | } catch(ex) { 134 | console.error(ex, configFile); 135 | } 136 | 137 | if (config.ssl) { 138 | config.ssl.key = fs.readFileSync(config.ssl.key); 139 | config.ssl.cert = fs.readFileSync(config.ssl.cert); 140 | } 141 | 142 | var mongodbdata = require('./config/mongodbdata').dbdata; 143 | config.databaseConnectionString = require('./config/mongodbdata').dbdata.getConnectionString(); 144 | 145 | return config; 146 | } 147 | 148 | var wmapp = new WardleyMapsApp(getConfig()); 149 | wmapp.start(); 150 | -------------------------------------------------------------------------------- /server/svgtemplate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 |
84 | 85 | -------------------------------------------------------------------------------- /server/user-provider.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2015 and Scott Weinstein and Krzysztof Daniel. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License.*/ 14 | 15 | var config = { 16 | userProvider : 'stormpath' 17 | }; 18 | try { 19 | config = require('../config.json'); 20 | } catch (ex) { 21 | 22 | } 23 | 24 | module.exports = function(app) { 25 | if (config.userProvider === 'stormpath') { 26 | var stormpathconfig = require('./config/stormpathconfig').stormpathconfig; 27 | var googleauth = require('./config/googleauth').googleauth; 28 | var stormpath = require('express-stormpath'); 29 | var user = new require('./user')(); 30 | 31 | app.use(stormpath.init(app, { 32 | debug: 'info, error', 33 | client: { 34 | apiKey: { 35 | id: stormpathconfig.getApiKeyId(), 36 | secret: stormpathconfig.getApiKeySecret() 37 | } 38 | }, 39 | application: { 40 | href: stormpathconfig.getApplication() 41 | }, 42 | website : true, 43 | api : true, 44 | postRegistrationHandler : function(account, req, res, next) { 45 | user.processLoginInfo(account, res, next); 46 | }, 47 | socialProviders : { 48 | google : { 49 | enabled : true, 50 | clientId : googleauth.getClientID(), 51 | clientSecret : googleauth.getClientSecret(), 52 | callbackUri : '/callbacks/google', 53 | scopes : 'https://www.googleapis.com/auth/userinfo.profile,https://www.googleapis.com/auth/userinfo.profile,https://www.googleapis.com/auth/userinfo.email' 54 | } 55 | }, 56 | expand: { 57 | customData: true, 58 | providerData : true 59 | }, 60 | web : { 61 | login: { 62 | view: __dirname + '/../client/views/' + 'login.jade' 63 | }, 64 | register : { 65 | enable: true, 66 | view: __dirname + '/../client/views/' + 'register.jade', 67 | fields: { 68 | givenName: { 69 | required: false, 70 | enabled: false 71 | }, 72 | surname: { 73 | required: false, 74 | enabled: false 75 | }, 76 | email : { 77 | required: true 78 | }, 79 | password : { 80 | required: true 81 | }, 82 | passwordConfirm : { 83 | required: true 84 | }, 85 | "tc" : { 86 | required: true, 87 | type: 'checkbox', 88 | name:"tc", 89 | placeholder:"I hereby accept terms and conditions" 90 | }, 91 | }, 92 | fieldOrder: [ "email", "password", "passwordConfirm" ] 93 | } 94 | }, 95 | templateContext : { 96 | toc : config.toc ? config.toc : false, 97 | tocupdate : config.tocupdate 98 | } 99 | })); 100 | 101 | return stormpath; 102 | } 103 | 104 | if (config.userProvider === 'os') { 105 | console.log('WARNING : development mode'); 106 | console.log('WARNING : auth disabled'); 107 | function osUserMiddleware(req, res, next) { 108 | req.user = { 109 | href : process.env.USER || process.env.USERNAME 110 | }; 111 | next(); 112 | } 113 | osUserMiddleware.loginRequired = function(req, res, next) { 114 | next(); 115 | }; 116 | osUserMiddleware.authenticationRequired = function(req, res, next) { 117 | next(); 118 | }; 119 | app.use(osUserMiddleware); 120 | return osUserMiddleware; 121 | } 122 | 123 | var someOtherProvider = require(config.userProvider); 124 | app.use(someOtherProvider); 125 | return someOtherProvider; 126 | }; 127 | -------------------------------------------------------------------------------- /server/user.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2014,2015 by Krzysztof Daniel 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License.*/ 14 | 15 | var logger = require('./util/log.js').getLogger('user'); 16 | var request = require('request'); 17 | var mailchimp = require('./mailchimp'); 18 | 19 | logger.setLevel('ALL'); 20 | 21 | var user = function() { 22 | 23 | var self = this; 24 | 25 | this.fetchStormPathProviderData = function(account, res, next) { 26 | account.getProviderData(function(err, providerData) { 27 | if (err) { 28 | logger.error('error while getting data for', account.href, err); 29 | return; 30 | } 31 | if (!providerData) { 32 | logger.warn("no data for " + account.href); 33 | return; 34 | } 35 | if (providerData.providerId === 'google') { 36 | logger.debug('got google stormpath data for ' + account.href); 37 | account.providerData = providerData; 38 | self.fetchAndStoreGoogleProfile(account, account.providerData.accessToken, next); 39 | } else { 40 | logger.error('support for ' + providerData.providerId + ' not implemented'); 41 | next(); 42 | } 43 | }); 44 | }; 45 | 46 | this.fetchAndStoreGoogleProfile = function(account, token, next) { 47 | logger.debug('requested google profile for ' + account); 48 | request('https://www.googleapis.com/oauth2/v2/userinfo?access_token=' + token, function(err, res1, body) { 49 | if (err) { 50 | logger.error(err); 51 | return; 52 | } 53 | if (res1.statusCode !== 200) { 54 | return logger.error('Invalid access token: ' + body); 55 | } else { 56 | account.customData.googleProfile = JSON.parse(body); 57 | account.save(function(err) { 58 | if (err) { 59 | logger.error('error while getting user profile' + err); 60 | } 61 | logger.debug('stored google profile for ' + account); 62 | next(); 63 | }); 64 | } 65 | }); 66 | }; 67 | 68 | this.processLoginInfo = function(account, res, next) { 69 | self.fetchStormPathProviderData(account, res, next); 70 | 71 | mailchimp.exportToMailChimp(account.givenName, account.surname, account.email); 72 | }; 73 | 74 | return self; 75 | }; 76 | 77 | module.exports = user; 78 | -------------------------------------------------------------------------------- /server/util/Access.js: -------------------------------------------------------------------------------- 1 | //#!/bin/env node 2 | /* Copyright 2015 Krzysztof Daniel 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License.*/ 15 | 16 | var logger = require('./log.js').getLogger('Access'); 17 | var Q = require('q'); 18 | Q.longStackSupport = true; 19 | 20 | module.exports = function(db) { 21 | var module = {}; 22 | 23 | /** 24 | * params {req, res, mapId, userId } 25 | * returns {code:403} if not authorized 26 | */ 27 | module.writeAccessCheck = function(params, callback){ 28 | var deferred = Q.defer(); 29 | db.maps.find({ 30 | "userIdGoogle" : params.userId, /* only if user id matches */ 31 | "_id" : params.mapId, /* only if map id matches */ 32 | deleted : false /* don't return deleted maps */ 33 | }).toArray(function(err, maps) { 34 | if (err || !maps || maps.length !== 1) { 35 | deferred.reject(err || {code:403}); 36 | } 37 | if (maps.length === 1) { 38 | deferred.resolve(params); 39 | } 40 | }); 41 | return deferred.promise.nodeify(callback); 42 | }; 43 | 44 | return module; 45 | }; 46 | -------------------------------------------------------------------------------- /server/util/log.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2014 Krzysztof Daniel 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License.*/ 14 | 15 | 16 | var log4js = require('log4js'); 17 | var fs = require('fs'); 18 | fs.mkdir('logs', function ignore (err) { 19 | }); 20 | 21 | 22 | log4js.configure({ 23 | appenders : [ { 24 | type : 'console' 25 | }, { 26 | type : 'file', 27 | filename : 'logs/wardleymaps.log', 28 | category : 'wardleymaps' 29 | } ], 30 | replaceConsole : true 31 | }); 32 | 33 | 34 | module.exports = log4js; 35 | -------------------------------------------------------------------------------- /supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | 4 | [program:mongo] 5 | command=/usr/bin/mongod 6 | stdout_logfile=/dev/stdout 7 | stdout_logfile_maxbytes=0 8 | stderr_logfile=/dev/stdout 9 | stderr_logfile_maxbytes=0 10 | 11 | [program:node] 12 | command=/usr/bin/node /srv/server/server.js 13 | stdout_logfile=/dev/stdout 14 | stdout_logfile_maxbytes=0 15 | stderr_logfile=/dev/stdout 16 | stderr_logfile_maxbytes=0 --------------------------------------------------------------------------------