tag component update, collapsable branches frontend added

This commit is contained in:
Pavel Laptev 2024-02-09 23:18:10 +01:00 committed by GitButler
parent c6bc57cb90
commit e107f6b00c
7 changed files with 397 additions and 179 deletions

View File

@ -0,0 +1,93 @@
<script lang="ts">
import Tag from '$lib/components/Tag.svelte';
import { normalizeBranchName } from '$lib/utils/branch';
import { open } from '@tauri-apps/api/shell';
import type { BaseBranch, Branch } from '$lib/vbranches/types';
export let base: BaseBranch | undefined | null;
export let branch: Branch;
export let prUrl: string | undefined;
export let isUnapplied = false;
export let hasIntegratedCommits = false;
export let isLaneCollapsed = false;
</script>
{#if !branch.upstream}
{#if !branch.active}
<Tag
icon="virtual-branch-small"
color="light"
help="These changes are stashed away from your working directory."
reversedDirection
verticalOrientation={isLaneCollapsed}>unapplied</Tag
>
{:else if hasIntegratedCommits}
<Tag
icon="removed-branch-small"
color="success"
help="These changes have been integrated upstream, update your workspace to make this lane disappear."
reversedDirection
verticalOrientation={isLaneCollapsed}>integrated</Tag
>
{:else}
<Tag
icon="virtual-branch-small"
color="light"
help="These changes are in your working directory."
reversedDirection
verticalOrientation={isLaneCollapsed}>virtual</Tag
>
{/if}
{#if !isUnapplied}
<Tag
disabled
help="Branch name that will be used when pushing. You can change it from the lane menu."
verticalOrientation={isLaneCollapsed}
>
origin/{branch.upstreamName
? branch.upstreamName
: normalizeBranchName(branch.name)}</Tag
>
{/if}
{:else}
<Tag
color="dark"
icon="remote-branch-small"
help="At least some of your changes have been pushed"
verticalOrientation={isLaneCollapsed}
reversedDirection>remote</Tag
>
<Tag
icon="open-link"
color="ghost"
border
clickable
shrinkable
verticalOrientation={isLaneCollapsed}
on:click={(e) => {
const url = base?.branchUrl(branch.upstream?.name);
if (url) open(url);
e.preventDefault();
e.stopPropagation();
}}
>
{isLaneCollapsed ? 'View branch' : `origin/${branch.upstream?.name}`}
</Tag>
{#if prUrl}
<Tag
icon="pr-small"
color="ghost"
border
clickable
verticalOrientation={isLaneCollapsed}
on:click={(e) => {
const url = prUrl;
if (url) open(url);
e.preventDefault();
e.stopPropagation();
}}
>
View PR
</Tag>
{/if}
{/if}

View File

@ -121,6 +121,7 @@
{projectPath} {projectPath}
{user} {user}
{githubService} {githubService}
hasNextSibling={branches.find((b) => b.order === branch.order + 1)}
></BranchLane> ></BranchLane>
</div> </div>
{/each} {/each}

View File

@ -131,8 +131,6 @@
} }
let isLaneCollapsed: boolean; let isLaneCollapsed: boolean;
$: console.log('collapsed', isLaneCollapsed);
</script> </script>
{#if isLaneCollapsed} {#if isLaneCollapsed}
@ -154,156 +152,154 @@
/> />
</div> </div>
{:else} {:else}
<div class="branch-card-wrapper"> <div
class="branch-card"
data-tauri-drag-region
class:target-branch={branch.active && branch.selectedForChanges}
>
<div <div
class="branch-card" bind:this={rsViewport}
data-tauri-drag-region style:width={`${laneWidth || $defaultBranchWidthRem}rem`}
class:target-branch={branch.active && branch.selectedForChanges} class="branch-card__contents"
> >
<div <BranchHeader
bind:this={rsViewport} {isUnapplied}
style:width={`${laneWidth || $defaultBranchWidthRem}rem`} {branchController}
class="branch-card__contents" {branch}
> {base}
<BranchHeader {githubService}
{isUnapplied} {branchService}
{branchController} bind:isLaneCollapsed
{branch} projectId={project.id}
{base} on:action={(e) => {
{githubService} if (e.detail == 'generate-branch-name') {
{branchService} generateBranchName();
bind:isLaneCollapsed }
projectId={project.id}
on:action={(e) => {
if (e.detail == 'generate-branch-name') {
generateBranchName();
}
}}
/>
<!-- DROPZONES -->
<DropzoneOverlay class="cherrypick-dz-marker" label="Apply here" />
<DropzoneOverlay class="lane-dz-marker" label="Move here" />
<div
class="branch-card__dropzone-wrapper"
use:dropzone={{
hover: 'cherrypick-dz-hover',
active: 'cherrypick-dz-active',
accepts: acceptCherrypick,
onDrop: onCherrypicked,
disabled: isUnapplied
}}
use:dropzone={{
hover: 'lane-dz-hover',
active: 'lane-dz-active',
accepts: acceptBranchDrop,
onDrop: onBranchDrop,
disabled: isUnapplied
}}
>
<DropzoneOverlay class="cherrypick-dz-marker" label="Apply here" />
<DropzoneOverlay class="lane-dz-marker" label="Move here" />
{#if branch.files?.length > 0}
<div class="card">
<BranchFiles
{branch}
{isUnapplied}
{selectedOwnership}
{selectedFiles}
showCheckboxes={$commitBoxOpen}
/>
{#if branch.active}
<CommitDialog
projectId={project.id}
{branchController}
{branch}
{cloud}
{selectedOwnership}
{user}
bind:expanded={commitBoxOpen}
on:action={(e) => {
if (e.detail == 'generate-branch-name') {
generateBranchName();
}
}}
/>
{/if}
</div>
{:else if branch.commits.length == 0}
<div class="new-branch card" data-dnd-ignore>
<div class="new-branch__content">
<div class="new-branch__image">
<ImgThemed
imgSet={{
light: '/images/lane-new-light.webp',
dark: '/images/lane-new-dark.webp'
}}
/>
</div>
<h2 class="new-branch__title text-base-body-15 text-semibold">
This is a new branch.
</h2>
<p class="new-branch__caption text-base-body-13">
You can drag and drop files or parts of files here.
</p>
</div>
</div>
{:else}
<!-- attention: these markers have custom css at the bottom of thise file -->
<div class="no-changes card" data-dnd-ignore>
<div class="new-branch__content">
<div class="new-branch__image">
<ImgThemed
imgSet={{
light: '/images/lane-no-changes-light.webp',
dark: '/images/lane-no-changes-dark.webp'
}}
/>
</div>
<h2 class="new-branch__caption text-base-body-13">
No uncommitted changes<br />on this branch
</h2>
</div>
</div>
{/if}
</div>
<BranchCommits
{base}
{branch}
{project}
{githubService}
{branchController}
{branchService}
{branchCount}
{isUnapplied}
{selectedFiles}
/>
</div>
</div>
<div class="divider-line">
<Resizer
viewport={rsViewport}
direction="right"
inside={$selectedFiles.length > 0}
minWidth={320}
sticky
on:width={(e) => {
laneWidth = e.detail / (16 * $userSettings.zoom);
lscache.set(laneWidthKey + branch.id, laneWidth, 7 * 1440); // 7 day ttl
$defaultBranchWidthRem = laneWidth;
}} }}
/> />
<!-- DROPZONES -->
<DropzoneOverlay class="cherrypick-dz-marker" label="Apply here" />
<DropzoneOverlay class="lane-dz-marker" label="Move here" />
<div
class="branch-card__dropzone-wrapper"
use:dropzone={{
hover: 'cherrypick-dz-hover',
active: 'cherrypick-dz-active',
accepts: acceptCherrypick,
onDrop: onCherrypicked,
disabled: isUnapplied
}}
use:dropzone={{
hover: 'lane-dz-hover',
active: 'lane-dz-active',
accepts: acceptBranchDrop,
onDrop: onBranchDrop,
disabled: isUnapplied
}}
>
<DropzoneOverlay class="cherrypick-dz-marker" label="Apply here" />
<DropzoneOverlay class="lane-dz-marker" label="Move here" />
{#if branch.files?.length > 0}
<div class="card">
<BranchFiles
{branch}
{isUnapplied}
{selectedOwnership}
{selectedFiles}
showCheckboxes={$commitBoxOpen}
/>
{#if branch.active}
<CommitDialog
projectId={project.id}
{branchController}
{branch}
{cloud}
{selectedOwnership}
{user}
bind:expanded={commitBoxOpen}
on:action={(e) => {
if (e.detail == 'generate-branch-name') {
generateBranchName();
}
}}
/>
{/if}
</div>
{:else if branch.commits.length == 0}
<div class="new-branch card" data-dnd-ignore>
<div class="new-branch__content">
<div class="new-branch__image">
<ImgThemed
imgSet={{
light: '/images/lane-new-light.webp',
dark: '/images/lane-new-dark.webp'
}}
/>
</div>
<h2 class="new-branch__title text-base-body-15 text-semibold">
This is a new branch.
</h2>
<p class="new-branch__caption text-base-body-13">
You can drag and drop files or parts of files here.
</p>
</div>
</div>
{:else}
<!-- attention: these markers have custom css at the bottom of thise file -->
<div class="no-changes card" data-dnd-ignore>
<div class="new-branch__content">
<div class="new-branch__image">
<ImgThemed
imgSet={{
light: '/images/lane-no-changes-light.webp',
dark: '/images/lane-no-changes-dark.webp'
}}
/>
</div>
<h2 class="new-branch__caption text-base-body-13">
No uncommitted changes<br />on this branch
</h2>
</div>
</div>
{/if}
</div>
<BranchCommits
{base}
{branch}
{project}
{githubService}
{branchController}
{branchService}
{branchCount}
{isUnapplied}
{selectedFiles}
/>
</div> </div>
</div> </div>
<div class="divider-line">
<Resizer
viewport={rsViewport}
direction="right"
inside={$selectedFiles.length > 0}
minWidth={320}
sticky
on:width={(e) => {
laneWidth = e.detail / (16 * $userSettings.zoom);
lscache.set(laneWidthKey + branch.id, laneWidth, 7 * 1440); // 7 day ttl
$defaultBranchWidthRem = laneWidth;
}}
/>
</div>
{/if} {/if}
<style lang="postcss"> <style lang="postcss">
.branch-card-wrapper { /* .branch-card-wrapper {
position: relative; position: relative;
display: flex; display: flex;
height: 100%; height: 100%;
} } */
.branch-card { .branch-card {
height: 100%; height: 100%;
position: relative; position: relative;
@ -432,5 +428,10 @@
flex-direction: column; flex-direction: column;
padding: var(--space-12); padding: var(--space-12);
height: 100%; height: 100%;
border-right: 1px solid var(--clr-theme-container-outline-light);
} }
/* .brach-collapsed {
display: none;
} */
</style> </style>

View File

@ -1,16 +1,18 @@
<script lang="ts"> <script lang="ts">
import BranchHeaderSecondaryActions from './BranchHeaderSecondaryActions.svelte'; import ActiveBranchStatus from './ActiveBranchStatus.svelte';
import BranchLabel from './BranchLabel.svelte'; import BranchLabel from './BranchLabel.svelte';
import BranchLanePopupMenu from './BranchLanePopupMenu.svelte';
// import BranchLanePopupMenu from './BranchLanePopupMenu.svelte'; // import BranchLanePopupMenu from './BranchLanePopupMenu.svelte';
import MergeButton from './MergeButton.svelte'; import MergeButton from './MergeButton.svelte';
import Tag from './Tag.svelte'; import Tag from './Tag.svelte';
import { clickOutside } from '$lib/clickOutside';
// import { clickOutside } from '$lib/clickOutside'; // import { clickOutside } from '$lib/clickOutside';
import Button from '$lib/components/Button.svelte'; import Button from '$lib/components/Button.svelte';
import Icon, { type IconColor } from '$lib/components/Icon.svelte'; import Icon, { type IconColor } from '$lib/components/Icon.svelte';
import { normalizeBranchName } from '$lib/utils/branch'; // import { normalizeBranchName } from '$lib/utils/branch';
import * as toasts from '$lib/utils/toasts'; import * as toasts from '$lib/utils/toasts';
import { tooltip } from '$lib/utils/tooltip'; import { tooltip } from '$lib/utils/tooltip';
import { open } from '@tauri-apps/api/shell'; // import { open } from '@tauri-apps/api/shell';
import toast from 'svelte-french-toast'; import toast from 'svelte-french-toast';
import type { BranchService } from '$lib/branches/service'; import type { BranchService } from '$lib/branches/service';
import type { GitHubService } from '$lib/github/service'; import type { GitHubService } from '$lib/github/service';
@ -121,18 +123,40 @@
</script> </script>
{#if isLaneCollapsed} {#if isLaneCollapsed}
<div class="collapsed-lane" data-remove-from-draggable data-tauri-drag-region> <div class="card collapsed-lane" data-tauri-drag-region>
<div class="collapsed-lane__actions"> <div class="collapsed-lane__actions">
<BranchHeaderSecondaryActions <div class="collapsed-lane__draggable" data-drag-handle>
{visible} <Icon name="draggable-narrow" />
{isUnapplied} </div>
{branch} <Button
{branchController} icon="unfold-lane"
{projectId} kind="outlined"
bind:isLaneCollapsed color="neutral"
bind:meatballButton help="Collapse lane"
on:click={() => {
isLaneCollapsed = false;
}}
/> />
</div> </div>
<div class="collapsed-lane__info">
<h3 class="collapsed-lane__label text-base-13 text-bold">
{branch.name}
</h3>
<div class="collapsed-lane__info__details">
<ActiveBranchStatus
{base}
{branch}
{isUnapplied}
{hasIntegratedCommits}
{isLaneCollapsed}
prUrl={$pr$?.htmlUrl}
/>
{#if branch.selectedForChanges}
<Tag color="pop" filled icon="target" verticalOrientation>Default branch</Tag>
{/if}
</div>
</div>
</div> </div>
{:else} {:else}
<div class="header__wrapper"> <div class="header__wrapper">
@ -146,7 +170,14 @@
/> />
</div> </div>
<div class="header__remote-branch"> <div class="header__remote-branch">
{#if !branch.upstream} <ActiveBranchStatus
{base}
{branch}
{isUnapplied}
{hasIntegratedCommits}
prUrl={$pr$?.htmlUrl}
/>
<!-- {#if !branch.upstream}
{#if !branch.active} {#if !branch.active}
<Tag <Tag
icon="virtual-branch-small" icon="virtual-branch-small"
@ -179,14 +210,9 @@
: normalizeBranchName(branch.name)}</Tag : normalizeBranchName(branch.name)}</Tag
> >
{/if} {/if}
{:else} {:else} -->
<Tag {#if branch.upstream}
color="dark" <!-- <Tag
icon="remote-branch-small"
help="At least some of your changes have been pushed"
reversedDirection>remote</Tag
>
<Tag
icon="open-link" icon="open-link"
color="ghost" color="ghost"
border border
@ -216,7 +242,7 @@
> >
View PR View PR
</Tag> </Tag>
{/if} {/if} -->
{#if prIcon} {#if prIcon}
<div <div
class="pr-status" class="pr-status"
@ -355,15 +381,40 @@
</Button> </Button>
{:else} {:else}
<div class="header__buttons"> <div class="header__buttons">
<BranchHeaderSecondaryActions <Button
{visible} icon="fold-lane"
{isUnapplied} kind="outlined"
{branch} color="neutral"
{branchController} help="Collapse lane"
{projectId} on:click={() => {
bind:isLaneCollapsed isLaneCollapsed = true;
bind:meatballButton }}
/> />
<Button
icon="kebab"
kind="outlined"
color="neutral"
on:click={() => {
console.log('meatballButton', meatballButton);
visible = !visible;
}}
/>
<div
class="branch-popup-menu"
use:clickOutside={{
trigger: meatballButton,
handler: () => (visible = false)
}}
>
<BranchLanePopupMenu
{branchController}
{branch}
{projectId}
{isUnapplied}
bind:visible
on:action
/>
</div>
</div> </div>
{/if} {/if}
</div> </div>
@ -435,12 +486,12 @@
gap: var(--space-4); gap: var(--space-4);
} }
.draggable { .draggable {
display: flex;
cursor: grab;
position: absolute; position: absolute;
right: var(--space-4); right: var(--space-4);
top: var(--space-6); top: var(--space-6);
opacity: 0; opacity: 0;
display: flex;
cursor: grab;
color: var(--clr-theme-scale-ntrl-50); color: var(--clr-theme-scale-ntrl-50);
transition: transition:
opacity var(--transition-slow), opacity var(--transition-slow),
@ -450,13 +501,13 @@
color: var(--clr-theme-scale-ntrl-40); color: var(--clr-theme-scale-ntrl-40);
} }
} }
/*
.branch-popup-menu { .branch-popup-menu {
position: absolute; position: absolute;
top: calc(100% + var(--space-4)); top: calc(100% + var(--space-4));
right: 0; right: 0;
z-index: 10; z-index: 10;
} */ }
.header__remote-branch { .header__remote-branch {
color: var(--clr-theme-scale-ntrl-50); color: var(--clr-theme-scale-ntrl-50);
@ -472,4 +523,53 @@
.pr-status { .pr-status {
cursor: default; cursor: default;
} }
/* COLLAPSABLE LANE */
.collapsed-lane {
user-select: none;
align-items: center;
height: 100%;
gap: var(--space-16);
padding: var(--space-8) var(--space-8) var(--space-16);
}
.collapsed-lane__actions {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-4);
}
.collapsed-lane__draggable {
cursor: grab;
transform: rotate(90deg);
margin-bottom: var(--space-4);
}
.collapsed-lane__info {
flex: 1;
display: flex;
flex-direction: row-reverse;
align-items: center;
justify-content: space-between;
writing-mode: vertical-rl;
gap: var(--space-8);
/* flex-direction: column-reverse; */
/* writing-mode: vertical-rl;
background-color: aquamarine; */
}
.collapsed-lane__info__details {
display: flex;
flex-direction: row-reverse;
align-items: center;
gap: var(--space-4);
}
.collapsed-lane__label {
color: var(--clr-theme-scale-ntrl-0);
transform: rotate(180deg);
padding: var(--space-8) 0;
}
</style> </style>

View File

@ -18,10 +18,10 @@
<div style="display: contents;"> <div style="display: contents;">
<Button <Button
icon="fold-lane" icon={isLaneCollapsed ? 'unfold-lane' : 'fold-lane'}
kind="outlined" kind="outlined"
color="neutral" color="neutral"
help="Fold this lane" help={isLaneCollapsed ? 'Expand lane' : 'Collapse lane'}
on:click={() => { on:click={() => {
isLaneCollapsed = !isLaneCollapsed; isLaneCollapsed = !isLaneCollapsed;
}} }}

View File

@ -34,6 +34,7 @@
export let user: User | undefined; export let user: User | undefined;
export let projectPath: string; export let projectPath: string;
export let githubService: GitHubService; export let githubService: GitHubService;
export let hasNextSibling: Branch | undefined;
$: selectedOwnership = writable(Ownership.fromBranch(branch)); $: selectedOwnership = writable(Ownership.fromBranch(branch));
$: selected = setSelected($selectedFiles, branch); $: selected = setSelected($selectedFiles, branch);
@ -60,6 +61,8 @@
if (!match) $selectedFiles = []; if (!match) $selectedFiles = [];
return match; return match;
} }
$: console.log('hasNextSibling', hasNextSibling?.name);
</script> </script>
<div <div
@ -103,7 +106,9 @@
selectable={$commitBoxOpen && !isUnapplied} selectable={$commitBoxOpen && !isUnapplied}
on:close={() => { on:close={() => {
const selectedId = selected?.id; const selectedId = selected?.id;
selectedFiles.update((fileIds) => fileIds.filter((file) => file.id != selectedId)); selectedFiles.update((fileIds) =>
fileIds.filter((file) => file.id != selectedId)
);
}} }}
/> />
<Resizer <Resizer

View File

@ -24,6 +24,7 @@
export let disabled = false; export let disabled = false;
export let clickable = false; export let clickable = false;
export let shrinkable = false; export let shrinkable = false;
export let verticalOrientation = false;
</script> </script>
<div <div
@ -41,17 +42,18 @@
class:disabled class:disabled
class:shrinkable class:shrinkable
class:iconLeft={reversedDirection} class:iconLeft={reversedDirection}
class:verticalOrientation
class:not-button={!clickable} class:not-button={!clickable}
on:click on:click
role={clickable ? 'button' : undefined} role={clickable ? 'button' : undefined}
class:clickable class:clickable
use:tooltip={help} use:tooltip={help}
> >
<span class="label"> <span class="label" class:verticalLabel={verticalOrientation}>
<slot /> <slot />
</span> </span>
{#if icon} {#if icon}
<div class="icon"> <div class="icon" class:verticalIcon={verticalOrientation}>
<Icon name={icon} /> <Icon name={icon} />
</div> </div>
{/if} {/if}
@ -226,4 +228,20 @@
text-overflow: ellipsis; text-overflow: ellipsis;
} }
} }
.verticalOrientation {
writing-mode: vertical-rl;
height: max-content;
width: var(--size-btn-s);
padding: var(--space-4) var(--space-2);
transform: rotate(180deg);
}
.verticalIcon {
transform: rotate(90deg);
}
.verticalLabel {
padding: var(--space-2) 0;
}
</style> </style>