Skip to content

Commit 0e9cbba

Browse files
authored
"Lanes & branches" new global settings domain (#11313)
* feat(settings): add Lanes & Branches settings panel and feature flag * feat(ui): auto-select branch name inputs if the setting is on * feat(settings): link branch placement to Lanes & Branches settings and update styles * fix(ui): remove unused selectall prop from Textbox stories * fix(textbox,modals): refactoring and simplification
1 parent b2a3bae commit 0e9cbba

File tree

11 files changed

+165
-53
lines changed

11 files changed

+165
-53
lines changed

apps/desktop/src/components/BranchNameTextbox.svelte

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,15 @@
99
[key: string]: any;
1010
};
1111
12-
let { value = $bindable(), helperText, onslugifiedvalue, ...restProps }: Props = $props();
12+
let {
13+
value = $bindable(),
14+
helperText,
15+
16+
onslugifiedvalue,
17+
...restProps
18+
}: Props = $props();
19+
20+
let textbox = $state<ReturnType<typeof Textbox>>();
1321
1422
const slugifiedName = $derived(value && slugify(value));
1523
const namesDiverge = $derived(!!value && slugifiedName !== value);
@@ -20,6 +28,10 @@
2028
$effect(() => {
2129
onslugifiedvalue?.(slugifiedName);
2230
});
31+
32+
export async function selectAll() {
33+
await textbox?.selectAll();
34+
}
2335
</script>
2436

25-
<Textbox bind:value helperText={computedHelperText} {...restProps} />
37+
<Textbox bind:this={textbox} bind:value helperText={computedHelperText} {...restProps} />

apps/desktop/src/components/BranchRenameModal.svelte

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,13 @@
2323
let slugifiedRefName: string | undefined = $state();
2424
let modal: Modal | undefined = $state();
2525
26-
export function show() {
26+
let branchNameInput = $state<ReturnType<typeof BranchNameTextbox>>();
27+
28+
export async function show() {
2729
newName = branchName;
2830
modal?.show();
31+
// Select text after async value is set
32+
await branchNameInput?.selectAll();
2933
}
3034
</script>
3135

@@ -43,6 +47,7 @@
4347
}}
4448
>
4549
<BranchNameTextbox
50+
bind:this={branchNameInput}
4651
placeholder="New name"
4752
id={ElementId.NewBranchNameInput}
4853
bind:value={newName}

apps/desktop/src/components/ChangedFilesContextMenu.svelte

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import { CLIPBOARD_SERVICE } from '$lib/backend/clipboard';
99
import { changesToDiffSpec } from '$lib/commits/utils';
1010
import { projectAiExperimentalFeaturesEnabled, projectAiGenEnabled } from '$lib/config/config';
11+
import { autoSelectBranchCreationFeature } from '$lib/config/uiFeatureFlags';
1112
import { FILE_SERVICE } from '$lib/files/fileService';
1213
import { isTreeChange, type TreeChange } from '$lib/hunks/change';
1314
import { vscodePath } from '$lib/project/project';
@@ -19,7 +20,6 @@
1920
import { computeChangeStatus } from '$lib/utils/fileStatus';
2021
import { getEditorUri, URL_SERVICE } from '$lib/utils/url';
2122
import { inject } from '@gitbutler/core/context';
22-
2323
import {
2424
AsyncButton,
2525
Button,
@@ -31,6 +31,7 @@
3131
Modal,
3232
chipToasts
3333
} from '@gitbutler/ui';
34+
3435
import type { SelectionId } from '$lib/selection/key';
3536
3637
type Props = {
@@ -147,6 +148,7 @@
147148
148149
let stashBranchName = $state<string>();
149150
let slugifiedRefName: string | undefined = $state();
151+
let stashBranchNameInput = $state<ReturnType<typeof BranchNameTextbox>>();
150152
151153
async function confirmStashIntoBranch(item: ChangedFilesItem, branchName: string | undefined) {
152154
if (!branchName) {
@@ -350,11 +352,13 @@
350352
<ContextMenuItem
351353
label="Stash into branch…"
352354
icon="stash"
353-
onclick={() => {
354-
stackService.fetchNewBranchName(projectId).then((name) => {
355-
stashBranchName = name || '';
356-
});
355+
onclick={async () => {
357356
stashConfirmationModal?.show(item);
357+
stashBranchName = await stackService.fetchNewBranchName(projectId);
358+
// Select text after async value is loaded and DOM is updated
359+
if ($autoSelectBranchCreationFeature) {
360+
await stashBranchNameInput?.selectAll();
361+
}
358362
contextMenu.close();
359363
}}
360364
/>
@@ -592,6 +596,7 @@
592596
{#snippet children(item)}
593597
<div class="content-wrap">
594598
<BranchNameTextbox
599+
bind:this={stashBranchNameInput}
595600
id="stashBranchName"
596601
placeholder="Enter your branch name..."
597602
bind:value={stashBranchName}

apps/desktop/src/components/CreateBranchModal.svelte

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import dependentBranchSvg from '$components/stackTabs/assets/dependent-branch.svg?raw';
44
import newStackLefttSvg from '$components/stackTabs/assets/new-stack-left.svg?raw';
55
import newStackRightSvg from '$components/stackTabs/assets/new-stack-right.svg?raw';
6+
import { autoSelectBranchCreationFeature } from '$lib/config/uiFeatureFlags';
7+
import { useSettingsModal } from '$lib/settings/settingsModal.svelte';
68
import { STACK_SERVICE } from '$lib/stacks/stackService.svelte';
79
import { inject } from '@gitbutler/core/context';
810
import { persisted } from '@gitbutler/shared/persisted';
@@ -16,8 +18,7 @@
1618
RadioButton,
1719
Select,
1820
SelectItem,
19-
TestId,
20-
Toggle
21+
TestId
2122
} from '@gitbutler/ui';
2223
import { isDefined } from '@gitbutler/ui/utils/typeguards';
2324
@@ -30,11 +31,13 @@
3031
const stackService = inject(STACK_SERVICE);
3132
const [createNewStack, stackCreation] = stackService.newStack;
3233
const [createNewBranch, branchCreation] = stackService.newBranch;
34+
const { openGeneralSettings } = useSettingsModal();
3335
3436
let createRefModal = $state<ReturnType<typeof Modal>>();
3537
let createRefName = $state<string>();
3638
let createRefType = $state<'stack' | 'dependent'>('stack');
3739
let selectedStackId = $state<string>();
40+
let branchNameInput = $state<ReturnType<typeof BranchNameTextbox>>();
3841
3942
// Persisted preference for branch placement
4043
const addToLeftmost = persisted<boolean>(false, 'branch-placement-leftmost');
@@ -118,6 +121,11 @@
118121
export async function show(initialType?: 'stack' | 'dependent') {
119122
createRefModal?.show();
120123
createRefName = await stackService.fetchNewBranchName(projectId);
124+
125+
// Select text after async value is loaded and DOM is updated
126+
if ($autoSelectBranchCreationFeature) {
127+
await branchNameInput?.selectAll();
128+
}
121129
// Reset selected stack to default
122130
selectedStackId = undefined;
123131
// Set branch type - default to 'stack' unless explicitly provided
@@ -132,9 +140,10 @@
132140
<Modal bind:this={createRefModal} width={500} testId={TestId.CreateNewBranchModal}>
133141
<div class="content-wrap">
134142
<BranchNameTextbox
143+
bind:this={branchNameInput}
135144
label="New branch"
136145
id={ElementId.NewBranchNameInput}
137-
bind:value={createRefName}
146+
value={createRefName}
138147
autofocus
139148
onslugifiedvalue={(value) => (slugifiedRefName = value)}
140149
/>
@@ -201,16 +210,21 @@
201210
</div>
202211

203212
{#if createRefType === 'stack'}
204-
<label for="add-leftmost" class="placement-toggle">
205-
<div class="flex items-center gap-8">
206-
<p class="text-13 text-semibold full-width">Place new branch on the left side</p>
207-
<Toggle id="add-leftmost" small bind:checked={$addToLeftmost} />
208-
</div>
209-
210-
<p class="text-12 text-body clr-text-3">
211-
By default, new branches are added to the rightmost position.
213+
<div class="settings-link-container">
214+
<p class="text-12 text-body clr-text-2">
215+
Configure branch placement and other preferences in
216+
<button
217+
type="button"
218+
class="settings-link underline-dotted"
219+
onclick={() => {
220+
createRefModal?.close();
221+
openGeneralSettings('lanes-and-branches');
222+
}}
223+
>
224+
Settings → Lanes & Branches
225+
</button>
212226
</p>
213-
</label>
227+
</div>
214228
{/if}
215229

216230
{#if createRefType === 'dependent'}
@@ -286,15 +300,26 @@
286300
gap: 8px;
287301
}
288302
289-
.placement-toggle {
303+
.settings-link-container {
290304
display: flex;
291-
flex-direction: column;
292305
padding: 12px;
293-
gap: 5px;
294306
border: 1px solid var(--clr-border-2);
295307
border-radius: var(--radius-m);
296308
}
297309
310+
.settings-link {
311+
padding: 0;
312+
border: none;
313+
background: none;
314+
color: var(--clr-link);
315+
font: inherit;
316+
cursor: pointer;
317+
318+
&:hover {
319+
color: var(--clr-link-hover);
320+
}
321+
}
322+
298323
.radio-label {
299324
/* variables */
300325
--btn-bg: var(--clr-btn-ntrl-outline-bg);

apps/desktop/src/components/GeneralSettingsModalContent.svelte

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import GeneralSettings from '$components/profileSettings/GeneralSettings.svelte';
77
import GitSettings from '$components/profileSettings/GitSettings.svelte';
88
import IntegrationsSettings from '$components/profileSettings/IntegrationsSettings.svelte';
9+
import LanesAndBranchesSettings from '$components/profileSettings/LanesAndBranchesSettings.svelte';
910
import OrganisationSettings from '$components/profileSettings/OrganisationSettings.svelte';
1011
import TelemetrySettings from '$components/profileSettings/TelemetrySettings.svelte';
1112
import AppearanceSettings from '$components/projectSettings/AppearanceSettings.svelte';
@@ -37,6 +38,11 @@
3738
label: 'Appearance',
3839
icon: 'appearance' as keyof typeof iconsJson
3940
},
41+
{
42+
id: 'lanes-and-branches',
43+
label: 'Lanes & branches',
44+
icon: 'lanes' as keyof typeof iconsJson
45+
},
4046
{
4147
id: 'git',
4248
label: 'Git stuff',
@@ -90,6 +96,8 @@
9096
<GeneralSettings />
9197
{:else if currentPage.id === 'appearance'}
9298
<AppearanceSettings />
99+
{:else if currentPage.id === 'lanes-and-branches'}
100+
<LanesAndBranchesSettings />
93101
{:else if currentPage.id === 'git'}
94102
<GitSettings />
95103
{:else if currentPage.id === 'integrations'}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<script lang="ts">
2+
import {
3+
autoSelectBranchNameFeature,
4+
autoSelectBranchCreationFeature
5+
} from '$lib/config/uiFeatureFlags';
6+
import { persisted } from '@gitbutler/shared/persisted';
7+
import { SectionCard, Toggle } from '@gitbutler/ui';
8+
9+
const addToLeftmost = persisted<boolean>(false, 'branch-placement-leftmost');
10+
</script>
11+
12+
<SectionCard labelFor="add-leftmost" orientation="row">
13+
{#snippet title()}
14+
Place new lanes on the left side
15+
{/snippet}
16+
{#snippet caption()}
17+
By default, new lanes are added to the rightmost position. Enable this to add them to the
18+
leftmost position instead.
19+
{/snippet}
20+
{#snippet actions()}
21+
<Toggle
22+
id="add-leftmost"
23+
checked={$addToLeftmost}
24+
onclick={() => ($addToLeftmost = !$addToLeftmost)}
25+
/>
26+
{/snippet}
27+
</SectionCard>
28+
29+
<div class="stack-v">
30+
<SectionCard labelFor="auto-select-creation" roundedBottom={false} orientation="row">
31+
{#snippet title()}
32+
Auto-select text on branch creation
33+
{/snippet}
34+
{#snippet caption()}
35+
Automatically select the pre-populated text in the branch name field when creating a new
36+
branch, making it easier to type your own name.
37+
{/snippet}
38+
{#snippet actions()}
39+
<Toggle
40+
id="auto-select-creation"
41+
checked={$autoSelectBranchCreationFeature}
42+
onclick={() => ($autoSelectBranchCreationFeature = !$autoSelectBranchCreationFeature)}
43+
/>
44+
{/snippet}
45+
</SectionCard>
46+
47+
<SectionCard labelFor="auto-select-rename" roundedTop={false} orientation="row">
48+
{#snippet title()}
49+
Auto-select text on branch rename
50+
{/snippet}
51+
{#snippet caption()}
52+
Automatically select the text when renaming a branch or lane, making it easier to replace the
53+
entire name.
54+
{/snippet}
55+
{#snippet actions()}
56+
<Toggle
57+
id="auto-select-rename"
58+
checked={$autoSelectBranchNameFeature}
59+
onclick={() => ($autoSelectBranchNameFeature = !$autoSelectBranchNameFeature)}
60+
/>
61+
{/snippet}
62+
</SectionCard>
63+
</div>

apps/desktop/src/components/projectSettings/AppearanceSettings.svelte

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
<script lang="ts">
22
import ThemeSelector from '$components/ThemeSelector.svelte';
3-
import {
4-
autoSelectBranchNameFeature,
5-
stagingBehaviorFeature,
6-
type StagingBehavior
7-
} from '$lib/config/uiFeatureFlags';
3+
import { stagingBehaviorFeature, type StagingBehavior } from '$lib/config/uiFeatureFlags';
84
import { SETTINGS } from '$lib/settings/userSettings';
95
import { inject } from '@gitbutler/core/context';
106
import {
@@ -342,22 +338,6 @@
342338
</SectionCard>
343339
</form>
344340

345-
<SectionCard labelFor="branchLaneContents" orientation="row">
346-
{#snippet title()}
347-
Auto-select text on branch/lane rename
348-
{/snippet}
349-
{#snippet caption()}
350-
Enable this option to automatically select the text when the input is focused.
351-
{/snippet}
352-
{#snippet actions()}
353-
<Toggle
354-
id="branchLaneContents"
355-
checked={$autoSelectBranchNameFeature}
356-
onclick={() => ($autoSelectBranchNameFeature = !$autoSelectBranchNameFeature)}
357-
/>
358-
{/snippet}
359-
</SectionCard>
360-
361341
<form class="stack-v" onchange={(e) => onStagingBehaviorFormChange(e.currentTarget)}>
362342
<SectionCard roundedBottom={false} orientation="row" labelFor="stage-all">
363343
{#snippet title()}

apps/desktop/src/lib/config/uiFeatureFlags.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import { persisted, persistWithExpiration } from '@gitbutler/shared/persisted';
88

99
export const autoSelectBranchNameFeature = persisted(false, 'autoSelectBranchLaneContentsFeature');
10+
export const autoSelectBranchCreationFeature = persisted(false, 'autoSelectBranchCreationFeature');
1011
export const ircEnabled = persistWithExpiration(false, 'feature-irc', 1440 * 30);
1112
export const ircServer = persistWithExpiration('', 'feature-irc-server', 1440 * 30);
1213
export const rewrapCommitMessage = persistWithExpiration(true, 'rewrap-commit-msg', 1440 * 30);

0 commit comments

Comments
 (0)