From 119da453e1a10d575572722d97d886c463dbd9f4 Mon Sep 17 00:00:00 2001 From: Evan Morikawa Date: Mon, 9 Nov 2015 10:03:55 -0500 Subject: [PATCH] feat(selection): add selection of read, unread, starred, etc Summary: Can select all, deselect-all, read, unread, starred, unstarred. Yes, it's not REALLY select "all", but it uses the items in the current `retainedRange`. This is actually similar to what gmail does (only selects on the first page of a 100). Test Plan: new test Reviewers: juan, bengotow Reviewed By: bengotow Differential Revision: https://phab.nylas.com/D2241 --- .../thread-list/lib/thread-list-store.coffee | 22 +++++++++++++++++++ keymaps/templates/Gmail.cson | 4 ++-- spec/model-view-spec.coffee | 8 +++++++ src/components/multiselect-list.cjsx | 8 +++++++ src/flux/stores/model-view.coffee | 7 ++++++ 5 files changed, 47 insertions(+), 2 deletions(-) diff --git a/internal_packages/thread-list/lib/thread-list-store.coffee b/internal_packages/thread-list/lib/thread-list-store.coffee index 2eb1f7b98..1ad692523 100644 --- a/internal_packages/thread-list/lib/thread-list-store.coffee +++ b/internal_packages/thread-list/lib/thread-list-store.coffee @@ -23,6 +23,12 @@ class ThreadListStore extends NylasStore @listenTo AccountStore, @_onAccountChanged @listenTo FocusedMailViewStore, @_onMailViewChanged + atom.commands.add "body", + 'thread-list:select-read' : @_onSelectRead + 'thread-list:select-unread' : @_onSelectUnread + 'thread-list:select-starred' : @_onSelectStarred + 'thread-list:select-unstarred': @_onSelectUnstarred + # We can't create a @view on construction because the CategoryStore # has hot yet been populated from the database with the list of # categories and their corresponding ids. Once that is ready, the @@ -81,6 +87,22 @@ class ThreadListStore extends NylasStore Actions.setFocus(collection: 'thread', item: null) + _onSelectRead: => + items = @_view.itemsCurrentlyInViewMatching (item) -> not item.unread + @_view.selection.set(items) + + _onSelectUnread: => + items = @_view.itemsCurrentlyInViewMatching (item) -> item.unread + @_view.selection.set(items) + + _onSelectStarred: => + items = @_view.itemsCurrentlyInViewMatching (item) -> item.starred + @_view.selection.set(items) + + _onSelectUnstarred: => + items = @_view.itemsCurrentlyInViewMatching (item) -> not item.starred + @_view.selection.set(items) + # Inbound Events _onMailViewChanged: -> diff --git a/keymaps/templates/Gmail.cson b/keymaps/templates/Gmail.cson index 167bfec11..21d8498af 100644 --- a/keymaps/templates/Gmail.cson +++ b/keymaps/templates/Gmail.cson @@ -13,8 +13,8 @@ 'g l' : 'navigation:go-to-label' ### Threadlist selection ### - '* a': 'thread-list:select-all' - '* n': 'thread-list:deselect-all' + '* a': 'multiselect-list:select-all' + '* n': 'multiselect-list:deselect-all' '* r': 'thread-list:select-read' '* u': 'thread-list:select-unread' '* s': 'thread-list:select-starred' diff --git a/spec/model-view-spec.coffee b/spec/model-view-spec.coffee index 88aba183b..1aee87ec4 100644 --- a/spec/model-view-spec.coffee +++ b/spec/model-view-spec.coffee @@ -33,6 +33,14 @@ describe "ModelView", -> beforeEach -> @view = new TestModelView() + describe "itemsCurrentlyInViewMatching", -> + it "returns matching items", -> + @view.stubFillPage(0) + items = @view.itemsCurrentlyInViewMatching (item) -> + item.id == "A55" + expect(items.length).toBe 1 + expect(items[0].id).toBe "A55" + describe "setRetainedRange", -> it "should perform basic bounds checks to avoid fetching non-existent pages", -> @view.setRetainedRange({start: -100, end: 15000}) diff --git a/src/components/multiselect-list.cjsx b/src/components/multiselect-list.cjsx index be3c9f712..11399edb1 100644 --- a/src/components/multiselect-list.cjsx +++ b/src/components/multiselect-list.cjsx @@ -83,6 +83,9 @@ class MultiselectList extends React.Component 'core:list-page-up': => @_onScrollByPage(-1) 'core:list-page-down': => @_onScrollByPage(1) 'application:pop-sheet': => @_onDeselect() + 'multiselect-list:select-all': @_onSelectAll + 'core:select-all': @_onSelectAll + 'multiselect-list:deselect-all': => @_onDeselect() render: => # IMPORTANT: DO NOT pass inline functions as props. _.isEqual thinks these @@ -152,6 +155,11 @@ class MultiselectList extends React.Component return unless @state.handler @state.handler.onSelect() + _onSelectAll: => + return unless @state.handler + items = @state.dataView.itemsCurrentlyInViewMatching -> true + @state.dataView.selection.set(items) + _onDeselect: => return unless @_visible() and @state.dataView @state.dataView.selection.clear() diff --git a/src/flux/stores/model-view.coffee b/src/flux/stores/model-view.coffee index 1cf7aca0c..f6809ba0e 100644 --- a/src/flux/stores/model-view.coffee +++ b/src/flux/stores/model-view.coffee @@ -73,6 +73,13 @@ class ModelView pagesRetained: -> [Math.floor(@_retainedRange.start / @_pageSize)..Math.floor(@_retainedRange.end / @_pageSize)] + itemsCurrentlyInViewMatching: (matchFn) -> + matchedItems = [] + for index, page of @_pages + for item in (page.items ? []) + matchedItems.push item if matchFn(item) + return matchedItems + setRetainedRange: ({start, end}) -> {start, end} = @padRetainedRange({start, end}) start = Math.max(0, Math.min(@count(), start))