mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-18 23:02:31 +03:00
Refactor branch lane layout
- whole lane scrollable rather than individual sections - new upstream commit section - fixed header at the top - fixes scroll lock issue with nested scroll containers - known issue with grey padding on drag
This commit is contained in:
parent
18294abc20
commit
965a51e1e8
@ -5,6 +5,7 @@
|
||||
export let icon: keyof typeof iconsJson;
|
||||
export let size: 's' | 'm' | 'l' = 'l';
|
||||
export let loading = false;
|
||||
export let border = false;
|
||||
|
||||
let className = '';
|
||||
let selected = false;
|
||||
@ -15,6 +16,7 @@
|
||||
<button
|
||||
class="icon-btn {className}"
|
||||
class:selected
|
||||
class:border
|
||||
class:small={size == 's'}
|
||||
class:medium={size == 'm'}
|
||||
class:large={size == 'l'}
|
||||
@ -37,6 +39,9 @@
|
||||
color: var(--clr-theme-scale-ntrl-40);
|
||||
}
|
||||
}
|
||||
.border {
|
||||
border: 1px solid var(--clr-theme-container-outline-light);
|
||||
}
|
||||
.selected {
|
||||
background-color: var(--clr-theme-container-sub);
|
||||
}
|
||||
|
@ -27,4 +27,4 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<img src={imgSrc} alt="Decorative Art" />
|
||||
<img src={imgSrc} alt="Decorative Art" class="inline-block" />
|
||||
|
@ -42,13 +42,13 @@
|
||||
}}
|
||||
class="viewport hide-native-scrollbar"
|
||||
style:height
|
||||
style:overflow-y={scrollable ? 'scroll' : 'hidden'}
|
||||
style:overflow-y={scrollable ? 'auto' : 'hidden'}
|
||||
>
|
||||
<div bind:this={contents} class="contents">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<Scrollbar {viewport} {contents} thickness="0.4rem" {initiallyVisible} />
|
||||
<Scrollbar {viewport} {contents} thickness="0.375rem" {initiallyVisible} />
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
@ -59,7 +59,6 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
.viewport {
|
||||
overscroll-behavior: none;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -107,6 +107,17 @@ export class BranchController {
|
||||
}
|
||||
}
|
||||
|
||||
async setSelectedForChanges(branchId: string) {
|
||||
try {
|
||||
await invoke<void>('update_virtual_branch', {
|
||||
projectId: this.projectId,
|
||||
branch: { id: branchId, selected_for_changes: true }
|
||||
});
|
||||
} catch (err) {
|
||||
toasts.error('Failed to set as target');
|
||||
}
|
||||
}
|
||||
|
||||
async updateBranchOrder(branchId: string, order: number) {
|
||||
try {
|
||||
await invoke<void>('update_virtual_branch', {
|
||||
|
@ -77,9 +77,11 @@ export class Branch {
|
||||
isMergeable!: Promise<boolean>;
|
||||
@Transform((obj) => new Date(obj.value))
|
||||
updatedAt!: Date;
|
||||
// Indicates that branch is default target for new changes
|
||||
selectedForChanges!: boolean;
|
||||
}
|
||||
|
||||
export type CommitStatus = 'local' | 'remote' | 'integrated';
|
||||
export type CommitStatus = 'local' | 'remote' | 'integrated' | 'upstream';
|
||||
|
||||
export class Commit {
|
||||
id!: string;
|
||||
|
@ -53,7 +53,7 @@
|
||||
<button on:click={() => httpsWarningBannerDismissed.set(true)}>Dismiss</button>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="relative h-full flex-grow overscroll-none">
|
||||
<div class="relative h-full flex-grow">
|
||||
<div class="scroll-viewport hide-native-scrollbar" bind:this={viewport}>
|
||||
<div class="scroll-contents" bind:this={contents}>
|
||||
<Board
|
||||
@ -76,7 +76,6 @@
|
||||
<style lang="postcss">
|
||||
.scroll-viewport {
|
||||
overflow-x: scroll;
|
||||
overscroll-behavior: none;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -53,7 +53,7 @@
|
||||
// We account for the NewBranchDropZone by subtracting 2
|
||||
for (let i = 0; i < children.length - 2; i++) {
|
||||
const pos = children[i].getBoundingClientRect();
|
||||
if (e.clientX > pos.left + dragged.offsetWidth / 2) {
|
||||
if (e.clientX > pos.right + dragged.offsetWidth / 2) {
|
||||
dropPosition = i + 1; // Note that this is declared in the <script>
|
||||
} else {
|
||||
break;
|
||||
@ -193,8 +193,7 @@
|
||||
flex-shrink: 1;
|
||||
align-items: flex-start;
|
||||
height: 100%;
|
||||
padding: var(--space-16);
|
||||
gap: var(--space-12);
|
||||
padding: 0 var(--space-8);
|
||||
}
|
||||
.loading {
|
||||
display: flex;
|
||||
|
@ -72,15 +72,15 @@
|
||||
<span class="text-base-body-13 new-branch-caption"
|
||||
>Drag and drop files<br />to create a new branch</span
|
||||
>
|
||||
|
||||
<div class="new-branch-button">
|
||||
<Button
|
||||
color="neutral"
|
||||
kind="outlined"
|
||||
icon="plus-small"
|
||||
on:click={() => branchController.createBranch({})}>New branch</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="new-branch-button">
|
||||
<Button
|
||||
wide
|
||||
color="neutral"
|
||||
kind="outlined"
|
||||
icon="plus-small"
|
||||
on:click={() => branchController.createBranch({})}>New branch</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -89,19 +89,18 @@
|
||||
.canvas-dropzone {
|
||||
user-select: none;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding: var(--space-16) var(--space-16) var(--space-16) var(--space-4);
|
||||
}
|
||||
|
||||
.new-virtual-branch {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24rem;
|
||||
height: 100%;
|
||||
border-radius: var(--radius-m);
|
||||
border: 1px dashed color-mix(in srgb, var(--clr-theme-container-outline-pale) 50%, transparent);
|
||||
background-color: transparent;
|
||||
padding: var(--space-20);
|
||||
gap: var(--space-20);
|
||||
|
||||
outline-color: color-mix(in srgb, var(--clr-theme-scale-pop-40) 0%, transparent);
|
||||
outline-style: dashed;
|
||||
@ -120,8 +119,9 @@
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-16);
|
||||
gap: var(--space-12);
|
||||
transition: transform var(--transition-medium);
|
||||
padding: var(--space-20) var(--space-24) var(--space-16) var(--space-24);
|
||||
}
|
||||
|
||||
/* ILLUSTRATION */
|
||||
|
@ -20,7 +20,6 @@
|
||||
import type { GitHubService } from '$lib/github/service';
|
||||
import { isDraggableRemoteCommit, type DraggableRemoteCommit } from '$lib/draggables';
|
||||
import BranchHeader from './BranchHeader.svelte';
|
||||
import UpstreamCommits from './UpstreamCommits.svelte';
|
||||
import BranchFiles from './BranchFiles.svelte';
|
||||
import { projectAiGenEnabled } from '$lib/config/config';
|
||||
import { persisted } from '$lib/persisted/persisted';
|
||||
@ -30,6 +29,7 @@
|
||||
import ImgThemed from '$lib/components/ImgThemed.svelte';
|
||||
|
||||
import DropzoneOverlay from './DropzoneOverlay.svelte';
|
||||
import ScrollableContainer from '$lib/components/ScrollableContainer.svelte';
|
||||
|
||||
export let branch: Branch;
|
||||
export let readonly = false;
|
||||
@ -37,7 +37,6 @@
|
||||
export let base: BaseBranch | undefined | null;
|
||||
export let cloud: ReturnType<typeof getCloudApiClient>;
|
||||
export let branchController: BranchController;
|
||||
export let maximized = false;
|
||||
export let branchCount = 1;
|
||||
export let user: User | undefined;
|
||||
export let selectedFiles: Writable<File[]>;
|
||||
@ -50,12 +49,14 @@
|
||||
const aiGenEnabled = projectAiGenEnabled(project.id);
|
||||
|
||||
let rsViewport: HTMLElement;
|
||||
let commitsScrollable = false;
|
||||
|
||||
const userSettings = getContext<SettingsStore>(SETTINGS_CONTEXT);
|
||||
const defaultBranchWidthRem = persisted<number | undefined>(24, 'defaulBranchWidth' + project.id);
|
||||
const laneWidthKey = 'laneWidth_';
|
||||
|
||||
let laneWidth: number;
|
||||
let headerHeight: number | undefined;
|
||||
$: console.log(headerHeight);
|
||||
|
||||
$: {
|
||||
// On refresh we need to check expansion status from localStorage
|
||||
@ -143,195 +144,190 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div bind:this={rsViewport} class="resize-viewport">
|
||||
<div class="branch-card" style:width={`${laneWidth || $defaultBranchWidthRem}rem`}>
|
||||
<div class="flex flex-col">
|
||||
<BranchHeader
|
||||
{readonly}
|
||||
{branchController}
|
||||
{branch}
|
||||
{base}
|
||||
{githubService}
|
||||
projectId={project.id}
|
||||
on:action={(e) => {
|
||||
if (e.detail == 'generate-branch-name') {
|
||||
generateBranchName();
|
||||
}
|
||||
<div bind:this={rsViewport} class="branch-card resize-viewport">
|
||||
<BranchHeader
|
||||
{readonly}
|
||||
{branchController}
|
||||
{branch}
|
||||
{base}
|
||||
{githubService}
|
||||
bind:height={headerHeight}
|
||||
projectId={project.id}
|
||||
on:action={(e) => {
|
||||
if (e.detail == 'generate-branch-name') {
|
||||
generateBranchName();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ScrollableContainer>
|
||||
<div
|
||||
style:width={`${laneWidth || $defaultBranchWidthRem}rem`}
|
||||
class="branch-card__contents"
|
||||
style:padding-top={`${headerHeight}px`}
|
||||
>
|
||||
<div
|
||||
class="relative flex flex-grow flex-col gap-1 overflow-y-hidden"
|
||||
use:dropzone={{
|
||||
hover: 'cherrypick-dz-hover',
|
||||
active: 'cherrypick-dz-active',
|
||||
accepts: acceptCherrypick,
|
||||
onDrop: onCherrypicked
|
||||
}}
|
||||
/>
|
||||
use:dropzone={{
|
||||
hover: 'lane-dz-hover',
|
||||
active: 'lane-dz-active',
|
||||
accepts: acceptBranchDrop,
|
||||
onDrop: onBranchDrop
|
||||
}}
|
||||
>
|
||||
<!-- DROPZONES -->
|
||||
<DropzoneOverlay class="cherrypick-dz-marker" label="Apply here" />
|
||||
<DropzoneOverlay class="lane-dz-marker" label="Move here" />
|
||||
|
||||
{#if branch.upstream?.commits.length && branch.upstream?.commits.length > 0 && !branch.conflicted}
|
||||
<UpstreamCommits
|
||||
upstream={branch.upstream}
|
||||
branchId={branch.id}
|
||||
{#if branch.files?.length > 0}
|
||||
<div class="card">
|
||||
<BranchFiles
|
||||
{branch}
|
||||
{readonly}
|
||||
{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.<br />Let's start creating!
|
||||
</h2>
|
||||
<p class="new-branch__caption text-base-body-13">
|
||||
Get some work done,<br />then throw some files my way
|
||||
</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}
|
||||
<BranchCommits
|
||||
{base}
|
||||
{branch}
|
||||
{project}
|
||||
{githubService}
|
||||
{branchController}
|
||||
{branchCount}
|
||||
projectId={project.id}
|
||||
{base}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<div
|
||||
class="relative flex flex-grow flex-col overflow-y-hidden"
|
||||
use:dropzone={{
|
||||
hover: 'cherrypick-dz-hover',
|
||||
active: 'cherrypick-dz-active',
|
||||
accepts: acceptCherrypick,
|
||||
onDrop: onCherrypicked
|
||||
}}
|
||||
use:dropzone={{
|
||||
hover: 'lane-dz-hover',
|
||||
active: 'lane-dz-active',
|
||||
accepts: acceptBranchDrop,
|
||||
onDrop: onBranchDrop
|
||||
}}
|
||||
>
|
||||
<!-- DROPZONES -->
|
||||
<DropzoneOverlay class="cherrypick-dz-marker" label="Apply here" />
|
||||
<DropzoneOverlay class="lane-dz-marker" label="Move here" />
|
||||
|
||||
{#if branch.files?.length > 0}
|
||||
<BranchFiles
|
||||
{branch}
|
||||
{readonly}
|
||||
{selectedOwnership}
|
||||
{selectedFiles}
|
||||
showCheckboxes={$commitBoxOpen}
|
||||
forceResizable={commitsScrollable}
|
||||
enableResizing={branch.commits.length > 0}
|
||||
/>
|
||||
{#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}
|
||||
{:else if branch.commits.length == 0}
|
||||
<div class="new-branch" 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.<br />Let's start creating!
|
||||
</h2>
|
||||
<p class="new-branch__caption text-base-body-13">
|
||||
Get some work done,<br />then throw some files my way
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- attention: these markers have custom css at the bottom of thise file -->
|
||||
<div class="no-changes" 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__title-caption text-base-body-13">
|
||||
No uncommitted changes<br />on this branch
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<BranchCommits
|
||||
{base}
|
||||
{branch}
|
||||
{project}
|
||||
{githubService}
|
||||
{branchController}
|
||||
{readonly}
|
||||
bind:scrollable={commitsScrollable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#if !maximized}
|
||||
<Resizer
|
||||
viewport={rsViewport}
|
||||
direction="right"
|
||||
inside={$selectedFiles.length > 0}
|
||||
minWidth={320}
|
||||
on:width={(e) => {
|
||||
laneWidth = e.detail / (16 * $userSettings.zoom);
|
||||
lscache.set(laneWidthKey + branch.id, laneWidth, 7 * 1440); // 7 day ttl
|
||||
$defaultBranchWidthRem = laneWidth;
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</ScrollableContainer>
|
||||
|
||||
<Resizer
|
||||
viewport={rsViewport}
|
||||
direction="right"
|
||||
inside={$selectedFiles.length > 0}
|
||||
minWidth={320}
|
||||
on:width={(e) => {
|
||||
laneWidth = e.detail / (16 * $userSettings.zoom);
|
||||
lscache.set(laneWidthKey + branch.id, laneWidth, 7 * 1440); // 7 day ttl
|
||||
$defaultBranchWidthRem = laneWidth;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.resize-viewport {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.branch-card {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
cursor: default;
|
||||
overflow-x: hidden;
|
||||
background: var(--clr-theme-container-light);
|
||||
}
|
||||
.branch-card__contents {
|
||||
padding: var(--space-16) var(--space-8) var(--space-16) var(--space-8);
|
||||
}
|
||||
|
||||
.resize-viewport {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.new-branch,
|
||||
.no-changes {
|
||||
user-select: none;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
color: var(--clr-theme-scale-ntrl-60);
|
||||
background: var(--clr-theme-container-light);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: var(--space-24) var(--space-40);
|
||||
padding: var(--space-48) 0;
|
||||
border-radius: var(--radius-m);
|
||||
}
|
||||
|
||||
.new-branch__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--space-8);
|
||||
max-width: 14rem;
|
||||
}
|
||||
|
||||
.new-branch__image {
|
||||
width: 7.5rem;
|
||||
margin-bottom: var(--space-12);
|
||||
}
|
||||
|
||||
.new-branch__title {
|
||||
.no-changes {
|
||||
color: var(--clr-theme-scale-ntrl-40);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.new-branch__caption,
|
||||
.new-branch__title-caption {
|
||||
.new-branch__title {
|
||||
color: var(--clr-theme-scale-ntrl-40);
|
||||
}
|
||||
|
||||
.new-branch__caption {
|
||||
color: var(--clr-theme-scale-ntrl-50);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.new-branch__caption,
|
||||
.new-branch__title {
|
||||
text-align: center;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.new-branch__image {
|
||||
margin-bottom: var(--space-20);
|
||||
}
|
||||
|
||||
/* hunks drop zone */
|
||||
:global(.lane-dz-active .lane-dz-marker) {
|
||||
display: flex;
|
||||
|
@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { Project } from '$lib/backend/projects';
|
||||
import ScrollableContainer from '$lib/components/ScrollableContainer.svelte';
|
||||
import type { GitHubService } from '$lib/github/service';
|
||||
import type { BranchController } from '$lib/vbranches/branchController';
|
||||
import type { BaseBranch, Branch } from '$lib/vbranches/types';
|
||||
@ -12,40 +11,45 @@
|
||||
export let githubService: GitHubService;
|
||||
export let branchController: BranchController;
|
||||
export let readonly: boolean;
|
||||
|
||||
// Intended for 2 way binding.
|
||||
export let scrollable: boolean | undefined = undefined;
|
||||
export let branchCount: number;
|
||||
</script>
|
||||
|
||||
{#if branch.commits.length > 0}
|
||||
<!-- Note that 11.25rem min height is just observational, it might need updating -->
|
||||
<ScrollableContainer bind:scrollable minHeight="9rem" showBorderWhenScrolled>
|
||||
<CommitList
|
||||
{branch}
|
||||
{base}
|
||||
{project}
|
||||
{branchController}
|
||||
{githubService}
|
||||
{readonly}
|
||||
type="local"
|
||||
/>
|
||||
<CommitList
|
||||
{branch}
|
||||
{base}
|
||||
{project}
|
||||
{branchController}
|
||||
{githubService}
|
||||
{readonly}
|
||||
type="remote"
|
||||
/>
|
||||
<CommitList
|
||||
{branch}
|
||||
{base}
|
||||
{project}
|
||||
{branchController}
|
||||
{githubService}
|
||||
{readonly}
|
||||
type="integrated"
|
||||
/>
|
||||
</ScrollableContainer>
|
||||
{#if branch.commits.length > 0 || (branch.upstream && branch.upstream.commits.length > 0)}
|
||||
<CommitList
|
||||
{branch}
|
||||
{base}
|
||||
{project}
|
||||
{branchController}
|
||||
{branchCount}
|
||||
{githubService}
|
||||
{readonly}
|
||||
type="upstream"
|
||||
/>
|
||||
<CommitList
|
||||
{branch}
|
||||
{base}
|
||||
{project}
|
||||
{branchController}
|
||||
{githubService}
|
||||
{readonly}
|
||||
type="local"
|
||||
/>
|
||||
<CommitList
|
||||
{branch}
|
||||
{base}
|
||||
{project}
|
||||
{branchController}
|
||||
{githubService}
|
||||
{readonly}
|
||||
type="remote"
|
||||
/>
|
||||
<CommitList
|
||||
{branch}
|
||||
{base}
|
||||
{project}
|
||||
{branchController}
|
||||
{githubService}
|
||||
{readonly}
|
||||
type="integrated"
|
||||
/>
|
||||
{/if}
|
||||
|
@ -7,8 +7,6 @@
|
||||
import SegmentedControl from '$lib/components/SegmentControl/SegmentedControl.svelte';
|
||||
import Segment from '$lib/components/SegmentControl/Segment.svelte';
|
||||
import FileTree from './FileTree.svelte';
|
||||
import Resizer from '$lib/components/Resizer.svelte';
|
||||
import ScrollableContainer from '$lib/components/ScrollableContainer.svelte';
|
||||
import Checkbox from '$lib/components/Checkbox.svelte';
|
||||
import BranchFilesList from './BranchFilesList.svelte';
|
||||
|
||||
@ -16,18 +14,10 @@
|
||||
export let readonly: boolean;
|
||||
export let selectedOwnership: Writable<Ownership>;
|
||||
export let selectedFiles: Writable<File[]>;
|
||||
export let forceResizable = false;
|
||||
export let enableResizing = false;
|
||||
export let showCheckboxes = false;
|
||||
|
||||
let selectedListMode: string;
|
||||
|
||||
let scrollViewport: HTMLDivElement | undefined;
|
||||
let rsViewport: HTMLElement;
|
||||
|
||||
let scrollable: boolean | undefined;
|
||||
let height: number | undefined = undefined;
|
||||
let maxHeight: number | undefined;
|
||||
let headerElement: HTMLDivElement;
|
||||
|
||||
function isAllChecked(selectedOwnership: Ownership): boolean {
|
||||
@ -73,90 +63,57 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="header" bind:this={headerElement}>
|
||||
<div class="header__left">
|
||||
{#if showCheckboxes && selectedListMode == 'list' && branch.files.length > 1}
|
||||
<Checkbox
|
||||
small
|
||||
{checked}
|
||||
{indeterminate}
|
||||
on:change={(e) => {
|
||||
if (e.detail) {
|
||||
selectAll(selectedOwnership, branch.files);
|
||||
} else {
|
||||
selectedOwnership.update((ownership) => ownership.clear());
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
<div class="header__title text-base-13 text-semibold">
|
||||
<span>Changes</span>
|
||||
<Badge count={branch.files.length} />
|
||||
</div>
|
||||
</div>
|
||||
<SegmentedControl bind:selected={selectedListMode} selectedIndex={0}>
|
||||
<Segment id="list" icon="list-view" />
|
||||
<Segment id="tree" icon="tree-view" />
|
||||
</SegmentedControl>
|
||||
</div>
|
||||
<div
|
||||
class="resize-viewport flex-grow"
|
||||
class:flex-shrink-0={(scrollable || forceResizable) && branch.commits.length > 0 && height}
|
||||
style:min-height={scrollable || forceResizable ? `${headerElement.offsetHeight}px` : undefined}
|
||||
style:height={scrollable || forceResizable ? `${height}px` : undefined}
|
||||
style:max-height={forceResizable && maxHeight ? maxHeight + 'px' : undefined}
|
||||
bind:this={rsViewport}
|
||||
>
|
||||
{#if branch.files.length > 0}
|
||||
<ScrollableContainer
|
||||
showBorderWhenScrolled
|
||||
bind:viewport={scrollViewport}
|
||||
bind:maxHeight
|
||||
bind:scrollable
|
||||
>
|
||||
<div class="scroll-container">
|
||||
<!-- TODO: This is an experiment in file sorting. Accept or reject! -->
|
||||
{#if selectedListMode == 'list'}
|
||||
<BranchFilesList
|
||||
{branch}
|
||||
{selectedOwnership}
|
||||
{selectedFiles}
|
||||
{showCheckboxes}
|
||||
{readonly}
|
||||
/>
|
||||
{:else}
|
||||
<FileTree
|
||||
node={filesToFileTree(branch.files)}
|
||||
{showCheckboxes}
|
||||
branchId={branch.id}
|
||||
isRoot={true}
|
||||
{selectedOwnership}
|
||||
{selectedFiles}
|
||||
{readonly}
|
||||
/>
|
||||
{/if}
|
||||
<div class="branch-files">
|
||||
<div class="header" bind:this={headerElement}>
|
||||
<div class="header__left">
|
||||
{#if showCheckboxes && selectedListMode == 'list' && branch.files.length > 1}
|
||||
<Checkbox
|
||||
small
|
||||
{checked}
|
||||
{indeterminate}
|
||||
on:change={(e) => {
|
||||
if (e.detail) {
|
||||
selectAll(selectedOwnership, branch.files);
|
||||
} else {
|
||||
selectedOwnership.update((ownership) => ownership.clear());
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
<div class="header__title text-base-13 text-semibold">
|
||||
<span>Changes</span>
|
||||
<Badge count={branch.files.length} />
|
||||
</div>
|
||||
</ScrollableContainer>
|
||||
{/if}
|
||||
<!-- Resizing makes no sense if there are no branch commits. -->
|
||||
{#if (forceResizable || scrollable) && enableResizing}
|
||||
<Resizer
|
||||
inside
|
||||
direction="down"
|
||||
viewport={rsViewport}
|
||||
on:height={(e) => {
|
||||
height = e.detail;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<SegmentedControl bind:selected={selectedListMode} selectedIndex={0}>
|
||||
<Segment id="list" icon="list-view" />
|
||||
<Segment id="tree" icon="tree-view" />
|
||||
</SegmentedControl>
|
||||
</div>
|
||||
{#if branch.files.length > 0}
|
||||
<div class="scroll-container">
|
||||
<!-- TODO: This is an experiment in file sorting. Accept or reject! -->
|
||||
{#if selectedListMode == 'list'}
|
||||
<BranchFilesList {branch} {selectedOwnership} {selectedFiles} {showCheckboxes} {readonly} />
|
||||
{:else}
|
||||
<FileTree
|
||||
node={filesToFileTree(branch.files)}
|
||||
{showCheckboxes}
|
||||
branchId={branch.id}
|
||||
isRoot={true}
|
||||
{selectedOwnership}
|
||||
{selectedFiles}
|
||||
{readonly}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.resize-viewport {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
.branch-files {
|
||||
background: var(--clr-theme-container-light);
|
||||
border-radius: var(--radius-m) var(--radius-m) 0 0;
|
||||
}
|
||||
.scroll-container {
|
||||
display: flex;
|
||||
|
@ -3,21 +3,22 @@
|
||||
import Icon from '$lib/icons/Icon.svelte';
|
||||
import type { BranchController } from '$lib/vbranches/branchController';
|
||||
import type { BaseBranch, Branch } from '$lib/vbranches/types';
|
||||
import { fade } from 'svelte/transition';
|
||||
import BranchLabel from './BranchLabel.svelte';
|
||||
import BranchLanePopupMenu from './BranchLanePopupMenu.svelte';
|
||||
import { clickOutside } from '$lib/clickOutside';
|
||||
import Tag from './Tag.svelte';
|
||||
import { branchUrl } from './commitList';
|
||||
import type { GitHubService } from '$lib/github/service';
|
||||
import BranchIcon from '../navigation/BranchIcon.svelte';
|
||||
import { open } from '@tauri-apps/api/shell';
|
||||
import Button from '$lib/components/Button.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let readonly = false;
|
||||
export let branch: Branch;
|
||||
export let base: BaseBranch | undefined | null;
|
||||
export let branchController: BranchController;
|
||||
export let projectId: string;
|
||||
export let height: number | undefined;
|
||||
|
||||
export let githubService: GitHubService;
|
||||
$: pr$ = githubService.get(branch.upstreamName);
|
||||
@ -25,125 +26,168 @@
|
||||
|
||||
let meatballButton: HTMLDivElement;
|
||||
let visible = false;
|
||||
let observer: ResizeObserver;
|
||||
let container: HTMLDivElement;
|
||||
|
||||
onMount(() => {
|
||||
observer = new ResizeObserver(() => {
|
||||
if (container) {
|
||||
height = container.offsetHeight;
|
||||
}
|
||||
});
|
||||
return observer.observe(container);
|
||||
});
|
||||
|
||||
function handleBranchNameChange() {
|
||||
branchController.updateBranchName(branch.id, branch.name);
|
||||
}
|
||||
|
||||
function normalizeBranchName(value: string) {
|
||||
return value.toLowerCase().replace(/[^0-9a-z/_]/g, '-');
|
||||
}
|
||||
|
||||
$: hasIntegratedCommits = branch.commits?.some((b) => b.isIntegrated);
|
||||
</script>
|
||||
|
||||
<div class="card__header">
|
||||
{#if !readonly}
|
||||
<div class="draggable" data-drag-handle>
|
||||
<Icon name="draggable-narrow" />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="header__content">
|
||||
<div class="header__row">
|
||||
<div class="header__label">
|
||||
<BranchLabel bind:name={branch.name} on:change={handleBranchNameChange} />
|
||||
</div>
|
||||
<div class="flex items-center gap-x-1" transition:fade={{ duration: 150 }}>
|
||||
{#if !readonly}
|
||||
<div bind:this={meatballButton}>
|
||||
<IconButton icon="kebab" size="m" on:click={() => (visible = !visible)} />
|
||||
</div>
|
||||
<div
|
||||
class="branch-popup-menu"
|
||||
use:clickOutside={{
|
||||
trigger: meatballButton,
|
||||
handler: () => (visible = false)
|
||||
}}
|
||||
>
|
||||
<BranchLanePopupMenu {branchController} {branch} {projectId} bind:visible on:action />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if branch.upstreamName}
|
||||
<div class="header__remote-branch text-base-body-11">
|
||||
{#if !branch.upstream}
|
||||
{#if hasIntegratedCommits}
|
||||
<div class="status-tag deleted">deleted</div>
|
||||
<div class="wrapper" bind:this={container}>
|
||||
<div class="concealer">
|
||||
<div class="header card">
|
||||
<div class="header__info">
|
||||
<div class="header__label">
|
||||
<BranchLabel bind:name={branch.name} on:change={handleBranchNameChange} />
|
||||
</div>
|
||||
<div class="header__remote-branch text-base-body-11">
|
||||
{#if !branch.upstream}
|
||||
{#if hasIntegratedCommits}
|
||||
<div class="status-tag deleted"><Icon name="remote-branch-small" /> deleted</div>
|
||||
{:else}
|
||||
<div class="status-tag pending"><Icon name="remote-branch-small" /> new</div>
|
||||
{/if}
|
||||
<div class="text-semibold pending-name text-base-11">
|
||||
origin/{branch.upstreamName ? branch.upstreamName : normalizeBranchName(branch.name)}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="status-tag pending">pending</div>
|
||||
<div class="status-tag remote"><Icon name="remote-branch-small" /> remote</div>
|
||||
<Tag
|
||||
icon="open-link"
|
||||
color="ghost"
|
||||
border
|
||||
clickable
|
||||
on:click={(e) => {
|
||||
const url = branchUrl(base, branch.upstream?.name);
|
||||
if (url) open(url);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
origin/{branch.upstreamName}
|
||||
</Tag>
|
||||
{#if $pr$?.htmlUrl}
|
||||
<Tag
|
||||
icon="pr-small"
|
||||
color="ghost"
|
||||
border
|
||||
clickable
|
||||
on:click={(e) => {
|
||||
const url = $pr$?.htmlUrl;
|
||||
if (url) open(url);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
View PR
|
||||
</Tag>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
<div>origin/{branch.upstreamName}</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if branch.upstream}
|
||||
<div class="header__links">
|
||||
<BranchIcon name="remote-branch" color="neutral" />
|
||||
|
||||
<Tag
|
||||
icon="open-link"
|
||||
color="ghost"
|
||||
border
|
||||
clickable
|
||||
on:click={(e) => {
|
||||
const url = branchUrl(base, branch.upstream?.name);
|
||||
if (url) open(url);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
View remote branch
|
||||
</Tag>
|
||||
{#if $pr$?.htmlUrl}
|
||||
<Tag
|
||||
icon="pr-small"
|
||||
color="ghost"
|
||||
border
|
||||
clickable
|
||||
on:click={(e) => {
|
||||
const url = $pr$?.htmlUrl;
|
||||
if (url) open(url);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
View PR
|
||||
</Tag>
|
||||
</div>
|
||||
{#if !readonly}
|
||||
<div class="draggable" data-drag-handle>
|
||||
<Icon name="draggable-narrow" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="header__actions">
|
||||
<div class="header__buttons">
|
||||
{#if branch.selectedForChanges}
|
||||
<Button icon="target">Target branch</Button>
|
||||
{:else}
|
||||
<Button
|
||||
icon="target"
|
||||
kind="outlined"
|
||||
on:click={async () => {
|
||||
await branchController.setSelectedForChanges(branch.id);
|
||||
}}
|
||||
>
|
||||
Make target
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{#if !readonly}
|
||||
<div class="relative" bind:this={meatballButton}>
|
||||
<IconButton border icon="kebab" size="m" on:click={() => (visible = !visible)} />
|
||||
<div
|
||||
class="branch-popup-menu"
|
||||
use:clickOutside={{
|
||||
trigger: meatballButton,
|
||||
handler: () => (visible = false)
|
||||
}}
|
||||
>
|
||||
<BranchLanePopupMenu {branchController} {branch} {projectId} bind:visible on:action />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.card__header {
|
||||
.wrapper {
|
||||
padding: 0 var(--space-8) var(--space-16) var(--space-8);
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
width: 100%;
|
||||
}
|
||||
.concealer {
|
||||
background: var(--clr-theme-container-pale);
|
||||
border-radius: 0 0 var(--radius-m) var(--radius-m);
|
||||
padding-top: var(--space-16);
|
||||
}
|
||||
.header {
|
||||
user-select: none;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-12);
|
||||
top: 0;
|
||||
|
||||
&:hover {
|
||||
& .header__content {
|
||||
margin-left: var(--space-6);
|
||||
}
|
||||
|
||||
& .draggable {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.header__content {
|
||||
|
||||
.header__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: margin var(--transition-slow);
|
||||
padding: var(--space-12);
|
||||
gap: var(--space-10);
|
||||
}
|
||||
.header__row {
|
||||
width: 100%;
|
||||
.header__actions {
|
||||
display: flex;
|
||||
gap: var(--space-4);
|
||||
background: var(--clr-theme-container-pale);
|
||||
padding: var(--space-12);
|
||||
justify-content: space-between;
|
||||
gap: var(--space-8);
|
||||
overflow-x: hidden;
|
||||
border-radius: 0 0 var(--radius-m) var(--radius-m);
|
||||
}
|
||||
.header__buttons {
|
||||
display: flex;
|
||||
position: relative;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
.header__label {
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
align-items: center;
|
||||
@ -162,7 +206,7 @@
|
||||
|
||||
.draggable {
|
||||
position: absolute;
|
||||
left: var(--space-4);
|
||||
right: var(--space-4);
|
||||
top: var(--space-6);
|
||||
opacity: 0;
|
||||
display: flex;
|
||||
@ -183,8 +227,8 @@
|
||||
|
||||
.branch-popup-menu {
|
||||
position: absolute;
|
||||
top: calc(var(--space-2) + var(--space-40));
|
||||
right: var(--space-12);
|
||||
top: calc(100% + var(--space-4));
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
@ -192,6 +236,7 @@
|
||||
color: var(--clr-theme-scale-ntrl-50);
|
||||
padding-left: var(--space-4);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-4);
|
||||
text-overflow: ellipsis;
|
||||
overflow-x: hidden;
|
||||
@ -200,18 +245,35 @@
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
padding: var(--space-2) var(--space-4);
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-6) var(--space-2) var(--space-4);
|
||||
border-radius: var(--radius-s);
|
||||
margin-right: var(--space-2);
|
||||
}
|
||||
|
||||
.pending {
|
||||
color: var(--clr-theme-scale-ntrl-40);
|
||||
background: var(--clr-theme-container-sub);
|
||||
color: var(--clr-theme-scale-pop-30);
|
||||
background: var(--clr-theme-scale-pop-80);
|
||||
}
|
||||
|
||||
.pending-name {
|
||||
background: color-mix(in srgb, var(--clr-theme-scale-ntrl-50) 10%, transparent);
|
||||
border-radius: var(--radius-m);
|
||||
line-height: 120%;
|
||||
height: var(--space-20);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 var(--space-6);
|
||||
}
|
||||
|
||||
.deleted {
|
||||
color: var(--clr-theme-scale-warn-30);
|
||||
background: var(--clr-theme-warn-container-dim);
|
||||
}
|
||||
|
||||
.remote {
|
||||
color: var(--clr-theme-scale-ntrl-100);
|
||||
background: var(--clr-theme-scale-ntrl-40);
|
||||
}
|
||||
</style>
|
||||
|
@ -15,7 +15,6 @@
|
||||
export let base: BaseBranch | undefined | null;
|
||||
export let cloud: ReturnType<typeof getCloudApiClient>;
|
||||
export let branchController: BranchController;
|
||||
export let maximized = false;
|
||||
export let branchCount = 1;
|
||||
export let user: User | undefined;
|
||||
export let projectPath: string;
|
||||
@ -36,7 +35,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="wrapper card">
|
||||
<div class="wrapper">
|
||||
<BranchCard
|
||||
{branch}
|
||||
{readonly}
|
||||
@ -46,7 +45,6 @@
|
||||
{branchController}
|
||||
{selectedOwnership}
|
||||
bind:commitBoxOpen
|
||||
{maximized}
|
||||
{branchCount}
|
||||
{user}
|
||||
{selectedFiles}
|
||||
@ -73,11 +71,10 @@
|
||||
|
||||
<style lang="postcss">
|
||||
.wrapper {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: self-start;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.card {
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
@ -228,6 +228,7 @@
|
||||
background: var(--clr-theme-container-light);
|
||||
border-top: 1px solid var(--clr-theme-container-outline-light);
|
||||
transition: background-color var(--transition-medium);
|
||||
border-radius: 0 0 var(--radius-m) var(--radius-m);
|
||||
}
|
||||
.commit-box__expander {
|
||||
display: flex;
|
||||
|
@ -14,33 +14,48 @@
|
||||
export let type: CommitStatus;
|
||||
export let githubService: GitHubService;
|
||||
export let readonly: boolean;
|
||||
export let branchCount: number = 0;
|
||||
|
||||
let headerHeight: number;
|
||||
|
||||
$: headCommit = branch.commits[0];
|
||||
$: commits = branch.commits.filter((c) => c.status == type);
|
||||
$: upstreamCommitCount = branch.upstream?.commits.length;
|
||||
|
||||
$: commits =
|
||||
type == 'upstream' ? branch.upstream?.commits : branch.commits.filter((c) => c.status == type);
|
||||
let expanded = true;
|
||||
</script>
|
||||
|
||||
{#if commits.length > 0}
|
||||
<div class="commit-list" style:min-height={expanded ? `${2 * headerHeight}px` : undefined}>
|
||||
<CommitListHeader bind:expanded {type} bind:height={headerHeight} />
|
||||
{#if commits && commits.length > 0}
|
||||
<div
|
||||
class="commit-list card"
|
||||
class:upstream={type == 'upstream'}
|
||||
style:min-height={expanded ? `${2 * headerHeight}px` : undefined}
|
||||
>
|
||||
<CommitListHeader {type} {upstreamCommitCount} bind:expanded bind:height={headerHeight} />
|
||||
{#if expanded}
|
||||
<div class="commit-list__content">
|
||||
<div class="commits">
|
||||
{#each commits as commit, idx (commit.id)}
|
||||
<CommitListItem
|
||||
{branch}
|
||||
{branchController}
|
||||
{commit}
|
||||
{base}
|
||||
{project}
|
||||
{readonly}
|
||||
isChained={idx != commits.length - 1}
|
||||
isHeadCommit={commit.id === headCommit?.id}
|
||||
/>
|
||||
{/each}
|
||||
{#if commits}
|
||||
{#each commits as commit, idx (commit.id)}
|
||||
<CommitListItem
|
||||
{branch}
|
||||
{branchController}
|
||||
{commit}
|
||||
{base}
|
||||
{project}
|
||||
{readonly}
|
||||
isChained={idx != commits.length - 1}
|
||||
isHeadCommit={commit.id === headCommit?.id}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{#if type == 'upstream' && branchCount > 1}
|
||||
<div class="upstream-message text-base-body-11">
|
||||
You have {branchCount} active branches. To merge upstream work, we will unapply all other
|
||||
branches.
|
||||
</div>{/if}
|
||||
<CommitListFooter
|
||||
{branchController}
|
||||
{branch}
|
||||
@ -57,10 +72,13 @@
|
||||
|
||||
<style lang="postcss">
|
||||
.commit-list {
|
||||
&.upstream {
|
||||
background-color: var(--clr-theme-container-pale);
|
||||
}
|
||||
background-color: var(--clr-theme-container-light);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-top: 1px solid var(--clr-theme-container-outline-light);
|
||||
/* border-top: 1px solid var(--clr-theme-container-outline-light); */
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@ -70,4 +88,11 @@
|
||||
padding: 0 var(--space-16) var(--space-20) var(--space-16);
|
||||
gap: var(--space-8);
|
||||
}
|
||||
.upstream-message {
|
||||
color: var(--clr-theme-scale-warn-30);
|
||||
border-radius: var(--radius-m);
|
||||
background: var(--clr-theme-scale-warn-80);
|
||||
padding: var(--space-12);
|
||||
margin-left: var(--space-16);
|
||||
}
|
||||
</style>
|
||||
|
@ -20,6 +20,7 @@
|
||||
$: pr$ = githubService.get(branch.upstreamName);
|
||||
|
||||
let isPushing: boolean;
|
||||
let isMerging: boolean;
|
||||
|
||||
async function push() {
|
||||
isPushing = true;
|
||||
@ -97,6 +98,24 @@
|
||||
Push
|
||||
{/if}
|
||||
</Button>
|
||||
{:else if type == 'upstream'}
|
||||
<Button
|
||||
wide
|
||||
color="warn"
|
||||
loading={isMerging}
|
||||
on:click={async () => {
|
||||
isMerging = true;
|
||||
try {
|
||||
await branchController.mergeUpstream(branch.id);
|
||||
} catch (err) {
|
||||
toast.error('Failed to merge upstream commits');
|
||||
} finally {
|
||||
isMerging = false;
|
||||
}
|
||||
}}
|
||||
>
|
||||
Merge upstream commits
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
@ -6,6 +6,7 @@
|
||||
export let expanded: boolean;
|
||||
export let type: CommitStatus;
|
||||
export let height: number | undefined;
|
||||
export let upstreamCommitCount = 0;
|
||||
|
||||
let element: HTMLButtonElement | undefined = undefined;
|
||||
|
||||
@ -31,6 +32,9 @@
|
||||
Remote branch
|
||||
{:else if type == 'integrated'}
|
||||
Integrated
|
||||
{:else if type == 'upstream'}
|
||||
{upstreamCommitCount} upstream {upstreamCommitCount == 1 ? 'commit' : 'commits'}
|
||||
<Icon name="warning" color="warn" />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="expander">
|
||||
|
@ -10,7 +10,7 @@
|
||||
} from '$lib/draggables';
|
||||
import { dropzone } from '$lib/utils/draggable';
|
||||
import type { BranchController } from '$lib/vbranches/branchController';
|
||||
import type { BaseBranch, Branch, Commit } from '$lib/vbranches/types';
|
||||
import { RemoteCommit, type BaseBranch, type Branch, type Commit } from '$lib/vbranches/types';
|
||||
import { get } from 'svelte/store';
|
||||
import CommitCard from './CommitCard.svelte';
|
||||
import DropzoneOverlay from './DropzoneOverlay.svelte';
|
||||
@ -18,14 +18,17 @@
|
||||
|
||||
export let branch: Branch;
|
||||
export let project: Project;
|
||||
export let commit: Commit;
|
||||
export let commit: Commit | RemoteCommit;
|
||||
export let base: BaseBranch | undefined | null;
|
||||
export let isHeadCommit: boolean;
|
||||
export let isChained: boolean;
|
||||
export let readonly = false;
|
||||
export let branchController: BranchController;
|
||||
|
||||
function acceptAmend(commit: Commit) {
|
||||
function acceptAmend(commit: Commit | RemoteCommit) {
|
||||
if (commit instanceof RemoteCommit) {
|
||||
return () => false;
|
||||
}
|
||||
return (data: any) => {
|
||||
if (!project.ok_with_force_push && commit.isRemote) {
|
||||
return false;
|
||||
@ -60,7 +63,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
function acceptSquash(commit: Commit) {
|
||||
function acceptSquash(commit: Commit | RemoteCommit) {
|
||||
if (commit instanceof RemoteCommit) {
|
||||
return () => false;
|
||||
}
|
||||
return (data: any) => {
|
||||
if (!isDraggableCommit(data)) return false;
|
||||
if (data.branchId != branch.id) return false;
|
||||
@ -79,7 +85,10 @@
|
||||
};
|
||||
}
|
||||
|
||||
function onSquash(commit: Commit) {
|
||||
function onSquash(commit: Commit | RemoteCommit) {
|
||||
if (commit instanceof RemoteCommit) {
|
||||
return () => false;
|
||||
}
|
||||
return (data: DraggableCommit) => {
|
||||
if (data.commit.isParentOf(commit)) {
|
||||
branchController.squashBranchCommit(data.branchId, commit.id);
|
||||
|
@ -116,13 +116,9 @@
|
||||
...draggableFile(branchId, file, writable([file])),
|
||||
disabled: readonly
|
||||
}}
|
||||
style:width={`${fileWidth || $defaultFileWidthRem}rem`}
|
||||
>
|
||||
<div
|
||||
id={`file-${file.id}`}
|
||||
class="file-card"
|
||||
style:width={`${fileWidth || $defaultFileWidthRem}rem`}
|
||||
class:opacity-80={isFileLocked}
|
||||
>
|
||||
<div id={`file-${file.id}`} class="file-card card">
|
||||
<FileCardHeader {file} {isFileLocked} on:close />
|
||||
{#if conflicted}
|
||||
<div class="mb-2 bg-red-500 px-2 py-0 font-bold text-white">
|
||||
@ -251,12 +247,20 @@
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.resize-viewport {
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: self-start;
|
||||
overflow: hidden;
|
||||
padding: var(--space-16) var(--space-6);
|
||||
}
|
||||
.file-card {
|
||||
background: var(--clr-theme-container-light);
|
||||
border-left: 1px solid var(--clr-theme-container-outline-light);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 100%;
|
||||
}
|
||||
.hunks {
|
||||
display: flex;
|
||||
@ -291,7 +295,6 @@
|
||||
border-radius: var(--radius-s);
|
||||
border: 1px solid var(--clr-theme-container-outline-light);
|
||||
overflow-x: hidden;
|
||||
overscroll-behavior: none;
|
||||
transition: border-color var(--transition-fast);
|
||||
}
|
||||
.hunk__inner_inner {
|
||||
@ -312,10 +315,6 @@
|
||||
.removed {
|
||||
color: #ff3e00;
|
||||
}
|
||||
.resize-viewport {
|
||||
position: relative;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@keyframes wiggle {
|
||||
0% {
|
||||
|
@ -84,7 +84,6 @@
|
||||
base={$baseBranch$}
|
||||
{cloud}
|
||||
project={$project$}
|
||||
maximized={false}
|
||||
readonly={true}
|
||||
user={$user$}
|
||||
projectPath={$project$.path}
|
||||
|
@ -4,7 +4,6 @@
|
||||
border: 1px solid var(--clr-theme-container-outline-light);
|
||||
border-radius: var(--radius-m);
|
||||
background: var(--clr-theme-container-light);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card__header {
|
||||
|
@ -190,7 +190,7 @@
|
||||
--clr-theme-warn-container-dim: var(--clr-core-warn-80);
|
||||
--clr-theme-warn-element: var(--clr-core-warn-50);
|
||||
--clr-theme-warn-element-dark: var(--clr-core-warn-40);
|
||||
--clr-theme-warn-element-dim: var(--clr-core-warn-50);
|
||||
--clr-theme-warn-element-dim: var(--clr-core-warn-45);
|
||||
--clr-theme-warn-on-container: var(--clr-core-warn-40);
|
||||
--clr-theme-warn-on-element: var(--clr-core-warn-95);
|
||||
--clr-theme-warn-outline: var(--clr-core-warn-45);
|
||||
|
Loading…
Reference in New Issue
Block a user