From dd736ddf67df334aa820ad508f76cd8d5adb3c8d Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 24 Apr 2012 12:51:01 -0600 Subject: [PATCH] TreeView directories can be selected by clicking them. Start on keyboard nav. --- spec/extensions/tree-view-spec.coffee | 56 ++++++++++++++++++++------- src/extensions/tree-view.coffee | 42 +++++++++++++------- static/tree-view.css | 2 +- 3 files changed, 73 insertions(+), 27 deletions(-) diff --git a/spec/extensions/tree-view-spec.coffee b/spec/extensions/tree-view-spec.coffee index ff9e4c285..401187a7c 100644 --- a/spec/extensions/tree-view-spec.coffee +++ b/spec/extensions/tree-view-spec.coffee @@ -15,8 +15,8 @@ describe "TreeView", -> describe ".initialize(project)", -> it "renders the root of the project and its contents alphabetically with subdirectories first in a collapsed state", -> - expect(rootDirectoryView.find('> .disclosure-arrow')).toHaveText('▾') - expect(rootDirectoryView.find('> .name')).toHaveText('fixtures/') + expect(rootDirectoryView.find('> .header .disclosure-arrow')).toHaveText('▾') + expect(rootDirectoryView.find('> .header .name')).toHaveText('fixtures/') rootEntries = rootDirectoryView.find('.entries') subdir1 = rootEntries.find('> li:eq(0)') @@ -34,27 +34,26 @@ describe "TreeView", -> describe "when a directory's disclosure arrow is clicked", -> it "expands / collapses the associated directory", -> - subdir = rootDirectoryView.find('.entries > li:contains(dir/)') + subdir = rootDirectoryView.find('.entries > li:contains(dir/)').view() - disclosureArrow = subdir.find('.disclosure-arrow') - expect(disclosureArrow).toHaveText('▸') + expect(subdir.disclosureArrow).toHaveText('▸') expect(subdir.find('.entries')).not.toExist() - disclosureArrow.click() + subdir.disclosureArrow.click() - expect(disclosureArrow).toHaveText('▾') + expect(subdir.disclosureArrow).toHaveText('▾') expect(subdir.find('.entries')).toExist() - disclosureArrow.click() - expect(disclosureArrow).toHaveText('▸') + subdir.disclosureArrow.click() + expect(subdir.disclosureArrow).toHaveText('▸') expect(subdir.find('.entries')).not.toExist() it "restores the expansion state of descendant directories", -> - child = rootDirectoryView.find('.entries > li:contains(dir/)') - child.find('> .disclosure-arrow').click() + child = rootDirectoryView.find('.entries > li:contains(dir/)').view() + child.disclosureArrow.click() - grandchild = child.find('.entries > li:contains(a-dir/)') - grandchild.find('> .disclosure-arrow').click() + grandchild = child.find('.entries > li:contains(a-dir/)').view() + grandchild.disclosureArrow.click() rootDirectoryView.find('> .disclosure-arrow').click() rootDirectoryView.find('> .disclosure-arrow').click() @@ -84,6 +83,12 @@ describe "TreeView", -> expect(treeView.find('.selected').length).toBe 1 expect(rootView.activeEditor().buffer.path).toBe require.resolve('fixtures/sample.txt') + describe "when a directory is clicked", -> + it "is selected", -> + subdir = rootDirectoryView.find('.directory:first').view() + subdir.click() + expect(subdir).toHaveClass 'selected' + describe "when a new file is opened in the active editor", -> it "is selected in the tree view if visible", -> sampleJs.click() @@ -102,3 +107,28 @@ describe "TreeView", -> expect(sampleTxt).toHaveClass('selected') leftEditor.focus() expect(sampleJs).toHaveClass('selected') + + describe "keyboard navigation", -> + afterEach -> + expect(treeView.find('.selected').length).toBeLessThan 2 + + describe "move-down", -> + describe "if nothing is selected", -> + it "selects the first entry", -> + treeView.trigger 'move-down' + expect(rootDirectoryView).toHaveClass 'selected' + + describe "if a collapsed directory is selected", -> + it "skips to the next directory", -> + rootDirectoryView.find('.directory:eq(0)').click() + treeView.trigger 'move-down' + expect(rootDirectoryView.find('.directory:eq(1)')).toHaveClass 'selected' + + describe "if an expanded directory is selected", -> + it "selects the first entry of the directory", -> + + describe "if the last entry of an expanded directory is selected", -> + it "selects the entry after its parent directory", -> + + describe "if the last entry of the last directory is selected", -> + it "does not change the selection", -> diff --git a/src/extensions/tree-view.coffee b/src/extensions/tree-view.coffee index 3683a672e..5ff8f525d 100644 --- a/src/extensions/tree-view.coffee +++ b/src/extensions/tree-view.coffee @@ -13,26 +13,41 @@ class TreeView extends View @subview 'root', new DirectoryView(directory: rootView.project.getRootDirectory(), isExpanded: true) initialize: (@rootView) -> - @on 'click', '.file', (e) => - clickedLi = $(e.target) - @rootView.open(clickedLi.attr('path')) - @find('.selected').removeClass('selected') - clickedLi.addClass('selected') + @on 'click', '.entry', (e) => + entry = $(e.currentTarget) + @rootView.open(entry.attr('path')) if entry.is('.file') + @selectEntry(entry) + false + @on 'move-down', => @moveDown() @on 'tree-view:expand-directory', => @selectActiveFile() @rootView.on 'active-editor-path-change', => @selectActiveFile() selectActiveFile: -> - console.log "" - @find('.selected').removeClass('selected') activeFilePath = @rootView.activeEditor()?.buffer.path - @find(".file[path='#{activeFilePath}']").addClass('selected') + @selectEntry(@find(".file[path='#{activeFilePath}']")) + + moveDown: -> + selectedEntry = @selectedEntry() + + if selectedEntry.length + @selectEntry(selectedEntry.next()) + else + @selectEntry(@root) + + selectedEntry: -> + @find('.selected') + + selectEntry: (entry) -> + @find('.selected').removeClass('selected') + entry.addClass('selected') class DirectoryView extends View @content: ({directory, isExpanded}) -> - @li class: 'directory', => - @span '▸', class: 'disclosure-arrow', outlet: 'disclosureArrow', click: 'toggleExpansion' - @span directory.getName(), class: 'name' + @li class: 'directory entry', => + @div class: 'header', => + @span '▸', class: 'disclosure-arrow', outlet: 'disclosureArrow', click: 'toggleExpansion' + @span directory.getName(), class: 'name' entries: null @@ -45,7 +60,7 @@ class DirectoryView extends View if entry instanceof Directory @entries.append(new DirectoryView(directory: entry, isExpanded: false)) else - @entries.append $$ -> @li entry.getName(), class: 'file', path: entry.path + @entries.append $$ -> @li entry.getName(), class: 'file entry', path: entry.path @append(@entries) toggleExpansion: -> @@ -59,6 +74,7 @@ class DirectoryView extends View @deserializeEntries(@entryStates) if @entryStates? @isExpanded = true @trigger 'tree-view:expand-directory' + false collapse: -> @entryStates = @serializeEntries() @@ -77,7 +93,7 @@ class DirectoryView extends View deserializeEntries: (entryStates) -> for directoryName, childEntryStates of entryStates - @entries.find("> .directory:contains(#{directoryName})").each -> + @entries.find("> .directory:contains('#{directoryName}')").each -> view = $(this).view() view.entryStates = childEntryStates view.expand() diff --git a/static/tree-view.css b/static/tree-view.css index 42c47cc77..cf318404e 100644 --- a/static/tree-view.css +++ b/static/tree-view.css @@ -16,7 +16,7 @@ padding-left: 2ex; } -.tree-view .directory .selected { +.tree-view .file.selected, .tree-view .directory.selected > .header { background: #333; }