import $ from "jquery" ; import menuDotsIcon from '../img/menudots.svg' ; import OncoprintModel , { TrackGroupProp , TrackId , TrackProp , TrackSortDirection } from "./oncoprintmodel" ; import ClickEvent = JQuery . ClickEvent ; import { CLOSE_MENUS_EVENT as HEADER_VIEW_CLOSE_MENUS_EVENT } from "./oncoprintheaderview" ; const TOGGLE_BTN_CLASS = "oncoprintjs__track_options__toggle_btn_img" ; const TOGGLE_BTN_OPEN_CLASS = "oncoprintjs__track_options__open" ; const DROPDOWN_CLASS = "oncoprintjs__track_options__dropdown" ; const SEPARATOR_CLASS = "oncoprintjs__track_options__separator" ; const NTH_CLASS_PREFIX = "nth-" ; export const CLOSE_MENUS_EVENT = "oncoprint-track-options-view.do-close-menus" ; type TrackCallback = ( trackId : TrackId ) => void ; export default class OncoprintTrackOptionsView { private $ctr : JQuery ; private $buttons_ctr : JQuery ; private $dropdown_ctr : JQuery ; private img_size : number ; private rendering_suppressed = false ; private track_options_$elts : TrackProp < { $div : JQuery ; $img : JQuery ; $dropdown : JQuery ; } > = { } ; private menu_shown : TrackProp < boolean > = { } ; private clickHandler : ( ) => void ; private interaction_disabled = false ; constructor ( private $div , private moveUpCallback , private moveDownCallback , private removeCallback , private sortChangeCallback , private unexpandCallback , private showGapsCallback ) { const position = $div . css ( 'position' ) ; if ( position !== 'absolute' && position !== 'relative' ) { console . log ( "WARNING: div passed to OncoprintTrackOptionsView must be absolute or relative positioned - layout problems will occur" ) ; } this . $ctr = $ ( '<div></div>' ) . css ( { 'position' : 'absolute' , 'overflow-y' : 'hidden' , 'overflow-x' : 'hidden' } ) . appendTo ( this . $div ) ; this . $buttons_ctr = $ ( '<div></div>' ) . css ( { 'position' : 'absolute' } ) . appendTo ( this . $ctr ) ; this . $dropdown_ctr = $ ( '<div></div>' ) . css ( { 'position' : 'absolute' } ) . appendTo ( this . $div ) ; const self = this ; this . clickHandler = function ( ) { $ ( document ) . trigger ( CLOSE_MENUS_EVENT ) ; } ; $ ( document ) . on ( "click" , this . clickHandler ) ; } private renderAllOptions ( model ) { if ( this . rendering_suppressed ) { return ; } const self = this ; $ ( document ) . off ( CLOSE_MENUS_EVENT ) ; $ ( document ) . on ( CLOSE_MENUS_EVENT , function ( ) { self . hideAllMenus ( ) ; } ) ; this . $buttons_ctr . empty ( ) ; this . $dropdown_ctr . empty ( ) ; this . scroll ( model . getVertScroll ( ) ) ; var tracks = model . getTracks ( ) ; var minimum_track_height = Number . POSITIVE_INFINITY ; for ( let i = 0 ; i < tracks . length ; i ++ ) { minimum_track_height = Math . min ( minimum_track_height , model . getTrackHeight ( tracks [ i ] ) ) ; } this . img_size = Math . floor ( minimum_track_height * 0.75 ) ; for ( let i = 0 ; i < tracks . length ; i ++ ) { this . renderTrackOptions ( model , tracks [ i ] , i ) ; } } private scroll ( scroll_y ) { if ( this . rendering_suppressed ) { return ; } this . $buttons_ctr . css ( { 'top' : - scroll_y } ) ; this . $dropdown_ctr . css ( { 'top' : - scroll_y } ) ; this . hideAllMenus ( ) ; } private resize ( model , getCellViewHeight ) { if ( this . rendering_suppressed ) { return ; } this . $div . css ( { 'width' : this . getWidth ( ) , 'height' : getCellViewHeight ( ) } ) ; this . $ctr . css ( { 'width' : this . getWidth ( ) , 'height' : getCellViewHeight ( ) } ) ; } private hideTrackMenu ( track_id ) { this . menu_shown [ track_id ] = false ; const $elts = this . track_options_$elts [ track_id ] ; $elts . $dropdown . css ( { 'z-index' : 1 } ) ; $elts . $dropdown . css ( { 'border' : '1px solid rgba(125,125,125,0)' } ) ; $elts . $img . css ( { 'border' : '1px solid rgba(125,125,125,0)' } ) ; $elts . $dropdown . fadeOut ( 100 ) ; } private showTrackMenu ( track_id ) { this . menu_shown [ track_id ] = true ; const $elts = this . track_options_$elts [ track_id ] ; $elts . $dropdown . css ( { 'z-index' : 10 } ) ; $elts . $dropdown . css ( { 'border' : '1px solid rgba(125,125,125,1)' } ) ; $elts . $img . css ( { 'border' : '1px solid rgba(125,125,125,1)' } ) ; $elts . $dropdown . fadeIn ( 100 ) ; } private hideAllMenus ( ) { for ( const track_id in this . track_options_$elts ) { if ( this . track_options_$elts . hasOwnProperty ( track_id ) ) { this . hideTrackMenu ( parseInt ( track_id , 10 ) ) ; } } } private hideMenusExcept ( track_id ) { for ( const _other_track_id in this . track_options_$elts ) { if ( this . track_options_$elts . hasOwnProperty ( _other_track_id ) ) { const other_track_id = parseInt ( _other_track_id , 10 ) ; if ( other_track_id === track_id ) { continue ; } this . hideTrackMenu ( other_track_id ) ; } } $ ( document ) . trigger ( HEADER_VIEW_CLOSE_MENUS_EVENT ) ; } private static $makeDropdownOption ( text , weight , disabled ? , callback ? ) { const li = $ ( '<li>' ) . text ( text ) . css ( { 'font-weight' : weight , 'font-size' : 12 , 'border-bottom' : '1px solid rgba(0,0,0,0.3)' } ) ; if ( ! disabled ) { if ( callback ) { li . addClass ( "clickable" ) ; li . css ( { 'cursor' : 'pointer' } ) ; li . click ( callback ) . hover ( function ( ) { $ ( this ) . css ( { 'background-color' : 'rgb(200,200,200)' } ) ; } , function ( ) { $ ( this ) . css ( { 'background-color' : 'rgba(255,255,255,0)' } ) ; } ) ; } else { li . click ( function ( evt ) { evt . stopPropagation ( ) ; } ) ; } } else { li . addClass ( "disabled" ) ; li . css ( { 'color' : 'rgb(200, 200, 200)' , 'cursor' : 'default' } ) ; } return li ; } private static $makeDropdownSeparator ( ) { return $ ( '<li>' ) . css ( { 'border-top' : '1px solid black' } ) . addClass ( SEPARATOR_CLASS ) ; } private static renderSortArrow ( $sortarrow , model , track_id ) { let sortarrow_char = '' ; if ( model . isTrackSortDirectionChangeable ( track_id ) ) { sortarrow_char = { '1' : '<i class="fa fa-signal" aria-hidden="true" title="Sorted ascending"></i>' , '-1' : '<i class="fa fa-signal" style="transform: scaleX(-1);" aria-hidden="true" title="Sorted descending"></i>' , '0' : '' } [ model . getTrackSortDirection ( track_id ) ] ; } $sortarrow . html ( sortarrow_char ) ; } private renderTrackOptions ( model , track_id , index ) { let $div , $img , $sortarrow , $dropdown ; const top = model . getZoomedTrackTops ( track_id ) ; $div = $ ( '<div>' ) . appendTo ( this . $buttons_ctr ) . css ( { 'position' : 'absolute' , 'left' : '0px' , 'top' : top + 'px' , 'white-space' : 'nowrap' } ) ; $img = $ ( '<img/>' ) . appendTo ( $div ) . attr ( { 'src' : menuDotsIcon , 'width' : this . img_size , 'height' : this . img_size } ) . css ( { 'float' : 'left' , 'cursor' : 'pointer' , 'border' : '1px solid rgba(125,125,125,0)' } ) . addClass ( TOGGLE_BTN_CLASS ) . addClass ( NTH_CLASS_PREFIX + ( index + 1 ) ) ; $sortarrow = $ ( '<span>' ) . appendTo ( $div ) . css ( { 'position' : 'absolute' , 'top' : Math . floor ( this . img_size / 4 ) + 'px' } ) ; $dropdown = $ ( '<ul>' ) . appendTo ( this . $dropdown_ctr ) . css ( { 'position' : 'absolute' , 'width' : 120 , 'display' : 'none' , 'list-style-type' : 'none' , 'padding-left' : '6' , 'padding-right' : '6' , 'float' : 'right' , 'background-color' : 'rgb(255,255,255)' , 'left' : '0px' , 'top' : top + this . img_size + 'px' } ) . addClass ( DROPDOWN_CLASS ) . addClass ( NTH_CLASS_PREFIX + ( index + 1 ) ) ; this . track_options_$elts [ track_id ] = { '$div' : $div , '$img' : $img , '$dropdown' : $dropdown } ; OncoprintTrackOptionsView . renderSortArrow ( $sortarrow , model , track_id ) ; const self = this ; $img . hover ( function ( evt ) { if ( ! self . menu_shown [ track_id ] ) { $ ( this ) . css ( { 'border' : '1px solid rgba(125,125,125,0.3)' } ) ; } } , function ( evt ) { if ( ! self . menu_shown [ track_id ] ) { $ ( this ) . css ( { 'border' : '1px solid rgba(125,125,125,0)' } ) ; } } ) ; $img . click ( function ( evt ) { evt . stopPropagation ( ) ; if ( $dropdown . is ( ":visible" ) ) { $img . addClass ( TOGGLE_BTN_OPEN_CLASS ) ; self . hideTrackMenu ( track_id ) ; } else { $img . removeClass ( TOGGLE_BTN_OPEN_CLASS ) ; self . showTrackMenu ( track_id ) ; } self . hideMenusExcept ( track_id ) ; } ) ; const movingDisabled = model . getTrackMovable ( track_id ) && model . isTrackInClusteredGroup ( track_id ) ; if ( model . getTrackMovable ( track_id ) ) { $dropdown . append ( OncoprintTrackOptionsView . $makeDropdownOption ( 'Move up' , 'normal' , movingDisabled , function ( evt ) { evt . stopPropagation ( ) ; self . moveUpCallback ( track_id ) ; } ) ) ; $dropdown . append ( OncoprintTrackOptionsView . $makeDropdownOption ( 'Move down' , 'normal' , movingDisabled , function ( evt ) { evt . stopPropagation ( ) ; self . moveDownCallback ( track_id ) ; } ) ) ; } if ( model . isTrackRemovable ( track_id ) ) { $dropdown . append ( OncoprintTrackOptionsView . $makeDropdownOption ( 'Remove track' , 'normal' , false , function ( evt ) { evt . stopPropagation ( ) ; self . removeCallback ( track_id ) ; } ) ) ; } if ( model . isTrackSortDirectionChangeable ( track_id ) ) { $dropdown . append ( OncoprintTrackOptionsView . $makeDropdownSeparator ( ) ) ; let $sort_inc_li ; let $sort_dec_li ; let $dont_sort_li ; $sort_inc_li = OncoprintTrackOptionsView . $makeDropdownOption ( 'Sort a-Z' , ( model . getTrackSortDirection ( track_id ) === 1 ? 'bold' : 'normal' ) , false , function ( evt ) { evt . stopPropagation ( ) ; $sort_inc_li . css ( 'font-weight' , 'bold' ) ; $sort_dec_li . css ( 'font-weight' , 'normal' ) ; $dont_sort_li . css ( 'font-weight' , 'normal' ) ; self . sortChangeCallback ( track_id , 1 ) ; OncoprintTrackOptionsView . renderSortArrow ( $sortarrow , model , track_id ) ; } ) ; $sort_dec_li = OncoprintTrackOptionsView . $makeDropdownOption ( 'Sort Z-a' , ( model . getTrackSortDirection ( track_id ) === - 1 ? 'bold' : 'normal' ) , false , function ( evt ) { evt . stopPropagation ( ) ; $sort_inc_li . css ( 'font-weight' , 'normal' ) ; $sort_dec_li . css ( 'font-weight' , 'bold' ) ; $dont_sort_li . css ( 'font-weight' , 'normal' ) ; self . sortChangeCallback ( track_id , - 1 ) ; OncoprintTrackOptionsView . renderSortArrow ( $sortarrow , model , track_id ) ; } ) ; $dont_sort_li = OncoprintTrackOptionsView . $makeDropdownOption ( 'Don\'t sort track' , ( model . getTrackSortDirection ( track_id ) === 0 ? 'bold' : 'normal' ) , false , function ( evt ) { evt . stopPropagation ( ) ; $sort_inc_li . css ( 'font-weight' , 'normal' ) ; $sort_dec_li . css ( 'font-weight' , 'normal' ) ; $dont_sort_li . css ( 'font-weight' , 'bold' ) ; self . sortChangeCallback ( track_id , 0 ) ; OncoprintTrackOptionsView . renderSortArrow ( $sortarrow , model , track_id ) ; } ) ; $dropdown . append ( $sort_inc_li ) ; $dropdown . append ( $sort_dec_li ) ; $dropdown . append ( $dont_sort_li ) ; } if ( model . isTrackExpandable ( track_id ) ) { $dropdown . append ( OncoprintTrackOptionsView . $makeDropdownOption ( model . getExpandButtonText ( track_id ) , 'normal' , false , function ( evt ) { evt . stopPropagation ( ) ; self . renderAllOptions ( model ) ; model . expandTrack ( track_id ) ; } ) ) ; } if ( model . isTrackExpanded ( track_id ) ) { $dropdown . append ( OncoprintTrackOptionsView . $makeDropdownOption ( 'Remove expansion' , 'normal' , false , function ( evt ) { evt . stopPropagation ( ) ; self . unexpandCallback ( track_id ) ; } ) ) ; } if ( model . getTrackCanShowGaps ( track_id ) ) { $dropdown . append ( OncoprintTrackOptionsView . $makeDropdownSeparator ( ) ) ; const $show_gaps_opt = OncoprintTrackOptionsView . $makeDropdownOption ( 'Show gaps' , model . getTrackShowGaps ( track_id ) ? 'bold' : 'normal' , false , function ( evt ) { evt . stopPropagation ( ) ; $show_gaps_opt . css ( 'font-weight' , 'bold' ) ; $dont_show_gaps_opt . css ( 'font-weight' , 'normal' ) ; self . showGapsCallback ( track_id , true ) ; } ) ; const $dont_show_gaps_opt = OncoprintTrackOptionsView . $makeDropdownOption ( "Don't show gaps" , model . getTrackShowGaps ( track_id ) ? 'normal' : 'bold' , false , function ( evt ) { evt . stopPropagation ( ) ; $show_gaps_opt . css ( 'font-weight' , 'normal' ) ; $dont_show_gaps_opt . css ( 'font-weight' , 'bold' ) ; self . showGapsCallback ( track_id , false ) ; } ) ; $dropdown . append ( $show_gaps_opt ) ; $dropdown . append ( $dont_show_gaps_opt ) ; } const custom_options = model . getTrackCustomOptions ( track_id ) ; if ( custom_options && custom_options . length > 0 ) { for ( var i = 0 ; i < custom_options . length ; i ++ ) { ( function ( ) { var option = custom_options [ i ] ; if ( option . separator ) { $dropdown . append ( OncoprintTrackOptionsView . $makeDropdownSeparator ( ) ) ; } else { $dropdown . append ( OncoprintTrackOptionsView . $makeDropdownOption ( option . label || "" , option . weight || "normal" , option . disabled , option . onClick && function ( evt ) { evt . stopPropagation ( ) ; option . onClick ( track_id ) ; } ) ) ; } } ) ( ) ; } } if ( $dropdown . is ( ":empty" ) ) { $div . remove ( ) ; $dropdown . remove ( ) ; } } public enableInteraction ( ) { this . interaction_disabled = false ; } public disableInteraction ( ) { this . interaction_disabled = true ; } public suppressRendering ( ) { this . rendering_suppressed = true ; } public releaseRendering ( model , getCellViewHeight ) { this . rendering_suppressed = false ; this . renderAllOptions ( model ) ; this . resize ( model , getCellViewHeight ) ; this . scroll ( model . getVertScroll ( ) ) ; } public setScroll ( model ) { this . setVertScroll ( model ) ; } public setHorzScroll ( model ) { } public setVertScroll ( model ) { this . scroll ( model . getVertScroll ( ) ) ; } public setZoom ( model , getCellViewHeight ) { this . setVertZoom ( model , getCellViewHeight ) ; } public setVertZoom ( model , getCellViewHeight ) { this . renderAllOptions ( model ) ; this . resize ( model , getCellViewHeight ) ; } public setTrackGroupHeader ( model , getCellViewHeight ) { this . renderAllOptions ( model ) ; this . resize ( model , getCellViewHeight ) ; } public sort ( model , getCellViewHeight ) { this . renderAllOptions ( model ) ; this . resize ( model , getCellViewHeight ) ; } public setViewport ( model , getCellViewHeight ) { this . renderAllOptions ( model ) ; this . resize ( model , getCellViewHeight ) ; this . scroll ( model . getVertScroll ( ) ) ; } public getWidth ( ) { if ( this . $buttons_ctr . is ( ":empty" ) ) { return 0 ; } else { return 18 + this . img_size ; } } public setTrackShowGaps ( model , getCellViewHeight ) { this . renderAllOptions ( model ) ; this . resize ( model , getCellViewHeight ) ; } public addTracks ( model , getCellViewHeight ) { this . renderAllOptions ( model ) ; this . resize ( model , getCellViewHeight ) ; } public moveTrack ( model , getCellViewHeight ) { this . renderAllOptions ( model ) ; this . resize ( model , getCellViewHeight ) ; } public setTrackGroupOrder ( model ) { this . renderAllOptions ( model ) ; } public setSortConfig ( model ) { this . renderAllOptions ( model ) ; } public removeTrack ( model , track_id , getCellViewHeight ) { delete this . track_options_$elts [ track_id ] ; this . renderAllOptions ( model ) ; this . resize ( model , getCellViewHeight ) ; } public destroy ( ) { $ ( document ) . off ( "click" , this . clickHandler ) ; $ ( document ) . off ( CLOSE_MENUS_EVENT ) ; } ; public setTrackCustomOptions ( model ) { this . renderAllOptions ( model ) ; } ; public setTrackMovable ( model ) { this . renderAllOptions ( model ) ; } }