mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-29 20:43:37 +03:00
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:
parent
8671249176
commit
a56ea36666
@ -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([]));
|
||||||
|
|
||||||
|
@ -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$}
|
||||||
|
@ -115,6 +115,7 @@
|
|||||||
{projectPath}
|
{projectPath}
|
||||||
{user}
|
{user}
|
||||||
{githubService}
|
{githubService}
|
||||||
|
readonly={!branch.active}
|
||||||
></BranchLane>
|
></BranchLane>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -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" />
|
||||||
|
@ -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;
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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>;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user