1const SQUARE_ASPECT_RATIO = '1' 2const EXIF_VIEWER_PATH = '/exif-metadata-viewer' 3const EXIF_REMOVER_PATH = '/remove-exif' 4const IMAGE_DETAILS_PATH = '/image-details' 5const PDF_COMPRESS_PATH_ARRAY = ['/reduce-pdf-file-size', '/compress-pdf'] 6const VERIFIED_STATUS = 'verified'; 7const VERIFY_USER_API_URL = '/api-check-is-premium-user-verified' 8const VERIFY_ONE_TIME_PURCHASE_API_URL = '/verify-one-time-purchase' 9// error message 10const setErrorMsg = (uploadedImagesArray) => { 11 if (uploadedImagesArray.length === 0) { 12 return 'Please upload your images first\n' + 13 '(Tap/click \'Select images\' above or drag and drop a file)' 14 } 15 return '' 16} 17 18const showPOSTRequestServerError = () => { 19 hideElement(uploadImageAnimationContainer) 20 hideElement(loadingBtn) 21 showElement(submitBtn) 22 showElement(serverErrorMessage) 23} 24 25const clearErrorMsg = () => { 26 if (uploadedImages.length > 0) { 27 hideElement(errorMsg) 28 } 29} 30 31const showErrorMsg = () => { 32 showElement(errorMsg) 33 errorMsg.innerText = setErrorMsg(uploadedImages) 34} 35 36const reloadPageWithNewURL = (base_path, param) => { 37 let path = `${base_path}/${param}`; 38 history.replaceState(null, null, path) 39 location.reload() 40} 41 42// POST Request 43const fetchPOSTRequest = async (url, body) => { 44 try { 45 46 const response = await fetch(url, { 47 method: "POST", 48 body: JSON.stringify(body), 49 headers: { 50 'Content-Type': 'application/json;charset=UTF-8' 51 } 52 }); 53 54 return await response.json(); 55 56 } catch (error) { 57 console.error('â fetchPOSTRequest error:', error) 58 throw error 59 } 60} 61 62const fetchGETRequest = async (url, params = {}) => { 63 // Build query string from params 64 const queryString = new URLSearchParams(params).toString(); 65 const requestUrl = queryString ? `${url}?${queryString}` : url; 66 67 try { 68 const response = await fetch(requestUrl, { 69 method: "GET", 70 headers: { 71 'Content-Type': 'application/json;charset=UTF-8' 72 } 73 }); 74 75 if (!response.ok) { 76 // Handle HTTP errors 77 const errorMessage = await response.text(); 78 throw new Error(`Request failed with status ${response.status}: ${errorMessage}`); 79 } 80 81 // Parse and return the JSON response 82 return await response.json(); 83 } catch (error) { 84 console.error('Error fetching data:', error.message); 85 throw error; 86 } 87}; 88 89const isAPIResponseVerified = async (apiUrl) => { 90 try { 91 const response = await fetchGETRequest(apiUrl, {}); 92 if (response.status === VERIFIED_STATUS) { 93 return true 94 } else { 95 console.warn('API validation failed:', response.message); 96 return false 97 } 98 } catch (error) { 99 console.error('Error during API validation:', error); 100 return false 101 } 102} 103 104const updateInputsValue = (width, widthInput, height, heightInput) => { 105 if (widthInput && heightInput) { 106 widthInput.value = width; 107 heightInput.value = height; 108 } 109} 110// display uploaded image thumbnail 111const displayDefaultImage = (imageClass) => { 112 const imageThumbnail = document.getElementsByClassName(imageClass) 113 if (imageThumbnail) { 114 for (let i = 0; i < imageThumbnail.length; i++) { 115 imageThumbnail[i].addEventListener('error', (e) => { 116 e.target.src = '/static/images/default-image.png'; 117 e.onerror = null; 118 }) 119 } 120 } 121} 122 123const isUploadedFileHEICFormat = (file) => { 124 return file.type.toLowerCase() === "image/heic" || 125 file.name.toLowerCase().includes(".heic"); 126} 127 128const displayHEICImageThumbnail = (file, thumbnail) => { 129 if (isUploadedFileHEICFormat(file)) { 130 heic2any({blob: file, toType: "image/png", quality: 1}).then((file) => { 131 thumbnail.src = URL.createObjectURL(file) 132 }) 133 } 134} 135 136// display uploaded images shorten file names 137const shortenFileName = (fileName, maxCharacters) => { 138 // Extract the file extension 139 const fileExtension = fileName.slice(fileName.lastIndexOf('.')); 140 const baseFileName = fileName.slice(0, fileName.lastIndexOf('.')); 141 142 // Determine the maximum characters based on the screen size 143 const screenSize = window.innerWidth; 144 let maxChars = maxCharacters; 145 if (screenSize <= 576) { 146 maxChars = Math.min(maxChars, 28); 147 } else if (screenSize <= 768) { 148 maxChars = Math.min(40); 149 } else if (screenSize <= 992) { 150 maxChars = Math.min(50); 151 } else { 152 maxChars = Math.min(60) 153 } 154 155 if (fileName.length <= maxChars) { 156 return fileName; // No need to shorten 157 } 158 159 // Calculate the parts of the file name to show 160 const startLength = Math.floor((maxChars - 3 - fileExtension.length) / 2); 161 const endLength = maxChars - 3 - fileExtension.length - startLength; 162 return baseFileName.substring(0, startLength) + "..." + 163 baseFileName.substring(baseFileName.length - endLength) + 164 fileExtension; 165} 166 167const shortenUploadedFileNames = (filesArray, maxCharNum) => { 168 let MAX_CHAR_NUM = maxCharNum 169 for (let i = 0; i < filesArray.length; i++) { 170 let fileName = filesArray[i].name; 171 172 if (fileName.length > 5) { 173 if (fileName.length <= MAX_CHAR_NUM) { 174 shortName = fileName; 175 } 176 if (xs && fileName.length > MAX_CHAR_NUM) { 177 shortName = fileName.slice(0, 5) + '...' + fileName.slice(-12); 178 } 179 if (s && fileName.length > MAX_CHAR_NUM) { 180 shortName = fileName.slice(0, 8) + '...' + fileName.slice(-10); 181 } 182 if (md && fileName.length > MAX_CHAR_NUM) { 183 shortName = fileName.slice(0, 5) + '...' + fileName.slice(-12); 184 } 185 if (lg && fileName.length > MAX_CHAR_NUM) { 186 shortName = fileName.slice(0, 5) + '...' + fileName.slice(-10); 187 } 188 if (xl && fileName.length > MAX_CHAR_NUM) { 189 shortName = fileName.slice(0, 5) + '...' + fileName.slice(-12); 190 } 191 return shortName 192 } 193 } 194} 195 196const displayShortenedFileNames = (name) => { 197 if (xs && name.length >= MAX_CHAR_NUM_MOBILE) { 198 shortName = name.slice(0, 5) + '...' + name.slice(-18); 199 } 200 if (s && name.length >= MAX_CHAR_NUM_MOBILE) { 201 shortName = name.slice(0, 8) + '...' + name.slice(-20); 202 } 203 if (md && name.length <= MAX_CHAR_NUM) { 204 shortName = name 205 } else if (md && name.length >= MAX_CHAR_NUM) { 206 shortName = name.slice(0, 5) + '...' + name.slice(-40); 207 } 208 if (lg && name.length <= MAX_CHAR_NUM) { 209 shortName = name 210 } else if (lg && name.length >= MAX_CHAR_NUM) { 211 shortName = name.slice(0, 5) + '...' + name.slice(-40); 212 } 213} 214 215const createResizerParametersCookie = () => { 216 if (percentRadioInput.checked) { 217 SAVE_RESIZER_PARAMETERS_COOKIE_VALUE = rangeSlider.value 218 } 219 if (dimensionRadioInput.checked) { 220 isPercentMethodSelected = false 221 SAVE_RESIZER_PARAMETERS_COOKIE_VALUE = `${resizerWidthInput.value}_${resizerHeightInput.value}` 222 } 223 224 if (resizerWidthInput.value === '') { 225 resizerWidthInput.value = null 226 } 227 if (resizerHeightInput.value === '') { 228 resizerHeightInput.value = null 229 } 230 createCookie(SAVE_RESIZER_PARAMETERS_COOKIE, SAVE_RESIZER_PARAMETERS_COOKIE_VALUE, 1) 231} 232 233const updateResizerInputValues = (cookieValue) => { 234 if (cookieValue.includes('_')) { 235 resizerWidthInput.value = cookieValue.split('_')[0] 236 resizerHeightInput.value = cookieValue.split('_')[1] 237 dimensionMethodSelected() 238 dimensionRadioInput.checked = true 239 } else { 240 percentRadioInput.checked = true 241 rangeSlider.value = cookieValue 242 percentValue.innerText = rangeSlider.value 243 } 244} 245 246const getUploadedImageFileSize = (uploadedImage) => { 247 const sizeInMB = uploadedImage.size / 1000000; 248 if (sizeInMB >= 1000) { 249 return `${(sizeInMB / 1000).toFixed(2)} GB` 250 } else if (sizeInMB >= 1) { 251 return `${sizeInMB.toFixed(1)} MB` 252 } else { 253 return `${Math.round(uploadedImage.size / 1000)} KB` 254 } 255} 256 257const getUploadedImageResolution = (imageEl, displayEl, imageWidthEl, imageHeightEl, resolutionsArray) => { 258 const reader = new FileReader();
259 reader.readAsDataURL(imageEl) 260 reader.onload = (e) => { 261 const image = new Image(); 262 image.src = e.target.result; 263 image.onload = () => { 264 imageWidthEl = image.width 265 imageHeightEl = image.height 266 let imageResolution = `${imageWidthEl} x ${imageHeightEl}` 267 resolutionsArray.push(imageResolution) 268 displayEl.innerText = `Resolution: ${imageResolution}` 269 } 270 } 271} 272 273const displayUploadedImagesResolutionAndSize = (uploadedImage, displayElement, widthInput, heightInput) => { 274 const reader = new FileReader(); 275 reader.readAsDataURL(uploadedImage) 276 reader.onload = (e) => { 277 const image = new Image(); 278 if (isUploadedFileHEICFormat(uploadedImage)) { 279 heic2any({blob: uploadedImage, toType: "image/png", quality: 1}).then((uploadedImage) => { 280 image.src = URL.createObjectURL(uploadedImage); 281 }) 282 } else { 283 image.src = e.target.result; 284 } 285 image.onload = () => { 286 imageWidth = image.width 287 imageHeight = image.height 288 imageResolution = `${imageWidth}x${imageHeight}` 289 imageSize = getUploadedImageFileSize(uploadedImage) 290 uploadedResolutions.push(imageResolution) 291 displayElement.innerText = `${imageResolution} px (${imageSize})` 292 updateInputsValue(imageWidth, widthInput, imageHeight, heightInput) 293 } 294 295 if (PDF_COMPRESS_PATH_ARRAY.includes(pageUrl)) { 296 displayElement.innerText = `File size: ${getUploadedImageFileSize(uploadedImage)}` 297 } 298 } 299} 300 301const displayPaidActionPopup = () => { 302 const paidOptionPopup = document.getElementById('paid-option-popup-wrapper-id') 303 const paidOptionPopupBg = document.getElementById('popup-paid-option-dark-bgc-id') 304 showElement(paidOptionPopup) 305 showElement(paidOptionPopupBg) 306} 307 308// image crop 309// const uploadImageToCrop = (e, baseStringArray) => { 310// const reader = new FileReader(); 311// if (e.target.files.length) { 312// uploadAndShowImage(reader) 313// reader.readAsDataURL(e.target.files[0]); 314// addFiles(e.target.files) 315// convertUploadedImageToBytes(e.target.files, baseStringArray) 316// } 317// } 318 319const cropImageAnimation = (imageContainer, loadingBtn, submitBtn, selectImageBtn, cropperContainer, animationContainer, uploadedImages, animationTxtEl, animationTxt, progressBarEl, progressPercentEl) => { 320 hideElement(imageContainer) 321 hideElement(selectImageBtn) 322 hideElement(cropperContainer) 323 showElement(animationContainer) 324 showResizeProgressBarAnimation(180, uploadedImages, animationTxtEl, animationTxt, progressBarEl, progressPercentEl) 325 showFlexElement(loadingBtn) 326 hideElement(submitBtn) 327} 328 329const scrollToTopOfPage = () => { 330 window.scrollTo({ 331 top: 0, 332 behavior: "smooth" 333 }); 334} 335 336// After initial images optimization, when success screen is displayed 337// Optimize uploaded images again with new params 338const optimizeImageWithNewParams = () => { 339 removeSuccessColorClassFromDragAndDrop(dropArea) 340 hideElement(uploadImageAnimationContainer) 341 hideElement(resizedImageContainer) 342 resetProgressBar() 343 submitPOSTRequest() 344} 345 346const unsetUploadedImageTemplateDisplay = () => { 347 const uploadedImageTemplates = document.getElementsByClassName('uploaded-image-template') 348 Object.values(uploadedImageTemplates).forEach(template => { 349 addClassToElement(template, 'unset') 350 }) 351} 352// display success screen 353const displaySuccessScreen = (dropArea) => { 354 const instagramToolPaths = ['/resize-image-for-instagram', '/whole-image-for-instagram'] 355 hideElement(loadingBtn) 356 hideElement(inputContainer) 357 hideElement(uploadImageAnimationContainer) 358 hideElement(fileInputBtn) 359 hideElement(dragAndDropSpan) 360 hideElement(trustTxtWrapper) 361 hideElement(uploadedImageContainer) 362 showElement(submitBtn) 363 364 if (pageUrl !== '/image-rotate') { 365 showElement(inputContainer) 366 } 367 368 showElement(resizedImageContainer) 369 addSuccessColorClassToDragAndDrop(dropArea) 370 371 if (instagramToolPaths.includes(pageUrl)) { 372 unsetUploadedImageTemplateDisplay() 373 } 374 375 if (getMobileOperatingSystem() === iOS) { 376 showElement(resizeWithAppPopup) 377 } 378} 379 380const resetSuccessScreen = () => { 381 removeSuccessColorClassFromDragAndDrop(dropArea) 382 hideElement(uploadImageAnimationContainer) 383 hideElement(resizedImageContainer) 384 resetProgressBar() 385} 386 387// update value of download merged images in ZIP
388const updateDownloadButton = (mergedFilURL, mergedFileName) => { 389 const downloadBtn = buttonsPanel.querySelector('.btn-merged-download'); 390 if (downloadBtn) { 391 downloadBtn.setAttribute('href', mergedFilURL); 392 downloadBtn.setAttribute('download', mergedFileName); 393 downloadBtn.innerText = `Download Images in ${mergedFileName.split('.')[1].toUpperCase()}` 394 } 395} 396// display buttons to: perform optimization with new images / same uploaded images but new parameters / 397// button to download images in ZIP file 398// if buttons already exist - update their value or display them for the first time 399const displayOrUpdateButtonsPanelElements = (mergedFilURL, mergedFileName) => { 400 if (!buttonsPanel.querySelector('.btn-new-action')) { 401 displayNewActionButtons(mergedFilURL, mergedFileName) 402 } else { 403 updateDownloadButton(mergedFilURL, mergedFileName); 404 } 405} 406const displayNewActionButtons = (mergedFilURL, mergedFileName) => { 407 if (!buttonsPanel.querySelector('.btn-new-action')) { 408 appendNewActionButton(buttonsPanel, NEW_ACTION_BTN_TXT); 409 } 410 if (!buttonsPanel.querySelector('.btn-merged-download')) { 411 appendDownloadMergedFilesButton(uploadedImages, buttonsPanel, mergedFilURL, mergedFileName); 412 } 413} 414 415 416const handleResponse = (response) => { 417 setTimeout(displaySuccessScreen, 4000, dropArea) 418 showResizedImages(resizedImageContainer, response) 419 420 displayOrUpdateButtonsPanelElements(response.mergedFileUrl, response.mergedFileName) 421 422 // if (response.surveyDisplay && socialMediaSurveyWrapper) { 423 // setTimeout(() => showElement(socialMediaSurveyWrapper), 4000) 424 // } 425} 426 427const onSuccessResponse = (response) => { 428 if (!response) return; 429 430 try { 431 handleResponse(response) 432 } catch (error) { 433 console.error("Response Error:", error); 434 } 435} 436 437 438const getGreatestCommonDivisor = (a, b) => { 439 if (a === 0 || b === 0) return Math.max(a, b); 440 441 a = Math.abs(a); 442 b = Math.abs(b); 443 444 while (b !== 0) { 445 let t = b; 446 b = a % b; 447 a = t; 448 } 449 return a; 450} 451 452const calculateAspectRatioBasedOnGCD = (width, height) => { 453 const divisor = getGreatestCommonDivisor(width, height); 454 const firstDivider = width / divisor 455 const secondDivider = height / divisor 456 return {first: firstDivider, second: secondDivider} 457} 458 459const areAspectRatiosEqual = (ratio1, ratio2, tolerance = 0.001) => { 460 const decimal1 = ratio1.first / ratio1.second; 461 const decimal2 = ratio2.first / ratio2.second; 462 return Math.abs(decimal1 - decimal2) < tolerance; 463} 464 465const isTool = (path) => { 466 return pageUrl === path 467} 468 469// upload & image action animation (progress bar) 470const showUploadedImageContainer = () => { 471 if (isTool(EXIF_VIEWER_PATH) || isTool(EXIF_REMOVER_PATH) || isTool(IMAGE_DETAILS_PATH)) { 472 showFlexElement(uploadedImageContainer) 473 } else { 474 showElement(uploadedImageContainer) 475 } 476} 477 478const hideUploadImageAnimation = () => { 479 hideElement(uploadImageAnimationContainer) 480 resetProgressBar(); 481} 482 483// uploading image animation progress bar (compressor) 484const showUploadProgressBarAnimation = (delay, imagesArray, txtElement, txt, progressBar, progressBarPercent, hideAnimation, showImages) => { 485 removeSuccessColorClassFromDragAndDrop(dropArea) 486 let progress = 0; 487 let id = setInterval(loadingAnimation, delay); 488 489 function loadingAnimation() { 490 IS_IMAGE_UPLOADED = false 491 if (imagesArray.length > 1) { 492 txtElement.innerText = txt 493 } else { 494 txtElement.innerText = txt 495 } 496 if (progress >= 60) { 497 progressBarPercent.style.color = '#fff'; 498 } 499 500 if (progress >= 100) { 501 clearInterval(id); 502 setTimeout(hideAnimation, 1000) 503 setTimeout(showImages, 1000); 504 IS_IMAGE_UPLOADED = true 505 } else { 506 progress += 5; 507 progressBar.style.width = progress + '%'; 508 progressBarPercent.innerText = progress + '%'; 509 } 510 } 511} 512 513// resize animation progress bar (compressor) 514const showResizeProgressBarAnimation = (delay, imagesArray, txtElement, txt, progressBar, progressBarPercent) => { 515 let progress = 0; 516 let id = setInterval(loadingAnimation, delay); 517 518 function loadingAnimation() { 519 if (imagesArray.length > 1) { 520 txtElement.innerText = txt 521 } else { 522 txtElement.innerText = txt 523 } 524 if (progress >= 70) { 525 progressBarPercent.style.color = '#fff'; 526 } 527 528 if (progress >= 95) { 529 clearInterval(id); 530 } else { 531 progress += 5; 532 progressBar.style.width = progress + '%'; 533 progressBarPercent.innerText = progress + '%'; 534 } 535 } 536} 537 538 539const uploadImageAnimation = (text) => { 540 if (resizerJpUrls.includes(pageUrl)) { 541 text = 'ç»åãã¢ãããã¼ãä¸...' 542 } 543 showElement(uploadImageAnimationContainer) 544 545 showUploadProgressBarAnimation( 546 100, 547 uploadedImages, 548 uploadImageAnimationTxt, 549 text, 550 progressBar, 551 progressBarPercent, 552 hideUploadImageAnimation, 553 showUploadedImageContainer 554 ) 555 showUploadProgressBarAnimation( 556 100, 557 uploadedImages, 558 uploadImageAnimationTxt, 559 text, 560 progressBar, 561 progressBarPercent, 562 hideUploadImageAnimation, 563 showUploadedImageContainer 564 ) 565 hideElement(inputContainer) 566 hideElement(uploadedImageContainer) 567} 568 569const returnAppropriateAnimationTime = () => { 570 let _2_5_MB = 2621440 571 let _5_MB = 5242880 572 let _10_MB = 10485760 573 let _20_MB = 20971520 574 // get number & file size of uploaded images 575 let uploadedImagesNumber = uploadedImages.length 576 // get sum of uploaded files in bytes 577 let fileSizeSumInBytes = uploadedImages.reduce((total, image) => total += image.size, 0) 578 579 // default animation time in milliseconds 580 let defaultAnimationTime = 200 581 let animationTime = defaultAnimationTime 582 583 // increase animation time based on file size sum & number of uploaded images 584 if (fileSizeSumInBytes > _2_5_MB || uploadedImagesNumber >= 3) { 585 animationTime = defaultAnimationTime * 2 586 } 587 if (fileSizeSumInBytes > _5_MB || uploadedImagesNumber >= 3) { 588 animationTime = defaultAnimationTime * 4 589 } 590 if (fileSizeSumInBytes > _10_MB || uploadedImagesNumber >= 3) { 591 animationTime = defaultAnimationTime * 6 592 } 593 if (fileSizeSumInBytes > _20_MB || uploadedImagesNumber >= 3) { 594 animationTime = defaultAnimationTime * 8 595 } 596 return animationTime 597} 598 599const imageActionAnimation = () => { 600 if (resizerJpUrls.includes(pageUrl)) { 601 ACTION_TEXT = 'ç»åããªãµã¤ãºä¸...' 602 } 603 showElement(uploadImageAnimationContainer) 604 showResizeProgressBarAnimation( 605 returnAppropriateAnimationTime(), 606 uploadedImages, 607 uploadImageAnimationTxt, 608 ACTION_TEXT, 609 progressBar, 610 progressBarPercent 611 ) 612 hideElement(inputContainer) 613 hideElement(uploadedImageContainer) 614 hideElement(submitBtn) 615 showElement(loadingBtn) 616} 617 618// aspect ratio related code (image resizer to sm & whole image for instagram) 619 620const calculateWidthAspectRatio = (width, height, newParam) => { 621 let quotient = height / width 622 return Math.round(quotient * newParam) 623} 624 625const calculateHeightAspectRatio = (width, height, newParam) => { 626 let quotient = width / height 627 return Math.round(quotient * newParam) 628} 629 630const updateInputWithNewWidthBasedOnAspectRatio = (updatedInput, listenerInput, aspectRatioCheckbox, width, height) => { 631 if (isCheckboxChecked(aspectRatioCheckbox)) { 632 updatedInput.value = calculateWidthAspectRatio(width, height, Math.round(listenerInput.value)) 633 } 634} 635 636const updateInputWithNewHeightBasedOnAspectRatio = (updatedInput, listenerInput, aspectRatioCheckbox, width, height) => { 637 if (isCheckboxChecked(aspectRatioCheckbox)) { 638 updatedInput.value = calculateHeightAspectRatio(width, height, Math.round(listenerInput.value)) 639 } 640} 641const resizeAndDisplayImagesInModal = (file) => { 642 const imgStretched = document.getElementById('stretched-img-id') 643 const imgAutoCrop = document.getElementById('auto-crop-img-id') 644 const imgManualCrop = document.getElementById('manual-crop-img-id') 645 const imgFitSize = document.getElementById('fit-size-img-id') 646 const blurredBgc = document.getElementById('blurred-img-bgc-id') 647 648 if (file) { 649 if (backgroundColor === DEFAULT_BACKGROUND) { 650 let blurredUrl = URL.createObjectURL(file) 651 blurredBgc.style.backgroundImage = `url(${blurredUrl})` 652 653 resizeImageInCss(imgStretched, file) 654 resizeImageInCss(imgAutoCrop, file) 655 resizeImageInCss(imgManualCrop, file) 656 resizeImageInCss(imgFitSize, file) 657 } else { 658 resizeImageInCss(imgStretched, file) 659 resizeImageInCss(imgAutoCrop, file) 660 resizeImageInCss(imgManualCrop, file) 661 resizeImageInCss(imgFitSize, file) 662 blurredBgc.style.backgroundImage = `` 663 } 664 } 665} 666 667const displayResizeModal = () => { 668 if (uploadedImages.length > 1) { 669 const manualBox = document.getElementById('manual-option-box-id') 670 if (manualBox) { 671 hideElement(manualBox) 672 } 673 } 674 showElement(aspectRatioPopup) 675 if (dpiSelectWrapper) { 676 hideElement(dpiSelectWrapper) 677 } 678 if (imageCropWindow) { 679 hideElement(imageCropWindow) 680 } 681} 682 683const setCropperFrameAspectRatio = () => { 684 if (width === height) { 685 cropperFrameAspectRatio = SQUARE_ASPECT_RATIO 686 } else { 687 cropperFrameAspectRatio = calculateAspectRatioBasedOnGCD(width, height) 688 } 689 return cropperFrameAspectRatio 690} 691 692const isAspectRatioCompatible = () => { 693 if (imageWidth === undefined || imageHeight === undefined) { 694 imageWidth = 360 695 imageHeight = 640 696 } 697 if (width === imageWidth && height === imageHeight) { 698 if (newWidthInPx && newHeightInPx) { 699 width = newWidthInPx 700 height = newHeightInPx 701 } 702 } 703 704 let imgRatio = calculateAspectRatioBasedOnGCD(Number(imageWidth), Number(imageHeight)) 705 let resolutionRatio = calculateAspectRatioBasedOnGCD(Number(width), Number(height)) 706 if (!areAspectRatiosEqual(imgRatio, resolutionRatio)) { 707 setCropperFrameAspectRatio() 708 displayResizeModal() 709 return false 710 } 711 return true 712} 713 714const updateAndDisplayAspectRatioModal = () => { 715 updateTxtContentOfAspectRatioPopup() 716 setCropperFrameAspectRatio() 717 displayResizeModal() 718} 719 720// image crop in Aspect ratio popup 721const hideCropUploadImageAnimation = () => { 722 hideElement(cropUploadImageAnimationContainer) 723 resetProgressBar(); 724} 725 726const uploadAndShowImage = () => { 727 let withRatio; 728 let heightRatio; 729 let file = uploadedImages[0] 730 let imgSource = URL.createObjectURL(file) 731 732 if (isUploadedFileHEICFormat(file)) {
733 heic2any({blob: file, toType: "image/png", quality: 1}).then((file) => { 734 imgSource = URL.createObjectURL(file) 735 const uploadedImage = uploadedImageToCropComponent(cropperImageContainer, URL.createObjectURL(file)) 736 displayImageInCrop(uploadedImage, withRatio, heightRatio) 737 }) 738 } 739 740 const uploadedImage = uploadedImageToCropComponent(cropperImageContainer, URL.createObjectURL(file)) 741 let value = setCropperFrameAspectRatio() 742 743 if (value === SQUARE_ASPECT_RATIO) { 744 withRatio = 1 745 heightRatio = 1 746 } else { 747 withRatio = value.first 748 heightRatio = value.second 749 } 750 displayImageInCrop(uploadedImage, withRatio, heightRatio) 751} 752 753const displayImageInCrop = (uploadedImage, withRatio, heightRatio) => { 754 cropper = new Cropper(uploadedImage, { 755 aspectRatio: withRatio / heightRatio, 756 crop: (event) => { 757 if (areInputsAdjustable) { 758 let maxHeight = 600 759 cropperDetailsInput.value = JSON.stringify(event.detail) 760 let containerData = cropper.getContainerData() 761 if (containerData.height > maxHeight) { 762 popupWrapper.style.overflow = 'scroll' 763 } 764 } 765 } 766 }) 767} 768 769const submitCropPOSTRequest = () => { 770 if (uploadedImages.length === 0) { 771 showErrorMsg() 772 } else { 773 cropPOSTRequest() 774 } 775} 776 777const displayCropSuccessScreen = () => { 778 hideElement(document.getElementById('crop-upload-image-animation-container-id')) 779 hideElement(selectImageBtn) 780 hideElement(document.getElementById('btn-loading-crop-id')) 781 showElement(cropperImageContainer) 782 addSuccessColorClassToDragAndDrop(document.getElementById('drop-area-crop-id')) 783} 784 785const handleCropResponse = (response) => { 786 setTimeout(displayCropSuccessScreen, 4000, dropArea) 787 showResizedImages(cropperImageContainer, response) 788 displayOrUpdateButtonsPanelElements(response.mergedFileUrl, response.mergedFileName) 789} 790 791const onCropSuccessResponse = (response) => { 792 if (!response) return; 793 try { 794 handleCropResponse(response) 795 } catch (error) { 796 console.error("Error:", error); 797 } 798} 799 800 801// Loop through passed radio inputs and check one with appropriate passed value 802const checkRadioInputWithPassedValue = (radioInputs, inputValue) => { 803 let found = false; 804 for (let i = 0; i < radioInputs.length; i++) { 805 if (radioInputs[i].value === inputValue) { 806 radioInputs[i].checked = true; 807 found = true; 808 break; 809 } 810 } 811} 812 813// check passed select option with right value and inner text description 814checkSelectOptionWithPassedValue = (options, value, desc) => { 815 let foundOption = null 816 options.forEach(option => { 817 if (option.value === value && option.innerHTML === desc) { 818 foundOption = option 819 } 820 }) 821 822 if (foundOption) { 823 foundOption.selected = true 824 } 825} 826// return file extension in lower case 827const getFileExtension = (file_name) => file_name.split('.').pop().toLowerCase() 828 829const resizeImageInCss = (imgEl, file) => { 830 let size = (width === height && width > 300) || width > height 831 ? {width: 300, height: width > height ? 200 : 300} 832 : {width: 250, height: 250}; 833 834 imgEl.style.width = `${size.width}px`; 835 imgEl.style.height = `${size.height}px`; 836 displayHEICImageThumbnail(file, imgEl) 837 imgEl.src = URL.createObjectURL(file); 838} 839 840// update select options based on passed obj with values 841// selectOptionsObj = [{'key': value}] 842const excludeOptionsFromSelectAndUpdate = (select, selectOptionsObj, extensionToExclude) => { 843 selectOptionsObj.unshift({value: NO_CHANGE, txt: `No change (${extensionToExclude})`}) 844 select.innerHTML = ''; 845 selectOptionsObj.forEach(format => { 846 if (format.value.toLowerCase() !== extensionToExclude) { 847 const option = document.createElement('option') 848 option.value = format.value; 849 option.innerText = format.txt.toUpperCase() 850 appendToElement(select, option) 851 } 852 }) 853} 854// Unit calculators (cm,mm,inch,px) 855const calculatePixelsFromCentimeters = (cm, dpi) => { 856 let onCmInPx = (dpi / ONE_INCH_IN_CM).toFixed(2)
857 let countCm = Math.floor(cm) 858 return (countCm * onCmInPx).toFixed(2) 859} 860 861const calculatePixelsFromMillimeters = (mm, dpi) => { 862 let mmToCm = (mm / 10).toFixed(2) 863 return calculatePixelsFromCentimeters(mmToCm, dpi) 864} 865 866const calculatePixelsFromInch = (inch, dpi) => { 867 let countCm = (inch * ONE_INCH_IN_CM).toFixed(2) 868 return calculatePixelsFromCentimeters(countCm, dpi) 869} 870 871const calculateMillimetersFromPixels = (px, dpi) => { 872 let onePx = ((ONE_INCH_IN_CM * 10) / dpi) 873 return (px * onePx).toFixed(2) 874} 875 876const calculateCentimetersFromPixels = (px, dpi) => { 877 let onePx = (ONE_INCH_IN_CM / dpi) 878 return (px * onePx).toFixed(2) 879} 880 881const calculateInchFromPixels = (px, dpi) => { 882 let onePx = (1.0 / dpi) 883 return (px * onePx).toFixed(2) 884} 885 886// shared function for resize for printing & resize to units 887const calculateNewResolutionParameter = (width, height, newParam, unit) => { 888 let quotient = height / width 889 let newSizeParamInPixels = calculateNewSizeInPixels(unit, newParam) 890 let result = (quotient * newParam).toFixed(2) 891 updateNewPictureResolutionInPx(Math.round(newSizeParamInPixels), Math.round(quotient * newSizeParamInPixels)) 892 return result 893} 894 895const updateNewPictureResolutionInPx = (width, height) => { 896 newWidthInPx = Math.round(width) 897 newHeightInPx = Math.round(height) 898} 899 900const calculateNewResolutionInPxWithoutAspectRatio = () => { 901 let newWidth = calculateNewSizeInPixels(unitValue, widthResizerInput.value) 902 let newHeight = calculateNewSizeInPixels(unitValue, heightResizerInput.value) 903 return {width: newWidth, height: newHeight} 904} 905 906const convertPxToUnits = (unit, width, height) => {
907 let convertedWidth; 908 let convertedHeight; 909 910 if (unit === CM_UNIT) { 911 convertedWidth = calculateCentimetersFromPixels(width, dpiValue) 912 convertedHeight = calculateCentimetersFromPixels(height, dpiValue) 913 } 914 if (unit === MM_UNIT) { 915 convertedWidth = calculateMillimetersFromPixels(width, dpiValue) 916 convertedHeight = calculateMillimetersFromPixels(height, dpiValue) 917 } 918 if (unit === INCH_UNIT) { 919 convertedWidth = calculateInchFromPixels(width, dpiValue) 920 convertedHeight = calculateInchFromPixels(height, dpiValue) 921 } 922 if (unit === PX_UNIT) { 923 convertedWidth = width 924 convertedHeight = height 925 } 926 return {width: convertedWidth, height: convertedHeight} 927} 928 929const displayConvertedImageSize = (width, height) => { 930 const convertedSize = convertPxToUnits(unitValue, width, height) 931 updateInputsValue(convertedSize.width, widthResizerInput, convertedSize.height, heightResizerInput) 932} 933 934const updateImageSizeByUnit = (unit) => { 935 const convertedSize = convertPxToUnits(unit, width, height) 936 updateInputsValue(convertedSize.width, widthResizerInput, convertedSize.height, heightResizerInput) 937 938} 939 940const calculateNewSizeInPixels = (unit, size) => {
941 let convertedSizeFromUnitValue; 942 if (unit === CM_UNIT) { 943 convertedSizeFromUnitValue = calculatePixelsFromCentimeters(size, dpiValue) 944 } 945 if (unit === MM_UNIT) { 946 convertedSizeFromUnitValue = calculatePixelsFromMillimeters(size, dpiValue) 947 } 948 if (unit === INCH_UNIT) { 949 convertedSizeFromUnitValue = calculatePixelsFromInch(size, dpiValue) 950 } 951 if (unit === PX_UNIT) { 952 convertedSizeFromUnitValue = calculateInchFromPixels(size, dpiValue) * dpiValue 953 } 954 return convertedSizeFromUnitValue 955} 956 957// returns chosen value from select element 958const getSelectedValue = (select) => select.options[select.selectedIndex].value 959// returns text of chosen option from select element 960const getSelectedOptionText = (select) => select.options[select.selectedIndex].text 961 962const geValueOfSelectedUnits = (selectEl) => { 963 unitValue = selectEl.value 964 updateUnitValues(unitValue) 965 updateImageSizeByUnit(unitValue) 966} 967 968const updateUnitValues = (value) => { 969 unitWidthSelect.value = value 970 unitHeightSelect.value = value 971} 972 973// Limit number of characters in input type number 974const limitNumberInputLength = (input) => { 975 if (input.value.length > MAX_LENGTH) { 976 input.value = input.value.slice(0, MAX_LENGTH) 977 } 978} 979 980const showInputErrorMessage = (inputElement, errorMsgElement) => { 981 errorMsgElement.innerText = setTxtInputErrorMessage() 982 inputElement.classList.add('error-msg') 983} 984 985const clearInputErrorMessage = (inputElement, errorMsgElement) => { 986 errorMsgElement.innerText = setTxtInputErrorMessage() 987 inputElement.classList.remove('error-msg') 988} 989 990const isTxtInputValidated = (inputElement, errorMsgElement, maxCharNum) => { 991 if (inputElement.value === '' || inputElement.value.length > maxCharNum) { 992 showInputErrorMessage(inputElement, errorMsgElement) 993 return false 994 } else { 995 clearInputErrorMessage(inputElement, errorMsgElement) 996 return true 997 } 998} 999 1000const getAndDisplayUploadedImagesResolution = (uploadedImage, convertFromFormat, imageWidth, imageHeight) => { 1001 const reader = new FileReader(); 1002 reader.readAsDataURL(uploadedImage) 1003 reader.onload = (e) => { 1004 const image = new Image(); 1005 image.src = e.target.result; 1006 image.onload = () => { 1007 imageWidth = image.width 1008 imageHeight = image.height 1009 let imageResolution = `${imageWidth} x ${imageHeight}` 1010 uploadedResolutions.push(imageResolution) 1011 imageSize = getUploadedImageFileSize(uploadedImage) 1012 convertFromFormat.innerHTML = `<strong>${imageResolution} px (${imageSize})</strong>` 1013 1014 width = imageWidth 1015 height = imageHeight 1016 displayConvertedImageSize(imageWidth, imageHeight) 1017 convertPxToUnits(unitValue, imageWidth, imageHeight) 1018 } 1019 } 1020} 1021 1022// create URL for compare images page 1023const createComparePageURL = (compareURLObj) => { 1024 return `/compare-images?orImg=${encodeURIComponent(compareURLObj.originImg)}&orName=${compareURLObj.originName} 1025 &orRes=${compareURLObj.originResolution} 1026 &orS=${compareURLObj.originSize}&optImg=${encodeURIComponent(compareURLObj.optimizedImg)}&optName= 1027 ${compareURLObj.optimizedName}&optRes=${compareURLObj.optimizedResolution} 1028 &optS=${compareURLObj.optimizedSize}` 1029} 1030 1031const createPreviewPdfPageURL = (demoPdfUrl) => { 1032 return `/preview-pdf?demoPdfUrl=${encodeURIComponent(demoPdfUrl)}` 1033} 1034 1035// create Google Analytics event 1036const createGtagEvent = (eventName, eventParametersObj) => { 1037 gtag('event', eventName, eventParametersObj) 1038} 1039 1040const toggleFaqActiveClass = (clickedElement) => { 1041 const wrapper = clickedElement.closest('.question-and-answer-wrapper') 1042 const showIcon = wrapper.querySelector('img[alt="display answer"]') 1043 const hideIcon = wrapper.querySelector('img[alt="hide answer"]') 1044 const answer = wrapper.querySelector('.single-answer'); 1045 const question = wrapper.querySelector('.single-question'); 1046 1047 if (clickedElement === showIcon || (clickedElement === question && !answer.classList.contains('active'))) { 1048 // Show the answer 1049 showIcon.classList.remove('active'); 1050 hideIcon.classList.add('active'); 1051 answer.classList.add('active'); 1052 } else if (clickedElement === hideIcon) { 1053 // Hide the answer when hideIcon is clicked 1054 showIcon.classList.add('active'); 1055 hideIcon.classList.remove('active'); 1056 answer.classList.remove('active'); 1057 } else if (clickedElement === question && answer.classList.contains('active')) { 1058 // Hide the answer when question is clicked and the answer is currently active 1059 showIcon.classList.add('active'); 1060 hideIcon.classList.remove('active'); 1061 answer.classList.remove('active'); 1062 } 1063} 1064 1065const toDecimal = (coord, ref) => { 1066 if (!coord || coord.length !== 3) { 1067 console.warn("Wrong coordinates EXIF:", coord); 1068 return NaN; 1069 } 1070 1071 const d = coord[0][0] / coord[0][1]; 1072 const m = coord[1][0] / coord[1][1];
1073 const s = coord[2][0] / coord[2][1]; 1074 1075 let decimal = d + m / 60 + s / 3600; 1076 return (ref === "S" || ref === "W") ? -decimal : decimal; 1077} 1078 1079const getDirection = (ref) => { 1080 if (ref.includes("North")) return "N"; 1081 if (ref.includes("South")) return "S"; 1082 if (ref.includes("East")) return "E"; 1083 if (ref.includes("West")) return "W"; 1084 return null; 1085} 1086 1087const getCoordinates = (tags) => { 1088 const latTag = tags["GPSLatitude"]; 1089 const lonTag = tags["GPSLongitude"]; 1090 let lat 1091 let lon 1092 if (!latTag || !lonTag) { 1093 console.warn("There's no GPS data in EXIF"); 1094 lat = DEFAULT_LATITUDE 1095 lon = DEFAULT_LONGITUDE 1096 } else { 1097 const latRef = tags["GPSLatitudeRef"] ? getDirection(tags["GPSLatitudeRef"].description) : null; 1098 const lonRef = tags["GPSLongitudeRef"] ? getDirection(tags["GPSLongitudeRef"].description) : null; 1099 1100 lat = toDecimal(latTag.value, latRef); 1101 lon = toDecimal(lonTag.value, lonRef); 1102 } 1103 1104 return {'latitude': lat, 'longitude': lon} 1105} 1106 1107const initializeMap = (lat, lon, index) => { 1108 if (!lat || !lon) { 1109 console.warn("There's no GPS data"); 1110 return; 1111 } 1112 setTimeout(() => { 1113 const map = L.map('map-' + index).setView([lat, lon], 15); 1114 1115 L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { 1116 attribution: '© OpenStreetMap contributors' 1117 }).addTo(map); 1118 1119 L.marker([lat, lon]).addTo(map) 1120 .openPopup(); 1121 1122 setTimeout(() => { 1123 map.invalidateSize(); 1124 }, 1000) 1125 1126 }, 3000); 1127} 1128 1129const createTable = () => { 1130 const table = document.createElement('table'); 1131 table.className = 'table table-nowrap table-vertical-border-striped'; 1132 return table 1133} 1134 1135const createSectionHeader = (groupName) => { 1136 const sectionHeader = document.createElement('tr'); 1137 sectionHeader.innerHTML = `<th colspan="3" class="exif-group-header p-3 text-center"><strong>${groupName}</strong></th>`; 1138 return sectionHeader 1139} 1140 1141const createMapElement = (index) => { 1142 const mapElement = document.createElement('div') 1143 mapElement.id = `map-${index}` 1144 mapElement.className = 'map-container' 1145 return mapElement 1146} 1147 1148const createMapCell = (index) => { 1149 const mapCell = document.createElement('td'); 1150 mapCell.setAttribute('colspan', '3'); 1151 const map = createMapElement(index) 1152 1153 appendToElement(mapCell, map) 1154 return mapCell 1155} 1156 1157const isLocationData = (coordinatesObj) => { 1158 return coordinatesObj.latitude !== DEFAULT_LATITUDE && coordinatesObj.longitude !== DEFAULT_LONGITUDE 1159} 1160 1161const limitCharNumber = (value) => { 1162 if (value.length >= 120) { 1163 let limitedValue = value.substring(0, 120) 1164 value = `${limitedValue} ...` 1165 } 1166 return value 1167} 1168 1169const displayInfoAlert = (image) => { 1170 const alert = document.createElement('div') 1171 alert.innerHTML = `<span class="alert alert-success text-center mb-0">This image doesn't have any EXIF data.</span>` 1172 1173 appendUploadedImagesEXIFComponent(image, alert); 1174 1175 const exifDataContainer = document.querySelector('.exif-data-container') 1176 exifDataContainer.classList.add('no-data') 1177}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.