folk-canvas/src/folk-toolset.ts

158 lines
3.9 KiB
TypeScript

import { FolkShape } from './folk-shape';
export abstract class FolkInteractionHandler extends HTMLElement {
abstract readonly events: string[];
abstract handleEvent(event: Event): void;
static toolbar: FolkToolset | null = null;
protected button: HTMLButtonElement;
constructor() {
super();
this.attachShadow({ mode: 'open' });
const style = document.createElement('style');
style.textContent = `
button {
padding: 8px 16px;
border: 2px solid transparent;
cursor: pointer;
}
:host(.active) button {
background-color: #00aaff;
outline: none;
}
`;
this.button = document.createElement('button');
this.shadowRoot!.appendChild(style);
this.shadowRoot!.appendChild(this.button);
this.button.addEventListener('click', () => this.activate());
}
activate() {
console.log('activate', this);
FolkToolset.setActiveTool(this);
}
}
export class FolkShapeTool extends FolkInteractionHandler {
static tagName = 'folk-shape-tool';
readonly events = ['pointerdown'];
constructor() {
super();
this.button.textContent = 'Create Shape';
}
handleEvent(event: Event): void {
if (!(event instanceof PointerEvent)) return;
const target = event.target as HTMLElement;
if (!target || target instanceof FolkShape) return;
event.stopImmediatePropagation();
const shape = new FolkShape();
const rect = target.getBoundingClientRect();
const width = 100;
const height = 100;
shape.x = event.clientX - rect.left - width / 2;
shape.y = event.clientY - rect.top - height / 2;
shape.width = width;
shape.height = height;
target.appendChild(shape);
shape.focus();
}
static define() {
if (!customElements.get(this.tagName)) {
customElements.define(this.tagName, this);
}
}
}
export class FolkDeleteTool extends FolkInteractionHandler {
static tagName = 'folk-delete-tool';
readonly events = ['pointerdown'];
constructor() {
super();
this.button.textContent = 'Delete';
}
handleEvent(event: Event): void {
if (!(event instanceof PointerEvent)) return;
const target = event.target as HTMLElement;
if (!target || !(target instanceof FolkShape)) return;
event.stopImmediatePropagation();
target.remove();
}
static define() {
if (!customElements.get(this.tagName)) {
customElements.define(this.tagName, this);
}
}
}
export class FolkToolset extends HTMLElement {
static tagName = 'folk-toolset';
private static instance: FolkToolset | null = null;
private currentHandler: ((event: Event) => void) | null = null;
private activeTool: FolkInteractionHandler | null = null;
static setActiveTool(tool: FolkInteractionHandler) {
if (this.instance) {
this.instance.activateTool(tool);
}
}
constructor() {
super();
FolkToolset.instance = this;
}
private activateTool(tool: FolkInteractionHandler) {
// Remove active class from previous tool
if (this.activeTool) {
this.activeTool.classList.remove('active');
}
// Deactivate current handler
if (this.currentHandler) {
tool.events.forEach((event) => {
this.removeEventListener(event, this.currentHandler!, true);
});
}
// If clicking same tool, just deactivate
if (this.currentHandler === tool.handleEvent.bind(tool)) {
this.currentHandler = null;
this.activeTool = null;
return;
}
// Activate new handler
this.currentHandler = tool.handleEvent.bind(tool);
tool.events.forEach((event) => {
this.addEventListener(event, this.currentHandler!, true);
});
// Add active class to new tool
tool.classList.add('active');
this.activeTool = tool;
}
static define() {
if (!customElements.get(this.tagName)) {
customElements.define(this.tagName, this);
}
}
}
FolkShapeTool.define();
FolkDeleteTool.define();
FolkToolset.define();