浏览代码

add loading animation, remove console logs
init edit features only on edit page

Paul Paffe 1 年之前
父节点
当前提交
975040afe9

+ 1 - 1
web/src/lib/components/asset-viewer/photo-editor/adjust-element.svelte

@@ -44,7 +44,7 @@
     }
     }
     progressBar.style.background = '#373737';
     progressBar.style.background = '#373737';
     progressBar.style.background = progressColor;
     progressBar.style.background = progressColor;
-    console.log(progressColor);
+    //console.log(progressColor);
     dispatch('applyFilter');
     dispatch('applyFilter');
   };
   };
 </script>
 </script>

+ 1 - 1
web/src/lib/components/asset-viewer/photo-editor/filter-card.svelte

@@ -1,7 +1,7 @@
 <script lang="ts">
 <script lang="ts">
   import { onMount, createEventDispatcher } from 'svelte';
   import { onMount, createEventDispatcher } from 'svelte';
 
 
-  export let title: string = 'Without';
+  export let title = 'Without';
 
 
   export let currentFilter: string;
   export let currentFilter: string;
   export let thumbData: string;
   export let thumbData: string;

+ 134 - 91
web/src/lib/components/asset-viewer/photo-editor/photo-editor.svelte

@@ -1,44 +1,43 @@
 <script lang="ts">
 <script lang="ts">
+  import { mdiContrastCircle } from '@mdi/js'; // Contrast
+  import { mdiBrightness6 } from '@mdi/js'; // Brightness
+  import { mdiInvertColors } from '@mdi/js'; // Invert
+  import { mdiBlur } from '@mdi/js'; // Blur
+  import { mdiCircleHalfFull } from '@mdi/js'; // Vignette
+  import { mdiDotsCircle } from '@mdi/js'; // Tilt-shift
+  import { mdiSelectInverse } from '@mdi/js'; // Selective
+  import { mdiPillar } from '@mdi/js'; // Pillarbox
+
   import { onMount, SvelteComponent } from 'svelte';
   import { onMount, SvelteComponent } from 'svelte';
   import { api, AssetResponseDto } from '@api';
   import { api, AssetResponseDto } from '@api';
   import { handleError } from '$lib/utils/handle-error';
   import { handleError } from '$lib/utils/handle-error';
 
 
   import Icon from '$lib/components/elements/icon.svelte';
   import Icon from '$lib/components/elements/icon.svelte';
 
 
-  import { mdiAutoFix } from '@mdi/js';
-  import { mdiImageFilterHdr } from '@mdi/js';
-  import { mdiWeatherSunny } from '@mdi/js';
-  import { mdiWeatherCloudy } from '@mdi/js';
-  import { mdiCropRotate } from '@mdi/js';
-  import { mdiTune } from '@mdi/js';
-  import { mdiImageAutoAdjust } from '@mdi/js';
-  import { mdiFullscreen } from '@mdi/js';
-  import { mdiRelativeScale } from '@mdi/js';
-  import { mdiRectangleOutline } from '@mdi/js';
-  import { mdiSquareOutline } from '@mdi/js';
-
-  import { mdiClose } from '@mdi/js';
-  import { mdiDotsVertical } from '@mdi/js';
-  import { mdiFlipHorizontal } from '@mdi/js';
-  import { mdiFlipVertical } from '@mdi/js';
-  import { mdiFormatRotate90 } from '@mdi/js';
-  import { mdiTriangleSmallUp } from '@mdi/js';
+  import { mdiAutoFix } from '@mdi/js'; // Auto
+  import { mdiImageFilterHdr } from '@mdi/js'; // HDR
+  import { mdiWeatherSunny } from '@mdi/js'; // Exposure
+  import { mdiWeatherCloudy } from '@mdi/js'; // Contrast
+  import { mdiCropRotate } from '@mdi/js'; // Rotate
+  import { mdiTune } from '@mdi/js'; // Adjust
+  import { mdiImageAutoAdjust } from '@mdi/js'; // Auto Adjust
+  import { mdiFullscreen } from '@mdi/js'; // Fullscreen
+  import { mdiRelativeScale } from '@mdi/js'; // Ratio
+  import { mdiRectangleOutline } from '@mdi/js'; // Rectangle
+  import { mdiSquareOutline } from '@mdi/js'; // Square
+
+  import { mdiClose } from '@mdi/js'; // Close
+  import { mdiDotsVertical } from '@mdi/js'; // More
+  import { mdiFlipHorizontal } from '@mdi/js'; // Flip horizontal
+  import { mdiFlipVertical } from '@mdi/js'; // Flip vertical
+  import { mdiFormatRotate90 } from '@mdi/js'; // Rotate
+  import { mdiTriangleSmallUp } from '@mdi/js'; // Triangle
 
 
   import SuggestionsButton from './suggestions-button.svelte';
   import SuggestionsButton from './suggestions-button.svelte';
   import AspectRatioButton from './aspect-ratio-button.svelte';
   import AspectRatioButton from './aspect-ratio-button.svelte';
   import AdjustElement from './adjust-element.svelte';
   import AdjustElement from './adjust-element.svelte';
   import FilterCard from './filter-card.svelte';
   import FilterCard from './filter-card.svelte';
 
 
-  //Filter icons
-  import { mdiContrastCircle } from '@mdi/js';
-  import { mdiBrightness6 } from '@mdi/js';
-  import { mdiInvertColors } from '@mdi/js';
-  import { mdiBlur } from '@mdi/js';
-  import { mdiCircleHalfFull } from '@mdi/js';
-  import { mdiDotsCircle } from '@mdi/js';
-  import { mdiSelectInverse } from '@mdi/js';
-  import { mdiPillar } from '@mdi/js';
-
   import Render from './render.svelte';
   import Render from './render.svelte';
   import { presets as presetsObject } from './filter.js';
   import { presets as presetsObject } from './filter.js';
 
 
@@ -57,6 +56,7 @@
   };
   };
 
 
   import { createEventDispatcher } from 'svelte';
   import { createEventDispatcher } from 'svelte';
+  import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
 
 
   const dispatch = createEventDispatcher();
   const dispatch = createEventDispatcher();
 
 
@@ -81,6 +81,7 @@
   let imageElement: HTMLImageElement;
   let imageElement: HTMLImageElement;
 
 
   let originalImage: HTMLImageElement;
   let originalImage: HTMLImageElement;
+  let isLoaded = false;
 
 
   let imageWrapper: HTMLDivElement;
   let imageWrapper: HTMLDivElement;
   let cropElement: HTMLDivElement;
   let cropElement: HTMLDivElement;
@@ -160,23 +161,24 @@
 
 
   const applyFilter = () => {
   const applyFilter = () => {
     if (!isFilter(presets[currentFilter] as Preset)) {
     if (!isFilter(presets[currentFilter] as Preset)) {
-      console.log(currentFilter);
-      console.log(presets[currentFilter]);
-      console.log(filter);
+      // console.log(currentFilter);
+      // console.log(presets[currentFilter]);
+      // console.log(filter);
       currentFilter = 'custom';
       currentFilter = 'custom';
     }
     }
 
 
-    console.log('apply filter');
-    console.log(filter);
-    (imageElement.style.filter = `blur(${filter.blur * 10}px) brightness(${filter.brightness}) contrast(${
+    // console.log('apply filter');
+    // console.log(filter);
+    imageElement.style.filter = `blur(${filter.blur * 10}px) brightness(${filter.brightness}) contrast(${
       filter.contrast
       filter.contrast
     }) grayscale(${filter.grayscale}) hue-rotate(${(filter.hueRotate - 1) * 180}deg) invert(${filter.invert}) opacity(${
     }) grayscale(${filter.grayscale}) hue-rotate(${(filter.hueRotate - 1) * 180}deg) invert(${filter.invert}) opacity(${
       filter.opacity
       filter.opacity
-    }) saturate(${filter.saturation}) sepia(${filter.sepia})`),
-      console.log('applied filter');
+    }) saturate(${filter.saturation}) sepia(${filter.sepia})`;
+    //console.log('applied filter');
   };
   };
 
 
   onMount(async () => {
   onMount(async () => {
+    isLoaded = false;
     try {
     try {
       await loadThumbData();
       await loadThumbData();
       await loadAssetData();
       await loadAssetData();
@@ -186,16 +188,13 @@
     }
     }
     imageElement.src = thumbData || assetData;
     imageElement.src = thumbData || assetData;
     imageElement.onload = () => {
     imageElement.onload = () => {
-      console.log('imageElement.onload');
-      initAngleSlider();
-      initAssetDrag();
-      initZoom();
+      isLoaded = true;
       setAspectRatio('original');
       setAspectRatio('original');
     };
     };
   });
   });
 
 
   const initZoom = () => {
   const initZoom = () => {
-    document.addEventListener('wheel', function (e) {
+    document.onwheel = (e: WheelEvent) => {
       if (e.deltaY > 0) {
       if (e.deltaY > 0) {
         if (currentZoom <= 5) {
         if (currentZoom <= 5) {
           currentZoom += zoomSpeed;
           currentZoom += zoomSpeed;
@@ -206,12 +205,12 @@
         }
         }
       }
       }
       setImageWrapperTransform();
       setImageWrapperTransform();
-    });
+    };
   };
   };
 
 
+  // Sets the filter to the preset passed in by the event
   const setPreset = (event: CustomEvent<string>) => {
   const setPreset = (event: CustomEvent<string>) => {
     const preset = event.detail;
     const preset = event.detail;
-    console.log(presets);
     filter.blur = presets[preset].blur;
     filter.blur = presets[preset].blur;
     filter.brightness = presets[preset].brightness;
     filter.brightness = presets[preset].brightness;
     filter.contrast = presets[preset].contrast;
     filter.contrast = presets[preset].contrast;
@@ -221,14 +220,12 @@
     filter.opacity = presets[preset].opacity;
     filter.opacity = presets[preset].opacity;
     filter.saturation = presets[preset].saturation;
     filter.saturation = presets[preset].saturation;
     filter.sepia = presets[preset].sepia;
     filter.sepia = presets[preset].sepia;
-
-    console.log(presets);
-    console.log(preset);
     applyFilter();
     applyFilter();
     currentFilter = preset;
     currentFilter = preset;
   };
   };
 
 
   const loadAssetData = async () => {
   const loadAssetData = async () => {
+    // Load original image
     try {
     try {
       const { data } = await api.assetApi.serveFile(
       const { data } = await api.assetApi.serveFile(
         { id: asset.id, isThumb: false, isWeb: false, key: publicSharedKey },
         { id: asset.id, isThumb: false, isWeb: false, key: publicSharedKey },
@@ -277,6 +274,10 @@
     activeButton = button;
     activeButton = button;
     const cropWrapperParent = cropElementWrapper.parentElement;
     const cropWrapperParent = cropElementWrapper.parentElement;
 
 
+    removeAngleSlider();
+    removeAssetDrag();
+    removeZoom();
+
     //TODO: better solution
     //TODO: better solution
     if (!cropWrapperParent) {
     if (!cropWrapperParent) {
       return;
       return;
@@ -288,6 +289,9 @@
         setAspectRatio(currentAspectRatio);
         setAspectRatio(currentAspectRatio);
         break;
         break;
       case 'crop':
       case 'crop':
+        initAngleSlider();
+        initAssetDrag();
+        initZoom();
         if (!cropWrapperParent.classList.contains('p-24')) {
         if (!cropWrapperParent.classList.contains('p-24')) {
           cropWrapperParent.classList.add('p-24');
           cropWrapperParent.classList.add('p-24');
           cropWrapperParent.classList.add('pb-52');
           cropWrapperParent.classList.add('pb-52');
@@ -318,13 +322,13 @@
         aspectRatio = strings[1] + '_' + strings[0];
         aspectRatio = strings[1] + '_' + strings[0];
       }
       }
       if (currentAngleOffset % 180 == 0) {
       if (currentAngleOffset % 180 == 0) {
-        console.log('currentAngleOffset', currentAngleOffset);
-        console.log('isRotate', isRotate);
+        //console.log('currentAngleOffset', currentAngleOffset);
+        //console.log('isRotate', isRotate);
         isRotate = false;
         isRotate = false;
       }
       }
     }
     }
 
 
-    console.log('isRotate', isRotate);
+    //console.log('isRotate', isRotate);
 
 
     currentAspectRatio = aspectRatio;
     currentAspectRatio = aspectRatio;
 
 
@@ -374,8 +378,8 @@
     cropElement.style.aspectRatio = '' + aspectRatioNum;
     cropElement.style.aspectRatio = '' + aspectRatioNum;
     const cropElementWrapperAspectRatio = cropElementWrapper.offsetWidth / cropElementWrapper.offsetHeight;
     const cropElementWrapperAspectRatio = cropElementWrapper.offsetWidth / cropElementWrapper.offsetHeight;
 
 
-    console.log('aspectRatioNum', aspectRatioNum);
-    console.log('cropElementWrapperAspectRatio', cropElementWrapperAspectRatio);
+    //console.log('aspectRatioNum', aspectRatioNum);
+    //console.log('cropElementWrapperAspectRatio', cropElementWrapperAspectRatio);
 
 
     if (aspectRatioNum >= 1 && cropElementWrapperAspectRatio >= 1 && aspectRatioNum < cropElementWrapperAspectRatio) {
     if (aspectRatioNum >= 1 && cropElementWrapperAspectRatio >= 1 && aspectRatioNum < cropElementWrapperAspectRatio) {
       cropElement.style.width = 'auto';
       cropElement.style.width = 'auto';
@@ -408,8 +412,8 @@
       //cropElement.style.maxWidth = '100%';
       //cropElement.style.maxWidth = '100%';
     }
     }
 
 
-    console.log('cropElement.offsetWidth', cropElement.offsetWidth);
-    console.log('cropElement.offsetHeight', cropElement.offsetHeight);
+    //console.log('cropElement.offsetWidth', cropElement.offsetWidth);
+    //console.log('cropElement.offsetHeight', cropElement.offsetHeight);
 
 
     currentTranslate = { x: 0, y: 0 };
     currentTranslate = { x: 0, y: 0 };
     calcImageElement(currentAngle);
     calcImageElement(currentAngle);
@@ -443,7 +447,7 @@
         a = Math.min(a, (125 / 49) * 45);
         a = Math.min(a, (125 / 49) * 45);
       }
       }
 
 
-      console.log('a', a);
+      //console.log('a', a);
 
 
       let angle = Math.round((a / 125) * 49);
       let angle = Math.round((a / 125) * 49);
       angle = angle * -1;
       angle = angle * -1;
@@ -459,7 +463,7 @@
       // set the element's new position:
       // set the element's new position:
       let a = angleSlider.offsetLeft - pos1;
       let a = angleSlider.offsetLeft - pos1;
 
 
-      console.log('a', a);
+      //console.log('a', a);
       if (a < 0) {
       if (a < 0) {
         a = Math.max(a, (-125 / 49) * 45);
         a = Math.max(a, (-125 / 49) * 45);
       } else {
       } else {
@@ -485,7 +489,7 @@
 
 
     const dragTouchStart = (e: TouchEvent) => {
     const dragTouchStart = (e: TouchEvent) => {
       e.preventDefault();
       e.preventDefault();
-      console.log('dragTouchStart');
+      //console.log('dragTouchStart');
       // get the mouse cursor position at startup:
       // get the mouse cursor position at startup:
       pos2 = e.touches[0].clientX;
       pos2 = e.touches[0].clientX;
       document.ontouchend = closeDragElement;
       document.ontouchend = closeDragElement;
@@ -556,17 +560,17 @@
 
 
       maxY = maxY / currentZoom;
       maxY = maxY / currentZoom;
 
 
-      console.log('currentZoom', currentZoom);
-      console.log('offsetHeight', imageWrapper.offsetHeight);
-      console.log('realHight', imageWrapper.offsetHeight * currentZoom);
-      console.log('maxY', maxY);
+      // console.log('currentZoom', currentZoom);
+      // console.log('offsetHeight', imageWrapper.offsetHeight);
+      // console.log('realHight', imageWrapper.offsetHeight * currentZoom);
+      // console.log('maxY', maxY);
 
 
       // Calc max x translation
       // Calc max x translation
       const h3 = Math.sin((Math.abs(currentAngle) * Math.PI) / 180) * h1;
       const h3 = Math.sin((Math.abs(currentAngle) * Math.PI) / 180) * h1;
       const h4 = Math.cos((Math.abs(currentAngle) * Math.PI) / 180) * w1;
       const h4 = Math.cos((Math.abs(currentAngle) * Math.PI) / 180) * w1;
       let maxX = (imageWrapper.offsetWidth * currentZoom - h3 - h4) / 2;
       let maxX = (imageWrapper.offsetWidth * currentZoom - h3 - h4) / 2;
       maxX = maxX / currentZoom;
       maxX = maxX / currentZoom;
-      console.log('maxX', maxX);
+      //console.log('maxX', maxX);
 
 
       if (currentTranslate.x - pos1 > maxX) {
       if (currentTranslate.x - pos1 > maxX) {
         x = maxX;
         x = maxX;
@@ -584,8 +588,8 @@
         y = currentTranslate.y - pos2;
         y = currentTranslate.y - pos2;
       }
       }
 
 
-      console.log('y:', Math.round(y));
-      console.log('x:', Math.round(x));
+      // console.log('y:', Math.round(y));
+      // console.log('x:', Math.round(x));
 
 
       // Decide which direction to translate
       // Decide which direction to translate
       // if (currentTranslateDirection === 'y') {
       // if (currentTranslateDirection === 'y') {
@@ -608,14 +612,14 @@
         x: x,
         x: x,
         y: y,
         y: y,
       };
       };
-      console.log('currentTranslateBefore', currentTranslate);
+      // console.log('currentTranslateBefore', currentTranslate);
 
 
-      console.log('currentTranslate', currentTranslate);
+      // console.log('currentTranslate', currentTranslate);
       setImageWrapperTransform();
       setImageWrapperTransform();
     };
     };
 
 
     const dragMouseDown = (e: MouseEvent) => {
     const dragMouseDown = (e: MouseEvent) => {
-      console.log('dragMouseDown');
+      //console.log('dragMouseDown');
 
 
       e.preventDefault();
       e.preventDefault();
       // get the mouse cursor position at startup:
       // get the mouse cursor position at startup:
@@ -628,6 +632,25 @@
     assetDragHandle.onmousedown = dragMouseDown;
     assetDragHandle.onmousedown = dragMouseDown;
   };
   };
 
 
+  const removeAssetDrag = () => {
+    assetDragHandle.onmousedown = null;
+    document.onmouseup = null;
+    document.onmousemove = null;
+  };
+
+  const removeAngleSlider = () => {
+    angleSliderHandle.onmousedown = null;
+    document.onmouseup = null;
+    document.onmousemove = null;
+    document.ontouchmove = null;
+    document.ontouchend = null;
+    angleSliderHandle.ontouchstart = null;
+  };
+
+  const removeZoom = () => {
+    document.onwheel = null;
+  };
+
   const calcImageElement = (angle: number) => {
   const calcImageElement = (angle: number) => {
     // Get image wrapper width and height
     // Get image wrapper width and height
 
 
@@ -644,8 +667,8 @@
     const cropElementWidth = cropElement.offsetWidth;
     const cropElementWidth = cropElement.offsetWidth;
     const cropElementHeight = cropElement.offsetHeight;
     const cropElementHeight = cropElement.offsetHeight;
 
 
-    console.log('cropElementWidth', cropElementWidth);
-    console.log('cropElementHeight', cropElementHeight);
+    // console.log('cropElementWidth', cropElementWidth);
+    // console.log('cropElementHeight', cropElementHeight);
 
 
     const x1 = Math.cos((Math.abs(angle) * Math.PI) / 180) * cropElementWidth;
     const x1 = Math.cos((Math.abs(angle) * Math.PI) / 180) * cropElementWidth;
     const x2 = Math.cos(((90 - Math.abs(angle)) * Math.PI) / 180) * cropElementHeight;
     const x2 = Math.cos(((90 - Math.abs(angle)) * Math.PI) / 180) * cropElementHeight;
@@ -656,29 +679,29 @@
     if ((x1 + x2) / (y1 + y2) > originalAspect) {
     if ((x1 + x2) / (y1 + y2) > originalAspect) {
       newWidth = `${x1 + x2}px`;
       newWidth = `${x1 + x2}px`;
       newHeight = `${(x1 + x2) / originalAspect}px`;
       newHeight = `${(x1 + x2) / originalAspect}px`;
-      console.log('Translation in Y possible');
-      console.log('case4');
+      // console.log('Translation in Y possible');
+      // console.log('case4');
       currentTranslateDirection = 'y';
       currentTranslateDirection = 'y';
     } else if ((x1 + x2) / (y1 + y2) < originalAspect) {
     } else if ((x1 + x2) / (y1 + y2) < originalAspect) {
       newHeight = `${y1 + y2}px`;
       newHeight = `${y1 + y2}px`;
       newWidth = `${(y1 + y2) / (1 / originalAspect)}px`;
       newWidth = `${(y1 + y2) / (1 / originalAspect)}px`;
-      console.log('Translation in X possible');
+      // console.log('Translation in X possible');
       currentTranslateDirection = 'x';
       currentTranslateDirection = 'x';
-      console.log('case5');
+      // console.log('case5');
     } else {
     } else {
       newHeight = `${y1 + y2}px`;
       newHeight = `${y1 + y2}px`;
       newWidth = `${(y1 + y2) / (1 / originalAspect)}px`;
       newWidth = `${(y1 + y2) / (1 / originalAspect)}px`;
       currentTranslateDirection = '';
       currentTranslateDirection = '';
-      console.log('case6');
+      // console.log('case6');
     }
     }
 
 
     // Set image element width and height
     // Set image element width and height
-    console.log('newWidth', newWidth);
-    console.log('newHeight', newHeight);
-    console.log('currentAngleOffset', currentAngleOffset);
-    console.log('currentTranslateDirection', currentTranslateDirection);
-    console.log('currentTranslate', currentTranslate);
-    console.log('currentAngle', currentAngle);
+    // console.log('newWidth', newWidth);
+    // console.log('newHeight', newHeight);
+    // console.log('currentAngleOffset', currentAngleOffset);
+    // console.log('currentTranslateDirection', currentTranslateDirection);
+    // console.log('currentTranslate', currentTranslate);
+    // console.log('currentAngle', currentAngle);
 
 
     if (currentAngleOffset === 90 || currentAngleOffset === 270) {
     if (currentAngleOffset === 90 || currentAngleOffset === 270) {
       imageWrapper.style.height = newWidth;
       imageWrapper.style.height = newWidth;
@@ -695,7 +718,7 @@
   };
   };
 
 
   const navigateEdit = (edit: activeEdit) => {
   const navigateEdit = (edit: activeEdit) => {
-    console.log('navigateEdit');
+    // console.log('navigateEdit');
     let revert = false;
     let revert = false;
     if (activeEdit === edit) {
     if (activeEdit === edit) {
       revert = true;
       revert = true;
@@ -719,61 +742,73 @@
   };
   };
 
 
   const resetCropAndRotate = async () => {
   const resetCropAndRotate = async () => {
+    // Reset the image orientation.
     currentFlipX = false;
     currentFlipX = false;
     currentFlipY = false;
     currentFlipY = false;
     currentZoom = 1;
     currentZoom = 1;
     rotate(0, 0);
     rotate(0, 0);
+
+    // Reset the aspect ratio.
     await setAspectRatio('original');
     await setAspectRatio('original');
   };
   };
 
 
   const flipVertical = async () => {
   const flipVertical = async () => {
     currentFlipY = !currentFlipY;
     currentFlipY = !currentFlipY;
-    console.log('flipVertical');
     rotate(currentAngle, currentAngleOffset);
     rotate(currentAngle, currentAngleOffset);
   };
   };
   const flipHorizontal = async () => {
   const flipHorizontal = async () => {
     currentFlipX = !currentFlipX;
     currentFlipX = !currentFlipX;
-    console.log('flipHorizontal');
     rotate(currentAngle, currentAngleOffset);
     rotate(currentAngle, currentAngleOffset);
   };
   };
 
 
   const rotate = async (angle: number, angleOffset: number, isRotate?: boolean) => {
   const rotate = async (angle: number, angleOffset: number, isRotate?: boolean) => {
+    // If the angle offset is greater than 360 degrees, reset it back to 0
     if (angleOffset > 360) {
     if (angleOffset > 360) {
       angleOffset = angleOffset - 360;
       angleOffset = angleOffset - 360;
     }
     }
-    console.log('isRotate', isRotate);
 
 
+    // Set current angle and angle offset
     currentAngle = angle;
     currentAngle = angle;
     currentAngleOffset = angleOffset;
     currentAngleOffset = angleOffset;
 
 
+    // Set aspect ratio
     setAspectRatio(currentAspectRatio, isRotate ? true : false);
     setAspectRatio(currentAspectRatio, isRotate ? true : false);
 
 
+    // Set slider handle position
     let a = -1 * angle * (125 / 49);
     let a = -1 * angle * (125 / 49);
-    let b = a + 'px';
-    angleSliderHandle.style.left = b;
-    angleSlider.style.left = b;
+    angleSliderHandle.style.left = a + 'px';
+    angleSlider.style.left = a + 'px';
   };
   };
 
 
-  // Temporary function
   const save = async () => {
   const save = async () => {
-    // TBD
+    // Save element
     if (isRendering) {
     if (isRendering) {
       return;
       return;
     }
     }
     await renderElement.start();
     await renderElement.start();
   };
   };
 
 
+  // Set the transform of the image wrapper, which includes the rotation, translation, and scale.
   const setImageWrapperTransform = () => {
   const setImageWrapperTransform = () => {
     let transformString = '';
     let transformString = '';
 
 
+    // Add rotation to the transform string.
     transformString += `rotate(${currentAngle - currentAngleOffset}deg)`;
     transformString += `rotate(${currentAngle - currentAngleOffset}deg)`;
+
+    // If translation is non-zero, add it to the transform string.
     if (currentTranslate.x || currentTranslate.y) {
     if (currentTranslate.x || currentTranslate.y) {
       transformString += ` translate(${currentTranslate.x * currentZoom}px, ${currentTranslate.y * currentZoom}px)`;
       transformString += ` translate(${currentTranslate.x * currentZoom}px, ${currentTranslate.y * currentZoom}px)`;
     }
     }
+
+    // Add scale to the transform string.
     transformString += ` scaleX(${(currentFlipX ? -1 : 1) * currentZoom}) scaleY(${
     transformString += ` scaleX(${(currentFlipX ? -1 : 1) * currentZoom}) scaleY(${
       (currentFlipY ? -1 : 1) * currentZoom
       (currentFlipY ? -1 : 1) * currentZoom
     })`;
     })`;
-    imageWrapper.style.transform = transformString;
+    // Set the transform of the image wrapper.
+
+    if (imageWrapper && imageWrapper.style) {
+      imageWrapper.style.transform = transformString;
+    }
   };
   };
 </script>
 </script>
 
 
@@ -820,8 +855,16 @@
       <div class="-z-10 flex h-full w-full items-center justify-center">
       <div class="-z-10 flex h-full w-full items-center justify-center">
         <div bind:this={cropElementWrapper} class="relative flex h-full w-full items-center justify-center">
         <div bind:this={cropElementWrapper} class="relative flex h-full w-full items-center justify-center">
           <div>
           <div>
-            <div bind:this={imageWrapper} class="">
-              <img class="h-full w-full" bind:this={imageElement} src="" alt="" />
+            <div bind:this={imageWrapper}>
+              <div
+                class="{isLoaded
+                  ? 'hidden'
+                  : 'flex'} absolute z-[1001] left-0 top-0 w-full h-full bg-black justify-center items-center gap-1"
+              >
+                <span>Loading</span>
+                <LoadingSpinner />
+              </div>
+              <img class="h-full w-full {isLoaded ? '' : 'hidden'}" bind:this={imageElement} src="" alt="" />
             </div>
             </div>
             <div
             <div
               bind:this={cropElement}
               bind:this={cropElement}