2019-05-01 04:31:12 +08:00
/ * !
* Draggabilly PACKAGED v2 . 2.0
* Make that shiz draggable
* https : //draggabilly.desandro.com
* MIT license
* /
2020-01-15 04:52:18 +08:00
import BasicWidget from "./basic_widget.js" ;
import contextMenuService from "../services/context_menu.js" ;
2020-01-13 02:05:09 +08:00
! function ( i , e ) { "function" == typeof define && define . amd ? define ( "jquery-bridget/jquery-bridget" , [ "jquery" ] , function ( t ) { return e ( i , t ) } ) : "object" == typeof module && module . exports ? module . exports = e ( i , require ( "jquery" ) ) : i . jQueryBridget = e ( i , i . jQuery ) } ( window , function ( t , i ) { "use strict" ; var c = Array . prototype . slice , e = t . console , p = void 0 === e ? function ( ) { } : function ( t ) { e . error ( t ) } ; function n ( d , o , u ) { ( u = u || i || t . jQuery ) && ( o . prototype . option || ( o . prototype . option = function ( t ) { u . isPlainObject ( t ) && ( this . options = u . extend ( ! 0 , this . options , t ) ) } ) , u . fn [ d ] = function ( t ) { if ( "string" == typeof t ) { var i = c . call ( arguments , 1 ) ; return s = i , a = "$()." + d + '("' + ( r = t ) + '")' , ( e = this ) . each ( function ( t , i ) { var e = u . data ( i , d ) ; if ( e ) { var n = e [ r ] ; if ( n && "_" != r . charAt ( 0 ) ) { var o = n . apply ( e , s ) ; h = void 0 === h ? o : h } else p ( a + " is not a valid method" ) } else p ( d + " not initialized. Cannot call methods, i.e. " + a ) } ) , void 0 !== h ? h : e } var e , r , s , h , a , n ; return n = t , this . each ( function ( t , i ) { var e = u . data ( i , d ) ; e ? ( e . option ( n ) , e . _init ( ) ) : ( e = new o ( i , n ) , u . data ( i , d , e ) ) } ) , this } , r ( u ) ) } function r ( t ) { ! t || t && t . bridget || ( t . bridget = n ) } return r ( i || t . jQuery ) , n } ) , function ( t , i ) { "use strict" ; "function" == typeof define && define . amd ? define ( "get-size/get-size" , [ ] , function ( ) { return i ( ) } ) : "object" == typeof module && module . exports ? module . exports = i ( ) : t . getSize = i ( ) } ( window , function ( ) { "use strict" ; function m ( t ) { var i = parseFloat ( t ) ; return - 1 == t . indexOf ( "%" ) && ! isNaN ( i ) && i } var e = "undefined" == typeof console ? function ( ) { } : function ( t ) { console . error ( t ) } , y = [ "paddingLeft" , "paddingRight" , "paddingTop" , "paddingBottom" , "marginLeft" , "marginRight" , "marginTop" , "marginBottom" , "borderLeftWidth" , "borderRightWidth" , "borderTopWidth" , "borderBottomWidth" ] , b = y . length ; function E ( t ) { var i = getComputedStyle ( t ) ; return i || e ( "Style returned " + i + ". Are you running this code in a hidden iframe on Firefox? See http://bit.ly/getsizebug1" ) , i } var _ , x = ! 1 ; function P ( t ) { if ( function ( ) { if ( ! x ) { x = ! 0 ; var t = document . createElement ( "div" ) ; t . style . width = "200px" , t . style . padding = "1px 2px 3px 4px" , t . style . borderStyle = "solid" , t . style . borderWidth = "1px 2px 3px 4px" , t . style . boxSizing = "border-box" ; var i = document . body || document . documentElement ; i . appendChild ( t ) ; var e = E ( t ) ; P . isBoxSizeOuter = _ = 200 == m ( e . width ) , i . removeChild ( t ) } } ( ) , "string" == typeof t && ( t = document . querySelector ( t ) ) , t && "object" == typeof t && t . nodeType ) { var i = E ( t ) ; if ( "none" == i . display ) return function ( ) { for ( var t = { width : 0 , height : 0 , innerWidth : 0 , innerHeight : 0 , outerWidth : 0 , outerHeight : 0 } , i = 0 ; i < b ; i ++ ) t [ y [ i ] ] = 0 ; return t } ( ) ; var e = { } ; e . width = t . offsetWidth , e . height = t . offsetHeight ; for ( var n = e . isBorderBox = "border-box" == i . boxSizing , o = 0 ; o < b ; o ++ ) { var r = y [ o ] , s = i [ r ] , h = parseFloat ( s ) ; e [ r ] = isNaN ( h ) ? 0 : h } var a = e . paddingLeft + e . paddingRight , d = e . paddingTop + e . paddingBottom , u = e . marginLeft + e . marginRight , c = e . marginTop + e . marginBottom , p = e . borderLeftWidth + e . borderRightWidth , f = e . borderTopWidth + e . borderBottomWidth , g = n && _ , l = m ( i . width ) ; ! 1 !== l && ( e . width = l + ( g ? 0 : a + p ) ) ; var v = m ( i . height ) ; return ! 1 !== v && ( e . height = v + ( g ? 0 : d + f ) ) , e . innerWidth = e . width - ( a + p ) , e . innerHeight = e . height - ( d + f ) , e . outerWidth = e . width + u , e . outerHeight = e . height + c , e } } return P } ) , function ( t , i ) { "function" == typeof define && define . amd ? define ( "ev-emitter/ev-emitter" , i ) : "object" == typeof module && module . exports ? module . exports = i ( ) : t . EvEmitter = i ( ) } ( "undefined" != typeof window ? window : this , function ( ) { function t ( ) { } var i = t . prototype ; return i . on = function ( t , i ) { if ( t && i ) { var e = this . _events = this . _events || { } , n = e [ t ] = e [ t ] || [ ] ; return - 1 == n . indexOf ( i ) && n . push ( i ) , this } } , i . once = function ( t , i ) { if ( t && i ) { this . on ( t , i ) ; var e = this . _onceEvents = this . _onceEvents || { } ; return ( e [ t ] = e [ t ] || { } ) [ i ] = ! 0 , this } } , i . off = function ( t , i ) { var e = this . _events && this . _events [ t ] ; if ( e && e . length ) { var n = e . indexOf ( i ) ; return - 1 != n && e . splice ( n , 1 ) , this } } , i . emitEvent = function ( t , i ) { var e = this . _events && this . _events [ t ] ; if ( e && e . length ) { e = e . slice ( 0 ) , i = i || [ ] ; for ( var n = this . _onceEvents && this . _onceEvents [ t ] , o = 0 ; o < e . length ; o ++ ) { var r = e [ o ] ; n && n [ r ] && ( this . off ( t , r ) , delete n [ r ] ) , r . apply ( this , i ) } return this } } , i . allOff = function ( ) { delete this . _events , delete this . _onceEvents } , t } ) , function ( i , e ) { "function" == typeof define && define . amd ? define ( "unipointer/unipointer" , [ "ev-emitter/ev-emitter" ] , funct
2019-05-01 04:31:12 +08:00
2019-05-12 01:44:58 +08:00
const Draggabilly = window . Draggabilly ;
2019-05-01 04:31:12 +08:00
2019-05-12 01:44:58 +08:00
const TAB _CONTENT _MIN _WIDTH = 24 ;
const TAB _CONTENT _MAX _WIDTH = 240 ;
2019-05-12 16:59:53 +08:00
const NEW _TAB _WIDTH = 32 ;
2019-12-23 18:52:45 +08:00
const MIN _FILLER _WIDTH = 50 ;
2019-05-01 04:31:12 +08:00
2019-05-12 01:44:58 +08:00
const TAB _SIZE _SMALL = 84 ;
const TAB _SIZE _SMALLER = 60 ;
const TAB _SIZE _MINI = 48 ;
2019-05-01 04:31:12 +08:00
2020-01-13 03:15:05 +08:00
const TAB _TPL = `
2019-05-12 01:44:58 +08:00
< div class = "note-tab" >
< div class = "note-tab-wrapper" >
< div class = "note-tab-title" > < / d i v >
< div class = "note-tab-drag-handle" > < / d i v >
< div class = "note-tab-close" > < span > × < / s p a n > < / d i v >
< / d i v >
2019-05-12 16:11:41 +08:00
< / d i v > ` ;
2019-05-12 01:44:58 +08:00
2020-01-13 03:15:05 +08:00
const NEW _TAB _BUTTON _TPL = ` <div class="note-new-tab" title="Add new tab">+</div> ` ;
const FILLER _TPL = ` <div class="tab-row-filler">
2019-12-23 18:52:45 +08:00
< div class = "tab-row-border" > < / d i v >
< / d i v > ` ;
2019-05-12 16:59:53 +08:00
2020-01-13 03:15:05 +08:00
const TAB _ROW _TPL = `
2020-01-13 02:05:09 +08:00
< div class = "note-tab-row" >
2020-01-13 03:15:05 +08:00
< style >
. note - tab - row {
box - sizing : border - box ;
position : relative ;
height : 34 px ;
min - height : 34 px ;
width : 100 % ;
background : var ( -- main - background - color ) ;
border - radius : 5 px 5 px 0 0 ;
overflow : hidden ;
margin - top : 2 px ;
}
. note - tab - row * {
box - sizing : inherit ;
font : inherit ;
}
. note - tab - row . note - tab - row - content {
position : relative ;
width : 100 % ;
height : 100 % ;
}
. note - tab - row . note - tab {
position : absolute ;
left : 0 ;
height : 33 px ;
width : 240 px ;
border : 0 ;
margin : 0 ;
z - index : 1 ;
pointer - events : none ;
}
. note - new - tab {
position : absolute ;
left : 0 ;
height : 33 px ;
width : 32 px ;
border : 0 ;
margin : 0 ;
z - index : 1 ;
text - align : center ;
font - size : 24 px ;
cursor : pointer ;
border - bottom : 1 px solid var ( -- main - border - color ) ;
}
. note - new - tab : hover {
background - color : var ( -- accented - background - color ) ;
border - radius : 5 px ;
}
. tab - row - filler {
- webkit - app - region : drag ;
position : absolute ;
left : 0 ;
height : 33 px ;
}
. tab - row - filler . tab - row - border {
position : relative ;
background : linear - gradient ( to right , var ( -- main - border - color ) , transparent ) ;
height : 1 px ;
margin - top : 32 px ;
}
. note - tab - row . note - tab [ active ] {
z - index : 5 ;
}
. note - tab - row . note - tab ,
. note - tab - row . note - tab * {
user - select : none ;
cursor : default ;
}
. note - tab - row . note - tab . note - tab - was - just - added {
top : 10 px ;
animation : note - tab - was - just - added 120 ms forwards ease - in - out ;
}
. note - tab - row . note - tab . note - tab - wrapper {
position : absolute ;
display : flex ;
top : 0 ;
bottom : 0 ;
left : 0 ;
right : 0 ;
padding : 5 px 8 px ;
border - top - left - radius : 8 px ;
border - top - right - radius : 8 px ;
overflow : hidden ;
pointer - events : all ;
background - color : var ( -- accented - background - color ) ;
border - bottom : 1 px solid var ( -- main - border - color ) ;
}
. note - tab - row . note - tab [ active ] . note - tab - wrapper {
background - color : var ( -- main - background - color ) ;
border : 1 px solid var ( -- main - border - color ) ;
border - bottom : 0 ;
font - weight : bold ;
}
. note - tab - row . note - tab [ is - mini ] . note - tab - wrapper {
padding - left : 2 px ;
padding - right : 2 px ;
}
. note - tab - row . note - tab . note - tab - title {
flex : 1 ;
vertical - align : top ;
overflow : hidden ;
white - space : nowrap ;
color : var ( -- muted - text - color ) ;
}
. note - tab - row . note - tab [ is - small ] . note - tab - title {
margin - left : 0 ;
}
. note - tab - row . note - tab [ active ] . note - tab - title {
color : var ( -- main - text - color ) ;
}
. note - tab - row . note - tab . note - tab - drag - handle {
position : absolute ;
top : 0 ;
bottom : 0 ;
right : 0 ;
left : 0 ;
border - top - left - radius : 8 px ;
border - top - right - radius : 8 px ;
}
. note - tab - row . note - tab . note - tab - close {
flex - grow : 0 ;
flex - shrink : 0 ;
border - radius : 50 % ;
z - index : 100 ;
width : 24 px ;
height : 24 px ;
text - align : center ;
}
. note - tab - row . note - tab . note - tab - close span {
font - size : 24 px ;
position : relative ;
top : - 6 px ;
}
. note - tab - row . note - tab . note - tab - close : hover {
background - color : var ( -- hover - item - background - color ) ;
color : var ( -- hover - item - text - color ) ;
}
. note - tab - row . note - tab [ is - smaller ] . note - tab - close {
margin - left : auto ;
}
. note - tab - row . note - tab [ is - mini ] : not ( [ active ] ) . note - tab - close {
display : none ;
}
. note - tab - row . note - tab [ is - mini ] [ active ] . note - tab - close {
margin - left : auto ;
margin - right : auto ;
}
@ - moz - keyframes note - tab - was - just - added {
to {
top : 0 ;
}
}
@ - webkit - keyframes note - tab - was - just - added {
to {
top : 0 ;
}
}
@ - o - keyframes note - tab - was - just - added {
to {
top : 0 ;
}
}
@ keyframes note - tab - was - just - added {
to {
top : 0 ;
}
}
. note - tab - row . note - tab - row - is - sorting . note - tab : not ( . note - tab - is - dragging ) ,
. note - tab - row : not ( . note - tab - row - is - sorting ) . note - tab . note - tab - was - just - dragged {
transition : transform 120 ms ease - in - out ;
}
< / s t y l e >
2020-01-13 02:05:09 +08:00
< div class = "note-tab-row-content" > < / d i v >
< / d i v > ` ;
export default class TabRowWidget extends BasicWidget {
2020-01-13 03:15:05 +08:00
doRender ( ) {
2020-01-16 05:11:30 +08:00
this . $widget = $ ( TAB _ROW _TPL ) ;
2020-01-13 02:05:09 +08:00
2019-05-12 03:27:27 +08:00
this . draggabillies = [ ] ;
this . eventListeners = { } ;
2019-05-01 04:31:12 +08:00
2020-01-16 05:11:30 +08:00
this . setupStyle ( ) ;
2019-05-12 01:44:58 +08:00
this . setupEvents ( ) ;
this . setupDraggabilly ( ) ;
2019-05-12 16:59:53 +08:00
this . setupNewButton ( ) ;
2019-11-17 17:22:26 +08:00
this . setupFiller ( ) ;
this . layoutTabs ( ) ;
2019-05-12 01:44:58 +08:00
this . setVisibility ( ) ;
2019-05-12 03:27:27 +08:00
2020-01-16 05:11:30 +08:00
this . $widget . on ( 'contextmenu' , '.note-tab' , e => {
2020-01-13 02:05:09 +08:00
e . preventDefault ( ) ;
const tab = $ ( e . target ) . closest ( ".note-tab" ) ;
contextMenuService . initContextMenu ( e , {
getContextMenuItems : ( ) => {
return [
{ title : "Close all tabs" , cmd : "removeAllTabs" , uiIcon : "empty" } ,
{ title : "Close all tabs except for this" , cmd : "removeAllTabsExceptForThis" , uiIcon : "empty" }
] ;
} ,
selectContextMenuItem : ( e , cmd ) => {
if ( cmd === 'removeAllTabs' ) {
this . removeAllTabs ( ) ;
} else if ( cmd === 'removeAllTabsExceptForThis' ) {
this . removeAllTabsExceptForThis ( tab [ 0 ] ) ;
}
}
} ) ;
2020-01-13 03:15:05 +08:00
} ) ;
2020-01-16 05:11:30 +08:00
return this . $widget ;
2019-05-01 04:31:12 +08:00
}
2020-01-16 05:11:30 +08:00
setupStyle ( ) {
this . $style = $ ( "<style>" ) ;
this . $widget . append ( this . $style ) ;
2019-05-01 04:31:12 +08:00
}
setupEvents ( ) {
2019-07-21 04:01:50 +08:00
const resizeListener = _ => {
2019-05-12 01:44:58 +08:00
this . cleanUpPreviouslyDraggedTabs ( ) ;
this . layoutTabs ( ) ;
2019-07-21 04:01:50 +08:00
} ;
// ResizeObserver exists only in FF69
if ( typeof ResizeObserver !== "undefined" ) {
2020-01-16 05:11:30 +08:00
new ResizeObserver ( resizeListener ) . observe ( this . $widget [ 0 ] ) ;
2019-07-21 04:01:50 +08:00
}
else {
// for older firefox
window . addEventListener ( 'resize' , resizeListener ) ;
}
2019-05-01 04:31:12 +08:00
2019-05-12 01:44:58 +08:00
this . tabEls . forEach ( ( tabEl ) => this . setTabCloseEventListener ( tabEl ) ) ;
2019-05-01 04:31:12 +08:00
}
2019-05-08 04:14:35 +08:00
setVisibility ( ) {
2020-01-16 05:11:30 +08:00
this . $widget . show ( ) ;
2019-05-08 04:14:35 +08:00
}
2019-05-01 04:31:12 +08:00
get tabEls ( ) {
2020-01-16 05:11:30 +08:00
return Array . prototype . slice . call ( this . $widget . find ( '.note-tab' ) ) ;
2019-05-01 04:31:12 +08:00
}
2020-01-16 05:35:15 +08:00
get $tabContent ( ) {
return this . $widget . find ( '.note-tab-row-content' ) ;
2019-05-01 04:31:12 +08:00
}
get tabContentWidths ( ) {
2019-05-12 01:44:58 +08:00
const numberOfTabs = this . tabEls . length ;
2020-01-16 05:35:15 +08:00
const tabsContentWidth = this . $tabContent [ 0 ] . clientWidth - NEW _TAB _WIDTH - MIN _FILLER _WIDTH ;
2019-05-12 16:21:27 +08:00
const targetWidth = tabsContentWidth / numberOfTabs ;
2019-05-12 01:44:58 +08:00
const clampedTargetWidth = Math . max ( TAB _CONTENT _MIN _WIDTH , Math . min ( TAB _CONTENT _MAX _WIDTH , targetWidth ) ) ;
const flooredClampedTargetWidth = Math . floor ( clampedTargetWidth ) ;
2019-05-12 16:21:27 +08:00
const totalTabsWidthUsingTarget = flooredClampedTargetWidth * numberOfTabs ;
2019-05-12 01:44:58 +08:00
const totalExtraWidthDueToFlooring = tabsContentWidth - totalTabsWidthUsingTarget ;
const widths = [ ] ;
let extraWidthRemaining = totalExtraWidthDueToFlooring ;
2019-11-17 17:22:26 +08:00
2019-05-12 01:44:58 +08:00
for ( let i = 0 ; i < numberOfTabs ; i += 1 ) {
const extraWidth = flooredClampedTargetWidth < TAB _CONTENT _MAX _WIDTH && extraWidthRemaining > 0 ? 1 : 0 ;
widths . push ( flooredClampedTargetWidth + extraWidth ) ;
if ( extraWidthRemaining > 0 ) extraWidthRemaining -= 1 ;
}
2019-05-01 04:31:12 +08:00
2020-01-16 05:35:15 +08:00
if ( this . $filler ) {
this . $filler . css ( "width" , ( extraWidthRemaining + MIN _FILLER _WIDTH ) + "px" ) ;
2019-11-17 17:22:26 +08:00
}
2019-05-12 16:11:41 +08:00
return widths ;
2019-05-01 04:31:12 +08:00
}
2019-05-12 16:59:53 +08:00
getTabPositions ( ) {
const tabPositions = [ ] ;
2019-05-12 01:44:58 +08:00
const tabContentWidths = this . tabContentWidths ;
2019-05-12 16:11:41 +08:00
let position = 0 ;
2019-05-12 16:21:27 +08:00
tabContentWidths . forEach ( width => {
2019-05-12 16:59:53 +08:00
tabPositions . push ( position ) ;
2019-05-12 01:44:58 +08:00
position += width ;
} ) ;
2019-05-01 04:31:12 +08:00
2019-05-12 16:59:53 +08:00
const newTabPosition = position ;
2019-11-17 17:22:26 +08:00
const fillerPosition = position + 32 ;
2019-05-01 04:31:12 +08:00
2019-11-17 17:22:26 +08:00
return { tabPositions , newTabPosition , fillerPosition } ;
2019-05-01 04:31:12 +08:00
}
layoutTabs ( ) {
2019-05-12 01:44:58 +08:00
const tabContentWidths = this . tabContentWidths ;
this . tabEls . forEach ( ( tabEl , i ) => {
2019-05-12 16:11:41 +08:00
const width = tabContentWidths [ i ] ;
2019-05-12 01:44:58 +08:00
tabEl . style . width = width + 'px' ;
tabEl . removeAttribute ( 'is-small' ) ;
tabEl . removeAttribute ( 'is-smaller' ) ;
tabEl . removeAttribute ( 'is-mini' ) ;
2019-05-12 16:11:41 +08:00
if ( width < TAB _SIZE _SMALL ) tabEl . setAttribute ( 'is-small' , '' ) ;
if ( width < TAB _SIZE _SMALLER ) tabEl . setAttribute ( 'is-smaller' , '' ) ;
if ( width < TAB _SIZE _MINI ) tabEl . setAttribute ( 'is-mini' , '' ) ;
2019-05-12 01:44:58 +08:00
} ) ;
let styleHTML = '' ;
2019-05-12 16:59:53 +08:00
2019-11-17 17:22:26 +08:00
const { tabPositions , newTabPosition , fillerPosition } = this . getTabPositions ( ) ;
2019-05-12 16:59:53 +08:00
tabPositions . forEach ( ( position , i ) => {
2019-05-14 05:08:59 +08:00
styleHTML += ` .note-tab:nth-child( ${ i + 1 } ) { transform: translate3d( ${ position } px, 0, 0)} ` ;
2019-05-12 01:44:58 +08:00
} ) ;
2019-05-14 05:08:59 +08:00
styleHTML += ` .note-new-tab { transform: translate3d( ${ newTabPosition } px, 0, 0) } ` ;
2019-11-17 17:22:26 +08:00
styleHTML += ` .tab-row-filler { transform: translate3d( ${ fillerPosition } px, 0, 0) } ` ;
2019-05-12 16:59:53 +08:00
2020-01-16 05:11:30 +08:00
this . $style . html ( styleHTML ) ;
2019-05-01 04:31:12 +08:00
}
2019-05-15 04:29:47 +08:00
addTab ( tabId ) {
2019-05-12 01:44:58 +08:00
const div = document . createElement ( 'div' ) ;
2020-01-13 03:15:05 +08:00
div . innerHTML = TAB _TPL ;
2019-05-12 01:44:58 +08:00
const tabEl = div . firstElementChild ;
2019-05-15 04:29:47 +08:00
tabEl . setAttribute ( 'data-tab-id' , tabId ) ;
2019-05-01 04:31:12 +08:00
2019-05-12 16:59:53 +08:00
tabEl . classList . add ( 'note-tab-was-just-added' ) ;
setTimeout ( ( ) => tabEl . classList . remove ( 'note-tab-was-just-added' ) , 500 ) ;
2019-05-01 04:31:12 +08:00
2020-01-16 05:35:15 +08:00
this . $newTab . before ( tabEl ) ;
2019-05-12 01:44:58 +08:00
this . setVisibility ( ) ;
this . setTabCloseEventListener ( tabEl ) ;
2019-05-15 04:29:47 +08:00
this . updateTab ( tabEl , { title : 'New tab' } ) ;
2019-05-12 01:44:58 +08:00
this . cleanUpPreviouslyDraggedTabs ( ) ;
this . layoutTabs ( ) ;
this . setupDraggabilly ( ) ;
return tabEl ;
2019-05-01 04:31:12 +08:00
}
setTabCloseEventListener ( tabEl ) {
2020-01-13 03:15:05 +08:00
tabEl . querySelector ( '.note-tab-close' )
2020-01-16 05:27:52 +08:00
. addEventListener ( 'click' , _ => this . removeTab ( tabEl . getAttribute ( 'data-tab-id' ) ) ) ;
2019-05-16 03:50:27 +08:00
tabEl . addEventListener ( 'mousedown' , e => {
if ( e . which === 2 ) {
2020-01-16 05:27:52 +08:00
this . removeTab ( tabEl . getAttribute ( 'data-tab-id' ) ) ;
2019-05-16 03:50:27 +08:00
return true ; // event has been handled
}
} ) ;
2019-05-01 04:31:12 +08:00
}
2020-01-16 05:27:52 +08:00
get activeTabId ( ) {
const tabEl = this . activeTabEl ;
return tabEl ? tabEl . getAttribute ( 'data-tab-id' ) : null ;
}
2019-05-01 04:31:12 +08:00
get activeTabEl ( ) {
2020-01-16 05:11:30 +08:00
return this . $widget . find ( '.note-tab[active]' ) [ 0 ] ;
2019-05-01 04:31:12 +08:00
}
2019-05-08 03:04:07 +08:00
get previousTabEl ( ) {
2019-05-12 01:44:58 +08:00
const tabEls = this . tabEls ;
2019-05-08 03:04:07 +08:00
2019-05-12 01:44:58 +08:00
if ( tabEls . length <= 1 ) {
return null ;
}
2019-05-08 03:04:07 +08:00
2019-05-12 01:44:58 +08:00
let prev = tabEls [ tabEls . length - 1 ] ;
2019-05-08 03:04:07 +08:00
2019-05-12 01:44:58 +08:00
for ( const tabEl of tabEls ) {
if ( tabEl . hasAttribute ( "active" ) ) {
return prev ;
}
2019-05-08 03:04:07 +08:00
2019-05-12 01:44:58 +08:00
prev = tabEl ;
}
2019-05-08 03:04:07 +08:00
2019-05-12 01:44:58 +08:00
return null ;
2019-05-08 03:04:07 +08:00
}
get nextTabEl ( ) {
2019-05-12 01:44:58 +08:00
const tabEls = this . tabEls ;
2019-05-08 03:04:07 +08:00
2019-05-12 01:44:58 +08:00
if ( tabEls . length <= 1 ) {
return null ;
}
2019-05-08 03:04:07 +08:00
2019-05-12 01:44:58 +08:00
let prev = tabEls [ tabEls . length - 1 ] ;
2019-05-08 03:04:07 +08:00
2019-05-12 01:44:58 +08:00
for ( const tabEl of tabEls ) {
if ( prev . hasAttribute ( "active" ) ) {
return tabEl ;
}
2019-05-08 03:04:07 +08:00
2019-05-12 01:44:58 +08:00
prev = tabEl ;
}
2019-05-08 03:04:07 +08:00
2019-05-12 01:44:58 +08:00
return null ;
2019-05-08 03:04:07 +08:00
}
2019-05-01 04:31:12 +08:00
hasActiveTab ( ) {
2019-05-12 01:44:58 +08:00
return ! ! this . activeTabEl ;
2019-05-01 04:31:12 +08:00
}
2020-01-16 05:27:52 +08:00
activateTab ( tabEl ) {
2019-05-12 01:44:58 +08:00
const activeTabEl = this . activeTabEl ;
if ( activeTabEl === tabEl ) return ;
if ( activeTabEl ) activeTabEl . removeAttribute ( 'active' ) ;
tabEl . setAttribute ( 'active' , '' ) ;
2020-01-16 05:27:52 +08:00
this . trigger ( 'activeTabChanged' , { tabId : tabEl . getAttribute ( 'data-tab-id' ) } ) ;
2019-05-01 04:31:12 +08:00
}
2020-01-16 05:27:52 +08:00
removeTab ( tabId ) {
const tabEl = this . $widget . find ( ` [data-tab-id=' ${ tabId } '] ` ) [ 0 ] ;
2019-05-12 01:44:58 +08:00
if ( tabEl === this . activeTabEl ) {
2019-05-12 23:28:20 +08:00
if ( tabEl . nextElementSibling && tabEl . nextElementSibling . classList . contains ( "note-tab" ) ) {
2020-01-16 05:27:52 +08:00
this . activateTab ( tabEl . nextElementSibling )
2019-05-12 23:28:20 +08:00
} else if ( tabEl . previousElementSibling && tabEl . previousElementSibling . classList . contains ( "note-tab" ) ) {
2020-01-16 05:27:52 +08:00
this . activateTab ( tabEl . previousElementSibling )
2019-05-12 01:44:58 +08:00
}
2019-05-01 04:31:12 +08:00
}
2019-05-12 01:44:58 +08:00
tabEl . parentNode . removeChild ( tabEl ) ;
2020-01-16 05:27:52 +08:00
this . trigger ( 'tabRemove' , { tabId : tabEl . getAttribute ( 'data-tab-id' ) } ) ;
2019-05-12 01:44:58 +08:00
this . cleanUpPreviouslyDraggedTabs ( ) ;
this . layoutTabs ( ) ;
this . setupDraggabilly ( ) ;
this . setVisibility ( ) ;
2019-05-01 04:31:12 +08:00
}
2020-01-16 05:27:52 +08:00
removeAllTabs ( ) {
2019-05-16 03:50:27 +08:00
for ( const tabEl of this . tabEls ) {
2020-01-16 05:27:52 +08:00
this . removeTab ( tabEl . getAttribute ( 'data-tab-id' ) ) ;
2019-05-16 03:50:27 +08:00
}
}
2020-01-16 05:27:52 +08:00
removeAllTabsExceptForThis ( remainingTabEl ) {
2019-05-12 01:44:58 +08:00
for ( const tabEl of this . tabEls ) {
if ( remainingTabEl !== tabEl ) {
2020-01-16 05:27:52 +08:00
this . removeTab ( tabEl . getAttribute ( 'data-tab-id' ) ) ;
2019-05-12 01:44:58 +08:00
}
2019-05-08 03:34:01 +08:00
}
}
2019-05-01 04:31:12 +08:00
updateTab ( tabEl , tabProperties ) {
2019-05-12 01:44:58 +08:00
tabEl . querySelector ( '.note-tab-title' ) . textContent = tabProperties . title ;
2019-05-01 04:31:12 +08:00
}
cleanUpPreviouslyDraggedTabs ( ) {
2019-05-12 01:44:58 +08:00
this . tabEls . forEach ( ( tabEl ) => tabEl . classList . remove ( 'note-tab-was-just-dragged' ) ) ;
2019-05-01 04:31:12 +08:00
}
setupDraggabilly ( ) {
2019-05-12 01:44:58 +08:00
const tabEls = this . tabEls ;
2019-05-12 16:59:53 +08:00
const { tabPositions } = this . getTabPositions ( ) ;
2019-05-12 01:44:58 +08:00
if ( this . isDragging ) {
this . isDragging = false ;
2020-01-16 05:11:30 +08:00
this . $widget . removeClass ( 'note-tab-row-is-sorting' ) ;
2019-05-12 01:44:58 +08:00
this . draggabillyDragging . element . classList . remove ( 'note-tab-is-dragging' ) ;
this . draggabillyDragging . element . style . transform = '' ;
this . draggabillyDragging . dragEnd ( ) ;
this . draggabillyDragging . isDragging = false ;
2019-05-12 03:27:27 +08:00
this . draggabillyDragging . positionDrag = _ => { } ; // Prevent Draggabilly from updating tabEl.style.transform in later frames
2019-05-12 01:44:58 +08:00
this . draggabillyDragging . destroy ( ) ;
this . draggabillyDragging = null ;
}
2019-05-01 04:31:12 +08:00
2019-05-12 01:44:58 +08:00
this . draggabillies . forEach ( d => d . destroy ( ) ) ;
tabEls . forEach ( ( tabEl , originalIndex ) => {
const originalTabPositionX = tabPositions [ originalIndex ] ;
const draggabilly = new Draggabilly ( tabEl , {
axis : 'x' ,
handle : '.note-tab-drag-handle' ,
2020-01-16 05:35:15 +08:00
containment : this . $tabContent [ 0 ]
2019-05-12 01:44:58 +08:00
} ) ;
this . draggabillies . push ( draggabilly ) ;
draggabilly . on ( 'pointerDown' , _ => {
2019-05-15 04:29:47 +08:00
this . activateTab ( tabEl )
2019-05-12 01:44:58 +08:00
} ) ;
draggabilly . on ( 'dragStart' , _ => {
this . isDragging = true ;
this . draggabillyDragging = draggabilly ;
tabEl . classList . add ( 'note-tab-is-dragging' ) ;
2020-01-16 05:11:30 +08:00
this . $widget . addClass ( 'note-tab-row-is-sorting' ) ;
2019-05-12 01:44:58 +08:00
} ) ;
draggabilly . on ( 'dragEnd' , _ => {
this . isDragging = false ;
const finalTranslateX = parseFloat ( tabEl . style . left , 10 ) ;
tabEl . style . transform = ` translate3d(0, 0, 0) ` ;
// Animate dragged tab back into its place
requestAnimationFrame ( _ => {
tabEl . style . left = '0' ;
tabEl . style . transform = ` translate3d( ${ finalTranslateX } px, 0, 0) ` ;
requestAnimationFrame ( _ => {
tabEl . classList . remove ( 'note-tab-is-dragging' ) ;
2020-01-16 05:11:30 +08:00
this . $widget . removeClass ( 'note-tab-row-is-sorting' ) ;
2019-05-12 01:44:58 +08:00
tabEl . classList . add ( 'note-tab-was-just-dragged' ) ;
requestAnimationFrame ( _ => {
tabEl . style . transform = '' ;
this . layoutTabs ( ) ;
this . setupDraggabilly ( ) ;
} )
} )
} )
} ) ;
draggabilly . on ( 'dragMove' , ( event , pointer , moveVector ) => {
// Current index be computed within the event since it can change during the dragMove
const tabEls = this . tabEls ;
const currentIndex = tabEls . indexOf ( tabEl ) ;
const currentTabPositionX = originalTabPositionX + moveVector . x ;
2019-05-12 16:59:53 +08:00
const destinationIndexTarget = this . closest ( currentTabPositionX , tabPositions ) ;
2019-05-12 01:44:58 +08:00
const destinationIndex = Math . max ( 0 , Math . min ( tabEls . length , destinationIndexTarget ) ) ;
if ( currentIndex !== destinationIndex ) {
this . animateTabMove ( tabEl , currentIndex , destinationIndex ) ;
}
2019-05-01 04:31:12 +08:00
} )
} )
}
2020-01-16 05:27:52 +08:00
animateTabMove ( tabEl , originIndex , destinationIndex ) {
2019-05-12 01:44:58 +08:00
if ( destinationIndex < originIndex ) {
tabEl . parentNode . insertBefore ( tabEl , this . tabEls [ destinationIndex ] ) ;
} else {
2020-01-16 05:35:15 +08:00
const beforeEl = this . tabEls [ destinationIndex + 1 ] || this . $newTab ;
2019-05-23 03:59:14 +08:00
tabEl . parentNode . insertBefore ( tabEl , beforeEl ) ;
2019-05-12 01:44:58 +08:00
}
2020-01-16 05:27:52 +08:00
this . trigger ( 'tabReorder' ) ;
2019-05-12 01:44:58 +08:00
this . layoutTabs ( ) ;
2019-05-01 04:31:12 +08:00
}
2019-05-12 16:59:53 +08:00
setupNewButton ( ) {
2020-01-16 05:35:15 +08:00
this . $newTab = $ ( NEW _TAB _BUTTON _TPL ) ;
2019-05-12 16:59:53 +08:00
2020-01-16 05:35:15 +08:00
this . $tabContent . append ( this . $newTab ) ;
2019-05-12 18:58:55 +08:00
2020-01-16 05:35:15 +08:00
this . $newTab . on ( 'click' , _ => this . trigger ( 'newTab' ) ) ;
2019-05-12 16:59:53 +08:00
}
2019-11-17 17:22:26 +08:00
setupFiller ( ) {
2020-01-16 05:35:15 +08:00
this . $filler = $ ( FILLER _TPL ) ;
2019-11-17 17:22:26 +08:00
2020-01-16 05:35:15 +08:00
this . $tabContent . append ( this . $filler ) ;
2019-11-17 17:22:26 +08:00
}
2019-05-12 16:59:53 +08:00
closest ( value , array ) {
let closest = Infinity ;
let closestIndex = - 1 ;
array . forEach ( ( v , i ) => {
if ( Math . abs ( value - v ) < closest ) {
closest = Math . abs ( value - v ) ;
closestIndex = i
}
} ) ;
return closestIndex ;
} ;
2020-01-13 02:05:09 +08:00
}