import { ElementModel , ElementIri , ElementTypeIri , LinkTypeIri , PropertyTypeIri , } from '../data/model' ; import { GenerateID } from '../data/schema' ; import { EventSource , Events , EventObserver , AnyEvent , } from '../viewUtils/events' ; import { Element , ElementEvents , Link , LinkEvents , FatLinkType , FatLinkTypeEvents , FatClassModel , FatClassModelEvents , RichProperty , } from './elements' ; import { Graph , CellsChangedEvent } from './graph' ; import { CommandHistory , Command } from './history' ; export interface DiagramModelEvents { changeCells : CellsChangedEvent ; elementEvent : AnyEvent < ElementEvents > ; linkEvent : AnyEvent < LinkEvents > ; linkTypeEvent : AnyEvent < FatLinkTypeEvents > ; classEvent : AnyEvent < FatClassModelEvents > ; changeGroupContent : { group : string ; } ; } /**
 * Model of diagram.
 */ export class DiagramModel { protected readonly source = new EventSource < DiagramModelEvents > ( ) ; readonly events : Events < DiagramModelEvents > = this . source ; protected graph = new Graph ( ) ; protected graphListener = new EventObserver ( ) ; constructor ( readonly history ) { } get elements ( ) { return this . graph . getElements ( ) ; } get links ( ) { return this . graph . getLinks ( ) ; } getElement ( elementId ) { return this . graph . getElement ( elementId ) ; } getLinkById ( linkId ) { return this . graph . getLink ( linkId ) ; } linksOfType ( linkTypeId ) { return this . graph . getLinks ( ) . filter ( ( link ) => link . typeId === linkTypeId ) ; } findLink ( linkTypeId , sourceId , targetId ) { return this . graph . findLink ( linkTypeId , sourceId , targetId ) ; } sourceOf ( link ) { return this . getElement ( link . sourceId ) ; } targetOf ( link ) { return this . getElement ( link . targetId ) ; } isSourceAndTargetVisible ( link ) { return Boolean ( this . sourceOf ( link ) && this . targetOf ( link ) ) ; } resetGraph ( ) { if ( this . graphListener ) { this . graphListener . stopListening ( ) ; this . graphListener = new EventObserver ( ) ; } this . graph = new Graph ( ) ; } subscribeGraph ( ) { this . graphListener . listen ( this . graph . events , 'changeCells' , ( e ) => { this . source . trigger ( 'changeCells' , e ) ; } ) ; this . graphListener . listen ( this . graph . events , 'elementEvent' , ( e ) => { this . source . trigger ( 'elementEvent' , e ) ; } ) ; this . graphListener . listen ( this . graph . events , 'linkEvent' , ( e ) => { this . source . trigger ( 'linkEvent' , e ) ; } ) ; this . graphListener . listen ( this . graph . events , 'linkTypeEvent' , ( e ) => { this . source . trigger ( 'linkTypeEvent' , e ) ; } ) ; this . graphListener . listen ( this . graph . events , 'classEvent' , ( e ) => { this . source . trigger ( 'classEvent' , e ) ; } ) ; this . source . trigger ( 'changeCells' , { updateAll : true } ) ; } reorderElements ( compare ) { this . graph . reorderElements ( compare ) ; } createElement ( elementIriOrModel , group ? ) { const elementIri = typeof elementIriOrModel === 'string' ? elementIriOrModel : ( elementIriOrModel as ElementModel ) . id ; const elements = this . elements . filter ( ( el ) => el . iri === elementIri && el . group === group ) ; if ( elements . length > 0 ) { return elements [ 0 ] ; } let data = typeof elementIriOrModel === 'string' ? placeholderDataFromIri ( elementIri ) : ( elementIriOrModel as ElementModel ) ; data = { ... data , id : data . id } ; const element = new Element ( { id : GenerateID . forElement ( ) , data , group } ) ; this . addElement ( element ) ; return element ; } addElement ( element ) { this . history . execute ( addElement ( this . graph , element , [ ] ) ) ; } removeElement ( elementId ) { const element = this . getElement ( elementId ) ; if ( element ) { this . history . execute ( removeElement ( this . graph , element ) ) ; } } addLink ( link ) { const { typeId , sourceId , targetId , data } = link ; if ( data && data . linkTypeId !== typeId ) { throw new Error ( 'linkTypeId must match linkType.id' ) ; } const existingLink = this . findLink ( typeId , sourceId , targetId ) ; if ( existingLink ) { if ( link . data ) { existingLink . setLayoutOnly ( false ) ; existingLink . setData ( data ) ; } return existingLink ; } const linkType = this . createLinkType ( link . typeId ) ; const source = this . getElement ( sourceId ) ; const target = this . getElement ( targetId ) ; const shouldBeVisible = linkType . visible && source && target ; if ( ! shouldBeVisible ) { return undefined ; } if ( ! link . data ) { link . setData ( { linkTypeId : typeId , sourceId : source . iri , targetId : target . iri , } ) ; } this . graph . addLink ( link ) ; return link ; } removeLink ( linkId ) { this . graph . removeLink ( linkId ) ; } getClass ( classIri ) { return this . graph . getClass ( classIri ) ; } createClass ( classIri ) { const existing = this . graph . getClass ( classIri ) ; if ( existing ) { return existing ; } const classModel = new FatClassModel ( { id : classIri } ) ; this . addClass ( classModel ) ; return classModel ; } addClass ( model ) { this . graph . addClass ( model ) ; } getLinkType ( linkTypeIri ) { return this . graph . getLinkType ( linkTypeIri ) ; } createLinkType ( linkTypeIri ) { const existing = this . graph . getLinkType ( linkTypeIri ) ; if ( existing ) { return existing ; } const linkType = new FatLinkType ( { id : linkTypeIri } ) ; this . graph . addLinkType ( linkType ) ; return linkType ; } getProperty ( propertyTypeIri ) { return this . graph . getProperty ( propertyTypeIri ) ; } createProperty ( propertyIri ) { const existing = this . graph . getProperty ( propertyIri ) ; if ( existing ) { return existing ; } const property = new RichProperty ( { id : propertyIri } ) ; this . graph . addProperty ( property ) ; return property ; } triggerChangeGroupContent ( group ) { this . source . trigger ( 'changeGroupContent' , { group } ) ; } createTemporaryElement ( ) { const target = new Element ( { id : GenerateID . forElement ( ) , data : placeholderDataFromIri ( '' as ElementIri ) , temporary : true , } ) ; this . graph . addElement ( target ) ; return target ; } } export function placeholderDataFromIri ( iri ) { return { id : iri , types : [ ] , label : { values : [ ] } , properties : { } , } ; } function addElement ( graph , element , connectedLinks ) { return Command . create ( 'Add element' , ( ) => { graph . addElement ( element ) ; for ( const link of connectedLinks ) { const existing = graph . getLink ( link . id ) || graph . findLink ( link . typeId , link . sourceId , link . targetId ) ; if ( ! existing ) { graph . addLink ( link ) ; } } return removeElement ( graph , element ) ; } ) ; } function removeElement ( graph , element ) { return Command . create ( 'Remove element' , ( ) => { const connectedLinks = [ ... element . links ] ; graph . removeElement ( element . id ) ; return addElement ( graph , element , connectedLinks ) ; } ) ; }