import { Dictionary , ElementModel , LinkModel , ClassModel , LinkType , ElementIri , LinkTypeIri , ElementTypeIri , PropertyTypeIri , } from '../data/model' ; import { DataProvider } from '../data/provider' ; import { PLACEHOLDER_LINK_TYPE } from '../data/schema' ; import { Element , FatLinkType , FatClassModel , RichProperty , FatLinkTypeEvents , Link } from '../diagram/elements' ; import { CommandHistory , Command } from '../diagram/history' ; import { DiagramModel , DiagramModelEvents , placeholderDataFromIri } from '../diagram/model' ; import { EventSource , Events , Listener } from '../viewUtils/events' ; import { DataFetcher } from './dataFetcher' ; import { LayoutData , LayoutElement , makeLayoutData , convertToSerializedDiagram , emptyDiagram , LinkTypeOptions , SerializedDiagram , makeSerializedDiagram , emptyLayoutData } from './serializedDiagram' ; export interface GroupBy { linkType : string ; linkDirection : 'in' | 'out' ; } export interface AsyncModelEvents extends DiagramModelEvents { loadingStart : { source : AsyncModel ; } ; loadingSuccess : { source : AsyncModel ; } ; loadingError : { source : AsyncModel ; error : any ; } ; createLoadedLink : { source : AsyncModel ; model : LinkModel ; cancel ( ) : void ; } ; } export class AsyncModel extends DiagramModel { readonly events : Events < AsyncModelEvents > ; private _dataProvider : DataProvider ; private fetcher : DataFetcher ; private linkSettings : { [ linkTypeId ] : LinkTypeOptions ; } = { } ; constructor ( history , private groupByProperties ) { super ( history ) ; } private get asyncSource ( ) : EventSource < AsyncModelEvents > { return this . source as EventSource < any > ; } get dataProvider ( ) { return this . _dataProvider ; } subscribeGraph ( ) { super . subscribeGraph ( ) ; this . graphListener . listen ( this . graph . events , 'linkTypeEvent' , e => { if ( e . key === 'changeVisibility' ) { this . onLinkTypeVisibilityChanged ( e . data [ e . key ] , e . key ) ; } } ) ; } private setDataProvider ( dataProvider ) { this . _dataProvider = dataProvider ; this . fetcher = new DataFetcher ( this . graph , dataProvider ) ; } createNewDiagram ( dataProvider ) { this . resetGraph ( ) ; this . setDataProvider ( dataProvider ) ; this . asyncSource . trigger ( 'loadingStart' , { source : this } ) ; return this . dataProvider . linkTypes ( ) . then ( ( linkTypes ) => { const allLinkTypes = this . initLinkTypes ( linkTypes ) ; return this . loadAndRenderLayout ( { allLinkTypes , markLinksAsLayoutOnly : false , } ) ; } ) . catch ( error => { console . error ( error ) ; this . asyncSource . trigger ( 'loadingError' , { source : this , error } ) ; return Promise . reject ( error ) ; } ) ; } importLayout ( params ) { this . resetGraph ( ) ; this . setDataProvider ( params . dataProvider ) ; this . asyncSource . trigger ( 'loadingStart' , { source : this } ) ; return this . dataProvider . linkTypes ( ) . then ( linkTypes => { const allLinkTypes = this . initLinkTypes ( linkTypes ) ; const diagram = params . diagram ? params . diagram : emptyDiagram ( ) ; this . setLinkSettings ( diagram . linkTypeOptions ) ; const loadingModels = this . loadAndRenderLayout ( { layoutData : diagram . layoutData , preloadedElements : params . preloadedElements || { } , markLinksAsLayoutOnly : params . validateLinks || false , allLinkTypes , hideUnusedLinkTypes : params . hideUnusedLinkTypes , } ) ; const requestingLinks = params . validateLinks ? this . requestLinksOfType ( ) : Promise . resolve ( ) ; return Promise . all ( [ loadingModels , requestingLinks ] ) ; } ) . then ( ( ) => { this . asyncSource . trigger ( 'loadingSuccess' , { source : this } ) ; } ) . catch ( error => { console . error ( error ) ; this . asyncSource . trigger ( 'loadingError' , { source : this , error } ) ; return Promise . reject ( error ) ; } ) ; } exportLayout ( ) { const layoutData = makeLayoutData ( this . graph . getElements ( ) , this . graph . getLinks ( ) ) ; const linkTypeOptions = this . graph . getLinkTypes ( ) . filter ( linkType => ( ! linkType . visible || ! linkType . showLabel ) && linkType . id !== PLACEHOLDER_LINK_TYPE ) . map ( ( { id , visible , showLabel } ) : LinkTypeOptions => ( { '@type' : 'LinkTypeOptions' , property : id , visible , showLabel } ) ) ; return makeSerializedDiagram ( { layoutData , linkTypeOptions } ) ; } private initLinkTypes ( linkTypes ) { const types = [ ] ; for ( const { id , label } of linkTypes ) { const linkType = new FatLinkType ( { id , label : label . values } ) ; this . graph . addLinkType ( linkType ) ; types . push ( linkType ) ; } return types ; } private setLinkSettings ( settings ) { if ( ! settings ) { return ; } for ( const setting of settings ) { const { visible = true , showLabel = true } = setting ; const linkTypeId = setting . property as LinkTypeIri ; this . linkSettings [ linkTypeId ] = { '@type' : 'LinkTypeOptions' , property : linkTypeId , visible , showLabel } ; const linkType = this . getLinkType ( linkTypeId ) ; if ( linkType ) { linkType . setVisibility ( { visible , showLabel } ) ; } } } private loadAndRenderLayout ( params ) { const { layoutData = emptyLayoutData ( ) , preloadedElements = { } , markLinksAsLayoutOnly , hideUnusedLinkTypes , } = params ; const elementIrisToRequestData = [ ] ; const usedLinkTypes = { } ; for ( const layoutElement of layoutData . elements ) { const { '@id' : id , iri , position , size , isExpanded , group , elementState } = layoutElement ; const template = preloadedElements [ iri ] ; const data = template || placeholderDataFromIri ( iri ) ; const element = new Element ( { id , data , position , size , expanded : isExpanded , group , elementState } ) ; this . graph . addElement ( element ) ; if ( ! template ) { elementIrisToRequestData . push ( element . iri ) ; } } for ( const layoutLink of layoutData . links ) { const { '@id' : id , property , source , target , vertices , linkState } = layoutLink ; const linkType = this . createLinkType ( property ) ; usedLinkTypes [ linkType . id ] = linkType ; const link = this . addLink ( new Link ( { id , typeId : linkType . id , sourceId : source [ '@id' ] , targetId : target [ '@id' ] , vertices , linkState , } ) ) ; if ( link ) { link . setLayoutOnly ( markLinksAsLayoutOnly ) ; } } this . subscribeGraph ( ) ; const requestingModels = this . requestElementData ( elementIrisToRequestData ) ; if ( hideUnusedLinkTypes && params . allLinkTypes ) { this . hideUnusedLinkTypes ( params . allLinkTypes , usedLinkTypes ) ; } return requestingModels ; } private hideUnusedLinkTypes ( allTypes , usedTypes ) { for ( const linkType of allTypes ) { if ( ! usedTypes [ linkType . id ] ) { linkType . setVisibility ( { visible : false , showLabel : linkType . showLabel , } ) ; } } } requestElementData ( elementIris ) { return this . fetcher . fetchElementData ( elementIris ) ; } requestLinksOfType ( linkTypeIds ? ) { const linkTypes = linkTypeIds || this . graph . getLinkTypes ( ) . filter ( type => type . visible ) . map ( type => type . id ) ; return this . dataProvider . linksInfo ( { elementIds : this . graph . getElements ( ) . map ( element => element . iri ) , linkTypeIds : linkTypes , } ) . then ( links => this . onLinkInfoLoaded ( links ) ) ; } createClass ( classId ) { if ( this . graph . getClass ( classId ) ) { return super . getClass ( classId ) ; } const classModel = super . createClass ( classId ) ; this . fetcher . fetchClass ( classModel ) ; return classModel ; } createLinkType ( linkTypeId ) { if ( this . graph . getLinkType ( linkTypeId ) ) { return super . createLinkType ( linkTypeId ) ; } const linkType = super . createLinkType ( linkTypeId ) ; const setting = this . linkSettings [ linkType . id ] ; if ( setting ) { const { visible , showLabel } = setting ; linkType . setVisibility ( { visible , showLabel , preventLoading : true } ) ; } this . fetcher . fetchLinkType ( linkType ) ; return linkType ; } createProperty ( propertyIri ) { if ( this . graph . getProperty ( propertyIri ) ) { return super . createProperty ( propertyIri ) ; } const property = super . createProperty ( propertyIri ) ; this . fetcher . fetchPropertyType ( property ) ; return property ; } private onLinkTypeVisibilityChanged : Listener < FatLinkTypeEvents , 'changeVisibility' > = e => { if ( e . source . visible ) { if ( ! e . preventLoading ) { this . requestLinksOfType ( [ e . source . id ] ) ; } } else { for ( const link of this . linksOfType ( e . source . id ) ) { this . graph . removeLink ( link . id ) ; } } } ; private onLinkInfoLoaded ( links ) { let allowToCreate ; const cancel = ( ) => { allowToCreate = false ; } ; for ( const linkModel of links ) { this . createLinkType ( linkModel . linkTypeId ) ; allowToCreate = true ; this . asyncSource . trigger ( 'createLoadedLink' , { source : this , model : linkModel , cancel } ) ; if ( allowToCreate ) { this . createLinks ( linkModel ) ; } } } createLinks ( data ) { const sources = this . graph . getElements ( ) . filter ( el => el . iri === data . sourceId ) ; const targets = this . graph . getElements ( ) . filter ( el => el . iri === data . targetId ) ; const typeId = data . linkTypeId ; for ( const source of sources ) { for ( const target of targets ) { this . addLink ( new Link ( { typeId , sourceId : source . id , targetId : target . id , data } ) ) ; } } } loadEmbeddedElements ( elementIri ) { const elements = this . groupByProperties . map ( groupBy => this . dataProvider . linkElements ( { elementId : elementIri , linkId : groupBy . linkType as LinkTypeIri , offset : 0 , direction : groupBy . linkDirection , } ) ) ; return Promise . all ( elements ) . then ( res => res . reduce ( ( memo , current ) => Object . assign ( memo , current ) , { } ) ) ; } } export function requestElementData ( model , elementIris ) { return Command . effect ( 'Fetch element data' , ( ) => { model . requestElementData ( elementIris ) ; } ) ; } export function restoreLinksBetweenElements ( model ) { return Command . effect ( 'Restore links between elements' , ( ) => { model . requestLinksOfType ( ) ; } ) ; }