Refactor a couple of things for clarity

This commit is contained in:
Mattias Granlund 2024-03-14 14:29:06 +01:00
parent f2856ea899
commit 263ef78ed6
6 changed files with 99 additions and 183 deletions

View File

@ -2,7 +2,7 @@
import BranchFiles from './BranchFiles.svelte'; import BranchFiles from './BranchFiles.svelte';
import Tag from '$lib/components/Tag.svelte'; import Tag from '$lib/components/Tag.svelte';
import TimeAgo from '$lib/components/TimeAgo.svelte'; import TimeAgo from '$lib/components/TimeAgo.svelte';
import { projectCurrentCommitMessage } from '$lib/config/config'; import { persistedCommitMessage } from '$lib/config/config';
import { draggable } from '$lib/dragging/draggable'; import { draggable } from '$lib/dragging/draggable';
import { draggableCommit, nonDraggable } from '$lib/dragging/draggables'; import { draggableCommit, nonDraggable } from '$lib/dragging/draggables';
import { openExternalUrl } from '$lib/utils/url'; import { openExternalUrl } from '$lib/utils/url';
@ -34,7 +34,7 @@
export let branchId: string | undefined = undefined; export let branchId: string | undefined = undefined;
const selectedOwnership = writable(Ownership.default()); const selectedOwnership = writable(Ownership.default());
const currentCommitMessage = projectCurrentCommitMessage(projectId, branchId || ''); const currentCommitMessage = persistedCommitMessage(projectId, branchId || '');
let showFiles = false; let showFiles = false;

View File

@ -13,14 +13,13 @@
projectCommitGenerationExtraConcise, projectCommitGenerationExtraConcise,
projectCommitGenerationUseEmojis, projectCommitGenerationUseEmojis,
projectRunCommitHooks, projectRunCommitHooks,
projectCurrentCommitMessage persistedCommitMessage
} from '$lib/config/config'; } from '$lib/config/config';
import { persisted } from '$lib/persisted/persisted'; import { persisted } from '$lib/persisted/persisted';
import * as toasts from '$lib/utils/toasts'; import * as toasts from '$lib/utils/toasts';
import { tooltip } from '$lib/utils/tooltip'; import { tooltip } from '$lib/utils/tooltip';
import { setAutoHeight, useAutoHeight } from '$lib/utils/useAutoHeight'; import { setAutoHeight } from '$lib/utils/useAutoHeight';
import { useResize } from '$lib/utils/useResize'; import { useResize } from '$lib/utils/useResize';
import { invoke } from '@tauri-apps/api/tauri';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import { quintOut } from 'svelte/easing'; import { quintOut } from 'svelte/easing';
import { fly, slide } from 'svelte/transition'; import { fly, slide } from 'svelte/transition';
@ -44,68 +43,62 @@
const aiGenEnabled = projectAiGenEnabled(projectId); const aiGenEnabled = projectAiGenEnabled(projectId);
const runCommitHooks = projectRunCommitHooks(projectId); const runCommitHooks = projectRunCommitHooks(projectId);
const currentCommitMessage = projectCurrentCommitMessage(projectId, branch.id); const commitMessage = persistedCommitMessage(projectId, branch.id);
const commitGenerationExtraConcise = projectCommitGenerationExtraConcise(projectId);
const commitGenerationUseEmojis = projectCommitGenerationUseEmojis(projectId);
function getCommitMessageTitleAndDescription(commitMessage: string) {
// Split the commit message into title and description
// get the first line as title and the rest as description
const [summary, description] = commitMessage.trim().split(/\n+(.*)/s);
return {
summary: summary || '',
description: description || ''
};
}
function concatCommitMessage(message: { summary: string; description: string }) {
return `${message.summary}\n${message.description}`;
}
let commitMessageSet = getCommitMessageTitleAndDescription($currentCommitMessage);
let isCommitting = false; let isCommitting = false;
let aiLoading = false;
let summaryTextareaElement: HTMLTextAreaElement; let contextMenu: ContextMenu;
let descriptionTextareaElement: HTMLTextAreaElement; let summarizer: Summarizer | undefined;
// need to detect if the textareas wrapper is focused let titleTextArea: HTMLTextAreaElement;
let isTextareaFocused = false; let descriptionTextArea: HTMLTextAreaElement;
let isSecondTextareaFocused = false;
$: [title, description] = splitMessage($commitMessage);
$: if ($commitMessage) {
setAutoHeight(titleTextArea);
setAutoHeight(descriptionTextArea);
}
$: if (user) {
const aiProvider = new ButlerAIProvider(cloud, user);
summarizer = new Summarizer(aiProvider);
}
function splitMessage(message: string) {
const parts = message.trim().split(/\n+(.*)/s);
return [parts[0] || '', parts[1] || ''];
}
function concatMessage(title: string, description: string) {
const message = `${title}\n\n${description}`;
return message.trim();
}
function focusTextareaOnMount(el: HTMLTextAreaElement) { function focusTextareaOnMount(el: HTMLTextAreaElement) {
if (el) el.focus(); if (el) el.focus();
} }
async function commit() { async function commit() {
if (!commitMessageSet.summary) return; const message = concatMessage(title, description);
if (!message) return;
isCommitting = true; isCommitting = true;
branchController try {
.commitBranch( await branchController.commitBranch(
branch.id, branch.id,
concatCommitMessage(commitMessageSet), message,
$selectedOwnership.toString(), $selectedOwnership.toString(),
$runCommitHooks $runCommitHooks
) );
.then(() => { $commitMessage = '';
commitMessageSet.summary = ''; } finally {
commitMessageSet.description = ''; isCommitting = false;
currentCommitMessage.set(''); }
})
.finally(() => (isCommitting = false));
} }
export function git_get_config(params: { key: string }) {
return invoke<string>('git_get_global_config', params);
}
let summarizer: Summarizer | undefined;
$: if (user) {
const aiProvider = new ButlerAIProvider(cloud, user);
summarizer = new Summarizer(aiProvider);
}
let isGeneratingCommitMessage = false;
async function generateCommitMessage(files: LocalFile[]) { async function generateCommitMessage(files: LocalFile[]) {
if (!user || !summarizer) return;
const diff = files const diff = files
.map((f) => f.hunks.filter((h) => $selectedOwnership.containsHunk(f.id, h.id))) .map((f) => f.hunks.filter((h) => $selectedOwnership.containsHunk(f.id, h.id)))
.flat() .flat()
@ -114,9 +107,6 @@
.join('\n') .join('\n')
.slice(0, 5000); .slice(0, 5000);
if (!user) return;
if (!summarizer) return;
// Branches get their names generated only if there are at least 4 lines of code // Branches get their names generated only if there are at least 4 lines of code
// If the change is a 'one-liner', the branch name is either left as "virtual branch" // If the change is a 'one-liner', the branch name is either left as "virtual branch"
// or the user has to manually trigger the name generation from the meatball menu // or the user has to manually trigger the name generation from the meatball menu
@ -125,129 +115,79 @@
dispatch('action', 'generate-branch-name'); dispatch('action', 'generate-branch-name');
} }
isGeneratingCommitMessage = true; aiLoading = true;
summarizer try {
.commit(diff, $commitGenerationUseEmojis, $commitGenerationExtraConcise) $commitMessage = await summarizer.commit(
.then((message) => { diff,
commitMessageSet = getCommitMessageTitleAndDescription(message); $commitGenerationUseEmojis,
currentCommitMessage.set(message); $commitGenerationExtraConcise
);
setTimeout(() => { } catch {
summaryTextareaElement.focus(); toasts.error('Failed to generate commit message');
setAutoHeight(summaryTextareaElement); } finally {
setAutoHeight(descriptionTextareaElement); aiLoading = false;
}, 0); }
}) setTimeout(() => titleTextArea.focus(), 0);
.catch(() => {
toasts.error('Failed to generate commit message');
})
.finally(() => {
isGeneratingCommitMessage = false;
});
}
const commitGenerationExtraConcise = projectCommitGenerationExtraConcise(projectId);
const commitGenerationUseEmojis = projectCommitGenerationUseEmojis(projectId);
let contextMenu: ContextMenu;
// concat commit message set into a single commit message
$: if (commitMessageSet.summary.length > 0) {
currentCommitMessage.set(concatCommitMessage(commitMessageSet));
}
// move description to title if title is empty
$: if (commitMessageSet.summary.length == 0 && commitMessageSet.description.length > 0) {
const messageSet = getCommitMessageTitleAndDescription(commitMessageSet.description);
commitMessageSet.summary = messageSet.summary;
commitMessageSet.description = messageSet.description;
}
// set auto height on textareas on each message change
$: if (commitMessageSet) {
setAutoHeight(summaryTextareaElement);
setAutoHeight(descriptionTextareaElement);
} }
</script> </script>
<div class="commit-box" class:commit-box__expanded={$expanded}> <div class="commit-box" class:commit-box__expanded={$expanded}>
{#if $expanded} {#if $expanded}
<div class="commit-box__expander" transition:slide={{ duration: 150, easing: quintOut }}> <div class="commit-box__expander" transition:slide={{ duration: 150, easing: quintOut }}>
<div <div class="commit-box__textarea-wrapper text-input">
class="commit-box__textarea-wrapper text-input"
class:text-input__focused={$expanded && (isTextareaFocused || isSecondTextareaFocused)}
>
<textarea <textarea
bind:this={summaryTextareaElement} value={title}
bind:value={commitMessageSet.summary} placeholder="Commit summary"
disabled={aiLoading}
class="text-base-body-13 text-semibold commit-box__textarea commit-box__textarea__title"
class:commit-box__textarea_bottom-padding={title.length == 0 && description.length == 0}
spellcheck="false"
rows="1"
bind:this={titleTextArea}
use:focusTextareaOnMount use:focusTextareaOnMount
use:useResize={() => { use:useResize={() => {
setAutoHeight(summaryTextareaElement); setAutoHeight(titleTextArea);
setAutoHeight(descriptionTextareaElement); setAutoHeight(descriptionTextArea);
}}
on:input={useAutoHeight}
on:focus={(e) => {
useAutoHeight(e);
isTextareaFocused = true;
}}
on:blur={() => {
isTextareaFocused = false;
}} }}
on:focus={(e) => setAutoHeight(e.currentTarget)}
on:input={() => ($commitMessage = concatMessage(titleTextArea.value, description))}
on:keydown={(e) => { on:keydown={(e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') commit();
commit();
}
if (e.key === 'Tab' || e.key === 'Enter') { if (e.key === 'Tab' || e.key === 'Enter') {
e.preventDefault(); e.preventDefault();
descriptionTextareaElement.focus(); descriptionTextArea.focus();
} }
}} }}
spellcheck={false}
class="text-base-body-13 text-semibold commit-box__textarea commit-box__textarea__title"
class:commit-box__textarea_bottom-padding={commitMessageSet.description.length == 0 &&
commitMessageSet.summary.length == 0}
rows="1"
disabled={isGeneratingCommitMessage}
placeholder="Commit summary"
/> />
{#if commitMessageSet.summary.length > 0} {#if title.length > 0}
<textarea <textarea
bind:this={descriptionTextareaElement} value={description}
bind:value={commitMessageSet.description} disabled={aiLoading}
on:focus={(e) => {
useAutoHeight(e);
isSecondTextareaFocused = true;
}}
on:blur={() => {
isSecondTextareaFocused = false;
}}
use:setAutoHeight
spellcheck={false}
class="text-base-body-13 commit-box__textarea commit-box__textarea__description"
class:commit-box__textarea_bottom-padding={commitMessageSet.summary.length > 0}
rows="1"
disabled={isGeneratingCommitMessage}
placeholder="Commit description (optional)" placeholder="Commit description (optional)"
class="text-base-body-13 commit-box__textarea commit-box__textarea__description"
class:commit-box__textarea_bottom-padding={description.length > 0}
spellcheck="false"
rows="1"
bind:this={descriptionTextArea}
on:focus={(e) => setAutoHeight(e.currentTarget)}
on:input={() => ($commitMessage = concatMessage(title, descriptionTextArea.value))}
on:keydown={(e) => { on:keydown={(e) => {
if (commitMessageSet.description.length == 0 && e.key === 'Backspace') { const value = e.currentTarget.value;
if (e.key == 'Backspace' && value.length == 0) {
e.preventDefault(); e.preventDefault();
summaryTextareaElement.focus(); titleTextArea.focus();
} setAutoHeight(e.currentTarget);
} else if (e.key == 'a' && (e.metaKey || e.ctrlKey) && value.length == 0) {
// select previous textarea on cmd+a if this textarea is empty // select previous textarea on cmd+a if this textarea is empty
if (
e.key === 'a' &&
(e.metaKey || e.ctrlKey) &&
commitMessageSet.description.length == 0
) {
e.preventDefault(); e.preventDefault();
summaryTextareaElement.focus(); titleTextArea.select();
summaryTextareaElement.setSelectionRange(0, summaryTextareaElement.value.length);
} }
}} }}
/> />
{/if} {/if}
{#if commitMessageSet.summary.length > 50} {#if title.length > 50}
<div <div
transition:fly={{ y: 2, duration: 150 }} transition:fly={{ y: 2, duration: 150 }}
class="commit-box__textarea-tooltip" class="commit-box__textarea-tooltip"
@ -271,7 +211,7 @@
icon="ai-small" icon="ai-small"
color="neutral" color="neutral"
disabled={!$aiGenEnabled || !user} disabled={!$aiGenEnabled || !user}
loading={isGeneratingCommitMessage} loading={aiLoading}
on:click={() => generateCommitMessage(branch.files)} on:click={() => generateCommitMessage(branch.files)}
> >
Generate message Generate message
@ -315,8 +255,7 @@
color="primary" color="primary"
kind="filled" kind="filled"
loading={isCommitting} loading={isCommitting}
disabled={(isCommitting || !commitMessageSet.summary || $selectedOwnership.isEmpty()) && disabled={(isCommitting || !title || $selectedOwnership.isEmpty()) && $expanded}
$expanded}
id="commit-to-branch" id="commit-to-branch"
on:click={() => { on:click={() => {
if ($expanded) { if ($expanded) {
@ -341,19 +280,21 @@
transition: background-color var(--transition-medium); transition: background-color var(--transition-medium);
border-radius: 0 0 var(--radius-m) var(--radius-m); border-radius: 0 0 var(--radius-m) var(--radius-m);
} }
.commit-box__expander { .commit-box__expander {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-bottom: var(--space-12); margin-bottom: var(--space-12);
} }
.commit-box__textarea-wrapper { .commit-box__textarea-wrapper {
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--space-4); gap: var(--space-4);
/* padding: var(--space-12) var(--space-12) var(--space-48) var(--space-12); */
padding: 0; padding: 0;
} }
.commit-box__textarea { .commit-box__textarea {
overflow: hidden; overflow: hidden;
display: flex; display: flex;
@ -361,9 +302,7 @@
align-items: flex-end; align-items: flex-end;
gap: var(--space-16); gap: var(--space-16);
background: none; background: none;
resize: none; resize: none;
&:focus { &:focus {
outline: none; outline: none;
} }
@ -405,7 +344,6 @@
gap: var(--space-6); gap: var(--space-6);
} }
/* modifiers */
.commit-box__expanded { .commit-box__expanded {
background-color: var(--clr-theme-container-pale); background-color: var(--clr-theme-container-pale);
} }

View File

@ -51,10 +51,6 @@ export function projectLaneCollapsed(projectId: string, laneId: string): Persist
return persisted(false, key + projectId + '_' + laneId); return persisted(false, key + projectId + '_' + laneId);
} }
export function projectCurrentCommitMessage( export function persistedCommitMessage(projectId: string, branchId: string): Persisted<string> {
projectId: string, return persisted('', 'projectCurrentCommitMessage_' + projectId + '_' + branchId);
branchId: string
): Persisted<string> {
const key = 'projectCurrentCommitMessage_';
return persisted('', key + projectId + '_' + branchId);
} }

View File

@ -1,12 +1,5 @@
export function setAutoHeight(element: HTMLTextAreaElement) { export function setAutoHeight(element: HTMLTextAreaElement) {
if (!element) return; if (!element) return;
element.style.height = 'auto'; element.style.height = 'auto';
element.style.height = `${element.scrollHeight + 2}px`; element.style.height = `${element.scrollHeight + 2}px`;
} }
export function useAutoHeight(event: Event) {
const textarea = event.target as HTMLTextAreaElement;
setAutoHeight(textarea);
}

View File

@ -64,6 +64,7 @@ export class BranchController {
} catch (err: any) { } catch (err: any) {
toasts.error('Failed to commit changes'); toasts.error('Failed to commit changes');
posthog.capture('Commit Failed', err); posthog.capture('Commit Failed', err);
throw err;
} }
} }

View File

@ -18,7 +18,8 @@
); );
} }
&:focus { &:focus,
&:focus-within {
background: color-mix( background: color-mix(
in srgb, in srgb,
var(--clr-theme-container-light), var(--clr-theme-container-light),
@ -42,16 +43,3 @@
background-color: var(--clr-theme-container-pale); background-color: var(--clr-theme-container-pale);
} }
} }
.text-input__focused {
border-color: color-mix(
in srgb,
var(--clr-theme-container-outline-light),
var(--darken-tint-extradark)
);
background: color-mix(
in srgb,
var(--clr-theme-container-light),
var(--clr-theme-container-pale) 20%
);
}