You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

419 lines
12 KiB

  1. // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. // MIT License. See license.txt
  3. // parent, args, callback
  4. frappe.upload = {
  5. make: function(opts) {
  6. if(!opts.args) opts.args = {};
  7. if(opts.allow_multiple === undefined) {
  8. opts.allow_multiple = 1
  9. }
  10. // whether to show public/private checkbox or not
  11. opts.show_private = !("is_private" in opts);
  12. // make private by default
  13. if (!("options" in opts) || ("options" in opts &&
  14. (opts.options && !opts.options.toLowerCase()=="public" && !opts.options.toLowerCase()=="image"))) {
  15. opts.is_private = 1;
  16. }
  17. var d = null;
  18. // create new dialog if no parent given
  19. if(!opts.parent) {
  20. d = new frappe.ui.Dialog({
  21. title: __('Upload Attachment'),
  22. primary_action_label: __('Attach'),
  23. primary_action: function() {}
  24. });
  25. opts.parent = d.body;
  26. opts.btn = d.get_primary_btn();
  27. d.show();
  28. }
  29. var $upload = $(frappe.render_template("upload", {opts:opts})).appendTo(opts.parent);
  30. var $file_input = $upload.find(".input-upload-file");
  31. var $uploaded_files_wrapper = $upload.find('.uploaded-filename');
  32. // bind pseudo browse button
  33. $upload.find(".btn-browse").on("click",
  34. function() { $file_input.click(); });
  35. $file_input.on("change", function() {
  36. if (this.files.length > 0 || opts.files) {
  37. var fileobjs = null;
  38. if (opts.files) {
  39. // files added programmatically
  40. fileobjs = opts.files;
  41. delete opts.files;
  42. } else {
  43. // files from input type file
  44. fileobjs = $upload.find(":file").get(0).files;
  45. }
  46. var file_array = $.makeArray(fileobjs);
  47. $upload.find(".web-link-wrapper").addClass("hidden");
  48. $upload.find(".btn-browse").removeClass("btn-primary").addClass("btn-default");
  49. $uploaded_files_wrapper.removeClass('hidden').empty();
  50. file_array = file_array.map(
  51. file => Object.assign(file, {is_private: opts.is_private ? 1 : 0})
  52. )
  53. $upload.data('attached_files', file_array);
  54. // List of files in a grid
  55. $uploaded_files_wrapper.append(`
  56. <div class="list-item list-item--head">
  57. <div class="list-item__content list-item__content--flex-2">
  58. ${__('Filename')}
  59. </div>
  60. ${opts.show_private
  61. ? `<div class="list-item__content file-public-column">
  62. ${__('Public')}
  63. </div>`
  64. : ''}
  65. <div class="list-item__content list-item__content--activity" style="flex: 0 0 32px">
  66. </div>
  67. </div>
  68. `);
  69. var file_pills = file_array.map(
  70. file => frappe.upload.make_file_row(file, opts)
  71. );
  72. $uploaded_files_wrapper.append(file_pills);
  73. } else {
  74. frappe.upload.show_empty_state($upload);
  75. }
  76. });
  77. if(opts.files && opts.files.length > 0) {
  78. $file_input.trigger('change');
  79. }
  80. // events
  81. $uploaded_files_wrapper.on('click', '.list-item-container', function (e) {
  82. var $item = $(this);
  83. var filename = $item.attr('data-filename');
  84. var attached_files = $upload.data('attached_files');
  85. var $target = $(e.target);
  86. if ($target.is(':checkbox')) {
  87. var is_private = !$target.is(':checked');
  88. attached_files = attached_files.map(file => {
  89. if (file.name === filename) {
  90. file.is_private = is_private ? 1 : 0;
  91. }
  92. return file;
  93. });
  94. $uploaded_files_wrapper
  95. .find(`.list-item-container[data-filename="${filename}"] .fa.fa-fw`)
  96. .toggleClass('fa-lock fa-unlock-alt');
  97. $upload.data('attached_files', attached_files);
  98. }
  99. else if ($target.is('.uploaded-file-remove, .fa-remove')) {
  100. // remove file from attached_files object
  101. attached_files = attached_files.filter(file => file.name !== filename);
  102. $upload.data('attached_files', attached_files);
  103. // remove row from dom
  104. $uploaded_files_wrapper
  105. .find(`.list-item-container[data-filename="${filename}"]`)
  106. .remove();
  107. if(attached_files.length === 0) {
  108. frappe.upload.show_empty_state($upload);
  109. }
  110. }
  111. });
  112. if(!opts.btn) {
  113. opts.btn = $('<button class="btn btn-default btn-sm attach-btn">' + __("Attach")
  114. + '</div>').appendTo($upload);
  115. } else {
  116. $(opts.btn).unbind("click");
  117. }
  118. // Primary button handler
  119. opts.btn.click(function() {
  120. // close created dialog
  121. d && d.hide();
  122. // convert functions to values
  123. if(opts.get_params) {
  124. opts.args.params = opts.get_params();
  125. }
  126. // Get file url if input is visible
  127. var file_url = $upload.find('[name="file_url"]:visible');
  128. file_url = file_url.length && file_url.get(0).value;
  129. if(opts.args.gs_template) {
  130. frappe.integration_service.gsuite.create_gsuite_file(opts.args,opts);
  131. } else if(file_url) {
  132. opts.args.file_url = file_url;
  133. frappe.upload.upload_file(null, opts.args, opts);
  134. } else {
  135. var files = $upload.data('attached_files');
  136. frappe.upload.upload_multiple_files(files, opts.args, opts);
  137. }
  138. });
  139. },
  140. make_file_row: function(file, { show_private } = {}) {
  141. var template = `
  142. <div class="list-item-container" data-filename="${file.name}">
  143. <div class="list-item">
  144. <div class="list-item__content list-item__content--flex-2 ellipsis">
  145. <span>${file.name}</span>
  146. <span style="margin-top: 1px; margin-left: 5px;"
  147. class="fa fa-fw text-warning ${file.is_private ? 'fa-lock': 'fa-unlock-alt'}">
  148. </span>
  149. </div>
  150. ${show_private?
  151. `<div class="list-item__content file-public-column ellipsis">
  152. <input type="checkbox" ${!file.is_private ? 'checked' : ''}/></div>`
  153. : ''}
  154. <div class="list-item__content list-item__content--activity ellipsis" style="flex: 0 0 32px;">
  155. <button class="btn btn-default btn-xs text-muted uploaded-file-remove">
  156. <span class="fa fa-remove"></span>
  157. </button>
  158. </div>
  159. </div>
  160. </div>`;
  161. return $(template);
  162. },
  163. show_empty_state: function($upload) {
  164. $upload.find(".uploaded-filename").addClass("hidden");
  165. $upload.find(".web-link-wrapper").removeClass("hidden");
  166. $upload.find(".private-file").addClass("hidden");
  167. $upload.find(".btn-browse").removeClass("btn-default").addClass("btn-primary");
  168. },
  169. upload_multiple_files: function(files /*FileData array*/, args, opts) {
  170. var i = -1;
  171. frappe.upload.total_files = files ? files.length : 0;
  172. // upload the first file
  173. upload_next();
  174. // subsequent files will be uploaded after
  175. // upload_complete event is fired for the previous file
  176. $(document).on('upload_complete', on_upload);
  177. function upload_next() {
  178. if(files) {
  179. i += 1;
  180. var file = files[i];
  181. args.is_private = file.is_private;
  182. if(!opts.progress) {
  183. frappe.show_progress(__('Uploading'), i, files.length);
  184. }
  185. }
  186. frappe.upload.upload_file(file, args, opts);
  187. }
  188. function on_upload(e, attachment) {
  189. if (!files || i === files.length - 1) {
  190. $(document).off('upload_complete', on_upload);
  191. frappe.hide_progress();
  192. return;
  193. }
  194. upload_next();
  195. }
  196. },
  197. upload_file: function(fileobj, args, opts) {
  198. if(!fileobj && !args.file_url) {
  199. if(opts.on_no_attach) {
  200. opts.on_no_attach();
  201. } else {
  202. frappe.msgprint(__("Please attach a file or set a URL"));
  203. }
  204. return;
  205. }
  206. if(fileobj) {
  207. frappe.upload.read_file(fileobj, args, opts);
  208. } else {
  209. // with file_url
  210. frappe.upload._upload_file(fileobj, args, opts);
  211. }
  212. },
  213. _upload_file: function(fileobj, args, opts, dataurl) {
  214. if (args.file_size) {
  215. frappe.upload.validate_max_file_size(args.file_size);
  216. }
  217. if(opts.on_attach) {
  218. opts.on_attach(args, dataurl)
  219. } else {
  220. if (opts.confirm_is_private) {
  221. frappe.prompt({
  222. label: __("Private"),
  223. fieldname: "is_private",
  224. fieldtype: "Check",
  225. "default": 1
  226. }, function(values) {
  227. args["is_private"] = values.is_private;
  228. frappe.upload.upload_to_server(fileobj, args, opts);
  229. }, __("Private or Public?"));
  230. } else {
  231. if (!("is_private" in args) && "is_private" in opts) {
  232. args["is_private"] = opts.is_private;
  233. }
  234. frappe.upload.upload_to_server(fileobj, args, opts);
  235. }
  236. }
  237. },
  238. read_file: function(fileobj, args, opts) {
  239. args.filename = fileobj.name.split(' ').join('_');
  240. args.file_url = null;
  241. if(opts.options && opts.options.toLowerCase()=="image") {
  242. if(!frappe.utils.is_image_file(args.filename)) {
  243. frappe.msgprint(__("Only image extensions (.gif, .jpg, .jpeg, .tiff, .png, .svg) allowed"));
  244. return;
  245. }
  246. }
  247. let start_complete = frappe.cur_progress ? frappe.cur_progress.percent : 0;
  248. var upload_with_filedata = function() {
  249. let freader = new FileReader();
  250. freader.onload = function() {
  251. var dataurl = freader.result;
  252. args.filedata = freader.result.split(",")[1];
  253. args.file_size = fileobj.size;
  254. frappe.upload._upload_file(fileobj, args, opts, dataurl);
  255. };
  256. freader.readAsDataURL(fileobj);
  257. }
  258. if (opts.no_socketio || frappe.flags.no_socketio) {
  259. upload_with_filedata();
  260. return;
  261. }
  262. frappe.socketio.uploader.start({
  263. file: fileobj,
  264. filename: args.filename,
  265. is_private: args.is_private,
  266. fallback: () => {
  267. // if fails, use old filereader
  268. upload_with_filedata();
  269. },
  270. callback: (data) => {
  271. args.file_url = data.file_url;
  272. frappe.upload._upload_file(fileobj, args, opts);
  273. },
  274. on_progress: (percent_complete) => {
  275. let increment = (flt(percent_complete) / frappe.upload.total_files);
  276. frappe.show_progress(__('Uploading'),
  277. start_complete + increment);
  278. }
  279. });
  280. },
  281. upload_to_server: function(file, args, opts) {
  282. if(opts.start) {
  283. opts.start();
  284. }
  285. var ajax_args = {
  286. "method": "uploadfile",
  287. args: args,
  288. callback: function(r) {
  289. if(!r._server_messages) {
  290. // msgbox.hide();
  291. }
  292. if(r.exc) {
  293. // if no onerror, assume callback will handle errors
  294. opts.onerror ? opts.onerror(r) : opts.callback(null, r);
  295. frappe.hide_progress();
  296. return;
  297. }
  298. var attachment = r.message;
  299. opts.loopcallback && opts.loopcallback();
  300. opts.callback && opts.callback(attachment, r);
  301. $(document).trigger("upload_complete", attachment);
  302. },
  303. error: function(r) {
  304. // if no onerror, assume callback will handle errors
  305. opts.onerror ? opts.onerror(r) : opts.callback(null, null, r);
  306. frappe.hide_progress();
  307. return;
  308. }
  309. }
  310. // copy handlers etc from opts
  311. $.each(['queued', 'running', "progress", "always", "btn"], function(i, key) {
  312. if(opts[key]) ajax_args[key] = opts[key];
  313. });
  314. return frappe.call(ajax_args);
  315. },
  316. get_string: function(dataURI) {
  317. // remove filename
  318. var parts = dataURI.split(',');
  319. if(parts[0].indexOf(":")===-1) {
  320. var a = parts[2];
  321. } else {
  322. var a = parts[1];
  323. }
  324. return decodeURIComponent(escape(atob(a)));
  325. },
  326. validate_max_file_size: function(file_size) {
  327. var max_file_size = frappe.boot.max_file_size || 5242880;
  328. if (file_size > max_file_size) {
  329. // validate max file size
  330. frappe.throw(__("File size exceeded the maximum allowed size of {0} MB", [max_file_size / 1048576]));
  331. }
  332. },
  333. multifile_upload:function(fileobjs, args, opts) {
  334. //loop through filenames and checkboxes then append to list
  335. var fields = [];
  336. for (var i =0,j = fileobjs.length;i<j;i++) {
  337. var filename = fileobjs[i].name;
  338. fields.push({'fieldname': 'label1', 'fieldtype': 'Heading', 'label': filename});
  339. fields.push({'fieldname': filename+'_is_private', 'fieldtype': 'Check', 'label': 'Private', 'default': 1});
  340. }
  341. var d = new frappe.ui.Dialog({
  342. 'title': __('Make file(s) private or public?'),
  343. 'fields': fields,
  344. primary_action: function(){
  345. var i =0,j = fileobjs.length;
  346. d.hide();
  347. opts.loopcallback = function (){
  348. if (i < j) {
  349. args.is_private = d.fields_dict[fileobjs[i].name + "_is_private"].get_value();
  350. frappe.upload.upload_file(fileobjs[i], args, opts);
  351. i++;
  352. }
  353. };
  354. opts.loopcallback();
  355. }
  356. });
  357. d.show();
  358. opts.confirm_is_private = 0;
  359. },
  360. create_gsuite_file: function(args, opts) {
  361. return frappe.call({
  362. type:'POST',
  363. method: 'frappe.integrations.doctype.gsuite_templates.gsuite_templates.create_gsuite_doc',
  364. args: args,
  365. callback: function(r) {
  366. var attachment = r.message;
  367. opts.callback && opts.callback(attachment, r);
  368. }
  369. });
  370. }
  371. }