diff --git a/app/common/state/tabContentState.js b/app/common/state/tabContentState.js index 310dafc67b8..03e73a0b822 100644 --- a/app/common/state/tabContentState.js +++ b/app/common/state/tabContentState.js @@ -87,7 +87,7 @@ const tabContentState = { const tabPageIndex = state.getIn(['ui', 'tabs', 'tabPageIndex'], 0) const previewTabPageIndex = state.getIn(['ui', 'tabs', 'previewTabPageIndex']) - return previewTabPageIndex !== undefined ? previewTabPageIndex : tabPageIndex + return previewTabPageIndex != null ? previewTabPageIndex : tabPageIndex }, isMediumView: (state, frameKey) => { diff --git a/app/renderer/components/tabs/tab.js b/app/renderer/components/tabs/tab.js index ec877c00d13..311f89924d6 100644 --- a/app/renderer/components/tabs/tab.js +++ b/app/renderer/components/tabs/tab.js @@ -28,7 +28,6 @@ const tabContentState = require('../../../common/state/tabContentState') // Constants const dragTypes = require('../../../../js/constants/dragTypes') -const settings = require('../../../../js/constants/settings') // Styles const styles = require('../styles/tab') @@ -44,7 +43,6 @@ const frameStateUtil = require('../../../../js/state/frameStateUtil') const {getTabBreakpoint, tabUpdateFrameRate} = require('../../lib/tabUtil') const {isWindows} = require('../../../common/lib/platformUtil') const {getCurrentWindowId} = require('../../currentWindow') -const {getSetting} = require('../../../../js/settings') const UrlUtil = require('../../../../js/lib/urlutil') const {hasBreakpoint} = require('../../lib/tabUtil') @@ -131,25 +129,10 @@ class Tab extends React.Component { } onMouseLeave () { - if (this.props.previewTabs) { - window.clearTimeout(this.hoverTimeout) - windowActions.setPreviewFrame(null) - } windowActions.setTabHoverState(this.props.frameKey, false) } onMouseEnter (e) { - // relatedTarget inside mouseenter checks which element before this event was the pointer on - // if this element has a tab-like class, then it's likely that the user was previewing - // a sequency of tabs. Called here as previewMode. - const previewMode = /tab(?!pages)/i.test(e.relatedTarget.classList) - - // If user isn't in previewMode, we add a bit of delay to avoid tab from flashing out - // as reported here: https://github.com/brave/browser-laptop/issues/1434 - if (this.props.previewTabs) { - this.hoverTimeout = - window.setTimeout(windowActions.setPreviewFrame.bind(null, this.props.frameKey), previewMode ? 0 : 200) - } windowActions.setTabHoverState(this.props.frameKey, true) } @@ -270,7 +253,6 @@ class Tab extends React.Component { // used in other functions props.totalTabs = state.get('tabs').size - props.previewTabs = getSetting(settings.SHOW_TAB_PREVIEWS) props.dragData = state.getIn(['dragData', 'type']) === dragTypes.TAB && state.get('dragData') props.hasTabInFullScreen = tabContentState.hasTabInFullScreen(currentWindow) props.tabId = frame.get('tabId') diff --git a/app/renderer/components/tabs/tabPage.js b/app/renderer/components/tabs/tabPage.js index 6a222d3eeeb..58e308a7e0b 100644 --- a/app/renderer/components/tabs/tabPage.js +++ b/app/renderer/components/tabs/tabPage.js @@ -33,20 +33,11 @@ class TabPage extends React.Component { } onMouseLeave () { - window.clearTimeout(this.hoverTimeout) - windowActions.setPreviewTabPageIndex() + windowActions.setTabPageHoverState(this.props.index, false) } onMouseEnter (e) { - // relatedTarget inside mouse enter checks which element before this event was the pointer on - // if this element has a tab-like class, then it's likely that the user was previewing - // a sequence of tabs. Called here as previewMode. - const previewMode = /tab(?!pages)/i.test(e.relatedTarget.classList) - - // If user isn't in previewMode, we add a bit of delay to avoid tab from flashing out - // as reported here: https://github.com/brave/browser-laptop/issues/1434 - this.hoverTimeout = - window.setTimeout(windowActions.setPreviewTabPageIndex.bind(null, this.props.index), previewMode ? 0 : 200) + windowActions.setTabPageHoverState(this.props.index, true) } onDrop (e) { diff --git a/app/renderer/components/tabs/tabs.js b/app/renderer/components/tabs/tabs.js index 019ecaa5220..53643bc67ec 100644 --- a/app/renderer/components/tabs/tabs.js +++ b/app/renderer/components/tabs/tabs.js @@ -164,7 +164,7 @@ class Tabs extends React.Component { onMouseLeave={this.onMouseLeave}> { const index = frameStateUtil.getFrameIndex(state, action.frameProps.get('key')) @@ -32,7 +29,6 @@ const setFullScreen = (state, action) => { } const closeFrame = (state, action) => { - const activeFrameIndex = frameStateUtil.getActiveFrameIndex(state) const index = frameStateUtil.getFrameIndex(state, action.frameKey) if (index === -1) { return state @@ -40,7 +36,6 @@ const closeFrame = (state, action) => { const frameProps = frameStateUtil.getFrameByKey(state, action.frameKey) const hoverState = state.getIn(['frames', index, 'hoverState']) - const framePreviewEnabled = getSetting(settings.SHOW_TAB_PREVIEWS) state = state.merge(frameStateUtil.removeFrame( state, @@ -56,15 +51,15 @@ const closeFrame = (state, action) => { const nextFrame = frameStateUtil.getFrameByIndex(state, index) - if (nextFrame && hoverState) { + if (nextFrame) { // Copy the hover state if tab closed with mouse as long as we have a next frame // This allow us to have closeTab button visible for sequential frames closing, // until onMouseLeave event happens. - windowActions.setTabHoverState(nextFrame.get('key'), hoverState) - if (framePreviewEnabled && index !== activeFrameIndex) { - // After closing a tab, preview the next frame as long as there is one - windowActions.setPreviewFrame(nextFrame.get('key')) + if (hoverState) { + state = frameStateUtil.setTabHoverState(state, nextFrame.get('key'), hoverState) } + } else if (hoverState && frameStateUtil.getPreviewFrameKey(state) === action.frameKey) { + state = frameStateUtil.setPreviewFrameKey(state, null) } return state @@ -124,12 +119,15 @@ const frameReducer = (state, action, immutableAction) => { const active = immutableAction.getIn(['tabValue', 'active']) if (active != null) { if (active) { - state = state.merge({ - activeFrameKey: frame.get('key'), - previewFrameKey: null - }) + state = state.set('activeFrameKey', frame.get('key')) + if (frame.get('hoverState')) { + state = state.set('previewFrameKey', null) + } + if (frame.getIn(['ui', 'tabs', 'hoverTabPageIndex']) == null) { + state = state.deleteIn(['ui', 'tabs', 'previewTabPageIndex']) + } state = state.setIn(['frames', index, 'lastAccessedTime'], new Date().getTime()) - state = state.deleteIn(['ui', 'tabs', 'previewTabPageIndex']) + state = frameStateUtil.updateTabPageIndex(state, frame) } } diff --git a/docs/windowActions.md b/docs/windowActions.md index 60643043108..ffdc9df335f 100644 --- a/docs/windowActions.md +++ b/docs/windowActions.md @@ -192,7 +192,8 @@ Dispatches a message to the store when the frame is active and the window is foc ### setPreviewFrame(frameKey) Dispatches a message to the store to set a preview frame. -This is done when hovering over a tab. +This should only be called internally by `WINDOW_SET_TAB_HOVER_STATE` +when we need to delay updating the preview frame value **Parameters** @@ -234,6 +235,18 @@ Dispatches a message to the store to set the current tab hover state. +### setTabPageHoverState(tabPageIndex, hoverState) + +Dispatches a message to the store to set the current tab hover state. + +**Parameters** + +**tabPageIndex**: `Object`, the frame key for the webview in question. + +**hoverState**: `boolean`, whether or not mouse is over tabPage + + + ### setPreviewTabPageIndex(previewTabPageIndex) Dispatches a message to the store to set the tab page index being previewed. diff --git a/js/actions/windowActions.js b/js/actions/windowActions.js index e6a4258155f..2f396345081 100644 --- a/js/actions/windowActions.js +++ b/js/actions/windowActions.js @@ -229,7 +229,8 @@ const windowActions = { /** * Dispatches a message to the store to set a preview frame. - * This is done when hovering over a tab. + * This should only be called internally by `WINDOW_SET_TAB_HOVER_STATE` + * when we need to delay updating the preview frame value * * @param {Object} frameKey - the frame key for the webview in question. */ @@ -280,6 +281,20 @@ const windowActions = { }) }, + /** + * Dispatches a message to the store to set the current tab hover state. + * + * @param {Object} tabPageIndex - the frame key for the webview in question. + * @param {boolean} hoverState - whether or not mouse is over tabPage + */ + setTabPageHoverState: function (tabPageIndex, hoverState) { + dispatch({ + actionType: windowConstants.WINDOW_SET_TAB_PAGE_HOVER_STATE, + tabPageIndex, + hoverState + }) + }, + /** * Dispatches a message to the store to set the tab page index being previewed. * diff --git a/js/constants/windowConstants.js b/js/constants/windowConstants.js index aae5ad7a779..07e308ffef1 100644 --- a/js/constants/windowConstants.js +++ b/js/constants/windowConstants.js @@ -15,6 +15,7 @@ const windowConstants = { WINDOW_SET_TAB_PAGE_INDEX: _, WINDOW_SET_TAB_BREAKPOINT: _, WINDOW_SET_TAB_HOVER_STATE: _, + WINDOW_SET_TAB_PAGE_HOVER_STATE: _, WINDOW_TAB_MOVE: _, WINDOW_SET_THEME_COLOR: _, WINDOW_WEBVIEW_LOAD_END: _, diff --git a/js/state/frameStateUtil.js b/js/state/frameStateUtil.js index 04bdc92f5bb..ba6ae891c38 100644 --- a/js/state/frameStateUtil.js +++ b/js/state/frameStateUtil.js @@ -21,6 +21,9 @@ const {getSetting} = require('../settings') const {isIntermediateAboutPage} = require('../lib/appUrlUtil') const urlParse = require('../../app/common/urlParse') +let tabPageHoverTimeout +let tabHoverTimeout = null + const comparatorByKeyAsc = (a, b) => a.get('key') > b.get('key') ? 1 : b.get('key') > a.get('key') ? -1 : 0 @@ -391,15 +394,13 @@ function removeFrame (state, frameProps, framePropsIndex) { } return { - previewFrameKey: null, closedFrames, frames: newFrames } } -function getFrameTabPageIndex (state, frameProps, tabsPerTabPage) { - frameProps = makeImmutable(frameProps) - const index = findNonPinnedDisplayIndexForFrameKey(state, frameProps.get('key')) +function getFrameTabPageIndex (state, frameKey, tabsPerTabPage = getSetting(settings.TABS_PER_PAGE)) { + const index = findNonPinnedDisplayIndexForFrameKey(state, frameKey) if (index === -1) { return -1 } @@ -451,7 +452,8 @@ function isPinned (state, frameKey) { * @param frameProps Any frame belonging to the page */ function updateTabPageIndex (state, frameProps) { - const index = getFrameTabPageIndex(state, frameProps, getSetting(settings.TABS_PER_PAGE)) + frameProps = makeImmutable(frameProps) + const index = getFrameTabPageIndex(state, frameProps.get('key')) if (index === -1) { return state @@ -532,7 +534,111 @@ const getTabPageCount = (state) => { return Math.ceil(frames.size / tabsPerPage) } +const getPreviewFrameKey = (state) => { + return state.get('previewFrameKey') +} + +const setPreviewTabPageIndex = (state, index, immediate = false) => { + clearTimeout(tabPageHoverTimeout) + const previewTabs = getSetting(settings.SHOW_TAB_PREVIEWS) + const isActive = state.getIn(['ui', 'tabs', 'tabPageIndex']) === index + let newTabPageIndex = index + + if (!previewTabs || state.getIn(['ui', 'tabs', 'hoverTabPageIndex']) !== index || isActive) { + newTabPageIndex = null + } + + if (!immediate) { + // if there is an existing preview tab page index then we're already in preview mode + // we use actions here because that is the only way to delay updating the state + const previewMode = state.getIn(['ui', 'tabs', 'previewTabPageIndex']) != null + if (previewMode && newTabPageIndex == null) { + // add a small delay when we are clearing the preview frame key so we don't lose + // previewMode if the user mouses over another tab - see below + tabPageHoverTimeout = setTimeout(windowActions.setPreviewTabPageIndex.bind(null, null), 200) + return state + } + + if (!previewMode) { + // If user isn't in previewMode so we add a bit of delay to avoid tab from flashing out + // as reported here: https://github.com/brave/browser-laptop/issues/1434 + // using an action here because that is the only way we can do a delayed state update + tabPageHoverTimeout = setTimeout(windowActions.setPreviewTabPageIndex.bind(null, newTabPageIndex), 200) + return state + } + } + + return state.setIn(['ui', 'tabs', 'previewTabPageIndex'], newTabPageIndex) +} + +const setPreviewFrameKey = (state, frameKey, immediate = false) => { + clearTimeout(tabHoverTimeout) + const frame = getFrameByKey(state, frameKey) + const isActive = isFrameKeyActive(state, frameKey) + const previewTabs = getSetting(settings.SHOW_TAB_PREVIEWS) + let newPreviewFrameKey = frameKey + + if (!previewTabs || frame == null || !frame.get('hoverState') || isActive) { + newPreviewFrameKey = null + } + + if (!immediate) { + // if there is an existing preview frame key then we're already in preview mode + // we use actions here because that is the only way to delay updating the state + const previewMode = getPreviewFrameKey(state) != null + if (previewMode && newPreviewFrameKey == null) { + // add a small delay when we are clearing the preview frame key so we don't lose + // previewMode if the user mouses over another tab - see below + tabHoverTimeout = setTimeout(windowActions.setPreviewFrame.bind(null, null), 200) + return state + } + + if (!previewMode) { + // If user isn't in previewMode so we add a bit of delay to avoid tab from flashing out + // as reported here: https://github.com/brave/browser-laptop/issues/1434 + // using an action here because that is the only way we can do a delayed state update + tabHoverTimeout = setTimeout(windowActions.setPreviewFrame.bind(null, newPreviewFrameKey), 200) + return state + } + } + + const index = getFrameTabPageIndex(state, frame) + if (index !== -1) { + if (index !== state.getIn(['ui', 'tabs', 'tabPageIndex'])) { + state = state.setIn(['ui', 'tabs', 'previewTabPageIndex'], index) + } else { + state = state.deleteIn(['ui', 'tabs', 'previewTabPageIndex']) + } + } + return state.set('previewFrameKey', newPreviewFrameKey) +} + +const setTabPageHoverState = (state, tabPageIndex, hoverState) => { + const currentHoverIndex = state.getIn(['ui', 'tabs', 'hoverTabPageIndex']) + if (!hoverState && currentHoverIndex === tabPageIndex) { + state = state.setIn(['ui', 'tabs', 'hoverTabPageIndex'], null) + } else if (hoverState) { + state = state.setIn(['ui', 'tabs', 'hoverTabPageIndex'], tabPageIndex) + } + state = setPreviewTabPageIndex(state, tabPageIndex) + return state +} + +const setTabHoverState = (state, frameKey, hoverState) => { + const frameIndex = getFrameIndex(state, frameKey) + if (frameIndex !== -1) { + state = state.setIn(['frames', frameIndex, 'hoverState'], hoverState) + state = setPreviewFrameKey(state, frameKey) + } + return state +} + module.exports = { + setTabPageHoverState, + setPreviewTabPageIndex, + setTabHoverState, + setPreviewFrameKey, + getPreviewFrameKey, deleteTabInternalIndex, deleteFrameInternalIndex, updateFramesInternalIndex, diff --git a/js/stores/windowStore.js b/js/stores/windowStore.js index f90d6ebd3f2..edd632f6d91 100644 --- a/js/stores/windowStore.js +++ b/js/stores/windowStore.js @@ -355,22 +355,13 @@ const doAction = (action) => { windowState = windowState.set('closedFrames', new Immutable.List()) break case windowConstants.WINDOW_SET_PREVIEW_FRAME: - windowState = windowState.merge({ - previewFrameKey: - action.frameKey != null && !frameStateUtil.isFrameKeyActive(windowState, action.frameKey) - ? action.frameKey - : null - }) + windowState = frameStateUtil.setPreviewFrameKey(windowState, action.frameKey, true) break case windowConstants.WINDOW_SET_PREVIEW_TAB_PAGE_INDEX: - if (action.previewTabPageIndex !== windowState.getIn(['ui', 'tabs', 'tabPageIndex'])) { - windowState = windowState.setIn(['ui', 'tabs', 'previewTabPageIndex'], action.previewTabPageIndex) - } else { - windowState = windowState.deleteIn(['ui', 'tabs', 'previewTabPageIndex']) - } + windowState = frameStateUtil.setPreviewTabPageIndex(windowState, action.previewTabPageIndex, true) break case windowConstants.WINDOW_SET_TAB_PAGE_INDEX: - if (action.index !== undefined) { + if (action.index != null) { windowState = windowState.setIn(['ui', 'tabs', 'tabPageIndex'], action.index) windowState = windowState.deleteIn(['ui', 'tabs', 'previewTabPageIndex']) } else { @@ -390,10 +381,12 @@ const doAction = (action) => { } case windowConstants.WINDOW_SET_TAB_HOVER_STATE: { - const frameIndex = frameStateUtil.getFrameIndex(windowState, action.frameKey) - if (frameIndex !== -1) { - windowState = windowState.setIn(['frames', frameIndex, 'hoverState'], action.hoverState) - } + windowState = frameStateUtil.setTabHoverState(windowState, action.frameKey, action.hoverState) + break + } + case windowConstants.WINDOW_SET_TAB_PAGE_HOVER_STATE: + { + windowState = frameStateUtil.setTabPageHoverState(windowState, action.tabPageIndex, action.hoverState) break } case windowConstants.WINDOW_TAB_MOVE: