oc-dialogs.js 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179
  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').attr('placeholder', name);
  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. *
  172. * In order to pick several types of mime types they need to be passed as an
  173. * array of strings.
  174. *
  175. * When no mime type filter is given only files can be selected. In order to
  176. * be able to select both files and folders "['*', 'httpd/unix-directory']"
  177. * should be used instead.
  178. *
  179. * @param title dialog title
  180. * @param callback which will be triggered when user presses Choose
  181. * @param multiselect whether it should be possible to select multiple files
  182. * @param mimetypeFilter mimetype to filter by - directories will always be included
  183. * @param modal make the dialog modal
  184. * @param type Type of file picker : Choose, copy, move, copy and move
  185. */
  186. filepicker:function(title, callback, multiselect, mimetypeFilter, modal, type) {
  187. var self = this;
  188. this.filepicker.sortField = 'name';
  189. this.filepicker.sortOrder = 'asc';
  190. // avoid opening the picker twice
  191. if (this.filepicker.loading) {
  192. return;
  193. }
  194. if (type === undefined) {
  195. type = this.FILEPICKER_TYPE_CHOOSE;
  196. }
  197. var emptyText = t('core', 'No files in here');
  198. var newText = t('files', 'New folder');
  199. if (type === this.FILEPICKER_TYPE_COPY || type === this.FILEPICKER_TYPE_MOVE || type === this.FILEPICKER_TYPE_COPY_MOVE) {
  200. emptyText = t('core', 'No more subfolders in here');
  201. }
  202. this.filepicker.loading = true;
  203. this.filepicker.filesClient = (OCA.Sharing && OCA.Sharing.PublicApp && OCA.Sharing.PublicApp.fileList)? OCA.Sharing.PublicApp.fileList.filesClient: OC.Files.getClient();
  204. this.filelist = null;
  205. $.when(this._getFilePickerTemplate()).then(function($tmpl) {
  206. self.filepicker.loading = false;
  207. var dialogName = 'oc-dialog-filepicker-content';
  208. if(self.$filePicker) {
  209. self.$filePicker.ocdialog('close');
  210. }
  211. if (mimetypeFilter === undefined || mimetypeFilter === null) {
  212. mimetypeFilter = [];
  213. }
  214. if (typeof(mimetypeFilter) === "string") {
  215. mimetypeFilter = [mimetypeFilter];
  216. }
  217. self.$filePicker = $tmpl.octemplate({
  218. dialog_name: dialogName,
  219. title: title,
  220. emptytext: emptyText,
  221. newtext: newText
  222. }).data('path', '').data('multiselect', multiselect).data('mimetype', mimetypeFilter);
  223. if (modal === undefined) {
  224. modal = false;
  225. }
  226. if (multiselect === undefined) {
  227. multiselect = false;
  228. }
  229. // No grid for IE!
  230. if (OC.Util.isIE()) {
  231. self.$filePicker.find('#picker-view-toggle').remove();
  232. self.$filePicker.find('#picker-filestable').removeClass('view-grid');
  233. }
  234. $('body').append(self.$filePicker);
  235. self.$showGridView = $('input#picker-showgridview');
  236. self.$showGridView.on('change', _.bind(self._onGridviewChange, self));
  237. if (!OC.Util.isIE()) {
  238. self._getGridSettings();
  239. }
  240. var newButton = self.$filePicker.find('.actions.creatable .button-add');
  241. newButton.on('focus', function() {
  242. self.$filePicker.ocdialog('setEnterCallback', function() {
  243. event.stopImmediatePropagation();
  244. event.preventDefault();
  245. newButton.click();
  246. });
  247. });
  248. newButton.on('blur', function() {
  249. self.$filePicker.ocdialog('unsetEnterCallback');
  250. });
  251. OC.registerMenu(newButton,self.$filePicker.find('.menu'),function () {
  252. $input.focus();
  253. self.$filePicker.ocdialog('setEnterCallback', function() {
  254. event.stopImmediatePropagation();
  255. event.preventDefault();
  256. self.$form.submit();
  257. });
  258. var newName = $input.val();
  259. lastPos = newName.lastIndexOf('.');
  260. if (lastPos === -1) {
  261. lastPos = newName.length;
  262. }
  263. $input.selectRange(0, lastPos);
  264. });
  265. var $form = self.$filePicker.find('.filenameform');
  266. var $input = $form.find('input[type=\'text\']');
  267. var $submit = $form.find('input[type=\'submit\']');
  268. $submit.on('click',function(event) {
  269. event.stopImmediatePropagation();
  270. event.preventDefault();
  271. $form.submit();
  272. });
  273. var checkInput = function () {
  274. var filename = $input.val();
  275. try {
  276. if (!Files.isFileNameValid(filename)) {
  277. // Files.isFileNameValid(filename) throws an exception itself
  278. } else if (self.filelist.find(function(file){return file.name === this;},filename)) {
  279. throw t('files', '{newName} already exists', {newName: filename}, undefined, {
  280. escape: false
  281. });
  282. } else {
  283. return true;
  284. }
  285. } catch (error) {
  286. $input.attr('title', error);
  287. $input.tooltip({placement: 'right', trigger: 'manual', 'container': '.newFolderMenu'});
  288. $input.tooltip('fixTitle');
  289. $input.tooltip('show');
  290. $input.addClass('error');
  291. }
  292. return false;
  293. };
  294. $form.on('submit', function(event) {
  295. event.stopPropagation();
  296. event.preventDefault();
  297. if (checkInput()) {
  298. var newname = $input.val();
  299. self.filepicker.filesClient.createDirectory(self.$filePicker.data('path') + "/" + newname).always(function (status) {
  300. self._fillFilePicker(self.$filePicker.data('path') + newname );
  301. });
  302. OC.hideMenus();
  303. self.$filePicker.ocdialog('unsetEnterCallback');
  304. self.$filePicker.click();
  305. $input.val(newText);
  306. }
  307. });
  308. $input.keypress(function(event) {
  309. if (event.keyCode === 13 || event.which === 13) {
  310. event.stopImmediatePropagation();
  311. event.preventDefault();
  312. $form.submit();
  313. }
  314. });
  315. self.$filePicker.ready(function() {
  316. self.$fileListHeader = self.$filePicker.find('.filelist thead tr');
  317. self.$filelist = self.$filePicker.find('.filelist tbody');
  318. self.$filelistContainer = self.$filePicker.find('.filelist-container');
  319. self.$dirTree = self.$filePicker.find('.dirtree');
  320. self.$dirTree.on('click', 'div:not(:last-child)', self, function (event) {
  321. self._handleTreeListSelect(event, type);
  322. });
  323. self.$filelist.on('click', 'tr', function(event) {
  324. self._handlePickerClick(event, $(this), type);
  325. });
  326. self.$fileListHeader.on('click', 'a', function(event) {
  327. var dir = self.$filePicker.data('path');
  328. self.filepicker.sortField = $(event.currentTarget).data('sort');
  329. self.filepicker.sortOrder = self.filepicker.sortOrder === 'asc' ? 'desc' : 'asc';
  330. self._fillFilePicker(dir);
  331. });
  332. self._fillFilePicker('');
  333. });
  334. // build buttons
  335. var functionToCall = function(returnType) {
  336. if (callback !== undefined) {
  337. var datapath;
  338. if (multiselect === true) {
  339. datapath = [];
  340. self.$filelist.find('tr.filepicker_element_selected').each(function(index, element) {
  341. datapath.push(self.$filePicker.data('path') + '/' + $(element).data('entryname'));
  342. });
  343. } else {
  344. datapath = self.$filePicker.data('path');
  345. var selectedName = self.$filelist.find('tr.filepicker_element_selected').data('entryname');
  346. if (selectedName) {
  347. datapath += '/' + selectedName;
  348. }
  349. }
  350. callback(datapath, returnType);
  351. self.$filePicker.ocdialog('close');
  352. }
  353. };
  354. var chooseCallback = function () {
  355. functionToCall(OCdialogs.FILEPICKER_TYPE_CHOOSE);
  356. };
  357. var copyCallback = function () {
  358. functionToCall(OCdialogs.FILEPICKER_TYPE_COPY);
  359. };
  360. var moveCallback = function () {
  361. functionToCall(OCdialogs.FILEPICKER_TYPE_MOVE);
  362. };
  363. var buttonlist = [];
  364. if (type === OCdialogs.FILEPICKER_TYPE_CHOOSE) {
  365. buttonlist.push({
  366. text: t('core', 'Choose'),
  367. click: chooseCallback,
  368. defaultButton: true
  369. });
  370. } else {
  371. if (type === OCdialogs.FILEPICKER_TYPE_COPY || type === OCdialogs.FILEPICKER_TYPE_COPY_MOVE) {
  372. buttonlist.push({
  373. text: t('core', 'Copy'),
  374. click: copyCallback,
  375. defaultButton: false
  376. });
  377. }
  378. if (type === OCdialogs.FILEPICKER_TYPE_MOVE || type === OCdialogs.FILEPICKER_TYPE_COPY_MOVE) {
  379. buttonlist.push({
  380. text: t('core', 'Move'),
  381. click: moveCallback,
  382. defaultButton: true
  383. });
  384. }
  385. }
  386. self.$filePicker.ocdialog({
  387. closeOnEscape: true,
  388. // max-width of 600
  389. width: 600,
  390. height: 500,
  391. modal: modal,
  392. buttons: buttonlist,
  393. style: {
  394. buttons: 'aside',
  395. },
  396. close: function() {
  397. try {
  398. $(this).ocdialog('destroy').remove();
  399. } catch(e) {}
  400. self.$filePicker = null;
  401. }
  402. });
  403. // We can access primary class only from oc-dialog.
  404. // Hence this is one of the approach to get the choose button.
  405. var getOcDialog = self.$filePicker.closest('.oc-dialog');
  406. var buttonEnableDisable = getOcDialog.find('.primary');
  407. if (self.$filePicker.data('mimetype').indexOf("httpd/unix-directory") !== -1) {
  408. buttonEnableDisable.prop("disabled", false);
  409. } else {
  410. buttonEnableDisable.prop("disabled", true);
  411. }
  412. })
  413. .fail(function(status, error) {
  414. // If the method is called while navigating away
  415. // from the page, it is probably not needed ;)
  416. self.filepicker.loading = false;
  417. if(status !== 0) {
  418. alert(t('core', 'Error loading file picker template: {error}', {error: error}));
  419. }
  420. });
  421. },
  422. /**
  423. * Displays raw dialog
  424. * You better use a wrapper instead ...
  425. */
  426. message:function(content, title, dialogType, buttons, callback, modal, allowHtml) {
  427. return $.when(this._getMessageTemplate()).then(function($tmpl) {
  428. var dialogName = 'oc-dialog-' + OCdialogs.dialogsCounter + '-content';
  429. var dialogId = '#' + dialogName;
  430. var $dlg = $tmpl.octemplate({
  431. dialog_name: dialogName,
  432. title: title,
  433. message: content,
  434. type: dialogType
  435. }, allowHtml ? {escapeFunction: ''} : {});
  436. if (modal === undefined) {
  437. modal = false;
  438. }
  439. $('body').append($dlg);
  440. var buttonlist = [];
  441. switch (buttons) {
  442. case OCdialogs.YES_NO_BUTTONS:
  443. buttonlist = [{
  444. text: t('core', 'No'),
  445. click: function(){
  446. if (callback !== undefined) {
  447. callback(false);
  448. }
  449. $(dialogId).ocdialog('close');
  450. }
  451. },
  452. {
  453. text: t('core', 'Yes'),
  454. click: function(){
  455. if (callback !== undefined) {
  456. callback(true);
  457. }
  458. $(dialogId).ocdialog('close');
  459. },
  460. defaultButton: true
  461. }];
  462. break;
  463. case OCdialogs.OK_BUTTON:
  464. var functionToCall = function() {
  465. $(dialogId).ocdialog('close');
  466. if(callback !== undefined) {
  467. callback();
  468. }
  469. };
  470. buttonlist[0] = {
  471. text: t('core', 'OK'),
  472. click: functionToCall,
  473. defaultButton: true
  474. };
  475. break;
  476. }
  477. $(dialogId).ocdialog({
  478. closeOnEscape: true,
  479. modal: modal,
  480. buttons: buttonlist
  481. });
  482. OCdialogs.dialogsCounter++;
  483. })
  484. .fail(function(status, error) {
  485. // If the method is called while navigating away from
  486. // the page, we still want to deliver the message.
  487. if(status === 0) {
  488. alert(title + ': ' + content);
  489. } else {
  490. alert(t('core', 'Error loading message template: {error}', {error: error}));
  491. }
  492. });
  493. },
  494. _fileexistsshown: false,
  495. /**
  496. * Displays file exists dialog
  497. * @param {object} data upload object
  498. * @param {object} original file with name, size and mtime
  499. * @param {object} replacement file with name, size and mtime
  500. * @param {object} controller with onCancel, onSkip, onReplace and onRename methods
  501. * @return {Promise} jquery promise that resolves after the dialog template was loaded
  502. */
  503. fileexists:function(data, original, replacement, controller) {
  504. var self = this;
  505. var dialogDeferred = new $.Deferred();
  506. var getCroppedPreview = function(file) {
  507. var deferred = new $.Deferred();
  508. // Only process image files.
  509. var type = file.type && file.type.split('/').shift();
  510. if (window.FileReader && type === 'image') {
  511. var reader = new FileReader();
  512. reader.onload = function (e) {
  513. var blob = new Blob([e.target.result]);
  514. window.URL = window.URL || window.webkitURL;
  515. var originalUrl = window.URL.createObjectURL(blob);
  516. var image = new Image();
  517. image.src = originalUrl;
  518. image.onload = function () {
  519. var url = crop(image);
  520. deferred.resolve(url);
  521. };
  522. };
  523. reader.readAsArrayBuffer(file);
  524. } else {
  525. deferred.reject();
  526. }
  527. return deferred;
  528. };
  529. var crop = function(img) {
  530. var canvas = document.createElement('canvas'),
  531. targetSize = 96,
  532. width = img.width,
  533. height = img.height,
  534. x, y, size;
  535. // Calculate the width and height, constraining the proportions
  536. if (width > height) {
  537. y = 0;
  538. x = (width - height) / 2;
  539. } else {
  540. y = (height - width) / 2;
  541. x = 0;
  542. }
  543. size = Math.min(width, height);
  544. // Set canvas size to the cropped area
  545. canvas.width = size;
  546. canvas.height = size;
  547. var ctx = canvas.getContext("2d");
  548. ctx.drawImage(img, x, y, size, size, 0, 0, size, size);
  549. // Resize the canvas to match the destination (right size uses 96px)
  550. resampleHermite(canvas, size, size, targetSize, targetSize);
  551. return canvas.toDataURL("image/png", 0.7);
  552. };
  553. /**
  554. * Fast image resize/resample using Hermite filter with JavaScript.
  555. *
  556. * @author: ViliusL
  557. *
  558. * @param {*} canvas
  559. * @param {number} W
  560. * @param {number} H
  561. * @param {number} W2
  562. * @param {number} H2
  563. */
  564. var resampleHermite = function (canvas, W, H, W2, H2) {
  565. W2 = Math.round(W2);
  566. H2 = Math.round(H2);
  567. var img = canvas.getContext("2d").getImageData(0, 0, W, H);
  568. var img2 = canvas.getContext("2d").getImageData(0, 0, W2, H2);
  569. var data = img.data;
  570. var data2 = img2.data;
  571. var ratio_w = W / W2;
  572. var ratio_h = H / H2;
  573. var ratio_w_half = Math.ceil(ratio_w / 2);
  574. var ratio_h_half = Math.ceil(ratio_h / 2);
  575. for (var j = 0; j < H2; j++) {
  576. for (var i = 0; i < W2; i++) {
  577. var x2 = (i + j * W2) * 4;
  578. var weight = 0;
  579. var weights = 0;
  580. var weights_alpha = 0;
  581. var gx_r = 0;
  582. var gx_g = 0;
  583. var gx_b = 0;
  584. var gx_a = 0;
  585. var center_y = (j + 0.5) * ratio_h;
  586. for (var yy = Math.floor(j * ratio_h); yy < (j + 1) * ratio_h; yy++) {
  587. var dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half;
  588. var center_x = (i + 0.5) * ratio_w;
  589. var w0 = dy * dy; //pre-calc part of w
  590. for (var xx = Math.floor(i * ratio_w); xx < (i + 1) * ratio_w; xx++) {
  591. var dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half;
  592. var w = Math.sqrt(w0 + dx * dx);
  593. if (w >= -1 && w <= 1) {
  594. //hermite filter
  595. weight = 2 * w * w * w - 3 * w * w + 1;
  596. if (weight > 0) {
  597. dx = 4 * (xx + yy * W);
  598. //alpha
  599. gx_a += weight * data[dx + 3];
  600. weights_alpha += weight;
  601. //colors
  602. if (data[dx + 3] < 255)
  603. weight = weight * data[dx + 3] / 250;
  604. gx_r += weight * data[dx];
  605. gx_g += weight * data[dx + 1];
  606. gx_b += weight * data[dx + 2];
  607. weights += weight;
  608. }
  609. }
  610. }
  611. }
  612. data2[x2] = gx_r / weights;
  613. data2[x2 + 1] = gx_g / weights;
  614. data2[x2 + 2] = gx_b / weights;
  615. data2[x2 + 3] = gx_a / weights_alpha;
  616. }
  617. }
  618. canvas.getContext("2d").clearRect(0, 0, Math.max(W, W2), Math.max(H, H2));
  619. canvas.width = W2;
  620. canvas.height = H2;
  621. canvas.getContext("2d").putImageData(img2, 0, 0);
  622. };
  623. var addConflict = function($conflicts, original, replacement) {
  624. var $conflict = $conflicts.find('.template').clone().removeClass('template').addClass('conflict');
  625. var $originalDiv = $conflict.find('.original');
  626. var $replacementDiv = $conflict.find('.replacement');
  627. $conflict.data('data',data);
  628. $conflict.find('.filename').text(original.name);
  629. $originalDiv.find('.size').text(humanFileSize(original.size));
  630. $originalDiv.find('.mtime').text(formatDate(original.mtime));
  631. // ie sucks
  632. if (replacement.size && replacement.lastModifiedDate) {
  633. $replacementDiv.find('.size').text(humanFileSize(replacement.size));
  634. $replacementDiv.find('.mtime').text(formatDate(replacement.lastModifiedDate));
  635. }
  636. var path = original.directory + '/' +original.name;
  637. var urlSpec = {
  638. file: path,
  639. x: 96,
  640. y: 96,
  641. c: original.etag,
  642. forceIcon: 0
  643. };
  644. var previewpath = Files.generatePreviewUrl(urlSpec);
  645. // Escaping single quotes
  646. previewpath = previewpath.replace(/'/g, "%27");
  647. $originalDiv.find('.icon').css({"background-image": "url('" + previewpath + "')"});
  648. getCroppedPreview(replacement).then(
  649. function(path){
  650. $replacementDiv.find('.icon').css('background-image','url(' + path + ')');
  651. }, function(){
  652. path = OC.MimeType.getIconUrl(replacement.type);
  653. $replacementDiv.find('.icon').css('background-image','url(' + path + ')');
  654. }
  655. );
  656. // connect checkboxes with labels
  657. var checkboxId = $conflicts.find('.conflict').length;
  658. $originalDiv.find('input:checkbox').attr('id', 'checkbox_original_'+checkboxId);
  659. $replacementDiv.find('input:checkbox').attr('id', 'checkbox_replacement_'+checkboxId);
  660. $conflicts.append($conflict);
  661. //set more recent mtime bold
  662. // ie sucks
  663. if (replacement.lastModifiedDate && replacement.lastModifiedDate.getTime() > original.mtime) {
  664. $replacementDiv.find('.mtime').css('font-weight', 'bold');
  665. } else if (replacement.lastModifiedDate && replacement.lastModifiedDate.getTime() < original.mtime) {
  666. $originalDiv.find('.mtime').css('font-weight', 'bold');
  667. } else {
  668. //TODO add to same mtime collection?
  669. }
  670. // set bigger size bold
  671. if (replacement.size && replacement.size > original.size) {
  672. $replacementDiv.find('.size').css('font-weight', 'bold');
  673. } else if (replacement.size && replacement.size < original.size) {
  674. $originalDiv.find('.size').css('font-weight', 'bold');
  675. } else {
  676. //TODO add to same size collection?
  677. }
  678. //TODO show skip action for files with same size and mtime in bottom row
  679. // always keep readonly files
  680. if (original.status === 'readonly') {
  681. $originalDiv
  682. .addClass('readonly')
  683. .find('input[type="checkbox"]')
  684. .prop('checked', true)
  685. .prop('disabled', true);
  686. $originalDiv.find('.message')
  687. .text(t('core','read-only'));
  688. }
  689. };
  690. //var selection = controller.getSelection(data.originalFiles);
  691. //if (selection.defaultAction) {
  692. // controller[selection.defaultAction](data);
  693. //} else {
  694. var dialogName = 'oc-dialog-fileexists-content';
  695. var dialogId = '#' + dialogName;
  696. if (this._fileexistsshown) {
  697. // add conflict
  698. var $conflicts = $(dialogId+ ' .conflicts');
  699. addConflict($conflicts, original, replacement);
  700. var count = $(dialogId+ ' .conflict').length;
  701. var title = n('core',
  702. '{count} file conflict',
  703. '{count} file conflicts',
  704. count,
  705. {count:count}
  706. );
  707. $(dialogId).parent().children('.oc-dialog-title').text(title);
  708. //recalculate dimensions
  709. $(window).trigger('resize');
  710. dialogDeferred.resolve();
  711. } else {
  712. //create dialog
  713. this._fileexistsshown = true;
  714. $.when(this._getFileExistsTemplate()).then(function($tmpl) {
  715. var title = t('core','One file conflict');
  716. var $dlg = $tmpl.octemplate({
  717. dialog_name: dialogName,
  718. title: title,
  719. type: 'fileexists',
  720. allnewfiles: t('core','New Files'),
  721. allexistingfiles: t('core','Already existing files'),
  722. why: t('core','Which files do you want to keep?'),
  723. what: t('core','If you select both versions, the copied file will have a number added to its name.')
  724. });
  725. $('body').append($dlg);
  726. if (original && replacement) {
  727. var $conflicts = $dlg.find('.conflicts');
  728. addConflict($conflicts, original, replacement);
  729. }
  730. var buttonlist = [{
  731. text: t('core', 'Cancel'),
  732. classes: 'cancel',
  733. click: function(){
  734. if ( typeof controller.onCancel !== 'undefined') {
  735. controller.onCancel(data);
  736. }
  737. $(dialogId).ocdialog('close');
  738. }
  739. },
  740. {
  741. text: t('core', 'Continue'),
  742. classes: 'continue',
  743. click: function(){
  744. if ( typeof controller.onContinue !== 'undefined') {
  745. controller.onContinue($(dialogId + ' .conflict'));
  746. }
  747. $(dialogId).ocdialog('close');
  748. }
  749. }];
  750. $(dialogId).ocdialog({
  751. width: 500,
  752. closeOnEscape: true,
  753. modal: true,
  754. buttons: buttonlist,
  755. closeButton: null,
  756. close: function() {
  757. self._fileexistsshown = false;
  758. $(this).ocdialog('destroy').remove();
  759. }
  760. });
  761. $(dialogId).css('height','auto');
  762. var $primaryButton = $dlg.closest('.oc-dialog').find('button.continue');
  763. $primaryButton.prop('disabled', true);
  764. function updatePrimaryButton() {
  765. var checkedCount = $dlg.find('.conflicts .checkbox:checked').length;
  766. $primaryButton.prop('disabled', checkedCount === 0);
  767. }
  768. //add checkbox toggling actions
  769. $(dialogId).find('.allnewfiles').on('click', function() {
  770. var $checkboxes = $(dialogId).find('.conflict .replacement input[type="checkbox"]');
  771. $checkboxes.prop('checked', $(this).prop('checked'));
  772. });
  773. $(dialogId).find('.allexistingfiles').on('click', function() {
  774. var $checkboxes = $(dialogId).find('.conflict .original:not(.readonly) input[type="checkbox"]');
  775. $checkboxes.prop('checked', $(this).prop('checked'));
  776. });
  777. $(dialogId).find('.conflicts').on('click', '.replacement,.original:not(.readonly)', function() {
  778. var $checkbox = $(this).find('input[type="checkbox"]');
  779. $checkbox.prop('checked', !$checkbox.prop('checked'));
  780. });
  781. $(dialogId).find('.conflicts').on('click', '.replacement input[type="checkbox"],.original:not(.readonly) input[type="checkbox"]', function() {
  782. var $checkbox = $(this);
  783. $checkbox.prop('checked', !$checkbox.prop('checked'));
  784. });
  785. //update counters
  786. $(dialogId).on('click', '.replacement,.allnewfiles', function() {
  787. var count = $(dialogId).find('.conflict .replacement input[type="checkbox"]:checked').length;
  788. if (count === $(dialogId+ ' .conflict').length) {
  789. $(dialogId).find('.allnewfiles').prop('checked', true);
  790. $(dialogId).find('.allnewfiles + .count').text(t('core','(all selected)'));
  791. } else if (count > 0) {
  792. $(dialogId).find('.allnewfiles').prop('checked', false);
  793. $(dialogId).find('.allnewfiles + .count').text(t('core','({count} selected)',{count:count}));
  794. } else {
  795. $(dialogId).find('.allnewfiles').prop('checked', false);
  796. $(dialogId).find('.allnewfiles + .count').text('');
  797. }
  798. updatePrimaryButton();
  799. });
  800. $(dialogId).on('click', '.original,.allexistingfiles', function(){
  801. var count = $(dialogId).find('.conflict .original input[type="checkbox"]:checked').length;
  802. if (count === $(dialogId+ ' .conflict').length) {
  803. $(dialogId).find('.allexistingfiles').prop('checked', true);
  804. $(dialogId).find('.allexistingfiles + .count').text(t('core','(all selected)'));
  805. } else if (count > 0) {
  806. $(dialogId).find('.allexistingfiles').prop('checked', false);
  807. $(dialogId).find('.allexistingfiles + .count')
  808. .text(t('core','({count} selected)',{count:count}));
  809. } else {
  810. $(dialogId).find('.allexistingfiles').prop('checked', false);
  811. $(dialogId).find('.allexistingfiles + .count').text('');
  812. }
  813. updatePrimaryButton();
  814. });
  815. dialogDeferred.resolve();
  816. })
  817. .fail(function() {
  818. dialogDeferred.reject();
  819. alert(t('core', 'Error loading file exists template'));
  820. });
  821. }
  822. //}
  823. return dialogDeferred.promise();
  824. },
  825. // get the gridview setting and set the input accordingly
  826. _getGridSettings: function() {
  827. var self = this;
  828. $.get(OC.generateUrl('/apps/files/api/v1/showgridview'), function(response) {
  829. self.$showGridView.get(0).checked = response.gridview;
  830. self.$showGridView.next('#picker-view-toggle')
  831. .removeClass('icon-toggle-filelist icon-toggle-pictures')
  832. .addClass(response.gridview ? 'icon-toggle-filelist' : 'icon-toggle-pictures')
  833. $('.list-container').toggleClass('view-grid', response.gridview);
  834. });
  835. },
  836. _onGridviewChange: function() {
  837. var show = this.$showGridView.is(':checked');
  838. // only save state if user is logged in
  839. if (OC.currentUser) {
  840. $.post(OC.generateUrl('/apps/files/api/v1/showgridview'), {
  841. show: show
  842. });
  843. }
  844. this.$showGridView.next('#picker-view-toggle')
  845. .removeClass('icon-toggle-filelist icon-toggle-pictures')
  846. .addClass(show ? 'icon-toggle-filelist' : 'icon-toggle-pictures')
  847. $('.list-container').toggleClass('view-grid', show);
  848. },
  849. _getFilePickerTemplate: function() {
  850. var defer = $.Deferred();
  851. if(!this.$filePickerTemplate) {
  852. var self = this;
  853. $.get(OC.filePath('core', 'templates', 'filepicker.html'), function(tmpl) {
  854. self.$filePickerTemplate = $(tmpl);
  855. self.$listTmpl = self.$filePickerTemplate.find('.filelist tbody tr:first-child').detach();
  856. defer.resolve(self.$filePickerTemplate);
  857. })
  858. .fail(function(jqXHR, textStatus, errorThrown) {
  859. defer.reject(jqXHR.status, errorThrown);
  860. });
  861. } else {
  862. defer.resolve(this.$filePickerTemplate);
  863. }
  864. return defer.promise();
  865. },
  866. _getMessageTemplate: function() {
  867. var defer = $.Deferred();
  868. if(!this.$messageTemplate) {
  869. var self = this;
  870. $.get(OC.filePath('core', 'templates', 'message.html'), function(tmpl) {
  871. self.$messageTemplate = $(tmpl);
  872. defer.resolve(self.$messageTemplate);
  873. })
  874. .fail(function(jqXHR, textStatus, errorThrown) {
  875. defer.reject(jqXHR.status, errorThrown);
  876. });
  877. } else {
  878. defer.resolve(this.$messageTemplate);
  879. }
  880. return defer.promise();
  881. },
  882. _getFileExistsTemplate: function () {
  883. var defer = $.Deferred();
  884. if (!this.$fileexistsTemplate) {
  885. var self = this;
  886. $.get(OC.filePath('files', 'templates', 'fileexists.html'), function (tmpl) {
  887. self.$fileexistsTemplate = $(tmpl);
  888. defer.resolve(self.$fileexistsTemplate);
  889. })
  890. .fail(function () {
  891. defer.reject();
  892. });
  893. } else {
  894. defer.resolve(this.$fileexistsTemplate);
  895. }
  896. return defer.promise();
  897. },
  898. _getFileList: function(dir, mimeType) { //this is only used by the spreedme app atm
  899. if (typeof(mimeType) === "string") {
  900. mimeType = [mimeType];
  901. }
  902. return $.getJSON(
  903. OC.filePath('files', 'ajax', 'list.php'),
  904. {
  905. dir: dir,
  906. mimetypes: JSON.stringify(mimeType)
  907. }
  908. );
  909. },
  910. /**
  911. * fills the filepicker with files
  912. */
  913. _fillFilePicker:function(dir) {
  914. var self = this;
  915. this.$filelist.empty();
  916. this.$filePicker.find('.emptycontent').hide();
  917. this.$filelistContainer.addClass('icon-loading');
  918. this.$filePicker.data('path', dir);
  919. var filter = this.$filePicker.data('mimetype');
  920. if (typeof(filter) === "string") {
  921. filter = [filter];
  922. }
  923. self.$fileListHeader.find('.sort-indicator').addClass('hidden').removeClass('icon-triangle-n').removeClass('icon-triangle-s');
  924. self.$fileListHeader.find('[data-sort=' + self.filepicker.sortField + '] .sort-indicator').removeClass('hidden');
  925. if (self.filepicker.sortOrder === 'asc') {
  926. self.$fileListHeader.find('[data-sort=' + self.filepicker.sortField + '] .sort-indicator').addClass('icon-triangle-n');
  927. } else {
  928. self.$fileListHeader.find('[data-sort=' + self.filepicker.sortField + '] .sort-indicator').addClass('icon-triangle-s');
  929. }
  930. self.filepicker.filesClient.getFolderContents(dir).then(function(status, files) {
  931. self.filelist = files;
  932. if (filter && filter.length > 0 && filter.indexOf('*') === -1) {
  933. files = files.filter(function (file) {
  934. return file.type === 'dir' || filter.indexOf(file.mimetype) !== -1;
  935. });
  936. }
  937. var Comparators = {
  938. name: function(fileInfo1, fileInfo2) {
  939. if (fileInfo1.type === 'dir' && fileInfo2.type !== 'dir') {
  940. return -1;
  941. }
  942. if (fileInfo1.type !== 'dir' && fileInfo2.type === 'dir') {
  943. return 1;
  944. }
  945. return OC.Util.naturalSortCompare(fileInfo1.name, fileInfo2.name);
  946. },
  947. size: function(fileInfo1, fileInfo2) {
  948. return fileInfo1.size - fileInfo2.size;
  949. },
  950. mtime: function(fileInfo1, fileInfo2) {
  951. return fileInfo1.mtime - fileInfo2.mtime;
  952. }
  953. };
  954. var comparator = Comparators[self.filepicker.sortField] || Comparators.name;
  955. files = files.sort(function(file1, file2) {
  956. var isFavorite = function(fileInfo) {
  957. return fileInfo.tags && fileInfo.tags.indexOf(OC.TAG_FAVORITE) >= 0;
  958. };
  959. if (isFavorite(file1) && !isFavorite(file2)) {
  960. return -1;
  961. } else if (!isFavorite(file1) && isFavorite(file2)) {
  962. return 1;
  963. }
  964. return self.filepicker.sortOrder === 'asc' ? comparator(file1, file2) : -comparator(file1, file2);
  965. });
  966. self._fillSlug();
  967. if (files.length === 0) {
  968. self.$filePicker.find('.emptycontent').show();
  969. self.$fileListHeader.hide();
  970. } else {
  971. self.$filePicker.find('.emptycontent').hide();
  972. self.$fileListHeader.show();
  973. }
  974. $.each(files, function(idx, entry) {
  975. entry.icon = OC.MimeType.getIconUrl(entry.mimetype);
  976. var simpleSize, sizeColor;
  977. if (typeof(entry.size) !== 'undefined' && entry.size >= 0) {
  978. simpleSize = humanFileSize(parseInt(entry.size, 10), true);
  979. sizeColor = Math.round(160 - Math.pow((entry.size / (1024 * 1024)), 2));
  980. } else {
  981. simpleSize = t('files', 'Pending');
  982. sizeColor = 80;
  983. }
  984. var $row = self.$listTmpl.octemplate({
  985. type: entry.type,
  986. dir: dir,
  987. filename: entry.name,
  988. date: OC.Util.relativeModifiedDate(entry.mtime),
  989. size: simpleSize,
  990. sizeColor: sizeColor,
  991. icon: entry.icon
  992. });
  993. if (entry.type === 'file') {
  994. var urlSpec = {
  995. file: dir + '/' + entry.name,
  996. x: 100,
  997. y: 100
  998. };
  999. var img = new Image();
  1000. var previewUrl = OC.generateUrl('/core/preview.png?') + $.param(urlSpec);
  1001. img.onload = function() {
  1002. if (img.width > 5) {
  1003. $row.find('td.filename').attr('style', 'background-image:url(' + previewUrl + ')');
  1004. }
  1005. };
  1006. img.src = previewUrl;
  1007. }
  1008. self.$filelist.append($row);
  1009. });
  1010. self.$filelistContainer.removeClass('icon-loading');
  1011. });
  1012. },
  1013. /**
  1014. * fills the tree list with directories
  1015. */
  1016. _fillSlug: function() {
  1017. this.$dirTree.empty();
  1018. var self = this;
  1019. var dir;
  1020. var path = this.$filePicker.data('path');
  1021. var $template = $('<div data-dir="{dir}"><a>{name}</a></div>').addClass('crumb');
  1022. if(path) {
  1023. var paths = path.split('/');
  1024. $.each(paths, function(index, dir) {
  1025. dir = paths.pop();
  1026. if(dir === '') {
  1027. return false;
  1028. }
  1029. self.$dirTree.prepend($template.octemplate({
  1030. dir: paths.join('/') + '/' + dir,
  1031. name: dir
  1032. }));
  1033. });
  1034. }
  1035. $template.octemplate({
  1036. dir: '',
  1037. name: '' // Ugly but works ;)
  1038. }, {escapeFunction: null}).prependTo(this.$dirTree);
  1039. },
  1040. /**
  1041. * handle selection made in the tree list
  1042. */
  1043. _handleTreeListSelect:function(event, type) {
  1044. var self = event.data;
  1045. var dir = $(event.target).closest('.crumb').data('dir');
  1046. self._fillFilePicker(dir);
  1047. var getOcDialog = (event.target).closest('.oc-dialog');
  1048. var buttonEnableDisable = $('.primary', getOcDialog);
  1049. this._changeButtonsText(type, dir.split(/[/]+/).pop());
  1050. if (this.$filePicker.data('mimetype').indexOf("httpd/unix-directory") !== -1) {
  1051. buttonEnableDisable.prop("disabled", false);
  1052. } else {
  1053. buttonEnableDisable.prop("disabled", true);
  1054. }
  1055. },
  1056. /**
  1057. * handle clicks made in the filepicker
  1058. */
  1059. _handlePickerClick:function(event, $element, type) {
  1060. var getOcDialog = this.$filePicker.closest('.oc-dialog');
  1061. var buttonEnableDisable = getOcDialog.find('.primary');
  1062. if ($element.data('type') === 'file') {
  1063. if (this.$filePicker.data('multiselect') !== true || !event.ctrlKey) {
  1064. this.$filelist.find('.filepicker_element_selected').removeClass('filepicker_element_selected');
  1065. }
  1066. $element.toggleClass('filepicker_element_selected');
  1067. buttonEnableDisable.prop("disabled", false);
  1068. } else if ( $element.data('type') === 'dir' ) {
  1069. this._fillFilePicker(this.$filePicker.data('path') + '/' + $element.data('entryname'));
  1070. this._changeButtonsText(type, $element.data('entryname'));
  1071. if (this.$filePicker.data('mimetype').indexOf("httpd/unix-directory") !== -1) {
  1072. buttonEnableDisable.prop("disabled", false);
  1073. } else {
  1074. buttonEnableDisable.prop("disabled", true);
  1075. }
  1076. }
  1077. },
  1078. /**
  1079. * Handle
  1080. * @param type of action
  1081. * @param dir on which to change buttons text
  1082. * @private
  1083. */
  1084. _changeButtonsText: function(type, dir) {
  1085. var copyText = dir === '' ? t('core', 'Copy') : t('core', 'Copy to {folder}', {folder: dir});
  1086. var moveText = dir === '' ? t('core', 'Move') : t('core', 'Move to {folder}', {folder: dir});
  1087. var buttons = $('.oc-dialog-buttonrow button');
  1088. switch (type) {
  1089. case this.FILEPICKER_TYPE_CHOOSE:
  1090. break;
  1091. case this.FILEPICKER_TYPE_COPY:
  1092. buttons.text(copyText);
  1093. break;
  1094. case this.FILEPICKER_TYPE_MOVE:
  1095. buttons.text(moveText);
  1096. break;
  1097. case this.FILEPICKER_TYPE_COPY_MOVE:
  1098. buttons.eq(0).text(copyText);
  1099. buttons.eq(1).text(moveText);
  1100. break;
  1101. }
  1102. }
  1103. };