UNPKG

@igo2/context

Version:
1 lines 320 kB
{"version":3,"file":"igo2-context.mjs","sources":["../../../packages/context/src/lib/context-manager/shared/context.enum.ts","../../../packages/context/src/lib/context-manager/shared/context.service.ts","../../../packages/context/src/lib/context-import-export/shared/context-export.errors.ts","../../../packages/context/src/lib/context-import-export/shared/context-export.service.ts","../../../packages/context/src/lib/context-import-export/shared/context-export.utils.ts","../../../packages/context/src/lib/context-import-export/shared/context-import.errors.ts","../../../packages/context/src/lib/context-import-export/shared/context-import.utils.ts","../../../packages/context/src/lib/context-import-export/shared/context-import.service.ts","../../../packages/context/src/lib/context-import-export/context-import-export/context-import-export.component.ts","../../../packages/context/src/lib/context-import-export/context-import-export/context-import-export.component.html","../../../packages/context/src/lib/context-import-export/context-import-export.module.ts","../../../packages/context/src/lib/context-manager/context-form/context-form.component.ts","../../../packages/context/src/lib/context-manager/context-form/context-form.component.html","../../../packages/context/src/lib/context-manager/context-edit/context-edit.component.ts","../../../packages/context/src/lib/context-manager/context-edit/context-edit.component.html","../../../packages/context/src/lib/context-manager/context-edit/context-edit-binding.directive.ts","../../../packages/context/src/lib/context-manager/context-item/context-item.component.ts","../../../packages/context/src/lib/context-manager/context-item/context-item.component.html","../../../packages/context/src/lib/context-map-button/bookmark-button/bookmark-dialog.component.ts","../../../packages/context/src/lib/context-map-button/bookmark-button/bookmark-dialog.component.html","../../../packages/context/src/lib/context-manager/context-list/context-list.enum.ts","../../../packages/context/src/lib/context-manager/context-list/context-list.component.ts","../../../packages/context/src/lib/context-manager/context-list/context-list.component.html","../../../packages/context/src/lib/context-manager/context-list/context-list-binding.directive.ts","../../../packages/context/src/lib/context-manager/context-permissions/context-permissions.component.ts","../../../packages/context/src/lib/context-manager/context-permissions/context-permissions.component.html","../../../packages/context/src/lib/context-manager/context-permissions/context-permissions-binding.directive.ts","../../../packages/context/src/lib/context-manager/shared/layer-context.directive.ts","../../../packages/context/src/lib/context-manager/shared/map-context.directive.ts","../../../packages/context/src/lib/context-manager/context-manager.module.ts","../../../packages/context/src/lib/context-map-button/bookmark-button/bookmark-button.component.ts","../../../packages/context/src/lib/context-map-button/bookmark-button/bookmark-button.component.html","../../../packages/context/src/lib/context-map-button/poi-button/poi-dialog.component.ts","../../../packages/context/src/lib/context-map-button/poi-button/poi-dialog.component.html","../../../packages/context/src/lib/context-map-button/poi-button/shared/poi.service.ts","../../../packages/context/src/lib/context-map-button/poi-button/poi-button.component.ts","../../../packages/context/src/lib/context-map-button/poi-button/poi-button.component.html","../../../packages/context/src/lib/context-map-button/user-button/user-button.animation.ts","../../../packages/context/src/lib/context-map-button/user-button/user-dialog.component.ts","../../../packages/context/src/lib/context-map-button/user-button/user-dialog.component.html","../../../packages/context/src/lib/context-map-button/user-button/user-button.component.ts","../../../packages/context/src/lib/context-map-button/user-button/user-button.component.html","../../../packages/context/src/lib/context-map-button/context-map-button.module.ts","../../../packages/context/src/lib/share-map/shared/share-map.service.ts","../../../packages/context/src/lib/share-map/share-map/share-map-api.component.ts","../../../packages/context/src/lib/share-map/share-map/share-map-api.component.html","../../../packages/context/src/lib/share-map/share-map/share-map-url.component.ts","../../../packages/context/src/lib/share-map/share-map/share-map-url.component.html","../../../packages/context/src/lib/share-map/share-map/share-map.component.ts","../../../packages/context/src/lib/share-map/share-map/share-map.component.html","../../../packages/context/src/lib/share-map/share-map.module.ts","../../../packages/context/src/lib/sidenav/sidenav.component.ts","../../../packages/context/src/lib/sidenav/sidenav.component.html","../../../packages/context/src/lib/sidenav/sidenav.module.ts","../../../packages/context/src/lib/context.module.ts","../../../packages/context/src/lib/context-manager/context-manager.directive.ts","../../../packages/context/src/lib/context-map-button/context-map-button.directive.ts","../../../packages/context/src/public_api.ts","../../../packages/context/src/igo2-context.ts"],"sourcesContent":["export enum TypePermission {\n null,\n read,\n write\n}\n\nexport enum Scope {\n public,\n protected,\n private\n}\n","import { HttpClient, HttpErrorResponse } from '@angular/common/http';\nimport { Injectable, Optional } from '@angular/core';\n\nimport { AuthService } from '@igo2/auth';\nimport { Tool } from '@igo2/common/tool';\nimport { ConfigService } from '@igo2/core/config';\nimport { LanguageService } from '@igo2/core/language';\nimport { Message, MessageService } from '@igo2/core/message';\nimport { RouteService } from '@igo2/core/route';\nimport { StorageService } from '@igo2/core/storage';\nimport type { AnyLayer, IgoMap, Layer } from '@igo2/geo';\nimport { ExportService, isLayerGroup } from '@igo2/geo';\nimport { ObjectUtils, uuid } from '@igo2/utils';\n\nimport GeoJSON from 'ol/format/GeoJSON';\nimport { Geometry } from 'ol/geom';\nimport olPoint from 'ol/geom/Point';\nimport Cluster from 'ol/source/Cluster';\nimport olVectorSource from 'ol/source/Vector';\n\nimport { Feature } from 'ol';\nimport { BehaviorSubject, Observable, Subject, of } from 'rxjs';\nimport {\n catchError,\n debounceTime,\n first,\n map,\n mergeMap,\n skip,\n tap\n} from 'rxjs/operators';\n\nimport { TypePermission } from './context.enum';\nimport {\n Context,\n ContextMapView,\n ContextPermission,\n ContextProfils,\n ContextServiceOptions,\n ContextsList,\n DetailedContext,\n ExtraFeatures\n} from './context.interface';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ContextService {\n public context$ = new BehaviorSubject<DetailedContext>(undefined);\n public contexts$ = new BehaviorSubject<ContextsList>({ ours: [] });\n public defaultContextId$ = new BehaviorSubject<string>(undefined);\n public editedContext$ = new BehaviorSubject<DetailedContext>(undefined);\n public importedContext: DetailedContext[] = [];\n public toolsChanged$ = new Subject<DetailedContext>();\n private mapViewFromRoute: ContextMapView = {};\n private options: ContextServiceOptions;\n private baseUrl: string;\n\n // Until the ContextService is completely refactored, this is needed\n // to track the current tools\n private tools: Tool[];\n private toolbar: string[];\n\n get defaultContextUri(): string {\n return (\n (this.storageService.get('favorite.context.uri') as string) ||\n this._defaultContextUri ||\n this.options.defaultContextUri\n );\n }\n set defaultContextUri(uri: string) {\n this._defaultContextUri = uri;\n }\n private _defaultContextUri: string;\n\n constructor(\n private http: HttpClient,\n private authService: AuthService,\n private languageService: LanguageService,\n private config: ConfigService,\n private messageService: MessageService,\n private storageService: StorageService,\n private exportService: ExportService,\n @Optional() private route: RouteService\n ) {\n this.options = Object.assign(\n {\n basePath: 'contexts',\n contextListFile: '_contexts.json',\n defaultContextUri: '_default'\n },\n this.config.getConfig('context')\n );\n\n this.baseUrl = this.options.url ?? '';\n\n this.readParamsFromRoute();\n\n if (this.authService.hasAuthService) {\n this.authService.logged$.subscribe((logged) => {\n if (logged) {\n this.contexts$.pipe(skip(1), first()).subscribe(() => {\n this.handleContextsChange();\n });\n this.loadContexts();\n }\n });\n } else {\n this.loadContexts();\n this.handleContextsChange(false);\n }\n }\n\n get(permissions?: string[], hidden?: boolean): Observable<ContextsList> {\n let url = this.baseUrl + '/contexts';\n if (permissions && this.authService.authenticated) {\n url += '?permission=' + permissions.join();\n if (hidden) {\n url += '&hidden=true';\n }\n }\n return this.http.get<ContextsList>(url);\n }\n\n getById(id: string): Observable<Context> {\n const url = this.baseUrl + '/contexts/' + id;\n return this.http.get<Context>(url);\n }\n\n getDetails(id: string): Observable<DetailedContext> {\n const url = `${this.baseUrl}/contexts/${id}/details`;\n return this.http.get<DetailedContext>(url).pipe(\n catchError((res) => {\n this.handleError(res, id);\n throw res;\n })\n );\n }\n\n getDefault(): Observable<DetailedContext> {\n if (this.authService.authenticated) {\n const url = this.baseUrl + '/contexts/default';\n return this.http.get<DetailedContext>(url).pipe(\n tap((context) => {\n this.defaultContextId$.next(context.id);\n })\n );\n } else {\n const uri = this.storageService.get('favorite.context.uri') as string;\n this.defaultContextId$.next(uri);\n return this.getContextByUri(uri);\n }\n }\n\n getProfilByUser(): Observable<ContextProfils[]> {\n if (this.baseUrl) {\n const url = this.baseUrl + '/profils?';\n return this.http.get<ContextProfils[]>(url);\n }\n return of([]);\n }\n\n setDefault(id: string): Observable<any> {\n if (this.authService.authenticated) {\n const url = this.baseUrl + '/contexts/default';\n return this.http.post(url, { defaultContextId: id });\n } else {\n this.storageService.set('favorite.context.uri', id);\n return of(undefined);\n }\n }\n\n hideContext(id: string) {\n const url = this.baseUrl + '/contexts/' + id + '/hide';\n return this.http.post(url, {});\n }\n\n showContext(id: string) {\n const url = this.baseUrl + '/contexts/' + id + '/show';\n return this.http.post(url, {});\n }\n\n delete(id: string, imported = false): Observable<void> {\n const contexts: ContextsList = { ours: [] };\n Object.keys(this.contexts$.value).forEach(\n (key) =>\n (contexts[key] = this.contexts$.value[key].filter((c) => c.id !== id))\n );\n\n if (imported) {\n this.importedContext = this.importedContext.filter((c) => c.id !== id);\n return of(this.contexts$.next(contexts));\n }\n\n const url = this.baseUrl + '/contexts/' + id;\n return this.http.delete<void>(url).pipe(\n tap(() => {\n this.contexts$.next(contexts);\n })\n );\n }\n\n create(context: DetailedContext): Observable<Context> {\n const url = this.baseUrl + '/contexts';\n return this.http.post<Context>(url, context).pipe(\n map((contextCreated) => {\n if (this.authService.authenticated) {\n contextCreated.permission = TypePermission[TypePermission.write];\n } else {\n contextCreated.permission = TypePermission[TypePermission.read];\n }\n this.contexts$.value.ours.unshift(contextCreated);\n this.contexts$.next(this.contexts$.value);\n return contextCreated;\n })\n );\n }\n\n clone(id: string, properties = {}): Observable<Context> {\n const url = this.baseUrl + '/contexts/' + id + '/clone';\n return this.http.post<Context>(url, properties).pipe(\n map((contextCloned) => {\n contextCloned.permission = TypePermission[TypePermission.write];\n this.contexts$.value.ours.unshift(contextCloned);\n this.contexts$.next(this.contexts$.value);\n return contextCloned;\n })\n );\n }\n\n update(id: string, context: DetailedContext): Observable<Context> {\n const url = this.baseUrl + '/contexts/' + id;\n return this.http.patch<Context>(url, context);\n }\n\n // =================================================================\n\n addToolAssociation(contextId: string, toolId: string): Observable<void> {\n const url = `${this.baseUrl}/contexts/${contextId}/tools`;\n const association = {\n toolId\n };\n return this.http.post<void>(url, association);\n }\n\n deleteToolAssociation(contextId: string, toolId: string): Observable<any> {\n const url = `${this.baseUrl}/contexts/${contextId}/tools/${toolId}`;\n return this.http.delete(url);\n }\n\n getPermissions(id: string): Observable<ContextPermission[]> {\n const url = this.baseUrl + '/contexts/' + id + '/permissions';\n return this.http.get<ContextPermission[]>(url);\n }\n\n addPermissionAssociation(\n contextId: string,\n profil: string,\n type: TypePermission\n ): Observable<ContextPermission[]> {\n const url = `${this.baseUrl}/contexts/${contextId}/permissions`;\n const association = {\n profil,\n typePermission: type\n };\n\n return this.http.post<ContextPermission[]>(url, association).pipe(\n catchError((res) => {\n this.handleError(res, undefined, true);\n throw [res]; // TODO Not sure about this.\n })\n );\n }\n\n deletePermissionAssociation(\n contextId: string,\n permissionId: string\n ): Observable<void> {\n const url = `${this.baseUrl}/contexts/${contextId}/permissions/${permissionId}`;\n return this.http.delete<void>(url);\n }\n\n // ======================================================================\n\n getLocalContexts(): Observable<ContextsList> {\n const url = this.getPath(this.options.contextListFile);\n return this.http.get<ContextsList>(url).pipe(\n map((res: any) => {\n return { ours: res };\n })\n );\n }\n\n getLocalContext(uri: string): Observable<DetailedContext> {\n const url = this.getPath(`${uri}.json`);\n return this.http.get<DetailedContext>(url).pipe(\n mergeMap((res) => {\n if (!res.base) {\n return of(res);\n }\n const urlBase = this.getPath(`${res.base}.json`);\n return this.http.get<DetailedContext>(urlBase).pipe(\n map((resBase: DetailedContext) => {\n const resMerge = res;\n resMerge.map = ObjectUtils.mergeDeep(resBase.map, res.map);\n resMerge.layers = (resBase.layers || [])\n .concat(res.layers || [])\n .reverse()\n .filter(\n (l, index, self) =>\n !l.id || self.findIndex((l2) => l2.id === l.id) === index\n )\n .reverse();\n resMerge.toolbar = res.toolbar || resBase.toolbar;\n resMerge.message = res.message || resBase.message;\n resMerge.messages = res.messages || resBase.messages;\n resMerge.tools = (res.tools || [])\n .concat(resBase.tools || [])\n .filter(\n (t, index, self) =>\n self.findIndex((t2) => t2.name === t.name) === index\n );\n return resMerge;\n }),\n catchError((err) => {\n this.handleError(err, uri);\n throw err;\n })\n );\n }),\n catchError((err2) => {\n this.handleError(err2, uri);\n throw err2;\n })\n );\n }\n\n loadContexts(permissions?: string[], hidden?: boolean) {\n let request;\n if (this.baseUrl) {\n request = this.get(permissions, hidden);\n } else {\n request = this.getLocalContexts();\n }\n request.subscribe((contexts) => {\n contexts.ours = this.importedContext.concat(contexts.ours);\n this.contexts$.next(contexts);\n });\n }\n\n loadDefaultContext() {\n const loadFct = (direct = false) => {\n if (!direct && this.baseUrl && this.authService.authenticated) {\n this.getDefault().subscribe(\n (_context: DetailedContext) => {\n this.defaultContextUri = _context.uri;\n this.addContextToList(_context);\n this.setContext(_context);\n },\n () => {\n this.defaultContextId$.next(undefined);\n this.loadContext(this.defaultContextUri);\n }\n );\n } else {\n this.loadContext(this.defaultContextUri);\n }\n };\n\n if (this.route && this.route.options.contextKey) {\n this.route.queryParams.pipe(debounceTime(100)).subscribe((params) => {\n const contextParam = params[this.route.options.contextKey as string];\n let direct = false;\n if (contextParam) {\n this.defaultContextUri = contextParam;\n direct = true;\n }\n loadFct(direct);\n });\n } else {\n loadFct();\n }\n }\n\n loadContext(uri: string) {\n const context = this.context$.value;\n\n if (context && context.uri === uri) {\n return;\n }\n\n this.getContextByUri(uri)\n .pipe(first())\n .subscribe(\n (_context: DetailedContext) => {\n this.addContextToList(_context);\n this.setContext(_context);\n },\n () => {\n if (uri !== this.options.defaultContextUri) {\n this.loadContext(this.options.defaultContextUri);\n }\n }\n );\n }\n\n setContext(context: DetailedContext) {\n this.handleContextMessage(context);\n const currentContext = this.context$.value;\n if (currentContext && context && context.id === currentContext.id) {\n if (context.map.view.keepCurrentView === undefined) {\n context.map.view.keepCurrentView = true;\n }\n this.context$.next(context);\n return;\n }\n\n if (!context.map) {\n context.map = { view: {} };\n }\n\n Object.assign(context.map.view, this.mapViewFromRoute);\n\n this.context$.next(context);\n }\n\n loadEditedContext(uri: string) {\n this.getContextByUri(uri).subscribe((_context: DetailedContext) => {\n this.setEditedContext(_context);\n });\n }\n\n setEditedContext(context: DetailedContext) {\n this.editedContext$.next(context);\n }\n\n getContextFromMap(igoMap: IgoMap, empty?: boolean): DetailedContext {\n const view = igoMap.ol.getView();\n const proj = view.getProjection().getCode();\n const center = new olPoint(view.getCenter()).transform(proj, 'EPSG:4326');\n\n const context: DetailedContext = {\n uri: uuid(),\n title: '',\n scope: 'private',\n map: {\n view: {\n center: center.getCoordinates() as [number, number],\n zoom: view.getZoom(),\n projection: proj,\n maxZoomOnExtent: igoMap.viewController.maxZoomOnExtent\n }\n },\n layers: [],\n tools: []\n };\n\n const layers: AnyLayer[] = empty\n ? []\n : [...igoMap.layerController.treeLayers];\n\n const baseLayer = igoMap.layerController.baseLayer;\n if (baseLayer) {\n layers.unshift(baseLayer);\n }\n\n // Remove null for the first level, the LayerGroup is already doing that for it's children\n context.layers = layers\n .map((layer) => layer.saveableOptions)\n .filter(Boolean);\n\n context.tools = this.tools.map((tool) => {\n return {\n id: String(tool.id),\n global: tool.global\n } as Tool;\n });\n\n return context;\n }\n\n getContextFromLayers(\n igoMap: IgoMap,\n layers: AnyLayer[],\n name: string,\n keepCurrentView?: boolean\n ): DetailedContext {\n const view = igoMap.ol.getView();\n const proj = view.getProjection().getCode();\n const center = new olPoint(view.getCenter()).transform(proj, 'EPSG:4326');\n\n const context: DetailedContext = {\n uri: name,\n title: name,\n map: {\n view: {\n center: center.getCoordinates() as [number, number],\n zoom: view.getZoom(),\n projection: proj,\n keepCurrentView\n }\n },\n layers: [],\n toolbar: [],\n tools: [],\n extraFeatures: []\n };\n\n context.layers = igoMap.layerController.baseLayers.map((l: Layer) => {\n return {\n baseLayer: true,\n sourceOptions: l.options.sourceOptions,\n title: l.options.title,\n visible: l.visible\n };\n });\n\n layers.forEach((layer) => {\n if (isLayerGroup(layer)) {\n return context.layers.push(layer.options);\n }\n if (!(layer.ol.getSource() instanceof olVectorSource)) {\n const layerOptions = layer.options;\n layerOptions.zIndex = layer.zIndex;\n layerOptions.visible = layer.visible;\n layerOptions.opacity = layer.opacity;\n delete layerOptions.source;\n context.layers.push(layerOptions);\n } else {\n if (!(layer.ol.getSource() instanceof olVectorSource)) {\n const catalogLayer = layer.options;\n catalogLayer.zIndex = layer.zIndex;\n catalogLayer.visible = layer.visible;\n catalogLayer.opacity = layer.opacity;\n delete catalogLayer.source;\n context.layers.push(catalogLayer);\n } else {\n const extraFeatures = this.getExtraFeatures(layer);\n extraFeatures.name = layer.options.title;\n extraFeatures.opacity = layer.opacity;\n extraFeatures.visible = layer.visible;\n context.extraFeatures.push(extraFeatures);\n }\n }\n });\n\n context.toolbar = this.toolbar;\n context.tools = this.tools;\n\n return context;\n }\n\n private getExtraFeatures(layer: Layer): ExtraFeatures {\n const writer = new GeoJSON();\n let olFeatures: Feature<Geometry>[];\n if (layer.ol.getSource() instanceof Cluster) {\n const clusterSource = layer.ol.getSource() as Cluster;\n olFeatures = clusterSource.getFeatures();\n olFeatures = (olFeatures as any).flatMap((cluster: any) =>\n cluster.get('features')\n );\n } else {\n const source = layer.ol.getSource() as olVectorSource;\n olFeatures = source.getFeatures();\n }\n const cleanedOlFeatures = this.exportService.cleanFeatures(\n olFeatures,\n 'GeoJSON',\n '_featureStore'\n );\n const features = writer.writeFeatures(cleanedOlFeatures, {\n dataProjection: 'EPSG:4326',\n featureProjection: 'EPSG:3857'\n });\n return JSON.parse(features);\n }\n\n setTools(tools: Tool[]) {\n this.tools = tools;\n }\n\n setToolbar(toolbar: string[]) {\n this.toolbar = toolbar;\n }\n\n private handleContextMessage(context: DetailedContext) {\n if (\n this.context$.value &&\n context.uri &&\n this.context$.value.uri !== context.uri\n ) {\n this.messageService.removeAllAreNotError();\n }\n\n context.messages = context.messages ? context.messages : [];\n context.messages.push(context.message);\n context.messages.map((message) => {\n if (message) {\n this.messageService.message(message as Message);\n }\n });\n }\n\n private getContextByUri(uri: string): Observable<DetailedContext> {\n if (this.baseUrl) {\n let contextToLoad;\n for (const key of Object.keys(this.contexts$.value)) {\n contextToLoad = this.contexts$.value[key].find((c) => {\n return c.uri === uri;\n });\n if (contextToLoad) {\n break;\n }\n }\n\n if (contextToLoad && contextToLoad.imported) {\n return of(contextToLoad);\n }\n\n // TODO : use always id or uri\n const id = contextToLoad ? contextToLoad.id : uri;\n return this.getDetails(id);\n }\n\n const importedContext = this.contexts$.value.ours.find((currentContext) => {\n return currentContext.uri === uri && currentContext.imported === true;\n });\n\n if (importedContext) {\n return of(importedContext);\n } else {\n return this.getLocalContext(uri);\n }\n }\n\n getContextLayers(map: IgoMap) {\n return map.layerController.treeLayers;\n }\n\n private readParamsFromRoute() {\n if (!this.route) {\n return;\n }\n\n this.route.queryParams.subscribe((params) => {\n const centerKey = this.route.options.centerKey;\n if (centerKey && params[centerKey as string]) {\n const centerParams = params[centerKey as string];\n this.mapViewFromRoute.center = centerParams.split(',').map(Number);\n }\n\n const projectionKey = this.route.options.projectionKey;\n if (projectionKey && params[projectionKey as string]) {\n const projectionParam = params[projectionKey as string];\n this.mapViewFromRoute.projection = projectionParam;\n }\n\n const zoomKey = this.route.options.zoomKey;\n if (zoomKey && params[zoomKey as string]) {\n const zoomParam = params[zoomKey as string];\n this.mapViewFromRoute.zoom = Number(zoomParam);\n }\n });\n }\n\n private getPath(file: string) {\n const basePath = this.options.basePath.replace(/\\/$/, '');\n\n return `${basePath}/${file}`;\n }\n\n private handleError(\n error: HttpErrorResponse,\n uri: string,\n permissionError?: boolean\n ) {\n const context = this.contexts$.value.ours.find((obj) => obj.uri === uri);\n const titleContext = context ? context.title : uri;\n error.error.title = this.languageService.translate.instant(\n 'igo.context.contextManager.invalid.title'\n );\n\n error.error.message = this.languageService.translate.instant(\n 'igo.context.contextManager.invalid.text',\n { value: titleContext }\n );\n\n error.error.toDisplay = true;\n\n if (permissionError) {\n error.error.title = this.languageService.translate.instant(\n 'igo.context.contextManager.errors.addPermissionTitle'\n );\n error.error.message = this.languageService.translate.instant(\n 'igo.context.contextManager.errors.addPermission'\n );\n }\n this.messageService.error(\n 'igo.context.contextManager.errors.addPermission',\n 'igo.context.contextManager.errors.addPermissionTitle'\n );\n }\n\n private handleContextsChange(keepCurrentContext = true) {\n const context = this.context$.value;\n const editedContext = this.editedContext$.value;\n if (!context || context.uri === this.options.defaultContextUri) {\n keepCurrentContext = false;\n }\n if (!keepCurrentContext || !this.findContext(context)) {\n this.defaultContextUri = undefined;\n this.loadDefaultContext();\n } else {\n this.getContextByUri(context.uri)\n .pipe(first())\n .subscribe((newContext) => {\n this.toolsChanged$.next(newContext);\n });\n\n if (this.baseUrl && this.authService.authenticated) {\n this.getDefault().subscribe();\n }\n }\n const editedFound = this.findContext(editedContext);\n if (!editedFound || editedFound.permission !== 'write') {\n this.setEditedContext(undefined);\n }\n }\n\n private addContextToList(context: Context) {\n const contextFound = this.findContext(context);\n if (!contextFound) {\n const contextSimplifie = {\n id: context.id,\n uri: context.uri,\n title: context.title,\n scope: context.scope,\n permission: TypePermission[TypePermission.read]\n };\n\n if (this.contexts$.value && this.contexts$.value.public) {\n this.contexts$.value.public.push(contextSimplifie);\n this.contexts$.next(this.contexts$.value);\n }\n }\n }\n\n private findContext(context: Context) {\n if (!context) {\n return false;\n }\n\n const contexts = this.contexts$.value;\n let found;\n for (const key of Object.keys(contexts)) {\n const value = contexts[key];\n found = value.find(\n (c) =>\n (context.id && c.id === context.id) ||\n (context.uri && c.uri === context.uri)\n );\n if (found) {\n break;\n }\n }\n\n return found;\n }\n}\n","export class ExportError extends Error {}\n\nexport class ExportInvalidFileError extends ExportError {\n constructor() {\n super('Invalid context');\n Object.setPrototypeOf(this, ExportInvalidFileError.prototype);\n }\n}\n\nexport class ExportNothingToExportError extends ExportError {\n constructor() {\n super('Nothing to export');\n Object.setPrototypeOf(this, ExportNothingToExportError.prototype);\n }\n}\n","import { Injectable } from '@angular/core';\n\nimport { downloadContent } from '@igo2/utils';\n\nimport { Observable, Observer } from 'rxjs';\n\nimport { DetailedContext } from '../../context-manager/shared/context.interface';\nimport { ExportNothingToExportError } from './context-export.errors';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ContextExportService {\n export(res: DetailedContext): Observable<void> {\n return this.exportAsync(res);\n }\n\n protected exportAsync(res: DetailedContext): Observable<void> {\n const doExport = (observer: Observer<void>) => {\n const nothingToExport = this.nothingToExport(res);\n if (nothingToExport === true) {\n observer.error(new ExportNothingToExportError());\n return;\n }\n const contextJSON = JSON.stringify(res);\n downloadContent(\n contextJSON,\n 'text/json;charset=utf-8',\n `${res.uri}.json`\n );\n observer.complete();\n };\n return new Observable(doExport);\n }\n\n protected nothingToExport(res: DetailedContext): boolean {\n if (res.map === undefined) {\n return true;\n }\n return false;\n }\n}\n","import { MessageService } from '@igo2/core/message';\n\nimport { ExportNothingToExportError } from './context-export.errors';\n\nexport function handleFileExportError(\n error: Error,\n messageService: MessageService\n) {\n if (error instanceof ExportNothingToExportError) {\n this.handleNothingToExportError(messageService);\n return;\n }\n messageService.error(\n 'igo.context.contextImportExport.export.failed.text',\n 'igo.context.contextImportExport.export.failed.title'\n );\n}\n\nexport function handleFileExportSuccess(messageService: MessageService) {\n messageService.success(\n 'igo.context.contextImportExport.export.success.text',\n 'igo.context.contextImportExport.export.success.title'\n );\n}\n\nexport function handleNothingToExportError(messageService: MessageService) {\n messageService.error(\n 'igo.context.contextImportExport.export.nothing.text',\n 'igo.context.contextImportExport.export.nothing.title'\n );\n}\n","export class ImportError extends Error {}\n\nexport class ImportInvalidFileError extends ImportError {\n constructor() {\n super('Invalid file');\n Object.setPrototypeOf(this, ImportInvalidFileError.prototype);\n }\n}\n\nexport class ImportUnreadableFileError extends ImportError {\n constructor() {\n super('Failed to read file');\n Object.setPrototypeOf(this, ImportUnreadableFileError.prototype);\n }\n}\n\nexport class ImportNothingToImportError extends ImportError {\n constructor() {\n super('Nothing to import');\n Object.setPrototypeOf(this, ImportNothingToImportError.prototype);\n }\n}\n\nexport class ImportSizeError extends ImportError {\n constructor() {\n super('File is too large');\n Object.setPrototypeOf(this, ImportNothingToImportError.prototype);\n }\n}\n\nexport class ImportSRSError extends ImportError {\n constructor() {\n super('Invalid SRS definition');\n Object.setPrototypeOf(this, ImportNothingToImportError.prototype);\n }\n}\n","import { MessageService } from '@igo2/core/message';\nimport {\n ClusterDataSource,\n ClusterDataSourceOptions,\n ClusterParam,\n FeatureDataSource,\n FeatureDataSourceOptions,\n IgoMap,\n QueryableDataSourceOptions,\n StyleByAttribute,\n StyleListService,\n StyleService,\n VectorLayer,\n featureRandomStyle,\n featureRandomStyleFunction\n} from '@igo2/geo';\n\nimport OlFeature from 'ol/Feature';\nimport GeoJSON from 'ol/format/GeoJSON';\nimport type { default as OlGeometry } from 'ol/geom/Geometry';\n\nimport {\n DetailedContext,\n ExtraFeatures\n} from '../../context-manager/shared/context.interface';\nimport { ContextService } from '../../context-manager/shared/context.service';\n\nexport function handleFileImportSuccess(\n file: File,\n context: DetailedContext,\n messageService: MessageService,\n contextService: ContextService\n) {\n if (Object.keys(context).length <= 0) {\n handleNothingToImportError(file, messageService);\n return;\n }\n\n const contextTitle = computeLayerTitleFromFile(file);\n\n addContextToContextList(context, contextTitle, contextService);\n\n messageService.success(\n 'igo.context.contextImportExport.import.success.text',\n 'igo.context.contextImportExport.import.success.title',\n undefined,\n {\n value: contextTitle\n }\n );\n}\n\nexport function handleFileImportError(\n file: File,\n error: Error,\n messageService: MessageService,\n sizeMb?: number\n) {\n sizeMb = sizeMb ? sizeMb : 30;\n const errMapping = {\n 'Invalid file': handleInvalidFileImportError,\n 'File is too large': handleSizeFileImportError,\n 'Failed to read file': handleUnreadbleFileImportError\n };\n errMapping[error.message](file, error, messageService, sizeMb);\n}\n\nexport function handleInvalidFileImportError(\n file: File,\n error: Error,\n messageService: MessageService\n) {\n messageService.error(\n 'igo.context.contextImportExport.import.invalid.text',\n 'igo.context.contextImportExport.import.invalid.title',\n undefined,\n {\n value: file.name,\n mimeType: file.type\n }\n );\n}\n\nexport function handleSizeFileImportError(\n file: File,\n error: Error,\n messageService: MessageService,\n sizeMb: number\n) {\n messageService.error(\n 'igo.context.contextImportExport.import.tooLarge.text',\n 'igo.context.contextImportExport.import.tooLarge.title',\n undefined,\n {\n value: file.name,\n size: sizeMb\n }\n );\n}\n\nexport function handleUnreadbleFileImportError(\n file: File,\n error: Error,\n messageService: MessageService\n) {\n messageService.error(\n 'igo.context.contextImportExport.import.unreadable.text',\n 'igo.context.contextImportExport.import.unreadable.title',\n undefined,\n {\n value: file.name\n }\n );\n}\n\nexport function handleNothingToImportError(\n file: File,\n messageService: MessageService\n) {\n messageService.error(\n 'igo.context.contextImportExport.import.empty.text',\n 'igo.context.contextImportExport.import.empty.title',\n undefined,\n { value: file.name }\n );\n}\n\nexport function addContextToContextList(\n context: DetailedContext,\n contextTitle: string,\n contextService: ContextService\n) {\n context.title = contextTitle;\n context.imported = true;\n contextService.contexts$.value.ours.unshift(context);\n contextService.contexts$.next(contextService.contexts$.value);\n contextService.importedContext.unshift(context);\n contextService.loadContext(context.uri);\n}\n\nexport function getFileExtension(file: File): string {\n return file.name.split('.').pop().toLowerCase();\n}\n\nexport function computeLayerTitleFromFile(file: File): string {\n return file.name.substr(0, file.name.lastIndexOf('.'));\n}\n\nexport function addImportedFeaturesToMap(\n extraFeatures: ExtraFeatures,\n map: IgoMap\n): VectorLayer {\n const sourceOptions: FeatureDataSourceOptions & QueryableDataSourceOptions = {\n type: 'vector',\n queryable: true\n };\n\n const olFeatures = collectFeaturesFromExtraFeatures(extraFeatures);\n const source = new FeatureDataSource(sourceOptions);\n source.ol.addFeatures(olFeatures);\n\n let randomStyle;\n let editable = false;\n const featureKeys = olFeatures[0]?.getKeys() ?? [];\n if (featureKeys.includes('_style') || featureKeys.includes('_mapTitle')) {\n randomStyle = featureRandomStyleFunction();\n } else {\n randomStyle = featureRandomStyle();\n editable = true;\n }\n const layer = new VectorLayer({\n title: extraFeatures.name,\n isIgoInternalLayer: true,\n source,\n igoStyle: { editable },\n style: randomStyle,\n visible: extraFeatures.visible,\n opacity: extraFeatures.opacity\n });\n map.layerController.add(layer);\n\n return layer;\n}\n\nexport function addImportedFeaturesStyledToMap(\n extraFeatures: ExtraFeatures,\n map: IgoMap,\n styleListService: StyleListService,\n styleService: StyleService\n): VectorLayer {\n let style;\n let distance: number;\n\n if (styleListService.getStyleList(extraFeatures.name + '.styleByAttribute')) {\n const styleByAttribute: StyleByAttribute = styleListService.getStyleList(\n extraFeatures.name + '.styleByAttribute'\n );\n\n style = (feature, resolution) => {\n return styleService.createStyleByAttribute(\n feature,\n styleByAttribute,\n resolution\n );\n };\n } else if (\n styleListService.getStyleList(extraFeatures.name + '.clusterStyle')\n ) {\n const clusterParam: ClusterParam = styleListService.getStyleList(\n extraFeatures.name + '.clusterParam'\n );\n distance = styleListService.getStyleList(extraFeatures.name + '.distance');\n\n style = (feature, resolution) => {\n const baseStyle = styleService.createStyle(\n styleListService.getStyleList(extraFeatures.name + '.clusterStyle'),\n feature,\n resolution\n );\n return styleService.createClusterStyle(\n feature,\n resolution,\n clusterParam,\n baseStyle\n );\n };\n } else if (styleListService.getStyleList(extraFeatures.name + '.style')) {\n style = (feature, resolution) =>\n styleService.createStyle(\n styleListService.getStyleList(extraFeatures.name + '.style'),\n feature,\n resolution\n );\n } else {\n style = (feature, resolution) =>\n styleService.createStyle(\n styleListService.getStyleList('default.style'),\n feature,\n resolution\n );\n }\n let source;\n const olFeatures = collectFeaturesFromExtraFeatures(extraFeatures);\n if (styleListService.getStyleList(extraFeatures.name + '.clusterStyle')) {\n const sourceOptions: ClusterDataSourceOptions & QueryableDataSourceOptions =\n {\n distance,\n type: 'cluster',\n queryable: true\n };\n source = new ClusterDataSource(sourceOptions);\n source.ol.source.addFeatures(olFeatures);\n } else {\n const sourceOptions: FeatureDataSourceOptions & QueryableDataSourceOptions =\n {\n type: 'vector',\n queryable: true\n };\n source = new FeatureDataSource(sourceOptions);\n source.ol.addFeatures(olFeatures);\n }\n\n const layer = new VectorLayer({\n title: extraFeatures.name,\n isIgoInternalLayer: true,\n source,\n style,\n opacity: extraFeatures.opacity,\n visible: extraFeatures.visible\n });\n map.layerController.add(layer);\n\n return layer;\n}\n\nfunction collectFeaturesFromExtraFeatures(\n featureCollection: ExtraFeatures\n): OlFeature<OlGeometry>[] {\n const format = new GeoJSON();\n const features = format.readFeatures(featureCollection, {\n dataProjection: 'EPSG:4326',\n featureProjection: 'EPSG:3857'\n });\n return features;\n}\n","import { Injectable } from '@angular/core';\n\nimport { ConfigService } from '@igo2/core/config';\n\nimport { Observable, Observer } from 'rxjs';\n\nimport { DetailedContext } from '../../context-manager/shared/context.interface';\nimport {\n ImportInvalidFileError,\n ImportSizeError,\n ImportUnreadableFileError\n} from './context-import.errors';\nimport { getFileExtension } from './context-import.utils';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ContextImportService {\n static allowedMimeTypes = ['application/json'];\n\n static allowedExtensions = 'json';\n\n private clientSideFileSizeMax: number;\n\n constructor(private config: ConfigService) {\n const configFileSizeMb = this.config.getConfig(\n 'importExport.clientSideFileSizeMaxMb'\n );\n this.clientSideFileSizeMax =\n (configFileSizeMb ? configFileSizeMb : 30) * Math.pow(1024, 2);\n }\n\n import(file: File): Observable<DetailedContext> {\n return this.importAsync(file);\n }\n\n private getFileImporter(\n file: File\n ): (\n file: File,\n observer: Observer<DetailedContext>,\n projectionIn: string,\n projectionOut: string\n ) => void {\n const extension = getFileExtension(file);\n const mimeType = file.type;\n const allowedMimeTypes = [...ContextImportService.allowedMimeTypes];\n const allowedExtensions = ContextImportService.allowedExtensions;\n\n if (\n allowedMimeTypes.indexOf(mimeType) < 0 &&\n allowedExtensions.indexOf(extension) < 0\n ) {\n return undefined;\n } else if (\n mimeType === 'application/json' ||\n extension === ContextImportService.allowedExtensions\n ) {\n return this.importFile;\n }\n return undefined;\n }\n\n private importAsync(file: File): Observable<DetailedContext> {\n const doImport = (observer: Observer<DetailedContext>) => {\n if (file.size >= this.clientSideFileSizeMax) {\n observer.error(new ImportSizeError());\n return;\n }\n const importer = this.getFileImporter(file);\n if (importer === undefined) {\n observer.error(new ImportInvalidFileError());\n return;\n }\n\n importer.call(this, file, observer);\n };\n\n return new Observable(doImport);\n }\n\n private importFile(file: File, observer: Observer<DetailedContext>) {\n const reader = new FileReader();\n\n reader.onload = (event: any) => {\n try {\n const context = this.parseContextFromFile(file, event.target.result);\n observer.next(context);\n } catch {\n observer.error(new ImportUnreadableFileError());\n }\n\n observer.complete();\n };\n\n reader.onerror = () => {\n observer.error(new ImportUnreadableFileError());\n };\n\n reader.readAsText(file, 'UTF-8');\n }\n\n private parseContextFromFile(file: File, data: string): DetailedContext {\n const context: DetailedContext = JSON.parse(data);\n return context;\n }\n}\n","import { AsyncPipe, NgFor, NgIf } from '@angular/common';\nimport { Component, Input, OnInit } from '@angular/core';\nimport {\n FormsModule,\n ReactiveFormsModule,\n UntypedFormBuilder,\n UntypedFormGroup,\n Validators\n} from '@angular/forms';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatButtonToggleModule } from '@angular/material/button-toggle';\nimport { MatOptionModule } from '@angular/material/core';\nimport { MatDividerModule } from '@angular/material/divider';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\n\nimport { SpinnerComponent } from '@igo2/common/spinner';\nimport { ConfigService } from '@igo2/core/config';\nimport { IgoLanguageModule } from '@igo2/core/language';\nimport { MessageService } from '@igo2/core/message';\nimport { type AnyLayer, type IgoMap, VectorLayer } from '@igo2/geo';\n\nimport { BehaviorSubject } from 'rxjs';\nimport { take } from 'rxjs/operators';\n\nimport { DetailedContext } from '../../context-manager/shared/context.interface';\nimport { ContextService } from '../../context-manager/shared/context.service';\nimport { ContextExportService } from '../shared/context-export.service';\nimport { handleFileExportError } from '../shared/context-export.utils';\nimport { handleFileExportSuccess } from '../shared/context-export.utils';\nimport { ContextImportService } from '../shared/context-import.service';\nimport {\n handleFileImportError,\n handleFileImportSuccess\n} from '../shared/context-import.utils';\n\n@Component({\n selector: 'igo-context-import-export',\n templateUrl: './context-import-export.component.html',\n styleUrls: ['./context-import-export.component.scss'],\n standalone: true,\n imports: [\n MatButtonToggleModule,\n NgIf,\n FormsModule,\n MatButtonModule,\n SpinnerComponent,\n ReactiveFormsModule,\n MatFormFieldModule,\n MatInputModule,\n MatSelectModule,\n MatOptionModule,\n MatDividerModule,\n NgFor,\n AsyncPipe,\n IgoLanguageModule\n ]\n})\nexport class ContextImportExportComponent implements OnInit {\n public form: UntypedFormGroup;\n public layers: VectorLayer[];\n public inputProj = 'EPSG:4326';\n public loading$ = new BehaviorSubject(false);\n public forceNaming = false;\n public layerList: AnyLayer[];\n public userControlledLayerList: readonly AnyLayer[];\n public res: DetailedContext;\n private clientSideFileSizeMax: number;\n public fileSizeMb: number;\n public activeImportExport = 'import';\n\n @Input() map: IgoMap;\n\n constructor(\n private contextImportService: ContextImportService,\n private contextExportService: ContextExportService,\n private messageService: MessageService,\n private formBuilder: UntypedFormBuilder,\n private config: ConfigService,\n private contextService: ContextService\n ) {\n this.buildForm();\n }\n\n ngOnInit() {\n const configFileSizeMb = this.config.getConfig(\n 'importExport.clientSideFileSizeMaxMb'\n );\n this.clientSideFileSizeMax =\n (configFileSizeMb ? configFileSizeMb : 30) * Math.pow(1024, 2);\n this.fileSizeMb = this.clientSideFileSizeMax / Math.pow(1024, 2);\n\n this.layerList = this.map.layerController.all;\n this.userControlledLayerList = this.map.layerController.treeLayers;\n }\n\n importFiles(files: File[]) {\n this.loading$.next(true);\n for (const file of files) {\n this.contextImportService\n .import(file)\n .pipe(take(1))\n .subscribe(\n (context: DetailedContext) => this.onFileImportSuccess(file, context),\n (error: Error) => this.onFileImportError(file, error),\n () => {\n this.loading$.next(false);\n }\n );\n }\n }\n\n handleExportFormSubmit(contextOptions) {\n this.loading$.next(true);\n this.res = this.contextService.getContextFromLayers(\n this.map,\n contextOptions.layers,\n contextOptions.name,\n false\n );\n this.res.imported = true;\n this.contextExportService\n .export(this.res)\n .pipe(take(1))\n .subscribe({\n error: (error: Error) => this.onFileExportError(error),\n complete: () => {\n this.onFileExportSuccess();\n this.loading$.next(false);\n }\n });\n }\n\n private buildForm() {\n this.form = this.formBuilder.group({\n layers: ['', [Validators.required]],\n name: ['', [Validators.required]]\n });\n }\n\n private onFileImportSuccess(file: File, context: DetailedContext) {\n handleFileImportSuccess(\n file,\n context,\n this.messageService,\n this.contextService\n );\n }\n\n private onFileImportError(file: File, error: Error) {\n this.loading$.next(false);\n handleFileImportError(file, error, this.messageService, this.fileSizeMb);\n }\n\n private onFileExportError(error: Error) {\n this.loading$.next(false);\n handleFileExportError(error, this.messageService);\n }\n\n private onFileExportSuccess() {\n handleFileExportSuccess(this.messageService);\n }\n\n selectAll(e) {\n if (e._selected) {\n this.form.controls.layers.setValue(this.userControlledLayerList);\n e._selected = true;\n }\n if (e._selected === false) {\n this.form.controls.layers.setValue([]);\n }\n }\n\n onImportExportChange(event) {\n this.activeImportExport = event.value;\n }\n}\n","<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: fileS