Skip to content

Commit

Permalink
Reduce costly operations, improve app flow and respond to all inputs …
Browse files Browse the repository at this point in the history
…on runtime (#602)
  • Loading branch information
loiddy authored May 24, 2024
1 parent 8f5da5e commit 9e968b4
Show file tree
Hide file tree
Showing 10 changed files with 374 additions and 212 deletions.
86 changes: 56 additions & 30 deletions projects/demo-app/src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,59 +1,85 @@
<input type="file" (change)="fileChangeEvent($event)" accept="image/*" />
<br />
<br />
<input [(ngModel)]="imageURL" placeholder="URL to load image" type="text" />
<input type="file" (change)="fileChangeEvent($event)" accept="image/*" />
<br />
<br />
<button (click)="toggleAspectRatio()">Aspect ratio: {{aspectRatio === 4/3 ? '4/3' : '16/5'}}</button>
<button (click)="containWithinAspectRatio = !containWithinAspectRatio;">{{containWithinAspectRatio ? 'Fill Aspect Ratio' : 'Contain Within Aspect Ratio'}}</button>
<button (click)="rotateLeft()">Rotate left</button>
<button (click)="rotateRight()">Rotate right</button>
<button (click)="flipHorizontal()">Flip horizontal</button>
<button (click)="flipVertical()">Flip vertical</button>
<br />
<br />
<button (click)="toggleContainWithinAspectRatio()">{{containWithinAspectRatio?'Fill Aspect Ratio':'Contain Within Aspect Ratio'}}</button>
<button (click)="toggleAspectRatio()">Aspect ratio: {{aspectRatio === 4/3 ? '4/3' : '16/5'}}</button>
<button (click)="resetImage()">Reset image</button>
<section>
<button (click)="maintainAspectRatio = !maintainAspectRatio;">{{maintainAspectRatio ? "Don't Maintain Aspect Ratio" : 'Maintain Aspect Ratio'}}</button>
<label for="cropperStaticWidth">Cropper Static Width:</label>
<input id="cropperStaticWidth" type="number" (keyup)="debounce($event)" />
<label for="cropperMinWidth">Cropper Min Width:</label>
<input id="cropperMinWidth" type="number" (keyup)="debounce($event)"/>
<label for="cropperMaxWidth">Cropper Max Width:</label>
<input id="cropperMaxWidth" type="number" (keyup)="debounce($event)"/>
<span></span>
<label for="cropperStaticHeight">Cropper Static Height:</label>
<input id="cropperStaticHeight" type="number" (keyup)="debounce($event)" />
<label for="cropperMinHeight">Cropper Min Height:</label>
<input id="cropperMinHeight" type="number" (keyup)="debounce($event)"/>
<label for="cropperMaxHeight">Cropper Max Height:</label>
<input id="cropperMaxHeight" type="number" (keyup)="debounce($event)"/>
</section>
<br />
<br />
<input [(ngModel)]="rotation" placeholder="Rotation" type="number" (ngModelChange)="updateRotation()" /> <button (click)="zoomOut()">Zoom -</button> <button (click)="zoomIn()">Zoom +</button>
<br />
<br/>
<input [(ngModel)]="transform.rotate" placeholder="Rotation" type="number" (ngModelChange)="updateRotation()" />
<button (click)="zoomOut()">Zoom -</button>
<button (click)="zoomIn()">Zoom +</button>
<button (click)="moveLeft()">move left</button>
<button (click)="moveRight()">move right</button>

<button (click)="moveTop()">move top</button>
<button (click)="moveBottom()">move bottom</button>
<button (click)="moveUp()">move up</button>
<button (click)="moveDown()">move down</button>
<button (click)="allowMoveImage = !allowMoveImage;">{{allowMoveImage ? 'Disable' : 'Enable'}} image panning</button>
<br/>
<br/>
<button (click)="allowMoveImage = !allowMoveImage;">{{allowMoveImage ? 'Disable' : 'Enable' }} image panning</button>
<button (click)="hidden = !hidden;">{{hidden ? 'Show' : 'Hide' }}</button>
<button (click)="hidden = !hidden;">{{hidden ? 'Show' : 'Hide'}}</button>
<button (click)="disabled = !disabled;">{{disabled ? 'Enable' : 'Disable'}} Cropper</button>
<button (click)="hideResizeSquares = !hideResizeSquares;">{{hideResizeSquares ? 'Show' : 'Hide'}} Resize Squares</button>
<button (click)="resetCropOnAspectRatioChange = !resetCropOnAspectRatioChange;">{{resetCropOnAspectRatioChange ? "Don't" : ''}} Reset Crop On Aspect Ratio Change</button>
<button (click)="toggleBackgroundColor()">Background Color: {{backgroundColor === 'red' ? 'blue' : 'red'}}</button>
<button (click)="test()">Random test</button>
<button (click)="resetImage()">Reset image</button>
<br/>
<br/>


<div class="cropper-wrapper">
<image-cropper
<div [style.display]="showCropper ? null : 'none'" class="cropper-wrapper">
<image-cropper
[imageChangedEvent]="imageChangedEvent"
[imageURL]="imageURL"
[maintainAspectRatio]="true"
[containWithinAspectRatio]="containWithinAspectRatio"
[cropperMinWidth]="128"
[aspectRatio]="aspectRatio"
[onlyScaleDown]="true"
[roundCropper]="false"
[canvasRotation]="canvasRotation"
[(transform)]="transform"
[alignImage]="'center'"
[style.display]="showCropper ? null : 'none'"
[allowMoveImage]="allowMoveImage"
[hidden]="hidden"
[disabled]="disabled"
[alignImage]="alignImage"
[roundCropper]="roundCropper"
[backgroundColor]="backgroundColor"
imageAltText="Alternative image text"
backgroundColor="red"
[allowMoveImage]="allowMoveImage"
[hideResizeSquares]="hideResizeSquares"
[canvasRotation]="canvasRotation"
[aspectRatio]="aspectRatio"
[containWithinAspectRatio]="containWithinAspectRatio"
[maintainAspectRatio]="maintainAspectRatio"
[cropperStaticWidth]="cropperStaticWidth"
[cropperStaticHeight]="cropperStaticHeight"
[cropperMinWidth]="cropperMinWidth"
[cropperMinHeight]="cropperMinHeight"
[cropperMaxWidth]="cropperMaxWidth"
[cropperMaxHeight]="cropperMaxHeight"
[resetCropOnAspectRatioChange]="resetCropOnAspectRatioChange"
[cropper]="cropper"
[(transform)]="transform"
[onlyScaleDown]="true"
output="blob"
format="png"
(imageCropped)="imageCropped($event)"
(imageLoaded)="imageLoaded()"
(cropperReady)="cropperReady($event)"
(loadImageFailed)="loadImageFailed()"
(transformChange)="transformChange($event)"
></image-cropper>
<div *ngIf="loading" class="loader">Loading...</div>
</div>
Expand Down
36 changes: 36 additions & 0 deletions projects/demo-app/src/app/app.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,39 @@ image-cropper {
font-size: 20px;
color: white;
}

input[type=number]{
width: 65px;
}

section {
border: 0;
display: grid;
justify-content: start;
align-items: center;
grid-template-columns: auto repeat(3, auto auto);
grid-template-rows: auto;
grid-gap: 0 5px;

label {
font-family: Arial, Helvetica, sans-serif;
font-size: 14px;
}

input {
height: -webkit-fill-available;
margin-right: 5px;
-moz-appearance: textfield;

&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
}

button {
height: fit-content;
margin-right: 7px;
}
}
122 changes: 87 additions & 35 deletions projects/demo-app/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component } from '@angular/core';
import { Dimensions, ImageCroppedEvent, ImageTransform, ImageCropperComponent } from 'ngx-image-cropper';
import { CropperPosition, Dimensions, ImageCroppedEvent, ImageTransform, ImageCropperComponent } from 'ngx-image-cropper';
import {DomSanitizer, SafeUrl} from '@angular/platform-browser';
import { NgIf } from '@angular/common';
import { FormsModule } from "@angular/forms";
Expand All @@ -12,23 +12,43 @@ import { FormsModule } from "@angular/forms";
imports: [NgIf, FormsModule, ImageCropperComponent]
})
export class AppComponent {
imageChangedEvent: Event | null = null;
showCropper = false;
loading = false;
croppedImage: SafeUrl = '';

imageChangedEvent: Event | null = null;
imageURL?: string;
hidden = false;
disabled = false;
alignImage = 'center' as const;
roundCropper = false;
backgroundColor = 'red';
allowMoveImage = false;
hideResizeSquares = false;
canvasRotation = 0;
rotation?: number;
translateH = 0;
translateV = 0;
scale = 1;
aspectRatio = 4 / 3;
showCropper = false;
containWithinAspectRatio = false;
maintainAspectRatio = false;
cropperStaticWidth = 0;
cropperStaticHeight = 0;
cropperMinWidth = 0;
cropperMinHeight = 0;
cropperMaxWidth = 0;
cropperMaxHeight = 0;
resetCropOnAspectRatioChange = true;
cropper: CropperPosition = { x1: 0, y1: 0, x2:0, y2:0 };
transform: ImageTransform = {
translateUnit: 'px'
translateUnit: 'px',
scale: 1,
rotate: 0,
flipH: false,
flipV: false,
translateH: 0,
translateV: 0
};
imageURL?: string;
loading = false;
allowMoveImage = false;
hidden = false;

timeout: any;
eventList = {};

constructor(
private sanitizer: DomSanitizer
Expand All @@ -42,7 +62,7 @@ export class AppComponent {

imageCropped(event: ImageCroppedEvent) {
this.croppedImage = this.sanitizer.bypassSecurityTrustUrl(event.objectUrl || event.base64 || '');
console.log(event);
console.log('CROPPED', event);
}

imageLoaded() {
Expand All @@ -59,6 +79,10 @@ export class AppComponent {
console.error('Load image failed');
}

transformChange(transform: ImageTransform){
console.log('transform changed', transform)
}

rotateLeft() {
this.loading = true;
setTimeout(() => { // Use timeout because rotating image is a heavy operation and will block the ui thread
Expand All @@ -78,28 +102,28 @@ export class AppComponent {
moveLeft() {
this.transform = {
...this.transform,
translateH: ++this.translateH
translateH: --this.transform.translateH!
};
}

moveRight() {
this.transform = {
...this.transform,
translateH: --this.translateH
translateH: ++this.transform.translateH!
};
}

moveTop() {
moveDown() {
this.transform = {
...this.transform,
translateV: ++this.translateV
translateV: ++this.transform.translateV!
};
}

moveBottom() {
moveUp() {
this.transform = {
...this.transform,
translateV: --this.translateV
translateV: --this.transform.translateV!
};
}

Expand All @@ -109,10 +133,10 @@ export class AppComponent {
this.transform = {
...this.transform,
flipH: flippedV,
flipV: flippedH
flipV: flippedH,
translateH: 0,
translateV: 0
};
this.translateH = 0;
this.translateV = 0;
}

flipHorizontal() {
Expand All @@ -130,42 +154,70 @@ export class AppComponent {
}

resetImage() {
this.scale = 1;
this.rotation = 0;
this.canvasRotation = 0;
this.cropper = {x1: 0, y1: 0, x2: 0, y2: 0};
this.maintainAspectRatio = false;
this.transform = {
translateUnit: 'px'
translateUnit: 'px',
scale: 1,
rotate: 0,
flipH: false,
flipV: false,
translateH: 0,
translateV: 0
};
}

zoomOut() {
this.scale -= .1;
this.transform = {
...this.transform,
scale: this.scale
scale: this.transform.scale! - .1
};
}

zoomIn() {
this.scale += .1;
this.transform = {
...this.transform,
scale: this.scale
scale: this.transform.scale! + .1
};
}

toggleContainWithinAspectRatio() {
this.containWithinAspectRatio = !this.containWithinAspectRatio;
}

updateRotation() {
this.transform = {
...this.transform,
rotate: this.rotation
};
}

toggleAspectRatio() {
this.aspectRatio = this.aspectRatio === 4 / 3 ? 16 / 5 : 4 / 3;
}
}

toggleBackgroundColor() {
this.backgroundColor = this.backgroundColor === 'red' ? 'blue' : 'red';
}

// prevent over triggering app when typing
debounce(event: any) {
clearTimeout(this.timeout);
(this.eventList as any)[event.target!.id] = event.target.value;
this.timeout = setTimeout(() => {
for (const [key, value] of Object.entries(this.eventList)) {
(this as any)[key] = Number(value);
}
this.eventList = {};
}, 500);
}

/*
Random Test button triggers this method
use it to test whatever you want
*/
test() {
this.canvasRotation = 3;
this.transform = {
...this.transform,
scale: 2
}
this.cropper = { x1: 190, y1: 221.5, x2: 583, y2: 344.3125 } // has 16/5 aspect ratio
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
class="ngx-ic-move"
role="presentation">
</div>
<ng-container *ngIf="!hideResizeSquares">
<ng-container *ngIf="!hideResizeSquares && !(cropperStaticWidth && cropperStaticHeight)">
<span class="ngx-ic-resize ngx-ic-topleft"
role="presentation"
(mousedown)="startMove($event, moveTypes.Resize, 'topleft')"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@
}
}

&.ngx-ix-hidden {
&.ngx-ic-hidden {
display: none;
}
}
Loading

0 comments on commit 9e968b4

Please sign in to comment.