oc-dialogs.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616
  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. /**
  22. * this class to ease the usage of jquery dialogs
  23. */
  24. var OCdialogs = {
  25. // dialog button types
  26. YES_NO_BUTTONS: 70,
  27. OK_BUTTONS: 71,
  28. // used to name each dialog
  29. dialogs_counter: 0,
  30. /**
  31. * displays alert dialog
  32. * @param text content of dialog
  33. * @param title dialog title
  34. * @param callback which will be triggered when user presses OK
  35. * @param modal make the dialog modal
  36. */
  37. alert:function(text, title, callback, modal) {
  38. this.message(text, title, 'alert', OCdialogs.OK_BUTTON, callback, modal);
  39. },
  40. /**
  41. * displays info dialog
  42. * @param text content of dialog
  43. * @param title dialog title
  44. * @param callback which will be triggered when user presses OK
  45. * @param modal make the dialog modal
  46. */
  47. info:function(text, title, callback, modal) {
  48. this.message(text, title, 'info', OCdialogs.OK_BUTTON, callback, modal);
  49. },
  50. /**
  51. * displays confirmation dialog
  52. * @param text content of dialog
  53. * @param title dialog title
  54. * @param callback which will be triggered when user presses YES or NO (true or false would be passed to callback respectively)
  55. * @param modal make the dialog modal
  56. */
  57. confirm:function(text, title, callback, modal) {
  58. this.message(text, title, 'notice', OCdialogs.YES_NO_BUTTONS, callback, modal);
  59. },
  60. /**
  61. * show a file picker to pick a file from
  62. * @param title dialog title
  63. * @param callback which will be triggered when user presses Choose
  64. * @param multiselect whether it should be possible to select multiple files
  65. * @param mimetype_filter mimetype to filter by
  66. * @param modal make the dialog modal
  67. */
  68. filepicker:function(title, callback, multiselect, mimetype_filter, modal) {
  69. var self = this;
  70. $.when(this._getFilePickerTemplate()).then(function($tmpl) {
  71. var dialog_name = 'oc-dialog-filepicker-content';
  72. var dialog_id = '#' + dialog_name;
  73. if(self.$filePicker) {
  74. self.$filePicker.ocdialog('close');
  75. }
  76. self.$filePicker = $tmpl.octemplate({
  77. dialog_name: dialog_name,
  78. title: title
  79. }).data('path', '').data('multiselect', multiselect).data('mimetype', mimetype_filter);
  80. if (modal === undefined) {
  81. modal = false;
  82. }
  83. if (multiselect === undefined) {
  84. multiselect = false;
  85. }
  86. if (mimetype_filter === undefined) {
  87. mimetype_filter = '';
  88. }
  89. $('body').append(self.$filePicker);
  90. self.$filePicker.ready(function() {
  91. self.$filelist = self.$filePicker.find('.filelist');
  92. self.$dirTree = self.$filePicker.find('.dirtree');
  93. self.$dirTree.on('click', 'span:not(:last-child)', self, self._handleTreeListSelect);
  94. self.$filelist.on('click', 'li', function(event) {
  95. self._handlePickerClick(event, $(this));
  96. });
  97. self._fillFilePicker('');
  98. });
  99. // build buttons
  100. var functionToCall = function() {
  101. if (callback !== undefined) {
  102. var datapath;
  103. if (multiselect === true) {
  104. datapath = [];
  105. self.$filelist.find('.filepicker_element_selected .filename').each(function(index, element) {
  106. datapath.push(self.$filePicker.data('path') + '/' + $(element).text());
  107. });
  108. } else {
  109. datapath = self.$filePicker.data('path');
  110. datapath += '/' + self.$filelist.find('.filepicker_element_selected .filename').text();
  111. }
  112. callback(datapath);
  113. self.$filePicker.ocdialog('close');
  114. }
  115. };
  116. var buttonlist = [{
  117. text: t('core', 'Choose'),
  118. click: functionToCall,
  119. defaultButton: true
  120. }];
  121. self.$filePicker.ocdialog({
  122. closeOnEscape: true,
  123. width: (4/9)*$(document).width(),
  124. height: 420,
  125. modal: modal,
  126. buttons: buttonlist,
  127. close: function(event, ui) {
  128. try {
  129. $(this).ocdialog('destroy').remove();
  130. } catch(e) {}
  131. self.$filePicker = null;
  132. }
  133. });
  134. })
  135. .fail(function(status, error) {
  136. // If the method is called while navigating away
  137. // from the page, it is probably not needed ;)
  138. if(status !== 0) {
  139. alert(t('core', 'Error loading file picker template: {error}', {error: error}));
  140. }
  141. });
  142. },
  143. /**
  144. * Displays raw dialog
  145. * You better use a wrapper instead ...
  146. */
  147. message:function(content, title, dialog_type, buttons, callback, modal) {
  148. $.when(this._getMessageTemplate()).then(function($tmpl) {
  149. var dialog_name = 'oc-dialog-' + OCdialogs.dialogs_counter + '-content';
  150. var dialog_id = '#' + dialog_name;
  151. var $dlg = $tmpl.octemplate({
  152. dialog_name: dialog_name,
  153. title: title,
  154. message: content,
  155. type: dialog_type
  156. });
  157. if (modal === undefined) {
  158. modal = false;
  159. }
  160. $('body').append($dlg);
  161. var buttonlist = [];
  162. switch (buttons) {
  163. case OCdialogs.YES_NO_BUTTONS:
  164. buttonlist = [{
  165. text: t('core', 'Yes'),
  166. click: function(){
  167. if (callback !== undefined) {
  168. callback(true);
  169. }
  170. $(dialog_id).ocdialog('close');
  171. },
  172. defaultButton: true
  173. },
  174. {
  175. text: t('core', 'No'),
  176. click: function(){
  177. if (callback !== undefined) {
  178. callback(false);
  179. }
  180. $(dialog_id).ocdialog('close');
  181. }
  182. }];
  183. break;
  184. case OCdialogs.OK_BUTTON:
  185. var functionToCall = function() {
  186. $(dialog_id).ocdialog('close');
  187. if(callback !== undefined) {
  188. callback();
  189. }
  190. };
  191. buttonlist[0] = {
  192. text: t('core', 'Ok'),
  193. click: functionToCall,
  194. defaultButton: true
  195. };
  196. break;
  197. }
  198. $(dialog_id).ocdialog({
  199. closeOnEscape: true,
  200. modal: modal,
  201. buttons: buttonlist
  202. });
  203. OCdialogs.dialogs_counter++;
  204. })
  205. .fail(function(status, error) {
  206. // If the method is called while navigating away from
  207. // the page, we still want to deliver the message.
  208. if(status === 0) {
  209. alert(title + ': ' + content);
  210. } else {
  211. alert(t('core', 'Error loading message template: {error}', {error: error}));
  212. }
  213. });
  214. },
  215. _fileexistsshown: false,
  216. /**
  217. * Displays file exists dialog
  218. * @param {object} data upload object
  219. * @param {object} original file with name, size and mtime
  220. * @param {object} replacement file with name, size and mtime
  221. * @param {object} controller with onCancel, onSkip, onReplace and onRename methods
  222. */
  223. fileexists:function(data, original, replacement, controller) {
  224. var self = this;
  225. var getCroppedPreview = function(file) {
  226. var deferred = new $.Deferred();
  227. // Only process image files.
  228. var type = file.type && file.type.split('/').shift();
  229. if (window.FileReader && type === 'image') {
  230. var reader = new FileReader();
  231. reader.onload = function (e) {
  232. var blob = new Blob([e.target.result]);
  233. window.URL = window.URL || window.webkitURL;
  234. var originalUrl = window.URL.createObjectURL(blob);
  235. var image = new Image();
  236. image.src = originalUrl;
  237. image.onload = function () {
  238. var url = crop(image);
  239. deferred.resolve(url);
  240. }
  241. };
  242. reader.readAsArrayBuffer(file);
  243. } else {
  244. deferred.reject();
  245. }
  246. return deferred;
  247. };
  248. var crop = function(img) {
  249. var canvas = document.createElement('canvas'),
  250. width = img.width,
  251. height = img.height,
  252. x, y, size;
  253. // calculate the width and height, constraining the proportions
  254. if (width > height) {
  255. y = 0;
  256. x = (width - height) / 2;
  257. } else {
  258. y = (height - width) / 2;
  259. x = 0;
  260. }
  261. size = Math.min(width, height);
  262. // resize the canvas and draw the image data into it
  263. canvas.width = 64;
  264. canvas.height = 64;
  265. var ctx = canvas.getContext("2d");
  266. ctx.drawImage(img, x, y, size, size, 0, 0, 64, 64);
  267. return canvas.toDataURL("image/png", 0.7);
  268. };
  269. var addConflict = function(conflicts, original, replacement) {
  270. var conflict = conflicts.find('.template').clone().removeClass('template').addClass('conflict');
  271. conflict.data('data',data);
  272. conflict.find('.filename').text(original.name);
  273. conflict.find('.original .size').text(humanFileSize(original.size));
  274. conflict.find('.original .mtime').text(formatDate(original.mtime*1000));
  275. // ie sucks
  276. if (replacement.size && replacement.lastModifiedDate) {
  277. conflict.find('.replacement .size').text(humanFileSize(replacement.size));
  278. conflict.find('.replacement .mtime').text(formatDate(replacement.lastModifiedDate));
  279. }
  280. var path = getPathForPreview(original.name);
  281. Files.lazyLoadPreview(path, original.mime, function(previewpath){
  282. conflict.find('.original .icon').css('background-image','url('+previewpath+')');
  283. }, 96, 96, original.etag);
  284. getCroppedPreview(replacement).then(
  285. function(path){
  286. conflict.find('.replacement .icon').css('background-image','url(' + path + ')');
  287. }, function(){
  288. Files.getMimeIcon(replacement.type,function(path){
  289. conflict.find('.replacement .icon').css('background-image','url(' + path + ')');
  290. });
  291. }
  292. );
  293. conflicts.append(conflict);
  294. //set more recent mtime bold
  295. // ie sucks
  296. if (replacement.lastModifiedDate && replacement.lastModifiedDate.getTime() > original.mtime*1000) {
  297. conflict.find('.replacement .mtime').css('font-weight', 'bold');
  298. } else if (replacement.lastModifiedDate && replacement.lastModifiedDate.getTime() < original.mtime*1000) {
  299. conflict.find('.original .mtime').css('font-weight', 'bold');
  300. } else {
  301. //TODO add to same mtime collection?
  302. }
  303. // set bigger size bold
  304. if (replacement.size && replacement.size > original.size) {
  305. conflict.find('.replacement .size').css('font-weight', 'bold');
  306. } else if (replacement.size && replacement.size < original.size) {
  307. conflict.find('.original .size').css('font-weight', 'bold');
  308. } else {
  309. //TODO add to same size collection?
  310. }
  311. //TODO show skip action for files with same size and mtime in bottom row
  312. };
  313. //var selection = controller.getSelection(data.originalFiles);
  314. //if (selection.defaultAction) {
  315. // controller[selection.defaultAction](data);
  316. //} else {
  317. var dialog_name = 'oc-dialog-fileexists-content';
  318. var dialog_id = '#' + dialog_name;
  319. if (this._fileexistsshown) {
  320. // add conflict
  321. var conflicts = $(dialog_id+ ' .conflicts');
  322. addConflict(conflicts, original, replacement);
  323. var count = $(dialog_id+ ' .conflict').length;
  324. var title = n('files',
  325. '{count} file conflict',
  326. '{count} file conflicts',
  327. count,
  328. {count:count}
  329. );
  330. $(dialog_id).parent().children('.oc-dialog-title').text(title);
  331. //recalculate dimensions
  332. $(window).trigger('resize');
  333. } else {
  334. //create dialog
  335. this._fileexistsshown = true;
  336. $.when(this._getFileExistsTemplate()).then(function($tmpl) {
  337. var title = t('files','One file conflict');
  338. var $dlg = $tmpl.octemplate({
  339. dialog_name: dialog_name,
  340. title: title,
  341. type: 'fileexists',
  342. why: t('files','Which files do you want to keep?'),
  343. what: t('files','If you select both versions, the copied file will have a number added to its name.')
  344. });
  345. $('body').append($dlg);
  346. var conflicts = $($dlg).find('.conflicts');
  347. addConflict(conflicts, original, replacement);
  348. buttonlist = [{
  349. text: t('core', 'Cancel'),
  350. classes: 'cancel',
  351. click: function(){
  352. if ( typeof controller.onCancel !== 'undefined') {
  353. controller.onCancel(data);
  354. }
  355. $(dialog_id).ocdialog('close');
  356. }
  357. },
  358. {
  359. text: t('core', 'Continue'),
  360. classes: 'continue',
  361. click: function(){
  362. if ( typeof controller.onContinue !== 'undefined') {
  363. controller.onContinue($(dialog_id + ' .conflict'));
  364. }
  365. $(dialog_id).ocdialog('close');
  366. }
  367. }];
  368. $(dialog_id).ocdialog({
  369. width: 500,
  370. closeOnEscape: true,
  371. modal: true,
  372. buttons: buttonlist,
  373. closeButton: null,
  374. close: function(event, ui) {
  375. self._fileexistsshown = false;
  376. $(this).ocdialog('destroy').remove();
  377. }
  378. });
  379. $(dialog_id).css('height','auto');
  380. //add checkbox toggling actions
  381. $(dialog_id).find('.allnewfiles').on('click', function() {
  382. var checkboxes = $(dialog_id).find('.conflict .replacement input[type="checkbox"]');
  383. checkboxes.prop('checked', $(this).prop('checked'));
  384. });
  385. $(dialog_id).find('.allexistingfiles').on('click', function() {
  386. var checkboxes = $(dialog_id).find('.conflict .original input[type="checkbox"]');
  387. checkboxes.prop('checked', $(this).prop('checked'));
  388. });
  389. $(dialog_id).find('.conflicts').on('click', '.replacement,.original', function() {
  390. var checkbox = $(this).find('input[type="checkbox"]');
  391. checkbox.prop('checked', !checkbox.prop('checked'));
  392. });
  393. $(dialog_id).find('.conflicts').on('click', 'input[type="checkbox"]', function() {
  394. var checkbox = $(this);
  395. checkbox.prop('checked', !checkbox.prop('checked'));
  396. });
  397. //update counters
  398. $(dialog_id).on('click', '.replacement,.allnewfiles', function() {
  399. var count = $(dialog_id).find('.conflict .replacement input[type="checkbox"]:checked').length;
  400. if (count === $(dialog_id+ ' .conflict').length) {
  401. $(dialog_id).find('.allnewfiles').prop('checked', true);
  402. $(dialog_id).find('.allnewfiles + .count').text(t('files','(all selected)'));
  403. } else if (count > 0) {
  404. $(dialog_id).find('.allnewfiles').prop('checked', false);
  405. $(dialog_id).find('.allnewfiles + .count').text(t('files','({count} selected)',{count:count}));
  406. } else {
  407. $(dialog_id).find('.allnewfiles').prop('checked', false);
  408. $(dialog_id).find('.allnewfiles + .count').text('');
  409. }
  410. });
  411. $(dialog_id).on('click', '.original,.allexistingfiles', function(){
  412. var count = $(dialog_id).find('.conflict .original input[type="checkbox"]:checked').length;
  413. if (count === $(dialog_id+ ' .conflict').length) {
  414. $(dialog_id).find('.allexistingfiles').prop('checked', true);
  415. $(dialog_id).find('.allexistingfiles + .count').text(t('files','(all selected)'));
  416. } else if (count > 0) {
  417. $(dialog_id).find('.allexistingfiles').prop('checked', false);
  418. $(dialog_id).find('.allexistingfiles + .count').text(t('files','({count} selected)',{count:count}));
  419. } else {
  420. $(dialog_id).find('.allexistingfiles').prop('checked', false);
  421. $(dialog_id).find('.allexistingfiles + .count').text('');
  422. }
  423. });
  424. })
  425. .fail(function() {
  426. alert(t('core', 'Error loading file exists template'));
  427. });
  428. }
  429. //}
  430. },
  431. _getFilePickerTemplate: function() {
  432. var defer = $.Deferred();
  433. if(!this.$filePickerTemplate) {
  434. var self = this;
  435. $.get(OC.filePath('core', 'templates', 'filepicker.html'), function(tmpl) {
  436. self.$filePickerTemplate = $(tmpl);
  437. self.$listTmpl = self.$filePickerTemplate.find('.filelist li:first-child').detach();
  438. defer.resolve(self.$filePickerTemplate);
  439. })
  440. .fail(function(jqXHR, textStatus, errorThrown) {
  441. defer.reject(jqXHR.status, errorThrown);
  442. });
  443. } else {
  444. defer.resolve(this.$filePickerTemplate);
  445. }
  446. return defer.promise();
  447. },
  448. _getMessageTemplate: function() {
  449. var defer = $.Deferred();
  450. if(!this.$messageTemplate) {
  451. var self = this;
  452. $.get(OC.filePath('core', 'templates', 'message.html'), function(tmpl) {
  453. self.$messageTemplate = $(tmpl);
  454. defer.resolve(self.$messageTemplate);
  455. })
  456. .fail(function(jqXHR, textStatus, errorThrown) {
  457. defer.reject(jqXHR.status, errorThrown);
  458. });
  459. } else {
  460. defer.resolve(this.$messageTemplate);
  461. }
  462. return defer.promise();
  463. },
  464. _getFileExistsTemplate: function () {
  465. var defer = $.Deferred();
  466. if (!this.$fileexistsTemplate) {
  467. var self = this;
  468. $.get(OC.filePath('files', 'templates', 'fileexists.html'), function (tmpl) {
  469. self.$fileexistsTemplate = $(tmpl);
  470. defer.resolve(self.$fileexistsTemplate);
  471. })
  472. .fail(function () {
  473. defer.reject();
  474. });
  475. } else {
  476. defer.resolve(this.$fileexistsTemplate);
  477. }
  478. return defer.promise();
  479. },
  480. _getFileList: function(dir, mimeType) {
  481. if (typeof(mimeType) === "string") {
  482. mimeType = [mimeType];
  483. }
  484. return $.getJSON(
  485. OC.filePath('files', 'ajax', 'rawlist.php'),
  486. {
  487. dir: dir,
  488. mimetypes: JSON.stringify(mimeType)
  489. }
  490. );
  491. },
  492. _determineValue: function(element) {
  493. if ( $(element).attr('type') === 'checkbox' ) {
  494. return element.checked;
  495. } else {
  496. return $(element).val();
  497. }
  498. },
  499. /**
  500. * fills the filepicker with files
  501. */
  502. _fillFilePicker:function(dir) {
  503. var dirs = [];
  504. var others = [];
  505. var self = this;
  506. this.$filelist.empty().addClass('loading');
  507. this.$filePicker.data('path', dir);
  508. $.when(this._getFileList(dir, this.$filePicker.data('mimetype'))).then(function(response) {
  509. $.each(response.data, function(index, file) {
  510. if (file.type === 'dir') {
  511. dirs.push(file);
  512. } else {
  513. others.push(file);
  514. }
  515. });
  516. self._fillSlug();
  517. var sorted = dirs.concat(others);
  518. $.each(sorted, function(idx, entry) {
  519. var $li = self.$listTmpl.octemplate({
  520. type: entry.type,
  521. dir: dir,
  522. filename: entry.name,
  523. date: OC.mtime2date(entry.mtime)
  524. });
  525. $li.find('img').attr('src', entry.mimetype_icon);
  526. self.$filelist.append($li);
  527. });
  528. self.$filelist.removeClass('loading');
  529. });
  530. },
  531. /**
  532. * fills the tree list with directories
  533. */
  534. _fillSlug: function() {
  535. this.$dirTree.empty();
  536. var self = this;
  537. var path = this.$filePicker.data('path');
  538. var $template = $('<span data-dir="{dir}">{name}</span>');
  539. if(path) {
  540. var paths = path.split('/');
  541. $.each(paths, function(index, dir) {
  542. dir = paths.pop();
  543. if(dir === '') {
  544. return false;
  545. }
  546. self.$dirTree.prepend($template.octemplate({
  547. dir: paths.join('/') + '/' + dir,
  548. name: dir
  549. }));
  550. });
  551. }
  552. $template.octemplate({
  553. dir: '',
  554. name: '&nbsp;&nbsp;&nbsp;&nbsp;' // Ugly but works ;)
  555. }, {escapeFunction: null}).addClass('home svg').prependTo(this.$dirTree);
  556. },
  557. /**
  558. * handle selection made in the tree list
  559. */
  560. _handleTreeListSelect:function(event) {
  561. var self = event.data;
  562. var dir = $(event.target).data('dir');
  563. self._fillFilePicker(dir);
  564. },
  565. /**
  566. * handle clicks made in the filepicker
  567. */
  568. _handlePickerClick:function(event, $element) {
  569. if ($element.data('type') === 'file') {
  570. if (this.$filePicker.data('multiselect') !== true || !event.ctrlKey) {
  571. this.$filelist.find('.filepicker_element_selected').removeClass('filepicker_element_selected');
  572. }
  573. $element.toggleClass('filepicker_element_selected');
  574. return;
  575. } else if ( $element.data('type') === 'dir' ) {
  576. this._fillFilePicker(this.$filePicker.data('path') + '/' + $element.data('entryname'));
  577. }
  578. }
  579. };