Fixes stray drag/drop on missed dropzones, allows removing of non uploaded files
This commit is contained in:
parent
2d1c58f725
commit
dd74fcb12e
186
dist/formulate.esm.js
vendored
186
dist/formulate.esm.js
vendored
@ -115,11 +115,12 @@ var library = {
|
||||
* The file upload class holds and represents a file’s upload state durring
|
||||
* the upload flow.
|
||||
*/
|
||||
var FileUpload = function FileUpload (fileList, context, options) {
|
||||
this.fileList = fileList;
|
||||
var FileUpload = function FileUpload (input, context, options) {
|
||||
this.input = input;
|
||||
this.fileList = input.files;
|
||||
this.files = [];
|
||||
this.options = options;
|
||||
this.setFileList(fileList);
|
||||
this.addFileList(this.fileList);
|
||||
this.context = context;
|
||||
};
|
||||
|
||||
@ -127,16 +128,28 @@ var FileUpload = function FileUpload (fileList, context, options) {
|
||||
* Produce an array of files and alert the callback.
|
||||
* @param {FileList}
|
||||
*/
|
||||
FileUpload.prototype.setFileList = function setFileList (fileList) {
|
||||
for (var i = 0; i < fileList.length; i++) {
|
||||
var file = fileList.item(i);
|
||||
this.files.push({
|
||||
progress: 0,
|
||||
FileUpload.prototype.addFileList = function addFileList (fileList) {
|
||||
var this$1 = this;
|
||||
|
||||
var loop = function ( i ) {
|
||||
var file = fileList[i];
|
||||
var uuid = nanoid();
|
||||
var removeFile = function () {
|
||||
this.removeFile(uuid);
|
||||
};
|
||||
this$1.files.push({
|
||||
progress: false,
|
||||
error: false,
|
||||
complete: false,
|
||||
justFinished: false,
|
||||
name: file.name || 'file-upload',
|
||||
file: file,
|
||||
uuid: nanoid()
|
||||
uuid: uuid,
|
||||
removeFile: removeFile.bind(this$1)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
for (var i = 0; i < fileList.length; i++) loop( i );
|
||||
};
|
||||
|
||||
/**
|
||||
@ -196,8 +209,21 @@ FileUpload.prototype.upload = function upload () {
|
||||
Promise.all(this$1.files.map(function (file) {
|
||||
return this$1.getUploader(
|
||||
file.file,
|
||||
function (progress) { file.progress = progress; },
|
||||
function (error) { return reject(new Error(error)); },
|
||||
function (progress) {
|
||||
file.progress = progress;
|
||||
if (progress >= 100) {
|
||||
if (!file.complete) {
|
||||
file.justFinished = true;
|
||||
setTimeout(function () { file.justFinished = false; }, this$1.options.uploadJustCompleteDuration);
|
||||
}
|
||||
file.complete = true;
|
||||
}
|
||||
},
|
||||
function (error) {
|
||||
file.progress = 0;
|
||||
file.error = error;
|
||||
file.complete = true;
|
||||
},
|
||||
this$1.options
|
||||
)
|
||||
}))
|
||||
@ -206,6 +232,20 @@ FileUpload.prototype.upload = function upload () {
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a file from the uploader (and the file list)
|
||||
* @param {string} uuid
|
||||
*/
|
||||
FileUpload.prototype.removeFile = function removeFile (uuid) {
|
||||
this.files = this.files.filter(function (file) { return file.uuid !== uuid; });
|
||||
if (window) {
|
||||
var transfer = new DataTransfer();
|
||||
this.files.map(function (file) { return transfer.items.add(file.file); });
|
||||
this.fileList = transfer.files;
|
||||
this.input.files = this.fileList;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the files.
|
||||
*/
|
||||
@ -538,14 +578,10 @@ var rules = {
|
||||
|
||||
return Promise.resolve((function () {
|
||||
if (files instanceof FileUpload) {
|
||||
if (files.hasUploader()) {
|
||||
return false
|
||||
}
|
||||
files = files.getFiles();
|
||||
}
|
||||
if (typeof window !== 'undefined' && typeof FileReader !== 'undefined' && typeof Blob !== 'undefined') {
|
||||
for (var i in files) {
|
||||
if (!types.includes(files[i].type)) {
|
||||
var fileList = files.getFileList();
|
||||
for (var i = 0; i < fileList.length; i++) {
|
||||
var file = fileList[i];
|
||||
if (!types.includes(file.type)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -776,6 +812,16 @@ var en = {
|
||||
return ((sentence(name)) + " must be less than " + (args[0]) + " characters long.")
|
||||
},
|
||||
|
||||
/**
|
||||
* The (field-level) error message for mime errors.
|
||||
*/
|
||||
mime: function (ref) {
|
||||
var name = ref.name;
|
||||
var args = ref.args;
|
||||
|
||||
return ((sentence(name)) + " must of the the type: " + (args[0] || 'No file formats allowed.'))
|
||||
},
|
||||
|
||||
/**
|
||||
* The maximum value allowed.
|
||||
*/
|
||||
@ -842,17 +888,29 @@ var en = {
|
||||
*/
|
||||
function fauxUploader (file, progress, error, options) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var totalTime = options.fauxUploaderDuration || 2000;
|
||||
var totalTime = (options.fauxUploaderDuration || 2000) * (0.5 + Math.random());
|
||||
var start = performance.now();
|
||||
/**
|
||||
* @todo - remove, intentional failure
|
||||
*/
|
||||
var fail = (Math.random() > 0.5);
|
||||
var advance = function () { return setTimeout(function () {
|
||||
var elapsed = performance.now() - start;
|
||||
var currentProgress = Math.min(100, Math.round(elapsed / totalTime * 100));
|
||||
progress(currentProgress);
|
||||
|
||||
/**
|
||||
* @todo - remove, intentional failure
|
||||
*/
|
||||
if (fail && currentProgress > 50) {
|
||||
return error('There was an error uploading the file.')
|
||||
}
|
||||
|
||||
if (currentProgress >= 100) {
|
||||
resolve({
|
||||
return resolve({
|
||||
url: 'http://via.placeholder.com/350x150.png',
|
||||
name: file.name
|
||||
});
|
||||
})
|
||||
} else {
|
||||
advance();
|
||||
}
|
||||
@ -881,7 +939,8 @@ var context = {
|
||||
showImage: this.showImage,
|
||||
uploadUrl: this.uploadUrl,
|
||||
uploader: this.uploader || this.$formulate.getUploader(),
|
||||
immediateUpload: this.immediateUpload},
|
||||
uploadBehavior: this.uploadBehavior,
|
||||
preventWindowDrops: this.preventWindowDrops},
|
||||
this.typeContext))
|
||||
},
|
||||
nameOrFallback: nameOrFallback,
|
||||
@ -1182,7 +1241,11 @@ var script = {
|
||||
type: [Function, Object, Boolean],
|
||||
default: false
|
||||
},
|
||||
immediateUpload: {
|
||||
uploadBehavior: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
preventWindowDrops: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
@ -2200,18 +2263,52 @@ var __vue_render__$6 = function() {
|
||||
"ul",
|
||||
{ staticClass: "formulate-files" },
|
||||
_vm._l(_vm.fileUploads, function(file) {
|
||||
return _c("li", { key: file.uuid, staticClass: "formulate-file" }, [
|
||||
_c("span", {
|
||||
return _c(
|
||||
"li",
|
||||
{ key: file.uuid, attrs: { "data-has-error": !!file.error } },
|
||||
[
|
||||
_c("div", { staticClass: "formulate-file" }, [
|
||||
_c("div", {
|
||||
staticClass: "formualte-file-name",
|
||||
domProps: { textContent: _vm._s(file.name) }
|
||||
}),
|
||||
_vm._v(" "),
|
||||
file.progress > 0 && file.progress < 100
|
||||
? _c("span", {
|
||||
domProps: { textContent: _vm._s(file.progress + "%") }
|
||||
file.progress !== false
|
||||
? _c(
|
||||
"div",
|
||||
{
|
||||
staticClass: "formulate-file-progress",
|
||||
attrs: {
|
||||
"data-just-finished": file.justFinished,
|
||||
"data-is-finished":
|
||||
!file.justFinished && file.complete
|
||||
}
|
||||
},
|
||||
[
|
||||
_c("div", {
|
||||
staticClass: "formulate-file-progress-inner",
|
||||
style: { width: file.progress + "%" }
|
||||
})
|
||||
]
|
||||
)
|
||||
: _vm._e(),
|
||||
_vm._v(" "),
|
||||
(file.complete && !file.justFinished) || file.progress === false
|
||||
? _c("div", {
|
||||
staticClass: "formulate-file-remove",
|
||||
on: { click: file.removeFile }
|
||||
})
|
||||
: _vm._e()
|
||||
])
|
||||
]),
|
||||
_vm._v(" "),
|
||||
file.error
|
||||
? _c("div", {
|
||||
staticClass: "formulate-file-upload-error",
|
||||
domProps: { textContent: _vm._s(file.error) }
|
||||
})
|
||||
: _vm._e()
|
||||
]
|
||||
)
|
||||
}),
|
||||
0
|
||||
)
|
||||
@ -2264,16 +2361,36 @@ var script$7 = {
|
||||
},
|
||||
computed: {
|
||||
hasFiles: function hasFiles () {
|
||||
return (this.context.model instanceof FileUpload && this.context.model.files.length)
|
||||
return !!(this.context.model instanceof FileUpload && this.context.model.files.length)
|
||||
}
|
||||
},
|
||||
mounted: function mounted () {
|
||||
// Add a listener to the window to prevent drag/drops that miss the dropzone
|
||||
// from opening the file and navigating the user away from the page.
|
||||
if (window && this.context.preventWindowDrops) {
|
||||
window.addEventListener('dragover', this.preventDefault);
|
||||
window.addEventListener('drop', this.preventDefault);
|
||||
}
|
||||
},
|
||||
destroyed: function destroyed () {
|
||||
if (window && this.context.preventWindowDrops) {
|
||||
window.removeEventListener('dragover', this.preventDefault);
|
||||
window.removeEventListener('drop', this.preventDefault);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
preventDefault: function preventDefault (e) {
|
||||
if (e.target.tagName !== 'INPUT' && e.target.getAttribute('type') !== 'file') {
|
||||
e = e || event;
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
handleFile: function handleFile () {
|
||||
var input = this.$refs.file;
|
||||
if (input.files.length) {
|
||||
this.context.model = this.$formulate.createUpload(input.files, this.context);
|
||||
this.context.model = this.$formulate.createUpload(input, this.context);
|
||||
}
|
||||
if (this.context.immediateUpload && this.context.model instanceof FileUpload) {
|
||||
if (this.context.uploadBehavior === 'live' && this.context.model instanceof FileUpload) {
|
||||
this.context.model.upload();
|
||||
}
|
||||
},
|
||||
@ -2819,6 +2936,7 @@ var Formulate = function Formulate () {
|
||||
rules: rules,
|
||||
locale: 'en',
|
||||
uploader: fauxUploader,
|
||||
uploadJustCompleteDuration: 1000,
|
||||
locales: {
|
||||
en: en
|
||||
}
|
||||
|
186
dist/formulate.min.js
vendored
186
dist/formulate.min.js
vendored
@ -118,11 +118,12 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
|
||||
* The file upload class holds and represents a file’s upload state durring
|
||||
* the upload flow.
|
||||
*/
|
||||
var FileUpload = function FileUpload (fileList, context, options) {
|
||||
this.fileList = fileList;
|
||||
var FileUpload = function FileUpload (input, context, options) {
|
||||
this.input = input;
|
||||
this.fileList = input.files;
|
||||
this.files = [];
|
||||
this.options = options;
|
||||
this.setFileList(fileList);
|
||||
this.addFileList(this.fileList);
|
||||
this.context = context;
|
||||
};
|
||||
|
||||
@ -130,16 +131,28 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
|
||||
* Produce an array of files and alert the callback.
|
||||
* @param {FileList}
|
||||
*/
|
||||
FileUpload.prototype.setFileList = function setFileList (fileList) {
|
||||
for (var i = 0; i < fileList.length; i++) {
|
||||
var file = fileList.item(i);
|
||||
this.files.push({
|
||||
progress: 0,
|
||||
FileUpload.prototype.addFileList = function addFileList (fileList) {
|
||||
var this$1 = this;
|
||||
|
||||
var loop = function ( i ) {
|
||||
var file = fileList[i];
|
||||
var uuid = nanoid();
|
||||
var removeFile = function () {
|
||||
this.removeFile(uuid);
|
||||
};
|
||||
this$1.files.push({
|
||||
progress: false,
|
||||
error: false,
|
||||
complete: false,
|
||||
justFinished: false,
|
||||
name: file.name || 'file-upload',
|
||||
file: file,
|
||||
uuid: nanoid()
|
||||
uuid: uuid,
|
||||
removeFile: removeFile.bind(this$1)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
for (var i = 0; i < fileList.length; i++) loop( i );
|
||||
};
|
||||
|
||||
/**
|
||||
@ -199,8 +212,21 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
|
||||
Promise.all(this$1.files.map(function (file) {
|
||||
return this$1.getUploader(
|
||||
file.file,
|
||||
function (progress) { file.progress = progress; },
|
||||
function (error) { return reject(new Error(error)); },
|
||||
function (progress) {
|
||||
file.progress = progress;
|
||||
if (progress >= 100) {
|
||||
if (!file.complete) {
|
||||
file.justFinished = true;
|
||||
setTimeout(function () { file.justFinished = false; }, this$1.options.uploadJustCompleteDuration);
|
||||
}
|
||||
file.complete = true;
|
||||
}
|
||||
},
|
||||
function (error) {
|
||||
file.progress = 0;
|
||||
file.error = error;
|
||||
file.complete = true;
|
||||
},
|
||||
this$1.options
|
||||
)
|
||||
}))
|
||||
@ -209,6 +235,20 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a file from the uploader (and the file list)
|
||||
* @param {string} uuid
|
||||
*/
|
||||
FileUpload.prototype.removeFile = function removeFile (uuid) {
|
||||
this.files = this.files.filter(function (file) { return file.uuid !== uuid; });
|
||||
if (window) {
|
||||
var transfer = new DataTransfer();
|
||||
this.files.map(function (file) { return transfer.items.add(file.file); });
|
||||
this.fileList = transfer.files;
|
||||
this.input.files = this.fileList;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the files.
|
||||
*/
|
||||
@ -541,14 +581,10 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
|
||||
|
||||
return Promise.resolve((function () {
|
||||
if (files instanceof FileUpload) {
|
||||
if (files.hasUploader()) {
|
||||
return false
|
||||
}
|
||||
files = files.getFiles();
|
||||
}
|
||||
if (typeof window !== 'undefined' && typeof FileReader !== 'undefined' && typeof Blob !== 'undefined') {
|
||||
for (var i in files) {
|
||||
if (!types.includes(files[i].type)) {
|
||||
var fileList = files.getFileList();
|
||||
for (var i = 0; i < fileList.length; i++) {
|
||||
var file = fileList[i];
|
||||
if (!types.includes(file.type)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -779,6 +815,16 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
|
||||
return ((sentence(name)) + " must be less than " + (args[0]) + " characters long.")
|
||||
},
|
||||
|
||||
/**
|
||||
* The (field-level) error message for mime errors.
|
||||
*/
|
||||
mime: function (ref) {
|
||||
var name = ref.name;
|
||||
var args = ref.args;
|
||||
|
||||
return ((sentence(name)) + " must of the the type: " + (args[0] || 'No file formats allowed.'))
|
||||
},
|
||||
|
||||
/**
|
||||
* The maximum value allowed.
|
||||
*/
|
||||
@ -845,17 +891,29 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
|
||||
*/
|
||||
function fauxUploader (file, progress, error, options) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var totalTime = options.fauxUploaderDuration || 2000;
|
||||
var totalTime = (options.fauxUploaderDuration || 2000) * (0.5 + Math.random());
|
||||
var start = performance.now();
|
||||
/**
|
||||
* @todo - remove, intentional failure
|
||||
*/
|
||||
var fail = (Math.random() > 0.5);
|
||||
var advance = function () { return setTimeout(function () {
|
||||
var elapsed = performance.now() - start;
|
||||
var currentProgress = Math.min(100, Math.round(elapsed / totalTime * 100));
|
||||
progress(currentProgress);
|
||||
|
||||
/**
|
||||
* @todo - remove, intentional failure
|
||||
*/
|
||||
if (fail && currentProgress > 50) {
|
||||
return error('There was an error uploading the file.')
|
||||
}
|
||||
|
||||
if (currentProgress >= 100) {
|
||||
resolve({
|
||||
return resolve({
|
||||
url: 'http://via.placeholder.com/350x150.png',
|
||||
name: file.name
|
||||
});
|
||||
})
|
||||
} else {
|
||||
advance();
|
||||
}
|
||||
@ -884,7 +942,8 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
|
||||
showImage: this.showImage,
|
||||
uploadUrl: this.uploadUrl,
|
||||
uploader: this.uploader || this.$formulate.getUploader(),
|
||||
immediateUpload: this.immediateUpload},
|
||||
uploadBehavior: this.uploadBehavior,
|
||||
preventWindowDrops: this.preventWindowDrops},
|
||||
this.typeContext))
|
||||
},
|
||||
nameOrFallback: nameOrFallback,
|
||||
@ -1185,7 +1244,11 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
|
||||
type: [Function, Object, Boolean],
|
||||
default: false
|
||||
},
|
||||
immediateUpload: {
|
||||
uploadBehavior: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
preventWindowDrops: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
@ -2203,18 +2266,52 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
|
||||
"ul",
|
||||
{ staticClass: "formulate-files" },
|
||||
_vm._l(_vm.fileUploads, function(file) {
|
||||
return _c("li", { key: file.uuid, staticClass: "formulate-file" }, [
|
||||
_c("span", {
|
||||
return _c(
|
||||
"li",
|
||||
{ key: file.uuid, attrs: { "data-has-error": !!file.error } },
|
||||
[
|
||||
_c("div", { staticClass: "formulate-file" }, [
|
||||
_c("div", {
|
||||
staticClass: "formualte-file-name",
|
||||
domProps: { textContent: _vm._s(file.name) }
|
||||
}),
|
||||
_vm._v(" "),
|
||||
file.progress > 0 && file.progress < 100
|
||||
? _c("span", {
|
||||
domProps: { textContent: _vm._s(file.progress + "%") }
|
||||
file.progress !== false
|
||||
? _c(
|
||||
"div",
|
||||
{
|
||||
staticClass: "formulate-file-progress",
|
||||
attrs: {
|
||||
"data-just-finished": file.justFinished,
|
||||
"data-is-finished":
|
||||
!file.justFinished && file.complete
|
||||
}
|
||||
},
|
||||
[
|
||||
_c("div", {
|
||||
staticClass: "formulate-file-progress-inner",
|
||||
style: { width: file.progress + "%" }
|
||||
})
|
||||
]
|
||||
)
|
||||
: _vm._e(),
|
||||
_vm._v(" "),
|
||||
(file.complete && !file.justFinished) || file.progress === false
|
||||
? _c("div", {
|
||||
staticClass: "formulate-file-remove",
|
||||
on: { click: file.removeFile }
|
||||
})
|
||||
: _vm._e()
|
||||
])
|
||||
]),
|
||||
_vm._v(" "),
|
||||
file.error
|
||||
? _c("div", {
|
||||
staticClass: "formulate-file-upload-error",
|
||||
domProps: { textContent: _vm._s(file.error) }
|
||||
})
|
||||
: _vm._e()
|
||||
]
|
||||
)
|
||||
}),
|
||||
0
|
||||
)
|
||||
@ -2267,16 +2364,36 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
|
||||
},
|
||||
computed: {
|
||||
hasFiles: function hasFiles () {
|
||||
return (this.context.model instanceof FileUpload && this.context.model.files.length)
|
||||
return !!(this.context.model instanceof FileUpload && this.context.model.files.length)
|
||||
}
|
||||
},
|
||||
mounted: function mounted () {
|
||||
// Add a listener to the window to prevent drag/drops that miss the dropzone
|
||||
// from opening the file and navigating the user away from the page.
|
||||
if (window && this.context.preventWindowDrops) {
|
||||
window.addEventListener('dragover', this.preventDefault);
|
||||
window.addEventListener('drop', this.preventDefault);
|
||||
}
|
||||
},
|
||||
destroyed: function destroyed () {
|
||||
if (window && this.context.preventWindowDrops) {
|
||||
window.removeEventListener('dragover', this.preventDefault);
|
||||
window.removeEventListener('drop', this.preventDefault);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
preventDefault: function preventDefault (e) {
|
||||
if (e.target.tagName !== 'INPUT' && e.target.getAttribute('type') !== 'file') {
|
||||
e = e || event;
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
handleFile: function handleFile () {
|
||||
var input = this.$refs.file;
|
||||
if (input.files.length) {
|
||||
this.context.model = this.$formulate.createUpload(input.files, this.context);
|
||||
this.context.model = this.$formulate.createUpload(input, this.context);
|
||||
}
|
||||
if (this.context.immediateUpload && this.context.model instanceof FileUpload) {
|
||||
if (this.context.uploadBehavior === 'live' && this.context.model instanceof FileUpload) {
|
||||
this.context.model.upload();
|
||||
}
|
||||
},
|
||||
@ -2822,6 +2939,7 @@ var Formulate = (function (exports, isUrl, nanoid, isPlainObject) {
|
||||
rules: rules,
|
||||
locale: 'en',
|
||||
uploader: fauxUploader,
|
||||
uploadJustCompleteDuration: 1000,
|
||||
locales: {
|
||||
en: en
|
||||
}
|
||||
|
186
dist/formulate.umd.js
vendored
186
dist/formulate.umd.js
vendored
@ -121,11 +121,12 @@
|
||||
* The file upload class holds and represents a file’s upload state durring
|
||||
* the upload flow.
|
||||
*/
|
||||
var FileUpload = function FileUpload (fileList, context, options) {
|
||||
this.fileList = fileList;
|
||||
var FileUpload = function FileUpload (input, context, options) {
|
||||
this.input = input;
|
||||
this.fileList = input.files;
|
||||
this.files = [];
|
||||
this.options = options;
|
||||
this.setFileList(fileList);
|
||||
this.addFileList(this.fileList);
|
||||
this.context = context;
|
||||
};
|
||||
|
||||
@ -133,16 +134,28 @@
|
||||
* Produce an array of files and alert the callback.
|
||||
* @param {FileList}
|
||||
*/
|
||||
FileUpload.prototype.setFileList = function setFileList (fileList) {
|
||||
for (var i = 0; i < fileList.length; i++) {
|
||||
var file = fileList.item(i);
|
||||
this.files.push({
|
||||
progress: 0,
|
||||
FileUpload.prototype.addFileList = function addFileList (fileList) {
|
||||
var this$1 = this;
|
||||
|
||||
var loop = function ( i ) {
|
||||
var file = fileList[i];
|
||||
var uuid = nanoid();
|
||||
var removeFile = function () {
|
||||
this.removeFile(uuid);
|
||||
};
|
||||
this$1.files.push({
|
||||
progress: false,
|
||||
error: false,
|
||||
complete: false,
|
||||
justFinished: false,
|
||||
name: file.name || 'file-upload',
|
||||
file: file,
|
||||
uuid: nanoid()
|
||||
uuid: uuid,
|
||||
removeFile: removeFile.bind(this$1)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
for (var i = 0; i < fileList.length; i++) loop( i );
|
||||
};
|
||||
|
||||
/**
|
||||
@ -202,8 +215,21 @@
|
||||
Promise.all(this$1.files.map(function (file) {
|
||||
return this$1.getUploader(
|
||||
file.file,
|
||||
function (progress) { file.progress = progress; },
|
||||
function (error) { return reject(new Error(error)); },
|
||||
function (progress) {
|
||||
file.progress = progress;
|
||||
if (progress >= 100) {
|
||||
if (!file.complete) {
|
||||
file.justFinished = true;
|
||||
setTimeout(function () { file.justFinished = false; }, this$1.options.uploadJustCompleteDuration);
|
||||
}
|
||||
file.complete = true;
|
||||
}
|
||||
},
|
||||
function (error) {
|
||||
file.progress = 0;
|
||||
file.error = error;
|
||||
file.complete = true;
|
||||
},
|
||||
this$1.options
|
||||
)
|
||||
}))
|
||||
@ -212,6 +238,20 @@
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a file from the uploader (and the file list)
|
||||
* @param {string} uuid
|
||||
*/
|
||||
FileUpload.prototype.removeFile = function removeFile (uuid) {
|
||||
this.files = this.files.filter(function (file) { return file.uuid !== uuid; });
|
||||
if (window) {
|
||||
var transfer = new DataTransfer();
|
||||
this.files.map(function (file) { return transfer.items.add(file.file); });
|
||||
this.fileList = transfer.files;
|
||||
this.input.files = this.fileList;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the files.
|
||||
*/
|
||||
@ -544,14 +584,10 @@
|
||||
|
||||
return Promise.resolve((function () {
|
||||
if (files instanceof FileUpload) {
|
||||
if (files.hasUploader()) {
|
||||
return false
|
||||
}
|
||||
files = files.getFiles();
|
||||
}
|
||||
if (typeof window !== 'undefined' && typeof FileReader !== 'undefined' && typeof Blob !== 'undefined') {
|
||||
for (var i in files) {
|
||||
if (!types.includes(files[i].type)) {
|
||||
var fileList = files.getFileList();
|
||||
for (var i = 0; i < fileList.length; i++) {
|
||||
var file = fileList[i];
|
||||
if (!types.includes(file.type)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -782,6 +818,16 @@
|
||||
return ((sentence(name)) + " must be less than " + (args[0]) + " characters long.")
|
||||
},
|
||||
|
||||
/**
|
||||
* The (field-level) error message for mime errors.
|
||||
*/
|
||||
mime: function (ref) {
|
||||
var name = ref.name;
|
||||
var args = ref.args;
|
||||
|
||||
return ((sentence(name)) + " must of the the type: " + (args[0] || 'No file formats allowed.'))
|
||||
},
|
||||
|
||||
/**
|
||||
* The maximum value allowed.
|
||||
*/
|
||||
@ -848,17 +894,29 @@
|
||||
*/
|
||||
function fauxUploader (file, progress, error, options) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var totalTime = options.fauxUploaderDuration || 2000;
|
||||
var totalTime = (options.fauxUploaderDuration || 2000) * (0.5 + Math.random());
|
||||
var start = performance.now();
|
||||
/**
|
||||
* @todo - remove, intentional failure
|
||||
*/
|
||||
var fail = (Math.random() > 0.5);
|
||||
var advance = function () { return setTimeout(function () {
|
||||
var elapsed = performance.now() - start;
|
||||
var currentProgress = Math.min(100, Math.round(elapsed / totalTime * 100));
|
||||
progress(currentProgress);
|
||||
|
||||
/**
|
||||
* @todo - remove, intentional failure
|
||||
*/
|
||||
if (fail && currentProgress > 50) {
|
||||
return error('There was an error uploading the file.')
|
||||
}
|
||||
|
||||
if (currentProgress >= 100) {
|
||||
resolve({
|
||||
return resolve({
|
||||
url: 'http://via.placeholder.com/350x150.png',
|
||||
name: file.name
|
||||
});
|
||||
})
|
||||
} else {
|
||||
advance();
|
||||
}
|
||||
@ -887,7 +945,8 @@
|
||||
showImage: this.showImage,
|
||||
uploadUrl: this.uploadUrl,
|
||||
uploader: this.uploader || this.$formulate.getUploader(),
|
||||
immediateUpload: this.immediateUpload},
|
||||
uploadBehavior: this.uploadBehavior,
|
||||
preventWindowDrops: this.preventWindowDrops},
|
||||
this.typeContext))
|
||||
},
|
||||
nameOrFallback: nameOrFallback,
|
||||
@ -1188,7 +1247,11 @@
|
||||
type: [Function, Object, Boolean],
|
||||
default: false
|
||||
},
|
||||
immediateUpload: {
|
||||
uploadBehavior: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
preventWindowDrops: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
@ -2206,18 +2269,52 @@
|
||||
"ul",
|
||||
{ staticClass: "formulate-files" },
|
||||
_vm._l(_vm.fileUploads, function(file) {
|
||||
return _c("li", { key: file.uuid, staticClass: "formulate-file" }, [
|
||||
_c("span", {
|
||||
return _c(
|
||||
"li",
|
||||
{ key: file.uuid, attrs: { "data-has-error": !!file.error } },
|
||||
[
|
||||
_c("div", { staticClass: "formulate-file" }, [
|
||||
_c("div", {
|
||||
staticClass: "formualte-file-name",
|
||||
domProps: { textContent: _vm._s(file.name) }
|
||||
}),
|
||||
_vm._v(" "),
|
||||
file.progress > 0 && file.progress < 100
|
||||
? _c("span", {
|
||||
domProps: { textContent: _vm._s(file.progress + "%") }
|
||||
file.progress !== false
|
||||
? _c(
|
||||
"div",
|
||||
{
|
||||
staticClass: "formulate-file-progress",
|
||||
attrs: {
|
||||
"data-just-finished": file.justFinished,
|
||||
"data-is-finished":
|
||||
!file.justFinished && file.complete
|
||||
}
|
||||
},
|
||||
[
|
||||
_c("div", {
|
||||
staticClass: "formulate-file-progress-inner",
|
||||
style: { width: file.progress + "%" }
|
||||
})
|
||||
]
|
||||
)
|
||||
: _vm._e(),
|
||||
_vm._v(" "),
|
||||
(file.complete && !file.justFinished) || file.progress === false
|
||||
? _c("div", {
|
||||
staticClass: "formulate-file-remove",
|
||||
on: { click: file.removeFile }
|
||||
})
|
||||
: _vm._e()
|
||||
])
|
||||
]),
|
||||
_vm._v(" "),
|
||||
file.error
|
||||
? _c("div", {
|
||||
staticClass: "formulate-file-upload-error",
|
||||
domProps: { textContent: _vm._s(file.error) }
|
||||
})
|
||||
: _vm._e()
|
||||
]
|
||||
)
|
||||
}),
|
||||
0
|
||||
)
|
||||
@ -2270,16 +2367,36 @@
|
||||
},
|
||||
computed: {
|
||||
hasFiles: function hasFiles () {
|
||||
return (this.context.model instanceof FileUpload && this.context.model.files.length)
|
||||
return !!(this.context.model instanceof FileUpload && this.context.model.files.length)
|
||||
}
|
||||
},
|
||||
mounted: function mounted () {
|
||||
// Add a listener to the window to prevent drag/drops that miss the dropzone
|
||||
// from opening the file and navigating the user away from the page.
|
||||
if (window && this.context.preventWindowDrops) {
|
||||
window.addEventListener('dragover', this.preventDefault);
|
||||
window.addEventListener('drop', this.preventDefault);
|
||||
}
|
||||
},
|
||||
destroyed: function destroyed () {
|
||||
if (window && this.context.preventWindowDrops) {
|
||||
window.removeEventListener('dragover', this.preventDefault);
|
||||
window.removeEventListener('drop', this.preventDefault);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
preventDefault: function preventDefault (e) {
|
||||
if (e.target.tagName !== 'INPUT' && e.target.getAttribute('type') !== 'file') {
|
||||
e = e || event;
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
handleFile: function handleFile () {
|
||||
var input = this.$refs.file;
|
||||
if (input.files.length) {
|
||||
this.context.model = this.$formulate.createUpload(input.files, this.context);
|
||||
this.context.model = this.$formulate.createUpload(input, this.context);
|
||||
}
|
||||
if (this.context.immediateUpload && this.context.model instanceof FileUpload) {
|
||||
if (this.context.uploadBehavior === 'live' && this.context.model instanceof FileUpload) {
|
||||
this.context.model.upload();
|
||||
}
|
||||
},
|
||||
@ -2825,6 +2942,7 @@
|
||||
rules: rules,
|
||||
locale: 'en',
|
||||
uploader: fauxUploader,
|
||||
uploadJustCompleteDuration: 1000,
|
||||
locales: {
|
||||
en: en
|
||||
}
|
||||
|
117
dist/snow.css
vendored
117
dist/snow.css
vendored
@ -19,7 +19,8 @@
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0; }
|
||||
.formulate-input .formulate-input-error {
|
||||
.formulate-input .formulate-input-error,
|
||||
.formulate-input .formulate-file-upload-error {
|
||||
color: #960505;
|
||||
font-size: .8em;
|
||||
font-weight: 300;
|
||||
@ -220,9 +221,7 @@
|
||||
.formulate-input[data-classification="file"] .formulate-input-upload-area {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
padding: 2em; }
|
||||
.formulate-input[data-classification="file"] .formulate-input-upload-area[data-has-files] {
|
||||
padding: 0; }
|
||||
padding: 2em 0; }
|
||||
.formulate-input[data-classification="file"] .formulate-input-upload-area input {
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
@ -235,6 +234,10 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 5; }
|
||||
.formulate-input[data-classification="file"] .formulate-input-upload-area[data-has-files] {
|
||||
padding: 0; }
|
||||
.formulate-input[data-classification="file"] .formulate-input-upload-area[data-has-files] input {
|
||||
display: none; }
|
||||
.formulate-input[data-classification="file"] .formulate-input-upload-area-mask {
|
||||
border-radius: .4em;
|
||||
position: absolute;
|
||||
@ -253,6 +256,8 @@
|
||||
content: '';
|
||||
background-color: #a8a8a8;
|
||||
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 58 58"><path d="M29,58A29,29,0,1,0,0,29,29,29,0,0,0,29,58ZM29,4A25,25,0,1,1,4,29,25,25,0,0,1,29,4Z"/><polygon points="27 22 27 44.4 31 44.4 31 22 41.7 31.1 44.3 28.1 29 15 13.7 28.1 16.3 31.1 27 22"/></svg>');
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
position: absolute;
|
||||
@ -269,7 +274,98 @@
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0; }
|
||||
.formulate-input[data-classification="file"] .formulate-files .formulate-file-progress {
|
||||
background-color: #cecece;
|
||||
height: .3em;
|
||||
border-radius: 1.25em;
|
||||
width: 5em;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
right: .75em;
|
||||
transition: height .25s, width .25s;
|
||||
z-index: 2; }
|
||||
.formulate-input[data-classification="file"] .formulate-files .formulate-file-progress::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: block;
|
||||
opacity: 0;
|
||||
transform: scale(0.08);
|
||||
background-color: #ffffff;
|
||||
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path d="M8.76,56.2c-6.38-6.34,3.26-16,9.64-9.69L38,65.88,80.56,23.29c6.38-6.38,16.07,3.32,9.69,9.69L42.84,80.37a6.83,6.83,0,0,1-9.65,0Z"/></svg>');
|
||||
mask-size: 77%;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
z-index: 3; }
|
||||
.formulate-input[data-classification="file"] .formulate-files .formulate-file-progress[data-just-finished] {
|
||||
width: 1.25em;
|
||||
height: 1.25em; }
|
||||
.formulate-input[data-classification="file"] .formulate-files .formulate-file-progress[data-just-finished]::before {
|
||||
transition: transform .25s .2s, opacity .25s .2s;
|
||||
transform: scale(1);
|
||||
opacity: 1; }
|
||||
.formulate-input[data-classification="file"] .formulate-files .formulate-file-progress[data-is-finished] {
|
||||
transition: height .25s, width .25s, left .25s, top, .25s, border-radius .25s;
|
||||
width: .3em;
|
||||
height: 100%;
|
||||
right: 0;
|
||||
border-radius: 0; }
|
||||
.formulate-input[data-classification="file"] .formulate-files .formulate-file-progress[data-is-finished]::before {
|
||||
transition: opacity .1s;
|
||||
opacity: 0; }
|
||||
.formulate-input[data-classification="file"] .formulate-files .formulate-file-progress .formulate-file-progress-inner {
|
||||
background-color: #41b883;
|
||||
width: 1%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
z-index: 2; }
|
||||
.formulate-input[data-classification="file"] .formulate-files .formualte-file-name {
|
||||
padding-left: 1.5em;
|
||||
padding-right: 2em; }
|
||||
.formulate-input[data-classification="file"] .formulate-files .formualte-file-name::before {
|
||||
position: absolute;
|
||||
left: .7em;
|
||||
top: 50%;
|
||||
margin-top: -.7em;
|
||||
background-color: #a8a8a8;
|
||||
content: '';
|
||||
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64.06 83.59"><path d="M55.94,83.59a8.16,8.16,0,0,0,8.12-8.16V19.12a1.77,1.77,0,0,0-.52-1.25L46.21.59A1.69,1.69,0,0,0,45.14.08L44.69,0l-.18,0H8.13A8.18,8.18,0,0,0,0,8.16V75.41a8.16,8.16,0,0,0,8.13,8.16H55.94ZM46.68,6,58.11,17.38H46.68ZM3.52,75.43V8.16A4.64,4.64,0,0,1,8.13,3.52h35V19.16a1.75,1.75,0,0,0,1.76,1.74H60.55V75.43a4.65,4.65,0,0,1-4.61,4.65H8.13A4.65,4.65,0,0,1,3.52,75.43Z"/></svg>');
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
width: 1.25em;
|
||||
height: 1.25em;
|
||||
display: inline-block;
|
||||
margin-right: .5em; }
|
||||
.formulate-input[data-classification="file"] .formulate-files .formulate-file-remove {
|
||||
width: 1.25em;
|
||||
height: 1.25em;
|
||||
border-radius: 1em;
|
||||
border: 1px solid #a8a8a8;
|
||||
background-color: #a8a8a8;
|
||||
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 59.1 59.2"><path d="M1.6,57.7a5,5,0,0,0,3.5,1.5,4.85,4.85,0,0,0,3.5-1.5l21-21,21,21a5,5,0,0,0,3.5,1.5,4.85,4.85,0,0,0,3.5-1.5,5,5,0,0,0,0-7.1l-21-21,21-21a5,5,0,0,0,0-7.1,5,5,0,0,0-7.1,0l-21,21L8.6,1.7a5,5,0,0,0-7.1,0,5,5,0,0,0,0,7.1l21,21L1.6,50.7A4.83,4.83,0,0,0,1.6,57.7Z"/></svg>');
|
||||
mask-size: .6em;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: .75em;
|
||||
z-index: 1;
|
||||
transition: transform .25s; }
|
||||
@media (pointer: fine) {
|
||||
.formulate-input[data-classification="file"] .formulate-files .formulate-file-remove:hover {
|
||||
transform: scale(1.5); } }
|
||||
.formulate-input[data-classification="file"] .formulate-files li {
|
||||
display: block; }
|
||||
.formulate-input[data-classification="file"] .formulate-files li[data-has-error] .formulate-file-progress {
|
||||
background-color: #dc2c2c; }
|
||||
.formulate-input[data-classification="file"] .formulate-files li + li {
|
||||
margin-top: .5em; }
|
||||
.formulate-input[data-classification="file"] .formulate-files .formulate-file {
|
||||
appearance: none;
|
||||
border-radius: .3em;
|
||||
border: 1px solid #cecece;
|
||||
@ -286,14 +382,19 @@
|
||||
display: block;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between; }
|
||||
.formulate-input[data-classification="file"] .formulate-files li::placeholder {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
overflow: hidden; }
|
||||
.formulate-input[data-classification="file"] .formulate-files .formulate-file::placeholder {
|
||||
color: #a8a8a8; }
|
||||
.formulate-input[data-classification="file"] .formulate-files li:focus {
|
||||
.formulate-input[data-classification="file"] .formulate-files .formulate-file:focus {
|
||||
outline: 0;
|
||||
border: 1px solid #41b883; }
|
||||
.formulate-input[data-classification="file"] .formulate-files li ::-webkit-progress-bar {
|
||||
.formulate-input[data-classification="file"] .formulate-files .formulate-file ::-webkit-progress-bar {
|
||||
appearance: none;
|
||||
height: .5em;
|
||||
border-radius: .5em;
|
||||
overflow: hidden; }
|
||||
.formulate-input[data-classification="file"] [data-type="image"] .formulate-input-upload-area .formulate-input-upload-area-mask::before {
|
||||
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 90 71.05"><path d="M82.89,0H7.1A7.12,7.12,0,0,0,0,7.11V64a7.11,7.11,0,0,0,7.1,7.1H82.9A7.11,7.11,0,0,0,90,64V7.11A7.12,7.12,0,0,0,82.89,0ZM69.28,39.35a5.44,5.44,0,0,0-8,0L50.58,50.74,32.38,30.88a5.31,5.31,0,0,0-7.92,0L4.74,52.4V7.11A2.37,2.37,0,0,1,7.11,4.74H82.9a2.37,2.37,0,0,1,2.36,2.37V56.3Z"/><circle cx="67.74" cy="22.26" r="8.53"/></svg>'); }
|
||||
|
4
dist/snow.min.css
vendored
4
dist/snow.min.css
vendored
File diff suppressed because one or more lines are too long
@ -10,11 +10,12 @@ class FileUpload {
|
||||
* @param {FileList} fileList
|
||||
* @param {object} context
|
||||
*/
|
||||
constructor (fileList, context, options) {
|
||||
this.fileList = fileList
|
||||
constructor (input, context, options) {
|
||||
this.input = input
|
||||
this.fileList = input.files
|
||||
this.files = []
|
||||
this.options = options
|
||||
this.setFileList(fileList)
|
||||
this.addFileList(this.fileList)
|
||||
this.context = context
|
||||
}
|
||||
|
||||
@ -22,14 +23,22 @@ class FileUpload {
|
||||
* Produce an array of files and alert the callback.
|
||||
* @param {FileList}
|
||||
*/
|
||||
setFileList (fileList) {
|
||||
addFileList (fileList) {
|
||||
for (let i = 0; i < fileList.length; i++) {
|
||||
const file = fileList.item(i)
|
||||
const file = fileList[i]
|
||||
const uuid = nanoid()
|
||||
const removeFile = function () {
|
||||
this.removeFile(uuid)
|
||||
}
|
||||
this.files.push({
|
||||
progress: 0,
|
||||
progress: false,
|
||||
error: false,
|
||||
complete: false,
|
||||
justFinished: false,
|
||||
name: file.name || 'file-upload',
|
||||
file: file,
|
||||
uuid: nanoid()
|
||||
file,
|
||||
uuid,
|
||||
removeFile: removeFile.bind(this)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -85,8 +94,21 @@ class FileUpload {
|
||||
Promise.all(this.files.map(file => {
|
||||
return this.getUploader(
|
||||
file.file,
|
||||
(progress) => { file.progress = progress },
|
||||
(error) => reject(new Error(error)),
|
||||
(progress) => {
|
||||
file.progress = progress
|
||||
if (progress >= 100) {
|
||||
if (!file.complete) {
|
||||
file.justFinished = true
|
||||
setTimeout(() => { file.justFinished = false }, this.options.uploadJustCompleteDuration)
|
||||
}
|
||||
file.complete = true
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
file.progress = 0
|
||||
file.error = error
|
||||
file.complete = true
|
||||
},
|
||||
this.options
|
||||
)
|
||||
}))
|
||||
@ -95,6 +117,20 @@ class FileUpload {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a file from the uploader (and the file list)
|
||||
* @param {string} uuid
|
||||
*/
|
||||
removeFile (uuid) {
|
||||
this.files = this.files.filter(file => file.uuid !== uuid)
|
||||
if (window) {
|
||||
const transfer = new DataTransfer()
|
||||
this.files.map(file => transfer.items.add(file.file))
|
||||
this.fileList = transfer.files
|
||||
this.input.files = this.fileList
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the files.
|
||||
*/
|
||||
|
@ -40,6 +40,7 @@ class Formulate {
|
||||
rules,
|
||||
locale: 'en',
|
||||
uploader: fauxUploader,
|
||||
uploadJustCompleteDuration: 1000,
|
||||
locales: {
|
||||
en
|
||||
}
|
||||
|
@ -6,15 +6,34 @@
|
||||
<li
|
||||
v-for="file in fileUploads"
|
||||
:key="file.uuid"
|
||||
class="formulate-file"
|
||||
:data-has-error="!!file.error"
|
||||
>
|
||||
<span
|
||||
<div class="formulate-file">
|
||||
<div
|
||||
class="formualte-file-name"
|
||||
v-text="file.name"
|
||||
/>
|
||||
<span
|
||||
v-if="file.progress > 0 && file.progress < 100"
|
||||
v-text="`${file.progress}%`"
|
||||
<div
|
||||
v-if="file.progress !== false"
|
||||
:data-just-finished="file.justFinished"
|
||||
:data-is-finished="!file.justFinished && file.complete"
|
||||
class="formulate-file-progress"
|
||||
>
|
||||
<div
|
||||
class="formulate-file-progress-inner"
|
||||
:style="{width: file.progress + '%'}"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="(file.complete && !file.justFinished) || file.progress === false"
|
||||
class="formulate-file-remove"
|
||||
@click="file.removeFile"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="file.error"
|
||||
class="formulate-file-upload-error"
|
||||
v-text="file.error"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -149,7 +149,11 @@ export default {
|
||||
type: [Function, Object, Boolean],
|
||||
default: false
|
||||
},
|
||||
immediateUpload: {
|
||||
uploadBehavior: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
preventWindowDrops: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
|
@ -48,16 +48,36 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
hasFiles () {
|
||||
return (this.context.model instanceof FileUpload && this.context.model.files.length)
|
||||
return !!(this.context.model instanceof FileUpload && this.context.model.files.length)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// Add a listener to the window to prevent drag/drops that miss the dropzone
|
||||
// from opening the file and navigating the user away from the page.
|
||||
if (window && this.context.preventWindowDrops) {
|
||||
window.addEventListener('dragover', this.preventDefault)
|
||||
window.addEventListener('drop', this.preventDefault)
|
||||
}
|
||||
},
|
||||
destroyed () {
|
||||
if (window && this.context.preventWindowDrops) {
|
||||
window.removeEventListener('dragover', this.preventDefault)
|
||||
window.removeEventListener('drop', this.preventDefault)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
preventDefault (e) {
|
||||
if (e.target.tagName !== 'INPUT' && e.target.getAttribute('type') !== 'file') {
|
||||
e = e || event
|
||||
e.preventDefault()
|
||||
}
|
||||
},
|
||||
handleFile () {
|
||||
const input = this.$refs.file
|
||||
if (input.files.length) {
|
||||
this.context.model = this.$formulate.createUpload(input.files, this.context)
|
||||
this.context.model = this.$formulate.createUpload(input, this.context)
|
||||
}
|
||||
if (this.context.immediateUpload && this.context.model instanceof FileUpload) {
|
||||
if (this.context.uploadBehavior === 'live' && this.context.model instanceof FileUpload) {
|
||||
this.context.model.upload()
|
||||
}
|
||||
},
|
||||
|
@ -22,7 +22,8 @@ export default {
|
||||
showImage: this.showImage,
|
||||
uploadUrl: this.uploadUrl,
|
||||
uploader: this.uploader || this.$formulate.getUploader(),
|
||||
immediateUpload: this.immediateUpload,
|
||||
uploadBehavior: this.uploadBehavior,
|
||||
preventWindowDrops: this.preventWindowDrops,
|
||||
...this.typeContext
|
||||
})
|
||||
},
|
||||
|
@ -8,14 +8,26 @@
|
||||
*/
|
||||
export default function (file, progress, error, options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const totalTime = options.fauxUploaderDuration || 2000
|
||||
const totalTime = (options.fauxUploaderDuration || 2000) * (0.5 + Math.random())
|
||||
const start = performance.now()
|
||||
/**
|
||||
* @todo - remove, intentional failure
|
||||
*/
|
||||
const fail = (Math.random() > 0.5)
|
||||
const advance = () => setTimeout(() => {
|
||||
const elapsed = performance.now() - start
|
||||
const currentProgress = Math.min(100, Math.round(elapsed / totalTime * 100))
|
||||
progress(currentProgress)
|
||||
|
||||
/**
|
||||
* @todo - remove, intentional failure
|
||||
*/
|
||||
if (fail && currentProgress > 50) {
|
||||
return error('There was an error uploading the file.')
|
||||
}
|
||||
|
||||
if (currentProgress >= 100) {
|
||||
resolve({
|
||||
return resolve({
|
||||
url: 'http://via.placeholder.com/350x150.png',
|
||||
name: file.name
|
||||
})
|
||||
|
@ -149,14 +149,10 @@ export default {
|
||||
mime: function (files, ...types) {
|
||||
return Promise.resolve((() => {
|
||||
if (files instanceof FileUpload) {
|
||||
if (files.hasUploader()) {
|
||||
return false
|
||||
}
|
||||
files = files.getFiles()
|
||||
}
|
||||
if (typeof window !== 'undefined' && typeof FileReader !== 'undefined' && typeof Blob !== 'undefined') {
|
||||
for (const i in files) {
|
||||
if (!types.includes(files[i].type)) {
|
||||
const fileList = files.getFileList()
|
||||
for (let i = 0; i < fileList.length; i++) {
|
||||
const file = fileList[i]
|
||||
if (!types.includes(file.type)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -113,6 +113,13 @@ export default {
|
||||
return `${s(name)} must be less than ${args[0]} characters long.`
|
||||
},
|
||||
|
||||
/**
|
||||
* The (field-level) error message for mime errors.
|
||||
*/
|
||||
mime: function ({ name, args }) {
|
||||
return `${s(name)} must of the the type: ${args[0] || 'No file formats allowed.'}`
|
||||
},
|
||||
|
||||
/**
|
||||
* The maximum value allowed.
|
||||
*/
|
||||
|
@ -32,7 +32,6 @@ test('type "radio" with options renders a group', () => {
|
||||
expect(wrapper.contains(FormulateInputGroup)).toBe(true)
|
||||
})
|
||||
|
||||
|
||||
test('labelPosition of type "checkbox" defaults to after', () => {
|
||||
const wrapper = mount(FormulateInput, { propsData: { type: 'checkbox' } })
|
||||
expect(wrapper.vm.context.labelPosition).toBe('after')
|
||||
|
@ -1,4 +1,5 @@
|
||||
import rules from '@/libs/rules'
|
||||
import FileUpload from '../src/FileUpload'
|
||||
|
||||
/**
|
||||
* Required rule
|
||||
@ -295,9 +296,26 @@ describe('email', () => {
|
||||
* Mime types.
|
||||
*/
|
||||
describe('mime', () => {
|
||||
it('passes basic image/jpeg stack', async () => expect(await rules.mime([{type: 'image/jpeg'}], 'image/png', 'image/jpeg')).toBe(true))
|
||||
it('passes basic image/jpeg stack', async () => {
|
||||
const fileUpload = new FileUpload({
|
||||
files: [ { type: 'image/jpeg' } ]
|
||||
})
|
||||
expect(await rules.mime(fileUpload, 'image/png', 'image/jpeg')).toBe(true)
|
||||
})
|
||||
|
||||
it('fails when not in stack', async () => expect(await rules.mime([{type: 'application/json'}], 'image/png', 'image/jpeg')).toBe(false))
|
||||
it('passes when match is at begining of stack', async () => {
|
||||
const fileUpload = new FileUpload({
|
||||
files: [ { type: 'document/pdf' } ]
|
||||
})
|
||||
expect(await rules.mime(fileUpload, 'document/pdf')).toBe(true)
|
||||
})
|
||||
|
||||
it('fails when not in stack', async () => {
|
||||
const fileUpload = new FileUpload({
|
||||
files: [ { type: 'application/json' } ]
|
||||
})
|
||||
expect(await rules.mime(fileUpload, 'image/png', 'image/jpeg')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
|
@ -31,7 +31,8 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.formulate-input-error {
|
||||
.formulate-input-error,
|
||||
.formulate-file-upload-error {
|
||||
color: $formulate-error;
|
||||
font-size: .8em;
|
||||
font-weight: 300;
|
||||
@ -286,11 +287,7 @@
|
||||
.formulate-input-upload-area {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
padding: 2em;
|
||||
|
||||
&[data-has-files] {
|
||||
padding: 0;
|
||||
}
|
||||
padding: 2em 0;
|
||||
|
||||
input {
|
||||
cursor: pointer;
|
||||
@ -306,6 +303,15 @@
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
|
||||
&[data-has-files] {
|
||||
padding: 0;
|
||||
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-mask {
|
||||
border-radius: .4em;
|
||||
position: absolute;
|
||||
@ -325,6 +331,8 @@
|
||||
content: '';
|
||||
background-color: $formulate-gray-dd;
|
||||
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 58 58"><path d="M29,58A29,29,0,1,0,0,29,29,29,0,0,0,29,58ZM29,4A25,25,0,1,1,4,29,25,25,0,0,1,29,4Z"/><polygon points="27 22 27 44.4 31 44.4 31 22 41.7 31.1 44.3 28.1 29 15 13.7 28.1 16.3 31.1 27 22"/></svg>');
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
position: absolute;
|
||||
@ -345,17 +353,145 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.formulate-files {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
.formulate-file-progress {
|
||||
background-color: $formulate-gray-d;
|
||||
height: .3em;
|
||||
border-radius: 1.25em;
|
||||
width: 5em;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
right: .75em;
|
||||
transition: height .25s, width .25s;
|
||||
z-index: 2;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: block;
|
||||
opacity: 0;
|
||||
transform: scale(.08);
|
||||
background-color: $formulate-white;
|
||||
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><path d="M8.76,56.2c-6.38-6.34,3.26-16,9.64-9.69L38,65.88,80.56,23.29c6.38-6.38,16.07,3.32,9.69,9.69L42.84,80.37a6.83,6.83,0,0,1-9.65,0Z"/></svg>');
|
||||
mask-size: 77%;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
&[data-just-finished] {
|
||||
width: 1.25em;
|
||||
height: 1.25em;
|
||||
|
||||
&::before {
|
||||
transition: transform .25s .2s, opacity .25s .2s;
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-is-finished] {
|
||||
transition: height .25s, width .25s, left .25s, top, .25s, border-radius .25s;
|
||||
width: .3em;
|
||||
height: 100%;
|
||||
right: 0;
|
||||
border-radius: 0;
|
||||
|
||||
|
||||
&::before {
|
||||
transition: opacity .1s;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.formulate-file-progress-inner {
|
||||
background-color: $formulate-green;
|
||||
width: 1%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.formualte-file-name {
|
||||
padding-left: 1.5em;
|
||||
padding-right: 2em;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: .7em;
|
||||
top: 50%;
|
||||
margin-top: -.7em;
|
||||
background-color: $formulate-gray-dd;
|
||||
content: '';
|
||||
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64.06 83.59"><path d="M55.94,83.59a8.16,8.16,0,0,0,8.12-8.16V19.12a1.77,1.77,0,0,0-.52-1.25L46.21.59A1.69,1.69,0,0,0,45.14.08L44.69,0l-.18,0H8.13A8.18,8.18,0,0,0,0,8.16V75.41a8.16,8.16,0,0,0,8.13,8.16H55.94ZM46.68,6,58.11,17.38H46.68ZM3.52,75.43V8.16A4.64,4.64,0,0,1,8.13,3.52h35V19.16a1.75,1.75,0,0,0,1.76,1.74H60.55V75.43a4.65,4.65,0,0,1-4.61,4.65H8.13A4.65,4.65,0,0,1,3.52,75.43Z"/></svg>');
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
width: 1.25em;
|
||||
height: 1.25em;
|
||||
display: inline-block;
|
||||
margin-right: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.formulate-file-remove {
|
||||
width: 1.25em;
|
||||
height: 1.25em;
|
||||
border-radius: 1em;
|
||||
border: 1px solid $formulate-gray-dd;
|
||||
background-color: $formulate-gray-dd;
|
||||
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 59.1 59.2"><path d="M1.6,57.7a5,5,0,0,0,3.5,1.5,4.85,4.85,0,0,0,3.5-1.5l21-21,21,21a5,5,0,0,0,3.5,1.5,4.85,4.85,0,0,0,3.5-1.5,5,5,0,0,0,0-7.1l-21-21,21-21a5,5,0,0,0,0-7.1,5,5,0,0,0-7.1,0l-21,21L8.6,1.7a5,5,0,0,0-7.1,0,5,5,0,0,0,0,7.1l21,21L1.6,50.7A4.83,4.83,0,0,0,1.6,57.7Z"/></svg>');
|
||||
mask-size: .6em;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: .75em;
|
||||
z-index: 1;
|
||||
transition: transform .25s;
|
||||
|
||||
@media (pointer: fine) {
|
||||
&:hover {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
display: block;
|
||||
|
||||
&[data-has-error] {
|
||||
.formulate-file-progress {
|
||||
background-color: $formulate-error-l;
|
||||
}
|
||||
}
|
||||
|
||||
& + li {
|
||||
margin-top: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.formulate-file {
|
||||
@include baseinput;
|
||||
display: block;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
@mixin progress {
|
||||
appearance: none;
|
||||
@ -369,5 +505,21 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Image uploads
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
[data-type="image"] {
|
||||
.formulate-input-upload-area {
|
||||
.formulate-input-upload-area-mask {
|
||||
&::before {
|
||||
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 90 71.05"><path d="M82.89,0H7.1A7.12,7.12,0,0,0,0,7.11V64a7.11,7.11,0,0,0,7.1,7.1H82.9A7.11,7.11,0,0,0,90,64V7.11A7.12,7.12,0,0,0,82.89,0ZM69.28,39.35a5.44,5.44,0,0,0-8,0L50.58,50.74,32.38,30.88a5.31,5.31,0,0,0-7.92,0L4.74,52.4V7.11A2.37,2.37,0,0,1,7.11,4.74H82.9a2.37,2.37,0,0,1,2.36,2.37V56.3Z"/><circle cx="67.74" cy="22.26" r="8.53"/></svg>');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -16,11 +16,14 @@ $formulate-blue-l: #f3f4f4;
|
||||
$formulate-green: #41b883;
|
||||
|
||||
$formulate-error: #960505;
|
||||
$formulate-error-l: #dc2c2c;
|
||||
|
||||
$formulate-yellow-d: #6b5900;
|
||||
$formulate-yellow: #e6c000;
|
||||
$formulate-yellow-l: #fff8d2;
|
||||
|
||||
$formulate-white: #ffffff;
|
||||
|
||||
|
||||
|
||||
// Mixins
|
||||
|
Loading…
Reference in New Issue
Block a user