diff --git a/internal_packages/composer/lib/main.cjsx b/internal_packages/composer/lib/main.cjsx index 5e1c26615..f9905ce51 100644 --- a/internal_packages/composer/lib/main.cjsx +++ b/internal_packages/composer/lib/main.cjsx @@ -80,7 +80,7 @@ module.exports = ComponentRegistry.register view: NewComposeButton name: 'NewComposeButton' - role: 'Global:Toolbar' + role: 'Root:Left:Toolbar' _showInitialErrorDialog: (msg) -> remote = require('remote') diff --git a/internal_packages/composer/lib/new-compose-button.cjsx b/internal_packages/composer/lib/new-compose-button.cjsx index a6f74f210..3987e0537 100644 --- a/internal_packages/composer/lib/new-compose-button.cjsx +++ b/internal_packages/composer/lib/new-compose-button.cjsx @@ -5,7 +5,7 @@ React = require 'react' module.exports = NewComposeButton = React.createClass render: -> - + + + + +ComponentRegistry.register + view: ToolbarWindowControls + name: 'ToolbarWindowControls' + role: 'Global:Left:Toolbar' Toolbar = React.createClass className: 'Toolbar' + propTypes: type: React.PropTypes.string + depth: React.PropTypes.number getInitialState: -> @_getStateFromStores() @@ -31,54 +56,60 @@ Toolbar = React.createClass @setState(@_getStateFromStores()) @unlisteners.push ComponentRegistry.listen (event) => @setState(@_getStateFromStores()) - window.addEventListener "resize", (event) => - @recomputeLayout() + window.addEventListener("resize", @_onWindowResize) + window.requestAnimationFrame => @recomputeLayout() componentWillUnmount: -> - @unlistener() if @unlistener + window.removeEventListener("resize", @_onWindowResize) + unlistener() for unlistener in @unlisteners componentWillReceiveProps: (props) -> - @setState(@_getStateFromStores(props)) + @replaceState(@_getStateFromStores(props)) componentDidUpdate: -> # Wait for other components that are dirty (the actual columns in the sheet) # 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: -> - # The main toolbar contains items with roles :Toolbar - # and Global:Toolbar - mainToolbar = @_flexboxForItems(@state.items) + style = + position:'absolute' + backgroundColor:'white' + width:'100%' + height:'100%' + zIndex: 1 - # Column toolbars contain items with roles attaching them to items - # in the sheet. Ex: MessageList:Toolbar items appear in the column - # toolbar for the column containing . - columnToolbars = @state.itemsForColumns.map ({column, name, items}) => + toolbars = @state.itemsForColumns.map ({column, items}) =>
{@_flexboxForItems(items)}
- - {mainToolbar} - {columnToolbars} - +
+ {toolbars} +
_flexboxForItems: (items) -> components = items.map ({view, name}) => - {components} - + recomputeLayout: -> return unless @isMounted() @@ -87,8 +118,9 @@ Toolbar = React.createClass columnToolbarEls = @getDOMNode().querySelectorAll('[data-column]') # 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 # as their respective columns in the top sheet for columnToolbarEl in columnToolbarEls @@ -100,32 +132,42 @@ Toolbar = React.createClass columnToolbarEl.style.left = "#{columnEl.offsetLeft}px" columnToolbarEl.style.width = "#{columnEl.offsetWidth}px" + _onWindowResize: -> + @recomputeLayout() + _getStateFromStores: (props) -> props ?= @props state = mode: WorkspaceStore.selectedLayoutMode() - items: [] itemsForColumns: [] - for role in ["Global:Toolbar", "#{props.type}:Toolbar"] - for entry in ComponentRegistry.findAllByRole(role) - continue if entry.mode? and entry.mode != state.mode - state.items.push(entry) - + items = {} for column in ["Left", "Center", "Right"] - role = "#{props.type}:#{column}:Toolbar" - items = [] - for entry in ComponentRegistry.findAllByRole(role) - continue if entry.mode? and entry.mode != state.mode - items.push(entry) - if items.length > 0 - state.itemsForColumns.push({column, name, items}) + items[column] = [] + for role in ["Global:#{column}:Toolbar", "#{props.type}:#{column}:Toolbar"] + for entry in ComponentRegistry.findAllByRole(role) + continue if entry.mode? and entry.mode != state.mode + items[column].push(entry) + 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 FlexboxForRoles = React.createClass className: 'FlexboxForRoles' + propTypes: roles: React.PropTypes.arrayOf(React.PropTypes.string) @@ -139,9 +181,17 @@ FlexboxForRoles = React.createClass componentWillUnmount: -> @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: -> components = @state.items.map ({view, name}) => - + {components} @@ -171,26 +221,43 @@ SheetContainer = React.createClass render: -> topSheetType = @state.stack[@state.stack.length - 1] + - -
- -
+ + {@_toolbarComponents()} + +
-
- - {@_sheetComponents()} - -
+ + + {@_sheetComponents()} + +
+ _toolbarComponents: -> + @state.stack.map (type, index) -> + + _sheetComponents: -> @state.stack.map (type, index) => - _onColumnSizeChanged: -> - @refs.toolbar.recomputeLayout() + _onColumnSizeChanged: (sheet) -> + @refs["toolbar-#{sheet.props.depth}"]?.recomputeLayout() _onStoreChange: -> - @setState @_getStateFromStores() + _.defer => @setState(@_getStateFromStores()) _getStateFromStores: -> stack: WorkspaceStore.sheetStack() diff --git a/src/sheet.cjsx b/src/sheet.cjsx index ab4f84f8a..3cfeac25a 100644 --- a/src/sheet.cjsx +++ b/src/sheet.cjsx @@ -28,6 +28,12 @@ Sheet = React.createClass @unlisteners.push WorkspaceStore.listen (event) => @setState(@_getStateFromStores()) + componentDidUpdate: -> + @props.onColumnSizeChanged(@) if @props.onColumnSizeChanged + + shouldComponentUpdate: (nextProps, nextState) -> + not _.isEqual(nextProps, @props) or not _.isEqual(nextState, @state) + componentWillUnmount: -> unlisten() for unlisten in @unlisteners @@ -49,24 +55,16 @@ Sheet = React.createClass style={style} data-type={@props.type}> - {@_backButtonComponent()} {@_columnFlexboxComponents()} - _backButtonComponent: -> - return [] if @props.depth is 0 -
-
-
-
- _columnFlexboxComponents: -> @props.columns.map (column) => classes = @state[column] || [] return if classes.length is 0 - components = classes.map ({name, view}) => + components = classes.map ({name, view}) -> maxWidth = _.reduce classes, ((m,{view}) -> Math.min(view.maxWidth ? 10000, m)), 10000 minWidth = _.reduce classes, ((m,{view}) -> Math.max(view.minWidth ? 0, m)), 0 @@ -78,7 +76,7 @@ Sheet = React.createClass @props.onColumnSizeChanged(@) } minWidth={minWidth} maxWidth={maxWidth} handle={handle}> diff --git a/src/titlebar.cjsx b/src/titlebar.cjsx deleted file mode 100644 index 0de77e0b4..000000000 --- a/src/titlebar.cjsx +++ /dev/null @@ -1,13 +0,0 @@ -React = require 'react' - -module.exports = -TitleBar = React.createClass - displayName: 'TitleBar' - - render: -> -
- {atom.getCurrentWindow().getTitle()} - - - -
diff --git a/static/components/spinner.less b/static/components/spinner.less index b229f763d..f652e78a2 100644 --- a/static/components/spinner.less +++ b/static/components/spinner.less @@ -7,6 +7,7 @@ opacity: 1; -webkit-transition: opacity 0.2s linear 0.3s; //transition in } + .spinner.hidden { opacity: 0; -webkit-transition: opacity 0.2s linear; //transition out diff --git a/static/components/tokenizing-text-field.less b/static/components/tokenizing-text-field.less index 17a4c66d5..acba9dbbc 100644 --- a/static/components/tokenizing-text-field.less +++ b/static/components/tokenizing-text-field.less @@ -39,7 +39,7 @@ } &:hover { background-color: darken(@background-secondary, 5%); - cursor: -webkit-grab; + cursor: default; } &.selected, &.dragging { diff --git a/static/images/message-list/toolbar-down-arrow@2x.png b/static/images/message-list/toolbar-down-arrow@2x.png new file mode 100644 index 000000000..7f95168df Binary files /dev/null and b/static/images/message-list/toolbar-down-arrow@2x.png differ diff --git a/static/images/message-list/toolbar-up-arrow@2x.png b/static/images/message-list/toolbar-up-arrow@2x.png new file mode 100644 index 000000000..81b3d3b6c Binary files /dev/null and b/static/images/message-list/toolbar-up-arrow@2x.png differ diff --git a/static/images/sheets/sheet-back@2x.png b/static/images/sheets/sheet-back@2x.png index 5882180b7..9320a78c7 100644 Binary files a/static/images/sheets/sheet-back@2x.png and b/static/images/sheets/sheet-back@2x.png differ diff --git a/static/images/splitpane/toolbar-icon-toggle-pane@2x.png b/static/images/splitpane/toolbar-icon-toggle-pane@2x.png new file mode 100644 index 000000000..718e5cca9 Binary files /dev/null and b/static/images/splitpane/toolbar-icon-toggle-pane@2x.png differ diff --git a/static/workspace.less b/static/workspace.less index 2573223fd..c72a68828 100644 --- a/static/workspace.less +++ b/static/workspace.less @@ -36,33 +36,33 @@ atom-workspace { } .sheet-stack-enter { - left:100%; - transition: left .20s ease-out; + left:7%; + opacity: 0; + transition: all .15s ease-out; } .sheet-stack-enter.sheet-stack-enter-active { left:0; + opacity: 1; } .sheet-stack-leave { left:0; - transition: left .20s ease-in; + opacity: 1; + transition: all .15s ease-in; } .sheet-stack-leave.sheet-stack-leave-active { - left:100%; + left:7%; + opacity: 0; } -.sheet-title-bar { - height:24px; - line-height: 24px; - cursor: default; - background: @toolbar-background-color; - -webkit-app-region: drag; - padding: 3px; +.toolbar-window-controls { + padding-top:14px; padding-left:@spacing-half; - padding-right:60px; - text-align: center; + order: -1000; + min-width: 102px; + width: 102px; button { -webkit-app-region: no-drag; @@ -105,13 +105,13 @@ atom-workspace { } body.platform-win32, body.platform-linux { - .sheet-title-bar { + .toolbar-window-controls { display:none; } } body.is-blurred { - .sheet-title-bar { + .toolbar-window-controls { button { background-color: desaturate(fade(#FCB40A, 20%), 100%); } @@ -121,11 +121,12 @@ body.is-blurred { .sheet-toolbar { position: relative; -webkit-app-region: drag; + -webkit-user-select:none; background: @toolbar-background-color; border-bottom: 1px solid @border-color-divider; width: 100%; height: 50px; - + // prevent flexbox from ever, ever resizing toolbars, no matter // how much it thinks other content is being squished min-height: 50px; @@ -141,6 +142,11 @@ body.is-blurred { .item-spacer { -webkit-app-region: drag; } + .item-back { + order:-999; + padding-top: 5px; + padding-left: @spacing-three-quarters; + } .btn-toolbar { margin-top: @spacing-half; @@ -167,30 +173,6 @@ body.is-blurred { 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 { width: 6px; top: 0;