Skip to content

Commit 91e633e

Browse files
committed
Animate swapping file/directory A & B, general new diff dialog adjustments
1 parent 575e7f0 commit 91e633e

File tree

4 files changed

+81
-68
lines changed

4 files changed

+81
-68
lines changed

web/src/lib/components/files/MultimodalFileInput.svelte

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
import SingleFileInput from "$lib/components/files/SingleFileInput.svelte";
66
import FileTypeSelect from "$lib/components/files/FileTypeSelect.svelte";
77
8-
let { state = $bindable(), label = "File", required = false, fileTypeOverride = true }: MultimodalFileInputProps = $props();
8+
let { state = $bindable(), label = "File", required = false, fileTypeOverride = true, defaultMode = "text" }: MultimodalFileInputProps = $props();
99
1010
const instance = new MultimodalFileInputState({
1111
state,
1212
label: box.with(() => label),
1313
required: box.with(() => required),
1414
fileTypeOverride: box.with(() => fileTypeOverride),
15+
defaultMode: box.with(() => defaultMode),
1516
});
1617
state = instance;
1718
@@ -76,10 +77,7 @@
7677
</script>
7778

7879
{#snippet radioItem(name: string)}
79-
<RadioGroup.Item
80-
value={name.toLowerCase()}
81-
class="rounded-sm px-2 text-sm data-[state=checked]:btn-primary data-[state=unchecked]:border data-[state=unchecked]:btn-ghost"
82-
>
80+
<RadioGroup.Item value={name.toLowerCase()} class="rounded-sm px-2 text-sm data-[state=checked]:btn-primary data-[state=unchecked]:btn-ghost">
8381
{name}
8482
</RadioGroup.Item>
8583
{/snippet}
@@ -93,10 +91,10 @@
9391
ondragleavecapture={handleDragLeave}
9492
>
9593
<div class="mb-1 flex w-full flex-wrap items-center gap-1">
96-
<RadioGroup.Root class="me-2 flex gap-1" bind:value={instance.mode}>
94+
<RadioGroup.Root class="me-2 flex overflow-hidden rounded-sm border" bind:value={instance.mode}>
9795
{@render radioItem("File")}
98-
{@render radioItem("URL")}
9996
{@render radioItem("Text")}
97+
{@render radioItem("URL")}
10098
</RadioGroup.Root>
10199
{#if fileTypeOverride}
102100
<FileTypeSelect allowAuto={instance.mode !== "text"} bind:value={() => instance.getFileType(), (v) => instance.setFileType(v)} />

web/src/lib/components/files/index.svelte.ts

Lines changed: 13 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ export type MultimodalFileInputProps = {
148148
label?: string | undefined;
149149
required?: boolean | undefined;
150150
fileTypeOverride?: boolean | undefined;
151+
defaultMode?: FileInputMode | undefined;
151152
};
152153

153154
export type MultimodalFileInputStateProps = {
@@ -156,11 +157,12 @@ export type MultimodalFileInputStateProps = {
156157
label: string;
157158
required: boolean;
158159
fileTypeOverride: boolean;
160+
defaultMode: FileInputMode;
159161
}>;
160162

161163
export class MultimodalFileInputState {
162164
private readonly opts: MultimodalFileInputStateProps;
163-
mode: FileInputMode = $state("file");
165+
mode: FileInputMode = $state("text");
164166
text: string = $state("");
165167
textType: FileType = $state("plaintext");
166168
file: File | undefined = $state(undefined);
@@ -199,26 +201,30 @@ export class MultimodalFileInputState {
199201
this.url = this.opts.state.url;
200202
this.urlType = this.opts.state.urlType;
201203
this.urlResolver = this.opts.state.urlResolver;
204+
} else {
205+
this.mode = this.opts.defaultMode.current ?? "text";
202206
}
203207
}
204208

205209
getFileType(): FileType {
206-
if (this.mode === "file") {
210+
const mode = this.mode;
211+
if (mode === "file") {
207212
return this.fileType;
208-
} else if (this.mode === "url") {
213+
} else if (mode === "url") {
209214
return this.urlType;
210-
} else if (this.mode === "text") {
215+
} else if (mode === "text") {
211216
return this.textType;
212217
}
213218
throw new Error("Invalid mode");
214219
}
215220

216221
setFileType(fileType: FileType) {
217-
if (this.mode === "file") {
222+
const mode = this.mode;
223+
if (mode === "file") {
218224
this.fileType = fileType;
219-
} else if (this.mode === "url") {
225+
} else if (mode === "url") {
220226
this.urlType = fileType;
221-
} else if (this.mode === "text") {
227+
} else if (mode === "text") {
222228
this.textType = fileType;
223229
} else {
224230
throw new Error("Invalid mode");
@@ -266,33 +272,4 @@ export class MultimodalFileInputState {
266272
this.file = undefined;
267273
this.url = "";
268274
}
269-
270-
swapState(other: MultimodalFileInputState) {
271-
const mode = this.mode;
272-
const text = this.text;
273-
const textType = this.textType;
274-
const file = this.file;
275-
const fileType = this.fileType;
276-
const url = this.url;
277-
const urlType = this.urlType;
278-
const urlResolver = this.urlResolver;
279-
280-
this.mode = other.mode;
281-
this.text = other.text;
282-
this.textType = other.textType;
283-
this.file = other.file;
284-
this.fileType = other.fileType;
285-
this.url = other.url;
286-
this.urlType = other.urlType;
287-
this.urlResolver = other.urlResolver;
288-
289-
other.mode = mode;
290-
other.text = text;
291-
other.textType = textType;
292-
other.file = file;
293-
other.fileType = fileType;
294-
other.url = url;
295-
other.urlType = urlType;
296-
other.urlResolver = urlResolver;
297-
}
298275
}

web/src/routes/+page.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
import { type Action } from "svelte/action";
2828
import { on } from "svelte/events";
2929
import ActionsPopover from "./ActionsPopover.svelte";
30-
import LoadDiffDialog from "./LoadDiffDialog.svelte";
30+
import OpenDiffDialog from "./OpenDiffDialog.svelte";
3131
import InfoPopup from "./InfoPopup.svelte";
3232
import { Button, Label } from "bits-ui";
3333
import { onClickOutside } from "runed";
@@ -244,7 +244,7 @@
244244
<DiffTitle meta={viewer.diffMetadata} />
245245
{/if}
246246
<div class="ml-auto flex h-fit flex-row gap-2">
247-
<LoadDiffDialog />
247+
<OpenDiffDialog />
248248
<ActionsPopover />
249249
{@render settingsPopover()}
250250
<InfoPopup>

web/src/routes/LoadDiffDialog.svelte renamed to web/src/routes/OpenDiffDialog.svelte

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import { DirectoryEntry, FileEntry, MultimodalFileInputState } from "$lib/components/files/index.svelte";
1313
import { SvelteSet } from "svelte/reactivity";
1414
import MultimodalFileInput from "$lib/components/files/MultimodalFileInput.svelte";
15+
import { flip } from "svelte/animate";
1516
1617
const viewer = MultiFileDiffViewerState.get();
1718
let modalOpen = $state(false);
@@ -20,11 +21,13 @@
2021
2122
let patchFile = $state<MultimodalFileInputState | undefined>();
2223
23-
let fileA = $state<MultimodalFileInputState | undefined>();
24-
let fileB = $state<MultimodalFileInputState | undefined>();
24+
let fileOne = $state<MultimodalFileInputState | undefined>();
25+
let fileTwo = $state<MultimodalFileInputState | undefined>();
26+
let flipFiles = $state(["1", "arrow", "2"]);
2527
26-
let dirA = $state<DirectoryEntry | undefined>();
27-
let dirB = $state<DirectoryEntry | undefined>();
28+
let dirOne = $state<DirectoryEntry | undefined>();
29+
let dirTwo = $state<DirectoryEntry | undefined>();
30+
let flipDirs = $state(["1", "arrow", "2"]);
2831
let dirBlacklistInput = $state<string>("");
2932
const defaultDirBlacklist = [".git/"];
3033
let dirBlacklist = new SvelteSet(defaultDirBlacklist);
@@ -57,6 +60,8 @@
5760
});
5861
5962
async function compareFiles() {
63+
const fileA = flipFiles[0] === "1" ? fileOne : fileTwo;
64+
const fileB = flipFiles[0] === "1" ? fileTwo : fileOne;
6065
if (!fileA || !fileB || !fileA.metadata || !fileB.metadata) {
6166
alert("Both files must be selected to compare.");
6267
return;
@@ -131,6 +136,8 @@
131136
};
132137
133138
async function compareDirs() {
139+
const dirA = flipDirs[0] === "1" ? dirOne : dirTwo;
140+
const dirB = flipDirs[0] === "1" ? dirTwo : dirOne;
134141
if (!dirA || !dirB) {
135142
alert("Both directories must be selected to compare.");
136143
return;
@@ -378,21 +385,21 @@
378385
{/snippet}
379386

380387
<Dialog.Root bind:open={modalOpen}>
381-
<Dialog.Trigger class="h-fit rounded-md btn-primary px-2 py-0.5">Load another diff</Dialog.Trigger>
388+
<Dialog.Trigger class="h-fit rounded-md btn-primary px-2 py-0.5">Open new diff</Dialog.Trigger>
382389
<Dialog.Portal>
383390
<Dialog.Overlay class="fixed inset-0 z-50 bg-black/50 dark:bg-white/20" />
384391
<Dialog.Content
385392
class="fixed top-1/2 left-1/2 z-50 max-h-svh w-192 max-w-full -translate-x-1/2 -translate-y-1/2 overflow-y-auto rounded-md bg-neutral shadow-md sm:max-w-[95%]"
386393
>
387394
<header class="sticky top-0 z-10 flex flex-row items-center justify-between rounded-t-md bg-neutral-2 p-4">
388-
<Dialog.Title class="text-xl font-semibold">Load a diff</Dialog.Title>
395+
<Dialog.Title class="text-xl font-semibold">Open New Diff</Dialog.Title>
389396
<Dialog.Close title="Close dialog" class="flex size-6 items-center justify-center rounded-md btn-ghost text-primary">
390397
<span class="iconify octicon--x-16" aria-hidden="true"></span>
391398
</Dialog.Close>
392399
</header>
393400

394401
<section class="flex flex-col p-4">
395-
<h3 class="mb-2 flex items-center gap-1 text-lg font-semibold">
402+
<h3 class="mb-4 flex items-center gap-1 text-lg font-semibold">
396403
<span class="iconify size-6 shrink-0 octicon--mark-github-24"></span>
397404
From GitHub
398405
</h3>
@@ -460,11 +467,11 @@
460467
handlePatchFile();
461468
}}
462469
>
463-
<h3 class="mb-2 flex items-center gap-1 text-lg font-semibold">
470+
<h3 class="mb-4 flex items-center gap-1 text-lg font-semibold">
464471
<span class="iconify size-6 shrink-0 octicon--file-diff-24"></span>
465472
From Patch File
466473
</h3>
467-
<MultimodalFileInput bind:state={patchFile} required fileTypeOverride={false} label="Patch File" />
474+
<MultimodalFileInput bind:state={patchFile} required fileTypeOverride={false} defaultMode="file" label="Patch File" />
468475
<Button.Root type="submit" class="mt-2 rounded-md btn-primary px-2 py-1">Go</Button.Root>
469476
</form>
470477

@@ -477,16 +484,24 @@
477484
compareFiles();
478485
}}
479486
>
480-
<h3 class="mb-2 flex items-center gap-1 text-lg font-semibold">
487+
<h3 class="mb-4 flex items-center gap-1 text-lg font-semibold">
481488
<span class="iconify size-6 shrink-0 octicon--file-24"></span>
482489
From Files
483490
</h3>
484-
<div class="mb-2 flex flex-wrap items-center gap-1">
485-
<MultimodalFileInput bind:state={fileA} required label="File A" />
486-
<div class="flex w-full">
487-
<span class="iconify size-4 shrink-0 octicon--arrow-down-16"></span>
488-
</div>
489-
<MultimodalFileInput bind:state={fileB} required label="File B" />
491+
<div class="mb-2 flex flex-col gap-1">
492+
{#each flipFiles as id, index (id)}
493+
<div animate:flip={{ duration: 250 }}>
494+
{#if id === "1"}
495+
<MultimodalFileInput bind:state={fileOne} required label={index === 0 ? "File A" : "File B"} />
496+
{:else if id === "arrow"}
497+
<div class="flex w-full">
498+
<span class="iconify size-4 shrink-0 octicon--arrow-down-16"></span>
499+
</div>
500+
{:else if id === "2"}
501+
<MultimodalFileInput bind:state={fileTwo} required label={index === 2 ? "File B" : "File A"} />
502+
{/if}
503+
</div>
504+
{/each}
490505
</div>
491506
<div class="flex items-center gap-1">
492507
<Button.Root type="submit" class="rounded-md btn-primary px-2 py-1">Go</Button.Root>
@@ -495,8 +510,9 @@
495510
type="button"
496511
class="flex size-6 items-center justify-center rounded-md btn-primary"
497512
onclick={() => {
498-
if (!fileA || !fileB) return;
499-
fileA.swapState(fileB);
513+
const a = flipFiles[0];
514+
flipFiles[0] = flipFiles[2];
515+
flipFiles[2] = a;
500516
}}
501517
>
502518
<span class="iconify size-4 shrink-0 octicon--arrow-switch-16" aria-hidden="true"></span>
@@ -513,7 +529,7 @@
513529
compareDirs();
514530
}}
515531
>
516-
<h3 class="mb-2 flex items-center gap-1 text-lg font-semibold">
532+
<h3 class="mb-4 flex items-center gap-1 text-lg font-semibold">
517533
<span class="iconify size-6 shrink-0 octicon--file-directory-24"></span>
518534
From Directories
519535
<InfoPopup>
@@ -522,10 +538,20 @@
522538
button should be preferred.
523539
</InfoPopup>
524540
</h3>
525-
<div class="flex flex-wrap items-center gap-1">
526-
<DirectorySelect bind:directory={dirA} placeholder="Directory A" />
527-
<span class="iconify size-4 shrink-0 octicon--arrow-right-16"></span>
528-
<DirectorySelect bind:directory={dirB} placeholder="Directory B" />
541+
<div class="mb-2 flex w-full flex-col gap-1">
542+
{#each flipDirs as id, index (id)}
543+
<div animate:flip={{ duration: 250 }} class="flex">
544+
{#if id === "1"}
545+
<DirectorySelect bind:directory={dirOne} placeholder={index === 0 ? "Directory A" : "Directory B"} />
546+
{:else if id === "arrow"}
547+
<span class="iconify size-4 shrink-0 octicon--arrow-down-16"></span>
548+
{:else if id === "2"}
549+
<DirectorySelect bind:directory={dirTwo} placeholder={index === 2 ? "Directory B" : "Directory A"} />
550+
{/if}
551+
</div>
552+
{/each}
553+
</div>
554+
<div class="flex items-center gap-1">
529555
<Button.Root type="submit" class="rounded-md btn-primary px-2 py-1">Go</Button.Root>
530556
<Popover.Root>
531557
<Popover.Trigger
@@ -541,6 +567,18 @@
541567
</Popover.Content>
542568
</Popover.Portal>
543569
</Popover.Root>
570+
<Button.Root
571+
title="Swap Directory A and Directory B"
572+
type="button"
573+
class="flex size-6 items-center justify-center rounded-md btn-primary"
574+
onclick={() => {
575+
const a = flipDirs[0];
576+
flipDirs[0] = flipDirs[2];
577+
flipDirs[2] = a;
578+
}}
579+
>
580+
<span class="iconify size-4 shrink-0 octicon--arrow-switch-16" aria-hidden="true"></span>
581+
</Button.Root>
544582
</div>
545583
</form>
546584
</Dialog.Content>

0 commit comments

Comments
 (0)