Commit 71056579 authored by LAVENIER's avatar LAVENIER
Browse files

Merge branch 'release/1.6.6'

parents 31064236 05eadb57
<?xml version='1.0' encoding='utf-8'?>
<widget android-versionCode="10605" id="net.sumaris.app" version="1.6.5" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<widget android-versionCode="10606" id="net.sumaris.app" version="1.6.6" 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.5" #lastest
echo "1.6.6" #lastest
}
api_release_url() {
......
{
"name": "sumaris-app",
"description": "SUMARiS app",
"version": "1.6.5",
"version": "1.6.6",
"author": "contact@e-is.pro",
"license": "AGPL-3.0",
"readmeFilename": "README.md",
......@@ -24,36 +24,36 @@
"e2e": "npm run ng e2e"
},
"dependencies": {
"@angular/animations": "^11.0.2",
"@angular/cdk": "^11.0.1",
"@angular/common": "^11.0.2",
"@angular/core": "^11.0.2",
"@angular/forms": "^11.0.2",
"@angular/material": "^11.0.1",
"@angular/material-moment-adapter": "^11.0.1",
"@angular/platform-browser": "^11.0.2",
"@angular/platform-browser-dynamic": "^11.0.2",
"@angular/router": "^11.0.2",
"@apollo/client": "^3.3.2",
"@angular/animations": "^11.0.4",
"@angular/cdk": "^11.0.2",
"@angular/common": "^11.0.4",
"@angular/core": "^11.0.4",
"@angular/forms": "^11.0.4",
"@angular/material": "^11.0.2",
"@angular/material-moment-adapter": "^11.0.2",
"@angular/platform-browser": "^11.0.4",
"@angular/platform-browser-dynamic": "^11.0.4",
"@angular/router": "^11.0.4",
"@apollo/client": "^3.3.4",
"@asymmetrik/ngx-leaflet": "^8.1.0",
"@e-is/ngx-material-table": "0.11.1",
"@ionic-native/audio-management": "^5.29.0",
"@ionic-native/camera": "^5.29.0",
"@ionic-native/core": "^5.29.0",
"@ionic-native/downloader": "^5.29.0",
"@ionic-native/geolocation": "^5.29.0",
"@ionic-native/in-app-browser": "^5.29.0",
"@ionic-native/keyboard": "^5.29.0",
"@ionic-native/native-audio": "^5.29.0",
"@ionic-native/network": "^5.29.0",
"@ionic-native/splash-screen": "^5.29.0",
"@ionic-native/status-bar": "^5.29.0",
"@ionic-native/vibration": "^5.29.0",
"@ionic/angular": "^5.5.0",
"@ionic/core": "^5.5.0",
"@ionic-native/audio-management": "^5.30.0",
"@ionic-native/camera": "^5.30.0",
"@ionic-native/core": "^5.30.0",
"@ionic-native/downloader": "^5.30.0",
"@ionic-native/geolocation": "^5.30.0",
"@ionic-native/in-app-browser": "^5.30.0",
"@ionic-native/keyboard": "^5.30.0",
"@ionic-native/native-audio": "^5.30.0",
"@ionic-native/network": "^5.30.0",
"@ionic-native/splash-screen": "^5.30.0",
"@ionic-native/status-bar": "^5.30.0",
"@ionic-native/vibration": "^5.30.0",
"@ionic/angular": "^5.5.2",
"@ionic/core": "^5.5.2",
"@ionic/pwa-elements": "^3.0.1",
"@ionic/storage": "^2.3.1",
"@ngtools/webpack": "^11.0.2",
"@ngtools/webpack": "^11.0.4",
"@ngx-translate/core": "^13.0.0",
"@ngx-translate/http-loader": "^6.0.0",
"angular2-text-mask": "^9.0.0",
......@@ -87,12 +87,11 @@
"ionic-cache": "^5.2.0",
"ionicons": "^5.2.3",
"leaflet": "^1.6.0",
"leaflet-graticule": "^0.0.1",
"localforage": "1.7.1",
"localforage": "1.7.4",
"lodash.clonedeep": "^4.5.0",
"luxon": "^1.24.1",
"luxon": "^1.25.0",
"material-design-icons": "^3.0.1",
"moment": "^2.27.0",
"moment": "^2.29.1",
"ng2-charts": "^2.4.2",
"ng2-charts-schematics": "^0.1.7",
"ngx-color-picker": "^10.1.0",
......@@ -103,12 +102,12 @@
"rxjs": "^6.6.3",
"scrypt-async": "^2.0.1",
"seedrandom": "^3.0.5",
"subscriptions-transport-ws": "^0.9.16",
"subscriptions-transport-ws": "^0.9.18",
"tslib": "^2.0.3",
"tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1",
"uuid": "^3.3.3",
"zone.js": "~0.10.3"
"zone.js": "~0.11.3"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.1100.2",
......
import {ChangeDetectionStrategy, Component, OnDestroy} from '@angular/core';
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy} from '@angular/core';
import {Subscription} from 'rxjs';
import {AccountService} from '../services/account.service';
import {Account} from '../services/model/account.model';
import {Locales} from '../services/model/settings.model';
import {referentialToString} from '../services/model/referential.model';
import {UserSettingsValidatorService} from '../services/validator/user-settings.validator';
import {FormBuilder, FormGroup} from '@angular/forms';
import {AccountValidatorService} from '../services/validator/account.validator';
......@@ -50,7 +49,8 @@ export class AccountPage extends AppForm<Account> implements OnDestroy {
protected validatorService: AccountValidatorService,
protected settingsValidatorService: UserSettingsValidatorService,
protected translate: TranslateService,
protected settings: LocalSettingsService
protected settings: LocalSettingsService,
protected cd: ChangeDetectorRef
) {
super(dateAdapter, validatorService.getFormGroup(accountService.account), settings);
......@@ -212,5 +212,8 @@ export class AccountPage extends AppForm<Account> implements OnDestroy {
this.markForCheck();
}
referentialToString = referentialToString;
protected markForCheck() {
this.cd.markForCheck();
}
}
......@@ -45,6 +45,7 @@ import {
import {ShowToastOptions, Toasts} from "../../shared/toasts";
import {Alerts} from "../../shared/alerts";
import {createPromiseEventEmitter, emitPromiseEvent} from "../../shared/events";
import {environment} from "../../../environments/environment";
export const SETTINGS_DISPLAY_COLUMNS = "displayColumns";
export const SETTINGS_SORTED_COLUMN = "sortedColumn";
......@@ -270,7 +271,7 @@ export abstract class AppTable<T extends Entity<T>, F = any>
return this.sort && this.sort.direction && (this.sort.direction === 'desc' ? 'desc' : 'asc') || undefined;
}
@ViewChild(MatTable, {static: true}) table: MatTable<T>;
@ViewChild(MatTable, {static: false}) table: MatTable<T>;
@ViewChild(MatPaginator, {static: true}) paginator: MatPaginator;
@ViewChild(MatSort, {static: true}) sort: MatSort;
......@@ -418,9 +419,18 @@ export abstract class AppTable<T extends Entity<T>, F = any>
}
ngAfterViewInit() {
if (!this.table) console.warn(`[table] Missing <mat-table> in the HTML template! Component: ${this.constructor.name}`);
if (!environment.production) {
// Warn if table not exists
if (!this.table) {
setTimeout(() => {
if (!this.table) {
console.warn(`[table] Missing <mat-table> in the HTML template (after waiting 500ms)! Component: ${this.constructor.name}`);
}
}, 500);
}
if (!this.displayedColumns) console.warn(`[table] Missing 'displayedColumns'. Did you call super.ngOnInit() in component ${this.constructor.name} ?`);
if (!this.displayedColumns) console.warn(`[table] Missing 'displayedColumns'. Did you call super.ngOnInit() in component ${this.constructor.name} ?`);
}
}
ngOnDestroy() {
......
......@@ -141,7 +141,7 @@ export abstract class AppRootDataEditor<
}
protected computePageUrl(id: number|'new') {
let parentUrl = this.getParentPageUrl();
const parentUrl = this.getParentPageUrl();
return `${parentUrl}/${id}`;
}
......
import {ValidatorService} from "@e-is/ngx-material-table";
import {FormBuilder, FormGroup} from "@angular/forms";
import {AbstractControlOptions, FormBuilder, FormGroup} from "@angular/forms";
import {SharedValidators} from "../../../shared/validator/validators";
import {DataEntity} from "../model/data-entity.model";
import {toBoolean, toNumber} from "../../../shared/functions";
......@@ -47,10 +47,8 @@ export abstract class DataEntityValidatorService<T extends DataEntity<T>, O exte
};
}
getFormGroupOptions(data?: T, opts?: O): {
[key: string]: any;
} {
return {};
getFormGroupOptions(data?: T, opts?: O): AbstractControlOptions | null {
return null;
}
updateFormGroup(formGroup: FormGroup, opts?: O) {
......
......@@ -27,15 +27,14 @@ export abstract class DataRootEntityValidatorService<T extends RootDataEntity<T>
[key: string]: any;
} {
return Object.assign(
super.getFormGroupConfig(data),
{
program: [data && data.program || null, Validators.compose([Validators.required, SharedValidators.entity])],
creationDate: [data && data.creationDate || null],
recorderPerson: [data && data.recorderPerson || null, SharedValidators.entity],
comments: [data && data.comments || null, Validators.maxLength(2000)],
synchronizationStatus: [data && data.synchronizationStatus || null]
});
return {
...super.getFormGroupConfig(data),
program: [data && data.program || null, Validators.compose([Validators.required, SharedValidators.entity])],
creationDate: [data && data.creationDate || null],
recorderPerson: [data && data.recorderPerson || null, SharedValidators.entity],
comments: [data && data.comments || null, Validators.maxLength(2000)],
synchronizationStatus: [data && data.synchronizationStatus || null]
};
}
getObserversFormArray(data?: IWithObserversEntity<T>) {
......
......@@ -39,6 +39,7 @@ import {DurationPipe} from "../../shared/pipes/duration.pipe";
import {AggregationStrata, AggregationType, IAggregationStrata} from "../services/model/aggregation-type.model";
import {ExtractionUtils} from "../services/extraction.utils";
import {AggregationService, AggregationTypeFilter} from "../services/aggregation.service";
import {UnitLabel, UnitLabelPatterns} from "../../referential/services/model/model.enum";
declare interface LegendOptions {
min: number;
......@@ -933,18 +934,12 @@ export class ExtractionMapPage extends ExtractionAbstractPage<AggregationType> {
if (value) value += ` ${unit}`;
// Try to compute other value, using unit
switch (unit) {
case 'hours':
case 'h.dec':
case 'h. dec':
// Days
otherValue = this.durationPipe.transform(parseFloat(aggValue), 'hours');
break;
case 'kg':
// Tons
otherValue = this.floatToLocaleString(parseFloat(aggValue) / 1000) + ' t';
break;
default:
if (UnitLabelPatterns.DECIMAL_HOURS.test(unit)) {
otherValue = this.durationPipe.transform(parseFloat(aggValue), 'hours');
}
else if (unit === UnitLabel.KG) {
otherValue = this.floatToLocaleString(parseFloat(aggValue) / 1000) + ' t';
}
}
}
......
......@@ -94,13 +94,25 @@
</mat-boolean-field>
<!-- date -->
<mat-date-time-field *ngSwitchCase="'date'" #matInput
<mat-date-field *ngSwitchCase="'date'" #matInput
[formControl]="formControl"
[placeholder]="compact ? ('COMMON.DATE_PLACEHOLDER'|translate): placeholder"
[floatLabel]="floatLabel"
[required]="required"
[readonly]="readonly"
[compact]="compact"
[tabindex]="tabindex">
<div matSuffix>
<ng-content select="[matSuffix]"></ng-content>
</div>
</mat-date-field>
<mat-date-time-field *ngSwitchCase="'dateTime'" #matInput
[formControl]="formControl"
[placeholder]="compact ? ('COMMON.DATE_PLACEHOLDER'|translate): placeholder"
[floatLabel]="floatLabel"
[required]="required"
[readonly]="readonly"
[displayTime]="displayTime()"
[compact]="compact"
[tabindex]="tabindex">
<div matSuffix>
......
......@@ -20,6 +20,7 @@ import {filterNumberInput, focusInput, InputElement, setTabIndex} from "../../sh
import {getPmfmName, PmfmStrategy} from "../services/model/pmfm-strategy.model";
import {PmfmUtils} from "../services/model/pmfm.model";
import {PmfmValidators} from "../services/validator/pmfm.validators";
import {PmfmLabelPatterns, UnitLabel, UnitLabelPatterns} from "../services/model/model.enum";
const noop = () => {
};
......@@ -117,17 +118,23 @@ export class PmfmFormField implements OnInit, ControlValueAccessor, InputElement
type = "hidden";
}
else if (type === "double") {
if (this.pmfm.label === "LATITUDE") {
if (PmfmLabelPatterns.LATITUDE.test(this.pmfm.label) ) {
type = "latitude";
} else if (this.pmfm.label === "LONGITUDE") {
} else if (PmfmLabelPatterns.LONGITUDE.test(this.pmfm.label)) {
type = "longitude";
} else if (this.pmfm.unitLabel === 'h dec.') { // TODO get from program properties
}
else if (this.pmfm.unitLabel === UnitLabel.DECIMAL_HOURS || UnitLabelPatterns.DECIMAL_HOURS.test(this.pmfm.unitLabel)) {
type = "duration";
}
else {
this.numberInputStep = this.computeNumberInputStep(this.pmfm);
}
}
else if (type === "date") {
if (this.pmfm.unitLabel === UnitLabel.DATE_TIME || UnitLabelPatterns.DATE_TIME.test(this.pmfm.unitLabel)) {
type = 'dateTime';
}
}
this.type = type;
}
......@@ -178,10 +185,6 @@ export class PmfmFormField implements OnInit, ControlValueAccessor, InputElement
}
}
displayTime(): boolean {
return this.pmfm && this.pmfm.unitLabel === 'Date & Time'; // TODO get it from program properties
}
selectInputContent = AppFormUtils.selectInputContent;
/* -- protected method -- */
......
......@@ -85,9 +85,22 @@ export const MethodIds = {
};
export const PmfmLabelPatterns = {
BATCH_WEIGHT: /^BATCH_(.+)_WEIGHT$/
BATCH_WEIGHT: /^BATCH_(.+)_WEIGHT$/,
LATITUDE: /^latitude$/i,
LONGITUDE: /^longitude$/i
};
export const UnitLabelPatterns = {
DECIMAL_HOURS: /^(h[. ]+dec[.]?|hours)$/,
DATE_TIME: /^Date[ &]+Time$/
};
// TODO Should be override by config properties
export const UnitLabel = {
DECIMAL_HOURS: 'h dec.',
DATE_TIME: 'Date & Time',
KG: 'kg'
};
export const QualityFlagIds = {
NOT_QUALIFIED: 0,
GOOD: 1,
......
import {FormBuilder, Validators} from "@angular/forms";
import {AbstractControlOptions, FormBuilder, Validators} from "@angular/forms";
import {isNotNil, toNumber} from "../../../shared/functions";
import {SharedFormGroupValidators, SharedValidators} from "../../../shared/validator/validators";
import {Injectable} from "@angular/core";
......@@ -32,11 +32,10 @@ export class PmfmValidatorService extends ReferentialValidatorService<Pmfm> {
} ;
}
getFormGroupOptions(data?: Pmfm, opts?: any): { [key: string]: any } {
return {
/*validator: Validators.compose([
SharedFormGroupValidators.requiredIf('fraction', 'matrix')
])*/
};
getFormGroupOptions(data?: Pmfm, opts?: any): AbstractControlOptions {
/*return {validator: Validators.compose([
SharedFormGroupValidators.requiredIf('fraction', 'matrix')
])}*/
return null;
}
}
......@@ -50,7 +50,7 @@ export class ReferentialValidatorService<T extends Referential = Referential>
return controlsConfig;
}
getFormGroupOptions(data?: T, opts?: any): AbstractControlOptions | any {
return {};
getFormGroupOptions(data?: T, opts?: any): AbstractControlOptions | null {
return null;
}
}
......@@ -48,7 +48,8 @@
</mat-autocomplete-field>
</ion-col>
<ion-col size="auto">
<mat-date-time-field [placeholder]="'VESSEL.REGISTRATION.START_DATE'|translate" formControlName="startDate" [displayTime]="false"
<mat-date-time-field [placeholder]="'VESSEL.REGISTRATION.START_DATE'|translate"
formControlName="startDate"
[required]="true">
<mat-error *ngIf="form.get('registration.startDate').hasError('dateIsAfter')">
{{'ERROR.FIELD_NOT_VALID_DATE_AFTER' | translate: form.get('registration.startDate').errors.dateIsAfter }}
......@@ -67,12 +68,12 @@
</mat-form-field>
</ion-col>
<ion-col size="auto">
<mat-date-time-field [placeholder]="'VESSEL.FEATURES.START_DATE'|translate" formControlName="startDate" [displayTime]="false"
[required]="true">
<mat-date-field [placeholder]="'VESSEL.FEATURES.START_DATE'|translate" formControlName="startDate"
[required]="true">
<mat-error *ngIf="form.get('features.startDate').hasError('dateIsAfter')">
{{'ERROR.FIELD_NOT_VALID_DATE_AFTER' | translate: form.get('features.startDate').errors.dateIsAfter }}
</mat-error>
</mat-date-time-field>
</mat-date-field>
</ion-col>
</ion-row>
......
......@@ -63,10 +63,10 @@
<ion-row>
<!-- date -->
<ion-col>
<mat-date-time-field formControlName="date" [placeholder]="'VESSEL.LIST.FILTER.DATE'|translate" [displayTime]="false"
<mat-date-field formControlName="date" [placeholder]="'VESSEL.LIST.FILTER.DATE'|translate"
[clearable]="true">
<ion-icon matPrefix name="calendar"></ion-icon>
</mat-date-time-field>
</mat-date-field>
</ion-col>
<!-- status -->
......
......@@ -73,20 +73,19 @@
</mat-boolean-field>
<!-- date -->
<mat-date-time-field *ngSwitchCase="'date'" #matInput
<mat-date-field *ngSwitchCase="'date'" #matInput
[class]="classList"
[formControl]="formControl"
[placeholder]="compact ? ('COMMON.DATE_PLACEHOLDER'|translate): placeholder"
[floatLabel]="floatLabel"
[required]="required"
[readonly]="readonly"
[displayTime]="false"
[compact]="compact"
[tabindex]="tabindex">
<div matSuffix>
<ng-content select="[matSuffix]"></ng-content>
</div>
</mat-date-time-field>
</mat-date-field>
<!-- date time -->
<mat-date-time-field *ngSwitchCase="'dateTime'"
......@@ -96,7 +95,6 @@
[floatLabel]="floatLabel"
[required]="required"
[readonly]="readonly"
[displayTime]="true"
[compact]="compact"
[tabindex]="tabindex">
<div matSuffix>
......
......@@ -17,14 +17,14 @@
[textMask]="{mask: dayMask, keepCharPositions: true, placeholderChar: placeholderChar}"
[placeholder]="'COMMON.DATE_PLACEHOLDER'|translate"
(blur)="checkIfTouched()"
(keyup.arrowdown)="openDatePicker($event, datePicker1)"
(keyup.arrowdown)="openDatePicker($event, datePicker)"
(keyup.escape)="preventEvent($event)"
[required]="required"
[tabindex]="tabindex">
<input matInput #matInput autocomplete="off" type="text"
*ngIf="mobile"
[formControl]="dayControl"
(click)="openDatePickerIfMobile($event, datePicker1)"
(click)="openDatePickerIfMobile($event, datePicker)"
[required]="required"
[tabindex]="tabindex"
readonly>
......@@ -33,13 +33,12 @@
<input matInput type="text"
[formControl]="formControl"
hidden
[matDatepicker]="datePicker1"
[matDatepicker]="datePicker"
(dateChange)="onDatePickerChange($event)">
<button matSuffix mat-icon-button tabindex="-1"
type="button"
(click)="openDatePicker($event, datePicker1)"
(click)="openDatePicker($event, datePicker)"
[disabled]="formControl.disabled">
<div *ngIf="mobile; then iconDate; else iconDesktop"></div>
</button>
......@@ -61,7 +60,7 @@
<ng-content select="mat-error"></ng-content>
</mat-form-field>
<mat-datepicker #datePicker1
<mat-datepicker #datePicker
[touchUi]="mobile"
[disabled]="formControl.disabled"
[startAt]="startDate"></mat-datepicker>
......
......@@ -28,7 +28,7 @@ import {DATE_ISO_PATTERN, DEFAULT_PLACEHOLDER_CHAR, KEYBOARD_HIDE_DELAY_MS} from
import {SharedValidators} from '../../validator/validators';
import {sleep, isNil, isNilOrBlank, toBoolean, toDateISOString} from "../../functions";
import {Keyboard} from "@ionic-native/keyboard/ngx";
import {first} from "rxjs/operators";
import {filter, first} from "rxjs/operators";
import {InputElement, setTabIndex} from "../../inputs";
import {BehaviorSubject, Subscription} from "rxjs";
import {FloatLabelType} from "@angular/material/form-field";
......@@ -106,8 +106,7 @@ export class MatDate implements OnInit, OnDestroy, ControlValueAccessor, InputEl
@Input() clearable = false;
@ViewChild('datePicker1') datePicker1: MatDatepicker<Moment>;
@ViewChild('datePicker2') datePicker2: MatDatepicker<Moment>;
@ViewChild('datePicker') datePicker: MatDatepicker<Moment>;
@ViewChildren('matInput') matInputs: QueryList<ElementRef>;
......@@ -145,10 +144,10 @@ export class MatDate implements OnInit, OnDestroy, ControlValueAccessor, InputEl
this.formControl.setValidators(this.required ? [Validators.required, SharedValidators.validDate] : SharedValidators.validDate);
// Get patterns to display date
this.updatePattern(this.translate.instant(['COMMON.DATE_PATTERN']))
this.updatePattern(this.translate.instant('COMMON.DATE_PATTERN'));
this._subscription.add(
this.translate.get(['COMMON.DATE_PATTERN'])
.subscribe((patterns) => this.updatePattern(patterns))
this.translate.get('COMMON.DATE_PATTERN')
.subscribe((pattern) => this.updatePattern(pattern))
);
this._subscription.add(
......@@ -158,8 +157,8 @@ export class MatDate implements OnInit, OnDestroy, ControlValueAccessor, InputEl
// Listen status changes outside the component (e.g. when setErrors() is calling on the formControl)
this._subscription.add(
this.formControl.statusChanges
.pipe(filter(() => !this.readonly && !this.writing && !this.disabling)) // Skip
.subscribe((status) => {
if (this.readonly || this.writing || this.disabling) return; // Skip
if (status === 'INVALID') {
$error.next(this.formControl.errors);
}
......@@ -256,9 +255,13 @@ export class MatDate implements OnInit, OnDestroy, ControlValueAccessor, InputEl
this._onChangeCallback(dateStr);
}
private updatePattern(patterns: {[key: string]: string}) {
this.displayPattern = patterns['COMMON.DATE_PATTERN'] !== 'COMMON.DATE_PATTERN' ? patterns['COMMON.DATE_PATTERN'] : 'L';
this.dayPattern = (patterns['COMMON.DATE_PATTERN'] !== 'COMMON.DATE_PATTERN' ? patterns['COMMON.DATE_PATTERN'] : 'L');
private updatePattern(pattern: string) {
pattern = pattern !== 'COMMON.DATE_PATTERN' ? pattern : 'L';
if (this.displayPattern !== pattern) {
this.displayPattern = pattern;
this.dayPattern = pattern;
this.markForCheck();
}
}