Commit 863f8ea7 authored by LAVENIER's avatar LAVENIER
Browse files

Merge branch 'release/1.6.3'

parents 8b13ec74 10e3356d
......@@ -90,7 +90,6 @@
"styles": [
"node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"src/global.scss",
"node_modules/leaflet/dist/leaflet.css",
"node_modules/prismjs/themes/prism-okaidia.css"
],
"scripts": [
......
<?xml version='1.0' encoding='utf-8'?>
<widget android-versionCode="10602" id="net.sumaris.app" version="1.6.2" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<widget android-versionCode="10603" id="net.sumaris.app" version="1.6.3" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>SUMARiS</name>
<description>Halieutic data capture</description>
<author email="contact@e-is.pro" href="http://www.e-is.pro">Environmental Information Systems</author>
......
......@@ -15,7 +15,7 @@ if [[ "_$INSTALL_DIR" == "_" ]]; then
fi
latest_version() {
echo "1.6.2" #lastest
echo "1.6.3" #lastest
}
api_release_url() {
......
{
"name": "sumaris-app",
"description": "SUMARiS app",
"version": "1.6.2",
"version": "1.6.3",
"author": "contact@e-is.pro",
"license": "AGPL-3.0",
"readmeFilename": "README.md",
......@@ -59,10 +59,10 @@
"angular2-text-mask": "^9.0.0",
"apollo-angular": "^2.1.0",
"apollo-angular-link-http": "^2.0.0-alpha.6",
"apollo3-cache-persist": "^0.9.1",
"apollo-link-logger": "^2.0.0",
"apollo-link-queue": "^3.0.0",
"apollo-link-serialize": "^3.1.1",
"apollo3-cache-persist": "^0.9.1",
"chart.js": "^2.9.4",
"clovelced-plugin-audiomanagement": "^1.0.2",
"cordova-android": "^8.1.0",
......@@ -81,12 +81,13 @@
"geojson": "^0.5.0",
"graphql": "^15.4.0",
"graphql-tag": "^2.11.0",
"hammerjs": "^2.0.8",
"hammer-timejs": "^1.1.0",
"hammerjs": "^2.0.8",
"integrator-cordova-plugin-downloader": "^1.1.0",
"ionic-cache": "^5.2.0",
"ionicons": "^5.2.3",
"leaflet": "^1.6.0",
"leaflet-graticule": "^0.0.1",
"localforage": "1.7.1",
"lodash.clonedeep": "^4.5.0",
"luxon": "^1.24.1",
......@@ -99,6 +100,7 @@
"ngx-material-timepicker": "5.5.3",
"ngx-quicklink": "^0.2.4",
"roboto-fontface": "^0.10.0",
"rxjs": "^6.6.3",
"scrypt-async": "^2.0.1",
"seedrandom": "^3.0.5",
"subscriptions-transport-ws": "^0.9.16",
......@@ -106,7 +108,6 @@
"tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1",
"uuid": "^3.3.3",
"rxjs": "^6.6.3",
"zone.js": "~0.10.3"
},
"devDependencies": {
......@@ -125,7 +126,6 @@
"acorn": "^7.3.1",
"codelyzer": "^6.0.1",
"cordova": "^9.0.0",
"cordova-res": "^0.15.2",
"cordova-plugin-camera": "^4.1.0",
"cordova-plugin-device": "^2.0.3",
"cordova-plugin-ionic-keyboard": "^2.2.0",
......@@ -133,7 +133,7 @@
"cordova-plugin-splashscreen": "^5.0.4",
"cordova-plugin-statusbar": "^2.4.3",
"cordova-plugin-whitelist": "^1.3.4",
"react": "^17.0.1",
"cordova-res": "^0.15.2",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.2",
"karma": "~5.2.3",
......@@ -142,13 +142,14 @@
"karma-jasmine": "~4.0.1",
"karma-jasmine-html-reporter": "^1.5.4",
"protractor": "~7.0.0",
"react": "^17.0.1",
"sass": "^1.29.0",
"sass-loader": "^10.1.0",
"tar": "^5.0.5",
"ts-node": "^8.10.2",
"tslint": "^6.1.3",
"typescript": "~4.0.5",
"webpack": "^4.6.0",
"tslint": "^6.1.3"
"webpack": "^4.6.0"
},
"cordova": {
"plugins": {
......
......@@ -14,20 +14,20 @@ import {ExtractionCriteriaForm} from "./extraction-criteria.form";
import {TranslateService} from "@ngx-translate/core";
import {ActivatedRoute, Router} from "@angular/router";
import {ExtractionService} from "../services/extraction.service";
import {AlertController, ToastController} from "@ionic/angular";
import {AlertController, ModalController, ToastController} from "@ionic/angular";
import {AccountService} from "../../core/services/account.service";
import {LocalSettingsService} from "../../core/services/local-settings.service";
import {PlatformService} from "../../core/services/platform.service";
import {AppTabEditor} from "../../core/form/tab-editor.class";
import {AggregationType} from "../services/model/aggregation-type.model";
import {ExtractionUtils} from "../services/extraction.utils";
import {ExtractionHelpModal} from "../help/help.modal";
export const DEFAULT_CRITERION_OPERATOR = '=';
@Directive()
export abstract class ExtractionAbstractPage<T extends ExtractionType | AggregationType>
extends AppTabEditor implements OnInit {
export abstract class ExtractionAbstractPage<T extends ExtractionType | AggregationType> extends AppTabEditor {
type: T;
form: FormGroup;
......@@ -67,7 +67,8 @@ export abstract class ExtractionAbstractPage<T extends ExtractionType | Aggregat
protected service: ExtractionService,
protected settings: LocalSettingsService,
protected formBuilder: FormBuilder,
protected platform: PlatformService
protected platform: PlatformService,
protected modalCtrl: ModalController
) {
super(route, router, alertCtrl, translate);
// Create the main form
......@@ -132,7 +133,7 @@ export abstract class ExtractionAbstractPage<T extends ExtractionType | Aggregat
});
// Execute the first load
await this.loadData();
await this.loadGeoData();
}));
}
......@@ -264,7 +265,7 @@ export abstract class ExtractionAbstractPage<T extends ExtractionType | Aggregat
if (type) {
this.setType(type, {emitEvent: false});
this.loadData();
this.loadGeoData();
}
return undefined;
}
......@@ -276,12 +277,30 @@ export abstract class ExtractionAbstractPage<T extends ExtractionType | Aggregat
return undefined;
}
abstract async loadData();
abstract async loadGeoData();
async reload(): Promise<any> {
return this.load(this.type && this.type.id);
}
async openHelpModal(event?: UIEvent) {
const modal = await this.modalCtrl.create({
component: ExtractionHelpModal,
componentProps: {
type: this.type
},
keyboardClose: true,
cssClass: 'modal-large'
});
// Open the modal
await modal.present();
// Wait until closed
await modal.onDidDismiss();
}
/* -- protected method -- */
protected abstract watchTypes(): Observable<T[]>;
......
<app-toolbar [title]="$title | async" color="primary">
<app-toolbar color="primary">
<ion-title>
<span [innerHTML]="$title | async"></span>
<!-- Help button -->
<button mat-icon-button
*ngIf="type?.description"
[matTooltip]="'COMMON.BTN_SHOW_HELP'|translate"
(click)="openHelpModal($event)">
<mat-icon>help_outline</mat-icon>
</button>
</ion-title>
<ion-buttons slot="end">
<!-- select map modal -->
<ion-button (click)="openSelectTypeModal($event)"
color="accent"
fill="solid">
fill="solid"
[matTooltip]="'EXTRACTION.MAP.BTN_SELECT_TYPE_HELP'|translate">
<ion-icon slot="start" name="list"></ion-icon>
<ion-label translate>EXTRACTION.MAP.BTN_SELECT_TYPE</ion-label>
<mat-icon slot="end">arrow_drop_down</mat-icon>
</ion-button>
<!-- options sub menu -->
<ion-button *ngIf="canEdit"
[matMenuTriggerFor]="optionMenu">
<mat-icon slot="icon-only">more_vert</mat-icon>
......@@ -31,7 +47,7 @@
<!-- Change base layer -->
<button mat-menu-item [matMenuTriggerFor]="baseLayerMenu" translate>EXTRACTION.MAP.BASE_LAYER</button>
<mat-menu #baseLayerMenu="matMenu">
<button mat-menu-item *ngFor="let item of baseLayers" (click)="setBaseLayer(item.layer)">
<button mat-menu-item *ngFor="let item of availableBaseLayers" (click)="setBaseLayer(item.layer)">
<ion-icon *ngIf="baseLayer === item.layer" name="checkmark"></ion-icon>
{{item.title|translate}}
</button>
......@@ -42,6 +58,12 @@
<ion-icon *ngIf="showGraticule" name="checkmark"></ion-icon>
<span translate> EXTRACTION.MAP.SHOW_GRATICULE</span>
</button>
<!-- Show countries layer -->
<button mat-menu-item (click)="toggleShowCountriesLayer()">
<ion-icon *ngIf="!showCountriesLayer" name="checkmark"></ion-icon>
<span translate> EXTRACTION.MAP.SHOW_INVALID_GEOMETRIES</span>
</button>
</mat-menu>
<!-- tech columns menu -->
......@@ -229,7 +251,7 @@
<form [formGroup]="form" class="form-container">
<ion-grid *ngIf="form.controls.strata as strataForm">
<ion-grid>
<ion-row [formGroup]="strataForm" class="strata-row odd">
<!-- space -->
......@@ -243,7 +265,7 @@
</ion-col>
<!-- tech -->
<ion-col class="ion-no-padding" *ngIf="strataForm.get('techColumnName') as control">
<ion-col class="ion-no-padding" *ngIf="strataForm.controls.techColumnName as control">
<mat-form-field>
<mat-select [formControl]="control"
[placeholder]="'EXTRACTION.MAP.TECH_VALUE'|translate">
......@@ -325,15 +347,17 @@
<!-- chart -->
<ion-card *ngIf="$tech | async as tech; else addChartButton"
color="light" class="chart no-margin-end"
color="light"
class="chart no-margin-end"
[class.chart-bar]="techChartOptions.type==='bar'"
@fadeInAnimation>
<ion-toolbar color="transparent">
<ion-label [matMenuTriggerFor]="techColumnMenu"
<ion-toolbar color="transparent" class="mat-expansion-panel-header"
[matMenuTriggerFor]="techColumnMenu"
(click)="$event.stopPropagation()">
{{tech.title | translate: tech.titleParams }}
<mat-icon class="ion-color-medium">arrow_drop_down</mat-icon>
</ion-label>
<ion-text color="primary" [matTooltip]="'EXTRACTION.MAP.BTN_CHART_SERIES_HELP'|translate">
<span [innerHTML]="tech.title | translate: tech.titleParams"></span>
<mat-icon class="ion-color-primary">arrow_drop_down</mat-icon>
</ion-text>
<ion-buttons slot="end">
<button mat-icon-button class="ion-color-dark"
......@@ -342,10 +366,9 @@
(click)="$event.stopPropagation()">
<mat-icon>more_vert</mat-icon>
</button>
<button mat-icon-button class="ion-color-medium"
[matTooltip]="'COMMON.BTN_CLOSE'|translate"
<button mat-icon-button
(click)="hideTechChart()">
<mat-icon>close</mat-icon>
<mat-icon class="ion-color-medium">close</mat-icon>
</button>
</ion-buttons>
</ion-toolbar>
......@@ -364,16 +387,17 @@
<!-- add chart button -->
<ng-template #addChartButton>
<ion-card *ngIf="$techColumns | async | isNotEmptyArray"
[class.cdk-visually-hidden]="!started"
color="light" class="no-margin-end"
[matMenuTriggerFor]="techColumnMenu">
[matMenuTriggerFor]="techColumnMenu" @fadeInAnimation>
<ion-card-content class="ion-no-padding">
<!-- tech column selector -->
<button mat-stroked-button class="ion-color-medium"
[matMenuTriggerFor]="techColumnMenu"
[matTooltip]="'EXTRACTION.MAP.BTN_ADD_CHART_HELP'|translate"
(click)="$event.stopPropagation()">
<mat-icon class="ion-color-medium" matPrefix>insert_chart</mat-icon>
<ion-text color="medium" translate>EXTRACTION.MAP.BTN_ADD_TECH_GRAPH</ion-text>
<mat-icon class="ion-color-medium">arrow_drop_down</mat-icon>
<mat-icon class="ion-color-medium" >insert_chart</mat-icon>
<ion-text color="medium" translate>EXTRACTION.MAP.BTN_ADD_CHART</ion-text>
</button>
</ion-card-content>
</ion-card>
......@@ -388,9 +412,9 @@
*ngIf="showLegend"
[ngStyle]="legendStyle"
@fadeInAnimation>
<ion-card-header>
<ion-card-header (click)="openLegendForm($event)" [matTooltip]="'EXTRACTION.MAP.BTN_EDIT_LEGEND_HELP'|translate">
<ion-card-subtitle>
<ion-label (click)="openLegendForm($event)">
<ion-label>
<span translate>EXTRACTION.MAP.LEGEND</span>
<div hidden-xs hidden-sm *ngIf="!showLegendForm">
<mat-icon>edit</mat-icon>
......@@ -535,12 +559,14 @@
<!-- Leaflet map -->
<div *ngIf="ready"
leaflet
class="leaflet-reset-pane-z-index"
(leafletMapReady)="onMapReady($event)"
[leafletOptions]="mapOptions">
<div *ngIf="showCountriesLayer" [leafletLayer]="countriesLayer"></div>
<div *ngFor="let layer of $layers | async" [leafletLayer]="layer"></div>
<!--<div [leafletLayer]="countriesLayer"></div>-->
</div>
......
......@@ -117,15 +117,19 @@ ion-card.chart {
}
ion-toolbar {
padding-left: unset;
padding-left: unset !important;
padding-right: unset;
-webkit-padding-start: 8px;
padding-inline-start: 8px;
-webkit-padding-start: 8px !important;
padding-inline-start: 8px !important;
-webkit-padding-end: 8px;
padding-inline-end: 8px;
ion-label {
ion-label,
ion-text,
ion-title {
cursor: pointer;
font-size: inherit;
font-weight: inherit;
ion-icon,
mat-icon {
......@@ -138,6 +142,7 @@ ion-card.chart {
&:hover {
color: var(--ion-color-primary);
background: rgba(0, 0, 0, 0.04);
}
}
......@@ -155,6 +160,7 @@ ion-card.chart {
ion-card.legend {
ion-card-header,
ion-card-content {
padding-left: unset;
......@@ -168,8 +174,6 @@ ion-card.legend {
ion-card-subtitle {
ion-label {
cursor: pointer;
ion-icon,
mat-icon {
visibility: hidden;
......@@ -184,17 +188,22 @@ ion-card.legend {
content: ' ';
}
&:hover {
color: var(--ion-color-primary);
ion-icon,
mat-icon {
visibility: visible;
}
}
}
}
}
ion-card-header:hover {
color: var(--ion-color-primary);
background: rgba(0, 0, 0, 0.04);
cursor: pointer;
ion-card-subtitle ion-label {
ion-icon,
mat-icon {
visibility: visible;
}
}
}
ion-row {
padding-top: 0;
......
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
NgZone,
OnDestroy,
OnInit,
ViewChild
} from "@angular/core";
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, NgZone, ViewChild} from "@angular/core";
import {PlatformService} from "../../core/services/platform.service";
import {ExtractionService} from "../services/extraction.service";
import {BehaviorSubject, Observable, Subject, Subscription, timer} from "rxjs";
......@@ -18,7 +10,8 @@ import {
isNotNil,
isNotNilOrBlank,
isNumber,
isNumberRange
isNumberRange,
sleep
} from "../../shared/functions";
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
import {ExtractionColumn, ExtractionFilter, ExtractionFilterCriterion} from "../services/model/extraction.model";
......@@ -26,7 +19,7 @@ import {Location} from "@angular/common";
import {Color, ColorScale, fadeInAnimation, fadeInOutAnimation} from "../../shared/shared.module";
import {ColorScaleLegendItem} from "../../shared/graph/graph-colors";
import * as L from 'leaflet';
import {CRS, WMSParams} from 'leaflet';
import {CRS, GeoJSON, MapOptions, WMSParams} from 'leaflet';
import {Feature} from "geojson";
import {debounceTime, filter, map, switchMap, tap, throttleTime} from "rxjs/operators";
import {AlertController, ModalController, ToastController} from "@ionic/angular";
......@@ -40,7 +33,7 @@ import {AggregationTypeValidatorService} from "../services/validator/aggregation
import {AppFormUtils} from "../../core/core.module";
import {MatExpansionPanel} from "@angular/material/expansion";
import {Label, SingleOrMultiDataSet} from "ng2-charts";
import {ChartLegendOptions, ChartOptions, ChartType, LinearScale} from "chart.js";
import {ChartLegendOptions, ChartOptions, ChartType} from "chart.js";
import {DEFAULT_CRITERION_OPERATOR} from "../table/extraction-table.page";
import {DurationPipe} from "../../shared/pipes/duration.pipe";
import {AggregationStrata, AggregationType, IAggregationStrata} from "../services/model/aggregation-type.model";
......@@ -64,24 +57,24 @@ declare interface TechChartOptions extends ChartOptions {
const REGEXP_NAME_WITH_UNIT = /^([^(]+)(?: \(([^)]+)\))?$/;
const BASE_LAYER_SLD_BODY = '<sld:StyledLayerDescriptor version="1.0.0" xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd">\n' +
' <sld:NamedLayer>\n' +
' <sld:Name>ESPACES_TERRESTRES_P</sld:Name>\n' +
' <sld:UserStyle>\n' +
' <sld:Name>pointSymbolizer</sld:Name>\n' +
' <sld:Title>pointSymbolizer</sld:Title>\n' +
' <sld:FeatureTypeStyle>\n' +
' <sld:Rule>\n' +
' <sld:PolygonSymbolizer>\n' +
' <sld:Fill>\n' +
' <sld:CssParameter name="fill">#666666</sld:CssParameter>\n' +
' <sld:CssParameter name="fill-opacity">1</sld:CssParameter>\n' +
' </sld:Fill>\n' +
' </sld:PolygonSymbolizer>\n' +
' </sld:Rule>\n' +
' </sld:FeatureTypeStyle>\n' +
' </sld:UserStyle>\n' +
' </sld:NamedLayer>\n' +
const BASE_LAYER_SLD_BODY = '<sld:StyledLayerDescriptor version="1.0.0" xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd">' +
' <sld:NamedLayer>' +
' <sld:Name>ESPACES_TERRESTRES_P</sld:Name>' +
' <sld:UserStyle>' +
' <sld:Name>polygonSymbolizer</sld:Name>' +
' <sld:Title>polygonSymbolizer</sld:Title>' +
' <sld:FeatureTypeStyle>' +
' <sld:Rule>' +
' <sld:PolygonSymbolizer>' +
' <sld:Fill>' +
' <sld:CssParameter name="fill">#8C8C8C</sld:CssParameter>' +
' <sld:CssParameter name="fill-opacity">1</sld:CssParameter>' +
' </sld:Fill>' +
' </sld:PolygonSymbolizer>' +
' </sld:Rule>' +
' </sld:FeatureTypeStyle>' +
' </sld:UserStyle>' +
' </sld:NamedLayer>' +
'</sld:StyledLayerDescriptor>';
@Component({
......@@ -91,9 +84,10 @@ const BASE_LAYER_SLD_BODY = '<sld:StyledLayerDescriptor version="1.0.0" xsi:sche
animations: [fadeInAnimation, fadeInOutAnimation],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ExtractionMapPage extends ExtractionAbstractPage<AggregationType> implements OnInit, OnDestroy {
export class ExtractionMapPage extends ExtractionAbstractPage<AggregationType> {
ready = false;
started = false;
// -- Map Layers --
osmBaseLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
......@@ -113,40 +107,40 @@ export class ExtractionMapPage extends ExtractionAbstractPage<AggregationType> i
crs: CRS.EPSG3857,
format: "image/png",
transparent: true,
styles: "style_sld_body",
attribution: "<a href='https://www.ifremer.fr'>Ifremer</a>"
zIndex: 9999, // Important, to bring this layer to top
attribution: "<a href='https://sextant.ifremer.fr'>Sextant</a>"
}).setParams({
layers: "ESPACES_TERRESTRES_P",
service: 'WMS',
sld_body: encodeURIComponent(BASE_LAYER_SLD_BODY.replace(/[ \t\n]+/, ' '))
sld_body: BASE_LAYER_SLD_BODY
} as WMSParams);
sextantGraticuleLayer = L.tileLayer.wms('https://www.ifremer.fr/services/wms1', {
graticuleLayer = L.tileLayer.wms('https://www.ifremer.fr/services/wms1', {
maxZoom: 18,
version: '1.3.0',
crs: CRS.EPSG3857,
format: "image/png",
transparent: true,
attribution: "<a href='https://www.ifremer.fr'>Ifremer</a>"
attribution: "<a href='https://sextant.ifremer.fr'>Sextant</a>"
}).setParams({
layers: "graticule_4326",
service: 'WMS'
});
mapOptions = {
layers: [this.sextantBaseLayer],
baseLayer: L.TileLayer = this.sextantBaseLayer;
availableBaseLayers = [
{title: 'Sextant (Ifremer)', layer: this.sextantBaseLayer},
{title: 'Open Street Map', layer: this.osmBaseLayer}
];
mapOptions: MapOptions = {
preferCanvas: false,
layers: [this.baseLayer],
maxZoom: 10, // max zoom to sextant layer
zoom: 5,
center: L.latLng(46.879966, -10) // Atlantic centric
};
baseLayer: L.TileLayer = this.mapOptions.layers[0];
baseLayers = [
{title: 'Sextant (Ifremer)', layer: this.sextantBaseLayer},
{title: 'Open Street Map', layer: this.osmBaseLayer}
];
map: L.Map;
$layers = new BehaviorSubject<L.GeoJSON<L.Polygon>[]>(null);
$layers = new BehaviorSubject<L.Layer[]>(null);
showGraticule = false;
showCountriesLayer = true;
// -- Legend card --
showLegend = false;
......@@ -161,7 +155,7 @@ export class ExtractionMapPage extends ExtractionAbstractPage<AggregationType> i
$selectedFeature = new BehaviorSubject<Feature | undefined>(undefined);
$details = new Subject<{ title: string; value?: string; otherValue?: string; properties: { name: string; value: string }[]; }>();
// -- Chart card
// -- Tech chart card
techChartOptions: TechChartOptions = {
type: 'bar',
responsive: true,
......@@ -182,6 +176,7 @@ export class ExtractionMapPage extends ExtractionAbstractPage<AggregationType> i
aggMax: undefined
};
chartTypes: ChartType[] = ['pie', 'bar', 'doughnut'];
showTechChart = true;
// -- Data --
data = {
......@@ -212,15 +207,15 @@ export class ExtractionMapPage extends ExtractionAbstractPage<AggregationType> i
@ViewChild('aggExpansionPanel', { static: true }) aggExpansionPanel: MatExpansionPanel;
get year(): number {
return this.form.get('year').value;
return this.form.controls.year.value;
}