partial commit ui

This commit is contained in:
Nikita Galaiko 2023-09-11 15:24:47 +02:00 committed by GitButler
parent 5209aea6e2
commit 2f9dd738cf
5 changed files with 121 additions and 7 deletions

View File

@ -28,9 +28,9 @@ export class BranchController {
}
}
async commitBranch(branch: string, message: string) {
async commitBranch(params: { branch: string; message: string; ownership?: string }) {
try {
await invoke<void>('commit_virtual_branch', { projectId: this.projectId, branch, message });
await invoke<void>('commit_virtual_branch', { projectId: this.projectId, ...params });
await this.virtualBranchStore.reload();
} catch (err) {
toasts.error('Failed to commit branch');

View File

@ -28,6 +28,7 @@
import IconBackspace from '$lib/icons/IconBackspace.svelte';
import { sortLikeFileTree } from '$lib/vbranches/filetree';
import CommitDialog from './CommitDialog.svelte';
import { writable } from 'svelte/store';
const [send, receive] = crossfade({
duration: (d) => Math.sqrt(d * 200),
@ -158,6 +159,9 @@
}
});
});
const selectedFileIds = writable(branch.files.map((f) => f.id));
$: selectedFileIds.set(branch.files.map((f) => f.id));
</script>
<div
@ -285,6 +289,7 @@
{branch}
{cloudEnabled}
{cloud}
selectedFileIds={$selectedFileIds}
user={$user}
/>
{/if}
@ -298,7 +303,11 @@
name: 'files',
displayName: 'Changed files (' + branch.files.length + ')',
component: FileTreeTabPanel,
props: { files: branch.files }
props: {
files: branch.files,
selectedFileIds,
withCheckboxes: commitDialogShown
}
},
{
name: 'notes',

View File

@ -16,14 +16,23 @@
export let cloudEnabled: boolean;
export let cloud: ReturnType<typeof getCloudApiClient>;
export let user: User | null;
export let selectedFileIds: string[];
const dispatch = createEventDispatcher<{ close: null }>();
let commitMessage: string;
$: messageRows = Math.min(Math.max(commitMessage ? commitMessage.split('\n').length : 0, 1), 10);
$: ownership = branch.files
.filter((f) => selectedFileIds.includes(f.id))
.map((f) => {
return f.id + ':' + f.hunks.map((h) => h.id).join(',');
})
.join('\n');
$: console.log(ownership)
function commit() {
branchController.commitBranch(branch.id, commitMessage);
branchController.commitBranch({ branch: branch.id, message: commitMessage, ownership });
}
export function git_get_config(params: { key: string }) {

View File

@ -3,6 +3,7 @@
</script>
<script lang="ts">
import { Checkbox } from '$lib/components';
import TimeAgo from '$lib/components/TimeAgo/TimeAgo.svelte';
import IconChevronDownSmall from '$lib/icons/IconChevronDownSmall.svelte';
@ -10,12 +11,65 @@
import IconFile from '$lib/icons/IconFile.svelte';
import IconFolder from '$lib/icons/IconFolder.svelte';
import type { TreeNode } from '$lib/vbranches/filetree';
import { createEventDispatcher } from 'svelte';
let className = '';
export { className as class };
export let expanded = true;
export let node: TreeNode;
export let isRoot = false;
export let selectedFileIds: string[];
export let withCheckboxes: boolean;
const dispatch = createEventDispatcher<{
checked: { fileId: string };
unchecked: { fileId: string };
}>();
function isNodeChecked(selectedFileIds: string[], node: TreeNode): boolean {
if (node.file) {
return selectedFileIds.includes(node.file.id);
}
return node.children.every((child) => isNodeChecked(selectedFileIds, child));
}
$: isChecked = isNodeChecked(selectedFileIds, node);
function isNodeIndeterminate(selectedFileIds: string[], node: TreeNode): boolean {
if (node.file) return false;
if (node.children.length === 0) return false;
const isFirstNodeChecked = isNodeChecked(selectedFileIds, node.children[0]);
const isFirstNodeIndeterminate = isNodeIndeterminate(selectedFileIds, node.children[0]);
for (const child of node.children) {
if (isFirstNodeChecked !== isNodeChecked(selectedFileIds, child)) {
return true;
}
if (isFirstNodeIndeterminate !== isNodeIndeterminate(selectedFileIds, child)) {
return true;
}
}
return false;
}
$: isIndeterminate = isNodeIndeterminate(selectedFileIds, node);
function idWithChildren(node: TreeNode): string[] {
if (node.file) {
return [node.file.id];
}
return node.children.flatMap(idWithChildren);
}
function onCheckboxChange() {
idWithChildren(node).forEach((id) => {
if (isChecked) {
dispatch('unchecked', { fileId: id });
} else {
dispatch('checked', { fileId: id });
}
});
}
function toggle() {
expanded = !expanded;
@ -28,7 +82,13 @@
<ul id={`fileTree-${fileTreeId++}`}>
{#each node.children as childNode}
<li>
<svelte:self node={childNode} />
<svelte:self
node={childNode}
{selectedFileIds}
{withCheckboxes}
on:checked
on:unchecked
/>
</li>
{/each}
</ul>
@ -69,6 +129,9 @@
-{removed}
</span>
</div>
{#if withCheckboxes}
<Checkbox checked={isChecked} on:change={onCheckboxChange} />
{/if}
</button>
{:else if node.children.length > 0}
<!-- Node is a folder -->
@ -86,6 +149,13 @@
<div class="flex-grow truncate pl-2">
{node.name}
</div>
{#if withCheckboxes}
<Checkbox
checked={isChecked}
indeterminate={isIndeterminate}
on:change={onCheckboxChange}
/>
{/if}
</button>
<!-- We assume a folder cannot be empty -->
{#if expanded}
@ -98,7 +168,14 @@
<ul class="w-full overflow-hidden">
{#each node.children as childNode}
<li>
<svelte:self node={childNode} expanded={true} />
<svelte:self
node={childNode}
expanded={true}
{selectedFileIds}
{withCheckboxes}
on:checked
on:unchecked
/>
</li>
{/each}
</ul>

View File

@ -2,8 +2,27 @@
import { filesToFileTree } from '$lib/vbranches/filetree';
import type { File } from '$lib/vbranches/types';
import FileTree from './FileTree.svelte';
import type { Writable } from 'svelte/store';
export let files: File[];
export let withCheckboxes: boolean;
export let selectedFileIds: Writable<string[]>;
function handleChecked(event: CustomEvent<{ fileId: string }>) {
$selectedFileIds = [...$selectedFileIds, event.detail.fileId];
}
function handleUnchecked(event: CustomEvent<{ fileId: string }>) {
$selectedFileIds = $selectedFileIds.filter((id) => id !== event.detail.fileId);
}
</script>
<FileTree node={filesToFileTree(files)} isRoot={true} class="p-2" />
<FileTree
node={filesToFileTree(files)}
isRoot={true}
class="p-2"
selectedFileIds={$selectedFileIds}
on:checked={handleChecked}
on:unchecked={handleUnchecked}
{withCheckboxes}
/>