Skip to content

add support for multiple selection in changes list#4188

Merged
niik merged 67 commits intodesktop:masterfrom
icosamuel:feature/multiple-file-selection
Apr 19, 2018
Merged

add support for multiple selection in changes list#4188
niik merged 67 commits intodesktop:masterfrom
icosamuel:feature/multiple-file-selection

Conversation

@icosamuel
Copy link
Contributor

@icosamuel icosamuel commented Mar 6, 2018

New feature

Added the ability to select multiple lines in the changes-list. Usable with shift and ctrl, as you would expect it from any other software.

Multi-selection with Shift and Cmd/Ctrl

screenshot from 2018-03-08 17-15-55

You can toggle the checkboxes (for later commiting) or use the contextual menu to ignore or discard the selected changes.

Discard selection

screenshot from 2018-03-06 15-50-37

This PR closes #1712

Impact

list.tsx
All components using List must encapsulate their selectedRow in a single-element array if they are not using the multiple-selection feature. The onSelectionChanged(selectedItem: number): void is now split into tree distinct callbacks which you can choose to use depending on the situation:

onSelectedRowChanged(index: number): void
onSelectedRangeChanged(start: number, end: number): void
onSelectionChanged(selectedItems: ReadonlyArray<number>): void

Their usage (as well as the possible selection mechanism) are governed by the new property which is assumed to be'single' by default

selectionMode: 'single' | 'range' | 'multi'

changes-list.tsx
is not working with the list in 'multi'mode and stores its selection in this.props.selectedFilesID: string[]

Some features (like the diff pannel, or the "show in your file manager") use only one file-change as their source. For those, I have them set up to get the last selected Item (wich will also be the focused one)

app-state.ts
in the app store, the selection is also stored as an array of ids readonly selectedFilesID: string[]


public render() {
// ripped from https://stackoverflow.com/a/9229821/1964166
private static Uniquify(array: string[]) {

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

@niik niik self-assigned this Mar 7, 2018
@niik
Copy link
Member

niik commented Mar 7, 2018

Hey @icosamuel, thank you so much for contributing! This is impressive work. We'll have time to get into the details of the implementation as we're reviewing of this further but I wanted to start with some general thoughts.

My hope is that we choose a color that will augment visibility and contrast, and will also please the core team. Any suggestion is welcome!

I think what we should do here is keep the original color for inactive selection but change the behavior so that a list item is treated as being actively selected (i.e. selected+focused) as long as focus remains within the List itself. See FocusContainer and its use in CommitMessage for more information of the behavior I'm talking about.

screen shot 2018-03-07 at 15 40 25

That matches the macOS behavior.

screen shot 2018-03-07 at 15 41 52

On Windows we can look into whether we want to bring back the dotted focus outline for the item that currently has keyboard focus or not.

I believe this could also potential have the benefit of solving #2957.

Some features (like the diff pannel, or the "show in your file manager") use only one file-change as their source. For those, I have them set up to get the last selected Item (wich will also be the focused one)

I would want to have this be more explicit. Ideally there's three type of list selection "modes" that we'd want to support here. There's single selection (which should be the default), "range" which would only allow for selecting contiguous ranges and "multi" (for lack of a better word) which allows range and/or arbitrary selection (Cmd/Ctrl). The file list is a good example of where the latter mode works well and while not implemented yet the range selection will be a key piece of the planned comparison work we're doing for 1.2 where you should be able to diff a range of commits but never arbitrary non-contiguous commits.

I'd propose a new "mode" prop for the list component that governs selection logic and dedicated callbacks for the different modes so that consumers can chose to only subscribe to the callback they care about. I.e.

onSelectedRowChanged(index: number): void
onSelectedRangeChanged(start: number, end: number): void
onSelectionChanged(selectedItems: ReadonlyArray<number>): void

Some features (like the diff pannel, or the "show in your file manager") use only one file-change as their source. For those, I have them set up to get the last selected Item (wich will also be the focused one)

As for the UI implications of this, speaking specifically about the Diff view I think we should play around with the idea of having a dedicated view for multi-selections. Ideally we'd show all diffs for the selected files stacked on top of each other but I believe we're unable to do so at the moment due to performance concerns. I think a dedicated view could help out with discoverability though. Here's a super-quick mock of how something like that could look.

screen shot 2018-03-07 at 16 11 08

All-in, a great start here @icosamuel!

cc @donokuda for visibility and more design thoughts.

@j-f1
Copy link
Contributor

j-f1 commented Mar 7, 2018

As for the UI implications of this, speaking specifically about the Diff view I think we should play around with the idea of having a dedicated view for multi-selections.

What about putting the diffs together in a scroll view?

@icosamuel
Copy link
Contributor Author

icosamuel commented Mar 7, 2018

@niik Thanks for the feedback

I'm afraid that we can't focus on multiple HTMLElements. We would have to make a new css class for this state (or is that what you suggested?). Although, you will see that the TAB behaviour is now looping through selected rows before changing context. On top of that, I find the distinction between selected and focused to be useful for single element operations.

I really like the idea of List modes: "single" | "range" | "multi". This will make the three callbacks facultative, but will provide for a flexible interface.

As for the UI implications of this, speaking specifically about the Diff view I think we should play around with the idea of having a dedicated view for multi-selections.

Since my original goal was to provide multiple selection for commit, discard and ignore, I personally won't get into a diff range view for the time being. That being said, I like the idea of the simple dedicated page and I welcome any help from other contributors for this part!

Cheers!

@niik
Copy link
Member

niik commented Mar 8, 2018

I'm afraid that we can't focus on multiple HTMLElements. We would have to make a new css class for this state (or is that what you suggested?).

Yup, that's exactly what I'm suggesting. We'd add a '.focus-inside' class to the list whenever keyboard focus resides on, or inside of the list wrapper element. We'd then use that to style the list items (.list.focus-inside .list-item) rather than the .list-item:focus pseudo element.

you will see that the TAB behaviour is now looping through selected rows before changing context

Yeah, I think that's an unintended side-effect of

tabIndex = selected ? 0 : -1
that we need to tweak now that we're allowing multiple selections. Only on item in the list should be directly focusable.

On top of that, I find the distinction between selected and focused to be useful for single element operations.

I think platform consistency will have to take precedence here. There's precedence on Windows (at least in older versions) for rendering an inset focus outline for the selected+focused item but other than that I believe that all selected items in the list should have the same blue selected background color (and white foreground) for as long as the list itself has focus.

That being said, I like the idea of the simple dedicated page and I welcome any help from other contributors for this part!

That part was more me thinking out loud and trying to figure out what the implications of this will be. I don't think it has to be part of this PR but we'll either feature-flag the multi-selection behavior until we can land that or scope this PR more tightly so that we're only dealing with the list improvements.

* user keyboard or mouse action (i.e. not when props change). This function
* will not be invoked when an already selected row is clicked on.
* @param start - The index of the first selected row
* @param end - The index of the last selected row

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

* a keyboard event (arrow up/down)
*/
readonly onSelectionChanged?: (
row: number | number[],

This comment was marked as spam.

This comment was marked as spam.

if (selectedFileID !== selectedFileIDBeforeLoad) {
if (
selectedFilesID.length !== selectedFilesIDBeforeLoad.length ||
selectedFilesIDBeforeLoad.some((element, index) => {

This comment was marked as spam.

This comment was marked as spam.

const selectedRowChanged =
prevProps.selectedRow !== this.props.selectedRow
prevProps.selectedRows.length !== this.props.selectedRows.length ||
this.props.selectedRows.some((element, index) => {

This comment was marked as spam.

This comment was marked as spam.

event,
})
}
} else if (event.ctrlKey && this.props.selectionMode === 'multi') {

This comment was marked as spam.

This comment was marked as spam.

* for keyboard selection.
*/
readonly selectedRow: number
readonly selectedRows: number[]

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

@donokuda
Copy link
Contributor

donokuda commented Mar 8, 2018

As for the UI implications of this, speaking specifically about the Diff view I think we should play around with the idea of having a dedicated view for multi-selections. Ideally we'd show all diffs for the selected files stacked on top of each other but I believe we're unable to do so at the moment due to performance concerns. I think a dedicated view could help out with discoverability though. Here's a super-quick mock of how something like that could look.

💯 I think for the sake of moving this PR forward, we can have a simple view that says "# files selected." We can open a separate issue and start implementing those actions from there.

I noticed that toggling multiple checkboxes only works with the keyboard. Is there an expectation that this should also happen when clicking the checkbox with multiple files selected? Are there any UI patterns on macOS or Windows that could tell us?

@niik
Copy link
Member

niik commented Mar 9, 2018

I noticed that toggling multiple checkboxes only works with the keyboard. Is there an expectation that this should also happen when clicking the checkbox with multiple files selected? Are there any UI patterns on macOS or Windows that could tell us?

This is a great question. We should definitely look into it. The checkboxes certainly adds some complexity. Without them I'd say that clicking on a single item would revert any range or multi selection to a single item selection. But with the checkboxes it's almost like it's a weird click-through mode.

The only precedence I could think of straight away was Windows File Explorer which does use the behavior you described (except it doesn't support using the space bar for toggling selection).

multi-select-toggle

Apologies for the weird gif. LiceCap on my old PC is not cooperating.

On macOS the Reminders app has a different behavior

multi-select-toggle-mac

@icosamuel
Copy link
Contributor Author

@niik @donokuda I don't mind, really. Just tell me which behaviour you prefer ;) at the moment it selects one row and then toggles its state

@donokuda
Copy link
Contributor

donokuda commented Mar 12, 2018

Let's go with selecting one row and toggling its state (as in, how it works right now.) Currently hitting spacebar when multiple files are selected is closer to simultaneously clicking on every checkbox. Given how each platform does it differently, I think that it's okay to keeping it to how it currently works.

I also don't anticipate any huge consequences to workflows as a result of going with this direction.

@tierninho
Copy link
Contributor

I tested this and it works well, however I spotted one inconsistency which looks to be new behavior.

Reproduce:

  • Select 2 of X files and navigate away from Desktop to another app
  • Return to Desktop and notice the selected files are now highlighted in grey (expected)
  • Click into the Changes area and now the highlighted files are blue
  • Try to use the spacebar to check/uncheck the files. Nothing happens.

Expected behavior: I can use space-bar to check/uncheck the files

@icosamuel
Copy link
Contributor Author

icosamuel commented Mar 13, 2018

I see, @tierninho . Thanks for the tests
Looks like this is not new behaviour, but it is now misleading since the rows are shown to be selected, even if the focus in on the list's frame.

We will have to also bind the list's key input event to toggle the selected elements.

@icosamuel
Copy link
Contributor Author

Impressive work, @niik! It was interesting to read and I'm grateful for the opportunity to learn from your reviews and changes!

@LautaroL20
Copy link

Many people is following the progress of this feature and we are willing to get it on the app asap!

selectedFileIDs.forEach(fileID => {
const file = changesState.workingDirectory.findFileWithID(fileID)
if (file) {
selectedFiles.push(file)

This comment was marked as spam.

@@ -194,23 +239,27 @@ export class ChangesList extends React.Component<IChangesListProps, {}> {
event.preventDefault()

const fileList = this.props.workingDirectory.files

This comment was marked as spam.

Copy link
Member

@shiftkey shiftkey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more usability thing I picked up while testing...

oldPath={file.oldPath}
include={includeAll}
key={file.id}
onContextMenu={this.onItemContextMenu}

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

@niik
Copy link
Member

niik commented Apr 19, 2018

Impressive work, @niik! It was interesting to read and I'm grateful for the opportunity to learn from your reviews and changes!

Thank you @icosamuel. The impressive work is all on your end. This is a fantastic PR and we're all very excited to land it. @shiftkey just did a review of the additions and tweaks that I made so we should be very close to merging now.

niik and others added 2 commits April 19, 2018 15:50
Co-Authored-By: icosamuel <begs1307@gmail.com>
Co-Authored-By: icosamuel <begs1307@gmail.com>
@niik niik merged commit c463915 into desktop:master Apr 19, 2018
@niik
Copy link
Member

niik commented Apr 19, 2018

tumblr_lp0k2gfq1s1qh2o7zo1_500

@LautaroL20
Copy link

Fantastic work guys, a feature that have been wished by many users, has now become a reality.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants