├── .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 | [![Build Status](https://travis-ci.org/ROGUE-JCTD/MapLoom.svg?branch=master)](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 | 7 |
8 |
9 | 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 | 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 | 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 | 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 |
2 |
3 |
    4 |
  • 5 |
    6 |
    7 |
    8 | 9 |
    10 |
    11 | 12 |
    13 |
    14 | 15 |
    16 |
    17 | 18 |
    19 |
    20 | 22 |
    {{getCommitAuthor(commit)}}
    23 |
    {{getCommitTime(commit)}}
    24 | 25 |
  • 26 |
  • 27 |
    28 |
  • 29 |
  • 30 |
  • 31 |
32 |
33 |
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 | 17 | -------------------------------------------------------------------------------- /src/common/layers/partials/layerinfo.tpl.html: -------------------------------------------------------------------------------- 1 | 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 |
4 |
5 | 6 |
7 |
8 |
9 |
10 |
11 |
12 | 13 |
14 |
15 |
{{layer.get('metadata').title}} 17 |
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 | 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 | 9 | 16 |
-------------------------------------------------------------------------------- /src/common/modal/partials/modal.tpl.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/common/modal/partials/password.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | 6 | 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 | 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 |
10 |
11 | 12 |

13 | {{notification.text}}

14 | {{notification.timestr}} 15 |
17 |
18 |
19 |
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 |
6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 |
14 |
15 |
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 | 51 | 57 | -------------------------------------------------------------------------------- /src/common/sync/partials/syncconfig.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | 64 | 74 | -------------------------------------------------------------------------------- /src/common/sync/partials/synclinks.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | 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 |
2 |
3 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 29 | 31 | 32 | 33 |
34 |
-------------------------------------------------------------------------------- /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 |
2 |
3 | 4 |
5 | 6 |
7 | 8 |
9 |
10 |
11 |
12 |
-------------------------------------------------------------------------------- /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 |
152 |
153 |
154 |
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 | --------------------------------------------------------------------------------