mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-25 02:26:14 +03:00
BranchFilesList keybindings: Stage and unstage files
Stage or unstage the files by pressing the space-bar on the selected files. Click enter to focus on the commit message input field
This commit is contained in:
parent
f3b3649bca
commit
d41827c9d1
@ -52,6 +52,7 @@
|
||||
|
||||
let laneWidth: number | undefined = $state();
|
||||
|
||||
let commitDialog = $state<CommitDialog>();
|
||||
let scrollViewport: HTMLElement | undefined = $state();
|
||||
let rsViewport: HTMLElement | undefined = $state();
|
||||
|
||||
@ -131,6 +132,7 @@
|
||||
showCheckboxes={$commitBoxOpen}
|
||||
allowMultiple
|
||||
commitDialogExpanded={commitBoxOpen}
|
||||
focusCommitDialog={() => commitDialog?.focus()}
|
||||
/>
|
||||
{#if branch.conflicted}
|
||||
<div class="card-notifications">
|
||||
@ -149,6 +151,7 @@
|
||||
</Dropzones>
|
||||
|
||||
<CommitDialog
|
||||
bind:this={commitDialog}
|
||||
projectId={project.id}
|
||||
expanded={commitBoxOpen}
|
||||
hasSectionsAfter={branch.commits.length > 0}
|
||||
|
@ -45,6 +45,10 @@
|
||||
function close() {
|
||||
$expanded = false;
|
||||
}
|
||||
|
||||
export function focus() {
|
||||
commitMessageInput.focus();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
@ -77,14 +81,7 @@
|
||||
<div class="actions" class:commit-box__actions-expanded={$expanded}>
|
||||
{#if $expanded && !isCommitting}
|
||||
<div class="cancel-btn-wrapper" transition:slideFade={{ duration: 200, axis: 'x' }}>
|
||||
<Button
|
||||
style="ghost"
|
||||
outline
|
||||
id="commit-to-branch"
|
||||
onclick={close}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button style="ghost" outline id="commit-to-branch" onclick={close}>Cancel</Button>
|
||||
</div>
|
||||
{/if}
|
||||
<Button
|
||||
|
@ -10,6 +10,7 @@
|
||||
export let isUnapplied: boolean;
|
||||
export let showCheckboxes = false;
|
||||
export let commitDialogExpanded: Writable<boolean>;
|
||||
export let focusCommitDialog: () => void;
|
||||
|
||||
export let allowMultiple = false;
|
||||
export let readonly = false;
|
||||
@ -27,6 +28,7 @@
|
||||
{showCheckboxes}
|
||||
{isUnapplied}
|
||||
{commitDialogExpanded}
|
||||
{focusCommitDialog}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -5,13 +5,14 @@
|
||||
import TextBox from '$lib/shared/TextBox.svelte';
|
||||
import { chunk } from '$lib/utils/array';
|
||||
import { copyToClipboard } from '$lib/utils/clipboard';
|
||||
import { getContext } from '$lib/utils/context';
|
||||
import { getContext, maybeGetContextStore } from '$lib/utils/context';
|
||||
import { KeyName } from '$lib/utils/hotkeys';
|
||||
import { selectFilesInList } from '$lib/utils/selectFilesInList';
|
||||
import { updateSelection } from '$lib/utils/selection';
|
||||
import { getCommitStore } from '$lib/vbranches/contexts';
|
||||
import { FileIdSelection, stringifyFileKey } from '$lib/vbranches/fileIdSelection';
|
||||
import { sortLikeFileTree } from '$lib/vbranches/filetree';
|
||||
import { SelectedOwnership, updateOwnership } from '$lib/vbranches/ownership';
|
||||
import Button from '@gitbutler/ui/Button.svelte';
|
||||
import type { AnyFile } from '$lib/vbranches/types';
|
||||
import type { Writable } from 'svelte/store';
|
||||
@ -24,7 +25,8 @@
|
||||
showCheckboxes?: boolean;
|
||||
allowMultiple?: boolean;
|
||||
readonly?: boolean;
|
||||
commitDialogExpanded: Writable<boolean> | undefined;
|
||||
commitDialogExpanded?: Writable<boolean>;
|
||||
focusCommitDialog?: () => void;
|
||||
}
|
||||
|
||||
const {
|
||||
@ -33,20 +35,65 @@
|
||||
showCheckboxes = false,
|
||||
allowMultiple = false,
|
||||
readonly = false,
|
||||
commitDialogExpanded = undefined
|
||||
commitDialogExpanded,
|
||||
focusCommitDialog
|
||||
}: Props = $props();
|
||||
|
||||
const fileIdSelection = getContext(FileIdSelection);
|
||||
const selectedOwnership: Writable<SelectedOwnership> | undefined =
|
||||
maybeGetContextStore(SelectedOwnership);
|
||||
const commit = getCommitStore();
|
||||
|
||||
let chunkedFiles: AnyFile[][] = $derived(chunk(sortLikeFileTree(files), 100));
|
||||
let currentDisplayIndex = $state(0);
|
||||
let displayedFiles: AnyFile[] = $derived(chunkedFiles.slice(0, currentDisplayIndex + 1).flat());
|
||||
|
||||
function startCommit() {
|
||||
function handleSpace() {
|
||||
if (commitDialogExpanded === undefined) return;
|
||||
|
||||
// Start commit
|
||||
if (!$commitDialogExpanded) {
|
||||
$commitDialogExpanded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Stage/unstage files
|
||||
updateOwnership({
|
||||
selectedFileIds: $fileIdSelection,
|
||||
files: displayedFiles,
|
||||
selectedOwnership
|
||||
});
|
||||
}
|
||||
|
||||
function handleEnter() {
|
||||
if (commitDialogExpanded === undefined || focusCommitDialog === undefined) return;
|
||||
if ($commitDialogExpanded) {
|
||||
focusCommitDialog();
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyDown(e: KeyboardEvent) {
|
||||
e.preventDefault();
|
||||
updateSelection({
|
||||
allowMultiple,
|
||||
shiftKey: e.shiftKey,
|
||||
key: e.key,
|
||||
targetElement: e.currentTarget as HTMLElement,
|
||||
files: displayedFiles,
|
||||
selectedFileIds: $fileIdSelection,
|
||||
fileIdSelection,
|
||||
commitId: $commit?.id
|
||||
});
|
||||
|
||||
switch (e.key) {
|
||||
case KeyName.Space: {
|
||||
handleSpace();
|
||||
break;
|
||||
}
|
||||
case KeyName.Enter: {
|
||||
handleEnter();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,28 +133,7 @@
|
||||
loadMore();
|
||||
}}
|
||||
role="listbox"
|
||||
onkeydown={(e) => {
|
||||
e.preventDefault();
|
||||
updateSelection(
|
||||
{
|
||||
allowMultiple,
|
||||
shiftKey: e.shiftKey,
|
||||
key: e.key,
|
||||
targetElement: e.currentTarget as HTMLElement,
|
||||
files: displayedFiles,
|
||||
selectedFileIds: $fileIdSelection,
|
||||
fileIdSelection,
|
||||
commitId: $commit?.id
|
||||
}
|
||||
);
|
||||
switch (e.key) {
|
||||
case KeyName.Space: {
|
||||
e.preventDefault();
|
||||
startCommit();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}}
|
||||
onkeydown={handleKeyDown}
|
||||
>
|
||||
{#each displayedFiles as file (file.id)}
|
||||
<FileListItem
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { unstringifyFileKey } from './fileIdSelection';
|
||||
import type { VirtualBranch, AnyFile, Hunk, RemoteHunk, RemoteFile } from './types';
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
export function filesToOwnership(files: AnyFile[]) {
|
||||
return files
|
||||
@ -203,3 +205,62 @@ export class SelectedOwnership {
|
||||
return this.selection.size === 0;
|
||||
}
|
||||
}
|
||||
|
||||
interface OwnershipUpdateParams {
|
||||
selectedFileIds: string[];
|
||||
files: AnyFile[];
|
||||
selectedOwnership: Writable<SelectedOwnership> | undefined;
|
||||
}
|
||||
|
||||
export function updateOwnership(params: OwnershipUpdateParams) {
|
||||
const selectedFiles = params.selectedFileIds
|
||||
.map((id) => unstringifyFileKey(id))
|
||||
.map((id) => params.files.find((f) => f.id === id))
|
||||
.filter((f): f is AnyFile => !!f);
|
||||
|
||||
if (selectedFiles.length === 0) return;
|
||||
|
||||
// Only one file selected
|
||||
//
|
||||
// Select all hunks if none are selected,
|
||||
// otherwise ignore all hunks
|
||||
if (selectedFiles.length === 1) {
|
||||
const file = selectedFiles[0]!;
|
||||
|
||||
params.selectedOwnership?.update((ownership) => {
|
||||
const someHunksSelected = file.hunks.some((h) => ownership.isSelected(file.id, h.id));
|
||||
|
||||
if (someHunksSelected) {
|
||||
ownership.ignore(file.id, ...file.hunkIds);
|
||||
} else {
|
||||
ownership.select(file.id, ...file.hunks);
|
||||
}
|
||||
|
||||
return ownership;
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Multiple files selected
|
||||
//
|
||||
// Select all files if none are selected,
|
||||
// otherwise ignore all files
|
||||
params.selectedOwnership?.update((ownership) => {
|
||||
const someFilesSelected = selectedFiles.some((f) =>
|
||||
f.hunks.some((h) => ownership.isSelected(f.id, h.id))
|
||||
);
|
||||
|
||||
if (someFilesSelected) {
|
||||
for (const file of selectedFiles) {
|
||||
ownership.ignore(file.id, ...file.hunkIds);
|
||||
}
|
||||
} else {
|
||||
for (const file of selectedFiles) {
|
||||
ownership.select(file.id, ...file.hunks);
|
||||
}
|
||||
}
|
||||
|
||||
return ownership;
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user