Skip to content

WIP - File checkbox #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 130 additions & 7 deletions packages/filebrowser/src/listing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,9 @@ export class DirListing extends Widget {
*/
selectNext(keepExisting = false): void {
let index = -1;
const selected = Object.keys(this.selection);
const selected = this._softSelection
? [this._softSelection]
: Object.keys(this.selection);
const items = this._sortedItems;
if (selected.length === 1 || keepExisting) {
// Select the next item.
Expand Down Expand Up @@ -528,7 +530,9 @@ export class DirListing extends Widget {
*/
selectPrevious(keepExisting = false): void {
let index = -1;
const selected = Object.keys(this.selection);
const selected = this._softSelection
? [this._softSelection]
: Object.keys(this.selection);
const items = this._sortedItems;
if (selected.length === 1 || keepExisting) {
// Select the previous item.
Expand Down Expand Up @@ -630,6 +634,7 @@ export class DirListing extends Widget {
throw new Error('Item does not exist.');
}
this._selectItem(index, false, focus);
// TODO? the line above already calls the line below? _selectItem() -> update() -> sendMessage()?
MessageLoop.sendMessage(this, Widget.Msg.UpdateRequest);
ElementExt.scrollIntoViewIfNeeded(this.contentNode, this._items[index]);
}
Expand Down Expand Up @@ -786,11 +791,35 @@ export class DirListing extends Widget {

// Remove extra classes from the nodes.
nodes.forEach(item => {
const checkbox = item.querySelector('input[type="checkbox"');
if (checkbox) {
(checkbox as HTMLInputElement).checked = false;
}
item.classList.remove(SELECTED_CLASS);
item.classList.remove(RUNNING_CLASS);
item.classList.remove(CUT_CLASS);
});

// Put the check-all checkbox into the correct state
const checkAllCheckbox = this.headerNode.querySelector(
'input[type=checkbox]'
) as HTMLInputElement;
const totalSelected = Object.keys(this.selection).length;
console.log({ totalSelected, totalItems: items.length });
if (totalSelected === items.length) {
checkAllCheckbox.indeterminate = false;
checkAllCheckbox.dataset.indeterminate = 'false';
checkAllCheckbox.checked = true;
} else if (totalSelected === 0) {
checkAllCheckbox.indeterminate = false;
checkAllCheckbox.dataset.indeterminate = 'false';
checkAllCheckbox.checked = false;
} else {
checkAllCheckbox.indeterminate = true;
checkAllCheckbox.checked = false;
checkAllCheckbox.dataset.indeterminate = 'true';
}

// Add extra classes to item nodes based on widget state.
items.forEach((item, i) => {
const node = nodes[i];
Expand All @@ -803,6 +832,10 @@ export class DirListing extends Widget {
this._hiddenColumns
);
if (this.selection[item.path]) {
const checkbox = node.querySelector('input[type="checkbox"]');
if (checkbox) {
(checkbox as HTMLInputElement).checked = true;
}
node.classList.add(SELECTED_CLASS);

if (this._isCut && this._model.path === this._prevPath) {
Expand Down Expand Up @@ -880,6 +913,25 @@ export class DirListing extends Widget {

const header = this.headerNode;
if (header.contains(target)) {
const checkAllCheckbox = header.querySelector(
'input[type=checkbox]'
) as HTMLInputElement;
if (target.contains(checkAllCheckbox)) {
const shouldCheckAll =
checkAllCheckbox.dataset.indeterminate !== 'true' &&
checkAllCheckbox.checked;

for (const item of this._sortedItems) {
if (shouldCheckAll) {
this.selection[item.path] = true;
} else {
delete this.selection[item.path];
}
}
this.update();
return;
}

const state = this.renderer.handleHeaderClick(header, event);
if (state) {
this.sort(state);
Expand Down Expand Up @@ -1047,13 +1099,50 @@ export class DirListing extends Widget {
this.handleOpen(item);
break;
}
case 32: {
// Space bar
const selected = Object.keys(this.selection);
const path = this._softSelection || selected[0];
if (this.selection[path]) {
delete this.selection[path];
this._softSelection = path;
} else {
this.selection[path] = true;
}
this.update();
break;
}
case 38: // Up arrow
this.selectPrevious(event.shiftKey);
if (event.ctrlKey) {
const selected = Object.keys(this.selection);
const path = this._softSelection || selected[0];
const i = ArrayExt.findFirstIndex(
this._sortedItems,
value => value.path === path
);
const pathAbove = this._sortedItems[i - 1].path;
this._softSelection = pathAbove;
this._focusSelectedFile(i - 1);
} else {
this.selectPrevious(event.shiftKey);
}
event.stopPropagation();
event.preventDefault();
break;
case 40: // Down arrow
this.selectNext(event.shiftKey);
if (event.ctrlKey) {
const selected = Object.keys(this.selection);
const path = this._softSelection || selected[0];
const i = ArrayExt.findFirstIndex(
this._sortedItems,
value => value.path === path
);
const pathBelow = this._sortedItems[i + 1].path;
this._softSelection = pathBelow;
this._focusSelectedFile(i + 1);
} else {
this.selectNext(event.shiftKey);
}
event.stopPropagation();
event.preventDefault();
break;
Expand Down Expand Up @@ -1381,8 +1470,19 @@ export class DirListing extends Widget {
const path = items[index].path;
const selected = Object.keys(this.selection);

// Heuristic: was the user trying to click the checkbox? Yes if click was on
// the label element that contains the checkbox or if click was on the
// checkbox itself.
const wasItemSelectedViaCheckbox =
(event.target as HTMLInputElement).type === 'checkbox' ||
(event.target as Element).classList.contains('hit-area');

// Handle toggling.
if ((IS_MAC && event.metaKey) || (!IS_MAC && event.ctrlKey)) {
if (
(IS_MAC && event.metaKey) ||
(!IS_MAC && event.ctrlKey) ||
wasItemSelectedViaCheckbox
) {
if (this.selection[path]) {
delete this.selection[path];
} else {
Expand All @@ -1395,6 +1495,8 @@ export class DirListing extends Widget {

// Handle a 'soft' selection
} else if (path in this.selection && selected.length > 1) {
// try
console.log(path);
this._softSelection = path;

// Default to selecting the only the item.
Expand Down Expand Up @@ -1563,7 +1665,7 @@ export class DirListing extends Widget {
}
if (this._inRename) {
// No need to catch because `newName` will always exit.
void this.selectItemByName(newName);
void this.selectItemByName(newName, true);
}
this._inRename = false;
return newName;
Expand Down Expand Up @@ -1591,6 +1693,7 @@ export class DirListing extends Widget {
if (!keepExisting && focus) {
this._focusSelectedFile(index);
}
this._softSelection = '';
this.update();
}

Expand Down Expand Up @@ -1882,6 +1985,18 @@ export namespace DirListing {
modified.classList.add(MODIFIED_ID_CLASS);
narrow.classList.add(NARROW_ID_CLASS);
narrow.textContent = '...';

const checkboxLabel = document.createElement('label');
checkboxLabel.classList.add('hit-area');
checkboxLabel.classList.add('visible');
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.addEventListener('click', event => {
event.preventDefault();
});
checkboxLabel.appendChild(checkbox);

node.appendChild(checkboxLabel);
node.appendChild(name);
node.appendChild(narrow);
node.appendChild(modified);
Expand Down Expand Up @@ -1979,12 +2094,20 @@ export namespace DirListing {
hiddenColumns?: Set<DirListing.ToggleableColumn>
): HTMLElement {
const node = document.createElement('li');
node.tabIndex = -1;
const checkboxLabel = document.createElement('label');
checkboxLabel.classList.add('hit-area');
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.addEventListener('click', event => event.preventDefault());
checkboxLabel.appendChild(checkbox);
const icon = document.createElement('span');
const text = document.createElement('span');
const modified = document.createElement('span');
icon.className = ITEM_ICON_CLASS;
text.className = ITEM_TEXT_CLASS;
modified.className = ITEM_MODIFIED_CLASS;
node.appendChild(checkboxLabel);
node.appendChild(icon);
node.appendChild(text);
node.appendChild(modified);
Expand All @@ -1993,7 +2116,7 @@ export namespace DirListing {
// text node was specifically chosen to receive shortcuts because
// text element gets substituted with input area during file name edits
// which conveniently deactivate irrelevant shortcuts.
text.tabIndex = 0;
text.tabIndex = -1;

if (hiddenColumns?.has?.('last_modified')) {
modified.classList.add(MODIFIED_COLUMN_HIDDEN);
Expand Down
22 changes: 21 additions & 1 deletion packages/filebrowser/style/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -214,13 +214,33 @@
.jp-DirListing-item {
display: flex;
flex-direction: row;
padding: 4px 12px;
align-items: center;
padding-right: 12px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}

.hit-area {
padding: 4px;
}
.hit-area {
visibility: hidden;
}
.hit-area.visible {
visibility: visible;
}
.jp-DirListing-item:hover .hit-area,
.jp-DirListing-item:focus-within .hit-area,
.jp-DirListing-item.jp-mod-selected .hit-area {
visibility: visible;
}

.jp-DirListing-item:focus-within {
outline: -webkit-focus-ring-color auto 1px;
}

.jp-DirListing-item[data-is-dot] {
opacity: 75%;
}
Expand Down