Commit 8b13ec74 authored by LAVENIER's avatar LAVENIER
Browse files

Merge branch 'release/1.6.2'

parents dbdc9851 a3275adb
<?xml version='1.0' encoding='utf-8'?>
<widget android-versionCode="10601" id="net.sumaris.app" version="1.6.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<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">
<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.1" #lastest
echo "1.6.2" #lastest
}
api_release_url() {
......
{
"name": "sumaris-app",
"description": "SUMARiS app",
"version": "1.6.1",
"version": "1.6.2",
"author": "contact@e-is.pro",
"license": "AGPL-3.0",
"readmeFilename": "README.md",
......
......@@ -12,6 +12,7 @@ import {ConfigOptions} from "../../core/services/config/core.config";
import {APP_CONFIG_OPTIONS, ConfigService} from "../../core/services/config.service";
import {FormFieldDefinitionMap} from "../../shared/form/field.model";
import {AbstractSoftwarePage} from "../../referential/software/abstract-software.page";
import {HistoryPageReference} from "../../core/services/model/history.model";
declare interface CacheStatistic {
name: string;
......@@ -176,5 +177,9 @@ export class ConfigurationPage extends AbstractSoftwarePage<Configuration, Confi
});
this.cacheStatisticTotal.next(total);
}
protected async computePageHistory(title: string): Promise<HistoryPageReference> {
return null; // No page history
}
}
......@@ -23,8 +23,8 @@ export class PersonValidatorService implements ValidatorService {
// Use account validator as base form group definition
// BUT add more flexibility (e.g. 'pubkey' become optional)
// This is need to be able to store person that are not using SUMARiS tools (e.g. onboard obsevers)
const formConfig = this.accountValidatorService.getFormGroupConfig(data && Account.fromObject(data.asObject));
// This is need to be able to store person that are not using SUMARiS tools (e.g. onboard observers)
const formConfig = this.accountValidatorService.getFormGroupConfig(data && Account.fromObject(data.asObject()));
formConfig.pubkey = [data && data.pubkey || null, SharedValidators.pubkey];
formConfig.avatar = [''];
......
......@@ -286,7 +286,7 @@
<ion-row class="ion-no-padding">
<ion-col></ion-col>
<ion-col class="ion-no-padding" size="auto">
<mat-paginator [length]="resultsLength" [pageSize]="20" [pageSizeOptions]="[20, 50, 100, 200]" showFirstLastButtons>
<mat-paginator [length]="resultsLength" [pageSize]="defaultPageSize" [pageSizeOptions]="[20, 50, 100, 200]" showFirstLastButtons>
</mat-paginator>
</ion-col>
</ion-row>
......
......@@ -220,6 +220,9 @@ import {SocialModule} from "./social/social.module";
},
'TaxonNameStrategyVO': {
keyFields: ['__typename', 'strategyId', 'taxonName', ['entityName', 'id']]
},
'ExtractionTypeVO': {
keyFields: ['category', 'label']
}
}
},
......
......@@ -29,10 +29,10 @@ export class AuthForm extends AppForm<AuthData> implements OnInit {
showPwd = false;
@Output()
onCancel: EventEmitter<any> = new EventEmitter<any>();
onCancel = new EventEmitter<any>();
@Output()
onSubmit: EventEmitter<AuthData> = new EventEmitter<AuthData>();
onSubmit = new EventEmitter<AuthData>();
disable(opts?: { onlySelf?: boolean; emitEvent?: boolean }) {
super.disable(opts);
......
......@@ -66,6 +66,7 @@ export abstract class AppEntityEditor<
saving = false;
hasRemoteListener = false;
defaultBackHref: string;
historyIcon: {icon?: string; matIcon?: string; };
onUpdateView = new EventEmitter<T>();
get usageMode(): UsageMode {
......@@ -135,6 +136,7 @@ export abstract class AppEntityEditor<
// Defaults
this._autoOpenNextTab = toBoolean(this._autoOpenNextTab, !this.isOnFieldMode);
this.historyIcon = this.historyIcon || {icon: 'list'};
// Register forms
this.registerForms();
......@@ -604,14 +606,14 @@ export abstract class AppEntityEditor<
// If NOT data, then add to page history
if (!this.isNewData) {
return this.addToPageHistory({
title,
path: this.router.url
});
const page = await this.computePageHistory(title);
return this.addToPageHistory(page);
}
}
protected async addToPageHistory(page: HistoryPageReference, opts?: AddToPageHistoryOptions) {
if (!page) return; // Skip
return this.settings.addToPageHistory(page, {
removePathQueryParams: true,
removeTitleSmallTag: true,
......@@ -620,6 +622,13 @@ export abstract class AppEntityEditor<
});
}
protected async computePageHistory(title: string): Promise<HistoryPageReference> {
return {
title,
path: this.router.url
};
}
protected async removePageHistory(opts?: { emitEvent?: boolean; }) {
return this.settings.removePageHistory(this.router.url, opts);
}
......
......@@ -20,16 +20,16 @@ export class FormButtonsBarComponent implements OnDestroy{
disabledCancel = false;
@Output()
onCancel: EventEmitter<Event> = new EventEmitter<Event>();
onCancel = new EventEmitter<Event>();
@Output()
onSave: EventEmitter<Event> = new EventEmitter<Event>();
onSave = new EventEmitter<Event>();
@Output()
onNext: EventEmitter<Event> = new EventEmitter<Event>();
onNext = new EventEmitter<Event>();
@Output()
onBack: EventEmitter<Event> = new EventEmitter<Event>();
onBack = new EventEmitter<Event>();
constructor(private hotkeys: Hotkeys) {
......
......@@ -100,10 +100,10 @@ export abstract class AppForm<T> implements IAppForm, OnInit, OnDestroy {
}
@Output()
onCancel: EventEmitter<any> = new EventEmitter<any>();
onCancel = new EventEmitter<any>();
@Output()
onSubmit: EventEmitter<any> = new EventEmitter<any>();
onSubmit = new EventEmitter<any>();
protected constructor(
protected dateAdapter: DateAdapter<Moment> | DateFormatPipe,
......
......@@ -136,7 +136,7 @@ export class GraphqlService {
mergeMap(() => this.network.checkPeerAlive()),
filter(alive => !alive)
)
.subscribe(() => this.network.setForceOffline(true, {displayToast: true}));
.subscribe(() => this.network.setForceOffline(true, {showToast: true}));
}
......@@ -816,10 +816,11 @@ export class GraphqlService {
private toApolloError<T>(err: any, defaultError?: any): ApolloQueryResult<T> {
let error =
// If network error: try to convert to App (read as JSON), or create an UNKNOWN_NETWORK_ERROR
(err.networkError &&
(err.networkError && (
(err.networkError.error && this.toAppError(err.networkError.error))
|| this.toAppError(err.networkError)
|| this.createAppErrorByCode(ErrorCodes.UNKNOWN_NETWORK_ERROR)
)
)
// If graphQL: try to convert the first error found
|| (err.graphQLErrors && err.graphQLErrors.length && this.toAppError(err.graphQLErrors[0]))
......
......@@ -151,7 +151,7 @@
mat-icon-button class="ion-float-start ion-no-margin">
<mat-icon>close</mat-icon>
</button>
<ion-label [innerHTML]="page.subtitle"></ion-label>
<ion-label [innerHTML]="page.subtitle|translate"></ion-label>
<ion-text class="ion-float-end" [title]="page.time|dateFormat:{time: true}">
<small><ion-icon name="time-outline"></ion-icon>&nbsp;{{ page.time|dateFromNow }}</small>&nbsp;
</ion-text>
......@@ -167,7 +167,7 @@
<ion-icon *ngIf="page.icon" slot="start" [name]="page.icon"></ion-icon>
<mat-icon *ngIf="page.matIcon" slot="start">{{page.matIcon}}</mat-icon>
<ion-label color="primary" [innerHTML]="page.title"></ion-label>
<ion-label color="primary" [title]="page.title">{{page.title}}</ion-label>
</ion-item>
</ion-card-title>
</ion-card-header>
......
......@@ -65,9 +65,8 @@
<ion-col size="auto">
<!-- Download app link -->
<ion-button color="tertiary" class="ion-float-end"
(click)="downloadApp($event, link)">
<!-- Download button -->
<ion-button color="tertiary" class="ion-float-end" download [href]="link.url">
<ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>
</ion-button>
</ion-col>
......@@ -93,9 +92,9 @@
</ion-text>
</ion-col>
<ion-col size="auto">
<!-- Display App download link -->
<ion-button color="tertiary" class="ion-float-end"
(click)="downloadApp($event, link)">
<!-- Download button -->
<ion-button color="tertiary" class="ion-float-end" download [href]="link.url">
<ion-label translate>COMMON.BTN_DOWNLOAD</ion-label>
</ion-button>
</ion-col>
......
......@@ -27,7 +27,13 @@ import {ConfigOptions} from "../services/config/core.config";
import {VersionUtils} from "../../shared/version/versions";
export declare type InstallAppLink = { name: string; url: string; platform?: 'android' | 'ios'; version?: string; };
export declare interface InstallAppLink {
name: string;
url: string;
platform?: 'android' | 'ios';
version?: string;
downloadFilename?: string;
}
@Component({
selector: 'app-install-upgrade-card',
......@@ -133,14 +139,16 @@ export class AppInstallUpgradeCard implements OnInit, OnDestroy {
this._subscription.unsubscribe();
}
downloadApp(event: UIEvent, link: InstallAppLink) {
event.preventDefault();
openDownloadLink(event: UIEvent, url: string) {
if (!url) return; // Skip
if (link && link.url) {
console.info(`[install] Opening App download link: ${link.url}`);
this.platform.open(link.url, '_system', 'location=yes');
return false;
if (event) {
event.preventDefault();
}
console.info(`[install] Opening App download link: ${url}`);
this.platform.open(url, '_system', 'location=yes');
return false;
}
tryOnline() {
......@@ -162,14 +170,18 @@ export class AppInstallUpgradeCard implements OnInit, OnDestroy {
getPlatformName(platform: 'android'|'ios') {
switch (platform) {
case 'android':
return 'Android'
return 'Android';
case 'ios':
return 'iOS'
return 'iOS';
default:
return ''
return '';
}
}
getAppFileName(): string {
return
}
/* -- protected method -- */
private getCompatibleInstallLinks(installLinks: InstallAppLink[]): InstallAppLink[] {
......@@ -210,9 +222,14 @@ export class AppInstallUpgradeCard implements OnInit, OnDestroy {
let url = config.getProperty(ConfigOptions.ANDROID_INSTALL_URL);
const name: string = isNotNilOrBlank(url) && config.label || environment.defaultAppName || 'SUMARiS';
let version;
const filename = name;
if (isNilOrBlank(url)) {
url = environment.defaultAndroidInstallUrl || null;
}
else {
version = config.getProperty(ConfigOptions.APP_MIN_VERSION);
}
result.push({ name, url, platform: 'android', version });
}
......
<ion-header>
<ion-toolbar>
<ion-toolbar color="light">
<ion-buttons slot="start">
<ion-button (click)="cancel()" *ngIf="canCancel"
visible-xs visible-sm visible-mobile>
......@@ -11,11 +11,19 @@
{{'NETWORK.PEER.SELECT_MODAL.TITLE'|translate}}
</ion-title>
<!-- loader -->
<ion-text *ngIf="loading"
slot="end" class="ion-float-end ion-padding-end">
<ion-spinner></ion-spinner>
</ion-text>
<ion-buttons slot="end">
<!-- loader -->
<ion-spinner *ngIf="loading; else endButtons"></ion-spinner>
<ng-template #endButtons>
<!-- refresh button -->
<ion-button *ngIf="onRefresh.observers | isNotEmptyArray"
[matTooltip]="'COMMON.BTN_REFRESH'|translate"
(click)="refresh($event)">
<mat-icon slot="icon-only">refresh</mat-icon>
</ion-button>
</ng-template>
</ion-buttons>
</ion-toolbar>
</ion-header>
......
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy} from '@angular/core';
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, EventEmitter, Output} from '@angular/core';
import {ModalController} from '@ionic/angular';
import {Peer} from "../services/model/peer.model";
import {Observable, Subject, Subscription} from "rxjs";
......@@ -24,6 +24,7 @@ export class SelectPeerModal implements OnDestroy {
@Input() canCancel = true;
@Input() allowSelectDownPeer = true;
@Input() onRefresh = new EventEmitter<UIEvent>();
constructor(
......@@ -54,6 +55,22 @@ export class SelectPeerModal implements OnDestroy {
}
}
/**
* Check the min pod version, defined by the app
* @param peer
*/
isCompatible(peer: Peer): boolean {
return !this.peerMinVersion || (peer && peer.softwareVersion && VersionUtils.isCompatible(this.peerMinVersion, peer.softwareVersion));
}
refresh(event: UIEvent) {
this.loading = true;
this.onRefresh.emit(event);
}
/* -- protected methods -- */
async refreshPeers(peers: Peer[]) {
peers = peers || [];
......@@ -81,9 +98,8 @@ export class SelectPeerModal implements OnDestroy {
this._subscription.add(
this.$peers
.subscribe(() => {
this.cd.markForCheck();
}));
.subscribe(() => this.cd.markForCheck())
);
try {
await jobs;
......@@ -95,14 +111,6 @@ export class SelectPeerModal implements OnDestroy {
this.cd.markForCheck();
}
/**
* Check the min pod version, defined by the app
* @param peer
*/
isCompatible(peer: Peer): boolean {
return !this.peerMinVersion || (peer && peer.softwareVersion && VersionUtils.isCompatible(this.peerMinVersion, peer.softwareVersion));
}
protected async refreshPeer(peer: Peer): Promise<Peer> {
try {
const summary: NodeInfo = await NetworkUtils.getNodeInfo(this.http, peer.url);
......@@ -121,4 +129,8 @@ export class SelectPeerModal implements OnDestroy {
}
return peer;
}
protected markForCheck() {
this.cd.markForCheck();
}
}
......@@ -45,10 +45,10 @@ export class RegisterForm implements OnInit {
@ViewChild('stepper', { static: true }) private stepper: MatHorizontalStepper;
@Output()
onCancel: EventEmitter<any> = new EventEmitter<any>();
onCancel = new EventEmitter<any>();
@Output()
onSubmit: EventEmitter<RegisterData> = new EventEmitter<RegisterData>();
onSubmit = new EventEmitter<RegisterData>();
constructor(
private accountService: AccountService,
......
......@@ -455,7 +455,7 @@ export class AccountService extends BaseEntityService {
this.data.authToken = previousToken;
// Make sure network if set as offline
this.network.setForceOffline(true, {displayToast: false});
this.network.setForceOffline(true, {showToast: false});
console.info(`[account] Login [OK] {pubkey: ${this.data.pubkey.substr(0, 8)}}, {offline: true}`);
}
......@@ -607,7 +607,7 @@ export class AccountService extends BaseEntityService {
// Offline feature are enable: continue in offline mode
if (this.settings.hasOfflineFeature()) {
console.warn("[account] Unable to authenticate on pod: forcing offline mode");
this.network.setForceOffline(true, {displayToast: false});
this.network.setForceOffline(true, {showToast: false});
// Continue
}
// No offline features enable (=offline mode not allowed)
......
......@@ -455,6 +455,8 @@ export class LocalSettingsService {
removePathQueryParams?: boolean;
removeTitleSmallTag?: boolean;
}): HistoryPageReference {
if (!page || !page.title || !page.path) throw Error("Missing required argument 'page', 'page.path' or 'page.title'");
// Set time
page.time = page.time || moment();
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment