@geogirafe/lib-geoportal 1.1.0-dev.2602404359 → 1.1.0-dev.2610902439
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/about/component.js +1 -1
- package/components/getdirections/component.js +1 -0
- package/components/prototypebanner/component.js +1 -1
- package/components/treeview/treeviewroot/style.css +1 -1
- package/package.json +4 -3
- package/templates/index.html +1 -1
- package/templates/public/about.json +1 -1
- package/templates/tsconfig.json +1 -1
- package/templates/vite.config.js +1 -1
- package/tools/app/geogirafeapp.js +15 -6
- package/tools/configuration/girafeconfig.d.ts +2 -0
- package/tools/configuration/girafeconfig.js +23 -21
- package/tools/i18n/i18nmanager.js +1 -1
- package/tools/main.d.ts +2 -0
- package/tools/main.js +1 -0
- package/tools/offline/offlinemanager.d.ts +5 -4
- package/tools/offline/offlinemanager.js +50 -80
- package/tools/sw/service-worker.d.ts +1 -0
- package/tools/sw/service-worker.js +94 -0
- package/tools/sw/service-worker.tools.d.ts +83 -0
- package/tools/sw/service-worker.tools.js +321 -0
- package/tools/tests/mockconfig.d.ts +1 -1
- package/tools/tests/mockconfig.js +1 -1
- package/service-worker.js +0 -331
|
@@ -11,7 +11,7 @@ class AboutComponent extends GirafeHTMLElement {
|
|
|
11
11
|
.panel{color:var(--text-color)}.panel>div{padding:1rem}img.logo{filter:var(--svg-filter);opacity:.8;height:4rem}.list{flex-direction:column;margin-top:1rem;display:flex}
|
|
12
12
|
</style>
|
|
13
13
|
<style>${this.customStyle}</style>
|
|
14
|
-
<div class="panel"><div><img class="logo" alt="geogirafe-logo" src="images/logo/horizontal_black.svg"><div class="list"><div class="elem"><span i18n="Version">Version</span> : ${this.version}</div><div class="elem"><span i18n="Build">Build</span> : ${this.build}</div><div class="elem"><span i18n="Date">Date</span> : ${this.date}</div></div><div class="list"><div class="elem"><span i18n="Source">Source</span>: <a href="https://gitlab.com/geogirafe/gg-viewer" i18n="GitLab" rel="noopener" target="_blank">GitLab</a></div><div class="elem"><span i18n="Documentation">Documentation</span>: <a href="https://doc.
|
|
14
|
+
<div class="panel"><div><img class="logo" alt="geogirafe-logo" src="images/logo/horizontal_black.svg"><div class="list"><div class="elem"><span i18n="Version">Version</span> : ${this.version}</div><div class="elem"><span i18n="Build">Build</span> : ${this.build}</div><div class="elem"><span i18n="Date">Date</span> : ${this.date}</div></div><div class="list"><div class="elem"><span i18n="Source">Source</span>: <a href="https://gitlab.com/geogirafe/gg-viewer" i18n="GitLab" rel="noopener" target="_blank">GitLab</a></div><div class="elem"><span i18n="Documentation">Documentation</span>: <a href="https://doc.geogirafe.org" rel="noopener" target="_blank">https://doc.geogirafe.org</a></div></div></div></div>
|
|
15
15
|
${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
|
|
16
16
|
};
|
|
17
17
|
isPanelVisible = false;
|
|
@@ -11,7 +11,7 @@ class PrototypeBannerComponent extends GirafeHTMLElement {
|
|
|
11
11
|
a{color:#fff;text-transform:uppercase;text-decoration:none}a:visited{color:#fff}
|
|
12
12
|
</style>
|
|
13
13
|
<style>${this.customStyle}</style>
|
|
14
|
-
<a href="https://doc.
|
|
14
|
+
<a href="https://doc.geogirafe.org/" i18n="Prototype" target="_blank" rel="noopener">PROTOTYPE</a>
|
|
15
15
|
${this.htmlUnsafe(this.feedbackTemplateHtml ?? '')}`;
|
|
16
16
|
};
|
|
17
17
|
constructor() {
|
package/package.json
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
"description": "GeoGirafe is a flexible application to build online geoportals.",
|
|
4
4
|
"author": {
|
|
5
5
|
"name": "GeoGirafe PSC",
|
|
6
|
-
"url": "https://doc.
|
|
6
|
+
"url": "https://doc.geogirafe.org"
|
|
7
7
|
},
|
|
8
|
-
"version": "1.1.0-dev.
|
|
8
|
+
"version": "1.1.0-dev.2610902439",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"engines": {
|
|
11
11
|
"node": ">=20.19.0"
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"websig"
|
|
32
32
|
],
|
|
33
33
|
"license": "Apache-2.0",
|
|
34
|
-
"homepage": "https://doc.
|
|
34
|
+
"homepage": "https://doc.geogirafe.org",
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"buffer": "6.0.3",
|
|
37
37
|
"d3": "7.9.0",
|
|
@@ -83,6 +83,7 @@
|
|
|
83
83
|
"cross-env": "10.1.0",
|
|
84
84
|
"eslint": "10.2.0",
|
|
85
85
|
"eslint-plugin-es-x": "9.6.0",
|
|
86
|
+
"fake-indexeddb": "6.2.5",
|
|
86
87
|
"fast-glob": "3.3.3",
|
|
87
88
|
"fs-extra": "11.3.4",
|
|
88
89
|
"globals": "17.4.0",
|
package/templates/index.html
CHANGED
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
<span i18n="Information without public faith">Information without public faith</span>
|
|
153
153
|
<span>
|
|
154
154
|
©
|
|
155
|
-
<a href="https://doc.
|
|
155
|
+
<a href="https://doc.geogirafe.org" target="_blank">GeoGirafe</a>,
|
|
156
156
|
<a href="https://www.swisstopo.admin.ch/en/legal-basis" target="_blank">swisstopo</a>,
|
|
157
157
|
<a href="http://www.openstreetmap.org/copyright" target="_blank">OSM</a>
|
|
158
158
|
</span>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.1.0-dev.
|
|
1
|
+
{"version":"1.1.0-dev.2610902439", "build":"2610902439", "date":"18/06/2026"}
|
package/templates/tsconfig.json
CHANGED
package/templates/vite.config.js
CHANGED
|
@@ -120,7 +120,7 @@ export default defineConfig(({ command, mode }) => {
|
|
|
120
120
|
{ src: `${cesiumSource}/Workers`, dest: cesiumBaseUrl },
|
|
121
121
|
{ src: `${cesiumSource}/Assets`, dest: cesiumBaseUrl },
|
|
122
122
|
{ src: `${cesiumSource}/Widgets`, dest: cesiumBaseUrl },
|
|
123
|
-
{ src: `${geogirafeSource}/
|
|
123
|
+
{ src: `${geogirafeSource}/tools/sw/*.js`, dest: '' },
|
|
124
124
|
{ src: `${geogirafeSource}/styles/*.css`, dest: 'styles/' },
|
|
125
125
|
{ src: `${geogirafeSource}/assets/*`, dest: '' },
|
|
126
126
|
{ src: `${geogirafeSource}/tools/auth/silentlogincallback.html`, dest: '' },
|
|
@@ -50,6 +50,7 @@ import SelectionToolComponent from '../../components/selectiontool/component.js'
|
|
|
50
50
|
import AdvancedFilterComponent from '../../components/advancedfilter/component.js';
|
|
51
51
|
import FixedDimensionComponent from '../../components/drawing/fixed-dimension/component.js';
|
|
52
52
|
import GetDirectionsArtifact from '../../components/getdirections/component.js';
|
|
53
|
+
import ServiceWorkerHelper from '../utils/swhelper.js';
|
|
53
54
|
import FeedbackButtonComponent from '../../components/feedbackbutton/component.js';
|
|
54
55
|
import AddBookmarkComponent from '../../components/addbookmark/component.js';
|
|
55
56
|
import DrawingToolbarComponent from '../../components/drawing-toolbar/component.js';
|
|
@@ -168,21 +169,29 @@ export default class GeoGirafeApp {
|
|
|
168
169
|
catch (err) {
|
|
169
170
|
throw new Error(`Service worker registration failed. ${err}`, { cause: err });
|
|
170
171
|
}
|
|
171
|
-
// Communicate logging configuration to service-worker
|
|
172
172
|
const config = this.context.configManager.Config;
|
|
173
|
-
|
|
174
|
-
// Communicate
|
|
173
|
+
const serviceWorkerHelper = new ServiceWorkerHelper(sw);
|
|
174
|
+
// Communicate configuration to service-worker
|
|
175
|
+
// NOTE REG : Important : we have to wait that the config has been correctly sent to the SW
|
|
176
|
+
// Otherwise the first requests can be sent without the correct auhentication.
|
|
175
177
|
const issuerConfig = config.oauth?.issuer ?? config.gmfauth;
|
|
176
178
|
const gmfConfig = config.oauth?.geomapfish ?? config.gmfauth;
|
|
177
179
|
if (issuerConfig && gmfConfig) {
|
|
178
180
|
const issuerHostname = new URL(issuerConfig.url).hostname;
|
|
179
181
|
const audience = [...issuerConfig.audience, issuerHostname];
|
|
180
|
-
|
|
182
|
+
const swConfig = {
|
|
183
|
+
logLevel: config.general.logLevel,
|
|
181
184
|
audience: audience,
|
|
182
185
|
audienceExcludedPaths: issuerConfig.audienceExcludedPaths,
|
|
183
186
|
authMode: gmfConfig.authMode,
|
|
187
|
+
alwaysSendCookies: issuerConfig.alwaysSendCookies,
|
|
184
188
|
refererPolicy: gmfConfig.refererPolicy
|
|
185
|
-
}
|
|
189
|
+
};
|
|
190
|
+
await serviceWorkerHelper.sendMessageToServiceWorker(swConfig);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
// If no auth, we just update the log level, but we do not have to wait
|
|
194
|
+
sw.postMessage({ logLevel: config.general.logLevel });
|
|
186
195
|
}
|
|
187
196
|
await this.context.offlineManager.setServiceWorker(sw, storeVersion, dbCacheName);
|
|
188
197
|
await this.context.authManager.initialize(sw);
|
|
@@ -194,7 +203,7 @@ export default class GeoGirafeApp {
|
|
|
194
203
|
}
|
|
195
204
|
}
|
|
196
205
|
async waitForServiceWorkerActivation() {
|
|
197
|
-
const registration = await navigator.serviceWorker.register('service-worker.js');
|
|
206
|
+
const registration = await navigator.serviceWorker.register('service-worker.js', { type: 'module' });
|
|
198
207
|
if (registration.active) {
|
|
199
208
|
return registration.active;
|
|
200
209
|
}
|
|
@@ -191,6 +191,7 @@ declare class GirafeConfig {
|
|
|
191
191
|
checkSessionOnLoad: boolean;
|
|
192
192
|
audience: string[];
|
|
193
193
|
authMode: 'cookie';
|
|
194
|
+
alwaysSendCookies: boolean;
|
|
194
195
|
refererPolicy: ReferrerPolicy;
|
|
195
196
|
audienceExcludedPaths: string[];
|
|
196
197
|
};
|
|
@@ -205,6 +206,7 @@ declare class GirafeConfig {
|
|
|
205
206
|
checkSessionOnLoad: boolean;
|
|
206
207
|
audience: string[];
|
|
207
208
|
audienceExcludedPaths: string[];
|
|
209
|
+
alwaysSendCookies: boolean;
|
|
208
210
|
};
|
|
209
211
|
geomapfish: {
|
|
210
212
|
userInfoUrl: string;
|
|
@@ -49,7 +49,7 @@ class GirafeConfig {
|
|
|
49
49
|
* @param config the configuration
|
|
50
50
|
*/
|
|
51
51
|
constructor(config) {
|
|
52
|
-
// Default values are documented here : https://doc.
|
|
52
|
+
// Default values are documented here : https://doc.geogirafe.org/docs/configuration
|
|
53
53
|
// NOTE: Please adapt the documentation if necessary when doing changes here.
|
|
54
54
|
this.general = this.initConfigGeneral(config);
|
|
55
55
|
this.languages = this.initConfigLanguages(config);
|
|
@@ -127,7 +127,7 @@ class GirafeConfig {
|
|
|
127
127
|
}
|
|
128
128
|
initConfigProjections(config) {
|
|
129
129
|
if (!config.projections) {
|
|
130
|
-
throw new Error(`Configuration for projections is required. See https://doc.
|
|
130
|
+
throw new Error(`Configuration for projections is required. See https://doc.geogirafe.org/docs/configuration`);
|
|
131
131
|
}
|
|
132
132
|
return config.projections;
|
|
133
133
|
}
|
|
@@ -137,16 +137,16 @@ class GirafeConfig {
|
|
|
137
137
|
initConfigMap(config) {
|
|
138
138
|
// Map
|
|
139
139
|
if (!config.map?.srid) {
|
|
140
|
-
throw new Error(`Configuration for projections is required. See https://doc.
|
|
140
|
+
throw new Error(`Configuration for projections is required. See https://doc.geogirafe.org/docs/configuration`);
|
|
141
141
|
}
|
|
142
142
|
if (!config.map?.scales) {
|
|
143
|
-
throw new Error(`Configuration for projections is required. See https://doc.
|
|
143
|
+
throw new Error(`Configuration for projections is required. See https://doc.geogirafe.org/docs/configuration`);
|
|
144
144
|
}
|
|
145
145
|
if (!config.map?.startPosition) {
|
|
146
|
-
throw new Error(`Configuration for projections is required. See https://doc.
|
|
146
|
+
throw new Error(`Configuration for projections is required. See https://doc.geogirafe.org/docs/configuration`);
|
|
147
147
|
}
|
|
148
148
|
if (!config.map?.startZoom) {
|
|
149
|
-
throw new Error(`Configuration for projections is required. See https://doc.
|
|
149
|
+
throw new Error(`Configuration for projections is required. See https://doc.geogirafe.org/docs/configuration`);
|
|
150
150
|
}
|
|
151
151
|
return {
|
|
152
152
|
srid: config.map.srid,
|
|
@@ -179,10 +179,10 @@ class GirafeConfig {
|
|
|
179
179
|
}
|
|
180
180
|
initConfigShare(config) {
|
|
181
181
|
if (!config.share?.createUrl) {
|
|
182
|
-
throw new Error(`Configuration for share.createUrl is required. See https://doc.
|
|
182
|
+
throw new Error(`Configuration for share.createUrl is required. See https://doc.geogirafe.org/docs/configuration`);
|
|
183
183
|
}
|
|
184
184
|
if (config.share?.service === 'geogirafe' && !config.share?.getUrl) {
|
|
185
|
-
throw new Error(`Configuration for share.getUrl is required if service type is 'geogirafe'. See https://doc.
|
|
185
|
+
throw new Error(`Configuration for share.getUrl is required if service type is 'geogirafe'. See https://doc.geogirafe.org/docs/configuration`);
|
|
186
186
|
}
|
|
187
187
|
return {
|
|
188
188
|
service: config.share?.service ?? 'gmf',
|
|
@@ -204,7 +204,7 @@ class GirafeConfig {
|
|
|
204
204
|
}
|
|
205
205
|
initConfigPrint(config) {
|
|
206
206
|
if (!config.print?.url) {
|
|
207
|
-
throw new Error(`Configuration for print.url is required. See https://doc.
|
|
207
|
+
throw new Error(`Configuration for print.url is required. See https://doc.geogirafe.org/docs/configuration`);
|
|
208
208
|
}
|
|
209
209
|
config.print.attributeNames ??= ['title', 'comments', 'legend'];
|
|
210
210
|
config.print.formats ??= ['pdf', 'png'];
|
|
@@ -212,10 +212,10 @@ class GirafeConfig {
|
|
|
212
212
|
}
|
|
213
213
|
initConfigSearch(config) {
|
|
214
214
|
if (!config.search?.url) {
|
|
215
|
-
throw new Error(`Configuration for search.url is required. See https://doc.
|
|
215
|
+
throw new Error(`Configuration for search.url is required. See https://doc.geogirafe.org/docs/configuration`);
|
|
216
216
|
}
|
|
217
217
|
if (!config.search.url.includes('###SEARCHTERM###')) {
|
|
218
|
-
throw new Error(`search.url is missing the expected pattern. See https://doc.
|
|
218
|
+
throw new Error(`search.url is missing the expected pattern. See https://doc.geogirafe.org/docs/configuration`);
|
|
219
219
|
}
|
|
220
220
|
return {
|
|
221
221
|
url: config.search.url,
|
|
@@ -301,7 +301,7 @@ class GirafeConfig {
|
|
|
301
301
|
}
|
|
302
302
|
initConfigThemes(config) {
|
|
303
303
|
if (!config.themes?.url) {
|
|
304
|
-
throw new Error(`Configuration for themes.url is required. See https://doc.
|
|
304
|
+
throw new Error(`Configuration for themes.url is required. See https://doc.geogirafe.org/docs/configuration.`);
|
|
305
305
|
}
|
|
306
306
|
return {
|
|
307
307
|
url: config.themes.url,
|
|
@@ -313,7 +313,7 @@ class GirafeConfig {
|
|
|
313
313
|
}
|
|
314
314
|
initConfigLanguages(config) {
|
|
315
315
|
if (!config.languages) {
|
|
316
|
-
throw new Error(`Configuration for languages is required. See https://doc.
|
|
316
|
+
throw new Error(`Configuration for languages is required. See https://doc.geogirafe.org/docs/configuration.`);
|
|
317
317
|
}
|
|
318
318
|
return config.languages;
|
|
319
319
|
}
|
|
@@ -343,14 +343,14 @@ class GirafeConfig {
|
|
|
343
343
|
return undefined;
|
|
344
344
|
}
|
|
345
345
|
if (!config.gmfauth.url) {
|
|
346
|
-
throw new Error(`Configuration for gmfauth.url is required. See https://doc.
|
|
346
|
+
throw new Error(`Configuration for gmfauth.url is required. See https://doc.geogirafe.org/docs/configuration.`);
|
|
347
347
|
}
|
|
348
348
|
let gmfauthUrl = config.gmfauth.url;
|
|
349
349
|
if (!config.gmfauth.url.endsWith('/')) {
|
|
350
350
|
gmfauthUrl = `${config.gmfauth.url}/`;
|
|
351
351
|
}
|
|
352
352
|
if (!config.gmfauth.audience) {
|
|
353
|
-
throw new Error(`Configuration for gmfauth.audience is required. See https://doc.
|
|
353
|
+
throw new Error(`Configuration for gmfauth.audience is required. See https://doc.geogirafe.org/docs/configuration.`);
|
|
354
354
|
}
|
|
355
355
|
return {
|
|
356
356
|
url: gmfauthUrl,
|
|
@@ -358,6 +358,7 @@ class GirafeConfig {
|
|
|
358
358
|
loginRequired: config.gmfauth.loginRequired ?? false,
|
|
359
359
|
checkSessionOnLoad: config.gmfauth.checkSessionOnLoad ?? true,
|
|
360
360
|
authMode: 'cookie',
|
|
361
|
+
alwaysSendCookies: config.gmfauth.alwaysSendCookies ?? false,
|
|
361
362
|
refererPolicy: config.gmfauth.refererPolicy ?? 'strict-origin-when-cross-origin',
|
|
362
363
|
audienceExcludedPaths: config.gmfauth.audienceExcludedPaths ?? []
|
|
363
364
|
};
|
|
@@ -368,16 +369,16 @@ class GirafeConfig {
|
|
|
368
369
|
return undefined;
|
|
369
370
|
}
|
|
370
371
|
if (!config.oauth.issuer.url) {
|
|
371
|
-
throw new Error(`Configuration for oauth.issuer.url is required. See https://doc.
|
|
372
|
+
throw new Error(`Configuration for oauth.issuer.url is required. See https://doc.geogirafe.org/docs/configuration.`);
|
|
372
373
|
}
|
|
373
374
|
if (!config.oauth.issuer.clientId) {
|
|
374
|
-
throw new Error(`Configuration for oauth.issuer.clientId is required. See https://doc.
|
|
375
|
+
throw new Error(`Configuration for oauth.issuer.clientId is required. See https://doc.geogirafe.org/docs/configuration.`);
|
|
375
376
|
}
|
|
376
377
|
if (!config.oauth.issuer.audience) {
|
|
377
|
-
throw new Error(`Configuration for oauth.issuer.audience is required. See https://doc.
|
|
378
|
+
throw new Error(`Configuration for oauth.issuer.audience is required. See https://doc.geogirafe.org/docs/configuration.`);
|
|
378
379
|
}
|
|
379
380
|
if (!config.oauth.geomapfish.userInfoUrl) {
|
|
380
|
-
throw new Error(`Configuration for oauth.geomapfish.userInfoUrl is required. See https://doc.
|
|
381
|
+
throw new Error(`Configuration for oauth.geomapfish.userInfoUrl is required. See https://doc.geogirafe.org/docs/configuration.`);
|
|
381
382
|
}
|
|
382
383
|
const issuerConfig = {
|
|
383
384
|
url: config.oauth.issuer.url,
|
|
@@ -388,7 +389,8 @@ class GirafeConfig {
|
|
|
388
389
|
loginRequired: config.oauth.issuer.loginRequired ?? false,
|
|
389
390
|
checkSessionOnLoad: config.oauth.issuer.checkSessionOnLoad ?? false,
|
|
390
391
|
audience: config.oauth.issuer.audience,
|
|
391
|
-
audienceExcludedPaths: config.oauth.issuer.audienceExcludedPaths ?? []
|
|
392
|
+
audienceExcludedPaths: config.oauth.issuer.audienceExcludedPaths ?? [],
|
|
393
|
+
alwaysSendCookies: config.oauth.issuer.alwaysSendCookies ?? false
|
|
392
394
|
};
|
|
393
395
|
const geomapfishConfig = {
|
|
394
396
|
userInfoUrl: config.oauth.geomapfish.userInfoUrl,
|
|
@@ -445,7 +447,7 @@ class GirafeConfig {
|
|
|
445
447
|
if (config.filtering?.gmfLayerMetadataUrl &&
|
|
446
448
|
!(config.filtering.gmfLayerMetadataUrl.includes('###LAYERNAME###') &&
|
|
447
449
|
config.filtering.gmfLayerMetadataUrl.includes('###ATTRIBUTENAME###'))) {
|
|
448
|
-
throw new Error(`Configuration for filtering.gmfLayerMetadataUrl is missing the expected pattern. See https://doc.
|
|
450
|
+
throw new Error(`Configuration for filtering.gmfLayerMetadataUrl is missing the expected pattern. See https://doc.geogirafe.org/docs/configuration`);
|
|
449
451
|
}
|
|
450
452
|
return config.filtering ?? undefined;
|
|
451
453
|
}
|
|
@@ -42,7 +42,7 @@ class I18nManager extends GirafeSingleton {
|
|
|
42
42
|
this.loadingLanguagePromise = this.context.configManager.loadConfig().then(async () => {
|
|
43
43
|
if (this.context.configManager.Config?.languages.translations &&
|
|
44
44
|
language in this.context.configManager.Config.languages.translations) {
|
|
45
|
-
let mergedTranslations = {};
|
|
45
|
+
let mergedTranslations = this.translations[language] ?? {};
|
|
46
46
|
// Translations are loaded in the order defined in the list of files
|
|
47
47
|
// If an element is present in both results, the last value overwrite all the others
|
|
48
48
|
for (const url of this.context.configManager.Config.languages.translations[language]) {
|
package/tools/main.d.ts
CHANGED
|
@@ -104,6 +104,8 @@ export type { GgUserInteractionListener } from './state/userInteractionManager.j
|
|
|
104
104
|
export { default as UserInteractionManager } from './state/userInteractionManager.js';
|
|
105
105
|
export type { GgUserInteractionEvent } from './state/userinteractionevent.js';
|
|
106
106
|
export { gGEventDependencies, isPrimaryPointerAction, isAlternateMouseClick, isMouseWheelClick } from './state/userinteractionevent.js';
|
|
107
|
+
export type { SwState } from './sw/service-worker.tools';
|
|
108
|
+
export { swLog, SwHelper, IndexedDbHelper, CacheHelper } from './sw/service-worker.tools';
|
|
107
109
|
export { default as CustomThemesManager } from './themes/customthemesmanager.js';
|
|
108
110
|
export { default as ThemeFavoritesManager } from './themes/themefavoritesmanager.js';
|
|
109
111
|
export { DEFAULT_OPACITY, OPACITY_FOR_DEFAULT_BASEMAP } from './themes/themes-config.js';
|
package/tools/main.js
CHANGED
|
@@ -77,6 +77,7 @@ export { default as StateToggleManager } from './state/stateToggleManager.js';
|
|
|
77
77
|
export { default as StateManager } from './state/statemanager.js';
|
|
78
78
|
export { default as UserInteractionManager } from './state/userInteractionManager.js';
|
|
79
79
|
export { gGEventDependencies, isPrimaryPointerAction, isAlternateMouseClick, isMouseWheelClick } from './state/userinteractionevent.js';
|
|
80
|
+
export { swLog, SwHelper, IndexedDbHelper, CacheHelper } from './sw/service-worker.tools';
|
|
80
81
|
export { default as CustomThemesManager } from './themes/customthemesmanager.js';
|
|
81
82
|
export { default as ThemeFavoritesManager } from './themes/themefavoritesmanager.js';
|
|
82
83
|
export { DEFAULT_OPACITY, OPACITY_FOR_DEFAULT_BASEMAP } from './themes/themes-config.js';
|
|
@@ -3,14 +3,15 @@ import LayerWmts from '../../models/layers/layerwmts.js';
|
|
|
3
3
|
import { Extent } from 'ol/extent.js';
|
|
4
4
|
declare class OfflineManager extends GirafeSingleton {
|
|
5
5
|
private serviceWorker;
|
|
6
|
-
private
|
|
6
|
+
private readonly indexedDbHelper;
|
|
7
7
|
private get map();
|
|
8
8
|
private get state();
|
|
9
|
+
private database;
|
|
9
10
|
private totalLength;
|
|
10
11
|
private counter;
|
|
11
12
|
private progressCallback?;
|
|
12
|
-
private storeVersion
|
|
13
|
-
private dbCacheName
|
|
13
|
+
private storeVersion;
|
|
14
|
+
private dbCacheName;
|
|
14
15
|
private readonly tilesStoreName;
|
|
15
16
|
private readonly bboxStoreName;
|
|
16
17
|
private readonly vectorLayer;
|
|
@@ -19,7 +20,7 @@ declare class OfflineManager extends GirafeSingleton {
|
|
|
19
20
|
private registerEvents;
|
|
20
21
|
/** Exports all the WMTS tiles for the layers in parameter and store them to local cache */
|
|
21
22
|
exportWMTSTiles(bbox: Extent, wmtsLayers: LayerWmts[], progressCallback?: CallableFunction): Promise<void>;
|
|
22
|
-
|
|
23
|
+
upgradeIndexedDb(database: IDBDatabase): void;
|
|
23
24
|
/** The offline manager works with a ServiceWorker in charge of intercepting
|
|
24
25
|
* the fetch requests and read the data from the local cache if the application
|
|
25
26
|
* is offline. This method defines the servicework object to use.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
import GirafeSingleton from '../../base/GirafeSingleton.js';
|
|
3
|
+
import { IndexedDbHelper } from '../sw/service-worker.tools';
|
|
3
4
|
import { Feature } from 'ol';
|
|
4
5
|
import VectorLayer from 'ol/layer/Vector.js';
|
|
5
6
|
import VectorSource from 'ol/source/Vector.js';
|
|
@@ -8,13 +9,17 @@ import Style from 'ol/style/Style.js';
|
|
|
8
9
|
import { Stroke } from 'ol/style.js';
|
|
9
10
|
class OfflineManager extends GirafeSingleton {
|
|
10
11
|
serviceWorker = null;
|
|
11
|
-
|
|
12
|
+
indexedDbHelper = new IndexedDbHelper();
|
|
12
13
|
get map() {
|
|
13
14
|
return this.context.mapManager.getMap();
|
|
14
15
|
}
|
|
15
16
|
get state() {
|
|
16
17
|
return this.context.stateManager.state;
|
|
17
18
|
}
|
|
19
|
+
async database() {
|
|
20
|
+
const database = await this.indexedDbHelper.openDb(this.dbCacheName, this.storeVersion, this.upgradeIndexedDb);
|
|
21
|
+
return database;
|
|
22
|
+
}
|
|
18
23
|
totalLength = 0;
|
|
19
24
|
counter = 0;
|
|
20
25
|
progressCallback;
|
|
@@ -38,10 +43,10 @@ class OfflineManager extends GirafeSingleton {
|
|
|
38
43
|
this.context.stateManager.state.isOffline = isOffline;
|
|
39
44
|
}
|
|
40
45
|
registerEvents() {
|
|
41
|
-
|
|
46
|
+
globalThis.addEventListener('offline', () => {
|
|
42
47
|
this.state.isOffline = true;
|
|
43
48
|
});
|
|
44
|
-
|
|
49
|
+
globalThis.addEventListener('online', () => {
|
|
45
50
|
this.state.isOffline = false;
|
|
46
51
|
});
|
|
47
52
|
this.context.stateManager.subscribe('isOffline', () => {
|
|
@@ -55,45 +60,16 @@ class OfflineManager extends GirafeSingleton {
|
|
|
55
60
|
const tileUrls = this.getAllTileUrls(bbox, wmtsLayers);
|
|
56
61
|
await this.fetchAndSaveTiles(tileUrls);
|
|
57
62
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
request.onerror = (event) => {
|
|
69
|
-
if (!timedOut) {
|
|
70
|
-
clearTimeout(timeoutId);
|
|
71
|
-
const message = event.target.error?.message;
|
|
72
|
-
console.debug(`IndexedDB could not be opened : ${message}`);
|
|
73
|
-
reject(new Error(message));
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
request.onsuccess = (event) => {
|
|
77
|
-
if (!timedOut) {
|
|
78
|
-
clearTimeout(timeoutId);
|
|
79
|
-
console.debug('IndexedDB is open');
|
|
80
|
-
resolve(event.target.result);
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
request.onupgradeneeded = (event) => {
|
|
84
|
-
// Version migration is necessary.
|
|
85
|
-
// open the indexedDB and create the new structure
|
|
86
|
-
console.debug('Upgrading IndexedDB');
|
|
87
|
-
const database = event.target.result;
|
|
88
|
-
// First : a store for tiles
|
|
89
|
-
const tilesStore = database.createObjectStore('tiles', { autoIncrement: true });
|
|
90
|
-
tilesStore.createIndex('url', 'url', { unique: true });
|
|
91
|
-
// Second : A store for offline available bbox
|
|
92
|
-
database.createObjectStore('bbox', { autoIncrement: true });
|
|
93
|
-
console.debug('IndexedDB upgraded.');
|
|
94
|
-
resolve(database);
|
|
95
|
-
};
|
|
96
|
-
});
|
|
63
|
+
upgradeIndexedDb(database) {
|
|
64
|
+
// Version migration is necessary.
|
|
65
|
+
// open the indexedDB and create the new structure
|
|
66
|
+
console.debug('Upgrading IndexedDB');
|
|
67
|
+
// First : a store for tiles
|
|
68
|
+
const tilesStore = database.createObjectStore('tiles', { autoIncrement: true });
|
|
69
|
+
tilesStore.createIndex('url', 'url', { unique: true });
|
|
70
|
+
// Second : A store for offline available bbox
|
|
71
|
+
database.createObjectStore('bbox', { autoIncrement: true });
|
|
72
|
+
console.debug('IndexedDB upgraded.');
|
|
97
73
|
}
|
|
98
74
|
/** The offline manager works with a ServiceWorker in charge of intercepting
|
|
99
75
|
* the fetch requests and read the data from the local cache if the application
|
|
@@ -108,7 +84,6 @@ class OfflineManager extends GirafeSingleton {
|
|
|
108
84
|
this.serviceWorker = sw;
|
|
109
85
|
this.storeVersion = storeVersion;
|
|
110
86
|
this.dbCacheName = dbCacheName;
|
|
111
|
-
this.database = await this.openIndexedDB();
|
|
112
87
|
this.serviceWorker.postMessage({
|
|
113
88
|
storeVersion: this.storeVersion,
|
|
114
89
|
dbCacheName: this.dbCacheName,
|
|
@@ -165,9 +140,7 @@ class OfflineManager extends GirafeSingleton {
|
|
|
165
140
|
const iterator = tileUrls.values();
|
|
166
141
|
// Use 4 parallel workers to download tiles
|
|
167
142
|
this.counter = 0;
|
|
168
|
-
const workers = Array(4)
|
|
169
|
-
.fill(iterator)
|
|
170
|
-
.map((iterator) => this.doWork(iterator));
|
|
143
|
+
const workers = new Array(4).fill(iterator).map((iterator) => this.doWork(iterator));
|
|
171
144
|
Promise.allSettled(workers).then(() => {
|
|
172
145
|
console.debug('Everything downloaded.');
|
|
173
146
|
if (this.progressCallback) {
|
|
@@ -180,41 +153,41 @@ class OfflineManager extends GirafeSingleton {
|
|
|
180
153
|
for (const url of iterator) {
|
|
181
154
|
const response = await fetch(url);
|
|
182
155
|
if (response.ok) {
|
|
183
|
-
response.blob()
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
156
|
+
const blob = await response.blob();
|
|
157
|
+
const transaction = (await this.database()).transaction([this.tilesStoreName], 'readwrite');
|
|
158
|
+
const store = transaction.objectStore(this.tilesStoreName);
|
|
159
|
+
const index = store.index('url');
|
|
160
|
+
const dbRequest = index.getKey(url);
|
|
161
|
+
dbRequest.onsuccess = () => {
|
|
162
|
+
let request;
|
|
163
|
+
if (dbRequest.result) {
|
|
164
|
+
const key = dbRequest.result;
|
|
165
|
+
request = store.put({ url: url, data: blob }, key);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
request = store.put({ url: url, data: blob });
|
|
169
|
+
}
|
|
170
|
+
request.onsuccess = () => {
|
|
171
|
+
if (this.progressCallback) {
|
|
172
|
+
this.progressCallback(Math.round((this.counter * 100) / this.totalLength));
|
|
196
173
|
}
|
|
197
|
-
|
|
198
|
-
if (this.progressCallback) {
|
|
199
|
-
this.progressCallback(Math.round((this.counter * 100) / this.totalLength));
|
|
200
|
-
}
|
|
201
|
-
console.debug(`${this.counter++}/${this.totalLength} Tile ${url} added.`);
|
|
202
|
-
};
|
|
203
|
-
request.onerror = () => {
|
|
204
|
-
console.error(`Error while saving tile ${url}`);
|
|
205
|
-
};
|
|
174
|
+
console.debug(`${this.counter++}/${this.totalLength} Tile ${url} added.`);
|
|
206
175
|
};
|
|
207
|
-
|
|
176
|
+
request.onerror = () => {
|
|
177
|
+
console.error(`Error while saving tile ${url}`);
|
|
178
|
+
};
|
|
179
|
+
};
|
|
208
180
|
}
|
|
209
181
|
}
|
|
210
182
|
}
|
|
211
183
|
async getTotalSizeMB() {
|
|
184
|
+
const database = await this.database();
|
|
212
185
|
return new Promise((resolve, reject) => {
|
|
213
|
-
if (!
|
|
186
|
+
if (!database) {
|
|
214
187
|
reject(new Error('Database is not initialized.'));
|
|
215
188
|
return;
|
|
216
189
|
}
|
|
217
|
-
const transaction =
|
|
190
|
+
const transaction = database.transaction([this.tilesStoreName], 'readonly');
|
|
218
191
|
const store = transaction.objectStore(this.tilesStoreName);
|
|
219
192
|
const request = store.openCursor();
|
|
220
193
|
let totalBytes = 0;
|
|
@@ -239,12 +212,13 @@ class OfflineManager extends GirafeSingleton {
|
|
|
239
212
|
});
|
|
240
213
|
}
|
|
241
214
|
async clearStore(storeName) {
|
|
215
|
+
const database = await this.database();
|
|
242
216
|
return new Promise((resolve, reject) => {
|
|
243
217
|
if (!this.database) {
|
|
244
218
|
reject(new Error('Database is not initialized.'));
|
|
245
219
|
return;
|
|
246
220
|
}
|
|
247
|
-
const transaction =
|
|
221
|
+
const transaction = database.transaction([storeName], 'readwrite');
|
|
248
222
|
const store = transaction.objectStore(storeName);
|
|
249
223
|
const request = store.clear();
|
|
250
224
|
request.onsuccess = () => {
|
|
@@ -264,11 +238,9 @@ class OfflineManager extends GirafeSingleton {
|
|
|
264
238
|
this.clearStore(this.bboxStoreName);
|
|
265
239
|
}
|
|
266
240
|
async saveBoundingBox(bbox) {
|
|
267
|
-
|
|
268
|
-
this.database = await this.openIndexedDB();
|
|
269
|
-
}
|
|
241
|
+
const database = await this.database();
|
|
270
242
|
// Save bbox to the store
|
|
271
|
-
const transaction =
|
|
243
|
+
const transaction = database.transaction([this.bboxStoreName], 'readwrite');
|
|
272
244
|
const store = transaction.objectStore(this.bboxStoreName);
|
|
273
245
|
const request = store.put(bbox);
|
|
274
246
|
request.onsuccess = () => {
|
|
@@ -279,11 +251,9 @@ class OfflineManager extends GirafeSingleton {
|
|
|
279
251
|
};
|
|
280
252
|
}
|
|
281
253
|
async displayBoundBoxes() {
|
|
282
|
-
|
|
283
|
-
this.database = await this.openIndexedDB();
|
|
284
|
-
}
|
|
254
|
+
const database = await this.database();
|
|
285
255
|
// Read bbox for offline tiles
|
|
286
|
-
const transaction =
|
|
256
|
+
const transaction = database.transaction([this.bboxStoreName], 'readonly');
|
|
287
257
|
const store = transaction.objectStore(this.bboxStoreName);
|
|
288
258
|
const request = store.getAll();
|
|
289
259
|
request.onsuccess = () => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|