Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

vor 11 Jahren
vor 11 Jahren
vor 11 Jahren
vor 11 Jahren
vor 10 Jahren
vor 10 Jahren
vor 10 Jahren
vor 10 Jahren
vor 10 Jahren
vor 10 Jahren
vor 10 Jahren
vor 10 Jahren
vor 10 Jahren
vor 10 Jahren
vor 10 Jahren
vor 10 Jahren
vor 11 Jahren
vor 11 Jahren
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