Skip to content

Commit 597f640

Browse files
authored
Merge pull request #243 from it-at-m/MPDZBS-842-dateiupload
File drag and drop zone
2 parents 27198db + 7511b85 commit 597f640

File tree

5 files changed

+387
-0
lines changed

5 files changed

+387
-0
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<template>
2+
<svg
3+
aria-hidden="true"
4+
width="108"
5+
height="56"
6+
viewBox="0 0 108 56"
7+
fill="none"
8+
xmlns="http://www.w3.org/2000/svg"
9+
>
10+
<g clip-path="url(#clip0_875_841)">
11+
<path
12+
d="M107.655 18.699L106.594 11.0435L98.9873 12.1173"
13+
stroke="#005A9F"
14+
stroke-miterlimit="10"
15+
stroke-linecap="round"
16+
/>
17+
<path
18+
d="M106.594 11.0435C106.594 11.0435 94.1981 33.255 58.2578 27.2483"
19+
stroke="#005A9F"
20+
stroke-miterlimit="10"
21+
stroke-linecap="round"
22+
/>
23+
<path
24+
d="M85.5371 32.8255C92.2897 31.142 97.5195 28.1144 101.261 25.2393"
25+
stroke="#005A9F"
26+
stroke-miterlimit="10"
27+
stroke-linecap="round"
28+
/>
29+
<path
30+
d="M0.344727 45.982C0.344727 45.982 13.4778 24.2 49.1976 31.426"
31+
stroke="#005A9F"
32+
stroke-miterlimit="10"
33+
stroke-linecap="round"
34+
/>
35+
<path
36+
d="M22.1184 24.9275C15.3107 26.3755 9.98438 29.2299 6.15332 31.9734"
37+
stroke="#005A9F"
38+
stroke-miterlimit="10"
39+
stroke-linecap="round"
40+
/>
41+
<path
42+
d="M68.3603 2.69507H37.1055V55.6536H77.5314V11.9164L68.3603 2.69507Z"
43+
fill="#005A9F"
44+
stroke="#005A9F"
45+
stroke-linecap="round"
46+
stroke-linejoin="round"
47+
/>
48+
<path
49+
d="M73.7345 53.3119H33.3086V0.346436H64.5634L73.7345 9.57469V53.3119Z"
50+
fill="white"
51+
stroke="#005A9F"
52+
stroke-linejoin="round"
53+
/>
54+
<path
55+
d="M38.5039 14.4868H45.3667"
56+
stroke="#005A9F"
57+
stroke-linejoin="round"
58+
/>
59+
<path
60+
d="M38.5039 21.1584H68.5391"
61+
stroke="#005A9F"
62+
stroke-linejoin="round"
63+
/>
64+
<path
65+
d="M38.5039 27.8372H68.5391"
66+
stroke="#005A9F"
67+
stroke-linejoin="round"
68+
/>
69+
<path
70+
d="M38.5039 34.509H68.5391"
71+
stroke="#005A9F"
72+
stroke-linejoin="round"
73+
/>
74+
<path
75+
d="M38.5039 41.1877H68.5391"
76+
stroke="#005A9F"
77+
stroke-linejoin="round"
78+
/>
79+
<path
80+
d="M73.7346 9.57469H64.5635V0.346436L73.7346 9.57469Z"
81+
fill="white"
82+
stroke="#005A9F"
83+
stroke-linejoin="round"
84+
/>
85+
</g>
86+
<defs>
87+
<clipPath id="clip0_875_841">
88+
<rect
89+
width="108"
90+
height="56"
91+
fill="white"
92+
/>
93+
</clipPath>
94+
</defs>
95+
</svg>
96+
</template>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import MucFileDropzone from "./MucFileDropzone.vue";
2+
3+
export default {
4+
component: MucFileDropzone,
5+
title: "MucFileDropzone",
6+
tags: ["autodocs"],
7+
parameters: {
8+
docs: {
9+
description: {
10+
component: `The \`muc-file-dropzone\` component provides a drop zone for selecting one or multiple files. It does not upload the files but emits an array of files for further handling, e. g. the real upload to a backend.`,
11+
},
12+
},
13+
},
14+
};
15+
16+
export const Example = {
17+
args: {
18+
buttonText: "Dokument hochladen",
19+
additionalInformation: "Maximale Dateigröße: 2 MB",
20+
invalidAmountWarning: "Es kann nur eine Datei hochgeladen werden.",
21+
maxFileSize: 2,
22+
maxFileSizeWarning:
23+
"Eine Datei hat mehr als 2 MB und kann nicht angefügt werden.",
24+
maxTotalFileSize: 10,
25+
maxTotalFileSizeWarning:
26+
"Die Dateien haben zusammen mehr als 10 MB und können nicht angefügt werden.",
27+
},
28+
};
29+
export const Default = {};
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
<template>
2+
<div
3+
class="drop-zone"
4+
:class="{
5+
'is-dragover': isDragOver,
6+
'is-not-disabled': !disabled,
7+
}"
8+
@dragover.prevent="onDragOver"
9+
@dragleave.prevent="onDragLeave"
10+
@drop.prevent="onDrop"
11+
@click="selectFiles"
12+
>
13+
<IconFileUpload />
14+
<MucButton
15+
variant="secondary"
16+
icon="upload"
17+
:disabled="disabled"
18+
>
19+
{{ buttonText }}
20+
</MucButton>
21+
<small>{{ additionalInformation }}</small>
22+
</div>
23+
<span
24+
v-if="!validFileSizes && maxFileSizeWarning"
25+
class="m-error-message"
26+
>
27+
<MucIcon icon="warning" />
28+
{{ maxFileSizeWarning }}
29+
</span>
30+
<span
31+
v-if="!validTotalFileSizes && maxTotalFileSizeWarning"
32+
class="m-error-message"
33+
>
34+
<MucIcon icon="warning" />
35+
{{ maxTotalFileSizeWarning }}
36+
</span>
37+
<span
38+
v-if="!validFilesAmount"
39+
class="m-error-message"
40+
>
41+
<MucIcon icon="warning" />
42+
{{ invalidAmountWarning }}
43+
</span>
44+
</template>
45+
46+
<script setup lang="ts">
47+
import { onMounted, onUpdated, ref } from "vue";
48+
49+
import { MucButton } from "../Button";
50+
import { MucIcon } from "../Icon";
51+
import IconFileUpload from "./IconFileUpload.vue";
52+
53+
const {
54+
buttonText = "Upload file",
55+
additionalInformation,
56+
disabled = false,
57+
multiple = true,
58+
invalidAmountWarning,
59+
maxFileSize = 0,
60+
maxFileSizeWarning,
61+
maxTotalFileSize = 0,
62+
maxTotalFileSizeWarning,
63+
} = defineProps<{
64+
/**
65+
* Text on the upload button
66+
*/
67+
buttonText: string;
68+
/**
69+
* Additional Information, e.g. max file size
70+
*/
71+
additionalInformation?: string;
72+
/**
73+
* Flag to disable the upload field
74+
*/
75+
disabled?: boolean;
76+
/**
77+
* Flag to switch between multiple and single file upload
78+
*/
79+
multiple?: boolean;
80+
/**
81+
* Warning for invalid amount of files
82+
*/
83+
invalidAmountWarning?: string;
84+
/**
85+
* Maximum file size in MByte
86+
*/
87+
maxFileSize?: number;
88+
/**
89+
* Warning for invalid file size
90+
*/
91+
maxFileSizeWarning?: string;
92+
/**
93+
* Maximum file size sum in MByte
94+
*/
95+
maxTotalFileSize?: number;
96+
/**
97+
* Warning for invalid file size sum
98+
*/
99+
maxTotalFileSizeWarning?: string;
100+
}>();
101+
102+
const emit = defineEmits([
103+
/**
104+
* Dropped files as {@link File[]} array
105+
*/
106+
"files",
107+
]);
108+
109+
/** Flag signals if file size is valid */
110+
const validFileSizes = ref(true);
111+
112+
const validTotalFileSizes = ref(true);
113+
114+
/** Flag signals if files amount is valid */
115+
const validFilesAmount = ref(true);
116+
117+
/** Flag signals if files where dragged over the drop zone */
118+
const isDragOver = ref(false);
119+
120+
/** Hidden HTML input field that opens the file explorer */
121+
const fileInput = document.createElement("input");
122+
123+
/**
124+
* Configuration of the HTML input field.
125+
*/
126+
onMounted(() => {
127+
fileInput.type = "file";
128+
fileInput.multiple = multiple;
129+
/* catch 'onchange' event and trigger emit to the surrounding element */
130+
fileInput.onchange = (event) => {
131+
const target = event.target as HTMLInputElement;
132+
if (target?.files && target.files.length > 0) {
133+
const filesArray = Array.from(target.files);
134+
_emitFiles(filesArray);
135+
}
136+
};
137+
});
138+
139+
/**
140+
* Pass property {multiple} to the input tag.
141+
*/
142+
onUpdated(() => {
143+
fileInput.multiple = multiple;
144+
});
145+
146+
/**
147+
* Opens the file explorer by firing a click event to the hidden file input field if the property {disabled} is false.
148+
*/
149+
const selectFiles = () => {
150+
if (disabled) return;
151+
fileInput.click();
152+
};
153+
154+
/** Sets flag {@link isDragOver} true. */
155+
const onDragOver = (event: DragEvent) => {
156+
if (disabled) return;
157+
if (!fileInput?.multiple) {
158+
const dataTransfer: DataTransfer = event.dataTransfer as DataTransfer;
159+
if (dataTransfer?.items?.length > 1) {
160+
validFilesAmount.value = false;
161+
return;
162+
}
163+
}
164+
isDragOver.value = true;
165+
};
166+
167+
/** Sets flag {@link isDragOver} false. */
168+
const onDragLeave = () => {
169+
isDragOver.value = false;
170+
validFilesAmount.value = true;
171+
};
172+
173+
/**
174+
* Catches a DragEvent in the drop zone and emits dropped files to the surrounding element if the property {@link Props#disabled} is false.
175+
* @param {DragEvent} event dropped files
176+
*/
177+
const onDrop = (event: DragEvent) => {
178+
if (disabled) return;
179+
if (!validFilesAmount.value) {
180+
/*
181+
user drops files with invalid amount
182+
-> warning disappears
183+
*/
184+
validFilesAmount.value = true;
185+
return;
186+
}
187+
isDragOver.value = false;
188+
const dataTransfer: DataTransfer = event.dataTransfer as DataTransfer;
189+
if (dataTransfer?.files?.length > 0) {
190+
const filesArray = Array.from(dataTransfer.files);
191+
_emitFiles(filesArray);
192+
}
193+
};
194+
195+
/**
196+
* Emits the files to upload to the surrounding element.
197+
* @param {File[]} files array of dropped or chosen files to upload
198+
*/
199+
const _emitFiles = (files: File[]) => {
200+
validFileSizes.value = _areFilesValid(files);
201+
validTotalFileSizes.value = _isTotalFilesSumValid(files);
202+
203+
if (!validFileSizes.value || !validTotalFileSizes.value) {
204+
return;
205+
}
206+
207+
emit("files", files);
208+
};
209+
210+
/**
211+
* Checks if all files are inside the allowed file size range that is given by {@link Props#maxFileSize}.
212+
* @param {File[]} files array files
213+
*/
214+
const _areFilesValid = (files: File[]) => {
215+
if (maxFileSize)
216+
return !files.some((file) => file.size > maxFileSize * 1024 * 1024);
217+
return true;
218+
};
219+
220+
/**
221+
* Checks if the sum of all files is inside the allowed range that is given by {@link Props#maxTotalFileSize}.
222+
* @param {File[]} files array files
223+
*/
224+
const _isTotalFilesSumValid = (files: File[]) => {
225+
if (maxTotalFileSize)
226+
return (
227+
files.reduce((acc, file) => acc + (file.size || 0), 0) <=
228+
maxTotalFileSize * 1024 * 1024
229+
);
230+
return true;
231+
};
232+
</script>
233+
234+
<style scoped>
235+
.drop-zone {
236+
background-color: #fff;
237+
width: 100%;
238+
height: 100%;
239+
position: relative;
240+
border: 2px dashed #337bb2;
241+
display: flex;
242+
flex-direction: column;
243+
justify-content: center;
244+
align-items: center;
245+
gap: 15px;
246+
padding: 20px;
247+
margin-bottom: 20px;
248+
}
249+
250+
.drop-zone.is-dragover.is-not-disabled {
251+
background-color: #f1f1f1;
252+
}
253+
254+
.drop-zone.is-not-disabled {
255+
cursor: pointer;
256+
}
257+
</style>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import MucFileDropzone from "./MucFileDropzone.vue";
2+
3+
export { MucFileDropzone };

0 commit comments

Comments
 (0)