Newer
Older
import { Component, OnInit, OnDestroy, HostListener, ViewChild, ElementRef, isDevMode } from "@angular/core";
import { Router, Event, NavigationEnd, ActivationEnd } from "@angular/router";
import { MatDialog } from "@angular/material/dialog";
import { MatSidenav } from "@angular/material/sidenav";
import { MatToolbar } from "@angular/material/toolbar";
import { CdkDragDrop, moveItemInArray } from "@angular/cdk/drag-drop";
import { Observer, jalhydDateRev, jalhydVersion, CalculatorType, Session } from "jalhyd";
import { I18nService } from "./services/internationalisation.service";
import { FormulaireService } from "./services/formulaire.service";
import { FormulaireDefinition } from "./formulaire/definition/form-definition";
import { ServiceFactory } from "./services/service-factory";
import { HttpService } from "./services/http.service";
import { ApplicationSetupService } from "./services/app-setup.service";
import { nghydDateRev, nghydVersion } from "../date_revision";
import { DialogConfirmCloseCalcComponent } from "./components/dialog-confirm-close-calc/dialog-confirm-close-calc.component";
mathias.chouet
committed
import { DialogConfirmEmptySessionComponent } from "./components/dialog-confirm-empty-session/dialog-confirm-empty-session.component";
import { DialogLoadSessionComponent } from "./components/dialog-load-session/dialog-load-session.component";
import { DialogSaveSessionComponent } from "./components/dialog-save-session/dialog-save-session.component";
import { QuicknavComponent } from "./components/quicknav/quicknav.component";
import { NotificationsService } from "./services/notifications.service";
import { decodeHtml } from "./util/util";
import { HotkeysService, Hotkey } from "angular2-hotkeys";
import { MatomoTracker } from "@ngx-matomo/tracker";
import { saveAs } from "file-saver";
import * as XLSX from "xlsx";
import * as pako from "pako";
François Grand
committed
import { DialogConfirmComponent } from "./components/dialog-confirm/dialog-confirm.component";
import { UserConfirmationService } from "./services/user-confirmation.service";
import { ServiceWorkerUpdateService } from "./services/service-worker-update.service";
selector: "nghyd-app",
templateUrl: "./app.component.html",
export class AppComponent implements OnInit, OnDestroy, Observer {
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
@ViewChild("sidenav")
public sidenav: MatSidenav;
@ViewChild("navbar")
public navbar: MatToolbar;
/** current calculator, inferred from _currentFormId by setActiveCalc() (used for navbar menu) */
public currentCalc: any;
/** shows or hides the progressbar under the navbar */
public showProgressBar = false;
/** liste des modules de calcul ouverts */
private _calculators: Array<{
title: string,
type: CalculatorType,
uid: string,
active?: boolean,
latestAnchor?: string
}> = [];
/**
* id du formulaire courant
* on utilise pas directement FormulaireService.currentFormId pour éviter l'erreur
* ExpressionChangedAfterItHasBeenCheckedError
*/
private _currentFormId: string;
private _innerWidth: number;
constructor(
private intlService: I18nService,
private appSetupService: ApplicationSetupService,
private router: Router,
private formulaireService: FormulaireService,
private httpService: HttpService,
private notificationsService: NotificationsService,
private confirmEmptySessionDialog: MatDialog,
private saveSessionDialog: MatDialog,
private loadSessionDialog: MatDialog,
private confirmCloseCalcDialog: MatDialog,
private hotkeysService: HotkeysService,
private matomoTracker: MatomoTracker,
François Grand
committed
private confirmDialog: MatDialog,
private serviceWorkerUpdateService: ServiceWorkerUpdateService,
François Grand
committed
private userConfirmationService: UserConfirmationService
) {
ServiceFactory.httpService = httpService;
ServiceFactory.applicationSetupService = appSetupService;
ServiceFactory.i18nService = intlService;
ServiceFactory.formulaireService = formulaireService;
ServiceFactory.notificationsService = notificationsService;
ServiceFactory.serviceWorkerUpdateService = serviceWorkerUpdateService;
if (!isDevMode()) {
// évite de mettre en place un bandeau RGPD
this.matomoTracker.disableCookies();
// Set custom dimension for Electron / pure Web browser
this.matomoTracker.setCustomDimension(1, this.getRunningPlatform());
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
this.router.events.subscribe((event: Event) => {
// close side navigation when clicking a calculator tab
if (event instanceof NavigationEnd) {
this.sidenav.close();
window.scrollTo(0, 0);
}
// [de]activate calc tabs depending on loaded route
if (event instanceof ActivationEnd) {
const path = event.snapshot.url[0].path;
if (path === "calculator") {
const calcUid = event.snapshot.params.uid;
if (this.calculatorExists(calcUid)) {
this.setActiveCalc(calcUid);
} else {
// if required calculator does not exist, redirect to list page
this.toList();
}
} else {
this.setActiveCalc(null);
}
}
});
// hotkeys listeners
this.hotkeysService.add(new Hotkey("alt+s", AppComponent.onHotkey(this.saveForm, this)));
this.hotkeysService.add(new Hotkey("alt+o", AppComponent.onHotkey(this.loadSession, this)));
this.hotkeysService.add(new Hotkey("alt+q", AppComponent.onHotkey(this.emptySession, this)));
this.hotkeysService.add(new Hotkey("alt+n", AppComponent.onHotkey(this.toList, this)));
this.hotkeysService.add(new Hotkey("alt+g", AppComponent.onHotkey(this.toDiagram, this)));
}
/**
* Wrapper for hotkeys triggers, that executes given function only if
* hotkeys are enabled in app preferences
* @param func function to execute when hotkey is entered
*/
public static onHotkey(func: any, that: any) {
return (event: KeyboardEvent): boolean => {
if (ServiceFactory.applicationSetupService.enableHotkeys) {
func.call(that);
return false; // Prevent bubbling
} else {
console.log("Hotkeys are disabled in app preferences");
}
};
}
public static exportAsImage(canvas: HTMLCanvasElement) {
canvas.toBlob((blob) => {
AppComponent.download(blob, "chart.png", "image/png");
}); // defaults to image/png
}
/**
* Exports a results data table to XLSX format, and removes "help" mentions
* from the parameters names columns if needed
* @param table results data table
* @param namesInFirstCol if true, will look for parameters names in 1st column
* (for fixed results), else in 1st row (variable results, by default)
*/
public static exportAsSpreadsheet(table: ElementRef, namesInFirstCol: boolean = false) {
const ws: XLSX.WorkSheet = XLSX.utils.table_to_sheet(table);
let regExCellKey;
if (namesInFirstCol) {
regExCellKey = new RegExp("^A\\d$");
} else {
regExCellKey = new RegExp("^\\w1$");
// browse all cells
for (const key in ws) {
// look for 1st row or 1st col
if (regExCellKey.test(key) === true) {
const regExCellName = new RegExp("help$");
const v: string = ws[key].v;
// remove "help" from cell name's ending
if (regExCellName.test(v) === true) {
const newV = v.substr(0, v.length - 4);
ws[key].v = newV;
}
}
AppComponent.downloadSpreadsheet(ws);
}
/**
* Adds the given XLSX worksheet to a new XLSX workbook and triggers its download
*/
public static downloadSpreadsheet(ws: XLSX.WorkSheet) {
const wb: XLSX.WorkBook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "default");
const wopts: any = { bookType: "xlsx", bookSST: false, type: "array" };
const wbout = XLSX.write(wb, wopts);
AppComponent.download(
new Blob([wbout], { type: "application/octet-stream" }),
"cassiopee-results.xlsx",
"application/vnd.ms-excel"
);
}
/**
* @param blob binary object to download
* @param filename
* @param mimeType
*/
public static download(blob: Blob, filename: string, mimeType: string) {
}
/**
* Triggered at app startup.
* Preferences are loaded by app setup service
* @see ApplicationSetupService.construct()
*/
ngOnInit() {
this.formulaireService.addObserver(this);
this._innerWidth = window.innerWidth;
this.logRevisionInfo();
François Grand
committed
// Initialise communication with UserConfirmationService.
// When receiving a message from it, open a dialog to ask user to confirm.
// Will then reply to UserConfirmationService with a message holding confirmation status.
this.userConfirmationService.subscribe(this);
this.userConfirmationService.addHandler(this, {
next: (data) => this.displayConfirmationDialog(data["title"], data["body"]),
error: () => { },
complete: () => { },
});
}
ngOnDestroy() {
this.formulaireService.removeObserver(this);
François Grand
committed
// cancel communication link with UserConfirmationService
this.userConfirmationService.unsubscribe(this);
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
}
@HostListener("window:resize", ["$event"])
onResize(event) {
// keep track of window size for navbar tabs arrangement
this._innerWidth = window.innerWidth;
}
public get uitextSidenavNewCalc() {
return this.intlService.localizeText("INFO_MENU_NOUVELLE_CALC");
}
public get uitextSidenavParams() {
return this.intlService.localizeText("INFO_SETUP_TITLE");
}
public get uitextSidenavLoadSession() {
return this.intlService.localizeText("INFO_MENU_LOAD_SESSION_TITLE");
}
public get uitextSidenavSaveSession() {
return this.intlService.localizeText("INFO_MENU_SAVE_SESSION_TITLE");
}
public get uitextSidenavEmptySession() {
return this.intlService.localizeText("INFO_MENU_EMPTY_SESSION_TITLE");
}
public get uitextSidenavDiagram() {
return this.intlService.localizeText("INFO_MENU_DIAGRAM_TITLE");
}
public get uitextSidenavSessionProps() {
return this.intlService.localizeText("INFO_MENU_SESSION_PROPS");
}
public get uitextSidenavReportBug() {
return this.intlService.localizeText("INFO_MENU_REPORT_BUG");
}
public get uitextSidenavHelp() {
return this.intlService.localizeText("INFO_MENU_HELP_TITLE");
}
public get uitextSelectCalc() {
return this.intlService.localizeText("INFO_MENU_SELECT_CALC");
}
public get uitextSearch() {
return this.intlService.localizeText("INFO_MENU_RECHERCHE_MODULES");
}
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
public getCalculatorLabel(t: CalculatorType) {
return decodeHtml(this.formulaireService.getLocalisedTitleFromCalculatorType(t));
}
public getFullCalculatorTitle(calc: { title: string, type: CalculatorType, active?: boolean }): string {
return decodeHtml(calc.title + " (" + this.getCalculatorLabel(calc.type) + ")");
}
public get calculators() {
return this._calculators;
}
public get currentFormId() {
return this._currentFormId;
}
public get currentRoute(): string {
return this.router.url;
}
public get currentCalcUid(): string {
return this.currentCalc ? this.currentCalc.uid : undefined;
}
public setActiveCalc(uid: string) {
this._calculators.forEach((calc) => {
calc.active = (calc.uid === uid);
});
// mark current calc for navbar menu
const index = this.getCalculatorIndexFromId(uid);
this.currentCalc = this._calculators[index];
}
/**
* Close calculator using middle click on tab
*/
public onMouseUp(event: any, uid: string) {
if (event.which === 2) {
const dialogRef = this.confirmCloseCalcDialog.open(
DialogConfirmCloseCalcComponent,
{
data: {
uid: uid
},
disableClose: true
}
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.formulaireService.requestCloseForm(uid);
}
});
}
}
/**
* Returns true if sum of open calculator tabs witdh is lower than navbar
* available space (ie. if navbar is not overflowing), false otherwise
*/
public get tabsFitInNavbar() {
// manual breakpoints
// @WARNING keep in sync with .calculator-buttons sizes in app.component.scss
let tabsLimit = 0;
if (this._innerWidth > 480) {
tabsLimit = 3;
}
if (this._innerWidth > 640) {
tabsLimit = 4;
}
if (this._innerWidth > 800) {
tabsLimit = 6;
}
const fits = this._calculators.length <= tabsLimit;
return fits;
}
public get enableHeaderDoc(): boolean {
return this.currentRoute.includes("/list") && this._calculators.length === 0;
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
}
public get enableSaveSessionMenu(): boolean {
return this._calculators.length > 0;
}
public get enableModulesDiagramMenu(): boolean {
return this._calculators.length > 0;
}
public get enableSessionPropertiesMenu(): boolean {
return this._calculators.length > 0;
}
public get enableEmptySessionMenu(): boolean {
return this._calculators.length > 0;
}
// interface Observer
public update(sender: any, data: any): void {
if (sender instanceof FormulaireService) {
switch (data["action"]) {
case "createForm":
// add newly created form to calculators list
const f: FormulaireDefinition = data["form"];
this._calculators.push(
{
"title": f.calculatorName,
"type": f.calculatorType,
"uid": f.uid
}
);
// abonnement en tant qu'observateur du nouveau formulaire
f.addObserver(this);
break;
case "invalidFormId":
this.toList();
break;
case "currentFormChanged":
this._currentFormId = data["formId"];
break;
case "saveForm":
this.saveForm(data["form"]);
break;
case "closeForm":
const form: FormulaireDefinition = data["form"];
this.closeCalculator(form);
break;
}
} else if (sender instanceof FormulaireDefinition) {
switch (data["action"]) {
case "nameChanged":
this.updateCalculatorTitle(sender, data["name"]);
break;
}
}
}
/**
* Returns true if a form having "formUid" as UID exists
* @param formId UID to look for
*/
private calculatorExists(formId: string): boolean {
return (this.getCalculatorIndexFromId(formId) > -1);
}
private getCalculatorIndexFromId(formId: string) {
const index = this._calculators.reduce((resultIndex, calc, currIndex) => {
if (resultIndex === -1 && calc["uid"] === formId) {
resultIndex = currIndex;
}
return resultIndex;
}, -1);
return index;
}
private updateCalculatorTitle(f: FormulaireDefinition, title: string) {
const formIndex = this.getCalculatorIndexFromId(f.uid);
this._calculators[formIndex]["title"] = title;
}
/**
* Saves a JSON serialised session file, for one or more calc modules
* @param calcList modules to save
* @param filename
*/
private saveSession(calcList: any[], filename: string) {
const session: string = this.buildSessionFile(calcList);
if (!isDevMode()) {
this.matomoTracker.trackEvent("userAction", "saveSession");
}
this.formulaireService.downloadTextFile(session, filename);
}
/**
* Builds a session file including Nubs, GUI-specific Nubs metadata,
* model settings, GUI settings
* @param calcList Nubs to save
*/
private buildSessionFile(calcList: any[]): string {
const serialiseOptions: { [key: string]: {} } = {};
for (const c of calcList) {
if (c.selected) {
serialiseOptions[c.uid] = { // GUI-dependent metadata to add to the session file
title: c.title
};
}
}
const settings = {
precision: this.appSetupService.computePrecision,
maxIterations: this.appSetupService.maxIterations,
displayPrecision: this.appSetupService.displayPrecision,
};
return Session.getInstance().serialise(serialiseOptions, settings);
}
/**
* Supprime un module de calcul **de l'interface**
* ATTENTION, ne supprime pas le module de calcul en mémoire !
* Pour cela, utiliser FormulaireService.requestCloseForm(form.uid);
* @param form module de calcul à fermer
*/
private closeCalculator(form: FormulaireDefinition) {
const formId: string = form.uid;
// désabonnement en tant qu'observateur
form.removeObserver(this);
// recherche du module de calcul correspondant à formId
const closedIndex = this.getCalculatorIndexFromId(formId);
/*
* détermination du nouveau module de calcul à afficher :
* - celui après celui supprimé
* - ou celui avant celui supprimé si on supprime le dernier
*/
let newId = null;
const l = this._calculators.length;
if (l > 1) {
if (closedIndex === l - 1) {
newId = this._calculators[closedIndex - 1]["uid"];
} else {
newId = this._calculators[closedIndex + 1]["uid"];
}
}
// suppression
this._calculators = this._calculators.filter(calc => {
return formId !== calc["uid"];
});
// MAJ affichage
if (newId === null) {
this.toList();
this._currentFormId = null;
} else {
this.toCalc(newId);
}
}
private toList() {
this.router.navigate(["/list"]);
}
public toDiagram() {
this.router.navigate(["/diagram"]);
}
AUBRY JEAN-PASCAL
committed
public toNotes() {
this.router.navigate(["/properties"]);
}
public toCalc(id: string) {
this.router.navigate(["/calculator", id]);
this.setActiveCalc(id);
setTimeout(() => { // @WARNING clodo trick to wait for Angular refresh
this.scrollToLatestQuicknav(id);
}, 50);
}
/**
* restarts a fresh session by closing all calculators
*/
public emptySession() {
const dialogRef = this.confirmEmptySessionDialog.open(
DialogConfirmEmptySessionComponent,
{ disableClose: true }
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.doEmptySession();
}
});
}
public doEmptySession() {
// temporarily disable notifications
const oldNotifState = this.appSetupService.enableNotifications;
this.appSetupService.enableNotifications = false;
// close all calculators
for (const c of this._calculators) {
const form = this.formulaireService.getFormulaireFromId(c.uid);
this.formulaireService.requestCloseForm(form.uid);
}
// just to be sure, get rid of any Nub possibly stuck in session without any form attached
Session.getInstance().clear();
Session.getInstance().documentation = "";
// just to be sure, get rid of any Formulaire possibly stuck in the service without any Nub attached
this.formulaireService.clearFormulaires();
// restore notifications
this.appSetupService.enableNotifications = oldNotifState;
}
public loadSession() {
// création du dialogue de sélection des formulaires à sauver
const dialogRef = this.loadSessionDialog.open(
DialogLoadSessionComponent,
{ disableClose: false }
);
dialogRef.afterClosed().subscribe(result => {
if (result) {
if (result.emptySession) {
this.doEmptySession();
}
this.loadSessionFile(result.file, result.calculators);
}
});
}
public async loadSessionFile(f: File|string, info?: any) {
// notes merge detection: was there already some notes ?
const existingNotes = Session.getInstance().documentation;
// load
try {
const data = await this.formulaireService.loadSession(f, info);
if (data.hasErrors) {
this.notificationsService.notify(this.intlService.localizeText("ERROR_PROBLEM_LOADING_SESSION"), 3500);
} else {
if (data.loaded && data.loaded.length > 0) {
if (!isDevMode()) {
this.matomoTracker.trackEvent("userAction", "loadSession");
}
// notes merge detection: was there already some notes ?
const currentNotes = Session.getInstance().documentation;
if (existingNotes !== "" && currentNotes !== existingNotes) {
this.notificationsService.notify(this.intlService.localizeText("WARNING_SESSION_LOAD_NOTES_MERGED"), 3500);
}
// go to calc or diagram depending on what was loaded
AUBRY JEAN-PASCAL
committed
if (data.loaded.length > 1 && currentNotes) {
this.toNotes();
}
else if(data.loaded.length > 1 && !currentNotes) {
this.toDiagram()
}
else {
}
} catch (err) {
this.notificationsService.notify(this.intlService.localizeText("ERROR_LOADING_SESSION"), 3500);
console.error("error loading session - ", err);
// rollback to ensure session is clean
this.doEmptySession();
}
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
}
/**
* Demande au client d'envoyer un email (génère un lien mailto:), pré-rempli
* avec un texte standard, et le contenu de la session au format JSON
*/
public reportBug() {
const recipient = "bug@cassiopee.g-eau.fr";
const subject = "[ISSUE] " + this.intlService.localizeText("INFO_REPORT_BUG_SUBJECT");
let body = this.intlService.localizeText("INFO_REPORT_BUG_BODY");
// add session description
// get all forms
const list = [];
for (const c of this._calculators) {
list.push({
title: c.title,
uid: c.uid,
selected: true
});
}
let session = this.buildSessionFile(list);
// compress
session = pako.deflate(session, { to: "string" }); // gzip (zlib)
session = btoa(session); // base64
body += session + "\n";
body = encodeURIComponent(body);
const mailtoURL = `mailto:${recipient}?subject=${subject}&body=${body}`;
// temporarily disable tab closing alert, as tab won't be closed for real
this.appSetupService.warnBeforeTabClose = false;
window.location.href = mailtoURL;
this.appSetupService.warnBeforeTabClose = true;
}
public get revisionInfo(): any {
return {
jalhyd: {
date: jalhydDateRev,
version: jalhydVersion,
nghyd: {
date: nghydDateRev,
version: nghydVersion
}
};
}
private logRevisionInfo() {
const ri = this.revisionInfo;
console.log("JaLHyd", ri.jalhyd.date, ri.jalhyd.version);
console.log("ngHyd", ri.nghyd.date, ri.nghyd.version);
}
/**
* sauvegarde du/des formulaires
* @param form formulaire à sélectionner par défaut dans la liste
*/
public saveForm(form?: FormulaireDefinition) {
// liste des formulaires
const list = [];
for (const c of this._calculators) {
const uid = c["uid"];
const nub = Session.getInstance().findNubByUid(uid);
let required = nub.getTargettedNubs().map((req) => {
return req.uid;
});
required = required.filter(
(item, index) => required.indexOf(item) === index // deduplicate
);
list.push({
"children": nub.getChildren().map((child) => {
return child.uid;
}),
"requires": required,
"selected": form ? (uid === form.uid) : true,
"title": c["title"],
"uid": uid
});
// dialogue de sélection des formulaires à sauver
const dialogRef = this.saveSessionDialog.open(
DialogSaveSessionComponent,
data: {
calculators: list
},
disableClose: false
);
dialogRef.afterClosed().subscribe(result => {
if (result) {
let name = result.filename;
// ajout extension ".json"
const re = /.+\.json/;
const match = re.exec(name.toLowerCase());
if (match === null) {
name = name + ".json";
}
this.saveSession(result.calculators, name);
}
});
}
/**
* Moves the view to one of the Quicknav anchors in the page, and saves this anchor
* as the latest visited, in _calculators list
* @param itemId a Quicknav anchor id (ex: "input" or "results")
public scrollToQuicknav(itemId: string, behavior: ScrollBehavior = "smooth") {
const idx = this.getCalculatorIndexFromId(this.currentFormId);
let succeeded = false;
if (idx > -1) {
const id = QuicknavComponent.prefix + itemId;
// Scroll https://stackoverflow.com/a/56391657/5986614
const element = document.getElementById(id);
if (element && element.offsetParent !== null) { // offsetParent is null when element is not visible
const yCoordinate = element.getBoundingClientRect().top + window.pageYOffset;
window.scrollTo({
top: yCoordinate - 60, // substract a little more than navbar height
behavior: behavior
});
succeeded = true;
// Save position
this._calculators[idx].latestAnchor = itemId;
}
mathias.chouet
committed
}
if (!succeeded) {
// throw an error so that caller CalculatorComponent.scrollToResults()
// switches to plan B, in case we're trying to scroll to results pane
// after a module is calculated
throw Error("unable to scroll to quicknav anchor");
}
/**
* Moves the view to the latest known Quicknav anchor of the current module
*/
public scrollToLatestQuicknav(formId: string) {
// Get position
const idx = this.getCalculatorIndexFromId(formId);
if (idx > -1) {
const itemId = this._calculators[idx].latestAnchor;
// Scroll
if (itemId) {
this.scrollToQuicknav(itemId, "auto");
}
}
public dropCalcButton(event: CdkDragDrop<string[]>) {
moveItemInArray(this.calculators, event.previousIndex, event.currentIndex);
francois.grand
committed
}
public get docIndexPath(): string {
return "assets/docs/" + this.appSetupService.language + "/index.html";
}
/**
* Returns a string representing the running platform :
*/
public getRunningPlatform(): string {
let runningPlatform = "browser";
if (navigator.userAgent.toLowerCase().indexOf("electron") > -1) {
runningPlatform = "electron";
}
// console.log(">> running platform: ", runningPlatform);
return runningPlatform;
}
/**
* détection de la fermeture de la page/navigateur et demande de confirmation
*/
@HostListener("window:beforeunload", [ "$event" ]) confirmExit($event) {
if (
this.appSetupService.warnBeforeTabClose
&& ! isDevMode() // otherwise prevents dev server to reload app after recompiling
) {
// affecter une valeur différente de null provoque l'affichage d'un dialogue de confirmation, mais le texte n'est pas affiché
$event.returnValue = "Your data will be lost !";
}
@HostListener("keydown", ["$event"]) onKeyDown($event: any) {
if ($event.which === 38 || $event.which === 40) { // up / down arrow
if ($event.srcElement.type === "number") {
$event.preventDefault();
François Grand
committed
/**
* display a confirmation display upon request from UserConfirmationService
*/
private displayConfirmationDialog(title: string, text: string) {
const dialogRef = this.confirmDialog.open(
DialogConfirmComponent,
{
data: {
title: title,
text: text
},
disableClose: true
}
);
dialogRef.afterClosed().subscribe(result => {
// reply to UserConfirmationService
this.userConfirmationService.postConfirmation(this, { "confirm": result });
});
}