208 lines
7.0 KiB
TypeScript
208 lines
7.0 KiB
TypeScript
import TrackUnit, { TrackUnitEvent, TrackUnitStickingType, TrackUnitType } from "@/TrackUnit";
|
|
import "./TrackUnit.css";
|
|
import { Capsule, h, IPublisher, ISubscriber, ISubscription, Publisher, Rung, RungOptions } from "@djledda/ladder";
|
|
import IconView, { IconName } from "@/ui/Widgets/Icon/IconView";
|
|
|
|
export type TrackUnitUINodeOptions = RungOptions & {
|
|
trackUnit: TrackUnit,
|
|
};
|
|
|
|
const EventTypeSubscriptions = [
|
|
TrackUnitEvent.On,
|
|
TrackUnitEvent.Off,
|
|
TrackUnitEvent.TypeChange,
|
|
];
|
|
type EventTypeSubscriptions = typeof EventTypeSubscriptions[number];
|
|
|
|
export const StickingTypeIconMap = {
|
|
none: null,
|
|
lf: 'lf',
|
|
lh: 'lh',
|
|
rf: 'rf',
|
|
rh: 'rh',
|
|
} as const satisfies Readonly<Record<TrackUnitStickingType, IconName | null>>;
|
|
|
|
const TypeClasses = [ "Ghost", "Accent" ] as const;
|
|
export const TrackUnitTypeClassMap = {
|
|
"Normal": [],
|
|
"GhostNote": ["Ghost"],
|
|
"Accent": ["Accent"],
|
|
"GhostNoteAccent": ["Ghost", "Accent"],
|
|
} as const satisfies Readonly<Record<TrackUnitType, Readonly<string[]>>>;
|
|
|
|
export default class TrackUnitView extends Rung<HTMLDivElement> implements ISubscriber<EventTypeSubscriptions> {
|
|
private trackUnit: TrackUnit;
|
|
private subscription: ISubscription | null = null;
|
|
private publisher: IPublisher<TrackUnitEvent> = new Publisher<TrackUnitEvent, TrackUnitView>(this);
|
|
private rotationTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
private mouseDownListeners: ((ev: MouseEvent) => void)[] = [];
|
|
private hoverListeners: ((ev: MouseEvent) => void)[] = [];
|
|
private blockNextMouseUp = false;
|
|
private icon: IconView = new IconView({ iconName: 'list' });
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
private handleMouseOut(ev: MouseEvent): void {
|
|
if (this.rotationTimeout) {
|
|
clearTimeout(this.rotationTimeout);
|
|
this.rotationTimeout = null;
|
|
}
|
|
}
|
|
|
|
private handleMouseUp(ev: MouseEvent): void {
|
|
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 {
|
|
if (this.trackUnit.isOn()) {
|
|
this.trackUnit.setStickingType(type);
|
|
}
|
|
}
|
|
|
|
setType(type: TrackUnitType): void {
|
|
this.trackUnit.setType(type);
|
|
}
|
|
|
|
turnOn(): void {
|
|
this.trackUnit.setOn(true);
|
|
}
|
|
|
|
turnOff(): void {
|
|
this.trackUnit.setStickingType("none");
|
|
this.trackUnit.setOn(false);
|
|
}
|
|
|
|
notify(publisher: unknown, event: EventTypeSubscriptions): void {
|
|
switch (event) {
|
|
case TrackUnitEvent.On:
|
|
this.render().classList.add("on");
|
|
break;
|
|
case TrackUnitEvent.Off:
|
|
this.render().classList.remove("on");
|
|
break;
|
|
case TrackUnitEvent.TypeChange:
|
|
this.syncTrackUnitType();
|
|
this.syncStickingType(this.trackUnit.getStickingType());
|
|
}
|
|
}
|
|
|
|
private syncStickingType(type: TrackUnitStickingType) {
|
|
if (StickingTypeIconMap[this.trackUnit.getStickingType()]) {
|
|
this.render().classList.add("icon-visible");
|
|
} else {
|
|
this.render().classList.remove("icon-visible");
|
|
}
|
|
const icon = StickingTypeIconMap[this.trackUnit.getStickingType()];
|
|
if (icon) {
|
|
this.icon.setIcon(icon);
|
|
}
|
|
}
|
|
|
|
private syncTrackUnitType() {
|
|
for (const className of TypeClasses) {
|
|
this.render().classList.remove(className);
|
|
}
|
|
for (const className of TrackUnitTypeClassMap[this.trackUnit.getType()]) {
|
|
this.render().classList.add(className);
|
|
}
|
|
}
|
|
|
|
static getClasses(options: { on: boolean, stickingType: TrackUnitStickingType, type: TrackUnitType, highlightable?: boolean }) {
|
|
const classes = ["track-unit"];
|
|
if (options.on) {
|
|
classes.push("on");
|
|
}
|
|
if (StickingTypeIconMap[options.stickingType]) {
|
|
classes.push("icon-visible");
|
|
}
|
|
if (options.type) {
|
|
classes.push(...TrackUnitTypeClassMap[options.type]);
|
|
}
|
|
if (options.highlightable) {
|
|
classes.push("highlightable");
|
|
}
|
|
return classes;
|
|
}
|
|
|
|
build() {
|
|
const classes = TrackUnitView.getClasses({
|
|
on: this.trackUnit.isOn(),
|
|
stickingType: this.trackUnit.getStickingType(),
|
|
type: this.trackUnit.getType(),
|
|
highlightable: true,
|
|
});
|
|
return (
|
|
<div classes={classes} oncontextmenu={() => false}>
|
|
{this.icon}
|
|
</div>
|
|
) as HTMLDivElement;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|