UNPKG

@igo2/context

Version:
1,089 lines (1,080 loc) 260 kB
import * as i0 from '@angular/core'; import { Injectable, Optional, Component, Input, NgModule, EventEmitter, Output, Directive, Self, HostListener, ChangeDetectionStrategy } from '@angular/core'; import { NgIf, NgFor, AsyncPipe, NgClass, KeyValuePipe } from '@angular/common'; import * as i1$3 from '@angular/forms'; import { Validators, FormsModule, ReactiveFormsModule, UntypedFormControl } from '@angular/forms'; import * as i5 from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button'; import * as i7 from '@angular/material/button-toggle'; import { MatButtonToggleModule } from '@angular/material/button-toggle'; import * as i12 from '@angular/material/core'; import { MatOptionModule } from '@angular/material/core'; import * as i13 from '@angular/material/divider'; import { MatDividerModule } from '@angular/material/divider'; import * as i6$1 from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field'; import * as i7$1 from '@angular/material/input'; import { MatInputModule } from '@angular/material/input'; import * as i11 from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select'; import { SpinnerComponent } from '@igo2/common/spinner'; import * as i1$1 from '@igo2/core/config'; import * as i4 from '@igo2/core/language'; import { IgoLanguageModule } from '@igo2/core/language'; import * as i3 from '@igo2/core/message'; import { BehaviorSubject, Subject, of, Observable, ReplaySubject, timer, EMPTY, combineLatest } from 'rxjs'; import { skip, first, catchError, tap, map, mergeMap, debounceTime, take, debounce, filter } from 'rxjs/operators'; import * as i1 from '@angular/common/http'; import * as i2 from '@igo2/auth'; import * as i8 from '@igo2/core/route'; import * as i6 from '@igo2/core/storage'; import * as i1$2 from '@igo2/geo'; import { isLayerGroup, FeatureDataSource, featureRandomStyleFunction, featureRandomStyle, VectorLayer, ClusterDataSource, isLayerGroupOptions, sortLayersByZindex, isLayerItem, moveToOlFeatures, FeatureMotion, FeatureDetailsComponent } from '@igo2/geo'; import { ObjectUtils, uuid, downloadContent, Clipboard } from '@igo2/utils'; import GeoJSON from 'ol/format/GeoJSON'; import olPoint from 'ol/geom/Point'; import Cluster from 'ol/source/Cluster'; import olVectorSource from 'ol/source/Vector'; import * as i7$2 from '@ngx-translate/core'; import * as i6$2 from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon'; import * as i5$1 from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip'; import * as i3$1 from '@angular/material/list'; import { MatListModule } from '@angular/material/list'; import { CollapseDirective, CollapsibleComponent } from '@igo2/common/collapsible'; import { StopPropagationDirective } from '@igo2/common/stop-propagation'; import * as i5$2 from '@igo2/common/confirm-dialog'; import { ConfirmDialogService } from '@igo2/common/confirm-dialog'; import * as i13$1 from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox'; import * as i1$4 from '@angular/material/dialog'; import { MatDialogTitle, MatDialogContent, MatDialogActions } from '@angular/material/dialog'; import * as i12$1 from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu'; import { ActionStore, ActionbarMode, ActionbarComponent } from '@igo2/common/action'; import { ListComponent, ListItemDirective } from '@igo2/common/list'; import * as i8$1 from '@angular/material/autocomplete'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; import * as i5$3 from '@angular/material/radio'; import { MatRadioModule } from '@angular/material/radio'; import * as i3$2 from '@igo2/core/media'; import * as oleasing from 'ol/easing'; import * as olproj from 'ol/proj'; import { trigger, state, style, transition, animate } from '@angular/animations'; import { CustomHtmlComponent } from '@igo2/common/custom-html'; import * as i2$1 from '@angular/material/tabs'; import { MatTabsModule } from '@angular/material/tabs'; import * as i2$2 from '@angular/material/sidenav'; import { MatSidenavModule } from '@angular/material/sidenav'; import * as i1$5 from '@angular/platform-browser'; import { getEntityTitle } from '@igo2/common/entity'; import { FlexibleComponent } from '@igo2/common/flexible'; import { PanelComponent } from '@igo2/common/panel'; var TypePermission; (function (TypePermission) { TypePermission[TypePermission["null"] = 0] = "null"; TypePermission[TypePermission["read"] = 1] = "read"; TypePermission[TypePermission["write"] = 2] = "write"; })(TypePermission || (TypePermission = {})); var Scope; (function (Scope) { Scope[Scope["public"] = 0] = "public"; Scope[Scope["protected"] = 1] = "protected"; Scope[Scope["private"] = 2] = "private"; })(Scope || (Scope = {})); class ContextService { http; authService; languageService; config; messageService; storageService; exportService; route; context$ = new BehaviorSubject(undefined); contexts$ = new BehaviorSubject({ ours: [] }); defaultContextId$ = new BehaviorSubject(undefined); editedContext$ = new BehaviorSubject(undefined); importedContext = []; toolsChanged$ = new Subject(); mapViewFromRoute = {}; options; baseUrl; // Until the ContextService is completely refactored, this is needed // to track the current tools tools; toolbar; get defaultContextUri() { return (this.storageService.get('favorite.context.uri') || this._defaultContextUri || this.options.defaultContextUri); } set defaultContextUri(uri) { this._defaultContextUri = uri; } _defaultContextUri; constructor(http, authService, languageService, config, messageService, storageService, exportService, route) { this.http = http; this.authService = authService; this.languageService = languageService; this.config = config; this.messageService = messageService; this.storageService = storageService; this.exportService = exportService; this.route = route; this.options = Object.assign({ basePath: 'contexts', contextListFile: '_contexts.json', defaultContextUri: '_default' }, this.config.getConfig('context')); this.baseUrl = this.options.url ?? ''; this.readParamsFromRoute(); if (this.authService.hasAuthService) { this.authService.logged$.subscribe((logged) => { if (logged) { this.contexts$.pipe(skip(1), first()).subscribe(() => { this.handleContextsChange(); }); this.loadContexts(); } }); } else { this.loadContexts(); this.handleContextsChange(false); } } get(permissions, hidden) { let url = this.baseUrl + '/contexts'; if (permissions && this.authService.authenticated) { url += '?permission=' + permissions.join(); if (hidden) { url += '&hidden=true'; } } return this.http.get(url); } getById(id) { const url = this.baseUrl + '/contexts/' + id; return this.http.get(url); } getDetails(id) { const url = `${this.baseUrl}/contexts/${id}/details`; return this.http.get(url).pipe(catchError((res) => { this.handleError(res, id); throw res; })); } getDefault() { if (this.authService.authenticated) { const url = this.baseUrl + '/contexts/default'; return this.http.get(url).pipe(tap((context) => { this.defaultContextId$.next(context.id); })); } else { const uri = this.storageService.get('favorite.context.uri'); this.defaultContextId$.next(uri); return this.getContextByUri(uri); } } getProfilByUser() { if (this.baseUrl) { const url = this.baseUrl + '/profils?'; return this.http.get(url); } return of([]); } setDefault(id) { if (this.authService.authenticated) { const url = this.baseUrl + '/contexts/default'; return this.http.post(url, { defaultContextId: id }); } else { this.storageService.set('favorite.context.uri', id); return of(undefined); } } hideContext(id) { const url = this.baseUrl + '/contexts/' + id + '/hide'; return this.http.post(url, {}); } showContext(id) { const url = this.baseUrl + '/contexts/' + id + '/show'; return this.http.post(url, {}); } delete(id, imported = false) { const contexts = { ours: [] }; Object.keys(this.contexts$.value).forEach((key) => (contexts[key] = this.contexts$.value[key].filter((c) => c.id !== id))); if (imported) { this.importedContext = this.importedContext.filter((c) => c.id !== id); return of(this.contexts$.next(contexts)); } const url = this.baseUrl + '/contexts/' + id; return this.http.delete(url).pipe(tap(() => { this.contexts$.next(contexts); })); } create(context) { const url = this.baseUrl + '/contexts'; return this.http.post(url, context).pipe(map((contextCreated) => { if (this.authService.authenticated) { contextCreated.permission = TypePermission[TypePermission.write]; } else { contextCreated.permission = TypePermission[TypePermission.read]; } this.contexts$.value.ours.unshift(contextCreated); this.contexts$.next(this.contexts$.value); return contextCreated; })); } clone(id, properties = {}) { const url = this.baseUrl + '/contexts/' + id + '/clone'; return this.http.post(url, properties).pipe(map((contextCloned) => { contextCloned.permission = TypePermission[TypePermission.write]; this.contexts$.value.ours.unshift(contextCloned); this.contexts$.next(this.contexts$.value); return contextCloned; })); } update(id, context) { const url = this.baseUrl + '/contexts/' + id; return this.http.patch(url, context); } // ================================================================= addToolAssociation(contextId, toolId) { const url = `${this.baseUrl}/contexts/${contextId}/tools`; const association = { toolId }; return this.http.post(url, association); } deleteToolAssociation(contextId, toolId) { const url = `${this.baseUrl}/contexts/${contextId}/tools/${toolId}`; return this.http.delete(url); } getPermissions(id) { const url = this.baseUrl + '/contexts/' + id + '/permissions'; return this.http.get(url); } addPermissionAssociation(contextId, profil, type) { const url = `${this.baseUrl}/contexts/${contextId}/permissions`; const association = { profil, typePermission: type }; return this.http.post(url, association).pipe(catchError((res) => { this.handleError(res, undefined, true); throw [res]; // TODO Not sure about this. })); } deletePermissionAssociation(contextId, permissionId) { const url = `${this.baseUrl}/contexts/${contextId}/permissions/${permissionId}`; return this.http.delete(url); } // ====================================================================== getLocalContexts() { const url = this.getPath(this.options.contextListFile); return this.http.get(url).pipe(map((res) => { return { ours: res }; })); } getLocalContext(uri) { const url = this.getPath(`${uri}.json`); return this.http.get(url).pipe(mergeMap((res) => { if (!res.base) { return of(res); } const urlBase = this.getPath(`${res.base}.json`); return this.http.get(urlBase).pipe(map((resBase) => { const resMerge = res; resMerge.map = ObjectUtils.mergeDeep(resBase.map, res.map); resMerge.layers = (resBase.layers || []) .concat(res.layers || []) .reverse() .filter((l, index, self) => !l.id || self.findIndex((l2) => l2.id === l.id) === index) .reverse(); resMerge.toolbar = res.toolbar || resBase.toolbar; resMerge.message = res.message || resBase.message; resMerge.messages = res.messages || resBase.messages; resMerge.tools = (res.tools || []) .concat(resBase.tools || []) .filter((t, index, self) => self.findIndex((t2) => t2.name === t.name) === index); return resMerge; }), catchError((err) => { this.handleError(err, uri); throw err; })); }), catchError((err2) => { this.handleError(err2, uri); throw err2; })); } loadContexts(permissions, hidden) { let request; if (this.baseUrl) { request = this.get(permissions, hidden); } else { request = this.getLocalContexts(); } request.subscribe((contexts) => { contexts.ours = this.importedContext.concat(contexts.ours); this.contexts$.next(contexts); }); } loadDefaultContext() { const loadFct = (direct = false) => { if (!direct && this.baseUrl && this.authService.authenticated) { this.getDefault().subscribe((_context) => { this.defaultContextUri = _context.uri; this.addContextToList(_context); this.setContext(_context); }, () => { this.defaultContextId$.next(undefined); this.loadContext(this.defaultContextUri); }); } else { this.loadContext(this.defaultContextUri); } }; if (this.route && this.route.options.contextKey) { this.route.queryParams.pipe(debounceTime(100)).subscribe((params) => { const contextParam = params[this.route.options.contextKey]; let direct = false; if (contextParam) { this.defaultContextUri = contextParam; direct = true; } loadFct(direct); }); } else { loadFct(); } } loadContext(uri) { const context = this.context$.value; if (context && context.uri === uri) { return; } this.getContextByUri(uri) .pipe(first()) .subscribe((_context) => { this.addContextToList(_context); this.setContext(_context); }, () => { if (uri !== this.options.defaultContextUri) { this.loadContext(this.options.defaultContextUri); } }); } setContext(context) { this.handleContextMessage(context); const currentContext = this.context$.value; if (currentContext && context && context.id === currentContext.id) { if (context.map.view.keepCurrentView === undefined) { context.map.view.keepCurrentView = true; } this.context$.next(context); return; } if (!context.map) { context.map = { view: {} }; } Object.assign(context.map.view, this.mapViewFromRoute); this.context$.next(context); } loadEditedContext(uri) { this.getContextByUri(uri).subscribe((_context) => { this.setEditedContext(_context); }); } setEditedContext(context) { this.editedContext$.next(context); } getContextFromMap(igoMap, empty) { const view = igoMap.ol.getView(); const proj = view.getProjection().getCode(); const center = new olPoint(view.getCenter()).transform(proj, 'EPSG:4326'); const context = { uri: uuid(), title: '', scope: 'private', map: { view: { center: center.getCoordinates(), zoom: view.getZoom(), projection: proj, maxZoomOnExtent: igoMap.viewController.maxZoomOnExtent } }, layers: [], tools: [] }; const layers = empty ? [] : [...igoMap.layerController.treeLayers]; const baseLayer = igoMap.layerController.baseLayer; if (baseLayer) { layers.unshift(baseLayer); } // Remove null for the first level, the LayerGroup is already doing that for it's children context.layers = layers .map((layer) => layer.saveableOptions) .filter(Boolean); context.tools = this.tools.map((tool) => { return { id: String(tool.id), global: tool.global }; }); return context; } getContextFromLayers(igoMap, layers, name, keepCurrentView) { const view = igoMap.ol.getView(); const proj = view.getProjection().getCode(); const center = new olPoint(view.getCenter()).transform(proj, 'EPSG:4326'); const context = { uri: name, title: name, map: { view: { center: center.getCoordinates(), zoom: view.getZoom(), projection: proj, keepCurrentView } }, layers: [], toolbar: [], tools: [], extraFeatures: [] }; context.layers = igoMap.layerController.baseLayers.map((l) => { return { baseLayer: true, sourceOptions: l.options.sourceOptions, title: l.options.title, visible: l.visible }; }); layers.forEach((layer) => { if (isLayerGroup(layer)) { return context.layers.push(layer.options); } if (!(layer.ol.getSource() instanceof olVectorSource)) { const layerOptions = layer.options; layerOptions.zIndex = layer.zIndex; layerOptions.visible = layer.visible; layerOptions.opacity = layer.opacity; delete layerOptions.source; context.layers.push(layerOptions); } else { if (!(layer.ol.getSource() instanceof olVectorSource)) { const catalogLayer = layer.options; catalogLayer.zIndex = layer.zIndex; catalogLayer.visible = layer.visible; catalogLayer.opacity = layer.opacity; delete catalogLayer.source; context.layers.push(catalogLayer); } else { const extraFeatures = this.getExtraFeatures(layer); extraFeatures.name = layer.options.title; extraFeatures.opacity = layer.opacity; extraFeatures.visible = layer.visible; context.extraFeatures.push(extraFeatures); } } }); context.toolbar = this.toolbar; context.tools = this.tools; return context; } getExtraFeatures(layer) { const writer = new GeoJSON(); let olFeatures; if (layer.ol.getSource() instanceof Cluster) { const clusterSource = layer.ol.getSource(); olFeatures = clusterSource.getFeatures(); olFeatures = olFeatures.flatMap((cluster) => cluster.get('features')); } else { const source = layer.ol.getSource(); olFeatures = source.getFeatures(); } const cleanedOlFeatures = this.exportService.cleanFeatures(olFeatures, 'GeoJSON', '_featureStore'); const features = writer.writeFeatures(cleanedOlFeatures, { dataProjection: 'EPSG:4326', featureProjection: 'EPSG:3857' }); return JSON.parse(features); } setTools(tools) { this.tools = tools; } setToolbar(toolbar) { this.toolbar = toolbar; } handleContextMessage(context) { if (this.context$.value && context.uri && this.context$.value.uri !== context.uri) { this.messageService.removeAllAreNotError(); } context.messages = context.messages ? context.messages : []; context.messages.push(context.message); context.messages.map((message) => { if (message) { this.messageService.message(message); } }); } getContextByUri(uri) { if (this.baseUrl) { let contextToLoad; for (const key of Object.keys(this.contexts$.value)) { contextToLoad = this.contexts$.value[key].find((c) => { return c.uri === uri; }); if (contextToLoad) { break; } } if (contextToLoad && contextToLoad.imported) { return of(contextToLoad); } // TODO : use always id or uri const id = contextToLoad ? contextToLoad.id : uri; return this.getDetails(id); } const importedContext = this.contexts$.value.ours.find((currentContext) => { return currentContext.uri === uri && currentContext.imported === true; }); if (importedContext) { return of(importedContext); } else { return this.getLocalContext(uri); } } getContextLayers(map) { return map.layerController.treeLayers; } readParamsFromRoute() { if (!this.route) { return; } this.route.queryParams.subscribe((params) => { const centerKey = this.route.options.centerKey; if (centerKey && params[centerKey]) { const centerParams = params[centerKey]; this.mapViewFromRoute.center = centerParams.split(',').map(Number); } const projectionKey = this.route.options.projectionKey; if (projectionKey && params[projectionKey]) { const projectionParam = params[projectionKey]; this.mapViewFromRoute.projection = projectionParam; } const zoomKey = this.route.options.zoomKey; if (zoomKey && params[zoomKey]) { const zoomParam = params[zoomKey]; this.mapViewFromRoute.zoom = Number(zoomParam); } }); } getPath(file) { const basePath = this.options.basePath.replace(/\/$/, ''); return `${basePath}/${file}`; } handleError(error, uri, permissionError) { const context = this.contexts$.value.ours.find((obj) => obj.uri === uri); const titleContext = context ? context.title : uri; error.error.title = this.languageService.translate.instant('igo.context.contextManager.invalid.title'); error.error.message = this.languageService.translate.instant('igo.context.contextManager.invalid.text', { value: titleContext }); error.error.toDisplay = true; if (permissionError) { error.error.title = this.languageService.translate.instant('igo.context.contextManager.errors.addPermissionTitle'); error.error.message = this.languageService.translate.instant('igo.context.contextManager.errors.addPermission'); } this.messageService.error('igo.context.contextManager.errors.addPermission', 'igo.context.contextManager.errors.addPermissionTitle'); } handleContextsChange(keepCurrentContext = true) { const context = this.context$.value; const editedContext = this.editedContext$.value; if (!context || context.uri === this.options.defaultContextUri) { keepCurrentContext = false; } if (!keepCurrentContext || !this.findContext(context)) { this.defaultContextUri = undefined; this.loadDefaultContext(); } else { this.getContextByUri(context.uri) .pipe(first()) .subscribe((newContext) => { this.toolsChanged$.next(newContext); }); if (this.baseUrl && this.authService.authenticated) { this.getDefault().subscribe(); } } const editedFound = this.findContext(editedContext); if (!editedFound || editedFound.permission !== 'write') { this.setEditedContext(undefined); } } addContextToList(context) { const contextFound = this.findContext(context); if (!contextFound) { const contextSimplifie = { id: context.id, uri: context.uri, title: context.title, scope: context.scope, permission: TypePermission[TypePermission.read] }; if (this.contexts$.value && this.contexts$.value.public) { this.contexts$.value.public.push(contextSimplifie); this.contexts$.next(this.contexts$.value); } } } findContext(context) { if (!context) { return false; } const contexts = this.contexts$.value; let found; for (const key of Object.keys(contexts)) { const value = contexts[key]; found = value.find((c) => (context.id && c.id === context.id) || (context.uri && c.uri === context.uri)); if (found) { break; } } return found; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ContextService, deps: [{ token: i1.HttpClient }, { token: i2.AuthService }, { token: i4.LanguageService }, { token: i1$1.ConfigService }, { token: i3.MessageService }, { token: i6.StorageService }, { token: i1$2.ExportService }, { token: i8.RouteService, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ContextService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ContextService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i1.HttpClient }, { type: i2.AuthService }, { type: i4.LanguageService }, { type: i1$1.ConfigService }, { type: i3.MessageService }, { type: i6.StorageService }, { type: i1$2.ExportService }, { type: i8.RouteService, decorators: [{ type: Optional }] }] }); class ExportError extends Error { } class ExportInvalidFileError extends ExportError { constructor() { super('Invalid context'); Object.setPrototypeOf(this, ExportInvalidFileError.prototype); } } class ExportNothingToExportError extends ExportError { constructor() { super('Nothing to export'); Object.setPrototypeOf(this, ExportNothingToExportError.prototype); } } class ContextExportService { export(res) { return this.exportAsync(res); } exportAsync(res) { const doExport = (observer) => { const nothingToExport = this.nothingToExport(res); if (nothingToExport === true) { observer.error(new ExportNothingToExportError()); return; } const contextJSON = JSON.stringify(res); downloadContent(contextJSON, 'text/json;charset=utf-8', `${res.uri}.json`); observer.complete(); }; return new Observable(doExport); } nothingToExport(res) { if (res.map === undefined) { return true; } return false; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ContextExportService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ContextExportService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ContextExportService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); function handleFileExportError(error, messageService) { if (error instanceof ExportNothingToExportError) { this.handleNothingToExportError(messageService); return; } messageService.error('igo.context.contextImportExport.export.failed.text', 'igo.context.contextImportExport.export.failed.title'); } function handleFileExportSuccess(messageService) { messageService.success('igo.context.contextImportExport.export.success.text', 'igo.context.contextImportExport.export.success.title'); } function handleNothingToExportError(messageService) { messageService.error('igo.context.contextImportExport.export.nothing.text', 'igo.context.contextImportExport.export.nothing.title'); } class ImportError extends Error { } class ImportInvalidFileError extends ImportError { constructor() { super('Invalid file'); Object.setPrototypeOf(this, ImportInvalidFileError.prototype); } } class ImportUnreadableFileError extends ImportError { constructor() { super('Failed to read file'); Object.setPrototypeOf(this, ImportUnreadableFileError.prototype); } } class ImportNothingToImportError extends ImportError { constructor() { super('Nothing to import'); Object.setPrototypeOf(this, ImportNothingToImportError.prototype); } } class ImportSizeError extends ImportError { constructor() { super('File is too large'); Object.setPrototypeOf(this, ImportNothingToImportError.prototype); } } class ImportSRSError extends ImportError { constructor() { super('Invalid SRS definition'); Object.setPrototypeOf(this, ImportNothingToImportError.prototype); } } function handleFileImportSuccess(file, context, messageService, contextService) { if (Object.keys(context).length <= 0) { handleNothingToImportError(file, messageService); return; } const contextTitle = computeLayerTitleFromFile(file); addContextToContextList(context, contextTitle, contextService); messageService.success('igo.context.contextImportExport.import.success.text', 'igo.context.contextImportExport.import.success.title', undefined, { value: contextTitle }); } function handleFileImportError(file, error, messageService, sizeMb) { sizeMb = sizeMb ? sizeMb : 30; const errMapping = { 'Invalid file': handleInvalidFileImportError, 'File is too large': handleSizeFileImportError, 'Failed to read file': handleUnreadbleFileImportError }; errMapping[error.message](file, error, messageService, sizeMb); } function handleInvalidFileImportError(file, error, messageService) { messageService.error('igo.context.contextImportExport.import.invalid.text', 'igo.context.contextImportExport.import.invalid.title', undefined, { value: file.name, mimeType: file.type }); } function handleSizeFileImportError(file, error, messageService, sizeMb) { messageService.error('igo.context.contextImportExport.import.tooLarge.text', 'igo.context.contextImportExport.import.tooLarge.title', undefined, { value: file.name, size: sizeMb }); } function handleUnreadbleFileImportError(file, error, messageService) { messageService.error('igo.context.contextImportExport.import.unreadable.text', 'igo.context.contextImportExport.import.unreadable.title', undefined, { value: file.name }); } function handleNothingToImportError(file, messageService) { messageService.error('igo.context.contextImportExport.import.empty.text', 'igo.context.contextImportExport.import.empty.title', undefined, { value: file.name }); } function addContextToContextList(context, contextTitle, contextService) { context.title = contextTitle; context.imported = true; contextService.contexts$.value.ours.unshift(context); contextService.contexts$.next(contextService.contexts$.value); contextService.importedContext.unshift(context); contextService.loadContext(context.uri); } function getFileExtension(file) { return file.name.split('.').pop().toLowerCase(); } function computeLayerTitleFromFile(file) { return file.name.substr(0, file.name.lastIndexOf('.')); } function addImportedFeaturesToMap(extraFeatures, map) { const sourceOptions = { type: 'vector', queryable: true }; const olFeatures = collectFeaturesFromExtraFeatures(extraFeatures); const source = new FeatureDataSource(sourceOptions); source.ol.addFeatures(olFeatures); let randomStyle; let editable = false; const featureKeys = olFeatures[0]?.getKeys() ?? []; if (featureKeys.includes('_style') || featureKeys.includes('_mapTitle')) { randomStyle = featureRandomStyleFunction(); } else { randomStyle = featureRandomStyle(); editable = true; } const layer = new VectorLayer({ title: extraFeatures.name, isIgoInternalLayer: true, source, igoStyle: { editable }, style: randomStyle, visible: extraFeatures.visible, opacity: extraFeatures.opacity }); map.layerController.add(layer); return layer; } function addImportedFeaturesStyledToMap(extraFeatures, map, styleListService, styleService) { let style; let distance; if (styleListService.getStyleList(extraFeatures.name + '.styleByAttribute')) { const styleByAttribute = styleListService.getStyleList(extraFeatures.name + '.styleByAttribute'); style = (feature, resolution) => { return styleService.createStyleByAttribute(feature, styleByAttribute, resolution); }; } else if (styleListService.getStyleList(extraFeatures.name + '.clusterStyle')) { const clusterParam = styleListService.getStyleList(extraFeatures.name + '.clusterParam'); distance = styleListService.getStyleList(extraFeatures.name + '.distance'); style = (feature, resolution) => { const baseStyle = styleService.createStyle(styleListService.getStyleList(extraFeatures.name + '.clusterStyle'), feature, resolution); return styleService.createClusterStyle(feature, resolution, clusterParam, baseStyle); }; } else if (styleListService.getStyleList(extraFeatures.name + '.style')) { style = (feature, resolution) => styleService.createStyle(styleListService.getStyleList(extraFeatures.name + '.style'), feature, resolution); } else { style = (feature, resolution) => styleService.createStyle(styleListService.getStyleList('default.style'), feature, resolution); } let source; const olFeatures = collectFeaturesFromExtraFeatures(extraFeatures); if (styleListService.getStyleList(extraFeatures.name + '.clusterStyle')) { const sourceOptions = { distance, type: 'cluster', queryable: true }; source = new ClusterDataSource(sourceOptions); source.ol.source.addFeatures(olFeatures); } else { const sourceOptions = { type: 'vector', queryable: true }; source = new FeatureDataSource(sourceOptions); source.ol.addFeatures(olFeatures); } const layer = new VectorLayer({ title: extraFeatures.name, isIgoInternalLayer: true, source, style, opacity: extraFeatures.opacity, visible: extraFeatures.visible }); map.layerController.add(layer); return layer; } function collectFeaturesFromExtraFeatures(featureCollection) { const format = new GeoJSON(); const features = format.readFeatures(featureCollection, { dataProjection: 'EPSG:4326', featureProjection: 'EPSG:3857' }); return features; } class ContextImportService { config; static allowedMimeTypes = ['application/json']; static allowedExtensions = 'json'; clientSideFileSizeMax; constructor(config) { this.config = config; const configFileSizeMb = this.config.getConfig('importExport.clientSideFileSizeMaxMb'); this.clientSideFileSizeMax = (configFileSizeMb ? configFileSizeMb : 30) * Math.pow(1024, 2); } import(file) { return this.importAsync(file); } getFileImporter(file) { const extension = getFileExtension(file); const mimeType = file.type; const allowedMimeTypes = [...ContextImportService.allowedMimeTypes]; const allowedExtensions = ContextImportService.allowedExtensions; if (allowedMimeTypes.indexOf(mimeType) < 0 && allowedExtensions.indexOf(extension) < 0) { return undefined; } else if (mimeType === 'application/json' || extension === ContextImportService.allowedExtensions) { return this.importFile; } return undefined; } importAsync(file) { const doImport = (observer) => { if (file.size >= this.clientSideFileSizeMax) { observer.error(new ImportSizeError()); return; } const importer = this.getFileImporter(file); if (importer === undefined) { observer.error(new ImportInvalidFileError()); return; } importer.call(this, file, observer); }; return new Observable(doImport); } importFile(file, observer) { const reader = new FileReader(); reader.onload = (event) => { try { const context = this.parseContextFromFile(file, event.target.result); observer.next(context); } catch { observer.error(new ImportUnreadableFileError()); } observer.complete(); }; reader.onerror = () => { observer.error(new ImportUnreadableFileError()); }; reader.readAsText(file, 'UTF-8'); } parseContextFromFile(file, data) { const context = JSON.parse(data); return context; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ContextImportService, deps: [{ token: i1$1.ConfigService }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ContextImportService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ContextImportService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i1$1.ConfigService }] }); class ContextImportExportComponent { contextImportService; contextExportService; messageService; formBuilder; config; contextService; form; layers; inputProj = 'EPSG:4326'; loading$ = new BehaviorSubject(false); forceNaming = false; layerList; userControlledLayerList; res; clientSideFileSizeMax; fileSizeMb; activeImportExport = 'import'; map; constructor(contextImportService, contextExportService, messageService, formBuilder, config, contextService) { this.contextImportService = contextImportService; this.contextExportService = contextExportService; this.messageService = messageService; this.formBuilder = formBuilder; this.config = config; this.contextService = contextService; this.buildForm(); } ngOnInit() { const configFileSizeMb = this.config.getConfig('importExport.clientSideFileSizeMaxMb'); this.clientSideFileSizeMax = (configFileSizeMb ? configFileSizeMb : 30) * Math.pow(1024, 2); this.fileSizeMb = this.clientSideFileSizeMax / Math.pow(1024, 2); this.layerList = this.map.layerController.all; this.userControlledLayerList = this.map.layerController.treeLayers; } importFiles(files) { this.loading$.next(true); for (const file of files) { this.contextImportService .import(file) .pipe(take(1)) .subscribe((context) => this.onFileImportSuccess(file, context), (error) => this.onFileImportError(file, error), () => { this.loading$.next(false); }); } } handleExportFormSubmit(contextOptions) { this.loading$.next(true); this.res = this.contextService.getContextFromLayers(this.map, contextOptions.layers, contextOptions.name, false); this.res.imported = true; this.contextExportService .export(this.res) .pipe(take(1)) .subscribe({ error: (error) => this.onFileExportError(error), complete: () => { this.onFileExportSuccess(); this.loading$.next(false); } }); } buildForm() { this.form = this.formBuilder.group({ layers: ['', [Validators.required]], name: ['', [Validators.required]] }); } onFileImportSuccess(file, context) { handleFileImportSuccess(file, context, this.messageService, this.contextService); } onFileImportError(file, error) { this.loading$.next(false); handleFileImportError(file, error, this.messageService, this.fileSizeMb); } onFileExportError(error) { this.loading$.next(false); handleFileExportError(error, this.messageService); } onFileExportSuccess() { handleFileExportSuccess(this.messageService); } selectAll(e) { if (e._selected) { this.form.controls.layers.setValue(this.userControlledLayerList); e._selected = true; } if (e._selected === false) { this.form.controls.layers.setValue([]); } } onImportExportChange(event) { this.activeImportExport = event.value; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ContextImportExportComponent, deps: [{ token: ContextImportService }, { token: ContextExportService }, { token: i3.MessageService }, { token: i1$3.UntypedFormBuilder }, { token: i1$1.ConfigService }, { token: ContextService }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: ContextImportExportComponent, isStandalone: true, selector: "igo-context-import-export", inputs: { map: "map" }, ngImport: i0, template: "<div class=\"import-export-toggle mat-typography\">\n <mat-button-toggle-group\n [value]=\"activeImportExport\"\n (change)=\"onImportExportChange($event)\"\n >\n <mat-button-toggle [value]=\"'import'\">\n {{ 'igo.geo.importExportForm.importTabTitle' | translate }}\n </mat-button-toggle>\n <mat-button-toggle [value]=\"'export'\">\n {{ 'igo.geo.importExportForm.exportTabTitle' | translate }}\n </mat-button-toggle>\n </mat-button-toggle-group>\n</div>\n\n<div *ngIf=\"activeImportExport === 'import'\">\n <form class=\"igo-form\">\n <div class=\"igo-form-button-group\">\n <button\n mat-raised-button\n type=\"button\"\n (click)=\"fileInput.click()\"\n [disabled]=\"loading$ | async\"\n >\n {{ 'igo.geo.importExportForm.importButton' | translate }}\n </button>\n <igo-spinner [shown]=\"loading$ | async\"></igo-spinner>\n <input\n #fileInput\n type=\"file\"\n [style.display]=\"'none'\"\n (click)=\"fileInput.value = null\"\n (change)=\"importFiles($any($event.target).files)\"\n />\n </div>\n </form>\n <section class=\"mat-typography\">\n <h4>{{ 'igo.geo.importExportForm.importClarifications' | translate }}</h4>\n <ul>\n <li>\n {{\n 'igo.geo.importExportForm.importSizeMax'\n | translate: { size: fileSizeMb }\n }}\n </li>\n </ul>\n </section>\n</div>\n\n<form\n class=\"igo-form\"\n *ngIf=\"activeImportExport === 'export'\"\n [formGroup]=\"form\"\n>\n <div class=\"igo-input-container\">\n <mat-form-field class=\"example-full-width\">\n <mat-label>{{\n 'igo.context.contextImportExport.export.exportContextName' | translate\n }}</mat-label>\n <input formControlName=\"name\" matInput [value]=\"\" />\n </mat-form-field>\n </div>\n <div class=\"igo-input-container\">\n <mat-form-field>\n <mat-label>{{\n 'igo.context.contextImportExport.export.exportPlaceHolder' | translate\n }}</mat-label>\n <mat-select formControlName=\"layers\" multiple>\n <mat-option [value]=\"1\" (click)=\"selectAll(e)\" #e>\n {{\n 'igo.context.contextImportExport.export.exportSelectAll' | translate\n }}\n </mat-option>\n <mat-divider></mat-divider>\n <mat-option\n *ngFor=\"let layer of userControlledLayerList\"\n [value]=\"layer\"\n >{{ layer.title }}</mat-option\n >\n </mat-select>\n </mat-form-field>\n </div>\n <div class=\"igo-form-button-group\">\n <button\n mat-raised-button\n type=\"button\"\n [disabled]=\"!form.valid || (loading$ | async)\"\n (click)=\"handleExportFormSubmit(form.value)\"\n >\n {{ 'igo.geo.importExportForm.exportButton' | translate }}\n </button>\n <igo-spinner [shown]=\"loading$ | async\"></igo-spinner>\n </div>\n</form>\n", styles: [".import-export-toggle{padding:10px;text-align:center}.import-export-toggle mat-button-toggle-group{width:100%}.import-export-toggle mat-button-toggle-group mat-button-toggle{width:50%}.igo-input-container{padding:10px}.igo-input-container mat-form-field{width:100%}h4{padding:0 5px}.igo-form{padding:15px 5px}.igo-form-button-group{text-align:center;padding-top:10px}igo-spinner{position:absolute;padding-left:10px}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonToggleModule }, { kind: "directive", type: i7.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i7.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled", "disabledInteractive"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i5.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: SpinnerComponent, selector: "igo-spinner", inputs: ["shown"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i6$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLab