import TrackUnit, { TrackUnitEvent, TrackUnitStickingType, TrackUnitType } from "@/TrackUnit"; import "./TrackUnit.css"; import { h, IPublisher, ISubscriber, ISubscription, Publisher, Rung, RungOptions } from "@djledda/ladder"; export type TrackUnitUINodeOptions = RungOptions & { trackUnit: TrackUnit, }; const EventTypeSubscriptions = [ TrackUnitEvent.On, TrackUnitEvent.Off, TrackUnitEvent.TypeChange, ]; type EventTypeSubscriptions = typeof EventTypeSubscriptions[number]; export default class TrackUnitView extends Rung implements ISubscriber { private trackUnit: TrackUnit; private subscription: ISubscription | null = null; private publisher: IPublisher = new Publisher(this); private rotationTimeout: ReturnType | null = null; private mouseDownListeners: ((ev: MouseEvent) => void)[] = []; private hoverListeners: ((ev: MouseEvent) => void)[] = []; private blockNextMouseUp = false; constructor(options: TrackUnitUINodeOptions) { super(options); this.trackUnit = options.trackUnit; this.setUnit(this.trackUnit); } setUnit(trackUnit: TrackUnit | null): void { if (trackUnit) { this.trackUnit = trackUnit; this.setupBindings(); this.notify(this.publisher, trackUnit.isOn() ? TrackUnitEvent.On : TrackUnitEvent.Off); this.notify(this.publisher, TrackUnitEvent.TypeChange); } else { this.subscription?.unbind(); } } private setupBindings() { this.subscription?.unbind(); this.subscription = this.trackUnit.addSubscriber(this, EventTypeSubscriptions); this.hoverListeners.forEach(listener => this.render().removeEventListener("mouseover", listener)); this.mouseDownListeners.forEach(listener => this.render().removeEventListener("mousedown", listener)); this.redraw(); this.hoverListeners.forEach(listener => this.render().addEventListener("mouseover", listener)); this.mouseDownListeners.forEach(listener => this.render().addEventListener("mousedown", listener)); this.render().addEventListener("mousedown", (ev) => this.handleMouseDown(ev)); this.render().addEventListener("mouseout", (ev) => this.handleMouseOut(ev)); this.render().addEventListener("mouseup", (ev) => this.handleMouseUp(ev)); this.render().addEventListener("touchstart", (ev) => this.handleTouchStart(ev)); this.render().addEventListener("touchend", (ev) => this.handleTouchEnd(ev)); } private handleMouseDown(ev: MouseEvent): void { if (ev.button === 1) { this.trackUnit.rotateType(); } else { this.rotationTimeout = this.rotationTimeout || setTimeout(() => { this.trackUnit.rotateType(); this.blockNextMouseUp = true; this.rotationTimeout = null; }, 400); } } private handleMouseOut(ev: MouseEvent): void { if (this.rotationTimeout) { clearTimeout(this.rotationTimeout); this.rotationTimeout = null; } } private handleMouseUp(ev: MouseEvent): void { if (this.rotationTimeout) { clearTimeout(this.rotationTimeout); this.rotationTimeout = null; } if (!this.blockNextMouseUp) { this.mouseDownListeners.forEach(listener => listener(ev)); } this.blockNextMouseUp = false; } private handleTouchStart(ev: TouchEvent): void { this.rotationTimeout = this.rotationTimeout || setTimeout(() => { this.trackUnit.rotateType(); this.rotationTimeout = null; }, 400); } private handleTouchEnd(ev: TouchEvent): void { if (this.rotationTimeout) { clearTimeout(this.rotationTimeout); this.rotationTimeout = null; } } toggle(): void { this.trackUnit.toggle(); } setStickingType(type: TrackUnitStickingType): void { this.trackUnit.setStickingType(type); } setType(type: TrackUnitType): void { this.trackUnit.setType(type); } turnOn(): void { this.trackUnit.setOn(true); } turnOff(): void { this.trackUnit.setOn(false); } notify(publisher: unknown, event: EventTypeSubscriptions): void { switch (event) { case TrackUnitEvent.On: this.render().classList.add("track-unit-on"); break; case TrackUnitEvent.Off: this.render().classList.remove("track-unit-on"); break; case TrackUnitEvent.TypeChange: this.syncTrackUnitType(); this.syncStickingType(this.trackUnit.getStickingType()); } } private syncStickingType(type: TrackUnitStickingType) { const node = this.render(); node.classList.remove("track-unit-lh"); node.classList.remove("track-unit-rh"); node.classList.remove("track-unit-lf"); node.classList.remove("track-unit-rf"); if (type !== "none") { node.classList.add(`track-unit-${ type }`); } } private syncTrackUnitType() { switch (this.trackUnit.getType()) { case TrackUnitType.Normal: this.render().classList.remove("track-unit-ghost"); this.render().classList.remove("track-unit-accent"); break; case TrackUnitType.GhostNote: this.render().classList.add("track-unit-ghost"); this.render().classList.remove("track-unit-accent"); break; case TrackUnitType.Accent: this.render().classList.remove("track-unit-ghost"); this.render().classList.add("track-unit-accent"); break; case TrackUnitType.GhostNoteAccent: this.render().classList.add("track-unit-ghost"); this.render().classList.add("track-unit-accent"); break; } } build(): HTMLElement { const classes = ["track-unit"]; if (this.trackUnit.isOn()) { classes.push("track-unit-on"); } return
false} /> as HTMLElement; } onHover(cb: () => void): void { this.hoverListeners.push(cb); this.render().addEventListener("mouseover", cb); } onMouseDown(cb: (ev: MouseEvent) => void): void { this.mouseDownListeners.push(cb); this.render().addEventListener("mousedown", cb); } }