mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-10-06 03:14:39 +08:00
feat(ui): Updates from March 17th mockups
Summary: There are two known issues: - toolbar is not draggable in some areas when in three-pane mode. - archive button appears over very long subjects. Propose moving this button elsewhere. WIP WIP Test Plan: Run tests Reviewers: evan Reviewed By: evan Differential Revision: https://review.inboxapp.com/D1311
This commit is contained in:
parent
015bd62937
commit
aad21317e5
27 changed files with 507 additions and 159 deletions
|
@ -80,7 +80,7 @@ module.exports =
|
||||||
ComponentRegistry.register
|
ComponentRegistry.register
|
||||||
view: NewComposeButton
|
view: NewComposeButton
|
||||||
name: 'NewComposeButton'
|
name: 'NewComposeButton'
|
||||||
role: 'Global:Toolbar'
|
role: 'Root:Left:Toolbar'
|
||||||
|
|
||||||
_showInitialErrorDialog: (msg) ->
|
_showInitialErrorDialog: (msg) ->
|
||||||
remote = require('remote')
|
remote = require('remote')
|
||||||
|
|
|
@ -5,7 +5,7 @@ React = require 'react'
|
||||||
module.exports =
|
module.exports =
|
||||||
NewComposeButton = React.createClass
|
NewComposeButton = React.createClass
|
||||||
render: ->
|
render: ->
|
||||||
<button style={order: -100}
|
<button style={order: 101}
|
||||||
className="btn btn-toolbar"
|
className="btn btn-toolbar"
|
||||||
data-tooltip="Compose new message"
|
data-tooltip="Compose new message"
|
||||||
onClick={@_onNewCompose}>
|
onClick={@_onNewCompose}>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
@import "buttons";
|
@import "buttons";
|
||||||
|
|
||||||
@compose-width: 800px;
|
@compose-width: 800px;
|
||||||
@compose-min-height: 250px;
|
@compose-min-height: 150px;
|
||||||
|
|
||||||
.composer-inner-wrap {
|
.composer-inner-wrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -257,7 +257,9 @@ body.is-blurred .composer-inner-wrap .tokenizing-field .token {
|
||||||
#message-list {
|
#message-list {
|
||||||
.message-item-wrap.composer-outer-wrap {
|
.message-item-wrap.composer-outer-wrap {
|
||||||
padding-top: @spacing-standard;
|
padding-top: @spacing-standard;
|
||||||
background: @background-off-primary;
|
background-image: -webkit-gradient(linear, left top, left bottom, from(@background-off-primary), to(@background-primary));
|
||||||
|
background-repeat:no-repeat;
|
||||||
|
-webkit-background-size:100% 150px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -189,7 +189,7 @@ EmailFrame = React.createClass
|
||||||
@getDOMNode().dispatchEvent(new KeyboardEvent(event.type, event))
|
@getDOMNode().dispatchEvent(new KeyboardEvent(event.type, event))
|
||||||
|
|
||||||
_delegateMouseEvents: (doc, method="addEventListener") ->
|
_delegateMouseEvents: (doc, method="addEventListener") ->
|
||||||
for type in ["mousemove", "mouseup", "mousedown", "mouseover", "mouseout"]
|
for type in ["mouseup", "mousedown"]
|
||||||
doc?[method]?(type, @_onMouseEvent)
|
doc?[method]?(type, @_onMouseEvent)
|
||||||
|
|
||||||
_onMouseEvent: (event) ->
|
_onMouseEvent: (event) ->
|
||||||
|
|
|
@ -1,7 +1,28 @@
|
||||||
React = require "react"
|
React = require "react"
|
||||||
MessageList = require "./message-list"
|
MessageList = require "./message-list"
|
||||||
MessageToolbarItems = require "./message-toolbar-items.cjsx"
|
MessageToolbarItems = require "./message-toolbar-items"
|
||||||
|
MessageSubjectItem = require "./message-subject-item"
|
||||||
{ComponentRegistry} = require 'inbox-exports'
|
{ComponentRegistry} = require 'inbox-exports'
|
||||||
|
{RetinaImg} = require 'ui-components'
|
||||||
|
|
||||||
|
DownButton = React.createClass
|
||||||
|
render: ->
|
||||||
|
<div className="message-toolbar-arrow down" onClick={@_onClick}>
|
||||||
|
<RetinaImg name="toolbar-down-arrow.png"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
_onClick: ->
|
||||||
|
atom.commands.dispatch(document.body, 'application:next-item')
|
||||||
|
|
||||||
|
UpButton = React.createClass
|
||||||
|
render: ->
|
||||||
|
<div className="message-toolbar-arrow up" onClick={@_onClick}>
|
||||||
|
<RetinaImg name="toolbar-up-arrow.png"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
_onClick: ->
|
||||||
|
atom.commands.dispatch(document.body, 'application:previous-item')
|
||||||
|
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
item: null # The DOM item the main React component renders into
|
item: null # The DOM item the main React component renders into
|
||||||
|
@ -32,6 +53,24 @@ module.exports =
|
||||||
mode: 'list'
|
mode: 'list'
|
||||||
view: MessageToolbarItems
|
view: MessageToolbarItems
|
||||||
|
|
||||||
|
ComponentRegistry.register
|
||||||
|
name: 'MessageSubjectItem'
|
||||||
|
role: 'Thread:Center:Toolbar'
|
||||||
|
mode: 'list'
|
||||||
|
view: MessageSubjectItem
|
||||||
|
|
||||||
|
ComponentRegistry.register
|
||||||
|
name: 'DownButton'
|
||||||
|
role: 'Thread:Right:Toolbar'
|
||||||
|
mode: 'list'
|
||||||
|
view: DownButton
|
||||||
|
|
||||||
|
ComponentRegistry.register
|
||||||
|
name: 'UpButton'
|
||||||
|
role: 'Thread:Right:Toolbar'
|
||||||
|
mode: 'list'
|
||||||
|
view: UpButton
|
||||||
|
|
||||||
|
|
||||||
deactivate: ->
|
deactivate: ->
|
||||||
ComponentRegistry.unregister 'MessageToolbarItems'
|
ComponentRegistry.unregister 'MessageToolbarItems'
|
||||||
|
|
|
@ -36,6 +36,9 @@ MessageItem = React.createClass
|
||||||
componentWillUnmount: ->
|
componentWillUnmount: ->
|
||||||
@_storeUnlisten() if @_storeUnlisten
|
@_storeUnlisten() if @_storeUnlisten
|
||||||
|
|
||||||
|
shouldComponentUpdate: (nextProps, nextState) ->
|
||||||
|
not _.isEqual(nextProps, @props) or not _.isEqual(nextState, @state)
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
messageIndicators = ComponentRegistry.findAllViewsByRole('MessageIndicator')
|
messageIndicators = ComponentRegistry.findAllViewsByRole('MessageIndicator')
|
||||||
attachments = @_attachmentComponents()
|
attachments = @_attachmentComponents()
|
||||||
|
|
|
@ -23,6 +23,9 @@ MessageList = React.createClass
|
||||||
componentWillUnmount: ->
|
componentWillUnmount: ->
|
||||||
unsubscribe() for unsubscribe in @_unsubscribers
|
unsubscribe() for unsubscribe in @_unsubscribers
|
||||||
|
|
||||||
|
shouldComponentUpdate: (nextProps, nextState) ->
|
||||||
|
not _.isEqual(nextProps, @props) or not _.isEqual(nextState, @state)
|
||||||
|
|
||||||
componentDidUpdate: (prevProps, prevState) ->
|
componentDidUpdate: (prevProps, prevState) ->
|
||||||
didLoad = prevState.messages.length is 0 and @state.messages.length > 0
|
didLoad = prevState.messages.length is 0 and @state.messages.length > 0
|
||||||
|
|
||||||
|
@ -48,6 +51,7 @@ MessageList = React.createClass
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
return <div></div> if not @state.currentThread?
|
return <div></div> if not @state.currentThread?
|
||||||
|
|
||||||
wrapClass = React.addons.classSet
|
wrapClass = React.addons.classSet
|
||||||
"messages-wrap": true
|
"messages-wrap": true
|
||||||
"ready": @state.ready
|
"ready": @state.ready
|
||||||
|
@ -74,7 +78,7 @@ MessageList = React.createClass
|
||||||
lastHeight = -1
|
lastHeight = -1
|
||||||
stableCount = 0
|
stableCount = 0
|
||||||
scrollIfSettled = =>
|
scrollIfSettled = =>
|
||||||
return done() unless @isMounted()
|
return unless @isMounted()
|
||||||
|
|
||||||
messageWrapHeight = messageWrap.getBoundingClientRect().height
|
messageWrapHeight = messageWrap.getBoundingClientRect().height
|
||||||
if messageWrapHeight isnt lastHeight
|
if messageWrapHeight isnt lastHeight
|
||||||
|
@ -101,8 +105,6 @@ MessageList = React.createClass
|
||||||
MessageListHeaders = ComponentRegistry.findAllViewsByRole('MessageListHeader')
|
MessageListHeaders = ComponentRegistry.findAllViewsByRole('MessageListHeader')
|
||||||
|
|
||||||
<div className="message-list-headers">
|
<div className="message-list-headers">
|
||||||
<h2 className="message-subject">{@state.currentThread.subject}</h2>
|
|
||||||
|
|
||||||
{for MessageListHeader in MessageListHeaders
|
{for MessageListHeader in MessageListHeaders
|
||||||
<MessageListHeader thread={@state.currentThread} />
|
<MessageListHeader thread={@state.currentThread} />
|
||||||
}
|
}
|
||||||
|
@ -151,9 +153,12 @@ MessageList = React.createClass
|
||||||
ready: if MessageStore.itemsLoading() then false else @state?.ready ? false
|
ready: if MessageStore.itemsLoading() then false else @state?.ready ? false
|
||||||
|
|
||||||
_prepareContentForDisplay: ->
|
_prepareContentForDisplay: ->
|
||||||
|
_.delay =>
|
||||||
|
return unless @isMounted()
|
||||||
focusedMessage = @getDOMNode().querySelector(".initial-focus")
|
focusedMessage = @getDOMNode().querySelector(".initial-focus")
|
||||||
@scrollToMessage focusedMessage, =>
|
@scrollToMessage focusedMessage, =>
|
||||||
@setState(ready: true)
|
@setState(ready: true)
|
||||||
|
, 100
|
||||||
|
|
||||||
_threadParticipants: ->
|
_threadParticipants: ->
|
||||||
# We calculate the list of participants instead of grabbing it from
|
# We calculate the list of participants instead of grabbing it from
|
||||||
|
|
25
internal_packages/message-list/lib/message-subject-item.cjsx
Normal file
25
internal_packages/message-list/lib/message-subject-item.cjsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
React = require 'react'
|
||||||
|
{ThreadStore} = require 'inbox-exports'
|
||||||
|
|
||||||
|
module.exports =
|
||||||
|
MessageSubjectItem = React.createClass
|
||||||
|
displayName: 'MessageSubjectItem'
|
||||||
|
|
||||||
|
getInitialState: ->
|
||||||
|
@_getStateFromStores()
|
||||||
|
|
||||||
|
componentDidMount: ->
|
||||||
|
@_unsubscriber = ThreadStore.listen @_onChange
|
||||||
|
|
||||||
|
componentWillUnmount: ->
|
||||||
|
@_unsubscriber() if @_unsubscriber
|
||||||
|
|
||||||
|
render: ->
|
||||||
|
<div className="message-toolbar-subject">{@state.thread?.subject}</div>
|
||||||
|
|
||||||
|
_onChange: ->
|
||||||
|
@setState(@_getStateFromStores())
|
||||||
|
|
||||||
|
_getStateFromStores: ->
|
||||||
|
thread: ThreadStore.selectedThread()
|
||||||
|
|
|
@ -71,9 +71,6 @@ module.exports = React.createClass
|
||||||
|
|
||||||
<div className={classes}>
|
<div className={classes}>
|
||||||
<div className="message-toolbar-items-inner">
|
<div className="message-toolbar-items-inner">
|
||||||
<ReplyButton />
|
|
||||||
<ReplyAllButton />
|
|
||||||
<ForwardButton />
|
|
||||||
<ArchiveButton />
|
<ArchiveButton />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,11 +3,12 @@
|
||||||
|
|
||||||
@message-max-width: 800px;
|
@message-max-width: 800px;
|
||||||
|
|
||||||
// This class wraps the items that appear above the message list in the
|
.sheet-toolbar {
|
||||||
// toolbar. We want the toolbar items to sit right above the centered
|
// This class wraps the items that appear above the message list in the
|
||||||
// content, so we need another 800px-wide container in the toolbar...
|
// toolbar. We want the toolbar items to sit right above the centered
|
||||||
.message-toolbar-items {
|
// content, so we need another 800px-wide container in the toolbar...
|
||||||
order: -100;
|
.message-toolbar-items {
|
||||||
|
order: -10000;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -16,13 +17,42 @@
|
||||||
.message-toolbar-items-inner {
|
.message-toolbar-items-inner {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
max-width: @message-max-width - @spacing-three-quarters*2;
|
max-width: @message-max-width - @spacing-three-quarters*2;
|
||||||
text-align: left;
|
text-align: right;
|
||||||
|
pointer-events: none;
|
||||||
|
& > * {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.message-toolbar-items.hidden {
|
.message-toolbar-items.hidden {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-toolbar-subject {
|
||||||
|
order:-99;
|
||||||
|
cursor: default;
|
||||||
|
color:@text-color-heading;
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
margin:0;
|
||||||
|
margin-top:13px;
|
||||||
|
font-size: @font-size-h4;
|
||||||
|
font-weight: @font-weight-normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-toolbar-arrow.down {
|
||||||
|
order:101;
|
||||||
|
padding-top:6px;
|
||||||
|
}
|
||||||
|
.message-toolbar-arrow.up {
|
||||||
|
order:102;
|
||||||
|
padding-top:6px;
|
||||||
|
// <1 because of hit region padding on the button
|
||||||
|
margin-right: @spacing-standard * 0.75;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#message-list {
|
#message-list {
|
||||||
|
@ -36,8 +66,6 @@
|
||||||
|
|
||||||
.message-list-headers {
|
.message-list-headers {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: @spacing-double;
|
|
||||||
padding-bottom: @spacing-standard;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: @message-max-width;
|
max-width: @message-max-width;
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{ComponentRegistry} = require 'inbox-exports'
|
{ComponentRegistry} = require 'inbox-exports'
|
||||||
ModeSwitch = require './mode-switch'
|
ModeToggle = require './mode-toggle'
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
activate: (state) ->
|
activate: (state) ->
|
||||||
ComponentRegistry.register
|
ComponentRegistry.register
|
||||||
name: 'ModeSwitch'
|
name: 'ModeToggle'
|
||||||
view: ModeSwitch
|
view: ModeToggle
|
||||||
role: 'Root:Toolbar'
|
role: 'Root:Center:Toolbar'
|
||||||
|
|
38
internal_packages/mode-switch/lib/mode-toggle.cjsx
Normal file
38
internal_packages/mode-switch/lib/mode-toggle.cjsx
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
{ComponentRegistry,
|
||||||
|
WorkspaceStore,
|
||||||
|
Actions} = require "inbox-exports"
|
||||||
|
{RetinaImg} = require 'ui-components'
|
||||||
|
React = require "react"
|
||||||
|
_ = require "underscore-plus"
|
||||||
|
|
||||||
|
module.exports =
|
||||||
|
ModeToggle = React.createClass
|
||||||
|
displayName: 'ModeToggle'
|
||||||
|
|
||||||
|
getInitialState: ->
|
||||||
|
mode: WorkspaceStore.selectedLayoutMode()
|
||||||
|
|
||||||
|
componentDidMount: ->
|
||||||
|
@unsubscribe = WorkspaceStore.listen(@_onStateChanged, @)
|
||||||
|
|
||||||
|
componentWillUnmount: ->
|
||||||
|
@unsubscribe?()
|
||||||
|
|
||||||
|
render: ->
|
||||||
|
<div className="mode-switch"
|
||||||
|
style={order:51, marginTop:10, marginRight:14}
|
||||||
|
onClick={@_onToggleMode}>
|
||||||
|
<RetinaImg
|
||||||
|
name="toolbar-icon-toggle-pane.png"
|
||||||
|
onClick={@_onToggleMode} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
_onStateChanged: ->
|
||||||
|
@setState
|
||||||
|
mode: WorkspaceStore.selectedLayoutMode()
|
||||||
|
|
||||||
|
_onToggleMode: ->
|
||||||
|
if @state.mode is 'list'
|
||||||
|
Actions.selectLayoutMode('split')
|
||||||
|
else
|
||||||
|
Actions.selectLayoutMode('list')
|
|
@ -13,7 +13,7 @@ module.exports =
|
||||||
ComponentRegistry.register
|
ComponentRegistry.register
|
||||||
view: SearchBar
|
view: SearchBar
|
||||||
name: 'SearchBar'
|
name: 'SearchBar'
|
||||||
role: 'Global:Toolbar'
|
role: 'Root:Center:Toolbar'
|
||||||
|
|
||||||
deactivate: ->
|
deactivate: ->
|
||||||
ComponentRegistry.unregister 'SearchBar'
|
ComponentRegistry.unregister 'SearchBar'
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
|
|
||||||
.search-bar {
|
.search-bar {
|
||||||
position: relative;
|
position: relative;
|
||||||
order: 100;
|
order: -100;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
width:270px;
|
width:450px;
|
||||||
margin-top: (50px - 30px) / 2;
|
margin-top: (50px - 30px) / 2;
|
||||||
margin-left: 12px;
|
margin-left: 12px;
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
|
@ -19,12 +19,12 @@
|
||||||
border:none;
|
border:none;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
padding-left:25px;
|
padding-left:30px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
input.empty {
|
input.empty {
|
||||||
text-align: center;
|
text-align: left;
|
||||||
}
|
}
|
||||||
input.empty:focus {
|
input.empty:focus {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
|
@ -8,8 +8,18 @@ Spinner = React.createClass
|
||||||
style: React.PropTypes.object
|
style: React.PropTypes.object
|
||||||
|
|
||||||
getInitialState: ->
|
getInitialState: ->
|
||||||
hidden: false
|
hidden: true
|
||||||
paused: false
|
paused: true
|
||||||
|
|
||||||
|
componentDidMount: ->
|
||||||
|
# The spinner always starts hidden. After it's mounted, it unhides itself
|
||||||
|
# if it's set to visible. This is a bit strange, but ensures that the CSS
|
||||||
|
# transition from .spinner.hidden => .spinner always happens, along with
|
||||||
|
# it's associated animation delay.
|
||||||
|
if @props.visible
|
||||||
|
_.defer =>
|
||||||
|
return unless @isMounted()
|
||||||
|
@setState({paused: false, hidden: false})
|
||||||
|
|
||||||
componentWillReceiveProps: (nextProps) ->
|
componentWillReceiveProps: (nextProps) ->
|
||||||
hidden = if nextProps.visible? then !nextProps.visible else false
|
hidden = if nextProps.visible? then !nextProps.visible else false
|
||||||
|
|
162
src/components/timeout-transition-group.cjsx
Normal file
162
src/components/timeout-transition-group.cjsx
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
# WHY IS THIS FILE HERE? ReactCSSTransitionGroup is causing
|
||||||
|
# inconsitency exceptions when you hammer on the animations and don't let them
|
||||||
|
# finish. This is from http://khan.github.io/react-components/#timeout-transition-group
|
||||||
|
# and uses timeouts to clean up elements rather than listeners on CSS events, which
|
||||||
|
# don't always seem to fire.
|
||||||
|
|
||||||
|
# https://github.com/facebook/react/issues/1707
|
||||||
|
|
||||||
|
###*
|
||||||
|
# The CSSTransitionGroup component uses the 'transitionend' event, which
|
||||||
|
# browsers will not send for any number of reasons, including the
|
||||||
|
# transitioning node not being painted or in an unfocused tab.
|
||||||
|
#
|
||||||
|
# This TimeoutTransitionGroup instead uses a user-defined timeout to determine
|
||||||
|
# when it is a good time to remove the component. Currently there is only one
|
||||||
|
# timeout specified, but in the future it would be nice to be able to specify
|
||||||
|
# separate timeouts for enter and leave, in case the timeouts for those
|
||||||
|
# animations differ. Even nicer would be some sort of inspection of the CSS to
|
||||||
|
# automatically determine the duration of the animation or transition.
|
||||||
|
#
|
||||||
|
# This is adapted from Facebook's CSSTransitionGroup which is in the React
|
||||||
|
# addons and under the Apache 2.0 License.
|
||||||
|
###
|
||||||
|
|
||||||
|
React = require('react/addons')
|
||||||
|
ReactTransitionGroup = React.addons.TransitionGroup
|
||||||
|
TICK = 17
|
||||||
|
|
||||||
|
endEvents = ['webkitTransitionEnd', 'webkitAnimationEnd']
|
||||||
|
|
||||||
|
animationSupported = ->
|
||||||
|
endEvents.length != 0
|
||||||
|
|
||||||
|
###*
|
||||||
|
# Functions for element class management to replace dependency on jQuery
|
||||||
|
# addClass, removeClass and hasClass
|
||||||
|
###
|
||||||
|
|
||||||
|
addClass = (element, className) ->
|
||||||
|
if element.classList
|
||||||
|
element.classList.add className
|
||||||
|
else if !hasClass(element, className)
|
||||||
|
element.className = element.className + ' ' + className
|
||||||
|
element
|
||||||
|
|
||||||
|
removeClass = (element, className) ->
|
||||||
|
if hasClass(className)
|
||||||
|
if element.classList
|
||||||
|
element.classList.remove className
|
||||||
|
else
|
||||||
|
element.className = (' ' + element.className + ' ').replace(' ' + className + ' ', ' ').trim()
|
||||||
|
element
|
||||||
|
|
||||||
|
hasClass = (element, className) ->
|
||||||
|
if element.classList
|
||||||
|
element.classList.contains className
|
||||||
|
else
|
||||||
|
(' ' + element.className + ' ').indexOf(' ' + className + ' ') > -1
|
||||||
|
|
||||||
|
TimeoutTransitionGroupChild = React.createClass(
|
||||||
|
transition: (animationType, finishCallback) ->
|
||||||
|
node = @getDOMNode()
|
||||||
|
className = @props.name + '-' + animationType
|
||||||
|
activeClassName = className + '-active'
|
||||||
|
|
||||||
|
endListener = ->
|
||||||
|
removeClass node, className
|
||||||
|
removeClass node, activeClassName
|
||||||
|
# Usually this optional callback is used for informing an owner of
|
||||||
|
# a leave animation and telling it to remove the child.
|
||||||
|
finishCallback and finishCallback()
|
||||||
|
return
|
||||||
|
|
||||||
|
if !animationSupported()
|
||||||
|
endListener()
|
||||||
|
else
|
||||||
|
if animationType == 'enter'
|
||||||
|
@animationTimeout = setTimeout(endListener, @props.enterTimeout)
|
||||||
|
else if animationType == 'leave'
|
||||||
|
@animationTimeout = setTimeout(endListener, @props.leaveTimeout)
|
||||||
|
addClass node, className
|
||||||
|
# Need to do this to actually trigger a transition.
|
||||||
|
@queueClass activeClassName
|
||||||
|
return
|
||||||
|
|
||||||
|
queueClass: (className) ->
|
||||||
|
@classNameQueue.push className
|
||||||
|
if !@timeout
|
||||||
|
@timeout = setTimeout(@flushClassNameQueue, TICK)
|
||||||
|
return
|
||||||
|
|
||||||
|
flushClassNameQueue: ->
|
||||||
|
if @isMounted()
|
||||||
|
@classNameQueue.forEach ((name) ->
|
||||||
|
addClass @getDOMNode(), name
|
||||||
|
return
|
||||||
|
).bind(this)
|
||||||
|
@classNameQueue.length = 0
|
||||||
|
@timeout = null
|
||||||
|
return
|
||||||
|
|
||||||
|
componentWillMount: ->
|
||||||
|
@classNameQueue = []
|
||||||
|
return
|
||||||
|
|
||||||
|
componentWillUnmount: ->
|
||||||
|
if @timeout
|
||||||
|
clearTimeout @timeout
|
||||||
|
if @animationTimeout
|
||||||
|
clearTimeout @animationTimeout
|
||||||
|
return
|
||||||
|
|
||||||
|
componentWillEnter: (done) ->
|
||||||
|
if @props.enter
|
||||||
|
@transition 'enter', done
|
||||||
|
else
|
||||||
|
done()
|
||||||
|
return
|
||||||
|
|
||||||
|
componentWillLeave: (done) ->
|
||||||
|
if @props.leave
|
||||||
|
@transition 'leave', done
|
||||||
|
else
|
||||||
|
done()
|
||||||
|
return
|
||||||
|
|
||||||
|
render: ->
|
||||||
|
React.Children.only @props.children
|
||||||
|
)
|
||||||
|
|
||||||
|
TimeoutTransitionGroup = React.createClass(
|
||||||
|
propTypes:
|
||||||
|
enterTimeout: React.PropTypes.number.isRequired
|
||||||
|
leaveTimeout: React.PropTypes.number.isRequired
|
||||||
|
transitionName: React.PropTypes.string.isRequired
|
||||||
|
transitionEnter: React.PropTypes.bool
|
||||||
|
transitionLeave: React.PropTypes.bool
|
||||||
|
|
||||||
|
getDefaultProps: ->
|
||||||
|
transitionEnter: true
|
||||||
|
transitionLeave: true
|
||||||
|
|
||||||
|
_wrapChild: (child) ->
|
||||||
|
<TimeoutTransitionGroupChild
|
||||||
|
enterTimeout={@props.enterTimeout}
|
||||||
|
leaveTimeout={@props.leaveTimeout}
|
||||||
|
name={@props.transitionName}
|
||||||
|
enter={@props.transitionEnter}
|
||||||
|
leave={@props.transitionLeave}>
|
||||||
|
{child}
|
||||||
|
</TimeoutTransitionGroupChild>
|
||||||
|
|
||||||
|
render: ->
|
||||||
|
<ReactTransitionGroup
|
||||||
|
{...@props}
|
||||||
|
childFactory={@_wrapChild} />
|
||||||
|
)
|
||||||
|
|
||||||
|
module.exports = TimeoutTransitionGroup
|
||||||
|
|
||||||
|
# ---
|
||||||
|
# generated by js2coffee 2.0.1
|
|
@ -147,6 +147,10 @@ class AttributeCollection extends Attribute
|
||||||
objs = []
|
objs = []
|
||||||
for objJSON in json
|
for objJSON in json
|
||||||
obj = new @itemClass(objJSON)
|
obj = new @itemClass(objJSON)
|
||||||
|
# Important: if no ids are in the JSON, don't make them up randomly.
|
||||||
|
# This causes an object to be "different" each time it's de-serialized
|
||||||
|
# even if it's actually the same, makes React components re-render!
|
||||||
|
obj.id = undefined
|
||||||
obj.fromJSON(objJSON) if obj.fromJSON?
|
obj.fromJSON(objJSON) if obj.fromJSON?
|
||||||
objs.push(obj)
|
objs.push(obj)
|
||||||
objs
|
objs
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
React = require 'react/addons'
|
React = require 'react/addons'
|
||||||
Sheet = require './sheet'
|
Sheet = require './sheet'
|
||||||
TitleBar = require './titlebar'
|
|
||||||
Flexbox = require './components/flexbox.cjsx'
|
Flexbox = require './components/flexbox.cjsx'
|
||||||
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup
|
RetinaImg = require './components/retina-img'
|
||||||
|
TimeoutTransitionGroup = require './components/timeout-transition-group'
|
||||||
|
_ = require 'underscore-plus'
|
||||||
|
|
||||||
{Actions,
|
{Actions,
|
||||||
ComponentRegistry,
|
ComponentRegistry,
|
||||||
|
@ -12,15 +13,39 @@ ToolbarSpacer = React.createClass
|
||||||
className: 'ToolbarSpacer'
|
className: 'ToolbarSpacer'
|
||||||
propTypes:
|
propTypes:
|
||||||
order: React.PropTypes.number
|
order: React.PropTypes.number
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
<div className="item-spacer" style={flex: 1, order:@props.order ? 0}></div>
|
<div className="item-spacer" style={flex: 1, order:@props.order ? 0}></div>
|
||||||
|
|
||||||
|
ToolbarBack = React.createClass
|
||||||
|
className: 'ToolbarBack'
|
||||||
|
render: ->
|
||||||
|
<div className="item-back" onClick={@_onClick}>
|
||||||
|
<RetinaImg name="sheet-back.png" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
_onClick: ->
|
||||||
|
Actions.popSheet()
|
||||||
|
|
||||||
|
ToolbarWindowControls = React.createClass
|
||||||
|
displayName: 'ToolbarWindowControls'
|
||||||
|
render: ->
|
||||||
|
<div name="ToolbarWindowControls" className="toolbar-window-controls">
|
||||||
|
<button className="close" onClick={ -> atom.close()}></button>
|
||||||
|
<button className="minimize" onClick={ -> atom.minimize()}></button>
|
||||||
|
<button className="maximize" onClick={ -> atom.maximize()}></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
ComponentRegistry.register
|
||||||
|
view: ToolbarWindowControls
|
||||||
|
name: 'ToolbarWindowControls'
|
||||||
|
role: 'Global:Left:Toolbar'
|
||||||
|
|
||||||
Toolbar = React.createClass
|
Toolbar = React.createClass
|
||||||
className: 'Toolbar'
|
className: 'Toolbar'
|
||||||
|
|
||||||
propTypes:
|
propTypes:
|
||||||
type: React.PropTypes.string
|
type: React.PropTypes.string
|
||||||
|
depth: React.PropTypes.number
|
||||||
|
|
||||||
getInitialState: ->
|
getInitialState: ->
|
||||||
@_getStateFromStores()
|
@_getStateFromStores()
|
||||||
|
@ -31,54 +56,60 @@ Toolbar = React.createClass
|
||||||
@setState(@_getStateFromStores())
|
@setState(@_getStateFromStores())
|
||||||
@unlisteners.push ComponentRegistry.listen (event) =>
|
@unlisteners.push ComponentRegistry.listen (event) =>
|
||||||
@setState(@_getStateFromStores())
|
@setState(@_getStateFromStores())
|
||||||
window.addEventListener "resize", (event) =>
|
window.addEventListener("resize", @_onWindowResize)
|
||||||
@recomputeLayout()
|
window.requestAnimationFrame => @recomputeLayout()
|
||||||
|
|
||||||
componentWillUnmount: ->
|
componentWillUnmount: ->
|
||||||
@unlistener() if @unlistener
|
window.removeEventListener("resize", @_onWindowResize)
|
||||||
|
unlistener() for unlistener in @unlisteners
|
||||||
|
|
||||||
componentWillReceiveProps: (props) ->
|
componentWillReceiveProps: (props) ->
|
||||||
@setState(@_getStateFromStores(props))
|
@replaceState(@_getStateFromStores(props))
|
||||||
|
|
||||||
componentDidUpdate: ->
|
componentDidUpdate: ->
|
||||||
# Wait for other components that are dirty (the actual columns in the sheet)
|
# Wait for other components that are dirty (the actual columns in the sheet)
|
||||||
# to update as well.
|
# to update as well.
|
||||||
setTimeout(( => @recomputeLayout()), 1)
|
window.requestAnimationFrame => @recomputeLayout()
|
||||||
|
|
||||||
|
shouldComponentUpdate: (nextProps, nextState) ->
|
||||||
|
# This is very important. Because toolbar uses ReactCSSTransitionGroup,
|
||||||
|
# repetitive unnecessary updates can break animations and cause performance issues.
|
||||||
|
not _.isEqual(nextProps, @props) or not _.isEqual(nextState, @state)
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
# The main toolbar contains items with roles <sheet type>:Toolbar
|
style =
|
||||||
# and Global:Toolbar
|
position:'absolute'
|
||||||
mainToolbar = @_flexboxForItems(@state.items)
|
backgroundColor:'white'
|
||||||
|
width:'100%'
|
||||||
|
height:'100%'
|
||||||
|
zIndex: 1
|
||||||
|
|
||||||
# Column toolbars contain items with roles attaching them to items
|
toolbars = @state.itemsForColumns.map ({column, items}) =>
|
||||||
# in the sheet. Ex: MessageList:Toolbar items appear in the column
|
|
||||||
# toolbar for the column containing <MessageList/>.
|
|
||||||
columnToolbars = @state.itemsForColumns.map ({column, name, items}) =>
|
|
||||||
<div style={position: 'absolute', top:0, display:'none'}
|
<div style={position: 'absolute', top:0, display:'none'}
|
||||||
data-owner-name={name}
|
|
||||||
data-column={column}
|
data-column={column}
|
||||||
key={column}>
|
key={column}>
|
||||||
{@_flexboxForItems(items)}
|
{@_flexboxForItems(items)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ReactCSSTransitionGroup transitionName="sheet-toolbar">
|
<div style={style}>
|
||||||
{mainToolbar}
|
{toolbars}
|
||||||
{columnToolbars}
|
</div>
|
||||||
</ReactCSSTransitionGroup>
|
|
||||||
|
|
||||||
_flexboxForItems: (items) ->
|
_flexboxForItems: (items) ->
|
||||||
components = items.map ({view, name}) =>
|
components = items.map ({view, name}) =>
|
||||||
<view key={name} {...@props} />
|
<view key={name} {...@props} />
|
||||||
|
|
||||||
<ReactCSSTransitionGroup
|
<TimeoutTransitionGroup
|
||||||
className="item-container"
|
className="item-container"
|
||||||
component={Flexbox}
|
component={Flexbox}
|
||||||
direction="row"
|
direction="row"
|
||||||
|
leaveTimeout={200}
|
||||||
|
enterTimeout={200}
|
||||||
transitionName="sheet-toolbar">
|
transitionName="sheet-toolbar">
|
||||||
{components}
|
{components}
|
||||||
<ToolbarSpacer key="spacer-50" order={-50}/>
|
<ToolbarSpacer key="spacer-50" order={-50}/>
|
||||||
<ToolbarSpacer key="spacer+50" order={50}/>
|
<ToolbarSpacer key="spacer+50" order={50}/>
|
||||||
</ReactCSSTransitionGroup>
|
</TimeoutTransitionGroup>
|
||||||
|
|
||||||
recomputeLayout: ->
|
recomputeLayout: ->
|
||||||
return unless @isMounted()
|
return unless @isMounted()
|
||||||
|
@ -87,7 +118,8 @@ Toolbar = React.createClass
|
||||||
columnToolbarEls = @getDOMNode().querySelectorAll('[data-column]')
|
columnToolbarEls = @getDOMNode().querySelectorAll('[data-column]')
|
||||||
|
|
||||||
# Find the top sheet in the stack
|
# Find the top sheet in the stack
|
||||||
sheet = document.querySelector("[name='Sheet']:last-child")
|
sheet = document.querySelector("[name='Sheet']:nth-child(#{@props.depth+1})")
|
||||||
|
return unless sheet
|
||||||
|
|
||||||
# Position item containers so they have the position and width
|
# Position item containers so they have the position and width
|
||||||
# as their respective columns in the top sheet
|
# as their respective columns in the top sheet
|
||||||
|
@ -100,32 +132,42 @@ Toolbar = React.createClass
|
||||||
columnToolbarEl.style.left = "#{columnEl.offsetLeft}px"
|
columnToolbarEl.style.left = "#{columnEl.offsetLeft}px"
|
||||||
columnToolbarEl.style.width = "#{columnEl.offsetWidth}px"
|
columnToolbarEl.style.width = "#{columnEl.offsetWidth}px"
|
||||||
|
|
||||||
|
_onWindowResize: ->
|
||||||
|
@recomputeLayout()
|
||||||
|
|
||||||
_getStateFromStores: (props) ->
|
_getStateFromStores: (props) ->
|
||||||
props ?= @props
|
props ?= @props
|
||||||
state =
|
state =
|
||||||
mode: WorkspaceStore.selectedLayoutMode()
|
mode: WorkspaceStore.selectedLayoutMode()
|
||||||
items: []
|
|
||||||
itemsForColumns: []
|
itemsForColumns: []
|
||||||
|
|
||||||
for role in ["Global:Toolbar", "#{props.type}:Toolbar"]
|
items = {}
|
||||||
for entry in ComponentRegistry.findAllByRole(role)
|
|
||||||
continue if entry.mode? and entry.mode != state.mode
|
|
||||||
state.items.push(entry)
|
|
||||||
|
|
||||||
for column in ["Left", "Center", "Right"]
|
for column in ["Left", "Center", "Right"]
|
||||||
role = "#{props.type}:#{column}:Toolbar"
|
items[column] = []
|
||||||
items = []
|
for role in ["Global:#{column}:Toolbar", "#{props.type}:#{column}:Toolbar"]
|
||||||
for entry in ComponentRegistry.findAllByRole(role)
|
for entry in ComponentRegistry.findAllByRole(role)
|
||||||
continue if entry.mode? and entry.mode != state.mode
|
continue if entry.mode? and entry.mode != state.mode
|
||||||
items.push(entry)
|
items[column].push(entry)
|
||||||
if items.length > 0
|
|
||||||
state.itemsForColumns.push({column, name, items})
|
|
||||||
|
|
||||||
|
if @props.depth > 0
|
||||||
|
items['Left'].push(view: ToolbarBack, name: 'ToolbarBack')
|
||||||
|
|
||||||
|
# If the left or right column does not contain any components, it won't
|
||||||
|
# be in the sheet. Go ahead and shift those toolbar items into the center
|
||||||
|
# region.
|
||||||
|
for column in ["Left", "Right"]
|
||||||
|
if ComponentRegistry.findAllByRole("#{props.type}:#{column}").length is 0
|
||||||
|
items['Center'].push(items[column]...)
|
||||||
|
delete items[column]
|
||||||
|
|
||||||
|
for key, val of items
|
||||||
|
state.itemsForColumns.push({column: key, items: val}) if val.length > 0
|
||||||
state
|
state
|
||||||
|
|
||||||
|
|
||||||
FlexboxForRoles = React.createClass
|
FlexboxForRoles = React.createClass
|
||||||
className: 'FlexboxForRoles'
|
className: 'FlexboxForRoles'
|
||||||
|
|
||||||
propTypes:
|
propTypes:
|
||||||
roles: React.PropTypes.arrayOf(React.PropTypes.string)
|
roles: React.PropTypes.arrayOf(React.PropTypes.string)
|
||||||
|
|
||||||
|
@ -139,9 +181,17 @@ FlexboxForRoles = React.createClass
|
||||||
componentWillUnmount: ->
|
componentWillUnmount: ->
|
||||||
@unlistener() if @unlistener
|
@unlistener() if @unlistener
|
||||||
|
|
||||||
|
shouldComponentUpdate: (nextProps, nextState) ->
|
||||||
|
# Note: we actually ignore props.roles. If roles change, but we get
|
||||||
|
# the same items, we don't need to re-render. Our render function is
|
||||||
|
# a function of state only.
|
||||||
|
nextItemNames = nextState.items.map (i) -> i.name
|
||||||
|
itemNames = @state.items?.map (i) -> i.name
|
||||||
|
!_.isEqual(nextItemNames, itemNames)
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
components = @state.items.map ({view, name}) =>
|
components = @state.items.map ({view, name}) =>
|
||||||
<view key={name} {...@props} />
|
<view key={name} />
|
||||||
|
|
||||||
<Flexbox direction="row">
|
<Flexbox direction="row">
|
||||||
{components}
|
{components}
|
||||||
|
@ -171,26 +221,43 @@ SheetContainer = React.createClass
|
||||||
|
|
||||||
render: ->
|
render: ->
|
||||||
topSheetType = @state.stack[@state.stack.length - 1]
|
topSheetType = @state.stack[@state.stack.length - 1]
|
||||||
|
|
||||||
<Flexbox direction="column">
|
<Flexbox direction="column">
|
||||||
<TitleBar />
|
<TimeoutTransitionGroup name="Toolbar"
|
||||||
<div name="Toolbar" style={order:0} className="sheet-toolbar">
|
style={order:0}
|
||||||
<Toolbar ref="toolbar" type={topSheetType}/>
|
leaveTimeout={200}
|
||||||
</div>
|
enterTimeout={200}
|
||||||
|
className="sheet-toolbar"
|
||||||
|
transitionName="sheet-toolbar">
|
||||||
|
{@_toolbarComponents()}
|
||||||
|
</TimeoutTransitionGroup>
|
||||||
|
|
||||||
<div name="Top" style={order:1}>
|
<div name="Top" style={order:1}>
|
||||||
<FlexboxForRoles roles={["Global:Top", "#{topSheetType}:Top"]}
|
<FlexboxForRoles roles={["Global:Top", "#{topSheetType}:Top"]}
|
||||||
type={topSheetType}/>
|
type={topSheetType}/>
|
||||||
</div>
|
</div>
|
||||||
<div name="Center" style={order:2, flex: 1, position:'relative'}>
|
|
||||||
<ReactCSSTransitionGroup transitionName="sheet-stack">
|
<TimeoutTransitionGroup name="Center"
|
||||||
|
style={order:2, flex: 1, position:'relative'}
|
||||||
|
leaveTimeout={150}
|
||||||
|
enterTimeout={150}
|
||||||
|
transitionName="sheet-stack">
|
||||||
{@_sheetComponents()}
|
{@_sheetComponents()}
|
||||||
</ReactCSSTransitionGroup>
|
</TimeoutTransitionGroup>
|
||||||
</div>
|
|
||||||
<div name="Footer" style={order:3}>
|
<div name="Footer" style={order:3}>
|
||||||
<FlexboxForRoles roles={["Global:Footer", "#{topSheetType}:Footer"]}
|
<FlexboxForRoles roles={["Global:Footer", "#{topSheetType}:Footer"]}
|
||||||
type={topSheetType}/>
|
type={topSheetType}/>
|
||||||
</div>
|
</div>
|
||||||
</Flexbox>
|
</Flexbox>
|
||||||
|
|
||||||
|
_toolbarComponents: ->
|
||||||
|
@state.stack.map (type, index) ->
|
||||||
|
<Toolbar type={type}
|
||||||
|
ref={"toolbar-#{index}"}
|
||||||
|
depth={index}
|
||||||
|
key={index} />
|
||||||
|
|
||||||
_sheetComponents: ->
|
_sheetComponents: ->
|
||||||
@state.stack.map (type, index) =>
|
@state.stack.map (type, index) =>
|
||||||
<Sheet type={type}
|
<Sheet type={type}
|
||||||
|
@ -198,11 +265,11 @@ SheetContainer = React.createClass
|
||||||
key={index}
|
key={index}
|
||||||
onColumnSizeChanged={@_onColumnSizeChanged} />
|
onColumnSizeChanged={@_onColumnSizeChanged} />
|
||||||
|
|
||||||
_onColumnSizeChanged: ->
|
_onColumnSizeChanged: (sheet) ->
|
||||||
@refs.toolbar.recomputeLayout()
|
@refs["toolbar-#{sheet.props.depth}"]?.recomputeLayout()
|
||||||
|
|
||||||
_onStoreChange: ->
|
_onStoreChange: ->
|
||||||
@setState @_getStateFromStores()
|
_.defer => @setState(@_getStateFromStores())
|
||||||
|
|
||||||
_getStateFromStores: ->
|
_getStateFromStores: ->
|
||||||
stack: WorkspaceStore.sheetStack()
|
stack: WorkspaceStore.sheetStack()
|
||||||
|
|
|
@ -28,6 +28,12 @@ Sheet = React.createClass
|
||||||
@unlisteners.push WorkspaceStore.listen (event) =>
|
@unlisteners.push WorkspaceStore.listen (event) =>
|
||||||
@setState(@_getStateFromStores())
|
@setState(@_getStateFromStores())
|
||||||
|
|
||||||
|
componentDidUpdate: ->
|
||||||
|
@props.onColumnSizeChanged(@) if @props.onColumnSizeChanged
|
||||||
|
|
||||||
|
shouldComponentUpdate: (nextProps, nextState) ->
|
||||||
|
not _.isEqual(nextProps, @props) or not _.isEqual(nextState, @state)
|
||||||
|
|
||||||
componentWillUnmount: ->
|
componentWillUnmount: ->
|
||||||
unlisten() for unlisten in @unlisteners
|
unlisten() for unlisten in @unlisteners
|
||||||
|
|
||||||
|
@ -49,24 +55,16 @@ Sheet = React.createClass
|
||||||
style={style}
|
style={style}
|
||||||
data-type={@props.type}>
|
data-type={@props.type}>
|
||||||
<Flexbox direction="row">
|
<Flexbox direction="row">
|
||||||
{@_backButtonComponent()}
|
|
||||||
{@_columnFlexboxComponents()}
|
{@_columnFlexboxComponents()}
|
||||||
</Flexbox>
|
</Flexbox>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
_backButtonComponent: ->
|
|
||||||
return [] if @props.depth is 0
|
|
||||||
<div className="sheet-edge" onClick={@_pop} key="back">
|
|
||||||
<div className="gradient"></div>
|
|
||||||
<div className="x"><RetinaImg name="sheet-back.png"/></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
_columnFlexboxComponents: ->
|
_columnFlexboxComponents: ->
|
||||||
@props.columns.map (column) =>
|
@props.columns.map (column) =>
|
||||||
classes = @state[column] || []
|
classes = @state[column] || []
|
||||||
return if classes.length is 0
|
return if classes.length is 0
|
||||||
|
|
||||||
components = classes.map ({name, view}) => <view key={name} {...@props} />
|
components = classes.map ({name, view}) -> <view key={name} />
|
||||||
|
|
||||||
maxWidth = _.reduce classes, ((m,{view}) -> Math.min(view.maxWidth ? 10000, m)), 10000
|
maxWidth = _.reduce classes, ((m,{view}) -> Math.min(view.maxWidth ? 10000, m)), 10000
|
||||||
minWidth = _.reduce classes, ((m,{view}) -> Math.max(view.minWidth ? 0, m)), 0
|
minWidth = _.reduce classes, ((m,{view}) -> Math.max(view.minWidth ? 0, m)), 0
|
||||||
|
@ -78,7 +76,7 @@ Sheet = React.createClass
|
||||||
<ResizableRegion key={"#{@props.type}:#{column}"}
|
<ResizableRegion key={"#{@props.type}:#{column}"}
|
||||||
name={"#{@props.type}:#{column}"}
|
name={"#{@props.type}:#{column}"}
|
||||||
data-column={column}
|
data-column={column}
|
||||||
onResize={@props.onColumnSizeChanged}
|
onResize={ => @props.onColumnSizeChanged(@) }
|
||||||
minWidth={minWidth}
|
minWidth={minWidth}
|
||||||
maxWidth={maxWidth}
|
maxWidth={maxWidth}
|
||||||
handle={handle}>
|
handle={handle}>
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
React = require 'react'
|
|
||||||
|
|
||||||
module.exports =
|
|
||||||
TitleBar = React.createClass
|
|
||||||
displayName: 'TitleBar'
|
|
||||||
|
|
||||||
render: ->
|
|
||||||
<div name="TitleBar" className="sheet-title-bar">
|
|
||||||
{atom.getCurrentWindow().getTitle()}
|
|
||||||
<button className="close" onClick={ -> atom.close()}></button>
|
|
||||||
<button className="minimize" onClick={ -> atom.minimize()}></button>
|
|
||||||
<button className="maximize" onClick={ -> atom.maximize()}></button>
|
|
||||||
</div>
|
|
|
@ -7,6 +7,7 @@
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
-webkit-transition: opacity 0.2s linear 0.3s; //transition in
|
-webkit-transition: opacity 0.2s linear 0.3s; //transition in
|
||||||
}
|
}
|
||||||
|
|
||||||
.spinner.hidden {
|
.spinner.hidden {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
-webkit-transition: opacity 0.2s linear; //transition out
|
-webkit-transition: opacity 0.2s linear; //transition out
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: darken(@background-secondary, 5%);
|
background-color: darken(@background-secondary, 5%);
|
||||||
cursor: -webkit-grab;
|
cursor: default;
|
||||||
}
|
}
|
||||||
&.selected,
|
&.selected,
|
||||||
&.dragging {
|
&.dragging {
|
||||||
|
|
BIN
static/images/message-list/toolbar-down-arrow@2x.png
Normal file
BIN
static/images/message-list/toolbar-down-arrow@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 265 KiB |
BIN
static/images/message-list/toolbar-up-arrow@2x.png
Normal file
BIN
static/images/message-list/toolbar-up-arrow@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 264 KiB |
Binary file not shown.
Before Width: | Height: | Size: 230 KiB After Width: | Height: | Size: 263 KiB |
BIN
static/images/splitpane/toolbar-icon-toggle-pane@2x.png
Normal file
BIN
static/images/splitpane/toolbar-icon-toggle-pane@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 230 KiB |
|
@ -36,33 +36,33 @@ atom-workspace {
|
||||||
}
|
}
|
||||||
|
|
||||||
.sheet-stack-enter {
|
.sheet-stack-enter {
|
||||||
left:100%;
|
left:7%;
|
||||||
transition: left .20s ease-out;
|
opacity: 0;
|
||||||
|
transition: all .15s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sheet-stack-enter.sheet-stack-enter-active {
|
.sheet-stack-enter.sheet-stack-enter-active {
|
||||||
left:0;
|
left:0;
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sheet-stack-leave {
|
.sheet-stack-leave {
|
||||||
left:0;
|
left:0;
|
||||||
transition: left .20s ease-in;
|
opacity: 1;
|
||||||
|
transition: all .15s ease-in;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sheet-stack-leave.sheet-stack-leave-active {
|
.sheet-stack-leave.sheet-stack-leave-active {
|
||||||
left:100%;
|
left:7%;
|
||||||
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sheet-title-bar {
|
.toolbar-window-controls {
|
||||||
height:24px;
|
padding-top:14px;
|
||||||
line-height: 24px;
|
|
||||||
cursor: default;
|
|
||||||
background: @toolbar-background-color;
|
|
||||||
-webkit-app-region: drag;
|
|
||||||
padding: 3px;
|
|
||||||
padding-left:@spacing-half;
|
padding-left:@spacing-half;
|
||||||
padding-right:60px;
|
order: -1000;
|
||||||
text-align: center;
|
min-width: 102px;
|
||||||
|
width: 102px;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
|
@ -105,13 +105,13 @@ atom-workspace {
|
||||||
}
|
}
|
||||||
|
|
||||||
body.platform-win32, body.platform-linux {
|
body.platform-win32, body.platform-linux {
|
||||||
.sheet-title-bar {
|
.toolbar-window-controls {
|
||||||
display:none;
|
display:none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body.is-blurred {
|
body.is-blurred {
|
||||||
.sheet-title-bar {
|
.toolbar-window-controls {
|
||||||
button {
|
button {
|
||||||
background-color: desaturate(fade(#FCB40A, 20%), 100%);
|
background-color: desaturate(fade(#FCB40A, 20%), 100%);
|
||||||
}
|
}
|
||||||
|
@ -121,6 +121,7 @@ body.is-blurred {
|
||||||
.sheet-toolbar {
|
.sheet-toolbar {
|
||||||
position: relative;
|
position: relative;
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
|
-webkit-user-select:none;
|
||||||
background: @toolbar-background-color;
|
background: @toolbar-background-color;
|
||||||
border-bottom: 1px solid @border-color-divider;
|
border-bottom: 1px solid @border-color-divider;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -141,6 +142,11 @@ body.is-blurred {
|
||||||
.item-spacer {
|
.item-spacer {
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
}
|
}
|
||||||
|
.item-back {
|
||||||
|
order:-999;
|
||||||
|
padding-top: 5px;
|
||||||
|
padding-left: @spacing-three-quarters;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-toolbar {
|
.btn-toolbar {
|
||||||
margin-top: @spacing-half;
|
margin-top: @spacing-half;
|
||||||
|
@ -167,30 +173,6 @@ body.is-blurred {
|
||||||
opacity:0;
|
opacity:0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sheet-edge {
|
|
||||||
height:100%;
|
|
||||||
z-index: @zindex-popover;
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
.x {
|
|
||||||
position: absolute;
|
|
||||||
top: @spacing-standard * 1.5;
|
|
||||||
left: @spacing-standard * 1.5;
|
|
||||||
}
|
|
||||||
.gradient {
|
|
||||||
width:9px;
|
|
||||||
height:100%;
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
background-image: -webkit-gradient(linear, left center, right center, from(rgb(244, 244, 244)), to(rgb(209, 209, 209)));
|
|
||||||
background-image: -webkit-linear-gradient(left, rgb(244, 244, 244), rgb(209, 209, 209));
|
|
||||||
background-image: -moz-linear-gradient(left, rgb(244, 244, 244), rgb(209, 209, 209));
|
|
||||||
background-image: -o-linear-gradient(left, rgb(244, 244, 244), rgb(209, 209, 209));
|
|
||||||
background-image: -ms-linear-gradient(left, rgb(244, 244, 244), rgb(209, 209, 209));
|
|
||||||
background-image: linear-gradient(left, rgb(244, 244, 244), rgb(209, 209, 209));
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=1,StartColorStr='#f4f4f4', EndColorStr='#d1d1d1');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.flexbox-handle-horizontal {
|
.flexbox-handle-horizontal {
|
||||||
width: 6px;
|
width: 6px;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
Loading…
Add table
Reference in a new issue