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

Commit

Permalink
Merge pull request #1 from brave/tabs
Browse files Browse the repository at this point in the history
Basic tab structure
  • Loading branch information
bbondy committed Dec 1, 2015
2 parents 84a9f23 + de4a34d commit 8ed40b0
Show file tree
Hide file tree
Showing 10 changed files with 619 additions and 2 deletions.
65 changes: 65 additions & 0 deletions js/actions/appActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,71 @@ const AppActions = {
frameOpts: frameOpts,
openInForeground
})
},

setActiveFrame: function (frameProps) {
AppDispatcher.dispatch({
actionType: AppConstants.APP_SET_ACTIVE_FRAME,
frameProps: frameProps
})
},

tabDragStart: function (frameProps) {
AppDispatcher.dispatch({
actionType: AppConstants.APP_TAB_DRAG_START,
frameProps
})
},

tabDragStop: function (frameProps) {
AppDispatcher.dispatch({
actionType: AppConstants.APP_TAB_DRAG_STOP,
frameProps
})
},

tabDragDraggingOverLeftHalf: function (frameProps) {
AppDispatcher.dispatch({
actionType: AppConstants.APP_TAB_DRAGGING_OVER_LEFT,
frameProps
})
},

tabDragDraggingOverRightHalf: function (frameProps) {
AppDispatcher.dispatch({
actionType: AppConstants.APP_TAB_DRAGGING_OVER_RIGHT,
frameProps
})
},

tabDragExit: function (frameProps) {
AppDispatcher.dispatch({
actionType: AppConstants.APP_TAB_DRAG_EXIT,
frameProps
})
},

tabDragExitRightHalf: function (frameProps) {
AppDispatcher.dispatch({
actionType: AppConstants.APP_TAB_DRAG_EXIT_RIGHT,
frameProps
})
},

tabDraggingOn: function (frameProps) {
AppDispatcher.dispatch({
actionType: AppConstants.APP_TAB_DRAGGING_ON,
frameProps
})
},

moveTab: function (sourceFrameProps, destinationFrameProps, prepend) {
AppDispatcher.dispatch({
actionType: AppConstants.APP_TAB_MOVE,
sourceFrameProps,
destinationFrameProps,
prepend
})
}
}

Expand Down
9 changes: 8 additions & 1 deletion js/components/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const AppActions = require('../actions/appActions')
// Components
const NavigationBar = require('./navigationBar')
const Frame = require('./frame')
const Tabs = require('./tabs')

// Constants
const Config = require('../constants/config')
Expand All @@ -34,11 +35,17 @@ class Main extends ImmutableComponent {
? 1 : b.get('key') > a.get('key') ? -1 : 0

return <div id='browser'>
<div>
<div className='top'>
<NavigationBar
navbar={this.props.browser.getIn(['ui', 'navbar'])}
activeFrame={this.props.browser.get('frame')}
/>
<Tabs
tabs={this.props.browser.getIn(['ui', 'tabs'])}
frames={this.props.browser.get('frames')}
key='tab-bar'
activeFrame={FrameStateUtil.getActiveFrame(this.props.browser)}
/>
</div>
<div className='mainContainer'>
<div className='tabContainer'>
Expand Down
197 changes: 197 additions & 0 deletions js/components/tabs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
const React = require('react')
const ReactDOM = require('react-dom')

const ImmutableComponent = require('./immutableComponent')

const AppActions = require('../actions/appActions')
const cx = require('../lib/classSet.js')

const getFavicon = require('../lib/faviconUtil.js')

class DragIndicator extends ImmutableComponent {
constructor (props) {
super(props)
}

render () {
return <hr className={cx({
dragIndicator: true,
dragActive: this.props.active,
dragIndicatorEnd: this.props.end
})}/>
}
}

class Tab extends ImmutableComponent {
constructor (props) {
super(props)
}

get displayValue () {
// YouTube tries to change the title to add a play icon when
// there is audio. Since we have our own audio indicator we get
// rid of it.
return (this.props.frameProps.get('title') ||
this.props.frameProps.get('location')).replace('▶ ', '')
}

onDragStart (e) {
AppActions.tabDragStart(this.props.frameProps)
}

onDragEnd () {
AppActions.tabDragStop(this.props.frameProps)
}

onDragOver (e) {
e.preventDefault()

// Otherise, only accept it if we have some frameProps
if (!this.props.activeDraggedTab) {
AppActions.tabDraggingOn(this.props.frameProps)
return
}

let rect = ReactDOM.findDOMNode(this.refs.tab).getBoundingClientRect()
if (e.clientX > rect.left && e.clientX < rect.left + rect.width / 2 &&
!this.props.frameProps.get('tabIsDraggingOverLeftHalf')) {
AppActions.tabDragDraggingOverLeftHalf(this.props.frameProps)
} else if (e.clientX < rect.right && e.clientX >= rect.left + rect.width / 2 &&
!this.props.frameProps.get('tabIsDraggingOverRightHalf')) {
AppActions.tabDragDraggingOverRightHalf(this.props.frameProps)
}
}

onDragLeave () {
if (this.props.frameProps.get('tabIsDraggingOverLeftHalf') ||
this.props.frameProps.get('tabIsDraggingOn') ||
this.props.frameProps.get('tabIsDraggingOverLeftHalf')) {
AppActions.tabDragExit(this.props.frameProps)
} else if (this.props.frameProps.get('tabIsDraggingOverRightHalf')) {
AppActions.tabDragExitRightHalf(this.props.frameProps)
}
}

onDrop (e) {
let sourceFrameProps = this.props.activeDraggedTab
if (!sourceFrameProps) {
return
}

if (this.props.frameProps.get('tabIsDraggingOverLeftHalf')) {
AppActions.moveTab(sourceFrameProps, this.props.frameProps, true)
} else {
AppActions.moveTab(sourceFrameProps, this.props.frameProps, false)
}
AppActions.tabDragExit(this.props.frameProps)
}

setActiveFrame () {
AppActions.setActiveFrame(this.props.frameProps)
}

render () {
const thumbnailWidth = 160
const thumbnailHeight = 100

let thumbnailStyle = {
backgroundSize: `${thumbnailWidth} ${thumbnailHeight}`,
width: thumbnailWidth,
height: thumbnailHeight
}
if (this.props.frameProps.get('thumbnailUrl')) {
thumbnailStyle.backgroundImage = `url(${this.props.frameProps.get('thumbnailUrl')})`
}

// Style based on theme-color
var activeTabStyle = {}
if (this.props.isActive && (this.props.frameProps.get('themeColor') || this.props.frameProps.get('computedThemeColor'))) {
activeTabStyle.backgroundColor = this.props.frameProps.get('themeColor') || this.props.frameProps.get('computedThemeColor')
}

const iconStyle = {
backgroundImage: `url(${getFavicon(this.props.frameProps)})`,
backgroundSize: 16,
width: 16,
height: 16
}

let playIcon = null
if (this.props.frameProps.get('audioPlaybackActive') ||
this.props.frameProps.get('audioMuted')) {
playIcon = <span className={cx({
playIcon: true,
fa: true,
'fa-volume-up': this.props.frameProps.get('audioPlaybackActive') &&
!this.props.frameProps.get('audioMuted'),
'fa-volume-off': this.props.frameProps.get('audioMuted')
})}
onClick={this.props.frameProps.get('audioMuted') ? this.props.onUnmuteFrame : this.props.onMuteFrame} />
}

return <div className='tabArea'
style={{
width: `${this.props.tabWidth}%`
}}>
<DragIndicator active={this.props.frameProps.get('tabIsDraggingOverLeftHalf')}/>
<div className={cx({
tab: true,
active: this.props.isActive,
private: this.props.isPrivate,
draggingOn: this.props.frameProps.get('tabIsDraggingOn'),
dragging: this.props.frameProps.get('tabIsDragging'),
'dragging-over': this.props.frameProps.get('tabIsDraggingOverLeftHalf') ||
this.props.frameProps.get('tabIsDraggingOverRightHalf')
})}
ref='tab'
draggable='true'
title={this.props.frameProps.get('title')}
onDragStart={this.onDragStart.bind(this)}
onDragEnd={this.onDragEnd.bind(this)}
onDragLeave={this.onDragLeave.bind(this)}
onDragOver={this.onDragOver.bind(this)}
onDrop={this.onDrop.bind(this)}
onClick={this.setActiveFrame.bind(this)}
style={activeTabStyle}>
<div className='thumbnail'
style={thumbnailStyle} />
<span onClick={this.props.onCloseFrame}
className='closeTab fa fa-times-circle'/>
<div className='tabIcon' style={iconStyle}/>
<div className='tabTitle'>
{playIcon}
{this.displayValue}
</div>
</div>
<DragIndicator
end
active={this.props.frameProps.get('tabIsDraggingOverRightHalf')}/>
</div>
}
}

class Tabs extends ImmutableComponent {
constructor () {
super()
}

render () {
var tabWidth = 100 / this.props.frames.size

return <div className='tabs'>
<div className='tabRow'>
{
this.props.frames.map(frameProps => <Tab
activeDraggedTab={this.props.tabs.get('activeDraggedTab')}
frameProps={frameProps}
key={'tab-' + frameProps.get('key')}
isActive={this.props.activeFrame === frameProps}
isPrivate={frameProps.get('isPrivate')}
tabWidth={tabWidth} />)
}
</div>
</div>
}
}

module.exports = Tabs
11 changes: 10 additions & 1 deletion js/constants/appConstants.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
module.exports = {
APP_SET_URL: 1,
APP_SET_NAVBAR_INPUT: 2,
APP_NEW_FRAME: 3
APP_NEW_FRAME: 3,
APP_SET_ACTIVE_FRAME: 4,
APP_TAB_DRAG_START: 5,
APP_TAB_DRAG_STOP: 6,
APP_TAB_DRAGGING_OVER_LEFT: 7,
APP_TAB_DRAGGING_OVER_RIGHT: 8,
APP_TAB_DRAGGING_ON: 9,
APP_TAB_DRAG_EXIT: 10,
APP_TAB_DRAG_EXIT_RIGHT: 11,
APP_TAB_MOVE: 12
}
1 change: 1 addition & 0 deletions js/entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
require('../less/browser.less')
require('../less/main.less')
require('../less/navigationBar.less')
require('../less/tabs.less')

const React = require('react')
const ReactDOM = require('react-dom')
Expand Down
74 changes: 74 additions & 0 deletions js/lib/appUrlUtil.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
const Immutable = require('immutable')

/**
* Determines the path of a relative URL from the hosted app
*/
export function getAppUrl (relativeUrl = './') {
return new window.URL(relativeUrl, window.location).href
}

/**
* Returns the URL to the application's manifest
*/
export function getManifestUrl () {
return getAppUrl('./manifest.webapp')
}

// Map of source about: URLs mapped to target URLs
export const aboutUrls = new Immutable.Map({
'about:about': getAppUrl('./about-about.html'),
'about:blank': getAppUrl('./about-blank.html'),
'about:history': getAppUrl('./about-history.html'),
'about:newtab': getAppUrl('./about-newtab.html'),
'about:preferences': getAppUrl('./about-preferences.html'),
'about:settings': getAppUrl('./about-settings.html')
})

// Map of target URLs mapped to source about: URLs
const aboutUrlsReverse = new Immutable.Map(aboutUrls.reduce((obj, v, k) => {
obj[v] = k
return obj
}, {}))

/**
* Obtains the target URL associated with an about: source URL
* Example:
* about:blank -> http://localhost:8000/about-blank/index.html
*/
export function getTargetAboutUrl (input) {
return aboutUrls.get(input)
}

/**
* Obtains the source about: URL associated with a target URL
* Example:
* http://localhost:8000/about-blank.html -> about:blank
*/
export function getSourceAboutUrl (input) {
return aboutUrlsReverse.get(input)
}

/**
* Determines if the passed in string is a source about: URL
* Example: isSourceAboutUrl('about:blank') -> true
*/
export function isSourceAboutUrl (input) {
return !!getTargetAboutUrl(input)
}

/**
* Determines if the passed in string is the target of a source about: URL
* Example: isTargetAboutUrl('http://localhost:8000/about-blank/index.html') -> true
*/
export function isTargetAboutUrl (input) {
return !!getSourceAboutUrl(input)
}

/**
* Determines whether the passed in string is pointing to a URL that
* should be privileged (mozapp attribute on the iframe)
* For now this is the same as an about URL.
*/
export function isPrivilegedUrl (input) {
return isSourceAboutUrl(input)
}
Loading

0 comments on commit 8ed40b0

Please sign in to comment.