mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-01 20:45:57 +03:00
Change the UX around applying/unapplying lanes
- checkbox has been removed - actions appear on hovering tray vbranches - close icon in top right corner of lane stashes lane - virtual branches tray section now shows stashed branches only
This commit is contained in:
parent
cb34e351bc
commit
3f093f5500
16
src/lib/components/IconButton.svelte
Normal file
16
src/lib/components/IconButton.svelte
Normal file
@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import type { ComponentType } from 'svelte';
|
||||
|
||||
let className = '';
|
||||
export { className as class };
|
||||
export let icon: ComponentType;
|
||||
export let title: string;
|
||||
</script>
|
||||
|
||||
<button
|
||||
on:click|stopPropagation
|
||||
class="{className} text-light-600 hover:text-light-800 disabled:cursor-not-allowed disabled:text-light-200 dark:text-dark-400 hover:dark:text-dark-100 dark:disabled:text-dark-400"
|
||||
{title}
|
||||
>
|
||||
<svelte:component this={icon} />
|
||||
</button>
|
20
src/lib/icons/IconCloseSmall.svelte
Normal file
20
src/lib/icons/IconCloseSmall.svelte
Normal file
@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
let className = '';
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<svg
|
||||
class={className}
|
||||
width="12"
|
||||
height="13"
|
||||
viewBox="0 0 12 13"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M11.6569 0.656854C11.364 0.363961 10.8891 0.363961 10.5962 0.656854L6 5.25305L1.40381 0.656854C1.11091 0.363961 0.636039 0.363961 0.343146 0.656854C0.0502529 0.949747 0.0502535 1.42462 0.343146 1.71751L4.93934 6.31371L0.343146 10.9099C0.0502529 11.2028 0.0502526 11.6777 0.343146 11.9706C0.636039 12.2635 1.11091 12.2635 1.40381 11.9706L6 7.37437L10.5962 11.9706C10.8891 12.2635 11.364 12.2635 11.6569 11.9706C11.9497 11.6777 11.9497 11.2028 11.6569 10.9099L7.06066 6.31371L11.6569 1.71751C11.9497 1.42462 11.9497 0.949747 11.6569 0.656854Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
@ -82,7 +82,7 @@
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#each branches.filter((c) => c.active) as { id, name, files, commits, order, conflicted, upstream } (id)}
|
||||
{#each branches.filter((c) => c.active) as { id, name, files, commits, order, conflicted, upstream, notes } (id)}
|
||||
<Lane
|
||||
on:dragstart={(e) => {
|
||||
if (!e.dataTransfer) return;
|
||||
@ -104,6 +104,7 @@
|
||||
{cloud}
|
||||
{upstream}
|
||||
{branchController}
|
||||
{notes}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
|
@ -24,10 +24,10 @@
|
||||
import Resizer from '$lib/components/Resizer.svelte';
|
||||
import { SETTINGS_CONTEXT, type SettingsStore } from '$lib/userSettings';
|
||||
import lscache from 'lscache';
|
||||
import FileTree from './FileTree.svelte';
|
||||
import { filesToFileTree } from '$lib/vbranches/filetree';
|
||||
import IconTriangleUp from '$lib/icons/IconTriangleUp.svelte';
|
||||
import IconTriangleDown from '$lib/icons/IconTriangleDown.svelte';
|
||||
import IconCloseSmall from '$lib/icons/IconCloseSmall.svelte';
|
||||
import Tabs from './Tabs.svelte';
|
||||
import NotesTabPanel from './NotesTabPanel.svelte';
|
||||
import FileTreeTabPanel from './FileTreeTabPanel.svelte';
|
||||
|
||||
const [send, receive] = crossfade({
|
||||
duration: (d) => Math.sqrt(d * 200),
|
||||
@ -59,6 +59,7 @@
|
||||
export let cloudEnabled: boolean;
|
||||
export let cloud: ReturnType<typeof getCloudApiClient>;
|
||||
export let upstream: string | undefined;
|
||||
export let notes: string | undefined;
|
||||
export let branchController: BranchController;
|
||||
|
||||
const user = userStore;
|
||||
@ -73,23 +74,17 @@
|
||||
let allExpanded: boolean | undefined;
|
||||
let maximized = false;
|
||||
let isPushing = false;
|
||||
let treeExpanded = false;
|
||||
let popupMenu: PopupMenu;
|
||||
let meatballButton: HTMLButtonElement;
|
||||
let textAreaInput: HTMLTextAreaElement;
|
||||
let viewport: Element;
|
||||
let contents: Element;
|
||||
let rsViewport: HTMLElement;
|
||||
let thViewport: HTMLElement;
|
||||
let thContents: HTMLElement;
|
||||
let laneWidth: number;
|
||||
let treeHeight: number;
|
||||
|
||||
const hoverClass = 'drop-zone-hover';
|
||||
const dzType = 'text/hunk';
|
||||
const laneWidthKey = 'laneWidth:';
|
||||
const treeHeightKey = 'treeHeight:';
|
||||
const treeExpandedKey = 'treeExpanded:';
|
||||
|
||||
function commit() {
|
||||
branchController.commitBranch(branchId, commitMessage);
|
||||
@ -105,8 +100,6 @@
|
||||
onMount(() => {
|
||||
expandFromCache();
|
||||
laneWidth = lscache.get(laneWidthKey + branchId) ?? $userSettings.defaultLaneWidth;
|
||||
treeHeight = lscache.get(treeHeightKey + branchId) ?? $userSettings.defaultTreeHeight;
|
||||
treeExpanded = Boolean(lscache.get(treeExpandedKey + branchId));
|
||||
});
|
||||
|
||||
$: {
|
||||
@ -246,7 +239,7 @@
|
||||
class="flex bg-light-200 text-light-900 dark:bg-dark-800 dark:font-normal dark:text-dark-100"
|
||||
>
|
||||
<div class="flex flex-grow flex-col border-b border-light-400 dark:border-dark-600">
|
||||
<div class="flex w-full items-center px-1.5 py-1">
|
||||
<div class="flex w-full items-center py-1 pl-1.5">
|
||||
<button
|
||||
bind:this={meatballButton}
|
||||
class="h-8 w-8 flex-grow-0 p-2 text-light-600 transition-colors hover:bg-zinc-300 dark:text-dark-200 dark:hover:bg-zinc-800"
|
||||
@ -284,6 +277,15 @@
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
<button
|
||||
class="scale-90 px-2 py-2 text-light-600 hover:text-light-800"
|
||||
title="Stash this branch"
|
||||
on:click={() => {
|
||||
if (branchId) branchController.unapplyBranch(branchId);
|
||||
}}
|
||||
>
|
||||
<IconCloseSmall />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if commitDialogShown}
|
||||
@ -367,50 +369,22 @@
|
||||
</div>
|
||||
</div>
|
||||
{#if files.length !== 0}
|
||||
<div
|
||||
class="border-b border-t border-light-300 bg-light-50 dark:border-dark-500 dark:bg-dark-800"
|
||||
>
|
||||
<button
|
||||
class="flex w-full items-center gap-x-4 py-0 text-left"
|
||||
on:click|stopPropagation={() => {
|
||||
treeExpanded = !treeExpanded;
|
||||
lscache.set(treeExpandedKey + branchId, treeExpanded);
|
||||
}}
|
||||
>
|
||||
<div class="flex-grow p-2 font-semibold">Changed files ({files.length})</div>
|
||||
<div class="pr-2">
|
||||
{#if treeExpanded}
|
||||
<IconTriangleUp />
|
||||
{:else}
|
||||
<IconTriangleDown />
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
{#if treeExpanded}
|
||||
<div class="relative" transition:slide={{ duration: 250 }}>
|
||||
<div
|
||||
bind:this={thViewport}
|
||||
style:height={`${treeHeight}px`}
|
||||
class="hide-native-scrollbar relative max-h-fit shrink-0 overflow-scroll overscroll-none"
|
||||
>
|
||||
<div bind:this={thContents} class="px-2 pb-2">
|
||||
<FileTree node={filesToFileTree(files)} isRoot={true} />
|
||||
</div>
|
||||
</div>
|
||||
<Scrollbar viewport={thViewport} contents={thContents} width="0.4rem" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<Resizer
|
||||
minHeight={40}
|
||||
viewport={thViewport}
|
||||
direction="vertical"
|
||||
class="z-30"
|
||||
on:height={(e) => {
|
||||
treeHeight = e.detail;
|
||||
lscache.set(treeHeightKey + branchId, e.detail, 7 * 1440); // 7 day ttl
|
||||
userSettings.update((s) => ({ ...s, defaultTreeHeight: e.detail }));
|
||||
}}
|
||||
<Tabs
|
||||
{branchId}
|
||||
items={[
|
||||
{
|
||||
name: 'files',
|
||||
displayName: 'Changed files (' + files.length + ')',
|
||||
component: FileTreeTabPanel,
|
||||
props: { files }
|
||||
},
|
||||
{
|
||||
name: 'notes',
|
||||
displayName: 'Notes',
|
||||
component: NotesTabPanel,
|
||||
props: { notes: notes, branchId, branchController }
|
||||
}
|
||||
]}
|
||||
/>
|
||||
{/if}
|
||||
<div class="relative flex flex-grow overflow-y-hidden">
|
||||
|
@ -9,6 +9,8 @@
|
||||
import IconFolder from '$lib/icons/IconFolder.svelte';
|
||||
import type { TreeNode } from '$lib/vbranches/filetree';
|
||||
|
||||
let className = '';
|
||||
export { className as class };
|
||||
export let expanded = true;
|
||||
export let node: TreeNode;
|
||||
export let isRoot = false;
|
||||
@ -18,83 +20,84 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if isRoot}
|
||||
<!-- Node is a root and should only render children -->
|
||||
<ul id={`fileTree-${fileTreeId++}`}>
|
||||
{#each node.children as childNode}
|
||||
<li>
|
||||
<svelte:self node={childNode} />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else if node.file}
|
||||
{@const { status, added, removed } = node.file.getSummary()}
|
||||
<!-- Node is a file -->
|
||||
<button
|
||||
class="flex w-full items-center gap-x-2 py-0 text-left"
|
||||
on:click={() => {
|
||||
const el = document.getElementById('file-' + node.file?.id);
|
||||
console.log(el);
|
||||
el?.scrollIntoView({ behavior: 'smooth' });
|
||||
setTimeout(() => el?.classList.add('wiggle'), 50);
|
||||
setTimeout(() => el?.classList.remove('wiggle'), 550);
|
||||
}}
|
||||
>
|
||||
<div class="w-4 shrink-0 text-center">
|
||||
<IconFile class="h-4 w-4" />
|
||||
</div>
|
||||
<div
|
||||
class="flex-grow truncate"
|
||||
class:text-red-500={status == 'D'}
|
||||
class:dark:text-red-400={status == 'D'}
|
||||
class:text-green-700={status == 'A'}
|
||||
class:dark:text-green-500={status == 'A'}
|
||||
class:text-orange-800={status == 'M'}
|
||||
class:dark:text-orange-400={status == 'M'}
|
||||
<div class={className}>
|
||||
{#if isRoot}
|
||||
<!-- Node is a root and should only render children! -->
|
||||
<ul id={`fileTree-${fileTreeId++}`}>
|
||||
{#each node.children as childNode}
|
||||
<li>
|
||||
<svelte:self node={childNode} />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else if node.file}
|
||||
{@const { status, added, removed } = node.file.getSummary()}
|
||||
<!-- Node is a file -->
|
||||
<button
|
||||
class="flex w-full items-center gap-x-2 py-0 text-left"
|
||||
on:click={() => {
|
||||
const el = document.getElementById('file-' + node.file?.id);
|
||||
el?.scrollIntoView({ behavior: 'smooth' });
|
||||
setTimeout(() => el?.classList.add('wiggle'), 50);
|
||||
setTimeout(() => el?.classList.remove('wiggle'), 550);
|
||||
}}
|
||||
>
|
||||
{node.name}
|
||||
</div>
|
||||
<div class="flex gap-1 font-mono text-xs font-bold">
|
||||
<span class="text-green-500">
|
||||
+{added}
|
||||
</span>
|
||||
<span class="text-red-500">
|
||||
-{removed}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
{:else if node.children.length > 0}
|
||||
<!-- Node is a folder -->
|
||||
<button class="flex w-full items-center py-0 text-left" class:expanded on:click={toggle}>
|
||||
<div class="w-3 shrink-0 text-center">
|
||||
{#if expanded}
|
||||
<IconChevronDownSmall class="scale-90 text-light-600 dark:text-dark-200" />
|
||||
{:else}
|
||||
<IconChevronRightSmall class="scale-90 text-light-600 dark:text-dark-200" />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="w-4 shrink-0 pl-1 text-center">
|
||||
<IconFolder class="h-4 w-4 scale-75 text-blue-400" />
|
||||
</div>
|
||||
<div class="flex-grow truncate pl-2">
|
||||
{node.name}
|
||||
</div>
|
||||
</button>
|
||||
<!-- We assume a folder cannot be empty -->
|
||||
{#if expanded}
|
||||
<div class="flex">
|
||||
<div class="flex">
|
||||
<div class="w-3 shrink-0 text-center">
|
||||
<div class="inline-block h-full w-px bg-light-200 dark:bg-dark-400" />
|
||||
</div>
|
||||
<div class="w-4 shrink-0 text-center">
|
||||
<IconFile class="h-4 w-4" />
|
||||
</div>
|
||||
<ul class="w-full overflow-hidden">
|
||||
{#each node.children as childNode}
|
||||
<li>
|
||||
<svelte:self node={childNode} expanded={true} />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
class="flex-grow truncate"
|
||||
class:text-red-500={status == 'D'}
|
||||
class:dark:text-red-400={status == 'D'}
|
||||
class:text-green-700={status == 'A'}
|
||||
class:dark:text-green-500={status == 'A'}
|
||||
class:text-orange-800={status == 'M'}
|
||||
class:dark:text-orange-400={status == 'M'}
|
||||
>
|
||||
{node.name}
|
||||
</div>
|
||||
<div class="flex gap-1 font-mono text-xs font-bold">
|
||||
<span class="text-green-500">
|
||||
+{added}
|
||||
</span>
|
||||
<span class="text-red-500">
|
||||
-{removed}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
{:else if node.children.length > 0}
|
||||
<!-- Node is a folder -->
|
||||
<button class="flex w-full items-center py-0 text-left" class:expanded on:click={toggle}>
|
||||
<div class="w-3 shrink-0 text-center">
|
||||
{#if expanded}
|
||||
<IconChevronDownSmall class="scale-90 text-light-600 dark:text-dark-200" />
|
||||
{:else}
|
||||
<IconChevronRightSmall class="scale-90 text-light-600 dark:text-dark-200" />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="w-4 shrink-0 pl-1 text-center">
|
||||
<IconFolder class="h-4 w-4 scale-75 text-blue-400" />
|
||||
</div>
|
||||
<div class="flex-grow truncate pl-2">
|
||||
{node.name}
|
||||
</div>
|
||||
</button>
|
||||
<!-- We assume a folder cannot be empty -->
|
||||
{#if expanded}
|
||||
<div class="flex">
|
||||
<div class="flex">
|
||||
<div class="w-3 shrink-0 text-center">
|
||||
<div class="inline-block h-full w-px bg-light-200 dark:bg-dark-400" />
|
||||
</div>
|
||||
</div>
|
||||
<ul class="w-full overflow-hidden">
|
||||
{#each node.children as childNode}
|
||||
<li>
|
||||
<svelte:self node={childNode} expanded={true} />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
9
src/routes/repo/[projectId]/FileTreeTabPanel.svelte
Normal file
9
src/routes/repo/[projectId]/FileTreeTabPanel.svelte
Normal file
@ -0,0 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { filesToFileTree } from '$lib/vbranches/filetree';
|
||||
import type { File } from '$lib/vbranches/types';
|
||||
import FileTree from './FileTree.svelte';
|
||||
|
||||
export let files: File[];
|
||||
</script>
|
||||
|
||||
<FileTree node={filesToFileTree(files)} isRoot={true} class="p-2" />
|
23
src/routes/repo/[projectId]/NotesTabPanel.svelte
Normal file
23
src/routes/repo/[projectId]/NotesTabPanel.svelte
Normal file
@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import type { BranchController } from '$lib/vbranches/branchController';
|
||||
|
||||
export let notes: string;
|
||||
export let branchController: BranchController;
|
||||
export let branchId: string;
|
||||
|
||||
function handleUpdateNotes() {
|
||||
if (!notes) return;
|
||||
branchController.updateBranchNotes(branchId, notes);
|
||||
}
|
||||
</script>
|
||||
|
||||
<textarea
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
spellcheck="true"
|
||||
bind:value={notes}
|
||||
on:change={handleUpdateNotes}
|
||||
name="notes"
|
||||
class="outline-none-important h-full w-full resize-none bg-transparent p-2 align-top text-light-900 dark:text-dark-100"
|
||||
placeholder="Branch notes (optional)"
|
||||
/>
|
113
src/routes/repo/[projectId]/Tabs.svelte
Normal file
113
src/routes/repo/[projectId]/Tabs.svelte
Normal file
@ -0,0 +1,113 @@
|
||||
<script lang="ts">
|
||||
import IconTriangleUp from '$lib/icons/IconTriangleUp.svelte';
|
||||
import IconTriangleDown from '$lib/icons/IconTriangleDown.svelte';
|
||||
import { type ComponentType, getContext } from 'svelte';
|
||||
import lscache from 'lscache';
|
||||
import { SETTINGS_CONTEXT, type SettingsStore } from '$lib/userSettings';
|
||||
import { slide } from 'svelte/transition';
|
||||
import Scrollbar from '$lib/components/Scrollbar.svelte';
|
||||
import Resizer from '$lib/components/Resizer.svelte';
|
||||
|
||||
interface Tab {
|
||||
name: string;
|
||||
displayName: string;
|
||||
component: ComponentType;
|
||||
props: any;
|
||||
}
|
||||
|
||||
const userSettings = getContext<SettingsStore>(SETTINGS_CONTEXT);
|
||||
const treeHeightKey = 'treeHeight:';
|
||||
const activeTabKey = 'activeTab:';
|
||||
const expandedKey = 'expanded:';
|
||||
|
||||
export let items: Tab[] = [];
|
||||
export let branchId: string;
|
||||
|
||||
$: expanded = branchId ? Boolean(lscache.get(expandedKey + branchId)) : false;
|
||||
$: activeTabValue = lscache.get(activeTabKey + branchId) ?? items[0].name;
|
||||
$: treeHeight = lscache.get(treeHeightKey + branchId) || $userSettings.defaultTreeHeight;
|
||||
|
||||
let thViewport: HTMLElement;
|
||||
let thContents: HTMLElement;
|
||||
|
||||
function setTreeExpanded(value: boolean) {
|
||||
expanded = value;
|
||||
lscache.set(expandedKey + branchId, expanded, 7 * 1440);
|
||||
}
|
||||
|
||||
function setActiveTab(value: string) {
|
||||
activeTabValue = value;
|
||||
lscache.set(activeTabKey + branchId, activeTabValue, 7 * 1440);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="border-b border-t border-light-300 bg-light-50 dark:border-dark-500 dark:bg-dark-800">
|
||||
<div
|
||||
class="flex w-full border-b border-light-200 text-light-700 dark:border-dark-500 dark:text-dark-200"
|
||||
>
|
||||
{#each items as item}
|
||||
<button
|
||||
class:text-light-800={activeTabValue == item.name}
|
||||
class:dark:text-white={activeTabValue == item.name}
|
||||
class="-mb-px rounded-none p-2 font-medium"
|
||||
on:click={() => {
|
||||
if (activeTabValue == item.name && expanded) {
|
||||
setTreeExpanded(false);
|
||||
setActiveTab('');
|
||||
return;
|
||||
}
|
||||
setTreeExpanded(true);
|
||||
setActiveTab(item.name);
|
||||
}}
|
||||
>
|
||||
{item.displayName}
|
||||
</button>
|
||||
{/each}
|
||||
<div class="flex-grow" />
|
||||
<button
|
||||
class="flex items-center gap-x-4 py-0 text-light-600"
|
||||
on:click|stopPropagation={() => {
|
||||
setTreeExpanded(!expanded);
|
||||
}}
|
||||
>
|
||||
<div class="pr-3">
|
||||
{#if expanded}
|
||||
<IconTriangleUp />
|
||||
{:else}
|
||||
<IconTriangleDown />
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
{#if expanded}
|
||||
<div class="relative">
|
||||
<div
|
||||
class="hide-native-scrollbar relative shrink-0 overflow-scroll overscroll-none bg-white dark:bg-dark-1000"
|
||||
transition:slide|local={{ duration: 250 }}
|
||||
style:height={`${treeHeight}px`}
|
||||
bind:this={thViewport}
|
||||
>
|
||||
<div bind:this={thContents} class="h-full">
|
||||
{#each items as item}
|
||||
{#if activeTabValue == item.name}
|
||||
<svelte:component this={item.component} {...item.props} />
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<Scrollbar viewport={thViewport} contents={thContents} width="0.4rem" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<Resizer
|
||||
minHeight={40}
|
||||
viewport={thViewport}
|
||||
direction="vertical"
|
||||
class="z-30"
|
||||
on:height={(e) => {
|
||||
treeHeight = e.detail;
|
||||
console.log(branchId);
|
||||
lscache.set(treeHeightKey + branchId, e.detail, 7 * 1440); // 7 day ttl
|
||||
userSettings.update((s) => ({ ...s, defaultTreeHeight: e.detail }));
|
||||
}}
|
||||
/>
|
@ -15,10 +15,12 @@
|
||||
import IconRefresh from '$lib/icons/IconRefresh.svelte';
|
||||
import IconGithub from '$lib/icons/IconGithub.svelte';
|
||||
import TimeAgo from '$lib/components/TimeAgo/TimeAgo.svelte';
|
||||
import Checkbox from '$lib/components/Checkbox/Checkbox.svelte';
|
||||
import Button from '$lib/components/Button/Button.svelte';
|
||||
import Modal from '$lib/components/Modal/Modal.svelte';
|
||||
import Resizer from '$lib/components/Resizer.svelte';
|
||||
import IconDelete from '$lib/icons/IconDelete.svelte';
|
||||
import IconAdd from '$lib/icons/IconAdd.svelte';
|
||||
import IconButton from '$lib/components/IconButton.svelte';
|
||||
|
||||
export let vbranchStore: Loadable<Branch[] | undefined>;
|
||||
export let remoteBranchStore: Loadable<BranchData[] | undefined>;
|
||||
@ -42,6 +44,7 @@
|
||||
let rbContents: HTMLElement;
|
||||
let rbSection: HTMLElement;
|
||||
let baseContents: HTMLElement;
|
||||
let deleteBranchModal: Modal;
|
||||
|
||||
let selectedItem: Readable<Branch | BranchData | BaseBranch | undefined> | undefined;
|
||||
let overlayOffsetTop = 0;
|
||||
@ -111,16 +114,6 @@
|
||||
removed: comitted.removed + uncomitted.removed
|
||||
};
|
||||
}
|
||||
|
||||
function toggleBranch(branch: Branch) {
|
||||
if (branch.active) {
|
||||
branchController.unapplyBranch(branch.id);
|
||||
} else if (!branch.baseCurrent) {
|
||||
applyConflictedModal.show(branch);
|
||||
} else {
|
||||
branchController.applyBranch(branch.id);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<PeekTray
|
||||
@ -200,7 +193,7 @@
|
||||
<div
|
||||
class="flex items-center justify-between border-b border-t border-light-300 bg-light-100 px-2 py-1 pr-1 dark:border-dark-600 dark:bg-dark-800"
|
||||
>
|
||||
<div class="font-bold">Your Virtual Branches</div>
|
||||
<div class="font-bold">Stashed branches</div>
|
||||
<div class="flex h-4 w-4 justify-around">
|
||||
<button class="h-full w-full" on:click={() => (yourBranchesOpen = !yourBranchesOpen)}>
|
||||
{#if yourBranchesOpen}
|
||||
@ -227,9 +220,11 @@
|
||||
{:else if $branchesState?.isError}
|
||||
<div class="px-2 py-1">Something went wrong!</div>
|
||||
{:else if !$vbranchStore || $vbranchStore.length == 0}
|
||||
<div class="p-4 text-light-700">You currently have no virtual branches.</div>
|
||||
<div class="p-2 text-light-700">You currently have no virtual branches</div>
|
||||
{:else if $vbranchStore.filter((b) => !b.active).length == 0}
|
||||
<div class="p-2 text-light-700">You have no stashed branches</div>
|
||||
{:else}
|
||||
{#each $vbranchStore as branch, i (branch.id)}
|
||||
{#each $vbranchStore.filter((b) => !b.active) as branch, i (branch.id)}
|
||||
{@const { added, removed } = sumBranchLinesAddedRemoved(branch)}
|
||||
{@const latestModifiedAt = branch.files.at(0)?.hunks.at(0)?.modifiedAt}
|
||||
<div
|
||||
@ -237,7 +232,7 @@
|
||||
tabindex="0"
|
||||
on:click={() => select(branch, i)}
|
||||
on:keypress|capture={() => select(branch, i)}
|
||||
class="border-b border-light-200 p-2 last:border-b dark:border-dark-600"
|
||||
class="group border-b border-light-200 p-2 pr-0 last:border-b dark:border-dark-600"
|
||||
class:bg-light-50={$selectedItem == branch && peekTrayExpanded}
|
||||
class:dark:bg-zinc-700={$selectedItem == branch && peekTrayExpanded}
|
||||
>
|
||||
@ -280,11 +275,23 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="shrink-0">
|
||||
<Checkbox
|
||||
on:change={() => toggleBranch(branch)}
|
||||
bind:checked={branch.active}
|
||||
disabled={!(branch.mergeable || !branch.baseCurrent) || branch.conflicted}
|
||||
<div
|
||||
class="w-0 shrink-0 self-center overflow-hidden whitespace-nowrap transition-width group-hover:w-12 group-focus:w-12"
|
||||
>
|
||||
<IconButton
|
||||
icon={IconDelete}
|
||||
class="scale-90 p-0"
|
||||
title="delete branch"
|
||||
on:click={() => deleteBranchModal.show(branch)}
|
||||
/>
|
||||
<IconButton
|
||||
icon={IconAdd}
|
||||
class="scale-90 p-0"
|
||||
title="apply branch"
|
||||
on:click={() => {
|
||||
peekTrayExpanded = false;
|
||||
branchController.applyBranch(branch.id);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -449,3 +456,25 @@
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
<!-- Delete branch confirmation modal -->
|
||||
|
||||
<Modal width="small" bind:this={deleteBranchModal} let:item>
|
||||
<svelte:fragment slot="title">Delete branch</svelte:fragment>
|
||||
<div>
|
||||
Deleting <code>{item.name}</code> cannot be undone.
|
||||
</div>
|
||||
<svelte:fragment slot="controls" let:close let:item>
|
||||
<Button height="small" kind="outlined" on:click={close}>Cancel</Button>
|
||||
<Button
|
||||
height="small"
|
||||
color="destructive"
|
||||
on:click={() => {
|
||||
branchController.deleteBranch(item.id);
|
||||
close();
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
@ -28,23 +28,11 @@
|
||||
|
||||
{#if branch != undefined}
|
||||
<div class="flex w-full max-w-full flex-col gap-y-4 p-4">
|
||||
<div class="flex">
|
||||
<div class="flex-grow items-center">
|
||||
<p class="text-lg font-bold" title="name of virtual branch">{branch.name}</p>
|
||||
<p class="text-light-700 dark:text-dark-200" title="upstream target">
|
||||
{branch.upstream?.replace('refs/remotes/', '') || ''}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="p-0 align-middle text-light-500 hover:text-light-700 disabled:cursor-not-allowed disabled:text-light-200 dark:text-dark-400 hover:dark:text-dark-200 dark:disabled:text-dark-400"
|
||||
disabled={branch.active}
|
||||
title={branch.active ? 'branch cannot be deleted while applied' : 'deletes this branch'}
|
||||
on:click={() => deleteBranchModal.show(branch)}
|
||||
>
|
||||
<IconDelete class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-lg font-bold" title="name of virtual branch">{branch.name}</p>
|
||||
<p class="text-light-700 dark:text-dark-200" title="upstream target">
|
||||
{branch.upstream?.replace('refs/remotes/', '') || ''}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
{#if branch.active}
|
||||
@ -128,26 +116,4 @@
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
<!-- Delete branch confirmation modal -->
|
||||
|
||||
<Modal width="small" bind:this={deleteBranchModal} let:item>
|
||||
<svelte:fragment slot="title">Delete branch</svelte:fragment>
|
||||
<div>
|
||||
Deleting <code>{item.name}</code> cannot be undone.
|
||||
</div>
|
||||
<svelte:fragment slot="controls" let:close let:item>
|
||||
<Button height="small" kind="outlined" on:click={close}>Cancel</Button>
|
||||
<Button
|
||||
height="small"
|
||||
color="destructive"
|
||||
on:click={() => {
|
||||
branchController.deleteBranch(item.id);
|
||||
close();
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
{/if}
|
||||
|
Loading…
Reference in New Issue
Block a user