import * as React from 'react' ; import { Dictionary , ElementModel , ElementIri , LinkTypeIri , } from '../data/model' ; import { FatLinkType , Element } from '../diagram/elements' ; import { DiagramView } from '../diagram/view' ; import { EditorController } from '../editor/editorController' ; import { EventObserver } from '../viewUtils/events' ; import { ProgressBar , ProgressState } from './progressBar' ; import { highlightSubstring } from './listElementView' ; import { SearchResults } from './searchResults' ; import { WorkspaceContextTypes , WorkspaceContextWrapper , WorkspaceEventKey , } from '../workspace/workspaceContext' ; interface ConnectionCount { inCount : number ; outCount : number ; } export interface ReactElementModel { model : ElementModel ; presentOnDiagram : boolean ; } const MAX_LINK_COUNT = 100 ; const ALL_RELATED_ELEMENTS_LINK = new FatLinkType ( { id : 'allRelatedElements' as LinkTypeIri , label : [ { value : 'All' , language : '' } ] , } ) ; export interface PropertySuggestionParams { elementId : string ; token : string ; properties : string [ ] ; lang : string ; } export type PropertySuggestionHandler = ( params : PropertySuggestionParams ) => Promise < Dictionary < PropertyScore > > ; type SortMode = 'alphabet' | 'smart' ; export interface PropertyScore { propertyIri : string ; score : number ; } export interface LinkDataChunk { link : FatLinkType ; direction ? : 'in' | 'out' ; expectedCount : number ; offset ? : number ; } export interface ObjectsData { linkDataChunk : LinkDataChunk ; objects : ReactElementModel [ ] ; } export interface ConnectionsMenuProps { view : DiagramView ; editor : EditorController ; target : Element ; onClose : ( ) => void ; onAddElements : ( elementIris : ElementIri [ ] , linkType : FatLinkType | undefined ) => void ; suggestProperties ? : PropertySuggestionHandler ; } export class ConnectionsMenu extends React . Component < ConnectionsMenuProps , { } > { static contextTypes = WorkspaceContextTypes ; readonly context : WorkspaceContextWrapper ; private readonly handler = new EventObserver ( ) ; private readonly linkTypesListener = new EventObserver ( ) ; private loadingState = ProgressState . none ; private links : FatLinkType [ ] ; private countMap : { [ linkTypeId ] : ConnectionCount ; } ; private linkDataChunk : LinkDataChunk ; private objects : ReactElementModel [ ] ; private updateAll = ( ) => this . forceUpdate ( ) ; componentDidMount ( ) { const { view } = this . props ; this . handler . listen ( view . events , 'changeLanguage' , this . updateAll ) ; this . loadLinks ( ) ; } componentWillUnmount ( ) { this . handler . stopListening ( ) ; this . linkTypesListener . stopListening ( ) ; } private resubscribeOnLinkTypeEvents ( linkTypesOfElement ) { this . linkTypesListener . stopListening ( ) ; for ( const linkType of linkTypesOfElement ) { this . linkTypesListener . listen ( linkType . events , 'changeLabel' , this . updateAll ) ; this . linkTypesListener . listen ( linkType . events , 'changeVisibility' , this . updateAll ) ; } } private loadLinks ( ) { const { view , editor , target } = this . props ; this . loadingState = ProgressState . loading ; this . links = [ ] ; this . countMap = { } ; editor . model . dataProvider . linkTypesOf ( { elementId : target . iri } ) . then ( ( linkTypes ) => { this . loadingState = ProgressState . completed ; const countMap = { } ; const links = [ ] ; for ( const { id : linkTypeId , inCount , outCount } of linkTypes ) { countMap [ linkTypeId ] = { inCount , outCount } ; links . push ( view . model . createLinkType ( linkTypeId ) ) ; } countMap [ ALL_RELATED_ELEMENTS_LINK . id ] = Object . keys ( countMap ) . map ( ( key ) => countMap [ key ] ) . reduce ( ( a , b ) => { return { inCount : a . inCount + b . inCount , outCount : a . outCount + b . outCount , } ; } , { inCount : 0 , outCount : 0 } ) ; this . countMap = countMap ; this . links = links ; this . resubscribeOnLinkTypeEvents ( this . links ) ; this . updateAll ( ) ; this . context . workspace . triggerWorkspaceEvent ( WorkspaceEventKey . connectionsLoadLinks ) ; } ) . catch ( ( err ) => { console . error ( err ) ; this . loadingState = ProgressState . error ; this . updateAll ( ) ; } ) ; this . updateAll ( ) ; } private loadObjects ( linkDataChunk ) { const { view , editor , target } = this . props ; const { link , direction } = linkDataChunk ; const offset = linkDataChunk . offset || 0 ; this . loadingState = ProgressState . loading ; this . linkDataChunk = linkDataChunk ; this . objects = [ ] ; editor . model . dataProvider . linkElements ( { elementId : target . iri , linkId : link === ALL_RELATED_ELEMENTS_LINK ? undefined : link . id , limit : offset + MAX_LINK_COUNT , offset : offset , direction , } ) . then ( ( elements ) => { this . loadingState = ProgressState . completed ; this . objects = Object . keys ( elements ) . map ( ( iri ) => ( { model : elements [ iri ] , presentOnDiagram : view . model . elements . findIndex ( ( element ) => element . iri === iri && element . group === undefined ) >= 0 , } ) ) ; this . updateAll ( ) ; this . context . workspace . triggerWorkspaceEvent ( WorkspaceEventKey . connectionsLoadElements ) ; } ) . catch ( ( err ) => { console . error ( err ) ; this . loadingState = ProgressState . error ; this . updateAll ( ) ; } ) ; } private addSelectedElements = ( selectedObjects ) => { const { onClose , onAddElements } = this . props ; const addedElementsIris = selectedObjects . map ( ( item ) => item . model . id ) ; const linkType = this . linkDataChunk ? this . linkDataChunk . link : undefined ; const hasChosenLinkType = this . linkDataChunk && linkType !== ALL_RELATED_ELEMENTS_LINK ; onAddElements ( addedElementsIris , hasChosenLinkType ? linkType : undefined ) ; onClose ( ) ; } ; private onExpandLink = ( linkDataChunk ) => { const alreadyLoaded = this . objects && this . linkDataChunk && this . linkDataChunk . link === linkDataChunk . link && this . linkDataChunk . direction === linkDataChunk . direction ; if ( ! alreadyLoaded ) { this . loadObjects ( linkDataChunk ) ; } this . updateAll ( ) ; this . context . workspace . triggerWorkspaceEvent ( WorkspaceEventKey . connectionsExpandLink ) ; } ; private onMoveToFilter = ( linkDataChunk ) => { const { view , target } = this . props ; const { link , direction } = linkDataChunk ; if ( link === ALL_RELATED_ELEMENTS_LINK ) { target . addToFilter ( ) ; } else { const selectedElement = view . model . getElement ( target . id ) ; selectedElement . addToFilter ( link , direction ) ; } } ; render ( ) { const connectionsData = { links : this . links || [ ] , countMap : this . countMap || { } , } ; let objectsData = null ; if ( this . linkDataChunk && this . objects ) { objectsData = { linkDataChunk : this . linkDataChunk , objects : this . objects , } ; } const { view , target , suggestProperties } = this . props ; return ( < ConnectionsMenuMarkup target = { target } connectionsData = { connectionsData } objectsData = { objectsData } state = { this . loadingState } view = { view } onExpandLink = { this . onExpandLink } onPressAddSelected = { this . addSelectedElements } onMoveToFilter = { this . onMoveToFilter } propertySuggestionCall = { suggestProperties } / > ) ; } } interface ConnectionsMenuMarkupProps { target : Element ; connectionsData : { links : FatLinkType [ ] ; countMap : { [ linkTypeId ] : ConnectionCount ; } ; } ; objectsData ? : ObjectsData ; view : DiagramView ; state : ProgressState ; onExpandLink ? : ( linkDataChunk : LinkDataChunk ) => void ; onPressAddSelected ? : ( selectedObjects : ReactElementModel [ ] ) => void ; onMoveToFilter ? : ( linkDataChunk : LinkDataChunk ) => void ; propertySuggestionCall ? : PropertySuggestionHandler ; } interface ConnectionsMenuMarkupState { filterKey ? : string ; panel ? : string ; sortMode ? : SortMode ; } class ConnectionsMenuMarkup extends React . Component < ConnectionsMenuMarkupProps , ConnectionsMenuMarkupState > { constructor ( props ) { super ( props ) ; this . state = { filterKey : '' , panel : 'connections' , sortMode : 'alphabet' , } ; } private onChangeFilter = ( e ) => { const filterKey = e . currentTarget . value ; this . setState ( { filterKey } ) ; } ; private getTitle = ( ) => { if ( this . props . objectsData && this . state . panel === 'objects' ) { return 'Objects' ; } else if ( this . props . connectionsData && this . state . panel === 'connections' ) { return 'Connections' ; } return 'Error' ; } ; private onExpandLink = ( linkDataChunk ) => { this . setState ( { filterKey : '' , panel : 'objects' } ) ; this . props . onExpandLink ( linkDataChunk ) ; } ; private onCollapseLink = ( ) => { this . setState ( { filterKey : '' , panel : 'connections' } ) ; } ; private getBreadCrumbs = ( ) => { if ( this . props . objectsData && this . state . panel === 'objects' ) { const { link , direction } = this . props . objectsData . linkDataChunk ; const localizedText = this . props . view . formatLabel ( link . label , link . id ) ; return ( < span className = "graph-explorer-connections-menu_bread-crumbs" > < a className = "graph-explorer-connections-menu__link" onClick = { this . onCollapseLink } > Connections < /a> { '\u00A0' + '/' + '\u00A0' } { localizedText } { direction ? `(${ direction })` : null } < /span>); } else { return null ; } } ; private getBody = ( ) => { if ( this . props . state === 'error' ) { return ( < label className = "graph-explorer-label graph-explorer-connections-menu__error" > Error < /label>); } else if ( this . props . objectsData && this . state . panel === 'objects' ) { return ( < ObjectsPanel data = { this . props . objectsData } onMoveToFilter = { this . props . onMoveToFilter } view = { this . props . view } filterKey = { this . state . filterKey } loading = { this . props . state === ProgressState . loading } onPressAddSelected = { this . props . onPressAddSelected } / > ) ; } else if ( this . props . connectionsData && this . state . panel === 'connections' ) { if ( this . props . state === ProgressState . loading ) { return ( < label className = "graph-explorer-label graph-explorer-connections-menu__loading" > Loading ... < /label>); } return ( < ConnectionsList id = { this . props . target . id } data = { this . props . connectionsData } view = { this . props . view } filterKey = { this . state . filterKey } onExpandLink = { this . onExpandLink } onMoveToFilter = { this . props . onMoveToFilter } propertySuggestionCall = { this . props . propertySuggestionCall } sortMode = { this . state . sortMode } / > ) ; } else { return < div />; } } ; private onSortChange = ( e ) => { const value = ( e . target as HTMLInputElement ) . value as SortMode ; if ( this . state . sortMode === value ) { return ; } this . setState ( { sortMode : value } ) ; } ; private renderSortSwitch = ( id , icon , title ) => { return ( < div > < input type = "radio" name = "sort" id = { id } value = { id } className = "graph-explorer-connections-menu__sort-switch" onChange = { this . onSortChange } checked = { this . state . sortMode === id } / > < label htmlFor = { id } className = "graph-explorer-connections-menu__sort-switch-label" title = { title } > < i className = { `fa ${ icon }` } /> < /label> < /div>); } ; private renderSortSwitches = ( ) => { if ( this . state . panel !== 'connections' || ! this . props . propertySuggestionCall ) { return null ; } return ( < div className = "graph-explorer-connections-menu_search-line-sort-switches" > { this . renderSortSwitch ( 'alphabet' , 'fa-sort-alpha-asc' , 'Sort alphabetically' ) } { this . renderSortSwitch ( 'smart' , 'fa-lightbulb-o' , 'Smart sort' ) } < /div>); } ; render ( ) { return ( < div className = "graph-explorer-connections-menu" > < label className = "graph-explorer-label graph-explorer-connections-menu__title-label" > { this . getTitle ( ) } < /label> { this . getBreadCrumbs ( ) } < div className = "graph-explorer-connections-menu_search-line" > < input type = "text" className = "search-input graph-explorer-form-control graph-explorer-connections-menu__search-line-input" value = { this . state . filterKey } onChange = { this . onChangeFilter } placeholder = "Search for..." / > { this . renderSortSwitches ( ) } < /div> < ProgressBar state = { this . props . state } height = { 10 } / > { this . getBody ( ) } < /div>); } } interface ConnectionsListProps { id : string ; data : { links : FatLinkType [ ] ; countMap : { [ linkTypeId ] : ConnectionCount ; } ; } ; view : DiagramView ; filterKey : string ; onExpandLink ? : ( linkDataChunk : LinkDataChunk ) => void ; onMoveToFilter ? : ( linkDataChunk : LinkDataChunk ) => void ; propertySuggestionCall ? : PropertySuggestionHandler ; sortMode : SortMode ; } class ConnectionsList extends React . Component < ConnectionsListProps , { scores : Dictionary < PropertyScore > ; } > { constructor ( props ) { super ( props ) ; this . state = { scores : { } } ; this . updateScores ( props ) ; } componentDidUpdate ( newProps ) { this . updateScores ( newProps ) ; } private updateScores = ( props ) => { if ( props . propertySuggestionCall && ( props . filterKey || props . sortMode === 'smart' ) ) { const { id , data , view , filterKey } = props ; const lang = view . getLanguage ( ) ; const token = filterKey . trim ( ) ; const properties = data . links . map ( ( l ) => l . id ) ; props . propertySuggestionCall ( { elementId : id , token , properties , lang } ) . then ( ( scores ) => this . setState ( { scores } ) ) ; } } ; private isSmartMode ( ) { return this . props . sortMode === 'smart' && ! this . props . filterKey ; } private compareLinks = ( a , b ) => { const { view } = this . props ; const aText = view . formatLabel ( a . label , a . id ) ; const bText = view . formatLabel ( b . label , b . id ) ; return aText . localeCompare ( bText ) ; } ; private compareLinksByWeight = ( a , b ) => { const { view } = this . props ; const aText = view . formatLabel ( a . label , a . id ) ; const bText = view . formatLabel ( b . label , b . id ) ; const aWeight = this . state . scores [ a . id ] ? this . state . scores [ a . id ] . score : 0 ; const bWeight = this . state . scores [ b . id ] ? this . state . scores [ b . id ] . score : 0 ; return aWeight > bWeight ? - 1 : aWeight < bWeight ? 1 : aText . localeCompare ( bText ) ; } ; private getLinks = ( ) => { const { view , data , filterKey } = this . props ; return ( data . links || [ ] ) . filter ( ( link ) => { const text = view . formatLabel ( link . label , link . id ) . toLowerCase ( ) ; return ! filterKey || text . indexOf ( filterKey . toLowerCase ( ) ) >= 0 ; } ) . sort ( this . compareLinks ) ; } ; private getProbableLinks = ( ) => { const isSmartMode = this . isSmartMode ( ) ; return ( this . props . data . links || [ ] ) . filter ( ( link ) => { return ( this . state . scores [ link . id ] && ( this . state . scores [ link . id ] . score > 0 || isSmartMode ) ) ; } ) . sort ( this . compareLinksByWeight ) ; } ; private getViews = ( links , notSure ? ) => { const { view } = this . props ; const countMap = this . props . data . countMap || { } ; const views = [ ] ; const addView = ( link , direction ) => { const count = direction === 'in' ? countMap [ link . id ] . inCount : countMap [ link . id ] . outCount ; if ( count === 0 ) { return ; } const postfix = notSure ? '-probable' : '' ; views . push ( < LinkInPopupMenu key = { `${ direction }-${ link . id }-${ postfix }` } link = { link } onExpandLink = { this . props . onExpandLink } view = { view } count = { count } direction = { direction } filterKey = { notSure ? '' : this . props . filterKey } onMoveToFilter = { this . props . onMoveToFilter } probability = { this . state . scores [ link . id ] && notSure ? this . state . scores [ link . id ] . score : 0 } / > ) ; } ; for ( const link of links ) { addView ( link , 'in' ) ; addView ( link , 'out' ) ; } return views ; } ; render ( ) { const { view } = this . props ; const isSmartMode = this . isSmartMode ( ) ; const links = isSmartMode ? [ ] : this . getLinks ( ) ; const probableLinks = this . getProbableLinks ( ) . filter ( ( link ) => links . indexOf ( link ) === - 1 ) ; const views = this . getViews ( links ) ; const probableViews = this . getViews ( probableLinks , true ) ; let viewList ; if ( views . length === 0 && probableViews . length === 0 ) { viewList = ( < label className = "graph-explorer-label graph-explorer-connections-menu_links-list__empty" > List empty < /label>); } else { viewList = views ; if ( views . length > 1 || ( isSmartMode && probableViews . length > 1 ) ) { const countMap = this . props . data . countMap || { } ; const allRelatedElements = countMap [ ALL_RELATED_ELEMENTS_LINK . id ] ; viewList = [ < LinkInPopupMenu key = { ALL_RELATED_ELEMENTS_LINK . id } link = { ALL_RELATED_ELEMENTS_LINK } onExpandLink = { this . props . onExpandLink } view = { view } count = { allRelatedElements . inCount + allRelatedElements . outCount } onMoveToFilter = { this . props . onMoveToFilter } / > , < hr key = "graph-explorer-hr-line" className = "graph-explorer-connections-menu_links-list__hr" / > , ] . concat ( viewList ) ; } } let probablePart = null ; if ( probableViews . length !== 0 ) { probablePart = [ isSmartMode ? null : ( < li key = "probable-links" > < span className = "graph-explorer-label" > Probably , you & lsquo ; re looking for . . < /span> < /li>), probableViews , ] ; } return ( < ul className = { 'graph-explorer-connections-menu_links-list ' + ( views . length === 0 && probableViews . length === 0 ? 'ocm_links-list-empty' : '' ) } > { viewList } { probablePart } < /ul>); } } interface LinkInPopupMenuProps { link : FatLinkType ; count : number ; direction ? : 'in' | 'out' ; view : DiagramView ; filterKey ? : string ; onExpandLink ? : ( linkDataChunk : LinkDataChunk ) => void ; onMoveToFilter ? : ( linkDataChunk : LinkDataChunk ) => void ; probability ? : number ; } class LinkInPopupMenu extends React . Component < LinkInPopupMenuProps , { } > { constructor ( props ) { super ( props ) ; } private onExpandLink = ( expectedCount , direction ? ) => { this . props . onExpandLink ( { link : this . props . link , direction , expectedCount , } ) ; } ; private onMoveToFilter = ( evt ) => { evt . stopPropagation ( ) ; this . props . onMoveToFilter ( { link : this . props . link , direction : this . props . direction , expectedCount : this . props . count , } ) ; } ; render ( ) { const { view , link } = this . props ; const fullText = view . formatLabel ( link . label , link . id ) ; const probability = Math . round ( this . props . probability * 100 ) ; const textLine = highlightSubstring ( fullText + ( probability > 0 ? ' (' + probability + '%)' : '' ) , this . props . filterKey ) ; const directionName = this . props . direction === 'in' ? 'source' : this . props . direction === 'out' ? 'target' : 'all connected' ; return ( < li data - linktypeid = { this . props . link . id } className = "link-in-popup-menu" title = { `${ directionName } of "${ fullText }" ${ view . formatIri ( link . id ) }` } onClick = { ( ) => this . onExpandLink ( this . props . count , this . props . direction ) } > { this . props . direction === 'in' || this . props . direction === 'out' ? ( < div className = "link-in-popup-menu_direction" > { this . props . direction === 'in' && ( < div className = "link-in-popup-menu_direction__in-direction" />)} { this . props . direction === 'out' && ( < div className = "link-in-popup-menu_direction__out-direction" / > ) } < /div>) : null} < div className = "link-in-popup-menu__link-title" > { textLine } < /div> < span className = "graph-explorer-badge link-in-popup-menu__count" > { this . props . count <= MAX_LINK_COUNT ? this . props . count : '100+' } < /span> < div className = "link-in-popup-menu__filter-button" onClick = { this . onMoveToFilter } title = "Set as filter in the Instances panel" / > < div className = "link-in-popup-menu__navigate-button" title = { `Navigate to ${ directionName } "${ fullText }" elements` } /> < /li>); } } interface ObjectsPanelProps { data : ObjectsData ; loading ? : boolean ; view : DiagramView ; filterKey ? : string ; onPressAddSelected ? : ( selectedObjects : ReactElementModel [ ] ) => void ; onMoveToFilter ? : ( linkDataChunk : LinkDataChunk ) => void ; } interface ObjectsPanelState { selection : ReadonlySet < ElementIri > ; } class ObjectsPanel extends React . Component < ObjectsPanelProps , ObjectsPanelState > { constructor ( props ) { super ( props ) ; this . state = { selection : new Set < ElementIri > ( ) } ; } componentDidUpdate ( nextProps ) { if ( this . props . data . objects . length < nextProps . data . objects . length ) { this . setState ( { selection : new Set < ElementIri > ( ) } ) ; } } private onSelectAll = ( ) => { const objects = this . props . data . objects ; if ( objects . length === 0 ) { return ; } const allSelected = allNonPresentedAreSelected ( objects , this . state . selection ) ; const newSelection = allSelected ? new Set < ElementIri > ( ) : selectNonPresented ( this . props . data . objects ) ; this . updateSelection ( newSelection ) ; } ; private getFilteredObjects ( ) { if ( ! this . props . filterKey ) { return this . props . data . objects ; } const filterKey = this . props . filterKey . toLowerCase ( ) ; return this . props . data . objects . filter ( ( element ) => { const text = this . props . view . formatLabel ( element . model . label . values , element . model . id ) . toLowerCase ( ) ; return text && text . indexOf ( filterKey ) >= 0 ; } ) ; } private getItems ( list ) { const added = { } ; const result = [ ] ; for ( const obj of list ) { if ( added [ obj . model . id ] ) { continue ; } added [ obj . model . id ] = true ; result . push ( obj . model ) ; } return result ; } private updateSelection = ( newSelection ) => { this . setState ( { selection : newSelection } ) ; } ; private counter = ( activeObjCount ) => { const countString = `${ activeObjCount }\u00A0of\u00A0${ this . props . data . objects . length }` ; const wrongNodes = Math . min ( MAX_LINK_COUNT , this . props . data . linkDataChunk . expectedCount ) - this . props . data . objects . length ; const wrongNodesString = Math . abs ( wrongNodes ) > MAX_LINK_COUNT ? `${ MAX_LINK_COUNT }+` : Math . abs ( wrongNodes ) . toString ( ) ; const wrongNodesCount = wrongNodes === 0 ? '' : wrongNodes < 0 ? `\u00A0(${ wrongNodesString })` : `\u00A0(${ wrongNodesString })` ; const wrongNodesTitle = wrongNodes === 0 ? '' : wrongNodes > 0 ? 'Unavailable nodes' : 'Extra nodes' ; return ( < div className = "graph-explorer-label graph-explorer-connections-menu_objects-panel_bottom-panel__count-label" > < span > { countString } < /span> < span className = "graph-explorer-connections-menu_objects-panel_bottom-panel__extra-elements" title = { wrongNodesTitle } > { wrongNodesCount } < /span> < /div>); } ; render ( ) { const { onPressAddSelected , filterKey } = this . props ; const { selection } = this . state ; const objects = this . getFilteredObjects ( ) ; const isAllSelected = allNonPresentedAreSelected ( objects , selection ) ; const nonPresented = objects . filter ( ( el ) => ! el . presentOnDiagram ) ; const active = nonPresented . filter ( ( el ) => selection . has ( el . model . id ) ) ; return ( < div className = "graph-explorer-connections-menu_objects-panel" > < div className = "graph-explorer-connections-menu_objects-panel__select-all" > < label > < input type = "checkbox" checked = { isAllSelected && nonPresented . length > 0 } onChange = { this . onSelectAll } disabled = { nonPresented . length === 0 } / > Select All < /label> < /div> { this . props . loading ? ( < label className = "graph-explorer-label graph-explorer-connections-menu__loading-objects" > Loading ... < /label>) : objects.length === 0 ? (<label className="graph-explorer-label graph-explorer-connections-menu__loading-objects"> No available nodes < /label>) : (<div className="graph-explorer-connections-menu_objects-panel_objects-list"> < SearchResults view = { this . props . view } items = { this . getItems ( objects ) } selection = { this . state . selection } onSelectionChanged = { this . updateSelection } highlightText = { filterKey } / > { this . props . data . linkDataChunk . expectedCount > MAX_LINK_COUNT ? ( < div className = "graph-explorer-connections-menu__move-to-filter" onClick = { ( ) => this . props . onMoveToFilter ( this . props . data . linkDataChunk ) } > The list was truncated , for more data click here to use the filter panel < /div>) : null} < /div>)} < div className = "graph-explorer-connections-menu_objects-panel_bottom-panel" > { this . counter ( active . length ) } < button className = { 'graph-explorer-btn graph-explorer-btn-primary pull-right ' + 'graph-explorer-connections-menu_objects-panel_bottom-panel__add-button' } disabled = { this . props . loading || nonPresented . length === 0 } onClick = { ( ) => onPressAddSelected ( active . length > 0 ? active : nonPresented ) } > { active . length > 0 ? 'Add selected' : 'Add all' } < /button> < /div> < /div>); } } function selectNonPresented ( objects ) { const selection = new Set < ElementIri > ( ) ; for ( const object of objects ) { if ( object . presentOnDiagram ) { continue ; } selection . add ( object . model . id ) ; } return selection ; } function allNonPresentedAreSelected ( objects , selection ) { let allSelected = true ; for ( const object of objects ) { if ( object . presentOnDiagram ) { continue ; } allSelected = allSelected && selection . has ( object . model . id ) ; } return allSelected ; }