Skip to content

Commit

Permalink
Support multi-selection in file lists (#1136)
Browse files Browse the repository at this point in the history
* enable multiple selection with control-click without functionality

* Implemented multiple file selection with control-click

* Remove handling this.state.selectedFiles is null in FileList selectFile method

* Enable toggle select/deselect files with control-click

* Implement Shift-Click file selection

Shift-click behaviour is not ideal. Failed to simulate shift-click behaviour on Google Drive.
Issues:
1. Can't deselect when shift click direction changes

Other issues:
1. IStatusFile object equality checking leads to a lot of duplicate codes
2. File status changes is not immediately reflected in FileList state, leading to invalid commands in context menu

* extract file comparison logic and remove duplicate selected files due to shift click

* limit multiple file selection by control-click to within the same category (git status)

* rename replaceSelectedFile to selectOnlyOneFile for better comprehension

* shift-click on a file with a different category (git status) becomes a simple click, handle partially staged files for shift clicks

* Make FileItem action commands (open, diff and stage file) apply to selected files

* Make FileItem action commands (open, diff and unstage file) apply to selected files

* Make FileItem action commands (discarding changes and staging file) apply to selected files

* fix bug - open correct files on clicking action button

* fix bug - diff correct files on clicking action button

* fix bug - unstage correct files on clicking action button

* fix bug - stage correct files on clicking action button

* fix bug - discard changes on correct files on clicking action button

* fix FileItem test

* Rename function filesAreEqual to areFilesEqual

Use _isSelectedFile where appropriate

* Remove selectedFileStatus from state

* change _openDiffViews function to async and await the execute command

* Remove unnecessary code in openContextMenu

* Ensure selectFiles arrives at correct state

* implement single selection callback 'setSelection'

* rework _selectUntilFile to index in sub array

* fix FileItem test

* implement advanced shift-click behaviours

* Added label to wrap around div object to allow click on whole div element to control checkbox

* Fixed label placement issue

* Styled filename div

* Fixed styling issue by importing and applying fileLabelStyle to the appropriate label container

* tried implementing shiftclick but unsuccessful:

* Fixed issue with shift click when clicking in reverse order

* Implement markedFile getter in gitModel

* implement shift click

problem: the clicked file is not selected

* Fix bug for shift-click for simple staging

* Clean up

* remove console logs

* insert  in FileItemStyle.ts

* added simple ui tests for ctlr and shift click on file selection for normal staging

* Add skeleton code for implementing de/select all button for simple staging

* Fix original ui tests

* Changed file name in ui-tests README and have the select all in simple staging selecting all files

* Changed name of variables and function from markAllFile to toggleAllFiles for clarity

* Added the ability to deselect items using select all

* Simplified code in toggleAllFiles to use state and removed redundant code.

* Improve mark-all button behaviour - sync checked state of the box to reflect if all files are marked

* Fix ui tests

* Add ui tests for simple staging

* Clear all signal connections when unmounting FileList

Co-authored-by: Frédéric Collonval <[email protected]>

* Delete commented code in GitPanel

Co-authored-by: Frédéric Collonval <[email protected]>

* Delete unused prop from interface (GitStage)

Co-authored-by: Frédéric Collonval <[email protected]>

* Improve consistency and fix bug

* fix ui test readme

* Change control-click ui test to expect exactly 2 selected items

* Stop propagation when users click on action buttons on FileItem

* Add doc strings for functions

* Document helper functions

Co-authored-by: iflinda <[email protected]>
Co-authored-by: Frédéric Collonval <[email protected]>
  • Loading branch information
3 people authored Jul 28, 2022
1 parent 2eabf94 commit 2091f48
Show file tree
Hide file tree
Showing 10 changed files with 746 additions and 168 deletions.
114 changes: 75 additions & 39 deletions src/components/FileItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import * as React from 'react';
import { classes } from 'typestyle';
import { GitExtension } from '../model';
import {
checkboxLabelContainerStyle,
checkboxLabelLastContainerStyle,
checkboxLabelStyle,
fileChangedLabelBrandStyle,
fileChangedLabelInfoStyle,
fileChangedLabelStyle,
Expand All @@ -12,6 +15,7 @@ import {
selectedFileChangedLabelStyle,
selectedFileStyle
} from '../style/FileItemStyle';
import { fileLabelStyle } from '../style/FilePathStyle';
import { Git } from '../tokens';
import { FilePath } from './FilePath';

Expand Down Expand Up @@ -44,20 +48,16 @@ interface IGitMarkBoxProps {
* File status
*/
stage: Git.Status;
/**
* Whether the checkbox is checked
*/
checked: boolean;
}

/**
* Render the selection box in simple mode
*/
class GitMarkBox extends React.PureComponent<IGitMarkBoxProps> {
protected _onClick = (): void => {
// toggle will emit a markChanged signal
this.props.model.toggleMark(this.props.fname);

// needed if markChanged doesn't force an update of a parent
this.forceUpdate();
};

protected _onDoubleClick = (
event: React.MouseEvent<HTMLInputElement>
): void => {
Expand All @@ -76,8 +76,7 @@ class GitMarkBox extends React.PureComponent<IGitMarkBoxProps> {
name="gitMark"
className={gitMarkBoxStyle}
type="checkbox"
checked={this.props.model.getMark(this.props.fname)}
onChange={this._onClick}
checked={this.props.checked}
onDoubleClick={this._onDoubleClick}
/>
);
Expand Down Expand Up @@ -117,9 +116,12 @@ export interface IFileItemProps {
*/
selected?: boolean;
/**
* Callback to select the file
* Callback to select file(s)
*/
selectFile?: (file: Git.IStatusFile | null) => void;
setSelection?: (
file: Git.IStatusFile,
options?: { singleton?: boolean; group?: boolean }
) => void;
/**
* Optional style class
*/
Expand All @@ -132,12 +134,40 @@ export interface IFileItemProps {
* The application language translator.
*/
trans: TranslationBundle;
/**
* Callback to implement shift-click for simple staging
*/
markUntilFile?: (file: Git.IStatusFile) => void;
/**
* whether the GitMarkBox is checked
*/
checked?: boolean;
}

export class FileItem extends React.PureComponent<IFileItemProps> {
constructor(props: IFileItemProps) {
super(props);
}

protected _onClick = (event: React.MouseEvent<HTMLInputElement>): void => {
if (this.props.markBox) {
if (event.shiftKey) {
this.props.markUntilFile(this.props.file);
} else {
this.props.model.toggleMark(this.props.file.to);
this.props.setSelection(this.props.file, { singleton: true });
}
} else {
if (event.ctrlKey || event.metaKey) {
this.props.setSelection(this.props.file);
} else if (event.shiftKey) {
this.props.setSelection(this.props.file, { group: true });
} else {
this.props.setSelection(this.props.file, { singleton: true });
}
}
};

protected _getFileChangedLabel(change: keyof typeof STATUS_CODES): string {
return STATUS_CODES[change] || 'Unmodified';
}
Expand Down Expand Up @@ -190,11 +220,10 @@ export class FileItem extends React.PureComponent<IFileItemProps> {

return (
<div
data-test-selected={this.props.selected}
data-test-checked={this.props.checked}
className={this._getFileClass()}
onClick={
this.props.selectFile &&
(() => this.props.selectFile(this.props.file))
}
onClick={this._onClick}
onContextMenu={
this.props.contextMenu &&
(event => {
Expand All @@ -205,29 +234,36 @@ export class FileItem extends React.PureComponent<IFileItemProps> {
style={this.props.style}
title={this.props.trans.__(`%1 • ${status}`, this.props.file.to)}
>
{this.props.markBox && (
<GitMarkBox
fname={this.props.file.to}
stage={this.props.file.status}
model={this.props.model}
/>
)}
<FilePath
filepath={this.props.file.to}
filetype={this.props.file.type}
/>
{this.props.actions}
<span
className={this._getFileChangedLabelClass(
this.props.file.status === 'unmerged' ? '!' : this.props.file.y
)}
>
{this.props.file.status === 'unmerged'
? '!'
: this.props.file.y === '?'
? 'U'
: status_code}
</span>
<div className={checkboxLabelContainerStyle}>
<div className={checkboxLabelStyle + ' ' + fileLabelStyle}>
{this.props.markBox && (
<GitMarkBox
fname={this.props.file.to}
stage={this.props.file.status}
model={this.props.model}
checked={this.props.checked}
/>
)}
<FilePath
filepath={this.props.file.to}
filetype={this.props.file.type}
/>
</div>
<div className={checkboxLabelLastContainerStyle}>
{this.props.actions}
<span
className={this._getFileChangedLabelClass(
this.props.file.status === 'unmerged' ? '!' : this.props.file.y
)}
>
{this.props.file.status === 'unmerged'
? '!'
: this.props.file.y === '?'
? 'U'
: status_code}
</span>
</div>
</div>
</div>
);
}
Expand Down
Loading

0 comments on commit 2091f48

Please sign in to comment.