Circle Drawer
HTML
<article>
<h1 style="text-align: center; margin-bottom: 5px;"
>Circle Drawer</h1>
<circle-drawer></circle-drawer>
</article>
<script src="./build/cami.cdn.js"></script>
<!-- CDN version below -->
<!-- <script src="https://unpkg.com/cami@latest/build/cami.cdn.js"></script> -->
<style>
.circle-drawer-container {
display: flex;
flex-direction: column;
align-items: center;
margin: 20px;
}
.button-group {
margin-bottom: 10px;
display: flex;
flex-direction: row;
gap: 10px;
}
.canvas-container {
width: 500px;
height: 400px;
border: 1px solid black;
position: relative;
}
</style>
<script type="module">
const { html, ReactiveElement } = cami;
class CircleDrawerElement extends ReactiveElement {
circles = [];
selectedCircle = null;
canvasClick$ = this.stream();
canvasMouseMove$ = this.stream();
history = [[]];
historyIndex = 0;
onConnect() {
this.canvasClick$
.map(e => {
const rect = canvas.getBoundingClientRect();
return { x: e.clientX - rect.left, y: e.clientY - rect.top };
})
.subscribe(({ x, y }) => {
console.log(x, y);
const selectedCircle = this.circles.find(circle => this.isInsideCircle(x, y, circle));
if (selectedCircle) {
alert("clicked inside circle");
} else {
this.circles.push({ x, y, radius: 25, color: 'white', borderColor: 'black' });
}
this.history = this.history.slice(0, this.historyIndex + 1);
this.history.push([...this.circles]);
this.historyIndex++;
});
this.canvasMouseMove$
.filter(() => this.selectedCircle !== null)
.map(e => {
const rect = e.target.getBoundingClientRect();
return { x: e.clientX - rect.left, y: e.clientY - rect.top };
})
.subscribe(({ x, y }) => {
const index = this.circles.indexOf(this.selectedCircle);
if (index !== -1) {
const updatedCircle = { ...this.selectedCircle, x, y };
this.circles[index] = updatedCircle;
this.selectedCircle = updatedCircle;
this.history = this.history.slice(0, this.historyIndex + 1);
this.history.push([...this.circles]);
this.historyIndex++;
}
});
}
isInsideCircle(x, y, circle) {
const dx = circle.x - x;
const dy = circle.y - y;
return dx * dx + dy * dy <= circle.radius * circle.radius;
}
undo() {
if (this.historyIndex > 0) {
this.historyIndex--;
this.circles = [...this.history[this.historyIndex]];
}
}
redo() {
if (this.historyIndex < this.history.length - 1) {
this.historyIndex++;
this.circles = [...this.history[this.historyIndex]];
}
}
template() {
return html`
<main class="circle-drawer-container">
<section class="button-group">
<button @click=${() => this.undo()}>Undo</button>
<button @click=${() => this.redo()}>Redo</button>
</section>
<section class="canvas-container" id="canvas" @click=${e => this.canvasClick$.next(e)} @mousemove=${e => this.canvasMouseMove$.next(e)}>
${this.circles.map(circle => html`
<div
role="img"
aria-label="circle"
style=${`position: absolute; left: ${circle.x - circle.radius}px; top: ${circle.y - circle.radius}px; width: ${circle.radius * 2}px; height: ${circle.radius * 2}px; border-radius: 50%; background-color: ${circle.color}; border: 1px solid ${circle.borderColor};`}
@mouseover=${() => {
const updatedCircle = { ...circle, color: 'gray' };
const index = this.circles.indexOf(circle);
this.circles.update(circles => {
circles[index] = updatedCircle;
});
}}
@mouseout=${() => {
const updatedCircle = { ...circle, color: 'white' };
const index = this.circles.indexOf(circle);
this.circles.update(circles => {
circles[index] = updatedCircle;
});
}}
></div>
`)}
</section>
</main>
`;
}
}
customElements.define('circle-drawer', CircleDrawerElement);
</script>