Skip to content

Commit 51104f6

Browse files
committed
complete FileSelector component
1 parent d416636 commit 51104f6

File tree

3 files changed

+197
-17
lines changed

3 files changed

+197
-17
lines changed

dist/index.js

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/App.vue

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@
22
<div>
33
My App.vue comp
44

5-
<FileSelector />
5+
<FileSelector
6+
accept-extensions=".zip"
7+
:height="300"
8+
@validate="validate"
9+
@change="change"
10+
>
11+
hahahah
12+
</FileSelector>
613
</div>
714
</template>
815

@@ -14,5 +21,15 @@ export default {
1421
components: {
1522
FileSelector,
1623
},
24+
25+
methods: {
26+
validate(result, files) {
27+
console.log('Validation result: ' + result);
28+
},
29+
30+
change(files) {
31+
console.log('Selected files: ', files);
32+
},
33+
},
1734
};
1835
</script>

src/components/FileSelector.vue

Lines changed: 174 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,195 @@
11
<template>
2-
<div class="bold">
3-
hello
4-
<div class="italic">
5-
{{ value }}
2+
<div class="fs-file-selector">
3+
<div class="fs-loader" v-if="isLoading">
4+
<slot name="loader">
5+
Loading...
6+
</slot>
7+
</div>
8+
9+
<div class="fs-droppable"
10+
ref="fsDroppable"
11+
:class="{ 'fs-drag-enter': isDragEnter }"
12+
:style="{ height: height + 'px' }"
13+
@dragenter.stop.prevent="isDragEnter = true"
14+
@dragover.stop.prevent="() => {}"
15+
@dragleave.stop.prevent="isDragEnter = false"
16+
@drop.stop.prevent="handleDrop">
17+
<input
18+
ref="fsFileInput"
19+
type="file"
20+
tabindex="-1"
21+
:multiple="multiple"
22+
:accept="acceptExtensions"
23+
@change="handleFilesChange"
24+
/>
25+
<slot name="top"></slot>
26+
27+
<div href="#" class="fs-btn-select" @click="$refs.fsFileInput.click()">
28+
<slot>Select</slot>
29+
</div>
30+
31+
<slot name="bottom"></slot>
632
</div>
733
</div>
834
</template>
935

36+
1037
<script>
1138
export default {
39+
props: {
40+
multiple: {
41+
type: Boolean,
42+
default: false,
43+
},
44+
45+
isLoading: {
46+
type: Boolean,
47+
default: false,
48+
},
49+
50+
acceptExtensions: {
51+
type: String,
52+
default: '',
53+
},
54+
55+
maxFileSize: { // in bytes
56+
type: Number,
57+
default: NaN,
58+
},
59+
60+
height: {
61+
type: Number,
62+
default: NaN,
63+
},
64+
65+
validateFn: {
66+
type: Function,
67+
default: () => true,
68+
},
69+
},
70+
1271
data() {
1372
return {
14-
value: 10,
73+
isDragEnter: false,
1574
};
1675
},
1776
18-
create() {
19-
console.log('created');
77+
methods: {
78+
handleFilesChange($event) {
79+
this.preprocessFiles($event.target.files);
80+
},
81+
82+
handleDrop($event) {
83+
this.isDragEnter = false;
84+
this.preprocessFiles($event.dataTransfer.files);
85+
},
86+
87+
checkFileExtensions(files) {
88+
// get non-empty, unique extension items
89+
const extList = [...new Set(
90+
this.acceptExtensions.toLowerCase()
91+
.split(',')
92+
.filter(Boolean)
93+
)];
94+
const list = Array.from(files);
95+
96+
// check if the selected files are in supported extensions
97+
const invalidFileIndex = list.findIndex(file => {
98+
const ext = `.${file.name.toLowerCase().split('.').pop()}`;
99+
100+
return !extList.includes(ext);
101+
});
102+
103+
// all exts are valid
104+
return invalidFileIndex === -1;
105+
},
106+
107+
checkFileSize(files) {
108+
if (Number.isNaN(this.maxFileSize)) {
109+
return true;
110+
}
111+
112+
const list = Array.from(files);
113+
114+
// find invalid file size
115+
const invalidFileIndex = list.findIndex(file => file.size > this.maxFileSize);
116+
117+
// all file size are valid
118+
return invalidFileIndex === -1;
119+
},
120+
121+
validate(files) {
122+
// file selection
123+
if (!this.multiple && files.length > 1) {
124+
return 'MULTIFILES_ERROR';
125+
}
126+
127+
// extension
128+
if (!this.checkFileExtensions(files)) {
129+
return 'EXTENSION_ERROR';
130+
}
131+
132+
// file size
133+
if (!this.checkFileSize(files)) {
134+
return 'FILE_SIZE_ERROR';
135+
}
136+
137+
// custom validation
138+
return this.validateFn(files);
139+
},
140+
141+
preprocessFiles(files) {
142+
const result = this.validate(files);
143+
this.$emit('validate', result, files);
144+
145+
// validation
146+
if (result === true) {
147+
this.$emit('change', files);
148+
}
149+
150+
// clear selected files
151+
this.$refs.fsFileInput.value = '';
152+
},
20153
},
21154
};
22155
</script>
23156

157+
24158
<style lang="scss" scoped>
25-
.bold {
26-
font-weight: 800;
159+
.fs-file-selector {
160+
position: relative;
161+
162+
.fs-loader {
163+
background: rgba(#fff, 0.8);
164+
position: absolute;
165+
top: 0;
166+
bottom: 0;
167+
left: 0;
168+
right: 0;
169+
z-index: 1;
170+
171+
display: flex;
172+
justify-content: center;
173+
align-items: center;
174+
}
175+
176+
.fs-droppable {
177+
display: flex;
178+
flex-direction: column;
179+
align-items: center;
180+
justify-content: center;
181+
position: relative;
182+
183+
text-align: center;
184+
border-radius: 8px;
185+
border: 1px dashed #000;
27186
28-
.italic {
29-
font-style: italic;
187+
input[type="file"] {
188+
visibility: hidden;
189+
position: absolute;
190+
width: 1px;
191+
height: 1px;
192+
}
30193
}
31194
}
32195
</style>

0 commit comments

Comments
 (0)