mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-01-01 05:06:53 +08:00
DataView => ListDataSource
This commit is contained in:
parent
c52e4bcd19
commit
ff01c3a502
18 changed files with 129 additions and 128 deletions
|
@ -29,11 +29,11 @@ Here's a quick look at standard components you can require from `nylas-component
|
|||
|
||||
- **{RetinaImg}**: Replacement for standard `<img>` tags which automatically resolves the best version of the image for the user's display and can apply many image transforms.
|
||||
|
||||
- **{ListTabular}**: Component for creating a list of items backed by a paginating ModelView.
|
||||
- **{ListTabular}**: Component for creating a list of items backed by a paginating ListDataSource.
|
||||
|
||||
- **{MultiselectList}**: Component for creating a list that supports multi-selection. (Internally wraps ListTabular)
|
||||
|
||||
- **{MultiselectActionBar}**: Component for creating a contextual toolbar that is activated when the user makes a selection on a ModelView.
|
||||
- **{MultiselectActionBar}**: Component for creating a contextual toolbar that is activated when the user makes a selection on a ListDataSource.
|
||||
|
||||
- **{ResizableRegion}**: Component that renders it's children inside a resizable region with a draggable handle.
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ _ = require 'underscore'
|
|||
Actions,
|
||||
AccountStore,
|
||||
MutableQuerySubscription,
|
||||
QueryResultSetView,
|
||||
ObservableListDataSource,
|
||||
FocusedPerspectiveStore,
|
||||
DatabaseStore} = require 'nylas-exports'
|
||||
|
||||
|
@ -17,7 +17,7 @@ class DraftListStore extends NylasStore
|
|||
@subscription = new MutableQuerySubscription(@_queryForCurrentAccount(), {asResultSet: true})
|
||||
$resultSet = Rx.Observable.fromPrivateQuerySubscription('draft-list', @subscription)
|
||||
|
||||
@_view = new QueryResultSetView $resultSet, ({start, end}) =>
|
||||
@_view = new ObservableListDataSource $resultSet, ({start, end}) =>
|
||||
@subscription.replaceQuery(@_queryForCurrentAccount().page(start, end))
|
||||
|
||||
view: =>
|
||||
|
|
|
@ -69,7 +69,7 @@ class EmptyState extends React.Component
|
|||
@displayName = 'EmptyState'
|
||||
@propTypes =
|
||||
visible: React.PropTypes.bool.isRequired
|
||||
dataView: React.PropTypes.object
|
||||
dataSource: React.PropTypes.object
|
||||
|
||||
constructor: (@props) ->
|
||||
@state =
|
||||
|
@ -85,9 +85,9 @@ class EmptyState extends React.Component
|
|||
@setState(active:true)
|
||||
|
||||
shouldComponentUpdate: (nextProps, nextState) ->
|
||||
# Avoid deep comparison of dataView, which is a very complex object
|
||||
# Avoid deep comparison of dataSource, which is a very complex object
|
||||
return true if nextProps.visible isnt @props.visible
|
||||
return true if nextProps.dataView isnt @props.dataView
|
||||
return true if nextProps.dataSource isnt @props.dataSource
|
||||
return not _.isEqual(nextState, @state)
|
||||
|
||||
componentWillUnmount: ->
|
||||
|
|
|
@ -5,7 +5,7 @@ Rx = require 'rx-lite'
|
|||
DatabaseStore,
|
||||
QuerySubscription,
|
||||
QueryResultSet,
|
||||
QueryResultSetView,
|
||||
ObservableListDataSource,
|
||||
MutableQuerySubscription} = require 'nylas-exports'
|
||||
|
||||
PaginatingSearch = require './paginating-search'
|
||||
|
@ -85,7 +85,7 @@ module.exports = ThreadListViewFactory =
|
|||
search = new PaginatingSearch(terms, accountId)
|
||||
$resultSet = _flatMapJoiningMessages(search.observable())
|
||||
|
||||
return new QueryResultSetView $resultSet, ({start, end}) =>
|
||||
return new ObservableListDataSource $resultSet, ({start, end}) =>
|
||||
search.setRange({start, end})
|
||||
|
||||
viewForQuery: (query) =>
|
||||
|
@ -93,5 +93,5 @@ module.exports = ThreadListViewFactory =
|
|||
$resultSet = Rx.Observable.fromPrivateQuerySubscription('thread-list', subscription)
|
||||
$resultSet = _flatMapJoiningMessages($resultSet)
|
||||
|
||||
return new QueryResultSetView $resultSet, ({start, end}) =>
|
||||
return new ObservableListDataSource $resultSet, ({start, end}) =>
|
||||
subscription.replaceQuery(query.clone().page(start, end))
|
||||
|
|
|
@ -15,7 +15,7 @@ describe "MultiselectListInteractionHandler", ->
|
|||
|
||||
data = [@item, @itemFocus, @itemAfterFocus, @itemKeyboardFocus, @itemAfterKeyboardFocus]
|
||||
|
||||
@dataView =
|
||||
@dataSource =
|
||||
selection:
|
||||
toggle: jasmine.createSpy('toggle')
|
||||
expandTo: jasmine.createSpy('expandTo')
|
||||
|
@ -29,7 +29,7 @@ describe "MultiselectListInteractionHandler", ->
|
|||
count: -> data.length
|
||||
|
||||
@collection = 'threads'
|
||||
@handler = new MultiselectListInteractionHandler(@dataView, @collection)
|
||||
@handler = new MultiselectListInteractionHandler(@dataSource, @collection)
|
||||
@isRootSheet = true
|
||||
|
||||
spyOn(WorkspaceStore, 'topSheet').andCallFake => {root: @isRootSheet}
|
||||
|
@ -52,7 +52,7 @@ describe "MultiselectListInteractionHandler", ->
|
|||
describe "onMetaClick", ->
|
||||
it "shoud toggle selection", ->
|
||||
@handler.onMetaClick(@item)
|
||||
expect(@dataView.selection.toggle).toHaveBeenCalledWith(@item)
|
||||
expect(@dataSource.selection.toggle).toHaveBeenCalledWith(@item)
|
||||
|
||||
it "should focus the keyboard on the clicked item", ->
|
||||
@handler.onMetaClick(@item)
|
||||
|
@ -61,7 +61,7 @@ describe "MultiselectListInteractionHandler", ->
|
|||
describe "onShiftClick", ->
|
||||
it "should expand selection", ->
|
||||
@handler.onShiftClick(@item)
|
||||
expect(@dataView.selection.expandTo).toHaveBeenCalledWith(@item)
|
||||
expect(@dataSource.selection.expandTo).toHaveBeenCalledWith(@item)
|
||||
|
||||
it "should focus the keyboard on the clicked item", ->
|
||||
@handler.onShiftClick(@item)
|
||||
|
@ -77,13 +77,13 @@ describe "MultiselectListInteractionHandler", ->
|
|||
it "should toggle the selection of the keyboard item", ->
|
||||
@isRootSheet = true
|
||||
@handler.onSelect()
|
||||
expect(@dataView.selection.toggle).toHaveBeenCalledWith(@itemKeyboardFocus)
|
||||
expect(@dataSource.selection.toggle).toHaveBeenCalledWith(@itemKeyboardFocus)
|
||||
|
||||
describe "on the thread view", ->
|
||||
it "should toggle the selection of the focused item", ->
|
||||
@isRootSheet = false
|
||||
@handler.onSelect()
|
||||
expect(@dataView.selection.toggle).toHaveBeenCalledWith(@itemFocus)
|
||||
expect(@dataSource.selection.toggle).toHaveBeenCalledWith(@itemFocus)
|
||||
|
||||
describe "onShift", ->
|
||||
describe "on the root view", ->
|
||||
|
@ -96,7 +96,7 @@ describe "MultiselectListInteractionHandler", ->
|
|||
|
||||
it "should walk selection if the select option is passed", ->
|
||||
@handler.onShift(1, select: true)
|
||||
expect(@dataView.selection.walk).toHaveBeenCalledWith({current: @itemKeyboardFocus, next: @itemAfterKeyboardFocus})
|
||||
expect(@dataSource.selection.walk).toHaveBeenCalledWith({current: @itemKeyboardFocus, next: @itemAfterKeyboardFocus})
|
||||
|
||||
describe "on the thread view", ->
|
||||
beforeEach ->
|
||||
|
|
|
@ -15,7 +15,7 @@ describe "MultiselectSplitInteractionHandler", ->
|
|||
|
||||
data = [@item, @itemFocus, @itemAfterFocus, @itemKeyboardFocus, @itemAfterKeyboardFocus]
|
||||
@selection = []
|
||||
@dataView =
|
||||
@dataSource =
|
||||
selection:
|
||||
toggle: jasmine.createSpy('toggle')
|
||||
expandTo: jasmine.createSpy('expandTo')
|
||||
|
@ -35,7 +35,7 @@ describe "MultiselectSplitInteractionHandler", ->
|
|||
count: -> data.length
|
||||
|
||||
@collection = 'threads'
|
||||
@handler = new MultiselectSplitInteractionHandler(@dataView, @collection)
|
||||
@handler = new MultiselectSplitInteractionHandler(@dataSource, @collection)
|
||||
@isRootSheet = true
|
||||
|
||||
spyOn(WorkspaceStore, 'topSheet').andCallFake => {root: @isRootSheet}
|
||||
|
@ -70,7 +70,7 @@ describe "MultiselectSplitInteractionHandler", ->
|
|||
|
||||
it "should turn the focused item into the first selected item", ->
|
||||
@handler.onMetaClick(@item)
|
||||
expect(@dataView.selection.add).toHaveBeenCalledWith(@itemFocus)
|
||||
expect(@dataSource.selection.add).toHaveBeenCalledWith(@itemFocus)
|
||||
|
||||
it "should clear the focus", ->
|
||||
@handler.onMetaClick(@item)
|
||||
|
@ -78,7 +78,7 @@ describe "MultiselectSplitInteractionHandler", ->
|
|||
|
||||
it "should toggle selection", ->
|
||||
@handler.onMetaClick(@item)
|
||||
expect(@dataView.selection.toggle).toHaveBeenCalledWith(@item)
|
||||
expect(@dataSource.selection.toggle).toHaveBeenCalledWith(@item)
|
||||
|
||||
it "should call _checkSelectionAndFocusConsistency", ->
|
||||
spyOn(@handler, '_checkSelectionAndFocusConsistency')
|
||||
|
@ -93,7 +93,7 @@ describe "MultiselectSplitInteractionHandler", ->
|
|||
|
||||
it "should turn the focused item into the first selected item", ->
|
||||
@handler.onMetaClick(@item)
|
||||
expect(@dataView.selection.add).toHaveBeenCalledWith(@itemFocus)
|
||||
expect(@dataSource.selection.add).toHaveBeenCalledWith(@itemFocus)
|
||||
|
||||
it "should clear the focus", ->
|
||||
@handler.onMetaClick(@item)
|
||||
|
@ -101,7 +101,7 @@ describe "MultiselectSplitInteractionHandler", ->
|
|||
|
||||
it "should expand selection", ->
|
||||
@handler.onShiftClick(@item)
|
||||
expect(@dataView.selection.expandTo).toHaveBeenCalledWith(@item)
|
||||
expect(@dataSource.selection.expandTo).toHaveBeenCalledWith(@item)
|
||||
|
||||
it "should call _checkSelectionAndFocusConsistency", ->
|
||||
spyOn(@handler, '_checkSelectionAndFocusConsistency')
|
||||
|
@ -127,13 +127,13 @@ describe "MultiselectSplitInteractionHandler", ->
|
|||
spyOn(FocusedContentStore, 'focused').andCallFake => @itemFocus
|
||||
spyOn(FocusedContentStore, 'focusedId').andCallFake -> 'focus'
|
||||
@handler.onShift(1, {select: true})
|
||||
expect(@dataView.selection.add).toHaveBeenCalledWith(@itemFocus)
|
||||
expect(@dataSource.selection.add).toHaveBeenCalledWith(@itemFocus)
|
||||
|
||||
it "should walk the selection to the shift target", ->
|
||||
spyOn(FocusedContentStore, 'focused').andCallFake => @itemFocus
|
||||
spyOn(FocusedContentStore, 'focusedId').andCallFake -> 'focus'
|
||||
@handler.onShift(1, {select: true})
|
||||
expect(@dataView.selection.walk).toHaveBeenCalledWith({current: @itemFocus, next: @itemAfterFocus})
|
||||
expect(@dataSource.selection.walk).toHaveBeenCalledWith({current: @itemFocus, next: @itemAfterFocus})
|
||||
|
||||
describe "when one or more items is selected", ->
|
||||
it "should move the keyboard cursor", ->
|
||||
|
@ -160,5 +160,5 @@ describe "MultiselectSplitInteractionHandler", ->
|
|||
|
||||
it "should clear the selection and make the item focused", ->
|
||||
@handler._checkSelectionAndFocusConsistency()
|
||||
expect(@dataView.selection.clear).toHaveBeenCalled()
|
||||
expect(@dataSource.selection.clear).toHaveBeenCalled()
|
||||
expect(Actions.setFocus).toHaveBeenCalledWith({collection: @collection, item: @item})
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
_ = require 'underscore'
|
||||
|
||||
Thread = require '../src/flux/models/thread'
|
||||
ModelView = require '../src/flux/stores/model-view'
|
||||
ModelViewSelection = require '../src/flux/stores/model-view-selection'
|
||||
ListDataSource = require '../src/flux/stores/list-data-source'
|
||||
ListSelection = require '../src/flux/stores/list-selection'
|
||||
|
||||
describe "ModelViewSelection", ->
|
||||
describe "ListSelection", ->
|
||||
beforeEach ->
|
||||
@trigger = jasmine.createSpy('trigger')
|
||||
|
||||
@items = []
|
||||
@items.push(new Thread(id: "#{ii}")) for ii in [0..99]
|
||||
|
||||
@view = new ModelView()
|
||||
@view = new ListDataSource()
|
||||
@view.indexOfId = jasmine.createSpy('indexOfId').andCallFake (id) =>
|
||||
_.findIndex(@items, _.matcher({id}))
|
||||
@view.get = jasmine.createSpy('get').andCallFake (idx) =>
|
||||
@items[idx]
|
||||
|
||||
@selection = new ModelViewSelection(@view, @trigger)
|
||||
@selection = new ListSelection(@view, @trigger)
|
||||
|
||||
it "should initialize with an empty set", ->
|
||||
expect(@selection.items()).toEqual([])
|
||||
expect(@selection.ids()).toEqual([])
|
||||
|
||||
it "should throw an exception if a view is not provided", ->
|
||||
expect( => new ModelViewSelection(null, @trigger)).toThrow()
|
||||
expect( => new ListSelection(null, @trigger)).toThrow()
|
||||
|
||||
describe "set", ->
|
||||
it "should replace the current selection with the provided models", ->
|
||||
|
|
|
@ -70,7 +70,7 @@ describe "MutableQueryResultSet", ->
|
|||
'C': {id: 'C', clientId: 'C-local'},
|
||||
})
|
||||
|
||||
fdescribe "addIdsInRange", ->
|
||||
describe "addIdsInRange", ->
|
||||
describe "when the set is currently empty", ->
|
||||
it "should set the result set to the provided one", ->
|
||||
@set = new MutableQueryResultSet()
|
||||
|
|
|
@ -68,7 +68,7 @@ class ListTabular extends React.Component
|
|||
@displayName = 'ListTabular'
|
||||
@propTypes =
|
||||
columns: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
|
||||
dataView: React.PropTypes.object
|
||||
dataSource: React.PropTypes.object
|
||||
itemPropsProvider: React.PropTypes.func
|
||||
itemHeight: React.PropTypes.number
|
||||
onSelect: React.PropTypes.func
|
||||
|
@ -93,7 +93,7 @@ class ListTabular extends React.Component
|
|||
componentDidUpdate: (prevProps, prevState) =>
|
||||
# If our view has been swapped out for an entirely different one,
|
||||
# reset our scroll position to the top.
|
||||
if prevProps.dataView isnt @props.dataView
|
||||
if prevProps.dataSource isnt @props.dataSource
|
||||
@refs.container.scrollTop = 0
|
||||
|
||||
unless @updateRangeStateFiring
|
||||
|
@ -120,7 +120,7 @@ class ListTabular extends React.Component
|
|||
# Expand the start/end so that you can advance the keyboard cursor fast and
|
||||
# we have items to move to and then scroll to.
|
||||
rangeStart = Math.max(0, rangeStart - 2)
|
||||
rangeEnd = Math.min(rangeEnd + 2, @props.dataView.count() + 1)
|
||||
rangeEnd = Math.min(rangeEnd + 2, @props.dataSource.count() + 1)
|
||||
|
||||
# Final sanity check to prevent needless work
|
||||
return if rangeStart is @state.renderedRangeStart and
|
||||
|
@ -128,7 +128,7 @@ class ListTabular extends React.Component
|
|||
|
||||
@updateRangeStateFiring = true
|
||||
|
||||
@props.dataView.setRetainedRange
|
||||
@props.dataSource.setRetainedRange
|
||||
start: rangeStart
|
||||
end: rangeEnd
|
||||
|
||||
|
@ -138,7 +138,7 @@ class ListTabular extends React.Component
|
|||
|
||||
render: =>
|
||||
innerStyles =
|
||||
height: @props.dataView.count() * @props.itemHeight
|
||||
height: @props.dataSource.count() * @props.itemHeight
|
||||
|
||||
<ScrollRegion
|
||||
ref="container"
|
||||
|
@ -155,7 +155,7 @@ class ListTabular extends React.Component
|
|||
rows = []
|
||||
|
||||
for idx in [@state.renderedRangeStart..@state.renderedRangeEnd-1]
|
||||
item = @props.dataView.get(idx)
|
||||
item = @props.dataSource.get(idx)
|
||||
continue unless item
|
||||
|
||||
itemProps = {}
|
||||
|
|
|
@ -43,7 +43,7 @@ class MultiselectActionBar extends React.Component
|
|||
###
|
||||
Public: React `props` supported by MultiselectActionBar:
|
||||
|
||||
- `dataStore` An instance of a {ModelView}.
|
||||
- `dataStore` An instance of a {ListDataSource}.
|
||||
- `collection` The name of the collection. The collection name is used for the text
|
||||
that appears in the bar "1 thread selected" and is also used to find components
|
||||
in the component registry that should appear in the bar (`thread` => `thread:BulkAtion`)
|
||||
|
|
|
@ -5,7 +5,7 @@ _ = require 'underscore'
|
|||
|
||||
module.exports =
|
||||
class MultiselectListInteractionHandler
|
||||
constructor: (@dataView, @collection) ->
|
||||
constructor: (@dataSource, @collection) ->
|
||||
|
||||
cssClass: ->
|
||||
'handler-list'
|
||||
|
@ -20,35 +20,35 @@ class MultiselectListInteractionHandler
|
|||
Actions.setFocus({collection: @collection, item: item})
|
||||
|
||||
onMetaClick: (item) ->
|
||||
@dataView.selection.toggle(item)
|
||||
@dataSource.selection.toggle(item)
|
||||
Actions.setCursorPosition({collection: @collection, item: item})
|
||||
|
||||
onShiftClick: (item) ->
|
||||
@dataView.selection.expandTo(item)
|
||||
@dataSource.selection.expandTo(item)
|
||||
Actions.setCursorPosition({collection: @collection, item: item})
|
||||
|
||||
onEnter: ->
|
||||
keyboardCursorId = FocusedContentStore.keyboardCursorId(@collection)
|
||||
if keyboardCursorId
|
||||
item = @dataView.getById(keyboardCursorId)
|
||||
item = @dataSource.getById(keyboardCursorId)
|
||||
Actions.setFocus({collection: @collection, item: item})
|
||||
|
||||
onSelect: ->
|
||||
{id} = @_keyboardContext()
|
||||
return unless id
|
||||
@dataView.selection.toggle(@dataView.getById(id))
|
||||
@dataSource.selection.toggle(@dataSource.getById(id))
|
||||
|
||||
onShift: (delta, options = {}) ->
|
||||
{id, action} = @_keyboardContext()
|
||||
|
||||
current = @dataView.getById(id)
|
||||
index = @dataView.indexOfId(id)
|
||||
index = Math.max(0, Math.min(index + delta, @dataView.count() - 1))
|
||||
next = @dataView.get(index)
|
||||
current = @dataSource.getById(id)
|
||||
index = @dataSource.indexOfId(id)
|
||||
index = Math.max(0, Math.min(index + delta, @dataSource.count() - 1))
|
||||
next = @dataSource.get(index)
|
||||
|
||||
action({collection: @collection, item: next})
|
||||
if options.select
|
||||
@dataView.selection.walk({current, next})
|
||||
@dataSource.selection.walk({current, next})
|
||||
|
||||
_keyboardContext: ->
|
||||
if WorkspaceStore.topSheet().root
|
||||
|
|
|
@ -16,7 +16,7 @@ MultiselectSplitInteractionHandler = require './multiselect-split-interaction-ha
|
|||
|
||||
###
|
||||
Public: MultiselectList wraps {ListTabular} and makes it easy to present a
|
||||
{ModelView} with selection support. It adds a checkbox column to the columns
|
||||
{ListDataSource} with selection support. It adds a checkbox column to the columns
|
||||
you provide, and also handles:
|
||||
|
||||
- Command-clicking individual items
|
||||
|
@ -98,7 +98,7 @@ class MultiselectList extends React.Component
|
|||
otherProps = _.omit(@props, _.keys(@constructor.propTypes))
|
||||
|
||||
className = @props.className
|
||||
if @state.dataView and @state.handler
|
||||
if @state.dataSource and @state.handler
|
||||
className += " " + @state.handler.cssClass()
|
||||
|
||||
@itemPropsProvider ?= (item) =>
|
||||
|
@ -114,7 +114,7 @@ class MultiselectList extends React.Component
|
|||
if @props.emptyComponent
|
||||
emptyElement = <@props.emptyComponent
|
||||
visible={@state.loaded and @state.empty}
|
||||
dataView={@state.dataView} />
|
||||
dataSource={@state.dataSource} />
|
||||
|
||||
spinnerElement = <Spinner visible={!@state.loaded and @state.empty} />
|
||||
|
||||
|
@ -124,7 +124,7 @@ class MultiselectList extends React.Component
|
|||
ref="list"
|
||||
columns={@state.computedColumns}
|
||||
scrollTooltipComponent={@props.scrollTooltipComponent}
|
||||
dataView={@state.dataView}
|
||||
dataSource={@state.dataSource}
|
||||
itemPropsProvider={@itemPropsProvider}
|
||||
itemHeight={@props.itemHeight}
|
||||
onSelect={@_onClickItem}
|
||||
|
@ -157,12 +157,12 @@ class MultiselectList extends React.Component
|
|||
|
||||
_onSelectAll: =>
|
||||
return unless @state.handler
|
||||
items = @state.dataView.itemsCurrentlyInViewMatching -> true
|
||||
@state.dataView.selection.set(items)
|
||||
items = @state.dataSource.itemsCurrentlyInViewMatching -> true
|
||||
@state.dataSource.selection.set(items)
|
||||
|
||||
_onDeselect: =>
|
||||
return unless @_visible() and @state.dataView
|
||||
@state.dataView.selection.clear()
|
||||
return unless @_visible() and @state.dataSource
|
||||
@state.dataSource.selection.clear()
|
||||
|
||||
_onShift: (delta, options = {}) =>
|
||||
return unless @state.handler
|
||||
|
@ -216,7 +216,7 @@ class MultiselectList extends React.Component
|
|||
else
|
||||
handler = new MultiselectSplitInteractionHandler(view, props.collection)
|
||||
|
||||
dataView: view
|
||||
dataSource: view
|
||||
handler: handler
|
||||
columns: props.columns
|
||||
computedColumns: computedColumns
|
||||
|
|
|
@ -5,7 +5,7 @@ _ = require 'underscore'
|
|||
|
||||
module.exports =
|
||||
class MultiselectSplitInteractionHandler
|
||||
constructor: (@dataView, @collection) ->
|
||||
constructor: (@dataSource, @collection) ->
|
||||
|
||||
cssClass: ->
|
||||
'handler-split'
|
||||
|
@ -14,21 +14,21 @@ class MultiselectSplitInteractionHandler
|
|||
true
|
||||
|
||||
shouldShowKeyboardCursor: ->
|
||||
@dataView.selection.count() > 1
|
||||
@dataSource.selection.count() > 1
|
||||
|
||||
onClick: (item) ->
|
||||
Actions.setFocus({collection: @collection, item: item, usingClick: true})
|
||||
@dataView.selection.clear()
|
||||
@dataSource.selection.clear()
|
||||
@_checkSelectionAndFocusConsistency()
|
||||
|
||||
onMetaClick: (item) ->
|
||||
@_turnFocusIntoSelection()
|
||||
@dataView.selection.toggle(item)
|
||||
@dataSource.selection.toggle(item)
|
||||
@_checkSelectionAndFocusConsistency()
|
||||
|
||||
onShiftClick: (item) ->
|
||||
@_turnFocusIntoSelection()
|
||||
@dataView.selection.expandTo(item)
|
||||
@dataSource.selection.expandTo(item)
|
||||
@_checkSelectionAndFocusConsistency()
|
||||
|
||||
onEnter: ->
|
||||
|
@ -40,39 +40,39 @@ class MultiselectSplitInteractionHandler
|
|||
if options.select
|
||||
@_turnFocusIntoSelection()
|
||||
|
||||
if @dataView.selection.count() > 0
|
||||
selection = @dataView.selection
|
||||
if @dataSource.selection.count() > 0
|
||||
selection = @dataSource.selection
|
||||
keyboardId = FocusedContentStore.keyboardCursorId(@collection)
|
||||
id = keyboardId ? @dataView.selection.top().id
|
||||
id = keyboardId ? @dataSource.selection.top().id
|
||||
action = Actions.setCursorPosition
|
||||
else
|
||||
id = FocusedContentStore.focusedId(@collection)
|
||||
action = Actions.setFocus
|
||||
|
||||
current = @dataView.getById(id)
|
||||
index = @dataView.indexOfId(id)
|
||||
index = Math.max(0, Math.min(index + delta, @dataView.count() - 1))
|
||||
next = @dataView.get(index)
|
||||
current = @dataSource.getById(id)
|
||||
index = @dataSource.indexOfId(id)
|
||||
index = Math.max(0, Math.min(index + delta, @dataSource.count() - 1))
|
||||
next = @dataSource.get(index)
|
||||
|
||||
action({collection: @collection, item: next})
|
||||
if options.select
|
||||
@dataView.selection.walk({current, next})
|
||||
@dataSource.selection.walk({current, next})
|
||||
|
||||
@_checkSelectionAndFocusConsistency()
|
||||
|
||||
_turnFocusIntoSelection: ->
|
||||
focused = FocusedContentStore.focused(@collection)
|
||||
Actions.setFocus({collection: @collection, item: null})
|
||||
@dataView.selection.add(focused)
|
||||
@dataSource.selection.add(focused)
|
||||
|
||||
_checkSelectionAndFocusConsistency: ->
|
||||
focused = FocusedContentStore.focused(@collection)
|
||||
selection = @dataView.selection
|
||||
selection = @dataSource.selection
|
||||
|
||||
if focused and selection.count() > 0
|
||||
@dataView.selection.add(focused)
|
||||
@dataSource.selection.add(focused)
|
||||
Actions.setFocus({collection: @collection, item: null})
|
||||
|
||||
if selection.count() is 1 and !focused
|
||||
Actions.setFocus({collection: @collection, item: selection.items()[0]})
|
||||
@dataView.selection.clear()
|
||||
@dataSource.selection.clear()
|
||||
|
|
46
src/flux/stores/list-data-source.coffee
Normal file
46
src/flux/stores/list-data-source.coffee
Normal file
|
@ -0,0 +1,46 @@
|
|||
_ = require 'underscore'
|
||||
EventEmitter = require('events').EventEmitter
|
||||
ListSelection = require './list-selection'
|
||||
|
||||
module.exports =
|
||||
class ListDataSource
|
||||
|
||||
constructor: ->
|
||||
@_emitter = new EventEmitter()
|
||||
@selection = new ListSelection(@, @trigger)
|
||||
@
|
||||
|
||||
# Accessing Data
|
||||
|
||||
trigger: (arg) =>
|
||||
@_emitter.emit('trigger', arg)
|
||||
|
||||
listen: (callback, bindContext) ->
|
||||
eventHandler = ->
|
||||
callback.apply(bindContext, arguments)
|
||||
@_emitter.addListener('trigger', eventHandler)
|
||||
return => @_emitter.removeListener('trigger', eventHandler)
|
||||
|
||||
loaded: ->
|
||||
throw new Error("ListDataSource base class does not implement loaded()")
|
||||
|
||||
empty: ->
|
||||
throw new Error("ListDataSource base class does not implement empty()")
|
||||
|
||||
get: (idx) ->
|
||||
throw new Error("ListDataSource base class does not implement get()")
|
||||
|
||||
getById: (id) ->
|
||||
throw new Error("ListDataSource base class does not implement getById()")
|
||||
|
||||
indexOfId: (id) ->
|
||||
throw new Error("ListDataSource base class does not implement indexOfId()")
|
||||
|
||||
count: ->
|
||||
throw new Error("ListDataSource base class does not implement count()")
|
||||
|
||||
itemsCurrentlyInViewMatching: (matchFn) ->
|
||||
throw new Error("ListDataSource base class does not implement itemsCurrentlyInViewMatching()")
|
||||
|
||||
setRetainedRange: ({start, end}) ->
|
||||
throw new Error("ListDataSource base class does not implement setRetainedRange()")
|
|
@ -2,10 +2,10 @@ Model = require '../models/model'
|
|||
_ = require 'underscore'
|
||||
|
||||
module.exports =
|
||||
class ModelViewSelection
|
||||
class ListSelection
|
||||
|
||||
constructor: (@_view, @trigger) ->
|
||||
throw new Error("new ModelViewSelection(): You must provide a view.") unless @_view
|
||||
throw new Error("new ListSelection(): You must provide a view.") unless @_view
|
||||
@_items = []
|
||||
|
||||
count: ->
|
|
@ -1,46 +0,0 @@
|
|||
_ = require 'underscore'
|
||||
EventEmitter = require('events').EventEmitter
|
||||
ModelViewSelection = require './model-view-selection'
|
||||
|
||||
module.exports =
|
||||
class ModelView
|
||||
|
||||
constructor: ->
|
||||
@_emitter = new EventEmitter()
|
||||
@selection = new ModelViewSelection(@, @trigger)
|
||||
@
|
||||
|
||||
# Accessing Data
|
||||
|
||||
trigger: (arg) =>
|
||||
@_emitter.emit('trigger', arg)
|
||||
|
||||
listen: (callback, bindContext) ->
|
||||
eventHandler = ->
|
||||
callback.apply(bindContext, arguments)
|
||||
@_emitter.addListener('trigger', eventHandler)
|
||||
return => @_emitter.removeListener('trigger', eventHandler)
|
||||
|
||||
loaded: ->
|
||||
throw new Error("ModelView base class does not implement loaded()")
|
||||
|
||||
empty: ->
|
||||
throw new Error("ModelView base class does not implement empty()")
|
||||
|
||||
get: (idx) ->
|
||||
throw new Error("ModelView base class does not implement get()")
|
||||
|
||||
getById: (id) ->
|
||||
throw new Error("ModelView base class does not implement getById()")
|
||||
|
||||
indexOfId: (id) ->
|
||||
throw new Error("ModelView base class does not implement indexOfId()")
|
||||
|
||||
count: ->
|
||||
throw new Error("ModelView base class does not implement count()")
|
||||
|
||||
itemsCurrentlyInViewMatching: (matchFn) ->
|
||||
throw new Error("ModelView base class does not implement itemsCurrentlyInViewMatching()")
|
||||
|
||||
setRetainedRange: ({start, end}) ->
|
||||
throw new Error("ModelView base class does not implement setRetainedRange()")
|
|
@ -5,7 +5,7 @@ Message = require '../models/message'
|
|||
QuerySubscriptionPool = require '../models/query-subscription-pool'
|
||||
QuerySubscription = require '../models/query-subscription'
|
||||
MutableQuerySubscription = require '../models/mutable-query-subscription'
|
||||
ModelView = require './model-view'
|
||||
ListDataSource = require './list-data-source'
|
||||
|
||||
###
|
||||
This class takes an observable which vends QueryResultSets and adapts it so that
|
||||
|
@ -14,7 +14,7 @@ you can make it the data source of a MultiselectList.
|
|||
When the MultiselectList is refactored to take an Observable, this class should
|
||||
go away!
|
||||
###
|
||||
class QueryResultSetView extends ModelView
|
||||
class ObservableListDataSource extends ListDataSource
|
||||
|
||||
constructor: ($resultSetObservable, @_setRetainedRange) ->
|
||||
super
|
||||
|
@ -31,6 +31,7 @@ class QueryResultSetView extends ModelView
|
|||
previousResultSet = @_resultSet
|
||||
@_resultSet = nextResultSet
|
||||
|
||||
@selection.updateModelReferences(@_resultSet.models())
|
||||
@trigger({previous: previousResultSet, next: nextResultSet})
|
||||
|
||||
setRetainedRange: ({start, end}) ->
|
||||
|
@ -64,4 +65,4 @@ class QueryResultSetView extends ModelView
|
|||
@_resultSet.models().filter(matchFn)
|
||||
|
||||
|
||||
module.exports = QueryResultSetView
|
||||
module.exports = ObservableListDataSource
|
|
@ -54,7 +54,7 @@ class NylasExports
|
|||
@load "DatabaseStore", 'flux/stores/database-store'
|
||||
@load "DatabaseTransaction", 'flux/stores/database-transaction'
|
||||
@load "QueryResultSet", 'flux/models/query-result-set'
|
||||
@load "QueryResultSetView", 'flux/stores/query-result-set-view'
|
||||
@load "ObservableListDataSource", 'flux/stores/observable-list-data-source'
|
||||
@load "QuerySubscription", 'flux/models/query-subscription'
|
||||
@load "MutableQuerySubscription", 'flux/models/mutable-query-subscription'
|
||||
@load "QuerySubscriptionPool", 'flux/models/query-subscription-pool'
|
||||
|
|
Loading…
Reference in a new issue