浏览代码

Enhance multi file upload (#2912)

* Allow multiple file upload, show progress while uploading

* Enhance multi file upload

You can now upload files programmatically like
```
frappe.upload.make({
  ...
  files: fileobj_array,
  ...
})
```
No need to use `multifile_upload` method.
Multiple file upload is enabled by default on all Attach dialogs.

* Don't `allow_multiple` in data import tool

* ControlAttach don't allow_multiple

* Show files in a table view

* Narrow width for Is Private column
version-14
Faris Ansari 8 年前
committed by Rushabh Mehta
父节点
当前提交
08da14996f
共有 9 个文件被更改,包括 195 次插入46 次删除
  1. +1
    -0
      frappe/core/page/data_import_tool/data_import_tool.js
  2. +12
    -1
      frappe/public/css/desk.css
  3. +1
    -0
      frappe/public/js/frappe/form/control.js
  4. +1
    -0
      frappe/public/js/frappe/form/footer/attachments.js
  5. +2
    -2
      frappe/public/js/frappe/ui/upload.html
  6. +158
    -38
      frappe/public/js/frappe/upload.js
  7. +4
    -4
      frappe/public/js/legacy/form.js
  8. +15
    -1
      frappe/public/less/desk.less
  9. +1
    -0
      frappe/utils/file_manager.py

+ 1
- 0
frappe/core/page/data_import_tool/data_import_tool.js 查看文件

@@ -119,6 +119,7 @@ frappe.DataImportTool = Class.extend({
args: {
method: 'frappe.core.page.data_import_tool.importer.upload',
},
allow_multiple: 0,
onerror: function(r) {
me.onerror(r);
},


+ 12
- 1
frappe/public/css/desk.css 查看文件

@@ -569,8 +569,19 @@ fieldset[disabled] .form-control {
display: inline-block;
vertical-align: middle;
}
.file-upload .input-upload {
vertical-align: top;
}
.file-upload .uploaded-filename {
border: 1px solid #d1d8dd;
border-radius: 3px;
}
.file-upload .uploaded-filename .btn-group {
margin-right: 5px;
margin-bottom: 5px;
}
.file-upload .uploaded-filename-display {
max-width: 194px;
max-width: 150px;
}
.frappe-rtl input,
.frappe-rtl textarea {


+ 1
- 0
frappe/public/js/frappe/form/control.js 查看文件

@@ -1000,6 +1000,7 @@ frappe.ui.form.ControlAttach = frappe.ui.form.ControlData.extend({
this.upload_options = {
parent: this.dialog.get_field("upload_area").$wrapper,
args: {},
allow_multiple: 0,
max_width: this.df.max_width,
max_height: this.df.max_height,
options: this.df.options,


+ 1
- 0
frappe/public/js/frappe/form/footer/attachments.js 查看文件

@@ -234,6 +234,7 @@ frappe.ui.get_upload_dialog = function(opts){
}
},
callback: function(r){
if(!r.message) return;
dialog.$wrapper.find('[name="file_url"]').val(r.message.file_url);
dialog.$wrapper.find('.private-file input').prop('checked', r.message.is_private);
opts.args.filename = r.message.file_name


+ 2
- 2
frappe/public/js/frappe/ui/upload.html 查看文件

@@ -1,9 +1,9 @@
<div class="file-upload">
<div class="input-upload">
<input class="input-upload-file hidden" type="file" name="filedata" />
<input class="input-upload-file hidden" type="file" {{ opts.allow_multiple ? "multiple" : "" }} name="filedata" />
<button class="btn btn-primary btn-sm btn-browse">{%= __("Browse") %}</button>
</div>
<div class="uploaded-filename hidden" style="width: calc(100% - 80px);"></div>
<div class="uploaded-filename hidden" style="width: 100%; margin-top: 12px;"></div>
<div class="web-link-wrapper" style="width: calc(100% - 80px);">
<span class="text-muted file-upload-or">{%= __("or") %}</span>
<div class="input-link" style="width: calc(100% - 30px);">


+ 158
- 38
frappe/public/js/frappe/upload.js 查看文件

@@ -5,45 +5,72 @@
frappe.upload = {
make: function(opts) {
if(!opts.args) opts.args = {};

if(opts.allow_multiple === undefined) {
opts.allow_multiple = 1
}

var d = null;
// create new dialog if no parent given
if(!opts.parent) {
d = new frappe.ui.Dialog({
title: __('Upload Attachment'),
primary_action_label: __('Attach'),
primary_action: function() {}
});
opts.parent = d.body;
opts.btn = d.get_primary_btn();
d.show();
}

var $upload = $(frappe.render_template("upload", {opts:opts})).appendTo(opts.parent);
var $file_input = $upload.find(".input-upload-file");
var $uploaded_files_wrapper = $upload.find('.uploaded-filename');

// bind pseudo browse button
$upload.find(".btn-browse").on("click",
function() { $file_input.click(); });

$file_input.on("change", function() {
if (this.files.length > 0) {
$upload.find(".web-link-wrapper").addClass("hidden");
$upload.find(".btn-browse").removeClass("btn-primary").addClass("btn-default");

var $uploaded_file_display = $(repl('<div class="btn-group" role="group">\
<button type="button" class="btn btn-default btn-sm \
ellipsis uploaded-filename-display">%(filename)s\
</button>\
<button type="button" class="btn btn-default btn-sm uploaded-file-remove">\
&times;</button>\
</div>', {filename: this.files[0].name}))
.appendTo($upload.find(".uploaded-filename").removeClass("hidden").empty());

$uploaded_file_display.find(".uploaded-filename-display").on("click", function() {
$file_input.click();
});

$uploaded_file_display.find(".uploaded-file-remove").on("click", function() {
$file_input.val("");
$file_input.trigger("change");
});

if(opts.on_select) {
opts.on_select();
}

if ( !("is_private" in opts) ) {
// show Private checkbox
$upload.find(".private-file").removeClass("hidden");
if (this.files.length > 0 || opts.files) {
var fileobjs = null;

if (opts.files) {
// files added programmatically
fileobjs = opts.files;
delete opts.files;
} else {
// files from input type file
fileobjs = $upload.find(":file").get(0).files;
}
var file_array = $.makeArray(fileobjs);

$upload.find(".web-link-wrapper").addClass("hidden");
$upload.find(".btn-browse").removeClass("btn-primary").addClass("btn-default");
$uploaded_files_wrapper.removeClass('hidden').empty();

file_array = file_array.map(
file => Object.assign(file, {is_private: 1})
)
$upload.data('attached_files', file_array);

// List of files in a grid
$uploaded_files_wrapper.append(`
<div class="list-item list-item--head">
<div class="list-item__content list-item__content--flex-2">
${__('Filename')}
</div>
<div class="list-item__content" style="flex: 0 0 64px">
${__('Is Private')}
</div>
<div class="list-item__content list-item__content--activity" style="flex: 0 0 32px">
</div>
</div>
`);
var file_pills = file_array.map(
file => frappe.upload.make_file_row(file.name, !("is_private" in opts))
);
$uploaded_files_wrapper.append(file_pills);
} else {
$upload.find(".uploaded-filename").addClass("hidden")
$upload.find(".web-link-wrapper").removeClass("hidden");
@@ -52,6 +79,40 @@ frappe.upload = {
}
});

if(opts.files && opts.files.length > 0) {
$file_input.trigger('change');
}

// events
$uploaded_files_wrapper.on('click', '.list-item-container', function (e) {
var $item = $(this);
var filename = $item.attr('data-filename');
var attached_files = $upload.data('attached_files');
var $target = $(e.target);

if ($target.is(':checkbox')) {
var is_private = $target.is(':checked');

attached_files = attached_files.map(file => {
if (file.name === filename) {
file.is_private = is_private ? 1 : 0;
}
return file;
});
$upload.data('attached_files', attached_files);
}
else if ($target.is('.uploaded-file-remove, .fa-remove')) {
// remove file from attached_files object
attached_files = attached_files.filter(file => file.name !== filename);
$upload.data('attached_files', attached_files);

// remove row from dom
$uploaded_files_wrapper
.find(`.list-item-container[data-filename="${filename}"]`)
.remove();
}
});


if(!opts.btn) {
opts.btn = $('<button class="btn btn-default btn-sm">' + __("Attach")
@@ -60,21 +121,77 @@ frappe.upload = {
$(opts.btn).unbind("click");
}

// get the first file
// Primary button handler
opts.btn.click(function() {
// convert functions to values
// close created dialog
d && d.hide();

// convert functions to values
if(opts.get_params) {
opts.args.params = opts.get_params();
}

opts.args.file_url = $upload.find('[name="file_url"]').val();
opts.args.is_private = $upload.find('.private-file input').prop('checked') ? 1 : 0;
// Get file url if input is visible
var file_url = $upload.find('[name="file_url"]:visible');
file_url = file_url.length && file_url.get(0).value;

var fileobj = $upload.find(":file").get(0).files[0];
frappe.upload.upload_file(fileobj, opts.args, opts);
if(file_url) {
opts.args.file_url = file_url;
frappe.upload.upload_file(null, opts.args, opts);
} else {
var files = $upload.data('attached_files');
frappe.upload.upload_multiple_files(files, opts.args, opts);
}
});
},
make_file_row: function(filename, show_private) {
var template = `
<div class="list-item-container" data-filename="${filename}">
<div class="list-item">
<div class="list-item__content list-item__content--flex-2 ellipsis">
${filename}
</div>
${show_private
? `<div class="list-item__content ellipsis" style="flex: 0 0 64px;">
<input type="checkbox" checked/>
</div>`
: ''}
<div class="list-item__content list-item__content--activity ellipsis" style="flex: 0 0 32px;">
<button class="btn btn-default btn-xs text-muted uploaded-file-remove">
<span class="fa fa-remove"></span>
</button>
</div>
</div>
</div>
`
return $(template);
},
upload_multiple_files: function(files /*FileData array*/, args, opts) {
var i = -1;

// upload the first file
upload_next();
// subsequent files will be uploaded after
// upload_complete event is fired for the previous file
$(document).on('upload_complete', on_upload);

function upload_next() {
i += 1;
var file = files[i];
args.is_private = file.is_private;
frappe.upload.upload_file(file, args, opts);
frappe.show_progress(__('Uploading'), i+1, files.length);
}

function on_upload(e, attachment) {
if (i === files.length - 1) {
$(document).off('upload_complete', on_upload);
frappe.hide_progress();
return;
}
upload_next();
}
},
upload_file: function(fileobj, args, opts) {
if(!fileobj && !args.file_url) {
if(opts.on_no_attach) {
@@ -153,7 +270,7 @@ frappe.upload = {
},

upload_to_server: function(fileobj, args, opts, dataurl) {
var msgbox = msgprint(__("Uploading..."));
// var msgbox = msgprint(__("Uploading..."));
if(opts.start) {
opts.start();
}
@@ -162,11 +279,12 @@ frappe.upload = {
args: args,
callback: function(r) {
if(!r._server_messages) {
msgbox.hide();
// msgbox.hide();
}
if(r.exc) {
// if no onerror, assume callback will handle errors
opts.onerror ? opts.onerror(r) : opts.callback(null, r);
frappe.hide_progress();
return;
}
var attachment = r.message;
@@ -177,6 +295,7 @@ frappe.upload = {
error: function(r) {
// if no onerror, assume callback will handle errors
opts.onerror ? opts.onerror(r) : opts.callback(null, null, r);
frappe.hide_progress();
return;
}
}
@@ -216,7 +335,7 @@ frappe.upload = {
for (var i =0,j = fileobjs.length;i<j;i++) {
var filename = fileobjs[i].name;
fields.push({'fieldname': 'label1', 'fieldtype': 'Heading', 'label': filename});
fields.push({'fieldname': 'is_private', 'fieldtype': 'Check', 'label': 'Private', 'default': 1});
fields.push({'fieldname': filename+'_is_private', 'fieldtype': 'Check', 'label': 'Private', 'default': 1});
}
var d = new frappe.ui.Dialog({
@@ -227,6 +346,7 @@ frappe.upload = {
d.hide();
opts.loopcallback = function (){
if (i < j) {
args.is_private = d.fields_dict[fileobjs[i].name + "_is_private"].get_value()
frappe.upload.upload_file(fileobjs[i], args, opts);
i++;
}


+ 4
- 4
frappe/public/js/legacy/form.js 查看文件

@@ -145,12 +145,12 @@ _f.Frm.prototype.setup_drag_drop = function() {
throw "attach error";
}

frappe.upload.multifile_upload(dataTransfer.files, me.attachments.get_args(), {
frappe.upload.make({
args: me.attachments.get_args(),
files: dataTransfer.files,
callback: function(attachment, r) {
me.attachments.attachment_uploaded(attachment, r);
},

confirm_is_private: true
}
});
});
}


+ 15
- 1
frappe/public/less/desk.less 查看文件

@@ -409,8 +409,22 @@ textarea.form-control {
vertical-align: middle;
}

.input-upload {
vertical-align: top;
}

.uploaded-filename {
border: 1px solid @border-color;
border-radius: 3px;
}

.uploaded-filename .btn-group {
margin-right: 5px;
margin-bottom: 5px;
}

.uploaded-filename-display {
max-width: 194px;
max-width: 150px;
}
}



+ 1
- 0
frappe/utils/file_manager.py 查看文件

@@ -50,6 +50,7 @@ def upload():
"name": filedata.name,
"file_name": filedata.file_name,
"file_url": filedata.file_url,
"is_private": filedata.is_private,
"comment": comment.as_dict() if comment else {}
}



正在加载...
取消
保存