mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2025-01-01 22:12:04 +03:00
partial commit ui
This commit is contained in:
parent
5209aea6e2
commit
2f9dd738cf
@ -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');
|
||||
|
@ -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',
|
||||
|
@ -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 }) {
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
/>
|
||||
|
Loading…
Reference in New Issue
Block a user