Merge pull request #3904 from gitbutlerapp/improve-file-id-selection

Sanitize file preview code
This commit is contained in:
Caleb Owens 2024-05-30 17:40:00 +02:00 committed by GitHub
commit a4878b234d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 246 additions and 204 deletions

View File

@ -7,7 +7,7 @@
import { selectFilesInList } from '$lib/utils/selectFilesInList';
import { maybeMoveSelection } from '$lib/utils/selection';
import { getCommitStore } from '$lib/vbranches/contexts';
import { FileIdSelection, fileKey } from '$lib/vbranches/fileIdSelection';
import { FileIdSelection, stringifyFileKey } from '$lib/vbranches/fileIdSelection';
import { sortLikeFileTree } from '$lib/vbranches/filetree';
import type { AnyFile } from '$lib/vbranches/types';
@ -76,13 +76,13 @@
{readonly}
{isUnapplied}
showCheckbox={showCheckboxes}
selected={$fileIdSelection.includes(fileKey(file.id, $commit?.id))}
selected={$fileIdSelection.includes(stringifyFileKey(file.id, $commit?.id))}
on:click={(e) => {
selectFilesInList(e, file, fileIdSelection, sortedFiles, allowMultiple, $commit);
}}
on:keydown={(e) => {
e.preventDefault();
maybeMoveSelection(e.key, sortedFiles, fileIdSelection);
maybeMoveSelection(e.key, file, sortedFiles, fileIdSelection);
}}
/>
{/each}

View File

@ -7,11 +7,9 @@
import { persisted } from '$lib/persisted/persisted';
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
import { getContext, getContextStoreBySymbol, createContextStore } from '$lib/utils/context';
import { isDefined } from '$lib/utils/typeguards';
import {
createLocalContextStore,
createRemoteContextStore,
createSelectedFiles,
createUnknownCommitsStore,
createUpstreamContextStore
} from '$lib/vbranches/contexts';
@ -46,22 +44,13 @@
const unknownCommits = createUnknownCommitsStore([]);
$: unknownCommits.set($upstreamCommits.filter((c) => !c.relatedTo));
const project = getContext(Project);
const fileIdSelection = new FileIdSelection();
setContext(FileIdSelection, fileIdSelection);
const selectedFiles = createSelectedFiles([]);
$: if ($fileIdSelection.length == 0) selectedFiles.set([]);
$: if ($fileIdSelection.length > 0 && fileIdSelection.only().commitId == 'undefined') {
selectedFiles.set(
$fileIdSelection
.map((fileId) => branch.files.find((f) => f.id + '|' + undefined == fileId))
.filter(isDefined)
);
}
$: selectedFile = fileIdSelection.selectedFile(branch.files, project.id);
$: displayedFile = $selectedFiles.length == 1 ? $selectedFiles[0] : undefined;
const project = getContext(Project);
const userSettings = getContextStoreBySymbol<Settings>(SETTINGS);
let rsViewport: HTMLElement;
@ -79,39 +68,41 @@
}
</script>
<div class="wrapper" data-tauri-drag-region class:file-selected={displayedFile}>
<div class="wrapper" data-tauri-drag-region>
<BranchCard {isUnapplied} {commitBoxOpen} bind:isLaneCollapsed />
{#if displayedFile}
<div
class="file-preview resize-viewport"
bind:this={rsViewport}
in:slide={{ duration: 180, easing: quintOut, axis: 'x' }}
style:width={`${fileWidth || $defaultFileWidthRem}rem`}
>
<FileCard
conflicted={displayedFile.conflicted}
file={displayedFile}
{isUnapplied}
readonly={displayedFile instanceof RemoteFile}
selectable={$commitBoxOpen && !isUnapplied}
on:close={() => {
fileIdSelection.clear();
}}
/>
<Resizer
viewport={rsViewport}
direction="right"
minWidth={400}
defaultLineColor="var(--clr-border-2)"
on:width={(e) => {
fileWidth = e.detail / (16 * $userSettings.zoom);
lscache.set(fileWidthKey + branch.id, fileWidth, 7 * 1440); // 7 day ttl
$defaultFileWidthRem = fileWidth;
}}
/>
</div>
{/if}
{#await $selectedFile then selected}
{#if selected}
<div
class="file-preview resize-viewport"
bind:this={rsViewport}
in:slide={{ duration: 180, easing: quintOut, axis: 'x' }}
style:width={`${fileWidth || $defaultFileWidthRem}rem`}
>
<FileCard
conflicted={selected.conflicted}
file={selected}
{isUnapplied}
readonly={selected instanceof RemoteFile}
selectable={$commitBoxOpen && !isUnapplied}
on:close={() => {
fileIdSelection.clear();
}}
/>
<Resizer
viewport={rsViewport}
direction="right"
minWidth={400}
defaultLineColor="var(--clr-border-2)"
on:width={(e) => {
fileWidth = e.detail / (16 * $userSettings.zoom);
lscache.set(fileWidthKey + branch.id, fileWidth, 7 * 1440); // 7 day ttl
$defaultFileWidthRem = fileWidth;
}}
/>
</div>
{/if}
{/await}
</div>
<style lang="postcss">

View File

@ -15,8 +15,7 @@
import { getTimeAgo } from '$lib/utils/timeAgo';
import { openExternalUrl } from '$lib/utils/url';
import { BranchController } from '$lib/vbranches/branchController';
import { createCommitStore, getSelectedFiles } from '$lib/vbranches/contexts';
import { FileIdSelection } from '$lib/vbranches/fileIdSelection';
import { createCommitStore } from '$lib/vbranches/contexts';
import { listRemoteCommitFiles } from '$lib/vbranches/remoteCommits';
import {
RemoteCommit,
@ -41,8 +40,6 @@
const branchController = getContext(BranchController);
const baseBranch = getContextStore(BaseBranch);
const project = getContext(Project);
const selectedFiles = getSelectedFiles();
const fileIdSelection = getContext(FileIdSelection);
const advancedCommitOperations = featureAdvancedCommitOperations();
const commitStore = createCommitStore(commit);
@ -55,12 +52,6 @@
let files: RemoteFile[] = [];
let showDetails = false;
$: selectedFile =
$fileIdSelection.length == 1 &&
fileIdSelection.only().commitId == commit.id &&
files.find((f) => f.id == fileIdSelection.only().fileId);
$: if (selectedFile) selectedFiles.set([selectedFile]);
async function loadFiles() {
files = await listRemoteCommitFiles(project.id, commit.id);
}
@ -320,7 +311,7 @@
{#if showDetails}
<div class="files-container">
<BranchFilesList title="Files" {files} {isUnapplied} />
<BranchFilesList title="Files" {files} {isUnapplied} readonly={type == 'upstream'} />
</div>
{/if}
</div>

View File

@ -1,15 +1,15 @@
<script lang="ts">
import FileContextMenu from './FileContextMenu.svelte';
import FileStatusIcons from './FileStatusIcons.svelte';
import { Project } from '$lib/backend/projects';
import Checkbox from '$lib/components/Checkbox.svelte';
import { draggable } from '$lib/dragging/draggable';
import { DraggableFile } from '$lib/dragging/draggables';
import { getVSIFileIcon } from '$lib/ext-icons';
import { getContext, maybeGetContextStore } from '$lib/utils/context';
import { updateFocus } from '$lib/utils/selection';
import { isDefined } from '$lib/utils/typeguards';
import { getCommitStore, getSelectedFiles } from '$lib/vbranches/contexts';
import { FileIdSelection, fileKey } from '$lib/vbranches/fileIdSelection';
import { getCommitStore } from '$lib/vbranches/contexts';
import { FileIdSelection } from '$lib/vbranches/fileIdSelection';
import { Ownership } from '$lib/vbranches/ownership';
import { Branch, type AnyFile } from '$lib/vbranches/types';
import { onDestroy } from 'svelte';
@ -24,9 +24,11 @@
const branch = maybeGetContextStore(Branch);
const selectedOwnership: Writable<Ownership> | undefined = maybeGetContextStore(Ownership);
const fileIdSelection = getContext(FileIdSelection);
const selectedFiles = getSelectedFiles();
const project = getContext(Project);
const commit = getCommitStore();
$: selectedFiles = fileIdSelection.files($branch?.files || [], project.id);
let checked = false;
let indeterminate = false;
let draggableElt: HTMLDivElement;
@ -67,15 +69,17 @@
data-locked={file.locked}
on:click
on:keydown
on:dragstart={() => {
on:dragstart={async () => {
// Reset selection if the file being dragged is not in the selected list
if ($fileIdSelection.length > 0 && !fileIdSelection.has(file.id, $commit?.id)) {
fileIdSelection.clear();
fileIdSelection.add(file.id, $commit?.id);
}
if ($selectedFiles.length > 0) {
$selectedFiles.forEach((f) => {
const files = await $selectedFiles;
if (files.length > 0) {
files.forEach((f) => {
if (f.locked) {
const lockedElement = document.getElementById(`file-${f.id}`);
@ -95,22 +99,22 @@
draggableElt.classList.remove('locked-file-animation');
}
}}
role="button"
tabindex="0"
use:draggable={{
data: new DraggableFile($branch?.id || '', file, $commit, selectedFiles),
data: $selectedFiles.then(
(files) => new DraggableFile($branch?.id || '', file, $commit, files)
),
disabled: readonly || isUnapplied,
viewportId: 'board-viewport',
selector: '.selected-draggable'
}}
role="button"
tabindex="0"
on:contextmenu|preventDefault={(e) => {
const files = fileIdSelection.has(file.id, $commit?.id)
? $fileIdSelection
.map((key) => $selectedFiles?.find((f) => fileKey(f.id, $commit?.id) == key))
.filter(isDefined)
: [file];
if (files.length > 0) popupMenu.openByMouse(e, { files });
else console.error('No files selected');
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}

View File

@ -8,7 +8,6 @@
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
import { getRemoteBranchData } from '$lib/stores/remoteBranches';
import { getContext, getContextStore, getContextStoreBySymbol } from '$lib/utils/context';
import { createSelectedFiles } from '$lib/vbranches/contexts';
import { FileIdSelection } from '$lib/vbranches/fileIdSelection';
import { BaseBranch, type RemoteBranch } from '$lib/vbranches/types';
import lscache from 'lscache';
@ -25,7 +24,7 @@
const fileIdSelection = new FileIdSelection();
setContext(FileIdSelection, fileIdSelection);
const selectedFiles = createSelectedFiles([]);
$: selectedFile = fileIdSelection.selectedFile([], project.id);
const defaultBranchWidthRem = 30;
const laneWidthKey = 'branchPreviewLaneWidth';
@ -34,8 +33,6 @@
let rsViewport: HTMLDivElement;
let laneWidth: number;
$: selected = $selectedFiles.length == 1 ? $selectedFiles[0] : undefined;
onMount(() => {
laneWidth = lscache.get(laneWidthKey);
});
@ -86,18 +83,20 @@
/>
</div>
<div class="base__right">
{#if selected}
<FileCard
conflicted={selected.conflicted}
file={selected}
isUnapplied={false}
readonly={true}
on:close={() => {
console.log(selected);
fileIdSelection.clear();
}}
/>
{/if}
{#await $selectedFile then selected}
{#if selected}
<FileCard
conflicted={selected.conflicted}
file={selected}
isUnapplied={false}
readonly={true}
on:close={() => {
console.log(selected);
fileIdSelection.clear();
}}
/>
{/if}
{/await}
</div>
</div>
@ -119,6 +118,7 @@
overflow-x: auto;
align-items: flex-start;
padding: var(--size-12) var(--size-12) var(--size-12) var(--size-6);
width: 50rem;
}
.branch-preview {

View File

@ -1,10 +1,11 @@
import { dzRegistry } from './dropzone';
import type { DraggableCommit, DraggableFile, DraggableHunk } from './draggables';
export type Draggable = DraggableFile | DraggableHunk | DraggableCommit;
export interface DraggableConfig {
readonly selector?: string;
readonly disabled?: boolean;
readonly data?: DraggableFile | DraggableHunk | DraggableCommit;
readonly data?: Draggable | Promise<Draggable>;
readonly viewportId?: string;
}
@ -118,63 +119,63 @@ export function draggable(node: HTMLElement, initialOpts: DraggableConfig) {
document.body.appendChild(clone);
// activate destination zones
dzRegistry
.filter(([_node, dz]) => dz.accepts(opts.data))
.forEach(([target, dz]) => {
function onDrop(e: DragEvent) {
e.preventDefault();
dz.onDrop(opts.data);
}
dzRegistry.forEach(async ([target, dz]) => {
if (!dz.accepts(await opts.data)) return;
function onDragEnter(e: DragEvent) {
e.preventDefault();
target.classList.add(dz.hover);
}
async function onDrop(e: DragEvent) {
e.preventDefault();
dz.onDrop(await opts.data);
}
function onDragLeave(e: DragEvent) {
e.preventDefault();
target.classList.remove(dz.hover);
}
function onDragEnter(e: DragEvent) {
e.preventDefault();
target.classList.add(dz.hover);
}
function onDragOver(e: DragEvent) {
e.preventDefault();
}
function onDragLeave(e: DragEvent) {
e.preventDefault();
target.classList.remove(dz.hover);
}
// keep track of listeners so that we can remove them later
if (onDropListeners.has(target)) {
onDropListeners.get(target)!.push(onDrop);
} else {
onDropListeners.set(target, [onDrop]);
}
function onDragOver(e: DragEvent) {
e.preventDefault();
}
if (onDragEnterListeners.has(target)) {
onDragEnterListeners.get(target)!.push(onDragEnter);
} else {
onDragEnterListeners.set(target, [onDragEnter]);
}
// keep track of listeners so that we can remove them later
if (onDropListeners.has(target)) {
onDropListeners.get(target)!.push(onDrop);
} else {
onDropListeners.set(target, [onDrop]);
}
if (onDragLeaveListeners.has(target)) {
onDragLeaveListeners.get(target)!.push(onDragLeave);
} else {
onDragLeaveListeners.set(target, [onDragLeave]);
}
if (onDragEnterListeners.has(target)) {
onDragEnterListeners.get(target)!.push(onDragEnter);
} else {
onDragEnterListeners.set(target, [onDragEnter]);
}
if (onDragOverListeners.has(target)) {
onDragOverListeners.get(target)!.push(onDragOver);
} else {
onDragOverListeners.set(target, [onDragOver]);
}
if (onDragLeaveListeners.has(target)) {
onDragLeaveListeners.get(target)!.push(onDragLeave);
} else {
onDragLeaveListeners.set(target, [onDragLeave]);
}
// https://stackoverflow.com/questions/14203734/dragend-dragenter-and-dragleave-firing-off-immediately-when-i-drag
setTimeout(() => {
target.classList.add(dz.active);
}, 10);
if (onDragOverListeners.has(target)) {
onDragOverListeners.get(target)!.push(onDragOver);
} else {
onDragOverListeners.set(target, [onDragOver]);
}
target.addEventListener('drop', onDrop);
target.addEventListener('dragenter', onDragEnter);
target.addEventListener('dragleave', onDragLeave);
target.addEventListener('dragover', onDragOver);
});
// https://stackoverflow.com/questions/14203734/dragend-dragenter-and-dragleave-firing-off-immediately-when-i-drag
setTimeout(() => {
target.classList.add(dz.active);
}, 10);
target.addEventListener('drop', onDrop);
target.addEventListener('dragenter', onDragEnter);
target.addEventListener('dragleave', onDragLeave);
target.addEventListener('dragover', onDragOver);
});
// Get chromium to fire dragover & drop events
// https://stackoverflow.com/questions/6481094/html5-drag-and-drop-ondragover-not-firing-in-chrome/6483205#6483205
@ -195,26 +196,25 @@ export function draggable(node: HTMLElement, initialOpts: DraggableConfig) {
});
// deactivate destination zones
dzRegistry
.filter(([_node, dz]) => dz.accepts(opts.data))
.forEach(([node, dz]) => {
// remove all listeners
onDropListeners.get(node)?.forEach((listener) => {
node.removeEventListener('drop', listener);
});
onDragEnterListeners.get(node)?.forEach((listener) => {
node.removeEventListener('dragenter', listener);
});
onDragLeaveListeners.get(node)?.forEach((listener) => {
node.removeEventListener('dragleave', listener);
});
onDragOverListeners.get(node)?.forEach((listener) => {
node.removeEventListener('dragover', listener);
});
node.classList.remove(dz.active);
node.classList.remove(dz.hover);
dzRegistry.forEach(async ([node, dz]) => {
if (!dz.accepts(await opts.data)) return;
// remove all listeners
onDropListeners.get(node)?.forEach((listener) => {
node.removeEventListener('drop', listener);
});
onDragEnterListeners.get(node)?.forEach((listener) => {
node.removeEventListener('dragenter', listener);
});
onDragLeaveListeners.get(node)?.forEach((listener) => {
node.removeEventListener('dragleave', listener);
});
onDragOverListeners.get(node)?.forEach((listener) => {
node.removeEventListener('dragover', listener);
});
node.classList.remove(dz.active);
node.classList.remove(dz.hover);
});
e.stopPropagation();
}

View File

@ -1,4 +1,3 @@
import { get, type Readable } from 'svelte/store';
import type { AnyCommit, AnyFile, Commit, Hunk, RemoteCommit } from '../vbranches/types';
export function nonDraggable() {
@ -20,12 +19,11 @@ export class DraggableFile {
public readonly branchId: string,
public file: AnyFile,
public commit: AnyCommit | undefined,
private selection: Readable<AnyFile[]> | undefined
private selection: AnyFile[] | undefined
) {}
get files(): AnyFile[] {
const selection = this.selection ? get(this.selection) : undefined;
if (selection && selection.length > 0) return selection;
if (this.selection && this.selection.length > 0) return this.selection;
return [this.file];
}
}

View File

@ -0,0 +1,12 @@
// TODO: Look into what browser versions we need to support and if we can use Object.groupBy
export function groupBy<T>(array: T[], callback: (item: T) => string) {
const groups: { [key: string]: T[] } = {};
for (const item of array) {
const key = callback(item);
if (!groups[key]) groups[key] = [];
groups[key].push(item);
}
return groups;
}

View File

@ -1,4 +1,4 @@
import { fileKey, type FileIdSelection } from '$lib/vbranches/fileIdSelection';
import { stringifyFileKey, type FileIdSelection } from '$lib/vbranches/fileIdSelection';
import { get } from 'svelte/store';
import type { AnyCommit, AnyFile } from '$lib/vbranches/types';
@ -22,7 +22,7 @@ export function selectFilesInList(
}
} else if (e.shiftKey && allowMultiple) {
const initiallySelectedIndex = sortedFiles.findIndex(
(file) => fileKey(file.id, undefined) == selectedFileIds[0]
(file) => stringifyFileKey(file.id, undefined) == selectedFileIds[0]
);
// detect the direction of the selection
@ -40,7 +40,7 @@ export function selectFilesInList(
) + 1
);
selectedFileIds = updatedSelection.map((f) => fileKey(f.id, commit?.id));
selectedFileIds = updatedSelection.map((f) => stringifyFileKey(f.id, commit?.id));
if (selectionDirection === 'down') {
selectedFileIds = selectedFileIds.reverse();
@ -51,7 +51,7 @@ export function selectFilesInList(
if (selectedFileIds.length == 1 && isAlreadySelected) {
fileIdSelection.clear();
} else {
fileIdSelection.set([fileKey(file.id, commit?.id)]);
fileIdSelection.set([stringifyFileKey(file.id, commit?.id)]);
}
}
}

View File

@ -35,22 +35,22 @@ export function updateFocus(
fileIdSelection: FileIdSelection,
commitId?: string
) {
if (fileIdSelection.length != 1) return;
const selected = fileIdSelection.only();
if (!selected) return;
if (selected.fileId == file.id && selected.commitId == commitId) elt.focus();
}
export function maybeMoveSelection(
key: string,
file: AnyFile,
files: AnyFile[],
selectedFileIds: FileIdSelection
fileIdSelection: FileIdSelection
) {
if (key != 'ArrowUp' && key != 'ArrowDown') return;
if (selectedFileIds.length == 0) return;
const newSelection = getFileByKey(key, selectedFileIds.only().fileId, files);
const newSelection = getFileByKey(key, file.id, files);
if (newSelection) {
selectedFileIds.clear();
selectedFileIds.add(newSelection.id);
fileIdSelection.clear();
fileIdSelection.add(newSelection.id);
}
}

View File

@ -1,6 +1,5 @@
import { buildContextStore } from '$lib/utils/context';
import type { AnyCommit, AnyFile, Commit, RemoteCommit } from './types';
import type { Writable } from 'svelte/store';
import type { AnyCommit, Commit, RemoteCommit } from './types';
// When we can't use type for context objects we build typed getter/setter pairs
// to avoid using symbols explicitly.
@ -12,10 +11,6 @@ export const [getUpstreamCommits, createUpstreamContextStore] =
buildContextStore<RemoteCommit[]>('upstreamCommits');
export const [getUnknownCommits, createUnknownCommitsStore] =
buildContextStore<RemoteCommit[]>('unknownCommits');
export const [getSelectedFiles, createSelectedFiles] = buildContextStore<
AnyFile[],
Writable<AnyFile[]>
>('selectedFiles');
export const [getCommitStore, createCommitStore] = buildContextStore<AnyCommit | undefined>(
'commit'
);

View File

@ -1,7 +1,26 @@
export function fileKey(fileId: string, commitId?: string) {
import { isDefined } from '$lib/utils/typeguards';
import { listRemoteCommitFiles } from '$lib/vbranches/remoteCommits';
import { derived } from 'svelte/store';
import type { AnyFile, LocalFile } from '$lib/vbranches/types';
export interface FileKey {
fileId: string;
commitId?: string;
}
export function stringifyFileKey(fileId: string, commitId?: string) {
return fileId + '|' + commitId;
}
export function parseFileKey(fileKeyString: string): FileKey {
const [fileId, commitId] = fileKeyString.split('|');
return {
fileId,
commitId: commitId == 'undefined' ? undefined : commitId
};
}
export type SelectedFile = {
context?: string;
fileId: string;
@ -13,9 +32,9 @@ export class FileIdSelection {
private value: string[];
private callbacks: CallBack[];
constructor() {
constructor(value: FileKey[] = []) {
this.callbacks = [];
this.value = [];
this.value = value.map((key) => stringifyFileKey(key.fileId, key.commitId));
}
subscribe(callback: (value: string[]) => void) {
@ -29,16 +48,16 @@ export class FileIdSelection {
}
add(fileId: string, commitId?: string) {
this.value.push(fileKey(fileId, commitId));
this.value.push(stringifyFileKey(fileId, commitId));
this.emit();
}
has(fileId: string, commitId?: string) {
return this.value.includes(fileKey(fileId, commitId));
return this.value.includes(stringifyFileKey(fileId, commitId));
}
remove(fileId: string, commitId?: string) {
this.value = this.value.filter((key) => key != fileKey(fileId, commitId));
this.value = this.value.filter((key) => key != stringifyFileKey(fileId, commitId));
this.emit();
}
@ -62,12 +81,42 @@ export class FileIdSelection {
}
}
only() {
const [fileId, commitId] = this.value[0].split('|');
return { fileId, commitId };
only(): FileKey | undefined {
if (this.value.length == 0) return;
const fileKey = parseFileKey(this.value[0]);
return fileKey;
}
selectedFile(localFiles: LocalFile[], branchId: string) {
return derived(this, async (value): Promise<AnyFile | undefined> => {
if (value.length != 1) return;
const fileKey = parseFileKey(value[0]);
return await findFileByKey(localFiles, branchId, fileKey);
});
}
files(localFiles: LocalFile[], branchId: string) {
return derived(this, async (value) => {
const files = await Promise.all(
value.map(async (fileKey) => {
return await findFileByKey(localFiles, branchId, parseFileKey(fileKey));
})
);
return files.filter(isDefined);
});
}
get length() {
return this.value.length;
}
}
export async function findFileByKey(localFiles: LocalFile[], projectId: string, key: FileKey) {
if (key.commitId) {
const remoteFiles = await listRemoteCommitFiles(projectId, key.commitId);
return remoteFiles.find((file) => file.id == key.fileId);
} else {
return localFiles.find((file) => file.id == key.fileId);
}
}

View File

@ -1,4 +1,5 @@
<script lang="ts">
import { Project } from '$lib/backend/projects';
import BaseBranch from '$lib/components/BaseBranch.svelte';
import FileCard from '$lib/components/FileCard.svelte';
import FullviewLoading from '$lib/components/FullviewLoading.svelte';
@ -7,7 +8,6 @@
import { SETTINGS, type Settings } from '$lib/settings/userSettings';
import { getContext, getContextStoreBySymbol } from '$lib/utils/context';
import { BaseBranchService } from '$lib/vbranches/baseBranch';
import { createSelectedFiles } from '$lib/vbranches/contexts';
import { FileIdSelection } from '$lib/vbranches/fileIdSelection';
import lscache from 'lscache';
import { onMount, setContext } from 'svelte';
@ -17,17 +17,17 @@
const baseBranchService = getContext(BaseBranchService);
const baseBranch = baseBranchService.base;
const project = getContext(Project);
const fileIdSelection = new FileIdSelection();
setContext(FileIdSelection, fileIdSelection);
const selectedFiles = createSelectedFiles([]);
$: selectedFile = fileIdSelection.selectedFile([], project.id);
let rsViewport: HTMLDivElement;
let laneWidth: number;
$: error$ = baseBranchService.error$;
$: selected = $selectedFiles.length == 1 ? $selectedFiles[0] : undefined;
onMount(() => {
laneWidth = lscache.get(laneWidthKey);
@ -61,17 +61,19 @@
/>
</div>
<div class="base__right">
{#if selected}
<FileCard
conflicted={selected.conflicted}
file={selected}
isUnapplied={false}
readonly={true}
on:close={() => {
fileIdSelection.clear();
}}
/>
{/if}
{#await $selectedFile then selected}
{#if selected}
<FileCard
conflicted={selected.conflicted}
file={selected}
isUnapplied={false}
readonly={true}
on:close={() => {
fileIdSelection.clear();
}}
/>
{/if}
{/await}
</div>
</div>
{/if}