Skip to content

Commit e8c18ea

Browse files
authored
Merge pull request #52 from hansu/implement-multi-selection-using-strg
Implement multi selection using Ctrl
2 parents 40d1877 + 1119ff0 commit e8c18ea

File tree

4 files changed

+48
-29
lines changed

4 files changed

+48
-29
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,12 +1093,12 @@
10931093
"git-graph.repository.selectMultipleAuthors": {
10941094
"type": "boolean",
10951095
"default": true,
1096-
"description": "Enables the selection of multiple authors from the Authors dropdown."
1096+
"description": "Enables the selection of multiple authors from the Authors dropdown. When set to false, you can only select multiple authors by 'Ctrl' (or 'Cmd' on macOS) + click."
10971097
},
10981098
"git-graph.repository.selectMultipleBranches": {
10991099
"type": "boolean",
11001100
"default": true,
1101-
"description": "Enables the selection of multiple branches from the Branches dropdown."
1101+
"description": "Enables the selection of multiple branches from the Branches dropdown. When set to false, you can only select multiple branches by 'Ctrl' (or 'Cmd' on macOS) + click."
11021102
},
11031103
"git-graph.repository.showCommitsOnlyReferencedByTags": {
11041104
"type": "boolean",

src/gitGraphView.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -721,17 +721,15 @@ export class GitGraphView extends Disposable {
721721
</body>`;
722722
} else if (numRepos > 0) {
723723
const stickyClassAttr = initialState.config.stickyHeader ? ' class="sticky"' : '';
724-
const branchDropdownLabel = initialState.config.selectMultipleBranches ? 'Branches' : 'Branch';
725-
const authorDropdownLabel = initialState.config.selectMultipleAuthors ? 'Authors' : 'Author';
726724
let hideRemotes = '', hideSimplify = '';
727725
if (!config.toolbarButtonVisibility.remotes) { hideRemotes = 'style="display: none"'; }
728726
if (!config.toolbarButtonVisibility.simplify) { hideSimplify = 'style="display: none"'; }
729727
body = `<body>
730728
<div id="view" tabindex="-1">
731729
<div id="controls"${stickyClassAttr}>
732730
<span id="repoControl"><span class="unselectable">Repo: </span><div id="repoDropdown" class="dropdown"></div></span>
733-
<span id="branchControl"><span class="unselectable">${branchDropdownLabel}: </span><div id="branchDropdown" class="dropdown"></div></span>
734-
<span id="authorControl"><span class="unselectable">${authorDropdownLabel}: </span><div id="authorDropdown" class="dropdown"></div></span>
731+
<span id="branchControl"><span class="unselectable">Branches: </span><div id="branchDropdown" class="dropdown"></div></span>
732+
<span id="authorControl"><span class="unselectable">Authors: </span><div id="authorDropdown" class="dropdown"></div></span>
735733
<label ${hideRemotes} id="showRemoteBranchesControl" title="Show Remote Branches"><input type="checkbox" id="showRemoteBranchesCheckbox" tabindex="-1"><span class="customCheckbox"></span>Remotes</label>
736734
<label ${hideSimplify} id="simplifyByDecorationControl" title="Simplify By Decoration"><input type="checkbox" id="simplifyByDecorationCheckbox" tabindex="-1"><span class="customCheckbox"></span>Simplify</label>
737735
<div id="currentBtn" title="Current"></div>

web/dropdown.ts

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ interface DropdownOption {
1010
class Dropdown {
1111
private readonly showInfo: boolean;
1212
private readonly multipleAllowed: boolean;
13+
private readonly selectMultipleWithCtrl: boolean;
1314
private readonly changeCallback: (values: string[]) => void;
1415

1516
private options: ReadonlyArray<DropdownOption> = [];
@@ -34,10 +35,12 @@ class Dropdown {
3435
* @param dropdownType The type of content the dropdown is being used for.
3536
* @param changeCallback A callback to be invoked when the selected item(s) of the dropdown changes.
3637
* @returns The Dropdown instance.
38+
* @param selectMultipleWithCtrl Select multiple items using Ctrl
3739
*/
38-
constructor(id: string, showInfo: boolean, multipleAllowed: boolean, dropdownType: string, changeCallback: (values: string[]) => void) {
40+
constructor(id: string, showInfo: boolean, multipleAllowed: boolean, dropdownType: string, changeCallback: (values: string[]) => void, selectMultipleWithCtrl: boolean = false) {
3941
this.showInfo = showInfo;
4042
this.multipleAllowed = multipleAllowed;
43+
this.selectMultipleWithCtrl = selectMultipleWithCtrl;
4144
this.changeCallback = changeCallback;
4245
this.elem = document.getElementById(id)!;
4346

@@ -61,7 +64,7 @@ class Dropdown {
6164
this.currentValueElem = this.elem.appendChild(document.createElement('div'));
6265
this.currentValueElem.className = 'dropdownCurrentValue';
6366

64-
alterClass(this.elem, 'multi', multipleAllowed);
67+
alterClass(this.elem, 'multi', (multipleAllowed && !selectMultipleWithCtrl));
6568
this.elem.appendChild(this.menuElem);
6669

6770
document.addEventListener('click', (e) => {
@@ -80,7 +83,7 @@ class Dropdown {
8083
} else {
8184
const option = <HTMLElement | null>(<HTMLElement>e.target).closest('.dropdownOption');
8285
if (option !== null && option.parentNode === this.optionsElem && typeof option.dataset.id !== 'undefined') {
83-
this.onOptionClick(parseInt(option.dataset.id!));
86+
this.onOptionClick(parseInt(option.dataset.id!), e);
8487
}
8588
}
8689
}
@@ -143,18 +146,24 @@ class Dropdown {
143146
*/
144147
public selectOption(value: string) {
145148
const optionIndex = this.options.findIndex((option) => value === option.value);
146-
if (this.multipleAllowed && optionIndex > -1 && !this.optionsSelected[0] && !this.optionsSelected[optionIndex]) {
149+
if (optionIndex < 0 && (this.optionsSelected[0] || this.optionsSelected[optionIndex])) return;
150+
if (this.multipleAllowed && !this.selectMultipleWithCtrl) {
147151
// Select the option with the specified value
148152
this.optionsSelected[optionIndex] = true;
149-
150-
// A change has occurred, re-render the dropdown options
151-
const menuScroll = this.menuElem.scrollTop;
152-
this.render();
153-
if (this.dropdownVisible) {
154-
this.menuElem.scroll(0, menuScroll);
153+
} else {
154+
for (let i = 1; i < this.optionsSelected.length; i++) {
155+
this.optionsSelected[i] = false;
155156
}
156-
this.changeCallback(this.getSelectedOptions(false));
157+
this.optionsSelected[optionIndex] = true;
157158
}
159+
// A change has occurred, re-render the dropdown options
160+
const menuScroll = this.menuElem.scrollTop;
161+
this.render();
162+
if (this.dropdownVisible) {
163+
this.menuElem.scroll(0, menuScroll);
164+
}
165+
this.changeCallback(this.getSelectedOptions(false));
166+
158167
}
159168

160169
/**
@@ -163,7 +172,8 @@ class Dropdown {
163172
*/
164173
public unselectOption(value: string) {
165174
const optionIndex = this.options.findIndex((option) => value === option.value);
166-
if (this.multipleAllowed && optionIndex > -1 && (this.optionsSelected[0] || this.optionsSelected[optionIndex])) {
175+
if (optionIndex < 0 && (this.optionsSelected[0] || this.optionsSelected[optionIndex])) return;
176+
if (this.multipleAllowed) {
167177
if (this.optionsSelected[0]) {
168178
// Show All is currently selected, so unselect it, and select all branch options
169179
this.optionsSelected[0] = false;
@@ -227,7 +237,7 @@ class Dropdown {
227237
for (let i = 0; i < this.options.length; i++) {
228238
const escapedName = escapeHtml(this.options[i].name);
229239
html += '<div class="dropdownOption' + (this.optionsSelected[i] ? ' ' + CLASS_SELECTED : '') + '" data-id="' + i + '" title="' + escapedName + '">' +
230-
(this.multipleAllowed && this.optionsSelected[i] ? '<div class="dropdownOptionMultiSelected">' + SVG_ICONS.check + '</div>' : '') +
240+
(this.multipleAllowed && !this.selectMultipleWithCtrl && this.optionsSelected[i] ? '<div class="dropdownOptionMultiSelected">' + SVG_ICONS.check + '</div>' : '') +
231241
escapedName + (typeof this.options[i].hint === 'string' && this.options[i].hint !== '' ? '<span class="dropdownOptionHint">' + escapeHtml(this.options[i].hint!) + '</span>' : '') +
232242
(this.showInfo ? '<div class="dropdownOptionInfo" title="' + escapeHtml(this.options[i].value) + '">' + SVG_ICONS.info + '</div>' : '') +
233243
'</div>';
@@ -240,7 +250,7 @@ class Dropdown {
240250
// Width must be at least 138px for the filter element.
241251
// Don't need to add 12px if showing (info icons or multi checkboxes) and the scrollbar isn't needed. The scrollbar isn't needed if: menuElem height + filter input (25px) < 297px
242252
const menuElemRect = this.menuElem.getBoundingClientRect();
243-
this.currentValueElem.style.width = Math.max(Math.ceil(menuElemRect.width) + ((this.showInfo || this.multipleAllowed) && menuElemRect.height < 272 ? 0 : 12), 138) + 'px';
253+
this.currentValueElem.style.width = Math.max(Math.ceil(menuElemRect.width) + ((this.showInfo || (this.multipleAllowed && !this.selectMultipleWithCtrl)) && menuElemRect.height < 272 ? 0 : 12), 138) + 'px';
244254
this.menuElem.style.cssText = 'right:0; overflow-y:auto; max-height:297px;'; // Max height for the dropdown is [filter (31px) + 9.5 * dropdown item (28px) = 297px]
245255
if (this.dropdownVisible) this.filter();
246256
}
@@ -280,12 +290,11 @@ class Dropdown {
280290
* Select a dropdown option.
281291
* @param option The index of the option to select.
282292
*/
283-
private onOptionClick(option: number) {
293+
private onOptionClick(option: number, event?: MouseEvent) {
284294
// Note: Show All is always the first option (0 index) when multiple selected items are allowed
285295
let change = false;
286296
let doubleClick = this.doubleClickTimeout !== null && this.lastClicked === option;
287297
if (this.doubleClickTimeout !== null) this.clearDoubleClickTimeout();
288-
289298
if (doubleClick) {
290299
// Double click
291300
if (this.multipleAllowed && option === 0) {
@@ -296,7 +305,7 @@ class Dropdown {
296305
}
297306
} else {
298307
// Single Click
299-
if (this.multipleAllowed) {
308+
if (this.multipleAllowed && (!this.selectMultipleWithCtrl || (event && (event.ctrlKey || event.metaKey)))) {
300309
// Multiple dropdown options can be selected
301310
if (option === 0) {
302311
// Show All was selected
@@ -324,15 +333,27 @@ class Dropdown {
324333
} else {
325334
// Only a single dropdown option can be selected
326335
this.close();
327-
if (this.lastSelected !== option) {
336+
if (option === 0) {
337+
// Show All was selected
338+
if (!this.optionsSelected[0]) {
339+
this.optionsSelected[0] = true;
340+
for (let i = 1; i < this.optionsSelected.length; i++) {
341+
this.optionsSelected[i] = false;
342+
}
343+
change = true;
344+
}
345+
} else if (this.lastSelected !== option) {
346+
for (let i = 0; i < this.optionsSelected.length; i++) {
347+
this.optionsSelected[i] = false;
348+
}
328349
this.optionsSelected[this.lastSelected] = false;
329350
this.optionsSelected[option] = true;
330-
this.lastSelected = option;
331351
change = true;
332352
}
333353
}
334354

335355
if (change) {
356+
this.lastSelected = option;
336357
// If a change has occurred, trigger the callback
337358
this.changeCallback(this.getSelectedOptions(false));
338359
}

web/main.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,20 +86,20 @@ class GitGraphView {
8686
this.loadRepo(values[0]);
8787
});
8888

89-
this.branchDropdown = new Dropdown('branchDropdown', false, this.config.selectMultipleBranches, 'Branches', (values) => {
89+
this.branchDropdown = new Dropdown('branchDropdown', false, true, 'Branches', (values) => {
9090
this.currentBranches = values;
9191
this.maxCommits = this.config.initialLoadCommits;
9292
this.saveState();
9393
this.clearCommits();
9494
this.requestLoadRepoInfoAndCommits(true, true);
95-
});
96-
this.authorDropdown = new Dropdown('authorDropdown', false, this.config.selectMultipleAuthors, 'Authors', (values) => {
95+
}, !this.config.selectMultipleBranches);
96+
this.authorDropdown = new Dropdown('authorDropdown', false, true, 'Authors', (values) => {
9797
this.currentAuthors = values;
9898
this.maxCommits = this.config.initialLoadCommits;
9999
this.saveState();
100100
this.clearCommits();
101101
this.requestLoadRepoInfoAndCommits(true, true);
102-
});
102+
}, !this.config.selectMultipleAuthors);
103103
this.showRemoteBranchesElem = <HTMLInputElement>document.getElementById('showRemoteBranchesCheckbox')!;
104104
this.showRemoteBranchesElem.addEventListener('change', () => {
105105
this.saveRepoStateValue(this.currentRepo, 'showRemoteBranchesV2', this.showRemoteBranchesElem.checked ? GG.BooleanOverride.Enabled : GG.BooleanOverride.Disabled);

0 commit comments

Comments
 (0)