├── .bowerrc
├── .gitignore
├── .gitmodules
├── .travis.yml
├── CHANGELOG.md
├── Gruntfile.js
├── LICENSE
├── README.md
├── bower-local
├── README.txt
└── ol3
│ ├── ol-debug.js
│ ├── ol.css
│ └── ol.js
├── bower.json
├── build.config.js
├── changelog.tpl
├── clean-build
├── geonode_config.js
├── gjslint.py
├── gjslint_config.conf
├── karma
└── karma-unit.tpl.js
├── module.prefix
├── module.suffix
├── package.json
├── quicksetup.sh
├── src
├── README.md
├── app
│ ├── DiffPanelController.js
│ ├── HistoryPanelController.js
│ ├── LoomModule.js
│ ├── NotificaationPanelController.js
│ ├── Proj4Defs.js
│ ├── PulldownController.js
│ ├── README.md
│ ├── app.js
│ ├── app.less
│ ├── app.spec.js
│ └── locales
│ │ ├── en.js
│ │ └── es.js
├── assets
│ ├── README.md
│ ├── media-default.png
│ └── media-error.png
├── common
│ ├── README.md
│ ├── addlayers
│ │ ├── AddLayersDirective.js
│ │ ├── AddLayersModule.js
│ │ ├── AddServerDirective.js
│ │ ├── LayerInfoPopoverDirective.js
│ │ ├── ServerService.js
│ │ ├── ServerService.spec.js
│ │ ├── partials
│ │ │ ├── addlayers.tpl.html
│ │ │ └── addserver.tpl.html
│ │ └── style
│ │ │ └── addlayers.less
│ ├── arrangeable
│ │ ├── ArrangeableDirective.js
│ │ ├── ArrangeableModule.js
│ │ └── style
│ │ │ └── arrangeable.less
│ ├── configuration
│ │ ├── ConfigModule.js
│ │ ├── ConfigService.js
│ │ └── ConfigService.spec.js
│ ├── diff
│ │ ├── DiffCommon.js
│ │ ├── DiffListDirective.js
│ │ ├── DiffModule.js
│ │ ├── DiffPanelDirective.js
│ │ ├── DiffService.js
│ │ ├── DiffService.spec.js
│ │ ├── FeatureBlameService.js
│ │ ├── FeatureDiffController.js
│ │ ├── FeatureDiffDirective.js
│ │ ├── FeatureDiffService.js
│ │ ├── FeaturePanelDirective.js
│ │ ├── PanelSeparatorDirective.js
│ │ ├── partial
│ │ │ ├── difflist.tpl.html
│ │ │ ├── diffpanel.tpl.html
│ │ │ ├── featurediff.tpl.html
│ │ │ ├── featurepanel.tpl.html
│ │ │ └── panelseparator.tpl.html
│ │ └── style
│ │ │ └── diff.less
│ ├── featuremanager
│ │ ├── AttributeEditDirective.js
│ │ ├── DrawSelectDirective.js
│ │ ├── ExclusiveModeDirective.js
│ │ ├── ExclusiveModeService.js
│ │ ├── FeatureInfoBoxDirective.js
│ │ ├── FeatureManagerModule.js
│ │ ├── FeatureManagerService.js
│ │ ├── FeatureManagerService.spec.js
│ │ ├── partial
│ │ │ ├── attributeedit.tpl.html
│ │ │ ├── drawselect.tpl.html
│ │ │ ├── exclusivemode.tpl.html
│ │ │ └── featureinfobox.tpl.html
│ │ └── style
│ │ │ └── featuremanager.less
│ ├── geogig
│ │ ├── GeoGigModule.js
│ │ ├── GeoGigPrototypes.js
│ │ └── GeoGigService.js
│ ├── history
│ │ ├── HistoryDiffDirective.js
│ │ ├── HistoryModule.js
│ │ ├── HistoryPanelDirective.js
│ │ ├── HistoryPopoverDirective.js
│ │ ├── HistoryService.js
│ │ ├── partial
│ │ │ ├── historydiff.tpl.html
│ │ │ └── historypanel.tpl.html
│ │ └── style
│ │ │ └── history.less
│ ├── layers
│ │ ├── LayerAttributeVisibilityDirective.js
│ │ ├── LayerInfoDirective.js
│ │ ├── LayersDirective.js
│ │ ├── LayersModule.js
│ │ ├── partials
│ │ │ ├── layerattributevisibility.tpl.html
│ │ │ ├── layerinfo.tpl.html
│ │ │ └── layers.tpl.html
│ │ └── style
│ │ │ └── layers.less
│ ├── legend
│ │ ├── LegendDirective.js
│ │ ├── LegendModule.js
│ │ ├── partial
│ │ │ └── legend.tpl.html
│ │ └── style
│ │ │ └── legend.less
│ ├── map
│ │ ├── MapModule.js
│ │ ├── MapSaveDirective.js
│ │ ├── MapService.js
│ │ ├── MapService.spec.js
│ │ ├── partial
│ │ │ └── savemap.tpl.html
│ │ └── style
│ │ │ └── map.less
│ ├── merge
│ │ ├── ConflictService.js
│ │ ├── MergeDirective.js
│ │ ├── MergeModule.js
│ │ ├── partials
│ │ │ └── merge.tpl.html
│ │ └── style
│ │ │ └── merge.less
│ ├── modal
│ │ ├── DialogDirective.js
│ │ ├── DialogService.js
│ │ ├── ModalDirective.js
│ │ ├── ModalModule.js
│ │ ├── PasswordDirective.js
│ │ ├── partials
│ │ │ ├── dialog.tpl.html
│ │ │ ├── modal.tpl.html
│ │ │ └── password.tpl.html
│ │ └── style
│ │ │ └── modal.less
│ ├── notificationposter
│ │ ├── NotificationPosterDirective.js
│ │ └── NotificationPosterModule.js
│ ├── notifications
│ │ ├── GenerateNotificationDirective.js
│ │ ├── NotificationBadgeDirective.js
│ │ ├── NotificationsDirective.js
│ │ ├── NotificationsModule.js
│ │ ├── NotificationsService.js
│ │ ├── partial
│ │ │ ├── generatenotification.tpl.html
│ │ │ ├── notificationbadge.tpl.html
│ │ │ └── notifications.tpl.html
│ │ └── style
│ │ │ └── notifications.less
│ ├── pulldown
│ │ ├── PulldownModule.js
│ │ └── PulldownService.js
│ ├── refresh
│ │ ├── RefreshModule.js
│ │ └── RefreshService.js
│ ├── search
│ │ ├── SearchDirective.js
│ │ ├── SearchModule.js
│ │ ├── SearchService.js
│ │ ├── partial
│ │ │ └── search.tpl.html
│ │ └── style
│ │ │ └── search.less
│ ├── statistics
│ │ ├── StatisticsDirective.js
│ │ ├── StatisticsModule.js
│ │ ├── StatisticsService.js
│ │ ├── partial
│ │ │ └── statistics.tpl.html
│ │ └── style
│ │ │ └── statistics.less
│ ├── sync
│ │ ├── AddSynchronizationLinkDirective.js
│ │ ├── RemoteService.js
│ │ ├── SyncModule.js
│ │ ├── SynchronizationConfigurationDirective.js
│ │ ├── SynchronizationLinksDirective.js
│ │ ├── SynchronizationPrototypes.js
│ │ ├── SynchronizationService.js
│ │ ├── partials
│ │ │ ├── addsync.tpl.html
│ │ │ ├── syncconfig.tpl.html
│ │ │ └── synclinks.tpl.html
│ │ └── style
│ │ │ └── sync.less
│ ├── tableview
│ │ ├── FilterOptionsDirective.js
│ │ ├── TableViewDirective.js
│ │ ├── TableViewModule.js
│ │ ├── TableViewService.js
│ │ ├── partial
│ │ │ ├── filteroptions.tpl.html
│ │ │ └── tableview.tpl.html
│ │ └── style
│ │ │ └── tableview.less
│ ├── test
│ │ ├── TestModule.js
│ │ └── TestService.js
│ ├── timeline
│ │ ├── TimelineDirective.js
│ │ ├── TimelineModule.js
│ │ ├── TimelineService.js
│ │ ├── partials
│ │ │ └── timeline.tpl.html
│ │ └── style
│ │ │ └── timeline.less
│ ├── updatenotification
│ │ ├── UpdateNotificationDirective.js
│ │ ├── UpdateNotificationModule.js
│ │ └── partial
│ │ │ └── updatenotification.tpl.html
│ └── utils
│ │ ├── Globals.js
│ │ ├── LoadingDirective.js
│ │ ├── UtilsModule.js
│ │ ├── partial
│ │ └── loading.tpl.html
│ │ ├── style
│ │ └── utils.less
│ │ └── wktparser.js
├── index-lite.html
├── index.html
└── less
│ ├── README.md
│ ├── main.less
│ └── variables.less
└── tools.md
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "vendor",
3 | "json": "bower.json"
4 | }
5 |
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.sw*
2 | *~
3 | build/
4 | bin/
5 | node_modules/
6 | bower_components/
7 | vendor/
8 | .idea
9 | ZZZIGNORE_*.js
10 | .log
11 | .DS_Store
12 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ROGUE-JCTD/MapLoom/7fceedb67b14046df0ba1c158305cd9a90997cf6/.gitmodules
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.10"
4 |
5 | before_install:
6 | - mkdir travis-phantomjs
7 | - wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2 -O $PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2
8 | - tar -xvf $PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64.tar.bz2 -C $PWD/travis-phantomjs
9 | - export PATH=$PWD/travis-phantomjs/phantomjs-2.1.1-linux-x86_64/bin:$PATH
10 |
11 | before_script:
12 | - export DISPLAY=:99.0
13 | - sh -e /etc/init.d/xvfb start
14 | - npm install --quiet -g grunt-cli karma bower
15 | - bower install
16 | - grunt
17 | - grunt karmaconfig
18 |
19 | script: grunt karma
20 |
21 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Change Log
2 | ==========
3 | All notable changes to this project will be documented in this file.
4 |
5 | [1.5] - 2016-3-20
6 | ------------------------
7 | - Added support for layers from TileJSON servers
8 | - Jasmine unit-test coverage added for major components
9 | - Layer attribute visibility is now editable through dialog box
10 |
11 | [1.2] - 2015-05-12
12 | ------------------------
13 | - Re-ordering of layers fix
14 | - Fixes adding TMS layer
15 | - FeatureInfoBox will show an attribute values as hyperlink when it start with http, ftp, https
16 |
17 | [1.1] - 2015-01-20
18 | ------------------
19 | - Removes validation that restricted users from adding WMS layers that do not have a url ending with 'geoserver/wms'.
20 | - Updates OL3.
21 | - Fixes bug in heatmap that made unneeded requests to Geoserver.
22 |
23 | [1.0] - 2014-12-04
24 | ------------------
25 | - Initial MapLoom release re-released using 1.0 tag.
26 |
27 | [unreleased]: https://github.com/ROGUE-JCTD/MapLoom/compare/release-1.2...HEAD
28 | [1.2]: https://github.com/ROGUE-JCTD/MapLoom/compare/release-1.1...release-1.2
29 | [1.1]: https://github.com/ROGUE-JCTD/MapLoom/compare/release-1.0...release-1.1
30 | [1.0]: https://github.com/ROGUE-JCTD/MapLoom/tree/release-1.0
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013-2014 LMN Solutions, LLC
4 | Copyright (c) 2014 Noblis NSP
5 | Copyright (c) 2015-2016 Prominent Edge, LLC.
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy of
8 | this software and associated documentation files (the "Software"), to deal in
9 | the Software without restriction, including without limitation the rights to
10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
11 | the Software, and to permit persons to whom the Software is furnished to do so,
12 | subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
19 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
20 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/ROGUE-JCTD/MapLoom)
2 |
3 | MapLoom
4 | =========
5 |
6 |
7 | MapLoom is a web client that leverages GeoGig to allow users to edit geographic information, view history, and sync layers with other GeoGig repositories. The goal is to put the power of distributed versioned editing into the hands of users. It's been developed to sit inside of [GeoNode](http://geonode.org) as an alternative to the default GeoNode map client but it can also be set up to stand alone. The best way to see MapLoom in action is to create a GeoSHAPE virtual machine. Please visit [How To Get GeoSHAPE](https://docs.google.com/document/d/1KMpk6dXuqvwfEi0pfRpaGY62j6ikoYtpYUPU0sJQAmk) document for instructions.
8 |
9 | ## Details
10 |
11 | MapLoom has been built using [OpenLayers 3](http://ol3js.org), Angular, and Bootstrap.
12 |
13 | License: The MapLoom source code is licensed under the [BSD 3 clause](http://opensource.org/licenses/BSD-3-Clause) license.
14 |
15 | ### WPS & Feature Type Attributes Statistics Considerations
16 | We are utilizing Geoserver Web Processing Services in a couple of places. For example, the 'zoom to layer data' uses a wps that is already included in geoserver. In the layer's table view, we have added the ability to view charts and statistics on the attribute values for the whole layer or a set of filtered features of a layer. To Accomplish this, we created a custom WPS called summarize_attrib which is using the geoscript functionality in geoserver. When the summarize/statistics button is clicked in maploom, geoserver will need to have the wps available. If you are using a geoserver instance that was created and configured using the [geoshape](http://www.geoshape.org) script, everything will be taken care of already and you do not need to do anything mentioned here. If you are using a different deployment of geoserver, you'll need: 1) the geoserver wps extention, 2) commons-math3-3.3.jar, 3) geoserver-2.5-SNAPSHOT-python-plugin.zip, and 4) the [scripts folder](https://github.com/ROGUE-JCTD/geoserver_data/blob/master/scripts)
17 |
18 | ## Quick Start
19 | Install Node.js and then:
20 |
21 | ```sh
22 | $ git clone git://github.com/ROGUE-JCTD/MapLoom
23 | $ cd MapLoom
24 | $ sudo npm -g install grunt-cli karma bower
25 | $ npm install
26 | $ bower install
27 | $ grunt watch
28 | ```
29 | You can then open `file:///path/to/MapLoom/build/index.html` in your browser.
30 |
31 | ```clean-build``` will ensure that all modules all pulled down again and is recommended when you pull the source where other might have added new dependancies.
32 |
33 | ## Recommended Development Setup
34 | The recommended way to develop maploom is to:
35 |
36 | - create a [GeoSHAPE CentOS Virtual Machine](https://docs.google.com/document/d/1SOX8pldVskbnngXNLEfxFPlWkgC93lr8j3AE5mgmC_8) which will have maploom configured as GeoNodes viewer/editor. We recommend using the ```vagrant``` approach specified in the document.
37 | - clone maploom repo on your host machine in the same folder in which you cloned the vagrant-geoshape repo
38 | - expose the MapLoom repository on the host into the VM by uncommenting this line in your Vagrant file in the `geoshape-vagrant` folder [```Vagrantfile```](https://github.com/ROGUE-JCTD/geoshape-vagrant/blob/master/Vagrantfile#L18). Note that this assumes your `MapLoom` and `geoshape-vagrant` repositories are in the same folder.
39 |
40 | ```config.vm.synced_folder "../MapLoom", "/MapLoom"```
41 |
42 | - run ```vagrant reload``` (or `vagrant halt` followed by `vagrant up`) to apply changes made to the vagrant file.
43 | - run ```grunt watch``` in the maploom directory on the host to buid maploom
44 | - symbolic link the built maploom into the virtual machine such that GeoNode uses the latest build maploom. from in-side the geoshape VM, run:
45 | ```
46 | geoshape-config maploom_dev
47 | ```
48 | - you can now go to http://geoshape_vm_server_ip/maps/new or http://geoshape_vm_server_ip/layers to create a map from a layer. Changes on your host machine will cause `grunt watch` to rebuild MapLoom, and the symbolic links created by `geoshape-config maploom_dev` will make the newly buit maploom immediately available to Geonode in your VM.
49 |
50 | Note that linter used by `grunt watch` is very paticular about the js programing style and guildlines. Be sure to fix all compile issues.
51 |
--------------------------------------------------------------------------------
/bower-local/README.txt:
--------------------------------------------------------------------------------
1 | These are dependencies that are not available currently through bower. When these packages become available, they should be removed from this folder.
--------------------------------------------------------------------------------
/bower-local/ol3/ol.css:
--------------------------------------------------------------------------------
1 | .ol-control,.ol-scale-line{position:absolute;padding:2px}.ol-box{box-sizing:border-box;border-radius:2px;border:2px solid #00f}.ol-mouse-position{top:8px;right:8px;position:absolute}.ol-scale-line{background:rgba(0,60,136,.3);border-radius:4px;bottom:8px;left:8px}.ol-scale-line-inner{border:1px solid #eee;border-top:none;color:#eee;font-size:10px;text-align:center;margin:1px;will-change:contents,width}.ol-overlay-container{will-change:left,right,top,bottom}.ol-unsupported{display:none}.ol-viewport .ol-unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.ol-control{background-color:rgba(255,255,255,.4);border-radius:4px}.ol-control:hover{background-color:rgba(255,255,255,.6)}.ol-zoom{top:.5em;left:.5em}.ol-rotate{top:.5em;right:.5em;transition:opacity .25s linear,visibility 0s linear}.ol-rotate.ol-hidden{opacity:0;visibility:hidden;transition:opacity .25s linear,visibility 0s linear .25s}.ol-zoom-extent{top:4.643em;left:.5em}.ol-full-screen{right:.5em;top:.5em}@media print{.ol-control{display:none}}.ol-control button{display:block;margin:1px;padding:0;color:#fff;font-size:1.14em;font-weight:700;text-decoration:none;text-align:center;height:1.375em;width:1.375em;line-height:.4em;background-color:rgba(0,60,136,.5);border:none;border-radius:2px}.ol-control button::-moz-focus-inner{border:none;padding:0}.ol-zoom-extent button{line-height:1.4em}.ol-compass{display:block;font-weight:400;font-size:1.2em;will-change:transform}.ol-touch .ol-control button{font-size:1.5em}.ol-touch .ol-zoom-extent{top:5.5em}.ol-control button:focus,.ol-control button:hover{text-decoration:none;background-color:rgba(0,60,136,.7)}.ol-zoom .ol-zoom-in{border-radius:2px 2px 0 0}.ol-zoom .ol-zoom-out{border-radius:0 0 2px 2px}.ol-attribution{text-align:right;bottom:.5em;right:.5em;max-width:calc(100% - 1.3em)}.ol-attribution ul{margin:0;padding:0 .5em;font-size:.7rem;line-height:1.375em;color:#000;text-shadow:0 0 2px #fff}.ol-attribution li{display:inline;list-style:none;line-height:inherit}.ol-attribution li:not(:last-child):after{content:" "}.ol-attribution img{max-height:2em;max-width:inherit;vertical-align:middle}.ol-attribution button,.ol-attribution ul{display:inline-block}.ol-attribution.ol-collapsed ul{display:none}.ol-attribution.ol-logo-only ul{display:block}.ol-attribution:not(.ol-collapsed){background:rgba(255,255,255,.8)}.ol-attribution.ol-uncollapsible{bottom:0;right:0;border-radius:4px 0 0;height:1.1em;line-height:1em}.ol-attribution.ol-logo-only{background:0 0;bottom:.4em;height:1.1em;line-height:1em}.ol-attribution.ol-uncollapsible img{margin-top:-.2em;max-height:1.6em}.ol-attribution.ol-logo-only button,.ol-attribution.ol-uncollapsible button{display:none}.ol-zoomslider{top:4.5em;left:.5em;height:200px}.ol-zoomslider button{position:relative;height:10px}.ol-touch .ol-zoomslider{top:5.5em}.ol-overviewmap{left:.5em;bottom:.5em}.ol-overviewmap.ol-uncollapsible{bottom:0;left:0;border-radius:0 4px 0 0}.ol-overviewmap .ol-overviewmap-map,.ol-overviewmap button{display:inline-block}.ol-overviewmap .ol-overviewmap-map{border:1px solid #7b98bc;height:150px;margin:2px;width:150px}.ol-overviewmap:not(.ol-collapsed) button{bottom:1px;left:2px;position:absolute}.ol-overviewmap.ol-collapsed .ol-overviewmap-map,.ol-overviewmap.ol-uncollapsible button{display:none}.ol-overviewmap:not(.ol-collapsed){background:rgba(255,255,255,.8)}.ol-overviewmap-box{border:2px dotted rgba(0,60,136,.7)}
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "MapLoom",
3 | "version": "1.6.0",
4 | "devDependencies": {},
5 | "dependencies": {
6 | "bootstrap": "3.0.0",
7 | "angular": "1.2.21",
8 | "angular-mocks": "1.2.21",
9 | "angular-bootstrap": "0.6.0",
10 | "angular-ui-router": "0.2.0",
11 | "jquery": "~2.0.3",
12 | "ol3": "./bower-local/ol3",
13 | "angular-translate": "2.2.0",
14 | "angular-cookies": "1.2.21",
15 | "jquery-sortable": "0.9.11",
16 | "blueimp-bootstrap-image-gallery": "3.1.3",
17 | "moment": "2.3.1",
18 | "bootstrap3-datetimepicker": "~2.1.11",
19 | "bootstrap-sortable": "*",
20 | "x2js": "1.1.7",
21 | "angular-xeditable": "0.1.8",
22 | "d3": "3.4.11",
23 | "proj4": "2.3.3",
24 | "mgrs": "git://github.com/proj4js/mgrs.git#0.0.3",
25 | "bootstrap-filestyl": "git://github.com/markusslima/bootstrap-filestyle.git#1.2.1"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/changelog.tpl:
--------------------------------------------------------------------------------
1 |
2 | # <%= version%> (<%= today%>)
3 |
4 | <% if (_(changelog.feat).size() > 0) { %> ## Features
5 | <% _(changelog.feat).forEach(function(changes, scope) { %>
6 | - **<%= scope%>:**
7 | <% changes.forEach(function(change) { %> - <%= change.msg%> (<%= helpers.commitLink(change.sha1) %>)
8 | <% }); %>
9 | <% }); %> <% } %>
10 |
11 | <% if (_(changelog.fix).size() > 0) { %> ## Fixes
12 | <% _(changelog.fix).forEach(function(changes, scope) { %>
13 | - **<%= scope%>:**
14 | <% changes.forEach(function(change) { %> - <%= change.msg%> (<%= helpers.commitLink(change.sha1) %>)
15 | <% }); %>
16 | <% }); %> <% } %>
17 |
18 | <% if (_(changelog.breaking).size() > 0) { %> ## Breaking Changes
19 | <% _(changelog.breaking).forEach(function(changes, scope) { %>
20 | - **<%= scope%>:**
21 | <% changes.forEach(function(change) { %> <%= change.msg%>
22 | <% }); %>
23 | <% }); %> <% } %>
24 |
--------------------------------------------------------------------------------
/clean-build:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo ========== Cleaning Bower ==========
4 | echo ====================================
5 | echo
6 | rm -rf vendor
7 | bower cache clean
8 | rc=$?
9 | if [ $rc != 0 ] ; then
10 | echo Failed to clean the bower cache
11 | exit $rc
12 | fi
13 |
14 | echo
15 | echo ========== Installing Bower Dependencies ==========
16 | echo ===================================================
17 | echo
18 | bower install
19 | rc=$?
20 | if [ $rc != 0 ] ; then
21 | echo Failed to install bower dependencies
22 | exit $rc
23 | fi
24 |
25 | echo
26 | echo ========== Running grunt ==========
27 | echo ===================================
28 | echo
29 | grunt $*
30 | rc=$?
31 | if [ $rc != 0 ] ; then
32 | exit $rc
33 | fi
34 |
--------------------------------------------------------------------------------
/gjslint.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # EASY-INSTALL-ENTRY-SCRIPT: 'closure-linter==2.3.11','console_scripts','gjslint'
3 | __requires__ = 'closure-linter==2.3.11'
4 | import sys
5 | from pkg_resources import load_entry_point
6 |
7 | sys.exit(
8 | load_entry_point('closure-linter==2.3.11', 'console_scripts', 'gjslint')()
9 | )
10 |
--------------------------------------------------------------------------------
/gjslint_config.conf:
--------------------------------------------------------------------------------
1 | --jslint_error=all
2 | --max_line_length=500
--------------------------------------------------------------------------------
/karma/karma-unit.tpl.js:
--------------------------------------------------------------------------------
1 | module.exports = function ( karma ) {
2 | karma.set({
3 | /**
4 | * From where to look for files, starting with the location of this file.
5 | */
6 | basePath: '../',
7 |
8 | /**
9 | * This is the list of file patterns to load into the browser during testing.
10 | */
11 | files: [
12 | <% scripts.forEach( function ( file ) { %>'<%= file %>',
13 | <% }); %>
14 | 'src/**/*.js'
15 | ],
16 | exclude: [
17 | 'src/assets/**/*.js'
18 | ],
19 | frameworks: [ 'jasmine' ],
20 | plugins: [ 'karma-jasmine', 'karma-phantomjs-launcher', 'karma-spec-reporter' ],
21 | preprocessors: {
22 | },
23 |
24 | /**
25 | * How to report, by default.
26 | */
27 | reporters: 'spec',
28 |
29 | /**
30 | * On which port should the browser connect, on which port is the test runner
31 | * operating, and what is the URL path for the browser to use.
32 | */
33 | port: 9018,
34 | runnerPort: 9100,
35 | urlRoot: '/',
36 |
37 | /**
38 | * Disable file watching by default.
39 | */
40 | autoWatch: false,
41 |
42 | /**
43 | * The list of browsers to launch to test on. This includes only "Firefox" by
44 | * default, but other browser names include:
45 | * Chrome, ChromeCanary, Firefox, Opera, Safari, PhantomJS
46 | *
47 | * Note that you can also use the executable name of the browser, like "chromium"
48 | * or "firefox", but that these vary based on your operating system.
49 | *
50 | * You may also leave this blank and manually navigate your browser to
51 | * http://localhost:9018/ when you're running tests. The window/tab can be left
52 | * open and the tests will automatically occur there during the build. This has
53 | * the aesthetic advantage of not launching a browser every time you save.
54 | */
55 | browsers: [
56 | 'PhantomJS'
57 | ]
58 | });
59 | };
60 |
--------------------------------------------------------------------------------
/module.prefix:
--------------------------------------------------------------------------------
1 | (function ( window, angular, undefined ) {
2 |
--------------------------------------------------------------------------------
/module.suffix:
--------------------------------------------------------------------------------
1 | })( window, window.angular );
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Prominent Edge",
3 | "name": "MapLoom",
4 | "version": "1.6.0",
5 | "homepage": "http://www.prominentedge.com",
6 | "licenses": {
7 | "type": "BSD",
8 | "url": "http://www.tldrlegal.com/license/bsd-2-clause-license-(freebsd)"
9 | },
10 | "bugs": "https://github.com/ROGUE-JCTD/MapLoom/issues",
11 | "repository": {
12 | "type": "git",
13 | "url": "git@github.com:ROGUE-JCTD/MapLoom.git"
14 | },
15 | "scripts": {
16 | "test": "grunt karma"
17 | },
18 | "dependencies": {},
19 | "devDependencies": {
20 | "closure-linter-wrapper": "~0.2.8",
21 | "grunt": "~0.4.1",
22 | "grunt-bump": "0.0.7",
23 | "grunt-coffeelint": "0.0.6",
24 | "grunt-contrib-clean": "~0.4.1",
25 | "grunt-contrib-coffee": "~0.7.0",
26 | "grunt-contrib-concat": "~0.3.0",
27 | "grunt-contrib-copy": "~0.4.1",
28 | "grunt-contrib-jshint": "~0.4.3",
29 | "grunt-contrib-uglify": "~0.2.0",
30 | "grunt-contrib-watch": "~0.4.0",
31 | "grunt-conventional-changelog": "~0.1.1",
32 | "grunt-gjslint": "~0.1.3",
33 | "grunt-html2js": "~0.1.3",
34 | "grunt-karma": "~0.12.1",
35 | "grunt-newer": "~0.7.0",
36 | "grunt-ngmin": "0.0.2",
37 | "grunt-recess": "~0.3.0",
38 | "jasmine": "^2.4.1",
39 | "karma": "^0.13.21",
40 | "karma-jasmine": "^0.3.7",
41 | "karma-phantomjs-launcher": "^1.0.0",
42 | "karma-spec-reporter": "~0.0.24",
43 | "phantomjs-prebuilt": "^2.0.0"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/quicksetup.sh:
--------------------------------------------------------------------------------
1 | sudo npm -g install grunt-cli karma bower
2 | npm install
3 | bower install
4 | grunt
--------------------------------------------------------------------------------
/src/README.md:
--------------------------------------------------------------------------------
1 | # The `src` Directory
2 |
3 | ## Overview
4 |
5 | The `src/` directory contains all code used in the application along with all
6 | tests of such code.
7 |
8 | ```
9 | src/
10 | |- app/
11 | | |- about/
12 | | |- home/
13 | | |- app.js
14 | | |- app.spec.js
15 | |- assets/
16 | |- common/
17 | | |- plusOne/
18 | |- less/
19 | | |- main.less
20 | | |- variables.less
21 | |- index.html
22 | ```
23 |
24 | - `src/app/` - application-specific code, i.e. code not likely to be reused in
25 | another application. [Read more »](app/README.md)
26 | - `src/assets/` - static files like fonts and images.
27 | [Read more »](assets/README.md)
28 | - `src/common/` - third-party libraries or components likely to be reused in
29 | another application. [Read more »](common/README.md)
30 | - `src/less/` - LESS CSS files. [Read more »](less/README.md)
31 | - `src/index.html` - this is the HTML document of the single-page application.
32 | See below.
33 |
34 | See each directory for a detailed explanation.
35 |
36 | ## `index.html`
37 |
38 | The `index.html` file is the HTML document of the single-page application (SPA)
39 | that should contain all markup that applies to everything in the app, such as
40 | the header and footer. It declares with `ngApp` that this is `ngBoilerplate`,
41 | specifies the main `AppCtrl` controller, and contains the `ngView` directive
42 | into which route templates are placed.
43 |
44 | Unlike any other HTML document (e.g. the templates), `index.html` is compiled as
45 | a Grunt template, so variables from `Gruntfile.js` and `package.json` can be
46 | referenced from within it. Changing `name` in `package.json` from
47 | "ng-boilerplate" will rename the resultant CSS and JavaScript placed in `build/`,
48 | so this HTML references them by variable for convenience.
49 |
--------------------------------------------------------------------------------
/src/app/DiffPanelController.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_diff_controller', []);
3 |
4 | module.controller('LoomDiffController',
5 | function($scope, diffService) {
6 | function assignScopeVariables() {
7 | $scope.title = diffService.title;
8 | $scope.diffService = diffService;
9 | var count = 0;
10 | for (var i = 0; i < diffService.conflicts.length; i++) {
11 | var obj = diffService.conflicts[i];
12 | if (!obj.resolved) {
13 | count += 1;
14 | }
15 | }
16 | $scope.isMerge = diffService.mergeDiff;
17 | $scope.numConflicts = count;
18 | }
19 |
20 | function updateScopeVariables() {
21 | if (!$scope.$$phase) {
22 | $scope.$apply(function() {
23 | assignScopeVariables();
24 | });
25 | } else {
26 | assignScopeVariables();
27 | }
28 | }
29 |
30 | $scope.clearDiff = function() {
31 | diffService.clearDiff();
32 | };
33 |
34 | assignScopeVariables();
35 |
36 | $scope.$watch('diffService.title', updateScopeVariables);
37 | $scope.$watch('diffService.conflicts', updateScopeVariables, true);
38 |
39 | $scope.$on('diff_performed', updateScopeVariables);
40 | $scope.$on('diff_cleared', updateScopeVariables);
41 | });
42 | })();
43 |
--------------------------------------------------------------------------------
/src/app/HistoryPanelController.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_history_controller', []);
3 |
4 | module.controller('LoomHistoryController',
5 | function($scope, historyService) {
6 | function assignScopeVariables() {
7 | $scope.title = historyService.title;
8 | }
9 |
10 | function updateScopeVariables() {
11 | if (!$scope.$$phase) {
12 | $scope.$apply(function() {
13 | assignScopeVariables();
14 | });
15 | } else {
16 | assignScopeVariables();
17 | }
18 | }
19 |
20 | $scope.clearHistory = function() {
21 | historyService.clearHistory();
22 | };
23 |
24 | assignScopeVariables();
25 |
26 | $scope.$watch('historyService.title', updateScopeVariables);
27 |
28 | $scope.$on('history_fetched', updateScopeVariables);
29 | $scope.$on('history_cleared', updateScopeVariables);
30 | });
31 | })();
32 |
--------------------------------------------------------------------------------
/src/app/LoomModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | angular.module('loom', [
3 | 'loom_configuration',
4 | 'loom_map',
5 | 'loom_notifications',
6 | 'loom_notification_poster',
7 | 'loom_notification_controller',
8 | 'loom_update_notification',
9 | 'loom_history',
10 | 'loom_history_controller',
11 | 'loom_diff',
12 | 'loom_diff_controller',
13 | 'loom_modal',
14 | 'loom_addlayers',
15 | 'loom_arrangeable',
16 | 'loom_layers',
17 | 'loom_geogig',
18 | 'loom_pulldown',
19 | 'loom_pulldown_controller',
20 | 'loom_feature_manager',
21 | 'loom_merge',
22 | 'loom_sync',
23 | 'loom_legend',
24 | 'loom_statistics',
25 | 'loom_table_view',
26 | 'loom_utils',
27 | 'loom_refresh',
28 | 'loom_search',
29 | 'loom_test',
30 | 'loom_timeline'
31 | ]);
32 | }());
33 |
--------------------------------------------------------------------------------
/src/app/NotificaationPanelController.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_notification_controller', []);
3 |
4 | module.controller('LoomNotificationController',
5 | function($scope, $translate, notificationService) {
6 | $scope.notificationStartTime = $translate.instant('since_time', {time: notificationService.startTime});
7 | });
8 | })();
9 |
--------------------------------------------------------------------------------
/src/app/PulldownController.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_pulldown_controller', []);
3 |
4 | module.controller('LoomPulldownController',
5 | function($scope, pulldownService, geogigService, diffService, historyService, mapService, configService) {
6 |
7 | $('#pulldown-content').on('show.bs.collapse', function(e) {
8 | $('#pulldown-content .in').not($(e.target).parents()).collapse('hide');
9 | });
10 |
11 | function assignScopeVariables() {
12 | // variables go here.
13 | $scope.diffPanel = pulldownService.diffPanel.getVisible();
14 | $scope.notificationsPanel = pulldownService.notificationsPanel.getVisible();
15 | $scope.layersPanel = pulldownService.layersPanel.getVisible();
16 | $scope.syncPanel = pulldownService.syncPanel.getVisible();
17 | $scope.mapService = mapService;
18 | $scope.configService = configService;
19 | $scope.historyPanel = pulldownService.historyPanel.getVisible();
20 | $scope.toggleEnabled = pulldownService.toggleEnabled;
21 | $scope.addLayers = pulldownService.addLayers;
22 | $scope.serversLoading = pulldownService.serversLoading;
23 | $scope.pulldownService = pulldownService;
24 | }
25 |
26 | function updateScopeVariables() {
27 | if (!$scope.$$phase) {
28 | $scope.$apply(function() {
29 | assignScopeVariables();
30 | });
31 | } else {
32 | assignScopeVariables();
33 | }
34 | }
35 |
36 | assignScopeVariables();
37 |
38 | $scope.$on('refresh-pulldown', updateScopeVariables);
39 | $scope.$watch('configService', updateScopeVariables);
40 | $scope.$watch('mapService', updateScopeVariables);
41 |
42 | var syncPanelEnabled = function() {
43 | pulldownService.syncPanel.enabled = geogigService.adminRepos.length > 0;
44 | updateScopeVariables();
45 | };
46 |
47 | $scope.$on('repoAdded', syncPanelEnabled);
48 | $scope.$on('repoRemoved', syncPanelEnabled);
49 |
50 | var diffPanelEnabled = function() {
51 | pulldownService.diffPanel.enabled = diffService.hasDifferences();
52 | updateScopeVariables();
53 | };
54 |
55 | $scope.$on('diff_performed', diffPanelEnabled);
56 | $scope.$on('diff_cleared', diffPanelEnabled);
57 |
58 | var historyPanelEnabled = function() {
59 | pulldownService.historyPanel.enabled = historyService.log.length > 0;
60 | updateScopeVariables();
61 | };
62 |
63 | $scope.$on('history_fetched', historyPanelEnabled);
64 | $scope.$on('history_cleared', historyPanelEnabled);
65 | $scope.$watch('pulldownService.toggleEnabled', updateScopeVariables);
66 | $scope.$watch('pulldownService.serversLoading', updateScopeVariables);
67 | });
68 | })();
69 |
--------------------------------------------------------------------------------
/src/app/README.md:
--------------------------------------------------------------------------------
1 | # The `src/app` Directory
2 |
3 | ## Overview
4 |
5 | ```
6 | src/
7 | |- app/
8 | | |- home/
9 | | |- about/
10 | | |- app.js
11 | | |- app.spec.js
12 | ```
13 |
14 | The `src/app` directory contains all code specific to this application. Apart
15 | from `app.js` and its accompanying tests (discussed below), this directory is
16 | filled with subdirectories corresponding to high-level sections of the
17 | application, often corresponding to top-level routes. Each directory can have as
18 | many subdirectories as it needs, and the build system will understand what to
19 | do. For example, a top-level route might be "products", which would be a folder
20 | within the `src/app` directory that conceptually corresponds to the top-level
21 | route `/products`, though this is in no way enforced. Products may then have
22 | subdirectories for "create", "view", "search", etc. The "view" submodule may
23 | then define a route of `/products/:id`, ad infinitum.
24 |
25 | As `ngBoilerplate` is quite minimal, take a look at the two provided submodules
26 | to gain a better understanding of how these are used as well as to get a
27 | glimpse of how powerful this simple construct can be.
28 |
29 | ## `app.js`
30 |
31 | This is our main app configuration file. It kickstarts the whole process by
32 | requiring all the modules from `src/app` that we need. We must load these now to
33 | ensure the routes are loaded. If as in our "products" example there are
34 | subroutes, we only require the top-level module, and allow the submodules to
35 | require their own submodules.
36 |
37 | As a matter of course, we also require the template modules that are generated
38 | during the build.
39 |
40 | However, the modules from `src/common` should be required by the app
41 | submodules that need them to ensure proper dependency handling. These are
42 | app-wide dependencies that are required to assemble your app.
43 |
44 | ```js
45 | angular.module( 'ngBoilerplate', [
46 | 'templates-app',
47 | 'templates-common',
48 | 'ngBoilerplate.home',
49 | 'ngBoilerplate.about'
50 | 'ui.state',
51 | 'ui.route'
52 | ])
53 | ```
54 |
55 | With app modules broken down in this way, all routing is performed by the
56 | submodules we include, as that is where our app's functionality is really
57 | defined. So all we need to do in `app.js` is specify a default route to follow,
58 | which route of course is defined in a submodule. In this case, our `home` module
59 | is where we want to start, which has a defined route for `/home` in
60 | `src/app/home/home.js`.
61 |
62 | ```js
63 | .config( function myAppConfig ( $stateProvider, $urlRouterProvider ) {
64 | $urlRouterProvider.otherwise( '/home' );
65 | })
66 | ```
67 |
68 | Use the main applications run method to execute any code after services
69 | have been instantiated.
70 |
71 | ```js
72 | .run( function run () {
73 | })
74 | ```
75 |
76 | And then we define our main application controller. This is a good place for logic
77 | not specific to the template or route, such as menu logic or page title wiring.
78 |
79 | ```js
80 | .controller( 'AppCtrl', function AppCtrl ( $scope, $location ) {
81 | $scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){
82 | if ( angular.isDefined( toState.data.pageTitle ) ) {
83 | $scope.pageTitle = toState.data.pageTitle + ' | ngBoilerplate' ;
84 | }
85 | });
86 | })
87 | ```
88 |
89 | ### Testing
90 |
91 | One of the design philosophies of `ngBoilerplate` is that tests should exist
92 | alongside the code they test and that the build system should be smart enough to
93 | know the difference and react accordingly. As such, the unit test for `app.js`
94 | is `app.spec.js`, though it is quite minimal.
95 |
--------------------------------------------------------------------------------
/src/app/app.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('MapLoom', [
3 | 'templates-app',
4 | 'templates-common',
5 | 'loom',
6 | 'ui.bootstrap',
7 | 'ui.router',
8 | 'pascalprecht.translate',
9 | 'loom_translations_en',
10 | 'loom_translations_es',
11 | 'xeditable'
12 | ]);
13 |
14 | module.run(function run(editableOptions) {
15 | editableOptions.theme = 'bs3';
16 | });
17 |
18 | module.controller('AppCtrl', function AppCtrl($scope, $window, $location, $translate, mapService, debugService,
19 | refreshService, dialogService) {
20 | $scope.$on('$stateChangeSuccess', function(event, toState) {
21 | if (angular.isDefined(toState.data.pageTitle)) {
22 | $scope.pageTitle = toState.data.pageTitle;
23 | }
24 | });
25 |
26 | $('body').on('show.bs.modal', function(e) {
27 | var modals = $('.modal.in');
28 | var backdrops = $('.modal-backdrop');
29 | for (var i = 0; i < modals.length; i++) {
30 | modals.eq(i).css('z-index', 760 - (modals.length - i) * 20);
31 | backdrops.eq(i).css('z-index', 750 - (modals.length - i) * 20);
32 | }
33 | $(e.target).css('z-index', 760);
34 | });
35 |
36 | var errorDialogShowing = false;
37 | onErrorCallback = function(msg) {
38 | if (goog.isDefAndNotNull(ignoreNextScriptError) && ignoreNextScriptError &&
39 | msg.indexOf('Script error') > -1) {
40 | ignoreNextScriptError = false;
41 | return;
42 | }
43 | if (errorDialogShowing) {
44 | return;
45 | }
46 | errorDialogShowing = true;
47 | console.log('==== onErrorCallback, error msg:', msg);
48 | var msg_string = msg;
49 | if (typeof msg != 'string') {
50 | msg_string = 'message not string. view console for object detail';
51 | }
52 | dialogService.error($translate.instant('error'), $translate.instant('script_error',
53 | {error: msg_string})).then(function() {
54 | errorDialogShowing = false;
55 | });
56 | };
57 |
58 | // Enable Proj4JS and add projection definitions
59 | ol.HAVE_PROJ4JS = ol.ENABLE_PROJ4JS && typeof proj4 == 'function';
60 |
61 | // load the predefined projections available when there is no network connectivity
62 | if (ol.HAVE_PROJ4JS === true) {
63 | maploomProj4Defs(proj4.defs);
64 | }
65 |
66 | $scope.mapService = mapService;
67 | $scope.refreshService = refreshService;
68 | });
69 |
70 | module.provider('debugService', function() {
71 | this.$get = function() {
72 | return this;
73 | };
74 |
75 | this.showDebugButtons = false;
76 | });
77 |
78 | module.provider('$exceptionHandler', function() {
79 | this.$get = function(errorLogService) {
80 | return errorLogService;
81 | };
82 | });
83 |
84 | module.factory('errorLogService', function($log, $window) {
85 | function log(exception, cause) {
86 | $log.error.apply($log, arguments);
87 | onErrorCallback(exception.toString());
88 | }
89 | // Return the logging function.
90 | return log;
91 | });
92 |
93 | module.config(function($translateProvider) {
94 | $translateProvider.preferredLanguage('en');
95 | });
96 | }());
97 |
98 |
--------------------------------------------------------------------------------
/src/app/app.spec.js:
--------------------------------------------------------------------------------
1 | describe( 'AppCtrl', function() {
2 | describe( 'isCurrentUrl', function() {
3 | var AppCtrl, $location, $scope;
4 |
5 | beforeEach( module( 'MapLoom' ) );
6 |
7 | beforeEach( inject( function( $controller, _$location_, $rootScope ) {
8 | $location = _$location_;
9 | $scope = $rootScope.$new();
10 | AppCtrl = $controller( 'AppCtrl', { $location: $location, $scope: $scope });
11 | }));
12 |
13 | it( 'should pass a dummy test', inject( function() {
14 | expect( AppCtrl ).toBeTruthy();
15 | }));
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/src/assets/README.md:
--------------------------------------------------------------------------------
1 | # The `src/assets` Directory
2 |
3 | There's really not much to say here. Every file in this directory is recursively transferred to `dist/assets/`.
4 |
5 |
--------------------------------------------------------------------------------
/src/assets/media-default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ROGUE-JCTD/MapLoom/7fceedb67b14046df0ba1c158305cd9a90997cf6/src/assets/media-default.png
--------------------------------------------------------------------------------
/src/assets/media-error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ROGUE-JCTD/MapLoom/7fceedb67b14046df0ba1c158305cd9a90997cf6/src/assets/media-error.png
--------------------------------------------------------------------------------
/src/common/README.md:
--------------------------------------------------------------------------------
1 | # The `src/common/` Directory
2 |
3 | The `src/common/` directory houses internal and third-party re-usable
4 | components. Essentially, this folder is for everything that isn't completely
5 | specific to this application.
6 |
7 | Each component resides in its own directory that may then be structured any way
8 | the developer desires. The build system will read all `*.js` files that do not
9 | end in `.spec.js` as source files to be included in the final build, all
10 | `*.spec.js` files as unit tests to be executed, and all `*.tpl.html` files as
11 | templates to compiled into the `$templateCache`. There is currently no way to
12 | handle components that do not meet this pattern.
13 |
14 | ```
15 | src/
16 | |- common/
17 | | |- plusOne/
18 | ```
19 |
20 | - `plusOne` - a simple directive to load a Google +1 Button on an element.
21 |
22 | Every component contained here should be drag-and-drop reusable in any other
23 | project; they should depend on no other components that aren't similarly
24 | drag-and-drop reusable.
25 |
--------------------------------------------------------------------------------
/src/common/addlayers/AddLayersModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | angular.module('loom_addlayers', [
3 | 'loom_addlayers_directive',
4 | 'loom_add_server_directive',
5 | 'loom_layerinfo_popover_directive',
6 | 'loom_server_service'
7 | ]);
8 | })();
9 |
--------------------------------------------------------------------------------
/src/common/addlayers/LayerInfoPopoverDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_layerinfo_popover_directive', []);
3 |
4 | module.directive('loomLayerinfoPopover',
5 | function($translate) {
6 | return {
7 | restrict: 'C',
8 | replace: false,
9 | link: function(scope, element) {
10 |
11 | var safeName = function() {
12 | if (goog.isDefAndNotNull(scope.layer.Name)) {
13 | var split = scope.layer.Name.split(':');
14 | return split[split.length - 1];
15 | }
16 | return '';
17 | };
18 |
19 | var safeTitle = function() {
20 | if (goog.isDefAndNotNull(scope.layer.Title)) {
21 | return scope.layer.Title;
22 | }
23 | return '';
24 | };
25 |
26 | var safeWorkspace = function() {
27 | if (goog.isDefAndNotNull(scope.layer.Name)) {
28 | var split = scope.layer.Name.split(':');
29 | return split[0];
30 | }
31 | return '';
32 | };
33 |
34 | var safeAbstract = function() {
35 | if (goog.isDefAndNotNull(scope.layer.Abstract)) {
36 | return scope.layer.Abstract;
37 | }
38 | return '';
39 | };
40 |
41 | var buildKeywords = function() {
42 | if (goog.isDefAndNotNull(scope.layer.KeywordList)) {
43 | return scope.layer.KeywordList.toString();
44 | }
45 | return '';
46 | };
47 |
48 | var content = '
' + $translate.instant('server_name') + ':
' +
49 | '' + safeName() + '
' +
50 | '' + $translate.instant('title') + ':
' +
51 | '' + safeTitle() + '
' +
52 | '' + $translate.instant('workspace') + ':
' +
53 | '' + safeWorkspace() + '
' +
54 | '' + $translate.instant('abstract') + ':
' +
55 | '' + safeAbstract() + '
' +
56 | '' + $translate.instant('keywords') + ':
' +
57 | '' + buildKeywords() + '
';
58 |
59 | element.popover({
60 | trigger: 'manual',
61 | animation: false,
62 | html: true,
63 | content: content,
64 | container: 'body',
65 | title: scope.layer.title
66 | });
67 | element.mouseenter(function() {
68 | if (element.closest('.collapsing').length === 0) {
69 | element.popover('show');
70 | }
71 | });
72 | element.mouseleave(function() {
73 | element.popover('hide');
74 | });
75 |
76 | scope.$watch('layer', function() {
77 | element.popover('destroy');
78 | var content = '' + $translate.instant('server_name') + ':
' +
79 | '' + safeName() + '
' +
80 | '' + $translate.instant('title') + ':
' +
81 | '' + safeTitle() + '
' +
82 | '' + $translate.instant('workspace') + ':
' +
83 | '' + safeWorkspace() + '
' +
84 | '' + $translate.instant('abstract') + ':
' +
85 | '' + safeAbstract() + '
' +
86 | '' + $translate.instant('keywords') + ':
' +
87 | '' + buildKeywords() + '
';
88 |
89 | element.popover({
90 | trigger: 'manual',
91 | animation: false,
92 | html: true,
93 | content: content,
94 | container: 'body',
95 | title: scope.layer.title
96 | });
97 | });
98 | }
99 | };
100 | });
101 | }());
102 |
--------------------------------------------------------------------------------
/src/common/addlayers/partials/addlayers.tpl.html:
--------------------------------------------------------------------------------
1 |
76 |
81 |
--------------------------------------------------------------------------------
/src/common/addlayers/partials/addserver.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
34 |
39 |
--------------------------------------------------------------------------------
/src/common/addlayers/style/addlayers.less:
--------------------------------------------------------------------------------
1 | #layer-list {
2 | .list-group-item {
3 | background-color: @addLayerListColor;
4 | color: @addLayerListFontColor;
5 | }
6 | }
7 |
8 | [loom-addlayers] .modal-body {
9 | overflow: hidden;
10 | overflow-y: auto;
11 | min-height: 200px;
12 | }
13 |
14 | .add-layers-loading.loom-loading {
15 | position: absolute;
16 | width: 320px;
17 | left: 0;
18 | height: 50px;
19 |
20 | .loading-container {
21 | background-color: @mainPanelColor;
22 | }
23 | }
24 |
25 | #server-list {
26 | max-height: 200px;
27 | }
28 |
29 | .custom-width-100 {
30 | width: 100%;
31 | }
32 |
33 | .custom-width-88 {
34 | width: 88%;
35 | }
36 |
37 | .right-and-center {
38 | position: relative;
39 | top: 8px;
40 | float: right;
41 | }
42 |
43 | .server-edit-icon {
44 | padding-right: 8px;
45 | }
46 |
47 | #server-add-loading {
48 | .loading-container {
49 | position: absolute;
50 | width: 100%;
51 | height: 100%;
52 | top: 0;
53 | left: 0;
54 | .z-index(@dialogLevel, 5);
55 | background-color: rgba(0,0,0,0.5);
56 | }
57 | .loading-spinner {
58 | border-color: white !important;
59 | }
60 | }
61 |
62 | .logged-in-as-input {
63 | background-color: darken(@featurePanelColor, 4%) !important;
64 | border: 1px solid darken(@featurePanelColor, 10%) !important;
65 | color: @mainFontColor !important;
66 | cursor: default !important;
67 | }
68 |
69 | .switch-user-btn {
70 | border: 1px solid darken(@featurePanelColor, 10%) !important;
71 | color: @mainFontColor !important;
72 | }
73 |
--------------------------------------------------------------------------------
/src/common/arrangeable/ArrangeableDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_arrangeable_directive', []);
3 |
4 | module.directive('loomArrangeable', function() {
5 | return {
6 | restrict: 'C',
7 | link: function(scope, element, attrs) { // Unused: attrs
8 | $(function() {
9 | var startIndex = -1;
10 | var config = {
11 | // set item relative to cursor position
12 | onDragStart: function($item, container, _super) {
13 | var height = $item.outerHeight();
14 | var width = $item.outerWidth();
15 | var offset = $item.offset(),
16 | pointer = container.rootGroup.pointer;
17 | startIndex = $item.index();
18 | adjustment = {
19 | left: pointer.left - offset.left,
20 | top: pointer.top - offset.top
21 | };
22 |
23 | _super($item, container);
24 |
25 | $item.css({
26 | width: width,
27 | height: height
28 | });
29 | },
30 | onDrag: function($item, position) {
31 | $item.css({
32 | left: position.left - adjustment.left,
33 | top: position.top - adjustment.top
34 | });
35 | },
36 | onDrop: function($item, container, _super) {
37 | scope.$eval(attrs.arrangeableCallback)(startIndex, $item.index());
38 | _super($item);
39 | },
40 | distance: 5
41 | };
42 |
43 | if (attrs.arrangeableHandle) {
44 | config.handle = attrs.arrangeableHandle;
45 | }
46 | if (attrs.arrangeableItemSelector) {
47 | config.itemSelector = attrs.arrangeableItemSelector;
48 | }
49 | if (attrs.arrangeablePlaceholder) {
50 | config.placeholder = attrs.arrangeablePlaceholder;
51 | }
52 | if (attrs.arrangeableDragDistance) {
53 | config.distance = attrs.arrangeableDragDistance;
54 | }
55 |
56 | element.sortable(config);
57 | });
58 | }
59 | };
60 | });
61 | }());
62 |
--------------------------------------------------------------------------------
/src/common/arrangeable/ArrangeableModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | angular.module('loom_arrangeable', [
3 | 'loom_arrangeable_directive'
4 | ]);
5 | }());
6 |
--------------------------------------------------------------------------------
/src/common/arrangeable/style/arrangeable.less:
--------------------------------------------------------------------------------
1 | .loom-arrangeable {
2 | body.dragging, body.dragging * {
3 | cursor: move !important;
4 | }
5 | .dragged {
6 | position: absolute;
7 | opacity: 0.5;
8 | .z-index(@dialogLevel, -1);
9 | }
10 | .placeholder {
11 | position: relative;
12 | height: 42px;
13 | box-shadow: inset 0 10px 10px -10px black,
14 | inset 0 -10px 10px -10px black;
15 | background-color: rgba(0,0,0,0.1);
16 | overflow: hidden;
17 | }
18 | .placeholder:before {
19 | position: absolute;
20 | display: none;
21 | }
22 | }
--------------------------------------------------------------------------------
/src/common/configuration/ConfigModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | angular.module('loom_configuration', [
3 | 'loom_config_service',
4 | 'ngCookies'
5 | ]);
6 | })();
7 |
--------------------------------------------------------------------------------
/src/common/configuration/ConfigService.spec.js:
--------------------------------------------------------------------------------
1 | describe('ConfigService', function() {
2 | var serverService;
3 | var configService;
4 | var q;
5 | var defer;
6 | var rootScope;
7 | var httpBackend;
8 | var dialogService;
9 | var window;
10 |
11 | //include the whole application to initialize all services and modules
12 | beforeEach(module('MapLoom'));
13 |
14 | beforeEach(inject(function(_serverService_, _configService_,_dialogService_, $httpBackend, $q, $rootScope, $window) {
15 | serverService = _serverService_;
16 | configService = _configService_;
17 | dialogService = _dialogService_;
18 | httpBackend = $httpBackend;
19 | q = $q;
20 | rootScope = $rootScope;
21 | window = $window;
22 | }));
23 |
24 | describe('service initialization', function() {
25 | it('should init the configuration object', function() {
26 | expect(configService.configuration).toBeDefined();
27 | expect(configService.configuration).not.toBe(null);
28 | expect(configService.username).toBe(configService.configuration.username);
29 | });
30 | });
31 |
32 | describe('getServerByUrl', function() {
33 | it('should return null if a server with a matching url cannot be found', function() {
34 | //the default servers do not have a url object defined, so even if we use a valid url
35 | //we should get no server
36 | var server = configService.getServerByURL(configService.configuration.sources[0].url);
37 | expect(server).toBe(null);
38 | });
39 | it('should return a server based on a given url if one is found', function(){
40 | //we will just use a url from a known/already created server and make sure the objects match
41 | configService.serverList[0].url = configService.configuration.sources[0].url;
42 | var server = configService.getServerByURL(configService.configuration.sources[0].url);
43 | expect(server).toBe(configService.serverList[0]);
44 | });
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/src/common/diff/DiffCommon.js:
--------------------------------------------------------------------------------
1 | var DiffColorMap = {
2 | ADDED: {
3 | fill: [0, 255, 0],
4 | stroke: [0, 102, 0]
5 | },
6 | REMOVED: {
7 | fill: [255, 0, 0],
8 | stroke: [102, 0, 0]
9 | },
10 | MODIFIED: {
11 | fill: [255, 255, 0],
12 | stroke: [102, 102, 0]
13 | },
14 | CONFLICT: {
15 | fill: [248, 117, 49],
16 | stroke: [150, 69, 20]
17 | },
18 | MERGED: {
19 | fill: [0, 0, 255],
20 | stroke: [0, 0, 102]
21 | },
22 | NO_CHANGE: {
23 | fill: [255, 255, 255],
24 | stroke: [102, 102, 102]
25 | }
26 | };
27 |
--------------------------------------------------------------------------------
/src/common/diff/DiffListDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_diff_list_directive', []);
3 |
4 | module.directive('loomDiffList', function(mapService) {
5 | return {
6 | restrict: 'C',
7 | replace: true,
8 | templateUrl: 'diff/partial/difflist.tpl.html',
9 | scope: {
10 | addList: '=',
11 | modifyList: '=',
12 | deleteList: '=',
13 | conflictList: '=',
14 | mergeList: '=',
15 | clickCallback: '='
16 | },
17 | link: function(scope) {
18 | scope.conciseName = function(input) {
19 | if (input.length > 9) {
20 | return input.substr(input.length - 4);
21 | } else {
22 | return input;
23 | }
24 | };
25 |
26 | scope.zoomToFeature = function(feature) {
27 | mapService.zoomToExtent(feature.extent, null, null, 0.5);
28 | };
29 | }
30 | };
31 | });
32 | }());
33 |
--------------------------------------------------------------------------------
/src/common/diff/DiffModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | angular.module('loom_diff', [
3 | 'loom_diff_list_directive',
4 | 'loom_diff_service',
5 | 'loom_diff_panel_directive',
6 | 'loom_feature_diff_directive',
7 | 'loom_feature_diff_service',
8 | 'loom_feature_diff_controller',
9 | 'loom_feature_panel_directive',
10 | 'loom_panel_separator_directive',
11 | 'loom_feature_blame_service'
12 | ]);
13 | }());
14 |
--------------------------------------------------------------------------------
/src/common/diff/DiffPanelDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_diff_panel_directive', []);
3 |
4 | module.directive('loomDiffPanel', function($rootScope, $translate, diffService, conflictService,
5 | pulldownService, dialogService) {
6 | return {
7 | restrict: 'C',
8 | replace: true,
9 | templateUrl: 'diff/partial/diffpanel.tpl.html',
10 | link: function(scope) { // Unused: element, attrs
11 | function updateVariables() {
12 | scope.adds = diffService.adds;
13 | scope.modifies = diffService.modifies;
14 | scope.deletes = diffService.deletes;
15 | scope.conflicts = diffService.conflicts;
16 | scope.merges = diffService.merges;
17 | scope.diffService = diffService;
18 | scope.featureClicked = diffService.clickCallback;
19 | scope.mergeButtons = diffService.mergeDiff;
20 | scope.conflictsText = $translate.instant('complete_merge');
21 | if (scope.numConflicts === 1) {
22 | scope.conflictsText = $translate.instant('single_conflict');
23 | } else if (scope.numConflicts > 1) {
24 | scope.conflictsText = $translate.instant('multiple_conflicts', {value: scope.numConflicts});
25 | }
26 | }
27 |
28 | updateVariables();
29 |
30 | scope.$watch('diffService.adds', updateVariables, true);
31 | scope.$watch('diffService.modifies', updateVariables, true);
32 | scope.$watch('diffService.deletes', updateVariables, true);
33 | scope.$watch('diffService.merges', updateVariables, true);
34 | scope.$watch('diffService.conflicts', updateVariables, true);
35 | scope.$watch('diffService.clickCallback', updateVariables);
36 |
37 | scope.cancel = function() {
38 | dialogService.warn($translate.instant('warning'), $translate.instant('sure_cancel_merge'),
39 | [$translate.instant('yes_btn'), $translate.instant('no_btn')], false).then(function(button) {
40 | switch (button) {
41 | case 0:
42 | diffService.clearDiff();
43 | conflictService.abort();
44 | pulldownService.defaultMode();
45 | break;
46 | case 1:
47 | break;
48 | }
49 | });
50 | };
51 |
52 | scope.done = function() {
53 | dialogService.open($translate.instant('commit_merge'), $translate.instant('sure_commit_merge'),
54 | [$translate.instant('yes_btn'), $translate.instant('no_btn')], false).then(function(button) {
55 | switch (button) {
56 | case 0:
57 | conflictService.commit();
58 | break;
59 | case 1:
60 | break;
61 | }
62 | });
63 | };
64 | }
65 | };
66 | });
67 | }());
68 |
--------------------------------------------------------------------------------
/src/common/diff/DiffService.spec.js:
--------------------------------------------------------------------------------
1 | describe('DiffService', function(){
2 | beforeEach(module('MapLoom'));
3 | beforeEach(module('loom_diff_service'));
4 |
5 | beforeEach(inject(function(_diffService_, _mapService_){
6 | diffService = _diffService_;
7 | mapService = _mapService_;
8 | repoId = '9726b5d4-95de-43e3-ae9c-9ff59a43a737';
9 |
10 | mockLayer = {
11 | get: function(type){
12 | if (type === 'metadata'){
13 | return {
14 | nativeName: 'incidentes_od3',
15 | geogigStore: repoId
16 | };
17 | }
18 | }
19 | };
20 |
21 | modifiedChange = {
22 | change: "MODIFIED",
23 | crs: "EPSG:4326",
24 | geometry: "POINT (-81.50724532541271 27.472059426243238)",
25 | id: "incidentes_od3/fid--4277d16a_14e8c4d9628_-7ffd"
26 | };
27 |
28 | }));
29 |
30 | describe('addChangeFeatureToCorrectChangeArray', function(){
31 | it('should push modified change to modified array', function(){
32 | var expectedResult;
33 |
34 | diffService.addChangeFeatureToCorrectChangeArray(modifiedChange, 'mockLayer');
35 | diffService.modifies.forEach(function(modifiedFeature) {
36 | if (modifiedFeature === 'mockLayer'){
37 | expectedResult = true;
38 | }
39 | });
40 | expect(expectedResult).toBe(true);
41 | });
42 | });
43 |
44 | describe('createOlFeatureBasedOnChange', function(){
45 | it('should return a valid OpenLayers feature object', function(){
46 | var diffLayer = new ol.layer.Vector({
47 | source: new ol.source.Vector({
48 | parser: null
49 | })
50 | });
51 | var validationFeature;
52 | var olChangeFeature = diffService.createOlFeatureBasedOnChange(modifiedChange, repoId);
53 | diffLayer.getSource().addFeature(olChangeFeature);
54 | diffLayer.getSource().forEachFeature(function(iterFeature){
55 | if (iterFeature === olChangeFeature) {
56 | validationFeature = iterFeature;
57 | }
58 | });
59 | expect(olChangeFeature).toBe(validationFeature);
60 | });
61 | });
62 |
63 | describe('findChangeLayer', function(){
64 | it('should return the map layer to which the change object belongs', function(){
65 | var testLayer = diffService.findChangeLayer(modifiedChange, repoId, [mockLayer]);
66 | expect(testLayer).toBe(mockLayer);
67 | });
68 | });
69 |
70 | });
71 |
--------------------------------------------------------------------------------
/src/common/diff/FeatureBlameService.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_feature_blame_service', []);
3 |
4 | // Private Variables
5 | var rootScope_ = null;
6 | var geogigService_ = null;
7 | var q_ = null;
8 |
9 | module.provider('featureBlameService', function() {
10 |
11 | this.$get = function($rootScope, $q, geogigService) {
12 | rootScope_ = $rootScope;
13 | geogigService_ = geogigService;
14 | q_ = $q;
15 |
16 | return this;
17 | };
18 |
19 | this.performBlame = function(repoId, options) {
20 | var deferredResponse = q_.defer();
21 | geogigService_.command(repoId, 'blame', options).then(function(response) {
22 | if (goog.isDefAndNotNull(response.Blame) && goog.isDefAndNotNull(response.Blame.Attribute)) {
23 | rootScope_.$broadcast('blame-performed');
24 | deferredResponse.resolve(response.Blame.Attribute);
25 | } else {
26 | deferredResponse.reject();
27 | }
28 | }, function(reject) {
29 | //failed to get diff
30 | deferredResponse.reject(reject);
31 | });
32 | return deferredResponse.promise;
33 | };
34 | });
35 | }());
36 |
--------------------------------------------------------------------------------
/src/common/diff/FeatureDiffController.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_feature_diff_controller', []);
3 |
4 | module.controller('LoomFeatureDiffController',
5 | function($scope, $rootScope, featureDiffService) {
6 | function assignScopeVariables() {
7 | $scope.title = featureDiffService.title;
8 | $scope.featureDiffService = featureDiffService;
9 | }
10 |
11 | function updateScopeVariables() {
12 | if (!$scope.$$phase && !$rootScope.$$phase) {
13 | $scope.$apply(function() {
14 | assignScopeVariables();
15 | });
16 | } else {
17 | assignScopeVariables();
18 | }
19 | }
20 |
21 | assignScopeVariables();
22 |
23 | $scope.$watch('featureDiffService.title', updateScopeVariables);
24 | });
25 | })();
26 |
--------------------------------------------------------------------------------
/src/common/diff/FeaturePanelDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | var module = angular.module('loom_feature_panel_directive', []);
4 |
5 | module.directive('loomFeaturePanel',
6 | function($rootScope, $translate, mapService, $timeout, featureDiffService, featureBlameService) {
7 | return {
8 | restrict: 'C',
9 | scope: {
10 | panel: '=',
11 | title: '=panelTitle'
12 | },
13 | templateUrl: 'diff/partial/featurepanel.tpl.html',
14 | link: function(scope, element, attrs) {
15 | scope.mapid = attrs.mapid;
16 | scope.authorsShown = false;
17 |
18 | var target = 'preview-map-' + scope.mapid;
19 | var loadingtarget = '#loading-' + scope.mapid;
20 |
21 | function updateVariables() {
22 |
23 | scope.authorsShown = false;
24 | scope.isMergePanel = scope.panel === featureDiffService.merged;
25 | scope.isConflictPanel = scope.isMergePanel && featureDiffService.change !== 'MERGED';
26 |
27 | if (scope.isMergePanel) {
28 | scope.$watch('panel.attributes', function() {
29 | for (var i = 0; i < scope.panel.attributes.length; i++) {
30 | featureDiffService.updateChangeType(scope.panel.attributes[i]);
31 | }
32 | $rootScope.$broadcast('merge-feature-modified');
33 | }, true);
34 | }
35 |
36 | $timeout(function() {
37 | scope.panel.map.setTarget(target);
38 | mapService.zoomToExtent(featureDiffService.combinedExtent, false, scope.panel.map, 0.1);
39 | $timeout(function() {
40 | $(loadingtarget).fadeOut();
41 | }, 500);
42 | }, 500);
43 | }
44 |
45 | scope.translate = function(value) {
46 | return $translate.instant(value);
47 | };
48 |
49 | scope.computeAuthorString = function(attribute) {
50 | if (scope.isConflictPanel) {
51 | return '---------------------';
52 | }
53 | if (goog.isDefAndNotNull(attribute) && goog.isDefAndNotNull(attribute.commit)) {
54 | var returnString = '';
55 | returnString += attribute.commit.author.name + ' - ';
56 | var date = new Date(attribute.commit.author.timestamp);
57 | returnString += date.toLocaleDateString() + ' @ ' + date.toLocaleTimeString();
58 | return returnString;
59 | }
60 | return '';
61 | };
62 |
63 | scope.selectValue = function(property, index) {
64 | if (index === null) {
65 | property.newvalue = null;
66 | } else {
67 | property.newvalue = property.enum[index]._value;
68 | }
69 | scope.validateField(property, 'newvalue');
70 | };
71 |
72 | scope.selectBooleanValue = function(property, index) {
73 | property.newvalue = property.enum[index]._value === 'true';
74 | scope.validateField(property, 'newvalue');
75 | };
76 |
77 | scope.validateInteger = function(property, key) {
78 | property.valid = validateInteger(property[key]);
79 | };
80 |
81 | scope.validateDouble = function(property, key) {
82 | property.valid = validateDouble(property[key]);
83 | };
84 |
85 | scope.validateField = function(property, key) {
86 | property.valid = true;
87 | switch (property.type) {
88 | case 'xsd:int':
89 | property.valid = validateInteger(property[key]);
90 | break;
91 | case 'xsd:integer':
92 | property.valid = validateInteger(property[key]);
93 | break;
94 | case 'xsd:double':
95 | property.valid = validateDouble(property[key]);
96 | break;
97 | case 'xsd:decimal':
98 | property.valid = validateDouble(property[key]);
99 | break;
100 | }
101 |
102 | if (featureDiffService.schema[property.attributename]._nillable === 'false' &&
103 | (property[key] === '' || property[key] === null)) {
104 | property.valid = false;
105 | }
106 | };
107 |
108 | scope.$on('feature-diff-performed', updateVariables);
109 | scope.$on('show-authors', function() {
110 | scope.authorsShown = true;
111 | });
112 |
113 | scope.$on('hide-authors', function() {
114 | scope.authorsShown = false;
115 | });
116 | }
117 | };
118 | }
119 | );
120 | })();
121 |
--------------------------------------------------------------------------------
/src/common/diff/PanelSeparatorDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | var module = angular.module('loom_panel_separator_directive', []);
4 |
5 |
6 | module.directive('loomPanelSeparator',
7 | function(featureDiffService) {
8 | return {
9 | restrict: 'C',
10 | templateUrl: 'diff/partial/panelseparator.tpl.html',
11 | scope: {
12 | icon: '@icon',
13 | clickfunction: '=',
14 | panel: '=',
15 | hover: '='
16 | },
17 | link: function(scope, element) {
18 | scope.featureDiffService = featureDiffService;
19 | scope.authorsShown = false;
20 | function updateVariables(event, panel) {
21 | if (scope.hover) {
22 | element.closest('.loom-panel-separator').addClass('hoverable');
23 | }
24 | scope.arrows = [];
25 | var i, attr;
26 | if (featureDiffService.change == 'MODIFIED') {
27 | scope.geometryChanged = featureDiffService.right.geometry.changetype !== 'NO_CHANGE';
28 | for (i = 0; i < featureDiffService.right.attributes.length; i++) {
29 | attr = featureDiffService.right.attributes[i];
30 | if (attr.changetype !== 'NO_CHANGE') {
31 | scope.arrows.push({active: true});
32 | } else {
33 | scope.arrows.push({active: false});
34 | }
35 | }
36 | } else {
37 | scope.geometryChanged = scope.panel.getGeometry() === featureDiffService.merged.getGeometry() &&
38 | goog.isDefAndNotNull(scope.panel.geometry) &&
39 | goog.isDefAndNotNull(featureDiffService.merged.geometry) &&
40 | scope.panel.geometry.changetype === featureDiffService.merged.geometry.changetype;
41 | for (i = 0; i < scope.panel.attributes.length; i++) {
42 | attr = scope.panel.attributes[i];
43 | if (featureDiffService.attributesEqual(attr, featureDiffService.merged.attributes[i]) &&
44 | attr.changetype !== 'NO_CHANGE') {
45 | scope.arrows.push({active: true});
46 | } else {
47 | scope.arrows.push({active: false});
48 | }
49 | }
50 | }
51 | }
52 |
53 | scope.geometryArrowClick = function() {
54 | if (scope.hover) {
55 | featureDiffService.chooseGeometry(scope.panel);
56 | updateVariables();
57 | }
58 | };
59 |
60 | scope.arrowClick = function(index) {
61 | if (scope.hover) {
62 | featureDiffService.chooseAttribute(index, scope.panel);
63 | }
64 | };
65 |
66 | var initialize = function() {
67 | updateVariables();
68 | scope.authorsShown = false;
69 | };
70 |
71 | scope.$on('merge-feature-modified', updateVariables);
72 | scope.$on('feature-diff-performed', initialize);
73 | scope.$on('update-merge-feature', updateVariables);
74 | scope.$on('show-authors', function() {
75 | scope.authorsShown = true;
76 | });
77 |
78 | scope.$on('hide-authors', function() {
79 | scope.authorsShown = false;
80 | });
81 | }
82 | };
83 | }
84 | );
85 | })();
86 |
--------------------------------------------------------------------------------
/src/common/diff/partial/diffpanel.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{'cancel_btn' | translate}}
6 |
7 |
8 |
9 |
10 | {{'done_btn' | translate}}
11 |
12 |
13 |
14 |
15 |
16 |
17 | {{adds.length}}
18 |
19 | {{modifies.length}}
20 |
21 | {{deletes.length}}
22 |
23 | {{merges.length}}
24 |
25 |
26 |
28 |
--------------------------------------------------------------------------------
/src/common/diff/partial/featurediff.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
10 |
12 |
15 |
17 |
18 |
19 |
20 |
21 |
41 |
--------------------------------------------------------------------------------
/src/common/diff/partial/panelseparator.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
10 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/common/featuremanager/DrawSelectDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | var module = angular.module('loom_drawselect_directive', []);
4 |
5 | module.directive('loomDrawselect',
6 | function($translate, mapService) {
7 | return {
8 | templateUrl: 'featuremanager/partial/drawselect.tpl.html',
9 | link: function(scope) {
10 | scope.mapService = mapService;
11 | scope.drawType = null;
12 | scope.geometryTypes = ['Point', 'LineString', 'Polygon'];
13 | }
14 | };
15 | }
16 | );
17 | })();
18 |
19 |
--------------------------------------------------------------------------------
/src/common/featuremanager/ExclusiveModeDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | var module = angular.module('loom_exclusive_mode_directive', []);
4 |
5 | module.directive('loomExclusiveMode',
6 | function(exclusiveModeService) {
7 | return {
8 | restrict: 'C',
9 | replace: true,
10 | templateUrl: 'featuremanager/partial/exclusivemode.tpl.html',
11 | link: function(scope) {
12 | scope.exclusiveModeService = exclusiveModeService;
13 | }
14 | };
15 | }
16 | );
17 | })();
18 |
--------------------------------------------------------------------------------
/src/common/featuremanager/FeatureManagerModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | angular.module('loom_feature_manager', [
3 | 'loom_feature_info_box_directive',
4 | 'loom_exclusive_mode_directive',
5 | 'loom_attribute_edit_directive',
6 | 'loom_feature_manager_service',
7 | 'loom_exclusive_mode_service',
8 | 'loom_drawselect_directive'
9 | ]);
10 | })();
11 |
--------------------------------------------------------------------------------
/src/common/featuremanager/partial/drawselect.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
Please select the geometry type you wish to draw.
3 |
4 |
5 |
6 |
7 |
10 |
--------------------------------------------------------------------------------
/src/common/featuremanager/partial/exclusivemode.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{exclusiveModeService.getTitle()}}
4 |
{{exclusiveModeService.getSubtitle()}}
5 |
6 |
7 |
9 |
10 |
12 |
14 |
16 |
17 |
19 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/common/geogig/GeoGigModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | angular.module('loom_geogig', [
3 | 'loom_geogig_service'
4 | ]);
5 | }());
6 |
--------------------------------------------------------------------------------
/src/common/history/HistoryModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | angular.module('loom_history', [
3 | 'loom_history_panel_directive',
4 | 'loom_history_popover_directive',
5 | 'loom_history_diff_directive',
6 | 'loom_history_service'
7 | ]);
8 | }());
9 |
--------------------------------------------------------------------------------
/src/common/history/HistoryPopoverDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_history_popover_directive', []);
3 |
4 | module.directive('loomHistoryPopover',
5 | function($translate) {
6 | return {
7 | restrict: 'C',
8 | replace: false,
9 | link: function(scope, element, attrs) {
10 | if (!goog.isDefAndNotNull(scope.commit)) {
11 | scope.commit = scope.$eval(attrs.commit);
12 | }
13 | if (!goog.isDefAndNotNull(scope.commit)) {
14 | element.popover('destroy');
15 | return;
16 | }
17 | var safeName = function(name) {
18 | if (goog.isDefAndNotNull(name) && name.length > 0) {
19 | return name;
20 | }
21 | return $translate.instant('anonymous');
22 | };
23 | var safeEmail = function(email) {
24 | if (goog.isDefAndNotNull(email) && email.length > 0) {
25 | return email;
26 | }
27 | return $translate.instant('no_email');
28 | };
29 | var prettyTime = function(time) {
30 | var date = new Date(time);
31 | return date.toLocaleDateString() + ' @ ' + date.toLocaleTimeString();
32 | };
33 | var prettyMessage = function() {
34 | return scope.commit.message;
35 | };
36 |
37 | var content = '' + $translate.instant('author_name') + ':
' +
38 | '' + safeName(scope.commit.author.name) + '
' +
39 | '' + $translate.instant('author_email') + ':
' +
40 | '' + safeEmail(scope.commit.author.email) + '
' +
41 | '' + $translate.instant('committer_name') + ':
' +
42 | '' + safeName(scope.commit.committer.name) + '
' +
43 | '' + $translate.instant('committer_email') + ':
' +
44 | '' + safeEmail(scope.commit.committer.email) + '
' +
45 | '' + $translate.instant('commit_time') + ':
' +
46 | '' + prettyTime(scope.commit.committer.timestamp) + '
' +
47 | '' + $translate.instant('message') + ':
' +
48 | '' + prettyMessage() + '
';
49 |
50 | element.popover({
51 | trigger: 'manual',
52 | animation: false,
53 | html: true,
54 | content: content,
55 | container: 'body',
56 | title: $translate.instant('id') + ': ' + scope.commit.id
57 | });
58 |
59 | element.mouseenter(function() {
60 | if (element.closest('.collapsing').length === 0) {
61 | element.popover('show');
62 | }
63 | });
64 | element.mouseleave(function() {
65 | element.popover('hide');
66 | });
67 | }
68 | };
69 | });
70 | }());
71 |
--------------------------------------------------------------------------------
/src/common/history/partial/historydiff.tpl.html:
--------------------------------------------------------------------------------
1 |
21 |
--------------------------------------------------------------------------------
/src/common/history/partial/historypanel.tpl.html:
--------------------------------------------------------------------------------
1 |
34 |
--------------------------------------------------------------------------------
/src/common/history/style/history.less:
--------------------------------------------------------------------------------
1 | .history-scroll-pane {
2 | max-height: 300px;
3 | overflow-y: auto;
4 |
5 | .history-loading .loading-container {
6 | background-color: inherit;
7 | }
8 |
9 | .diff-loading .loading-container {
10 | background-color: @loadingBackgroundColor;
11 | }
12 |
13 | .list-group {
14 | overflow-y: hidden;
15 | }
16 | .list-group-item {
17 | height: 40px;
18 | }
19 |
20 | .history-last-list-item {
21 | display: none;
22 | }
23 |
24 | .merged {
25 | position: absolute;
26 | right: 100px;
27 | top: 9px;
28 | }
29 | }
30 |
31 | .history-loading .loading-container {
32 | background-color: @loadingBackgroundColor;
33 | }
34 |
35 | .history-button {
36 | .loom-button-variant(@syncListButtonFontColor, @syncListButtonFontColor, @syncListButtonColor, @syncListButtonColor);
37 | border: 1px solid lighten(@syncListColor, 10%) !important;
38 | border-radius: 0;
39 | margin: 3px;
40 | .glyphicon {
41 | top: 2px;
42 | }
43 | }
44 |
45 | .commit-summary {
46 | height: 15px;
47 | position: absolute;
48 | width: 80px;
49 | margin-bottom: 0px;
50 | right: 14px;
51 | border: 1px solid @layerHeaderColor;
52 | background-color: #8dabc8 !important;
53 |
54 | .progress-bar-add {
55 | background-color: @addColor;
56 | -webkit-box-shadow: none;
57 | -moz-box-shadow: none;
58 | box-shadow: none;
59 | }
60 |
61 | .progress-bar-modify {
62 | background-color: darken(@modifyColor, 5%);
63 | -webkit-box-shadow: none;
64 | -moz-box-shadow: none;
65 | box-shadow: none;
66 | }
67 |
68 | .progress-bar-delete {
69 | background-color: @deleteColor;
70 | -webkit-box-shadow: none;
71 | -moz-box-shadow: none;
72 | box-shadow: none;
73 | }
74 |
75 | .progress-bar-none {
76 | background-color: darken(@mainListItemColor, 3%) !important;
77 | width: 100%;
78 | -webkit-box-shadow: none;
79 | -moz-box-shadow: none;
80 | box-shadow: none;
81 | }
82 | }
83 |
84 | .author-name {
85 | position: absolute;
86 | top: 0px;
87 | width: 170px;
88 | font-weight: 900;
89 | }
90 |
91 | .commit-time {
92 | position: absolute;
93 | top: 19px;
94 | left: 14px;
95 | width: 170px;
96 | font-size: 13px;
97 | }
--------------------------------------------------------------------------------
/src/common/layers/LayerAttributeVisibilityDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_layer_attribute_visibility_directive', []);
3 |
4 | module.directive('loomLayerAttributeVisibility',
5 | function($translate, serverService, geogigService) {
6 | return {
7 | templateUrl: 'layers/partials/layerattributevisibility.tpl.html',
8 | link: function(scope, element) {
9 | var resetVariables = function() {
10 | scope.layer = null;
11 | };
12 | resetVariables();
13 | scope.$on('getLayerAttributeVisibility', function(evt, layer) {
14 | //resetVariables();
15 | scope.layer = layer;
16 | scope.getAttrList = function(layer) {
17 | var attrList = [];
18 |
19 | for (var i in layer.get('metadata').schema) {
20 | if (layer.get('metadata').schema[i]._type.search('gml:') > -1) {
21 | continue;
22 | }
23 | attrList.push(layer.get('metadata').schema[i]);
24 | }
25 | return attrList;
26 | };
27 | scope.toggleAttributeVisibility = function(attribute) {
28 | attribute.visible = !attribute.visible;
29 | };
30 | element.closest('.modal').modal('toggle');
31 | });
32 | function onResize() {
33 | var height = $(window).height();
34 | element.children('.modal-body').css('max-height', (height - 200).toString() + 'px');
35 | }
36 |
37 | onResize();
38 |
39 | $(window).resize(onResize);
40 | }
41 | };
42 | });
43 | }());
44 |
--------------------------------------------------------------------------------
/src/common/layers/LayerInfoDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_layer_info_directive', []);
3 |
4 | module.directive('loomLayerInfo',
5 | function($translate, serverService, geogigService) {
6 | return {
7 | templateUrl: 'layers/partials/layerinfo.tpl.html',
8 | link: function(scope, element) {
9 | var resetVariables = function() {
10 | scope.layer = null;
11 | scope.name = null;
12 | scope.title = null;
13 | scope.workspace = null;
14 | scope.featureType = null;
15 | scope.abstract = null;
16 | scope.srs = null;
17 | scope.serverName = null;
18 | scope.keywords = null;
19 | scope.repoName = null;
20 | scope.repoUUID = null;
21 | scope.branchName = null;
22 | };
23 | resetVariables();
24 | scope.$on('getLayerInfo', function(evt, layer) {
25 | resetVariables();
26 | scope.layer = layer;
27 | var metadata = scope.layer.get('metadata');
28 | if (goog.isDefAndNotNull(metadata.name)) {
29 | var split = metadata.name.split(':');
30 | scope.name = split[split.length - 1];
31 | }
32 | if (goog.isDefAndNotNull(metadata.title)) {
33 | scope.title = metadata.title;
34 | }
35 | if (goog.isDefAndNotNull(metadata.workspace)) {
36 | scope.workspace = metadata.workspace;
37 | }
38 | if (goog.isDefAndNotNull(metadata.nativeName)) {
39 | scope.featureType = metadata.nativeName;
40 | }
41 | if (goog.isDefAndNotNull(metadata.abstract)) {
42 | scope.abstract = metadata.abstract;
43 | }
44 | if (goog.isDefAndNotNull(metadata.projection)) {
45 | scope.srs = metadata.projection;
46 | }
47 | if (goog.isDefAndNotNull(metadata.keywords)) {
48 | scope.keywords = metadata.keywords.toString();
49 | }
50 | if (metadata.isGeoGig) {
51 | var repo = geogigService.getRepoById(metadata.repoId);
52 | scope.branchName = metadata.branchName;
53 | scope.repoName = repo.name;
54 | scope.repoUUID = repo.uuid;
55 | }
56 | var server = serverService.getServerById(scope.layer.get('metadata').serverId);
57 | scope.serverName = server.name;
58 | scope.serverURL = server.url;
59 | element.closest('.modal').modal('toggle');
60 | });
61 | function onResize() {
62 | var height = $(window).height();
63 | element.children('.modal-body').css('max-height', (height - 200).toString() + 'px');
64 | }
65 |
66 | onResize();
67 |
68 | $(window).resize(onResize);
69 | }
70 | };
71 | });
72 | }());
73 |
--------------------------------------------------------------------------------
/src/common/layers/LayersModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | angular.module('loom_layers', [
4 | 'loom_layers_directive',
5 | 'loom_layer_info_directive',
6 | 'loom_layer_attribute_visibility_directive'
7 | ]);
8 | })();
9 |
--------------------------------------------------------------------------------
/src/common/layers/partials/layerattributevisibility.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{attribute._name}}
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/common/layers/partials/layerinfo.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
6 |
10 |
11 |
12 |
{{workspace}}
13 |
14 |
15 |
16 |
{{featureType}}
17 |
18 |
19 |
20 |
{{abstract}}
21 |
22 |
23 |
24 |
{{keywords}}
25 |
26 |
30 |
31 |
32 |
{{serverName}}
33 |
34 |
35 |
36 |
{{serverURL}}
37 |
38 |
39 |
40 |
{{repoName}}
41 |
42 |
43 |
44 |
{{repoUUID}}
45 |
46 |
47 |
48 |
{{branchName}}
49 |
50 |
51 |
--------------------------------------------------------------------------------
/src/common/layers/style/layers.less:
--------------------------------------------------------------------------------
1 | #layerpanel-group {
2 | max-height: 400px;
3 | overflow: auto;
4 | }
5 |
6 | .layer-row {
7 | margin-right: 0;
8 | }
9 |
10 | .layer-title {
11 | color: @layerHeaderFontColor !important;
12 | text-decoration: none !important;
13 | width: 64%;
14 | float: left;
15 | padding-left: 15px;
16 | padding-top: 2px;
17 | }
18 |
19 | .layer-heading {
20 | background-color: @layerHeaderColor !important;
21 | padding: 5px 15px;
22 | }
23 |
24 | .placeholder-layer {
25 | background-color: @placeholderLayerColor !important;
26 | }
27 | .placeholder-layer-title {
28 | color: @placeholderLayerFontColor !important;
29 | }
30 | .placeholder-layer-symbol {
31 | color: @remoteDownColor;
32 | }
33 |
34 | .layer-buttons {
35 | text-align: right;
36 | padding-right: 12px;
37 | .btn-xs {
38 | margin-left:0px;
39 | margin-right:0px;
40 | }
41 | }
42 |
43 | #attribute-label {
44 | padding-top: 10px;
45 | }
46 | #attributeRow {
47 | margin-left: 0px;
48 | margin-right: 0px;
49 |
50 | overflow: auto;
51 | max-height: 186px;
52 | .list-group-item {
53 | border: 1px solid lighten(@attributeListColor, 10%);
54 | color: @attributeListFontColor;
55 | background-color:@attributeListColor;
56 | }
57 | }
58 |
59 | .attribute-value {
60 | width: 75%;
61 | float: left;
62 | padding-top: 3px;
63 | }
64 |
65 | .layer-visible-button.layer-visible {
66 | background-color: @layerVisibleColor;
67 | color: @layerVisibleImageColor;
68 | }
69 |
70 | #layerpanel-group .placeholder {
71 | height: 33px;
72 | }
73 |
74 | .remove-layer-button {
75 | background-color: @removeLayerColor;
76 | color: @removeLayerFontColor;
77 | margin-top: 10px;
78 | }
79 |
80 | .layer-inner-panel {
81 | padding-top: 15px !important;
82 | padding-bottom: 15px !important;
83 | }
84 |
85 | #layerInfoDialog {
86 | .modal-body {
87 | min-height: 200px;
88 | overflow-y: auto;
89 | }
90 | .modal-footer {
91 | border-top: none;
92 | }
93 | }
--------------------------------------------------------------------------------
/src/common/legend/LegendDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_legend_directive', []);
3 |
4 | var legendOpen = false;
5 |
6 | module.directive('loomLegend',
7 | function($rootScope, mapService, serverService) {
8 | return {
9 | restrict: 'C',
10 | replace: true,
11 | templateUrl: 'legend/partial/legend.tpl.html',
12 | // The linking function will add behavior to the template
13 | link: function(scope, element) {
14 | scope.mapService = mapService;
15 | scope.serverService = serverService;
16 |
17 | var openLegend = function() {
18 | angular.element('#legend-container')[0].style.visibility = 'visible';
19 | angular.element('#legend-panel').collapse('show');
20 | legendOpen = true;
21 | };
22 | var closeLegend = function() {
23 | angular.element('#legend-panel').collapse('hide');
24 | legendOpen = false;
25 |
26 | //the timeout is so the transition will finish before hiding the div
27 | setTimeout(function() {
28 | angular.element('#legend-container')[0].style.visibility = 'hidden';
29 | }, 350);
30 | };
31 |
32 | scope.toggleLegend = function() {
33 | if (legendOpen === false) {
34 | if (angular.element('.legend-item').length > 0) {
35 | openLegend();
36 | }
37 | } else {
38 | closeLegend();
39 | }
40 | };
41 |
42 | scope.getLegendUrl = function(layer) {
43 | var url = null;
44 | var server = serverService.getServerById(layer.get('metadata').serverId);
45 | if (goog.isDefAndNotNull(server.virtualServiceUrl)) {
46 | domain = server.virtualServiceUrl;
47 | } else {
48 | domain = server.url;
49 | }
50 | url = domain + '?request=GetLegendGraphic&format=image%2Fpng&width=20&height=20&layer=' +
51 | layer.get('metadata').name + '&transparent=true&legend_options=fontColor:0xFFFFFF;' +
52 | 'fontAntiAliasing:true;fontSize:14;fontStyle:bold;';
53 | return url;
54 | };
55 |
56 | scope.$on('layer-added', function() {
57 | if (legendOpen === false) {
58 | openLegend();
59 | }
60 | });
61 |
62 | scope.$on('layerRemoved', function() {
63 | //close the legend if the last layer is removed
64 | if (legendOpen === true && angular.element('.legend-item').length == 1) {
65 | closeLegend();
66 | }
67 | });
68 | }
69 | };
70 | });
71 | }());
72 |
--------------------------------------------------------------------------------
/src/common/legend/LegendModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | angular.module('loom_legend', [
4 | 'loom_legend_directive'
5 | ]);
6 | })();
7 |
--------------------------------------------------------------------------------
/src/common/legend/partial/legend.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
14 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/common/legend/style/legend.less:
--------------------------------------------------------------------------------
1 | #legend-container {
2 | position: absolute;
3 | right: 50px;
4 | left: auto;
5 | top: 0;
6 | .z-index(@mapLevel);
7 | background-color: @legendColor !important;
8 | color: @legendFontColor;
9 | width: auto;
10 | padding-left: 5px;
11 | padding-right: 5px;
12 | padding-bottom: 5px;
13 | max-height: 100%;
14 | overflow-y: auto;
15 | visibility: hidden;
16 | }
17 |
18 | .legend-panel-body {
19 | background-color: transparent !important;
20 | }
21 |
22 | .legend-item {
23 | padding-left: 20px;
24 | padding-right: 5px;
25 | }
26 | .legend-item-header {
27 | background-color: @legendHeaderColor !important;
28 | border-bottom-color: @legendColor;
29 | padding-bottom: 0;
30 | padding-top: 0;
31 | margin-bottom: 5px;
32 | margin-top: 5px;
33 | width: auto;
34 | }
35 |
36 | #legend-btn-border {
37 | top: 325px;
38 | }
39 | #legend-btn {
40 | height: 22px;
41 | width: 22px;
42 | color: @legendButtonImageColor;
43 | background: @legendButtonColor;
44 | border-radius: 4px;
45 | .glyphicon {
46 | padding-left: 3px;
47 | padding-top: 2px;
48 | }
49 | &:hover {
50 | background-color: @mainHeaderColor;
51 | }
52 | }
53 |
54 | .legend-panel-title {
55 | display: inline-block;
56 | }
57 |
58 | #legend-title-heading {
59 | padding-bottom: 20px;
60 | background-color: @legendHeaderColor;
61 | }
62 |
63 | #legend-title-text {
64 | padding-right: 5px;
65 | }
66 |
--------------------------------------------------------------------------------
/src/common/map/MapModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | angular.module('loom_map', [
3 | 'loom_map_service',
4 | 'loom_savemap_directive'
5 | ]);
6 | }());
7 |
--------------------------------------------------------------------------------
/src/common/map/MapSaveDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | var module = angular.module('loom_savemap_directive', []);
4 |
5 | module.directive('loomSaveMap',
6 | function(mapService, configService, $translate) {
7 | return {
8 | restrict: 'AC',
9 | templateUrl: 'map/partial/savemap.tpl.html',
10 | link: function(scope, element, attrs) {
11 | scope.mapService = mapService;
12 | scope.configService = configService;
13 | scope.translate = $translate;
14 | function onResize() {
15 | var height = $(window).height();
16 | element.children('.modal-body').css('max-height', (height - 200).toString() + 'px');
17 | }
18 |
19 | onResize();
20 |
21 | $(window).resize(onResize);
22 | }
23 | };
24 | });
25 | })();
26 |
--------------------------------------------------------------------------------
/src/common/map/partial/savemap.tpl.html:
--------------------------------------------------------------------------------
1 |
16 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/common/map/style/map.less:
--------------------------------------------------------------------------------
1 | #map {
2 | min-height:100%;
3 | height: 100%;
4 | width: 100%;
5 | .ol-zoom {
6 | position: absolute;
7 | right: 8px;
8 | top: 38px;
9 | left: auto;
10 | }
11 | .ol-zoom-out {
12 | top: 30px;
13 | margin-top: 202px;
14 | }
15 | .ol-zoomslider {
16 | position: absolute;
17 | background-color: transparent;
18 | top: 62px;
19 | left: auto;
20 | right: 11px;
21 | width: 24px;
22 | }
23 | .ol-zoomslider-thumb {
24 | position: absolute;
25 | border-radius: 2px;
26 | width: 24px;
27 | height: 12px;
28 | }
29 | .ol-mouse-position {
30 | position: absolute;
31 | left: 36px;
32 | right: auto;
33 | top: auto;
34 | bottom: 7px;
35 | .z-index(@mapLevel);;
36 | }
37 | .metric-scale-line {
38 | bottom: 32px;
39 | }
40 | .imperial-scale-line {
41 | bottom: 56px;
42 | }
43 | }
44 |
45 | [loom-save-map] .modal-body {
46 | overflow: hidden;
47 | overflow-y: auto;
48 | min-height: 200px;
49 | max-height: 500px;
50 | }
51 |
52 | .map-shadow {
53 | position: absolute;
54 | width: 100%;
55 | height: 100%;
56 | top: 0;
57 | left: 0;
58 | .z-index(@mapLevel);
59 | opacity: 0.2;
60 | box-shadow: inset 0 0 80px 0 black;
61 | pointer-events: none;
62 | }
63 |
64 | #zoom-to-world {
65 | height: 22px;
66 | width: 22px;
67 | color: @zoomToExtentImageColor;
68 | background: @zoomToExtentColor;
69 | border-radius: 4px;
70 | .glyphicon {
71 | padding-left: 4px;
72 | padding-top: 2px;
73 | }
74 | &:hover {
75 | background-color: @mainHeaderColor;
76 | }
77 | }
78 |
79 | #zoom-to-world-border {
80 | top: 295px;
81 | }
82 |
83 | #toggle-fullscreen {
84 | height: 22px;
85 | width: 22px;
86 | color: @zoomToExtentImageColor;
87 | background: @zoomToExtentColor;
88 | border-radius: 4px;
89 | .glyphicon {
90 | padding-left: 4px;
91 | padding-top: 2px;
92 | }
93 | &:hover {
94 | background-color: @mainHeaderColor;
95 | }
96 | }
97 |
98 | #toggle-fullscreen-border {
99 | top: 7px;
100 | }
101 |
102 | #switch-coords {
103 | height: 22px;
104 | width: 22px;
105 | color: @zoomToExtentImageColor;
106 | background: @zoomToExtentColor;
107 | border-radius: 4px;
108 | .glyphicon {
109 | padding-left: 4px;
110 | padding-top: 2px;
111 | }
112 | &:hover {
113 | background-color: @mainHeaderColor;
114 | }
115 | }
116 |
117 | #switch-coords-border {
118 | left: 7px !important;
119 | bottom: 2px;
120 | right: auto !important;
121 | }
122 |
123 | .map-btn-border {
124 | padding: 2px;
125 | background: rgba(255, 255, 255, 0.4);
126 | position: absolute;
127 | right: 10px;
128 | left: auto;
129 | .z-index(@mapLevel);
130 | border-radius: 4px;
131 | }
132 |
133 |
--------------------------------------------------------------------------------
/src/common/merge/MergeModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | angular.module('loom_merge', [
3 | 'loom_merge_directive',
4 | 'loom_conflict_service'
5 | ]);
6 | })();
7 |
--------------------------------------------------------------------------------
/src/common/merge/partials/merge.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
22 |
23 |
28 |
29 |
--------------------------------------------------------------------------------
/src/common/merge/style/merge.less:
--------------------------------------------------------------------------------
1 | #merge-loading {
2 | .loading-container {
3 | position: absolute;
4 | width: 100%;
5 | height: 100%;
6 | top: 0;
7 | left: 0;
8 | .z-index(@dialogLevel, 1);
9 | background-color: rgba(0,0,0,0.5);
10 | }
11 | .loading-spinner {
12 | border-color: white !important;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/common/modal/DialogDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | var module = angular.module('loom_dialog_directive', []);
4 |
5 | module.directive('loomDialog',
6 | function() {
7 | return {
8 | replace: true,
9 | templateUrl: 'modal/partials/dialog.tpl.html'
10 | };
11 | }
12 | );
13 | })();
14 |
--------------------------------------------------------------------------------
/src/common/modal/ModalDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | var module = angular.module('loom_modal_directive', []);
4 |
5 | module.directive('loomModal',
6 | function($rootScope) {
7 | return {
8 | replace: true,
9 | transclude: true,
10 | scope: {
11 | title: '@modalTitle',
12 | closeButton: '@closeButton'
13 | },
14 | templateUrl: 'modal/partials/modal.tpl.html',
15 | link: function(scope, element, attrs) {
16 | scope.contentHidden = true;
17 |
18 | attrs.$observe('closeButton', function(val) {
19 | if (!angular.isDefined(val)) {
20 | scope.closeButton = true;
21 | }
22 | });
23 | scope.closeModal = function() {
24 | $rootScope.$broadcast('modal-closed', element);
25 | element.closest('.modal').modal('hide');
26 | };
27 | }
28 | };
29 | }
30 | );
31 | })();
32 |
--------------------------------------------------------------------------------
/src/common/modal/ModalModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | angular.module('loom_modal', [
4 | 'loom_modal_directive',
5 | 'loom_password_directive',
6 | 'loom_dialog_directive',
7 | 'loom_dialog_service'
8 | ]);
9 | })();
10 |
--------------------------------------------------------------------------------
/src/common/modal/PasswordDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | var module = angular.module('loom_password_directive', []);
4 |
5 | module.directive('loomPasswordDialog',
6 | function() {
7 | return {
8 | replace: true,
9 | templateUrl: 'modal/partials/password.tpl.html'
10 | };
11 | }
12 | );
13 | })();
14 |
--------------------------------------------------------------------------------
/src/common/modal/partials/dialog.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 | {{dialogContent}}
8 |
9 |
16 |
--------------------------------------------------------------------------------
/src/common/modal/partials/modal.tpl.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/common/modal/partials/password.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
{{serverURL}}
9 |
10 |
11 | :
12 |
14 | :
15 |
17 |
18 |
24 |
25 |
33 |
--------------------------------------------------------------------------------
/src/common/modal/style/modal.less:
--------------------------------------------------------------------------------
1 | .modal-backdrop {
2 | .z-index(@dialogLevel);
3 | }
4 |
5 | .modal {
6 | display: inline-block;
7 | width: auto;
8 | background-color: transparent;
9 | margin-left: 0;
10 | overflow: hidden;
11 | .z-index(@dialogLevel,1);
12 | .modal-header {
13 | color: @modalHeaderFontColor;
14 | background-color:@defaultModalHeaderColor;
15 | border-bottom: 0;
16 | }
17 | .modal-footer {
18 | background-color:@defaultModalPanelColor;
19 | border-top: 1px solid darken(@defaultModalPanelColor, 5%);
20 | margin-top: 0;
21 | .box-shadow(none);
22 | }
23 | .modal-body {
24 | background-color:@defaultModalPanelColor;
25 | max-height: 100%;
26 | overflow-y: visible;
27 | }
28 | }
29 |
30 | .modal.custom {
31 | display: none;
32 | }
33 |
34 | .loom-dialog {
35 | width: 26%;
36 | position: absolute;
37 | left: 37%;
38 | top: 30%;
39 | .box-shadow(0 0 12px rgba(0, 0, 0, 0.35));
40 | }
41 |
42 | .loom-password-dialog {
43 | top: 10%;
44 | .control-label {
45 | margin-top: 10px;
46 | }
47 |
48 | .anonymous-section {
49 | margin-top: 15px;
50 | margin-left: 1px;
51 | .anonymous-checkbox {
52 | display: inline-block;
53 | }
54 |
55 | .anonymous-title {
56 | font-weight: 500;
57 | display: inline-block;
58 | }
59 | }
60 | }
61 |
62 | // Make less parametric mixin for the dialogs
63 | .dialog-mixin (@header-color, @background-color, @font-color: white) {
64 | .modal-header {
65 | color: @font-color;
66 | border: none;
67 | background-color: @header-color;
68 | }
69 | .modal-footer {
70 | margin-top: 0;
71 | padding: 5px 5px 5px 5px;
72 | background-color: @background-color;
73 | border-top: 1px solid darken(@background-color, 5%);
74 | }
75 | .modal-body {
76 | background-color: @background-color;
77 | word-wrap: break-word;
78 | }
79 | }
80 |
81 | .loom-dialog.dialog-default {
82 | .dialog-mixin(@defaultModalHeaderColor, @defaultModalPanelColor);
83 | }
84 |
85 | .loom-dialog.dialog-warning {
86 | .dialog-mixin(@warningHeaderColor, @warningPanelColor);
87 | }
88 |
89 | .loom-dialog.dialog-error {
90 | .dialog-mixin(@errorHeaderColor, @errorPanelColor)
91 | }
92 |
--------------------------------------------------------------------------------
/src/common/notificationposter/NotificationPosterModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | angular.module('loom_notification_poster', [
3 | 'loom_notification_poster_directive'
4 | ]);
5 | }());
6 |
--------------------------------------------------------------------------------
/src/common/notifications/GenerateNotificationDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | var module = angular.module('loom_generate_notification_directive', []);
4 |
5 | module.directive('loomGenerateNotification',
6 | function($rootScope, $translate, mapService, geogigService, refreshService, dialogService) {
7 | return {
8 | templateUrl: 'notifications/partial/generatenotification.tpl.html',
9 | link: function(scope, element, attrs) {
10 | scope.startDate = [new Date().toISOString()];
11 | scope.active = true;
12 | scope.contentHidden = true;
13 | scope.isLoading = false;
14 |
15 | element.closest('.modal').on('hidden.bs.modal', function(e) {
16 | if (!scope.$$phase && !$rootScope.$$phase) {
17 | scope.$apply(function() {
18 | scope.contentHidden = true;
19 | });
20 | } else {
21 | scope.contentHidden = true;
22 | }
23 | });
24 | element.closest('.modal').on('show.bs.modal', function(e) {
25 | if (!scope.$$phase && !$rootScope.$$phase) {
26 | scope.$apply(function() {
27 | scope.contentHidden = false;
28 | scope.startDate = [new Date().toISOString()];
29 | });
30 | } else {
31 | scope.contentHidden = false;
32 | scope.startDate = [new Date().toISOString()];
33 | }
34 | });
35 |
36 | scope.cancel = function() {
37 | element.closest('.modal').modal('hide');
38 | scope.isLoading = false;
39 | };
40 |
41 | scope.onDiff = function() {
42 | scope.isLoading = true;
43 | var layers = mapService.getLayers();
44 |
45 | if (!goog.isArray(layers)) {
46 | layers = [layers];
47 | }
48 |
49 | var repos = {};
50 | var layer = null;
51 | var metadata = null;
52 | for (var i = 0; i < layers.length; i++) {
53 | layer = layers[i];
54 | metadata = layer.get('metadata');
55 | if (goog.isDefAndNotNull(metadata.isGeoGig) && metadata.isGeoGig === true &&
56 | !goog.isDefAndNotNull(repos[metadata.repoId])) {
57 | repos[metadata.repoId] = {};
58 | repos[metadata.repoId].branchName = metadata.branchName;
59 | }
60 | }
61 |
62 | var repoArray = [];
63 | for (var repoId in repos) {
64 | if (repos.hasOwnProperty(repoId)) {
65 | repoArray.push(repoId);
66 | }
67 | }
68 |
69 | if (repoArray.length < 1) {
70 | scope.isLoading = false;
71 | dialogService.warn($translate.instant('warning'), $translate.instant('no_layers_notification'));
72 | return;
73 | }
74 |
75 | var repoIndex = 0;
76 |
77 | var updateRepoCommit = function(response) {
78 | if (goog.isDefAndNotNull(response.sinceCommit)) {
79 | var lastCommit = response.sinceCommit;
80 | var lastCommitId = '0000000000000000000000000000000000000000';
81 |
82 | if (goog.isDefAndNotNull(lastCommit.parents) && goog.isObject(lastCommit.parents)) {
83 | if (goog.isDefAndNotNull(lastCommit.parents.id)) {
84 | if (goog.isArray(lastCommit.parents.id)) {
85 | lastCommitId = lastCommit.parents.id[0];
86 | } else {
87 | lastCommitId = lastCommit.parents.id;
88 | }
89 | }
90 | }
91 | geogigService.getRepoById(repoArray[repoIndex]).commitId = lastCommitId;
92 | }
93 | repoIndex++;
94 | if (repoIndex === repoArray.length) {
95 | scope.isLoading = false;
96 | refreshService.refreshLayers();
97 | scope.cancel();
98 | } else {
99 | processRepo();
100 | }
101 | };
102 | var processRepo = function() {
103 | var logOptions = new GeoGigLogOptions();
104 | logOptions.sinceTime = new Date(scope.startDate[0]).getTime();
105 | logOptions.returnRange = true;
106 | logOptions.until = repos[repoArray[repoIndex]].branchName;
107 | geogigService.command(repoArray[repoIndex], 'log', logOptions).then(updateRepoCommit);
108 | };
109 | processRepo();
110 | };
111 | }
112 | };
113 | }
114 | );
115 | })();
116 |
--------------------------------------------------------------------------------
/src/common/notifications/NotificationBadgeDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_notification_badge_directive', []);
3 |
4 | module.directive('loomNotificationBadge',
5 | function(notificationService) {
6 | return {
7 | restrict: 'C',
8 | replace: true,
9 | templateUrl: 'notifications/partial/notificationbadge.tpl.html',
10 | // The linking function will add behavior to the template
11 | link: function(scope) {
12 | scope.notificationService = notificationService;
13 | }
14 | };
15 | });
16 | }());
17 |
--------------------------------------------------------------------------------
/src/common/notifications/NotificationsDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_notifications_directive', []);
3 |
4 | module.directive('loomNotifications',
5 | function($rootScope, notificationService, $translate) {
6 | return {
7 | restrict: 'C',
8 | replace: true,
9 | templateUrl: 'notifications/partial/notifications.tpl.html',
10 | // The linking function will add behavior to the template
11 | link: function(scope, element, attrs) {
12 | scope.emptyText = scope.$eval(attrs.notificationEmptyText);
13 | scope.notificationService = notificationService;
14 |
15 | scope.$on('translation_change', function() {
16 | scope.emptyText = scope.$eval(attrs.notificationEmptyText);
17 | });
18 |
19 | scope.removeNotification = function(id) {
20 | notificationService.removeNotification(id);
21 | };
22 |
23 | scope.markAsRead = function(id) {
24 | notificationService.markAsRead(notificationService.getNotification(id));
25 | };
26 | }
27 | };
28 | });
29 | }());
30 |
--------------------------------------------------------------------------------
/src/common/notifications/NotificationsModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | angular.module('loom_notifications', [
3 | 'loom_notifications_directive',
4 | 'loom_notifications_service',
5 | 'loom_notification_badge_directive',
6 | 'loom_generate_notification_directive'
7 | ]);
8 | }());
9 |
--------------------------------------------------------------------------------
/src/common/notifications/NotificationsService.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_notifications_service', []);
3 |
4 | // Private Variables
5 | var notifications = [];
6 | var nextNotificationId = 0;
7 | var rootScope = null;
8 | var translate_ = null;
9 |
10 | module.provider('notificationService', function() {
11 | this.startTime = null;
12 |
13 | this.$get = function($rootScope, $timeout, $translate) {
14 | rootScope = $rootScope;
15 | translate_ = $translate;
16 | var momentDate = moment(new Date());
17 | momentDate.lang($translate.use());
18 | this.startTime = momentDate.format('LT');
19 | var updateTimestamps = function() {
20 | for (i = 0; i < notifications.length; i = i + 1) {
21 | momentDate = moment(notifications[i].time);
22 | momentDate.lang($translate.use());
23 | notifications[i].timestr = momentDate.fromNow();
24 | }
25 | $timeout(updateTimestamps, 10000, true);
26 | };
27 | updateTimestamps();
28 | return this;
29 | };
30 |
31 | this.getNotifications = function() {
32 | return notifications;
33 | };
34 |
35 | this.addNotification = function(notification) {
36 | notification.id = nextNotificationId;
37 | notification.time = new Date();
38 | var momentDate = moment(notification.time);
39 | momentDate.lang(translate_.use());
40 | notification.timestr = momentDate.fromNow();
41 | nextNotificationId = nextNotificationId + 1;
42 | notifications.push(notification);
43 | rootScope.$broadcast('notification_added', notification);
44 | };
45 |
46 | this.unreadCount = function() {
47 | var unread = 0, i;
48 |
49 | for (i = 0; i < notifications.length; i = i + 1) {
50 | if (notifications[i].read === false) {
51 | unread = unread + 1;
52 | }
53 | }
54 | return unread;
55 | };
56 |
57 | this.markAsRead = function(notification) {
58 | var i;
59 | for (i = 0; i < notifications.length; i = i + 1) {
60 | if (notifications[i].id === notification.id) {
61 | notifications[i].read = true;
62 | }
63 | }
64 | rootScope.$broadcast('notification_updated', notification);
65 | };
66 |
67 | this.getNotification = function(id) {
68 | var i;
69 | for (i = 0; i < notifications.length; i = i + 1) {
70 | if (notifications[i].id === id) {
71 | return notifications[i];
72 | }
73 | }
74 | return null;
75 | };
76 |
77 | this.removeNotification = function(id) {
78 | var index = -1, i;
79 | for (i = 0; i < notifications.length; i = i + 1) {
80 | if (notifications[i].id === id) {
81 | index = i;
82 | }
83 | }
84 | if (index > -1) {
85 | notifications.splice(index, 1);
86 | }
87 | rootScope.$broadcast('notification_removed', id);
88 | };
89 | });
90 |
91 | }());
92 |
--------------------------------------------------------------------------------
/src/common/notifications/partial/generatenotification.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
14 |
15 |
--------------------------------------------------------------------------------
/src/common/notifications/partial/notificationbadge.tpl.html:
--------------------------------------------------------------------------------
1 |
2 | {{notificationService.unreadCount()}}
3 |
--------------------------------------------------------------------------------
/src/common/notifications/partial/notifications.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{emptyText}}
5 |
6 |
8 |
20 |
{{notification.text}}
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/common/notifications/style/notifications.less:
--------------------------------------------------------------------------------
1 | .notification-panel-heading.read{
2 | letter-spacing: 1px;
3 | }
4 |
5 | .close-notification {
6 | position:absolute;
7 | right:-15px;
8 | font-size:18px;
9 | top: -3px;
10 | }
11 |
12 | .notification-content {
13 | overflow: auto;
14 | max-height: 300px;
15 | }
16 |
17 | .notification-time {
18 | letter-spacing: 1px;
19 | font: normal 13px "Helvetica Neue", Helvetica, Arial, sans-serif;
20 | font-weight: 200;
21 | }
--------------------------------------------------------------------------------
/src/common/pulldown/PulldownModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | angular.module('loom_pulldown', [
3 | 'loom_pulldown_service'
4 | ]);
5 | }());
6 |
--------------------------------------------------------------------------------
/src/common/pulldown/PulldownService.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_pulldown_service', []);
3 |
4 | // Private Variables
5 | var rootScope_ = null;
6 | var timeout_ = null;
7 |
8 | var PulldownPanel = function(visible, enabled) {
9 | this.visible = visible;
10 | this.enabled = enabled;
11 |
12 | this.getVisible = function() {
13 | return this.visible && this.enabled;
14 | };
15 | };
16 |
17 | module.provider('pulldownService', function() {
18 |
19 | this.diffPanel = new PulldownPanel(true, false);
20 | this.notificationsPanel = new PulldownPanel(true, true);
21 | this.layersPanel = new PulldownPanel(true, true);
22 | this.syncPanel = new PulldownPanel(true, false);
23 | this.historyPanel = new PulldownPanel(true, false);
24 | this.toggleEnabled = true;
25 | this.addLayers = true;
26 | this.serversLoading = false;
27 |
28 | this.$get = function($rootScope, $timeout) {
29 | rootScope_ = $rootScope;
30 | timeout_ = $timeout;
31 | return this;
32 | };
33 |
34 | this.apply = function() {
35 | rootScope_.$broadcast('refresh-pulldown');
36 | };
37 |
38 | this.conflictsMode = function() {
39 | this.diffPanel.visible = true;
40 | this.notificationsPanel.visible = false;
41 | this.layersPanel.visible = true;
42 | this.syncPanel.visible = false;
43 | this.historyPanel.visible = false;
44 | this.addLayers = false;
45 | rootScope_.$broadcast('conflict_mode');
46 | this.apply();
47 | this.showDiffPanel();
48 | };
49 |
50 | this.defaultMode = function() {
51 | this.diffPanel.visible = true;
52 | this.notificationsPanel.visible = true;
53 | this.layersPanel.visible = true;
54 | this.syncPanel.visible = true;
55 | this.historyPanel.visible = true;
56 | this.addLayers = true;
57 | rootScope_.$broadcast('default_mode');
58 | this.apply();
59 | this.showLayerPanel();
60 | };
61 |
62 | this.showHistoryPanel = function() {
63 | timeout_(function() {
64 | $('#history-panel').collapse('show');
65 | }, 1);
66 | };
67 |
68 | this.showDiffPanel = function() {
69 | timeout_(function() {
70 | $('#diff-panel').collapse('show');
71 | }, 1);
72 | };
73 |
74 | this.showLayerPanel = function() {
75 | timeout_(function() {
76 | $('#layer-manager-panel').collapse('show');
77 | }, 1);
78 | };
79 | });
80 |
81 | }());
82 |
--------------------------------------------------------------------------------
/src/common/refresh/RefreshModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | angular.module('loom_refresh', [
3 | 'loom_refresh_service'
4 | ]);
5 | }());
6 |
--------------------------------------------------------------------------------
/src/common/search/SearchDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_search_directive', []);
3 |
4 | module.directive('loomSearch',
5 | function($timeout, $translate, searchService, dialogService, mapService) {
6 | return {
7 | restrict: 'C',
8 | replace: true,
9 | templateUrl: 'search/partial/search.tpl.html',
10 | // The linking function will add behavior to the template
11 | link: function(scope) {
12 | scope.searchQuery = '';
13 | scope.searchInProgress = false;
14 | scope.searchResults = [];
15 |
16 | function zoomToResult(result) {
17 | var boxMin = ol.proj.transform([result.boundingbox[2], result.boundingbox[0]], 'EPSG:4326',
18 | mapService.map.getView().getProjection());
19 | var boxMax = ol.proj.transform([result.boundingbox[3], result.boundingbox[1]], 'EPSG:4326',
20 | mapService.map.getView().getProjection());
21 | var newBounds = [boxMin[0], boxMin[1], boxMax[0], boxMax[1]];
22 | var x = newBounds[3] - newBounds[1];
23 | var y = newBounds[2] - newBounds[0];
24 | x *= 0.5;
25 | y *= 0.5;
26 | newBounds[1] -= x;
27 | newBounds[3] += x;
28 | newBounds[0] -= y;
29 | newBounds[2] += y;
30 |
31 | mapService.zoomToExtent(newBounds);
32 | }
33 |
34 | scope.clearResults = function() {
35 | scope.searchResults = [];
36 | $('#search-results-panel').collapse('hide');
37 | searchService.clearSearchLayer();
38 | };
39 |
40 | scope.displayingResults = function() {
41 | return scope.searchResults.length > 0;
42 | };
43 |
44 | scope.performSearch = function() {
45 | if (scope.displayingResults()) {
46 | scope.searchQuery = '';
47 | scope.clearResults();
48 | return;
49 | }
50 | if (scope.searchInProgress === true) {
51 | return;
52 | }
53 | $('#search-results-panel').collapse('hide');
54 | if (goog.string.removeAll(goog.string.collapseWhitespace(scope.searchQuery), ' ') !== '') {
55 | scope.searchInProgress = true;
56 | searchService.performSearch(scope.searchQuery).then(function(results) {
57 | scope.searchResults = results;
58 | if (results.length === 0) {
59 | dialogService.open($translate.instant('search'), $translate.instant('search_no_results'));
60 | } else if (results.length === 1) {
61 | zoomToResult(results[0]);
62 | searchService.populateSearchLayer(results);
63 | } else {
64 | $timeout(function() {
65 | $('#search-results-panel').collapse('show');
66 | }, 10);
67 | searchService.populateSearchLayer(results);
68 | }
69 | scope.searchInProgress = false;
70 | }, function(_status) {
71 | scope.searchInProgress = false;
72 | scope.searchResults = [];
73 | if (goog.isDefAndNotNull(_status)) {
74 | dialogService.error($translate.instant('search'), $translate.instant('search_error_status',
75 | {status: _status}));
76 | } else {
77 | dialogService.error($translate.instant('search'), $translate.instant('search_error'));
78 | }
79 | });
80 | } else {
81 | scope.searchResults = [];
82 | }
83 | };
84 |
85 | scope.resultClicked = function(result) {
86 | zoomToResult(result);
87 | };
88 | }
89 | };
90 | });
91 | }());
92 |
--------------------------------------------------------------------------------
/src/common/search/SearchModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | angular.module('loom_search', [
3 | 'loom_search_service',
4 | 'loom_search_directive'
5 | ]);
6 | }());
7 |
--------------------------------------------------------------------------------
/src/common/search/SearchService.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_search_service', []);
3 |
4 | var httpService_ = null;
5 | var q_ = null;
6 | var configService_ = null;
7 | var mapService_ = null;
8 | var searchlayer_ = null;
9 |
10 | module.provider('searchService', function() {
11 | this.$get = function($rootScope, $http, $q, $translate, configService, mapService) {
12 | httpService_ = $http;
13 | q_ = $q;
14 | configService_ = configService;
15 | mapService_ = mapService;
16 |
17 | searchlayer_ = new ol.layer.Vector({
18 | metadata: {
19 | title: $translate.instant('search_results'),
20 | internalLayer: true
21 | },
22 | source: new ol.source.Vector({
23 | parser: null
24 | }),
25 | style: function(feature, resolution) {
26 | return [new ol.style.Style({
27 | image: new ol.style.Circle({
28 | radius: 8,
29 | fill: new ol.style.Fill({
30 | color: '#D6AF38'
31 | }),
32 | stroke: new ol.style.Stroke({
33 | color: '#000000'
34 | })
35 | })
36 | })];
37 | }
38 | });
39 |
40 | $rootScope.$on('translation_change', function() {
41 | searchlayer_.get('metadata').title = $translate.instant('search');
42 | });
43 |
44 | return this;
45 | };
46 |
47 | this.performSearch = function(address) {
48 | var currentView = mapService_.map.getView().calculateExtent([$(window).height(), $(window).width()]);
49 | var minBox = ol.proj.transform([currentView[0], currentView[1]],
50 | mapService_.map.getView().getProjection(), 'EPSG:4326');
51 | var maxBox = ol.proj.transform([currentView[2], currentView[3]],
52 | mapService_.map.getView().getProjection(), 'EPSG:4326');
53 | currentView[0] = minBox[0];
54 | currentView[1] = minBox[1];
55 | currentView[2] = maxBox[0];
56 | currentView[3] = maxBox[1];
57 | var promise = q_.defer();
58 | var nominatimUrl = configService_.configuration.nominatimUrl;
59 | if (nominatimUrl.substr(nominatimUrl.length - 1) === '/') {
60 | nominatimUrl = nominatimUrl.substr(0, nominatimUrl.length - 1);
61 | }
62 | var url = nominatimUrl + '/search?q=' + encodeURIComponent(address) +
63 | '&format=json&limit=30&viewboxlbrt=' + encodeURIComponent(currentView.toString());
64 | httpService_.get(url).then(function(response) {
65 | if (goog.isDefAndNotNull(response.data) && goog.isArray(response.data)) {
66 | var results = [];
67 | forEachArrayish(response.data, function(result) {
68 | var bbox = result.boundingbox;
69 | for (var i = 0; i < bbox.length; i++) {
70 | bbox[i] = parseFloat(bbox[i]);
71 | }
72 | results.push({
73 | location: [parseFloat(result.lon), parseFloat(result.lat)],
74 | boundingbox: bbox,
75 | name: result.display_name
76 | });
77 | });
78 | promise.resolve(results);
79 | } else {
80 | promise.reject(response.status);
81 | }
82 | }, function(reject) {
83 | promise.reject(reject.status);
84 | });
85 |
86 | return promise.promise;
87 | };
88 |
89 | this.populateSearchLayer = function(results) {
90 | searchlayer_.getSource().clear();
91 | mapService_.map.removeLayer(searchlayer_);
92 | mapService_.map.addLayer(searchlayer_);
93 | forEachArrayish(results, function(result) {
94 | var olFeature = new ol.Feature();
95 | olFeature.setGeometry(new ol.geom.Point(ol.proj.transform(result.location, 'EPSG:4326',
96 | mapService_.map.getView().getProjection())));
97 | searchlayer_.getSource().addFeature(olFeature);
98 | });
99 | };
100 |
101 | this.clearSearchLayer = function() {
102 | searchlayer_.getSource().clear();
103 | mapService_.map.removeLayer(searchlayer_);
104 | };
105 | });
106 | }());
107 |
--------------------------------------------------------------------------------
/src/common/search/partial/search.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
24 | {{result.name}}
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/common/search/style/search.less:
--------------------------------------------------------------------------------
1 | #search-panel {
2 |
3 | .loading-container {
4 | background-color: @buttonColor;
5 |
6 | .loading-spinner {
7 | border-color: @saveIconColor !important;
8 | }
9 | }
10 |
11 | .panel-heading {
12 | font-size: 20px;
13 | font-weight: 100;
14 | padding: 6px;
15 | border-top: 1px solid @mainHeaderColor;
16 | background-color: @mainHeaderColor;
17 | }
18 |
19 | i {
20 | color: @saveIconColor;
21 | cursor: pointer;
22 | }
23 |
24 | #search-results-panel {
25 | overflow-y: auto;
26 | max-height: 100px;
27 |
28 | .search-result-name {
29 | width: 98%;
30 | display: inline-block;
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/src/common/statistics/StatisticsModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | angular.module('loom_statistics', [
3 | 'loom_statistics_directive',
4 | 'loom_statistics_service'
5 | ]);
6 | }());
7 |
--------------------------------------------------------------------------------
/src/common/statistics/StatisticsService.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_statistics_service', []);
3 |
4 | var dialogService_ = null;
5 | var httpService_ = null;
6 | var tableViewService_ = null;
7 | var mapService_ = null;
8 | var q_ = null;
9 | var translate_ = null;
10 |
11 | module.provider('statisticsService', function() {
12 | this.$get = function($q, $http, mapService, dialogService, tableViewService, $translate) {
13 | q_ = $q;
14 | httpService_ = $http;
15 | mapService_ = mapService;
16 | dialogService_ = dialogService;
17 | tableViewService_ = tableViewService;
18 | translate_ = $translate;
19 | return this;
20 | };
21 |
22 | this.summarizeAttribute = function(layer, filters, attributeName) {
23 | var deferredResponse = q_.defer();
24 | if (!goog.isDefAndNotNull(layer)) {
25 | deferredResponse.reject('Invalid Layer');
26 | return deferredResponse.promise;
27 | }
28 |
29 | var wfsPayload = tableViewService_.getFeaturesPostPayloadXML(layer, filters, null, null, null, true);
30 | console.log('wfsPayload: ', wfsPayload);
31 |
32 | if (mapService_.layerIsEditable(layer)) {
33 | var url = layer.get('metadata').url + '/wps?version=' + settings.WPSVersion;
34 | var wpsPostData = '' +
35 | '' +
45 | 'py:summarize_attrib ' +
46 | '' +
47 | '' +
48 | 'attributeName ' +
49 | '' +
50 | '' + attributeName + ' ' +
51 | ' ' +
52 | ' ' +
53 | '' +
54 | 'features ' +
55 | '' +
56 | '' +
57 | wfsPayload +
58 | ' ' +
59 | ' ' +
60 | ' ' +
61 | ' ' +
62 | '' +
63 | '' +
64 | 'result ' +
65 | ' ' +
66 | ' ' +
67 | ' ';
68 |
69 | httpService_.post(url, wpsPostData).success(function(data, status, headers, config) {
70 | if (typeof data == 'string' && (data.indexOf('ProcessFailed') > -1)) {
71 | deferredResponse.reject('Service Error');
72 | dialogService_.error(translate_.instant('error'), translate_.instant('error'));
73 | } else {
74 | var stats = {};
75 | stats.fieldname = attributeName;
76 | stats.statistics = {};
77 | goog.object.extend(stats.statistics, data);
78 | deferredResponse.resolve(stats);
79 | }
80 | }).error(function(data, status, headers, config) {
81 | console.log('----[ Warning: StatisticsService.summarizeAttribute error', data, status, headers, config);
82 | deferredResponse.reject('Service Error');
83 | });
84 |
85 | } else {
86 | deferredResponse.reject('Invalid layer type');
87 | }
88 | return deferredResponse.promise;
89 | };
90 | });
91 | }());
92 |
93 |
--------------------------------------------------------------------------------
/src/common/statistics/style/statistics.less:
--------------------------------------------------------------------------------
1 | .black-stroke {
2 | stroke-width: 2px;
3 | stroke: black;
4 | opacity: .9;
5 | }
6 |
7 | .pie-chart-text {
8 | font-size: 13px;
9 | }
10 |
11 | .no-select {
12 | -webkit-touch-callout: none;
13 | -webkit-user-select: none;
14 | -khtml-user-select: none;
15 | -moz-user-select: none;
16 | -ms-user-select: none;
17 | user-select: none;
18 | cursor: default;
19 | }
20 |
21 | .path-red-fill {
22 | fill: #FF4136;
23 | }
24 |
25 | #statistics-chart-area {
26 | min-height: 330px
27 | }
28 |
29 | .statistics-legend-text {
30 | .pie-chart-text();
31 | padding-left: 5px;
32 | padding-bottom: 3px;
33 | }
34 |
35 | @statistics-legend-width: 135px;
36 |
37 | .statistics-legend {
38 | overflow-x: auto;
39 | overflow-y: auto;
40 | width: @statistics-legend-width;
41 | padding: 5px;
42 | display: block;
43 | float: left;
44 | height: 450px;
45 | }
46 |
47 | #loom-statistics-histogram {
48 | margin-left: @statistics-legend-width + 15px;
49 | overflow-x: auto;
50 | }
51 |
52 | #loom-statistics-pie {
53 | margin-left: @statistics-legend-width + 15px;
54 | overflow-x: auto;
55 | }
56 |
57 | #statistics-btns {
58 | margin-bottom: 10px;
59 | }
60 |
61 | #statistics-view-window hr{
62 | border: none;
63 | }
64 |
65 | #statistics-view-window .modal-dialog{
66 | min-width: 900px;
67 | }
68 |
69 | #statistics-view-window .modal-body{
70 | background-color: white;
71 | padding: 15px;
72 | }
73 |
74 | .axis path,
75 | .axis line {
76 | fill: none;
77 | stroke: #000;
78 | shape-rendering: crispEdges;
79 | }
80 |
81 | #statistics-hud-bar{
82 | float: right;
83 | min-width: 90px;
84 | padding-left: 3px;
85 | margin-right: -25px;
86 | }
87 |
88 | .statistics-title {
89 | font-variant: small-caps;
90 | font-size: 17px;
91 | }
92 |
93 | .statistics-value {
94 | color: #333;
95 | font-weight: bold;
96 | font-size: 18px;
97 | margin-top: -3px;
98 | }
99 |
100 | .table-center {
101 | display: table-cell;
102 | vertical-align: middle;
103 | text-align: center;
104 | }
105 |
106 | .statistics-hud-group {
107 | padding: 3px 0px 3px;
108 | display: block;
109 | }
110 |
111 |
--------------------------------------------------------------------------------
/src/common/sync/AddSynchronizationLinkDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | var module = angular.module('loom_addsync_directive', []);
4 |
5 | module.directive('loomAddsync',
6 | function($translate, synchronizationService, geogigService, dialogService, $q) {
7 | return {
8 | templateUrl: 'sync/partials/addsync.tpl.html',
9 | link: function(scope, element) {
10 | scope.geogigService = geogigService;
11 | scope.name = $translate.instant('link');
12 |
13 | scope.$on('translation_change', function() {
14 | scope.name = $translate.instant('link');
15 | });
16 |
17 | scope.createLink = function(name, repo, remote, localBranch, remoteBranch) {
18 | synchronizationService.addLink(new SynchronizationLink(name, repo, localBranch, remote, remoteBranch));
19 | };
20 |
21 | var reset = function() {
22 | scope.name = $translate.instant('link');
23 | scope.selectedRepo = null;
24 | scope.selectedRemote = null;
25 | scope.localBranch = null;
26 | scope.remoteBranch = null;
27 | };
28 |
29 | scope.dismiss = function() {
30 | reset();
31 | element.closest('.modal').modal('hide');
32 | };
33 |
34 | var parentModal = element.closest('.modal');
35 | var closeModal = function(event, element) {
36 | if (parentModal[0] === element[0]) {
37 | reset();
38 | }
39 | };
40 |
41 | scope.$watch('selectedRemote', function() {
42 | if (goog.isDefAndNotNull(scope.selectedRemote)) {
43 | if (scope.selectedRemote.branches.length === 0) {
44 | dialogService.open($translate.instant('fetch'), $translate.instant('remote_not_fetched'),
45 | [$translate.instant('btn_ok')], false).then(function(button) {
46 | switch (button) {
47 | case 0:
48 | // Fetch the remote
49 | element.find('#loading').toggleClass('hidden');
50 | var fetchResult = $q.defer();
51 | var fetchOptions = new GeoGigFetchOptions();
52 | fetchOptions.remote = scope.selectedRemote.name;
53 | geogigService.command(scope.selectedRepo.id, 'fetch', fetchOptions).then(function() {
54 | geogigService.loadRemotesAndBranches(scope.selectedRepo, fetchResult);
55 | fetchResult.promise.then(function() {
56 | element.find('#loading').toggleClass('hidden');
57 | });
58 | }, function(error) {
59 | var message = $translate.instant('fetch_error');
60 | if (error.status == '408' || error.status == '504') {
61 | message = $translate.instant('fetch_timeout');
62 | }
63 | dialogService.error($translate.instant('fetch'), message,
64 | [$translate.instant('btn_ok')], false);
65 | element.find('#loading').toggleClass('hidden');
66 | });
67 | }
68 | });
69 | }
70 | }
71 | });
72 |
73 | scope.$on('repoRemoved', reset);
74 | scope.$on('modal-closed', closeModal);
75 | scope.$on('loadLink', function(event, link) {
76 | $('#addSyncWindow').modal('toggle');
77 | scope.name = link.name;
78 | scope.selectedRepo = link.getRepo();
79 | scope.selectedRemote = link.getRemote();
80 | scope.localBranch = link.getLocalBranch();
81 | scope.remoteBranch = link.getRemoteBranch();
82 | });
83 | }
84 | };
85 | }
86 | );
87 | })();
88 |
--------------------------------------------------------------------------------
/src/common/sync/SyncModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | angular.module('loom_sync', [
4 | 'loom_addsync_directive',
5 | 'loom_syncconfig_directive',
6 | 'loom_synclinks_directive',
7 | 'loom_sync_service',
8 | 'loom_remote_service'
9 | ]);
10 | })();
11 |
--------------------------------------------------------------------------------
/src/common/sync/SynchronizationConfigurationDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | var module = angular.module('loom_syncconfig_directive', []);
4 |
5 | module.directive('loomSyncconfig',
6 | function($q, $translate, remoteService, geogigService) {
7 | return {
8 | templateUrl: 'sync/partials/syncconfig.tpl.html',
9 | link: function(scope, element) {
10 | scope.geogigService = geogigService;
11 | scope.remoteService = remoteService;
12 | scope.saving = false;
13 |
14 | angular.element('#remote-name')[0].attributes.placeholder.value = $translate.instant('repo_name');
15 | angular.element('#remoteUsername')[0].attributes.placeholder.value = $translate.instant('repo_username');
16 | angular.element('#remotePassword')[0].attributes.placeholder.value = $translate.instant('repo_password');
17 |
18 | scope.finish = function(save) {
19 | if (save) {
20 | scope.saving = true;
21 | var result = $q.defer();
22 | remoteService.finishRemoteOperation(save, result);
23 | result.promise.then(function() {
24 | remoteService.editing = false;
25 | scope.saving = false;
26 | });
27 | } else {
28 | remoteService.finishRemoteOperation(save);
29 | }
30 | };
31 |
32 | var parentModal = element.closest('.modal');
33 | var closeModal = function(event, element) {
34 | if (parentModal[0] === element[0]) {
35 | remoteService.reset();
36 | }
37 | };
38 |
39 | scope.$watch('remoteService.selectedRepo', function() {
40 | if (goog.isDefAndNotNull(remoteService.selectedRepo)) {
41 | var logOptions = new GeoGigLogOptions();
42 | logOptions.returnRange = true;
43 | geogigService.command(remoteService.selectedRepo.id, 'log', logOptions).then(function(logInfo) {
44 | if (logInfo.success === true) {
45 | remoteService.selectedRepoInitialCommit = logInfo.sinceCommit.id;
46 | }
47 | });
48 | }
49 | });
50 |
51 | scope.$on('translation_change', function() {
52 | remoteService.selectedText = '*' + $translate.instant('new_remote');
53 | });
54 |
55 | scope.$on('repoRemoved', remoteService.reset);
56 | scope.$on('modal-closed', closeModal);
57 |
58 | function onResize() {
59 | var height = $(window).height();
60 | element.children('.modal-body').css('max-height', (height - 200).toString() + 'px');
61 | }
62 |
63 | onResize();
64 |
65 | $(window).resize(onResize);
66 | }
67 | };
68 | }
69 | );
70 | })();
71 |
--------------------------------------------------------------------------------
/src/common/sync/SynchronizationLinksDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | var module = angular.module('loom_synclinks_directive', []);
4 |
5 | module.directive('loomSynclinks',
6 | function($translate, synchronizationService, dialogService, mapService) {
7 | return {
8 | restrict: 'C',
9 | replace: true,
10 | templateUrl: 'sync/partials/synclinks.tpl.html',
11 | link: function(scope) {
12 | scope.syncService = synchronizationService;
13 | var createDefaultLinks = function(event, repo, remote) {
14 | if (!goog.isDefAndNotNull(repo.unique) || repo.unique === false) {
15 | return;
16 | }
17 | var localMaster = false;
18 | var index;
19 | for (index = 0; index < repo.branches.length; index++) {
20 | if (repo.branches[index] === 'master') {
21 | localMaster = true;
22 | break;
23 | }
24 | }
25 | for (index = 0; index < repo.remotes.length; index++) {
26 | if (repo.remotes[index].branches.length > 0 && (!goog.isDefAndNotNull(remote) ||
27 | remote === repo.remotes[index].name)) {
28 | for (var branchIndex = 0; branchIndex < repo.remotes[index].branches.length; branchIndex++) {
29 | if (repo.remotes[index].branches[branchIndex] === 'master' && localMaster) {
30 | scope.syncService.addLink(new SynchronizationLink(repo.name + ':' + repo.remotes[index].name,
31 | repo, 'master', repo.remotes[index], 'master'));
32 | }
33 | }
34 | }
35 | }
36 | };
37 |
38 | scope.singleSync = function(link) {
39 | if (!link.isSyncing && !scope.syncService.getIsSyncing()) {
40 | link.isSyncing = true;
41 | link.singleSync = true;
42 | scope.syncService.sync(link).then(function(syncedLink) {
43 | syncedLink.isSyncing = false;
44 | link.singleSync = false;
45 | dialogService.open($translate.instant('synchronization'),
46 | $translate.instant('synchronization_success'));
47 | mapService.dumpTileCache();
48 | }, function(error) {
49 | // Something failed
50 | link.isSyncing = false;
51 | link.singleSync = false;
52 | if (!(goog.isDefAndNotNull(error) && error === false)) {
53 | dialogService.error($translate.instant('synchronization'),
54 | $translate.instant('synchronization_failed'));
55 | }
56 | });
57 | }
58 | };
59 |
60 | scope.$on('repoAdded', createDefaultLinks);
61 | scope.$on('remoteAdded', createDefaultLinks);
62 | }
63 | };
64 | }
65 | );
66 | })();
67 |
--------------------------------------------------------------------------------
/src/common/sync/SynchronizationPrototypes.js:
--------------------------------------------------------------------------------
1 | var SynchronizationLink = function(_name, _repo, _localBranch, _remote, _remoteBranch) {
2 | this.name = _name;
3 |
4 | this.isSyncing = false;
5 |
6 | this.continuous = false;
7 |
8 | this.singleSync = false;
9 |
10 | this.syncInterval = 30000; // In milliseconds
11 |
12 | this.timeStamp = new Date().getTime();
13 |
14 | this.setContinuous = function(continuous) {
15 | this.continuous = continuous;
16 | };
17 |
18 | this.getRepo = function() {
19 | return _repo;
20 | };
21 | this.getRemote = function() {
22 | return _remote;
23 | };
24 | this.getLocalBranch = function() {
25 | return _localBranch;
26 | };
27 | this.getRemoteBranch = function() {
28 | return _remoteBranch;
29 | };
30 |
31 | this.getIsActive = function() {
32 | return _remote.active;
33 | };
34 |
35 | this.equals = function(link) {
36 | return this.getRepo() === link.getRepo() && this.getRemote() === link.getRemote() &&
37 | this.getLocalBranch() === link.getLocalBranch() && this.getRemoteBranch() === link.getRemoteBranch();
38 | };
39 | };
40 |
--------------------------------------------------------------------------------
/src/common/sync/partials/addsync.tpl.html:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
23 |
36 |
49 |
50 |
51 |
57 |
--------------------------------------------------------------------------------
/src/common/sync/partials/syncconfig.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
31 |
32 |
40 |
48 |
62 |
63 |
64 |
74 |
--------------------------------------------------------------------------------
/src/common/sync/partials/synclinks.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
17 |
19 |
20 |
{{link.name}}
21 |
22 |
23 |
26 |
27 |
28 |
29 |
32 |
33 |
34 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/src/common/sync/style/sync.less:
--------------------------------------------------------------------------------
1 | #SyncList {
2 | margin-bottom: 0;
3 | padding-left: 15px;
4 | .list-group-item {
5 | background-color: @syncListColor;
6 | color: @syncListFontColor;
7 | .row{
8 | padding: 0 !important;
9 | }
10 | }
11 | }
12 |
13 | #buttonRow {
14 | padding: 0 !important;
15 | }
16 |
17 | .sync-list-button {
18 | .loom-button-variant(@syncListButtonFontColor, @syncListButtonFontColor, @syncListButtonColor, @syncListButtonColor);
19 | border: 1px solid lighten(@syncListColor, 10%) !important;
20 | border-radius: 0;
21 | }
22 |
23 | .sync-on, .sync-on:hover {
24 | background-color: @syncOnColor;
25 | color: @syncOnImageColor;
26 | }
27 |
28 | .remote {
29 | color: @remoteDownColor;
30 | }
31 |
32 | .remote-up {
33 | color: @remoteUpColor;
34 | }
35 |
36 | [loom-syncconfig] .modal-body {
37 | overflow: hidden;
38 | overflow-y: auto;
39 | min-height: 200px;
40 | }
41 |
42 | #remote-loading {
43 | .loading-container {
44 | position: absolute;
45 | width: 100%;
46 | height: 100%;
47 | top: 0;
48 | left: 0;
49 | .z-index(@dialogLevel, 1);
50 | background-color: rgba(0,0,0,0.5);
51 | }
52 | .loading-spinner {
53 | border-color: white !important;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/common/tableview/FilterOptionsDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_filter_options_directive', []);
3 | module.directive('filteroptions',
4 | function(tableViewService) {
5 | return {
6 | restrict: 'E',
7 | templateUrl: 'tableview/partial/filteroptions.tpl.html',
8 | scope: {
9 | attribute: '=',
10 | typeRestriction: '=type'
11 | },
12 | replace: true,
13 | link: function(scope, element) {
14 | console.log('attribute', scope.attribute);
15 | scope.dirty = false;
16 |
17 | if (scope.typeRestriction === 'int' || scope.typeRestriction === 'double') {
18 | scope.filterType = 'number';
19 | } else if (scope.typeRestriction === 'datetime') {
20 | scope.filterType = 'datetime';
21 | } else if (scope.typeRestriction === 'date') {
22 | scope.filterType = 'date';
23 | } else if (scope.typeRestriction === 'time') {
24 | scope.filterType = 'time';
25 | } else {
26 | scope.filterType = 'text';
27 | }
28 |
29 | scope.exactMatch = function() {
30 | scope.attribute.filter.searchType = 'exactMatch';
31 | scope.checkFilterStatus();
32 | };
33 | scope.strContains = function() {
34 | scope.attribute.filter.searchType = 'strContains';
35 | scope.checkFilterStatus();
36 | };
37 | scope.numRange = function() {
38 | scope.attribute.filter.searchType = 'numRange';
39 | scope.checkFilterStatus();
40 | scope.updateFilterText();
41 | };
42 |
43 | scope.checkFilterStatus = function() {
44 | var filter = scope.attribute.filter;
45 | if (filter.text !== '' && filter.searchType === 'strContains' || (filter.searchType === 'numRange' &&
46 | ((goog.isDef(filter.start) && filter.start !== '') ||
47 | (goog.isDef(filter.end) && filter.end !== '')))) {
48 | scope.dirty = true;
49 | } else {
50 | scope.dirty = false;
51 | }
52 | };
53 |
54 | scope.updateFilterText = function() {
55 | var filter = scope.attribute.filter;
56 | if (goog.isDefAndNotNull(filter.start) && filter.start !== '' &&
57 | goog.isDefAndNotNull(filter.end) && filter.end !== '') {
58 | filter.text = filter.start + ' to ' + filter.end;
59 | } else if (goog.isDefAndNotNull(filter.start) && filter.start !== '') {
60 | filter.text = filter.start + ' to max';
61 | } else if (goog.isDefAndNotNull(filter.end) && filter.end !== '') {
62 | filter.text = 'min to ' + filter.end;
63 | } else {
64 | filter.text = '';
65 | }
66 | };
67 | }
68 | };
69 | }
70 | );
71 | }());
72 |
--------------------------------------------------------------------------------
/src/common/tableview/TableViewModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | angular.module('loom_table_view', [
3 | 'loom_table_view_directive',
4 | 'loom_filter_options_directive',
5 | 'loom_table_view_service'
6 | ]);
7 | }());
8 |
--------------------------------------------------------------------------------
/src/common/tableview/style/tableview.less:
--------------------------------------------------------------------------------
1 | .table-striped > tbody > tr > td, .table-striped > thead > tr > th{
2 | padding: 20px;
3 | max-width: 300px;
4 | overflow: hidden;
5 | text-overflow: ellipsis;
6 | }
7 |
8 | .table-striped > thead > tr > th{
9 | font-weight: bold;
10 | border-bottom: 2px solid #ddd;
11 | }
12 | .table-striped > tbody > tr > td {
13 | border-top: 1px solid #ddd;
14 | }
15 |
16 | .table-btn {
17 | margin-top: 10px;
18 | }
19 |
20 | .filters-label {
21 | font-weight: 800;
22 | margin-right: 10px;
23 | }
24 |
25 | .filter-button {
26 | display: inline-block;
27 | margin-bottom: 10px;
28 | margin-left: 5px;
29 | }
30 |
31 | #table-view-window .table-striped {
32 | white-space: nowrap;
33 | }
34 |
35 | #table-view-window .modal-dialog {
36 | width: 90%;
37 | height: 100%;
38 | }
39 | #table-view-window .modal-content {
40 | height: 100%;
41 | }
42 | #table-view {
43 | height: 100%;
44 | }
45 | #table-view-window .modal-body {
46 | height: 100%;
47 | padding-top: 5px;
48 | overflow: auto;
49 | }
50 | #table-view-window .panel {
51 | overflow: auto;
52 | max-width: 100%;
53 | height: 100%;
54 | background-color: #ffffff;
55 | }
56 |
57 | #filter-text {
58 | margin-bottom: 10px;
59 | margin-top: 10px;
60 | }
61 | #filter-button {
62 | padding-left: 10px;
63 | }
64 | #table-filter-group {
65 | width: 300px;
66 | }
67 |
68 | #table-datetime {
69 | width: 250px;
70 | height: 34px;
71 | }
72 | #table-datetime .form-group {
73 | margin-bottom: 0px;
74 | }
75 |
76 | .table-dropdown {
77 | width: 140px !important;
78 | }
79 |
80 | .table-editing {
81 | overflow: visible !important;
82 | }
83 |
84 | .selectedRow {
85 | background-color: @selectedTableRowColor !important;
86 | }
87 | .selectedRow > td {
88 | background-color: @selectedTableRowColor !important;
89 | }
90 |
91 | #previous-page-btn {
92 | margin-left: 10px;
93 | }
94 |
95 | .filter-row {
96 | padding: 5px;
97 | }
98 |
99 | .first-filter-row {
100 | padding-left: 20px;
101 | }
102 |
103 | .table-page-indicator {
104 | display: inline-block;
105 | position: relative;
106 | top: 5px;
107 | padding: 3px;
108 | font-weight: 600;
109 | }
110 |
111 | .table-view-footer-text {
112 | display: inline-block;
113 | position: relative;
114 | top: 5px;
115 | padding: 3px;
116 | font-weight: 600;
117 | }
118 |
119 | #table-loading {
120 | .loading-container {
121 | position: absolute;
122 | width: 100%;
123 | height: 100%;
124 | top: 0;
125 | left: 0;
126 | .z-index(@dialogLevel, 1);
127 | background-color: rgba(0,0,0,0.5);
128 | }
129 | .loading-spinner {
130 | border-color: white !important;
131 | }
132 | }
133 |
134 | .filter-option {
135 | cursor: pointer;
136 | }
137 |
138 | .filter-options-selected {
139 | background-color: lightgrey;
140 | font-weight: bold !important;
141 | }
142 |
143 | .selectedAttribute {
144 | background-color: @selectedTableRowColor !important;
145 | }
146 |
147 | .pointer {
148 | cursor: pointer;
149 | }
150 |
151 | .filter-range-text {
152 | width: 70px !important;
153 | border-bottom-left-radius: 4px !important;
154 | border-top-left-radius: 4px !important;
155 | //margin: 5px;
156 | }
157 |
158 | .range-label {
159 | color: black;
160 | float: left !important;
161 | }
162 |
163 | .dirty-filter {
164 | background-color: @selectedTableRowColor !important;
165 | border-color: #adadad !important;
166 | }
167 |
168 | .date-time-option {
169 | width: 300px;
170 | }
171 |
172 | .filter-form-group {
173 | margin-left: auto !important;
174 | }
175 |
176 | .range-filter-table {
177 | margin-left: 10px;
178 |
179 | .form-control {
180 | padding-left: 3px;
181 | border-radius: 4px;
182 | }
183 | }
184 |
185 | .advanced-filter {
186 | border-top: 1px solid gray;
187 | padding: 3px;
188 | }
189 |
190 | .no-radius .input-group-addon {
191 | border-radius: 0px;
192 | }
193 |
194 | .filter-input-group {
195 | .input-group-btn > button {
196 | border-left-width: 0px;
197 | }
198 | }
199 |
200 | .table-search-group {
201 | width: 212px;
202 | display: inline-table;
203 | }
204 |
205 | .wide-table-element {
206 | width: 160px;
207 | }
--------------------------------------------------------------------------------
/src/common/test/TestModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | angular.module('loom_test', [
4 | 'loom_test_service'
5 | ]);
6 | })();
7 |
--------------------------------------------------------------------------------
/src/common/timeline/TimelineModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | angular.module('loom_timeline', [
4 | 'loom_timeline_directive',
5 | 'loom_timeline_service'
6 | ]);
7 | })();
8 |
--------------------------------------------------------------------------------
/src/common/timeline/partials/timeline.tpl.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/common/timeline/style/timeline.less:
--------------------------------------------------------------------------------
1 | .timeline-background2 {
2 | width: 100%;
3 | height: 38px;
4 | background-color: rgba(0,0,0,0.2);
5 | position: fixed;
6 | bottom: 0px;
7 | padding: 5px;
8 | .z-index(@menuLevel, 5);
9 | }
10 |
11 | .timeline-slider {
12 | height: 10px;
13 | padding: 0px 6px 0px 6px;
14 | font-weight: 200;
15 | border-radius: 2px;
16 | min-width: 280px;
17 | max-width: 280px;
18 | }
19 |
20 | /* Popover Body */
21 | .timeline-background2 .popover.top .popover-content {
22 | color: white;
23 | padding: 4px;
24 | }
25 | /* Popover Arrow */
26 | .timeline-background2 .popover.top .arrow {
27 | display: none;
28 | }
29 |
30 | .timeline-background2 .popover.top {
31 | top: -18px!important;
32 | box-shadow: 0px 0px 0px;
33 | background-color: rgba(1, 0, 1, 0.87);
34 | border-radius: 4px;
35 | position: absolute;
36 | z-index: 1030;
37 | display: block;
38 | visibility: visible;
39 | font-size: 12px;
40 | line-height: 10px;
41 | }
42 |
43 | .timeline-background2 input{
44 | color: rgb(233, 91, 6)
45 | ;
46 | }
47 | /*
48 | input[type=range] {
49 | //removes default webkit styles
50 | //-webkit-appearance: none;
51 |
52 | background-color: rgba(0,0,0,0);
53 |
54 | // fixes firefox bar going into adjacent components.
55 | // but shortens it on webkit
56 | margin-left: 10px;
57 | margin-right: 10px;
58 |
59 | //fix for FF unable to apply focus style bug
60 | border: 1px solid rgba(0,0,0,0);
61 |
62 | //required for proper track sizing in FF
63 | width: 300px;
64 | }
65 | input[type=range]::-webkit-slider-runnable-track {
66 | width: 300px;
67 | height: 5px;
68 | background: #ddd;
69 | border: none;
70 | border-radius: 3px;
71 | }
72 | input[type=range]::-webkit-slider-thumb {
73 | -webkit-appearance: none;
74 | border: none;
75 | height: 16px;
76 | width: 16px;
77 | border-radius: 50%;
78 | background: #d96517;
79 | margin-top: -4px;
80 | }
81 | input[type=range]:focus {
82 | outline: none;
83 | }
84 | input[type=range]:focus::-webkit-slider-runnable-track {
85 | background: #ccc;
86 | }
87 |
88 | input[type=range]::-moz-range-track {
89 | width: 300px;
90 | height: 5px;
91 | background: #ddd;
92 | border: none;
93 | border-radius: 3px;
94 | }
95 | input[type=range]::-moz-range-thumb {
96 | border: none;
97 | height: 16px;
98 | width: 16px;
99 | border-radius: 50%;
100 | background: #d96517;
101 | }
102 |
103 | //hide the outline behind the border
104 | input[type=range]:-moz-focusring{
105 | outline: 1px solid rgba(0,0,0,0);
106 | outline-offset: -1px;
107 | }
108 |
109 | input[type=range]::-ms-track {
110 | width: 300px;
111 | height: 5px;
112 |
113 | ///*remove bg colour from the track, we'll use ms-fill-lower and ms-fill-upper instead
114 | background: transparent;
115 |
116 | ///*leave room for the larger thumb to overflow with a transparent border
117 | border-color: transparent;
118 | border-width: 6px 0;
119 |
120 | ///*remove default tick marks
121 | color: transparent;
122 | }
123 | input[type=range]::-ms-fill-lower {
124 | background: #777;
125 | border-radius: 10px;
126 | }
127 | input[type=range]::-ms-fill-upper {
128 | background: #ddd;
129 | border-radius: 10px;
130 | }
131 | input[type=range]::-ms-thumb {
132 | border: none;
133 | height: 16px;
134 | width: 16px;
135 | border-radius: 50%;
136 | background: #d96517;
137 | }
138 | input[type=range]:focus::-ms-fill-lower {
139 | background: #888;
140 | }
141 | input[type=range]:focus::-ms-fill-upper {
142 | background: #ccc;
143 | }
144 | */
--------------------------------------------------------------------------------
/src/common/updatenotification/UpdateNotificationDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_update_notification_directive', []);
3 |
4 | module.directive('loomUpdateNotification',
5 | function(mapService) {
6 | return {
7 | restrict: 'C',
8 | replace: true,
9 | scope: { notification: '=' },
10 | templateUrl: 'updatenotification/partial/updatenotification.tpl.html',
11 | // The linking function will add behavior to the template
12 | link: function(scope) { // Unused: element, attrs
13 |
14 | var headerElement = angular.element('#update-notification-header-' + scope.notification.id);
15 | headerElement.attr('data-toggle', 'collapse');
16 | headerElement.attr('data-target', '#notification-description-' + scope.notification.id);
17 | headerElement.attr('data-parent', '#notification-collapse-group');
18 | headerElement.addClass('toggle');
19 | headerElement.addClass('collapsed');
20 |
21 | var repos = scope.notification.repos;
22 | var adds = [];
23 | var modifies = [];
24 | var deletes = [];
25 | var merges = [];
26 | var conflicts = [];
27 |
28 | var i = 0;
29 | //this function has to be defined outside of the loop, otherwise the linter gets angry
30 | var featureHandler = function(repoFeature) {
31 | var splitFeature = repoFeature.id.split('/');
32 | var crs = goog.isDefAndNotNull(repoFeature.crs) ? repoFeature.crs : null;
33 | mapService.map.getLayers().forEach(function(layer) {
34 | var metadata = layer.get('metadata');
35 | if (goog.isDefAndNotNull(metadata)) {
36 | if (goog.isDefAndNotNull(metadata.geogigStore) && metadata.geogigStore === repos[i].name) {
37 | if (goog.isDefAndNotNull(metadata.nativeName) && metadata.nativeName === splitFeature[0]) {
38 | if (goog.isDefAndNotNull(metadata.projection)) {
39 | crs = metadata.projection;
40 | }
41 | }
42 | }
43 | }
44 | });
45 |
46 | var geom = WKT.read(repoFeature.geometry);
47 | if (goog.isDefAndNotNull(crs)) {
48 | geom.transform(crs, mapService.map.getView().getProjection());
49 | }
50 | var feature = {
51 | repo: repos[i].name,
52 | layer: splitFeature[0],
53 | feature: splitFeature[1],
54 | original: repoFeature,
55 | extent: geom.getExtent()
56 | };
57 | switch (repoFeature.change) {
58 | case 'ADDED':
59 | adds.push(feature);
60 | break;
61 | case 'MODIFIED':
62 | modifies.push(feature);
63 | break;
64 | case 'REMOVED':
65 | deletes.push(feature);
66 | break;
67 | case 'MERGED':
68 | merges.push(feature);
69 | break;
70 | case 'CONFLICT':
71 | conflicts.push(feature);
72 | break;
73 | }
74 | };
75 | for (i = 0; i < repos.length; i += 1) {
76 | var features = repos[i].features;
77 | if (goog.isDefAndNotNull(features)) {
78 | scope.noFeatures = false;
79 | forEachArrayish(features, featureHandler);
80 | } else {
81 | scope.noFeatures = true;
82 | scope.message = scope.notification.emptyMessage;
83 | }
84 | }
85 | scope.adds = adds;
86 | scope.modifies = modifies;
87 | scope.deletes = deletes;
88 | scope.merges = merges;
89 | scope.conflicts = conflicts;
90 | }
91 | };
92 | });
93 | }());
94 |
--------------------------------------------------------------------------------
/src/common/updatenotification/UpdateNotificationModule.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | angular.module('loom_update_notification', [
3 | 'loom_update_notification_directive'
4 | ]);
5 | }());
6 |
--------------------------------------------------------------------------------
/src/common/updatenotification/partial/updatenotification.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{message}}
6 |
7 |
8 |
9 |
11 |
--------------------------------------------------------------------------------
/src/common/utils/LoadingDirective.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var module = angular.module('loom_loading_directive', []);
3 |
4 | module.directive('loomLoading',
5 | function() {
6 | return {
7 | restrict: 'C',
8 | templateUrl: 'utils/partial/loading.tpl.html',
9 | scope: {
10 | spinnerHidden: '='
11 | },
12 | link: function(scope, element, attrs) {
13 | scope.spinnerWidth = 3;
14 | scope.spinnerRadius = 28;
15 | if (goog.isDefAndNotNull(attrs.spinnerWidth)) {
16 | scope.spinnerWidth = parseInt(attrs.spinnerWidth, 10);
17 | }
18 | if (goog.isDefAndNotNull(attrs.spinnerRadius)) {
19 | scope.spinnerRadius = parseInt(attrs.spinnerRadius, 10);
20 | }
21 | var loading = element.find('.loading');
22 | loading.css('width', scope.spinnerRadius + 'px');
23 | loading.css('height', scope.spinnerRadius + 'px');
24 | loading.css('margin', '-' + scope.spinnerRadius / 2 + 'px 0 0 -' + scope.spinnerRadius / 2 + 'px');
25 |
26 | var loadingSpinner = element.find('.loading-spinner');
27 | loadingSpinner.css('width', (scope.spinnerRadius - scope.spinnerWidth) + 'px');
28 | loadingSpinner.css('height', (scope.spinnerRadius - scope.spinnerWidth) + 'px');
29 | loadingSpinner.css('border', scope.spinnerWidth + 'px solid');
30 | loadingSpinner.css('border-radius', (scope.spinnerRadius / 2) + 'px');
31 |
32 | var mask = element.find('.mask');
33 | mask.css('width', (scope.spinnerRadius / 2) + 'px');
34 | mask.css('height', (scope.spinnerRadius / 2) + 'px');
35 |
36 | var spinner = element.find('.spinner');
37 | spinner.css('width', scope.spinnerRadius + 'px');
38 | spinner.css('height', scope.spinnerRadius + 'px');
39 | }
40 | };
41 | });
42 | }());
43 |
--------------------------------------------------------------------------------
/src/common/utils/partial/loading.tpl.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/common/utils/style/utils.less:
--------------------------------------------------------------------------------
1 | .loom-button-variant(@color-active; @color-inactive; @background-active; @background-inactive) {
2 | color: @color-inactive;
3 | background-color: @background-inactive;
4 | border-color: darken(@background-inactive, 12%);
5 |
6 | &:hover {
7 | background-color: @background-inactive;
8 | border-color: darken(@background-inactive, 12%)
9 | }
10 |
11 | &:focus,
12 | &:active,
13 | &.active,
14 | .open .dropdown-toggle & {
15 | color: @color-active;
16 | background-color: @background-active;
17 | border-color: darken(@background-active, 12%);
18 | }
19 | &:active,
20 | &.active,
21 | .open .dropdown-toggle & {
22 | background-image: none;
23 | }
24 | &.disabled,
25 | &[disabled],
26 | fieldset[disabled] & {
27 | &,
28 | &:hover,
29 | &:focus,
30 | &:active,
31 | &.active {
32 | background-color: @background-inactive;
33 | border-color: darken(@background-inactive, 12%)
34 | }
35 | }
36 | }
37 |
38 | .loom-check-button {
39 | .loom-button-variant(white, black, @checkButtonColor, white);
40 | }
41 |
42 | .ellipsis {
43 | white-space: nowrap;
44 | overflow: hidden;
45 | text-overflow: ellipsis;
46 | }
47 |
48 | /* Animation keyframes - you need to add prefixes */
49 | @keyframes spin {
50 | from {
51 | transform: rotate(0deg);
52 | }
53 | to {
54 | transform: rotate(360deg);
55 | }
56 | }
57 |
58 | @-webkit-keyframes webkitspin {
59 | from {
60 | -webkit-transform: rotate(0deg);
61 | }
62 | to {
63 | -webkit-transform: rotate(360deg);
64 | }
65 | }
66 |
67 | .loading-container {
68 | position: absolute;
69 | width: 100%;
70 | height: 100%;
71 | top: 0;
72 | left: 0;
73 | .z-index(@dialogLevel, 1);
74 | opacity: 1.0;
75 | background-color: @buttonColor;
76 |
77 | /* Loading animation container */
78 | .loading {
79 | position: absolute;
80 | top: 50%;
81 | left: 50%;
82 | width: 28px;
83 | height: 28px;
84 | margin: -14px 0 0 -14px;
85 |
86 | /* Spinning circle (inner circle) */
87 | .loading-spinner {
88 | width: 20px;
89 | height: 20px;
90 | border-radius: 12px;
91 | border: 3px solid;
92 | border-color: @buttonColor;
93 | }
94 |
95 | /* Spinning circle mask */
96 | .mask {
97 | position: absolute;
98 | width: 12px;
99 | height: 12px;
100 | overflow: hidden;
101 | text-overflow: ellipsis;
102 | }
103 |
104 | /* Spinner */
105 | .spinner {
106 | position: absolute;
107 | width: 26px;
108 | height: 26px;
109 | animation: spin 1s infinite linear;
110 | -webkit-animation: webkitspin 1s infinite linear;
111 | }
112 | }
113 | }
114 |
115 | .z-index(@level; @offset: 0) {
116 | z-index: ((1000 * @level) + (@offset * 10));
117 | }
118 |
119 | .auto-text-area {
120 | resize: none;
121 | overflow: hidden;
122 | max-height: 200px;
123 | }
124 |
125 | textarea.clone {
126 | position: absolute;
127 | //visibility: hidden;
128 | }
129 |
130 | .btn-group-wrap {
131 | text-align: center;
132 | }
--------------------------------------------------------------------------------
/src/common/utils/wktparser.js:
--------------------------------------------------------------------------------
1 | // Taken from ol3 before it was removed
2 | // TODO: Once this is added back to ol3 remove this file
3 | var regExes = {
4 | typeStr: /^\s*(\w+)\s*\(\s*(.*)\s*\)\s*$/,
5 | spaces: /\s+/,
6 | parenComma: /\)\s*,\s*\(/,
7 | doubleParenComma: /\)\s*\)\s*,\s*\(\s*\(/,
8 | trimParens: /^\s*\(?(.*?)\)?\s*$/,
9 | geomCollection: /,\s*([A-Za-z])/g,
10 | removeNewLine: /[\n\r]/g
11 | };
12 |
13 | var parsePoint_ = function(str) {
14 | var coords = goog.string.trim(str).split(regExes.spaces);
15 | return new ol.geom.Point(goog.array.map(coords, parseFloat));
16 | };
17 |
18 | var parseLineString_ = function(str) {
19 | var points = goog.string.trim(str).split(',');
20 | var coordinates = [];
21 | for (var i = 0, ii = points.length; i < ii; ++i) {
22 | coordinates.push(parsePoint_.apply(this,
23 | [points[i]]).getCoordinates());
24 | }
25 | return new ol.geom.LineString(coordinates);
26 | };
27 |
28 | var parseMultiPoint_ = function(str) {
29 | var point;
30 | var points = goog.string.trim(str).split(',');
31 | var parts = [];
32 | for (var i = 0, ii = points.length; i < ii; ++i) {
33 | point = points[i].replace(regExes.trimParens, '$1');
34 | parts.push(parsePoint_.apply(this, [point]).getCoordinates());
35 | }
36 | return new ol.geom.MultiPoint(parts);
37 | };
38 |
39 | var parseMultiLineString_ = function(str) {
40 | var line;
41 | var lines = goog.string.trim(str).split(regExes.parenComma);
42 | var parts = [];
43 | for (var i = 0, ii = lines.length; i < ii; ++i) {
44 | line = lines[i].replace(regExes.trimParens, '$1');
45 | parts.push(parseLineString_.apply(this, [line]).getCoordinates());
46 | }
47 | return new ol.geom.MultiLineString(parts);
48 | };
49 |
50 | var parsePolygon_ = function(str) {
51 | var ring, linestring;
52 | var rings = goog.string.trim(str).split(regExes.parenComma);
53 | var coordinates = [];
54 | for (var i = 0, ii = rings.length; i < ii; ++i) {
55 | ring = rings[i].replace(regExes.trimParens, '$1');
56 | linestring = parseLineString_.apply(this, [ring]).getCoordinates();
57 | coordinates.push(linestring);
58 | }
59 | return new ol.geom.Polygon(coordinates);
60 | };
61 |
62 | var parseMultiPolygon_ = function(str) {
63 | var polygon;
64 | var polygons = goog.string.trim(str).split(
65 | regExes.doubleParenComma);
66 | var parts = [];
67 | for (var i = 0, ii = polygons.length; i < ii; ++i) {
68 | polygon = polygons[i].replace(regExes.trimParens, '$1');
69 | parts.push(parsePolygon_.apply(this, [polygon]).getCoordinates());
70 | }
71 | return new ol.geom.MultiPolygon(parts);
72 | };
73 |
74 | var parseGeometryCollection_ = function(str) {
75 | // separate components of the collection with |
76 | str = str.replace(regExes.geomCollection, '|$1');
77 | var wktArray = goog.string.trim(str).split('|');
78 | var components = [];
79 | for (var i = 0, ii = wktArray.length; i < ii; ++i) {
80 | components.push(parse_.apply(this, [wktArray[i]]));
81 | }
82 | return new ol.geom.GeometryCollection(components);
83 | };
84 |
85 | var parse_ = function(wkt) {
86 | wkt = wkt.replace(regExes.removeNewLine, ' ');
87 | var matches = regExes.typeStr.exec(wkt);
88 | var geometry;
89 | if (matches) {
90 | var type = matches[1].toLowerCase();
91 | var str = matches[2];
92 | switch (type) {
93 | case 'point':
94 | geometry = parsePoint_(str);
95 | break;
96 | case 'multipoint':
97 | geometry = parseMultiPoint_(str);
98 | break;
99 | case 'linestring':
100 | geometry = parseLineString_(str);
101 | break;
102 | case 'multilinestring':
103 | geometry = parseMultiLineString_(str);
104 | break;
105 | case 'polygon':
106 | geometry = parsePolygon_(str);
107 | break;
108 | case 'multipolygon':
109 | geometry = parseMultiPolygon_(str);
110 | break;
111 | case 'geometrycollection':
112 | geometry = parseGeometryCollection_(str);
113 | break;
114 | default:
115 | throw new Error('Bad geometry type: ' + type);
116 | }
117 | }
118 | return geometry;
119 | };
120 |
121 | var read = function(str) {
122 | return parse_(str);
123 | };
124 |
125 | var readFeaturesFromString = function(str) {
126 | var geom = read(str);
127 | var obj = /** @type {ol.parser.ReadFeaturesResult} */
128 | ({});
129 | if (goog.isDef(geom)) {
130 | var feature = new ol.Feature();
131 | feature.setGeometry(geom);
132 | obj.features = [feature];
133 | }
134 | return obj;
135 | };
136 |
137 | var WKT = {
138 | read: function(str) {
139 | return read(str);
140 | }
141 | };
142 |
--------------------------------------------------------------------------------
/src/index-lite.html:
--------------------------------------------------------------------------------
1 | <% if (!(typeof django === 'undefined') && (django)) { %>
2 | {% load staticfiles i18n avatar_tags maploom_tags %}
3 | <% } %>
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | <% styles.forEach( function ( file ) { %>
14 | <% if (!(typeof django === 'undefined') && (django)) { %>
15 |
16 | <% }else{ %>
17 |
18 | <% } %>
19 |
20 | <% }); %>
21 |
22 | <% scripts.forEach( function ( file ) { %>
23 | <% if (!(typeof django === 'undefined') && (django)) { %>
24 |
25 | <% }else{ %>
26 |
27 | <% } %>
28 | <% }); %>
29 |
30 | <% if (!(typeof django === 'undefined') && (django)) { %>
31 | {% get_current_language as language%}
32 |
144 | <% } %>
145 |
146 |
147 |
148 | <% if (!(typeof django === 'undefined') && (django)) { %>
149 | {% verbatim %}
150 | <% } %>
151 |
155 | <% if (!(typeof django === 'undefined') && (django)) { %>
156 | {% endverbatim %}
157 | <% } %>
158 |
159 |
160 |
--------------------------------------------------------------------------------
/src/less/README.md:
--------------------------------------------------------------------------------
1 | # The `src/less` Directory
2 |
3 | This folder is actually fairly self-explanatory: it contains your LESS/CSS files to be compiled during the build.
4 | The only important thing to note is that *only* `main.less` will be processed during the build, meaning that all
5 | other stylesheets must be *imported* into that one.
6 |
7 | This should operate somewhat like the routing; the `main.less` file contains all of the site-wide styles, while
8 | any styles that are route-specific should be imported into here from LESS files kept alongside the JavaScript
9 | and HTML sources of that component. For example, the `home` section of the site has some custom styles, which
10 | are imported like so:
11 |
12 | ```css
13 | @import '../app/home/home.less';
14 | ```
15 |
16 | The same principal, though not demonstrated in the code, would also apply to reusable components. CSS or LESS
17 | files from external components would also be imported. If, for example, we had a Twitter feed directive with
18 | an accompanying template and style, we would similarly import it:
19 |
20 | ```css
21 | @import '../common/twitterFeed/twitterFeedDirective.less';
22 | ```
23 |
24 | Using this decentralized approach for all our code (JavaScript, HTML, and CSS) creates a framework where a
25 | component's directory can be dragged and dropped into *any other project* and it will "just work".
26 |
27 | I would like to eventually automate the importing during the build so that manually importing it here would no
28 | longer be required, but more thought must be put in to whether this is the best approach.
29 |
--------------------------------------------------------------------------------
/src/less/main.less:
--------------------------------------------------------------------------------
1 | /**
2 | * This is the main application stylesheet. It should include or import all
3 | * stylesheets used throughout the application as this is the only stylesheet in
4 | * the Grunt configuration that is automatically processed.
5 | */
6 |
7 | /**
8 | * First, we include the Twitter Bootstrap LESS files. Only the ones used in the
9 | * project should be imported as the rest are just wasting space.
10 | */
11 |
12 | @import '../../vendor/bootstrap/less/bootstrap.less';
13 |
14 |
15 | /**
16 | * This is our main variables file. It in turn imports the `variables` file from
17 | * Twitter Bootstrap. We must include it last so we can overwrite any variable
18 | * definitions in our imported stylesheets.
19 | */
20 |
21 | @import 'variables.less';
22 |
23 |
24 | /**
25 | * Now that all app-wide styles have been applied, we can load the styles for
26 | * all the submodules and components we are using.
27 | *
28 | * TODO: In a later version of this boilerplate, I'd like to automate this.
29 | */
30 |
31 | @import '../common/utils/style/utils.less';
32 | @import '../common/history/style/history.less';
33 | @import '../common/diff/style/diff.less';
34 | @import '../common/notifications/style/notifications.less';
35 | @import '../common/search/style/search.less';
36 | @import '../common/modal/style/modal.less';
37 | @import '../common/addlayers/style/addlayers.less';
38 | @import '../common/arrangeable/style/arrangeable.less';
39 | @import '../common/layers/style/layers.less';
40 | @import '../common/featuremanager/style/featuremanager.less';
41 | @import '../common/sync/style/sync.less';
42 | @import '../common/merge/style/merge.less';
43 | @import '../common/legend/style/legend.less';
44 | @import '../common/tableview/style/tableview.less';
45 | @import '../common/map/style/map.less';
46 | @import '../common/timeline/style/timeline.less';
47 | @import '../common/statistics/style/statistics.less';
48 | @import '../app/app.less';
49 |
50 |
--------------------------------------------------------------------------------
/src/less/variables.less:
--------------------------------------------------------------------------------
1 | /**
2 | * These are the variables used throughout the application. This is where
3 | * overwrites that are not specific to components should be maintained.
4 | */
5 |
6 | @import '../../vendor/bootstrap/less/variables.less';
7 |
8 |
9 | /**
10 | * Typography-related.
11 | */
12 |
13 | @sansFontFamily: 'Roboto', sans-serif;
14 | @mainFont: normal 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
15 | @mainFontColor: #333333;
16 | @featureInfoBoxLabelFont: bold 11px "Helvetica Neue","Helvetica",Arial;
17 | @featureInfoBoxLabelFontColor: dimgray;
18 | @featureInfoBoxValueFont: normal 12px "Helvetica",Arial;
19 | @featureInfoBoxValueFontColor: @mainFontColor;
20 | @addLayerListFontColor: white;
21 | @legendFontColor: white;
22 | @mainHeaderFontColor: white;
23 | @badgeFontColor: white;
24 | @modalHeaderFontColor: white;
25 | @layerHeaderFontColor: white;
26 | @removeLayerFontColor: white;
27 | @syncListFontColor: white;
28 | @syncListButtonFontColor: white;
29 | @formControlFontColor: black;
30 | @controlLabelFontColor: black;
31 | @editFeatureHeaderFontColor: @modalHeaderFontColor;
32 | @exclusiveModeFontColor: @legendFontColor;
33 | @attributeListFontColor: white;
34 | @placeholderLayerFontColor: lightgray;
35 | @saveIcon: "\e175";
36 | @refreshIcon: "\e031";
37 |
38 | /**
39 | * Z-index levels
40 | */
41 | @mapLevel: 0.25;
42 | @menuLevel: 0.5;
43 | @dialogLevel: 0.75;
44 |
45 | /**
46 | * Colors
47 | */
48 | @mainHeaderColor: #155481;
49 | @mainPanelColor: #97B3CD;
50 | @layerHeaderColor: #4977A3;
51 | @badgeColor: #aa5a12;
52 | @legendColor: rgba(100,150,200,0.75);
53 | @legendHeaderColor: rgba(140,190,240,0.75);
54 | @zoomToExtentColor: rgba(0,60,136,0.5);
55 | @loadingBackgroundColor: rgba(151, 175, 201, 0.75);
56 | @legendButtonColor: @zoomToExtentColor;
57 | @zoomToExtentImageColor: white;
58 | @legendButtonImageColor: @zoomToExtentImageColor;
59 | @addColor: #008800;
60 | @deleteColor: #880000;
61 | @modifyColor: #FFD300;
62 | @conflictColor: #aa5a12;
63 | @conflictBadgeColor: #9B1212;
64 | @resolvedBadgeColor: #0a520c;
65 | @featurePanelColor: #90adc7;
66 | @remoteUpColor: #008800;
67 | @remoteDownColor: #880000;
68 | @mergedColor: #224b66;
69 | @removeLayerColor: #d9534f;
70 | @warningHeaderColor: #9f5000;
71 | @warningPanelColor: #e8d0c8;
72 | @errorHeaderColor: #732f13;
73 | @errorPanelColor: #ded9d9;
74 | @syncListColor: #678BAD;
75 | @syncListButtonColor: @layerHeaderColor;
76 | @mainListItemColor: @mainPanelColor;
77 | @notificationHeaderColor: @layerHeaderColor;
78 | @layerVisibleColor: @mainHeaderColor;
79 | @syncOnColor: @mainHeaderColor;
80 | @defaultModalHeaderColor: @mainHeaderColor;
81 | @defaultModalPanelColor: @mainPanelColor;
82 | @addLayerListColor: @layerHeaderColor;
83 | @checkButtonColor: @mainHeaderColor;
84 | @featureInfoBoxColor: white;
85 | @featureInfoBoxListItemColor: rgba(251, 247, 255, 0.32);
86 | @featureInfoBoxListItemHoverColor: lightgrey;
87 | @closeImageColor: white;
88 | @layerVisibleImageColor: white;
89 | @syncOnImageColor: white;
90 | @saveIconColor: darken(@mainHeaderColor, 10%);
91 | @refreshIconColor: white;
92 | @buttonColor: white;
93 | @formControlColor: white;
94 | @editFeatureHeaderColor: @defaultModalHeaderColor;
95 | @editFeaturePanelColor: darken(aliceblue, 2%);
96 | @exclusiveModePanelColor: @legendColor;
97 | @attributeListColor: @layerHeaderColor;
98 | @mergePanelBorder: #4E4B68;
99 | @modifiedAttributeColor: mix(@featurePanelColor, @modifyColor, 80%);
100 | @removedAttributeColor: mix(@featurePanelColor, @deleteColor, 80%);
101 | @addedAttributeColor: mix(@featurePanelColor, @addColor, 80%);
102 | @selectedTableRowColor: #F7D386;
103 | @placeholderLayerColor: #678BAD;
104 |
--------------------------------------------------------------------------------