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

Commit

Permalink
make tab preview based on idle mouse time
Browse files Browse the repository at this point in the history
- Fix #8860
- Auditors: @bsclifton, @bradleyrichter
  • Loading branch information
cezaraugusto committed May 23, 2017
1 parent 6ec02d8 commit cabbc3e
Show file tree
Hide file tree
Showing 14 changed files with 104 additions and 17 deletions.
8 changes: 8 additions & 0 deletions app/common/constants/settingsEnums.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ const tabCloseAction = {
PARENT: 'parent'
}

// timing in milliseconds
const tabPreviewTiming = {
LONG: 2000,
NORMAL: 1000,
SHORT: 500
}

const fullscreenOption = {
ALWAYS_ASK: 'alwaysAsk',
ALWAYS_ALLOW: 'alwaysAllow'
Expand All @@ -42,6 +49,7 @@ module.exports = {
newTabMode,
bookmarksToolbarMode,
tabCloseAction,
tabPreviewTiming,
fullscreenOption,
autoplayOption
}
4 changes: 4 additions & 0 deletions app/extensions/brave/locales/en-US/preferences.properties
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,10 @@ paintTabs=Show tabs in page theme color
tabsPerTabPage=Number of tabs per tab set:
tabCloseAction=When closing an active tab:
showTabPreviews=Show tab previews on hover
tabPreviewTiming=Time to wait before previewing a tab
long=Long
normal=Normal
short=Short
showHistoryMatches=Show history matches
showBookmarkMatches=Show bookmark matches
showOpenedTabMatches=Show tab matches
Expand Down
48 changes: 33 additions & 15 deletions app/renderer/components/tabs/tab.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const windowStore = require('../../../../js/stores/windowStore')
// Constants
const dragTypes = require('../../../../js/constants/dragTypes')
const messages = require('../../../../js/constants/messages')
const settings = require('../../../../js/constants/settings')

// Styles
const styles = require('../styles/tab')
Expand All @@ -42,10 +43,12 @@ const {getTabBreakpoint, tabUpdateFrameRate} = require('../../lib/tabUtil')
const {isWindows} = require('../../../common/lib/platformUtil')
const {getCurrentWindowId} = require('../../currentWindow')
const UrlUtil = require('../../../../js/lib/urlutil')
const getSetting = require('../../../../js/settings').getSetting

class Tab extends ImmutableComponent {
constructor () {
super()
this.onMouseMove = this.onMouseMove.bind(this)
this.onMouseEnter = this.onMouseEnter.bind(this)
this.onMouseLeave = this.onMouseLeave.bind(this)
this.onUpdateTabSize = this.onUpdateTabSize.bind(this)
Expand Down Expand Up @@ -163,27 +166,43 @@ class Tab extends ImmutableComponent {
!this.props.frame.get('provisionalLocation').startsWith('chrome-extension://mnojpmjdmbbfmejpflffifhffcmidifd/'))
}

onMouseLeave () {
onMouseLeave (e) {
windowActions.setTabHoverState(this.props.frame.get('key'), false)

if (this.props.previewTabs) {
window.clearTimeout(this.hoverTimeout)
windowActions.setPreviewFrame(null)
clearTimeout(this.mouseTimeout)
const tabAsRelatedTarget = /^tab_/i.test(e.relatedTarget.classList)

// We are taking for granted that user hovering over another tab
// means that he wants to sequentially preview a set of tabs,
// so if previewMode was set by defined mouse idle time,
// only cancel previewMode if the next event doesn't happen in another tab.
if (!tabAsRelatedTarget) {
windowActions.setPreviewFrame(null)
windowActions.setPreviewMode(false)
}
}
windowActions.setTabHoverState(this.props.frame.get('key'), 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)
windowActions.setTabHoverState(this.props.frame.get('key'), true)

// 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.mouseTimeout = null
if (this.props.previewTabs && this.props.previewMode) {
windowActions.setPreviewFrame(this.props.frame.get('key'))
}
}

onMouseMove (e) {
// previewMode is only triggered if mouse is idle over a tab
// for a given amount of time based on timing defined in prefs->tabs
if (this.props.previewTabs) {
this.hoverTimeout =
window.setTimeout(windowActions.setPreviewFrame.bind(null, this.props.frame.get('key')), previewMode ? 0 : 200)
clearTimeout(this.mouseTimeout)
this.mouseTimeout = setTimeout(() => {
windowActions.setPreviewFrame(this.props.frame.get('key'))
windowActions.setPreviewMode(true)
}, getSetting(settings.TAB_PREVIEW_TIMING))
}
windowActions.setTabHoverState(this.props.frame.get('key'), true)
}

onAuxClick (e) {
Expand Down Expand Up @@ -288,6 +307,7 @@ class Tab extends ImmutableComponent {
partOfFullPageSet: this.props.partOfFullPageSet || !!this.props.tabWidth
})}
style={this.props.tabWidth ? { flex: `0 0 ${this.props.tabWidth}px` } : {}}
onMouseMove={this.onMouseMove}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}>
{
Expand Down Expand Up @@ -369,8 +389,6 @@ class Tab extends ImmutableComponent {
}

const paymentsEnabled = () => {
const getSetting = require('../../../../js/settings').getSetting
const settings = require('../../../../js/constants/settings')
return getSetting(settings.PAYMENTS_ENABLED)
}

Expand Down
1 change: 1 addition & 0 deletions app/renderer/components/tabs/tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ class Tabs extends ImmutableComponent {
frame={frame}
key={'tab-' + frame.get('key')}
paintTabs={this.props.paintTabs}
previewMode={this.props.previewMode}
previewTabs={this.props.previewTabs}
isActive={this.props.activeFrameKey === frame.get('key')}
onTabClosedWithMouse={this.onTabClosedWithMouse}
Expand Down
2 changes: 2 additions & 0 deletions app/renderer/components/tabs/tabsToolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class TabsToolbar extends ImmutableComponent {
? <PinnedTabs sites={this.props.sites}
activeFrameKey={this.props.activeFrameKey}
paintTabs={this.props.paintTabs}
previewMode={this.props.previewMode}
previewTabs={this.props.previewTabs}
dragData={this.props.dragData}
tabPageIndex={this.props.tabPageIndex}
Expand All @@ -59,6 +60,7 @@ class TabsToolbar extends ImmutableComponent {
shouldAllowWindowDrag={this.props.shouldAllowWindowDrag}
dragData={this.props.dragData}
paintTabs={this.props.paintTabs}
previewMode={this.props.previewMode}
previewTabs={this.props.previewTabs}
tabsPerTabPage={this.props.tabsPerTabPage}
activeFrameKey={this.props.activeFrameKey}
Expand Down
4 changes: 3 additions & 1 deletion docs/state.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ AppStore
'tabs.close-action': string, // one of: parent, lastActive, next
'tabs.paint-tabs': boolean, // true if the page theme color and favicon color should be used for tabs
'tabs.show-tab-previews': boolean, // true to show tab previews
'tabs.preview-timing': boolean, // how much in milliseconds user should wait before tab preview is fired
'tabs.switch-to-new-tabs': boolean, // true if newly opened tabs should be focused immediately
'tabs.tabs-per-page': number // number of tabs per tab page
},
Expand Down Expand Up @@ -657,8 +658,9 @@ WindowStore
},
size: array, // last known window size [x, y]
tabs: {
tabPageIndex: number, // index of the current tab page
previewMode: boolean, // whether or not tab preview should be fired based on mouse idle time
previewTabPageIndex: number // index of the tab being previewed
tabPageIndex: number, // index of the current tab page
},
},
widevinePanelDetail: {
Expand Down
11 changes: 11 additions & 0 deletions docs/windowActions.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,17 @@ This is done when hovering over a tab.



### setPreviewMode(shouldEnablePreview)

Dispatches a message to the store to set preview mode.
This will check whether setPreviewFrame should be fired or not.

**Parameters**

**shouldEnablePreview**: `Boolean`, true if user enters in previewMode state



### setTabPageIndex(index)

Dispatches a message to the store to set the tab page index.
Expand Down
23 changes: 22 additions & 1 deletion js/about/preferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,15 @@ const messages = require('../constants/messages')
const settings = require('../constants/settings')
const {changeSetting} = require('../../app/renderer/lib/settingsUtil')
const {passwordManagers, extensionIds} = require('../constants/passwordManagers')
const {startsWithOption, newTabMode, bookmarksToolbarMode, tabCloseAction, fullscreenOption, autoplayOption} = require('../../app/common/constants/settingsEnums')
const {
startsWithOption,
newTabMode,
bookmarksToolbarMode,
tabCloseAction,
fullscreenOption,
autoplayOption,
tabPreviewTiming
} = require('../../app/common/constants/settingsEnums')

const aboutActions = require('./aboutActions')
const appActions = require('../actions/appActions')
Expand Down Expand Up @@ -359,6 +367,19 @@ class TabsTab extends ImmutableComponent {
<SettingCheckbox dataL10nId='switchToNewTabs' prefKey={settings.SWITCH_TO_NEW_TABS} settings={this.props.settings} onChangeSetting={this.props.onChangeSetting} />
<SettingCheckbox dataL10nId='paintTabs' prefKey={settings.PAINT_TABS} settings={this.props.settings} onChangeSetting={this.props.onChangeSetting} />
<SettingCheckbox dataL10nId='showTabPreviews' prefKey={settings.SHOW_TAB_PREVIEWS} settings={this.props.settings} onChangeSetting={this.props.onChangeSetting} />
{
getSetting(settings.SHOW_TAB_PREVIEWS, this.props.settings)
? <SettingItem dataL10nId='tabPreviewTiming'>
<SettingDropdown
value={getSetting(settings.TAB_PREVIEW_TIMING, this.props.settings)}
onChange={changeSetting.bind(null, this.props.onChangeSetting, settings.TAB_PREVIEW_TIMING)}>
<option data-l10n-id='long' value={tabPreviewTiming.LONG} />
<option data-l10n-id='normal' value={tabPreviewTiming.NORMAL} />
<option data-l10n-id='short' value={tabPreviewTiming.SHORT} />
</SettingDropdown>
</SettingItem>
: null
}
<SettingItem dataL10nId='dashboardSettingsTitle'>
<SettingCheckbox dataL10nId='dashboardShowImages' prefKey={settings.SHOW_DASHBOARD_IMAGES} settings={this.props.settings} onChangeSetting={this.props.onChangeSetting} />
</SettingItem>
Expand Down
13 changes: 13 additions & 0 deletions js/actions/windowActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,19 @@ const windowActions = {
})
},

/**
* Dispatches a message to the store to set preview mode.
* This will check whether setPreviewFrame should be fired or not.
*
* @param {Boolean} shouldEnablePreview - true if user enters in previewMode state
*/
setPreviewMode: function (shouldEnablePreview) {
dispatch({
actionType: windowConstants.WINDOW_SET_PREVIEW_MODE,
shouldEnablePreview
})
},

/**
* Dispatches a message to the store to set the tab page index.
*
Expand Down
1 change: 1 addition & 0 deletions js/components/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,7 @@ class Main extends ImmutableComponent {
paintTabs={getSetting(settings.PAINT_TABS)}
shouldAllowWindowDrag={shouldAllowWindowDrag}
dragData={this.props.appState.getIn(['dragData', 'type']) === dragTypes.TAB && this.props.appState.get('dragData')}
previewMode={this.props.windowState.getIn(['ui', 'tabs', 'previewMode'])}
previewTabs={getSetting(settings.SHOW_TAB_PREVIEWS)}
tabsPerTabPage={tabsPerPage}
tabPageIndex={this.props.windowState.getIn(['ui', 'tabs', 'tabPageIndex'])}
Expand Down
1 change: 1 addition & 0 deletions js/constants/appConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ module.exports = {
'tabs.tabs-per-page': 20,
'tabs.close-action': 'parent',
'tabs.show-tab-previews': true,
'tabs.preview-timing': 2000,
'tabs.show-dashboard-images': true,
'privacy.history-suggestions': true,
'privacy.bookmark-suggestions': true,
Expand Down
1 change: 1 addition & 0 deletions js/constants/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const settings = {
PAINT_TABS: 'tabs.paint-tabs',
TABS_PER_PAGE: 'tabs.tabs-per-page',
SHOW_TAB_PREVIEWS: 'tabs.show-tab-previews',
TAB_PREVIEW_TIMING: 'tabs.preview-timing',
SHOW_DASHBOARD_IMAGES: 'tabs.show-dashboard-images',
// Privacy Tab
HISTORY_SUGGESTIONS: 'privacy.history-suggestions',
Expand Down
1 change: 1 addition & 0 deletions js/constants/windowConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const windowConstants = {
WINDOW_CLOSE_FRAMES: _,
WINDOW_SET_FOCUSED_FRAME: _,
WINDOW_SET_PREVIEW_FRAME: _,
WINDOW_SET_PREVIEW_MODE: _,
WINDOW_SET_PREVIEW_TAB_PAGE_INDEX: _,
WINDOW_SET_TAB_PAGE_INDEX: _,
WINDOW_SET_TAB_BREAKPOINT: _,
Expand Down
3 changes: 3 additions & 0 deletions js/stores/windowStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,9 @@ const doAction = (action) => {
case windowConstants.WINDOW_CLEAR_CLOSED_FRAMES:
windowState = windowState.set('closedFrames', new Immutable.List())
break
case windowConstants.WINDOW_SET_PREVIEW_MODE:
windowState = windowState.setIn(['ui', 'tabs', 'previewMode'], action.shouldEnablePreview)
break
case windowConstants.WINDOW_SET_PREVIEW_FRAME:
windowState = windowState.merge({
previewFrameKey: action.frameKey != null && action.frameKey !== windowState.get('activeFrameKey')
Expand Down

0 comments on commit cabbc3e

Please sign in to comment.