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.

oc-dialogs.js 30KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991
  1. /**
  2. * ownCloud
  3. *
  4. * @author Bartek Przybylski, Christopher Schäpers, Thomas Tanghus
  5. * @copyright 2012 Bartek Przybylski bartek@alefzero.eu
  6. *
  7. * This library is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  9. * License as published by the Free Software Foundation; either
  10. * version 3 of the License, or any later version.
  11. *
  12. * This library is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  16. *
  17. * You should have received a copy of the GNU Affero General Public
  18. * License along with this library. If not, see <http://www.gnu.org/licenses/>.
  19. *
  20. */
  21. /* global alert */
  22. /**
  23. * this class to ease the usage of jquery dialogs
  24. * @lends OC.dialogs
  25. */
  26. var OCdialogs = {
  27. // dialog button types
  28. YES_NO_BUTTONS: 70,
  29. OK_BUTTONS: 71,
  30. FILEPICKER_TYPE_CHOOSE: 1,
  31. FILEPICKER_TYPE_MOVE: 2,
  32. FILEPICKER_TYPE_COPY: 3,
  33. FILEPICKER_TYPE_COPY_MOVE: 4,
  34. // used to name each dialog
  35. dialogsCounter: 0,
  36. /**
  37. * displays alert dialog
  38. * @param text content of dialog
  39. * @param title dialog title
  40. * @param callback which will be triggered when user presses OK
  41. * @param modal make the dialog modal
  42. */
  43. alert:function(text, title, callback, modal) {
  44. this.message(
  45. text,
  46. title,
  47. 'alert',
  48. OCdialogs.OK_BUTTON,
  49. callback,
  50. modal
  51. );
  52. },
  53. /**
  54. * displays info dialog
  55. * @param text content of dialog
  56. * @param title dialog title
  57. * @param callback which will be triggered when user presses OK
  58. * @param modal make the dialog modal
  59. */
  60. info:function(text, title, callback, modal) {
  61. this.message(text, title, 'info', OCdialogs.OK_BUTTON, callback, modal);
  62. },
  63. /**
  64. * displays confirmation dialog
  65. * @param text content of dialog
  66. * @param title dialog title
  67. * @param callback which will be triggered when user presses YES or NO
  68. * (true or false would be passed to callback respectively)
  69. * @param modal make the dialog modal
  70. */
  71. confirm:function(text, title, callback, modal) {
  72. return this.message(
  73. text,
  74. title,
  75. 'notice',
  76. OCdialogs.YES_NO_BUTTONS,
  77. callback,
  78. modal
  79. );
  80. },
  81. /**
  82. * displays confirmation dialog
  83. * @param text content of dialog
  84. * @param title dialog title
  85. * @param callback which will be triggered when user presses YES or NO
  86. * (true or false would be passed to callback respectively)
  87. * @param modal make the dialog modal
  88. */
  89. confirmHtml:function(text, title, callback, modal) {
  90. return this.message(
  91. text,
  92. title,
  93. 'notice',
  94. OCdialogs.YES_NO_BUTTONS,
  95. callback,
  96. modal,
  97. true
  98. );
  99. },
  100. /**
  101. * displays prompt dialog
  102. * @param text content of dialog
  103. * @param title dialog title
  104. * @param callback which will be triggered when user presses YES or NO
  105. * (true or false would be passed to callback respectively)
  106. * @param modal make the dialog modal
  107. * @param name name of the input field
  108. * @param password whether the input should be a password input
  109. */
  110. prompt: function (text, title, callback, modal, name, password) {
  111. return $.when(this._getMessageTemplate()).then(function ($tmpl) {
  112. var dialogName = 'oc-dialog-' + OCdialogs.dialogsCounter + '-content';
  113. var dialogId = '#' + dialogName;
  114. var $dlg = $tmpl.octemplate({
  115. dialog_name: dialogName,
  116. title : title,
  117. message : text,
  118. type : 'notice'
  119. });
  120. var input = $('<input/>');
  121. input.attr('type', password ? 'password' : 'text').attr('id', dialogName + '-input');
  122. var label = $('<label/>').attr('for', dialogName + '-input').text(name + ': ');
  123. $dlg.append(label);
  124. $dlg.append(input);
  125. if (modal === undefined) {
  126. modal = false;
  127. }
  128. $('body').append($dlg);
  129. // wrap callback in _.once():
  130. // only call callback once and not twice (button handler and close
  131. // event) but call it for the close event, if ESC or the x is hit
  132. if (callback !== undefined) {
  133. callback = _.once(callback);
  134. }
  135. var buttonlist = [{
  136. text : t('core', 'No'),
  137. click: function () {
  138. if (callback !== undefined) {
  139. callback(false, input.val());
  140. }
  141. $(dialogId).ocdialog('close');
  142. }
  143. }, {
  144. text : t('core', 'Yes'),
  145. click : function () {
  146. if (callback !== undefined) {
  147. callback(true, input.val());
  148. }
  149. $(dialogId).ocdialog('close');
  150. },
  151. defaultButton: true
  152. }
  153. ];
  154. $(dialogId).ocdialog({
  155. closeOnEscape: true,
  156. modal : modal,
  157. buttons : buttonlist,
  158. close : function() {
  159. // callback is already fired if Yes/No is clicked directly
  160. if (callback !== undefined) {
  161. callback(false, input.val());
  162. }
  163. }
  164. });
  165. input.focus();
  166. OCdialogs.dialogsCounter++;
  167. });
  168. },
  169. /**
  170. * show a file picker to pick a file from
  171. * @param title dialog title
  172. * @param callback which will be triggered when user presses Choose
  173. * @param multiselect whether it should be possible to select multiple files
  174. * @param mimetypeFilter mimetype to filter by - directories will always be included
  175. * @param modal make the dialog modal
  176. * @param type Type of file picker : Choose, copy, move, copy and move
  177. */
  178. filepicker:function(title, callback, multiselect, mimetypeFilter, modal, type) {
  179. var self = this;
  180. // avoid opening the picker twice
  181. if (this.filepicker.loading) {
  182. return;
  183. }
  184. if (type === undefined) {
  185. type = this.FILEPICKER_TYPE_CHOOSE;
  186. }
  187. this.filepicker.loading = true;
  188. this.filepicker.filesClient = (OCA.Sharing && OCA.Sharing.PublicApp && OCA.Sharing.PublicApp.fileList)? OCA.Sharing.PublicApp.fileList.filesClient: OC.Files.getClient();
  189. $.when(this._getFilePickerTemplate()).then(function($tmpl) {
  190. self.filepicker.loading = false;
  191. var dialogName = 'oc-dialog-filepicker-content';
  192. if(self.$filePicker) {
  193. self.$filePicker.ocdialog('close');
  194. }
  195. self.$filePicker = $tmpl.octemplate({
  196. dialog_name: dialogName,
  197. title: title,
  198. emptytext: t('core', 'No files in here')
  199. }).data('path', '').data('multiselect', multiselect).data('mimetype', mimetypeFilter);
  200. if (modal === undefined) {
  201. modal = false;
  202. }
  203. if (multiselect === undefined) {
  204. multiselect = false;
  205. }
  206. if (mimetypeFilter === undefined) {
  207. mimetypeFilter = '';
  208. }
  209. $('body').append(self.$filePicker);
  210. self.$filePicker.ready(function() {
  211. self.$filelist = self.$filePicker.find('.filelist tbody');
  212. self.$dirTree = self.$filePicker.find('.dirtree');
  213. self.$dirTree.on('click', 'div:not(:last-child)', self, function (event) {
  214. self._handleTreeListSelect(event, type);
  215. });
  216. self.$filelist.on('click', 'tr', function(event) {
  217. self._handlePickerClick(event, $(this), type);
  218. });
  219. self._fillFilePicker('');
  220. });
  221. // build buttons
  222. var functionToCall = function(returnType) {
  223. if (callback !== undefined) {
  224. var datapath;
  225. if (multiselect === true) {
  226. datapath = [];
  227. self.$filelist.find('tr.filepicker_element_selected').each(function(index, element) {
  228. datapath.push(self.$filePicker.data('path') + '/' + $(element).data('entryname'));
  229. });
  230. } else {
  231. datapath = self.$filePicker.data('path');
  232. var selectedName = self.$filelist.find('tr.filepicker_element_selected').data('entryname');
  233. if (selectedName) {
  234. datapath += '/' + selectedName;
  235. }
  236. }
  237. callback(datapath, returnType);
  238. self.$filePicker.ocdialog('close');
  239. }
  240. };
  241. var chooseCallback = function () {
  242. functionToCall(OCdialogs.FILEPICKER_TYPE_CHOOSE);
  243. };
  244. var copyCallback = function () {
  245. functionToCall(OCdialogs.FILEPICKER_TYPE_COPY);
  246. };
  247. var moveCallback = function () {
  248. functionToCall(OCdialogs.FILEPICKER_TYPE_MOVE);
  249. };
  250. var buttonlist = [];
  251. if (type === OCdialogs.FILEPICKER_TYPE_CHOOSE) {
  252. buttonlist.push({
  253. text: t('core', 'Choose'),
  254. click: chooseCallback,
  255. defaultButton: true
  256. });
  257. } else {
  258. if (type === OCdialogs.FILEPICKER_TYPE_COPY || type === OCdialogs.FILEPICKER_TYPE_COPY_MOVE) {
  259. buttonlist.push({
  260. text: t('core', 'Copy'),
  261. click: copyCallback,
  262. defaultButton: false
  263. });
  264. }
  265. if (type === OCdialogs.FILEPICKER_TYPE_MOVE || type === OCdialogs.FILEPICKER_TYPE_COPY_MOVE) {
  266. buttonlist.push({
  267. text: t('core', 'Move'),
  268. click: moveCallback,
  269. defaultButton: true
  270. });
  271. }
  272. }
  273. self.$filePicker.ocdialog({
  274. closeOnEscape: true,
  275. // max-width of 600
  276. width: 600,
  277. height: 500,
  278. modal: modal,
  279. buttons: buttonlist,
  280. style: {
  281. buttons: 'aside',
  282. },
  283. close: function() {
  284. try {
  285. $(this).ocdialog('destroy').remove();
  286. } catch(e) {}
  287. self.$filePicker = null;
  288. }
  289. });
  290. // We can access primary class only from oc-dialog.
  291. // Hence this is one of the approach to get the choose button.
  292. var getOcDialog = self.$filePicker.closest('.oc-dialog');
  293. var buttonEnableDisable = getOcDialog.find('.primary');
  294. if (self.$filePicker.data('mimetype') === "httpd/unix-directory") {
  295. buttonEnableDisable.prop("disabled", false);
  296. } else {
  297. buttonEnableDisable.prop("disabled", true);
  298. }
  299. })
  300. .fail(function(status, error) {
  301. // If the method is called while navigating away
  302. // from the page, it is probably not needed ;)
  303. self.filepicker.loading = false;
  304. if(status !== 0) {
  305. alert(t('core', 'Error loading file picker template: {error}', {error: error}));
  306. }
  307. });
  308. },
  309. /**
  310. * Displays raw dialog
  311. * You better use a wrapper instead ...
  312. */
  313. message:function(content, title, dialogType, buttons, callback, modal, allowHtml) {
  314. return $.when(this._getMessageTemplate()).then(function($tmpl) {
  315. var dialogName = 'oc-dialog-' + OCdialogs.dialogsCounter + '-content';
  316. var dialogId = '#' + dialogName;
  317. var $dlg = $tmpl.octemplate({
  318. dialog_name: dialogName,
  319. title: title,
  320. message: content,
  321. type: dialogType
  322. }, allowHtml ? {escapeFunction: ''} : {});
  323. if (modal === undefined) {
  324. modal = false;
  325. }
  326. $('body').append($dlg);
  327. var buttonlist = [];
  328. switch (buttons) {
  329. case OCdialogs.YES_NO_BUTTONS:
  330. buttonlist = [{
  331. text: t('core', 'No'),
  332. click: function(){
  333. if (callback !== undefined) {
  334. callback(false);
  335. }
  336. $(dialogId).ocdialog('close');
  337. }
  338. },
  339. {
  340. text: t('core', 'Yes'),
  341. click: function(){
  342. if (callback !== undefined) {
  343. callback(true);
  344. }
  345. $(dialogId).ocdialog('close');
  346. },
  347. defaultButton: true
  348. }];
  349. break;
  350. case OCdialogs.OK_BUTTON:
  351. var functionToCall = function() {
  352. $(dialogId).ocdialog('close');
  353. if(callback !== undefined) {
  354. callback();
  355. }
  356. };
  357. buttonlist[0] = {
  358. text: t('core', 'OK'),
  359. click: functionToCall,
  360. defaultButton: true
  361. };
  362. break;
  363. }
  364. $(dialogId).ocdialog({
  365. closeOnEscape: true,
  366. modal: modal,
  367. buttons: buttonlist
  368. });
  369. OCdialogs.dialogsCounter++;
  370. })
  371. .fail(function(status, error) {
  372. // If the method is called while navigating away from
  373. // the page, we still want to deliver the message.
  374. if(status === 0) {
  375. alert(title + ': ' + content);
  376. } else {
  377. alert(t('core', 'Error loading message template: {error}', {error: error}));
  378. }
  379. });
  380. },
  381. _fileexistsshown: false,
  382. /**
  383. * Displays file exists dialog
  384. * @param {object} data upload object
  385. * @param {object} original file with name, size and mtime
  386. * @param {object} replacement file with name, size and mtime
  387. * @param {object} controller with onCancel, onSkip, onReplace and onRename methods
  388. * @return {Promise} jquery promise that resolves after the dialog template was loaded
  389. */
  390. fileexists:function(data, original, replacement, controller) {
  391. var self = this;
  392. var dialogDeferred = new $.Deferred();
  393. var getCroppedPreview = function(file) {
  394. var deferred = new $.Deferred();
  395. // Only process image files.
  396. var type = file.type && file.type.split('/').shift();
  397. if (window.FileReader && type === 'image') {
  398. var reader = new FileReader();
  399. reader.onload = function (e) {
  400. var blob = new Blob([e.target.result]);
  401. window.URL = window.URL || window.webkitURL;
  402. var originalUrl = window.URL.createObjectURL(blob);
  403. var image = new Image();
  404. image.src = originalUrl;
  405. image.onload = function () {
  406. var url = crop(image);
  407. deferred.resolve(url);
  408. };
  409. };
  410. reader.readAsArrayBuffer(file);
  411. } else {
  412. deferred.reject();
  413. }
  414. return deferred;
  415. };
  416. var crop = function(img) {
  417. var canvas = document.createElement('canvas'),
  418. targetSize = 96,
  419. width = img.width,
  420. height = img.height,
  421. x, y, size;
  422. // Calculate the width and height, constraining the proportions
  423. if (width > height) {
  424. y = 0;
  425. x = (width - height) / 2;
  426. } else {
  427. y = (height - width) / 2;
  428. x = 0;
  429. }
  430. size = Math.min(width, height);
  431. // Set canvas size to the cropped area
  432. canvas.width = size;
  433. canvas.height = size;
  434. var ctx = canvas.getContext("2d");
  435. ctx.drawImage(img, x, y, size, size, 0, 0, size, size);
  436. // Resize the canvas to match the destination (right size uses 96px)
  437. resampleHermite(canvas, size, size, targetSize, targetSize);
  438. return canvas.toDataURL("image/png", 0.7);
  439. };
  440. /**
  441. * Fast image resize/resample using Hermite filter with JavaScript.
  442. *
  443. * @author: ViliusL
  444. *
  445. * @param {*} canvas
  446. * @param {number} W
  447. * @param {number} H
  448. * @param {number} W2
  449. * @param {number} H2
  450. */
  451. var resampleHermite = function (canvas, W, H, W2, H2) {
  452. W2 = Math.round(W2);
  453. H2 = Math.round(H2);
  454. var img = canvas.getContext("2d").getImageData(0, 0, W, H);
  455. var img2 = canvas.getContext("2d").getImageData(0, 0, W2, H2);
  456. var data = img.data;
  457. var data2 = img2.data;
  458. var ratio_w = W / W2;
  459. var ratio_h = H / H2;
  460. var ratio_w_half = Math.ceil(ratio_w / 2);
  461. var ratio_h_half = Math.ceil(ratio_h / 2);
  462. for (var j = 0; j < H2; j++) {
  463. for (var i = 0; i < W2; i++) {
  464. var x2 = (i + j * W2) * 4;
  465. var weight = 0;
  466. var weights = 0;
  467. var weights_alpha = 0;
  468. var gx_r = 0;
  469. var gx_g = 0;
  470. var gx_b = 0;
  471. var gx_a = 0;
  472. var center_y = (j + 0.5) * ratio_h;
  473. for (var yy = Math.floor(j * ratio_h); yy < (j + 1) * ratio_h; yy++) {
  474. var dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half;
  475. var center_x = (i + 0.5) * ratio_w;
  476. var w0 = dy * dy; //pre-calc part of w
  477. for (var xx = Math.floor(i * ratio_w); xx < (i + 1) * ratio_w; xx++) {
  478. var dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half;
  479. var w = Math.sqrt(w0 + dx * dx);
  480. if (w >= -1 && w <= 1) {
  481. //hermite filter
  482. weight = 2 * w * w * w - 3 * w * w + 1;
  483. if (weight > 0) {
  484. dx = 4 * (xx + yy * W);
  485. //alpha
  486. gx_a += weight * data[dx + 3];
  487. weights_alpha += weight;
  488. //colors
  489. if (data[dx + 3] < 255)
  490. weight = weight * data[dx + 3] / 250;
  491. gx_r += weight * data[dx];
  492. gx_g += weight * data[dx + 1];
  493. gx_b += weight * data[dx + 2];
  494. weights += weight;
  495. }
  496. }
  497. }
  498. }
  499. data2[x2] = gx_r / weights;
  500. data2[x2 + 1] = gx_g / weights;
  501. data2[x2 + 2] = gx_b / weights;
  502. data2[x2 + 3] = gx_a / weights_alpha;
  503. }
  504. }
  505. canvas.getContext("2d").clearRect(0, 0, Math.max(W, W2), Math.max(H, H2));
  506. canvas.width = W2;
  507. canvas.height = H2;
  508. canvas.getContext("2d").putImageData(img2, 0, 0);
  509. };
  510. var addConflict = function($conflicts, original, replacement) {
  511. var $conflict = $conflicts.find('.template').clone().removeClass('template').addClass('conflict');
  512. var $originalDiv = $conflict.find('.original');
  513. var $replacementDiv = $conflict.find('.replacement');
  514. $conflict.data('data',data);
  515. $conflict.find('.filename').text(original.name);
  516. $originalDiv.find('.size').text(humanFileSize(original.size));
  517. $originalDiv.find('.mtime').text(formatDate(original.mtime));
  518. // ie sucks
  519. if (replacement.size && replacement.lastModifiedDate) {
  520. $replacementDiv.find('.size').text(humanFileSize(replacement.size));
  521. $replacementDiv.find('.mtime').text(formatDate(replacement.lastModifiedDate));
  522. }
  523. var path = original.directory + '/' +original.name;
  524. var urlSpec = {
  525. file: path,
  526. x: 96,
  527. y: 96,
  528. c: original.etag,
  529. forceIcon: 0
  530. };
  531. var previewpath = Files.generatePreviewUrl(urlSpec);
  532. // Escaping single quotes
  533. previewpath = previewpath.replace(/'/g, "%27");
  534. $originalDiv.find('.icon').css({"background-image": "url('" + previewpath + "')"});
  535. getCroppedPreview(replacement).then(
  536. function(path){
  537. $replacementDiv.find('.icon').css('background-image','url(' + path + ')');
  538. }, function(){
  539. path = OC.MimeType.getIconUrl(replacement.type);
  540. $replacementDiv.find('.icon').css('background-image','url(' + path + ')');
  541. }
  542. );
  543. // connect checkboxes with labels
  544. var checkboxId = $conflicts.find('.conflict').length;
  545. $originalDiv.find('input:checkbox').attr('id', 'checkbox_original_'+checkboxId);
  546. $replacementDiv.find('input:checkbox').attr('id', 'checkbox_replacement_'+checkboxId);
  547. $conflicts.append($conflict);
  548. //set more recent mtime bold
  549. // ie sucks
  550. if (replacement.lastModifiedDate && replacement.lastModifiedDate.getTime() > original.mtime) {
  551. $replacementDiv.find('.mtime').css('font-weight', 'bold');
  552. } else if (replacement.lastModifiedDate && replacement.lastModifiedDate.getTime() < original.mtime) {
  553. $originalDiv.find('.mtime').css('font-weight', 'bold');
  554. } else {
  555. //TODO add to same mtime collection?
  556. }
  557. // set bigger size bold
  558. if (replacement.size && replacement.size > original.size) {
  559. $replacementDiv.find('.size').css('font-weight', 'bold');
  560. } else if (replacement.size && replacement.size < original.size) {
  561. $originalDiv.find('.size').css('font-weight', 'bold');
  562. } else {
  563. //TODO add to same size collection?
  564. }
  565. //TODO show skip action for files with same size and mtime in bottom row
  566. // always keep readonly files
  567. if (original.status === 'readonly') {
  568. $originalDiv
  569. .addClass('readonly')
  570. .find('input[type="checkbox"]')
  571. .prop('checked', true)
  572. .prop('disabled', true);
  573. $originalDiv.find('.message')
  574. .text(t('core','read-only'));
  575. }
  576. };
  577. //var selection = controller.getSelection(data.originalFiles);
  578. //if (selection.defaultAction) {
  579. // controller[selection.defaultAction](data);
  580. //} else {
  581. var dialogName = 'oc-dialog-fileexists-content';
  582. var dialogId = '#' + dialogName;
  583. if (this._fileexistsshown) {
  584. // add conflict
  585. var $conflicts = $(dialogId+ ' .conflicts');
  586. addConflict($conflicts, original, replacement);
  587. var count = $(dialogId+ ' .conflict').length;
  588. var title = n('core',
  589. '{count} file conflict',
  590. '{count} file conflicts',
  591. count,
  592. {count:count}
  593. );
  594. $(dialogId).parent().children('.oc-dialog-title').text(title);
  595. //recalculate dimensions
  596. $(window).trigger('resize');
  597. dialogDeferred.resolve();
  598. } else {
  599. //create dialog
  600. this._fileexistsshown = true;
  601. $.when(this._getFileExistsTemplate()).then(function($tmpl) {
  602. var title = t('core','One file conflict');
  603. var $dlg = $tmpl.octemplate({
  604. dialog_name: dialogName,
  605. title: title,
  606. type: 'fileexists',
  607. allnewfiles: t('core','New Files'),
  608. allexistingfiles: t('core','Already existing files'),
  609. why: t('core','Which files do you want to keep?'),
  610. what: t('core','If you select both versions, the copied file will have a number added to its name.')
  611. });
  612. $('body').append($dlg);
  613. if (original && replacement) {
  614. var $conflicts = $dlg.find('.conflicts');
  615. addConflict($conflicts, original, replacement);
  616. }
  617. var buttonlist = [{
  618. text: t('core', 'Cancel'),
  619. classes: 'cancel',
  620. click: function(){
  621. if ( typeof controller.onCancel !== 'undefined') {
  622. controller.onCancel(data);
  623. }
  624. $(dialogId).ocdialog('close');
  625. }
  626. },
  627. {
  628. text: t('core', 'Continue'),
  629. classes: 'continue',
  630. click: function(){
  631. if ( typeof controller.onContinue !== 'undefined') {
  632. controller.onContinue($(dialogId + ' .conflict'));
  633. }
  634. $(dialogId).ocdialog('close');
  635. }
  636. }];
  637. $(dialogId).ocdialog({
  638. width: 500,
  639. closeOnEscape: true,
  640. modal: true,
  641. buttons: buttonlist,
  642. closeButton: null,
  643. close: function() {
  644. self._fileexistsshown = false;
  645. $(this).ocdialog('destroy').remove();
  646. }
  647. });
  648. $(dialogId).css('height','auto');
  649. var $primaryButton = $dlg.closest('.oc-dialog').find('button.continue');
  650. $primaryButton.prop('disabled', true);
  651. function updatePrimaryButton() {
  652. var checkedCount = $dlg.find('.conflicts .checkbox:checked').length;
  653. $primaryButton.prop('disabled', checkedCount === 0);
  654. }
  655. //add checkbox toggling actions
  656. $(dialogId).find('.allnewfiles').on('click', function() {
  657. var $checkboxes = $(dialogId).find('.conflict .replacement input[type="checkbox"]');
  658. $checkboxes.prop('checked', $(this).prop('checked'));
  659. });
  660. $(dialogId).find('.allexistingfiles').on('click', function() {
  661. var $checkboxes = $(dialogId).find('.conflict .original:not(.readonly) input[type="checkbox"]');
  662. $checkboxes.prop('checked', $(this).prop('checked'));
  663. });
  664. $(dialogId).find('.conflicts').on('click', '.replacement,.original:not(.readonly)', function() {
  665. var $checkbox = $(this).find('input[type="checkbox"]');
  666. $checkbox.prop('checked', !$checkbox.prop('checked'));
  667. });
  668. $(dialogId).find('.conflicts').on('click', '.replacement input[type="checkbox"],.original:not(.readonly) input[type="checkbox"]', function() {
  669. var $checkbox = $(this);
  670. $checkbox.prop('checked', !$checkbox.prop('checked'));
  671. });
  672. //update counters
  673. $(dialogId).on('click', '.replacement,.allnewfiles', function() {
  674. var count = $(dialogId).find('.conflict .replacement input[type="checkbox"]:checked').length;
  675. if (count === $(dialogId+ ' .conflict').length) {
  676. $(dialogId).find('.allnewfiles').prop('checked', true);
  677. $(dialogId).find('.allnewfiles + .count').text(t('core','(all selected)'));
  678. } else if (count > 0) {
  679. $(dialogId).find('.allnewfiles').prop('checked', false);
  680. $(dialogId).find('.allnewfiles + .count').text(t('core','({count} selected)',{count:count}));
  681. } else {
  682. $(dialogId).find('.allnewfiles').prop('checked', false);
  683. $(dialogId).find('.allnewfiles + .count').text('');
  684. }
  685. updatePrimaryButton();
  686. });
  687. $(dialogId).on('click', '.original,.allexistingfiles', function(){
  688. var count = $(dialogId).find('.conflict .original input[type="checkbox"]:checked').length;
  689. if (count === $(dialogId+ ' .conflict').length) {
  690. $(dialogId).find('.allexistingfiles').prop('checked', true);
  691. $(dialogId).find('.allexistingfiles + .count').text(t('core','(all selected)'));
  692. } else if (count > 0) {
  693. $(dialogId).find('.allexistingfiles').prop('checked', false);
  694. $(dialogId).find('.allexistingfiles + .count')
  695. .text(t('core','({count} selected)',{count:count}));
  696. } else {
  697. $(dialogId).find('.allexistingfiles').prop('checked', false);
  698. $(dialogId).find('.allexistingfiles + .count').text('');
  699. }
  700. updatePrimaryButton();
  701. });
  702. dialogDeferred.resolve();
  703. })
  704. .fail(function() {
  705. dialogDeferred.reject();
  706. alert(t('core', 'Error loading file exists template'));
  707. });
  708. }
  709. //}
  710. return dialogDeferred.promise();
  711. },
  712. _getFilePickerTemplate: function() {
  713. var defer = $.Deferred();
  714. if(!this.$filePickerTemplate) {
  715. var self = this;
  716. $.get(OC.filePath('core', 'templates', 'filepicker.html'), function(tmpl) {
  717. self.$filePickerTemplate = $(tmpl);
  718. self.$listTmpl = self.$filePickerTemplate.find('.filelist tr:first-child').detach();
  719. defer.resolve(self.$filePickerTemplate);
  720. })
  721. .fail(function(jqXHR, textStatus, errorThrown) {
  722. defer.reject(jqXHR.status, errorThrown);
  723. });
  724. } else {
  725. defer.resolve(this.$filePickerTemplate);
  726. }
  727. return defer.promise();
  728. },
  729. _getMessageTemplate: function() {
  730. var defer = $.Deferred();
  731. if(!this.$messageTemplate) {
  732. var self = this;
  733. $.get(OC.filePath('core', 'templates', 'message.html'), function(tmpl) {
  734. self.$messageTemplate = $(tmpl);
  735. defer.resolve(self.$messageTemplate);
  736. })
  737. .fail(function(jqXHR, textStatus, errorThrown) {
  738. defer.reject(jqXHR.status, errorThrown);
  739. });
  740. } else {
  741. defer.resolve(this.$messageTemplate);
  742. }
  743. return defer.promise();
  744. },
  745. _getFileExistsTemplate: function () {
  746. var defer = $.Deferred();
  747. if (!this.$fileexistsTemplate) {
  748. var self = this;
  749. $.get(OC.filePath('files', 'templates', 'fileexists.html'), function (tmpl) {
  750. self.$fileexistsTemplate = $(tmpl);
  751. defer.resolve(self.$fileexistsTemplate);
  752. })
  753. .fail(function () {
  754. defer.reject();
  755. });
  756. } else {
  757. defer.resolve(this.$fileexistsTemplate);
  758. }
  759. return defer.promise();
  760. },
  761. _getFileList: function(dir, mimeType) { //this is only used by the spreedme app atm
  762. if (typeof(mimeType) === "string") {
  763. mimeType = [mimeType];
  764. }
  765. return $.getJSON(
  766. OC.filePath('files', 'ajax', 'list.php'),
  767. {
  768. dir: dir,
  769. mimetypes: JSON.stringify(mimeType)
  770. }
  771. );
  772. },
  773. /**
  774. * fills the filepicker with files
  775. */
  776. _fillFilePicker:function(dir) {
  777. var self = this;
  778. this.$filelist.empty().addClass('icon-loading');
  779. this.$filePicker.data('path', dir);
  780. var filter = this.$filePicker.data('mimetype');
  781. if (typeof(filter) === "string") {
  782. filter = [filter];
  783. }
  784. self.filepicker.filesClient.getFolderContents(dir).then(function(status, files) {
  785. if (filter) {
  786. files = files.filter(function (file) {
  787. return filter == [] || file.type === 'dir' || filter.indexOf(file.mimetype) !== -1;
  788. });
  789. }
  790. files = files.sort(function(a, b) {
  791. if (a.type === 'dir' && b.type !== 'dir') {
  792. return -1;
  793. } else if(a.type !== 'dir' && b.type === 'dir') {
  794. return 1;
  795. } else {
  796. return 0;
  797. }
  798. });
  799. self._fillSlug();
  800. if (files.length === 0) {
  801. self.$filePicker.find('.emptycontent').show();
  802. } else {
  803. self.$filePicker.find('.emptycontent').hide();
  804. }
  805. $.each(files, function(idx, entry) {
  806. entry.icon = OC.MimeType.getIconUrl(entry.mimetype);
  807. var simpleSize, sizeColor;
  808. if (typeof(entry.size) !== 'undefined' && entry.size >= 0) {
  809. simpleSize = humanFileSize(parseInt(entry.size, 10), true);
  810. sizeColor = Math.round(160 - Math.pow((entry.size / (1024 * 1024)), 2));
  811. } else {
  812. simpleSize = t('files', 'Pending');
  813. sizeColor = 80;
  814. }
  815. var $row = self.$listTmpl.octemplate({
  816. type: entry.type,
  817. dir: dir,
  818. filename: entry.name,
  819. date: OC.Util.relativeModifiedDate(entry.mtime),
  820. size: simpleSize,
  821. sizeColor: sizeColor,
  822. icon: entry.icon
  823. });
  824. if (entry.type === 'file') {
  825. var urlSpec = {
  826. file: dir + '/' + entry.name,
  827. };
  828. var img = new Image();
  829. var previewUrl = OC.generateUrl('/core/preview.png?') + $.param(urlSpec);
  830. img.onload = function() {
  831. if (img.width > 5) {
  832. $row.find('td.filename').attr('style', 'background-image:url(' + previewUrl + ')');
  833. }
  834. };
  835. img.src = previewUrl;
  836. }
  837. self.$filelist.append($row);
  838. });
  839. self.$filelist.removeClass('icon-loading');
  840. });
  841. },
  842. /**
  843. * fills the tree list with directories
  844. */
  845. _fillSlug: function() {
  846. this.$dirTree.empty();
  847. var self = this;
  848. var dir;
  849. var path = this.$filePicker.data('path');
  850. var $template = $('<div data-dir="{dir}"><a>{name}</a></div>').addClass('crumb');
  851. if(path) {
  852. var paths = path.split('/');
  853. $.each(paths, function(index, dir) {
  854. dir = paths.pop();
  855. if(dir === '') {
  856. return false;
  857. }
  858. self.$dirTree.prepend($template.octemplate({
  859. dir: paths.join('/') + '/' + dir,
  860. name: dir
  861. }));
  862. });
  863. }
  864. $template.octemplate({
  865. dir: '',
  866. name: '' // Ugly but works ;)
  867. }, {escapeFunction: null}).prependTo(this.$dirTree);
  868. },
  869. /**
  870. * handle selection made in the tree list
  871. */
  872. _handleTreeListSelect:function(event, type) {
  873. var self = event.data;
  874. var dir = $(event.target).closest('.crumb').data('dir');
  875. self._fillFilePicker(dir);
  876. var getOcDialog = (event.target).closest('.oc-dialog');
  877. var buttonEnableDisable = $('.primary', getOcDialog);
  878. this._changeButtonsText(type, dir.split(/[/]+/).pop());
  879. if (this.$filePicker.data('mimetype') === "httpd/unix-directory") {
  880. buttonEnableDisable.prop("disabled", false);
  881. } else {
  882. buttonEnableDisable.prop("disabled", true);
  883. }
  884. },
  885. /**
  886. * handle clicks made in the filepicker
  887. */
  888. _handlePickerClick:function(event, $element, type) {
  889. var getOcDialog = this.$filePicker.closest('.oc-dialog');
  890. var buttonEnableDisable = getOcDialog.find('.primary');
  891. if ($element.data('type') === 'file') {
  892. if (this.$filePicker.data('multiselect') !== true || !event.ctrlKey) {
  893. this.$filelist.find('.filepicker_element_selected').removeClass('filepicker_element_selected');
  894. }
  895. $element.toggleClass('filepicker_element_selected');
  896. buttonEnableDisable.prop("disabled", false);
  897. } else if ( $element.data('type') === 'dir' ) {
  898. this._fillFilePicker(this.$filePicker.data('path') + '/' + $element.data('entryname'));
  899. this._changeButtonsText(type, $element.data('entryname'));
  900. if (this.$filePicker.data('mimetype') === "httpd/unix-directory") {
  901. buttonEnableDisable.prop("disabled", false);
  902. } else {
  903. buttonEnableDisable.prop("disabled", true);
  904. }
  905. }
  906. },
  907. /**
  908. * Handle
  909. * @param type of action
  910. * @param dir on which to change buttons text
  911. * @private
  912. */
  913. _changeButtonsText: function(type, dir) {
  914. var copyText = dir === '' ? t('core', 'Copy') : t('core', 'Copy to {folder}', {folder: dir});
  915. var moveText = dir === '' ? t('core', 'Move') : t('core', 'Move to {folder}', {folder: dir});
  916. var buttons = $('.oc-dialog-buttonrow button');
  917. switch (type) {
  918. case this.FILEPICKER_TYPE_CHOOSE:
  919. break;
  920. case this.FILEPICKER_TYPE_COPY:
  921. buttons.text(copyText);
  922. break;
  923. case this.FILEPICKER_TYPE_MOVE:
  924. buttons.text(moveText);
  925. break;
  926. case this.FILEPICKER_TYPE_COPY_MOVE:
  927. buttons.eq(0).text(copyText);
  928. buttons.eq(1).text(moveText);
  929. break;
  930. }
  931. }
  932. };