├── .gitignore
├── COPYING.jQuery
├── COPYING.requireJS
├── LICENSE
├── README.md
├── build-embed.def
├── build.def
├── compile
├── css
├── lib
│ ├── dragdealer.css
│ ├── icomoon.css
│ ├── jquery-ui-autocomplete.css
│ └── qunit-1.11.0.css
└── sass
│ ├── main.scss
│ └── pscp.scss
├── fonts
├── icomoon.dev.svg
├── icomoon.eot
├── icomoon.svg
├── icomoon.ttf
└── icomoon.woff
├── img
├── app
│ ├── arxiv.png
│ ├── cross.png
│ ├── doi.png
│ ├── favicon.ico
│ ├── favicon.png
│ ├── inspire.png
│ ├── mypscp.png
│ ├── paperscapeBanner.png
│ ├── paperscapeCol.png
│ ├── paperscapeTransparent.png
│ └── pdf.png
└── svg
│ ├── paperscapeBanner.svg
│ ├── paperscapeCol.svg
│ ├── paperscapeFB.svg
│ ├── paperscapeFBIcon.svg
│ ├── paperscapeIcon.svg
│ └── paperscapeTransparent.svg
├── index-embed.html
├── index-multimap.html
├── index.html
├── js
├── app-build-embed.js
├── app-build.js
├── coffee
│ ├── Vec2D.coffee
│ ├── Vec2D_test.coffee
│ ├── ajax.coffee
│ ├── ajax_test.coffee
│ ├── config-embed.coffee
│ ├── config.coffee
│ ├── config_unit_test.coffee
│ ├── detailview.coffee
│ ├── fadeview.coffee
│ ├── infoview.coffee
│ ├── input.coffee
│ ├── input_test.coffee
│ ├── main-embed.coffee
│ ├── main.coffee
│ ├── main_test.coffee
│ ├── mapview.coffee
│ ├── mapview_test.coffee
│ ├── newpapersview.coffee
│ ├── search.coffee
│ ├── selected.coffee
│ ├── world.coffee
│ └── world_test.coffee
└── lib
│ ├── jquery-3.1.1.js
│ ├── jquery-3.1.1.min.js
│ ├── jquery-ui-autocomplete.html.js
│ ├── jquery-ui-autocomplete.min.js
│ ├── jquery.mousewheel.js
│ ├── mousetrap.js
│ ├── qunit-1.11.0.js
│ └── require.js
├── minhtml
├── .gitignore
├── lexer.py
├── minhtml.py
└── parser.py
├── r.js
└── unittests.html
/.gitignore:
--------------------------------------------------------------------------------
1 | css/sass/.sass-cache
2 | css/app/*.css
3 | css/app/*.css.map
4 | local_serve
5 | local_serve/
6 | js/tests/*.js
7 | js/tests/*.map
8 | js/app-build/*.js
9 | js/app/*.js
10 | js/app/*.map
11 | build
12 | build-embed
13 | deploy*
14 |
--------------------------------------------------------------------------------
/COPYING.jQuery:
--------------------------------------------------------------------------------
1 | Copyright 2014 jQuery Foundation and other contributors
2 | http://jquery.com/
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining
5 | a copy of this software and associated documentation files (the
6 | "Software"), to deal in the Software without restriction, including
7 | without limitation the rights to use, copy, modify, merge, publish,
8 | distribute, sublicense, and/or sell copies of the Software, and to
9 | permit persons to whom the Software is furnished to do so, subject to
10 | the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/COPYING.requireJS:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010-2014, The Dojo Foundation
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 | * Neither the name of the Dojo Foundation nor the names of its contributors
13 | may be used to endorse or promote products derived from this software
14 | without specific prior written permission.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (C) 2011-2017 Damien P. George and Robert Knegjens
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of
5 | this software and associated documentation files (the "Software"), to deal in
6 | the Software without restriction, including without limitation the rights to
7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8 | the Software, and to permit persons to whom the Software is furnished to do so,
9 | subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Paperscape Map Client
2 | =====================
3 |
4 | This is the source code of the browser-based client of the [Paperscape map](http://paperscape.org) project.
5 | The source code of the [map generation](https://github.com/paperscape/paperscape-mapgen) and [backend web server](https://github.com/paperscape/paperscape-backend), as well as the [Paperscape data](https://github.com/paperscape/paperscape-data), are also available on Github.
6 | For more details and progress on Paperscape please visit the [development blog](http://blog.paperscape.org).
7 |
8 | Compiling
9 | ---------
10 |
11 | The Paperscape map client is written in [CoffeeScript](http://coffeescript.org), which must first be (trans)compiled into JavaScript code for it to run in a browser.
12 | Similarly, the style sheets are written in [Sass](http://sass-lang.com), which must be compiled into CSS.
13 | With CoffeeScript and Sass installed, the _compile_ script can be used (on unix-like systems) to automatically perform the compilations.
14 | Once started this script will continue to monitor for changes using the `inotifywait` command (from the inotify toolset) unless the runtime command `--single` is specified.
15 |
16 | The JavaScript/CoffeeScript code is split into modules, which are managed and loaded using [RequireJS](http://requirejs.org).
17 |
18 | Building (deploying)
19 | --------------------
20 |
21 | The Paperscape map client can be deployed using the included build script.
22 | Copy the default build script file _build.def_ to a file named _build_ (i.e. without the extension), and edit the build paths in the new file as appropriate.
23 | Then give the new build script executable permissions with `chmod +x`, and run it with the command `./build`.
24 | The JavaScript minification is handled by [RequireJS](http://requirejs.org).
25 | The HTML index file is minified by _minhtml/minthml.py_.
26 |
27 | About the Paperscape map
28 | ------------------------
29 |
30 | Paperscape is an interactive map that visualises the [arXiv](http://arxiv.org/), an open, online repository for scientific research papers.
31 | The map, which can be explored by panning and zooming, currently includes all of the papers from the arXiv and is updated daily.
32 |
33 | Each scientific paper is represented in the map by a circle whose size is determined by the number of times that paper has been cited by others.
34 | A paper's position in the map is determined by both its citation links (papers that cite it) and its reference links (papers it refers to).
35 | These links pull related papers together, whereas papers with no or few links in common push each other away.
36 |
37 | In the default colour scheme, where papers are coloured according to their scientific category, coloured "continents" emerge, such as theoretical high energy physics (blue) or astrophysics (pink).
38 | At their interface one finds cross-disciplinary fields, such as dark matter and cosmological inflation.
39 | Zooming in on a continent reveals substructures representing more specific fields of research.
40 | The automatically extracted keywords that appear on top of papers help to identify interesting papers and fields.
41 |
42 | Clicking on a paper reveals its meta data, such as title, authors, journal and abstract, as well as a link to the full text.
43 | It is also possible to view the references or citations for a paper as a star-like background on the map.
44 |
45 | Copyright
46 | ---------
47 |
48 | The MIT License (MIT)
49 |
50 | Copyright (C) 2011-2017 Damien P. George and Robert Knegjens
51 |
52 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
53 |
54 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
55 |
56 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
57 |
--------------------------------------------------------------------------------
/build-embed.def:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | SRC_DIR=.
4 |
5 | BUILD_DEST=embed-deploy
6 |
7 | mkdir -p $BUILD_DEST/img/app
8 | mkdir -p $BUILD_DEST/js/app
9 | mkdir -p $BUILD_DEST/css/lib
10 | mkdir -p $BUILD_DEST/css/app
11 | mkdir -p $BUILD_DEST/fonts
12 |
13 | $SRC_DIR/compile --single
14 |
15 | python2 $SRC_DIR/minhtml/minhtml.py $SRC_DIR/index-embed.html > $BUILD_DEST/index.html
16 |
17 | cp $SRC_DIR/img/app/* $BUILD_DEST/img/app/
18 |
19 | # optimize and copy javascript
20 | node $SRC_DIR/r.js -o $SRC_DIR/js/app-build-embed.js
21 | cp $SRC_DIR/js/app-build/pscp-embed.js $BUILD_DEST/js/app/
22 | gzip -kf $BUILD_DEST/js/app/*.js
23 |
24 | cp $SRC_DIR/css/lib/* $BUILD_DEST/css/lib/
25 | cp $SRC_DIR/css/app/* $BUILD_DEST/css/app/
26 | cp $SRC_DIR/fonts/* $BUILD_DEST/fonts/
27 |
--------------------------------------------------------------------------------
/build.def:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | SRC_DIR=.
4 |
5 | BUILD_DEST=deploy
6 |
7 | mkdir -p $BUILD_DEST/img/app
8 | mkdir -p $BUILD_DEST/js/app
9 | mkdir -p $BUILD_DEST/css/lib
10 | mkdir -p $BUILD_DEST/css/app
11 | mkdir -p $BUILD_DEST/fonts
12 |
13 | $SRC_DIR/compile --single
14 |
15 | python2 $SRC_DIR/minhtml/minhtml.py $SRC_DIR/index.html > $BUILD_DEST/index.html
16 | #python2 $SRC_DIR/minhtml/minhtml.py $SRC_DIR/index-multimap.html > $BUILD_DEST/index.html
17 |
18 | cp $SRC_DIR/img/app/* $BUILD_DEST/img/app/
19 |
20 | # optimize and copy javascript
21 | node $SRC_DIR/r.js -o $SRC_DIR/js/app-build.js
22 | cp $SRC_DIR/js/app-build/pscp.js $BUILD_DEST/js/app/
23 | gzip -kf $BUILD_DEST/js/app/*.js
24 |
25 | cp $SRC_DIR/css/lib/* $BUILD_DEST/css/lib/
26 | cp $SRC_DIR/css/app/* $BUILD_DEST/css/app/
27 | cp $SRC_DIR/fonts/* $BUILD_DEST/fonts/
28 |
--------------------------------------------------------------------------------
/compile:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 |
4 | single=false
5 |
6 | while [ $# -gt 0 ] ; do
7 | if [ $1 == "-s" -o $1 == "--single" ] ; then
8 | single=true
9 | fi
10 | shift 1
11 | done
12 |
13 | JS_BASE=js
14 | CSS_BASE=css
15 |
16 | JS_APP_DIR=${JS_BASE}/app
17 | JS_TESTS_DIR=${JS_BASE}/tests
18 |
19 |
20 | function compile {
21 | coffee_files=""
22 | coffee_test_files=""
23 | for f in ${JS_BASE}/coffee/*.coffee; do
24 | if [ ${f: -12} == "_test.coffee" ]; then
25 | coffee_test_files=$coffee_test_files" "$f
26 | else
27 | coffee_files=$coffee_files" "$f
28 | fi
29 | done
30 |
31 | if [ ${#coffee_files} -gt 0 ]; then
32 | coffee -o $JS_APP_DIR -c --map ${coffee_files}
33 | fi
34 | if [ ${#coffee_test_files} -gt 0 ]; then
35 | coffee -o $JS_TESTS_DIR -c --map ${coffee_test_files}
36 | fi
37 | sass --cache-location ${CSS_BASE}/sass/.sass-cache --update ${CSS_BASE}/sass:${CSS_BASE}/app
38 | }
39 |
40 | compile
41 |
42 | if [ $single = false ]; then
43 | echo "watching for changes and running coffee and sass"
44 | while true; do
45 | inotifywait -q -e modify ${JS_BASE}/coffee/*.coffee ${CSS_BASE}/sass/*.scss;
46 | compile;
47 | done
48 | fi
49 |
--------------------------------------------------------------------------------
/css/lib/dragdealer.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Dragdealer JS v0.9.5
3 | * http://code.ovidiu.ch/dragdealer-js
4 | *
5 | * Copyright (c) 2010, Ovidiu Chereches
6 | * MIT License
7 | * http://legal.ovidiu.ch/licenses/MIT
8 | */
9 |
10 | .dragdealer {
11 | position: relative;
12 | height: 30px;
13 | background: #EEE;
14 | }
15 | .dragdealer .handle {
16 | position: absolute;
17 | cursor: pointer;
18 | }
19 | .dragdealer .red-bar {
20 | width: 100px;
21 | height: 30px;
22 | background: #CC0000;
23 | color: #FFF;
24 | line-height: 30px;
25 | text-align: center;
26 | }
27 | .dragdealer .disabled {
28 | background: #898989;
29 | }
30 |
--------------------------------------------------------------------------------
/css/lib/icomoon.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'icomoon';
3 | src:url('../../fonts/icomoon.eot');
4 | src:url('../../fonts/icomoon.eot?#iefix') format('embedded-opentype'),
5 | url('../../fonts/icomoon.woff') format('woff'),
6 | url('../../fonts/icomoon.ttf') format('truetype'),
7 | url('../../fonts/icomoon.svg#icomoon') format('svg');
8 | font-weight: normal;
9 | font-style: normal;
10 | }
11 |
12 | /* Use the following CSS code if you want to use data attributes for inserting your icons */
13 | [data-icon]:before {
14 | font-family: 'icomoon';
15 | content: attr(data-icon);
16 | speak: none;
17 | font-weight: normal;
18 | font-variant: normal;
19 | text-transform: none;
20 | line-height: 1;
21 | -webkit-font-smoothing: antialiased;
22 | }
23 |
24 | /* Use the following CSS code if you want to have a class per icon */
25 | /*
26 | Instead of a list of all class selectors,
27 | you can use the generic selector below, but it's slower:
28 | [class*="icon-"] {
29 | */
30 | .icon-checkmark, .icon-close, .icon-notebook, .icon-checkbox-checked, .icon-link, .icon-file, .icon-file-2, .icon-share, .icon-cloud, .icon-cloud-2, .icon-stats, .icon-pencil, .icon-minus, .icon-plus, .icon-trashcan, .icon-remove, .icon-remove-2, .icon-arrow-down, .icon-arrow-up, .icon-arrow-right, .icon-arrow-left, .icon-arrow-down-2, .icon-arrow-up-2, .icon-arrow-right-2, .icon-arrow-left-2, .icon-arrow-down-3, .icon-arrow-up-3, .icon-arrow-right-3, .icon-arrow-left-3, .icon-move-horizontal, .icon-transfer, .icon-loop, .icon-loop-2, .icon-loop-3, .icon-zoom-out, .icon-zoom-in, .icon-search, .icon-info, .icon-list, .icon-star, .icon-tags, .icon-cog, .icon-coguser, .icon-groupselect2, .icon-sun, .icon-sun-2, .icon-radio-checked, .icon-radio-unchecked {
31 | font-family: 'icomoon';
32 | speak: none;
33 | font-style: normal;
34 | font-weight: normal;
35 | font-variant: normal;
36 | text-transform: none;
37 | line-height: 1;
38 | -webkit-font-smoothing: antialiased;
39 | }
40 | .icon-checkmark:before {
41 | content: "\e02a";
42 | }
43 | .icon-close:before {
44 | content: "\e029";
45 | }
46 | .icon-notebook:before {
47 | content: "\e027";
48 | }
49 | .icon-checkbox-checked:before {
50 | content: "\e026";
51 | }
52 | .icon-link:before {
53 | content: "\e023";
54 | }
55 | .icon-file:before {
56 | content: "\e025";
57 | }
58 | .icon-file-2:before {
59 | content: "\e024";
60 | }
61 | .icon-share:before {
62 | content: "\e022";
63 | }
64 | .icon-cloud:before {
65 | content: "\e021";
66 | }
67 | .icon-cloud-2:before {
68 | content: "\e020";
69 | }
70 | .icon-stats:before {
71 | content: "\e01f";
72 | }
73 | .icon-pencil:before {
74 | content: "\e01e";
75 | }
76 | .icon-minus:before {
77 | content: "\e01d";
78 | }
79 | .icon-plus:before {
80 | content: "\e01c";
81 | }
82 | .icon-trashcan:before {
83 | content: "\e01b";
84 | }
85 | .icon-remove:before {
86 | content: "\e01a";
87 | }
88 | .icon-remove-2:before {
89 | content: "\e019";
90 | }
91 | .icon-arrow-down:before {
92 | content: "\e018";
93 | }
94 | .icon-arrow-up:before {
95 | content: "\e017";
96 | }
97 | .icon-arrow-right:before {
98 | content: "\e016";
99 | }
100 | .icon-arrow-left:before {
101 | content: "\e015";
102 | }
103 | .icon-arrow-down-2:before {
104 | content: "\e014";
105 | }
106 | .icon-arrow-up-2:before {
107 | content: "\e013";
108 | }
109 | .icon-arrow-right-2:before {
110 | content: "\e012";
111 | }
112 | .icon-arrow-left-2:before {
113 | content: "\e011";
114 | }
115 | .icon-arrow-down-3:before {
116 | content: "\e010";
117 | }
118 | .icon-arrow-up-3:before {
119 | content: "\e00f";
120 | }
121 | .icon-arrow-right-3:before {
122 | content: "\e00e";
123 | }
124 | .icon-arrow-left-3:before {
125 | content: "\e00d";
126 | }
127 | .icon-move-horizontal:before {
128 | content: "\e00c";
129 | }
130 | .icon-transfer:before {
131 | content: "\e00b";
132 | }
133 | .icon-loop:before {
134 | content: "\e00a";
135 | }
136 | .icon-loop-2:before {
137 | content: "\e009";
138 | }
139 | .icon-loop-3:before {
140 | content: "\e008";
141 | }
142 | .icon-zoom-out:before {
143 | content: "\e004";
144 | }
145 | .icon-zoom-in:before {
146 | content: "\e003";
147 | }
148 | .icon-search:before {
149 | content: "\e007";
150 | }
151 | .icon-info:before {
152 | content: "\e006";
153 | }
154 | .icon-list:before {
155 | content: "\e005";
156 | }
157 | .icon-star:before {
158 | content: "\e002";
159 | }
160 | .icon-tags:before {
161 | content: "\e001";
162 | }
163 | .icon-cog:before {
164 | content: "\e000";
165 | }
166 | .icon-coguser:before {
167 | content: "\e028";
168 | }
169 | .icon-groupselect2:before {
170 | content: "\e02b";
171 | }
172 | .icon-sun:before {
173 | content: "\e02c";
174 | }
175 | .icon-sun-2:before {
176 | content: "\e02d";
177 | }
178 | .icon-radio-checked:before {
179 | content: "\e02e";
180 | }
181 | .icon-radio-unchecked:before {
182 | content: "\e02f";
183 | }
184 |
--------------------------------------------------------------------------------
/css/lib/qunit-1.11.0.css:
--------------------------------------------------------------------------------
1 | /**
2 | * QUnit v1.11.0 - A JavaScript Unit Testing Framework
3 | *
4 | * http://qunitjs.com
5 | *
6 | * Copyright 2012 jQuery Foundation and other contributors
7 | * Released under the MIT license.
8 | * http://jquery.org/license
9 | */
10 |
11 | /** Font Family and Sizes */
12 |
13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
15 | }
16 |
17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
18 | #qunit-tests { font-size: smaller; }
19 |
20 |
21 | /** Resets */
22 |
23 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
24 | margin: 0;
25 | padding: 0;
26 | }
27 |
28 |
29 | /** Header */
30 |
31 | #qunit-header {
32 | padding: 0.5em 0 0.5em 1em;
33 |
34 | color: #8699a4;
35 | background-color: #0d3349;
36 |
37 | font-size: 1.5em;
38 | line-height: 1em;
39 | font-weight: normal;
40 |
41 | border-radius: 5px 5px 0 0;
42 | -moz-border-radius: 5px 5px 0 0;
43 | -webkit-border-top-right-radius: 5px;
44 | -webkit-border-top-left-radius: 5px;
45 | }
46 |
47 | #qunit-header a {
48 | text-decoration: none;
49 | color: #c2ccd1;
50 | }
51 |
52 | #qunit-header a:hover,
53 | #qunit-header a:focus {
54 | color: #fff;
55 | }
56 |
57 | #qunit-testrunner-toolbar label {
58 | display: inline-block;
59 | padding: 0 .5em 0 .1em;
60 | }
61 |
62 | #qunit-banner {
63 | height: 5px;
64 | }
65 |
66 | #qunit-testrunner-toolbar {
67 | padding: 0.5em 0 0.5em 2em;
68 | color: #5E740B;
69 | background-color: #eee;
70 | overflow: hidden;
71 | }
72 |
73 | #qunit-userAgent {
74 | padding: 0.5em 0 0.5em 2.5em;
75 | background-color: #2b81af;
76 | color: #fff;
77 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
78 | }
79 |
80 | #qunit-modulefilter-container {
81 | float: right;
82 | }
83 |
84 | /** Tests: Pass/Fail */
85 |
86 | #qunit-tests {
87 | list-style-position: inside;
88 | }
89 |
90 | #qunit-tests li {
91 | padding: 0.4em 0.5em 0.4em 2.5em;
92 | border-bottom: 1px solid #fff;
93 | list-style-position: inside;
94 | }
95 |
96 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
97 | display: none;
98 | }
99 |
100 | #qunit-tests li strong {
101 | cursor: pointer;
102 | }
103 |
104 | #qunit-tests li a {
105 | padding: 0.5em;
106 | color: #c2ccd1;
107 | text-decoration: none;
108 | }
109 | #qunit-tests li a:hover,
110 | #qunit-tests li a:focus {
111 | color: #000;
112 | }
113 |
114 | #qunit-tests li .runtime {
115 | float: right;
116 | font-size: smaller;
117 | }
118 |
119 | .qunit-assert-list {
120 | margin-top: 0.5em;
121 | padding: 0.5em;
122 |
123 | background-color: #fff;
124 |
125 | border-radius: 5px;
126 | -moz-border-radius: 5px;
127 | -webkit-border-radius: 5px;
128 | }
129 |
130 | .qunit-collapsed {
131 | display: none;
132 | }
133 |
134 | #qunit-tests table {
135 | border-collapse: collapse;
136 | margin-top: .2em;
137 | }
138 |
139 | #qunit-tests th {
140 | text-align: right;
141 | vertical-align: top;
142 | padding: 0 .5em 0 0;
143 | }
144 |
145 | #qunit-tests td {
146 | vertical-align: top;
147 | }
148 |
149 | #qunit-tests pre {
150 | margin: 0;
151 | white-space: pre-wrap;
152 | word-wrap: break-word;
153 | }
154 |
155 | #qunit-tests del {
156 | background-color: #e0f2be;
157 | color: #374e0c;
158 | text-decoration: none;
159 | }
160 |
161 | #qunit-tests ins {
162 | background-color: #ffcaca;
163 | color: #500;
164 | text-decoration: none;
165 | }
166 |
167 | /*** Test Counts */
168 |
169 | #qunit-tests b.counts { color: black; }
170 | #qunit-tests b.passed { color: #5E740B; }
171 | #qunit-tests b.failed { color: #710909; }
172 |
173 | #qunit-tests li li {
174 | padding: 5px;
175 | background-color: #fff;
176 | border-bottom: none;
177 | list-style-position: inside;
178 | }
179 |
180 | /*** Passing Styles */
181 |
182 | #qunit-tests li li.pass {
183 | color: #3c510c;
184 | background-color: #fff;
185 | border-left: 10px solid #C6E746;
186 | }
187 |
188 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
189 | #qunit-tests .pass .test-name { color: #366097; }
190 |
191 | #qunit-tests .pass .test-actual,
192 | #qunit-tests .pass .test-expected { color: #999999; }
193 |
194 | #qunit-banner.qunit-pass { background-color: #C6E746; }
195 |
196 | /*** Failing Styles */
197 |
198 | #qunit-tests li li.fail {
199 | color: #710909;
200 | background-color: #fff;
201 | border-left: 10px solid #EE5757;
202 | white-space: pre;
203 | }
204 |
205 | #qunit-tests > li:last-child {
206 | border-radius: 0 0 5px 5px;
207 | -moz-border-radius: 0 0 5px 5px;
208 | -webkit-border-bottom-right-radius: 5px;
209 | -webkit-border-bottom-left-radius: 5px;
210 | }
211 |
212 | #qunit-tests .fail { color: #000000; background-color: #EE5757; }
213 | #qunit-tests .fail .test-name,
214 | #qunit-tests .fail .module-name { color: #000000; }
215 |
216 | #qunit-tests .fail .test-actual { color: #EE5757; }
217 | #qunit-tests .fail .test-expected { color: green; }
218 |
219 | #qunit-banner.qunit-fail { background-color: #EE5757; }
220 |
221 |
222 | /** Result */
223 |
224 | #qunit-testresult {
225 | padding: 0.5em 0.5em 0.5em 2.5em;
226 |
227 | color: #2b81af;
228 | background-color: #D2E0E6;
229 |
230 | border-bottom: 1px solid white;
231 | }
232 | #qunit-testresult .module-name {
233 | font-weight: bold;
234 | }
235 |
236 | /** Fixture */
237 |
238 | #qunit-fixture {
239 | position: absolute;
240 | top: -10000px;
241 | left: -10000px;
242 | width: 1000px;
243 | height: 1000px;
244 | }
245 |
--------------------------------------------------------------------------------
/css/sass/main.scss:
--------------------------------------------------------------------------------
1 | /* definitions */
2 |
3 | $col-bg: #456;
4 | $col-dark-blue: #346;
5 | $col-gold: #fb0;
6 |
7 | // within some CSS, use "@include no-select;" to prevent that element from being able to be text-selected
8 | @mixin no-select {
9 | -webkit-touch-callout: none;
10 | -webkit-user-select: none;
11 | -khtml-user-select: none;
12 | -moz-user-select: none;
13 | -ms-user-select: none;
14 | user-select: none;
15 | }
16 |
17 | /*****************/
18 |
19 | h1, h2 {
20 | color: white;
21 | }
22 |
23 | body {
24 | background-color: $col-bg;
25 | //background-image: url("bgtile.png");
26 | color: white;
27 | font-family: Verdana, Arial, sans-serif;
28 | }
29 |
30 | a:link {
31 | text-decoration: none;
32 | }
33 |
34 | h1.maintitle {
35 | font-size: 48pt;
36 | }
37 |
38 | h2.maintitle {
39 | font-size: 40pt;
40 | }
41 |
42 | .absolute {
43 | position: absolute;
44 | }
45 |
46 | /* common colours */
47 |
48 | #topHeader {
49 | background-image: url("../../img/app/paperscapeBanner.png");
50 | background-repeat: no-repeat;
51 | background-position: 25px 3px;
52 | width: 100%;
53 | height: 66px;
54 | }
55 |
56 | #searchHeader {
57 | position: absolute;
58 | top: 10px;
59 |
60 | }
61 |
62 | #searchMessage {
63 | font-size: 60%;
64 | margin-left: 3px;
65 |
66 | .clear {
67 | text-decoration: underline;
68 | cursor: pointer;
69 | }
70 | }
71 |
72 | #searchBox {
73 | -webkit-appearance: none;
74 | -webkit-border-radius:0;
75 | border-radius: 0;
76 | width: 200px;
77 | height: 24px;
78 | line-height: 24px;
79 | vertical-align: top;
80 | padding-right: 0px;
81 | padding-left: 6px;
82 | margin: 0px 0px 0px 0px;
83 | border: 1px solid $col-gold;
84 | text-shadow:none;
85 | box-shadow:none;
86 | }
87 |
88 | #searchButton {
89 | -webkit-appearance: none;
90 | -webkit-border-radius:0;
91 | border-radius: 0;
92 | width: 30px;
93 | height: 28px;
94 | line-height: 28px;
95 | vertical-align: top;
96 | cursor: pointer;
97 | position: relative;
98 | left: -6px;
99 | background-color: $col-gold;
100 | color: white;
101 | padding: 0px;
102 | padding-left: 0px;
103 | margin: 0px 0px 0px 0px;
104 | border:1px solid $col-gold;
105 | text-shadow:none;
106 | box-shadow:none;
107 | }
108 |
109 | #searchNewPapers {
110 | font-size: 80%;
111 | padding-left: 6px;
112 | cursor: pointer;
113 | text-decoration: underline;
114 | color: $col-gold;
115 | }
116 |
117 | #topRightMenu {
118 | position: absolute;
119 | top: 13px;
120 | right: 6px;
121 | cursor: default;
122 |
123 | @include no-select;
124 |
125 | a:visited, a:active, a:hover, a:link {
126 | text-decoration: none;
127 | color: white;
128 | }
129 |
130 | div {
131 | display: inline-block;
132 | color: white;
133 | font-size: 20px;
134 | padding: 5px;
135 | //border: 1px solid black;
136 | margin: 3px;
137 | /*background-color: #40557f;*/
138 | /*background-color: #234; TODO*/
139 | //background-color: $col-dark-blue;
140 | font-weight: bold;
141 | cursor: pointer;
142 | }
143 |
144 | .active {
145 | color: $col-gold;
146 | }
147 |
148 | .inactive {
149 | color: #999;
150 | cursor: default;
151 | }
152 | }
153 |
154 | // Obsolete?
155 | #newPapersButton {
156 | top: 18px;
157 | right: 96px;
158 | }
159 |
160 | #newpapersPopup {
161 | position: absolute;
162 | border: 1px solid black;
163 | z-index: 100;
164 | background-color: white;
165 | color: black;
166 | font-size: 75%;
167 | text-align: center;
168 | overflow-y: auto;
169 |
170 | .close {
171 | color: $col-dark-blue;
172 | float: right;
173 | cursor: pointer;
174 | margin: 4px;
175 | }
176 |
177 | .heading {
178 | font-size: 120%;
179 | font-weight: bold;
180 | margin-top: 10px;
181 | margin-bottom: 10px;
182 | }
183 |
184 | .searchButton {
185 | margin-bottom: 10px;
186 | }
187 |
188 | .slider-range {
189 | margin-top: 10px;
190 | margin-bottom: 10px;
191 | left: 50px;
192 | }
193 |
194 | .catTable {
195 | margin-left: 50px;
196 | }
197 |
198 | .catCheckbox {
199 | display: inline-block;
200 | font-size: 90%;
201 | }
202 |
203 | .catName {
204 | width: 90px;
205 | }
206 |
207 | z-index: 200;
208 | }
209 |
210 |
--------------------------------------------------------------------------------
/css/sass/pscp.scss:
--------------------------------------------------------------------------------
1 | /* contains style features for elements with xiwiArea */
2 |
3 | $col-bg: #456;
4 | $col-dark-blue: #346;
5 | $col-gold: #fb0;
6 |
7 | /* this gets dynamically sized */
8 | #xiwiArea {
9 | position: absolute;
10 | top: 0;
11 | left: 0;
12 | height: 100%;
13 | margin: 0px;
14 | }
15 |
16 | #welcomePopup {
17 | position: absolute;
18 | border: 1px solid #666;
19 | top: 65px;
20 | left: 10px;
21 | //margin-top: 10px; //0 auto;
22 | padding: 4px 5px 0px 5px;
23 | font-size: 13px;
24 | height: 24px;
25 | //font-size: 10pt;
26 | color: white;
27 | z-index: 100;
28 | background-color: #000;
29 | //line-height: 0;
30 |
31 | .valign {
32 | vertical-align: middle;
33 | }
34 |
35 | a {
36 | color: $col-gold;
37 | text-decoration: none;
38 | }
39 |
40 | .refresh {
41 | color: $col-gold;
42 | cursor: pointer;
43 | }
44 |
45 | }
46 |
47 | #detailView {
48 | position: absolute;
49 | left: 10px;
50 |
51 | .header {
52 | text-decoration: underline;
53 | }
54 |
55 | .value {
56 | font-weight: bold;
57 | }
58 |
59 | font-size: 10px;
60 | color: $col-gold;
61 | z-index: 100;
62 | }
63 |
64 | #colourSchemeSelect, #mapSelect {
65 | position: absolute;
66 | left: 10px;
67 | color: white;
68 | //background-color: $col-dark-blue;
69 | background-color: black;
70 | border: 1px solid #666;
71 | font-size: 13px;
72 | height: 24px;
73 | //font-size: 10pt;
74 | //font-family: sans-serif;
75 | z-index: 100;
76 | padding: 2px 5px 2px 5px;
77 |
78 | .select {
79 | color: $col-gold;
80 | //background-color: $col-dark-blue;
81 | background-color: black;
82 | font-size: 13px;
83 | //font-family: sans-serif;
84 | //font-weight: bold;
85 | //text-align: center;
86 | border: none;
87 | padding: 2px;
88 | cursor: pointer;
89 | -moz-appearance: none;
90 | -webkit-appearance: none;
91 | }
92 |
93 | .select option {
94 | cursor: pointer;
95 | color: $col-gold;
96 | //background-color: $col-dark-blue;
97 | background-color: black;
98 | }
99 |
100 | .legend {
101 | width: 15px;
102 | display: inline-block;
103 | color: $col-gold;
104 | cursor: pointer;
105 | margin-left: 3px;
106 | font-size: 16px;
107 | }
108 | }
109 |
110 |
111 | #keyPopup {
112 | position: absolute;
113 | bottom: 10px;
114 | background-color: black;
115 | border: 1px solid #666;
116 | margin-top: 60px; //0 auto;
117 | margin-right: 20px;
118 | padding: 5px;
119 | font-size: 10pt;
120 | color: white;
121 | z-index: 200;
122 |
123 | .close {
124 | color: white;
125 | float: right;
126 | cursor: pointer;
127 | margin: 0 0 0 6px;
128 | font-size: 8pt;
129 | }
130 |
131 | .header {
132 | display: inline-block;
133 | text-decoration: underline;
134 | }
135 |
136 | .cat, .age {
137 | display: inline-block;
138 | font-size: 10pt;
139 | font-weight: bold;
140 | }
141 | // TODO send this information in world_index from tiles
142 | // categories
143 | .hep-th { color: #88f; }
144 | .hep-ph { color: #8f8; }
145 | .hep-ex { color: #ff8; }
146 | .hep-lat { color: rgb(70%,36%,20%); }
147 | .gr-qc { color: #8ff; }
148 | .astro-ph { color: rgb(89%,53%,60%); }
149 | .cond-mat { color: rgb(70%,50%,40%); }
150 | .quant-ph { color: rgb(40%,70%,70%); }
151 | .physics { color: rgb(100%,50%,50%); }
152 | .math { color: rgb(62%,86%,24%); }
153 | .cs { color: rgb(70%,30%,60%); }
154 | .other { color: rgb(70%,100%,30%); }
155 |
156 | // heatmap
157 | .new { color: #f00 }
158 | .old { color: #77a }
159 | }
160 |
161 | /*****************/
162 |
163 | #canvasUnderlay {
164 | position: absolute;
165 | z-index: 0;
166 | border-top:1px solid black;
167 | margin: 0px;
168 | }
169 |
170 |
171 | #canvasTiles {
172 | position: absolute;
173 | z-index: 1;
174 | border-top:1px solid black;
175 | background-color: transparent;
176 | margin: 0px;
177 | }
178 |
179 | #canvasOverlay {
180 | position: absolute;
181 | z-index: 2;
182 | border-top:1px solid black;
183 | background-color: transparent;
184 | margin: 0px;
185 | }
186 |
187 | #zoomedPaper {
188 | border: 2px solid black;
189 | position: absolute;
190 | background-color: white;
191 | /*background-color: #ffe;*/
192 | /*background-color: #fe8;*/
193 | z-index: 100;
194 | }
195 |
196 |
197 | // for the zoom in/out icons
198 | #canvasZoomButtons {
199 | position: absolute;
200 | left: 10px;
201 | z-index: 100;
202 |
203 | div {
204 | display: inline-block;
205 | cursor: pointer;
206 | color: $col-gold;
207 | width: 26px;
208 | height: 20px;
209 | font-size: 20px;
210 | text-align: center;
211 | vertical-align: top;
212 |
213 | &:hover {
214 | font-size: 24px;
215 | margin-top: -6px;
216 | }
217 | }
218 | }
219 |
220 | #infoPopup {
221 | position: absolute;
222 | border: 1px solid black;
223 | z-index: 100;
224 | background-color: white;
225 | color: black;
226 | font-size: 75%;
227 | text-align: center;
228 | overflow-y: auto;
229 |
230 | .close {
231 | color: $col-dark-blue;
232 | float: right;
233 | cursor: pointer;
234 | margin: 4px;
235 | }
236 |
237 | .title {
238 | color: black;
239 | font-weight: bold;
240 | margin: 0.7em;
241 | }
242 |
243 | .authors {
244 | color: blue;
245 | font-weight: normal;
246 | margin: 0.7em;
247 | }
248 |
249 | .arxiv, .journal {
250 | color: black;
251 | margin: 0;
252 | padding: 0 0 0 2px;
253 | }
254 |
255 | .showAbstract {
256 | margin: 4px;
257 | margin-top: 10px;
258 | float: right;
259 | font-size: 90%;
260 | text-decoration: underline;
261 | cursor: pointer;
262 | }
263 |
264 | // Temporary until think up nicer style
265 | .showReferences {
266 | margin: 4px;
267 | margin-top: 10px;
268 | float: left;
269 | font-size: 90%;
270 | text-decoration: underline;
271 | cursor: pointer;
272 | }
273 | .showCitations {
274 | margin: 4px;
275 | margin-top: 10px;
276 | float: left;
277 | font-size: 90%;
278 | text-decoration: underline;
279 | cursor: pointer;
280 | }
281 |
282 | .abstract {
283 | color: black;
284 | //top: 0%;
285 | padding: 1em 1.5em;
286 | clear: left; // doesn't try wrap around previous floats
287 | //overflow-y: auto; handled by infoPopup itself
288 | overflow-x: hidden;
289 | text-align: justify;
290 | font-size: 100%;
291 | line-height: 130%;
292 | }
293 | }
294 |
295 |
296 | #aboutPopup {
297 | position: absolute;
298 | border: 1px solid black;
299 | z-index: 100;
300 | background-color: white;
301 | color: black;
302 | font-size: 75%;
303 | text-align: left;
304 | overflow-y: auto;
305 |
306 | .title {
307 | color: black;
308 | font-weight: bold;
309 | margin: 0.7em;
310 | }
311 |
312 | p {
313 | color: black;
314 | margin: 0.7em;
315 | }
316 |
317 | .close {
318 | color: $col-dark-blue;
319 | float: right;
320 | cursor: pointer;
321 | margin: 4px;
322 | }
323 | }
324 |
325 | .paperButton {
326 | min-width: 18px;
327 | margin: 0 0 0 4px;
328 | cursor: pointer;
329 | }
330 |
331 | /****************************************************************/
332 | /* for latex math mode */
333 |
334 | .Lmm {
335 | font-family: Times;
336 | font-style: italic;
337 | font-weight: normal;
338 | }
339 |
340 | .Lsup {
341 | position: relative;
342 | bottom: 0.5em;
343 | font-size: 0.8em;
344 | }
345 |
346 | .Lsub {
347 | position: relative;
348 | top: 0.3em;
349 | font-size: 0.8em;
350 | }
351 |
352 | .Lbar {
353 | text-decoration: overline;
354 | }
355 |
--------------------------------------------------------------------------------
/fonts/icomoon.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/fonts/icomoon.eot
--------------------------------------------------------------------------------
/fonts/icomoon.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/fonts/icomoon.ttf
--------------------------------------------------------------------------------
/fonts/icomoon.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/fonts/icomoon.woff
--------------------------------------------------------------------------------
/img/app/arxiv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/img/app/arxiv.png
--------------------------------------------------------------------------------
/img/app/cross.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/img/app/cross.png
--------------------------------------------------------------------------------
/img/app/doi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/img/app/doi.png
--------------------------------------------------------------------------------
/img/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/img/app/favicon.ico
--------------------------------------------------------------------------------
/img/app/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/img/app/favicon.png
--------------------------------------------------------------------------------
/img/app/inspire.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/img/app/inspire.png
--------------------------------------------------------------------------------
/img/app/mypscp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/img/app/mypscp.png
--------------------------------------------------------------------------------
/img/app/paperscapeBanner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/img/app/paperscapeBanner.png
--------------------------------------------------------------------------------
/img/app/paperscapeCol.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/img/app/paperscapeCol.png
--------------------------------------------------------------------------------
/img/app/paperscapeTransparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/img/app/paperscapeTransparent.png
--------------------------------------------------------------------------------
/img/app/pdf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paperscape/paperscape-mapclient/d176798758e73344b27fb43feff724ba60c111e2/img/app/pdf.png
--------------------------------------------------------------------------------
/img/svg/paperscapeFBIcon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
21 |
23 |
49 |
58 |
59 |
61 |
62 |
64 | image/svg+xml
65 |
67 |
68 |
69 |
70 |
71 |
76 |
84 |
87 |
89 |
90 |
94 |
106 |
109 |
111 |
112 |
115 |
117 |
118 |
123 |
133 |
143 |
153 |
163 |
173 |
183 |
193 |
203 |
213 |
223 |
233 |
243 |
253 |
263 |
273 |
283 |
284 |
285 |
--------------------------------------------------------------------------------
/img/svg/paperscapeIcon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
21 |
23 |
49 |
58 |
59 |
61 |
62 |
64 | image/svg+xml
65 |
67 |
68 |
69 |
70 |
71 |
76 |
84 |
87 |
89 |
90 |
94 |
106 |
109 |
111 |
112 |
115 |
117 |
118 |
123 |
133 |
143 |
153 |
154 |
155 |
--------------------------------------------------------------------------------
/index-embed.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Paperscape
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
31 |
35 |
36 |
52 |
53 |
54 |
55 |
Your browser is not modern enough to support this HTML5 canvas element
56 |
57 |
58 | Colouring:
59 |
60 | category
61 | age
62 |
63 |
64 |
65 |
90 |
91 |
95 |
96 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/index-multimap.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Paperscape
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
36 |
37 |
38 |
39 |
65 |
66 |
74 |
75 |
95 |
96 |
97 |
98 |
Your browser is not modern enough to support this HTML5 canvas element
99 |
100 |
107 |
108 |
109 | Time:
110 |
111 | Present
112 | 2013
113 | 2012
114 | 2011
115 | 2010
116 | 2009
117 | 2008
118 | 2007
119 | 2006
120 | 2005
121 | 2004
122 | 2003
123 | 2002
124 | 2001
125 | 2000
126 | 1999
127 | 1998
128 | 1997
129 | 1996
130 | 1995
131 | 1994
132 |
133 |
134 |
135 |
172 |
173 |
174 |
175 |
176 |
Size:
177 |
zoom factor:
178 |
zoom width:
179 |
180 |
181 |
185 |
186 |
187 |
188 |
189 |
190 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Paperscape
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
41 |
42 |
43 |
44 |
114 |
115 |
123 |
124 |
146 |
147 |
148 |
149 |
Your browser is not modern enough to support this HTML5 canvas element
150 |
151 |
152 | Colouring:
153 |
154 | category
155 | age
156 |
157 |
158 |
159 |
160 |
211 |
212 |
225 |
226 |
227 |
228 |
229 |
Size:
230 |
zoom factor:
231 |
zoom width:
232 |
233 |
234 |
238 |
239 |
240 |
241 |
242 |
243 |
--------------------------------------------------------------------------------
/js/app-build-embed.js:
--------------------------------------------------------------------------------
1 | ({
2 | baseUrl: ".",
3 | paths: {
4 | "jquery": "lib/jquery-3.1.1.min",
5 | "jquery.mousewheel": "lib/jquery.mousewheel",
6 | "jquery.autocomplete": "lib/jquery-ui-autocomplete.min",
7 | "jquery.autocomplete.html": "lib/jquery-ui-autocomplete.html",
8 | "requireLib": "lib/require"
9 | },
10 | shim: {
11 | "jquery.mousewheel": {
12 | deps: ["jquery"]
13 | },
14 | "jquery.autocomplete": {
15 | deps: ["jquery"]
16 | },
17 | "jquery.autocomplete.html": {
18 | deps: ["jquery", "jquery.autocomplete"]
19 | }
20 | },
21 | wrapShim: true,
22 | name: "app/config-embed",
23 | include: "requireLib",
24 | out: "app-build/pscp-embed.js"
25 | })
26 |
--------------------------------------------------------------------------------
/js/app-build.js:
--------------------------------------------------------------------------------
1 | ({
2 | baseUrl: ".",
3 | paths: {
4 | "jquery": "lib/jquery-3.1.1.min",
5 | "jquery.mousewheel": "lib/jquery.mousewheel",
6 | "jquery.autocomplete": "lib/jquery-ui-autocomplete.min",
7 | "jquery.autocomplete.html": "lib/jquery-ui-autocomplete.html",
8 | "requireLib": "lib/require"
9 | },
10 | shim: {
11 | "jquery.mousewheel": {
12 | deps: ["jquery"]
13 | },
14 | "jquery.autocomplete": {
15 | deps: ["jquery"]
16 | },
17 | "jquery.autocomplete.html": {
18 | deps: ["jquery", "jquery.autocomplete"]
19 | }
20 | },
21 | wrapShim: true,
22 | name: "app/config",
23 | include: "requireLib",
24 | out: "app-build/pscp.js"
25 | })
26 |
--------------------------------------------------------------------------------
/js/coffee/Vec2D.coffee:
--------------------------------------------------------------------------------
1 | # VEC2D module
2 | #
3 | # A simple two-dimensional vector for convenience
4 |
5 | define () ->
6 |
7 | # Simple 2D vector class (extend as needed)
8 | class Vec2D
9 | constructor: (x, y) ->
10 | @x = x
11 | @y = y
12 |
13 | copy: -> new Vec2D(@x, @y)
14 |
15 | add: (v) -> new Vec2D(@x + v.x, @y + v.y)
16 |
17 | sub: (v) -> new Vec2D(@x - v.x, @y - v.y)
18 |
19 | mul: (c) -> new Vec2D(@x * c , @y * c )
20 |
21 | scale: (cx,cy) -> new Vec2D(@x * cx , @y * cy )
22 |
23 | round: -> new Vec2D(Math.round(@x) , Math.round(@y))
24 |
25 | floor: -> new Vec2D(Math.floor(@x) , Math.floor(@y))
26 |
27 | ceil : -> new Vec2D(Math.ceil(@x) , Math.ceil(@y))
28 |
29 | len: -> Math.sqrt(@x*@x + @y*@y)
30 |
31 | return Vec2D
32 |
--------------------------------------------------------------------------------
/js/coffee/Vec2D_test.coffee:
--------------------------------------------------------------------------------
1 | define ['app/Vec2D'], (Vec2D) ->
2 | run : ->
3 | module "Vec2D"
4 |
5 | v1 = new Vec2D(3,4.5)
6 | v2 = new Vec2D(9,-6)
7 | v3 = new Vec2D(-3.456,+9.9)
8 | v4 = new Vec2D(3,4)
9 |
10 | test "Arithmetic", ->
11 | vRes = v1.add(v2)
12 | equal vRes.x, 12,
13 | "add: v1.x + v2.x = v3.x"
14 | equal vRes.y, -1.5,
15 | "add: v1.y + v2.y = v3.y"
16 | deepEqual vRes,new Vec2D(12,-1.5),
17 | "add: v1 + v2 = v3"
18 | vRes = v1.sub(v2)
19 | equal vRes.x, -6,
20 | "sub: v1.x - v2.x = v3.x"
21 | equal vRes.y, 10.5,
22 | "sub: v1.y - v2.y = v3.y"
23 | deepEqual vRes,new Vec2D(-6,10.5),
24 | "sub: v1 - v2 = v3"
25 | vRes = v1.mul(10)
26 | equal vRes.x, 30,
27 | "mul: v1.x * k = v2.x"
28 | equal vRes.y, 45,
29 | "mul: v1.y * k = v2.y"
30 | deepEqual vRes, new Vec2D(30,45),
31 | "mul: v1 *k = v2"
32 | vScale = v1.scale(4,-2)
33 | equal vScale.x, 12,
34 | "scale: v1.x * kx = v2.x"
35 | equal vScale.y, -9,
36 | "scale: v1.y * ky = v2.y"
37 | deepEqual vScale, new Vec2D(12,-9),
38 | "scale: v1 * (kx,ky) = v2"
39 | vRound = v3.round()
40 | equal vRound.x, -3,
41 | "round x component"
42 | equal vRound.y, 10,
43 | "round y component"
44 | deepEqual vRound, new Vec2D(-3,10),
45 | "round"
46 | vLen = v4.len()
47 | equal vLen,5,
48 | "length"
49 |
--------------------------------------------------------------------------------
/js/coffee/ajax.coffee:
--------------------------------------------------------------------------------
1 | # AJAX module
2 | #
3 | # Performs ajax requests. Holds no state except for server address.
4 |
5 | define ['jquery'], ($) ->
6 |
7 | exports = {}
8 |
9 | DEFAULT_TIMEOUT = 10000
10 | DEFAULT_URL_EXTENSION = "/wombat"
11 | DEFAULT_SERVER = "http://paperscape.org"
12 |
13 | server = $("#pscpConfig").data("server") ? DEFAULT_SERVER
14 | timeout = $("#pscpConfig").data("ajaxTimeout") ? DEFAULT_TIMEOUT
15 |
16 | ajaxSuccess = (data, status, xhr, callback) ->
17 | if callback?
18 | callback(data.r)
19 |
20 | ajaxStaticSuccess = (data, status, xhr, callback) ->
21 | if callback?
22 | callback(data)
23 |
24 | ajaxError = (status, xhr, callback) ->
25 | console.log(status,xhr)
26 | if callback?
27 | callback(status)
28 |
29 | exports.getServer = ->
30 | server
31 |
32 | exports.setTimeout = (newTimeout) ->
33 | timeout = newTimeout
34 |
35 | exports.setServer = (newServer) ->
36 | server = newServer
37 |
38 | exports.setDefaults = () ->
39 | server = DEFAULT_SERVER
40 | timeout = DEFAULT_TIMEOUT
41 |
42 | exports.doRequest = (inputData, successCallback, errorCallback, config) ->
43 | #console.log("ajax requ", inputData);
44 | urlExt = DEFAULT_URL_EXTENSION
45 | reqServer = server
46 | reqTimeout = timeout
47 | reqJsonpCallback = null
48 | reqJsonpStatic = false
49 | doPOST = false
50 | if config?
51 | if config.doPOST?
52 | doPOST = config.doPOST
53 | if config.urlExt?
54 | urlExt = config.urlExt
55 | if config.timeout?
56 | reqTimeout = config.timeout
57 | if config.server?
58 | reqServer = config.server
59 | if config.jsonpCallback?
60 | reqJsonpCallback = config.jsonpCallback
61 | if config.jsonpStatic?
62 | reqJsonpStatic = config.jsonpStatic
63 | url = reqServer + urlExt
64 | # jQuery 1.5 supports jsonp ajax fails provided a timeout is specified!
65 | # see http://www.haykranen.nl/2011/02/25/jquery-1-5-and-jsonp-requests/
66 | ajaxParams =
67 | url: url
68 | data: inputData
69 | success: (data, textStatus, xhr) ->
70 | ajaxSuccess(data, textStatus, xhr, successCallback)
71 | error: (xhr, textStatus, errorThrown) ->
72 | ajaxError(textStatus, xhr, errorCallback)
73 | dataType: "jsonp"
74 | timeout: reqTimeout
75 | if doPOST
76 | ajaxParams.type = "POST"
77 | ajaxParams.crossDomain = true
78 | ajaxParams.dataType = "json"
79 | if reqJsonpCallback?
80 | ajaxParams.jsonpCallback = reqJsonpCallback
81 | if reqJsonpStatic
82 | ajaxParams.success = (data, textStatus, xhr) ->
83 | ajaxStaticSuccess(data, textStatus, xhr, successCallback)
84 | $.ajax(ajaxParams)
85 |
86 | return exports
87 |
--------------------------------------------------------------------------------
/js/coffee/ajax_test.coffee:
--------------------------------------------------------------------------------
1 | define ['app/ajax'], (AJAX) ->
2 | run : ->
3 | module "AJAX"
4 |
5 | testServer = "http://paperscape.org"
6 | failServer = "http://paperscape.org:6666"
7 |
8 | test "GET callback: success", ->
9 | stop()
10 | AJAX.setServer(testServer)
11 | inputData =
12 | test: true
13 | testCallback = (data) ->
14 | equal data.test, "success",
15 | "Test property set to success"
16 | equal data.POST, false,
17 | "POST data set to false"
18 | start()
19 | errorCallback = (status) ->
20 | ok false,
21 | "Callback error: " + status
22 | start()
23 | AJAX.doRequest(inputData, testCallback, errorCallback)
24 |
25 | test "POST callback: success", ->
26 | stop()
27 | AJAX.setServer(testServer)
28 | inputData =
29 | test: true
30 | config =
31 | doPOST: true
32 | testCallback = (data) ->
33 | equal data.test, "success",
34 | "Test property set to success"
35 | equal data.POST, true,
36 | "POST data set to true"
37 | start()
38 | errorCallback = (status) ->
39 | ok false,
40 | "Callback error: " + status
41 | start()
42 | AJAX.doRequest(inputData, testCallback, errorCallback, config)
43 |
44 | asyncTest "Callback: error", ->
45 | AJAX.setServer(failServer)
46 | expect(1)
47 | inputData =
48 | test: true
49 | config =
50 | timeout: 1
51 | testCallback = (status) ->
52 | #equal status, "timeout",
53 | equal status, "error",
54 | "Status set to error"
55 | start()
56 | AJAX.doRequest(inputData, null, testCallback, config)
57 |
--------------------------------------------------------------------------------
/js/coffee/config-embed.coffee:
--------------------------------------------------------------------------------
1 | requirejs.config
2 | baseUrl: 'js'
3 | paths:
4 | 'jquery': "lib/jquery-3.1.1"
5 | 'jquery.mousewheel': "lib/jquery.mousewheel"
6 | 'jquery.autocomplete': "lib/jquery-ui-autocomplete.min"
7 | 'jquery.autocomplete.html': "lib/jquery-ui-autocomplete.html"
8 | shim:
9 | "jquery.mousewheel":
10 | deps: ["jquery"]
11 | "jquery.autocomplete":
12 | deps: ["jquery"]
13 | "jquery.autocomplete.html":
14 | deps: ["jquery","jquery.autocomplete"]
15 |
16 | requirejs ['app/main-embed']
17 |
--------------------------------------------------------------------------------
/js/coffee/config.coffee:
--------------------------------------------------------------------------------
1 | requirejs.config
2 | baseUrl: 'js'
3 | paths:
4 | 'jquery': "lib/jquery-3.1.1"
5 | 'jquery.mousewheel': "lib/jquery.mousewheel"
6 | 'jquery.autocomplete': "lib/jquery-ui-autocomplete.min"
7 | 'jquery.autocomplete.html': "lib/jquery-ui-autocomplete.html"
8 | shim:
9 | "jquery.mousewheel":
10 | deps: ["jquery"]
11 | "jquery.autocomplete":
12 | deps: ["jquery"]
13 | "jquery.autocomplete.html":
14 | deps: ["jquery","jquery.autocomplete"]
15 |
16 | requirejs ['app/main']
17 |
--------------------------------------------------------------------------------
/js/coffee/config_unit_test.coffee:
--------------------------------------------------------------------------------
1 | requirejs.config
2 | baseUrl: 'js'
3 | paths:
4 | 'jquery': "lib/jquery-3.1.1.min"
5 | 'jquery.mousewheel': "lib/jquery.mousewheel"
6 | 'QUnit': 'lib/qunit-1.11.0'
7 | shim:
8 | "jquery.mousewheel":
9 | deps: ["jquery"]
10 | exports: "jQuery.mousewheel"
11 | 'QUnit':
12 | exports: 'QUnit'
13 | init: ->
14 | QUnit.config.autoload = false
15 | QUnit.config.autostart = false
16 |
17 |
18 | # require the unit tests.
19 | requirejs [
20 | 'QUnit'
21 | 'tests/Vec2D_test'
22 | 'tests/ajax_test'
23 | 'tests/world_test'
24 | 'tests/mapview_test'
25 | ], (QUNIT, VEC2D_TEST, AJAX_TEST, WORLD_TEST, MAPVIEW_TEST) ->
26 |
27 | # run the tests:
28 | VEC2D_TEST.run()
29 | AJAX_TEST.run()
30 | WORLD_TEST.run()
31 | MAPVIEW_TEST.run()
32 |
33 | # start QUnit:
34 | QUNIT.load()
35 | QUNIT.start()
36 |
--------------------------------------------------------------------------------
/js/coffee/detailview.coffee:
--------------------------------------------------------------------------------
1 | # DETAILVIEW module
2 | #
3 | #
4 |
5 | define ['app/mapview','jquery'], (MAPVIEW,$) ->
6 |
7 | exports = {}
8 |
9 | detailViewVisible = false
10 |
11 | ###########################################################################
12 | # Public (exports)
13 | ###########################################################################
14 |
15 | exports.reload = ->
16 | if detailViewVisible
17 | zoomx = MAPVIEW.getXZoom()
18 | $("#detailView .zoomx").html(zoomx.toFixed(2))
19 | dims = MAPVIEW.getWorldDimensions()
20 | $("#detailView .dims").html("#{dims.x} x #{dims.y}")
21 | $("#detailView .viewx").html((dims.x/zoomx).toFixed(0))
22 |
23 | exports.show = ->
24 | if not detailViewVisible
25 | detailViewVisible = true
26 | exports.reload()
27 | $("#detailView").show()
28 |
29 | exports.hide = ->
30 | detailViewVisible = false
31 | $("#detailView").hide()
32 |
33 | exports.toggle = ->
34 | if detailViewVisible
35 | exports.hide()
36 | else
37 | exports.show()
38 |
39 | return exports
40 |
--------------------------------------------------------------------------------
/js/coffee/fadeview.coffee:
--------------------------------------------------------------------------------
1 | # FADEVIEW module
2 | #
3 | # Popup that displays information about the current WORLD.
4 | # Fades out view when user zooms in.
5 |
6 | define ['app/world','jquery'], (WORLD,$) ->
7 |
8 | exports = {}
9 |
10 | # for key-popup state; could be moved to its own module
11 | welcomePopupVisible = false
12 |
13 | ###########################################################################
14 | # Public (exports)
15 | ###########################################################################
16 |
17 | exports.show = ->
18 | if not welcomePopupVisible
19 | numPapers = "" + WORLD.getNumberArxivPapers()
20 | if numPapers.length > 6
21 | numPapers = numPapers[0...-6] + "," + numPapers[-6..]
22 | if numPapers.length > 3
23 | numPapers = numPapers[0...-3] + "," + numPapers[-3..]
24 | lastDownloadDate = WORLD.getLastDownloadDate()
25 | $("#welcomePopup .total").html(numPapers)
26 | $("#welcomePopup .update").html(lastDownloadDate)
27 | $("#welcomePopup").stop().fadeIn()
28 | welcomePopupVisible = true
29 |
30 | exports.hide = ->
31 | if welcomePopupVisible
32 | $("#welcomePopup").stop().fadeOut()
33 | welcomePopupVisible = false
34 |
35 | return exports
36 |
--------------------------------------------------------------------------------
/js/coffee/infoview.coffee:
--------------------------------------------------------------------------------
1 | # INFOVIEW module
2 | #
3 | # Popup that displays information about a selected paper.
4 |
5 | define ['app/selected','app/world','app/search','jquery','jquery.mousewheel'], (SELECTED,WORLD,SEARCH,$) ->
6 |
7 | MAX_AUTHORS = 12
8 |
9 | exports = {}
10 |
11 | metas = []
12 |
13 | class Meta
14 | constructor: (id) ->
15 | @id = id
16 | @numRefs = 0
17 | @numCites = 0
18 | @title = "Loading..."
19 | @authors = "Loading..."
20 | @journal = ""
21 | @arxivId = ""
22 | @categories = ""
23 | @inspire = ""
24 | @abstract = null
25 | @loaded = false
26 |
27 | load: =>
28 | if @loaded
29 | infoPopup(this)
30 | else
31 | callback = (data) =>
32 | @id = data.id
33 | @numRefs = data.numRefs
34 | @numCites = data.numCites
35 | @title = data.title
36 | @authors = data.authors
37 | @journal = data.journal
38 | @arxivId = data.arxivId
39 | @categories = data.categories
40 | @inspire = data.inspire
41 | @loaded = true
42 | infoPopup(this)
43 | WORLD.fetchMetaForPaperId(@id, callback)
44 |
45 | loadAbstract: =>
46 | if @abstract?
47 | abstractPopup(this)
48 | else
49 | callback = (data) =>
50 | @abstract = data.abstract
51 | abstractPopup(this)
52 | WORLD.fetchAbstractForPaperId(@id, callback)
53 |
54 | inspireURL: =>
55 | if @inspire != ""
56 | return "http://inspirehep.net/record/" + @inspire
57 | else if @arxivId != ""
58 | if @arxivId.length in [9,10] and @arxivId[4] == "."
59 | return "http://inspirehep.net/search?p=find+eprint+arxiv%3A" + @arxivId
60 | else
61 | return "http://inspirehep.net/search?p=find+eprint+" + @arxivId
62 | else
63 | return null
64 |
65 | findMetaById = (id) =>
66 | # TODO binary search
67 | for meta in metas when meta.id == id
68 | return meta
69 | null
70 |
71 | getMeta = (id) =>
72 | meta = findMetaById(id)
73 | if not meta?
74 | meta = new Meta(id)
75 | metas.push(meta)
76 | meta
77 |
78 | infoPopup = (meta) ->
79 | if meta?
80 | #console.log meta
81 | $("#infoPopup .title").html(makePrettyTitle(meta.title))
82 | makePrettyAuthors(meta.authors)
83 | publInfo = makePrettyJournal(meta.journal)
84 | if publInfo[0].length > 0
85 | $("#infoPopup .journal").show()
86 | $("#infoPopup .journalName").html(publInfo[0])
87 | if publInfo[1].length == 0
88 | $("#infoPopup .icoDoi").hide()
89 | else
90 | $("#infoPopup .icoDoi").show().attr("href","http://dx.doi.org/" + publInfo[1])
91 | else
92 | $("#infoPopup .journal").hide()
93 | if meta.arxivId?
94 | $("#infoPopup .arxiv").show()
95 | html = meta.arxivId
96 | if meta.categories != ""
97 | html += " [" + meta.categories + "]"
98 | $("#infoPopup .arxivId").html(html)
99 | $("#infoPopup .icoPDF").show().attr("href", "http://arxiv.org/pdf/" + meta.arxivId)
100 | $("#infoPopup .icoArxiv").show().attr("href", "http://arxiv.org/abs/" + meta.arxivId)
101 | else
102 | $("#infoPopup .arxiv").hide()
103 | mypscpURL = "http://my.paperscape.org/?s=#{meta.arxivId}"
104 | $("#infoPopup .icoMypscp").attr("href", mypscpURL)
105 |
106 | inspireURL = meta.inspireURL()
107 | if inspireURL?
108 | $("#infoPopup .inspire").show()
109 | $("#infoPopup .icoInspire").attr("href", inspireURL)
110 | else
111 | $("#infoPopup .inspire").hide()
112 | $("#infoPopup .showAbstract").show()
113 | $("#infoPopup .abstract").hide()
114 | $("#infoPopup").show()
115 |
116 | $("#infoPopup .showReferences").html("references (" + meta.numRefs + ")")
117 | $("#infoPopup .showCitations").html("citations (" + meta.numCites + ")")
118 |
119 | abstractPopup = (meta) ->
120 | if meta?.abstract?
121 | $("#infoPopup .abstract").html(meta.abstract).show()
122 | $("#infoPopup .showAbstract").hide()
123 |
124 | # used to parse latex in titles
125 | islower = (c) -> 'a' <= c and c <= 'z'
126 |
127 | makePrettyAuthors = (authors) ->
128 | # initially hide all author links
129 | for i in [1..13]
130 | $("#infoPopup .auth" + i).css('cursor','default').hide()
131 |
132 | if authors == undefined or authors == null or authors == "(unknown authors)"
133 | $("#infoPopup .auth1").html("(unknown authors)").show()
134 | return
135 |
136 | authList = authors.split(',')
137 | if authList.length != 0
138 | # truncate author list at a maximum of MAX_AUTHORS authors
139 | extraAuth = 0
140 | if authList.length > MAX_AUTHORS
141 | extraAuth = authList.length - (MAX_AUTHORS - 2)
142 | authList = authList.slice(0, (MAX_AUTHORS - 2))
143 |
144 | for au, i in authList
145 | htmlAuth = ""
146 | # and add author name to the lists
147 | dot = au.lastIndexOf('.')
148 | if dot >= 0
149 | # put a (non-breaking) space between initials and last name
150 | auPre = au.slice(0, dot + 1)
151 | auPost = au.slice(dot + 1)
152 | authList[i] = auPre + " " + auPost
153 | htmlAuth += auPre + " " + auPost
154 | else
155 | #authorListLastname.push(au)
156 | htmlAuth += au
157 | # add comma separator between authors
158 | htmlAuth += ", " if i < (authList.length - 1)
159 | $("#infoPopup .auth" + (i+1)).html(htmlAuth).css('cursor','pointer').show()
160 |
161 | # if extra authors, say so
162 | if extraAuth > 0
163 | htmlAuth = " and " + extraAuth + " more authors"
164 | $("#infoPopup .auth" + (authList.length+1)).html(htmlAuth).show()
165 |
166 | makePrettyTitle = (title) ->
167 | if title == undefined or title == null
168 | return "(unknown title)"
169 |
170 | # split the title into words, so we can draw it on multiple lines in the canvas
171 | title = title
172 | titleWords = title.split(' ')
173 |
174 | # remove $'s from title words (we don't interpret them when rendering the title on the canvas)
175 | for ti, i in titleWords
176 | titleWords[i] = titleWords[i].replace(/\$/g, "")
177 |
178 | # parse some latex math elements in the title for nicer printing
179 | mathMode = false
180 | parsedTitle = ""
181 | i = 0
182 | while i < title.length
183 | if title[i] == '{' or title[i] == '}'
184 | # ignore open/close braces
185 | i += 1
186 | else if title[i] == '$'
187 | if mathMode
188 | parsedTitle += ""
189 | mathMode = false
190 | else
191 | parsedTitle += ""
192 | mathMode = true
193 | i += 1
194 | else if mathMode and i + 1 < title.length
195 | needArg = false
196 | if title[i] == '_'
197 | parsedTitle += ""
198 | needArg = true
199 | else if title[i] == '^'
200 | parsedTitle += ""
201 | needArg = true
202 | else if title[i] == '\\' and (title[i + 1] == '{' or title[i + 1] == '}')
203 | parsedTitle += title[i + 1]
204 | i += 1
205 | else if title[i] == '\\' and islower(title[i + 1])
206 | i2 = i + 2
207 | while i2 < title.length and islower(title[i2])
208 | i2 += 1
209 | if i2 >= title.length
210 | # need at least one char for the argument... bit of a hack
211 | i2 = title.length - 1
212 | ctrl = title.slice(i + 1, i2)
213 | needArg = true
214 | if ctrl == "bar"
215 | parsedTitle += ""
216 | i = i2 - 1
217 | else
218 | # unknown control command, just add the slash and keep going
219 | parsedTitle += title[i]
220 | needArg = false
221 | # skip space after control word
222 | if needArg and i + 2 < title.length and title[i + 1] == ' '
223 | i += 1
224 | else
225 | parsedTitle += title[i]
226 | i += 1
227 | if needArg
228 | if title[i] == '{'
229 | j = i + 1
230 | while j < title.length and title[j] != '}'
231 | j++
232 | if j < title.length
233 | # found an argument in braces
234 | parsedTitle += title.slice(i + 1, j)
235 | i = j + 1
236 | else
237 | # unmatched open brace; just emit the open brace as the argument
238 | parsedTitle += '{'
239 | i += 1
240 | else
241 | # single character argument
242 | parsedTitle += title[i]
243 | i += 1
244 | parsedTitle += " "
245 | else if title[i] == '\\'
246 | parsedTitle += title.slice(i, i + 2)
247 | i += 2
248 | else
249 | parsedTitle += title[i]
250 | i += 1
251 | if mathMode
252 | parsedTitle += " "
253 | title = parsedTitle
254 |
255 | return title
256 |
257 | makePrettyJournal = (publInfo) ->
258 | if publInfo == undefined or publInfo == null or publInfo.length == 0
259 | return ["", ""]
260 |
261 | journal = ""
262 | doi = ""
263 |
264 | publInfo = publInfo.split('#')
265 | if publInfo.length >= 2
266 | journal = publInfo[0].split(',')
267 | if journal[0].length > 0 and journal[1].length > 1
268 | jname = journal[0][0]
269 | lastUpper = false
270 | for i in [1 ... journal[0].length]
271 | c = journal[0].charCodeAt(i)
272 | if 65 <= c and c <= 90
273 | # upper case
274 | jname += "."
275 | lastUpper = true
276 | else
277 | lastUpper = false
278 | jname += journal[0][i]
279 | if not lastUpper or jname[jname.length - 1] == 'J'
280 | jname += ". "
281 | else
282 | jname += " "
283 | if journal.length == 3
284 | # no jpage
285 | journal = jname + journal[2] + " (" + journal[1] + ")"
286 | else
287 | # has jpage
288 | journal = jname + journal[2] + " (" + journal[1] + ") " + journal[3]
289 | if publInfo[1].length > 0
290 | doi = publInfo[1]
291 |
292 | return [journal, doi]
293 |
294 | ###########################################################################
295 | # Public (exports)
296 | ###########################################################################
297 |
298 | exports.close = ->
299 | SELECTED.clearSelection()
300 | exports.update()
301 |
302 | exports.update = ->
303 | if SELECTED.isSelected()
304 | # hide the about popup as they share screen realestate
305 | $("#aboutPopup").hide()
306 | meta = getMeta(SELECTED.getSelectedId())
307 | meta.load()
308 | else
309 | $("#infoPopup").hide()
310 |
311 | exports.showAbstract = ->
312 | if SELECTED.isSelected()
313 | meta = getMeta(SELECTED.getSelectedId())
314 | meta.loadAbstract()
315 |
316 | exports.showReferences = (callback) ->
317 | if SELECTED.isSelected()
318 | arXivStr = findMetaById(SELECTED.getSelectedId())?.arxivId
319 | if arXivStr?
320 | SEARCH.setSearch("?refs #{arXivStr}")
321 | SEARCH.doSearch(callback)
322 |
323 | exports.showCitations = (callback) ->
324 | if SELECTED.isSelected()
325 | arXivStr = findMetaById(SELECTED.getSelectedId())?.arxivId
326 | if arXivStr?
327 | SEARCH.setSearch("?cites #{arXivStr}")
328 | SEARCH.doSearch(callback)
329 |
330 | exports.searchAuthor = (index,callbackPass) ->
331 | if SELECTED.isSelected()
332 | meta = getMeta(SELECTED.getSelectedId())
333 | authList = meta.authors.split(',')
334 | if authList.length <= MAX_AUTHORS or (index <= (MAX_AUTHORS - 2))
335 | authStr = authList[index-1]
336 | if authStr?
337 | SEARCH.setSearch("?author #{authStr}")
338 | SEARCH.doSearch(callbackPass)
339 |
340 |
341 | return exports
342 |
--------------------------------------------------------------------------------
/js/coffee/input.coffee:
--------------------------------------------------------------------------------
1 | # INPUT module
2 | #
3 | # Contains functions relating to user input e.g. mouse clicks, interface buttons etc.
4 |
5 | define ['app/Vec2D','app/selected','app/world','app/search','app/mapview','app/infoview','jquery','jquery.mousewheel'], (Vec2D,SELECTED,WORLD,SEARCH,MAPVIEW,INFOVIEW,$) ->
6 |
7 | exports = {}
8 |
9 | prevMouseEvent = null
10 | mouseDragged = false
11 | touchPrevNumTouches = 0
12 | touchPrevPos = null
13 | touchPrevDoubleDistance = null
14 | touchMoveCallback = null
15 |
16 | #############################################################
17 | # public methods involving top form and related buttons
18 | #############################################################
19 |
20 | # Called when the user submits a search query
21 | exports.formSearchSubmit = (event) ->
22 | event.preventDefault()
23 | callbackPass = ->
24 | MAPVIEW.draw()
25 | SEARCH.doSearch(callbackPass)
26 |
27 | exports.doExampleSearch = (searchTerm) ->
28 | callbackPass = ->
29 | MAPVIEW.draw()
30 | $("#formSearch input:text")[0].value = searchTerm
31 | SEARCH.doSearch(callbackPass)
32 |
33 | #############################################################
34 | # public methods involving canvas buttons
35 | #############################################################
36 |
37 | exports.canvasZoomInButton = (event) ->
38 | MAPVIEW.doZoomBy(null, 1 + 0.1 )
39 | MAPVIEW.draw()
40 |
41 | exports.canvasZoomOutButton = (event) ->
42 | MAPVIEW.doZoomBy(null, 1 - 0.1 )
43 | MAPVIEW.draw()
44 |
45 | exports.canvasColourSchemeSelect = (event) ->
46 | value = $("#colourSchemeSelect .select").val()
47 | if value == "paper_age"
48 | MAPVIEW.setHeatmap(true)
49 | $("#keyPopup .category").hide()
50 | $("#keyPopup .age").show()
51 | else
52 | MAPVIEW.setHeatmap(false)
53 | $("#keyPopup .age").hide()
54 | $("#keyPopup .category").show()
55 | MAPVIEW.draw()
56 |
57 | #############################################################
58 | # public methods to involving mouse
59 | #############################################################
60 |
61 | exports.iDevice = false
62 |
63 | exports.touchStart = (event) ->
64 | event.preventDefault()
65 | if event.touches.length == 1
66 | t0 = event.touches[0]
67 | touchPrevPos = {pageX:t0.pageX, pageY:t0.pageY, shiftKey:false, preventDefault:(->)}
68 | exports.mouseDown(touchPrevPos)
69 |
70 | exports.touchEnd = (event) ->
71 | event.preventDefault()
72 | if event.touches.length != 0 or touchPrevPos == null
73 | # only process a touchEnd request when all touches have finished
74 | return
75 | exports.mouseUp(touchPrevPos)
76 | touchPrevDoubleDistance = null
77 | touchPrevPos = null
78 |
79 | exports.touchMove = (event) ->
80 | event.preventDefault()
81 |
82 | # turn the touch into a centre pos and an optional distance
83 | centre
84 | dist
85 | if event.touches.length == 1
86 | t0 = event.touches[0]
87 | centre = {pageX:t0.pageX, pageY:t0.pageY, shiftKey:false}
88 | dist = null
89 | else if event.touches.length == 2
90 | t0 = event.touches[0]
91 | t1 = event.touches[1]
92 | centre = {pageX:(t0.pageX + t1.pageX) / 2, pageY:(t0.pageY + t1.pageY) / 2, shiftKey:false, preventDefault:(->)}
93 | dist = Math.sqrt(Math.pow(t0.pageX - t1.pageX, 2) + Math.pow(t0.pageY - t1.pageY, 2))
94 | else
95 | centre = null
96 | dist = null
97 |
98 | if event.touches.length != touchPrevNumTouches
99 | # if the user change the number of fingers in the touch, we reset the motion variables
100 | touchPrevNumTouches = event.touches.length
101 | touchPrevDoubleDistance = null
102 | else
103 | if touchMoveCallback != null
104 | touchMoveCallback(centre)
105 | if dist != null and touchPrevDoubleDistance != null
106 | distDiff = dist - touchPrevDoubleDistance
107 | # TODO this code should be same as mouseWheel!! (and it's not)
108 | MAPVIEW.doZoomBy(centre, 1 + distDiff / 100)
109 | MAPVIEW.draw()
110 | touchPrevPos = centre
111 | touchPrevDoubleDistance = dist
112 | prevMouseEvent = centre
113 |
114 | exports.bindMouseMove = (callback) ->
115 | if exports.iDevice
116 | touchMoveCallback = callback
117 | else
118 | MAPVIEW.jQueryAttach().mousemove(callback)
119 |
120 | exports.cancelMouseMove = () ->
121 | if exports.iDevice
122 | touchMoveCallback = null
123 | else
124 | MAPVIEW.jQueryAttach().off("mousemove")
125 | if MAPVIEW.highVerbosity()
126 | MAPVIEW.draw()
127 |
128 | exports.mouseDown = (event) ->
129 | event.preventDefault()
130 | mouseDragged = false
131 | # Uncomment to get position of clicks:
132 | #console.log(MAPVIEW.getEventWorldPosition(event).round())
133 | if MAPVIEW.highVerbosity()
134 | MAPVIEW.draw()
135 | # call the pan function when the mouse moves
136 | exports.bindMouseMove(exports.mouseMovePan)
137 | prevMouseEvent = {pageX:event.pageX, pageY:event.pageY}
138 |
139 | exports.mouseUp = (event) ->
140 | if not exports.iDevice
141 | event.preventDefault()
142 |
143 | exports.cancelMouseMove() # disable the mouse move callback
144 |
145 | if not mouseDragged
146 |
147 | pos = MAPVIEW.getEventWorldPosition(event)
148 |
149 | # If user clicks within white search halo, find closest search
150 | # result, which will be used if available
151 | searchData = null
152 | if SEARCH.areSearchResults() and !SEARCH.isParentLinkResult()
153 | searchData = SEARCH.closestResultWithinRadius(pos,MAPVIEW.getSearchHaloRad())
154 |
155 | if searchData?
156 | SELECTED.setSelection(searchData)
157 | MAPVIEW.draw()
158 | INFOVIEW.update()
159 | else
160 | isPaperCallback = (data) ->
161 | if data.id != 0
162 | SELECTED.setSelection(data)
163 | #else if searchData?.id != 0
164 | # elseSELECTED.setSelection(searchData)
165 | else
166 | SELECTED.clearSelection()
167 | MAPVIEW.draw()
168 | INFOVIEW.update()
169 |
170 | WORLD.fetchPaperIdAtLocation(pos.x,pos.y,isPaperCallback)
171 |
172 | exports.mouseLeave = (event) ->
173 | # disable the mouse move callback
174 | exports.cancelMouseMove()
175 |
176 | exports.mouseMovePan = (event) ->
177 | threshold = 1
178 | if Math.abs(event.pageX - prevMouseEvent.pageX) > threshold or Math.abs(event.pageY - prevMouseEvent.pageY) > threshold
179 | mouseDragged = true
180 | MAPVIEW.lowVerbosity()
181 |
182 | MAPVIEW.doMousePan(event, prevMouseEvent)
183 | MAPVIEW.draw()
184 |
185 | prevMouseEvent = {pageX:event.pageX, pageY:event.pageY}
186 |
187 | # Called when canvas is double clicked
188 | exports.mouseDoubleClick = (event) ->
189 | event.preventDefault()
190 | # zoom in using animation
191 | MAPVIEW.animateZoomIn(event, 1.10, 10, 25)
192 |
193 | # Called when mouse is scrolled over canvas
194 | exports.mouseWheel = (event, delta, deltaX, deltaY) ->
195 | event.preventDefault()
196 | if delta
197 | # hack to fix fast Mac scrolling; TODO fix it properly!
198 | if Math.abs(delta) > 100
199 | delta /= 40
200 | if delta < -3
201 | delta = -3
202 | if delta > 3
203 | delta = 3
204 |
205 | MAPVIEW.doZoomBy(event, 1 + delta / 10)
206 | MAPVIEW.draw()
207 |
208 | #############################################################
209 | # public methods for key presses
210 | #############################################################
211 |
212 | exports.keypressArrow = (key) ->
213 | dx = 0
214 | dy = 0
215 | if key == "left"
216 | dx = -1
217 | else if key == "right"
218 | dx = 1
219 | else if key == "up"
220 | dy = -1
221 | else if key == "down"
222 | dy = 1
223 | # pan the graph
224 | MAPVIEW.doKeyPan(new Vec2D(dx, dy))
225 | MAPVIEW.draw()
226 |
227 | exports.keypressZoom = (key) ->
228 | dz = 0
229 | if key == "=" or key == "+"
230 | dz = 1
231 | else if key == "-"
232 | dz = -1
233 |
234 | # zoom normally
235 | MAPVIEW.doZoomBy(null, 1 + 0.1 * dz)
236 | MAPVIEW.draw()
237 |
238 | #exports.keypressHeatmap = (key) ->
239 | # if MAPVIEW.isHeatmap()
240 | # MAPVIEW.setHeatmap(false)
241 | # else
242 | # MAPVIEW.setHeatmap(true)
243 |
244 | # MAPVIEW.draw()
245 |
246 | return exports
247 |
--------------------------------------------------------------------------------
/js/coffee/input_test.coffee:
--------------------------------------------------------------------------------
1 | define ['app/input'], (INPUT) ->
2 | run : ->
3 | module "INPUT"
4 |
5 | #test "dummy test of world", ->
6 | # equal WORLD.testFunction(1,2), 3, "return result should be 3"
7 |
--------------------------------------------------------------------------------
/js/coffee/main-embed.coffee:
--------------------------------------------------------------------------------
1 | # MAIN
2 | #
3 | # Module concept:
4 | # Handles non-canvas events, page refresh/resize etc.
5 |
6 | define ['app/ajax','app/mapview','app/infoview','app/input','lib/mousetrap','jquery','jquery.autocomplete','jquery.autocomplete.html'], (AJAX,MAPVIEW,INFOVIEW,INPUT,MOUSETRAP,$) ->
7 |
8 | # Version should match version stored in wombat
9 | # If not, hard reload of page is performed
10 |
11 | # NOTE this is dangerous is javascript not loaded from our server!!
12 | VERSION = "0.1"
13 |
14 | # for keeping track of day change
15 | currentPaperId = null
16 |
17 | resizeDOM = ->
18 | #newWidth = window.innerWidth
19 | #newHeight = window.innerHeight
20 |
21 | # Let xiwiArea be set manually
22 | # set xiwiArea dimensions
23 | xiwiArea = $("#xiwiArea")
24 | newWidth = xiwiArea.width()
25 | newHeight = xiwiArea.height()
26 | #xiwiArea.width(newWidth)
27 | #xiwiArea.height(newHeight)
28 |
29 | # welcome message
30 | #welcomePopup = $("#welcomePopup")
31 | #welcomePopup.css("top", 5 + 'px')
32 |
33 | # colour code
34 | #keyPopup = $("#keyPopup")
35 | #keyPopup.css("left", 30 + 'px')
36 |
37 | # colour scheme select
38 | schemeSelect = $("#colourSchemeSelect")
39 | schemeSelect.css("top", 10 + 'px')
40 |
41 | # zoom buttons
42 | zoomButtons = $("#canvasZoomButtons")
43 | zoomButtons.css("top", newHeight - 32 + 'px')
44 |
45 | # set canvas dimensions
46 | mapviewWidth = Math.round(newWidth)
47 | mapviewHeight = Math.round(newHeight)
48 |
49 | MAPVIEW.resize(mapviewWidth,mapviewHeight,0,0)
50 | MAPVIEW.draw()
51 |
52 | infoPopup = $("#infoPopup")
53 | infoPopupWidth = 400
54 | infoPopup.width(Math.min(infoPopupWidth,mapviewWidth-20))
55 | infoPopup.css("top", 10 + "px")
56 | #infoPopup.css("left", Math.floor(0.5 * (canvasWidth - infoPopupWidth)) + "px")
57 | infoPopup.css("right", "10px")
58 | $("#infoPopup").css("max-height", newHeight - 20)
59 |
60 |
61 | checkDateAndVersion = ->
62 | request =
63 | gdmv: 1
64 | handleSuccess = (ajaxData) ->
65 | # TODO may no be loaded from our server, so do not do hard reset...
66 | #if ajaxData?.v? and ajaxData?.v != VERSION
67 | # # hard reload of page
68 | # location.reload(true)
69 | if ajaxData?.d0?
70 | if currentPaperId? and currentPaperId != ajaxData.d0
71 | MAPVIEW.reload()
72 | # for now do hard reload here too
73 | # else some tiles will still be cached
74 | # in future could consider stamping tiles with date
75 | #location.reload(true)
76 | else
77 | currentPaperId = ajaxData.d0
78 | AJAX.doRequest(request, handleSuccess)
79 |
80 | main = ->
81 | MAPVIEW.initialise()
82 | MAPVIEW.reload()
83 |
84 | # work out if we are running on a portable device
85 | agent = navigator.userAgent.toLowerCase()
86 | INPUT.iDevice = agent.indexOf("iphone") >= 0 or agent.indexOf("ipad") >= 0 or agent.indexOf("android") >= 0
87 |
88 | # resize and orientation change events
89 | window.addEventListener('resize', resizeDOM, false)
90 | window.addEventListener('orientationchange', resizeDOM, false) # for smart phones etc.
91 |
92 | # bind events for the mouse/touch
93 | if INPUT.iDevice
94 | # an iDevice; bind the events for touch
95 | MAPVIEW.getTopCanvas().addEventListener("touchstart", INPUT.touchStart)
96 | MAPVIEW.getTopCanvas().addEventListener("touchend", INPUT.touchEnd)
97 | # since touchmove event only fires when the user is actually touching, we can afford to always have it bound
98 | MAPVIEW.getTopCanvas().addEventListener("touchmove", INPUT.touchMove)
99 | else
100 | # we have a real mouse; use jQuery to bind these events
101 | MAPVIEW.jQueryAttach().mousedown(INPUT.mouseDown)
102 | MAPVIEW.jQueryAttach().mouseup(INPUT.mouseUp)
103 | MAPVIEW.jQueryAttach().mouseleave(INPUT.mouseLeave)
104 | MAPVIEW.jQueryAttach().dblclick(INPUT.mouseDoubleClick)
105 | MAPVIEW.jQueryAttach().mousewheel(INPUT.mouseWheel)
106 |
107 | # mousetrap library, used to capture keys
108 | # see: http://craig.is/killing/mice
109 | if !INPUT.iDevice
110 | #MOUSETRAP.bind("esc", (e, key) -> INPUT.keypressEscape())
111 | #MOUSETRAP.bind("enter", (e, key) -> INPUT.keypressEnter())
112 | MOUSETRAP.bind(["left", "right", "up", "down"], (e, key) -> INPUT.keypressArrow(key))
113 | MOUSETRAP.bind(["=", "+", "-"], (e, key) -> INPUT.keypressZoom(key))
114 |
115 | # show everything
116 | $("#xiwiArea").show()
117 |
118 | # refresh world
119 | #$("#welcomePopup .refresh").click ->
120 | # MAPVIEW.reload()
121 |
122 | ## keypopup
123 | #$("#keyPopup .close").click ->
124 | # $("#keyPopup").hide()
125 | #$("#topRightMenu .info").click ->
126 | # $("#keyPopup").show()
127 | #$("#keyPopup").hide()
128 |
129 | # colour scheme select
130 | $("#colourSchemeSelect .select").val('categories').bind('change', (event) ->
131 | INPUT.canvasColourSchemeSelect(event)
132 | )
133 |
134 | # info popup
135 | $("#infoPopup .close").click ->
136 | INFOVIEW.close()
137 | MAPVIEW.draw()
138 | $("#infoPopup .showAbstract").click ->
139 | INFOVIEW.showAbstract()
140 | INFOVIEW.close()
141 |
142 | # zoom buttons
143 | $("#canvasZoomIn").click ->
144 | INPUT.canvasZoomInButton()
145 |
146 | $("#canvasZoomOut").click ->
147 | INPUT.canvasZoomOutButton()
148 |
149 | # now that the correct items are shown, resize everything to fit correctly
150 | resizeDOM()
151 |
152 | MAPVIEW.draw()
153 |
154 | checkDateAndVersion()
155 |
156 | # Check for version and date when window gets focus
157 | $(window).focus ->
158 | checkDateAndVersion()
159 |
160 | # request animation frame
161 | window.requestAnimationFrame = do ->
162 | workaround = (callback) ->
163 | window.setTimeout(callback, 1000 / 60 )
164 | window.requestAnimationFrame or window.mozRequestAnimationFrame or window.webkitRequestAnimationFrame or window.msRequestAnimationFrame or workaround
165 |
166 |
167 | # use jQuery to run the app
168 | $(document).ready(-> main())
169 |
170 | return {}
171 |
172 |
--------------------------------------------------------------------------------
/js/coffee/main.coffee:
--------------------------------------------------------------------------------
1 | # MAIN
2 | #
3 | # Handles non-canvas events, page refresh/resize etc.
4 |
5 | define ['app/ajax','app/search','app/mapview','app/infoview','app/fadeview','app/detailview','app/newpapersview','app/input','lib/mousetrap','jquery','jquery.autocomplete','jquery.autocomplete.html'], (AJAX,SEARCH,MAPVIEW,INFOVIEW,FADEVIEW,DETAILVIEW,NEWPAPERSVIEW,INPUT,MOUSETRAP,$) ->
6 |
7 | # Version should match version stored in wombat
8 | # If not, hard reload of page is performed
9 | VERSION = "0.3"
10 |
11 | SEARCH_OFFSET_LEFT = 230
12 | SEARCH_BOX_WIDTH_MIN = 200
13 | SEARCH_BOX_WIDTH_MAX = 400
14 | TOP_RIGHT_MENU_WIDTH = 120
15 |
16 | # for keeping track of day change
17 | currentPaperId = null
18 |
19 | # callback to call when MAPVIEW reloaded
20 | mapviewReloadCallback = ->
21 | DETAILVIEW.reload()
22 | if MAPVIEW.showOverlay()
23 | FADEVIEW.show()
24 |
25 | resizeDOM = ->
26 | newWidth = window.innerWidth
27 | newHeight = window.innerHeight
28 |
29 | # set xiwiArea dimensions
30 | xiwiArea = $("#xiwiArea")
31 | xiwiArea.width(newWidth)
32 | xiwiArea.height(newHeight)
33 |
34 |
35 | # set dimensions search and top right menu
36 | topRightMenuWidth = parseInt($("#topRightMenu").width())
37 | if topRightMenuWidth < TOP_RIGHT_MENU_WIDTH
38 | # when the page is first loaded, sometimes the font is not loaded before we compute this
39 | # so hack it to be the right size
40 | topRightMenuWidth = TOP_RIGHT_MENU_WIDTH
41 | #searchBoxWidth = Math.round(newWidth-70-parseInt(searchButton.width())-SEARCH_OFFSET_LEFT-topRightMenuWidth)
42 | searchBoxWidth = Math.round(newWidth-70-parseInt($("#searchButton").width()) - parseInt($("#searchNewPapers").width())-SEARCH_OFFSET_LEFT-TOP_RIGHT_MENU_WIDTH)
43 | if searchBoxWidth < SEARCH_BOX_WIDTH_MIN
44 | searchBoxWidth = SEARCH_BOX_WIDTH_MIN
45 | else if searchBoxWidth > SEARCH_BOX_WIDTH_MAX
46 | searchBoxWidth = SEARCH_BOX_WIDTH_MAX
47 |
48 | # set default dimensions for total header bar, based on logo image
49 | totalHeaderHeight = 67
50 | searchHeaderTop = 16
51 | # adjust defaults if necessary e.g. if an oversized input form:
52 | diff = $("#searchHeader").height() + 35 - totalHeaderHeight
53 | if diff > 0
54 | searchHeaderTop -= Math.round(diff/2)
55 | totalHeaderHeight += Math.round(diff/2)
56 |
57 | $("#searchBox").width(searchBoxWidth)
58 | $("#searchHeader").css("top", searchHeaderTop + "px")
59 | $("#searchHeader").css("left", SEARCH_OFFSET_LEFT + 'px')
60 |
61 | $("#topRightMenu").css("top", "13px")
62 | $("#topRightMenu").css("right", "6px")
63 |
64 | newpapersPopup = $("#newpapersPopup")
65 | # TODO smart width/height
66 | newpapersPopupWidth = 600
67 | #newpapersPopupHeight = 400
68 | newpapersPopup.width(newpapersPopupWidth)
69 | #newpapersPopup.height(newpapersPopupHeight)
70 | newpapersPopup.css("top",Math.round((newHeight - newpapersPopup.height())/2) + 'px')
71 | newpapersPopup.css("left",Math.round((newWidth - newpapersPopup.width())/2) + 'px')
72 | $("#newpapersPopup .slider-range").width(500) # TODO
73 |
74 | # colour scheme select
75 | schemeSelect = $("#colourSchemeSelect")
76 | schemeSelect.css("top", totalHeaderHeight + 10 + 'px')
77 |
78 | # colour scheme select
79 | mapSelect = $("#mapSelect")
80 | mapSelect.css("top", totalHeaderHeight + 10 + 'px')
81 | moveRight = 0
82 | if schemeSelect.width()?
83 | moveRight += schemeSelect.width() + 20
84 | mapSelect.css("left", 10 + moveRight + 'px')
85 |
86 | # welcome message
87 | welcomePopup = $("#welcomePopup")
88 | #welcomePopup.css("top", totalHeaderHeight + 20 + parseInt(schemeSelect.height()) + 'px')
89 | welcomePopup.css("top", totalHeaderHeight + 10 + 'px')
90 | if mapSelect.width()?
91 | moveRight += mapSelect.width() + 20
92 | welcomePopup.css("left", 10 + moveRight + 'px')
93 |
94 | # colour code
95 | keyPopup = $("#keyPopup")
96 | keyPopup.css("left", 90 + 'px')
97 | $("#keyPopup .age").hide()
98 | $("#keyPopup .category").show()
99 |
100 | # zoom buttons
101 | zoomButtons = $("#canvasZoomButtons")
102 | zoomButtons.css("top", newHeight - 32 + 'px')
103 |
104 | # detail view information
105 | detailView = $("#detailView")
106 | detailView.css("top", newHeight - 32 - 60 + 'px')
107 | if detailView.height()?
108 | detailView.css("top", newHeight - 32 - detailView.height() - 20 + 'px')
109 |
110 |
111 | # set canvas dimensions
112 | mapviewWidth = Math.round(newWidth)
113 | mapviewHeight = Math.round(newHeight-totalHeaderHeight-1); # -1 is for the 1px border
114 |
115 | MAPVIEW.resize(mapviewWidth,mapviewHeight,0,totalHeaderHeight)
116 | MAPVIEW.draw()
117 |
118 | #PANEL.resizePanel()
119 | #$("#infoPanel").hide()
120 |
121 | infoPopup = $("#infoPopup")
122 | infoPopupWidth = 400
123 | infoPopup.width(infoPopupWidth)
124 | infoPopup.css("top", totalHeaderHeight + 10 + "px")
125 | #infoPopup.css("left", Math.floor(0.5 * (canvasWidth - infoPopupWidth)) + "px")
126 | infoPopup.css("right", "10px")
127 | $("#infoPopup").css("max-height", newHeight - totalHeaderHeight - 20)
128 |
129 | aboutPopup = $("#aboutPopup")
130 | aboutPopupWidth = infoPopupWidth
131 | aboutPopup.width(aboutPopupWidth)
132 | aboutPopup.css("top", totalHeaderHeight + 10 + "px")
133 | aboutPopup.css("right", "10px")
134 | $("#aboutPopup").css("max-height", newHeight - totalHeaderHeight - 20)
135 |
136 | checkDateAndVersion = ->
137 | request =
138 | gdmv: 1
139 | handleSuccess = (ajaxData) ->
140 | if ajaxData?.v? and ajaxData?.v != VERSION
141 | # hard reload of page
142 | location.reload(true)
143 | if ajaxData?.d0?
144 | if currentPaperId? and currentPaperId != ajaxData.d0
145 | MAPVIEW.reload(mapviewReloadCallback)
146 | # for now do hard reload here too
147 | # else some tiles will still be cached
148 | # in future could consider stamping tiles with date
149 | #location.reload(true)
150 | currentPaperId = ajaxData.d0
151 | AJAX.doRequest(request, handleSuccess)
152 |
153 | checkForUrlVariables = ->
154 | vars = {}
155 | hash = null
156 | hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&')
157 | for i in [0...hashes.length]
158 | hash = hashes[i].split('=')
159 | vars[hash[0]] = decodeURIComponent((hash[1]+'').replace(/\+/g, '%20'))
160 | if vars.s? and vars.s.length < 64
161 | SEARCH.setSearch(vars.s)
162 | callbackPass = ->
163 | MAPVIEW.draw()
164 | SEARCH.doSearch(callbackPass)
165 |
166 | main = ->
167 | # bind first, so that init can safely set it after callback
168 | MAPVIEW.bindZoomFunction ->
169 | # TODO check if detailView showing
170 | DETAILVIEW.reload()
171 | if MAPVIEW.showOverlay()
172 | FADEVIEW.show()
173 | else
174 | FADEVIEW.hide()
175 |
176 | if $("#detailView").css('display') is "none"
177 | DETAILVIEW.hide()
178 | else
179 | DETAILVIEW.show()
180 |
181 | MAPVIEW.initialise()
182 | MAPVIEW.reload(mapviewReloadCallback)
183 |
184 | # work out if we are running on a portable device
185 | agent = navigator.userAgent.toLowerCase()
186 | INPUT.iDevice = agent.indexOf("iphone") >= 0 or agent.indexOf("ipad") >= 0 or agent.indexOf("android") >= 0
187 |
188 | # resize and orientation change events
189 | window.addEventListener('resize', resizeDOM, false)
190 | window.addEventListener('orientationchange', resizeDOM, false) # for smart phones etc.
191 |
192 | # bind events for the mouse/touch
193 | if INPUT.iDevice
194 | # an iDevice; bind the events for touch
195 | MAPVIEW.getTopCanvas().addEventListener("touchstart", INPUT.touchStart)
196 | MAPVIEW.getTopCanvas().addEventListener("touchend", INPUT.touchEnd)
197 | # since touchmove event only fires when the user is actually touching, we can afford to always have it bound
198 | MAPVIEW.getTopCanvas().addEventListener("touchmove", INPUT.touchMove)
199 | else
200 | # we have a real mouse; use jQuery to bind these events
201 | MAPVIEW.jQueryAttach().mousedown(INPUT.mouseDown)
202 | MAPVIEW.jQueryAttach().mouseup(INPUT.mouseUp)
203 | MAPVIEW.jQueryAttach().mouseleave(INPUT.mouseLeave)
204 | MAPVIEW.jQueryAttach().dblclick(INPUT.mouseDoubleClick)
205 | MAPVIEW.jQueryAttach().mousewheel(INPUT.mouseWheel)
206 |
207 | # mousetrap library, used to capture keys
208 | # see: http://craig.is/killing/mice
209 | if !INPUT.iDevice
210 | #MOUSETRAP.bind("esc", (e, key) -> INPUT.keypressEscape())
211 | #MOUSETRAP.bind("enter", (e, key) -> INPUT.keypressEnter())
212 | MOUSETRAP.bind(["left", "right", "up", "down"], (e, key) -> INPUT.keypressArrow(key))
213 | MOUSETRAP.bind(["=", "+", "-"], (e, key) -> INPUT.keypressZoom(key))
214 | MOUSETRAP.bind(["d"], (e, key) -> DETAILVIEW.toggle())
215 |
216 | $("#formSearch").submit(INPUT.formSearchSubmit)
217 | $("#formSearch input:button").submit(INPUT.formSearchSubmit)
218 | $("#searchMessage .clear").click ->
219 | SEARCH.clearSearchResults()
220 | MAPVIEW.draw()
221 |
222 | # search box autocomplete
223 | searchTerms = [
224 | {label: "?s mart (examples: hep-th/9908142, e.witten, seiberg string theory ) ", value: "?smart "}
225 | {label: "?a uthor (example: ?a witten; ?a z.bern ) ", value: "?author "}
226 | {label: "?k eyword (example: ?k majorana neutrino ) ", value: "?keyword "}
227 | {label: "?ti tle (example: ?ti 125 Higgs ) ", value: "?title "}
228 | {label: "?n ew-papers (example: ?n hep-ph, crosslists ) ", value: "?new-papers "}
229 | ]
230 |
231 | $("#searchBox").autocomplete(
232 | source: ( request, response ) ->
233 | matcher = new RegExp( "^" + $.ui.autocomplete.escapeRegex( request.term ), "i" )
234 | response( $.grep( searchTerms, ( item ) ->
235 | return matcher.test( item.value )
236 | )
237 | )
238 | html: true
239 | delay : 0
240 | autoFocus : true
241 | minLength: 0
242 | ).focus ->
243 | $("#searchBox").autocomplete("search",$("#searchBox").value)
244 |
245 | $("#searchMessage").hide()
246 |
247 | # New papers view
248 | $("#searchNewPapers").click ->
249 | NEWPAPERSVIEW.popup()
250 | NEWPAPERSVIEW.close()
251 | $("#newpapersPopup .close").click ->
252 | NEWPAPERSVIEW.close()
253 | $("#newpapersPopup .form").submit(NEWPAPERSVIEW.doSearch)
254 |
255 | $("#newpapersPopup .slider-range").slider(
256 | range: true
257 | min: 0
258 | max: 32
259 | step: 1
260 | values: [ 30, 32 ]
261 | slide: (event, ui) ->
262 | if ui.values[0] == ui.values[1] or ui.values[0] == ui.values[1]-1
263 | event.preventDefault()
264 | else
265 | $("#newpapersPopup .slider-range").slider("values",0,ui.values[0])
266 | $("#newpapersPopup .slider-range").slider("values",1,ui.values[1])
267 | NEWPAPERSVIEW.update()
268 | change: (event, ui) ->
269 | NEWPAPERSVIEW.update()
270 | )
271 | NEWPAPERSVIEW.update()
272 |
273 | # show everything
274 | $("#xiwiArea").show()
275 |
276 | # refresh world - should be done automatically now
277 | #$("#welcomePopup .refresh").click ->
278 | # MAPVIEW.reload()
279 |
280 | # keypopup
281 | $("#keyPopup .close").click ->
282 | $("#keyPopup").hide()
283 | $("#colourSchemeSelect .legend").click ->
284 | $("#keyPopup").toggle()
285 | $("#keyPopup").hide()
286 |
287 | # colour scheme select
288 | $("#colourSchemeSelect .select").val('categories').bind('change', (event) ->
289 | INPUT.canvasColourSchemeSelect(event)
290 | )
291 |
292 | # map select
293 | $("#mapSelect .select").bind('change', (event) ->
294 | FADEVIEW.hide()
295 | INFOVIEW.close()
296 | MAPVIEW.reload(mapviewReloadCallback)
297 | )
298 |
299 | # info popup
300 | $("#infoPopup .close").click ->
301 | INFOVIEW.close()
302 | MAPVIEW.draw()
303 | $("#infoPopup .showAbstract").click ->
304 | INFOVIEW.showAbstract()
305 | $("#infoPopup .showReferences").click ->
306 | INFOVIEW.showReferences(MAPVIEW.draw)
307 | $("#infoPopup .showCitations").click ->
308 | INFOVIEW.showCitations(MAPVIEW.draw)
309 | for index in [1..13]
310 | $("#infoPopup .auth" + index).click ->
311 | callbackPass = ->
312 | MAPVIEW.draw()
313 | INFOVIEW.searchAuthor($(this).data("id"),callbackPass)
314 | INFOVIEW.close()
315 |
316 | # about popup
317 | $("#topRightMenu .info").click ->
318 | INFOVIEW.close()
319 | $("#aboutPopup").toggle()
320 | MAPVIEW.draw()
321 | $("#aboutPopup .close").click ->
322 | $("#aboutPopup").hide()
323 | $("#aboutPopup").hide()
324 |
325 | # zoom buttons
326 | $("#canvasZoomIn").click ->
327 | INPUT.canvasZoomInButton()
328 |
329 | $("#canvasZoomOut").click ->
330 | INPUT.canvasZoomOutButton()
331 |
332 | # now that the correct items are shown, resize everything to fit correctly
333 | resizeDOM()
334 |
335 | MAPVIEW.draw()
336 |
337 | checkDateAndVersion()
338 |
339 | checkForUrlVariables()
340 |
341 | # Check for version and date when window gets focus
342 | $(window).focus ->
343 | checkDateAndVersion()
344 |
345 | # request animation frame
346 | window.requestAnimationFrame = do ->
347 | workaround = (callback) ->
348 | window.setTimeout(callback, 1000 / 60 )
349 | window.requestAnimationFrame or window.mozRequestAnimationFrame or window.webkitRequestAnimationFrame or window.msRequestAnimationFrame or workaround
350 |
351 |
352 | # use jQuery to run the app
353 | $(document).ready(-> main())
354 |
355 | return {}
356 |
357 |
--------------------------------------------------------------------------------
/js/coffee/main_test.coffee:
--------------------------------------------------------------------------------
1 | define ['app/main'], (MAIN) ->
2 | run : ->
3 | module "MAIN"
4 |
5 | #test "dummy test of world", ->
6 | # equal WORLD.testFunction(1,2), 3, "return result should be 3"
7 |
--------------------------------------------------------------------------------
/js/coffee/mapview_test.coffee:
--------------------------------------------------------------------------------
1 | define ['app/mapview'], (MAPVIEW) ->
2 | run : ->
3 | module "MAPVIEW"
4 |
5 |
--------------------------------------------------------------------------------
/js/coffee/newpapersview.coffee:
--------------------------------------------------------------------------------
1 | # NEWPAPERSVIEW module
2 | #
3 | # Popup that lets users search for new papers.
4 |
5 | define ['app/world','app/mapview','app/search','jquery','jquery.mousewheel'], (WORLD,MAPVIEW,SEARCH,$) ->
6 |
7 | exports = {}
8 |
9 | performSearch = ->
10 | checkboxes = $("#newpapersPopup .catCheckbox")
11 |
12 | numCategories = 0
13 | categoriesString = ""
14 | for checkbox in checkboxes
15 | if checkbox.children[0].checked
16 | if categoriesString.length > 0
17 | categoriesString += ","
18 | categoriesString += checkbox.children[0].value
19 | if checkbox.children[0].value != "crosslists"
20 | numCategories += 1
21 |
22 | #if checkbox.textContent == "Include crosslists"
23 | # categoriesString += "crosslists"
24 | #else
25 | # categoriesString += checkbox.textContent
26 |
27 | if numCategories == 0
28 | # don't search
29 | return
30 |
31 | lowerBound = 31 - $("#newpapersPopup .slider-range").slider("values",0)
32 | upperBound = 32 - $("#newpapersPopup .slider-range").slider("values",1)
33 |
34 | callbackPass = ->
35 | MAPVIEW.draw()
36 | SEARCH.setSearch("?n #{lowerBound}-#{upperBound}:#{categoriesString}")
37 | SEARCH.doSearch(callbackPass)
38 | exports.close()
39 |
40 | ###########################################################################
41 | # Public (exports)
42 | ###########################################################################
43 |
44 | exports.doSearch = (event) ->
45 | event.preventDefault()
46 | performSearch()
47 |
48 | exports.close = ->
49 | $("#newpapersPopup").hide()
50 |
51 | exports.popup = ->
52 | $("#newpapersPopup").show()
53 | exports.update()
54 |
55 | exports.update = ->
56 | lowerBound = 31 - $("#newpapersPopup .slider-range").slider("values",0)
57 | upperBound = 32 - $("#newpapersPopup .slider-range").slider("values",1)
58 | val = "New papers"
59 | latestDate = WORLD.getLastDownloadDate()
60 | if upperBound == 0
61 | if lowerBound == 1
62 | val += " for last submission day (#{latestDate})"
63 | else
64 | val += " for last #{lowerBound} submission days"
65 | else
66 | val += " from #{upperBound} to #{lowerBound} submission days ago"
67 | $("#newpapersPopup .searchButton").val(val)
68 |
69 | return exports
70 |
--------------------------------------------------------------------------------
/js/coffee/search.coffee:
--------------------------------------------------------------------------------
1 | # SEARCH module
2 | #
3 | # Handles user search requests.
4 |
5 | define ['app/world','jquery'], (WORLD,$) ->
6 | exports = {}
7 |
8 | SEARCH_TYPE_UNKNOWN = 0
9 | SEARCH_TYPE_AUTO = 1
10 | SEARCH_TYPE_ARXIV = 2
11 | SEARCH_TYPE_NEW = 3
12 | SEARCH_TYPE_AUTHOR = 4
13 | SEARCH_TYPE_TITLE = 5
14 | SEARCH_TYPE_KEYWORD = 6
15 | SEARCH_TYPE_REFS = 7
16 | SEARCH_TYPE_CITES = 8
17 |
18 | # Returns the type of search, and the rest of the input
19 | regexArxivOld = /^\s*[A-Za-z\-]{2,8}\/\d{7}\s*$/
20 | regexArxivNew = /^\s*\d{4}\.\d{4,5}\s*$/ # 2015 update
21 | regexCategory = /^\s*[A-Za-z\-]{2,8}\s*$/
22 | regexAuthor = /^\s*[A-Za-z][a-z]?\.-?[A-Za-z]/
23 |
24 | searchResults = []
25 |
26 | # flag for link parent node and its unique SearchResult
27 | searchParentResult = null
28 |
29 | zoomOnSearch = false
30 |
31 | class SearchResult
32 | constructor: (id,x,y,r) ->
33 | @id = id
34 | @x = x
35 | @y = y
36 | @r = r
37 | @freq = 1 # used by link search results
38 |
39 | detectSearchType = (input) ->
40 | # TODO this needs some work
41 | # A search term should end with ' ', ':', '='
42 | # and anything up to that should qualify
43 | # e.g "?a ", "?auth ", "?author:" etc.
44 | if input.length >= 1 and input[0] == '?'
45 | # find end of command
46 | endIndex = input.indexOf(" ",1)
47 | searchTerm = ""
48 | if endIndex == -1
49 | endIndex = input.length
50 | else
51 | searchTerm = input.slice(endIndex)
52 | command = input.slice(1,endIndex)
53 |
54 | if command == ""
55 | return [SEARCH_TYPE_UNKNOWN, ""]
56 | else if command == "smart".slice(0,endIndex-1)
57 | # proceed below
58 | input = searchTerm
59 | else if command == "author".slice(0,endIndex-1)
60 | return [SEARCH_TYPE_AUTHOR, searchTerm]
61 | else if command.length >= 2 and command == "title".slice(0,endIndex-1)
62 | return [SEARCH_TYPE_TITLE, searchTerm]
63 | else if command == "keyword".slice(0,endIndex-1)
64 | return [SEARCH_TYPE_KEYWORD, searchTerm]
65 | else if command == "new-papers".slice(0,endIndex-1)
66 | return [SEARCH_TYPE_NEW, searchTerm]
67 | else if command == "refs".slice(0,endIndex-1)
68 | return [SEARCH_TYPE_REFS, searchTerm]
69 | else if command == "cites".slice(0,endIndex-1)
70 | return [SEARCH_TYPE_CITES, searchTerm]
71 | else
72 | return [SEARCH_TYPE_UNKNOWN, searchTerm]
73 |
74 | if regexArxivNew.test(input) or regexArxivOld.test(input)
75 | return [SEARCH_TYPE_ARXIV, input]
76 | else if regexAuthor.test(input)
77 | return [SEARCH_TYPE_AUTHOR, input]
78 | else
79 | return [SEARCH_TYPE_AUTO, input]
80 |
81 | printInfoMessageCheckZoom = ->
82 | if searchResults.length > 0
83 | # Search success, so zoom in on it when we have the chance
84 | $("#searchMessage .results").html("Showing #{searchResults.length} results.")
85 | $("#searchMessage .clear").show()
86 | $("#searchMessage").show()
87 | zoomOnSearch = true
88 | else
89 | $("#searchMessage .results").html("Found no results.")
90 | $("#searchMessage .clear").hide()
91 | $("#searchMessage").show()
92 | zoomOnSearch = false
93 |
94 |
95 | handleSearchWrapper = (callbackPass) ->
96 | return (ajaxData) ->
97 | if ajaxData?
98 | ids = []
99 | for paper in ajaxData
100 | ids.push(paper.id)
101 | firstCallbackPass = (data) ->
102 | searchParentResult = null
103 | searchResults = []
104 | if data?
105 | for paper in data
106 | searchResults.push(new SearchResult(paper.id,paper.x,paper.y,paper.r))
107 | printInfoMessageCheckZoom()
108 | if callbackPass?
109 | callbackPass()
110 | WORLD.fetchLocationsForPaperIds(ids,firstCallbackPass)
111 |
112 | handleLinkSearchWrapper = (callbackPass) ->
113 | return (ajaxData) ->
114 | if ajaxData?
115 | searchParentResult = new SearchResult(ajaxData.id,ajaxData.x,ajaxData.y,ajaxData.r)
116 | searchResults = []
117 | # only include links with valid positions
118 | for link in ajaxData.links when link.x? and link.y? and link.r?
119 | sRes = new SearchResult(link.id,link.x,link.y,link.r)
120 | sRes.freq = link.freq
121 | searchResults.push(sRes)
122 | printInfoMessageCheckZoom()
123 | if callbackPass?
124 | callbackPass()
125 |
126 | handleSearchError = ->
127 | $("#searchMessage .results").html("Search failed.")
128 | $("#searchMessage .clear").show()
129 | $("#searchMessage").show()
130 |
131 | ###########################################################################
132 | # Public (exports)
133 | ###########################################################################
134 |
135 | exports.getSearchResults = ->
136 | return searchResults
137 |
138 | exports.getParentLinkResult = ->
139 | return searchParentResult
140 |
141 | exports.clearSearchResults = ->
142 | searchResults = []
143 | searchParentResult = null
144 | $("#searchMessage").hide()
145 | $("#formSearch input:text")[0].value = ""
146 |
147 | exports.areSearchResults = ->
148 | return searchResults.length != 0
149 |
150 | exports.isParentLinkResult = ->
151 | return searchParentResult != null
152 |
153 | exports.zoomOnceOnSearch = ->
154 | if zoomOnSearch
155 | zoomOnSearch = false
156 | return true
157 | else
158 | return false
159 |
160 | exports.closestResultWithinRadius = (pos,radius) ->
161 | searchData = null
162 | shortestDist2 = -1
163 | radius2 = radius*radius
164 | for res in searchResults
165 | dist2 = Math.pow(pos.x - res.x,2) + Math.pow(pos.y - res.y,2)
166 | if dist2 < radius2 and (shortestDist2 < 0 or dist2 < shortestDist2)
167 | shortestDist2 = dist2
168 | searchData =
169 | id: res.id
170 | x: res.x
171 | y: res.y
172 | r: res.r
173 | return searchData
174 |
175 | exports.setSearch = (searchString) ->
176 | $("#formSearch input:text")[0].value = searchString
177 |
178 | exports.doSearch = (callbackPass) ->
179 | # TODO should lock search while searching!
180 | # detect search type
181 | keyword = $("#formSearch input:text")[0].value
182 | searchTypeAndValue = detectSearchType(keyword)
183 |
184 | # remove white space from head and tail of search value
185 | searchValue = searchTypeAndValue[1].replace(/^\s*|\s*$/g,'')
186 |
187 | $("#searchMessage .results").html("Searching...")
188 | $("#searchMessage .clear").hide()
189 | $("#searchMessage").show()
190 |
191 | success = handleSearchWrapper(callbackPass)
192 | successLink = handleLinkSearchWrapper(callbackPass)
193 | error = handleSearchError
194 | switch searchTypeAndValue[0]
195 | when SEARCH_TYPE_AUTO
196 | WORLD.fetchSearchResults({sge: searchValue}, success, error)
197 | when SEARCH_TYPE_KEYWORD
198 | WORLD.fetchSearchResults({skw: searchValue}, success, error)
199 | when SEARCH_TYPE_ARXIV
200 | WORLD.fetchSearchResults({saxm: searchValue}, success, error)
201 | when SEARCH_TYPE_AUTHOR
202 | WORLD.fetchSearchResults({sau: searchValue}, success, error)
203 | when SEARCH_TYPE_TITLE
204 | WORLD.fetchSearchResults({sti: searchValue}, success, error)
205 | when SEARCH_TYPE_NEW
206 | #if (newPaperBoundaryId = WORLD.getNewPaperBoundaryId()) != 0
207 | crosslists = "false"
208 | daysAgoFrom = 1
209 | daysAgoTo = 0
210 |
211 | parts = searchValue.replace(/^\s*|\s*$/g,'').split(':')
212 | if parts.length >= 2
213 | days = parts[0].split('-')
214 | cats = parts[1].split(',')
215 | else
216 | cats = parts[0].split(',')
217 |
218 | if days?
219 | if days[0]? and days[0] > 0
220 | daysAgoFrom = days[0]
221 | if days[1]? and days[1] > 0
222 | daysAgoTo = days[1]
223 |
224 | newCats = []
225 | for cat in cats
226 | # remove white space
227 | cat = cat.replace(/\s/g,'')
228 | if cat == "crosslists"
229 | crosslists = "true"
230 | else
231 | newCats.push(cat)
232 | request =
233 | sca: newCats.join(",")
234 | #f:newPaperBoundaryId
235 | #t:0
236 | fd : daysAgoFrom
237 | td : daysAgoTo
238 | x:crosslists
239 | WORLD.fetchSearchResults(request, success, error)
240 | when SEARCH_TYPE_REFS
241 | WORLD.fetchReferencesForArXivId(searchValue,successLink,error)
242 | when SEARCH_TYPE_CITES
243 | WORLD.fetchCitationsForArXivId(searchValue,successLink,error)
244 | else
245 | $("#searchMessage .results").html("Invalid search type")
246 | $("#searchMessage .clear").show()
247 | $("#searchMessage").show()
248 | searchResults = []
249 | callbackPass()
250 |
251 | return exports
252 |
--------------------------------------------------------------------------------
/js/coffee/selected.coffee:
--------------------------------------------------------------------------------
1 | # SELECTED module
2 | #
3 | # Stores state of selected paper.
4 |
5 | define ['app/Vec2D','app/world'], (Vec2D,WORLD) ->
6 |
7 | exports = {}
8 |
9 | selectedId = null
10 | selectedPos = null
11 | selectedRad = null
12 |
13 | #referenceLinks = []
14 | #referencesLoading = false # a lock to prevent multiple requests
15 | #referencesReady = false
16 |
17 | #citationLinks = []
18 | #citationsLoading = false # a lock to prevent multiple requests
19 | #citationsReady = false
20 |
21 | #showReferences = false
22 | #showCitations = false
23 |
24 | class Link
25 | constructor: (id,freq,order,numCites) ->
26 | @id = id
27 | @freq = freq
28 | @order = order
29 | @nc = numCites
30 | @pos = null
31 | @rad = null
32 |
33 | ###########################################################################
34 | # Public (exports)
35 | ###########################################################################
36 |
37 | exports.getSelectedId = ->
38 | return selectedId
39 |
40 | exports.getSelectedPos = ->
41 | return selectedPos
42 |
43 | exports.getSelectedRad = ->
44 | return selectedRad
45 |
46 | exports.setSelection = (data) ->
47 | if data.id? and data.id > 0 and data.id != selectedId
48 | exports.clearSelection()
49 | selectedId = data.id
50 | if data.x? and data.y? and data.r?
51 | selectedPos = new Vec2D(data.x,data.y)
52 | selectedRad = data.r
53 |
54 | exports.clearSelection = () ->
55 | selectedId = null
56 | selectedPos = null
57 | selectedRad = null
58 | #showReferences = false
59 | #showCitations = false
60 | #referencesLoading = false
61 | #referencesReady = false
62 | #citationsLoading = false
63 | #citationsReady = false
64 |
65 | exports.isSelected = ->
66 | return selectedId?
67 |
68 | ###
69 | exports.loadReferences = (callback) ->
70 | if selectedId?
71 | showReferences = true
72 | showCitations = false
73 | if !referencesLoading and !referencesReady
74 | referencesLoading = true
75 | referencesReady = false
76 | referenceLinks = []
77 |
78 | callbackFail = () ->
79 | referencesLoading = false
80 | referenceLinks = []
81 |
82 | # this callback is called after the next one
83 | # TODO make single call in Wombat for all this info!
84 | callbackFetchLocations = (data) ->
85 | for location in data
86 | for link in referenceLinks when link.id == location.id
87 | link.pos = new Vec2D(location.x,location.y)
88 | link.rad = location.r
89 |
90 | referencesLoading = false
91 | referencesReady = true
92 | callback()
93 |
94 | callbackFetchRefs = (data) ->
95 | refIds = []
96 | for ref in data.references
97 | refIds.push(ref.id)
98 | referenceLinks.push(new Link(ref.id,ref.freq,ref.order,ref.numCites))
99 | WORLD.fetchLocationsForPaperIds(refIds,callbackFetchLocations,callbackFail)
100 | WORLD.fetchReferencesForPaperId(selectedId,callbackFetchRefs,callbackFail)
101 | else
102 | callback()
103 | ###
104 |
105 |
106 | ###
107 | exports.loadCitations = (callback) ->
108 | if selectedId?
109 | showReferences = false
110 | showCitations = true
111 | if !citationsLoading and !citationsReady
112 | citationsLoading = true
113 | citationsReady = false
114 | citationLinks = []
115 |
116 | callbackFail = () ->
117 | citationsLoading = false
118 | citationLinks = []
119 |
120 | callbackFetchLocations = (data) ->
121 | for location in data
122 | for link in citationLinks when link.id == location.id
123 | link.pos = new Vec2D(location.x,location.y)
124 | link.rad = location.r
125 |
126 | citationsLoading = false
127 | citationsReady = true
128 | callback()
129 |
130 | callbackFetchRefs = (data) ->
131 | citeIds = []
132 | for cite in data.citations
133 | citeIds.push(cite.id)
134 | citationLinks.push(new Link(cite.id,cite.freq,0,cite.numCites))
135 | WORLD.fetchLocationsForPaperIds(citeIds,callbackFetchLocations,callbackFail)
136 | WORLD.fetchCitationsForPaperId(selectedId,callbackFetchRefs,callbackFail)
137 | else
138 | callback()
139 | ###
140 |
141 | ###
142 | exports.drawLinks = ->
143 | return showReferences or showCitations
144 | ###
145 |
146 | ###
147 | exports.getLinks = ->
148 | if showReferences and referencesReady
149 | return referenceLinks
150 | else if showCitations and citationsReady
151 | return citationLinks
152 | else
153 | return []
154 | ###
155 |
156 | return exports
157 |
--------------------------------------------------------------------------------
/js/coffee/world.coffee:
--------------------------------------------------------------------------------
1 | # WORLD module
2 | #
3 | # Intended as an API.
4 | # Talks to server and gives back results.
5 | # Should hold no state beyond the details of todays "world".
6 | # Should shield world from server json return formats.
7 |
8 | define ['app/Vec2D','app/ajax'], (Vec2D, AJAX) ->
9 | exports = {}
10 |
11 | # Multiple tile servers to get around max http requests
12 | # per url
13 | DEFAULT_TILE_SERVERS = [
14 | "http://tile1.paperscape.org"
15 | "http://tile2.paperscape.org"
16 | "http://tile3.paperscape.org"
17 | "http://tile4.paperscape.org"
18 | ]
19 |
20 | tile_servers = $("#pscpConfig").data("tiles") ? DEFAULT_TILE_SERVERS
21 |
22 | tileServerIndex = 0
23 |
24 | worldReady = false
25 |
26 | subWorldPath = "/world" # if multple worlds on tile server
27 |
28 | dbSuffix = ""
29 | latestPaperId = 0
30 | newPaperBoundaryId = 0
31 | numberArxivPapers = 0
32 | lastDownloadDate = ""
33 |
34 | class TileDepth
35 | constructor: (depth, numx, numy, worldWidth, worldHeight) ->
36 | @depth = depth
37 | @numx = numx ? 0
38 | @numy = numy ? 0
39 | @width = worldWidth ? 0
40 | @height = worldHeight ? 0
41 |
42 | # Atm carbon copy of above, but may change in future
43 | class LabelDepth
44 | constructor: (depth, scale, numx, numy, worldWidth, worldHeight) ->
45 | @depth = depth
46 | @scale = scale
47 | @numx = numx ? 0
48 | @numy = numy ? 0
49 | @width = worldWidth ? 0
50 | @height = worldHeight ? 0
51 |
52 | tile_depths = null
53 |
54 | label_depths = null
55 |
56 | findTileDepth = (depth) ->
57 | for t in tile_depths when t.depth is depth
58 | return t
59 | return null
60 |
61 | findLabelDepth = (depth) ->
62 | for l in label_depths when l.depth is depth
63 | return l
64 | return null
65 |
66 | ###########################################################################
67 | # Public (exports)
68 | ###########################################################################
69 |
70 | exports.loadWorldData = (callbackPass,callbackFail) ->
71 | worldReady = false
72 | config =
73 | server: exports.getTileServer()
74 | urlExt: "/world_index.json"
75 | jsonpStatic: true
76 | jsonpCallback: "world_index"
77 | request = {}
78 | handleSuccess = (ajaxData) ->
79 | #if ajaxData? and ajaxData?.tile?
80 | if ajaxData? and ajaxData?.tilings?
81 | dbSuffix = ajaxData?.dbsuffix ? ""
82 | latestPaperId = ajaxData?.latestid ? 0
83 |
84 | newPaperBoundaryId = ajaxData?.newid ? latestPaperId
85 |
86 | numberArxivPapers = ajaxData?.numpapers ? 0
87 | lastDownloadDate = ajaxData?.lastdl ? ""
88 |
89 | # get tiles info
90 | tile_depths = []
91 | tile_depths = (new TileDepth(tiling?.z,tiling?.nx,tiling?.ny,tiling?.tw,tiling?.th) for tiling in ajaxData.tilings when tiling?.z?)
92 |
93 | # get label zones info
94 | label_depths = []
95 | label_depths = (new LabelDepth(labeling?.z,labeling?.s,labeling?.nx,labeling?.ny,labeling?.w,labeling?.h) for labeling in ajaxData.zones when labeling?.z?)
96 |
97 | worldReady = true
98 |
99 | returnData =
100 | x_min: ajaxData?.xmin ? 0
101 | y_min: ajaxData?.ymin ? 0
102 | x_max: ajaxData?.xmax ? 0
103 | y_max: ajaxData?.ymax ? 0
104 | tile_px_w: ajaxData?.pixelw ? 0
105 | tile_px_h: ajaxData?.pixelh ? 0
106 |
107 | if callbackPass?
108 | callbackPass(returnData)
109 | else if callbackFail?
110 | callbackFail()
111 | handleError = ->
112 | if callbackFail?
113 | callbackFail()
114 | AJAX.doRequest(request, handleSuccess, handleError,config)
115 | return
116 |
117 | exports.isWorldReady = ->
118 | worldReady
119 |
120 | exports.getLatestPaperId = ->
121 | latestPaperId
122 |
123 | exports.getNewPaperBoundaryId = ->
124 | newPaperBoundaryId
125 |
126 | exports.getNumberArxivPapers = ->
127 | numberArxivPapers
128 |
129 | exports.getLastDownloadDate = ->
130 | lastDownloadDate
131 |
132 | #exports.getClosestTiling = (totalNumTilesX) ->
133 | # if tile_depths? and tile_depths.length > 0
134 | # #closest = tile_depths[0]
135 | # closest = null
136 | # for tiling in tile_depths
137 | # #if tiling.numx >= totalNumTilesX and Math.abs(tiling.numx-totalNumTilesX) < Math.abs(closest.numx-totalNumTilesX)
138 | # if tiling.numx >= totalNumTilesX and (not closest? or tiling.numx < closest.numx)
139 | # closest = tiling
140 | # if not closest?
141 | # closest = tile_depths[0]
142 |
143 | # use maximum width calculation to make sure images are always sharp
144 | exports.getClosestTiling = (maximumTileWidth) ->
145 | if tile_depths? and tile_depths.length > 0
146 | closest = tile_depths[tile_depths.length - 1]
147 | for tiling in tile_depths
148 | if tiling.width <= maximumTileWidth and tiling.width > closest.width
149 | closest = tiling
150 |
151 | else
152 | # Make sure we return something
153 | closest = new TileDepth(0)
154 | return {
155 | depth: closest.depth
156 | numx: closest.numx
157 | numy: closest.numy
158 | width: closest.width
159 | height: closest.height
160 | }
161 |
162 | exports.getClosestLabelZone = (scale) ->
163 | if label_depths? and label_depths.length > 0
164 | closest = label_depths[0]
165 | for labeling in label_depths
166 | if Math.abs(labeling.scale-scale) < Math.abs(closest.scale-scale)
167 | closest = labeling
168 | else
169 | # Make sure we return something
170 | closest = new LabelDepth(0)
171 | return {
172 | depth: closest.depth
173 | scale: closest.scale
174 | numx: closest.numx
175 | numy: closest.numy
176 | width: closest.width
177 | height: closest.height
178 | }
179 |
180 | exports.setSubWorldPath = (path) ->
181 | if path? and path.length > 0
182 | if path[0] != "/"
183 | path = "/#{path}"
184 | subWorldPath = path
185 |
186 | exports.getTileServer = () ->
187 | tileServerIndex += 1
188 | if tileServerIndex >= tile_servers.length
189 | tileServerIndex = 0
190 | return "#{tile_servers[tileServerIndex]}#{subWorldPath}"
191 |
192 | exports.getTileInfoAtPosition = (depth,dx,dy,specialTiles) ->
193 | # dx and dy are offsets from top-left corner
194 | suffix = ""
195 | if specialTiles?
196 | if specialTiles == "heatmap"
197 | suffix = "-hm"
198 | if specialTiles == "grayscale"
199 | suffix = "-bw"
200 | # for now tiling is square
201 | tiling = findTileDepth(depth)
202 | nx = tiling.numx
203 | ny = tiling.numy
204 | ix = Math.ceil(dx / tiling.width)
205 | iy = Math.ceil(dy / tiling.height)
206 | #px = x_min + tiling.width*(ix-1)
207 | #py = y_min + tiling.height*(iy-1)
208 | if ix >= 1 and ix <= nx and iy >= 1 and iy <= ny
209 | path = "tiles#{suffix}/#{depth}/#{ix}/#{iy}.png"
210 | else
211 | path = null
212 | return {
213 | path: path
214 | #posx: px
215 | #posy: py
216 | }
217 |
218 | exports.fetchLabelZone = (depth,ix,iy,callbackPass,callbackFail) ->
219 | labeling = findLabelDepth(depth)
220 | labelZonePath = "/zones/#{depth}/#{ix}/#{iy}.json"
221 | config =
222 | server: exports.getTileServer()
223 | urlExt: labelZonePath
224 | jsonpStatic: true
225 | jsonpCallback: "lz_#{depth}_#{ix}_#{iy}"
226 | request = {}
227 | handleSuccess = (ajaxData) ->
228 | if ajaxData?
229 | returnData =
230 | lbls: ajaxData.lbls ? []
231 | if callbackPass?
232 | callbackPass(returnData)
233 | else if callbackFail?
234 | callbackFail()
235 | handleError = ->
236 | if callbackFail?
237 | callbackFail()
238 | AJAX.doRequest(request, handleSuccess, handleError,config)
239 | return
240 |
241 |
242 | exports.fetchMetaForPaperId = (id, callbackPass, callbackFail) ->
243 | request =
244 | gdata: [id]
245 | flags: [0x01]
246 | handleSuccess = (ajaxData) ->
247 | returnData =
248 | id : ajaxData.papr[0]?.id ? 0
249 | numRefs: ajaxData.papr[0]?.nr ? 0
250 | numCites: ajaxData.papr[0]?.nc ? 0
251 | title: ajaxData.papr[0]?.titl ? ""
252 | authors: ajaxData.papr[0]?.auth ? ""
253 | journal: ajaxData.papr[0]?.publ ? ""
254 | arxivId: ajaxData.papr[0]?.arxv ? ""
255 | categories: ajaxData.papr[0]?.cats ? ""
256 | inspire: ajaxData.papr[0]?.insp ? ""
257 | callbackPass(returnData)
258 | handleError = ->
259 | if callbackFail?
260 | callbackFail()
261 | AJAX.doRequest(request, handleSuccess, handleError)
262 |
263 | exports.fetchAbstractForPaperId = (id, callbackPass, callbackFail) ->
264 | request =
265 | gdata: [id]
266 | flags: [0x20]
267 | handleSuccess = (ajaxData) ->
268 | returnData =
269 | id : ajaxData.papr[0]?.id ? 0
270 | abstract: ajaxData.papr[0]?.abst ? ""
271 | callbackPass(returnData)
272 | handleError = ->
273 | if callbackFail?
274 | callbackFail()
275 | AJAX.doRequest(request, handleSuccess, handleError)
276 |
277 | exports.fetchReferencesForArXivId = (arXivId, callbackPass, callbackFail) ->
278 | request =
279 | mr2l: arXivId
280 | tbl: dbSuffix
281 | handleSuccess = (ajaxData) ->
282 | refs = []
283 | if ajaxData?.papr[0]?.ref?
284 | for ref in ajaxData.papr[0].ref
285 | refs.push
286 | id : ref.id ? 0
287 | freq: ref.f ? 1
288 | x : ref.x ? null
289 | y : ref.y ? null
290 | r : ref.r ? null
291 | returnData =
292 | id : ajaxData?.papr[0]?.id ? 0
293 | x : ajaxData?.papr[0]?.x ? null
294 | y : ajaxData?.papr[0]?.y ? null
295 | r : ajaxData?.papr[0]?.r ? null
296 | links : refs
297 | callbackPass(returnData)
298 | handleError = ->
299 | if callbackFail?
300 | callbackFail()
301 | AJAX.doRequest(request, handleSuccess, handleError)
302 |
303 | exports.fetchCitationsForArXivId = (arXivId, callbackPass, callbackFail) ->
304 | request =
305 | mc2l: arXivId
306 | tbl: dbSuffix
307 | handleSuccess = (ajaxData) ->
308 | cites = []
309 | if ajaxData?.papr[0]?.cite?
310 | for cite in ajaxData?.papr[0]?.cite
311 | cites.push
312 | id : cite.id ? 0
313 | freq: cite.f ? 1
314 | x : cite.x ? null
315 | y : cite.y ? null
316 | r : cite.r ? null
317 | returnData =
318 | id : ajaxData?.papr[0]?.id ? 0
319 | x : ajaxData?.papr[0]?.x ? null
320 | y : ajaxData?.papr[0]?.y ? null
321 | r : ajaxData?.papr[0]?.r ? null
322 | links: cites
323 | callbackPass(returnData)
324 | handleError = ->
325 | if callbackFail?
326 | callbackFail()
327 | AJAX.doRequest(request, handleSuccess, handleError)
328 |
329 | exports.fetchLocationsForPaperIds = (ids, callbackPass, callbackFail) ->
330 | # TODO consider integrating this with gdata
331 | config = null
332 | request =
333 | mp2l: ids
334 | tbl: dbSuffix
335 | if ids.length > 25
336 | config = { doPOST: true}
337 | handleSuccess = (ajaxData) ->
338 | returnData = []
339 | if ajaxData?
340 | for paper in ajaxData
341 | returnData.push
342 | id : paper?.id ? 0
343 | x : paper?.x ? null
344 | y : paper?.y ? null
345 | r : paper?.r ? null
346 | callbackPass(returnData)
347 | handleError = ->
348 | if callbackFail?
349 | callbackFail()
350 | AJAX.doRequest(request, handleSuccess, handleError, config)
351 |
352 | exports.fetchPaperIdAtLocation = (x, y, callbackPass, callbackFail) ->
353 | request =
354 | ml2p: [x,y]
355 | tbl: dbSuffix
356 | handleSuccess = (ajaxData) ->
357 | returnData =
358 | id : ajaxData?.id ? 0
359 | x : ajaxData?.x ? null
360 | y : ajaxData?.y ? null
361 | r : ajaxData?.r ? null
362 | callbackPass(returnData)
363 | handleError = ->
364 | if callbackFail?
365 | callbackFail()
366 | AJAX.doRequest(request, handleSuccess, handleError)
367 |
368 |
369 | exports.fetchSearchResults = (request,callbackPass,callbackFail) ->
370 | AJAX.doRequest(request,callbackPass,callbackFail)
371 |
372 |
373 | ###
374 | exports.fetchKeywordInWindow = (x, y, w, h, callbackPass, callbackFail) ->
375 | request =
376 | mkws: [x,y,w,h]
377 | handleSuccess = (ajaxData) ->
378 | returnData = ajaxData ? []
379 | callbackPass(returnData)
380 | handleError = ->
381 | if callbackFail?
382 | callbackFail()
383 | AJAX.doRequest(request, handleSuccess, handleError)
384 | ###
385 |
386 | return exports
387 |
--------------------------------------------------------------------------------
/js/coffee/world_test.coffee:
--------------------------------------------------------------------------------
1 | define ['app/Vec2D','app/ajax','app/world'], (Vec2D, AJAX, WORLD) ->
2 | run : ->
3 |
4 | module "WORLD"
5 |
6 | test "Load world tile data", ->
7 | AJAX.setDefaults()
8 | testCallback = (dims) ->
9 | #dims = WORLD.getDimensions()
10 | #ok not isNaN(dims.padding) and dims.padding >= 0,
11 | # "World padding should be non-negative number: #{dims.padding}"
12 | ok not isNaN(dims.tile_px_w) and dims.tile_px_w > 0 and
13 | not isNaN(dims.tile_px_h) and dims.tile_px_h > 0,
14 | "Tile pixel width and height should be positive numbers: #{dims.tile_px_w}x#{dims.tile_px_h}"
15 |
16 | ok not isNaN(dims.x_min) and not isNaN(dims.y_min) and
17 | not isNaN(dims.x_max) and not isNaN(dims.y_max) and
18 | dims.x_min < dims.x_max and dims.y_min < dims.y_max,
19 | "World dimensions should not be zero or negative: x = [#{dims.x_min},#{dims.x_max}] , y = [#{dims.y_min},#{dims.y_max}]"
20 |
21 | latestPaperId = WORLD.getLatestPaperId()
22 | ok not isNaN(latestPaperId) and latestPaperId.toString().length == 10,
23 | "Latest paper id should be 10 digit number: #{latestPaperId}"
24 |
25 | newPaperBoundaryId = WORLD.getNewPaperBoundaryId()
26 | ok not isNaN(newPaperBoundaryId) and newPaperBoundaryId.toString().length == 10 and newPaperBoundaryId < latestPaperId,
27 | "Boundary paper id should be 10 digit number: #{latestPaperId}, and less than latest paper id"
28 |
29 | ## Test tilings (tune as necessary with tile data)
30 |
31 | ok WORLD.isWorldReady(),
32 | "World ready"
33 |
34 | # TODO
35 | #equal WORLD.getClosestTiling(5).depth, 0
36 | #equal WORLD.getClosestTiling(5).numx, 4
37 | #equal WORLD.getClosestTiling(120).depth, 3
38 | #equal WORLD.getClosestTiling(120).numx, 72
39 | #equal WORLD.getClosestTiling(200).depth, 4
40 | #equal WORLD.getClosestTiling(200).numx, 216
41 |
42 |
43 | # Test tile urls
44 | #dims = WORLD.getDimensions()
45 | maxTileWidth = 1e8
46 | depth = WORLD.getClosestTiling(maxTileWidth).depth
47 | # Currently x direction sets the y-tiling
48 | farEdge = dims.x_max - 50 -dims.x_min
49 | # Add 10 as rounding errors adding up!
50 | middle = Math.ceil((dims.x_max - dims.x_min)/2)+50
51 | tileInfo = WORLD.getTileInfoAtPosition(depth,farEdge,middle)
52 | equal tileInfo.path, "tiles/0/4/3.png"
53 |
54 | equal WORLD.getClosestLabelZone(5).depth, 8
55 |
56 | start()
57 | errorCallback = ->
58 | ok false,
59 | "Callback error"
60 | start()
61 | stop()
62 | WORLD.loadWorldData(testCallback,errorCallback)
63 |
64 | test "Meta for paper id", ->
65 | testId = 2115344314
66 | AJAX.setDefaults()
67 | testCallback = (wrapData) ->
68 | equal wrapData.id, testId,
69 | "Get back correct id"
70 | equal wrapData.authors, "R.Fleischer,R.Knegjens",
71 | "Get back correct author"
72 | equal wrapData.title[..18], "Effective Lifetimes",
73 | "Get back correct title"
74 | equal wrapData.arxivId, "1109.5115",
75 | "Get back correct arxiv id"
76 | equal wrapData.categories, "hep-ph,hep-ex",
77 | "Get back correct categories"
78 | equal wrapData.inspire, "928287",
79 | "Get back correct inspire record"
80 | equal wrapData.journal[..9], "EuroPhysJC",
81 | "Get back correct journal"
82 | ok not isNaN(wrapData.numCites),
83 | "Number cites given as number"
84 | start()
85 | errorCallback = ->
86 | ok false,
87 | "Callback error"
88 | start()
89 | stop()
90 | WORLD.fetchMetaForPaperId(testId,testCallback,errorCallback)
91 |
92 |
93 | test "Abstract for paper id", ->
94 | testId = 2115344314
95 | stop()
96 | AJAX.setDefaults()
97 | testCallback = (wrapData) ->
98 | equal(wrapData.id, testId, "Get back correct id")
99 | abstStr = wrapData.abstract[..10]
100 | ok abstStr == "Measurement" or abstStr == "(no abstrac",
101 | "Get back first 10 characters of abstract"
102 | start()
103 | errorCallback = ->
104 | ok false,
105 | "Callback error"
106 | start()
107 | WORLD.fetchAbstractForPaperId(testId,testCallback,errorCallback)
108 |
109 | test "Fetch location of paper id", ->
110 | testId = 2115344314
111 | AJAX.setDefaults()
112 | testCallback = (wrapData) ->
113 | equal wrapData[0].id, testId,
114 | "Get back correct id"
115 | ok wrapData[0].x? and wrapData[0].y? and wrapData[0].r?,
116 | "x,y,r defined"
117 | start()
118 | errorCallback = ->
119 | ok false,
120 | "Callback error"
121 | start()
122 | stop()
123 | WORLD.fetchLocationsForPaperIds([testId],testCallback,errorCallback)
124 |
125 | test "Fetch paper id at location (calls inverse first)", ->
126 | testId = 2115344314
127 | AJAX.setDefaults()
128 | testCallback = (wrapData) ->
129 | equal wrapData.id, 2115344314,
130 | "Get back correct id"
131 | start()
132 | errorCallback = ->
133 | ok false,
134 | "Callback error"
135 | start()
136 | prepCallback = (wrapData) ->
137 | WORLD.fetchPaperIdAtLocation(wrapData[0].x,wrapData[0].y,testCallback,errorCallback)
138 | stop()
139 | WORLD.fetchLocationsForPaperIds([testId],prepCallback,errorCallback)
140 |
141 | ###
142 | test "Fetch keywords in window", ->
143 | AJAX.setDefaults()
144 | stop()
145 | testCallback = (wrapData) ->
146 | equal wrapData[0].kw, "test keyword",
147 | "Get back correct keyword"
148 | start()
149 | errorCallback = ->
150 | ok false,
151 | "Callback error"
152 | start()
153 | WORLD.fetchKeywordInWindow(0,0,100,100,testCallback,errorCallback)
154 | ###
155 |
--------------------------------------------------------------------------------
/js/lib/jquery-ui-autocomplete.html.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery UI Autocomplete HTML Extension
3 | *
4 | * Copyright 2010, Scott González (http://scottgonzalez.com)
5 | * Dual licensed under the MIT or GPL Version 2 licenses.
6 | *
7 | * http://github.com/scottgonzalez/jquery-ui-extensions
8 | */
9 | (function( $ ) {
10 |
11 | var proto = $.ui.autocomplete.prototype,
12 | initSource = proto._initSource;
13 |
14 | function filter( array, term ) {
15 | var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
16 | return $.grep( array, function(value) {
17 | return matcher.test( $( "" ).html( value.label || value.value || value ).text() );
18 | });
19 | }
20 |
21 | $.extend( proto, {
22 | _initSource: function() {
23 | if ( this.options.html && $.isArray(this.options.source) ) {
24 | this.source = function( request, response ) {
25 | response( filter( this.options.source, request.term ) );
26 | };
27 | } else {
28 | initSource.call( this );
29 | }
30 | },
31 |
32 | _renderItem: function( ul, item) {
33 | return $( "
" )
34 | .data( "item.autocomplete", item )
35 | .append( $( "
" )[ this.options.html ? "html" : "text" ]( item.label ) )
36 | .appendTo( ul );
37 | }
38 | });
39 |
40 | })( jQuery );
41 |
--------------------------------------------------------------------------------
/js/lib/jquery.mousewheel.js:
--------------------------------------------------------------------------------
1 | /*! Copyright (c) 2013 Brandon Aaron (http://brandonaaron.net)
2 | * Licensed under the MIT License (LICENSE.txt).
3 | *
4 | * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
5 | * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
6 | * Thanks to: Seamus Leahy for adding deltaX and deltaY
7 | *
8 | * Version: 3.1.3
9 | *
10 | * Requires: 1.2.2+
11 | */
12 |
13 | (function (factory) {
14 | if ( typeof define === 'function' && define.amd ) {
15 | // AMD. Register as an anonymous module.
16 | define(['jquery'], factory);
17 | } else if (typeof exports === 'object') {
18 | // Node/CommonJS style for Browserify
19 | module.exports = factory;
20 | } else {
21 | // Browser globals
22 | factory(jQuery);
23 | }
24 | }(function ($) {
25 |
26 | var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'];
27 | var toBind = 'onwheel' in document || document.documentMode >= 9 ? ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'];
28 | var lowestDelta, lowestDeltaXY;
29 |
30 | if ( $.event.fixHooks ) {
31 | for ( var i = toFix.length; i; ) {
32 | $.event.fixHooks[ toFix[--i] ] = $.event.mouseHooks;
33 | }
34 | }
35 |
36 | $.event.special.mousewheel = {
37 | setup: function() {
38 | if ( this.addEventListener ) {
39 | for ( var i = toBind.length; i; ) {
40 | this.addEventListener( toBind[--i], handler, false );
41 | }
42 | } else {
43 | this.onmousewheel = handler;
44 | }
45 | },
46 |
47 | teardown: function() {
48 | if ( this.removeEventListener ) {
49 | for ( var i = toBind.length; i; ) {
50 | this.removeEventListener( toBind[--i], handler, false );
51 | }
52 | } else {
53 | this.onmousewheel = null;
54 | }
55 | }
56 | };
57 |
58 | $.fn.extend({
59 | mousewheel: function(fn) {
60 | return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
61 | },
62 |
63 | unmousewheel: function(fn) {
64 | return this.unbind("mousewheel", fn);
65 | }
66 | });
67 |
68 |
69 | function handler(event) {
70 | var orgEvent = event || window.event,
71 | args = [].slice.call(arguments, 1),
72 | delta = 0,
73 | deltaX = 0,
74 | deltaY = 0,
75 | absDelta = 0,
76 | absDeltaXY = 0,
77 | fn;
78 | event = $.event.fix(orgEvent);
79 | event.type = "mousewheel";
80 |
81 | // Old school scrollwheel delta
82 | if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta; }
83 | if ( orgEvent.detail ) { delta = orgEvent.detail * -1; }
84 |
85 | // New school wheel delta (wheel event)
86 | if ( orgEvent.deltaY ) {
87 | deltaY = orgEvent.deltaY * -1;
88 | delta = deltaY;
89 | }
90 | if ( orgEvent.deltaX ) {
91 | deltaX = orgEvent.deltaX;
92 | delta = deltaX * -1;
93 | }
94 |
95 | // Webkit
96 | if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY; }
97 | if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = orgEvent.wheelDeltaX * -1; }
98 |
99 | // Look for lowest delta to normalize the delta values
100 | absDelta = Math.abs(delta);
101 | if ( !lowestDelta || absDelta < lowestDelta ) { lowestDelta = absDelta; }
102 | absDeltaXY = Math.max(Math.abs(deltaY), Math.abs(deltaX));
103 | if ( !lowestDeltaXY || absDeltaXY < lowestDeltaXY ) { lowestDeltaXY = absDeltaXY; }
104 |
105 | // Get a whole value for the deltas
106 | fn = delta > 0 ? 'floor' : 'ceil';
107 | delta = Math[fn](delta / lowestDelta);
108 | deltaX = Math[fn](deltaX / lowestDeltaXY);
109 | deltaY = Math[fn](deltaY / lowestDeltaXY);
110 |
111 | // Add event and delta to the front of the arguments
112 | args.unshift(event, delta, deltaX, deltaY);
113 |
114 | return ($.event.dispatch || $.event.handle).apply(this, args);
115 | }
116 |
117 | }));
118 |
--------------------------------------------------------------------------------
/js/lib/mousetrap.js:
--------------------------------------------------------------------------------
1 | /* mousetrap v1.3.2 craig.is/killing/mice */
2 | (function(){function s(a,b,c){a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent("on"+b,c)}function y(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return h[a.which]?h[a.which]:z[a.which]?z[a.which]:String.fromCharCode(a.which).toLowerCase()}function t(a,b){a=a||{};var c=!1,d;for(d in m)a[d]&&m[d]>b?c=!0:m[d]=0;c||(p=!1)}function A(a,b,c,d,g){var f,e,h=[],j=c.type;if(!l[a])return[];"keyup"==j&&u(a)&&(b=[a]);for(f=0;f
d||h.hasOwnProperty(d)&&(q[h[d]]=d)}c=q[a]?"keydown":"keypress"}"keypress"==
5 | c&&b.length&&(c="keydown");return c}function C(a,b,c,d,g){r[a+":"+c]=b;a=a.replace(/\s+/g," ");var f=a.split(" "),e,h,j=[];if(1":".","?":"/","|":"\\"},E={option:"alt",command:"meta","return":"enter",escape:"esc"},q,l={},r={},m={},D,x=!1,p=!1,g=1;20>g;++g)h[111+g]="f"+g;for(g=0;9>=g;++g)h[g+96]=g;s(document,"keypress",w);s(document,"keydown",w);s(document,"keyup",w);var k={bind:function(a,b,c){a=a instanceof Array?a:[a];for(var d=0;d':
138 | chs.append(ch)
139 | ch = self.__nextChar()
140 | chs.append(ch)
141 | self.__nextChar()
142 | tok.str += ''.join(chs)
143 | if tok.str[-1] == '>':
144 | tok.kind = Token.doctype
145 | self.__inTag = False
146 | else:
147 | tok.kind = Token.invalid
148 |
149 | elif tok.str == '
7 | stuff...
8 |
9 |
10 | Comment form (for turning stuff on):
11 |
12 | """
13 |
14 | import sys
15 | import re
16 |
17 | from lexer import Token, Lexer
18 |
19 | class DOMDoctype(object):
20 | def __init__(self, doctype):
21 | self.doctype = doctype
22 |
23 | def toStr(self, params):
24 | return self.doctype
25 |
26 | class DOMComment(object):
27 | def __init__(self, comment):
28 | self.comment = comment
29 | self.cond = None
30 | self.condId = None
31 | self.condStr = None
32 | if comment == '':
33 | self.cond = 'endif'
34 | else:
35 | match = re.match(r'', comment, re.DOTALL)
36 | if match:
37 | groups = match.groups()
38 | if groups[0] is not None:
39 | self.cond = 'ifnot'
40 | else:
41 | self.cond = 'if'
42 | self.condId = groups[1]
43 | if groups[2] is None:
44 | self.condStr = groups[3]
45 |
46 | def toStr(self, params):
47 | if self.condStr is not None:
48 | cond = self.condId in params and params[self.condId]
49 | if self.cond == 'ifnot':
50 | cond = not cond
51 | if cond:
52 | return self.condStr
53 | else:
54 | return ''
55 | elif 'discardComments' in params and params['discardComments']:
56 | return ''
57 | else:
58 | return self.comment
59 |
60 | class DOMText(object):
61 | def __init__(self, text):
62 | self.text = text
63 |
64 | def toStr(self, params):
65 | return re.sub(r'\n+', r'\n', self.text)
66 | #return self.text
67 |
68 | class DOMElement(object):
69 | def __init__(self, tagName, attrs, body):
70 | self.tagName = tagName
71 | self.attrs = attrs
72 | self.body = body
73 |
74 | def toStr(self, params):
75 | strs = ['<{}'.format(self.tagName)]
76 | for attr in self.attrs:
77 | strs.append(' {}={}'.format(attr[0], attr[1]))
78 | if self.body is None:
79 | strs.append(' />')
80 | else:
81 | strs.append('>')
82 | strs.append(self.body.toStr(params))
83 | strs.append('{}>'.format(self.tagName))
84 | return ''.join(strs)
85 |
86 | class DOMBody(object):
87 | def __init__(self, items):
88 | self.items = items
89 |
90 | def toStr(self, params={}):
91 | strs = []
92 | keep = True
93 | for i in xrange(len(self.items)):
94 | item = self.items[i]
95 | if isinstance(item, DOMComment) and item.condStr is None:
96 | if (item.cond == 'if' or item.cond == 'ifnot'):
97 | cond = item.condId in params and params[item.condId]
98 | if item.cond == 'ifnot':
99 | cond = not cond
100 | keep = cond
101 | elif item.cond == 'endif':
102 | keep = True
103 | elif not keep:
104 | pass
105 | else:
106 | strs.append(item.toStr(params))
107 | return ''.join(strs)
108 |
109 | class ParseException(Exception):
110 | def __init__(self, tok, msg):
111 | if tok is None:
112 | self.str = msg
113 | else:
114 | self.str = tok.locnStr() + " " + msg
115 |
116 | def parseSingleElement(lexer):
117 | if lexer.isKind(Token.doctype):
118 | tok = lexer.curToken()
119 | lexer.nextToken()
120 | return DOMDoctype(tok.str)
121 |
122 | elif lexer.isKind(Token.comment):
123 | tok = lexer.curToken()
124 | lexer.nextToken()
125 | return DOMComment(tok.str)
126 |
127 | elif lexer.optKind(Token.openStartTag):
128 | tagName = lexer.getId()
129 | attrs = []
130 | body = None
131 |
132 | # parse attributes
133 | while lexer.isKind(Token.identifier):
134 | attrName = lexer.getId()
135 | lexer.getKind(Token.equals)
136 | attrVal = lexer.getKind(Token.string)
137 | attrs.append((attrName, attrVal))
138 |
139 | # parse end of tag
140 | emptyElement = lexer.optKind(Token.forwardSlash)
141 | lexer.getKind(Token.closeTag)
142 |
143 | if not emptyElement:
144 | # parse body
145 | items = []
146 | while not lexer.isKind(Token.openEndTag):
147 | item = parseSingleElement(lexer)
148 | items.append(item)
149 | body = DOMBody(items)
150 |
151 | # parse closing tag
152 | tagTok = lexer.curToken()
153 | lexer.getKind(Token.openEndTag)
154 | endTagName = lexer.getId()
155 | lexer.getKind(Token.closeTag)
156 |
157 | if endTagName != tagName:
158 | raise ParseException(tagTok, "closing tag mismatch, expecting {}> got {}>".format(tagName, endTagName))
159 |
160 | return DOMElement(tagName, attrs, body)
161 |
162 | elif lexer.isKind(Token.text):
163 | # parse text
164 | tok = lexer.curToken()
165 | text = []
166 | while tok.kind == Token.text:
167 | text.append(tok.str)
168 | tok = lexer.nextToken()
169 | return DOMText(''.join(text))
170 |
171 | else:
172 | raise ParseException(lexer.curToken(), "unexpected")
173 |
174 | def parseHTML(fileName):
175 | fileObj = open(fileName)
176 | lexer = Lexer(fileName, fileObj)
177 | items = []
178 | while not lexer.isEnd():
179 | item = parseSingleElement(lexer)
180 | items.append(item)
181 | fileObj.close()
182 | return DOMBody(items)
183 |
184 | if __name__ == "__main__":
185 | try:
186 | body = parseHTML(sys.argv[1])
187 | sys.stdout.write(body.toStr())
188 | except ParseException as er:
189 | sys.stderr.write(str(er) + '\n')
190 |
--------------------------------------------------------------------------------
/unittests.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Xiwi:boa unit tests
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------