Untitled
unknown
typescript
3 years ago
20 kB
12
Indexable
// canvas.component.ts
import {
Component,
ElementRef,
AfterViewInit,
ViewChild,
EventEmitter,
Output,
Input,
OnChanges,
SimpleChanges,
} from '@angular/core';
import { Rectangle } from '../models/rectangle';
import { MatSnackBar } from '@angular/material/snack-bar';
@Component({
selector: 'app-canvas',
templateUrl: './canvas.component.html',
styleUrls: ['./canvas.component.css'],
})
export class CanvasComponent implements AfterViewInit {
//----------------- Canvas -----------------//
@ViewChild('canvas', { static: true })
canvasElementRef!: ElementRef<HTMLCanvasElement>;
@ViewChild('container', { static: true })
containerRef!: ElementRef<HTMLDivElement>;
//----- Input/Output ------//
@Output() rectangleDrawn = new EventEmitter<Rectangle>();
@Input() deletedRectangle = new EventEmitter<number>();
@Input() editRectangle = new EventEmitter<number | null>();
@Input() editInProgress= new EventEmitter<boolean>();
@Input() onMap = new EventEmitter<boolean>();
//----- Rectangle ------//
currentRectangle: Rectangle | null = null;
private ctx!: CanvasRenderingContext2D;
crossClickIndex: number | null = null;
editingInProgress: boolean = false;
editingIndex: number | null = null;
private canvas!: HTMLCanvasElement;
pdfSrc: string = 'assets/test.pdf';
lockedRectangles: Rectangle[] = []; // Array to store locked rectangles
showMarkedRegion: boolean = true;
// rectangles: Rectangle[] = [];
currentPage: number = 1;
rectangleColor = 'red';
index: number = 0;
isDrawing = false;
numPages = 0;
//----- Rectangle Selector ------//
private selectedRectangleIndex: number | null = null;
private dragging = false;
private resizing = false;
private resizingEdge: number | null = null;
private resizingStartX = 0;
private resizingStartY = 0;
private resizingStartWidth = 0;
private resizingStartHeight = 0;
private crossSize: number = 12;
constructor(private snackBar: MatSnackBar) {}
//----- Angular Lifecycle Hooks ------//
ngOnInit(): void {
this.ngOnDeleteRectangle();
this.ngOnEditRectangle();
this.ngOnEditComplete();
}
ngAfterViewInit(): void {
this.canvas = this.canvasElementRef.nativeElement;
this.ctx = this.canvas.getContext('2d')!;
this.drawRectangles();
this.ctx.lineWidth = 10;
this.adjustCanvasSize();
this.canvas.addEventListener('mousedown', (event) =>
this.onMouseDown(event)
);
this.canvas.addEventListener('mousemove', (event) =>
this.onMouseMove(event)
);
this.canvas.addEventListener('mouseup', () => this.onMouseUp());
this.canvas.addEventListener('mouseleave', () =>
this.onMouseLeave(this.ctx)
);
}
ngOnDeleteRectangle(): void {
this.deletedRectangle.subscribe((index) => {
this.lockedRectangles.splice(index, 1);
this.drawRectangles();
});
}
ngOnEditComplete(): void {
this.editInProgress.subscribe((inProgress) => {
this.saveCurrentRectangle();
this.editingInProgress = inProgress;
if (!inProgress) {
this.editingIndex = null;
this.currentRectangle = null;
this.drawRectangles();
}
});
}
ngOnMap(): void {
this.onMap.subscribe((onMap) => {
this.showMarkedRegion = !onMap;
this.drawRectangles();
});
}
ngOnEditRectangle(): void {
this.editRectangle.subscribe((index) => {
this.editingIndex = index;
if (index !== null) {
this.currentRectangle = this.lockedRectangles[index];
this.unlockRectangle(index);
}
this.drawRectangles();
});
}
//-----Canvas Events------//
changePage(pageChange: number): void {
const newPage = this.currentPage + pageChange;
this.currentPage = newPage;
}
afterLoadComplete(pdfData: any): void {
this.numPages = pdfData.numPages;
}
private clearCanvas(ctx: CanvasRenderingContext2D): void {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
this.drawAllRectangles();
}
private adjustCanvasSize(): void {
const container = this.containerRef.nativeElement;
const canvas = this.canvasElementRef.nativeElement;
// Set the canvas size to match its container size
canvas.width = container.clientWidth;
canvas.height = container.clientHeight;
}
onFileSelected() {
let $img: any = document.querySelector('#file');
if (typeof FileReader !== 'undefined') {
let reader = new FileReader();
reader.onload = (e: any) => {
this.pdfSrc = e.target.result;
};
reader.readAsArrayBuffer($img.files[0]);
}
}
//-------------------Mouse Events--------------------//
onMouseLeave(ctx: CanvasRenderingContext2D): void {
if (this.isDrawing) {
this.clearCanvas(ctx);
this.isDrawing = false;
} else if (this.dragging) {
this.clearCanvas(ctx);
this.dragging = false;
this.selectedRectangleIndex = null;
}
}
onMouseDown(event: MouseEvent): void {
const { offsetX, offsetY } = event;
if (this.editingIndex === null) {
this.selectedRectangleIndex = this.findSelectedRectangle(
offsetX,
offsetY
);
if (this.selectedRectangleIndex !== null) {
this.resizingEdge = this.getSegment(
offsetX,
offsetY,
this.rectangles[this.selectedRectangleIndex]
);
if (this.resizingEdge === 5) {
this.dragging = true;
this.resizing = false;
this.resizingStartX = this.rectangles[this.selectedRectangleIndex].x;
this.resizingStartY = this.rectangles[this.selectedRectangleIndex].y;
this.resizingStartWidth =
this.rectangles[this.selectedRectangleIndex].width;
this.resizingStartHeight =
this.rectangles[this.selectedRectangleIndex].height;
} else {
this.dragging = false;
this.resizing = true;
this.resizingStartX = this.rectangles[this.selectedRectangleIndex].x;
this.resizingStartY = this.rectangles[this.selectedRectangleIndex].y;
this.resizingStartWidth =
this.rectangles[this.selectedRectangleIndex].width;
this.resizingStartHeight =
this.rectangles[this.selectedRectangleIndex].height;
}
} else {
// this.crossClickIndex = this.onCrossClick(event);
// if (this.crossClickIndex !== null) {
// this.rectangles.splice(this.crossClickIndex, 1); // Delete the rectangle
// this.drawRectangles(); // Redraw the canvas
// } else {
// if (this.editingInProgress) {
// this.showSnackbar(
// 'Please save or cancel the current rectangle before drawing a new one.'
// );
// return;
// }
this.dragging = false;
this.resizing = false;
this.currentRectangle = {
x: offsetX,
y: offsetY,
width: 0,
height: 0,
};
this.isDrawing = true;
// }
}
} else {
//Edit or resize the edge
this.resizingEdge = this.getSegment(
offsetX,
offsetY,
this.rectangles[this.editingIndex!]
);
if (this.resizingEdge === 5) {
this.dragging = true;
this.resizing = false;
this.resizingStartX = this.rectangles[this.editingIndex!].x;
this.resizingStartY = this.rectangles[this.editingIndex!].y;
this.resizingStartWidth = this.rectangles[this.editingIndex!].width;
this.resizingStartHeight = this.rectangles[this.editingIndex!].height;
} else {
this.dragging = false;
this.resizing = true;
this.resizingStartX = this.rectangles[this.editingIndex!].x;
this.resizingStartY = this.rectangles[this.editingIndex!].y;
this.resizingStartWidth = this.rectangles[this.editingIndex!].width;
this.resizingStartHeight = this.rectangles[this.editingIndex!].height;
}
}
}
onMouseMove(event: MouseEvent): void {
const { offsetX, offsetY } = event;
if (this.editingIndex === null) {
if (this.isDrawing) {
this.canvas.style.cursor = 'crosshair';
this.currentRectangle!.width = offsetX - this.currentRectangle!.x;
this.currentRectangle!.height = offsetY - this.currentRectangle!.y;
this.drawRectangles();
} else if (this.dragging) {
this.dragRectangle(
event,
this.rectangles[this.selectedRectangleIndex!]
);
} else if (this.resizing) {
this.resizeRectangle(offsetX, offsetY);
} else {
this.selectedRectangleIndex = this.findSelectedRectangle(
offsetX,
offsetY
);
if (this.selectedRectangleIndex !== null) {
this.resizingEdge = this.getSegment(
offsetX,
offsetY,
this.rectangles[this.selectedRectangleIndex]
);
this.updateMouseCursor(this.resizingEdge);
}
}
} else {
if (this.dragging) {
this.dragRectangle(event, this.rectangles[this.editingIndex!]);
} else if (this.resizing) {
this.resizeRectangle(offsetX, offsetY);
} else {
this.selectedRectangleIndex = this.editingIndex;
if (this.selectedRectangleIndex !== null) {
this.resizingEdge = this.getSegment(
offsetX,
offsetY,
this.rectangles[this.editingIndex!]
);
this.updateMouseCursor(this.resizingEdge);
}
}
}
}
onMouseUp(): void {
this.isDrawing = false;
this.dragging = false;
this.resizing = false;
this.resizingEdge = null;
if (!this.currentRectangle) {
return;
}
this.editingInProgress = true;
if (this.editingIndex !== null) {
this.rectangles[this.editingIndex] = { ...this.currentRectangle };
} else {
// this.rectangles.push({ ...this.currentRectangle });
this.rectangleDrawn.emit({ ...this.currentRectangle }); // Emit the rectangle coordinates
}
// this.currentRectangle = null;
this.editingIndex = null;
// this.drawRectangles();
}
//---------------Rectangle Events--------------------//
private isPointInsideRectangle(
x: number,
y: number,
rectangle: Rectangle
): boolean {
return (
x >= rectangle.x &&
x <= rectangle.x + rectangle.width &&
y >= rectangle.y &&
y <= rectangle.y + rectangle.height
);
}
private findSelectedRectangle(x: number, y: number): number | null {
for (let i = this.rectangles.length - 1; i >= 0; i--) {
if (this.isPointInsideRectangle(x, y, this.rectangles[i])) {
return i;
}
}
return null;
}
lockRectangle(): void {
//update with current rectangle
this.lockedRectangles[this.selectedRectangleIndex!] = this.currentRectangle!;
this.currentRectangle = null;
this.drawRectangles();
}
unlockRectangle(index: number): void {
this.rectangles.push(this.lockedRectangles[index]);
this.selectedRectangleIndex = this.rectangles.length - 1;
this.editingIndex = this.selectedRectangleIndex;
this.lockedRectangles.splice(index, 1);
}
private isRectangleLocked(rectangle: Rectangle): boolean {
return this.lockedRectangles.some(
(r) => r.x === rectangle.x && r.y === rectangle.y
);
}
private resizeRectangle(x: number, y: number): void {
if (this.selectedRectangleIndex !== null && this.resizingEdge !== null) {
const selectedRectangle = this.rectangles[this.selectedRectangleIndex];
const deltaX = x - this.resizingStartX;
const deltaY = y - this.resizingStartY;
switch (this.resizingEdge) {
case 1:
selectedRectangle.x = this.resizingStartX + deltaX;
selectedRectangle.y = this.resizingStartY + deltaY;
selectedRectangle.width = this.resizingStartWidth - deltaX;
selectedRectangle.height = this.resizingStartHeight - deltaY;
break;
case 2:
selectedRectangle.y = this.resizingStartY + deltaY;
selectedRectangle.height = this.resizingStartHeight - deltaY;
break;
case 3:
selectedRectangle.y = this.resizingStartY + deltaY;
selectedRectangle.width = this.resizingStartWidth + deltaX;
selectedRectangle.height = this.resizingStartHeight - deltaY;
break;
case 4:
selectedRectangle.x = this.resizingStartX + deltaX;
selectedRectangle.width = this.resizingStartWidth - deltaX;
break;
case 5:
selectedRectangle.x = this.resizingStartX + deltaX;
selectedRectangle.y = this.resizingStartY + deltaY;
break;
case 6:
selectedRectangle.width = this.resizingStartWidth + deltaX;
break;
case 7:
selectedRectangle.x = this.resizingStartX + deltaX;
selectedRectangle.width = this.resizingStartWidth - deltaX;
selectedRectangle.height = this.resizingStartHeight + deltaY;
break;
case 8:
selectedRectangle.height = this.resizingStartHeight + deltaY;
break;
case 9:
selectedRectangle.width = this.resizingStartWidth + deltaX;
selectedRectangle.height = this.resizingStartHeight + deltaY;
break;
default:
break;
}
// Emit the updated coordinates, width, and height during resizing
this.rectangleDrawn.emit({ ...selectedRectangle });
this.drawRectangles();
}
}
dragRectangle(event: MouseEvent, rectangle: Rectangle): void {
const { offsetX, offsetY } = event;
const deltaX = offsetX - this.resizingStartX;
const deltaY = offsetY - this.resizingStartY;
rectangle.x = this.resizingStartX + deltaX;
rectangle.y = this.resizingStartY + deltaY;
// Emit the updated coordinates, width, and height during dragging
this.rectangleDrawn.emit({ ...rectangle });
this.drawRectangles();
}
private drawRectangles(): void {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.drawAllRectangles();
//todo: check if adding && this.isDrawing will break anything
if (this.currentRectangle) {
this.ctx.beginPath();
this.ctx.rect(
this.currentRectangle.x,
this.currentRectangle.y,
this.currentRectangle.width,
this.currentRectangle.height
);
this.ctx.stroke();
}
}
drawAllRectangles(): void {
this.canvas.style.strokeWidth = '4.5';
this.lockedRectangles.forEach((rectangle, index) => {
this.ctx.strokeStyle = 'blue';
this.ctx.beginPath();
this.ctx.rect(
rectangle.x,
rectangle.y,
rectangle.width,
rectangle.height
);
this.ctx.stroke();
// this.drawCrossMark(rectangle);
});
this.canvas.style.strokeWidth = '4.5';
this.rectangles.forEach((rectangle, index) => {
this.ctx.strokeStyle = 'red';
this.ctx.beginPath();
this.ctx.rect(
rectangle.x,
rectangle.y,
rectangle.width,
rectangle.height
);
this.ctx.stroke();
// this.drawCrossMark(rectangle);
});
}
getSegment(x: number, y: number, rectangle: Rectangle): number | null {
const segmentWidth = rectangle.width / 3;
const segmentHeight = rectangle.height / 3;
// Calculate which segment the cursor is in
const column = Math.floor((x - rectangle.x) / segmentWidth);
const row = Math.floor((y - rectangle.y) / segmentHeight);
// Check if the cursor is within the rectangle
if (column < 0 || column > 2 || row < 0 || row > 2) {
return null; // Cursor is outside the rectangle
}
// Calculate the corresponding segment number
const segment = row * 3 + column + 1;
return segment;
}
updateMouseCursor(segment: number | null): void {
switch (segment) {
case 1:
this.canvas.style.cursor = 'nwse-resize';
break;
case 2:
this.canvas.style.cursor = 'ns-resize';
break;
case 3:
this.canvas.style.cursor = 'nesw-resize';
break;
case 4:
this.canvas.style.cursor = 'ew-resize';
break;
case 5:
this.canvas.style.cursor = 'move';
break;
case 6:
this.canvas.style.cursor = 'ew-resize';
break;
case 7:
this.canvas.style.cursor = 'nesw-resize';
break;
case 8:
this.canvas.style.cursor = 'ns-resize';
break;
case 9:
this.canvas.style.cursor = 'nwse-resize';
break;
default:
this.canvas.style.cursor = 'crosshair';
break;
}
}
saveCurrentRectangle(): void {
if (this.editingIndex !== null) {
// Editing an existing rectangle
console.log('editing existing rectangle');
console.log(this.editingIndex);
console.log(this.currentRectangle);
if (this.currentRectangle) {
this.lockRectangle(this.editingIndex);
}
} else {
// Adding a new rectangle
if (this.currentRectangle) {
this.lockedRectangles.push({ ...this.currentRectangle });
this.rectangleDrawn.emit({ ...this.currentRectangle });
this.currentRectangle = null;
}
}
// Reset the editing flag
this.editingInProgress = false;
this.drawRectangles();
}
//------------Utility functions----------------//
showSnackbar(message: string): void {
const snackBarRef = this.snackBar.open(message, 'OK');
snackBarRef.onAction().subscribe(() => {
console.log('Saved changes');
});
//display snackbar for 3 seconds
setTimeout(() => {
snackBarRef.dismiss();
}, 3000);
}
toggleShowMarkedRegions(): void {
this.showMarkedRegion = !this.showMarkedRegion;
//Reset editing flag when toggling marked regions
this.editingInProgress = false;
this.drawRectangles();
}
private drawCrossMark(rectangle: Rectangle): void {
// Draw the cross mark on the top right edge
const crossX = rectangle.x + rectangle.width - this.crossSize / 2;
const crossY = rectangle.y - this.crossSize / 2;
this.ctx.strokeStyle = 'black';
this.ctx.beginPath();
this.ctx.moveTo(crossX, crossY);
this.ctx.lineTo(crossX + this.crossSize, crossY + this.crossSize);
this.ctx.moveTo(crossX, crossY + this.crossSize);
this.ctx.lineTo(crossX + this.crossSize, crossY);
this.ctx.stroke();
}
private onCrossClick(event: MouseEvent): number | null {
const { offsetX, offsetY } = event;
// Check if the click is on any cross mark
for (let i = 0; i < this.rectangles.length; i++) {
const rectangle = this.rectangles[i];
const crossX = rectangle.x + rectangle.width;
const crossY = rectangle.y;
if (
offsetX >= crossX - this.crossSize &&
offsetX <= crossX + this.crossSize &&
offsetY >= crossY - this.crossSize &&
offsetY <= crossY + this.crossSize
) {
this.editingInProgress = false;
return i; // Return the index of the deleted rectangle
}
}
return null;
}
}
Editor is loading...