refactor(layout): More configuration into WorkspaceStore, less in packages

This commit is contained in:
Ben Gotow 2015-03-20 14:52:10 -07:00
parent eb6cc11a83
commit 21e8455ef6
18 changed files with 174 additions and 171 deletions

View file

@ -1,6 +1,6 @@
React = require "react"
AccountSidebar = require "./account-sidebar"
{ComponentRegistry} = require "inbox-exports"
{ComponentRegistry, WorkspaceStore} = require "inbox-exports"
module.exports =
item: null # The DOM item the main React component renders into
@ -9,4 +9,4 @@ module.exports =
ComponentRegistry.register
view: AccountSidebar
name: 'AccountSidebar'
role: 'Root:Left'
location: WorkspaceStore.Location.RootSidebar

View file

@ -2,7 +2,11 @@ _ = require 'underscore-plus'
React = require 'react'
ipc = require 'ipc'
{NamespaceStore, DatabaseStore, Message, ComponentRegistry} = require('inbox-exports')
{NamespaceStore,
DatabaseStore,
Message,
ComponentRegistry,
WorkspaceStore} = require('inbox-exports')
NewComposeButton = require('./new-compose-button')
ComposerView = require('./composer-view')
@ -80,7 +84,7 @@ module.exports =
ComponentRegistry.register
view: NewComposeButton
name: 'NewComposeButton'
role: 'Root:Left:Toolbar'
location: WorkspaceStore.Location.RootSidebar.Toolbar
_showInitialErrorDialog: (msg) ->
remote = require('remote')

View file

@ -1,5 +1,5 @@
React = require 'react'
{ComponentRegistry} = require 'inbox-exports'
{ComponentRegistry, WorkspaceStore} = require 'inbox-exports'
module.exports =
item: null
@ -7,8 +7,8 @@ module.exports =
activate: (@state={}) ->
ComponentRegistry.register
name: 'activity-bar'
role: 'Global:Footer'
view: require './activity-bar'
location: WorkspaceStore.Sheet.Global.Footer
deactivate: ->
ComponentRegistry.unregister 'activity-bar'

View file

@ -2,7 +2,7 @@ React = require "react"
MessageList = require "./message-list"
MessageToolbarItems = require "./message-toolbar-items"
MessageSubjectItem = require "./message-subject-item"
{ComponentRegistry} = require 'inbox-exports'
{ComponentRegistry, WorkspaceStore} = require 'inbox-exports'
{RetinaImg} = require 'ui-components'
DownButton = React.createClass
@ -29,52 +29,39 @@ module.exports =
activate: (@state={}) ->
# Register Message List Actions we provide globally
ComponentRegistry.register
name: 'MessageListSplit'
role: 'Root:Right'
mode: 'split'
view: MessageList
ComponentRegistry.register
name: 'MessageToolbarItemsSplit'
role: 'Root:Right:Toolbar'
mode: 'split'
view: MessageToolbarItems
ComponentRegistry.register
name: 'MessageList'
role: 'Thread:Center'
mode: 'list'
view: MessageList
location: WorkspaceStore.Location.MessageList
ComponentRegistry.register
name: 'MessageToolbarItems'
role: 'Thread:Center:Toolbar'
mode: 'list'
view: MessageToolbarItems
location: WorkspaceStore.Location.MessageList.Toolbar
ComponentRegistry.register
name: 'MessageSubjectItem'
role: 'Thread:Center:Toolbar'
mode: 'list'
view: MessageSubjectItem
location: WorkspaceStore.Location.MessageList.Toolbar
ComponentRegistry.register
name: 'DownButton'
role: 'Thread:Right:Toolbar'
mode: 'list'
view: DownButton
location: WorkspaceStore.Sheet.Thread.Toolbar.Right
ComponentRegistry.register
name: 'UpButton'
role: 'Thread:Right:Toolbar'
mode: 'list'
view: UpButton
location: WorkspaceStore.Sheet.Thread.Toolbar.Right
deactivate: ->
ComponentRegistry.unregister 'MessageToolbarItems'
ComponentRegistry.unregister 'MessageListSplit'
ComponentRegistry.unregister 'MessageSubjectItem'
ComponentRegistry.unregister 'MessageList'
ComponentRegistry.unregister 'DownButton'
ComponentRegistry.unregister 'UpButton'
serialize: -> @state

View file

@ -65,7 +65,6 @@ MessageList = React.createClass
{@_messageListHeaders()}
{@_messageComponents()}
</div>
{@_messageListSidebar()}
<Spinner visible={!@state.ready} />
</div>
@ -101,22 +100,6 @@ MessageList = React.createClass
{<MLBar thread={@state.currentThread} /> for MLBar in MLBars}
</div>
_messageListSidebar: ->
sidebarItems = ComponentRegistry.findAllViewsByRole('MessageListSidebar')
if sidebarItems.length > 0
maxWidth = _.reduce sidebarItems, ((m,view) -> Math.min(view.maxWidth ? 640, m)), 640
minWidth = _.reduce sidebarItems, ((m,view) -> Math.max(view.minWidth ? 240, m)), 240
<ResizableRegion handle={ResizableRegion.Handle.Left}
minWidth={minWidth}
maxWidth={maxWidth}>
<div className="message-list-sidebar">
{<view thread={@state.currentThread} /> for view in sidebarItems}
</div>
</ResizableRegion>
else
return <div></div>
_messageListHeaders: ->
Participants = @state.Participants
MessageListHeaders = ComponentRegistry.findAllViewsByRole('MessageListHeader')

View file

@ -70,9 +70,7 @@ module.exports = React.createClass
"hidden": !@state.threadIsSelected
<div className={classes}>
<div className="message-toolbar-items-inner">
<ArchiveButton />
</div>
<ArchiveButton />
</div>
componentDidMount: ->

View file

@ -8,21 +8,10 @@
// toolbar. We want the toolbar items to sit right above the centered
// content, so we need another 800px-wide container in the toolbar...
.message-toolbar-items {
order: -10000;
width: 100%;
text-align: center;
position: absolute;
pointer-events: none;
.message-toolbar-items-inner {
margin: auto;
max-width: @message-max-width - @spacing-three-quarters*2;
text-align: right;
pointer-events: none;
& > * {
pointer-events: auto;
}
}
order: 200;
flex-grow: 0;
flex-shrink: 0;
margin-right: 10px;
}
.message-toolbar-items.hidden {
@ -66,13 +55,6 @@
padding: 0;
order: 2;
.message-list-sidebar {
display: flex;
flex-direction: column;
background: @background-off-primary;
height: 100%;
}
.message-list-headers {
margin: 0 auto;
width: 100%;
@ -191,6 +173,10 @@
}
.column-MessageListSidebar {
background-color: @background-off-primary;
}
///////////////////////////////
// message-participants.cjsx //
///////////////////////////////

View file

@ -1,4 +1,4 @@
{ComponentRegistry} = require 'inbox-exports'
{ComponentRegistry, WorkspaceStore} = require 'inbox-exports'
ModeToggle = require './mode-toggle'
module.exports =
@ -6,4 +6,4 @@ module.exports =
ComponentRegistry.register
name: 'ModeToggle'
view: ModeToggle
role: 'Root:Center:Toolbar'
location: WorkspaceStore.Sheet.Root.Toolbar.Right

View file

@ -1,7 +1,7 @@
React = require "react"
Notifications = require "./notifications"
NotificationsStickyBar = require "./notifications-sticky-bar"
{ComponentRegistry} = require("inbox-exports")
{ComponentRegistry, WorkspaceStore} = require("inbox-exports")
module.exports =
item: null # The DOM item the main React component renders into
@ -10,12 +10,12 @@ module.exports =
ComponentRegistry.register
view: Notifications
name: 'Notifications'
role: 'Root:Left'
location: WorkspaceStore.Location.RootSidebar
ComponentRegistry.register
view: NotificationsStickyBar
name: 'NotificationsStickyBar'
role: 'Root:Top'
location: WorkspaceStore.Sheet.Root.Header
deactivate: ->
ComponentRegistry.unregister('NotificationsStickyBar')

View file

@ -1,7 +1,7 @@
path = require 'path'
require 'coffee-react/register'
React = require 'react'
{ComponentRegistry} = require 'inbox-exports'
{ComponentRegistry, WorkspaceStore} = require 'inbox-exports'
SearchBar = require './search-bar'
SearchSettingsBar = require './search-settings-bar'
@ -13,7 +13,7 @@ module.exports =
ComponentRegistry.register
view: SearchBar
name: 'SearchBar'
role: 'Root:Center:Toolbar'
location: WorkspaceStore.Location.RootCenter.Toolbar
deactivate: ->
ComponentRegistry.unregister 'SearchBar'

View file

@ -1,7 +1,7 @@
_ = require 'underscore-plus'
React = require "react"
SidebarFullContact = require "./sidebar-fullcontact.cjsx"
{ComponentRegistry} = require("inbox-exports")
{ComponentRegistry, WorkspaceStore} = require "inbox-exports"
module.exports =
item: null
@ -10,7 +10,7 @@ module.exports =
ComponentRegistry.register
name: 'SidebarFullContact'
view: SidebarFullContact
role: 'MessageListSidebar'
location: WorkspaceStore.Location.MessageListSidebar
deactivate: ->
ComponentRegistry.unregister('SidebarFullContact')

View file

@ -61,3 +61,6 @@ SidebarFullContact = React.createClass
fullContactCache: FullContactStore.fullContactCache()
sortedContacts: FullContactStore.sortedContacts()
focusedContact: FullContactStore.focusedContact()
SidebarFullContact.maxWidth = 300
SidebarFullContact.minWidth = 200

View file

@ -33,4 +33,4 @@ module.exports =
ComponentRegistry.register
view: RootCenterComponent
name: 'RootCenterComponent'
role: 'Root:Center'
location: WorkspaceStore.Location.RootCenter

View file

@ -36,7 +36,7 @@ class Component
# Don't shit the bed if the user forgets `new`
return new Component(attributes) unless @ instanceof Component
['name', 'model', 'view', 'role', 'mode'].map (key) =>
['name', 'model', 'view', 'role', 'mode', 'location'].map (key) =>
@[key] = attributes[key] if attributes[key]
unless @name?
@ -84,6 +84,13 @@ ComponentRegistry = Reflux.createStore
findAllViewsByRole: (role) ->
_.map @findAllByRole(role), (component) -> component.view
findAllByLocationAndMode: (location, mode) ->
_.filter (_.values registry), (component) ->
return false unless component.location
return false if component.location.id isnt location.id
return false if component.mode and component.mode isnt mode
true
triggerDebounced: _.debounce(( -> @trigger(@)), 1)
_clear: ->

View file

@ -2,6 +2,30 @@ Reflux = require 'reflux'
NamespaceStore = require './namespace-store'
Actions = require '../actions'
Location = {}
for key in ['RootSidebar', 'RootCenter', 'MessageList', 'MessageListSidebar']
Location[key] = {id: "#{key}", Toolbar: {id: "#{key}:Toolbar"}}
defineSheet = (type, columns) ->
Toolbar:
Left: {id: "Sheet:#{type}:Toolbar:Left"}
Right: {id: "Sheet:#{type}:Toolbar:Right"}
Header: {id: "Sheet:#{type}:Header"}
Footer: {id: "Sheet:#{type}:Footer"}
type: type
columns: columns
Sheet =
Global: defineSheet 'Global'
Root: defineSheet 'Root',
list: [Location.RootSidebar, Location.RootCenter]
split: [Location.RootSidebar, Location.RootCenter, Location.MessageList, Location.MessageListSidebar]
Thread: defineSheet 'Thread',
list: [Location.MessageList, Location.MessageListSidebar]
WorkspaceStore = Reflux.createStore
init: ->
@_resetInstanceVars()
@ -16,7 +40,7 @@ WorkspaceStore = Reflux.createStore
'application:pop-sheet': => @popSheet()
_resetInstanceVars: ->
@_sheetStack = ["Root"]
@_sheetStack = [Sheet.Root]
@_view = 'threads'
@_layoutMode = 'list'
@ -51,8 +75,8 @@ WorkspaceStore = Reflux.createStore
@trigger()
pushThreadSheet: (threadId) ->
if @selectedLayoutMode() is 'list' and threadId and @sheet() isnt "Thread"
@pushSheet("Thread")
if @selectedLayoutMode() is 'list' and threadId and @sheet().type isnt Sheet.Thread.type
@pushSheet(Sheet.Thread)
popSheet: ->
if @_sheetStack.length > 1
@ -61,8 +85,11 @@ WorkspaceStore = Reflux.createStore
popToRootSheet: ->
if @_sheetStack.length > 1
@_sheetStack = ["Root"]
@_sheetStack = [Sheet.Root]
@trigger()
WorkspaceStore.Location = Location
WorkspaceStore.Sheet = Sheet
module.exports = WorkspaceStore

View file

@ -38,13 +38,13 @@ ToolbarWindowControls = React.createClass
ComponentRegistry.register
view: ToolbarWindowControls
name: 'ToolbarWindowControls'
role: 'Global:Left:Toolbar'
location: WorkspaceStore.Sheet.Global.Toolbar.Left
Toolbar = React.createClass
className: 'Toolbar'
propTypes:
type: React.PropTypes.string
data: React.PropTypes.object
depth: React.PropTypes.number
getInitialState: ->
@ -84,10 +84,10 @@ Toolbar = React.createClass
height:'100%'
zIndex: 1
toolbars = @state.itemsForColumns.map ({column, items}) =>
toolbars = @state.columns.map (items, idx) =>
<div style={position: 'absolute', top:0, display:'none'}
data-column={column}
key={column}>
data-column={idx}
key={idx}>
{@_flexboxForItems(items)}
</div>
@ -96,7 +96,7 @@ Toolbar = React.createClass
</div>
_flexboxForItems: (items) ->
components = items.map ({view, name}) =>
elements = items.map ({view, name}) =>
<view key={name} {...@props} />
<TimeoutTransitionGroup
@ -106,7 +106,7 @@ Toolbar = React.createClass
leaveTimeout={200}
enterTimeout={200}
transitionName="sheet-toolbar">
{components}
{elements}
<ToolbarSpacer key="spacer-50" order={-50}/>
<ToolbarSpacer key="spacer+50" order={50}/>
</TimeoutTransitionGroup>
@ -134,42 +134,37 @@ Toolbar = React.createClass
_onWindowResize: ->
@recomputeLayout()
_getStateFromStores: (props) ->
props ?= @props
state =
mode: WorkspaceStore.selectedLayoutMode()
itemsForColumns: []
columns: []
items = {}
for column in ["Left", "Center", "Right"]
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')
# Add items registered to Regions in the current sheet
for loc in @props.data.columns[state.mode]
entries = ComponentRegistry.findAllByLocationAndMode(loc.Toolbar, state.mode)
state.columns.push(entries)
# 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]
# Add left items registered to the Sheet instead of to a Region
for loc in [WorkspaceStore.Sheet.Global, @props.data]
entries = ComponentRegistry.findAllByLocationAndMode(loc.Toolbar.Left, state.mode)
state.columns[0].push(entries...)
state.columns[0].push(view: ToolbarBack, name: 'ToolbarBack') if @props.depth > 0
# Add right items registered to the Sheet instead of to a Region
for loc in [WorkspaceStore.Sheet.Global, @props.data]
entries = ComponentRegistry.findAllByLocationAndMode(loc.Toolbar.Right, state.mode)
state.columns[state.columns.length - 1].push(entries...)
for key, val of items
state.itemsForColumns.push({column: key, items: val}) if val.length > 0
state
FlexboxForRoles = React.createClass
className: 'FlexboxForRoles'
FlexboxForLocations = React.createClass
className: 'FlexboxForLocations'
propTypes:
roles: React.PropTypes.arrayOf(React.PropTypes.string)
locations: React.PropTypes.arrayOf(React.PropTypes.object)
getInitialState: ->
@_getComponentRegistryState()
@ -190,17 +185,18 @@ FlexboxForRoles = React.createClass
!_.isEqual(nextItemNames, itemNames)
render: ->
components = @state.items.map ({view, name}) =>
elements = @state.items.map ({view, name}) =>
<view key={name} />
<Flexbox direction="row">
{components}
{elements}
</Flexbox>
_getComponentRegistryState: ->
items = []
for role in @props.roles
items = items.concat(ComponentRegistry.findAllByRole(role))
mode = WorkspaceStore.selectedLayoutMode()
for location in @props.locations
items = items.concat(ComponentRegistry.findAllByLocationAndMode(location, mode))
{items}
module.exports =
@ -220,7 +216,7 @@ SheetContainer = React.createClass
@unsubscribe() if @unsubscribe
render: ->
topSheetType = @state.stack[@state.stack.length - 1]
topSheet = @state.stack[@state.stack.length - 1]
<Flexbox direction="column">
<TimeoutTransitionGroup name="Toolbar"
@ -229,12 +225,12 @@ SheetContainer = React.createClass
enterTimeout={200}
className="sheet-toolbar"
transitionName="sheet-toolbar">
{@_toolbarComponents()}
{@_toolbarElements()}
</TimeoutTransitionGroup>
<div name="Top" style={order:1}>
<FlexboxForRoles roles={["Global:Top", "#{topSheetType}:Top"]}
type={topSheetType}/>
<div name="Header" style={order:1}>
<FlexboxForLocations locations={[topSheet.Header, WorkspaceStore.Sheet.Global.Header]}
type={topSheet.type}/>
</div>
<TimeoutTransitionGroup name="Center"
@ -242,25 +238,25 @@ SheetContainer = React.createClass
leaveTimeout={150}
enterTimeout={150}
transitionName="sheet-stack">
{@_sheetComponents()}
{@_sheetElements()}
</TimeoutTransitionGroup>
<div name="Footer" style={order:3}>
<FlexboxForRoles roles={["Global:Footer", "#{topSheetType}:Footer"]}
type={topSheetType}/>
<FlexboxForLocations locations={[topSheet.Footer, WorkspaceStore.Sheet.Global.Footer]}
type={topSheet.type}/>
</div>
</Flexbox>
_toolbarComponents: ->
@state.stack.map (type, index) ->
<Toolbar type={type}
_toolbarElements: ->
@state.stack.map (data, index) ->
<Toolbar data={data}
ref={"toolbar-#{index}"}
depth={index}
key={index} />
_sheetComponents: ->
@state.stack.map (type, index) =>
<Sheet type={type}
_sheetElements: ->
@state.stack.map (data, index) =>
<Sheet data={data}
depth={index}
key={index}
onColumnSizeChanged={@_onColumnSizeChanged} />

View file

@ -5,19 +5,17 @@ RetinaImg = require './components/retina-img.cjsx'
Flexbox = require './components/flexbox.cjsx'
ResizableRegion = require './components/resizable-region.cjsx'
FLEX = 10000
module.exports =
Sheet = React.createClass
displayName: 'Sheet'
propTypes:
type: React.PropTypes.string.isRequired
data: React.PropTypes.object.isRequired
depth: React.PropTypes.number.isRequired
columns: React.PropTypes.arrayOf(React.PropTypes.string)
onColumnSizeChanged: React.PropTypes.func
getDefaultProps: ->
columns: ['Left', 'Center', 'Right']
getInitialState: ->
@_getStateFromStores()
@ -53,57 +51,67 @@ Sheet = React.createClass
<div name={"Sheet"}
style={style}
data-type={@props.type}>
data-type={@props.data.type}>
<Flexbox direction="row">
{@_columnFlexboxComponents()}
{@_columnFlexboxElements()}
</Flexbox>
</div>
_columnFlexboxComponents: ->
@props.columns.map (column) =>
classes = @state[column] || []
return if classes.length is 0
components = classes.map ({name, view}) -> <view key={name} />
maxWidth = _.reduce classes, ((m,{view}) -> Math.min(view.maxWidth ? 10000, m)), 10000
minWidth = _.reduce classes, ((m,{view}) -> Math.max(view.minWidth ? 0, m)), 0
resizable = minWidth != maxWidth && column != 'Center'
if resizable
if column is 'Left' then handle = ResizableRegion.Handle.Right
if column is 'Right' then handle = ResizableRegion.Handle.Left
<ResizableRegion key={"#{@props.type}:#{column}"}
name={"#{@props.type}:#{column}"}
data-column={column}
_columnFlexboxElements: ->
@state.columns.map ({entries, maxWidth, minWidth, handle, id}, idx) =>
elements = entries.map ({name, view}) -> <view key={name} />
if minWidth != maxWidth and maxWidth < FLEX
<ResizableRegion key={"#{@props.type}:#{idx}"}
name={"#{@props.type}:#{idx}"}
className={"column-#{id}"}
data-column={idx}
onResize={ => @props.onColumnSizeChanged(@) }
minWidth={minWidth}
maxWidth={maxWidth}
handle={handle}>
<Flexbox direction="column">
{components}
{elements}
</Flexbox>
</ResizableRegion>
else
<Flexbox direction="column"
key={"#{@props.type}:#{column}"}
name={"#{@props.type}:#{column}"}
data-column={column}
key={"#{@props.type}:#{idx}"}
name={"#{@props.type}:#{idx}"}
className={"column-#{id}"}
data-column={idx}
style={flex: 1}>
{components}
{elements}
</Flexbox>
_getStateFromStores: ->
state = {}
state.mode = WorkspaceStore.selectedLayoutMode()
state =
mode: WorkspaceStore.selectedLayoutMode()
columns: []
for column in @props.columns
views = []
for entry in ComponentRegistry.findAllByRole("#{@props.type}:#{column}")
continue if entry.mode? and entry.mode != state.mode
views.push(entry)
state["#{column}"] = views
widest = -1
widestWidth = -1
for location, idx in @props.data.columns[state.mode]
entries = ComponentRegistry.findAllByLocationAndMode(location, state.mode)
maxWidth = _.reduce entries, ((m,{view}) -> Math.min(view.maxWidth ? 10000, m)), 10000
minWidth = _.reduce entries, ((m,{view}) -> Math.max(view.minWidth ? 0, m)), 0
col = {entries, maxWidth, minWidth, id: location.id}
state.columns.push(col)
if maxWidth > widestWidth
widestWidth = maxWidth
widest = idx
# Once we've accumulated all the React components for the columns,
# ensure that at least one column has a huge max-width so that the columns
# expand to fill the window. This may make items in the column unhappy, but
# we pick the column with the highest max-width so the effect is minimal.
state.columns[widest].maxWidth = FLEX
# Assign flexible edges based on whether items are to the left or right
# of the flexible column (which has no edges)
state.columns[i].handle = ResizableRegion.Handle.Right for i in [0..widest-1] by 1
state.columns[i].handle = ResizableRegion.Handle.Left for i in [widest..state.columns.length-1] by 1
state
_pop: ->

View file

@ -63,6 +63,8 @@ atom-workspace {
order: -1000;
min-width: 102px;
width: 102px;
flex-grow: 0;
flex-shrink: 0;
button {
-webkit-app-region: no-drag;
@ -146,6 +148,8 @@ body.is-blurred {
order:-999;
padding-top: 5px;
padding-left: @spacing-three-quarters;
flex-grow: 0;
flex-shrink: 0;
}
.btn-toolbar {