jmacura 4 лет назад
Родитель
Сommit
4e61194f5d

+ 15 - 0
.editorconfig

@@ -0,0 +1,15 @@
+
+# EditorConfig is awesome: https://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Newline ending every file and set default charset
+[*]
+insert_final_newline = true
+charset = utf-8
+
+# 2 space indentation
+[*.{js,ts,html,css}]
+indent_style = space
+indent_size = 2

+ 46 - 5
.eslintrc.json

@@ -1,11 +1,52 @@
 {
-  "extends": "standard",
+  "env": {
+    "browser": true,
+    "es6": true
+  },
+  "extends": [
+    "openlayers",
+    "plugin:@typescript-eslint/eslint-recommended",
+    "plugin:@typescript-eslint/recommended"
+  ],
+  "plugins": ["@typescript-eslint"],
   "globals": {
     "angular": "readonly"
   },
+  "parser": "@typescript-eslint/parser",
+  "parserOptions": {
+    "ecmaVersion":  2018
+  },
   "rules": {
-    "semi": ["error", "always"],
-    "no-var": "error",
-    "prefer-const": "error"
+    "import/extensions": [
+      "error",
+      "ignorePackages",
+      {"js": "never", "ts": "never"}
+    ],
+    "@typescript-eslint/no-empty-function": ["error", {"allow": ["constructors"]}]
+  },
+  "overrides": [
+    {
+      "files": [
+        "**/*.spec.js"
+      ],
+      "env": {
+        "jasmine": true
+      }
+    },
+    {
+      "files": [
+        "**/webpack.*.js"
+      ],
+      "rules": {
+        "@typescript-eslint/no-var-requires": "off"
+      }
+    }
+  ],
+  "settings": {
+    "import/resolver": {
+      "node": {
+        "extensions": [".js", ".ts"]
+      }
+    }
   }
-}
+}

+ 1 - 0
.gitignore

@@ -33,3 +33,4 @@ static
 pidfile
 src/Attractivity.json
 src/Attractivity.csv
+/.vs

+ 1 - 1
README.md

@@ -1,2 +1,2 @@
-# polirural-attractiveness
+# polirural-attractiveness-cz
 

Разница между файлами не показана из-за своего большого размера
+ 624 - 419
package-lock.json


+ 37 - 29
package.json

@@ -1,59 +1,67 @@
 {
-  "name": "polirural-attractiveness",
+  "name": "polirural-attractiveness-cz",
   "version": "0.1.0",
   "description": "Rural attractiveness aplication",
   "main": "build/index.html",
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1",
-    "build": "node node_modules/hslayers-ng/scripts/bootstrap-isolate.js && npx webpack --config ./webpack.prod.js --progress",
-    "build-dev": "node node_modules/hslayers-ng/scripts/bootstrap-isolate.js && npx webpack --config ./webpack.dev.js --progress",
-    "start": "npx http-server ./static -p 8080",
-    "start-dev": "npx webpack-dev-server --config ./webpack.dev.js --watch"
+    "build": "webpack --config ./webpack.prod.js --progress",
+    "build-dev": "webpack --config ./webpack.dev.js --progress",
+    "start": "npx http-server ./build -p 8080",
+    "start-dev": "webpack-dev-server --config ./webpack.dev.js --watch"
   },
   "repository": {
     "type": "git",
-    "url": "https://github.com/jmacura/polirural-attractiveness.git"
+    "url": "https://git.lesprojekt.cz/jmacura/rural-attractiveness-cz"
   },
   "keywords": [
     "rural",
     "attractiveness"
   ],
   "author": "Jan Macura",
-  "contributors": ["Raitis Bērziņš"],
+  "contributors": [
+    "Raitis Bērziņš"
+  ],
   "license": "MIT",
   "dependencies": {
     "csvtojson": "^2.0.10",
-    "hslayers-ng": "^1.16.0",
-    "http-server": "^0.12.1"
+    "hslayers-ng": "^2.5.0",
+    "http-server": "^0.12.3"
   },
   "devDependencies": {
-    "@babel/core": "^7.9.0",
+    "@babel/core": "^7.11.6",
     "@babel/plugin-syntax-dynamic-import": "^7.8.3",
+    "@typescript-eslint/eslint-plugin": "^3.10.0",
+    "@typescript-eslint/parser": "^3.10.0",
     "babel-loader": "^8.1.0",
     "babel-plugin-angularjs-annotate": "^0.10.0",
-    "clean-webpack-plugin": "^2.0.2",
-    "css-loader": "^2.1.1",
-    "eslint": "^6.8.0",
-    "eslint-config-standard": "^14.1.1",
-    "eslint-plugin-import": "^2.20.2",
+    "clean-webpack-plugin": "^3.0.0",
+    "css-loader": "^3.6.0",
+    "eslint": "^7.10.0",
+    "eslint-config-openlayers": "^14.0.0",
+    "eslint-plugin-import": "^2.22.1",
     "eslint-plugin-node": "^11.1.0",
     "eslint-plugin-promise": "^4.2.1",
-    "eslint-plugin-standard": "^4.0.1",
-    "extract-loader": "^3.1.0",
-    "file-loader": "^3.0.1",
-    "html-loader": "^0.5.5",
-    "html-webpack-plugin": "^3.2.0",
-    "mini-css-extract-plugin": "^0.6.0",
+    "extract-loader": "^5.1.0",
+    "file-loader": "^6.1.0",
+    "html-loader": "^1.3.1",
+    "html-webpack-plugin": "^4.5.0",
+    "mini-css-extract-plugin": "^0.11.3",
+    "ng-annotate-loader": "^0.7.0",
     "ng-cache-loader": "0.0.26",
     "npm-run-all": "^4.1.5",
-    "optimize-css-assets-webpack-plugin": "^5.0.3",
-    "postcss": "^7.0.27",
+    "optimize-css-assets-webpack-plugin": "^5.0.4",
+    "postcss": "^8.1.1",
     "postcss-prefix-selector": "^1.7.1",
-    "style-loader": "^0.23.1",
-    "url-loader": "^2.1.0",
-    "webpack": "^4.42.1",
-    "webpack-cli": "^3.3.11",
-    "webpack-dev-server": "^3.10.3",
-    "webpack-merge": "^4.2.2"
+    "sass": "^1.32.7",
+    "sass-loader": "^10.1.1",
+    "style-loader": "^1.3.0",
+    "ts-loader": "^8.0.3",
+    "typescript": "^3.9.8",
+    "url-loader": "^4.1.0",
+    "webpack": "^4.44.2",
+    "webpack-cli": "^3.3.12",
+    "webpack-dev-server": "^3.11.0",
+    "webpack-merge": "^5.1.4"
   }
 }

+ 2 - 0
polyfills.ts

@@ -0,0 +1,2 @@
+(window as any).__Zone_disable_requestAnimationFrame = true;
+(window as any).__Zone_disable_setTimeout = true;

+ 0 - 4
src/adjuster/adjuster-sidebar-btn.directive.html

@@ -1,4 +0,0 @@
-<a href="#" class="sidebar-item list-group-item" ng-if="Core.exists('pra.adjuster')" ng-click="setMainPanel('adjuster')"  title="{{'Adjust factors'|translate}}" ng-class="{active: Core.mainpanel == 'adjuster'}">
-    <span class="menu-icon icon-alertpay"></span>
-    <span class="sidebar-item-title" translate>Adjust factors</span>
-</a>

+ 0 - 16
src/adjuster/adjuster.component.js

@@ -1,16 +0,0 @@
-export default {
-  template: require('./adjuster.directive.html'),
-  controller: ['$scope', 'hs.map.service', 'Core', 'config', 'pra.adjuster.service', 'hs.utils.service',
-    function ($scope, OlMap, Core, config, adjusterService, utils) {
-      $scope.loading = false;
-      $scope.utils = utils;
-
-      angular.extend($scope, {
-        Core,
-        adjusterService
-      });
-
-      $scope.$emit('scope_loaded', 'adjuster');
-    }
-  ]
-};

+ 61 - 0
src/adjuster/adjuster.component.ts

@@ -0,0 +1,61 @@
+import {Component, ViewRef} from '@angular/core';
+
+import {HsLayoutService} from 'hslayers-ng/components/layout/layout.service';
+import {HsPanelComponent} from 'hslayers-ng/components/layout/panels/panel-component.interface';
+
+//import {AdjusterEventService} from './adjuster-event.service';
+import {AdjusterService} from './adjuster.service';
+
+@Component({
+  selector: 'pra-adjuster',
+  template: require('./adjuster.directive.html'),
+})
+export class AdjusterComponent implements HsPanelComponent {
+  data: any;
+  descriptionVisible: boolean;
+  errorMsg: string;
+  viewRef: ViewRef;
+
+  constructor(
+    public adjusterService: AdjusterService,
+    //public adjusterEventService: AdjusterEventService,
+    public hsLayoutService: HsLayoutService
+  ) {
+    this.descriptionVisible = false;
+    /*this.adjusterEventService.clustersLoaded.subscribe(({success, err}) => {
+      if (!success) {
+        this.errorMsg = err.message;
+      }
+    });*/
+  }
+
+  ngOnInit(): void {
+    //this.adjusterService.init();
+  }
+
+  isVisible(): boolean {
+    return this.hsLayoutService.panelVisible('adjuster');
+  }
+
+  /*noDataSelected(): boolean {
+    if (this.adjusterService.factors.length === 0) {
+      return true;
+    }
+    let datasetsEffectivelyTurnedOn = [];
+    for (const factor of this.adjusterService.factors) {
+      if (factor.weight === 0) {
+        continue;
+      }
+      datasetsEffectivelyTurnedOn = [
+        ...datasetsEffectivelyTurnedOn,
+        ...factor.datasets.filter((ds) => ds.included),
+      ];
+    }
+    return datasetsEffectivelyTurnedOn.length === 0;
+  }*/
+
+  /*selectMethod(): void {
+    this.adjusterService.method = this.method;
+    this.adjusterEventService.methodChanged.next(this.method);
+  }*/
+}

+ 3 - 3
src/adjuster/adjuster.directive.html

@@ -1,5 +1,5 @@
-<div class="card card-default mainpanel">
-    <hs.layout.panel-header panel-name="adjuster" panel-title="'Adjust factors'||translate"></hs.layout.panel-header>
+<div [hidden]="!isVisible()" class="card mainpanel">
+    <hs-panel-header panel-name="adjuster" panel-title="'Adjust factors'||translate"></hs-panel-header>
     <style>
         .weather-table>tbody>tr>td,
         .table-condensed>tbody>tr>th,
@@ -18,7 +18,7 @@
                             <label>{{factor.factor}}</label>
                         </div>
                         <div class="p-2">{{(factor.weight * 100).toFixed(0)}}%</div>
-                    </div>                   
+                    </div>
                     <input type="range" class="custom-range" ng-change="adjusterService.apply()" ng-model="factor.weight" min="0" max="1.0" step="0.05">
                 </div>
         </div>

+ 0 - 13
src/adjuster/adjuster.module.js

@@ -1,13 +0,0 @@
-import adjusterComponent from './adjuster.component';
-import adjusterService from './adjuster.service';
-
-angular.module('pra.adjuster', ['hs.core', 'hs.map'])
-  .directive('pra.adjuster.sidebarBtn', function () {
-    return {
-      template: require('./adjuster-sidebar-btn.directive.html')
-    };
-  })
-
-  .service('pra.adjuster.service', adjusterService)
-
-  .component('pra.adjuster', adjusterComponent);

+ 21 - 0
src/adjuster/adjuster.module.ts

@@ -0,0 +1,21 @@
+import {CUSTOM_ELEMENTS_SCHEMA, NgModule} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {FormsModule} from '@angular/forms';
+import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
+
+import {HsPanelHelpersModule} from 'hslayers-ng/components/layout/panels/panel-helpers.module';
+
+import {AdjusterComponent} from './adjuster.component';
+//import {AdjusterEventService} from './adjuster-event.service';
+//import {AdjusterLoaderComponent} from './adjuster-loader.component';
+import {AdjusterService} from './adjuster.service';
+
+@NgModule({
+  schemas: [CUSTOM_ELEMENTS_SCHEMA],
+  imports: [CommonModule, FormsModule, HsPanelHelpersModule, NgbModule],
+  exports: [AdjusterComponent],
+  declarations: [AdjusterComponent, /*AdjusterLoaderComponent*/],
+  entryComponents: [AdjusterComponent, /*AdjusterLoaderComponent*/],
+  providers: [AdjusterService, /*AdjusterEventService*/],
+})
+export class AdjusterModule {}

+ 0 - 101
src/adjuster/adjuster.service.js

@@ -1,101 +0,0 @@
-// import attractivity from '../Attractivity.json';
-import nuts from '../nuts';
-
-export default ['Core', 'hs.utils.service', '$rootScope', '$http',
-  function (Core, utils, $rootScope, $http) {
-    const me = {
-      factors: [
-        {
-          name: 'Natural',
-          column: 'N_index',
-          weight: 1
-        },
-        {
-          name: 'Social & Human',
-          column: 'S_index',
-          weight: 1
-        },
-        {
-          name: 'Anthropic',
-          column: 'A_index',
-          weight: 1
-        },
-        {
-          name: 'Economical',
-          column: 'E_index',
-          weight: 1
-        },
-        {
-          name: 'Cultural',
-          column: 'C_index',
-          weight: 1
-        },
-        {
-          name: 'Institutional',
-          column: 'I_index',
-          weight: 1
-        }
-      ],
-      apply () {
-        utils.debounce(function () {
-          $http({
-            method: 'post',
-            url: 'https://publish.lesprojekt.cz/nodejs/scores',
-            data: {
-              factors: me.factors.map(f => {
-                return {
-                  factor: f.factor,
-                  weight: f.weight,
-                  datasets: f.datasets.filter(ds => ds.included).map(ds => ds.name)
-                };
-              })
-            }
-          })
-            .then(response => {
-              me.attractivity = response.data;
-              let max = 0;
-              me.attractivity.forEach(a => {
-                if (a.aggregate > max) max = a.aggregate;
-              });
-              const normalizer = 1 / max;
-              me.attractivity.forEach(a => {
-                a.aggregate *= normalizer;
-              });
-              me.attractivity.forEach(a => {
-                me.nutsCodeRecordRelations[a.code] = a;
-              });
-              nuts.nuts3Source.forEachFeature(feature => {
-                feature.set('total', me.nutsCodeRecordRelations[feature.get('NUTS_ID')].aggregate);
-                feature.set('totalForHumans', (me.nutsCodeRecordRelations[feature.get('NUTS_ID')].aggregate * 100).toFixed(2));
-                me.factors.forEach(factor => {
-                  feature.set(factor.factor, (me.nutsCodeRecordRelations[feature.get('NUTS_ID')][factor.factor] * 100).toFixed(2));
-                });
-              });
-            });
-          /*
-
-            */ }, 300)();
-      }
-    };
-    me.nutsCodeRecordRelations = {};
-
-    $http({ url: 'https://publish.lesprojekt.cz/nodejs/datasets' })
-      .then(response => {
-        me.factors = response.data.map(dataset => { return { factor: dataset.Factor, weight: 1, datasets: [] }; });
-        me.factors = utils.removeDuplicates(me.factors, 'factor');
-        me.factors.forEach(factor => {
-          factor.datasets = response.data
-            .filter(ds => ds.Factor === factor.factor)
-            .map(ds => {
-              return {
-                name: ds.Name,
-                included: true
-              };
-            });
-        });
-        me.apply();
-      });
-
-    return me;
-  }
-];

+ 129 - 0
src/adjuster/adjuster.service.ts

@@ -0,0 +1,129 @@
+import {HttpClient} from '@angular/common/http';
+import {Injectable} from '@angular/core';
+
+import {HsUtilsService} from 'hslayers-ng/components/utils/utils.service';
+
+// import attractivity from '../Attractivity.json';
+import nuts from '../nuts';
+
+@Injectable({providedIn: 'root'})
+export class AdjusterService {
+  nutsCodeRecordRelations = {};
+  factors: any = [
+    {
+      name: 'Natural',
+      column: 'N_index',
+      weight: 1,
+    },
+    {
+      name: 'Social & Human',
+      column: 'S_index',
+      weight: 1,
+    },
+    {
+      name: 'Anthropic',
+      column: 'A_index',
+      weight: 1,
+    },
+    {
+      name: 'Economical',
+      column: 'E_index',
+      weight: 1,
+    },
+    {
+      name: 'Cultural',
+      column: 'C_index',
+      weight: 1,
+    },
+    {
+      name: 'Institutional',
+      column: 'I_index',
+      weight: 1,
+    },
+  ];
+
+  constructor(public HsUtilsService: HsUtilsService, public $http: HttpClient) {
+    this.$http
+      .get('https://publish.lesprojekt.cz/nodejs/datasets')
+      .toPromise()
+      .then((response: any) => {
+        this.factors = response.data.map((dataset) => {
+          return {factor: dataset.Factor, weight: 1, datasets: []};
+        });
+        this.factors = HsUtilsService.removeDuplicates(this.factors, 'factor');
+        this.factors.forEach((factor) => {
+          factor.datasets = response.data
+            .filter((ds) => ds.Factor === factor.factor)
+            .map((ds) => {
+              return {
+                name: ds.Name,
+                included: true,
+              };
+            });
+        });
+        this.apply();
+      });
+  }
+
+  apply() {
+    this.HsUtilsService.debounce(
+      function () {
+        this.$http
+          .post('https://publish.lesprojekt.cz/nodejs/scores', {
+            factors: this.factors.map((f) => {
+              return {
+                factor: f.factor,
+                weight: f.weight,
+                datasets: f.datasets
+                  .filter((ds) => ds.included)
+                  .map((ds) => ds.name),
+              };
+            }),
+          })
+          .toPromise()
+          .then((response: any) => {
+            this.attractivity = response.data;
+            let max = 0;
+            this.attractivity.forEach((a) => {
+              if (a.aggregate > max) {
+                max = a.aggregate;
+              }
+            });
+            const normalizer = 1 / max;
+            this.attractivity.forEach((a) => {
+              a.aggregate *= normalizer;
+            });
+            this.attractivity.forEach((a) => {
+              this.nutsCodeRecordRelations[a.code] = a;
+            });
+            nuts.nuts3Source.forEachFeature((feature) => {
+              feature.set(
+                'total',
+                this.nutsCodeRecordRelations[feature.get('NUTS_ID')].aggregate
+              );
+              feature.set(
+                'totalForHumans',
+                (
+                  this.nutsCodeRecordRelations[feature.get('NUTS_ID')]
+                    .aggregate * 100
+                ).toFixed(2)
+              );
+              this.factors.forEach((factor) => {
+                feature.set(
+                  factor.factor,
+                  (
+                    this.nutsCodeRecordRelations[feature.get('NUTS_ID')][
+                      factor.factor
+                    ] * 100
+                  ).toFixed(2)
+                );
+              });
+            });
+          });
+      },
+      300,
+      false,
+      this
+    )();
+  }
+}

+ 23 - 0
src/adjuster/index.ts

@@ -0,0 +1,23 @@
+import * as angular from 'angular';
+import {downgrade} from 'hslayers-ng/common/downgrader';
+import {downgradeComponent, downgradeInjectable} from '@angular/upgrade/static';
+
+import {AdjusterComponent} from './adjuster.component';
+//import {AdjusterLoaderComponent} from './adjuster-loader.component';
+import {AdjusterModule} from './adjuster.module';
+import {AdjusterService} from './adjuster.service';
+
+export const downgradedModule = downgrade(AdjusterModule);
+
+angular
+  .module(downgradedModule, ['hs.core', 'hs.map'])
+  .service('AdjusterService', downgradeInjectable(AdjusterService))
+  .directive('praAdjuster', downgradeComponent({component: AdjusterComponent}));
+/*.directive(
+    'praAdjusterLoader',
+    downgradeComponent({component: AdjusterLoaderComponent})
+  );*/
+
+angular.module('pra.adjuster', [downgradedModule]);
+
+export {AdjusterModule} from './adjuster.module';

+ 21 - 0
src/app-js.ts

@@ -0,0 +1,21 @@
+'use strict';
+import 'hslayers-ng/components/core';
+// hslayers-ng components must be loaded first, otherwise angular will be undefined
+// eslint-disable-next-line sort-imports-es6-autofix/sort-imports-es6
+import * as angular from 'angular';
+import {downgradeInjectable} from '@angular/upgrade/static';
+
+import './adjuster/';
+import {AppComponent} from './app.component';
+import {AppConfig} from './app.config';
+import {AppService} from './app.service';
+
+export default angular
+  .module('hs', [
+    'hs.core',
+    //'getteext',
+    'pra.adjuster',
+  ])
+  .value('config', AppConfig)
+  .component('hs', AppComponent)
+  .service('AppService', downgradeInjectable(AppService));

+ 19 - 0
src/app.component.ts

@@ -0,0 +1,19 @@
+export const AppComponent = {
+  template: (HsCore) => {
+    'ngInject';
+    return HsCore.hslayersNgTemplate;
+  },
+  bindings: {},
+  controllerAs: 'vm',
+  controller: class AppComponent {
+    constructor(HsLayoutService, AdjusterService, AppService) {
+      'ngInject';
+      const vm = this;
+      //$scope.Core = Core;
+      //$scope.panelVisible = layoutService.panelVisible;
+      //layoutService.sidebarRight = false;
+      // layoutService.sidebarToggleable = false;
+      //Core.singleDatasources = true;
+    }
+  },
+};

+ 130 - 0
src/app.config.ts

@@ -0,0 +1,130 @@
+import VectorLayer from 'ol/layer/Vector';
+import View from 'ol/View';
+import {Fill, Stroke, Style} from 'ol/style';
+import {OSM} from 'ol/source';
+import {Tile} from 'ol/layer';
+
+import nuts from './nuts';
+
+function getHostname() {
+  const url = window.location.href;
+  const urlArr = url.split('/');
+  const domain = urlArr[2];
+  return urlArr[0] + '//' + domain;
+}
+
+const stroke = new Stroke({
+  color: '#3399CC',
+  width: 0.25,
+});
+
+function perc2color(perc) {
+  perc = perc * 100;
+  let r;
+  let g;
+  const b = 0;
+  if (perc < 50) {
+    r = 255;
+    g = Math.round(5.1 * perc);
+  } else {
+    g = 255;
+    r = Math.round(510 - 5.1 * perc);
+  }
+  // eslint-disable-next-line no-unused-vars
+  const h = r * 0x10000 + g * 0x100 + b * 0x1;
+  return `rgba(${r}, ${g}, ${b}, 0.7)`;
+}
+
+const styles = function (feature) {
+  if (isNaN(feature.get('total'))) {
+    return [
+      new Style({
+        fill: new Fill({
+          color: '#FFF',
+        }),
+        stroke: stroke,
+      }),
+    ];
+  } else {
+    return [
+      new Style({
+        fill: new Fill({
+          color: perc2color(feature.get('total')),
+        }),
+        stroke: stroke,
+      }),
+    ];
+  }
+};
+
+const nuts2Layer = new VectorLayer({
+  source: nuts.nuts2Source,
+  visible: false,
+  style: styles,
+  title: 'NUTS2 regions',
+});
+
+const nuts3Layer = new VectorLayer({
+  source: nuts.nuts3Source,
+  visible: true,
+  style: styles,
+  title: 'NUTS3 regions',
+});
+nuts3Layer.set('hoveredKeys', [
+  'NUTS_NAME',
+  'totalForHumans',
+  'Social & Human',
+  'Anthropic',
+  'Institutional',
+  'Economical',
+  'Natural',
+  'Cultural',
+]);
+nuts3Layer.set('hoveredKeysTranslations', {
+  NUTS_NAME: 'Name',
+  totalForHumans: 'Calculated score',
+});
+
+export const AppConfig = {
+  proxyPrefix: '../8085/',
+  default_layers: [
+    new Tile({
+      source: new OSM(),
+      title: 'Open street map',
+      base: true,
+      editor: {editable: false},
+      removable: false,
+    }),
+    nuts2Layer,
+    nuts3Layer,
+  ],
+  project_name: 'erra/map',
+  default_view: new View({
+    center: [2433348.3022471312, 7744501.813885343],
+    zoom: 3.6,
+    units: 'm',
+  }),
+  advanced_form: true,
+  datasources: [],
+  hostname: {
+    default: {
+      title: 'Default',
+      type: 'default',
+      editable: false,
+      url: getHostname(),
+    },
+  },
+  panelWidths: {},
+  panelsEnabled: {
+    language: false,
+    composition_browser: false,
+    legend: false,
+    ows: false,
+    info: false,
+    saveMap: false,
+    draw: false,
+  },
+  searchProvider: (q) => {
+    return `/app/jupyter-test/8085/search/?q=${q}`;
+  },
+};

+ 0 - 174
src/app.js

@@ -1,174 +0,0 @@
-'use strict';
-import 'toolbar.module';
-import 'print.module';
-import 'query.module';
-import 'search.module';
-import 'add-layers.module';
-import 'measure.module';
-import 'permalink.module';
-import 'info.module';
-import 'datasource-selector.module';
-import 'sidebar.module';
-import 'draw.module';
-import View from 'ol/View';
-import { Tile } from 'ol/layer';
-import { OSM } from 'ol/source';
-import { Style, Stroke, Fill } from 'ol/style';
-import VectorLayer from 'ol/layer/Vector';
-import './adjuster/adjuster.module';
-import nuts from './nuts';
-
-const module = angular.module('hs', [
-  'hs.sidebar',
-  'hs.draw',
-  'hs.info',
-  'hs.toolbar',
-  'hs.layermanager',
-  'hs.query',
-  'hs.search', 'hs.print', 'hs.permalink',
-  'hs.geolocation',
-  'hs.datasource_selector',
-  'hs.save-map',
-  'hs.measure',
-  'hs.addLayers',
-  'pra.adjuster'
-]);
-
-module.directive('hs', ['config', 'Core', function (config, Core) {
-  return {
-    template: Core.hslayersNgTemplate,
-    link: function (scope, element) {
-      Core.fullScreenMap(element);
-    }
-  };
-}]);
-
-function getHostname () {
-  const url = window.location.href;
-  const urlArr = url.split('/');
-  const domain = urlArr[2];
-  return urlArr[0] + '//' + domain;
-};
-
-const stroke = new Stroke({
-  color: '#3399CC',
-  width: 0.25
-});
-
-function perc2color (perc) {
-  perc = perc * 100;
-  let r; let g; const b = 0;
-  if (perc < 50) {
-    r = 255;
-    g = Math.round(5.1 * perc);
-  } else {
-    g = 255;
-    r = Math.round(510 - 5.10 * perc);
-  }
-  // eslint-disable-next-line no-unused-vars
-  const h = r * 0x10000 + g * 0x100 + b * 0x1;
-  return `rgba(${r}, ${g}, ${b}, 0.7)`;
-}
-
-const styles = function (feature) {
-  if (isNaN(feature.get('total'))) {
-    return [new Style({
-      fill: new Fill({
-        color: '#FFF'
-      }),
-      stroke: stroke
-    })];
-  } else {
-    return [new Style({
-      fill: new Fill({
-        color: perc2color(feature.get('total'))
-      }),
-      stroke: stroke
-    })];
-  }
-};
-
-const nuts2Layer = new VectorLayer({
-  source: nuts.nuts2Source,
-  visible: false,
-  style: styles,
-  title: 'NUTS2 regions'
-});
-
-const nuts3Layer = new VectorLayer({
-  source: nuts.nuts3Source,
-  visible: true,
-  style: styles,
-  title: 'NUTS3 regions'
-});
-nuts3Layer.set('hoveredKeys', ['NUTS_NAME', 'totalForHumans', 'Social & Human', 'Anthropic', 'Institutional', 'Economical', 'Natural', 'Cultural']);
-nuts3Layer.set('hoveredKeysTranslations', { NUTS_NAME: 'Name', totalForHumans: 'Calculated score' });
-
-module.value('config', {
-  proxyPrefix: '../8085/',
-  default_layers: [
-    new Tile({
-      source: new OSM(),
-      title: 'Open street map',
-      base: true,
-      editor: { editable: false },
-      removable: false
-    }),
-    nuts2Layer,
-    nuts3Layer
-  ],
-  project_name: 'erra/map',
-  default_view: new View({
-    center: [2433348.3022471312, 7744501.813885343],
-    zoom: 3.6,
-    units: 'm'
-  }),
-  advanced_form: true,
-  datasources: [],
-  hostname: {
-    default: {
-      title: 'Default',
-      type: 'default',
-      editable: false,
-      url: getHostname()
-    }
-  },
-  panelWidths: {
-  },
-  panelsEnabled: {
-    language: false,
-    composition_browser: false,
-    legend: false,
-    ows: false,
-    info: false,
-    saveMap: false,
-    draw: false
-  },
-  searchProvider: (q) => {
-    return `/app/jupyter-test/8085/search/?q=${q}`;
-  }
-});
-
-module.controller('Main', ['$scope', 'Core', '$compile', 'hs.layout.service', 'pra.adjuster.service',
-  function ($scope, Core, $compile, layoutService, adjusterService) {
-    $scope.Core = Core;
-    $scope.panelVisible = layoutService.panelVisible;
-    layoutService.sidebarRight = false;
-    // layoutService.sidebarToggleable = false;
-    Core.singleDatasources = true;
-    layoutService.sidebarButtons = true;
-    layoutService.setDefaultPanel('adjuster');
-    $scope.$on('scope_loaded', function (event, args) {
-      // eslint-disable-next-line eqeqeq
-      if (args == 'Sidebar') {
-        const el = angular.element('<pra.adjuster hs.draggable ng-if="Core.exists(\'pra.adjuster\')" ng-show="panelVisible(\'adjuster\', this)"></pra.adjuster>')[0];
-        layoutService.panelListElement.appendChild(el);
-        $compile(el)($scope);
-
-        const toolbarButton = angular.element('<div pra.adjuster.sidebar-btn></div>')[0];
-        layoutService.sidebarListElement.appendChild(toolbarButton);
-        $compile(toolbarButton)(event.targetScope);
-      }
-    });
-  }
-]);

+ 49 - 0
src/app.module.ts

@@ -0,0 +1,49 @@
+import 'core-js/es6/reflect';
+import 'core-js/es7/reflect';
+import 'reflect-metadata';
+import 'zone.js';
+import app from './app-js';
+import {
+  APP_BOOTSTRAP_LISTENER,
+  ApplicationRef,
+  ComponentRef,
+  NgModule,
+} from '@angular/core';
+import {BrowserModule} from '@angular/platform-browser';
+import {UpgradeModule} from '@angular/upgrade/static';
+
+import {BootstrapComponent} from 'hslayers-ng/bootstrap.component';
+import {HsCoreModule} from 'hslayers-ng/components/core/core.module';
+
+import {AdjusterModule} from './adjuster';
+import {AppService} from './app.service';
+
+@NgModule({
+  imports: [BrowserModule, UpgradeModule, HsCoreModule, AdjusterModule],
+  exports: [],
+  declarations: [],
+  providers: [
+    {
+      provide: APP_BOOTSTRAP_LISTENER,
+      multi: true,
+      useFactory: () => {
+        return (component: ComponentRef<BootstrapComponent>) => {
+          //When ng9 part is bootstrapped continue with AngularJs modules
+          component.instance.upgrade.bootstrap(
+            document.documentElement,
+            [app.name],
+            {strictDi: true}
+          );
+        };
+      },
+    },
+    AppService,
+  ],
+})
+export class AppModule {
+  constructor() {}
+  ngDoBootstrap(appRef: ApplicationRef): void {
+    //First bootstrap Angular 9 app part on hs element
+    appRef.bootstrap(BootstrapComponent);
+  }
+}

+ 31 - 0
src/app.service.ts

@@ -0,0 +1,31 @@
+import {Injectable} from '@angular/core';
+
+import {HsLayoutService} from 'hslayers-ng/components/layout/layout.service';
+import {HsPanelContainerService} from 'hslayers-ng/components/layout/panels/panel-container.service';
+import {HsSidebarService} from 'hslayers-ng/components/sidebar/sidebar.service';
+
+import {AdjusterComponent} from './adjuster/adjuster.component';
+
+@Injectable({providedIn: 'root'})
+export class AppService {
+  constructor(
+    public hsLayoutService: HsLayoutService,
+    public hsPanelContainerService: HsPanelContainerService,
+    public hsSidebarService: HsSidebarService
+  ) {
+    this.init();
+  }
+
+  init(): void {
+    this.hsSidebarService.buttons.push({
+      panel: 'adjuster',
+      module: 'pra.adjuster',
+      order: 0,
+      title: 'Adjust factors',
+      description: 'Adjust factors for computation',
+      icon: 'icon-analytics-piechart',
+    });
+    this.hsPanelContainerService.create(AdjusterComponent, {});
+    this.hsLayoutService.setDefaultPanel('adjuster');
+  }
+}

BIN
src/images/cropped-favicon-32x32.png


+ 11 - 8
src/index.html

@@ -5,16 +5,19 @@
   <title>Rural attractiveness</title>
   <meta name="description" content="Rural attractiveness calculation and visualization application developed in PoliRural project. 2020">
   <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+  <link href="<%= htmlWebpackPlugin.files.favicon %>" rel="icon">
   <meta name="apple-mobile-web-app-capable" content="yes" />
-  <meta name="apple-mobile-web-app-status-bar-style" content="black"/>
+  <meta name="apple-mobile-web-app-status-bar-style" content="black" />
   <% for (var css in htmlWebpackPlugin.files.css) { %>
     <link href="<%= htmlWebpackPlugin.files.css[css] %>" rel="stylesheet">
-    <% } %>
+  <% } %>
 </head>
-<body >
-    <div hs ng-app="hs" ng-controller="Main" style="height: 100vh;"></div>
-    <% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
-      <script type="application/javascript" src="<%= htmlWebpackPlugin.files.chunks[chunk].entry %>"></script>
-    <% } %>
+
+<body>
+  <hs id="hs-app" ng-strict-di></hs>
+  <% for (var js in htmlWebpackPlugin.files.js) { %>
+    <script type="application/javascript" src="<%= htmlWebpackPlugin.files.js[js] %>"></script>
+  <% } %>
 </body>
-</html>
+
+</html>

+ 13 - 0
src/main.ts

@@ -0,0 +1,13 @@
+import 'core-js/es6/reflect';
+import 'core-js/es7/reflect';
+import 'reflect-metadata';
+(window as any).__Zone_disable_requestAnimationFrame = true;
+(window as any).__Zone_disable_setTimeout = true;
+import 'zone.js';
+import * as angular from 'angular';
+import {AppModule} from './app.module';
+import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
+import {setAngularJSGlobal} from '@angular/upgrade/static';
+
+setAngularJSGlobal(angular);
+platformBrowserDynamic().bootstrapModule(AppModule);

+ 12 - 0
tsconfig.json

@@ -0,0 +1,12 @@
+{
+  "compilerOptions": {
+      "experimentalDecorators": true,
+      "allowJs": false,
+      "target": "es5",
+      "emitDecoratorMetadata": true,
+      "lib": ["esnext", "dom"],
+      "sourceMap": true,
+      "esModuleInterop": true,
+      "resolveJsonModule": true,
+  }
+}

+ 58 - 21
webpack.common.js

@@ -9,27 +9,26 @@
  * - Allow to automatically generates the dependencies injection for angularJS components annotated with
  *   `'ngInject';` or `@ngInject` in comments. See https://docs.angularjs.org/guide/di
  */
+const fs = require('fs');
 const path = require('path');
-const CleanWebpackPlugin = require('clean-webpack-plugin');
+const {CleanWebpackPlugin} = require('clean-webpack-plugin');
 const HtmlWebpackPlugin = require('html-webpack-plugin');
-const hslPaths = require(path.join(__dirname, './node_modules/hslayers-ng/common_paths'));
 
 module.exports = {
-  entry: { main: './src/app.js' },
+  entry: {main: './src/main.ts'},
   output: {
-    // Path where bundled files will be output
-    path: path.resolve(__dirname, './static'),
     // Path at which output assets will be served
-    publicPath: ''
+    publicPath: '',
   },
   // Just for build speed improvement
   resolve: {
+    extensions: ['.ts', '.js'],
     symlinks: true,
     modules: [
-      path.join(__dirname),
-      path.join(__dirname, './node_modules'),
-      path.resolve(path.join(__dirname, './node_modules', 'hslayers-ng'))
-    ].concat(hslPaths.paths)
+      __dirname,
+      path.resolve(path.join(__dirname, './node_modules')),
+      path.resolve(path.join(__dirname, './node_modules', 'hslayers-ng')),
+    ],
   },
   plugins: [
     // Clean before build
@@ -39,29 +38,67 @@ module.exports = {
       filename: 'index.html',
       template: './src/index.html',
       // We manually inject css and js files in our template
-      inject: false
-      // favicon: 'assets/img/favicon.ico'
-    })
+      inject: false,
+      favicon: './src/images/cropped-favicon-32x32.png',
+    }),
   ],
   module: {
     rules: [
+      {
+        test: /\.ts$/,
+        use: [
+          {loader: 'ng-annotate-loader'},
+          {loader: 'ts-loader', options: {allowTsInNodeModules: true}},
+        ],
+        exclude: /node_modules\/(?!(hslayers-ng)\/).*/,
+      },
       // Automatically generates $inject array for angularJS components annotated with:
       // 'ngInject';
       // or commented with /**@ngInject */
       {
         test: /\.js$/,
-        exclude: /node_modules/,
+        exclude: /node_modules\/(?!(hslayers-ng)\/).*/,
         use: [
           {
             loader: 'babel-loader',
             options: {
               // Babel syntax dynamic import plugin allow babel to correctly parse js files
               // using webpack dynamic import expression (i.e import('angular').then(...))
-              plugins: ['angularjs-annotate', '@babel/plugin-syntax-dynamic-import']
-            }
-          }
-        ]
-      }
-    ]
-  }
+              plugins: [
+                'angularjs-annotate',
+                '@babel/plugin-syntax-dynamic-import',
+              ],
+            },
+          },
+        ],
+      },
+      {
+        test: /\.scss$/,
+        use: [
+          'style-loader',
+          'css-loader',
+          {
+            loader: 'sass-loader',
+            options: {
+              additionalData: fs.existsSync('./src/custom.scss')
+                ? `@use "src/custom.scss" as *;`
+                : '',
+            },
+          },
+        ],
+      },
+      // Load data files
+      {
+        test: /\.geojson$/,
+        include: path.resolve(__dirname, 'src/data'),
+        use: {
+          loader: 'file-loader',
+          options: {
+            name: '[name].[ext]',
+            outputPath: 'data',
+          },
+        },
+      },
+    ],
+  },
 };

+ 30 - 30
webpack.dev.js

@@ -7,7 +7,7 @@
  * - Allow Load css files (import './myCssFile.css') -> Css rules will be automatically added to index.html into a <style></style> tag.
  * - Allow to load fonts and images (import './myFont.eot'; import './someImage.jpg')
  */
-const merge = require('webpack-merge');
+const {merge} = require('webpack-merge');
 const common = require('./webpack.common');
 const path = require('path');
 const env = process.env;
@@ -17,55 +17,55 @@ module.exports = merge(common, {
   devtool: 'cheap-eval-source-map',
   watchOptions: {
     aggregateTimeout: 300,
-    poll: 1000
+    poll: 1000,
   },
   resolve: {
-    symlinks: true
+    symlinks: true,
   },
   optimization: {
     // see https://webpack.js.org/guides/build-performance#avoid-extra-optimization-steps
     removeAvailableModules: false,
     removeEmptyChunks: false,
     // In dev mode we simply want to get a big bundle containing all our js
-    splitChunks: false
+    splitChunks: false,
   },
   output: {
     // see https://webpack.js.org/guides/build-performance#output-without-path-info
+    // Path where bundled files will be output
+    path: path.resolve(__dirname, './static'),
     pathinfo: false,
-    filename: '[name].bundle.js'
+    filename: '[name].bundle.js',
   },
   devServer: {
-    contentBase: './static',
+    contentBase: path.resolve(__dirname, './static'),
     hot: false,
     host: '0.0.0.0',
-    port: env.HTTP_PORT || 8082
+    port: env.HTTP_PORT || 8080,
   },
   module: {
     rules: [
       // Load css files which will be injected in html page at startup <style>...</style>)
       {
         test: /\.css$/,
-        use: ['style-loader', 'css-loader']
+        use: ['style-loader', 'css-loader'],
       },
       {
         test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
-        use: [{
-          loader: 'file-loader',
-          options: {
-            name: '[name].[ext]',
-            outputPath: 'fonts/'
-          }
-        }]
+        use: [
+          {
+            loader: 'file-loader',
+            options: {
+              name: '[name].[ext]',
+              outputPath: 'fonts/',
+            },
+          },
+        ],
       },
       // Load angularJS partials HTML file as URL
       {
         test: /\.html$/,
         exclude: path.resolve(__dirname, './src/index.html'),
-        use: [
-          'ng-cache-loader?prefix=[dir]/[dir]',
-          'extract-loader',
-          'html-loader'
-        ]
+        use: ['html-loader'],
       },
       // Load images as URLs
       {
@@ -74,9 +74,9 @@ module.exports = merge(common, {
           loader: 'file-loader',
           options: {
             name: '[name].[ext]',
-            outputPath: 'images'
-          }
-        }
+            outputPath: 'images',
+          },
+        },
       },
       // Load locales files
       {
@@ -88,11 +88,11 @@ module.exports = merge(common, {
             loader: 'file-loader',
             options: {
               name: '[name].[ext]',
-              outputPath: 'locales'
-            }
-          }
-        ]
-      }
-    ]
-  }
+              outputPath: 'locales',
+            },
+          },
+        ],
+      },
+    ],
+  },
 });

+ 31 - 33
webpack.prod.js

@@ -12,7 +12,7 @@
  * - Allow to load html angularjs partials (i.e all html files under src folder) as url ->
  *
  */
-const merge = require('webpack-merge');
+const {merge} = require('webpack-merge');
 const common = require('./webpack.common');
 const path = require('path');
 const webpack = require('webpack');
@@ -29,19 +29,19 @@ module.exports = merge(common, {
     // Path where bundled files will be output
     path: path.resolve(__dirname, './build'),
     // Path at which output assets will be served
-    publicPath: ''
+    publicPath: '',
   },
   resolve: {
-    symlinks: true
+    symlinks: true,
   },
   plugins: [
     // Extract CSS into separated css files
     new MiniCssExtractPlugin({
       // Add a chunkhash to file name so it will not be cached by browsers when content changed
-      filename: '[name].[hash].bundle.css'
+      filename: '[name].[hash].bundle.css',
     }),
     // see https://webpack.js.org/guides/caching#module-identifiers
-    new webpack.HashedModuleIdsPlugin()
+    new webpack.HashedModuleIdsPlugin(),
   ],
   optimization: {
     // See https://webpack.js.org/guides/caching
@@ -52,9 +52,9 @@ module.exports = merge(common, {
         vendor: {
           name: 'vendors',
           test: /node_modules/,
-          chunks: 'initial'
-        }
-      }
+          chunks: 'initial',
+        },
+      },
     },
     minimizer: [
       // JS minifier/uglifier
@@ -62,15 +62,15 @@ module.exports = merge(common, {
         parallel: true,
         sourceMap: true,
         // Remove comments as well
-        terserOptions: { output: { comments: false } }
+        terserOptions: {output: {comments: false}},
       }),
       // CSS minifier
       new OptimizeCSSAssetsPlugin({
         cssProcessorPluginOptions: {
-          preset: ['default', { discardComments: { removeAll: true } }]
-        }
-      })
-    ]
+          preset: ['default', {discardComments: {removeAll: true}}],
+        },
+      }),
+    ],
   },
   module: {
     rules: [
@@ -81,23 +81,23 @@ module.exports = merge(common, {
           'style-loader',
           {
             loader: MiniCssExtractPlugin.loader,
-            options: { publicPath: '' }
+            options: {publicPath: ''},
           },
-          'css-loader'
-        ]
+          'css-loader',
+        ],
       },
       {
         test: /\.(woff(2)?|ttf|eot)(\?v=\d+\.\d+\.\d+)?$/,
         use: {
-          loader: 'url-loader'
-        }
+          loader: 'url-loader',
+        },
       },
       // Load images as URLs
       {
         test: /\.(png|svg|jpg|gif)$/,
         use: {
-          loader: 'url-loader'
-        }
+          loader: 'url-loader',
+        },
       },
       // Load locales files
       {
@@ -109,24 +109,22 @@ module.exports = merge(common, {
             loader: 'file-loader',
             options: {
               name: '[name].[contenthash].[ext]',
-              outputPath: 'locales'
-            }
-          }
-        ]
+              outputPath: 'locales',
+            },
+          },
+        ],
       },
       // AngularJS templates are cached using cache template
       {
-        test: /\.html$/,
+        test: /\.html$/i,
         exclude: path.resolve(__dirname, './src/index.html'),
         use: [
-          'ng-cache-loader?prefix=[dir]/[dir]',
-          'extract-loader',
           {
             loader: 'html-loader',
-            options: { minimize: true }
-          }
-        ]
-      }
-    ]
-  }
+            options: {minimize: true},
+          },
+        ],
+      },
+    ],
+  },
 });

Некоторые файлы не были показаны из-за большого количества измененных файлов