mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2025-01-04 15:53:30 +03:00
Move branch lane to branch card
This commit is contained in:
parent
7350fc59d0
commit
9471e8567a
@ -37,8 +37,7 @@
|
||||
<div class="p-4">Loading...</div>
|
||||
{:else}
|
||||
<div
|
||||
id="branch-lanes"
|
||||
class="flex h-full flex-shrink flex-grow items-start px-1"
|
||||
class="board"
|
||||
role="group"
|
||||
bind:this={dropZone}
|
||||
on:dragover={(e) => {
|
||||
@ -171,3 +170,14 @@
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="postcss">
|
||||
.board {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
align-items: start;
|
||||
height: 100%;
|
||||
padding: var(--space-16);
|
||||
}
|
||||
</style>
|
||||
|
460
packages/ui/src/routes/[projectId]/components/BranchCard.svelte
Normal file
460
packages/ui/src/routes/[projectId]/components/BranchCard.svelte
Normal file
@ -0,0 +1,460 @@
|
||||
<script lang="ts">
|
||||
import type { BaseBranch, Branch, Commit } from '$lib/vbranches/types';
|
||||
import { getContext, onMount } from 'svelte';
|
||||
import { dropzone } from '$lib/utils/draggable';
|
||||
import {
|
||||
isDraggableHunk,
|
||||
isDraggableFile,
|
||||
isDraggableCommit,
|
||||
type DraggableCommit,
|
||||
type DraggableFile,
|
||||
type DraggableHunk
|
||||
} from '$lib/draggables';
|
||||
import { Ownership } from '$lib/vbranches/ownership';
|
||||
import { getExpandedWithCacheFallback, setExpandedWithCache } from './cache';
|
||||
import type { BranchController } from '$lib/vbranches/branchController';
|
||||
import { quintOut } from 'svelte/easing';
|
||||
import { crossfade } from 'svelte/transition';
|
||||
import type { User, getCloudApiClient } from '$lib/backend/cloud';
|
||||
import Resizer from '$lib/components/Resizer.svelte';
|
||||
import { SETTINGS_CONTEXT, type SettingsStore } from '$lib/settings/userSettings';
|
||||
import lscache from 'lscache';
|
||||
import CommitDialog from './CommitDialog.svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import { computedAddedRemoved } from '$lib/vbranches/fileStatus';
|
||||
import { getPullRequestByBranch, createPullRequest } from '$lib/github/pullrequest';
|
||||
import type { GitHubIntegrationContext } from '$lib/github/types';
|
||||
import { isDraggableRemoteCommit, type DraggableRemoteCommit } from '$lib/draggables';
|
||||
import BranchHeader from './BranchHeader.svelte';
|
||||
import UpstreamCommits from './UpstreamCommits.svelte';
|
||||
import BranchFiles from './BranchFiles.svelte';
|
||||
import LocalCommits from './LocalCommits.svelte';
|
||||
import RemoteCommits from './RemoteCommits.svelte';
|
||||
import IntegratedCommits from './IntegratedCommits.svelte';
|
||||
|
||||
const [send, receive] = crossfade({
|
||||
duration: (d) => Math.sqrt(d * 200),
|
||||
|
||||
fallback(node) {
|
||||
const style = getComputedStyle(node);
|
||||
const transform = style.transform === 'none' ? '' : style.transform;
|
||||
|
||||
return {
|
||||
duration: 600,
|
||||
easing: quintOut,
|
||||
css: (t) => `
|
||||
transform: ${transform} scale(${t});
|
||||
opacity: ${t}
|
||||
`
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
export let branch: Branch;
|
||||
export let readonly = false;
|
||||
export let projectId: string;
|
||||
export let base: BaseBranch | undefined | null;
|
||||
export let cloudEnabled: boolean;
|
||||
export let cloud: ReturnType<typeof getCloudApiClient>;
|
||||
export let branchController: BranchController;
|
||||
export let maximized = false;
|
||||
export let branchCount = 1;
|
||||
export let githubContext: GitHubIntegrationContext | undefined;
|
||||
export let user: User | undefined;
|
||||
|
||||
const userSettings = getContext<SettingsStore>(SETTINGS_CONTEXT);
|
||||
|
||||
const allExpanded = writable(false);
|
||||
const allCollapsed = writable(false);
|
||||
let rsViewport: HTMLElement;
|
||||
let viewport: HTMLElement;
|
||||
let contents: HTMLElement;
|
||||
|
||||
const laneWidthKey = 'laneWidth:';
|
||||
let laneWidth: number;
|
||||
|
||||
$: prPromise =
|
||||
githubContext && branch.upstream
|
||||
? getPullRequestByBranch(githubContext, branch.upstream?.name.split('/').slice(-1)[0])
|
||||
: undefined;
|
||||
|
||||
$: branchName = branch.upstream?.name.split('/').slice(-1)[0];
|
||||
|
||||
function createPr() {
|
||||
if (githubContext && base?.branchName && branchName) {
|
||||
createPullRequest(
|
||||
githubContext,
|
||||
branchName,
|
||||
base.branchName.split('/').slice(-1)[0],
|
||||
branch.name,
|
||||
branch.notes
|
||||
).then((pr) => {
|
||||
prPromise = Promise.resolve(pr);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
// On refresh we need to check expansion status from localStorage
|
||||
branch.files && expandFromCache();
|
||||
}
|
||||
|
||||
function expandFromCache() {
|
||||
// Exercise cache lookup for all files.
|
||||
$allExpanded = branch.files.every((f) => getExpandedWithCacheFallback(f));
|
||||
$allCollapsed = branch.files.every((f) => getExpandedWithCacheFallback(f) == false);
|
||||
}
|
||||
|
||||
function handleCollapseAll() {
|
||||
branch.files.forEach((f) => setExpandedWithCache(f, false));
|
||||
$allExpanded = false;
|
||||
branch.files = branch.files;
|
||||
}
|
||||
|
||||
function handleExpandAll() {
|
||||
branch.files.forEach((f) => setExpandedWithCache(f, true));
|
||||
$allExpanded = true;
|
||||
branch.files = branch.files;
|
||||
}
|
||||
|
||||
let commitDialogShown = false;
|
||||
|
||||
$: if (commitDialogShown && branch.files.length === 0) {
|
||||
commitDialogShown = false;
|
||||
}
|
||||
|
||||
function generateBranchName() {
|
||||
const diff = branch.files
|
||||
.map((f) => f.hunks)
|
||||
.flat()
|
||||
.map((h) => h.diff)
|
||||
.flat()
|
||||
.join('\n')
|
||||
.slice(0, 5000);
|
||||
|
||||
if (user) {
|
||||
cloud.summarize.branch(user.access_token, { diff }).then((result) => {
|
||||
if (result.message && result.message !== branch.name) {
|
||||
branch.name = result.message;
|
||||
branchController.updateBranchName(branch.id, branch.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$: linesTouched = computedAddedRemoved(...branch.files);
|
||||
$: if (
|
||||
branch.name.toLowerCase().includes('virtual branch') &&
|
||||
linesTouched.added + linesTouched.removed > 4
|
||||
) {
|
||||
generateBranchName();
|
||||
}
|
||||
|
||||
function resetHeadCommit() {
|
||||
if (branch.commits.length > 1) {
|
||||
branchController.resetBranch(branch.id, branch.commits[1].id);
|
||||
} else if (branch.commits.length === 1 && base) {
|
||||
branchController.resetBranch(branch.id, base.baseSha);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
expandFromCache();
|
||||
laneWidth = lscache.get(laneWidthKey + branch.id) ?? $userSettings.defaultLaneWidth;
|
||||
});
|
||||
|
||||
const selectedOwnership = writable(Ownership.fromBranch(branch));
|
||||
$: if (commitDialogShown) selectedOwnership.set(Ownership.fromBranch(branch));
|
||||
|
||||
function acceptCherrypick(data: any) {
|
||||
return isDraggableRemoteCommit(data) && data.branchId == branch.id;
|
||||
}
|
||||
|
||||
function onCherrypicked(data: DraggableRemoteCommit) {
|
||||
branchController.cherryPick(branch.id, data.remoteCommit.id);
|
||||
}
|
||||
|
||||
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: DraggableHunk | DraggableFile) {
|
||||
if (isDraggableHunk(data)) {
|
||||
const newOwnership = `${data.hunk.filePath}:${data.hunk.id}`;
|
||||
branchController.updateBranchOwnership(
|
||||
branch.id,
|
||||
(newOwnership + '\n' + branch.ownership).trim()
|
||||
);
|
||||
} else if (isDraggableFile(data)) {
|
||||
const newOwnership = `${data.file.path}:${data.file.hunks.map(({ id }) => id).join(',')}`;
|
||||
branchController.updateBranchOwnership(
|
||||
branch.id,
|
||||
(newOwnership + '\n' + branch.ownership).trim()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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="branch-card" style:width={maximized ? '100%' : `${laneWidth}px`}>
|
||||
<div class="flex">
|
||||
<div class="border-color-4 flex flex-grow flex-col">
|
||||
<BranchHeader
|
||||
{branchController}
|
||||
{branch}
|
||||
{allCollapsed}
|
||||
{allExpanded}
|
||||
on:action={(e) => {
|
||||
if (e.detail == 'expand') {
|
||||
handleExpandAll();
|
||||
} else if (e.detail == 'collapse') {
|
||||
handleCollapseAll();
|
||||
} else if (e.detail == 'generate-branch-name') {
|
||||
generateBranchName();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{#if branch.upstream?.commits.length && branch.upstream?.commits.length > 0 && !branch.conflicted}
|
||||
<UpstreamCommits
|
||||
upstream={branch.upstream}
|
||||
branchId={branch.id}
|
||||
{branchController}
|
||||
{branchCount}
|
||||
{projectId}
|
||||
{base}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="relative flex flex-grow overflow-y-hidden"
|
||||
use:dropzone={{
|
||||
hover: 'cherrypick-dz-hover',
|
||||
active: 'cherrypick-dz-active',
|
||||
accepts: acceptCherrypick,
|
||||
onDrop: onCherrypicked
|
||||
}}
|
||||
use:dropzone={{
|
||||
hover: 'lane-dz-hover',
|
||||
active: 'lane-dz-active',
|
||||
accepts: acceptBranchDrop,
|
||||
onDrop: onBranchDrop
|
||||
}}
|
||||
>
|
||||
<!-- TODO: Figure out why z-10 is necessary for expand up/down to not come out on top -->
|
||||
<div
|
||||
class="cherrypick-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 invisible font-semibold">Apply here</div>
|
||||
</div>
|
||||
|
||||
<!-- TODO: Figure out why z-10 is necessary for expand up/down to not come out on top -->
|
||||
<div
|
||||
class="lane-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 invisible font-semibold">Move here</div>
|
||||
</div>
|
||||
<div bind:this={viewport} class="scroll-container hide-native-scrollbar">
|
||||
<div bind:this={contents} class="flex min-h-full flex-col">
|
||||
{#if branch.files?.length > 0}
|
||||
<BranchFiles {branch} {readonly} {selectedOwnership} />
|
||||
{#if branch.active}
|
||||
<CommitDialog
|
||||
{projectId}
|
||||
{branchController}
|
||||
{branch}
|
||||
{cloudEnabled}
|
||||
{cloud}
|
||||
{selectedOwnership}
|
||||
{user}
|
||||
/>
|
||||
{/if}
|
||||
{:else if branch.commits.length == 0}
|
||||
<div class="new-branch" data-dnd-ignore>
|
||||
<h1 class="text-base-16 text-semibold">Nothing on this branch yet</h1>
|
||||
<p class="px-12">Get some work done, then throw some files my way!</p>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- attention: these markers have custom css at the bottom of thise file -->
|
||||
<div class="no-changes" data-dnd-ignore>
|
||||
<h1 class="text-base-16 text-semibold">No uncommitted changes on this branch</h1>
|
||||
</div>
|
||||
{/if}
|
||||
{#if branch.commits.length > 0}
|
||||
<LocalCommits
|
||||
{branch}
|
||||
{base}
|
||||
{send}
|
||||
{receive}
|
||||
{prPromise}
|
||||
{githubContext}
|
||||
{projectId}
|
||||
{branchController}
|
||||
{acceptAmend}
|
||||
{acceptSquash}
|
||||
{onAmend}
|
||||
{onSquash}
|
||||
{resetHeadCommit}
|
||||
{createPr}
|
||||
/>
|
||||
<RemoteCommits
|
||||
{branch}
|
||||
{base}
|
||||
{send}
|
||||
{receive}
|
||||
{prPromise}
|
||||
{githubContext}
|
||||
{projectId}
|
||||
{acceptAmend}
|
||||
{acceptSquash}
|
||||
{onAmend}
|
||||
{onSquash}
|
||||
{resetHeadCommit}
|
||||
{createPr}
|
||||
/>
|
||||
<IntegratedCommits {branch} {base} {send} {receive} {projectId} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#if !maximized}
|
||||
<Resizer
|
||||
minWidth={330}
|
||||
viewport={rsViewport}
|
||||
direction="horizontal"
|
||||
class="z-30"
|
||||
on:width={(e) => {
|
||||
laneWidth = e.detail;
|
||||
lscache.set(laneWidthKey + branch.id, e.detail, 7 * 1440); // 7 day ttl
|
||||
userSettings.update((s) => ({
|
||||
...s,
|
||||
defaultLaneWidth: e.detail
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<style lang="postcss">
|
||||
.branch-card {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
cursor: default;
|
||||
overflow-x: hidden;
|
||||
background: var(--clr-theme-container-light);
|
||||
border-radius: var(--radius-m);
|
||||
}
|
||||
|
||||
.scroll-container {
|
||||
max-height: 100%;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
overflow-y: scroll;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
.new-branch,
|
||||
.no-changes {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
background: var(--clr-theme-container-light);
|
||||
justify-content: center;
|
||||
gap: var(--space-8);
|
||||
|
||||
& h1 {
|
||||
color: var(--clr-theme-scale-ntrl-40);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.new-branch p {
|
||||
color: var(--clr-theme-scale-ntrl-50);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* hunks drop zone */
|
||||
:global(.lane-dz-active .lane-dz-marker) {
|
||||
@apply flex;
|
||||
}
|
||||
:global(.lane-dz-hover .hover-text) {
|
||||
@apply visible;
|
||||
}
|
||||
|
||||
/* cherry pick drop zone */
|
||||
:global(.cherrypick-dz-active .cherrypick-dz-marker) {
|
||||
@apply flex;
|
||||
}
|
||||
: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,56 +1,9 @@
|
||||
<script lang="ts">
|
||||
import type { BaseBranch, Branch, Commit } from '$lib/vbranches/types';
|
||||
import { getContext, onMount } from 'svelte';
|
||||
import { dropzone } from '$lib/utils/draggable';
|
||||
import {
|
||||
isDraggableHunk,
|
||||
isDraggableFile,
|
||||
isDraggableCommit,
|
||||
type DraggableCommit,
|
||||
type DraggableFile,
|
||||
type DraggableHunk
|
||||
} from '$lib/draggables';
|
||||
import { Ownership } from '$lib/vbranches/ownership';
|
||||
import { getExpandedWithCacheFallback, setExpandedWithCache } from './cache';
|
||||
import type { BaseBranch, Branch } from '$lib/vbranches/types';
|
||||
import type { BranchController } from '$lib/vbranches/branchController';
|
||||
import { quintOut } from 'svelte/easing';
|
||||
import { crossfade } from 'svelte/transition';
|
||||
import type { User, getCloudApiClient } from '$lib/backend/cloud';
|
||||
import Scrollbar from '$lib/components/Scrollbar.svelte';
|
||||
import Resizer from '$lib/components/Resizer.svelte';
|
||||
import { SETTINGS_CONTEXT, type SettingsStore } from '$lib/settings/userSettings';
|
||||
import lscache from 'lscache';
|
||||
import CommitDialog from './CommitDialog.svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import { computedAddedRemoved } from '$lib/vbranches/fileStatus';
|
||||
import { getPullRequestByBranch, createPullRequest } from '$lib/github/pullrequest';
|
||||
import type { GitHubIntegrationContext } from '$lib/github/types';
|
||||
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||
import { isDraggableRemoteCommit, type DraggableRemoteCommit } from '$lib/draggables';
|
||||
import BranchHeader from './BranchHeader.svelte';
|
||||
import UpstreamCommits from './UpstreamCommits.svelte';
|
||||
import BranchFiles from './BranchFiles.svelte';
|
||||
import LocalCommits from './LocalCommits.svelte';
|
||||
import RemoteCommits from './RemoteCommits.svelte';
|
||||
import IntegratedCommits from './IntegratedCommits.svelte';
|
||||
|
||||
const [send, receive] = crossfade({
|
||||
duration: (d) => Math.sqrt(d * 200),
|
||||
|
||||
fallback(node) {
|
||||
const style = getComputedStyle(node);
|
||||
const transform = style.transform === 'none' ? '' : style.transform;
|
||||
|
||||
return {
|
||||
duration: 600,
|
||||
easing: quintOut,
|
||||
css: (t) => `
|
||||
transform: ${transform} scale(${t});
|
||||
opacity: ${t}
|
||||
`
|
||||
};
|
||||
}
|
||||
});
|
||||
import BranchCard from './BranchCard.svelte';
|
||||
|
||||
export let branch: Branch;
|
||||
export let readonly = false;
|
||||
@ -63,412 +16,31 @@
|
||||
export let branchCount = 1;
|
||||
export let githubContext: GitHubIntegrationContext | undefined;
|
||||
export let user: User | undefined;
|
||||
|
||||
const userSettings = getContext<SettingsStore>(SETTINGS_CONTEXT);
|
||||
|
||||
const allExpanded = writable(false);
|
||||
const allCollapsed = writable(false);
|
||||
let viewport: Element;
|
||||
let contents: Element;
|
||||
let rsViewport: HTMLElement;
|
||||
let laneWidth: number;
|
||||
|
||||
const laneWidthKey = 'laneWidth:';
|
||||
|
||||
$: prPromise =
|
||||
githubContext && branch.upstream
|
||||
? getPullRequestByBranch(githubContext, branch.upstream?.name.split('/').slice(-1)[0])
|
||||
: undefined;
|
||||
|
||||
$: branchName = branch.upstream?.name.split('/').slice(-1)[0];
|
||||
|
||||
function createPr() {
|
||||
if (githubContext && base?.branchName && branchName) {
|
||||
createPullRequest(
|
||||
githubContext,
|
||||
branchName,
|
||||
base.branchName.split('/').slice(-1)[0],
|
||||
branch.name,
|
||||
branch.notes
|
||||
).then((pr) => {
|
||||
prPromise = Promise.resolve(pr);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
// On refresh we need to check expansion status from localStorage
|
||||
branch.files && expandFromCache();
|
||||
}
|
||||
|
||||
function expandFromCache() {
|
||||
// Exercise cache lookup for all files.
|
||||
$allExpanded = branch.files.every((f) => getExpandedWithCacheFallback(f));
|
||||
$allCollapsed = branch.files.every((f) => getExpandedWithCacheFallback(f) == false);
|
||||
}
|
||||
|
||||
function handleCollapseAll() {
|
||||
branch.files.forEach((f) => setExpandedWithCache(f, false));
|
||||
$allExpanded = false;
|
||||
branch.files = branch.files;
|
||||
}
|
||||
|
||||
function handleExpandAll() {
|
||||
branch.files.forEach((f) => setExpandedWithCache(f, true));
|
||||
$allExpanded = true;
|
||||
branch.files = branch.files;
|
||||
}
|
||||
|
||||
let commitDialogShown = false;
|
||||
|
||||
$: if (commitDialogShown && branch.files.length === 0) {
|
||||
commitDialogShown = false;
|
||||
}
|
||||
|
||||
function generateBranchName() {
|
||||
const diff = branch.files
|
||||
.map((f) => f.hunks)
|
||||
.flat()
|
||||
.map((h) => h.diff)
|
||||
.flat()
|
||||
.join('\n')
|
||||
.slice(0, 5000);
|
||||
|
||||
if (user) {
|
||||
cloud.summarize.branch(user.access_token, { diff }).then((result) => {
|
||||
if (result.message && result.message !== branch.name) {
|
||||
branch.name = result.message;
|
||||
branchController.updateBranchName(branch.id, branch.name);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$: linesTouched = computedAddedRemoved(...branch.files);
|
||||
$: if (
|
||||
branch.name.toLowerCase().includes('virtual branch') &&
|
||||
linesTouched.added + linesTouched.removed > 4
|
||||
) {
|
||||
generateBranchName();
|
||||
}
|
||||
|
||||
function resetHeadCommit() {
|
||||
if (branch.commits.length > 1) {
|
||||
branchController.resetBranch(branch.id, branch.commits[1].id);
|
||||
} else if (branch.commits.length === 1 && base) {
|
||||
branchController.resetBranch(branch.id, base.baseSha);
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
expandFromCache();
|
||||
laneWidth = lscache.get(laneWidthKey + branch.id) ?? $userSettings.defaultLaneWidth;
|
||||
});
|
||||
|
||||
const selectedOwnership = writable(Ownership.fromBranch(branch));
|
||||
$: if (commitDialogShown) selectedOwnership.set(Ownership.fromBranch(branch));
|
||||
|
||||
function acceptCherrypick(data: any) {
|
||||
return isDraggableRemoteCommit(data) && data.branchId == branch.id;
|
||||
}
|
||||
|
||||
function onCherrypicked(data: DraggableRemoteCommit) {
|
||||
branchController.cherryPick(branch.id, data.remoteCommit.id);
|
||||
}
|
||||
|
||||
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: DraggableHunk | DraggableFile) {
|
||||
if (isDraggableHunk(data)) {
|
||||
const newOwnership = `${data.hunk.filePath}:${data.hunk.id}`;
|
||||
branchController.updateBranchOwnership(
|
||||
branch.id,
|
||||
(newOwnership + '\n' + branch.ownership).trim()
|
||||
);
|
||||
} else if (isDraggableFile(data)) {
|
||||
const newOwnership = `${data.file.path}:${data.file.hunks.map(({ id }) => id).join(',')}`;
|
||||
branchController.updateBranchOwnership(
|
||||
branch.id,
|
||||
(newOwnership + '\n' + branch.ownership).trim()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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="relative flex h-full shrink-0 snap-center"
|
||||
style:width={maximized ? '100%' : `${laneWidth}px`}
|
||||
>
|
||||
<div class="wrapper">
|
||||
<div class="absolute h-3 w-full" data-tauri-drag-region></div>
|
||||
<div bind:this={rsViewport} class="branch-card">
|
||||
<div
|
||||
class="flex h-full flex-col overflow-hidden rounded-lg border"
|
||||
style:background-color="var(--bg-surface)"
|
||||
style:border-color="var(--border-surface)"
|
||||
>
|
||||
<div class="flex">
|
||||
<div class="border-color-4 flex flex-grow flex-col">
|
||||
<BranchHeader
|
||||
{branchController}
|
||||
{branch}
|
||||
{allCollapsed}
|
||||
{allExpanded}
|
||||
on:action={(e) => {
|
||||
if (e.detail == 'expand') {
|
||||
handleExpandAll();
|
||||
} else if (e.detail == 'collapse') {
|
||||
handleCollapseAll();
|
||||
} else if (e.detail == 'generate-branch-name') {
|
||||
generateBranchName();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{#if branch.upstream?.commits.length && branch.upstream?.commits.length > 0 && !branch.conflicted}
|
||||
<UpstreamCommits
|
||||
upstream={branch.upstream}
|
||||
branchId={branch.id}
|
||||
{branchController}
|
||||
{branchCount}
|
||||
{projectId}
|
||||
{base}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="relative flex flex-grow overflow-y-hidden"
|
||||
use:dropzone={{
|
||||
hover: 'cherrypick-dz-hover',
|
||||
active: 'cherrypick-dz-active',
|
||||
accepts: acceptCherrypick,
|
||||
onDrop: onCherrypicked
|
||||
}}
|
||||
use:dropzone={{
|
||||
hover: 'lane-dz-hover',
|
||||
active: 'lane-dz-active',
|
||||
accepts: acceptBranchDrop,
|
||||
onDrop: onBranchDrop
|
||||
}}
|
||||
>
|
||||
<!-- TODO: Figure out why z-10 is necessary for expand up/down to not come out on top -->
|
||||
<div
|
||||
class="cherrypick-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 invisible font-semibold">Apply here</div>
|
||||
</div>
|
||||
|
||||
<!-- TODO: Figure out why z-10 is necessary for expand up/down to not come out on top -->
|
||||
<div
|
||||
class="lane-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 invisible font-semibold">Move here</div>
|
||||
</div>
|
||||
<div bind:this={viewport} class="scroll-container hide-native-scrollbar">
|
||||
<div bind:this={contents} class="flex min-h-full flex-col">
|
||||
{#if branch.files?.length > 0}
|
||||
<BranchFiles {branch} {readonly} {selectedOwnership} />
|
||||
{#if branch.active}
|
||||
<CommitDialog
|
||||
{projectId}
|
||||
{branchController}
|
||||
{branch}
|
||||
{cloudEnabled}
|
||||
{cloud}
|
||||
{selectedOwnership}
|
||||
{user}
|
||||
/>
|
||||
{/if}
|
||||
{:else if branch.commits.length == 0}
|
||||
<div class="new-branch" data-dnd-ignore>
|
||||
<h1 class="text-base-16 text-semibold">Nothing on this branch yet</h1>
|
||||
<p class="px-12">Get some work done, then throw some files my way!</p>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- attention: these markers have custom css at the bottom of thise file -->
|
||||
<div class="no-changes" data-dnd-ignore>
|
||||
<h1 class="text-base-16 text-semibold">No uncommitted changes on this branch</h1>
|
||||
</div>
|
||||
{/if}
|
||||
{#if branch.commits.length > 0}
|
||||
<LocalCommits
|
||||
{branch}
|
||||
{base}
|
||||
{send}
|
||||
{receive}
|
||||
{prPromise}
|
||||
{githubContext}
|
||||
{projectId}
|
||||
{branchController}
|
||||
{acceptAmend}
|
||||
{acceptSquash}
|
||||
{onAmend}
|
||||
{onSquash}
|
||||
{resetHeadCommit}
|
||||
{createPr}
|
||||
/>
|
||||
<RemoteCommits
|
||||
{branch}
|
||||
{base}
|
||||
{send}
|
||||
{receive}
|
||||
{prPromise}
|
||||
{githubContext}
|
||||
{projectId}
|
||||
{acceptAmend}
|
||||
{acceptSquash}
|
||||
{onAmend}
|
||||
{onSquash}
|
||||
{resetHeadCommit}
|
||||
{createPr}
|
||||
/>
|
||||
<IntegratedCommits {branch} {base} {send} {receive} {projectId} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<Scrollbar {viewport} {contents} width="0.4rem" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#if !maximized}
|
||||
<Resizer
|
||||
minWidth={330}
|
||||
viewport={rsViewport}
|
||||
direction="horizontal"
|
||||
class="z-30"
|
||||
on:width={(e) => {
|
||||
laneWidth = e.detail;
|
||||
lscache.set(laneWidthKey + branch.id, e.detail, 7 * 1440); // 7 day ttl
|
||||
userSettings.update((s) => ({
|
||||
...s,
|
||||
defaultLaneWidth: e.detail
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
<BranchCard
|
||||
{branch}
|
||||
{readonly}
|
||||
{projectId}
|
||||
{base}
|
||||
{cloudEnabled}
|
||||
{cloud}
|
||||
{branchController}
|
||||
{maximized}
|
||||
{branchCount}
|
||||
{githubContext}
|
||||
{user}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
/* hunks drop zone */
|
||||
:global(.lane-dz-active .lane-dz-marker) {
|
||||
@apply flex;
|
||||
}
|
||||
:global(.lane-dz-hover .hover-text) {
|
||||
@apply visible;
|
||||
}
|
||||
|
||||
/* cherry pick drop zone */
|
||||
:global(.cherrypick-dz-active .cherrypick-dz-marker) {
|
||||
@apply flex;
|
||||
}
|
||||
: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;
|
||||
}
|
||||
|
||||
.branch-card {
|
||||
.wrapper {
|
||||
border: 1px solid var(--clr-theme-container-outline-light);
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
cursor: default;
|
||||
overflow-x: hidden;
|
||||
padding: var(--space-12) var(--space-4);
|
||||
}
|
||||
|
||||
.scroll-container {
|
||||
max-height: 100%;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
overflow-y: scroll;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
.new-branch,
|
||||
.no-changes {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
background: var(--clr-theme-container-light);
|
||||
justify-content: center;
|
||||
gap: var(--space-8);
|
||||
|
||||
& h1 {
|
||||
color: var(--clr-theme-scale-ntrl-40);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.new-branch p {
|
||||
color: var(--clr-theme-scale-ntrl-50);
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
flex-shrink: 0;
|
||||
border-radius: var(--radius-m);
|
||||
}
|
||||
</style>
|
||||
|
@ -147,7 +147,7 @@
|
||||
.commit-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-top: 1px solid var(--color-theme-container-outline-light);
|
||||
border-top: 1px solid var(--clr-theme-container-outline-light);
|
||||
background: var(--clr-theme-container-pale);
|
||||
padding: var(--space-16);
|
||||
gap: var(--space-8);
|
||||
|
Loading…
Reference in New Issue
Block a user