Skip to content

Commit dbd4730

Browse files
committed
feat(c-file-upload): paste image
Property paste-image to allow pasting an image directly from clipboard
1 parent 6d61960 commit dbd4730

File tree

1 file changed

+66
-2
lines changed

1 file changed

+66
-2
lines changed

src/ui/c-file-upload/c-file-upload.vue

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,44 @@ const props = withDefaults(defineProps<{
55
multiple?: boolean
66
accept?: string
77
title?: string
8+
pasteImage?: boolean
89
}>(), {
910
multiple: false,
1011
accept: undefined,
1112
title: 'Drag and drop files here, or click to select files',
13+
pasteImage: false,
1214
});
1315
1416
const emit = defineEmits<{
1517
(event: 'filesUpload', files: File[]): void
1618
(event: 'fileUpload', file: File): void
1719
}>();
1820
19-
const { multiple } = toRefs(props);
21+
const { multiple, pasteImage } = toRefs(props);
2022
2123
const isOverDropZone = ref(false);
2224
25+
function toBase64(file: File) {
26+
return new Promise<string>((resolve, reject) => {
27+
const reader = new FileReader();
28+
reader.readAsDataURL(file);
29+
reader.onload = () => resolve(reader.result?.toString() ?? '');
30+
reader.onerror = error => reject(error);
31+
});
32+
}
33+
2334
const fileInput = ref<HTMLInputElement | null>(null);
35+
const imgPreview = ref<HTMLImageElement | null>(null);
36+
async function handlePreview(image: File) {
37+
if (imgPreview.value) {
38+
imgPreview.value.src = await toBase64(image);
39+
}
40+
}
41+
function clearPreview() {
42+
if (imgPreview.value) {
43+
imgPreview.value.src = '';
44+
}
45+
}
2446
2547
function triggerFileInput() {
2648
fileInput.value?.click();
@@ -39,7 +61,30 @@ function handleDrop(event: DragEvent) {
3961
handleUpload(files);
4062
}
4163
42-
function handleUpload(files: FileList | null | undefined) {
64+
async function onPasteImage(evt: ClipboardEvent) {
65+
if (!pasteImage.value) {
66+
return false;
67+
}
68+
69+
const items = evt.clipboardData?.items;
70+
if (!items) {
71+
return false;
72+
}
73+
for (let i = 0; i < items.length; i++) {
74+
if (items[i].type.includes('image')) {
75+
const imageFile = items[i].getAsFile();
76+
if (imageFile) {
77+
await handlePreview(imageFile);
78+
emit('fileUpload', imageFile);
79+
}
80+
}
81+
}
82+
return true;
83+
}
84+
85+
async function handleUpload(files: FileList | null | undefined) {
86+
clearPreview();
87+
4388
if (_.isNil(files) || _.isEmpty(files)) {
4489
return;
4590
}
@@ -49,6 +94,7 @@ function handleUpload(files: FileList | null | undefined) {
4994
return;
5095
}
5196
97+
await handlePreview(files[0]);
5298
emit('fileUpload', files[0]);
5399
}
54100
</script>
@@ -60,6 +106,7 @@ function handleUpload(files: FileList | null | undefined) {
60106
'border-primary border-opacity-100': isOverDropZone,
61107
}"
62108
@click="triggerFileInput"
109+
@paste.prevent="onPasteImage"
63110
@drop.prevent="handleDrop"
64111
@dragover.prevent
65112
@dragenter="isOverDropZone = true"
@@ -73,6 +120,7 @@ function handleUpload(files: FileList | null | undefined) {
73120
:accept="accept"
74121
@change="handleFileInput"
75122
>
123+
76124
<slot>
77125
<span op-70>
78126
{{ title }}
@@ -90,6 +138,22 @@ function handleUpload(files: FileList | null | undefined) {
90138
<c-button>
91139
Browse files
92140
</c-button>
141+
142+
<div v-if="pasteImage">
143+
<!-- separator -->
144+
<div my-4 w-full flex items-center justify-center op-70>
145+
<div class="h-1px max-w-100px flex-1 bg-gray-300 op-50" />
146+
<div class="mx-2 text-gray-400">
147+
or
148+
</div>
149+
<div class="h-1px max-w-100px flex-1 bg-gray-300 op-50" />
150+
</div>
151+
152+
<p>Paste an image from clipboard</p>
153+
</div>
93154
</slot>
155+
<div mt-2>
156+
<img ref="imgPreview" width="150">
157+
</div>
94158
</div>
95159
</template>

0 commit comments

Comments
 (0)