-
-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathMultimodalFileInput.svelte
More file actions
152 lines (134 loc) · 4.79 KB
/
Copy pathMultimodalFileInput.svelte
File metadata and controls
152 lines (134 loc) · 4.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
<script lang="ts">
import { type MultimodalFileInputProps, MultimodalFileInputState } from "$lib/components/files/index.svelte";
import { box } from "svelte-toolbelt";
import { RadioGroup } from "bits-ui";
import SingleFileInput from "$lib/components/files/SingleFileInput.svelte";
let { state = $bindable(), label = "File", required = false }: MultimodalFileInputProps = $props();
const instance = new MultimodalFileInputState({
state,
label: box.with(() => label),
required: box.with(() => required),
});
state = instance;
function handleDragOver(event: DragEvent) {
instance.dragActive = true;
event.preventDefault();
}
function handleDragLeave(event: DragEvent) {
if (event.currentTarget === event.target) {
instance.dragActive = false;
}
event.preventDefault();
}
async function handleDrop(event: DragEvent) {
instance.dragActive = false;
event.preventDefault();
if (!event.dataTransfer) {
return;
}
const types = event.dataTransfer.types;
const files = event.dataTransfer.files;
// Handle file drops
if (files.length > 1) {
alert("Only one file can be dropped at a time.");
return;
} else if (files.length === 1) {
instance.file = files[0];
instance.mode = "file";
return;
}
// Handle URL drops
if (types.includes("text/uri-list")) {
const urls = event.dataTransfer
.getData("text/uri-list")
.split("\n")
.filter((url) => url && !url.startsWith("#"));
if (urls.length > 1) {
alert("Only one URL can be dropped at a time.");
return;
} else if (urls.length === 1) {
instance.url = urls[0];
instance.mode = "url";
return;
}
}
// Handle plain text drops
if (types.includes("text/plain")) {
const text = event.dataTransfer.getData("text/plain");
if (text) {
instance.text = text;
instance.mode = "text";
return;
}
}
}
</script>
{#snippet radioItem(name: string)}
<RadioGroup.Item value={name.toLowerCase()}>
{#snippet children({ checked })}
<span class="rounded-sm px-1 py-0.5 text-sm" class:btn-ghost={!checked} class:border={!checked} class:btn-primary={checked}>
{name}
</span>
{/snippet}
</RadioGroup.Item>
{/snippet}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="file-drop-target w-full"
data-drag-active={instance.dragActive}
ondragover={handleDragOver}
ondrop={handleDrop}
ondragleavecapture={handleDragLeave}
>
<RadioGroup.Root class="mb-1 flex w-full gap-1" bind:value={instance.mode}>
{@render radioItem("File")}
{@render radioItem("URL")}
{@render radioItem("Text")}
</RadioGroup.Root>
{#if instance.mode === "file"}
{@render fileInput()}
{:else if instance.mode === "url"}
{@render urlInput()}
{:else if instance.mode === "text"}
{@render textInput()}
{/if}
</div>
<!-- TODO: Implement required prop for SingleFileInput -->
{#snippet fileInput()}
<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">
<span class="iconify size-4 shrink-0 text-em-disabled octicon--file-16"></span>
{#if instance.file}
{instance.file.name}
{:else}
<span class="font-light">{label}</span>
{/if}
<span class="iconify size-4 shrink-0 text-em-disabled octicon--triangle-down-16"></span>
</SingleFileInput>
{/snippet}
{#snippet urlInput()}
<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" />
{/snippet}
{#snippet textInput()}
<textarea title="{label} Text" bind:value={instance.text} placeholder="Enter text here" {required} class="w-full rounded-md border px-2 py-1"></textarea>
{/snippet}
<style>
.file-drop-target {
position: relative;
}
.file-drop-target[data-drag-active="true"]::before {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
content: "Drop here";
font-size: var(--text-3xl);
color: var(--color-black);
background-color: rgba(255, 255, 255, 0.7);
border: dashed var(--color-primary);
border-radius: inherit;
}
</style>