Skip to content
This repository has been archived by the owner on Dec 11, 2019. It is now read-only.

Commit

Permalink
allow tab dragging on a non-first page, and drag between pages (activ…
Browse files Browse the repository at this point in the history
…e tab only since tab page doesn't change when moving inactive tab to a new page)
  • Loading branch information
petemill committed Nov 10, 2017
1 parent dd23f19 commit cbf9dc7
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 42 deletions.
17 changes: 15 additions & 2 deletions app/browser/reducers/tabDraggingReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,26 @@ const reducer = (state, action, immutableAction) => {
}
case appConstants.APP_TAB_DRAG_CHANGE_WINDOW_DISPLAY_INDEX: {
const sourceTabId = state.getIn([stateKey, 'sourceTabId'])
if (sourceTabId == null) {
break
}
const destinationDisplayIndex = action.get('destinationDisplayIndex')
const destinationFrameIndex = action.get('destinationFrameIndex')
state = state.mergeIn([stateKey], {
const stateUpdate = {
// cache what we're doing, so we don't repeat request to move tab
// since it may take longer than it takes to fire mousemove multiple times
displayIndexRequested: destinationDisplayIndex
})
}
// in case resulting in new component mount (e.g. if tab dragged to new page)
// then tell it where mouse is
if (action.get('requiresMouseUpdate')) {
const currentWindowId = tabState.getWindowId(state, sourceTabId)
const win = BrowserWindow.fromId(currentWindowId)
const cursorWindowPoint = browserWindowUtil.getWindowClientPointAtCursor(win)
stateUpdate.dragWindowClientX = cursorWindowPoint.x
stateUpdate.dragWindowClientY = cursorWindowPoint.y
}
state = state.mergeIn([stateKey], stateUpdate)
process.stdout.write(`POS-${sourceTabId}->${destinationFrameIndex}`)
setImmediate(() => {
process.stdout.write(`.`)
Expand Down
30 changes: 29 additions & 1 deletion app/common/state/tabDraggingState.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,41 @@ const dragDetachedWindowIdSelector = createSelector(
dragState => dragState && dragState.get('dragDetachedWindowId')
)

const windowUIStateSelector = windowState => windowState.get('ui')
const windowTabUIStateSelector = createSelector(
windowUIStateSelector,
uiState => uiState.get('tabs')
)
const windowDragSourceTabIdSelector = createSelector(
windowTabUIStateSelector,
tabUIState => tabUIState.get('tabDragSourceTabId')
)

const tabDraggingState = {
isCurrentWindowDetached: createSelector(
// re-run next function only if dragDetachedWindowId changes
dragDetachedWindowIdSelector,
detachedWindowId =>
detachedWindowId && detachedWindowId === getCurrentWindowId()
)
),

isDragging: createSelector(
dragDataSelector,
dragState => {
return dragState != null
}
),

windowStateIsDragging: createSelector(
windowDragSourceTabIdSelector,
dragSourceTabId => dragSourceTabId != null
),

setWindowStateDragSourceTabId: (windowState, sourceTabId) =>
windowState.setIn(['ui', 'tabs', 'tabDragSourceTabId'], sourceTabId),

clearWindowStateDragSourceTabId: windowState =>
windowState.deleteIn(['ui', 'tabs', 'tabDragSourceTabId'])
}

module.exports = tabDraggingState
2 changes: 1 addition & 1 deletion app/renderer/components/tabs/pinnedTabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class PinnedTabs extends React.Component {
key={`tab-${frame.get('tabId')}-${frame.get('key')}`}
isDragging={this.props.draggingTabId === frame.get('tabId')}
displayIndex={tabDisplayIndex}
displayedTabCount={this.props.pinnedTabs.count()}
displayedTabCount={this.props.pinnedTabs.size}
/>
)
}
Expand Down
92 changes: 74 additions & 18 deletions app/renderer/components/tabs/tab.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ const throttle = require('lodash.throttle')
const DRAG_DETACH_PX_THRESHOLD_INITIAL = 44
const DRAG_DETACH_PX_THRESHOLD_POSTSORT = 80
const DRAG_DETACH_MS_TIME_BUFFER = 0
// time to wait before moving page
const DRAG_PAGEMOVE_MS_TIME_BUFFER = 750

// HACK - see the related `createEventFromSendMouseMoveInput` in tabDraggingWindowReducer.js
function translateEventFromSendMouseMoveInput (receivedEvent) {
Expand Down Expand Up @@ -124,7 +126,7 @@ class Tab extends React.Component {

attachDragSortHandlers () {
// get tab width
this.draggingTabWidth = this.elementRef.getBoundingClientRect().width
window.requestAnimationFrame(() => this.evaluateDraggingTabWidth())
// initial distance that has to be travelled outside the tab bar in order to detach the tab
// (increases after some sorting has happened, as the user may be more 'relaxed' with the mouse)
this.draggingDetachThreshold = DRAG_DETACH_PX_THRESHOLD_INITIAL
Expand All @@ -134,6 +136,13 @@ class Tab extends React.Component {
// more often
this.parentClientRect = this.elementRef.parentElement.getBoundingClientRect()
window.addEventListener('mousemove', this.onTabDraggingMouseMove)
// fire sort handler manually with the first update, if we have one
// since we may have attached but not received mouse event yet
if (this.props.dragWindowClientX && this.props.dragWindowClientY) {
window.requestAnimationFrame(() => {
this.onTabDraggingMouseMove({ clientX: this.props.dragWindowClientX, clientY: this.props.dragWindowClientY })
})
}
}

removeDragSortHandlers () {
Expand Down Expand Up @@ -168,7 +177,7 @@ class Tab extends React.Component {
}

onTabDraggingMouseMoveDetectSortChange (e) {
if (!this.parentClientRect) {
if (!this.parentClientRect || !this.draggingTabWidth) {
return
}
// find when the order should be changed
Expand Down Expand Up @@ -201,21 +210,59 @@ class Tab extends React.Component {
const tabWidth = this.draggingTabWidth
const tabLeft = e.clientX - this.parentClientRect.left - this.props.relativeXDragStart
const currentIndex = this.props.displayIndex
const destinationIndex = Math.max(
let destinationIndex = Math.max(
0,
Math.min(this.props.displayedTabCount - 1, Math.floor((tabLeft + (tabWidth / 2)) / tabWidth))
Math.min(this.props.totalTabCount - 1, this.props.firstTabDisplayIndex + Math.floor((tabLeft + (tabWidth / 2)) / tabWidth))
)
if (currentIndex !== destinationIndex) {
windowActions.tabDragChangeGroupDisplayIndex(this.props.isPinnedTab, destinationIndex)
// now that we have sorted, increase the threshold
// required for detach
// only allow to drag to a different page if we hang here for a while
const lastIndexOnCurrentPage = (this.props.firstTabDisplayIndex + this.props.displayedTabCount) - 1
const firstIndexOnCurrentPage = this.props.firstTabDisplayIndex
const isDraggingToPreviousPage = destinationIndex < firstIndexOnCurrentPage
const isDraggingToNextPage = destinationIndex > lastIndexOnCurrentPage
const isDraggingToDifferentPage = isDraggingToPreviousPage || isDraggingToNextPage
if (isDraggingToDifferentPage) {
// dragging to a different page
// make sure the user wants to change page by enforcing a pause
// but at least make sure the tab has moved to the index just next to the threshold
// (since we might have done a big jump)
if (isDraggingToNextPage && currentIndex !== lastIndexOnCurrentPage) {
windowActions.tabDragChangeGroupDisplayIndex(this.props.isPinnedTab, lastIndexOnCurrentPage)
} else if (isDraggingToPreviousPage && currentIndex !== firstIndexOnCurrentPage) {
windowActions.tabDragChangeGroupDisplayIndex(this.props.isPinnedTab, firstIndexOnCurrentPage)
}
this.beginOrContinueTimeoutForDragPageIndexMove(destinationIndex)
} else {
// dragging to a different index within the same page,
// so clear the wait for changing page and move immediately
this.clearDragPageIndexMoveTimeout()
// move display index immediately
windowActions.tabDragChangeGroupDisplayIndex(this.props.isPinnedTab, destinationIndex)
}
// a display index has changed, so increase the threshold
// required for detach (different axis of movement)
this.draggingDetachThreshold = DRAG_DETACH_PX_THRESHOLD_POSTSORT
} else {
// no longer want to change tab page
this.clearDragPageIndexMoveTimeout()
}
}
}

clearDragPageIndexMoveTimeout () {
window.clearTimeout(this.draggingMoveTabPageTimeout)
this.draggingMoveTabPageTimeout = null
}

beginOrContinueTimeoutForDragPageIndexMove (destinationIndex) {
this.draggingMoveTabPageTimeout = this.draggingMoveTabPageTimeout || window.setTimeout(() => {
this.draggingMoveTabPageTimeout = null
windowActions.tabDragChangeGroupDisplayIndex(this.props.isPinnedTab, destinationIndex, true)
}, DRAG_PAGEMOVE_MS_TIME_BUFFER)
}

dragTab (e) {
if (!this.elementRef) {
if (!this.elementRef || !this.parentClientRect) {
return
}
// cache just in case we need to force the tab to move to the mouse cursor
Expand Down Expand Up @@ -266,6 +313,15 @@ class Tab extends React.Component {
appActions.tabDragSingleTabMoved(x, y, this.currentWindowId)
}

/*
* Should be called whenever tab size changes. Since Chrome does not yet support ResizeObserver,
* we have to figure out the times. Luckily it's probably just initial drag start and when
* then tab page changes
*/
evaluateDraggingTabWidth () {
this.draggingTabWidth = this.elementRef.getBoundingClientRect().width
}

//
// General Events
//
Expand Down Expand Up @@ -359,13 +415,12 @@ class Tab extends React.Component {
this.tabNode.addEventListener('auxclick', this.onAuxClick.bind(this))

// if a new tab is already dragging,
// that means that it has been attached from another window.
// That window is handling the mousemove -> store dispatch
// which is sending our window mousemove events.
// that means that it has been attached from another window,
// or moved from another page.
// All we have to do is move the tab DOM element,
// and let the store know when the tab should move to another
// tab's position
if (this.props.isDragging && !this.props.dragOriginatedThisWindow) {
if (this.props.isDragging) {
// setup tab moving
this.attachDragSortHandlers()
}
Expand Down Expand Up @@ -405,11 +460,13 @@ class Tab extends React.Component {
props.themeColor = tabUIState.getThemeColor(currentWindow, frameKey)
props.displayIndex = ownProps.displayIndex
props.displayedTabCount = ownProps.displayedTabCount
props.totalTabCount = ownProps.totalTabCount || ownProps.displayedTabCount
props.title = frame.get('title')
props.partOfFullPageSet = partOfFullPageSet
props.showAudioTopBorder = audioState.showAudioTopBorder(currentWindow, frameKey, isPinned)
props.centralizeTabIcons = tabUIState.centralizeTabIcons(currentWindow, frameKey, isPinned)

props.firstTabDisplayIndex = ownProps.firstTabDisplayIndex != null ? ownProps.firstTabDisplayIndex : 0
props.tabPageIndex = ownProps.tabPageIndex
// used in other functions
props.tabId = tabId
props.previewMode = currentWindow.getIn(['ui', 'tabs', 'previewMode'])
Expand Down Expand Up @@ -484,14 +541,13 @@ class Tab extends React.Component {
// is firing the event to the store which will check
// for detach / attach to windows
this.attachDragSortHandlers()
// fire sort handler manually with the first update, if we have one
// since we may have attached but not received mouse event yet
if (this.props.dragWindowClientX && this.props.dragWindowClientY) {
this.onTabDraggingMouseMove({ clientX: this.props.dragWindowClientX, clientY: this.props.dragWindowClientY })
}
} else if (prevProps.isDragging && !this.props.isDragging) {
// tear-down tab moving
this.removeDragSortHandlers()
} else if (this.props.isDragging && this.props.tabPageIndex !== prevProps.tabPageIndex) {
// reevaluate anything that's changed when tab is dragged to a new page
this.draggingTabWidth = null
window.requestAnimationFrame(() => this.evaluateDraggingTabWidth())
}

// detect sort order change during drag
Expand Down
40 changes: 24 additions & 16 deletions app/renderer/components/tabs/tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,10 @@ class Tabs extends React.Component {
props.draggingTabId = tabState.draggingTabId(state)

// used in other functions
props.firstTabDisplayIndex = startingFrameIndex
props.fixTabWidth = currentWindow.getIn(['ui', 'tabs', 'fixTabWidth'])
props.tabPageIndex = currentWindow.getIn(['ui', 'tabs', 'tabPageIndex'])
props.totalTabCount = unpinnedTabs.size
props.dragData = dragData
props.dragWindowId = dragData.get('windowId')
props.totalPages = totalPages
Expand All @@ -138,17 +140,29 @@ class Tabs extends React.Component {

render () {
const isPreview = this.props.previewTabPageIndex != null
const displayedTabIndex = this.props.previewTabPageIndex != null ? this.props.previewTabPageIndex : this.props.tabPageIndex
return <div className={css(styles.tabs)}
data-test-id='tabs'
onMouseLeave={this.onMouseLeave}
>
{
this.props.onPreviousPage
? <BrowserButton
key='prev'
iconClass={globalStyles.appIcons.prev}
size='21px'
custom={[styles.tabs__tabStrip__navigation, styles.tabs__tabStrip__navigation_prev]}
onClick={this.onPrevPage}
/>
: null
}
{[
<ListWithTransitions className={css(
styles.tabs__tabStrip,
isPreview && styles.tabs__tabStrip_isPreview,
this.props.shouldAllowWindowDrag && styles.tabs__tabStrip_allowDragging
)}
key={!isPreview ? 'normal' : this.props.previewTabPageIndex}
key={displayedTabIndex}
disableAllAnimations={isPreview}
data-test-preview-tab={isPreview}
typeName='span'
Expand All @@ -174,28 +188,21 @@ class Tabs extends React.Component {
]}
onDragOver={this.onDragOver}
onDrop={this.onDrop}>
{
this.props.onPreviousPage
? <BrowserButton
key='prev'
iconClass={globalStyles.appIcons.prev}
size='21px'
custom={[styles.tabs__tabStrip__navigation, styles.tabs__tabStrip__navigation_prev]}
onClick={this.onPrevPage}
/>
: null
}

{
this.props.currentTabs
.map((frame, tabDisplayIndex) =>
<Tab
key={`tab-${frame.get('tabId')}-${frame.get('key')}`}
frame={frame}
isDragging={this.props.draggingTabId === frame.get('tabId')}
displayIndex={tabDisplayIndex}
displayedTabCount={this.props.currentTabs.count()}
singleTab={this.props.currentTabs.count() === 1}
firstTabDisplayIndex={this.props.firstTabDisplayIndex}
displayIndex={tabDisplayIndex + this.props.firstTabDisplayIndex}
displayedTabCount={this.props.currentTabs.size}
totalTabCount={this.props.totalTabCount}
singleTab={this.props.totalTabCount === 1}
partOfFullPageSet={this.props.partOfFullPageSet}
tabPageIndex={displayedTabIndex}
/>
)
}
Expand Down Expand Up @@ -248,7 +255,8 @@ const styles = StyleSheet.create({
display: 'flex',
flex: 1,
zIndex: globalStyles.zindex.zindexTabs,
overflow: 'hidden'
overflow: 'hidden',
position: 'relative'
},

tabs__tabStrip_isPreview: globalStyles.animations.tabFadeIn,
Expand Down
3 changes: 2 additions & 1 deletion app/renderer/reducers/frameReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const appActions = require('../../../js/actions/appActions')

// Utils
const frameStateUtil = require('../../../js/state/frameStateUtil')
const tabDraggingState = require('../../common/state/tabDraggingState')
const {getSourceAboutUrl, getSourceMagnetUrl} = require('../../../js/lib/appUrlUtil')
const {isURL, isPotentialPhishingUrl, getUrlFromInput} = require('../../../js/lib/urlutil')
const bookmarkUtil = require('../../common/lib/bookmarkUtil')
Expand Down Expand Up @@ -132,7 +133,7 @@ const frameReducer = (state, action, immutableAction) => {
const activeFrame = frameStateUtil.getActiveFrame(state)
// avoid the race-condition of updating the tabPage
// while active frame is not yet defined
if (activeFrame) {
if (activeFrame && !tabDraggingState.windowStateIsDragging(state)) {
// Update tab page index to the active tab in case the active tab changed
state = frameStateUtil.updateTabPageIndex(state, activeFrame.get('tabId'))
// after tabPageIndex is updated we need to update framesInternalIndex too
Expand Down
Loading

0 comments on commit cbf9dc7

Please sign in to comment.