mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-10-06 00:47:09 +03:00
feat: use new Tabs components to organise Preferences page (#4906)
This commit is contained in:
parent
04b854c10c
commit
6476a1c754
@ -115,10 +115,12 @@
|
||||
|
||||
.section-card__title {
|
||||
color: var(--clr-scale-ntrl-0);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.section-card__text {
|
||||
color: var(--clr-text-2);
|
||||
user-select: none;
|
||||
|
||||
/* if empty hide the caption */
|
||||
&:empty {
|
||||
|
31
apps/desktop/src/lib/components/tabs/TabContent.svelte
Normal file
31
apps/desktop/src/lib/components/tabs/TabContent.svelte
Normal file
@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { type Snippet, getContext } from 'svelte';
|
||||
import type { TabContext } from './types';
|
||||
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const { children, value }: Props = $props();
|
||||
|
||||
const tabStore = getContext<TabContext>('tab');
|
||||
const selectedIndex = $derived(tabStore.selectedIndex);
|
||||
const isActive = $derived($selectedIndex === value);
|
||||
</script>
|
||||
|
||||
{#if isActive}
|
||||
<div data-value={value} class="tab-content">
|
||||
{@render children()}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.tab-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
justify-content: flex-start;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
</style>
|
21
apps/desktop/src/lib/components/tabs/TabList.svelte
Normal file
21
apps/desktop/src/lib/components/tabs/TabList.svelte
Normal file
@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { type Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
}
|
||||
|
||||
const { children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<ul class="tab-list">
|
||||
{@render children()}
|
||||
</ul>
|
||||
|
||||
<style>
|
||||
.tab-list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
border-radius: var(--radius-m);
|
||||
}
|
||||
</style>
|
37
apps/desktop/src/lib/components/tabs/TabTrigger.svelte
Normal file
37
apps/desktop/src/lib/components/tabs/TabTrigger.svelte
Normal file
@ -0,0 +1,37 @@
|
||||
<script lang="ts">
|
||||
import { type TabContext } from './types';
|
||||
import { getContext, type Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const { value, children, disabled }: Props = $props();
|
||||
|
||||
const tabStore = getContext<TabContext>('tab');
|
||||
const selectedIndex = $derived(tabStore.selectedIndex);
|
||||
const isActive = $derived($selectedIndex === value);
|
||||
|
||||
function setActive() {
|
||||
tabStore?.setSelected(value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<button
|
||||
role="tab"
|
||||
tabindex={isActive ? -1 : 0}
|
||||
aria-selected={isActive}
|
||||
id={value}
|
||||
{value}
|
||||
{disabled}
|
||||
onclick={setActive}
|
||||
class="segment-control-item"
|
||||
class:disabled
|
||||
class:active={isActive}
|
||||
>
|
||||
<span class="text-12 segment-control-item__label">
|
||||
{@render children()}
|
||||
</span>
|
||||
</button>
|
38
apps/desktop/src/lib/components/tabs/Tabs.svelte
Normal file
38
apps/desktop/src/lib/components/tabs/Tabs.svelte
Normal file
@ -0,0 +1,38 @@
|
||||
<script lang="ts">
|
||||
import { type TabContext } from './types';
|
||||
import { setContext } from 'svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
defaultSelected: string;
|
||||
}
|
||||
|
||||
const { children, defaultSelected }: Props = $props();
|
||||
|
||||
let selectedIndex = writable(defaultSelected);
|
||||
|
||||
const context: TabContext = {
|
||||
selectedIndex,
|
||||
setSelected: (i) => {
|
||||
selectedIndex.set(i);
|
||||
return selectedIndex;
|
||||
}
|
||||
};
|
||||
|
||||
setContext<TabContext>('tab', context);
|
||||
</script>
|
||||
|
||||
<section class="tab-wrapper">
|
||||
{@render children()}
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.tab-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
6
apps/desktop/src/lib/components/tabs/types.ts
Normal file
6
apps/desktop/src/lib/components/tabs/types.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import type { Writable } from 'svelte/store';
|
||||
|
||||
export interface TabContext {
|
||||
selectedIndex: Writable<string>;
|
||||
setSelected: (id: string) => Writable<string>;
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
import { pxToRem } from '@gitbutler/ui/utils/pxToRem';
|
||||
|
||||
export let spacer = false;
|
||||
export let gap = 20;
|
||||
export let gap = 16;
|
||||
</script>
|
||||
|
||||
<div class="settings-section" style="gap: {pxToRem(gap)}">
|
||||
|
@ -1,12 +1,12 @@
|
||||
<script lang="ts">
|
||||
import InfoMessage from '../shared/InfoMessage.svelte';
|
||||
import { Project } from '$lib/backend/projects';
|
||||
import { BaseBranch } from '$lib/baseBranch/baseBranch';
|
||||
import { getRemoteBranches } from '$lib/baseBranch/baseBranchService';
|
||||
import SectionCard from '$lib/components/SectionCard.svelte';
|
||||
import Select from '$lib/select/Select.svelte';
|
||||
import SelectItem from '$lib/select/SelectItem.svelte';
|
||||
import Section from '$lib/settings/Section.svelte';
|
||||
import InfoMessage from '$lib/shared/InfoMessage.svelte';
|
||||
import Spacer from '$lib/shared/Spacer.svelte';
|
||||
import { getContext, getContextStore } from '$lib/utils/context';
|
||||
import { BranchController } from '$lib/vbranches/branchController';
|
||||
import { VirtualBranchService } from '$lib/vbranches/virtualBranch';
|
||||
@ -57,76 +57,75 @@
|
||||
</InfoMessage>
|
||||
{:then remoteBranches}
|
||||
{#if remoteBranches.length > 0}
|
||||
<Section spacer>
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<Select
|
||||
value={selectedBranch.name}
|
||||
options={remoteBranches.map((b) => ({ label: b.name, value: b.name }))}
|
||||
onselect={(value) => {
|
||||
selectedBranch = { name: value };
|
||||
}}
|
||||
disabled={targetChangeDisabled}
|
||||
label="Current target branch"
|
||||
searchable
|
||||
>
|
||||
{#snippet itemSnippet({ item, highlighted })}
|
||||
<SelectItem selected={item.value === selectedBranch.name} {highlighted}>
|
||||
{item.label}
|
||||
</SelectItem>
|
||||
{/snippet}
|
||||
</Select>
|
||||
|
||||
{#if uniqueRemotes(remoteBranches).length > 1}
|
||||
<Select
|
||||
value={selectedBranch.name}
|
||||
options={remoteBranches.map((b) => ({ label: b.name, value: b.name }))}
|
||||
value={selectedRemote.name}
|
||||
options={uniqueRemotes(remoteBranches).map((r) => ({ label: r.name!, value: r.name!}))}
|
||||
onselect={(value) => {
|
||||
selectedBranch = { name: value };
|
||||
selectedRemote = { name: value };
|
||||
}}
|
||||
disabled={targetChangeDisabled}
|
||||
label="Current target branch"
|
||||
searchable
|
||||
label="Create branches on remote"
|
||||
>
|
||||
{#snippet itemSnippet({ item, highlighted })}
|
||||
<SelectItem selected={item.value === selectedBranch.name} {highlighted}>
|
||||
<SelectItem selected={item.value === selectedRemote.name} {highlighted}>
|
||||
{item.label}
|
||||
</SelectItem>
|
||||
{/snippet}
|
||||
</Select>
|
||||
{/if}
|
||||
|
||||
{#if uniqueRemotes(remoteBranches).length > 1}
|
||||
<Select
|
||||
value={selectedRemote.name}
|
||||
options={uniqueRemotes(remoteBranches).map((r) => ({ label: r.name!, value: r.name!}))}
|
||||
onselect={(value) => {
|
||||
selectedRemote = { name: value };
|
||||
}}
|
||||
disabled={targetChangeDisabled}
|
||||
label="Create branches on remote"
|
||||
>
|
||||
{#snippet itemSnippet({ item, highlighted })}
|
||||
<SelectItem selected={item.value === selectedRemote.name} {highlighted}>
|
||||
{item.label}
|
||||
</SelectItem>
|
||||
{/snippet}
|
||||
</Select>
|
||||
{/if}
|
||||
|
||||
{#if $activeBranches && targetChangeDisabled}
|
||||
<InfoMessage filled outlined={false} icon="info">
|
||||
<svelte:fragment slot="content">
|
||||
You have {$activeBranches.length === 1
|
||||
? '1 active branch'
|
||||
: `${$activeBranches.length} active branches`} in your workspace. Please clear the workspace
|
||||
before switching the base branch.
|
||||
</svelte:fragment>
|
||||
</InfoMessage>
|
||||
{:else}
|
||||
<Button
|
||||
size="cta"
|
||||
style="ghost"
|
||||
outline
|
||||
onclick={onSetBaseBranchClick}
|
||||
id="set-base-branch"
|
||||
loading={isSwitching}
|
||||
disabled={(selectedBranch.name === $baseBranch.branchName &&
|
||||
selectedRemote.name === $baseBranch.actualPushRemoteName()) ||
|
||||
targetChangeDisabled}
|
||||
>
|
||||
{isSwitching ? 'Switching branches...' : 'Update configuration'}
|
||||
</Button>
|
||||
{/if}
|
||||
</SectionCard>
|
||||
</Section>
|
||||
{#if $activeBranches && targetChangeDisabled}
|
||||
<InfoMessage filled outlined={false} icon="info">
|
||||
<svelte:fragment slot="content">
|
||||
You have {$activeBranches.length === 1
|
||||
? '1 active branch'
|
||||
: `${$activeBranches.length} active branches`} in your workspace. Please clear the workspace
|
||||
before switching the base branch.
|
||||
</svelte:fragment>
|
||||
</InfoMessage>
|
||||
{:else}
|
||||
<Button
|
||||
size="cta"
|
||||
style="ghost"
|
||||
outline
|
||||
onclick={onSetBaseBranchClick}
|
||||
id="set-base-branch"
|
||||
loading={isSwitching}
|
||||
disabled={(selectedBranch.name === $baseBranch.branchName &&
|
||||
selectedRemote.name === $baseBranch.actualPushRemoteName()) ||
|
||||
targetChangeDisabled}
|
||||
>
|
||||
{isSwitching ? 'Switching branches...' : 'Update configuration'}
|
||||
</Button>
|
||||
{/if}
|
||||
</SectionCard>
|
||||
<Spacer />
|
||||
{/if}
|
||||
{:catch}
|
||||
<InfoMessage filled outlined={true} style="error" icon="error">
|
@ -72,19 +72,20 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if !$user}
|
||||
<WelcomeSigninAction />
|
||||
<Spacer />
|
||||
{/if}
|
||||
|
||||
<Section spacer>
|
||||
<svelte:fragment slot="title">AI options</svelte:fragment>
|
||||
<Section>
|
||||
<svelte:fragment slot="description">
|
||||
GitButler supports the use of OpenAI and Anthropic to provide commit message and branch name
|
||||
generation. This works either through GitButler's API or in a bring your own key configuration
|
||||
and can be configured in the main preferences screen.
|
||||
</svelte:fragment>
|
||||
|
||||
<Spacer />
|
||||
|
||||
{#if !$user}
|
||||
<WelcomeSigninAction />
|
||||
<Spacer />
|
||||
{/if}
|
||||
|
||||
<div class="options">
|
||||
<SectionCard labelFor="aiGenEnabled" orientation="row">
|
||||
<svelte:fragment slot="title">Enable branch and commit message generation</svelte:fragment>
|
||||
@ -123,7 +124,7 @@
|
||||
</Section>
|
||||
|
||||
{#if $user?.role === 'admin'}
|
||||
<Section spacer>
|
||||
<Section>
|
||||
<svelte:fragment slot="title">Full data synchronization</svelte:fragment>
|
||||
|
||||
<SectionCard labelFor="historySync" orientation="row">
|
@ -3,7 +3,6 @@
|
||||
import { Project, ProjectService } from '$lib/backend/projects';
|
||||
import SectionCard from '$lib/components/SectionCard.svelte';
|
||||
import SectionCardDisclaimer from '$lib/components/SectionCardDisclaimer.svelte';
|
||||
import { projectRunCommitHooks } from '$lib/config/config';
|
||||
import Select from '$lib/select/Select.svelte';
|
||||
import SelectItem from '$lib/select/SelectItem.svelte';
|
||||
import Section from '$lib/settings/Section.svelte';
|
||||
@ -19,29 +18,10 @@
|
||||
const projectService = getContext(ProjectService);
|
||||
const project = getContext(Project);
|
||||
|
||||
let snaphotLinesThreshold = project?.snapshot_lines_threshold || 20; // when undefined, the default is 20
|
||||
let allowForcePushing = project?.ok_with_force_push;
|
||||
let omitCertificateCheck = project?.omit_certificate_check;
|
||||
let useNewLocking = project?.use_new_locking || false;
|
||||
let signCommits = false;
|
||||
|
||||
const gitConfig = getContext(GitConfigService);
|
||||
const runCommitHooks = projectRunCommitHooks(project.id);
|
||||
|
||||
async function setWithForcePush(value: boolean) {
|
||||
project.ok_with_force_push = value;
|
||||
await projectService.updateProject(project);
|
||||
}
|
||||
|
||||
async function setOmitCertificateCheck(value: boolean | undefined) {
|
||||
project.omit_certificate_check = !!value;
|
||||
await projectService.updateProject(project);
|
||||
}
|
||||
|
||||
async function setSnapshotLinesThreshold(value: number) {
|
||||
project.snapshot_lines_threshold = value;
|
||||
await projectService.updateProject(project);
|
||||
}
|
||||
|
||||
async function setSignCommits(targetState: boolean) {
|
||||
signCommits = targetState;
|
||||
@ -125,24 +105,14 @@
|
||||
async function handleSignCommitsClick(event: MouseEvent) {
|
||||
await setSignCommits((event.target as HTMLInputElement)?.checked);
|
||||
}
|
||||
|
||||
async function handleAllowForcePushClick(event: MouseEvent) {
|
||||
await setWithForcePush((event.target as HTMLInputElement)?.checked);
|
||||
}
|
||||
|
||||
async function handleOmitCertificateCheckClick(event: MouseEvent) {
|
||||
await setOmitCertificateCheck((event.target as HTMLInputElement)?.checked);
|
||||
}
|
||||
</script>
|
||||
|
||||
<Section spacer>
|
||||
<svelte:fragment slot="title">Commit signing</svelte:fragment>
|
||||
<svelte:fragment slot="description">
|
||||
Use GPG or SSH to sign your commits so they can be verified as authentic.
|
||||
</svelte:fragment>
|
||||
<Section>
|
||||
<SectionCard orientation="row" labelFor="signCommits">
|
||||
<svelte:fragment slot="title">Sign commits</svelte:fragment>
|
||||
<svelte:fragment slot="caption">
|
||||
Use GPG or SSH to sign your commits so they can be verified as authentic.
|
||||
<br />
|
||||
GitButler will sign commits as per your git configuration.
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
@ -155,7 +125,7 @@
|
||||
value={signingFormat}
|
||||
options={signingFormatOptions}
|
||||
label="Signing format"
|
||||
onselect={(value) => {
|
||||
onselect={(value: string) => {
|
||||
signingFormat = value;
|
||||
updateSigningInfo();
|
||||
}}
|
||||
@ -218,95 +188,3 @@
|
||||
</SectionCard>
|
||||
{/if}
|
||||
</Section>
|
||||
|
||||
<Section spacer>
|
||||
<svelte:fragment slot="title">Preferences</svelte:fragment>
|
||||
<svelte:fragment slot="description">
|
||||
Other settings to customize your GitButler experience.
|
||||
</svelte:fragment>
|
||||
|
||||
<SectionCard orientation="row" labelFor="allowForcePush">
|
||||
<svelte:fragment slot="title">Allow force pushing</svelte:fragment>
|
||||
<svelte:fragment slot="caption">
|
||||
Force pushing allows GitButler to override branches even if they were pushed to remote.
|
||||
GitButler will never force push to the target branch.
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Toggle
|
||||
id="allowForcePush"
|
||||
checked={allowForcePushing}
|
||||
on:click={handleAllowForcePushClick}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard orientation="row" labelFor="omitCertificateCheck">
|
||||
<svelte:fragment slot="title">Ignore host certificate checks</svelte:fragment>
|
||||
<svelte:fragment slot="caption">
|
||||
Enabling this will ignore host certificate checks when authenticating with ssh.
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Toggle
|
||||
id="omitCertificateCheck"
|
||||
checked={omitCertificateCheck}
|
||||
on:click={handleOmitCertificateCheckClick}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard labelFor="runHooks" orientation="row">
|
||||
<svelte:fragment slot="title">Run commit hooks</svelte:fragment>
|
||||
<svelte:fragment slot="caption">
|
||||
Enabling this will run any git pre and post commit hooks you have configured in your
|
||||
repository.
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Toggle id="runHooks" bind:checked={$runCommitHooks} />
|
||||
</svelte:fragment>
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard orientation="row" centerAlign>
|
||||
<svelte:fragment slot="title">Snapshot lines threshold</svelte:fragment>
|
||||
<svelte:fragment slot="caption">
|
||||
The number of lines that trigger a snapshot when saving.
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<TextBox
|
||||
type="number"
|
||||
width={100}
|
||||
textAlign="center"
|
||||
value={snaphotLinesThreshold?.toString()}
|
||||
minVal={5}
|
||||
maxVal={1000}
|
||||
showCountActions
|
||||
on:change={(e) => {
|
||||
setSnapshotLinesThreshold(parseInt(e.detail));
|
||||
}}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard labelFor="useNewLocking" orientation="row">
|
||||
<svelte:fragment slot="title">Use new experimental hunk locking algorithm</svelte:fragment>
|
||||
<svelte:fragment slot="caption">
|
||||
This new hunk locking algorithm is still in the testing phase but should more accurately catch
|
||||
locks and subsequently cause fewer errors.
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Toggle id="useNewLocking" bind:checked={useNewLocking} />
|
||||
</svelte:fragment>
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard labelFor="succeedingRebases" orientation="row">
|
||||
<svelte:fragment slot="title">Edit mode and succeeding rebases</svelte:fragment>
|
||||
<svelte:fragment slot="caption">
|
||||
This is an experimental setting which will ensure that rebasing will always succeed,
|
||||
introduces a mode for editing individual commits, and adds the ability to resolve conflicted
|
||||
commits.
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Toggle id="succeedingRebases" bind:checked={succeedingRebases} />
|
||||
</svelte:fragment>
|
||||
</SectionCard>
|
||||
</Section>
|
@ -0,0 +1,143 @@
|
||||
<script lang="ts">
|
||||
import { Project, ProjectService } from '$lib/backend/projects';
|
||||
import SectionCard from '$lib/components/SectionCard.svelte';
|
||||
import { projectRunCommitHooks } from '$lib/config/config';
|
||||
import Section from '$lib/settings/Section.svelte';
|
||||
import TextBox from '$lib/shared/TextBox.svelte';
|
||||
import Toggle from '$lib/shared/Toggle.svelte';
|
||||
import { getContext } from '$lib/utils/context';
|
||||
|
||||
const projectService = getContext(ProjectService);
|
||||
const project = getContext(Project);
|
||||
|
||||
let snaphotLinesThreshold = project?.snapshot_lines_threshold || 20; // when undefined, the default is 20
|
||||
let allowForcePushing = project?.ok_with_force_push;
|
||||
let omitCertificateCheck = project?.omit_certificate_check;
|
||||
let useNewLocking = project?.use_new_locking || false;
|
||||
|
||||
const runCommitHooks = projectRunCommitHooks(project.id);
|
||||
|
||||
async function setWithForcePush(value: boolean) {
|
||||
project.ok_with_force_push = value;
|
||||
await projectService.updateProject(project);
|
||||
}
|
||||
|
||||
async function setOmitCertificateCheck(value: boolean | undefined) {
|
||||
project.omit_certificate_check = !!value;
|
||||
await projectService.updateProject(project);
|
||||
}
|
||||
|
||||
async function setSnapshotLinesThreshold(value: number) {
|
||||
project.snapshot_lines_threshold = value;
|
||||
await projectService.updateProject(project);
|
||||
}
|
||||
|
||||
let succeedingRebases = project.succeedingRebases;
|
||||
|
||||
$: {
|
||||
project.succeedingRebases = succeedingRebases;
|
||||
projectService.updateProject(project);
|
||||
}
|
||||
|
||||
async function setUseNewLocking(value: boolean) {
|
||||
project.use_new_locking = value;
|
||||
await projectService.updateProject(project);
|
||||
}
|
||||
|
||||
$: setUseNewLocking(useNewLocking);
|
||||
|
||||
async function handleAllowForcePushClick(event: MouseEvent) {
|
||||
await setWithForcePush((event.target as HTMLInputElement)?.checked);
|
||||
}
|
||||
|
||||
async function handleOmitCertificateCheckClick(event: MouseEvent) {
|
||||
await setOmitCertificateCheck((event.target as HTMLInputElement)?.checked);
|
||||
}
|
||||
</script>
|
||||
|
||||
<Section gap={8}>
|
||||
<SectionCard orientation="row" labelFor="allowForcePush">
|
||||
<svelte:fragment slot="title">Allow force pushing</svelte:fragment>
|
||||
<svelte:fragment slot="caption">
|
||||
Force pushing allows GitButler to override branches even if they were pushed to remote.
|
||||
GitButler will never force push to the target branch.
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Toggle
|
||||
id="allowForcePush"
|
||||
checked={allowForcePushing}
|
||||
on:click={handleAllowForcePushClick}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard orientation="row" labelFor="omitCertificateCheck">
|
||||
<svelte:fragment slot="title">Ignore host certificate checks</svelte:fragment>
|
||||
<svelte:fragment slot="caption">
|
||||
Enabling this will ignore host certificate checks when authenticating with ssh.
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Toggle
|
||||
id="omitCertificateCheck"
|
||||
checked={omitCertificateCheck}
|
||||
on:click={handleOmitCertificateCheckClick}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard labelFor="runHooks" orientation="row">
|
||||
<svelte:fragment slot="title">Run commit hooks</svelte:fragment>
|
||||
<svelte:fragment slot="caption">
|
||||
Enabling this will run any git pre and post commit hooks you have configured in your
|
||||
repository.
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Toggle id="runHooks" bind:checked={$runCommitHooks} />
|
||||
</svelte:fragment>
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard orientation="row" centerAlign>
|
||||
<svelte:fragment slot="title">Snapshot lines threshold</svelte:fragment>
|
||||
<svelte:fragment slot="caption">
|
||||
The number of lines that trigger a snapshot when saving.
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<TextBox
|
||||
type="number"
|
||||
width={100}
|
||||
textAlign="center"
|
||||
value={snaphotLinesThreshold?.toString()}
|
||||
minVal={5}
|
||||
maxVal={1000}
|
||||
showCountActions
|
||||
on:change={(e) => {
|
||||
setSnapshotLinesThreshold(parseInt(e.detail));
|
||||
}}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard labelFor="useNewLocking" orientation="row">
|
||||
<svelte:fragment slot="title">Use new experimental hunk locking algorithm</svelte:fragment>
|
||||
<svelte:fragment slot="caption">
|
||||
This new hunk locking algorithm is still in the testing phase but should more accurately catch
|
||||
locks and subsequently cause fewer errors.
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Toggle id="useNewLocking" bind:checked={useNewLocking} />
|
||||
</svelte:fragment>
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard labelFor="succeedingRebases" orientation="row">
|
||||
<svelte:fragment slot="title">Edit mode and succeeding rebases</svelte:fragment>
|
||||
<svelte:fragment slot="caption">
|
||||
This is an experimental setting which will ensure that rebasing will always succeed,
|
||||
introduces a mode for editing individual commits, and adds the ability to resolve conflicted
|
||||
commits.
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Toggle id="succeedingRebases" bind:checked={succeedingRebases} />
|
||||
</svelte:fragment>
|
||||
</SectionCard>
|
||||
</Section>
|
@ -0,0 +1,40 @@
|
||||
<script lang="ts">
|
||||
import { Project, ProjectService } from '$lib/backend/projects';
|
||||
import RemoveProjectButton from '$lib/components/RemoveProjectButton.svelte';
|
||||
import SectionCard from '$lib/components/SectionCard.svelte';
|
||||
import { showError } from '$lib/notifications/toasts';
|
||||
import { getContext } from '$lib/utils/context';
|
||||
import * as toasts from '$lib/utils/toasts';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
const projectService = getContext(ProjectService);
|
||||
const project = getContext(Project);
|
||||
|
||||
let isDeleting = $state(false);
|
||||
|
||||
async function onDeleteClicked() {
|
||||
isDeleting = true;
|
||||
try {
|
||||
await projectService.deleteProject(project.id);
|
||||
await projectService.reload();
|
||||
goto('/');
|
||||
toasts.success('Project deleted');
|
||||
} catch (err: any) {
|
||||
console.error(err);
|
||||
showError('Failed to delete project', err);
|
||||
} finally {
|
||||
isDeleting = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<SectionCard>
|
||||
<svelte:fragment slot="title">Remove project</svelte:fragment>
|
||||
<svelte:fragment slot="caption">
|
||||
You can remove projects from GitButler, your code remains safe as this only clears
|
||||
configuration.
|
||||
</svelte:fragment>
|
||||
<div>
|
||||
<RemoveProjectButton projectTitle={project.title} {isDeleting} {onDeleteClicked} />
|
||||
</div>
|
||||
</SectionCard>
|
@ -7,7 +7,7 @@
|
||||
dotted?: boolean;
|
||||
}
|
||||
|
||||
const { margin = 16, noLine = false, dotted = false }: SpacerProps = $props();
|
||||
const { margin = 12, noLine = false, dotted = false }: SpacerProps = $props();
|
||||
|
||||
function getMargins() {
|
||||
if (margin === undefined) {
|
||||
@ -29,7 +29,7 @@
|
||||
.divider {
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
opacity: 0.2;
|
||||
opacity: 0.13;
|
||||
}
|
||||
|
||||
.divider.line {
|
||||
@ -37,6 +37,7 @@
|
||||
}
|
||||
|
||||
.divider.dotted {
|
||||
opacity: 0.2;
|
||||
background: repeating-linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
|
@ -193,6 +193,7 @@
|
||||
}
|
||||
|
||||
.textbox__icon {
|
||||
display: flex;
|
||||
z-index: var(--z-ground);
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
|
@ -1,74 +1,56 @@
|
||||
<script lang="ts">
|
||||
import { Project, ProjectService } from '$lib/backend/projects';
|
||||
import BaseBranchSwitch from '$lib/components/BaseBranchSwitch.svelte';
|
||||
import RemoveProjectButton from '$lib/components/RemoveProjectButton.svelte';
|
||||
import SectionCard from '$lib/components/SectionCard.svelte';
|
||||
import TabContent from '$lib/components/tabs/TabContent.svelte';
|
||||
import TabList from '$lib/components/tabs/TabList.svelte';
|
||||
import TabTrigger from '$lib/components/tabs/TabTrigger.svelte';
|
||||
import Tabs from '$lib/components/tabs/Tabs.svelte';
|
||||
import { featureBaseBranchSwitching } from '$lib/config/uiFeatureFlags';
|
||||
import { getGitHost } from '$lib/gitHost/interface/gitHost';
|
||||
import SettingsPage from '$lib/layout/SettingsPage.svelte';
|
||||
import { showError } from '$lib/notifications/toasts';
|
||||
import { platformName } from '$lib/platform/platform';
|
||||
import CloudForm from '$lib/settings/CloudForm.svelte';
|
||||
import DetailsForm from '$lib/settings/DetailsForm.svelte';
|
||||
import KeysForm from '$lib/settings/KeysForm.svelte';
|
||||
import PreferencesForm from '$lib/settings/PreferencesForm.svelte';
|
||||
import PullRequestTemplateForm from '$lib/settings/PullRequestTemplateForm.svelte';
|
||||
import Section from '$lib/settings/Section.svelte';
|
||||
import BaseBranchSwitch from '$lib/settings/userPreferences/BaseBranchSwitch.svelte';
|
||||
import CloudForm from '$lib/settings/userPreferences/CloudForm.svelte';
|
||||
import CommitSigningForm from '$lib/settings/userPreferences/CommitSigningForm.svelte';
|
||||
import DetailsForm from '$lib/settings/userPreferences/DetailsForm.svelte';
|
||||
import PreferencesForm from '$lib/settings/userPreferences/PreferencesForm.svelte';
|
||||
import RemoveProjectForm from '$lib/settings/userPreferences/RemoveProjectForm.svelte';
|
||||
import Spacer from '$lib/shared/Spacer.svelte';
|
||||
import { getContext } from '$lib/utils/context';
|
||||
import * as toasts from '$lib/utils/toasts';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
const baseBranchSwitching = featureBaseBranchSwitching();
|
||||
const projectService = getContext(ProjectService);
|
||||
const project = getContext(Project);
|
||||
const gitHost = getGitHost();
|
||||
|
||||
let deleteConfirmationModal: RemoveProjectButton;
|
||||
let isDeleting = $state(false);
|
||||
|
||||
async function onDeleteClicked() {
|
||||
isDeleting = true;
|
||||
try {
|
||||
await projectService.deleteProject(project.id);
|
||||
await projectService.reload();
|
||||
goto('/');
|
||||
toasts.success('Project deleted');
|
||||
} catch (err: any) {
|
||||
console.error(err);
|
||||
showError('Failed to delete project', err);
|
||||
} finally {
|
||||
isDeleting = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<SettingsPage title="Project settings">
|
||||
{#if $baseBranchSwitching}
|
||||
<BaseBranchSwitch />
|
||||
{/if}
|
||||
<CloudForm />
|
||||
<DetailsForm />
|
||||
{#if $gitHost}
|
||||
<PullRequestTemplateForm />
|
||||
{/if}
|
||||
{#if $platformName !== 'win32'}
|
||||
<KeysForm showProjectName={false} />
|
||||
<Spacer />
|
||||
{/if}
|
||||
<PreferencesForm />
|
||||
<SectionCard>
|
||||
<svelte:fragment slot="title">Remove project</svelte:fragment>
|
||||
<svelte:fragment slot="caption">
|
||||
You can remove projects from GitButler, your code remains safe as this only clears
|
||||
configuration.
|
||||
</svelte:fragment>
|
||||
<div>
|
||||
<RemoveProjectButton
|
||||
bind:this={deleteConfirmationModal}
|
||||
projectTitle={project.title}
|
||||
{isDeleting}
|
||||
{onDeleteClicked}
|
||||
/>
|
||||
</div>
|
||||
</SectionCard>
|
||||
<Tabs defaultSelected="project">
|
||||
<TabList>
|
||||
<TabTrigger value="project">Project</TabTrigger>
|
||||
<TabTrigger value="git">Git</TabTrigger>
|
||||
<TabTrigger value="ai">AI</TabTrigger>
|
||||
<TabTrigger value="feature-flags">Experimental</TabTrigger>
|
||||
</TabList>
|
||||
|
||||
<TabContent value="git">
|
||||
<Section>
|
||||
<CommitSigningForm />
|
||||
{#if $platformName !== 'win32'}
|
||||
<Spacer />
|
||||
<KeysForm showProjectName={false} />
|
||||
{/if}
|
||||
</Section>
|
||||
</TabContent>
|
||||
<TabContent value="ai">
|
||||
<CloudForm />
|
||||
</TabContent>
|
||||
<TabContent value="project">
|
||||
<Section>
|
||||
{#if $baseBranchSwitching}
|
||||
<BaseBranchSwitch />
|
||||
{/if}
|
||||
<DetailsForm />
|
||||
<RemoveProjectForm />
|
||||
</Section>
|
||||
</TabContent>
|
||||
<TabContent value="feature-flags">
|
||||
<PreferencesForm />
|
||||
</TabContent>
|
||||
</Tabs>
|
||||
</SettingsPage>
|
||||
|
@ -36,7 +36,7 @@
|
||||
<button
|
||||
bind:this={elRef}
|
||||
{id}
|
||||
class="segment"
|
||||
class="segment-control-item"
|
||||
role="tab"
|
||||
{disabled}
|
||||
tabindex={isSelected ? -1 : 0}
|
||||
@ -62,12 +62,12 @@
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span class="text-12 label">
|
||||
<span class="text-12 segment-control-item__label">
|
||||
{@render children()}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<style lang="postcss">
|
||||
<!-- <style lang="postcss">
|
||||
.segment {
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
@ -122,4 +122,4 @@
|
||||
white-space: nowrap;
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
</style>
|
||||
</style> -->
|
||||
|
54
packages/ui/src/styles/components/segment.css
Normal file
54
packages/ui/src/styles/components/segment.css
Normal file
@ -0,0 +1,54 @@
|
||||
.segment-control-item {
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
user-select: none;
|
||||
padding: 0 8px;
|
||||
gap: 4px;
|
||||
|
||||
border-top-width: 1px;
|
||||
border-bottom-width: 1px;
|
||||
border-left-width: 1px;
|
||||
|
||||
color: var(--clr-text-1);
|
||||
border-color: var(--clr-border-2);
|
||||
background-color: var(--clr-bg-1);
|
||||
height: var(--size-button);
|
||||
|
||||
transition: background var(--transition-fast);
|
||||
|
||||
&:first-of-type {
|
||||
border-top-left-radius: var(--radius-m);
|
||||
border-bottom-left-radius: var(--radius-m);
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
border-top-right-radius: var(--radius-m);
|
||||
border-bottom-right-radius: var(--radius-m);
|
||||
border-right-width: 1px;
|
||||
}
|
||||
|
||||
&:not([aria-selected='true']):hover {
|
||||
background-color: var(--clr-bg-1-muted);
|
||||
}
|
||||
|
||||
&[aria-selected='true'] {
|
||||
background-color: var(--clr-bg-2);
|
||||
color: var(--clr-text-2);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.segment-control-item__label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
transition: color var(--transition-fast);
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
@import './components/text-input.css';
|
||||
@import './components/commit-lines.css';
|
||||
@import './components/draggable.css';
|
||||
@import './components/segment.css';
|
||||
|
||||
/* LAYERS PRIORITY */
|
||||
@layer reset, sharable;
|
||||
|
Loading…
Reference in New Issue
Block a user