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

11 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
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