Skip to content

Commit

Permalink
Merge pull request #4884 from gitbutlerapp/keyboard-shortcuts
Browse files Browse the repository at this point in the history
Add some nice keyboard shortcuts for staging and selecting
  • Loading branch information
estib-vega committed Sep 16, 2024
2 parents b087235 + c9a5d7a commit 07c6db3
Show file tree
Hide file tree
Showing 12 changed files with 194 additions and 42 deletions.
6 changes: 4 additions & 2 deletions apps/desktop/src/lib/branch/BranchCard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
let laneWidth: number | undefined = $state();
let commitDialog = $state<CommitDialog>();
let scrollViewport: HTMLElement | undefined = $state();
let rsViewport: HTMLElement | undefined = $state();
Expand All @@ -75,9 +76,7 @@
});
async function generateBranchName() {
console.log('before');
if (!aiGenEnabled) return;
console.log('after');
const hunks = branch.files.flatMap((f) => f.hunks);
Expand Down Expand Up @@ -174,6 +173,8 @@
files={branch.files}
showCheckboxes={$commitBoxOpen}
allowMultiple
commitDialogExpanded={commitBoxOpen}
focusCommitDialog={() => commitDialog?.focus()}
/>
{#if branch.conflicted}
<div class="card-notifications">
Expand All @@ -192,6 +193,7 @@
</Dropzones>

<CommitDialog
bind:this={commitDialog}
projectId={project.id}
expanded={commitBoxOpen}
hasSectionsAfter={branch.commits.length > 0}
Expand Down
16 changes: 10 additions & 6 deletions apps/desktop/src/lib/commit/CommitCard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,15 @@
</script>

<Modal bind:this={commitMessageModal} width="small" onSubmit={submitCommitMessageModal}>
<CommitMessageInput
bind:commitMessage={description}
bind:valid={commitMessageValid}
isExpanded={true}
/>
{#snippet children(_, close)}
<CommitMessageInput
focusOnMount
bind:commitMessage={description}
bind:valid={commitMessageValid}
isExpanded={true}
cancel={close}
/>
{/snippet}
{#snippet controls(close)}
<Button style="ghost" outline onclick={close}>Cancel</Button>
<Button style="neutral" type="submit" kind="solid" grow disabled={!commitMessageValid}>
Expand Down Expand Up @@ -238,7 +242,7 @@
const mouseY = e.clientY;

const isTop = mouseY < targetTop + targetHeight / 2;

dragDirection = isTop ? 'up' : 'down';
}}
use:draggableCommit={commit instanceof DetailedCommit &&
Expand Down
23 changes: 13 additions & 10 deletions apps/desktop/src/lib/commit/CommitDialog.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
const runCommitHooks = projectRunCommitHooks(projectId);
const commitMessage = persistedCommitMessage(projectId, $branch.id);
let commitMessageInput: CommitMessageInput;
let isCommitting = false;
let commitMessageValid = false;
let isInViewport = false;
Expand All @@ -40,6 +41,14 @@
isCommitting = false;
}
}
function close() {
$expanded = false;
}
export function focus() {
commitMessageInput.focus();
}
</script>

<div
Expand All @@ -62,24 +71,17 @@
}}
>
<CommitMessageInput
bind:this={commitMessageInput}
bind:commitMessage={$commitMessage}
bind:valid={commitMessageValid}
isExpanded={$expanded}
cancel={close}
{commit}
/>
<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={() => {
$expanded = false;
}}
>
Cancel
</Button>
<Button style="ghost" outline id="commit-to-branch" onclick={close}>Cancel</Button>
</div>
{/if}
<Button
Expand All @@ -96,6 +98,7 @@
commit();
} else {
$expanded = true;
commitMessageInput.focus();
}
}}
>
Expand Down
20 changes: 19 additions & 1 deletion apps/desktop/src/lib/commit/CommitMessageInput.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@
export let isExpanded: boolean;
export let commitMessage: string;
export let focusOnMount: boolean = false;
export let valid: boolean = false;
export let commit: (() => void) | undefined = undefined;
export let cancel: () => void;
const user = getContextStore(User);
const selectedOwnership = getContextStore(SelectedOwnership);
Expand Down Expand Up @@ -61,7 +63,7 @@
}
function focusTextAreaOnMount(el: HTMLTextAreaElement) {
el.focus();
if (focusOnMount) el.focus();
}
function updateFieldsHeight() {
Expand Down Expand Up @@ -125,6 +127,12 @@
function handleDescriptionKeyDown(e: KeyboardEvent & { currentTarget: HTMLTextAreaElement }) {
const value = e.currentTarget.value;
if (e.key === KeyName.Escape) {
e.preventDefault();
cancel();
return;
}
if (e.key === KeyName.Delete && value.length === 0) {
e.preventDefault();
if (titleTextArea) {
Expand All @@ -144,6 +152,12 @@
}
function handleSummaryKeyDown(e: KeyboardEvent & { currentTarget: HTMLTextAreaElement }) {
if (e.key === KeyName.Escape) {
e.preventDefault();
cancel();
return;
}
if (commit && (e.ctrlKey || e.metaKey) && e.key === KeyName.Enter) commit();
if (e.key === KeyName.Enter) {
e.preventDefault();
Expand Down Expand Up @@ -171,6 +185,10 @@
descriptionTextArea?.focus();
}
}
export function focus() {
titleTextArea?.focus();
}
</script>

{#if isExpanded}
Expand Down
13 changes: 12 additions & 1 deletion apps/desktop/src/lib/file/BranchFiles.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
import { createCommitStore } from '$lib/vbranches/contexts';
import { FileIdSelection } from '$lib/vbranches/fileIdSelection';
import type { LocalFile, RemoteFile } from '$lib/vbranches/types';
import type { Writable } from 'svelte/store';
export let files: LocalFile[] | RemoteFile[];
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;
Expand All @@ -18,7 +21,15 @@

<div class="branch-files" role="presentation" on:click={() => fileIdSelection.clear()}>
{#if files.length > 0}
<BranchFilesList {allowMultiple} {readonly} {files} {showCheckboxes} {isUnapplied} />
<BranchFilesList
{allowMultiple}
{readonly}
{files}
{showCheckboxes}
{isUnapplied}
{commitDialogExpanded}
{focusCommitDialog}
/>
{/if}
</div>

Expand Down
77 changes: 60 additions & 17 deletions apps/desktop/src/lib/file/BranchFilesList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@
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';
const MERGE_DIFF_COMMAND = 'git diff-tree --cc ';
Expand All @@ -22,23 +25,77 @@
showCheckboxes?: boolean;
allowMultiple?: boolean;
readonly?: boolean;
commitDialogExpanded?: Writable<boolean>;
focusCommitDialog?: () => void;
}
const {
files,
isUnapplied = false,
showCheckboxes = false,
allowMultiple = false,
readonly = false
readonly = false,
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 handleSpace() {
if (commitDialogExpanded === undefined) return;
if (!$commitDialogExpanded) {
$commitDialogExpanded = true;
return;
}
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,
metaKey: e.metaKey,
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;
}
}
}
function loadMore() {
if (currentDisplayIndex + 1 >= chunkedFiles.length) return;
currentDisplayIndex += 1;
Expand Down Expand Up @@ -75,21 +132,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
}
);
}}
onkeydown={handleKeyDown}
>
{#each displayedFiles as file (file.id)}
<FileListItem
Expand Down
3 changes: 2 additions & 1 deletion apps/desktop/src/lib/utils/hotkeys.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { entries } from './object';
import { createKeybindingsHandler } from 'tinykeys';

interface KeybindDefinitions {
Expand Down Expand Up @@ -25,7 +26,7 @@ export function createKeybind(keybinds: KeybindDefinitions) {
Backspace: () => {}
};

for (const [combo, callback] of Object.entries(keybinds)) {
for (const [combo, callback] of entries(keybinds)) {
keys[combo] = (event: KeyboardEvent) => {
if (
event.repeat ||
Expand Down
5 changes: 5 additions & 0 deletions apps/desktop/src/lib/utils/object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type UnknowObject<T> = Record<string, T>;

export function entries<T, Obj extends UnknowObject<T>>(obj: Obj): [keyof Obj, Obj[keyof Obj]][] {
return Object.entries(obj) as [keyof Obj, Obj[keyof Obj]][];
}
9 changes: 9 additions & 0 deletions apps/desktop/src/lib/utils/selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ function getBottomFile(files: AnyFile[], selectedFileIds: string[]): AnyFile | u

interface UpdateSelectionParams {
allowMultiple: boolean;
metaKey: boolean;
shiftKey: boolean;
key: string;
targetElement: HTMLElement;
Expand All @@ -53,6 +54,7 @@ interface UpdateSelectionParams {

export function updateSelection({
allowMultiple,
metaKey,
shiftKey,
key,
targetElement,
Expand Down Expand Up @@ -99,6 +101,13 @@ export function updateSelection({
}

switch (key) {
case 'a':
if (allowMultiple && metaKey) {
for (const file of files) {
fileIdSelection.add(file.id, commitId);
}
}
break;
case KeyName.Up:
if (shiftKey && allowMultiple) {
// Handle case if only one file is selected
Expand Down
7 changes: 5 additions & 2 deletions apps/desktop/src/lib/vbranches/fileIdSelection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,11 @@ export class FileIdSelection {
}

add(fileId: string, commitId?: string) {
this.value.push(stringifyFileKey(fileId, commitId));
this.emit();
const fileKey = stringifyFileKey(fileId, commitId);
if (!this.value.includes(fileKey)) {
this.value.push(fileKey);
this.emit();
}
}

has(fileId: string, commitId?: string) {
Expand Down
Loading

0 comments on commit 07c6db3

Please sign in to comment.