@ -9,14 +9,12 @@ import IconCopy from '@mdi/svg/svg/folder-multiple-outline.svg?raw'
import { DialogBuilder , FilePickerType , getFilePickerBuilder } from '@nextcloud/dialogs'
import { t } from '@nextcloud/l10n'
import { spawnDialog } from '@nextcloud/vue/functions/dialog'
import $ from 'jquery'
import { basename } from 'path'
import { defineAsyncComponent } from 'vue'
import logger from '../logger.js'
import OC from './index.js'
/ * *
* this class to ease the usage of jquery dialogs
* this class to ease the usage of dialogs
* /
const Dialogs = {
// dialog button types
@ -410,396 +408,6 @@ const Dialogs = {
}
return buttonList
} ,
_fileexistsshown : false ,
/ * *
* Displays file exists dialog
*
* @ param { object } data upload object
* @ param { object } original file with name , size and mtime
* @ param { object } replacement file with name , size and mtime
* @ param { object } controller with onCancel , onSkip , onReplace and onRename methods
* @ return { Promise } jquery promise that resolves after the dialog template was loaded
*
* @ deprecated 29.0 . 0 Use openConflictPicker from the @ nextcloud / upload package instead
* /
fileexists : function ( data , original , replacement , controller ) {
const self = this
const dialogDeferred = new $ . Deferred ( )
const getCroppedPreview = function ( file ) {
const deferred = new $ . Deferred ( )
// Only process image files.
const type = file . type && file . type . split ( '/' ) . shift ( )
if ( window . FileReader && type === 'image' ) {
const reader = new FileReader ( )
reader . onload = function ( e ) {
const blob = new Blob ( [ e . target . result ] )
window . URL = window . URL || window . webkitURL
const originalUrl = window . URL . createObjectURL ( blob )
const image = new Image ( )
image . src = originalUrl
image . onload = function ( ) {
const url = crop ( image )
deferred . resolve ( url )
}
}
reader . readAsArrayBuffer ( file )
} else {
deferred . reject ( )
}
return deferred
}
/ * *
* @ param img
* /
function crop ( img ) {
const canvas = document . createElement ( 'canvas' )
const targetSize = 96
const width = img . width
const height = img . height
let x
let y
// Calculate the width and height, constraining the proportions
if ( width > height ) {
y = 0
x = ( width - height ) / 2
} else {
y = ( height - width ) / 2
x = 0
}
const size = Math . min ( width , height )
// Set canvas size to the cropped area
canvas . width = size
canvas . height = size
const ctx = canvas . getContext ( '2d' )
ctx . drawImage ( img , x , y , size , size , 0 , 0 , size , size )
// Resize the canvas to match the destination (right size uses 96px)
resampleHermite ( canvas , size , size , targetSize , targetSize )
return canvas . toDataURL ( 'image/png' , 0.7 )
}
/ * *
* Fast image resize / resample using Hermite filter with JavaScript .
*
* @ author ViliusL
*
* @ param { * } canvas
* @ param { number } W
* @ param { number } H
* @ param { number } W2
* @ param { number } H2
* /
function resampleHermite ( canvas , W , H , W2 , H2 ) {
W2 = Math . round ( W2 )
H2 = Math . round ( H2 )
const img = canvas . getContext ( '2d' ) . getImageData ( 0 , 0 , W , H )
const img2 = canvas . getContext ( '2d' ) . getImageData ( 0 , 0 , W2 , H2 )
const data = img . data
const data2 = img2 . data
const ratio _w = W / W2
const ratio _h = H / H2
const ratio _w _half = Math . ceil ( ratio _w / 2 )
const ratio _h _half = Math . ceil ( ratio _h / 2 )
for ( let j = 0 ; j < H2 ; j ++ ) {
for ( let i = 0 ; i < W2 ; i ++ ) {
const x2 = ( i + j * W2 ) * 4
let weight = 0
let weights = 0
let weights _alpha = 0
let gx _r = 0
let gx _g = 0
let gx _b = 0
let gx _a = 0
const center _y = ( j + 0.5 ) * ratio _h
for ( let yy = Math . floor ( j * ratio _h ) ; yy < ( j + 1 ) * ratio _h ; yy ++ ) {
const dy = Math . abs ( center _y - ( yy + 0.5 ) ) / ratio _h _half
const center _x = ( i + 0.5 ) * ratio _w
const w0 = dy * dy // pre-calc part of w
for ( let xx = Math . floor ( i * ratio _w ) ; xx < ( i + 1 ) * ratio _w ; xx ++ ) {
let dx = Math . abs ( center _x - ( xx + 0.5 ) ) / ratio _w _half
const w = Math . sqrt ( w0 + dx * dx )
if ( w >= - 1 && w <= 1 ) {
// hermite filter
weight = 2 * w * w * w - 3 * w * w + 1
if ( weight > 0 ) {
dx = 4 * ( xx + yy * W )
// alpha
gx _a += weight * data [ dx + 3 ]
weights _alpha += weight
// colors
if ( data [ dx + 3 ] < 255 ) {
weight = weight * data [ dx + 3 ] / 250
}
gx _r += weight * data [ dx ]
gx _g += weight * data [ dx + 1 ]
gx _b += weight * data [ dx + 2 ]
weights += weight
}
}
}
}
data2 [ x2 ] = gx _r / weights
data2 [ x2 + 1 ] = gx _g / weights
data2 [ x2 + 2 ] = gx _b / weights
data2 [ x2 + 3 ] = gx _a / weights _alpha
}
}
canvas . getContext ( '2d' ) . clearRect ( 0 , 0 , Math . max ( W , W2 ) , Math . max ( H , H2 ) )
canvas . width = W2
canvas . height = H2
canvas . getContext ( '2d' ) . putImageData ( img2 , 0 , 0 )
}
const addConflict = function ( $conflicts , original , replacement ) {
const $conflict = $conflicts . find ( '.template' ) . clone ( ) . removeClass ( 'template' ) . addClass ( 'conflict' )
const $originalDiv = $conflict . find ( '.original' )
const $replacementDiv = $conflict . find ( '.replacement' )
$conflict . data ( 'data' , data )
$conflict . find ( '.filename' ) . text ( original . name )
$originalDiv . find ( '.size' ) . text ( OC . Util . humanFileSize ( original . size ) )
$originalDiv . find ( '.mtime' ) . text ( OC . Util . formatDate ( original . mtime ) )
// ie sucks
if ( replacement . size && replacement . lastModified ) {
$replacementDiv . find ( '.size' ) . text ( OC . Util . humanFileSize ( replacement . size ) )
$replacementDiv . find ( '.mtime' ) . text ( OC . Util . formatDate ( replacement . lastModified ) )
}
let path = original . directory + '/' + original . name
const urlSpec = {
file : path ,
x : 96 ,
y : 96 ,
c : original . etag ,
forceIcon : 0 ,
}
let previewpath = Files . generatePreviewUrl ( urlSpec )
// Escaping single quotes
previewpath = previewpath . replace ( /'/g , '%27' )
$originalDiv . find ( '.icon' ) . css ( { 'background-image' : "url('" + previewpath + "')" } )
getCroppedPreview ( replacement ) . then ( function ( path ) {
$replacementDiv . find ( '.icon' ) . css ( 'background-image' , 'url(' + path + ')' )
} , function ( ) {
path = OC . MimeType . getIconUrl ( replacement . type )
$replacementDiv . find ( '.icon' ) . css ( 'background-image' , 'url(' + path + ')' )
} )
// connect checkboxes with labels
const checkboxId = $conflicts . find ( '.conflict' ) . length
$originalDiv . find ( 'input:checkbox' ) . attr ( 'id' , 'checkbox_original_' + checkboxId )
$replacementDiv . find ( 'input:checkbox' ) . attr ( 'id' , 'checkbox_replacement_' + checkboxId )
$conflicts . append ( $conflict )
// set more recent mtime bold
// ie sucks
if ( replacement . lastModified > original . mtime ) {
$replacementDiv . find ( '.mtime' ) . css ( 'font-weight' , 'bold' )
} else if ( replacement . lastModified < original . mtime ) {
$originalDiv . find ( '.mtime' ) . css ( 'font-weight' , 'bold' )
} else {
// TODO add to same mtime collection?
}
// set bigger size bold
if ( replacement . size && replacement . size > original . size ) {
$replacementDiv . find ( '.size' ) . css ( 'font-weight' , 'bold' )
} else if ( replacement . size && replacement . size < original . size ) {
$originalDiv . find ( '.size' ) . css ( 'font-weight' , 'bold' )
} else {
// TODO add to same size collection?
}
// TODO show skip action for files with same size and mtime in bottom row
// always keep readonly files
if ( original . status === 'readonly' ) {
$originalDiv
. addClass ( 'readonly' )
. find ( 'input[type="checkbox"]' )
. prop ( 'checked' , true )
. prop ( 'disabled' , true )
$originalDiv . find ( '.message' )
. text ( t ( 'core' , 'read-only' ) )
}
}
const dialogName = 'oc-dialog-fileexists-content'
const dialogId = '#' + dialogName
if ( this . _fileexistsshown ) {
// add conflict
const $conflicts = $ ( dialogId + ' .conflicts' )
addConflict ( $conflicts , original , replacement )
const count = $ ( dialogId + ' .conflict' ) . length
const title = n (
'core' ,
'{count} file conflict' ,
'{count} file conflicts' ,
count ,
{ count } ,
)
$ ( dialogId ) . parent ( ) . children ( '.oc-dialog-title' ) . text ( title )
// recalculate dimensions
$ ( window ) . trigger ( 'resize' )
dialogDeferred . resolve ( )
} else {
// create dialog
this . _fileexistsshown = true
$ . when ( this . _getFileExistsTemplate ( ) ) . then ( function ( $tmpl ) {
const title = t ( 'core' , 'One file conflict' )
const $dlg = $tmpl . octemplate ( {
dialog _name : dialogName ,
title ,
type : 'fileexists' ,
allnewfiles : t ( 'core' , 'New Files' ) ,
allexistingfiles : t ( 'core' , 'Already existing files' ) ,
why : t ( 'core' , 'Which files do you want to keep?' ) ,
what : t ( 'core' , 'If you select both versions, the copied file will have a number added to its name.' ) ,
} )
$ ( 'body' ) . append ( $dlg )
if ( original && replacement ) {
const $conflicts = $dlg . find ( '.conflicts' )
addConflict ( $conflicts , original , replacement )
}
const buttonlist = [ {
text : t ( 'core' , 'Cancel' ) ,
classes : 'cancel' ,
click : function ( ) {
if ( typeof controller . onCancel !== 'undefined' ) {
controller . onCancel ( data )
}
$ ( dialogId ) . ocdialog ( 'close' )
} ,
} , {
text : t ( 'core' , 'Continue' ) ,
classes : 'continue' ,
click : function ( ) {
if ( typeof controller . onContinue !== 'undefined' ) {
controller . onContinue ( $ ( dialogId + ' .conflict' ) )
}
$ ( dialogId ) . ocdialog ( 'close' )
} ,
} ]
$ ( dialogId ) . ocdialog ( {
width : 500 ,
closeOnEscape : true ,
modal : true ,
buttons : buttonlist ,
closeButton : null ,
close : function ( ) {
self . _fileexistsshown = false
try {
$ ( this ) . ocdialog ( 'destroy' ) . remove ( )
} catch {
// ignore
}
} ,
} )
$ ( dialogId ) . css ( 'height' , 'auto' )
const $primaryButton = $dlg . closest ( '.oc-dialog' ) . find ( 'button.continue' )
$primaryButton . prop ( 'disabled' , true )
/ * *
*
* /
function updatePrimaryButton ( ) {
const checkedCount = $dlg . find ( '.conflicts .checkbox:checked' ) . length
$primaryButton . prop ( 'disabled' , checkedCount === 0 )
}
// add checkbox toggling actions
$ ( dialogId ) . find ( '.allnewfiles' ) . on ( 'click' , function ( ) {
const $checkboxes = $ ( dialogId ) . find ( '.conflict .replacement input[type="checkbox"]' )
$checkboxes . prop ( 'checked' , $ ( this ) . prop ( 'checked' ) )
} )
$ ( dialogId ) . find ( '.allexistingfiles' ) . on ( 'click' , function ( ) {
const $checkboxes = $ ( dialogId ) . find ( '.conflict .original:not(.readonly) input[type="checkbox"]' )
$checkboxes . prop ( 'checked' , $ ( this ) . prop ( 'checked' ) )
} )
$ ( dialogId ) . find ( '.conflicts' ) . on ( 'click' , '.replacement,.original:not(.readonly)' , function ( ) {
const $checkbox = $ ( this ) . find ( 'input[type="checkbox"]' )
$checkbox . prop ( 'checked' , ! $checkbox . prop ( 'checked' ) )
} )
$ ( dialogId ) . find ( '.conflicts' ) . on ( 'click' , '.replacement input[type="checkbox"],.original:not(.readonly) input[type="checkbox"]' , function ( ) {
const $checkbox = $ ( this )
$checkbox . prop ( 'checked' , ! $checkbox . prop ( 'checked' ) )
} )
// update counters
$ ( dialogId ) . on ( 'click' , '.replacement,.allnewfiles' , function ( ) {
const count = $ ( dialogId ) . find ( '.conflict .replacement input[type="checkbox"]:checked' ) . length
if ( count === $ ( dialogId + ' .conflict' ) . length ) {
$ ( dialogId ) . find ( '.allnewfiles' ) . prop ( 'checked' , true )
$ ( dialogId ) . find ( '.allnewfiles + .count' ) . text ( t ( 'core' , '(all selected)' ) )
} else if ( count > 0 ) {
$ ( dialogId ) . find ( '.allnewfiles' ) . prop ( 'checked' , false )
$ ( dialogId ) . find ( '.allnewfiles + .count' ) . text ( t ( 'core' , '({count} selected)' , { count } ) )
} else {
$ ( dialogId ) . find ( '.allnewfiles' ) . prop ( 'checked' , false )
$ ( dialogId ) . find ( '.allnewfiles + .count' ) . text ( '' )
}
updatePrimaryButton ( )
} )
$ ( dialogId ) . on ( 'click' , '.original,.allexistingfiles' , function ( ) {
const count = $ ( dialogId ) . find ( '.conflict .original input[type="checkbox"]:checked' ) . length
if ( count === $ ( dialogId + ' .conflict' ) . length ) {
$ ( dialogId ) . find ( '.allexistingfiles' ) . prop ( 'checked' , true )
$ ( dialogId ) . find ( '.allexistingfiles + .count' ) . text ( t ( 'core' , '(all selected)' ) )
} else if ( count > 0 ) {
$ ( dialogId ) . find ( '.allexistingfiles' ) . prop ( 'checked' , false )
$ ( dialogId ) . find ( '.allexistingfiles + .count' )
. text ( t ( 'core' , '({count} selected)' , { count } ) )
} else {
$ ( dialogId ) . find ( '.allexistingfiles' ) . prop ( 'checked' , false )
$ ( dialogId ) . find ( '.allexistingfiles + .count' ) . text ( '' )
}
updatePrimaryButton ( )
} )
dialogDeferred . resolve ( )
} )
. fail ( function ( ) {
dialogDeferred . reject ( )
alert ( t ( 'core' , 'Error loading file exists template' ) )
} )
}
// }
return dialogDeferred . promise ( )
} ,
_getFileExistsTemplate : function ( ) {
const defer = $ . Deferred ( )
if ( ! this . $fileexistsTemplate ) {
const self = this
$ . get ( OC . filePath ( 'core' , 'templates/legacy' , 'fileexists.html' ) , function ( tmpl ) {
self . $fileexistsTemplate = $ ( tmpl )
defer . resolve ( self . $fileexistsTemplate )
} )
. fail ( function ( ) {
defer . reject ( )
} )
} else {
defer . resolve ( this . $fileexistsTemplate )
}
return defer . promise ( )
} ,
}
export default Dialogs