mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2025-01-04 15:53:30 +03:00
Merge pull request #1551 from gitbutlerapp/gb-601-squash-commits-ui
GB-601: squash commits ui
This commit is contained in:
commit
67cea3c292
@ -13,7 +13,7 @@ use crate::{
|
||||
reader,
|
||||
sessions::{self, SessionId},
|
||||
users,
|
||||
virtual_branches::{self, target},
|
||||
virtual_branches::target,
|
||||
watcher,
|
||||
};
|
||||
|
||||
|
@ -206,7 +206,7 @@ fn main() {
|
||||
virtual_branches::commands::cherry_pick_onto_virtual_branch,
|
||||
virtual_branches::commands::amend_virtual_branch,
|
||||
virtual_branches::commands::list_remote_branches,
|
||||
virtual_branches::commands::squash,
|
||||
virtual_branches::commands::squash_branch_commit,
|
||||
keys::commands::get_public_key,
|
||||
github::commands::init_device_oauth,
|
||||
github::commands::check_auth_status,
|
||||
|
@ -493,7 +493,7 @@ pub async fn list_remote_branches(
|
||||
|
||||
#[tauri::command(async)]
|
||||
#[instrument(skip(handle))]
|
||||
pub async fn squash(
|
||||
pub async fn squash_branch_commit(
|
||||
handle: tauri::AppHandle,
|
||||
project_id: &str,
|
||||
branch_id: &str,
|
||||
|
@ -12,7 +12,7 @@ use slug::slugify;
|
||||
use crate::{
|
||||
dedup::{dedup, dedup_fmt},
|
||||
gb_repository,
|
||||
git::{self, diff, RemoteBranchName},
|
||||
git::{self, diff, Commit, RemoteBranchName},
|
||||
keys,
|
||||
project_repository::{self, conflicts, LogUntil},
|
||||
reader, sessions, users,
|
||||
@ -68,6 +68,8 @@ pub struct VirtualBranchCommit {
|
||||
pub is_remote: bool,
|
||||
pub files: Vec<VirtualBranchFile>,
|
||||
pub is_integrated: bool,
|
||||
pub parent_ids: Vec<git::Oid>,
|
||||
pub branch_id: BranchId,
|
||||
}
|
||||
|
||||
// this struct is a mapping to the view `File` type in Typescript
|
||||
@ -704,6 +706,7 @@ pub fn list_virtual_branches(
|
||||
.map(|commit| {
|
||||
commit_to_vbranch_commit(
|
||||
project_repository,
|
||||
branch,
|
||||
&default_target,
|
||||
commit,
|
||||
Some(&pushed_commits),
|
||||
@ -895,6 +898,7 @@ fn list_virtual_commit_files(
|
||||
|
||||
pub fn commit_to_vbranch_commit(
|
||||
repository: &project_repository::Repository,
|
||||
branch: &branch::Branch,
|
||||
target: &target::Target,
|
||||
commit: &git::Commit,
|
||||
upstream_commits: Option<&HashMap<git::Oid, bool>>,
|
||||
@ -913,6 +917,8 @@ pub fn commit_to_vbranch_commit(
|
||||
|
||||
let is_integrated = is_commit_integrated(repository, target, commit)?;
|
||||
|
||||
let parent_ids = commit.parents()?.iter().map(Commit::id).collect::<Vec<_>>();
|
||||
|
||||
let commit = VirtualBranchCommit {
|
||||
id: commit.id(),
|
||||
created_at: timestamp * 1000,
|
||||
@ -921,6 +927,8 @@ pub fn commit_to_vbranch_commit(
|
||||
is_remote,
|
||||
files,
|
||||
is_integrated,
|
||||
parent_ids,
|
||||
branch_id: branch.id,
|
||||
};
|
||||
|
||||
Ok(commit)
|
||||
|
60
packages/ui/src/lib/draggables.ts
Normal file
60
packages/ui/src/lib/draggables.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import type { Commit, File, Hunk, RemoteCommit } from './vbranches/types';
|
||||
|
||||
export function nonDraggable() {
|
||||
return {
|
||||
disabled: true,
|
||||
data: {}
|
||||
};
|
||||
}
|
||||
|
||||
export type DraggableHunk = {
|
||||
branchId: string;
|
||||
hunk: Hunk;
|
||||
};
|
||||
|
||||
export function draggableHunk(branchId: string, hunk: Hunk) {
|
||||
return { data: { branchId, hunk } };
|
||||
}
|
||||
|
||||
export function isDraggableHunk(obj: any): obj is DraggableHunk {
|
||||
return obj && obj.branchId && obj.hunk;
|
||||
}
|
||||
|
||||
export type DraggableFile = {
|
||||
branchId: string;
|
||||
file: File;
|
||||
};
|
||||
|
||||
export function draggableFile(branchId: string, file: File) {
|
||||
return { data: { branchId, file } };
|
||||
}
|
||||
|
||||
export function isDraggableFile(obj: any): obj is DraggableFile {
|
||||
return obj && obj.branchId && obj.file;
|
||||
}
|
||||
|
||||
export type DraggableCommit = {
|
||||
branchId: string;
|
||||
commit: Commit;
|
||||
};
|
||||
|
||||
export function draggableCommit(branchId: string, commit: Commit) {
|
||||
return { data: { branchId, commit } };
|
||||
}
|
||||
|
||||
export function isDraggableCommit(obj: any): obj is DraggableCommit {
|
||||
return obj && obj.branchId && obj.commit;
|
||||
}
|
||||
|
||||
export type DraggableRemoteCommit = {
|
||||
branchId: string;
|
||||
remoteCommit: RemoteCommit;
|
||||
};
|
||||
|
||||
export function draggableRemoteCommit(branchId: string, remoteCommit: RemoteCommit) {
|
||||
return { data: { branchId, remoteCommit } };
|
||||
}
|
||||
|
||||
export function isDraggableRemoteCommit(obj: any): obj is DraggableRemoteCommit {
|
||||
return obj && obj.branchId && obj.remoteCommit;
|
||||
}
|
@ -17,42 +17,15 @@ const defaultDropzoneOptions: Dropzone = {
|
||||
export function dropzone(node: HTMLElement, opts: Partial<Dropzone> | undefined) {
|
||||
let currentOptions = { ...defaultDropzoneOptions, ...opts };
|
||||
|
||||
function handleDragEnter(e: DragEvent) {
|
||||
if (activeZones.has(node)) {
|
||||
node.classList.add(currentOptions.hover);
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
function handleDragLeave(_e: DragEvent) {
|
||||
if (activeZones.has(node)) {
|
||||
node.classList.remove(currentOptions.hover);
|
||||
}
|
||||
}
|
||||
|
||||
function handleDragOver(e: DragEvent) {
|
||||
if (activeZones.has(node)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
function setup(opts: Partial<Dropzone> | undefined) {
|
||||
currentOptions = { ...defaultDropzoneOptions, ...opts };
|
||||
if (currentOptions.disabled) return;
|
||||
|
||||
register(node, currentOptions);
|
||||
|
||||
node.addEventListener('dragenter', handleDragEnter);
|
||||
node.addEventListener('dragleave', handleDragLeave);
|
||||
node.addEventListener('dragover', handleDragOver);
|
||||
}
|
||||
|
||||
function clean() {
|
||||
unregister(currentOptions);
|
||||
|
||||
node.removeEventListener('dragenter', handleDragEnter);
|
||||
node.removeEventListener('dragleave', handleDragLeave);
|
||||
node.removeEventListener('dragover', handleDragOver);
|
||||
}
|
||||
|
||||
setup(opts);
|
||||
@ -96,6 +69,9 @@ export function draggable(node: HTMLElement, opts: Partial<Draggable> | undefine
|
||||
let clone: HTMLElement;
|
||||
|
||||
const onDropListeners = new Map<HTMLElement, Array<(e: DragEvent) => void>>();
|
||||
const onDragLeaveListeners = new Map<HTMLElement, Array<(e: DragEvent) => void>>();
|
||||
const onDragEnterListeners = new Map<HTMLElement, Array<(e: DragEvent) => void>>();
|
||||
const onDragOverListeners = new Map<HTMLElement, Array<(e: DragEvent) => void>>();
|
||||
|
||||
/**
|
||||
* The problem with the ghost element is that it gets clipped after rotation unless we enclose
|
||||
@ -129,6 +105,20 @@ export function draggable(node: HTMLElement, opts: Partial<Draggable> | undefine
|
||||
dz.onDrop(currentOptions.data);
|
||||
};
|
||||
|
||||
const onDragEnter = (e: DragEvent) => {
|
||||
e.preventDefault();
|
||||
target.classList.add(dz.hover);
|
||||
};
|
||||
|
||||
const onDragLeave = (e: DragEvent) => {
|
||||
e.preventDefault();
|
||||
target.classList.remove(dz.hover);
|
||||
};
|
||||
|
||||
const onDragOver = (e: DragEvent) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
// keep track of listeners so that we can remove them later
|
||||
if (onDropListeners.has(target)) {
|
||||
onDropListeners.get(target)!.push(onDrop);
|
||||
@ -136,10 +126,33 @@ export function draggable(node: HTMLElement, opts: Partial<Draggable> | undefine
|
||||
onDropListeners.set(target, [onDrop]);
|
||||
}
|
||||
|
||||
if (onDragEnterListeners.has(target)) {
|
||||
onDragEnterListeners.get(target)!.push(onDragEnter);
|
||||
} else {
|
||||
onDragEnterListeners.set(target, [onDragEnter]);
|
||||
}
|
||||
|
||||
if (onDragLeaveListeners.has(target)) {
|
||||
onDragLeaveListeners.get(target)!.push(onDragLeave);
|
||||
} else {
|
||||
onDragLeaveListeners.set(target, [onDragLeave]);
|
||||
}
|
||||
|
||||
if (onDragOverListeners.has(target)) {
|
||||
onDragOverListeners.get(target)!.push(onDragOver);
|
||||
} else {
|
||||
onDragOverListeners.set(target, [onDragOver]);
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/14203734/dragend-dragenter-and-dragleave-firing-off-immediately-when-i-drag
|
||||
setTimeout(() => target.classList.add(dz.active), 10);
|
||||
setTimeout(() => {
|
||||
target.classList.add(dz.active);
|
||||
}, 10);
|
||||
|
||||
target.addEventListener('drop', onDrop);
|
||||
target.addEventListener('dragenter', onDragEnter);
|
||||
target.addEventListener('dragleave', onDragLeave);
|
||||
target.addEventListener('dragover', onDragOver);
|
||||
activeZones.add(target);
|
||||
});
|
||||
|
||||
@ -156,14 +169,21 @@ export function draggable(node: HTMLElement, opts: Partial<Draggable> | undefine
|
||||
.filter(([_node, dz]) => dz.accepts(currentOptions.data))
|
||||
.forEach(([node, dz]) => {
|
||||
// remove all listeners
|
||||
const onDrop = onDropListeners.get(node);
|
||||
if (onDrop) {
|
||||
onDrop.forEach((listener) => {
|
||||
node.removeEventListener('drop', listener);
|
||||
});
|
||||
}
|
||||
onDropListeners.get(node)?.forEach((listener) => {
|
||||
node.removeEventListener('drop', listener);
|
||||
});
|
||||
onDragEnterListeners.get(node)?.forEach((listener) => {
|
||||
node.removeEventListener('dragenter', listener);
|
||||
});
|
||||
onDragLeaveListeners.get(node)?.forEach((listener) => {
|
||||
node.removeEventListener('dragleave', listener);
|
||||
});
|
||||
onDragOverListeners.get(node)?.forEach((listener) => {
|
||||
node.removeEventListener('dragover', listener);
|
||||
});
|
||||
|
||||
node.classList.remove(dz.active);
|
||||
node.classList.remove(dz.hover);
|
||||
activeZones.delete(node);
|
||||
});
|
||||
|
||||
|
@ -254,6 +254,19 @@ export class BranchController {
|
||||
}
|
||||
}
|
||||
|
||||
async squashBranchCommit(branchId: string, targetCommitOid: string) {
|
||||
try {
|
||||
await invoke<void>('squash_branch_commit', {
|
||||
projectId: this.projectId,
|
||||
branchId,
|
||||
targetCommitOid
|
||||
});
|
||||
await this.virtualBranchStore.reload();
|
||||
} catch (err: any) {
|
||||
toasts.error(`Failed to squash commit: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async amendBranch(branchId: string, ownership: string) {
|
||||
try {
|
||||
await invoke<void>('amend_virtual_branch', {
|
||||
|
@ -58,6 +58,8 @@ export class Commit {
|
||||
isIntegrated!: boolean;
|
||||
@Type(() => File)
|
||||
files!: File[];
|
||||
parentIds!: string[];
|
||||
branchId!: string;
|
||||
}
|
||||
|
||||
export class RemoteCommit {
|
||||
|
@ -1,21 +1,24 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
isDraggableHunk,
|
||||
isDraggableFile,
|
||||
type DraggableFile,
|
||||
type DraggableHunk
|
||||
} from '$lib/draggables';
|
||||
import { dropzone } from '$lib/utils/draggable';
|
||||
import type { BranchController } from '$lib/vbranches/branchController';
|
||||
import type { File, Hunk } from '$lib/vbranches/types';
|
||||
|
||||
export let branchController: BranchController;
|
||||
|
||||
function accepts(data: { hunk?: Hunk; file?: File }) {
|
||||
if (data.hunk !== undefined) return true;
|
||||
if (data.file !== undefined) return true;
|
||||
return false;
|
||||
function accepts(data: any) {
|
||||
return isDraggableFile(data) || isDraggableHunk(data);
|
||||
}
|
||||
|
||||
function onDrop(data: { hunk?: Hunk; file?: File }) {
|
||||
if (data.hunk) {
|
||||
function onDrop(data: DraggableFile | DraggableHunk) {
|
||||
if (isDraggableHunk(data)) {
|
||||
const ownership = `${data.hunk.filePath}:${data.hunk.id}`;
|
||||
branchController.createBranch({ ownership });
|
||||
} else if (data.file) {
|
||||
} else if (isDraggableFile(data)) {
|
||||
const ownership = `${data.file.path}:${data.file.hunks.map(({ id }) => id).join(',')}`;
|
||||
branchController.createBranch({ ownership });
|
||||
}
|
||||
|
@ -1,45 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { dropzone } from '$lib/utils/draggable';
|
||||
import type { Hunk, File, RemoteCommit } from '$lib/vbranches/types';
|
||||
import type { BranchController } from '$lib/vbranches/branchController';
|
||||
import CommitCard from './CommitCard.svelte';
|
||||
|
||||
export let branchController: BranchController;
|
||||
export let branchId: string;
|
||||
export let commit: RemoteCommit;
|
||||
export let projectId: string;
|
||||
export let commitUrl: string | undefined = undefined;
|
||||
|
||||
function acceptBranchDrop(data: { branchId: string; file?: File; hunk?: Hunk }) {
|
||||
if (data.branchId !== branchId) return false;
|
||||
return !!data.file || !!data.hunk;
|
||||
}
|
||||
|
||||
function onDrop(data: { file?: File; hunk?: Hunk }) {
|
||||
if (data.hunk) {
|
||||
const newOwnership = `${data.hunk.filePath}:${data.hunk.id}`;
|
||||
branchController.amendBranch(branchId, newOwnership);
|
||||
} else if (data.file) {
|
||||
const newOwnership = `${data.file.path}:${data.file.hunks.map(({ id }) => id).join(',')}`;
|
||||
branchController.amendBranch(branchId, newOwnership);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="relative h-full w-full"
|
||||
use:dropzone={{
|
||||
active: 'amend-dz-active',
|
||||
hover: 'amend-dz-hover',
|
||||
accepts: acceptBranchDrop,
|
||||
onDrop: onDrop
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class="amend-dz-marker absolute z-10 hidden h-full w-full items-center justify-center rounded bg-blue-100/70 outline-dashed outline-2 -outline-offset-8 outline-light-600 dark:bg-blue-900/60 dark:outline-dark-300"
|
||||
>
|
||||
<div class="hover-text font-semibold">Amend</div>
|
||||
</div>
|
||||
|
||||
<CommitCard {commit} {projectId} {commitUrl} />
|
||||
</div>
|
@ -1,8 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { userStore } from '$lib/stores/user';
|
||||
import type { BaseBranch, Branch, File, Hunk, RemoteCommit } from '$lib/vbranches/types';
|
||||
import type { BaseBranch, Branch, Commit } from '$lib/vbranches/types';
|
||||
import { getContext, onDestroy, onMount } from 'svelte';
|
||||
import { draggable, dropzone } from '$lib/utils/draggable';
|
||||
import {
|
||||
isDraggableHunk,
|
||||
isDraggableFile,
|
||||
isDraggableCommit,
|
||||
type DraggableCommit,
|
||||
type DraggableFile,
|
||||
type DraggableHunk,
|
||||
draggableRemoteCommit
|
||||
} from '$lib/draggables';
|
||||
import { Ownership } from '$lib/vbranches/ownership';
|
||||
import IconKebabMenu from '$lib/icons/IconKebabMenu.svelte';
|
||||
import CommitCard from './CommitCard.svelte';
|
||||
@ -39,7 +48,7 @@
|
||||
import Button from '$lib/components/Button.svelte';
|
||||
import Link from '$lib/components/Link.svelte';
|
||||
import Modal from '$lib/components/Modal.svelte';
|
||||
import AmendableCommitCard from './AmendableCommitCard.svelte';
|
||||
import { isDraggableRemoteCommit, type DraggableRemoteCommit } from '$lib/draggables';
|
||||
|
||||
const [send, receive] = crossfade({
|
||||
duration: (d) => Math.sqrt(d * 200),
|
||||
@ -259,29 +268,32 @@
|
||||
const selectedOwnership = writable(Ownership.fromBranch(branch));
|
||||
$: if (commitDialogShown) selectedOwnership.set(Ownership.fromBranch(branch));
|
||||
|
||||
function acceptCherrypick(data: { branchId?: string; commit?: RemoteCommit }) {
|
||||
return data?.branchId === branch.id && data.commit !== undefined;
|
||||
function acceptCherrypick(data: any) {
|
||||
return isDraggableRemoteCommit(data) && data.branchId == branch.id;
|
||||
}
|
||||
|
||||
function onCherrypicked(data: { branchId: string; commit: RemoteCommit }) {
|
||||
branchController.cherryPick(branch.id, data.commit.id);
|
||||
function onCherrypicked(data: DraggableRemoteCommit) {
|
||||
branchController.cherryPick(branch.id, data.remoteCommit.id);
|
||||
}
|
||||
|
||||
function acceptBranchDrop(data: { branchId: string; file?: File; hunk?: Hunk }) {
|
||||
if (data.branchId === branch.id) return false; // can't drag to the same branch
|
||||
if (data.hunk !== undefined && !data.hunk.locked) return true; // can only drag not locked hunks
|
||||
if (data.file !== undefined && data.file.hunks.some((hunk) => !hunk.locked)) return true; // can only draged non fully locked files
|
||||
return false;
|
||||
function acceptBranchDrop(data: any) {
|
||||
if (isDraggableHunk(data) && data.branchId != branch.id) {
|
||||
return true;
|
||||
} else if (isDraggableFile(data) && data.branchId != branch.id) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function onBranchDrop(data: { file?: File; hunk?: Hunk }) {
|
||||
if (data.hunk) {
|
||||
function onBranchDrop(data: DraggableHunk | DraggableFile) {
|
||||
if (isDraggableHunk(data)) {
|
||||
const newOwnership = `${data.hunk.filePath}:${data.hunk.id}`;
|
||||
branchController.updateBranchOwnership(
|
||||
branch.id,
|
||||
(newOwnership + '\n' + branch.ownership).trim()
|
||||
);
|
||||
} else if (data.file) {
|
||||
} else if (isDraggableFile(data)) {
|
||||
const newOwnership = `${data.file.path}:${data.file.hunks.map(({ id }) => id).join(',')}`;
|
||||
branchController.updateBranchOwnership(
|
||||
branch.id,
|
||||
@ -289,6 +301,59 @@
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function acceptAmend(commit: Commit) {
|
||||
return (data: any) => {
|
||||
if (
|
||||
isDraggableHunk(data) &&
|
||||
data.branchId == branch.id &&
|
||||
commit.id == branch.commits.at(0)?.id
|
||||
) {
|
||||
return true;
|
||||
} else if (
|
||||
isDraggableFile(data) &&
|
||||
data.branchId == branch.id &&
|
||||
commit.id == branch.commits.at(0)?.id
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function onAmend(data: DraggableFile | DraggableHunk) {
|
||||
if (isDraggableHunk(data)) {
|
||||
const newOwnership = `${data.hunk.filePath}:${data.hunk.id}`;
|
||||
branchController.amendBranch(branch.id, newOwnership);
|
||||
} else if (isDraggableFile(data)) {
|
||||
const newOwnership = `${data.file.path}:${data.file.hunks.map(({ id }) => id).join(',')}`;
|
||||
branchController.amendBranch(branch.id, newOwnership);
|
||||
}
|
||||
}
|
||||
|
||||
function acceptSquash(commit: Commit) {
|
||||
return (data: any) => {
|
||||
return (
|
||||
isDraggableCommit(data) &&
|
||||
data.branchId == branch.id &&
|
||||
(commit.parentIds.includes(data.commit.id) || data.commit.parentIds.includes(commit.id))
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function onSquash(commit: Commit) {
|
||||
function isParentOf(commit: Commit, other: Commit) {
|
||||
return commit.parentIds.includes(other.id);
|
||||
}
|
||||
return (data: DraggableCommit) => {
|
||||
if (isParentOf(commit, data.commit)) {
|
||||
branchController.squashBranchCommit(data.branchId, commit.id);
|
||||
} else if (isParentOf(data.commit, commit)) {
|
||||
branchController.squashBranchCommit(data.branchId, data.commit.id);
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex h-full shrink-0 snap-center" style:width={maximized ? '100%' : `${laneWidth}px`}>
|
||||
@ -435,7 +500,7 @@
|
||||
id="upstreamCommits"
|
||||
>
|
||||
{#each branch.upstream.commits as commit (commit.id)}
|
||||
<div use:draggable={{ data: { branchId: branch.id, commit } }}>
|
||||
<div use:draggable={draggableRemoteCommit(branch.id, commit)}>
|
||||
<CommitCard {commit} {projectId} commitUrl={base?.commitUrl(commit.id)} />
|
||||
</div>
|
||||
{/each}
|
||||
@ -649,17 +714,34 @@
|
||||
<div class="border-color-4 h-3 w-3 rounded-full border-2" />
|
||||
</div>
|
||||
{/if}
|
||||
{#if branch.commits.at(0)?.id === commit.id}
|
||||
<AmendableCommitCard
|
||||
{branchController}
|
||||
branchId={branch.id}
|
||||
{commit}
|
||||
{projectId}
|
||||
commitUrl={base?.commitUrl(commit.id)}
|
||||
/>
|
||||
{:else}
|
||||
<div
|
||||
class="relative h-full w-full"
|
||||
use:dropzone={{
|
||||
active: 'amend-dz-active',
|
||||
hover: 'amend-dz-hover',
|
||||
accepts: acceptAmend(commit),
|
||||
onDrop: onAmend
|
||||
}}
|
||||
use:dropzone={{
|
||||
active: 'squash-dz-active',
|
||||
hover: 'squash-dz-hover',
|
||||
accepts: acceptSquash(commit),
|
||||
onDrop: onSquash(commit)
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class="amend-dz-marker absolute z-10 hidden h-full w-full items-center justify-center rounded bg-blue-100/70 outline-dashed outline-2 -outline-offset-8 outline-light-600 dark:bg-blue-900/60 dark:outline-dark-300"
|
||||
>
|
||||
<div class="hover-text font-semibold">Amend</div>
|
||||
</div>
|
||||
<div
|
||||
class="squash-dz-marker absolute z-10 hidden h-full w-full items-center justify-center rounded bg-blue-100/70 outline-dashed outline-2 -outline-offset-8 outline-light-600 dark:bg-blue-900/60 dark:outline-dark-300"
|
||||
>
|
||||
<div class="hover-text font-semibold">Squash</div>
|
||||
</div>
|
||||
|
||||
<CommitCard {commit} {projectId} commitUrl={base?.commitUrl(commit.id)} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
@ -746,17 +828,35 @@
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if branch.commits.at(0)?.id === commit.id}
|
||||
<AmendableCommitCard
|
||||
{branchController}
|
||||
branchId={branch.id}
|
||||
{commit}
|
||||
{projectId}
|
||||
commitUrl={base?.commitUrl(commit.id)}
|
||||
/>
|
||||
{:else}
|
||||
|
||||
<div
|
||||
class="relative h-full w-full"
|
||||
use:dropzone={{
|
||||
active: 'amend-dz-active',
|
||||
hover: 'amend-dz-hover',
|
||||
accepts: acceptAmend(commit),
|
||||
onDrop: onAmend
|
||||
}}
|
||||
use:dropzone={{
|
||||
active: 'squash-dz-active',
|
||||
hover: 'squash-dz-hover',
|
||||
accepts: acceptSquash(commit),
|
||||
onDrop: onSquash(commit)
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class="amend-dz-marker absolute z-10 hidden h-full w-full items-center justify-center rounded bg-blue-100/70 outline-dashed outline-2 -outline-offset-8 outline-light-600 dark:bg-blue-900/60 dark:outline-dark-300"
|
||||
>
|
||||
<div class="hover-text font-semibold">Amend</div>
|
||||
</div>
|
||||
<div
|
||||
class="squash-dz-marker absolute z-10 hidden h-full w-full items-center justify-center rounded bg-blue-100/70 outline-dashed outline-2 -outline-offset-8 outline-light-600 dark:bg-blue-900/60 dark:outline-dark-300"
|
||||
>
|
||||
<div class="hover-text font-semibold">Squash</div>
|
||||
</div>
|
||||
|
||||
<CommitCard {commit} {projectId} commitUrl={base?.commitUrl(commit.id)} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
@ -884,4 +984,12 @@
|
||||
:global(.cherrypick-dz-hover .hover-text) {
|
||||
@apply visible;
|
||||
}
|
||||
|
||||
/* squash drop zone */
|
||||
:global(.squash-dz-active .squash-dz-marker) {
|
||||
@apply flex;
|
||||
}
|
||||
:global(.squash-dz-hover .hover-text) {
|
||||
@apply visible;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { RemoteFile, type RemoteCommit } from '$lib/vbranches/types';
|
||||
import { RemoteFile, type RemoteCommit, Commit } from '$lib/vbranches/types';
|
||||
import TimeAgo from '$lib/components/TimeAgo.svelte';
|
||||
import { getVSIFileIcon } from '$lib/ext-icons';
|
||||
import { ContentSection, HunkSection, parseFileSections } from './fileSections';
|
||||
@ -10,8 +10,10 @@
|
||||
import Modal from '$lib/components/Modal.svelte';
|
||||
import Button from '$lib/components/Button.svelte';
|
||||
import Link from '$lib/components/Link.svelte';
|
||||
import { draggableCommit, nonDraggable } from '$lib/draggables';
|
||||
import { draggable } from '$lib/utils/draggable';
|
||||
|
||||
export let commit: RemoteCommit;
|
||||
export let commit: Commit | RemoteCommit;
|
||||
export let projectId: string;
|
||||
export let commitUrl: string | undefined = undefined;
|
||||
|
||||
@ -35,10 +37,12 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="text-color-2 bg-color-5 border-color-4 relative w-full truncate rounded border p-2 text-left"
|
||||
use:draggable={commit instanceof Commit
|
||||
? draggableCommit(commit.branchId, commit)
|
||||
: nonDraggable()}
|
||||
>
|
||||
<div class="mb-1 flex justify-between">
|
||||
<div class="truncate">
|
||||
<div class="text-color-2 bg-color-5 border-color-4 truncate rounded border p-2 text-left">
|
||||
<div class="mb-1 truncate">
|
||||
<button
|
||||
on:click={() => {
|
||||
loadEntries();
|
||||
@ -48,21 +52,21 @@
|
||||
{commit.description}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-color-3 flex space-x-1 text-sm">
|
||||
<img
|
||||
class="relative inline-block h-4 w-4 rounded-full ring-1 ring-white dark:ring-black"
|
||||
title="Gravatar for {commit.author.email}"
|
||||
alt="Gravatar for {commit.author.email}"
|
||||
srcset="{commit.author.gravatarUrl} 2x"
|
||||
width="100"
|
||||
height="100"
|
||||
on:error
|
||||
/>
|
||||
<div class="flex-grow truncate">{commit.author.name}</div>
|
||||
<div class="truncate">
|
||||
<TimeAgo date={commit.createdAt} />
|
||||
<div class="text-color-3 flex space-x-1 text-sm">
|
||||
<img
|
||||
class="relative inline-block h-4 w-4 rounded-full ring-1 ring-white dark:ring-black"
|
||||
title="Gravatar for {commit.author.email}"
|
||||
alt="Gravatar for {commit.author.email}"
|
||||
srcset="{commit.author.gravatarUrl} 2x"
|
||||
width="100"
|
||||
height="100"
|
||||
on:error
|
||||
/>
|
||||
<div class="flex-1 truncate">{commit.author.name}</div>
|
||||
<div class="truncate">
|
||||
<TimeAgo date={commit.createdAt} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -77,7 +81,7 @@
|
||||
<div class="overflow-y-scroll">
|
||||
{#if isLoading}
|
||||
<div class="flex w-full justify-center">
|
||||
<div class="h-32 w-32 animate-spin rounded-full border-b-2 border-gray-900" />
|
||||
<div class="border-gray-900 h-32 w-32 animate-spin rounded-full border-b-2" />
|
||||
</div>
|
||||
{:else}
|
||||
{#each entries as [filepath, sections]}
|
||||
|
@ -23,6 +23,7 @@
|
||||
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||
import IconLock from '$lib/icons/IconLock.svelte';
|
||||
import HunkContextMenu from './HunkContextMenu.svelte';
|
||||
import { draggableFile, draggableHunk } from '$lib/draggables';
|
||||
|
||||
export let branchId: string;
|
||||
export let file: File;
|
||||
@ -96,7 +97,7 @@
|
||||
<div
|
||||
id={`file-${file.id}`}
|
||||
use:draggable={{
|
||||
data: { branchId, file },
|
||||
...draggableFile(branchId, file),
|
||||
disabled: readonly
|
||||
}}
|
||||
class="changed-file inner"
|
||||
@ -192,10 +193,7 @@
|
||||
tabindex="0"
|
||||
role="cell"
|
||||
use:draggable={{
|
||||
data: {
|
||||
branchId,
|
||||
hunk: section.hunk
|
||||
},
|
||||
...draggableHunk(branchId, section.hunk),
|
||||
disabled: readonly
|
||||
}}
|
||||
on:dblclick
|
||||
|
Loading…
Reference in New Issue
Block a user