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

dialogs.js 38KB

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