Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: custom drawing render #271

Merged
merged 2 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions libs/shared/src/features/drawing/drawing.component.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
<div class="svg-container">
<svg (mousedown)="handlePointerDown($event)" (mousemove)="handlePointerMove($event)" class="svg" *ngIf="points">
<path [attr.d]="pathData" />
</svg>
</div>
<button mat-button (click)="dialog.open(editorDialog)">Draw</button>

<ng-template #editorDialog>
<div class="svg-dialog-container">
<svg
#svgElement
width="100%"
height="100%"
(pointerdown)="handlePointerDown($event)"
(pointermove)="handlePointerMove($event)"
style="touch-action: none"
>
@if(points){
<path [attr.d]="pathData()" id="path-1" />
}
</svg>
</div>
</ng-template>
14 changes: 7 additions & 7 deletions libs/shared/src/features/drawing/drawing.component.scss
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
.svg-container{
border: 1px solid #eeeeee;
border-radius: 4px;
.svg-dialog-container {
border: 1px solid #eeeeee;
border-radius: 4px;
box-sizing: border-box;
// mat-dialog has max-width 80vw and includes 24px padding so just fit max
width: calc(80vw - 48px);
height: 80vh;
}
svg.svg{
width: 100%;
touch-action: none;
}
83 changes: 56 additions & 27 deletions libs/shared/src/features/drawing/drawing.component.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { Component, ElementRef, signal, ViewChild } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
import { getStroke } from 'perfect-freehand';

@Component({
selector: 'picsa-custom-drawing',
standalone: true,
imports: [CommonModule],
imports: [CommonModule, MatDialogModule, MatButtonModule],
templateUrl: './drawing.component.html',
styleUrls: ['./drawing.component.scss'],
})
Expand All @@ -27,19 +29,62 @@ export class PicsaDrawingComponent {
cap: true,
},
};
public points: any[] = [];
public stroke;
public pathData;
/** [x,y,pressure] point representation */
public points: [number, number, number][] = [];

constructor() {
this.stroke = getStroke(this.points, this.options);
this.pathData = this.getSvgPathFromStroke(this.stroke);
/** SVG representation of active path segment */
public pathData = signal<string>('');

/**
* When the svg is rendered in a parent element track position
* to use to offset
*/
private containerOffset = [0, 0];

@ViewChild('svgElement') svgElement: ElementRef<SVGElement>;

constructor(public dialog: MatDialog) {}

handlePointerDown(event: PointerEvent) {
const target = event.target as SVGElement;
target.setPointerCapture(event.pointerId);
this.calculateContainerOffset();
this.addPointToPath(event.pageX, event.pageY);
this.renderPath();
}

public getSvgPathFromStroke(stroke) {
console.log('This is the stroke received:', stroke);
if (!stroke.length) return '';
handlePointerMove(event: PointerEvent) {
if (event.buttons !== 1) return;
this.addPointToPath(event.pageX, event.pageY);
this.renderPath();
}

/** Add a point to the current path, adjusting absolute position for relative container */
private addPointToPath(x: number, y: number, pressure = 0.5) {
const [left, top] = this.containerOffset;
this.points.push([x - left, y - top, pressure]);
}

/** Determine current positioning of svg drawing container to use for path offsets */
private calculateContainerOffset() {
const svgEl = this.svgElement.nativeElement;
const { left, top } = svgEl.getBoundingClientRect();
this.containerOffset = [left, top];
}

/** Render an svg path element generated from current list of points */
private renderPath() {
const stroke = getStroke(this.points);
const svgPath = this.getSvgPathFromStroke(stroke);
this.pathData.set(svgPath);
}

/**
* Generate an svg path from array of [x,y] point arrays
* Copied from https://github.com/steveruizok/perfect-freehand
* */
private getSvgPathFromStroke(stroke: number[][]) {
if (!stroke.length) return '';
const d = stroke.reduce(
(acc, [x0, y0], i, arr) => {
const [x1, y1] = arr[(i + 1) % arr.length];
Expand All @@ -48,23 +93,7 @@ export class PicsaDrawingComponent {
},
['M', ...stroke[0], 'Q']
);

d.push('Z');
console.log('Path:', d);
return d.join(' ');
}

handlePointerDown(event) {
event.target.setPointerCapture(event.pointerId);
this.points = [[event.pageX, event.pageY, 0.5]];
}

handlePointerMove(event) {
if (event.buttons !== 1) return;
this.points = [...this.points, [event.pageX, event.pageY, 0.5]];
console.log('Points:', this.points);
this.stroke = getStroke(this.points, this.options);
console.log('Stroke:', this.stroke);
this.pathData = this.getSvgPathFromStroke(this.stroke);
}
}
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -16900,7 +16900,7 @@ __metadata:
"leaflet-draw@github:enketo/Leaflet.draw#ff730785db7fcccbf2485ffcf4dffe1238a7c617":
version: 1.0.4
resolution: "leaflet-draw@https://github.com/enketo/Leaflet.draw.git#commit=ff730785db7fcccbf2485ffcf4dffe1238a7c617"
checksum: b2280f200f5ea410e33cc5feb500d67d86cb5f16b294eea1306da055614ffc906be7de6dbc9bbf4db6e6e5d27d0396e4701a3b6c4c920548d2aac94b8223879f
checksum: b08b88994769667f11f2b6a8937656c89cea34dafd4661abab0b48b4b97f3bddbdce7b23ddfdb8d7c6335e065530e32a70e281314afa34afa134bf68597945fc
languageName: node
linkType: hard

Expand Down