PR template section: Persist the user selection

The user can choose whether to use a PR template and that will be persisted accross PRs.
- remove the PR template section from the Project settings
This commit is contained in:
estib 2024-10-21 11:37:00 +02:00
parent 41b9ed4ad7
commit eba7f9c388
4 changed files with 72 additions and 180 deletions

View File

@ -90,6 +90,7 @@
let isDraft = $state<boolean>($preferredPRAction === PRAction.CreateDraft);
let modal = $state<ReturnType<typeof Modal>>();
let templateSelector = $state<ReturnType<typeof PrTemplateSection>>();
let isEditing = $state<boolean>(true);
let isLoading = $state<boolean>(false);
let pullRequestTemplateBody = $state<string | undefined>(undefined);
@ -98,10 +99,10 @@
let aiDescriptionDirective = $state<string | undefined>(undefined);
let showAiBox = $state<boolean>(false);
let showPRTemplateSelect = $state<boolean>(false);
function handleToggleUseTemplate() {
showPRTemplateSelect = !showPRTemplateSelect;
async function handleToggleUseTemplate() {
if (!templateSelector) return;
const displaying = templateSelector.imports.showing;
await templateSelector.setUsePullRequestTemplate(!displaying);
}
const canUseAI = $derived.by(() => {
@ -350,8 +351,9 @@
<ToggleButton
icon="doc"
label="Use PR template"
checked={showPRTemplateSelect}
checked={!!templateSelector?.imports.showing}
onclick={handleToggleUseTemplate}
disabled={!templateSelector?.imports.hasTemplates}
/>
<ToggleButton
icon="ai-small"
@ -366,9 +368,7 @@
</div>
<!-- PR TEMPLATE SELECT -->
{#if showPRTemplateSelect}
<PrTemplateSection bind:pullRequestTemplateBody />
{/if}
<PrTemplateSection bind:this={templateSelector} bind:pullRequestTemplateBody />
<!-- DESCRIPTION FIELD -->
<div class="pr-description-field text-input">

View File

@ -1,6 +1,6 @@
<script lang="ts">
import { ForgeService } from '$lib/backend/forge';
import { Project } from '$lib/backend/projects';
import { ProjectService, ProjectsService } from '$lib/backend/projects';
import Select from '$lib/select/Select.svelte';
import SelectItem from '$lib/select/SelectItem.svelte';
import { getContext } from '@gitbutler/shared/context';
@ -11,29 +11,22 @@
let { pullRequestTemplateBody = $bindable() }: Props = $props();
const project = getContext(Project);
const projectsService = getContext(ProjectsService);
const projectService = getContext(ProjectService);
const forgeService = getContext(ForgeService);
let allAvailableTemplates = $state<{ label: string; value: string }[]>([]);
const defaultReviewTemplatePath = $derived(
project.git_host.reviewTemplatePath ?? allAvailableTemplates[0]?.value
);
let selectedReviewTemplatePath = $state<string | undefined>(undefined);
const actualReviewTemplatePath = $derived(
selectedReviewTemplatePath ?? defaultReviewTemplatePath
);
let useReviewTemplate = $state<boolean | undefined>(undefined);
const defaultUseReviewTemplate = $derived(!!project.git_host.reviewTemplatePath);
const actualUseReviewTemplate = $derived(useReviewTemplate ?? defaultUseReviewTemplate);
const projectStore = projectService.project;
const project = $derived($projectStore);
const reviewTemplatePath = $derived(project?.git_host.reviewTemplatePath);
const show = $derived(!!reviewTemplatePath);
// Fetch PR template content
$effect(() => {
console.log('defaultReviewTemplatePath', defaultReviewTemplatePath);
if (actualUseReviewTemplate && actualReviewTemplatePath) {
forgeService.getReviewTemplateContent(actualReviewTemplatePath).then((template) => {
if (!project) return;
if (reviewTemplatePath) {
forgeService.getReviewTemplateContent(reviewTemplatePath).then((template) => {
pullRequestTemplateBody = template;
});
}
@ -41,6 +34,7 @@
// Fetch available PR templates
$effect(() => {
if (!project) return;
forgeService.getAvailableReviewTemplates().then((availableTemplates) => {
if (availableTemplates) {
allAvailableTemplates = availableTemplates.map((availableTemplate) => {
@ -52,27 +46,61 @@
}
});
});
async function setPullRequestTemplatePath(value: string) {
if (!project) return;
project.git_host.reviewTemplatePath = value;
await projectsService.updateProject(project);
}
export async function setUsePullRequestTemplate(value: boolean) {
if (!project) return;
setTemplate: {
if (!value) {
project.git_host.reviewTemplatePath = undefined;
pullRequestTemplateBody = undefined;
break setTemplate;
}
if (allAvailableTemplates[0]) {
project.git_host.reviewTemplatePath = allAvailableTemplates[0].value;
break setTemplate;
}
}
await projectsService.updateProject(project);
}
export const imports = {
get showing() {
return show;
},
get hasTemplates() {
return allAvailableTemplates.length > 0;
}
};
</script>
<div class="pr-template__wrap">
<Select
value={actualReviewTemplatePath}
options={allAvailableTemplates.map(({ label, value }) => ({ label, value }))}
placeholder="No PR templates found ¯\_(ツ)_/¯"
flex="1"
searchable
disabled={allAvailableTemplates.length <= 1}
onselect={(value) => {
selectedReviewTemplatePath = value;
}}
>
{#snippet itemSnippet({ item, highlighted })}
<SelectItem selected={item.value === actualReviewTemplatePath} {highlighted}>
{item.label}
</SelectItem>
{/snippet}
</Select>
</div>
{#if show}
<div class="pr-template__wrap">
<Select
value={reviewTemplatePath}
options={allAvailableTemplates.map(({ label, value }) => ({ label, value }))}
placeholder="No PR templates found ¯\_(ツ)_/¯"
flex="1"
searchable
disabled={allAvailableTemplates.length <= 1}
onselect={setPullRequestTemplatePath}
>
{#snippet itemSnippet({ item, highlighted })}
<SelectItem selected={item.value === reviewTemplatePath} {highlighted}>
{item.label}
</SelectItem>
{/snippet}
</Select>
</div>
{/if}
<style lang="postcss">
.pr-template__wrap {

View File

@ -1,133 +0,0 @@
<script lang="ts">
import notFoundSvg from '$lib/assets/empty-state/not-found.svg?raw';
import { ForgeService } from '$lib/backend/forge';
import { ProjectService, ProjectsService } from '$lib/backend/projects';
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 Link from '$lib/shared/Link.svelte';
import Spacer from '$lib/shared/Spacer.svelte';
import Toggle from '$lib/shared/Toggle.svelte';
import { getContext } from '@gitbutler/shared/context';
import EmptyStatePlaceholder from '@gitbutler/ui/EmptyStatePlaceholder.svelte';
const projectsService = getContext(ProjectsService);
const projectService = getContext(ProjectService);
const forgeService = getContext(ForgeService);
const projectStore = projectService.project;
const project = $derived($projectStore);
const useTemplate = $derived(!!project?.git_host.reviewTemplatePath);
const selectedTemplate = $derived(project?.git_host.reviewTemplatePath);
let allAvailableTemplates = $state<{ label: string; value: string }[]>([]);
let isTemplatesAvailable = $state(false);
$effect(() => {
if (!project) {
return;
}
forgeService.getAvailableReviewTemplates().then((availableTemplates) => {
if (availableTemplates) {
allAvailableTemplates = availableTemplates.map((availableTemplate) => {
return {
label: availableTemplate,
value: availableTemplate
};
});
isTemplatesAvailable = allAvailableTemplates.length > 0;
}
});
});
async function setUsePullRequestTemplate(value: boolean) {
if (!project) return;
setTemplate: {
if (!value) {
project.git_host.reviewTemplatePath = undefined;
break setTemplate;
}
if (allAvailableTemplates[0]) {
project.git_host.reviewTemplatePath = allAvailableTemplates[0].value;
break setTemplate;
}
}
await projectsService.updateProject(project);
}
async function setPullRequestTemplatePath(value: string) {
if (!project) return;
project.git_host.reviewTemplatePath = value;
await projectsService.updateProject(project);
}
</script>
<Section>
<div class="stack-v">
<SectionCard
roundedBottom={!useTemplate}
orientation="row"
labelFor="use-pull-request-template-boolean"
>
<svelte:fragment slot="title">Pull request templates</svelte:fragment>
<svelte:fragment slot="actions">
<Toggle
id="use-pull-request-template-boolean"
checked={useTemplate}
disabled={!isTemplatesAvailable}
on:click={(e) => {
setUsePullRequestTemplate(
(e.target as MouseEvent['target'] & { checked: boolean }).checked
);
}}
/>
</svelte:fragment>
<svelte:fragment slot="caption">
If enabled, we will use the path below to set the initial body of any pull requested created
on this project through GitButler.
</svelte:fragment>
</SectionCard>
{#if useTemplate}
<SectionCard roundedTop={false} orientation="row">
{#if isTemplatesAvailable}
<Select
value={selectedTemplate}
options={allAvailableTemplates.map(({ label, value }) => ({ label, value }))}
label="Available Templates"
wide={true}
searchable
disabled={allAvailableTemplates.length === 0}
onselect={(value) => {
setPullRequestTemplatePath(value);
}}
>
{#snippet itemSnippet({ item, highlighted })}
<SelectItem selected={item.value === selectedTemplate} {highlighted}>
{item.label}
</SelectItem>
{/snippet}
</Select>
{:else}
<EmptyStatePlaceholder image={notFoundSvg} topBottomPadding={20}>
{#snippet caption()}
No templates found in the project
<span class="text-11">
<Link
href="https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository"
>How to create a template</Link
></span
>
{/snippet}
</EmptyStatePlaceholder>
{/if}
</SectionCard>
{/if}
</div>
</Section>
<Spacer />

View File

@ -13,7 +13,6 @@
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 PullRequestTemplateForm from '$lib/settings/userPreferences/PullRequestTemplateForm.svelte';
import RemoveProjectForm from '$lib/settings/userPreferences/RemoveProjectForm.svelte';
import Spacer from '$lib/shared/Spacer.svelte';
@ -47,8 +46,6 @@
{#if $baseBranchSwitching}
<BaseBranchSwitch />
{/if}
<PullRequestTemplateForm />
<RemoveProjectForm />
</Section>
</TabContent>