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, remoteBranchService: RemoteBranchService,
githubService: GitHubService githubService: GitHubService
) { ) {
const vbranchesWithEmpty$ = vbranchService.branches$.pipe(startWith([])); const vbranchesWithEmpty$ = vbranchService.activeBranches$.pipe(startWith([]));
const branchesWithEmpty$ = remoteBranchService.branches$.pipe(startWith([])); const branchesWithEmpty$ = remoteBranchService.branches$.pipe(startWith([]));
const prWithEmpty$ = githubService.prs$.pipe(startWith([])); const prWithEmpty$ = githubService.prs$.pipe(startWith([]));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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