Keep unapplied branches in the board

- disable draggables and drop zones when readonly
- dim header and add tag to distinguish from other statuses
This commit is contained in:
Mattias Granlund 2024-01-23 21:44:14 +01:00
parent 8671249176
commit a56ea36666
10 changed files with 178 additions and 66 deletions

View File

@ -15,7 +15,7 @@ export class BranchService {
remoteBranchService: RemoteBranchService,
githubService: GitHubService
) {
const vbranchesWithEmpty$ = vbranchService.branches$.pipe(startWith([]));
const vbranchesWithEmpty$ = vbranchService.activeBranches$.pipe(startWith([]));
const branchesWithEmpty$ = remoteBranchService.branches$.pipe(startWith([]));
const prWithEmpty$ = githubService.prs$.pipe(startWith([]));

View File

@ -17,7 +17,7 @@
$: githubService = data.githubService;
$: project$ = data.project$;
$: activeBranches$ = vbranchService.activeBranches$;
$: branches = vbranchService.branches$;
$: error$ = vbranchService.branchesError$;
$: githubEnabled$ = githubService.isEnabled$;
@ -61,7 +61,7 @@
project={$project$}
{cloud}
base={$base$}
branches={$activeBranches$}
branches={$branches}
projectPath={$project$?.path}
branchesError={$error$}
user={$user$}

View File

@ -115,6 +115,7 @@
{projectPath}
{user}
{githubService}
readonly={!branch.active}
></BranchLane>
</div>
{/each}

View File

@ -168,13 +168,15 @@
hover: 'cherrypick-dz-hover',
active: 'cherrypick-dz-active',
accepts: acceptCherrypick,
onDrop: onCherrypicked
onDrop: onCherrypicked,
disabled: readonly
}}
use:dropzone={{
hover: 'lane-dz-hover',
active: 'lane-dz-active',
accepts: acceptBranchDrop,
onDrop: onBranchDrop
onDrop: onBranchDrop,
disabled: readonly
}}
>
<DropzoneOverlay class="cherrypick-dz-marker" label="Apply here" />

View File

@ -63,7 +63,7 @@
</div>
{/if}
<div class="branch-files">
<div class="branch-files" class:readonly>
<div class="header" bind:this={headerElement}>
<div class="header__left">
{#if showCheckboxes && selectedListMode == 'list' && branch.files.length > 1}
@ -114,6 +114,9 @@
.branch-files {
background: var(--clr-theme-container-light);
border-radius: var(--radius-m) var(--radius-m) 0 0;
&.readonly {
border-radius: var(--radius-m);
}
}
.scroll-container {
display: flex;

View File

@ -10,6 +10,8 @@
import type { GitHubService } from '$lib/github/service';
import { open } from '@tauri-apps/api/shell';
import Button from '$lib/components/Button.svelte';
import toast from 'svelte-french-toast';
import Tooltip from '$lib/components/Tooltip.svelte';
export let readonly = false;
export let branch: Branch;
@ -24,6 +26,7 @@
let meatballButton: HTMLDivElement;
let visible = false;
let container: HTMLDivElement;
let isApplying = false;
function handleBranchNameChange() {
branchController.updateBranchName(branch.id, branch.name);
@ -37,33 +40,57 @@
</script>
<div class="header__wrapper">
<div class="header card" bind:this={container}>
<div class="header card" bind:this={container} class:readonly>
<div class="header__info">
<div class="header__label">
<BranchLabel bind:name={branch.name} on:change={handleBranchNameChange} />
<BranchLabel
bind:name={branch.name}
on:change={handleBranchNameChange}
disabled={readonly}
/>
</div>
<div class="header__remote-branch">
{#if !branch.upstream}
{#if hasIntegratedCommits}
<div class="status-tag text-base-11 text-semibold integrated">
<Icon name="pr-small" /> integrated
</div>
{#if !branch.active}
<Tooltip label="These changes are stashed away. Apply the lane to bring them back.">
<div class="status-tag text-base-11 text-semibold unapplied">
<Icon name="removed-branch-small" /> unapplied
</div>
</Tooltip>
{:else if hasIntegratedCommits}
<Tooltip
label="These changes have been integrated upstream, update your applied branches to make this lane disappear."
>
<div class="status-tag text-base-11 text-semibold integrated">
<Icon name="removed-branch-small" /> integrated
</div>
</Tooltip>
{:else}
<div class="status-tag text-base-11 text-semibold pending">
<Icon name="virtual-branch-small" /> virtual
<Tooltip label="These changes are in a virtual branch.">
<div class="status-tag text-base-11 text-semibold pending">
<Icon name="virtual-branch-small" /> virtual
</div>
</Tooltip>
{/if}
{#if !readonly}
<div class="pending-name">
<Tooltip
label="Branch name that will be used when pushing. You can change it from the lane menu."
>
<span class="text-base-11 text-semibold">
origin/{branch.upstreamName
? branch.upstreamName
: normalizeBranchName(branch.name)}
</span>
</Tooltip>
</div>
{/if}
<div class="pending-name">
<span class="text-base-11 text-semibold"
>origin/{branch.upstreamName
? branch.upstreamName
: normalizeBranchName(branch.name)}</span
>
</div>
{:else}
<div class="status-tag text-base-11 text-semibold remote">
<Icon name="remote-branch-small" /> remote
</div>
<Tooltip label="At least some of your changes have been pushed">
<div class="status-tag text-base-11 text-semibold remote">
<Icon name="remote-branch-small" /> remote
</div>
</Tooltip>
<Tag
icon="open-link"
color="ghost"
@ -96,50 +123,107 @@
</Tag>
{/if}
{/if}
</div>
{#if !readonly}
<div class="draggable" data-drag-handle>
<Icon name="draggable-narrow" />
</div>
{/if}
</div>
{#if !readonly}
<div class="header__actions">
<div class="header__buttons">
{#if branch.selectedForChanges}
<Button icon="target" notClickable>Target branch</Button>
{:else}
<Button
icon="target"
kind="outlined"
color="neutral"
on:click={async () => {
await branchController.setSelectedForChanges(branch.id);
}}
{#await branch.isMergeable then isMergeable}
{#if !isMergeable}
<Tooltip
timeoutMilliseconds={100}
label="Applying this branch will add merge conflict markers that you will have to resolve"
>
Make target
</Button>
<Tag icon="locked-small" color="warning">Conflict</Tag>
</Tooltip>
{/if}
</div>
<div class="relative" bind:this={meatballButton}>
{/await}
</div>
<div class="draggable" data-drag-handle>
<Icon name="draggable-narrow" />
</div>
</div>
<div class="header__actions">
<div class="header__buttons">
{#if branch.selectedForChanges}
<Button icon="target" notClickable disabled={readonly}>Target branch</Button>
{:else}
<Button
icon="kebab"
icon="target"
kind="outlined"
color="neutral"
on:click={() => (visible = !visible)}
/>
<div
class="branch-popup-menu"
use:clickOutside={{
trigger: meatballButton,
handler: () => (visible = false)
disabled={readonly}
on:click={async () => {
await branchController.setSelectedForChanges(branch.id);
}}
>
<BranchLanePopupMenu {branchController} {branch} {projectId} bind:visible on:action />
</div>
Make target
</Button>
{/if}
{#if !readonly}
<Button
icon="cross-small"
color="primary"
kind="outlined"
loading={isApplying}
on:click={async () => {
isApplying = true;
try {
await branchController.unapplyBranch(branch.id);
} catch (e) {
const err = 'Failed to apply branch';
toast.error(err);
console.error(err, e);
} finally {
isApplying = false;
}
}}
>
Unapply
</Button>
{:else}
<Button
icon="plus-small"
color="primary"
kind="outlined"
loading={isApplying}
on:click={async () => {
isApplying = true;
try {
await branchController.applyBranch(branch.id);
} catch (e) {
const err = 'Failed to apply branch';
toast.error(err);
console.error(err, e);
} finally {
isApplying = false;
}
}}
>
Apply
</Button>
{/if}
</div>
<div class="relative" bind:this={meatballButton}>
<Button
icon="kebab"
kind="outlined"
color="neutral"
on:click={() => (visible = !visible)}
/>
<div
class="branch-popup-menu"
use:clickOutside={{
trigger: meatballButton,
handler: () => (visible = false)
}}
>
<BranchLanePopupMenu
{branchController}
{branch}
{projectId}
{readonly}
bind:visible
on:action
/>
</div>
</div>
{/if}
</div>
</div>
<div class="header__top-overlay" data-remove-from-draggable data-tauri-drag-region />
</div>
@ -161,6 +245,9 @@
opacity: 1;
}
}
&.readonly {
background: var(--clr-theme-container-pale);
}
}
.header__top-overlay {
z-index: 1;
@ -186,6 +273,10 @@
padding: var(--space-12);
justify-content: space-between;
border-radius: 0 0 var(--radius-m) var(--radius-m);
user-select: none;
}
.readonly .header__actions {
background: var(--clr-theme-container-dim);
}
.header__buttons {
display: flex;
@ -281,4 +372,9 @@
color: var(--clr-theme-scale-ntrl-100);
background: var(--clr-theme-scale-ntrl-40);
}
.unapplied {
color: var(--clr-theme-scale-ntrl-30);
background: var(--clr-theme-scale-ntrl-80);
}
</style>

View File

@ -1,10 +1,12 @@
<script lang="ts">
export let name: string;
export let disabled = false;
let inputActive = false;
let label: HTMLDivElement;
let input: HTMLInputElement;
function activateInput() {
if (disabled) return;
inputActive = true;
setTimeout(() => input.select(), 0);
}
@ -25,6 +27,7 @@
<span class="branch-name-mesure-el text-base-13" bind:this={mesureEl}>{name}</span>
<input
type="text"
{disabled}
bind:this={input}
bind:value={name}
on:change

View File

@ -60,6 +60,7 @@
{projectPath}
{branchController}
{selectedOwnership}
{readonly}
selectable={$commitBoxOpen && !readonly}
on:close={() => {
const selectedId = selected?.id;

View File

@ -14,6 +14,7 @@
export let branch: Branch;
export let projectId: string;
export let visible: boolean;
export let readonly = false;
let deleteBranchModal: Modal;
let renameRemoteModal: Modal;
@ -33,10 +34,15 @@
{#if visible}
<ContextMenu>
<ContextMenuSection>
<ContextMenuItem
label="Unapply"
on:click={() => branch.id && branchController.unapplyBranch(branch.id)}
/>
{#if !readonly}
<ContextMenuItem
label="Unapply"
on:click={() => {
if (branch.id) branchController.unapplyBranch(branch.id);
visible = false;
}}
/>
{/if}
<ContextMenuItem
label="Delete"
@ -52,13 +58,13 @@
dispatch('action', 'generate-branch-name');
visible = false;
}}
disabled={!$aiGenEnabled || branch.files?.length == 0 || !branch.active}
disabled={readonly || !$aiGenEnabled || branch.files?.length == 0 || !branch.active}
/>
</ContextMenuSection>
<ContextMenuSection>
<ContextMenuItem
label="Set branch name"
disabled={hasIntegratedCommits}
disabled={readonly || hasIntegratedCommits}
on:click={() => {
newRemoteName = branch.upstreamName || '';
visible = false;

View File

@ -27,7 +27,7 @@
export let conflicted: boolean;
export let projectPath: string | undefined;
export let branchController: BranchController;
export let readonly = false;
export let readonly: boolean;
export let selectable = false;
export let selectedOwnership: Writable<Ownership>;