oc-dialogs.js 37 KB

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