2022-01-29 00:00:11 +03:00
import $ from 'jquery' ;
2023-10-11 15:34:21 +03:00
import { GET } from '../modules/fetch.js' ;
import { hideElem , loadElem } from '../utils/dom.js' ;
import { parseDom } from '../utils.js' ;
2022-01-29 00:00:11 +03:00
2023-10-11 15:34:21 +03:00
function getDefaultSvgBoundsIfUndefined ( text , src ) {
2021-06-05 15:32:19 +03:00
const DefaultSize = 300 ;
const MaxSize = 99999 ;
2023-10-11 15:34:21 +03:00
const svgDoc = parseDom ( text , 'image/svg+xml' ) ;
const svg = svgDoc . documentElement ;
2022-04-26 06:14:01 +03:00
const width = svg ? . width ? . baseVal ;
const height = svg ? . height ? . baseVal ;
if ( width === undefined || height === undefined ) {
return null ; // in case some svg is invalid or doesn't have the width/height
}
2021-06-05 15:32:19 +03:00
if ( width . unitType === SVGLength . SVG _LENGTHTYPE _PERCENTAGE || height . unitType === SVGLength . SVG _LENGTHTYPE _PERCENTAGE ) {
const img = new Image ( ) ;
img . src = src ;
if ( img . width > 1 && img . width < MaxSize && img . height > 1 && img . height < MaxSize ) {
return {
width : img . width ,
height : img . height
} ;
}
if ( svg . hasAttribute ( 'viewBox' ) ) {
const viewBox = svg . viewBox . baseVal ;
return {
width : DefaultSize ,
height : DefaultSize * viewBox . width / viewBox . height
} ;
}
return {
width : DefaultSize ,
height : DefaultSize
} ;
}
2022-04-26 06:14:01 +03:00
return null ;
2021-06-05 15:32:19 +03:00
}
2022-12-23 19:03:11 +03:00
export function initImageDiff ( ) {
2021-02-27 20:25:00 +03:00
function createContext ( image1 , image2 ) {
const size1 = {
width : image1 && image1 . width || 0 ,
height : image1 && image1 . height || 0
} ;
const size2 = {
width : image2 && image2 . width || 0 ,
height : image2 && image2 . height || 0
} ;
const max = {
width : Math . max ( size2 . width , size1 . width ) ,
height : Math . max ( size2 . height , size1 . height )
} ;
return {
image1 : $ ( image1 ) ,
image2 : $ ( image2 ) ,
size1 ,
size2 ,
max ,
ratio : [
Math . floor ( max . width - size1 . width ) / 2 ,
Math . floor ( max . height - size1 . height ) / 2 ,
Math . floor ( max . width - size2 . width ) / 2 ,
Math . floor ( max . height - size2 . height ) / 2
]
} ;
}
2023-10-11 15:34:21 +03:00
$ ( '.image-diff:not([data-image-diff-loaded])' ) . each ( async function ( ) {
2021-02-27 20:25:00 +03:00
const $container = $ ( this ) ;
2023-07-04 17:43:02 +03:00
$container . attr ( 'data-image-diff-loaded' , 'true' ) ;
2021-06-05 15:32:19 +03:00
2022-06-08 20:19:06 +03:00
// the container may be hidden by "viewed" checkbox, so use the parent's width for reference
const diffContainerWidth = Math . max ( $container . closest ( '.diff-file-box' ) . width ( ) - 300 , 100 ) ;
2021-02-27 20:25:00 +03:00
const imageInfos = [ {
2023-10-11 15:34:21 +03:00
path : this . getAttribute ( 'data-path-after' ) ,
mime : this . getAttribute ( 'data-mime-after' ) ,
$images : $container . find ( 'img.image-after' ) , // matches 3 <img>
2021-06-05 15:32:19 +03:00
$boundsInfo : $container . find ( '.bounds-info-after' )
2021-02-27 20:25:00 +03:00
} , {
2023-10-11 15:34:21 +03:00
path : this . getAttribute ( 'data-path-before' ) ,
mime : this . getAttribute ( 'data-mime-before' ) ,
$images : $container . find ( 'img.image-before' ) , // matches 3 <img>
2021-06-05 15:32:19 +03:00
$boundsInfo : $container . find ( '.bounds-info-before' )
2021-02-27 20:25:00 +03:00
} ] ;
2023-10-11 15:34:21 +03:00
await Promise . all ( imageInfos . map ( async ( info ) => {
const [ success ] = await Promise . all ( Array . from ( info . $images , ( img ) => {
return loadElem ( img , info . path ) ;
} ) ) ;
// only the first images is associated with $boundsInfo
if ( ! success ) info . $boundsInfo . text ( '(image error)' ) ;
if ( info . mime === 'image/svg+xml' ) {
const resp = await GET ( info . path ) ;
const text = await resp . text ( ) ;
const bounds = getDefaultSvgBoundsIfUndefined ( text , info . path ) ;
if ( bounds ) {
info . $images . attr ( 'width' , bounds . width ) ;
info . $images . attr ( 'height' , bounds . height ) ;
hideElem ( info . $boundsInfo ) ;
}
2021-02-27 20:25:00 +03:00
}
2023-10-11 15:34:21 +03:00
} ) ) ;
2021-02-27 20:25:00 +03:00
2023-10-11 15:34:21 +03:00
const $imagesAfter = imageInfos [ 0 ] . $images ;
const $imagesBefore = imageInfos [ 1 ] . $images ;
2021-02-27 20:25:00 +03:00
2023-10-11 15:34:21 +03:00
initSideBySide ( createContext ( $imagesAfter [ 0 ] , $imagesBefore [ 0 ] ) ) ;
if ( $imagesAfter . length > 0 && $imagesBefore . length > 0 ) {
initSwipe ( createContext ( $imagesAfter [ 1 ] , $imagesBefore [ 1 ] ) ) ;
initOverlay ( createContext ( $imagesAfter [ 2 ] , $imagesBefore [ 2 ] ) ) ;
2021-02-27 20:25:00 +03:00
}
2023-10-11 15:34:21 +03:00
$container . find ( '> .image-diff-tabs' ) . removeClass ( 'is-loading' ) ;
2021-02-27 20:25:00 +03:00
function initSideBySide ( sizes ) {
let factor = 1 ;
if ( sizes . max . width > ( diffContainerWidth - 24 ) / 2 ) {
factor = ( diffContainerWidth - 24 ) / 2 / sizes . max . width ;
}
2021-06-05 15:32:19 +03:00
const widthChanged = sizes . image1 . length !== 0 && sizes . image2 . length !== 0 && sizes . image1 [ 0 ] . naturalWidth !== sizes . image2 [ 0 ] . naturalWidth ;
const heightChanged = sizes . image1 . length !== 0 && sizes . image2 . length !== 0 && sizes . image1 [ 0 ] . naturalHeight !== sizes . image2 [ 0 ] . naturalHeight ;
if ( sizes . image1 . length !== 0 ) {
$container . find ( '.bounds-info-after .bounds-info-width' ) . text ( ` ${ sizes . image1 [ 0 ] . naturalWidth } px ` ) . addClass ( widthChanged ? 'green' : '' ) ;
$container . find ( '.bounds-info-after .bounds-info-height' ) . text ( ` ${ sizes . image1 [ 0 ] . naturalHeight } px ` ) . addClass ( heightChanged ? 'green' : '' ) ;
}
if ( sizes . image2 . length !== 0 ) {
$container . find ( '.bounds-info-before .bounds-info-width' ) . text ( ` ${ sizes . image2 [ 0 ] . naturalWidth } px ` ) . addClass ( widthChanged ? 'red' : '' ) ;
$container . find ( '.bounds-info-before .bounds-info-height' ) . text ( ` ${ sizes . image2 [ 0 ] . naturalHeight } px ` ) . addClass ( heightChanged ? 'red' : '' ) ;
}
2021-02-27 20:25:00 +03:00
sizes . image1 . css ( {
width : sizes . size1 . width * factor ,
height : sizes . size1 . height * factor
} ) ;
sizes . image1 . parent ( ) . css ( {
2023-03-07 15:11:24 +03:00
margin : ` 10px auto ` ,
2021-02-27 20:25:00 +03:00
width : sizes . size1 . width * factor + 2 ,
height : sizes . size1 . height * factor + 2
} ) ;
sizes . image2 . css ( {
width : sizes . size2 . width * factor ,
height : sizes . size2 . height * factor
} ) ;
sizes . image2 . parent ( ) . css ( {
2023-03-07 15:11:24 +03:00
margin : ` 10px auto ` ,
2021-02-27 20:25:00 +03:00
width : sizes . size2 . width * factor + 2 ,
height : sizes . size2 . height * factor + 2
} ) ;
}
function initSwipe ( sizes ) {
let factor = 1 ;
if ( sizes . max . width > diffContainerWidth - 12 ) {
factor = ( diffContainerWidth - 12 ) / sizes . max . width ;
}
sizes . image1 . css ( {
width : sizes . size1 . width * factor ,
height : sizes . size1 . height * factor
} ) ;
sizes . image1 . parent ( ) . css ( {
margin : ` 0px ${ sizes . ratio [ 0 ] * factor } px ` ,
width : sizes . size1 . width * factor + 2 ,
height : sizes . size1 . height * factor + 2
} ) ;
sizes . image1 . parent ( ) . parent ( ) . css ( {
padding : ` ${ sizes . ratio [ 1 ] * factor } px 0 0 0 ` ,
width : sizes . max . width * factor + 2
} ) ;
sizes . image2 . css ( {
width : sizes . size2 . width * factor ,
height : sizes . size2 . height * factor
} ) ;
sizes . image2 . parent ( ) . css ( {
margin : ` ${ sizes . ratio [ 3 ] * factor } px ${ sizes . ratio [ 2 ] * factor } px ` ,
width : sizes . size2 . width * factor + 2 ,
height : sizes . size2 . height * factor + 2
} ) ;
sizes . image2 . parent ( ) . parent ( ) . css ( {
width : sizes . max . width * factor + 2 ,
height : sizes . max . height * factor + 2
} ) ;
$container . find ( '.diff-swipe' ) . css ( {
width : sizes . max . width * factor + 2 ,
2023-08-24 15:13:23 +03:00
height : sizes . max . height * factor + 30 /* extra height for inner "position: absolute" elements */ ,
2021-02-27 20:25:00 +03:00
} ) ;
$container . find ( '.swipe-bar' ) . on ( 'mousedown' , function ( e ) {
e . preventDefault ( ) ;
const $swipeBar = $ ( this ) ;
const $swipeFrame = $swipeBar . parent ( ) ;
const width = $swipeFrame . width ( ) - $swipeBar . width ( ) - 2 ;
$ ( document ) . on ( 'mousemove.diff-swipe' , ( e2 ) => {
e2 . preventDefault ( ) ;
const value = Math . max ( 0 , Math . min ( e2 . clientX - $swipeFrame . offset ( ) . left , width ) ) ;
$swipeBar . css ( {
left : value
} ) ;
$container . find ( '.swipe-container' ) . css ( {
width : $swipeFrame . width ( ) - value
} ) ;
$ ( document ) . on ( 'mouseup.diff-swipe' , ( ) => {
$ ( document ) . off ( '.diff-swipe' ) ;
} ) ;
} ) ;
} ) ;
}
function initOverlay ( sizes ) {
let factor = 1 ;
if ( sizes . max . width > diffContainerWidth - 12 ) {
factor = ( diffContainerWidth - 12 ) / sizes . max . width ;
}
sizes . image1 . css ( {
width : sizes . size1 . width * factor ,
height : sizes . size1 . height * factor
} ) ;
sizes . image2 . css ( {
width : sizes . size2 . width * factor ,
height : sizes . size2 . height * factor
} ) ;
sizes . image1 . parent ( ) . css ( {
margin : ` ${ sizes . ratio [ 1 ] * factor } px ${ sizes . ratio [ 0 ] * factor } px ` ,
width : sizes . size1 . width * factor + 2 ,
height : sizes . size1 . height * factor + 2
} ) ;
sizes . image2 . parent ( ) . css ( {
margin : ` ${ sizes . ratio [ 3 ] * factor } px ${ sizes . ratio [ 2 ] * factor } px ` ,
width : sizes . size2 . width * factor + 2 ,
height : sizes . size2 . height * factor + 2
} ) ;
2023-03-07 15:11:24 +03:00
// some inner elements are `position: absolute`, so the container's height must be large enough
// the "css(width, height)" is somewhat hacky and not easy to understand, it could be improved in the future
2021-02-27 20:25:00 +03:00
sizes . image2 . parent ( ) . parent ( ) . css ( {
width : sizes . max . width * factor + 2 ,
2023-08-24 15:13:23 +03:00
height : sizes . max . height * factor + 2 ,
2021-02-27 20:25:00 +03:00
} ) ;
2023-02-01 14:48:35 +03:00
const $range = $container . find ( "input[type='range']" ) ;
2021-02-27 20:25:00 +03:00
const onInput = ( ) => sizes . image1 . parent ( ) . css ( {
opacity : $range . val ( ) / 100
} ) ;
$range . on ( 'input' , onInput ) ;
onInput ( ) ;
}
} ) ;
}