import { Injectable } from '@angular/core' import { UntypedFormGroup, UntypedFormControl } from '@angular/forms' import { Observable, BehaviorSubject, forkJoin, of as observableOf, Subject } from 'rxjs' import { map } from 'rxjs/operators' // import { Apollo, gql } from 'apollo-angular' import { MapControlService } from '../map/map-control.service' import { LayerNode } from '../map/models' import { Tag, TagAction } from './info-tags/tags.service' import { ConfigService } from '../config.service' import { DataService } from '../_services/data.service' // const getModelInfoQuery = gql` // query modelInfo ($store: String!) { // modelInfo(store: $store) { // store // modelName // symbol // values { // name // title // unit // chartType // chartColor // } // actions { // name // icon // formFields { // name // type // } // } // formName // formFields { // name // type // } // tagPlugins // tagActions { // domain // key // actions { // plugin // name // link // action // roles // } // } // downloaders { // name // icon // } // legend { // key // value // } // } // } // ` // const getTagKeysQuery = gql` // query tagKeyList { // tagKeyList { // keys // } // } // ` // const createTagMutation = gql` // mutation createTag( // $store: String! // $id: String! // $key: String! // $value: String! // ) { // createTag( // store: $store, // id: $id, // key: $key, // value: $value // ) { // tags { // key // value // } // } // } // ` // const createTagsMutations = gql` // mutation createTags( // $keys: [String]! // $values: [String]! // $stores: [String]! // $ids: [[String]]! // ) { // createTags( // keys: $keys, // values: $values, // stores: $stores, // ids: $ids, // ) { // tags { // key // value // } // } // } // ` // const getPlotParamsQuery = gql` // query plotParams($store: String!, $id:String!, $value:String!) { // plotParams(store: $store, id: $id, value: $value) { // baseLines { // name // value // color // } // bgShapes { // name // valueTop // valueBottom // color // } // barBase // } // } // ` // const getFeatureInfoQuery = gql` // query featureInfo($store: String!, $id:String!) { // featureInfo(store: $store, id: $id) { // id // itemName // geoInfoItems { // key // value // } // surveyInfoItems { // key // value // } // infoItems { // key // value // } // categorizedInfoItems { // name // infoItems { // key // value // } // } // tags { // key // value // } // graph // files { // name // path // } // images { // name // path // } // externalRecordUrl // } // } // ` // const deleteTagQuery = gql` // mutation deleteTag( // $store: String! // $id: String! // $key: String! // ) { // deleteTag( // store: $store, // id: $id, // key: $key, // ) { // tags { // key // value // } // } // } // ` // const getTaggedFeaturesQuery = gql` // query taggedFeatures($stores: [String], $ids: [[String]]) { // taggedFeatures(stores: $stores, ids: $ids) { // store // taggedFeatures { // id // lon // lat // tags { // key // value // } // } // } // } // ` // const getTaggedStoresQuery = gql` // query taggedStores($stores: [String]) { // taggedStores(stores: $stores) { // store // taggedFeatures { // id // lon // lat // tags { // key // value // } // } // } // } // ` // const executeFeatureActionMutation=gql` // mutation executeFeatureAction ( // $store: String!, // $id: String!, // $action: String!, // $value: String, // ) { // executeFeatureAction( // store: $store, // id: $id, // action: $action, // value: $value, // ) { // result // } // } // ` export class TaggedFeature { constructor( public id: number, public lon: number, public lat: number, public tags: Tag[], ) {} } export class TaggedLayer { constructor( public store: string, public features: TaggedFeature[] ) {} } export class ModelAction { constructor( public name: string, public icon: string, public formFields: FormField[], ) {} /* execute(fullInfo: FullInfo) { // XXX: for downloads (reports) only // TODO: make actions generic (query, mutation, parameters...) window.open('/download/action/' + this.name + '/' + fullInfo.modelInfo.store + '/' + fullInfo.featureInfo.id) } */ } export class FormFieldInput { constructor( public name: string, public value: string, ) {} } export class FormField { constructor( public name: string, public type: string, public dflt?: string, public value?: string ) {} } export class ModelValue { constructor( public name: string, public title: string, public unit: string, public chartType: string = 'line', public chartColor: string = 'blue', ) {} } export class Downloader { constructor( public name: string, public icon: string, ) {} } export class LegendItem { constructor( public key: string, public value: string, ) {} } export class ModelInfo { constructor( public store: string, public modelName: string, public symbol: string, public values: ModelValue[], public actions: ModelAction[], public formName: string, public formFields: FormField[], public tagPlugins: String[], public tagActions: TagAction[], public downloaders: Downloader[], public legend: LegendItem[], ) {} getFormFields(formGroup: UntypedFormGroup, fullInfo: FullInfo): object[] { // Return the form fields and build the FormGroup controls accordingly let formFields = [] fullInfo.modelInfo.formFields.forEach( field => { let control = new UntypedFormControl(field.name) //, field.validator) formGroup.addControl(field.name, control) formFields.push(field) } ) return formFields } } export class PlotBaseLine { constructor( public name: string, public value: number, public color: string, ) {} } export class PlotBgShape { constructor( public name: string, public valueTop: number, public valueBottom: number, public color: string, ) {} } export class PlotParams { constructor( public baseLines: PlotBaseLine[] = [], public bgShapes: PlotBgShape[] = [], public barBase?: number ) {} } export class PlotDataParams { constructor( public data: Object, public comment: string, public params: PlotParams, ) {} } export class InfoItem { constructor( public key: string, public value: string, ) {} } export class InfoCategory { constructor( public name: string, public infoItems: InfoItem[], ) {} } export class Attachment { constructor( public name: string, public path: string, ) {} } export class Feature { constructor ( public store: string, public id: string, ) {} } export class FeatureWithField { constructor ( public store: string, public field: string, public value: string, ) {} } export class FeatureInfo { constructor( public id: string, public itemName: string, public geoInfoItems: InfoItem[], public surveyInfoItems: InfoItem[], public infoItems: InfoItem[], public categorizedInfoItems: InfoCategory[], public tags: Tag[], public graph?: string, public files?: Attachment[], public images?: Attachment[], public externalRecordUrl?: string ) {} openExternalRecord() { window.open(this.externalRecordUrl) } } export class FullInfo { constructor( public modelInfo: ModelInfo, public featureInfo: FeatureInfo, ) {} hasForm(): Boolean { return this.modelInfo.formFields.length > 0 } } @Injectable() export class InfoDataService { constructor( public configService: ConfigService, // private apollo: Apollo, public mapControlService: MapControlService, protected dataService: DataService, ) {} public refresh = new Subject() public refresh$ = this.refresh.asObservable() public dataProviderService = new BehaviorSubject(undefined) public dataProviderService$ = this.dataProviderService.asObservable() public taggedFeaturesSelectionService = new BehaviorSubject([]) public taggedFeaturesSelectionService$ = this.taggedFeaturesSelectionService.asObservable() // taggedLayers: holds the tags for each feature, for each selected layer, for search public taggedLayers = new BehaviorSubject([]) public taggedLayers$ = this.taggedLayers.asObservable() getModelInfo(store: string): Observable { console.warn('Migrate Graphql') return observableOf() // return this.apollo.query({ // query: getModelInfoQuery, // variables: { // store: store, // } // }).pipe(map( // res => { // let info: Object = res['data']['modelInfo'] // let values = (info['values'] || []).map( // (value: Object) => new ModelValue( // value['name'], // value['title'], // value['unit'], // value['chartType'], // value['chartColor'], // ) // ) // return new ModelInfo( // info['store'], // info['modelName'], // info['symbol'], // values, // info['actions'] ? info['actions'].map( // action => new ModelAction( // action['name'], // action['icon'], // action['formFields'].map( // formField => new FormField( // formField['name'], // formField['type'], // formField['dflt'] // ) // ) // ) // ): [], // info['formName'], // info['formFields'] ? info['formFields'].map( // (formField: Object) => new FormField( // formField['name'], // formField['type'], // ) // ) : [], // info['tagPlugins'], // info['tagActions'] ? info['tagActions'].map( // tagAction => new TagAction( // // FIXME: set real data!!! // '**name**', // '**plugin_name**', // //tagAction['key'], // tagAction.actions[0]['action'], // ['**role**'], // '**link**', // ) // ) : [], // info['downloaders'] ? info['downloaders'].map( // downloader => new Downloader( // downloader['name'], // downloader['icon'], // ) // ) : [], // info['legend'] ? info['legend'].map( // legendItem => new LegendItem( // legendItem['key'], // legendItem['value'], // ) // ) : [], // ) // } // )) } getPlotDataAndParams(store: string, id: string, value: string, resampling: string): Observable { return forkJoin([ this.dataService.getValues(store, +id, value, resampling), this.getPlotParams(store, id, value), ]).pipe(map( res => new PlotDataParams( res[0].body, res[0].headers['comment'], res[1], ) )) } getPlotParams(store: string, id: string, value: string): Observable { console.warn('Migrate Graphql') return observableOf() // return this.apollo.query({ // query: getPlotParamsQuery, // variables: { // store: store, // id: id, // value: value // } // }).pipe(map( // info => info.data['plotParams'] ? new PlotParams( // (info.data['plotParams']['baseLines'] || []).map( // bl => new PlotBaseLine( // bl['name'], // bl['value'], // bl['color'] // ) // ), // (info.data['plotParams']['bgShapes'] || []).map( // bl => new PlotBgShape( // bl['name'], // bl['valueTop'], // bl['valueBottom'], // bl['color'] // ) // ), // info.data['plotParams']['barBase'] // ) : new PlotParams() // )) } getFeatureInfo(store: string, id: string): Observable { console.warn('Migrate Graphql') return observableOf() // return this.apollo.query({ // query: getFeatureInfoQuery, // variables: { // store: store, // id: id // } // }).pipe(map( // res => { // const info = res['data']['featureInfo'] // const geoInfoItems = info['geoInfoItems'].map(ii => new InfoItem(ii['key'], ii['value'])) // const surveyInfoItems = info['surveyInfoItems'].map(ii => new InfoItem(ii['key'], ii['value'])) // const infoItems = info['infoItems'].map(ii => new InfoItem(ii['key'], ii['value'])) // const categorizedInfoItems = info['categorizedInfoItems'] && info['categorizedInfoItems'].map( // ic => new InfoCategory( // ic['name'], // ic['infoItems'].map(ii => new InfoItem(ii['key'], ii['value'])) // ) // ) // const tags = info['tags'].map(ii => new Tag(ii['key'], ii['value'])) // return new FeatureInfo( // info['id'], // info['itemName'], // geoInfoItems, // surveyInfoItems, // infoItems, // categorizedInfoItems, // tags, // info['graph'] && info['graph'].replace(/width="\d+pt"/, '').replace(/height="\d+pt"/, ''), // info['files'] && info['files'].map(att => new Attachment(att['name'], att['path'])), // info['images'] && info['images'].map(att => new Attachment(att['name'], att['path'])), // info['externalRecordUrl'] // ) // } // )) } getFullInfo(feature: Feature): Observable { return forkJoin([ this.getModelInfo(feature.store), this.getFeatureInfo(feature.store, feature.id) ]).pipe(map( res => new FullInfo(res[0], res[1]) )) } public createTag(fullInfo: FullInfo, tag: any): Observable { let variables = { 'store': fullInfo.modelInfo.store, 'id': fullInfo.featureInfo.id, 'key': tag['key'], 'value': tag['value'], } console.warn('Migrate Graphql') return observableOf() // return this.apollo.mutate({ // mutation: createTagMutation, // variables: variables, // }).pipe(map( // res => res['data']['createTag']['tags'].map( // (tag: Object) => new Tag(tag['key'], tag['value'])) // )) } public createTags(keys: String[], values: String[], source: Object): Observable { let variables = { 'keys': keys, 'values': values, 'stores': Object.keys(source), 'ids': Object.values(source).map(ids => Array.from(ids)), } console.warn('Migrate Graphql') return observableOf() // return this.apollo.mutate({ // mutation: createTagsMutations, // variables: variables, // }).pipe(map( // res => { // let tags = res['data']['createTags']['tags'] // return tags.map( // (tag: Object) => new Tag(tag['key'], tag['value'])) // } // )) } public deleteTag(tag: Tag | any, fullInfo?: FullInfo): Observable { // tag can be FeatureTree, but circular dependencies: // import { FeatureTree } from './info-selection/info-selection-tags.component' let variables: Object if (tag instanceof Tag) { variables = { 'store': fullInfo.modelInfo.store, 'id': fullInfo.featureInfo.id, 'key': tag.key, } } else { // FeatureTree variables = { 'store': tag.getStore(), 'id': tag.id, 'key': tag.getKey(), } } console.warn('Migrate Graphql') return observableOf() // return this.apollo.mutate({ // mutation: deleteTagQuery, // variables: variables, // }) } public getTagKeys(): Observable { return observableOf(this.configService.conf.value.map['tagKeys']) // This could be fetched from the server /* return this.apollo.query({ query: getTagKeysQuery }).pipe(map( res => { return res['data']['tagKeyList']['keys'] } )) */ } private _getTaggedLayers(taggedItems: Object[]): TaggedLayer[] { /* Convert tagged features to tagged layers, putting the tag records in the graphql data structure */ return taggedItems.map( layer => new TaggedLayer( layer['store'], layer['taggedFeatures'].map( feature => new TaggedFeature( feature['id'], feature['lon'], feature['lat'], feature['tags'].map( tag => new Tag(tag['key'], tag['value']) ) ) ) ) ) } public getTaggedFeatures(features: Object): Observable { let stores = Object.keys(features) if (stores.length == 0) { this.taggedFeaturesSelectionService.next([]) return observableOf([]) } let ids = Object.values(features).map(t => Array.from(t)) console.warn('Migrate Graphql') return observableOf() // return this.apollo.query({ // query: getTaggedFeaturesQuery, // variables: { // stores: stores, // ids: ids // } // }).pipe(map( // res => { // let taggedLayers = this._getTaggedLayers(res['data']['taggedFeatures']) // // Add features with no tag // Object.entries(features).forEach( // ([store, _features]) => { // let taggedFeatures = taggedLayers.find(s => s.store==store) // if (!taggedFeatures) { // taggedFeatures = new TaggedLayer(store, []) // taggedLayers.push(taggedFeatures) // } // let taggedFeaturesIds = taggedFeatures.features.map(tf => +tf.id) // let featureIdsNoTag = Array(..._features).filter(x => !taggedFeaturesIds.includes(x)) // featureIdsNoTag.forEach( // id => { // taggedFeatures.features.push(new TaggedFeature(id, undefined, undefined, [])) // } // ) // } // ) // this.taggedFeaturesSelectionService.next(taggedLayers) // return taggedLayers // } // )) } public getTaggedStores(stores: string[]): Observable { console.warn('Migrate Graphql') return observableOf() // return this.apollo.query({ // query: getTaggedStoresQuery, // variables: { // stores: stores, // } // }).pipe(map( // res => this._getTaggedLayers(res['data']['taggedStores']) // )) } public getTagsActionsStores(stores: string[]): Observable { console.warn('Migrate Graphql') return observableOf() // return this.apollo.query({ // query: getTaggedStoresQuery, // variables: { // stores: stores, // } // }).pipe(map( // res => this._getTaggedLayers(res['data']['taggedStores']) // )) } // Load tags for selected layers public getTaggedLayers(selectedLayers: LayerNode[], forceReload: boolean=true): Observable { let selectedLayersStores = selectedLayers.map( (l: LayerNode) => l.store ) // FIXME: this.taggedLayers should be really an array let untaggedLayers = forceReload ? selectedLayersStores : selectedLayersStores.filter( (x: string) => !Object.keys(this.taggedLayers).includes(x) ) if (untaggedLayers.length > 0) { return this.getTaggedStores(untaggedLayers).pipe(map( taggedLayers => { // Update the known taggedLayers for (let taggedLayer of taggedLayers) { // FIXME: this.taggedLayers should be really an array this.taggedLayers[taggedLayer.store] = taggedLayer } // Make sure that even layers without features with tags are remembered for (let taggedLayer of untaggedLayers) { if (!this.taggedLayers.hasOwnProperty(taggedLayer)) { this.taggedLayers[taggedLayer] = new TaggedLayer(taggedLayer, []) } } // Save to mapControlService behavior subject this.taggedLayers.next(taggedLayers) return taggedLayers } )) } else { return this.taggedLayers } } }