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 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828
  1. /**
  2. * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at>
  3. * @copyright Copyright (c) 2019 Gary Kim <gary@garykim.dev>
  4. *
  5. * @author Bartek Przybylski <bart.p.pl@gmail.com>
  6. * @author Christopher Schäpers <kondou@ts.unde.re>
  7. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  8. * @author Daniel Calviño Sánchez <danxuliu@gmail.com>
  9. * @author Daniel Kesselberg <mail@danielkesselberg.de>
  10. * @author Florian Schunk <florian.schunk@rwth-aachen.de>
  11. * @author Gary Kim <gary@garykim.dev>
  12. * @author Hendrik Leppelsack <hendrik@leppelsack.de>
  13. * @author Jan-Christoph Borchardt <hey@jancborchardt.net>
  14. * @author Joas Schilling <coding@schilljs.com>
  15. * @author John Molakvoæ <skjnldsv@protonmail.com>
  16. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  17. * @author Julius Härtl <jus@bitgrid.net>
  18. * @author Loïc Hermann <loic.hermann@sciam.fr>
  19. * @author Morris Jobke <hey@morrisjobke.de>
  20. * @author Olivier Paroz <github@oparoz.com>
  21. * @author Robin Appelman <robin@icewind.nl>
  22. * @author Roeland Jago Douma <roeland@famdouma.nl>
  23. * @author Sujith Haridasan <Sujith_Haridasan@mentor.com>
  24. * @author Thomas Citharel <nextcloud@tcit.fr>
  25. * @author Thomas Müller <thomas.mueller@tmit.eu>
  26. * @author Thomas Tanghus <thomas@tanghus.net>
  27. * @author Vincent Petry <vincent@nextcloud.com>
  28. *
  29. * @license AGPL-3.0-or-later
  30. *
  31. * This program is free software: you can redistribute it and/or modify
  32. * it under the terms of the GNU Affero General Public License as
  33. * published by the Free Software Foundation, either version 3 of the
  34. * License, or (at your option) any later version.
  35. *
  36. * This program is distributed in the hope that it will be useful,
  37. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  38. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  39. * GNU Affero General Public License for more details.
  40. *
  41. * You should have received a copy of the GNU Affero General Public License
  42. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  43. *
  44. */
  45. /* eslint-disable */
  46. import _ from 'underscore'
  47. import $ from 'jquery'
  48. import IconMove from '@mdi/svg/svg/folder-move.svg?raw'
  49. import IconCopy from '@mdi/svg/svg/folder-multiple.svg?raw'
  50. import OC from './index.js'
  51. import { DialogBuilder, FilePickerType, getFilePickerBuilder, spawnDialog } from '@nextcloud/dialogs'
  52. import { translate as t } from '@nextcloud/l10n'
  53. import { basename } from 'path'
  54. import { defineAsyncComponent } from 'vue'
  55. /**
  56. * this class to ease the usage of jquery dialogs
  57. */
  58. const Dialogs = {
  59. // dialog button types
  60. /** @deprecated use `@nextcloud/dialogs` */
  61. YES_NO_BUTTONS: 70,
  62. /** @deprecated use `@nextcloud/dialogs` */
  63. OK_BUTTONS: 71,
  64. /** @deprecated use FilePickerType from `@nextcloud/dialogs` */
  65. FILEPICKER_TYPE_CHOOSE: 1,
  66. /** @deprecated use FilePickerType from `@nextcloud/dialogs` */
  67. FILEPICKER_TYPE_MOVE: 2,
  68. /** @deprecated use FilePickerType from `@nextcloud/dialogs` */
  69. FILEPICKER_TYPE_COPY: 3,
  70. /** @deprecated use FilePickerType from `@nextcloud/dialogs` */
  71. FILEPICKER_TYPE_COPY_MOVE: 4,
  72. /** @deprecated use FilePickerType from `@nextcloud/dialogs` */
  73. FILEPICKER_TYPE_CUSTOM: 5,
  74. /**
  75. * displays alert dialog
  76. * @param {string} text content of dialog
  77. * @param {string} title dialog title
  78. * @param {function} callback which will be triggered when user presses OK
  79. * @param {boolean} [modal] make the dialog modal
  80. *
  81. * @deprecated 30.0.0 Use `@nextcloud/dialogs` instead or build your own with `@nextcloud/vue` NcDialog
  82. */
  83. alert: function(text, title, callback, modal) {
  84. this.message(
  85. text,
  86. title,
  87. 'alert',
  88. Dialogs.OK_BUTTON,
  89. callback,
  90. modal
  91. )
  92. },
  93. /**
  94. * displays info dialog
  95. * @param {string} text content of dialog
  96. * @param {string} title dialog title
  97. * @param {function} callback which will be triggered when user presses OK
  98. * @param {boolean} [modal] make the dialog modal
  99. *
  100. * @deprecated 30.0.0 Use `@nextcloud/dialogs` instead or build your own with `@nextcloud/vue` NcDialog
  101. */
  102. info: function(text, title, callback, modal) {
  103. this.message(text, title, 'info', Dialogs.OK_BUTTON, callback, modal)
  104. },
  105. /**
  106. * displays confirmation dialog
  107. * @param {string} text content of dialog
  108. * @param {string} title dialog title
  109. * @param {function} callback which will be triggered when user presses OK (true or false would be passed to callback respectively)
  110. * @param {boolean} [modal] make the dialog modal
  111. * @returns {Promise}
  112. *
  113. * @deprecated 30.0.0 Use `@nextcloud/dialogs` instead or build your own with `@nextcloud/vue` NcDialog
  114. */
  115. confirm: function(text, title, callback, modal) {
  116. return this.message(
  117. text,
  118. title,
  119. 'notice',
  120. Dialogs.YES_NO_BUTTONS,
  121. callback,
  122. modal
  123. )
  124. },
  125. /**
  126. * displays confirmation dialog
  127. * @param {string} text content of dialog
  128. * @param {string} title dialog title
  129. * @param {(number|{type: number, confirm: string, cancel: string, confirmClasses: string})} buttons text content of buttons
  130. * @param {function} callback which will be triggered when user presses OK (true or false would be passed to callback respectively)
  131. * @param {boolean} [modal] make the dialog modal
  132. * @returns {Promise}
  133. *
  134. * @deprecated 30.0.0 Use `@nextcloud/dialogs` instead or build your own with `@nextcloud/vue` NcDialog
  135. */
  136. confirmDestructive: function(text, title, buttons = Dialogs.OK_BUTTONS, callback = () => {}, modal) {
  137. return (new DialogBuilder())
  138. .setName(title)
  139. .setText(text)
  140. .setButtons(
  141. buttons === Dialogs.OK_BUTTONS
  142. ? [
  143. {
  144. label: t('core', 'Yes'),
  145. type: 'error',
  146. callback: () => {
  147. callback.clicked = true
  148. callback(true)
  149. },
  150. }
  151. ]
  152. : Dialogs._getLegacyButtons(buttons, callback)
  153. )
  154. .build()
  155. .show()
  156. .then(() => {
  157. if (!callback.clicked) {
  158. callback(false)
  159. }
  160. })
  161. },
  162. /**
  163. * displays confirmation dialog
  164. * @param {string} text content of dialog
  165. * @param {string} title dialog title
  166. * @param {function} callback which will be triggered when user presses OK (true or false would be passed to callback respectively)
  167. * @param {boolean} [modal] make the dialog modal
  168. * @returns {Promise}
  169. *
  170. * @deprecated 30.0.0 Use `@nextcloud/dialogs` instead or build your own with `@nextcloud/vue` NcDialog
  171. */
  172. confirmHtml: function(text, title, callback, modal) {
  173. return (new DialogBuilder())
  174. .setName(title)
  175. .setText('')
  176. .setButtons([
  177. {
  178. label: t('core', 'No'),
  179. callback: () => {},
  180. },
  181. {
  182. label: t('core', 'Yes'),
  183. type: 'primary',
  184. callback: () => {
  185. callback.clicked = true
  186. callback(true)
  187. },
  188. },
  189. ])
  190. .build()
  191. .setHTML(text)
  192. .show()
  193. .then(() => {
  194. if (!callback.clicked) {
  195. callback(false)
  196. }
  197. })
  198. },
  199. /**
  200. * displays prompt dialog
  201. * @param {string} text content of dialog
  202. * @param {string} title dialog title
  203. * @param {function} callback which will be triggered when user presses OK (true or false would be passed to callback respectively)
  204. * @param {boolean} [modal] make the dialog modal
  205. * @param {string} name name of the input field
  206. * @param {boolean} password whether the input should be a password input
  207. * @returns {Promise}
  208. *
  209. * @deprecated Use NcDialog from `@nextcloud/vue` instead
  210. */
  211. prompt: function(text, title, callback, modal, name, password) {
  212. return new Promise((resolve) => {
  213. spawnDialog(
  214. defineAsyncComponent(() => import('../components/LegacyDialogPrompt.vue')),
  215. {
  216. text,
  217. name: title,
  218. callback,
  219. inputName: name,
  220. isPassword: !!password
  221. },
  222. (...args) => {
  223. callback(...args)
  224. resolve()
  225. },
  226. )
  227. })
  228. },
  229. /**
  230. * Legacy wrapper to the new Vue based filepicker from `@nextcloud/dialogs`
  231. *
  232. * Prefer to use the Vue filepicker directly instead.
  233. *
  234. * In order to pick several types of mime types they need to be passed as an
  235. * array of strings.
  236. *
  237. * When no mime type filter is given only files can be selected. In order to
  238. * be able to select both files and folders "['*', 'httpd/unix-directory']"
  239. * should be used instead.
  240. *
  241. * @param {string} title dialog title
  242. * @param {Function} callback which will be triggered when user presses Choose
  243. * @param {boolean} [multiselect] whether it should be possible to select multiple files
  244. * @param {string[]} [mimetype] mimetype to filter by - directories will always be included
  245. * @param {boolean} [_modal] do not use
  246. * @param {string} [type] Type of file picker : Choose, copy, move, copy and move
  247. * @param {string} [path] path to the folder that the the file can be picket from
  248. * @param {object} [options] additonal options that need to be set
  249. * @param {Function} [options.filter] filter function for advanced filtering
  250. * @param {boolean} [options.allowDirectoryChooser] Allow to select directories
  251. * @deprecated since 27.1.0 use the filepicker from `@nextcloud/dialogs` instead
  252. */
  253. filepicker(title, callback, multiselect = false, mimetype = undefined, _modal = undefined, type = FilePickerType.Choose, path = undefined, options = undefined) {
  254. /**
  255. * Create legacy callback wrapper to support old filepicker syntax
  256. * @param fn The original callback
  257. * @param type The file picker type which was used to pick the file(s)
  258. */
  259. const legacyCallback = (fn, type) => {
  260. const getPath = (node) => {
  261. const root = node?.root || ''
  262. let path = node?.path || ''
  263. // TODO: Fix this in @nextcloud/files
  264. if (path.startsWith(root)) {
  265. path = path.slice(root.length) || '/'
  266. }
  267. return path
  268. }
  269. if (multiselect) {
  270. return (nodes) => fn(nodes.map(getPath), type)
  271. } else {
  272. return (nodes) => fn(getPath(nodes[0]), type)
  273. }
  274. }
  275. /**
  276. * Coverting a Node into a legacy file info to support the OC.dialogs.filepicker filter function
  277. * @param node The node to convert
  278. */
  279. const nodeToLegacyFile = (node) => ({
  280. id: node.fileid || null,
  281. path: node.path,
  282. mimetype: node.mime || null,
  283. mtime: node.mtime?.getTime() || null,
  284. permissions: node.permissions,
  285. name: node.attributes?.displayName || node.basename,
  286. etag: node.attributes?.etag || null,
  287. hasPreview: node.attributes?.hasPreview || null,
  288. mountType: node.attributes?.mountType || null,
  289. quotaAvailableBytes: node.attributes?.quotaAvailableBytes || null,
  290. icon: null,
  291. sharePermissions: null,
  292. })
  293. const builder = getFilePickerBuilder(title)
  294. // Setup buttons
  295. if (type === this.FILEPICKER_TYPE_CUSTOM) {
  296. (options.buttons || []).forEach((button) => {
  297. builder.addButton({
  298. callback: legacyCallback(callback, button.type),
  299. label: button.text,
  300. type: button.defaultButton ? 'primary' : 'secondary',
  301. })
  302. })
  303. } else {
  304. builder.setButtonFactory((nodes, path) => {
  305. const buttons = []
  306. const node = nodes?.[0]?.attributes?.displayName || nodes?.[0]?.basename
  307. const target = node || basename(path)
  308. if (type === FilePickerType.Choose) {
  309. buttons.push({
  310. callback: legacyCallback(callback, FilePickerType.Choose),
  311. label: node && !this.multiSelect ? t('core', 'Choose {file}', { file: node }) : t('core', 'Choose'),
  312. type: 'primary',
  313. })
  314. }
  315. if (type === FilePickerType.CopyMove || type === FilePickerType.Copy) {
  316. buttons.push({
  317. callback: legacyCallback(callback, FilePickerType.Copy),
  318. label: target ? t('core', 'Copy to {target}', { target }) : t('core', 'Copy'),
  319. type: 'primary',
  320. icon: IconCopy,
  321. })
  322. }
  323. if (type === FilePickerType.Move || type === FilePickerType.CopyMove) {
  324. buttons.push({
  325. callback: legacyCallback(callback, FilePickerType.Move),
  326. label: target ? t('core', 'Move to {target}', { target }) : t('core', 'Move'),
  327. type: type === FilePickerType.Move ? 'primary' : 'secondary',
  328. icon: IconMove,
  329. })
  330. }
  331. return buttons
  332. })
  333. }
  334. if (mimetype) {
  335. builder.setMimeTypeFilter(typeof mimetype === 'string' ? [mimetype] : (mimetype || []))
  336. }
  337. if (typeof options?.filter === 'function') {
  338. builder.setFilter((node) => options.filter(nodeToLegacyFile(node)))
  339. }
  340. builder.allowDirectories(options?.allowDirectoryChooser === true || mimetype?.includes('httpd/unix-directory') || false)
  341. .setMultiSelect(multiselect)
  342. .startAt(path)
  343. .build()
  344. .pick()
  345. },
  346. /**
  347. * Displays raw dialog
  348. * You better use a wrapper instead ...
  349. *
  350. * @deprecated 30.0.0 Use `@nextcloud/dialogs` instead or build your own with `@nextcloud/vue` NcDialog
  351. */
  352. message: function(content, title, dialogType, buttons, callback = () => {}, modal, allowHtml) {
  353. const builder = (new DialogBuilder())
  354. .setName(title)
  355. .setText(allowHtml ? '' : content)
  356. .setButtons(Dialogs._getLegacyButtons(buttons, callback))
  357. switch (dialogType) {
  358. case 'alert':
  359. builder.setSeverity('warning')
  360. break
  361. case 'notice':
  362. builder.setSeverity('info')
  363. break
  364. default:
  365. break
  366. }
  367. const dialog = builder.build()
  368. if (allowHtml) {
  369. dialog.setHTML(content)
  370. }
  371. return dialog.show().then(() => {
  372. if(!callback._clicked) {
  373. callback(false)
  374. }
  375. })
  376. },
  377. /**
  378. * Helper for legacy API
  379. * @deprecated
  380. */
  381. _getLegacyButtons(buttons, callback) {
  382. const buttonList = []
  383. switch (typeof buttons === 'object' ? buttons.type : buttons) {
  384. case Dialogs.YES_NO_BUTTONS:
  385. buttonList.push({
  386. label: buttons?.cancel ?? t('core', 'No'),
  387. callback: () => {
  388. callback._clicked = true
  389. callback(false)
  390. },
  391. })
  392. buttonList.push({
  393. label: buttons?.confirm ?? t('core', 'Yes'),
  394. type: 'primary',
  395. callback: () => {
  396. callback._clicked = true
  397. callback(true)
  398. },
  399. })
  400. break
  401. case Dialogs.OK_BUTTONS:
  402. buttonList.push({
  403. label: buttons?.confirm ?? t('core', 'OK'),
  404. type: 'primary',
  405. callback: () => {
  406. callback._clicked = true
  407. callback(true)
  408. },
  409. })
  410. break
  411. default:
  412. console.error('Invalid call to OC.dialogs')
  413. break
  414. }
  415. return buttonList
  416. },
  417. _fileexistsshown: false,
  418. /**
  419. * Displays file exists dialog
  420. * @param {object} data upload object
  421. * @param {object} original file with name, size and mtime
  422. * @param {object} replacement file with name, size and mtime
  423. * @param {object} controller with onCancel, onSkip, onReplace and onRename methods
  424. * @returns {Promise} jquery promise that resolves after the dialog template was loaded
  425. *
  426. * @deprecated 29.0.0 Use openConflictPicker from the @nextcloud/upload package instead
  427. */
  428. fileexists: function(data, original, replacement, controller) {
  429. var self = this
  430. var dialogDeferred = new $.Deferred()
  431. var getCroppedPreview = function(file) {
  432. var deferred = new $.Deferred()
  433. // Only process image files.
  434. var type = file.type && file.type.split('/').shift()
  435. if (window.FileReader && type === 'image') {
  436. var reader = new FileReader()
  437. reader.onload = function(e) {
  438. var blob = new Blob([e.target.result])
  439. window.URL = window.URL || window.webkitURL
  440. var originalUrl = window.URL.createObjectURL(blob)
  441. var image = new Image()
  442. image.src = originalUrl
  443. image.onload = function() {
  444. var url = crop(image)
  445. deferred.resolve(url)
  446. }
  447. }
  448. reader.readAsArrayBuffer(file)
  449. } else {
  450. deferred.reject()
  451. }
  452. return deferred
  453. }
  454. var crop = function(img) {
  455. var canvas = document.createElement('canvas')
  456. var targetSize = 96
  457. var width = img.width
  458. var height = img.height
  459. var x; var y; var size
  460. // Calculate the width and height, constraining the proportions
  461. if (width > height) {
  462. y = 0
  463. x = (width - height) / 2
  464. } else {
  465. y = (height - width) / 2
  466. x = 0
  467. }
  468. size = Math.min(width, height)
  469. // Set canvas size to the cropped area
  470. canvas.width = size
  471. canvas.height = size
  472. var ctx = canvas.getContext('2d')
  473. ctx.drawImage(img, x, y, size, size, 0, 0, size, size)
  474. // Resize the canvas to match the destination (right size uses 96px)
  475. resampleHermite(canvas, size, size, targetSize, targetSize)
  476. return canvas.toDataURL('image/png', 0.7)
  477. }
  478. /**
  479. * Fast image resize/resample using Hermite filter with JavaScript.
  480. *
  481. * @author: ViliusL
  482. *
  483. * @param {*} canvas
  484. * @param {number} W
  485. * @param {number} H
  486. * @param {number} W2
  487. * @param {number} H2
  488. */
  489. var resampleHermite = function(canvas, W, H, W2, H2) {
  490. W2 = Math.round(W2)
  491. H2 = Math.round(H2)
  492. var img = canvas.getContext('2d').getImageData(0, 0, W, H)
  493. var img2 = canvas.getContext('2d').getImageData(0, 0, W2, H2)
  494. var data = img.data
  495. var data2 = img2.data
  496. var ratio_w = W / W2
  497. var ratio_h = H / H2
  498. var ratio_w_half = Math.ceil(ratio_w / 2)
  499. var ratio_h_half = Math.ceil(ratio_h / 2)
  500. for (var j = 0; j < H2; j++) {
  501. for (var i = 0; i < W2; i++) {
  502. var x2 = (i + j * W2) * 4
  503. var weight = 0
  504. var weights = 0
  505. var weights_alpha = 0
  506. var gx_r = 0
  507. var gx_g = 0
  508. var gx_b = 0
  509. var gx_a = 0
  510. var center_y = (j + 0.5) * ratio_h
  511. for (var yy = Math.floor(j * ratio_h); yy < (j + 1) * ratio_h; yy++) {
  512. var dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half
  513. var center_x = (i + 0.5) * ratio_w
  514. var w0 = dy * dy // pre-calc part of w
  515. for (var xx = Math.floor(i * ratio_w); xx < (i + 1) * ratio_w; xx++) {
  516. var dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half
  517. var w = Math.sqrt(w0 + dx * dx)
  518. if (w >= -1 && w <= 1) {
  519. // hermite filter
  520. weight = 2 * w * w * w - 3 * w * w + 1
  521. if (weight > 0) {
  522. dx = 4 * (xx + yy * W)
  523. // alpha
  524. gx_a += weight * data[dx + 3]
  525. weights_alpha += weight
  526. // colors
  527. if (data[dx + 3] < 255) { weight = weight * data[dx + 3] / 250 }
  528. gx_r += weight * data[dx]
  529. gx_g += weight * data[dx + 1]
  530. gx_b += weight * data[dx + 2]
  531. weights += weight
  532. }
  533. }
  534. }
  535. }
  536. data2[x2] = gx_r / weights
  537. data2[x2 + 1] = gx_g / weights
  538. data2[x2 + 2] = gx_b / weights
  539. data2[x2 + 3] = gx_a / weights_alpha
  540. }
  541. }
  542. canvas.getContext('2d').clearRect(0, 0, Math.max(W, W2), Math.max(H, H2))
  543. canvas.width = W2
  544. canvas.height = H2
  545. canvas.getContext('2d').putImageData(img2, 0, 0)
  546. }
  547. var addConflict = function($conflicts, original, replacement) {
  548. var $conflict = $conflicts.find('.template').clone().removeClass('template').addClass('conflict')
  549. var $originalDiv = $conflict.find('.original')
  550. var $replacementDiv = $conflict.find('.replacement')
  551. $conflict.data('data', data)
  552. $conflict.find('.filename').text(original.name)
  553. $originalDiv.find('.size').text(OC.Util.humanFileSize(original.size))
  554. $originalDiv.find('.mtime').text(OC.Util.formatDate(original.mtime))
  555. // ie sucks
  556. if (replacement.size && replacement.lastModified) {
  557. $replacementDiv.find('.size').text(OC.Util.humanFileSize(replacement.size))
  558. $replacementDiv.find('.mtime').text(OC.Util.formatDate(replacement.lastModified))
  559. }
  560. var path = original.directory + '/' + original.name
  561. var urlSpec = {
  562. file: path,
  563. x: 96,
  564. y: 96,
  565. c: original.etag,
  566. forceIcon: 0
  567. }
  568. var previewpath = Files.generatePreviewUrl(urlSpec)
  569. // Escaping single quotes
  570. previewpath = previewpath.replace(/'/g, '%27')
  571. $originalDiv.find('.icon').css({ 'background-image': "url('" + previewpath + "')" })
  572. getCroppedPreview(replacement).then(
  573. function(path) {
  574. $replacementDiv.find('.icon').css('background-image', 'url(' + path + ')')
  575. }, function() {
  576. path = OC.MimeType.getIconUrl(replacement.type)
  577. $replacementDiv.find('.icon').css('background-image', 'url(' + path + ')')
  578. }
  579. )
  580. // connect checkboxes with labels
  581. var checkboxId = $conflicts.find('.conflict').length
  582. $originalDiv.find('input:checkbox').attr('id', 'checkbox_original_' + checkboxId)
  583. $replacementDiv.find('input:checkbox').attr('id', 'checkbox_replacement_' + checkboxId)
  584. $conflicts.append($conflict)
  585. // set more recent mtime bold
  586. // ie sucks
  587. if (replacement.lastModified > original.mtime) {
  588. $replacementDiv.find('.mtime').css('font-weight', 'bold')
  589. } else if (replacement.lastModified < original.mtime) {
  590. $originalDiv.find('.mtime').css('font-weight', 'bold')
  591. } else {
  592. // TODO add to same mtime collection?
  593. }
  594. // set bigger size bold
  595. if (replacement.size && replacement.size > original.size) {
  596. $replacementDiv.find('.size').css('font-weight', 'bold')
  597. } else if (replacement.size && replacement.size < original.size) {
  598. $originalDiv.find('.size').css('font-weight', 'bold')
  599. } else {
  600. // TODO add to same size collection?
  601. }
  602. // TODO show skip action for files with same size and mtime in bottom row
  603. // always keep readonly files
  604. if (original.status === 'readonly') {
  605. $originalDiv
  606. .addClass('readonly')
  607. .find('input[type="checkbox"]')
  608. .prop('checked', true)
  609. .prop('disabled', true)
  610. $originalDiv.find('.message')
  611. .text(t('core', 'read-only'))
  612. }
  613. }
  614. // var selection = controller.getSelection(data.originalFiles);
  615. // if (selection.defaultAction) {
  616. // controller[selection.defaultAction](data);
  617. // } else {
  618. var dialogName = 'oc-dialog-fileexists-content'
  619. var dialogId = '#' + dialogName
  620. if (this._fileexistsshown) {
  621. // add conflict
  622. var $conflicts = $(dialogId + ' .conflicts')
  623. addConflict($conflicts, original, replacement)
  624. var count = $(dialogId + ' .conflict').length
  625. var title = n('core',
  626. '{count} file conflict',
  627. '{count} file conflicts',
  628. count,
  629. { count: count }
  630. )
  631. $(dialogId).parent().children('.oc-dialog-title').text(title)
  632. // recalculate dimensions
  633. $(window).trigger('resize')
  634. dialogDeferred.resolve()
  635. } else {
  636. // create dialog
  637. this._fileexistsshown = true
  638. $.when(this._getFileExistsTemplate()).then(function($tmpl) {
  639. var title = t('core', 'One file conflict')
  640. var $dlg = $tmpl.octemplate({
  641. dialog_name: dialogName,
  642. title: title,
  643. type: 'fileexists',
  644. allnewfiles: t('core', 'New Files'),
  645. allexistingfiles: t('core', 'Already existing files'),
  646. why: t('core', 'Which files do you want to keep?'),
  647. what: t('core', 'If you select both versions, the copied file will have a number added to its name.')
  648. })
  649. $('body').append($dlg)
  650. if (original && replacement) {
  651. var $conflicts = $dlg.find('.conflicts')
  652. addConflict($conflicts, original, replacement)
  653. }
  654. var buttonlist = [{
  655. text: t('core', 'Cancel'),
  656. classes: 'cancel',
  657. click: function() {
  658. if (typeof controller.onCancel !== 'undefined') {
  659. controller.onCancel(data)
  660. }
  661. $(dialogId).ocdialog('close')
  662. }
  663. },
  664. {
  665. text: t('core', 'Continue'),
  666. classes: 'continue',
  667. click: function() {
  668. if (typeof controller.onContinue !== 'undefined') {
  669. controller.onContinue($(dialogId + ' .conflict'))
  670. }
  671. $(dialogId).ocdialog('close')
  672. }
  673. }]
  674. $(dialogId).ocdialog({
  675. width: 500,
  676. closeOnEscape: true,
  677. modal: true,
  678. buttons: buttonlist,
  679. closeButton: null,
  680. close: function() {
  681. self._fileexistsshown = false
  682. try {
  683. $(this).ocdialog('destroy').remove()
  684. } catch (e) {
  685. // ignore
  686. }
  687. }
  688. })
  689. $(dialogId).css('height', 'auto')
  690. var $primaryButton = $dlg.closest('.oc-dialog').find('button.continue')
  691. $primaryButton.prop('disabled', true)
  692. function updatePrimaryButton() {
  693. var checkedCount = $dlg.find('.conflicts .checkbox:checked').length
  694. $primaryButton.prop('disabled', checkedCount === 0)
  695. }
  696. // add checkbox toggling actions
  697. $(dialogId).find('.allnewfiles').on('click', function() {
  698. var $checkboxes = $(dialogId).find('.conflict .replacement input[type="checkbox"]')
  699. $checkboxes.prop('checked', $(this).prop('checked'))
  700. })
  701. $(dialogId).find('.allexistingfiles').on('click', function() {
  702. var $checkboxes = $(dialogId).find('.conflict .original:not(.readonly) input[type="checkbox"]')
  703. $checkboxes.prop('checked', $(this).prop('checked'))
  704. })
  705. $(dialogId).find('.conflicts').on('click', '.replacement,.original:not(.readonly)', function() {
  706. var $checkbox = $(this).find('input[type="checkbox"]')
  707. $checkbox.prop('checked', !$checkbox.prop('checked'))
  708. })
  709. $(dialogId).find('.conflicts').on('click', '.replacement input[type="checkbox"],.original:not(.readonly) input[type="checkbox"]', function() {
  710. var $checkbox = $(this)
  711. $checkbox.prop('checked', !$checkbox.prop('checked'))
  712. })
  713. // update counters
  714. $(dialogId).on('click', '.replacement,.allnewfiles', function() {
  715. var count = $(dialogId).find('.conflict .replacement input[type="checkbox"]:checked').length
  716. if (count === $(dialogId + ' .conflict').length) {
  717. $(dialogId).find('.allnewfiles').prop('checked', true)
  718. $(dialogId).find('.allnewfiles + .count').text(t('core', '(all selected)'))
  719. } else if (count > 0) {
  720. $(dialogId).find('.allnewfiles').prop('checked', false)
  721. $(dialogId).find('.allnewfiles + .count').text(t('core', '({count} selected)', { count: count }))
  722. } else {
  723. $(dialogId).find('.allnewfiles').prop('checked', false)
  724. $(dialogId).find('.allnewfiles + .count').text('')
  725. }
  726. updatePrimaryButton()
  727. })
  728. $(dialogId).on('click', '.original,.allexistingfiles', function() {
  729. var count = $(dialogId).find('.conflict .original input[type="checkbox"]:checked').length
  730. if (count === $(dialogId + ' .conflict').length) {
  731. $(dialogId).find('.allexistingfiles').prop('checked', true)
  732. $(dialogId).find('.allexistingfiles + .count').text(t('core', '(all selected)'))
  733. } else if (count > 0) {
  734. $(dialogId).find('.allexistingfiles').prop('checked', false)
  735. $(dialogId).find('.allexistingfiles + .count')
  736. .text(t('core', '({count} selected)', { count: count }))
  737. } else {
  738. $(dialogId).find('.allexistingfiles').prop('checked', false)
  739. $(dialogId).find('.allexistingfiles + .count').text('')
  740. }
  741. updatePrimaryButton()
  742. })
  743. dialogDeferred.resolve()
  744. })
  745. .fail(function() {
  746. dialogDeferred.reject()
  747. alert(t('core', 'Error loading file exists template'))
  748. })
  749. }
  750. // }
  751. return dialogDeferred.promise()
  752. },
  753. _getFileExistsTemplate: function() {
  754. var defer = $.Deferred()
  755. if (!this.$fileexistsTemplate) {
  756. var self = this
  757. $.get(OC.filePath('files', 'templates', 'fileexists.html'), function(tmpl) {
  758. self.$fileexistsTemplate = $(tmpl)
  759. defer.resolve(self.$fileexistsTemplate)
  760. })
  761. .fail(function() {
  762. defer.reject()
  763. })
  764. } else {
  765. defer.resolve(this.$fileexistsTemplate)
  766. }
  767. return defer.promise()
  768. },
  769. }
  770. export default Dialogs