Updated branch switch on the Settings page

- copy updated
- layout updated
- select component max-height fixed
This commit is contained in:
Pavel Laptev 2024-05-04 00:41:15 +02:00
parent fb6dd2429e
commit c0cc85688f
6 changed files with 134 additions and 148 deletions

View File

@ -75,6 +75,8 @@
e.stopPropagation(); e.stopPropagation();
}} }}
> >
{isLaneCollapsed ? 'View branch' : `${$baseBranch.actualPushRemoteName()}/${$branch.upstreamName}`} {isLaneCollapsed
? 'View branch'
: `${$baseBranch.actualPushRemoteName()}/${$branch.upstreamName}`}
</Tag> </Tag>
{/if} {/if}

View File

@ -19,143 +19,123 @@
let project = getContext(Project); let project = getContext(Project);
let selectedBranch = {name: $baseBranch.branchName}; let selectedBranch = { name: $baseBranch.branchName };
let selectedRemote = {name: $baseBranch.actualPushRemoteName()}; let selectedRemote = { name: $baseBranch.actualPushRemoteName() };
let targetChangeDisabled = true; let targetChangeDisabled = true;
if ($activeBranches) { if ($activeBranches) {
targetChangeDisabled = $activeBranches.length > 0; targetChangeDisabled = $activeBranches.length > 0;
} }
let isSwitching = false; let isSwitching = false;
// Fetch the remote branches reactively
let remoteBranchesPromise: Promise<
{
name: string;
}[]
>;
$: {
if (project) {
remoteBranchesPromise = getRemoteBranches(project.id);
}
}
function uniqueRemotes(remoteBranches: { name: string }[]) { function uniqueRemotes(remoteBranches: { name: string }[]) {
return Array.from(new Set(remoteBranches.map((b) => b.name.split('/')[0]))).map((r) => ({ name: r })); return Array.from(new Set(remoteBranches.map((b) => b.name.split('/')[0]))).map((r) => ({
name: r
}));
} }
async function onSetBaseBranchClick() { async function onSetBaseBranchClick() {
if (!selectedBranch) return; if (!selectedBranch) return;
// while target is setting, display loading isSwitching = true; // Indicate switching in progress
isSwitching = true;
if(selectedRemote){ if (selectedRemote) {
await branchController await branchController.setTarget(selectedBranch.name, selectedRemote.name).finally(() => {
.setTarget(selectedBranch.name, selectedRemote.name) isSwitching = false;
.finally(() => { });
isSwitching = false;
});
} else { } else {
await branchController await branchController.setTarget(selectedBranch.name).finally(() => {
.setTarget(selectedBranch.name) isSwitching = false;
.finally(() => { });
isSwitching = false;
});
} }
} }
</script> </script>
<Section spacer> {#await remoteBranchesPromise}
<SectionCard labelFor="targetBranch" orientation="column"> <InfoMessage filled outlined={false} icon="info">
<svelte:fragment slot="title">Current Target Branch</svelte:fragment> <svelte:fragment slot="content">Loading remote branches...</svelte:fragment>
<svelte:fragment slot="caption"> </InfoMessage>
Your target branch is what you consider "production". This is where you {:then remoteBranches}
want to integrate any branches that you create. Normally something like {#if remoteBranches.length > 0}
'origin/master' or 'upstream/main'. <Section spacer>
</svelte:fragment> <SectionCard>
<svelte:fragment slot="title">Remote configuration</svelte:fragment>
<svelte:fragment slot="caption">
Lets you choose where to push code and set the target branch for contributions. The target
branch is usually the "production" branch like 'origin/master' or 'upstream/main.' This
section helps ensure your code goes to the correct remote and branch for integration.
</svelte:fragment>
<div class="inputs-group"> <Select
{#if isSwitching} items={remoteBranches}
<InfoMessage filled outlined={false} style="pop" icon="info"> bind:value={selectedBranch}
<svelte:fragment slot="title"> itemId="name"
Switching target branch... labelId="name"
</svelte:fragment> disabled={targetChangeDisabled}
</InfoMessage> wide={true}
{:else} label="Current target branch"
{#await getRemoteBranches(project.id)} >
loading remote branches... <SelectItem slot="template" let:item let:selected {selected}>
{:then remoteBranches} {item.name}
{#if remoteBranches.length == 0} </SelectItem>
<InfoMessage filled outlined={false} style="error" icon="error"> </Select>
<svelte:fragment slot="title">
You don't have any remote branches.
</svelte:fragment>
</InfoMessage>
{:else}
<div class="inputs-row">
<Select
items={remoteBranches}
bind:value={selectedBranch}
itemId="name"
labelId="name"
disabled={targetChangeDisabled}
wide={true}
>
<SelectItem slot="template" let:item let:selected {selected}>
{item.name}
</SelectItem>
</Select>
<Button
size="cta"
style="ghost"
kind="solid"
on:click={onSetBaseBranchClick}
id="set-base-branch"
loading={isSwitching}
disabled={(selectedBranch.name === $baseBranch.branchName) || targetChangeDisabled}
>
Change Target Branch
</Button>
</div>
{#if uniqueRemotes(remoteBranches).length > 1} {#if uniqueRemotes(remoteBranches).length > 1}
Create branches on remote: <Select
<Select items={uniqueRemotes(remoteBranches)}
items={uniqueRemotes(remoteBranches)} bind:value={selectedRemote}
bind:value={selectedRemote} itemId="name"
itemId="name" labelId="name"
labelId="name" disabled={targetChangeDisabled}
disabled={targetChangeDisabled} label="Create branches on remote"
> >
<SelectItem slot="template" let:item let:selected {selected}> <SelectItem slot="template" let:item let:selected {selected}>
{item.name} {item.name}
</SelectItem> </SelectItem>
</Select> </Select>
{/if} {/if}
{/if} {#if $activeBranches && targetChangeDisabled}
{:catch} <InfoMessage filled outlined={false} icon="info">
<InfoMessage filled outlined={true} style="error" icon="error"> <svelte:fragment slot="content">
<svelte:fragment slot="title"> You have {$activeBranches.length === 1
We got an error trying to list your remote branches ? '1 active branch'
: `${$activeBranches.length} active branches`} in your workspace. Please clear the workspace
before switching the base branch.
</svelte:fragment> </svelte:fragment>
</InfoMessage> </InfoMessage>
{/await} {:else}
{/if} <Button
size="cta"
{#if $activeBranches && targetChangeDisabled} style="ghost"
<InfoMessage filled outlined={false} icon="info"> kind="solid"
<svelte:fragment slot="content"> on:click={onSetBaseBranchClick}
You have {$activeBranches.length === 1 id="set-base-branch"
? '1 active branch' loading={isSwitching}
: `${$activeBranches.length} active branches`} in your workspace. Please clear the workspace disabled={selectedBranch.name === $baseBranch.branchName || targetChangeDisabled}
before switching the base branch. >
</svelte:fragment> {isSwitching ? 'Switching branches...' : 'Update configuration'}
</InfoMessage> </Button>
{/if} {/if}
</div> </SectionCard>
</SectionCard> </Section>
</Section> {/if}
{:catch}
<style> <InfoMessage filled outlined={true} style="error" icon="error">
.inputs-group { <svelte:fragment slot="title"
display: flex; >We got an error trying to list your remote branches</svelte:fragment
flex-direction: column; >
gap: var(--size-16); </InfoMessage>
width: 100%; {/await}
}
.inputs-row {
display: flex;
justify-content: space-between;
gap: var(--size-16);
}
</style>

View File

@ -26,7 +26,6 @@
let dragHandle: any; let dragHandle: any;
let clone: any; let clone: any;
let isSwitching = false;
</script> </script>
{#if $activeBranchesError} {#if $activeBranchesError}

View File

@ -36,35 +36,38 @@
let loading = false; let loading = false;
let selectedBranch = getBestBranch(remoteBranches); let selectedBranch = getBestBranch(remoteBranches);
function getBestBranch(branches: { name: string; }[]): { name: string } { function getBestBranch(branches: { name: string }[]): { name: string } {
// Function to calculate the rank of a branch // Function to calculate the rank of a branch
// eslint-disable-next-line func-style // eslint-disable-next-line func-style
const calculateRank = (branch: string): number => { const calculateRank = (branch: string): number => {
if (branch === 'upstream/main' || branch === 'upstream/master') { if (branch === 'upstream/main' || branch === 'upstream/master') {
return 100; // Highest preference return 100; // Highest preference
} }
if (branch === 'origin/main' || branch === 'origin/master') { if (branch === 'origin/main' || branch === 'origin/master') {
return 90; return 90;
} }
if (branch.startsWith('origin')) { if (branch.startsWith('origin')) {
return 80; return 80;
} }
if (branch.endsWith('master') || branch.endsWith('main')) { if (branch.endsWith('master') || branch.endsWith('main')) {
return 70; return 70;
} }
return 10; // Least preference return 10; // Least preference
}; };
// Sort the branches based on their rank // Sort the branches based on their rank
branches.sort((a, b) => calculateRank(b.name) - calculateRank(a.name)); branches.sort((a, b) => calculateRank(b.name) - calculateRank(a.name));
// Return the branch with the highest rank // Return the branch with the highest rank
return branches[0]; return branches[0];
} }
// split all the branches by the first '/' and gather the unique remote names // split all the branches by the first '/' and gather the unique remote names
// then turn remotes into an array of objects with a 'name' and 'value' key // then turn remotes into an array of objects with a 'name' and 'value' key
let remotes = Array.from(new Set(remoteBranches.map((b) => b.name.split('/')[0]))).map((r) => ({ name: r, value: r })); let remotes = Array.from(new Set(remoteBranches.map((b) => b.name.split('/')[0]))).map((r) => ({
name: r,
value: r
}));
let selectedRemote = remotes[0]; let selectedRemote = remotes[0];
// if there's an 'origin', select it by default // if there's an 'origin', select it by default
@ -95,8 +98,8 @@
</Select> </Select>
{#if remotes.length > 1} {#if remotes.length > 1}
<p class="text-base-body-12"> <p class="text-base-body-12">
You have branches from multiple remotes. If you want to specify a push target for You have branches from multiple remotes. If you want to specify a push target for creating
creating branches that is different from your production branch, change it here. branches that is different from your production branch, change it here.
</p> </p>
<Select items={remotes} bind:value={selectedRemote} itemId="name" labelId="name"> <Select items={remotes} bind:value={selectedRemote} itemId="name" labelId="name">
<SelectItem slot="template" let:item let:selected {selected}> <SelectItem slot="template" let:item let:selected {selected}>

View File

@ -2,6 +2,7 @@
import ScrollableContainer from './ScrollableContainer.svelte'; import ScrollableContainer from './ScrollableContainer.svelte';
import TextBox from './TextBox.svelte'; import TextBox from './TextBox.svelte';
import { clickOutside } from '$lib/clickOutside'; import { clickOutside } from '$lib/clickOutside';
import { pxToRem } from '$lib/utils/pxToRem';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
export let id: undefined | string = undefined; export let id: undefined | string = undefined;
@ -15,6 +16,7 @@
export let value: any = undefined; export let value: any = undefined;
export let selectedItemId: any = undefined; export let selectedItemId: any = undefined;
export let placeholder = ''; export let placeholder = '';
export let maxHeight: number | undefined = 260;
$: if (selectedItemId) value = items.find((item) => item[itemId] == selectedItemId); $: if (selectedItemId) value = items.find((item) => item[itemId] == selectedItemId);
@ -25,7 +27,6 @@
let listOpen = false; let listOpen = false;
let element: HTMLElement; let element: HTMLElement;
let options: HTMLDivElement; let options: HTMLDivElement;
let maxHeight = 200;
function handleItemClick(item: any) { function handleItemClick(item: any) {
if (item?.selectable === false) return; if (item?.selectable === false) return;
@ -35,6 +36,7 @@
listOpen = false; listOpen = false;
} }
function setMaxHeight() { function setMaxHeight() {
if (maxHeight) return;
maxHeight = window.innerHeight - element.getBoundingClientRect().bottom - maxPadding; maxHeight = window.innerHeight - element.getBoundingClientRect().bottom - maxPadding;
} }
@ -73,7 +75,7 @@
class="options card" class="options card"
style:display={listOpen ? undefined : 'none'} style:display={listOpen ? undefined : 'none'}
bind:this={options} bind:this={options}
style:max-height={`${maxHeight}px`} style:max-height={maxHeight && pxToRem(maxHeight)}
use:clickOutside={{ use:clickOutside={{
trigger: element, trigger: element,
handler: () => (listOpen = !listOpen), handler: () => (listOpen = !listOpen),

View File

@ -1,11 +1,12 @@
<script lang="ts"> <script lang="ts">
import Spacer from '../Spacer.svelte'; import Spacer from '../Spacer.svelte';
export let spacer = false; export let spacer = false;
export let gap = 'var(--size-20)';
const SLOTS = $$props.$$slots; const SLOTS = $$props.$$slots;
</script> </script>
<div class="settings-section"> <div class="settings-section" style="gap: {gap}">
{#if SLOTS.top} {#if SLOTS.top}
<slot name="top" /> <slot name="top" />
{/if} {/if}
@ -36,7 +37,6 @@
.settings-section { .settings-section {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--size-20);
} }
.description { .description {