fixed button styling, dev server, added switches for event handling, global baking, global reset, three BeatUnit states with styles, and mobile long touch to change beatunit type

This commit is contained in:
Daniel Ledda
2022-03-20 21:21:43 +01:00
parent 3ba01eb86b
commit 9121706184
15 changed files with 222 additions and 113 deletions

65
src/ui/Beat/Beat.css Normal file
View File

@@ -0,0 +1,65 @@
.beat > * {
padding-right: 1em;
padding-left: 1em;
}
.vertical-mode .beat > * {
padding-right: 0;
padding-left: 0;
}
.beat-unit-block {
height: 2em;
}
.vertical-mode .beat-unit-block {
height: auto;
width: 2em;
}
.beat-title {
width: 3em;
line-height: 32px;
margin: 0;
}
.vertical-mode .beat-title {
display: block;
width: auto;
text-align: center;
}
.beat-spacer {
display: inline-block;
width: 1em;
height: 2em;
}
.vertical-mode .beat-spacer {
display: block;
width: 2em;
height: 1em;
}
.beat-main {
display: inline-flex;
}
.vertical-mode .beat-main {
width: 2em;
margin-right: 4px;
display: block;
}
.beat-settings-container {
display: flex;
}
.beat {
width: max-content;
margin-bottom: 4px;
}
.vertical-mode .beat {
display: inline-block;
}

159
src/ui/Beat/BeatView.ts Normal file
View File

@@ -0,0 +1,159 @@
import UINode, {UINodeOptions} from "@/ui/UINode";
import Beat, {BeatEvents} from "@/Beat";
import {IPublisher} from "@/Publisher";
import ISubscriber from "@/Subscriber";
import BeatUnitView from "@/ui/BeatUnit/BeatUnitView";
import "./Beat.css";
export type BeatUINodeOptions = UINodeOptions & {
beat: Beat,
};
export default class BeatView extends UINode implements ISubscriber {
private beat: Beat;
private title!: HTMLHeadingElement;
private beatUnitViews: BeatUnitView[] = [];
private beatUnitViewBlock: HTMLElement | null = null;
private lastHoveredBeatUnitView: BeatUnitView | null = null;
static deselectingUnits = false;
static selectingUnits = false;
constructor(options: BeatUINodeOptions) {
super(options);
this.beat = options.beat;
this.setupBindings();
}
private onBeatViewHover(beatView: BeatUnitView) {
this.lastHoveredBeatUnitView = beatView;
if (BeatView.selectingUnits) {
this.lastHoveredBeatUnitView.turnOn();
} else if (BeatView.deselectingUnits) {
this.lastHoveredBeatUnitView.turnOff();
}
}
private setupBindings() {
this.beat.addSubscriber(this, "all");
}
notify<T extends string | number>(publisher: IPublisher<T>, event: "all" | T[] | T): void {
if (event === BeatEvents.NewName) {
this.title.innerText = this.beat.getName();
} else if (event === BeatEvents.NewTimeSig) {
this.setupBeatUnits();
} else if (event === BeatEvents.NewBarCount) {
this.setupBeatUnits();
} else if (event === BeatEvents.DisplayTypeChanged) {
this.setupBeatUnits();
} else if (event === BeatEvents.LoopLengthChanged) {
this.setupBeatUnits();
}
}
private rebuildBeatUnitViews() {
const beatUnitCount = this.beat.getBarCount() * this.beat.getTimeSigUp();
this.beatUnitViews.splice(beatUnitCount, this.beatUnitViews.length - beatUnitCount);
for (let i = 0; i < beatUnitCount; i++) {
const beatUnit = this.beat.getUnitByIndex(i);
if (beatUnit) {
let view: BeatUnitView;
if (this.beatUnitViews[i]) {
view = this.beatUnitViews[i];
view.setUnit(beatUnit);
} else {
view = new BeatUnitView({beatUnit});
this.beatUnitViews.push(view);
}
view.onHover(() => this.onBeatViewHover(view));
view.onMouseDown((event: MouseEvent) => this.onBeatUnitClick(event.button, i));
}
}
}
private onBeatUnitClick(button: number, index: number) {
if (button === 0) {
BeatView.selectingUnits = true;
this.beat.getUnitByIndex(index)?.toggle();
} else if (button === 2) {
BeatView.deselectingUnits = true;
this.beat.getUnitByIndex(index)?.setOn(false);
}
}
private buildBeatUnitViewBlock(): void {
const beatUnitNodes: HTMLElement[] = [];
for (let i = 0; i < this.beatUnitViews.length; i++) {
beatUnitNodes.push(this.beatUnitViews[i].render());
}
if (this.beatUnitViewBlock) {
this.beatUnitViewBlock.replaceChildren(...beatUnitNodes);
} else {
this.beatUnitViewBlock = UINode.make("div", {
classes: ["beat-unit-block"],
subs: [...beatUnitNodes],
});
}
}
private respaceBeatUnits(): void {
if (!this.beatUnitViewBlock) {
return;
}
this.beatUnitViewBlock.querySelectorAll(".beat-spacer").forEach(spacer => spacer.remove());
const barLength = this.beat.getTimeSigUp();
const barCount = this.beat.getBarCount();
let bars = 0;
let i = -1;
let spacersInserted = false;
while (!spacersInserted) {
i += barLength;
const newSpacer = UINode.make("div", {classes: ["beat-spacer"]});
const leftNeighbour = this.beatUnitViewBlock.children.item(i);
if (leftNeighbour) {
leftNeighbour.insertAdjacentElement("afterend", newSpacer);
} else {
break;
}
i++;
bars++;
if (bars === barCount) {
spacersInserted = true;
}
}
}
private setupBeatUnits(): void {
this.rebuildBeatUnitViews();
this.buildBeatUnitViewBlock();
this.respaceBeatUnits();
}
build(): HTMLElement {
this.title = UINode.make("h3", {
innerText: this.beat.getName(),
classes: ["beat-title"],
});
this.setupBeatUnits();
if (!this.beatUnitViewBlock) {
throw new Error("Beat unit block setup failed!");
}
return UINode.make("div", {
classes: ["beat"],
subs: [
UINode.make("div", {
classes: ["beat-main"],
subs: [
this.title,
this.beatUnitViewBlock,
]
}),
],
});
}
}
window.addEventListener("mouseup", () => {
BeatView.selectingUnits = false;
BeatView.deselectingUnits = false;
});