Skip to content

Commit 7ff86d8

Browse files
committed
Set relevant form elements required, support drag-dropping URLs and plain text
1 parent 18fff2e commit 7ff86d8

File tree

3 files changed

+57
-21
lines changed

3 files changed

+57
-21
lines changed

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

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44
import { RadioGroup } from "bits-ui";
55
import SingleFileInput from "$lib/components/files/SingleFileInput.svelte";
66
7-
let { label = "File", state = $bindable() }: MultimodalFileInputProps = $props();
7+
let { state = $bindable(), label = "File", required = false }: MultimodalFileInputProps = $props();
88
99
const instance = new MultimodalFileInputState({
10-
label: box.with(() => label),
1110
state,
11+
label: box.with(() => label),
12+
required: box.with(() => required),
1213
});
1314
state = instance;
1415
@@ -24,16 +25,51 @@
2425
event.preventDefault();
2526
}
2627
27-
async function handleFileDrop(event: DragEvent) {
28+
async function handleDrop(event: DragEvent) {
2829
instance.dragActive = false;
2930
event.preventDefault();
30-
const files = event.dataTransfer?.files;
31-
if (!files || files.length !== 1) {
31+
if (!event.dataTransfer) {
32+
return;
33+
}
34+
35+
const types = event.dataTransfer.types;
36+
const files = event.dataTransfer.files;
37+
38+
// Handle file drops
39+
if (files.length > 1) {
3240
alert("Only one file can be dropped at a time.");
3341
return;
42+
} else if (files.length === 1) {
43+
instance.file = files[0];
44+
instance.mode = "file";
45+
return;
46+
}
47+
48+
// Handle URL drops
49+
if (types.includes("text/uri-list")) {
50+
const urls = event.dataTransfer
51+
.getData("text/uri-list")
52+
.split("\n")
53+
.filter((url) => url && !url.startsWith("#"));
54+
if (urls.length > 1) {
55+
alert("Only one URL can be dropped at a time.");
56+
return;
57+
} else if (urls.length === 1) {
58+
instance.url = urls[0];
59+
instance.mode = "url";
60+
return;
61+
}
62+
}
63+
64+
// Handle plain text drops
65+
if (types.includes("text/plain")) {
66+
const text = event.dataTransfer.getData("text/plain");
67+
if (text) {
68+
instance.text = text;
69+
instance.mode = "text";
70+
return;
71+
}
3472
}
35-
instance.file = files[0];
36-
instance.mode = "file";
3773
}
3874
</script>
3975

@@ -52,7 +88,7 @@
5288
class="file-drop-target w-full"
5389
data-drag-active={instance.dragActive}
5490
ondragover={handleDragOver}
55-
ondrop={handleFileDrop}
91+
ondrop={handleDrop}
5692
ondragleavecapture={handleDragLeave}
5793
>
5894
<RadioGroup.Root class="mb-1 flex w-full gap-1" bind:value={instance.mode}>
@@ -69,6 +105,7 @@
69105
{/if}
70106
</div>
71107

108+
<!-- TODO: Implement required prop for SingleFileInput -->
72109
{#snippet fileInput()}
73110
<SingleFileInput bind:file={instance.file} class="flex w-fit items-center gap-2 rounded-md border btn-ghost px-2 py-1 has-focus-visible:outline-2">
74111
<span class="iconify size-4 shrink-0 text-em-disabled octicon--file-16"></span>
@@ -82,11 +119,11 @@
82119
{/snippet}
83120

84121
{#snippet urlInput()}
85-
<input title="{label} URL" placeholder="Enter file URL" bind:value={instance.url} type="url" class="w-full rounded-md border px-2 py-1" />
122+
<input title="{label} URL" bind:value={instance.url} placeholder="Enter file URL" type="url" {required} class="w-full rounded-md border px-2 py-1" />
86123
{/snippet}
87124

88125
{#snippet textInput()}
89-
<textarea title="{label} Text" bind:value={instance.text} placeholder="Enter text here" class="w-full rounded-md border px-2 py-1"></textarea>
126+
<textarea title="{label} Text" bind:value={instance.text} placeholder="Enter text here" {required} class="w-full rounded-md border px-2 py-1"></textarea>
90127
{/snippet}
91128

92129
<style>
@@ -103,7 +140,7 @@
103140
align-items: center;
104141
justify-content: center;
105142
106-
content: "Drop file here";
143+
content: "Drop here";
107144
font-size: var(--text-3xl);
108145
color: var(--color-black);
109146

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,12 +143,14 @@ export type MultimodalFileInputProps = {
143143
state?: MultimodalFileInputState | undefined;
144144

145145
label?: string | undefined;
146+
required?: boolean | undefined;
146147
};
147148

148149
export type MultimodalFileInputStateProps = {
149150
state: MultimodalFileInputState | undefined;
150151
} & ReadableBoxedValues<{
151152
label: string;
153+
required: boolean;
152154
}>;
153155

154156
export class MultimodalFileInputState {
@@ -158,10 +160,7 @@ export class MultimodalFileInputState {
158160
file: File | undefined = $state(undefined);
159161
url: string = $state("");
160162
private urlResolver = $derived.by(() => {
161-
let url = this.url;
162-
if (!url.startsWith("http://") && !url.startsWith("https://")) {
163-
url = `https://${url}`;
164-
}
163+
const url = this.url;
165164
return lazyPromise(async () => {
166165
let threw = false;
167166
try {

web/src/routes/LoadDiffDialog.svelte

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
const [aBinary, bBinary] = await Promise.all([isBinaryFile(blobA), isBinaryFile(blobB)]);
7474
if (aBinary || bBinary) {
7575
if (!isImageDiff) {
76-
alert("Cannot compare binary files.");
76+
alert("Cannot compare binary files (except image-to-image comparisons).");
7777
return;
7878
}
7979
}
@@ -290,7 +290,6 @@
290290
}
291291
292292
async function handleGithubUrl() {
293-
modalOpen = false;
294293
const url = new URL(githubUrl);
295294
// exclude hash + query params
296295
const test = url.protocol + "//" + url.hostname + url.pathname;
@@ -300,11 +299,11 @@
300299
301300
if (!match) {
302301
alert("Invalid GitHub URL. Use: https://github.com/owner/repo/(commit|pull|compare)/(id|ref_a...ref_b)");
303-
modalOpen = true;
304302
return;
305303
}
306304
307305
githubUrl = match[0];
306+
modalOpen = false;
308307
const success = await viewer.loadFromGithubApi(match);
309308
if (success) {
310309
await updateUrlParams({ githubUrl });
@@ -408,6 +407,7 @@
408407
<input
409408
id="githubUrl"
410409
type="url"
410+
required
411411
autocomplete="url"
412412
placeholder="https://github.com/"
413413
class="grow rounded-l-md border-t border-b border-l px-2 py-1 overflow-ellipsis"
@@ -464,7 +464,7 @@
464464
<span class="iconify size-6 shrink-0 octicon--file-diff-24"></span>
465465
From Patch File
466466
</h3>
467-
<MultimodalFileInput bind:state={patchFile} label="Patch File" />
467+
<MultimodalFileInput bind:state={patchFile} required label="Patch File" />
468468
<Button.Root type="submit" class="mt-1 rounded-md btn-primary px-2 py-1">Go</Button.Root>
469469
</form>
470470

@@ -482,11 +482,11 @@
482482
From Files
483483
</h3>
484484
<div class="flex flex-wrap items-center gap-1">
485-
<MultimodalFileInput bind:state={fileA} label="File A" />
485+
<MultimodalFileInput bind:state={fileA} required label="File A" />
486486
<div class="flex w-full">
487487
<span class="iconify size-4 shrink-0 octicon--arrow-down-16"></span>
488488
</div>
489-
<MultimodalFileInput bind:state={fileB} label="File B" />
489+
<MultimodalFileInput bind:state={fileB} required label="File B" />
490490
<Button.Root type="submit" class="rounded-md btn-primary px-2 py-1">Go</Button.Root>
491491
<Button.Root
492492
title="Swap File A and File B"

0 commit comments

Comments
 (0)