diff --git a/geoportal/geoportailv3_geoportal/static-ngeo/js/apps/MainController.js b/geoportal/geoportailv3_geoportal/static-ngeo/js/apps/MainController.js index 56384fbcc..bb52ad29e 100644 --- a/geoportal/geoportailv3_geoportal/static-ngeo/js/apps/MainController.js +++ b/geoportal/geoportailv3_geoportal/static-ngeo/js/apps/MainController.js @@ -196,7 +196,10 @@ import '../../less/geoportailv3.less'; import '../lux-iframe-preview/lux-iframe-preview.ts'; import '../gmf-lidar-panel/gmf-lidar-panel.ts'; import '../lidar-plot/lidar-plot.ts'; -import '../offlineWebComponent.ts'; +import '../offline/lux-offline/lux-offline.component.ts'; +import '../offline/lux-offline/lux-offline-alert.component.ts'; +import '../offline/lux-offline/lux-offline-reload.component.ts'; +import '../offline/lux-offline/lux-offline-error.component.ts'; import DragRotate from 'ol/interaction/DragRotate'; import {platformModifierKeyOnly} from 'ol/events/condition'; diff --git a/geoportal/geoportailv3_geoportal/static-ngeo/js/apps/main.html.ejs b/geoportal/geoportailv3_geoportal/static-ngeo/js/apps/main.html.ejs index 130b718ea..0ea68e5b5 100644 --- a/geoportal/geoportailv3_geoportal/static-ngeo/js/apps/main.html.ejs +++ b/geoportal/geoportailv3_geoportal/static-ngeo/js/apps/main.html.ejs @@ -376,20 +376,25 @@
{{mainCtrl.selectedLayers[0].get('label') | translate}}
+ + + diff --git a/geoportal/geoportailv3_geoportal/static-ngeo/js/offline/lux-offline/lux-offline-alert.component.ts b/geoportal/geoportailv3_geoportal/static-ngeo/js/offline/lux-offline/lux-offline-alert.component.ts new file mode 100644 index 000000000..32d776760 --- /dev/null +++ b/geoportal/geoportailv3_geoportal/static-ngeo/js/offline/lux-offline/lux-offline-alert.component.ts @@ -0,0 +1,88 @@ +import 'jquery'; +import 'bootstrap/js/modal.js'; +import i18next from 'i18next'; +import { combineLatestWith } from 'rxjs'; +import {LuxBaseElement} from '../../LuxBaseElement'; +import {html} from 'lit'; +import {customElement, state, query} from 'lit/decorators.js'; +import { LuxOfflineServiceInstance, LuxOfflineService } from './lux-offline.service'; +import { OfflineStatus } from './lux-offline.model'; + +@customElement('lux-offline-alert') +export class LuxOfflineAlert extends LuxBaseElement { + + @state() + private status; + + @state() + private subscription; + + @query('.modal-alert') + private modal: HTMLElement; + + private prevStatus; + private offlineService: LuxOfflineService; + + constructor() { + super(); + this.offlineService = LuxOfflineServiceInstance; + this.prevStatus = this.offlineService.status$.getValue(); + this.subscription = this.offlineService.status$.pipe( + combineLatestWith(this.offlineService.tileError$) + ).subscribe(([status, error])=> { + if ((!error + && status === OfflineStatus.UPDATE_AVAILABLE) + && (this.prevStatus === undefined + || this.prevStatus === OfflineStatus.UNINITIALIZED)) { + $(this.modal).modal('show'); + } + if (status === OfflineStatus.UP_TO_DATE) { + $(this.modal).modal('hide'); + } + this.status = status; + this.prevStatus = status + }); + } + + disconnectedCallback() { + this.subscription.unsubscribe(); + super.disconnectedCallback(); + } + + render() { + return html` + + `; + } + + updateTiles(){ + if (this.status === OfflineStatus.UPDATE_AVAILABLE) { + this.offlineService.updateTiles() + } + } + + createRenderRoot() { + // no shadow dom + return this; + } +} \ No newline at end of file diff --git a/geoportal/geoportailv3_geoportal/static-ngeo/js/offline/lux-offline/lux-offline-error.component.ts b/geoportal/geoportailv3_geoportal/static-ngeo/js/offline/lux-offline/lux-offline-error.component.ts new file mode 100644 index 000000000..fa049c902 --- /dev/null +++ b/geoportal/geoportailv3_geoportal/static-ngeo/js/offline/lux-offline/lux-offline-error.component.ts @@ -0,0 +1,72 @@ +import 'jquery'; +import 'bootstrap/js/modal.js'; +import i18next from 'i18next'; +import {LuxBaseElement} from '../../LuxBaseElement'; +import {html} from 'lit'; +import {customElement, state, query, property} from 'lit/decorators.js'; +import { LuxOfflineServiceInstance, LuxOfflineService } from './lux-offline.service'; +import { OfflineStatus } from './lux-offline.model'; + +@customElement('lux-offline-error') +export class LuxOfflineError extends LuxBaseElement { + + @state() + private tileError; + + @state() + private subscription; + + @query('.modal-error') + private modal: HTMLElement; + + private offlineService: LuxOfflineService; + + constructor() { + super(); + this.offlineService = LuxOfflineServiceInstance; + this.subscription = this.offlineService.tileError$.subscribe((tileError)=> { + if (tileError) { + $(this.modal).modal('show'); + } else { + $(this.modal).modal('hide'); + } + this.tileError = tileError; + }); + } + + disconnectedCallback() { + this.subscription.unsubscribe(); + super.disconnectedCallback(); + } + + render() { + return html` + + `; + } + + updateTiles(){ + this.offlineService.updateTiles() + } + + createRenderRoot() { + // no shadow dom + return this; + } +} \ No newline at end of file diff --git a/geoportal/geoportailv3_geoportal/static-ngeo/js/offline/lux-offline/lux-offline-reload.component.ts b/geoportal/geoportailv3_geoportal/static-ngeo/js/offline/lux-offline/lux-offline-reload.component.ts new file mode 100644 index 000000000..b62098fe3 --- /dev/null +++ b/geoportal/geoportailv3_geoportal/static-ngeo/js/offline/lux-offline/lux-offline-reload.component.ts @@ -0,0 +1,76 @@ +import 'jquery'; +import 'bootstrap/js/modal.js'; +import i18next from 'i18next'; +import { combineLatestWith } from 'rxjs'; +import {LuxBaseElement} from '../../LuxBaseElement'; +import {html} from 'lit'; +import {customElement, state, query} from 'lit/decorators.js'; +import { LuxOfflineServiceInstance, LuxOfflineService } from './lux-offline.service'; +import { OfflineStatus } from './lux-offline.model'; + +@customElement('lux-offline-reload') +export class LuxReloadAlert extends LuxBaseElement { + + @state() + private status; + + private prevStatus; + + @query('.modal-reload') + private modal: HTMLElement; + + private offlineService: LuxOfflineService + + constructor() { + super(); + this.offlineService = LuxOfflineServiceInstance + this.prevStatus = this.offlineService.status$.getValue(); + this.offlineService.status$.pipe( + combineLatestWith(this.offlineService.tileError$) + ).subscribe(([status, error])=> { + if ((status === OfflineStatus.UP_TO_DATE || + status === OfflineStatus.DELETED) + && this.prevStatus !== undefined + && this.prevStatus !== OfflineStatus.UNINITIALIZED + && !error) { + if (status !== this.prevStatus) { + $(this.modal).modal('show'); + } + } + this.prevStatus = status; + }); + $(this.modal).modal('hide'); + } + + render() { + return html` + + `; + } + + reloadApp(){ + location.reload() + $(this.modal).modal('hide'); + } + + createRenderRoot() { + // no shadow dom + return this; + } +} diff --git a/geoportal/geoportailv3_geoportal/static-ngeo/js/offline/lux-offline/lux-offline.component.ts b/geoportal/geoportailv3_geoportal/static-ngeo/js/offline/lux-offline/lux-offline.component.ts new file mode 100644 index 000000000..95f39d022 --- /dev/null +++ b/geoportal/geoportailv3_geoportal/static-ngeo/js/offline/lux-offline/lux-offline.component.ts @@ -0,0 +1,102 @@ +import i18next from 'i18next'; +import {LuxBaseElement} from '../../LuxBaseElement'; +import {html} from 'lit'; +import {customElement, state, property} from 'lit/decorators.js'; +import { LuxOfflineServiceInstance, LuxOfflineService } from './lux-offline.service'; +import { OfflineStatus } from './lux-offline.model'; + +@customElement('lux-offline') +export class LuxOffline extends LuxBaseElement { + + @property({type: Boolean}) + disabled: boolean = false; + + @property() + private bar; + + @property() + private scope; + + @state() + private menuDisplayed; + + @state() + private status; + + @state() + private subscription; + + private offlineService: LuxOfflineService; + + constructor() { + super(); + this.offlineService = LuxOfflineServiceInstance; + this.subscription = this.offlineService.status$.subscribe((status)=> { + this.status = status; + }); + } + + disconnectedCallback() { + this.subscription.unsubscribe(); + super.disconnectedCallback(); + } + + renderMenu() { + return html` +
+
+ ${i18next.t('Update offline data')} + ${this.status===OfflineStatus.IN_PROGRESS + ? html `` + : '' + } +
+
+
+ ${i18next.t('Delete offline data')} +
+
+
+ `; + } + + render() { + return html` +
+ + + +
+ ${this.menuDisplayed ? this.renderMenu() : ""} + `; + } + + updateTiles(){ + if (this.status === OfflineStatus.UPDATE_AVAILABLE) { + this.offlineService.updateTiles() + } + } + + deleteTiles(){ + if (this.status !== OfflineStatus.IN_PROGRESS) { + this.offlineService.deleteTiles() + } + } + + toggleMenu() { + this.menuDisplayed = !this.menuDisplayed; + if (this.menuDisplayed) { + this.offlineService.checkTiles(true); + } + this.bar.toggleFullOffline(); + this.scope.$digest() + } + + createRenderRoot() { + // no shadow dom + return this; + } +} diff --git a/geoportal/geoportailv3_geoportal/static-ngeo/js/offline/lux-offline/lux-offline.model.ts b/geoportal/geoportailv3_geoportal/static-ngeo/js/offline/lux-offline/lux-offline.model.ts new file mode 100644 index 000000000..2518edb8d --- /dev/null +++ b/geoportal/geoportailv3_geoportal/static-ngeo/js/offline/lux-offline/lux-offline.model.ts @@ -0,0 +1,32 @@ +export enum OfflineStatus { + IN_PROGRESS = 'IN_PROGRESS', + UPDATE_AVAILABLE = 'UPDATE_AVAILABLE', + UP_TO_DATE = 'UP_TO_DATE', + UNINITIALIZED = 'NO_INIT', + DELETED = 'DELETED' +} +export interface TilePackages { + ALL: string[], + IN_PROGRESS: string[], + UPDATE_AVAILABLE: string[], + UP_TO_DATE: string[], + UNAVAILABLE: string[], +}; +export enum PackageToSkip { + HILLSHADE = "hillshade-lu" +} +export interface StatusJson { + 'countours-lu': TileStatus, + 'fonts': TileStatus, + 'hillshade-lu': TileStatus, + 'omt-geoportail-lu': TileStatus, + 'omt-topo-geoportail-lu': TileStatus, + 'resources': TileStatus, + 'sprites': TileStatus +} +interface TileStatus { + status: string, + filesize: string, + current: string, + available: string +} diff --git a/geoportal/geoportailv3_geoportal/static-ngeo/js/offline/lux-offline/lux-offline.service.ts b/geoportal/geoportailv3_geoportal/static-ngeo/js/offline/lux-offline/lux-offline.service.ts new file mode 100644 index 000000000..4ede2d09f --- /dev/null +++ b/geoportal/geoportailv3_geoportal/static-ngeo/js/offline/lux-offline/lux-offline.service.ts @@ -0,0 +1,146 @@ +import { Observable, BehaviorSubject, of } from 'rxjs'; +import { OfflineStatus, PackageToSkip, StatusJson, TilePackages } from './lux-offline.model'; + +export class LuxOfflineService { + public status$: BehaviorSubject = new BehaviorSubject(OfflineStatus.UNINITIALIZED); + public tileError$: BehaviorSubject = new BehaviorSubject(false); + private tilePackages: TilePackages; + private server: string; + private checkTimeout: number; + + constructor(){ + const searchParams = new URLSearchParams(document.location.search); + const server = searchParams.get('embeddedserver'); + const proto = searchParams.get('embeddedserverprotocol') || 'http'; + this.baseURL = (server ? `${proto}://${server}` : "http://localhost:8766/map/"); + if (server) { + this.checkTiles(); + } + this.server = server; + } + + public hasLocalServer(){ + return !!this.server; + } + + public checkTiles(silent?: boolean) { + this.tileError$.next(false); + fetch(this.baseURL + "/check") + .then((response) => response.json()) + .then((statusJson) => this.setStatus(statusJson)) + .catch((error) => { + this.handleError(error, silent) + }); + } + + private setStatus(statusJson: StatusJson) { + this.tilePackages = { + ALL: [], + IN_PROGRESS: [], + UPDATE_AVAILABLE: [], + UP_TO_DATE: [], + UNAVAILABLE: [], + } + for(const tileKey in statusJson) { + // skip package hillshade (too large for transfers) + if (tileKey == PackageToSkip.HILLSHADE) { + continue; + } + this.tilePackages.ALL.push(tileKey); + if (!statusJson[tileKey].available) { + this.tilePackages.UNAVAILABLE.push(tileKey); + } else if (statusJson[tileKey].status === OfflineStatus.IN_PROGRESS) { + this.tilePackages.IN_PROGRESS.push(tileKey); + } else if ((statusJson[tileKey].current < statusJson[tileKey].available) + || (!statusJson[tileKey].current && statusJson[tileKey].available) + ) { + this.tilePackages.UPDATE_AVAILABLE.push(tileKey); + } else { + this.tilePackages.UP_TO_DATE.push(tileKey); + } + } + if (this.tilePackages.UNAVAILABLE.length > 0) { + this.handleError('AVAILABLE FALSY'); + } else if (this.tilePackages.IN_PROGRESS.length > 0) { + this.status$.next(OfflineStatus.IN_PROGRESS); + this.reCheckTilesTimeout(2500); + } else if (this.tilePackages.UPDATE_AVAILABLE.length > 0) { + if (this.status$.getValue() === OfflineStatus.IN_PROGRESS) { + this.handleError('IN_PROGRESS => UPDATE_AVAILABLE'); + } + this.status$.next(OfflineStatus.UPDATE_AVAILABLE); + } else { + this.status$.next(OfflineStatus.UP_TO_DATE); + } + } + + public updateTiles() { + this.tilePackages.UPDATE_AVAILABLE.forEach(tilePackage => { + this.sendRequest(tilePackage, 'PUT'); + }) + } + + public deleteTiles() { + this.tilePackages.ALL.forEach(tilePackage => { + this.sendRequest(tilePackage, 'DELETE'); + }) +} + + private sendRequest(tiles: string, method: string) { + fetch(this.baseURL + "/map/" + tiles, {method}) + .then((data) => { + console.log('Success:', data); + }) + .catch((error) => { + this.handleError(error); + }) + .finally(() => { + if (method === 'DELETE') { + //prevents a network request for 'DELETE' + if (this.tilePackages.UP_TO_DATE.length > 0) { + this.tilePackages.UPDATE_AVAILABLE = [...this.tilePackages.UP_TO_DATE] + this.status$.next(OfflineStatus.DELETED) + } + } else { + this.reCheckTilesTimeout(250) + } + }); + } + + /** + * There are three possible erroneous responses that can be returned by the local backend + * - A server error that is caught via the fetch().catch() statement + * - A tile package that has been in status 'IN_PROGRESS' turns back into status 'UPDATE_AVAILABLE' + * - A tile package has a falsy value in its 'available' property + * In all three cases handleError() is called from within this service. + */ + private handleError(error: Error | string, silent?: boolean) { + if (!silent) { + this.tileError$.next(true); + } + console.error('Error:', error); + console.log(this.tilePackages) + clearTimeout(this.checkTimeout); + if (this.tilePackages.IN_PROGRESS.length > 0) { + this.tilePackages.UPDATE_AVAILABLE = [...this.tilePackages.IN_PROGRESS]; + this.tilePackages.IN_PROGRESS = []; + } + if (this.tilePackages.UNAVAILABLE.length > 0) { + this.tilePackages.UPDATE_AVAILABLE = [...this.tilePackages.UNAVAILABLE]; + this.tilePackages.UNAVAILABLE = []; + } + this.status$.next(OfflineStatus.UPDATE_AVAILABLE); + } + + private reCheckTilesTimeout(timeout) { + // prevent multiple timers + if (this.checkTimeout !== undefined) { + clearTimeout(this.checkTimeout); + } + this.checkTimeout = setTimeout(()=> { + this.checkTiles(); + }, timeout) + } +} + +export const LuxOfflineServiceInstance = new LuxOfflineService(); diff --git a/geoportal/geoportailv3_geoportal/static-ngeo/js/offline/offlinebar.js b/geoportal/geoportailv3_geoportal/static-ngeo/js/offline/offlinebar.js index 317f89026..3d5a8a5dd 100644 --- a/geoportal/geoportailv3_geoportal/static-ngeo/js/offline/offlinebar.js +++ b/geoportal/geoportailv3_geoportal/static-ngeo/js/offline/offlinebar.js @@ -26,11 +26,15 @@ } toggleNgeoOffline() { - this.ngeoOffline_ = !this.ngeoOffline_; + if (!this.isFullOfflineActive()) { + this.ngeoOffline_ = !this.ngeoOffline_; + } } toggleFullOffline() { - this.fullOffline_ = !this.fullOffline_; + if (!this.isNgeoOfflineActive()) { + this.fullOffline_ = !this.fullOffline_; + } } }; diff --git a/geoportal/geoportailv3_geoportal/static-ngeo/js/offlineNgeoComponent.html b/geoportal/geoportailv3_geoportal/static-ngeo/js/offlineNgeoComponent.html index 2ae176c14..49e05aa9b 100644 --- a/geoportal/geoportailv3_geoportal/static-ngeo/js/offlineNgeoComponent.html +++ b/geoportal/geoportailv3_geoportal/static-ngeo/js/offlineNgeoComponent.html @@ -1,9 +1,13 @@ -
+
- + -
+
diff --git a/geoportal/geoportailv3_geoportal/static-ngeo/js/offlineWebComponent.ts b/geoportal/geoportailv3_geoportal/static-ngeo/js/offlineWebComponent.ts deleted file mode 100644 index 530367963..000000000 --- a/geoportal/geoportailv3_geoportal/static-ngeo/js/offlineWebComponent.ts +++ /dev/null @@ -1,160 +0,0 @@ -import i18next from 'i18next'; -import {LuxBaseElement} from './LuxBaseElement'; -import {html} from 'lit'; -import {customElement, state, property} from 'lit/decorators.js'; - -@customElement('lux-offline') -export class LuxOffline extends LuxBaseElement { - - @property({type: Boolean}) - disabled: boolean = false; - - @state() - private server; - - @state() - private menuDisplayed; - - @state() - private tilePackages = { - ALL: [], - IN_PROGRESS: [], - UPDATE_AVAILABLE: [], - UP_TO_DATE: [] - }; - - @state() - private status; - - private checkTimeout; - - constructor() { - super(); - const searchParams = new URLSearchParams(document.location.search); - const server = searchParams.get('embeddedserver'); - const proto = searchParams.get('embeddedserverprotocol') || 'http'; - this.baseURL = (server ? `${proto}://${server}` : "http://localhost:8766/map/"); - if (server) { - this.checkTiles(); - } - this.server = server; - } - - renderMenu() { - return html` -
-
- ${i18next.t('Update offline data')} - ${this.status==="IN_PROGRESS" - ? html `` - : '' - } -
-
-
- ${i18next.t('Delete offline data')} -
-
-
- `; - } - - render() { - return html` -
- - - -
- ${this.menuDisplayed?this.renderMenu():""} - `; - } - - toggleMenu() { - this.menuDisplayed = !this.menuDisplayed; - } - - checkTiles() { - fetch(this.baseURL + "/check") - .then((response) => response.json()) - .then((statusJson) => this.getStatus(statusJson)) - .catch((error) => { - console.error('Error:', error); - }); - } - - getStatus(tiles) { - this.tilePackages = { - ALL: [], - IN_PROGRESS: [], - UPDATE_AVAILABLE: [], - UP_TO_DATE: [] - } - for(const tileKey in tiles) { - // skip package hillshade (too large for transfers) - if (tileKey == "hillshade-lu") { - continue; - } - this.tilePackages.ALL.push(tileKey); - if (tiles[tileKey].status === "IN_PROGRESS") { - this.tilePackages.IN_PROGRESS.push(tileKey); - } else if ((tiles[tileKey].current < tiles[tileKey].available) - || (!tiles[tileKey].current && tiles[tileKey].available) - ) { - this.tilePackages.UPDATE_AVAILABLE.push(tileKey); - } else { - this.tilePackages.UP_TO_DATE.push(tileKey); - } - } - if (this.tilePackages.IN_PROGRESS.length > 0) { - this.status = 'IN_PROGRESS'; - } else if (this.tilePackages.UPDATE_AVAILABLE.length > 0) { - this.status = 'UPDATE_AVAILABLE'; - } else { - this.status = 'UP_TO_DATE'; - } - if (this.status == 'IN_PROGRESS') { - this.reCheckTilesTimeout(2500); - } - } - - updateTiles() { - this.tilePackages.UPDATE_AVAILABLE.forEach(tilePackage => { - this.sendRequest(tilePackage, 'PUT'); - }) - } - - deleteTiles() { - // keep resources, so that vector maps remain operational - this.tilePackages.ALL.filter(tp => tp != 'resources').forEach(tilePackage => { - this.sendRequest(tilePackage, 'DELETE'); - }) - } - - sendRequest(tiles: string, method: string) { - fetch(this.baseURL + "/map/" + tiles, {method}) - .then((data) => { - console.log('Success:', data); - }) - .catch((error) => { - console.error('Error:', error); - }) - .finally(() => {this.reCheckTilesTimeout(250)}); - } - - reCheckTilesTimeout(timeout) { - // prevent multiple timers - if (this.checkTimeout !== undefined) { - clearTimeout(this.checkTimeout); - } - this.checkTimeout = setTimeout(()=> { - this.checkTiles(); - }, timeout) - } - - createRenderRoot() { - // no shadow dom - return this; - } -} diff --git a/geoportal/geoportailv3_geoportal/static-ngeo/less/icons.less b/geoportal/geoportailv3_geoportal/static-ngeo/less/icons.less index d2c8ec72e..6f6096745 100644 --- a/geoportal/geoportailv3_geoportal/static-ngeo/less/icons.less +++ b/geoportal/geoportailv3_geoportal/static-ngeo/less/icons.less @@ -18,6 +18,16 @@ font-weight: normal; font-style: normal; } +/* Font that inlcudes all lux icons from the other font files defined in this CSS +** which will be used by the web components. To view or edit file use a tool like fontforge. +** Icon codes are between "e000" and "e064". +*/ +@font-face { + font-family: "geoportail-icons-wc"; + src: url("../webfonts/geoportail-icons-wc.woff") format("woff"); + font-weight: normal; + font-style: normal; +} @font-face { font-family: 'apart-geoportail'; src: url('../webfonts/apart-geoportail.eot'); diff --git a/geoportal/geoportailv3_geoportal/static-ngeo/less/offline.less b/geoportal/geoportailv3_geoportal/static-ngeo/less/offline.less index b3b59d410..6e2d238b2 100644 --- a/geoportal/geoportailv3_geoportal/static-ngeo/less/offline.less +++ b/geoportal/geoportailv3_geoportal/static-ngeo/less/offline.less @@ -1,3 +1,16 @@ +lux-offline-alert { + .offline-modal { + width: 300px; + } +} + +lux-offline-reload { + .offline-modal { + width: 300px; + top: 100px; + } +} + ngeo-offline, lux-offline { div { z-index: 1; @@ -5,7 +18,7 @@ ngeo-offline, lux-offline { .main-button, .db-button { position: absolute; right: 96px; - bottom: 152px; + bottom: 151px; cursor: pointer; .no-data { color: black; @@ -14,27 +27,17 @@ ngeo-offline, lux-offline { background-color: white; text-align: center; font-size: 2.7rem; - font-family: FontAwesome; - line-height: 1.5em; color: #2980b9; width: 41px; height: 41px; border: 1px solid #8394a0; - &::after { - content: @fa-var-arrow-circle-o-down; - } - } - .offline-wc { - &::after { - content: @fa-var-database; - } } .no-data[disabled], .with-data[disabled], .offline-wc[disabled] { opacity: 0.65; cursor: not-allowed; } .with-data { - color: red; + color: red !important; } } .db-button {right: 52px} @@ -42,7 +45,7 @@ ngeo-offline, lux-offline { &.offline-mode { .main-button { .no-data, .with-data{ - color: green; + color: green !important; } } } @@ -57,7 +60,6 @@ ngeo-offline, lux-offline { } .offline-btn { - // width: 20rem; margin: 5px; i { margin-left: 6px; diff --git a/geoportal/geoportailv3_geoportal/static-ngeo/ngeo/locales/app.de.json b/geoportal/geoportailv3_geoportal/static-ngeo/ngeo/locales/app.de.json index 48f94ee55..242b3ff38 100644 --- a/geoportal/geoportailv3_geoportal/static-ngeo/ngeo/locales/app.de.json +++ b/geoportal/geoportailv3_geoportal/static-ngeo/ngeo/locales/app.de.json @@ -1,20 +1,23 @@ { "(Deactivates zoom and pan on the profile!)": "", "Altitude: ": "", + "An error occured during download": "", + "App reload needed: Offline data updated successfully": "", + "Check for offline data updates": "", "Classes": "", "Classification: ": "", "Copy": "", "custom": "", + "Delete offline data": "Offline Daten löschen", "Distance: ": "", "Draw a lidar profile": "", "Draw a line on the map to dislay the corresponding LIDAR profile. Double clic to confirm.": "", - "Delete offline data": "Offline Daten herunterladen", "Embed map": "", "Export CSV": "", "Export PNG": "", + "Full offline (only available on mobile)": "", "Intensity: ": "", "It might be offline": "", - "Full offline (only available on mobile)": "Full offline (nur mobile App)", "large": "", "Lidar profile service error": "", "loaded": "", @@ -22,10 +25,13 @@ "LOD: ": "", "Measure distances on the profile below.": "", "medium": "", + "New offline data available": "Neue Offlinedaten verfügbar", "Or did you attempt to draw a profile outside data extent?": "", "Or did you attempt to draw such a small profile that no point was returned?": "", "Profile width: ": "", + "Restart app": "", + "Retry": "", "small": "", "Take measure": "", - "Update offline data": "Offline Daten löschen" + "Update offline data": "Offline Daten herunterladen" } diff --git a/geoportal/geoportailv3_geoportal/static-ngeo/ngeo/locales/app.en.json b/geoportal/geoportailv3_geoportal/static-ngeo/ngeo/locales/app.en.json index e4def2cf1..09bab717c 100644 --- a/geoportal/geoportailv3_geoportal/static-ngeo/ngeo/locales/app.en.json +++ b/geoportal/geoportailv3_geoportal/static-ngeo/ngeo/locales/app.en.json @@ -1,20 +1,23 @@ { "(Deactivates zoom and pan on the profile!)": "", "Altitude: ": "", + "An error occured during download": "", + "App reload needed: Offline data updated successfully": "", + "Check for offline data updates": "", "Classes": "", "Classification: ": "", "Copy": "", "custom": "", + "Delete offline data": "", "Distance: ": "", "Draw a lidar profile": "", "Draw a line on the map to dislay the corresponding LIDAR profile. Double clic to confirm.": "", - "Delete offline data": "", "Embed map": "", "Export CSV": "", "Export PNG": "", + "Full offline (only available on mobile)": "", "Intensity: ": "", "It might be offline": "", - "Full offline (only available on mobile)": "", "large": "", "Lidar profile service error": "", "loaded": "", @@ -22,9 +25,12 @@ "LOD: ": "", "Measure distances on the profile below.": "", "medium": "", + "New offline data available": "", "Or did you attempt to draw a profile outside data extent?": "", "Or did you attempt to draw such a small profile that no point was returned?": "", "Profile width: ": "", + "Restart app": "", + "Retry": "", "small": "", "Take measure": "", "Update offline data": "" diff --git a/geoportal/geoportailv3_geoportal/static-ngeo/ngeo/locales/app.fr.json b/geoportal/geoportailv3_geoportal/static-ngeo/ngeo/locales/app.fr.json index cdc3fc292..b2fdd2d5b 100644 --- a/geoportal/geoportailv3_geoportal/static-ngeo/ngeo/locales/app.fr.json +++ b/geoportal/geoportailv3_geoportal/static-ngeo/ngeo/locales/app.fr.json @@ -1,20 +1,23 @@ { "(Deactivates zoom and pan on the profile!)": "(Désactive le zoom et le pan sur le profil !)", "Altitude: ": "", + "An error occured during download": "Une erreur s'est produite lors du téléchargement", + "App reload needed: Offline data updated successfully": "Rechargement de l'application nécessaire : données hors connexion mises à jour avec succès", + "Check for offline data updates": "Vérifier les mises à jour des données hors ligne", "Classes": "Classes", "Classification: ": "", "Copy": "Copier", "custom": "Taille personnalisée", + "Delete offline data": "Supprimer données hors connexion", "Distance: ": "", "Draw a lidar profile": "Dessiner un profil lidar", "Draw a line on the map to dislay the corresponding LIDAR profile. Double clic to confirm.": "Dessiner une ligne sur la carte pour afficher le profil LIDAR correspondant. Utilisez un double-clic pour terminer le profil.", - "Delete offline data": "Supprimer données hors connexion", "Embed map": "Intégrer la carte", "Export CSV": "", "Export PNG": "", + "Full offline (only available on mobile)": "", "Intensity: ": "", "It might be offline": "", - "Full offline (only available on mobile)": "Full offline (seulement mobile)", "large": "Grande", "Lidar profile service error": "", "loaded": "", @@ -22,10 +25,13 @@ "LOD: ": "", "Measure distances on the profile below.": "Mesurer des distances sur le profil ci-dessous", "medium": "Moyenne", + "New offline data available": "Mise à jour des données hors connexion disponible", "Or did you attempt to draw a profile outside data extent?": "", "Or did you attempt to draw such a small profile that no point was returned?": "", "Profile width: ": "", - "Take measure": "Mesurer", + "Restart app": "Redémarrer l'application", + "Retry": "Recommencez", "small": "Petite", + "Take measure": "Mesurer", "Update offline data": "Mettre à jour données hors connexion" } diff --git a/geoportal/geoportailv3_geoportal/static-ngeo/ngeo/locales/app.lb.json b/geoportal/geoportailv3_geoportal/static-ngeo/ngeo/locales/app.lb.json index e4def2cf1..09bab717c 100644 --- a/geoportal/geoportailv3_geoportal/static-ngeo/ngeo/locales/app.lb.json +++ b/geoportal/geoportailv3_geoportal/static-ngeo/ngeo/locales/app.lb.json @@ -1,20 +1,23 @@ { "(Deactivates zoom and pan on the profile!)": "", "Altitude: ": "", + "An error occured during download": "", + "App reload needed: Offline data updated successfully": "", + "Check for offline data updates": "", "Classes": "", "Classification: ": "", "Copy": "", "custom": "", + "Delete offline data": "", "Distance: ": "", "Draw a lidar profile": "", "Draw a line on the map to dislay the corresponding LIDAR profile. Double clic to confirm.": "", - "Delete offline data": "", "Embed map": "", "Export CSV": "", "Export PNG": "", + "Full offline (only available on mobile)": "", "Intensity: ": "", "It might be offline": "", - "Full offline (only available on mobile)": "", "large": "", "Lidar profile service error": "", "loaded": "", @@ -22,9 +25,12 @@ "LOD: ": "", "Measure distances on the profile below.": "", "medium": "", + "New offline data available": "", "Or did you attempt to draw a profile outside data extent?": "", "Or did you attempt to draw such a small profile that no point was returned?": "", "Profile width: ": "", + "Restart app": "", + "Retry": "", "small": "", "Take measure": "", "Update offline data": "" diff --git a/geoportal/geoportailv3_geoportal/static-ngeo/ngeo/src/offline/component.js b/geoportal/geoportailv3_geoportal/static-ngeo/ngeo/src/offline/component.js index 5057f6b7d..fb13e6491 100644 --- a/geoportal/geoportailv3_geoportal/static-ngeo/ngeo/src/offline/component.js +++ b/geoportal/geoportailv3_geoportal/static-ngeo/ngeo/src/offline/component.js @@ -102,7 +102,7 @@ exports.Controller = class { * @ngdoc controller * @ngname ngeoOfflineController */ - constructor($timeout, ngeoFeatureOverlayMgr, ngeoOfflineServiceManager, ngeoOfflineConfiguration, ngeoOfflineMode, ngeoNetworkStatus) { + constructor($timeout, ngeoFeatureOverlayMgr, ngeoOfflineServiceManager, ngeoOfflineConfiguration, ngeoOfflineMode, ngeoNetworkStatus, appOfflineBar) { /** * @type {angular.$timeout} @@ -133,6 +133,8 @@ exports.Controller = class { */ this.offlineMode = ngeoOfflineMode; + this.offlineBar = appOfflineBar; + /** * @type {ngeo.offline.NetworkStatus} * @export @@ -329,6 +331,7 @@ exports.Controller = class { toggleViewExtentSelection(finished) { this.menuDisplayed = false; this.selectingExtent = !this.selectingExtent; + this.offlineBar.toggleNgeoOffline(); this.map.removeLayer(this.maskLayer_); this.removeZoomConstraints_(); diff --git a/geoportal/geoportailv3_geoportal/static-ngeo/webfonts/geoportail-icons-wc.woff b/geoportal/geoportailv3_geoportal/static-ngeo/webfonts/geoportail-icons-wc.woff new file mode 100644 index 000000000..e1e397f3d Binary files /dev/null and b/geoportal/geoportailv3_geoportal/static-ngeo/webfonts/geoportail-icons-wc.woff differ diff --git a/geoportal/package.json b/geoportal/package.json index 7b3b4624e..ddf0bdaf4 100644 --- a/geoportal/package.json +++ b/geoportal/package.json @@ -108,6 +108,7 @@ "proj4": "2.4.4", "raven-js": "3.24.2", "save-csv": "^4.1.0", + "rxjs": "7.5.6", "source-map-loader": "0.2.4", "svg2ttf": "4.1.0", "ttf2eot": "2.0.0", diff --git a/tile_server/README.md b/tile_server/README.md new file mode 100644 index 000000000..43acf9685 --- /dev/null +++ b/tile_server/README.md @@ -0,0 +1,13 @@ +This basic server can be used to test the offline functionality of the mobile app. + +To run the mobile app on a desktop launch the test server with: + +``` +python3 server.py +``` + +This opens the server on localhost port 8000 + +Then go to the mobile app dev home page: + +http://localhost:8080/dev/main.html/theme/main?embeddedserver=localhost%3A8000&embeddedserverprotocol=http diff --git a/tile_server/server.py b/tile_server/server.py new file mode 100644 index 000000000..15b13a6e1 --- /dev/null +++ b/tile_server/server.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 + +from http.server import HTTPServer, BaseHTTPRequestHandler +import json + + +RES_NAMES = ["contours-lu", "hillshade-lu", "omt-geoportail-lu", + "omt-topo-geoportail-lu", "resources", "fonts", "sprites"] +MAX_STATUS = 5 +OLD_VER = "0.1.0" +NEW_VER = "0.5.2" + + +class UpdateHandler(BaseHTTPRequestHandler): + dl_status = {res: 0 for res in RES_NAMES} + res_ver = {res: OLD_VER + for res in RES_NAMES} + + @staticmethod + def get_status_string(dl_status): + if dl_status == 0: + return "UNKNOWN" + elif dl_status < MAX_STATUS: + return "IN_PROGRESS" + else: + return "DONE" + + def do_GET(self): + if self.path == "/check": + self.send_response(200, "OK") + self.send_header("Access-Control-Allow-Origin", "*") + self.end_headers() + resp = { + res: { + "status": self.get_status_string(self.dl_status[res]), + "filesize": 5000 * self.dl_status[res], + "current": self.res_ver[res], + "available": NEW_VER + } for res in RES_NAMES + } + # self.wfile.write("bla") + self.wfile.write(json.dumps(resp).encode()) + else: + # import pdb;pdb.set_trace() + self.send_response(400, "bla") + self.send_header("Access-Control-Allow-Origin", "*") + self.end_headers() + self.wfile.write(b"Test\n") + + for res in RES_NAMES: + if 0 < self.dl_status[res] < MAX_STATUS: + self.dl_status[res] += 1 + if self.dl_status[res] >= MAX_STATUS: + self.res_ver[res] = NEW_VER + + def do_PUT(self): + res = self.path.replace("/map/", "") + if res in RES_NAMES: + self.dl_status[res] = 1 + + self.send_response(202, "ACCEPTED") + self.send_header("Access-Control-Allow-Origin", "*") + self.end_headers() + self.wfile.write(f"Update started for {res}\n".encode()) + + else: + self.send_response(404, "NOT FOUND") + self.send_header("Access-Control-Allow-Origin", "*") + self.end_headers() + self.wfile.write(f"Resource {res} not found.\n".encode()) + + def do_DELETE(self): + res = self.path.replace("/map/", "") + if res in RES_NAMES: + self.dl_status[res] = 0 + self.res_ver[res] = OLD_VER + + self.send_response(202, "ACCEPTED") + self.send_header("Access-Control-Allow-Origin", "*") + self.end_headers() + self.wfile.write(f"Deleted {res}\n".encode()) + + else: + self.send_response(404, "NOT FOUND") + self.send_header("Access-Control-Allow-Origin", "*") + self.end_headers() + self.wfile.write(f"Resource {res} not found.\n".encode()) + + def do_OPTIONS(self): + self.send_response(200, "OK") + self.send_header("Access-Control-Allow-Origin", "*") + self.send_header("Access-Control-Allow-Methods", "PUT, DELETE") + self.end_headers() + + +server_address = ('', 8000) +httpd = HTTPServer(server_address, UpdateHandler) +httpd.serve_forever()