Merge branch 'master' into askpass-pipe-windows-fix

This commit is contained in:
Nico Domino 2024-06-19 22:37:09 +02:00 committed by GitHub
commit 6d5730d558
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 447 additions and 267 deletions

View File

@ -1,3 +1,9 @@
# https://github.com/actions/labeler#create-githublabeleryml
rust: ["crates/**/*"]
svelte: ["app/**/*"]
# https://github.com/actions/labeler#basic-examples
rust:
- changed-files:
- any-glob-to-any-file: crates/**/*
svelte:
- changed-files:
- any-glob-to-any-file: app/**/*

View File

@ -1,16 +1,19 @@
# https://github.com/actions/labeler#create-workflow
name: Label Pull Requests
on:
pull_request_target:
jobs:
prs:
name: Triage
labeler:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v4
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
configuration-path: ".github/pr-labeler.yml"
- uses: actions/checkout@v4
with:
repository: "gitbutlerapp/gitbutler"
- uses: actions/labeler@v5
with:
configuration-path: '.github/pr-labeler.yml'

View File

@ -147,12 +147,6 @@
);
}
}
let branchFiles: BranchFiles | undefined;
function onBottomReached() {
branchFiles?.loadMore();
}
</script>
{#if $isLaneCollapsed}
@ -181,8 +175,6 @@
top: 12,
bottom: 12
}}
bottomBuffer={300}
on:bottomReached={onBottomReached}
>
<div
bind:this={rsViewport}
@ -243,7 +235,6 @@
{isUnapplied}
showCheckboxes={$commitBoxOpen}
allowMultiple
bind:this={branchFiles}
/>
{#if branch.active && branch.conflicted}
<div class="card-notifications">
@ -264,6 +255,7 @@
<CommitDialog
projectId={project.id}
expanded={commitBoxOpen}
hasSectionsAfter={branch.commits.length > 0}
on:action={(e) => {
if (e.detail === 'generate-branch-name') {
generateBranchName();
@ -283,7 +275,7 @@
</div>
{:else}
<div class="no-changes" data-dnd-ignore>
<EmptyStatePlaceholder image={noChangesSvg} width="11rem" hasBottomShift={false}>
<EmptyStatePlaceholder image={noChangesSvg} width="11rem" hasBottomMargin={false}>
<svelte:fragment slot="caption"
>No uncommitted changes on this branch</svelte:fragment
>
@ -292,8 +284,10 @@
{/if}
</div>
<CommitList {isUnapplied} />
<BranchFooter {isUnapplied} />
<div class="card-commits">
<CommitList {isUnapplied} />
<BranchFooter {isUnapplied} />
</div>
</div>
</div>
</ScrollableContainer>
@ -355,13 +349,18 @@
.card {
flex: 1;
overflow: hidden;
/* overflow: hidden; */
/* border: 1px solid var(--clr-border-2);
border-radius: var(--radius-m); */
}
.branch-card__files {
display: flex;
flex-direction: column;
flex: 1;
/* border-left: 1px solid var(--clr-border-2);
border-right: 1px solid var(--clr-border-2);
border-radius: var(--radius-m) var(--radius-m) 0 0; */
}
.card-notifications {

View File

@ -18,12 +18,6 @@
function unselectAllFiles() {
fileIdSelection.clear();
}
let branchFilesList: BranchFilesList | undefined;
export function loadMore() {
branchFilesList?.loadMore();
}
</script>
<div
@ -38,21 +32,12 @@
on:click={unselectAllFiles}
>
{#if files.length > 0}
<BranchFilesList
bind:this={branchFilesList}
{allowMultiple}
{readonly}
{files}
{showCheckboxes}
{isUnapplied}
/>
<BranchFilesList {allowMultiple} {readonly} {files} {showCheckboxes} {isUnapplied} />
{/if}
</div>
<style lang="postcss">
.branch-files {
flex: 1;
background: var(--clr-bg-1);
/* padding: 0 14px 14px; */
}
</style>

View File

@ -71,6 +71,9 @@
align-items: center;
justify-content: space-between;
padding: 14px;
border-bottom: none;
border-radius: var(--radius-m) var(--radius-m) 0 0;
background-color: var(--clr-bg-1);
}
.header__title {
display: flex;

View File

@ -2,6 +2,7 @@
import BranchFilesHeader from './BranchFilesHeader.svelte';
import Button from './Button.svelte';
import FileListItem from './FileListItem.svelte';
import LazyloadContainer from './LazyloadContainer.svelte';
import TextBox from '$lib/components/TextBox.svelte';
import { copyToClipboard } from '$lib/utils/clipboard';
import { getContext } from '$lib/utils/context';
@ -64,27 +65,39 @@
style="ghost"
outline
on:mousedown={() => copyToClipboard(mergeDiffCommand + $commit.id.slice(0, 7))}
></Button>
/>
</div>
</div>
{/if}
{#each displayedFiles as file (file.id)}
<FileListItem
{file}
{readonly}
{isUnapplied}
showCheckbox={showCheckboxes}
selected={$fileIdSelection.includes(stringifyFileKey(file.id, $commit?.id))}
on:click={(e) => {
selectFilesInList(e, file, fileIdSelection, displayedFiles, allowMultiple, $commit);
{#if displayedFiles.length > 0}
<!-- Maximum amount for initial render is 100 files
`minTriggerCount` set to 80 in order to start the loading a bit earlier. -->
<LazyloadContainer
minTriggerCount={80}
ontrigger={() => {
console.log('loading more files...');
loadMore();
}}
on:keydown={(e) => {
e.preventDefault();
maybeMoveSelection(e.key, file, displayedFiles, fileIdSelection);
}}
/>
{/each}
>
{#each displayedFiles as file (file.id)}
<FileListItem
{file}
{readonly}
{isUnapplied}
showCheckbox={showCheckboxes}
selected={$fileIdSelection.includes(stringifyFileKey(file.id, $commit?.id))}
on:click={(e) => {
selectFilesInList(e, file, fileIdSelection, displayedFiles, allowMultiple, $commit);
}}
on:keydown={(e) => {
e.preventDefault();
maybeMoveSelection(e.key, file, displayedFiles, fileIdSelection);
}}
/>
{/each}
</LazyloadContainer>
{/if}
<style lang="postcss">
.merge-commit-error {

View File

@ -5,6 +5,7 @@
import { PromptService } from '$lib/backend/prompt';
import { project } from '$lib/testing/fixtures';
import { getContext, getContextStore } from '$lib/utils/context';
import { intersectionObserver } from '$lib/utils/intersectionObserver';
import { BranchController } from '$lib/vbranches/branchController';
import { getLocalCommits, getRemoteCommits, getUnknownCommits } from '$lib/vbranches/contexts';
import { Branch } from '$lib/vbranches/types';
@ -25,6 +26,7 @@
const unknownCommits = getUnknownCommits();
let isLoading: boolean;
let isInViewport = false;
$: canBePushed = $localCommits.length !== 0 || $unknownCommits.length !== 0;
$: hasUnknownCommits = $unknownCommits.length > 0;
@ -33,7 +35,25 @@
</script>
{#if !isUnapplied && hasCommits}
<div class="actions">
<div
class="actions"
class:sticky={canBePushed}
class:not-in-viewport={!isInViewport}
use:intersectionObserver={{
callback: (entry) => {
if (entry.isIntersecting) {
isInViewport = true;
} else {
isInViewport = false;
}
},
options: {
root: null,
rootMargin: '-1px',
threshold: 1
}
}}
>
{#if canBePushed}
{#if $prompt}
<PassphraseBox prompt={$prompt} error={$promptError} />
@ -77,13 +97,14 @@
.actions {
background: var(--clr-bg-1);
padding: 16px;
border-top: 1px solid var(--clr-border-2);
border-radius: 0 0 var(--radius-m) var(--radius-m);
}
/* EMPTY STATE */
.empty-state {
display: flex;
/* justify-content: space-between; */
align-items: center;
gap: 20px;
}
@ -96,4 +117,16 @@
color: var(--clr-text-3);
flex: 1;
}
/* MODIFIERS */
.sticky {
z-index: var(--z-lifted);
position: sticky;
bottom: 0;
}
.not-in-viewport {
border-radius: 0;
/* background-color: aquamarine; */
}
</style>

View File

@ -17,8 +17,8 @@
</script>
<span
use:useResize={(frame) => {
inputWidth = `${Math.round(frame.width)}px`;
use:useResize={(e) => {
inputWidth = `${Math.round(e.frame.width)}px`;
}}
class="branch-name-mesure-el text-base-14 text-bold"
bind:this={mesureEl}>{name}</span

View File

@ -103,7 +103,11 @@
</script>
<Modal bind:this={commitMessageModal} width="small">
<CommitMessageInput bind:commitMessage={description} bind:valid={commitMessageValid} />
<CommitMessageInput
bind:commitMessage={description}
bind:valid={commitMessageValid}
isExpanded={true}
/>
{#snippet controls(close)}
<Button style="ghost" outline on:click={close}>Cancel</Button>
<Button

View File

@ -1,17 +1,17 @@
<script lang="ts">
import Button from './Button.svelte';
import CommitMessageInput from '$lib/components/CommitMessageInput.svelte';
import { projectRunCommitHooks, persistedCommitMessage } from '$lib/config/config';
import { persistedCommitMessage, projectRunCommitHooks } from '$lib/config/config';
import { getContext, getContextStore } from '$lib/utils/context';
import { intersectionObserver } from '$lib/utils/intersectionObserver';
import { BranchController } from '$lib/vbranches/branchController';
import { Ownership } from '$lib/vbranches/ownership';
import { Branch } from '$lib/vbranches/types';
import { quintOut } from 'svelte/easing';
import { slide } from 'svelte/transition';
import type { Writable } from 'svelte/store';
export let projectId: string;
export let expanded: Writable<boolean>;
export let hasSectionsAfter: boolean;
const branchController = getContext(BranchController);
const selectedOwnership = getContextStore(Ownership);
@ -21,8 +21,8 @@
const commitMessage = persistedCommitMessage(projectId, $branch.id);
let isCommitting = false;
let commitMessageValid = false;
let isInViewport = false;
async function commit() {
const message = $commitMessage;
@ -41,17 +41,32 @@
}
</script>
<div class="commit-box" class:commit-box__expanded={$expanded}>
{#if $expanded}
<div class="commit-box__expander" transition:slide={{ duration: 150, easing: quintOut }}>
<CommitMessageInput
bind:commitMessage={$commitMessage}
bind:valid={commitMessageValid}
{commit}
/>
</div>
{/if}
<div class="actions">
<div
class="commit-box"
class:not-in-viewport={!isInViewport}
class:no-sections-after={!hasSectionsAfter}
use:intersectionObserver={{
callback: (entry) => {
if (entry.isIntersecting) {
isInViewport = true;
} else {
isInViewport = false;
}
},
options: {
root: null,
rootMargin: '-1px',
threshold: 1
}
}}
>
<CommitMessageInput
bind:commitMessage={$commitMessage}
bind:valid={commitMessageValid}
isExpanded={$expanded}
{commit}
/>
<div class="actions" class:commit-box__actions-expanded={$expanded}>
{#if $expanded && !isCommitting}
<Button
style="ghost"
@ -87,27 +102,31 @@
<style lang="postcss">
.commit-box {
position: sticky;
bottom: 0;
display: flex;
flex-direction: column;
gap: 12px;
padding: 14px;
background: var(--clr-bg-1);
border-top: 1px solid var(--clr-border-2);
transition: background-color var(--transition-medium);
}
.commit-box__expander {
display: flex;
flex-direction: column;
margin-bottom: 12px;
}
.actions {
display: flex;
justify-content: right;
gap: 6px;
}
.commit-box__expanded {
background-color: var(--clr-bg-2);
/* MODIFIERS */
.not-in-viewport {
z-index: var(--z-ground);
}
.no-sections-after {
border-radius: 0 0 var(--radius-m) var(--radius-m);
}
</style>

View File

@ -264,7 +264,7 @@
<!-- BASE -->
<div class="base-row-container" class:base-row-container_unfolded={baseIsUnfolded}>
<div
class="commit-group base-row"
class="base-row"
tabindex="0"
role="button"
on:click|stopPropagation={() => (baseIsUnfolded = !baseIsUnfolded)}
@ -303,7 +303,7 @@
flex-direction: column;
background-color: var(--clr-bg-2);
border-top: 1px solid var(--clr-border-2);
border-bottom: 1px solid var(--clr-border-2);
/* border-bottom: 1px solid var(--clr-border-2); */
--base-top-margin: 8px;
--base-icon-top: 16px;
@ -313,10 +313,10 @@
--avatar-top: 16px;
}
.commit-group {
/* padding-right: 14px;
padding-left: 8px; */
}
/* .commit-group {
padding-right: 14px;
padding-left: 8px;
} */
/* BASE ROW */

View File

@ -19,11 +19,13 @@
import { getContext, getContextStore } from '$lib/utils/context';
import { tooltip } from '$lib/utils/tooltip';
import { useAutoHeight } from '$lib/utils/useAutoHeight';
import { useResize } from '$lib/utils/useResize';
import { Ownership } from '$lib/vbranches/ownership';
import { Branch, LocalFile } from '$lib/vbranches/types';
import { createEventDispatcher, onMount } from 'svelte';
import { fly } from 'svelte/transition';
export let isExpanded: boolean;
export let commitMessage: string;
export let valid: boolean = false;
export let commit: (() => void) | undefined = undefined;
@ -101,108 +103,117 @@
});
</script>
<div class="commit-box__textarea-wrapper text-input">
<textarea
value={title}
placeholder="Commit summary"
disabled={aiLoading}
class="text-base-body-13 text-semibold commit-box__textarea commit-box__textarea__title"
spellcheck="false"
rows="1"
bind:this={titleTextArea}
use:focusTextAreaOnMount
on:focus={(e) => useAutoHeight(e.currentTarget)}
on:input={(e) => {
commitMessage = concatMessage(e.currentTarget.value, description);
useAutoHeight(e.currentTarget);
{#if isExpanded}
<div
class="commit-box__textarea-wrapper text-input"
use:useResize={() => {
useAutoHeight(titleTextArea);
useAutoHeight(descriptionTextArea);
}}
on:keydown={(e) => {
if (commit && (e.ctrlKey || e.metaKey) && e.key === 'Enter') commit();
if (e.key === 'Enter') {
e.preventDefault();
descriptionTextArea.focus();
}
}}
></textarea>
{#if title.length > 0 || description}
>
<textarea
value={description}
value={title}
placeholder="Commit summary"
disabled={aiLoading}
placeholder="Commit description (optional)"
class="text-base-body-13 commit-box__textarea commit-box__textarea__description"
class="text-base-body-13 text-semibold commit-box__textarea commit-box__textarea__title"
spellcheck="false"
rows="1"
bind:this={descriptionTextArea}
bind:this={titleTextArea}
use:focusTextAreaOnMount
on:focus={(e) => useAutoHeight(e.currentTarget)}
on:input={(e) => {
commitMessage = concatMessage(title, e.currentTarget.value);
commitMessage = concatMessage(e.currentTarget.value, description);
useAutoHeight(e.currentTarget);
}}
on:keydown={(e) => {
const value = e.currentTarget.value;
if (e.key === 'Backspace' && value.length === 0) {
if (commit && (e.ctrlKey || e.metaKey) && e.key === 'Enter') commit();
if (e.key === 'Enter') {
e.preventDefault();
titleTextArea.focus();
useAutoHeight(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
e.preventDefault();
titleTextArea.select();
descriptionTextArea.focus();
}
}}
></textarea>
{/if}
{#if title.length > 50}
{#if title.length > 0 || description}
<textarea
value={description}
disabled={aiLoading}
placeholder="Commit description (optional)"
class="text-base-body-13 commit-box__textarea commit-box__textarea__description"
spellcheck="false"
rows="1"
bind:this={descriptionTextArea}
on:focus={(e) => useAutoHeight(e.currentTarget)}
on:input={(e) => {
commitMessage = concatMessage(title, e.currentTarget.value);
useAutoHeight(e.currentTarget);
}}
on:keydown={(e) => {
const value = e.currentTarget.value;
if (e.key === 'Backspace' && value.length === 0) {
e.preventDefault();
titleTextArea.focus();
useAutoHeight(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
e.preventDefault();
titleTextArea.select();
}
}}
></textarea>
{/if}
{#if title.length > 50}
<div
transition:fly={{ y: 2, duration: 150 }}
class="commit-box__textarea-tooltip"
use:tooltip={{
text: '50 characters or less is best. Extra info can be added in the description.',
delay: 200
}}
>
<Icon name="blitz" />
</div>
{/if}
<div
transition:fly={{ y: 2, duration: 150 }}
class="commit-box__textarea-tooltip"
use:tooltip={{
text: '50 characters or less is best. Extra info can be added in the description.',
delay: 200
}}
class="commit-box__texarea-actions"
class:commit-box-actions_expanded={isExpanded}
use:tooltip={$aiGenEnabled && aiConfigurationValid
? ''
: 'You must be logged in or have provided your own API key and have summary generation enabled to use this feature'}
>
<Icon name="blitz" />
<DropDownButton
style="ghost"
outline
icon="ai-small"
disabled={!($aiGenEnabled && aiConfigurationValid)}
loading={aiLoading}
menuPosition="top"
on:click={async () => await generateCommitMessage($branch.files)}
>
Generate message
<ContextMenu slot="context-menu">
<ContextMenuSection>
<ContextMenuItem
label="Extra concise"
on:click={() => ($commitGenerationExtraConcise = !$commitGenerationExtraConcise)}
>
<Checkbox small slot="control" bind:checked={$commitGenerationExtraConcise} />
</ContextMenuItem>
<ContextMenuItem
label="Use emojis 😎"
on:click={() => ($commitGenerationUseEmojis = !$commitGenerationUseEmojis)}
>
<Checkbox small slot="control" bind:checked={$commitGenerationUseEmojis} />
</ContextMenuItem>
</ContextMenuSection>
</ContextMenu>
</DropDownButton>
</div>
{/if}
<div
class="commit-box__texarea-actions"
use:tooltip={$aiGenEnabled && aiConfigurationValid
? ''
: 'You must be logged in or have provided your own API key and have summary generation enabled to use this feature'}
>
<DropDownButton
style="ghost"
outline
icon="ai-small"
disabled={!($aiGenEnabled && aiConfigurationValid)}
loading={aiLoading}
menuPosition="top"
on:click={async () => await generateCommitMessage($branch.files)}
>
Generate message
<ContextMenu slot="context-menu">
<ContextMenuSection>
<ContextMenuItem
label="Extra concise"
on:click={() => ($commitGenerationExtraConcise = !$commitGenerationExtraConcise)}
>
<Checkbox small slot="control" bind:checked={$commitGenerationExtraConcise} />
</ContextMenuItem>
<ContextMenuItem
label="Use emojis 😎"
on:click={() => ($commitGenerationUseEmojis = !$commitGenerationUseEmojis)}
>
<Checkbox small slot="control" bind:checked={$commitGenerationUseEmojis} />
</ContextMenuItem>
</ContextMenuSection>
</ContextMenu>
</DropDownButton>
</div>
</div>
{/if}
<style lang="postcss">
.commit-box__textarea-wrapper {
@ -211,6 +222,12 @@
padding: 0 0 48px;
flex-direction: column;
gap: 4px;
overflow: hidden;
animation: expand-box 0.2s ease forwards;
/* props to animate on mount */
/* display: none;
max-height: 0;
overflow: hidden; */
}
.commit-box__textarea {
@ -221,6 +238,7 @@
gap: 16px;
background: none;
resize: none;
&:focus {
outline: none;
}
@ -252,8 +270,45 @@
.commit-box__texarea-actions {
position: absolute;
display: flex;
right: 12px;
bottom: 12px;
/* props to animate on mount */
display: none;
opacity: 0;
transform: translateY(10px);
}
/* MODIFIERS */
/* .commit-box_expanded {
display: flex;
animation: expand-box 0.2s ease forwards;
} */
@keyframes expand-box {
from {
max-height: 0;
padding: 0 0 0;
}
to {
max-height: 600px;
padding: 0 0 48px;
}
}
.commit-box-actions_expanded {
display: flex;
animation: expand-actions 0.25s ease forwards;
animation-delay: 0.1s;
}
@keyframes expand-actions {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@ -1,14 +1,14 @@
<script lang="ts">
export let image: string;
export let width: string = '18rem';
export let hasBottomShift: boolean = true;
export let hasBottomMargin: boolean = true;
</script>
<div class="empty-state-container">
<div
class="empty-state"
style:max-width={width}
style:margin-bottom={hasBottomShift ? '48px' : '0'}
style:margin-bottom={hasBottomMargin ? '48px' : '0'}
>
<div class="empty-state__image">
{@html image}

View File

@ -104,6 +104,13 @@
}}
role="button"
tabindex="0"
on:contextmenu|preventDefault={async (e) => {
if (fileIdSelection.has(file.id, $commit?.id)) {
popupMenu.openByMouse(e, { files: await $selectedFiles });
} else {
popupMenu.openByMouse(e, { files: [file] });
}
}}
use:draggable={{
data: $selectedFiles.then(
(files) => new DraggableFile($branch?.id || '', file, $commit, files)
@ -112,13 +119,6 @@
viewportId: 'board-viewport',
selector: '.selected-draggable'
}}
on:contextmenu|preventDefault={async (e) => {
if (fileIdSelection.has(file.id, $commit?.id)) {
popupMenu.openByMouse(e, { files: await $selectedFiles });
} else {
popupMenu.openByMouse(e, { files: [file] });
}
}}
>
{#if showCheckbox}
<Checkbox

View File

@ -4,6 +4,7 @@
import FileCard from './FileCard.svelte';
import FullviewLoading from './FullviewLoading.svelte';
import Icon from './Icon.svelte';
import LazyloadContainer from './LazyloadContainer.svelte';
import ScrollableContainer from './ScrollableContainer.svelte';
import SnapshotCard from './SnapshotCard.svelte';
import emptyFolderSvg from '$lib/assets/empty-state/empty-folder.svg?raw';
@ -140,51 +141,59 @@
<!-- SNAPSHOTS -->
{#if $snapshots.length > 0}
<ScrollableContainer on:bottomReached={onLastInView}>
<ScrollableContainer>
<div class="container">
<!-- SNAPSHOTS FEED -->
{#each $snapshots as entry, idx (entry.id)}
{@const withinRestoreItems = findRestorationRanges($snapshots)}
{#if idx === 0 || createdOnDay(entry.createdAt) !== createdOnDay($snapshots[idx - 1].createdAt)}
<div class="sideview__date-header">
<h4 class="text-base-13 text-semibold">
{createdOnDay(entry.createdAt)}
</h4>
</div>
{/if}
<LazyloadContainer
minTriggerCount={30}
ontrigger={() => {
console.log('load more snapshots…');
onLastInView();
}}
>
{#each $snapshots as entry, idx (entry.id)}
{@const withinRestoreItems = findRestorationRanges($snapshots)}
{#if idx === 0 || createdOnDay(entry.createdAt) !== createdOnDay($snapshots[idx - 1].createdAt)}
<div class="sideview__date-header">
<h4 class="text-base-13 text-semibold">
{createdOnDay(entry.createdAt)}
</h4>
</div>
{/if}
{#if entry.details}
<SnapshotCard
isWithinRestore={withinRestoreItems.includes(entry.id)}
{entry}
on:restoreClick={() => {
historyService.restoreSnapshot(project.id, entry.id);
// In some cases, restoring the snapshot doesnt update the UI correctly
// Until we have that figured out, we need to reload the page.
location.reload();
}}
{selectedFile}
on:diffClick={async (filePath) => {
const path = filePath.detail;
{#if entry.details}
<SnapshotCard
isWithinRestore={withinRestoreItems.includes(entry.id)}
{entry}
on:restoreClick={() => {
historyService.restoreSnapshot(project.id, entry.id);
// In some cases, restoring the snapshot doesnt update the UI correctly
// Until we have that figured out, we need to reload the page.
location.reload();
}}
{selectedFile}
on:diffClick={async (filePath) => {
const path = filePath.detail;
if (snapshotFilesTempStore?.entryId === entry.id) {
if (selectedFile?.path === path) {
currentFilePreview = undefined;
selectedFile = undefined;
if (snapshotFilesTempStore?.entryId === entry.id) {
if (selectedFile?.path === path) {
currentFilePreview = undefined;
selectedFile = undefined;
} else {
updateFilePreview(entry, path);
}
} else {
snapshotFilesTempStore = {
entryId: entry.id,
diffs: await historyService.getSnapshotDiff(project.id, entry.id)
};
updateFilePreview(entry, path);
}
} else {
snapshotFilesTempStore = {
entryId: entry.id,
diffs: await historyService.getSnapshotDiff(project.id, entry.id)
};
updateFilePreview(entry, path);
}
}}
/>
{/if}
{/each}
}}
/>
{/if}
{/each}
</LazyloadContainer>
<!-- LOAD MORE -->
{#if $loading}

View File

@ -0,0 +1,51 @@
<script lang="ts">
import { onMount } from 'svelte';
interface Props {
children: any;
minTriggerCount: number;
ontrigger: (lastChild: Element) => void;
}
let { children, minTriggerCount, ontrigger }: Props = $props();
let lazyContainerEl: HTMLDivElement;
onMount(() => {
const containerChildren = lazyContainerEl.children;
if (containerChildren.length < minTriggerCount) return;
const iObserver = new IntersectionObserver((entries) => {
const lastChild = containerChildren[containerChildren.length - 1];
if (entries[0].target === lastChild && entries[0].isIntersecting) {
ontrigger(lastChild);
}
});
const mObserver = new MutationObserver(() => {
const lastChild = containerChildren[containerChildren.length - 1];
if (lastChild) {
iObserver.observe(lastChild);
}
});
iObserver.observe(containerChildren[containerChildren.length - 1]);
mObserver.observe(lazyContainerEl, { childList: true });
return () => {
iObserver.disconnect();
mObserver.disconnect();
};
});
</script>
<div class="lazy-container" bind:this={lazyContainerEl}>
{@render children()}
</div>
<style>
.lazy-container {
display: contents;
}
</style>

View File

@ -18,12 +18,9 @@
export let shift = '0';
export let thickness = '0.563rem';
// How much of a buffer there should be before we consider the bottom reached
export let bottomBuffer = 0;
let observer: ResizeObserver;
const dispatch = createEventDispatcher<{ dragging: boolean; bottomReached: boolean }>();
const dispatch = createEventDispatcher<{ dragging: boolean }>();
onMount(() => {
observer = new ResizeObserver(() => {
@ -46,14 +43,6 @@
>
<div
bind:this={viewport}
on:scroll={(e) => {
const target = e.currentTarget;
scrolled = target.scrollTop !== 0;
if (target.scrollTop + target.clientHeight + bottomBuffer >= target.scrollHeight) {
dispatch('bottomReached', true);
}
}}
class="viewport hide-native-scrollbar"
style:height
style:overflow-y={scrollable ? 'auto' : 'hidden'}

View File

@ -42,7 +42,7 @@
let filterText: string | undefined = undefined;
let filteredItems: Selectable[] = items;
function filterItems(items: Selectable[], filterText: string | undefined) {
const filterItems = throttle((items: Selectable[], filterText: string | undefined) => {
if (!filterText) {
return items;
}
@ -52,7 +52,7 @@
if (!isStr(property)) return false;
return property.includes(filterText);
});
}
}, INPUT_THROTTLE_TIME);
$: filteredItems = filterItems(items, filterText);
@ -111,13 +111,13 @@
}
}
const handleChar = throttle((char: string) => {
function handleChar(char: string) {
highlightIndex = undefined;
filterText ??= '';
filterText += char;
}, INPUT_THROTTLE_TIME);
}
const handleDelete = throttle(() => {
function handleDelete() {
if (filterText === undefined) return;
if (filterText.length === 1) {
@ -126,7 +126,7 @@
}
filterText = filterText.slice(0, -1);
}, INPUT_THROTTLE_TIME);
}
function handleKeyDown(e: CustomEvent<KeyboardEvent>) {
if (!listOpen) {

View File

@ -29,7 +29,11 @@
return `${createdOnDay(date)}, ${toHumanReadableTime(date)}`;
}
const dispatch = createEventDispatcher<{ restoreClick: void; diffClick: string }>();
const dispatch = createEventDispatcher<{
restoreClick: void;
diffClick: string;
visible: void;
}>();
function camelToTitleCase(str: string | undefined) {
if (!str) return '';

View File

@ -48,8 +48,8 @@
dispatch('change', e.currentTarget.value);
useAutoHeight(e.currentTarget);
}}
use:useResize={() => {
useAutoHeight(textareaElement);
use:useResize={(e) => {
useAutoHeight(e.currentTarget as HTMLTextAreaElement);
}}
on:focus={(e) => useAutoHeight(e.currentTarget)}
style:max-height={maxHeight ? pxToRem(maxHeight) : undefined}

View File

@ -0,0 +1,27 @@
export function intersectionObserver(
node: Element,
{
isDisabled,
callback,
options
}: {
isDisabled?: boolean;
callback: (entry: IntersectionObserverEntry, observer: IntersectionObserver) => void;
options?: IntersectionObserverInit;
}
) {
if (isDisabled) return;
const observer = new IntersectionObserver(
([entry], observer) => callback(entry, observer),
options
);
observer.observe(node);
return {
destroy() {
observer.disconnect();
}
};
}

View File

@ -1,14 +1,17 @@
export function useResize(
element: HTMLElement,
callback: (frame: { width: number; height: number }) => void
callback: (data: { currentTarget: HTMLElement; frame: { width: number; height: number } }) => void
) {
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const { inlineSize, blockSize } = entry.borderBoxSize[0];
callback({
width: Math.round(inlineSize),
height: Math.round(blockSize)
currentTarget: element,
frame: {
width: Math.round(inlineSize),
height: Math.round(blockSize)
}
});
}
});

View File

@ -1,23 +0,0 @@
export function useStickyPinned(
element: HTMLElement,
callback: (isPinned: boolean, element: HTMLElement) => void
) {
const observer = new IntersectionObserver(
([entry]) => {
callback(entry.intersectionRatio < 1, element);
console.log('sticky pinned', element, entry.intersectionRatio);
},
{
threshold: [1]
}
);
observer.observe(element);
return {
destroy() {
observer.disconnect();
}
};
}