From 9ff735fd4efe9d6e74896dbed46d21b28610e9f1 Mon Sep 17 00:00:00 2001 From: Pavel Laptev Date: Tue, 2 Jul 2024 20:49:17 +0200 Subject: [PATCH] Drag-n-drop update (#4220) * Reuse `splitFilePath` function * unnecessary `width` and `height` removed * added utils for draggable file list items - added separate CSS * WIP new styles for the commit draggable * styles for draggable commit cards updated * Draggable hunk added * Draggable lanes updated * Dropzone design updated * Dropzones code refactor * reordering lines design update * Update logic for determining reorder shift * Remove unused CSS fix scrollable container prop * dropzone animations added * Dropzone hover state UI updated * CSS update: Card overlay labels * Fix: horizontal scroll wrong observer trigger * UX: Automatically close the commit message box after commit --- app/src/lib/branch/BranchCard.svelte | 56 ++-- app/src/lib/branch/BranchFooter.svelte | 3 +- app/src/lib/branch/BranchLane.svelte | 2 +- app/src/lib/branch/Dropzones.svelte | 1 - app/src/lib/commit/CommitCard.svelte | 144 ++++++--- app/src/lib/commit/CommitDialog.svelte | 1 + app/src/lib/commit/CommitDragItem.svelte | 19 +- app/src/lib/commit/CommitList.svelte | 8 +- app/src/lib/components/Board.svelte | 6 +- app/src/lib/dragging/draggable.ts | 305 +++++++++++------- app/src/lib/dropzone/CardOverlay.svelte | 172 ++++++++-- app/src/lib/dropzone/LineOverlay.svelte | 32 +- app/src/lib/file/FileCardHeader.svelte | 15 +- app/src/lib/file/FileListItem.svelte | 7 +- app/src/lib/history/SnapshotCard.svelte | 9 +- app/src/lib/hunk/HunkViewer.svelte | 4 +- app/src/lib/shared/Resizer.svelte | 1 + app/src/lib/shared/ScrollableContainer.svelte | 12 +- app/src/lib/shared/Scrollbar.svelte | 6 +- app/src/lib/utils/filePath.ts | 9 + app/src/styles/draggable.css | 162 ++++++++++ app/src/styles/main.css | 1 + 22 files changed, 699 insertions(+), 276 deletions(-) create mode 100644 app/src/lib/utils/filePath.ts create mode 100644 app/src/styles/draggable.css diff --git a/app/src/lib/branch/BranchCard.svelte b/app/src/lib/branch/BranchCard.svelte index 6e29d1b41..086a2e08d 100644 --- a/app/src/lib/branch/BranchCard.svelte +++ b/app/src/lib/branch/BranchCard.svelte @@ -139,9 +139,9 @@
- - {#if branch.files?.length > 0} -
+ {#if branch.files?.length > 0} +
+
{/if} + - {#if branch.active} - 0} - on:action={(e) => { - if (e.detail === 'generate-branch-name') { - generateBranchName(); - } - }} - /> - {/if} -
- {:else if branch.commits.length === 0} + {#if branch.active} + 0} + on:action={(e) => { + if (e.detail === 'generate-branch-name') { + generateBranchName(); + } + }} + /> + {/if} +
+ {:else if branch.commits.length === 0} +
This is a new branch @@ -185,7 +187,9 @@
- {:else} +
+ {:else} +
- {/if} -
+ + {/if}
@@ -254,9 +258,6 @@ .card { flex: 1; - /* overflow: hidden; */ - /* border: 1px solid var(--clr-border-2); - border-radius: var(--radius-m); */ } .branch-card__files { @@ -264,9 +265,6 @@ flex-direction: column; flex: 1; height: 100%; - /* 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 { @@ -288,12 +286,6 @@ cursor: default; /* was defaulting to text cursor */ } - .branch-card :global(.contents) { - display: flex; - flex-direction: column; - min-height: 100%; - } - /* COLLAPSED LANE */ .collapsed-lane-container { display: flex; diff --git a/app/src/lib/branch/BranchFooter.svelte b/app/src/lib/branch/BranchFooter.svelte index effe66837..0c72dcc9e 100644 --- a/app/src/lib/branch/BranchFooter.svelte +++ b/app/src/lib/branch/BranchFooter.svelte @@ -54,7 +54,7 @@ options: { root: null, rootMargin: '-1px', - threshold: 1 + threshold: 0 } }} > @@ -131,6 +131,5 @@ .not-in-viewport { border-radius: 0; - /* background-color: aquamarine; */ } diff --git a/app/src/lib/branch/BranchLane.svelte b/app/src/lib/branch/BranchLane.svelte index d4dcac9b3..426fe4d04 100644 --- a/app/src/lib/branch/BranchLane.svelte +++ b/app/src/lib/branch/BranchLane.svelte @@ -77,7 +77,7 @@ {#await $selectedFile then selected} {#if selected}
diff --git a/app/src/lib/commit/CommitCard.svelte b/app/src/lib/commit/CommitCard.svelte index 130b71f50..e6c02209a 100644 --- a/app/src/lib/commit/CommitCard.svelte +++ b/app/src/lib/commit/CommitCard.svelte @@ -3,7 +3,7 @@ import { Project } from '$lib/backend/projects'; import CommitMessageInput from '$lib/commit/CommitMessageInput.svelte'; import { persistedCommitMessage } from '$lib/config/config'; - import { draggable } from '$lib/dragging/draggable'; + import { draggableCommit } from '$lib/dragging/draggable'; import { DraggableCommit, nonDraggable } from '$lib/dragging/draggables'; import BranchFilesList from '$lib/file/BranchFilesList.svelte'; import Button from '$lib/shared/Button.svelte'; @@ -25,7 +25,7 @@ BaseBranch, type CommitStatus } from '$lib/vbranches/types'; - import { createEventDispatcher, type Snippet } from 'svelte'; + import { type Snippet } from 'svelte'; export let branch: Branch | undefined = undefined; export let commit: Commit | RemoteCommit; @@ -46,8 +46,7 @@ const currentCommitMessage = persistedCommitMessage(project.id, branch?.id || ''); - const dispatch = createEventDispatcher<{ toggle: void }>(); - + let draggableCommitElement: HTMLElement | null = null; let files: RemoteFile[] = []; let showDetails = false; @@ -57,7 +56,6 @@ function toggleFiles() { showDetails = !showDetails; - dispatch('toggle'); if (showDetails) loadFiles(); } @@ -102,6 +100,14 @@ commitMessageModal.close(); } + function getTimeAndAuthor() { + const timeAgo = getTimeAgo(commit.createdAt); + const author = type === 'localAndRemote' || type === 'remote' ? commit.author.name : 'you'; + return `${timeAgo} by ${author}`; + } + + const commitShortSha = commit.id.substring(0, 7); + let topHeightPx = 24; $: { @@ -109,6 +115,9 @@ if (first) topHeightPx = 58; if (showDetails && !first) topHeightPx += 12; } + + let dragDirection: 'up' | 'down' | undefined; + let isDragTargeted = false; @@ -138,40 +147,77 @@ class:is-last={last} class:has-lines={lines} > + {#if dragDirection && isDragTargeted} +
+ {/if} + {#if lines}
{@render lines(topHeightPx)}
{/if} - -
-
+
+
{ + isDragTargeted = true; + }} + on:dragleave={() => { + isDragTargeted = false; + }} + on:drop={() => { + isDragTargeted = false; + }} + on:drag={(e) => { + const target = e.target as HTMLElement; + const targetHeight = target.offsetHeight; + const targetTop = target.getBoundingClientRect().top; + const mouseY = e.clientY; + + const isTop = mouseY < targetTop + targetHeight / 2; + + dragDirection = isTop ? 'up' : 'down'; + }} + use:draggableCommit={commit instanceof Commit && !isUnapplied && type !== 'integrated' ? { + label: commit.descriptionTitle, + sha: commitShortSha, + dateAndAuthor: getTimeAndAuthor(), + commitType: type, data: new DraggableCommit(commit.branchId, commit, isHeadCommit), - extendWithClass: 'commit_draggable' + viewportId: 'board-viewport' } : nonDraggable()} > -
- -
+
+ + {#if type === 'local' || type === 'localAndRemote'} +
+ +
+ {/if} {#if first}
@@ -209,7 +255,7 @@ class="commit__subtitle-btn commit__subtitle-btn_dashed" on:click|stopPropagation={() => copyToClipboard(commit.id)} > - {commit.id.substring(0, 7)} + {commitShortSha}
@@ -235,11 +281,7 @@ - {getTimeAgo(commit.createdAt)}{type === 'localAndRemote' || type === 'remote' - ? ` by ${commit.author.name}` - : ' by you'} + {getTimeAndAuthor()}
{/if}
@@ -285,25 +327,11 @@
{/if} -
- + +
diff --git a/app/src/lib/commit/CommitDialog.svelte b/app/src/lib/commit/CommitDialog.svelte index 1cd1607d5..7addfa831 100644 --- a/app/src/lib/commit/CommitDialog.svelte +++ b/app/src/lib/commit/CommitDialog.svelte @@ -38,6 +38,7 @@ $commitMessage = ''; } finally { isCommitting = false; + $expanded = false; } } diff --git a/app/src/lib/commit/CommitDragItem.svelte b/app/src/lib/commit/CommitDragItem.svelte index caf3fa786..6f5f230b3 100644 --- a/app/src/lib/commit/CommitDragItem.svelte +++ b/app/src/lib/commit/CommitDragItem.svelte @@ -38,7 +38,14 @@ {@render squashDropzone()} {#snippet overlay({ hovered, activated })} - + {/snippet} {/snippet} @@ -48,7 +55,14 @@ {@render children()} {#snippet overlay({ hovered, activated })} - + {/snippet} {/snippet} @@ -57,6 +71,5 @@ .dropzone-wrapper { position: relative; width: 100%; - overflow: hidden; } diff --git a/app/src/lib/commit/CommitList.svelte b/app/src/lib/commit/CommitList.svelte index 9a78a0696..1f67252c1 100644 --- a/app/src/lib/commit/CommitList.svelte +++ b/app/src/lib/commit/CommitList.svelte @@ -149,8 +149,8 @@ {@render reorderDropzone( reorderDropzoneManager.dropzoneBelowCommit(commit.id), getReorderDropzoneOffset({ - isLast: $localAndRemoteCommits.length === 0 && idx + 1 === $localCommits.length, - isMiddle: $localAndRemoteCommits.length > 0 && idx + 1 === $localCommits.length + isLast: idx + 1 === $localCommits.length, + isMiddle: idx + 1 === $localCommits.length }) )} @@ -181,7 +181,8 @@ {@render reorderDropzone( reorderDropzoneManager.dropzoneBelowCommit(commit.id), getReorderDropzoneOffset({ - isLast: idx + 1 === $localAndRemoteCommits.length + isMiddle: idx + 1 === $localAndRemoteCommits.length + // isLast: idx + 1 === $localAndRemoteCommits.length }) )} .commits { + position: relative; display: flex; flex-direction: column; background-color: var(--clr-bg-2); diff --git a/app/src/lib/components/Board.svelte b/app/src/lib/components/Board.svelte index 536954441..4d76a592b 100644 --- a/app/src/lib/components/Board.svelte +++ b/app/src/lib/components/Board.svelte @@ -4,7 +4,7 @@ import { Project } from '$lib/backend/projects'; import BranchDropzone from '$lib/branch/BranchDropzone.svelte'; import BranchLane from '$lib/branch/BranchLane.svelte'; - import { cloneWithRotation } from '$lib/dragging/draggable'; + import { cloneElement } from '$lib/dragging/draggable'; import { persisted } from '$lib/persisted/persisted'; import Icon from '$lib/shared/Icon.svelte'; import { getContext, getContextStore } from '$lib/utils/context'; @@ -94,12 +94,12 @@ e.stopPropagation(); return; } - clone = cloneWithRotation(e.target); + clone = cloneElement(e.target as HTMLElement); document.body.appendChild(clone); // Get chromium to fire dragover & drop events // https://stackoverflow.com/questions/6481094/html5-drag-and-drop-ondragover-not-firing-in-chrome/6483205#6483205 e.dataTransfer?.setData('text/html', 'd'); // cannot be empty string - e.dataTransfer?.setDragImage(clone, e.offsetX + 30, e.offsetY + 30); // Adds the padding + e.dataTransfer?.setDragImage(clone, e.offsetX, e.offsetY); // Adds the padding dragged = e.currentTarget; priorPosition = Array.from(dropZone.children).indexOf(dragged); }} diff --git a/app/src/lib/dragging/draggable.ts b/app/src/lib/dragging/draggable.ts index 3c3d1af77..8eb47fabc 100644 --- a/app/src/lib/dragging/draggable.ts +++ b/app/src/lib/dragging/draggable.ts @@ -1,82 +1,47 @@ import { dropzoneRegistry } from './dropzone'; +import { getVSIFileIcon } from '$lib/ext-icons'; +import { pxToRem } from '$lib/utils/pxToRem'; +import { type CommitStatus } from '$lib/vbranches/types'; import type { Draggable } from './draggables'; export interface DraggableConfig { readonly selector?: string; readonly disabled?: boolean; + readonly label?: string; + readonly filePath?: string; + readonly sha?: string; + readonly dateAndAuthor?: string; + readonly commitType?: CommitStatus; readonly data?: Draggable | Promise; readonly viewportId?: string; - readonly extendWithClass?: string; } -export function applyContainerStyle(element: HTMLElement) { - element.style.position = 'absolute'; - element.style.top = '-9999px'; // Element has to be in the DOM so we move it out of sight - element.style.display = 'inline-block'; - element.style.padding = '30px'; // To prevent clipping of rotated element +function createElement( + tag: string, + classNames: string[], + textContent?: string, + src?: string +): HTMLElement { + const el = document.createElement(tag); + el.classList.add(...classNames); + if (textContent) el.textContent = textContent; + if (src) (el as HTMLImageElement).src = src; + return el; } -export function createContainerForMultiDrag( - children: Element[], - extendWithClass: string | undefined -): HTMLDivElement { - const inner = document.createElement('div'); - inner.style.display = 'flex'; - inner.style.flexDirection = 'column'; - inner.style.gap = '0.125rem'; - - children.forEach((child) => { - inner.appendChild(cloneWithPreservedDimensions(child, extendWithClass)); - }); - rotateElement(inner); - - const container = document.createElement('div'); - container.appendChild(inner); - applyContainerStyle(container); - - return container; -} - -export function cloneWithPreservedDimensions(node: any, extendWithClass: string | undefined) { - const clone = node.cloneNode(true) as HTMLElement; - clone.style.height = node.clientHeight + 'px'; - clone.style.width = node.clientWidth + 'px'; - clone.classList.remove('selected-draggable'); - - extendWithClass && clone.classList.add(extendWithClass); - - return clone; -} - -export function cloneWithRotation(node: any, extendWithClass: string | undefined = undefined) { - const container = document.createElement('div'); - const clone = cloneWithPreservedDimensions(node, extendWithClass) as HTMLElement; - container.appendChild(clone); - - // exclude all ignored elements from the clone - const ignoredElements = container.querySelectorAll('[data-remove-from-draggable]'); - ignoredElements.forEach((element) => { - element.remove(); - }); - - applyContainerStyle(container); - - // Style the inner node so it retains the shape and then rotate - // TODO: This rotation puts a requirement on draggables to have - // an outer container, which feels extra. Consider refactoring. - rotateElement(clone); - return container as HTMLElement; -} - -function rotateElement(element: HTMLElement) { - element.style.rotate = `${Math.floor(Math.random() * 3)}deg`; -} - -export function draggable(node: HTMLElement, initialOpts: DraggableConfig) { - let opts = initialOpts; +function setupDragHandlers( + node: HTMLElement, + opts: DraggableConfig, + createClone: (opts: DraggableConfig, selectedElements: HTMLElement[]) => HTMLElement, + params: { + handlerWidth: boolean; + maxHeight?: number; + } = { + handlerWidth: false + } +) { let dragHandle: HTMLElement | null; - let clone: HTMLElement | undefined; - + let clone: HTMLElement; let selectedElements: HTMLElement[] = []; function handleMouseDown(e: MouseEvent) { @@ -84,84 +49,75 @@ export function draggable(node: HTMLElement, initialOpts: DraggableConfig) { } function handleDragStart(e: DragEvent) { - let elt: HTMLElement | null = dragHandle; + e.stopPropagation(); - while (elt) { - if (elt.dataset.noDrag !== undefined) { - e.stopPropagation(); - e.preventDefault(); - return false; - } - elt = elt.parentElement; + if (dragHandle && dragHandle.dataset.noDrag !== undefined) { + e.preventDefault(); + return false; } - // If the draggable specifies a selector then we check if we're dragging selected elements if (opts.selector) { - // Checking for selected siblings in the parent of the parent container likely works - // for most use-cases but it was done here primarily for dragging multiple files. - const parentNode = node.parentNode?.parentNode; - selectedElements = parentNode - ? Array.from(parentNode.querySelectorAll(opts.selector).values() as Iterable) - : []; - - if (selectedElements.length > 0) { - clone = createContainerForMultiDrag(selectedElements, opts.extendWithClass); - // Dim the original element while dragging - selectedElements.forEach((element) => { - element.style.opacity = '0.5'; - }); + const parentNode = node.parentElement?.parentElement; + if (!parentNode) { + console.error('draggable parent node not found'); + return; } + selectedElements = Array.from( + parentNode.querySelectorAll(opts.selector) as NodeListOf + ); } - if (!clone) { - clone = cloneWithRotation(node, opts.extendWithClass); + if (selectedElements.length === 0) { + selectedElements = [node]; } + clone = createClone(opts, selectedElements); + if (params.handlerWidth) { + clone.style.width = node.clientWidth + 'px'; + } + if (params.maxHeight) { + clone.style.maxHeight = pxToRem(params.maxHeight); + } + + // console.log('selectedElements', selectedElements); + selectedElements.forEach((el) => el.classList.add('drag-handle')); document.body.appendChild(clone); Array.from(dropzoneRegistry.values()).forEach((dropzone) => { dropzone.register(opts.data); }); - // Get chromium to fire dragover & drop events - // https://stackoverflow.com/questions/6481094/html5-drag-and-drop-ondragover-not-firing-in-chrome/6483205#6483205 - e.dataTransfer?.setData('text/html', 'placeholder copy'); // cannot be empty string - e.dataTransfer?.setDragImage(clone, e.offsetX + 30, e.offsetY + 30); // Adds the padding - e.stopPropagation(); + if (e.dataTransfer) { + if (params.handlerWidth) { + e.dataTransfer.setDragImage(clone, e.offsetX, e.offsetY); + } else { + e.dataTransfer.setDragImage(clone, clone.offsetWidth - 20, 25); + } + + // Get chromium to fire dragover & drop events + // https://stackoverflow.com/questions/6481094/html5-drag-and-drop-ondragover-not-firing-in-chrome/6483205#6483205 + e.dataTransfer?.setData('text/html', 'd'); // cannot be empty string + e.dataTransfer.effectAllowed = 'uninitialized'; + } } function handleDragEnd(e: DragEvent) { - if (clone) { - clone.remove(); - clone = undefined; - } - - // reset the opacity of the selected elements - selectedElements.forEach((element) => { - element.style.opacity = '1'; - }); - + e.stopPropagation(); + if (clone) clone.remove(); + selectedElements.forEach((el) => el.classList.remove('drag-handle')); Array.from(dropzoneRegistry.values()).forEach((dropzone) => { dropzone.unregister(); }); - - e.stopPropagation(); } - const viewport = opts.viewportId ? document.getElementById(opts.viewportId) : null; - const triggerRange = 150; - const scrollSpeed = (viewport?.clientWidth || 500) / 2; - let lastDrag = new Date().getTime(); - function handleDrag(e: DragEvent) { + e.preventDefault(); + const viewport = opts.viewportId ? document.getElementById(opts.viewportId) : null; if (!viewport) return; - if (new Date().getTime() - lastDrag < 500) return; - lastDrag = new Date().getTime(); - + const triggerRange = 150; + const scrollSpeed = (viewport.clientWidth || 500) / 2; const viewportWidth = viewport.clientWidth; const relativeX = e.clientX - viewport.getBoundingClientRect().left; - - // Scroll horizontally if the draggable is near the edge of the viewport if (relativeX < triggerRange) { viewport.scrollBy(-scrollSpeed, 0); } else if (relativeX > viewportWidth - triggerRange) { @@ -189,12 +145,123 @@ export function draggable(node: HTMLElement, initialOpts: DraggableConfig) { setup(opts); return { - update(opts: DraggableConfig) { + update(newOpts: DraggableConfig) { clean(); - setup(opts); + setup(newOpts); }, destroy() { clean(); } }; } + +////////////////////////// +//// COMMIT DRAGGABLE //// +////////////////////////// + +export function createCommitElement( + commitType: CommitStatus | undefined, + label: string | undefined, + sha: string | undefined, + dateAndAuthor: string | undefined +): HTMLDivElement { + const cardEl = createElement('div', ['draggable-commit']) as HTMLDivElement; + const labelEl = createElement('span', ['text-base-13', 'text-bold'], label || 'Empty commit'); + const infoEl = createElement('div', ['draggable-commit-info', 'text-base-11']); + const shaEl = createElement('span', ['draggable-commit-info-text'], sha); + const dateAndAuthorEl = createElement('span', ['draggable-commit-info-text'], dateAndAuthor); + + if (commitType) { + const indicatorClass = `draggable-commit-${commitType}`; + labelEl.classList.add('draggable-commit-indicator', indicatorClass); + } + + cardEl.appendChild(labelEl); + infoEl.appendChild(shaEl); + infoEl.appendChild(dateAndAuthorEl); + cardEl.appendChild(infoEl); + return cardEl; +} + +export function draggableCommit(node: HTMLElement, initialOpts: DraggableConfig) { + function createClone(opts: DraggableConfig) { + return createCommitElement(opts.commitType, opts.label, opts.sha, opts.dateAndAuthor); + } + return setupDragHandlers(node, initialOpts, createClone, { + handlerWidth: true + }); +} + +//////////////////////// +//// FILE DRAGGABLE //// +//////////////////////// + +export function createChipsElement( + childrenAmount: number, + label: string | undefined, + filePath: string | undefined +): HTMLDivElement { + const containerEl = createElement('div', ['draggable-chip-container']) as HTMLDivElement; + const chipEl = createElement('div', ['draggable-chip']); + containerEl.appendChild(chipEl); + + if (filePath) { + const iconEl = createElement( + 'img', + ['draggable-chip-icon'], + undefined, + getVSIFileIcon(filePath) + ); + chipEl.appendChild(iconEl); + } + + const labelEl = createElement('span', ['text-base-12'], label); + chipEl.appendChild(labelEl); + + if (childrenAmount > 1) { + const amountTag = createElement( + 'div', + ['text-base-11', 'text-bold', 'draggable-chip-amount'], + childrenAmount.toString() + ); + chipEl.appendChild(amountTag); + } + + if (childrenAmount === 2) { + containerEl.classList.add('draggable-chip-two'); + } else if (childrenAmount > 2) { + containerEl.classList.add('draggable-chip-multiple'); + } + + return containerEl; +} + +export function draggableChips(node: HTMLElement, initialOpts: DraggableConfig) { + function createClone(opts: DraggableConfig, selectedElements: HTMLElement[]) { + return createChipsElement(selectedElements.length, opts.label, opts.filePath); + } + return setupDragHandlers(node, initialOpts, createClone); +} + +//////////////////////// +//// HUNK DRAGGABLE //// +//////////////////////// + +export function cloneElement(node: HTMLElement) { + const cloneEl = node.cloneNode(true) as HTMLElement; + + // exclude all ignored elements from the clone + const ignoredElements = Array.from(cloneEl.querySelectorAll('[data-remove-from-draggable]')); + ignoredElements.forEach((el) => el.remove()); + + return cloneEl; +} + +export function draggableElement(node: HTMLElement, initialOpts: DraggableConfig) { + function createClone() { + return cloneElement(node); + } + return setupDragHandlers(node, initialOpts, createClone, { + handlerWidth: true + }); +} diff --git a/app/src/lib/dropzone/CardOverlay.svelte b/app/src/lib/dropzone/CardOverlay.svelte index 76ae9e393..35e13dd87 100644 --- a/app/src/lib/dropzone/CardOverlay.svelte +++ b/app/src/lib/dropzone/CardOverlay.svelte @@ -1,48 +1,83 @@ -
-
- -

{label}

+
+
+
+ + + + + {label} +
+ + + + +
diff --git a/app/src/lib/dropzone/LineOverlay.svelte b/app/src/lib/dropzone/LineOverlay.svelte index 205d43050..d337b10a3 100644 --- a/app/src/lib/dropzone/LineOverlay.svelte +++ b/app/src/lib/dropzone/LineOverlay.svelte @@ -13,21 +13,21 @@
-
+
diff --git a/app/src/lib/file/FileCardHeader.svelte b/app/src/lib/file/FileCardHeader.svelte index f62a2e28f..ce22a1ee7 100644 --- a/app/src/lib/file/FileCardHeader.svelte +++ b/app/src/lib/file/FileCardHeader.svelte @@ -2,6 +2,7 @@ import FileStatusTag from './FileStatusTag.svelte'; import { getVSIFileIcon } from '$lib/ext-icons'; import Button from '$lib/shared/Button.svelte'; + import { splitFilePath } from '$lib/utils/filePath'; import { computeFileStatus } from '$lib/utils/fileStatus'; import { computeAddedRemovedByFiles } from '$lib/utils/metrics'; import { createEventDispatcher } from 'svelte'; @@ -14,22 +15,12 @@ $: fileStats = computeAddedRemovedByFiles(file); $: fileStatus = computeFileStatus(file); - function boldenFilename(filepath: string): { filename: string; path: string } { - const parts = filepath.split('/'); - if (parts.length === 0) return { filename: '', path: '' }; - - const filename = parts[parts.length - 1]; - const path = parts.slice(0, -1).join('/'); - - return { filename, path }; - } - - $: fileTitle = boldenFilename(file.path); + $: fileTitle = splitFilePath(file.path);
- +
{fileTitle.filename} diff --git a/app/src/lib/file/FileListItem.svelte b/app/src/lib/file/FileListItem.svelte index fa6f36b70..f37d1ce50 100644 --- a/app/src/lib/file/FileListItem.svelte +++ b/app/src/lib/file/FileListItem.svelte @@ -1,7 +1,7 @@
- {filePath.split('/').pop()} + {splitFilePath(filePath).filename} - {getPathOnly(filePath)} + {splitFilePath(filePath).path}
diff --git a/app/src/lib/hunk/HunkViewer.svelte b/app/src/lib/hunk/HunkViewer.svelte index d760537ad..82f75376a 100644 --- a/app/src/lib/hunk/HunkViewer.svelte +++ b/app/src/lib/hunk/HunkViewer.svelte @@ -1,6 +1,6 @@
dispatch('dragging', e.detail)} + on:dragging={(data) => dispatch('dragging', data.detail)} + on:scroll={(data) => { + const event = data.detail; + const target = event.target as HTMLDivElement; + + scrolled = target.scrollTop > 0; + }} />
@@ -75,7 +81,9 @@ width: 100%; } .contents { - display: block; + display: flex; + flex-direction: column; + min-height: 100%; min-width: 100%; } .scrolled { diff --git a/app/src/lib/shared/Scrollbar.svelte b/app/src/lib/shared/Scrollbar.svelte index ef78d9133..ab8e4cef3 100644 --- a/app/src/lib/shared/Scrollbar.svelte +++ b/app/src/lib/shared/Scrollbar.svelte @@ -61,6 +61,7 @@ const dispatch = createEventDispatcher<{ dragging: boolean; + scroll: Event; }>(); ///////////////////// @@ -190,9 +191,11 @@ }; } - function onScroll() { + function onScroll(e: Event) { if (!isScrollable) return; + dispatch('scroll', e); + clearTimer(); setupTimer(); @@ -276,6 +279,7 @@
* { + pointer-events: none; + } +} + +@keyframes dropzone-scale { + from { + opacity: 0; + transform: scale(0.96); + } + to { + opacity: 1; + transform: scale(1); + } +} diff --git a/app/src/styles/main.css b/app/src/styles/main.css index 4d14f8d7c..50e6d07f6 100644 --- a/app/src/styles/main.css +++ b/app/src/styles/main.css @@ -12,6 +12,7 @@ @import './text-input.css'; @import './commit-lines.css'; @import './markdown.css'; +@import './draggable.css'; /* CSS VARIABLES */ :root {