diff --git a/src/Beat.ts b/src/Beat.ts index ec95695..cc64078 100644 --- a/src/Beat.ts +++ b/src/Beat.ts @@ -32,7 +32,7 @@ export default class Beat implements IPublisher{ this.key = `Beat-${Beat.count}`; this.name = options?.name ?? this.key; this.setTimeSignature({up: options?.timeSig?.up ?? 4, down: options?.timeSig?.down ?? 4}); - this.setBars(options?.bars ?? 48); + this.setBars(options?.bars ?? 4); Beat.count++; } @@ -61,6 +61,10 @@ export default class Beat implements IPublisher{ this.publisher.notifySubs(BeatEvents.NewBarCount); } + getUnitByIndex(index: number): BeatUnit | null { + return this.unitRecord[index] ?? null; + } + private updateBeatUnitLength() { const newBarCount = this.barCount * this.timeSigUp; if (newBarCount < this.unitRecord.length) { @@ -146,7 +150,7 @@ export default class Beat implements IPublisher{ return this.key; } - private static isValidTimeSigRange(sig: number): boolean { + static isValidTimeSigRange(sig: number): boolean { return sig >= 2 && sig <= 64; } diff --git a/src/BeatGroup.ts b/src/BeatGroup.ts index b5524db..e24dce1 100644 --- a/src/BeatGroup.ts +++ b/src/BeatGroup.ts @@ -6,15 +6,19 @@ type BeatGroupInitOptions = { beats: BeatInitOptions[], } -const enum BeatGroupEvents { +export const enum BeatGroupEvents { BeatOrderChanged, BeatListChanged, + GlobalBarCountChanged, + GlobalTimeSigUpChanged, } export default class BeatGroup implements IPublisher { private beats: Beat[] = []; private beatKeyMap: Record = {}; private publisher: Publisher = new Publisher(); + private lastGlobalBarCount: number; + private lastGlobalTimeSigUp: number; constructor(options?: BeatGroupInitOptions) { if (options?.beats) { @@ -23,6 +27,11 @@ export default class BeatGroup implements IPublisher { this.beats.push(newBeat); this.beatKeyMap[newBeat.getKey()] = this.beats.length - 1; } + this.lastGlobalBarCount = this.beats[0].getBarCount(); + this.lastGlobalTimeSigUp = this.beats[0].getTimeSigUp(); + } else { + this.lastGlobalBarCount = 4; + this.lastGlobalTimeSigUp = 4; } } @@ -30,6 +39,28 @@ export default class BeatGroup implements IPublisher { return this.publisher.addSubscriber(subscriber, eventType); } + setGlobalBarCount(barCount: number): void { + if (barCount <= 0 || (barCount | 0) !== barCount) { + return; + } + this.lastGlobalBarCount = barCount; + for (const beat of this.beats) { + beat.setBars(barCount); + } + this.publisher.notifySubs(BeatGroupEvents.GlobalBarCountChanged); + } + + setGlobalTimeSigUp(timeSigUp: number): void { + if (!Beat.isValidTimeSigRange(timeSigUp)) { + return; + } + this.lastGlobalTimeSigUp = timeSigUp; + for (const beat of this.beats) { + beat.setTimeSignature({up: timeSigUp}); + } + this.publisher.notifySubs(BeatGroupEvents.GlobalTimeSigUpChanged); + } + getBeatByKey(beatKey: string): Beat { if (typeof this.beatKeyMap[beatKey] === "undefined") { throw new Error(`Could not find the beat with key: ${beatKey}`); diff --git a/src/BeatUnit.ts b/src/BeatUnit.ts index 549792f..bf4a707 100644 --- a/src/BeatUnit.ts +++ b/src/BeatUnit.ts @@ -7,7 +7,7 @@ export enum BeatUnitType { } -const enum BeatUnitEvents { +export const enum BeatUnitEvents { Toggle, On, Off, diff --git a/src/Publisher.ts b/src/Publisher.ts index f60209e..be84daf 100644 --- a/src/Publisher.ts +++ b/src/Publisher.ts @@ -44,7 +44,7 @@ export class Publisher implements IPublisher { sub.notify(this, eventType); } for (const sub of this.getSubscribers("all")) { - sub.notify(this, "all"); + sub.notify(this, eventType); } } } diff --git a/src/main.ts b/src/main.ts index e3ae26d..758be1b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -26,7 +26,6 @@ mainBeatGroup.addBeat({ const appNode = document.querySelector("#app"); - if (appNode) { const appRoot = new RootView({ parent: appNode as HTMLDivElement, diff --git a/src/ui/BeatGroup/Beat/Beat.css b/src/ui/BeatGroup/Beat/Beat.css index e69de29..299a603 100644 --- a/src/ui/BeatGroup/Beat/Beat.css +++ b/src/ui/BeatGroup/Beat/Beat.css @@ -0,0 +1,32 @@ +.beat:first-child { + padding-left: 5px; +} + +.beat:last-child { + padding-right: 0; +} + +.beat > * { + padding-right: 1em; + padding-left: 1em; +} + +.beat-settings-btn { + margin: 0; + display: inline-block; +} + +.beat-unit-block { + display: inline-block; +} + +.beat-title { + display: inline-block; + width: 3em; + margin: 0; +} + +.beat-spacer { + display: inline-block; + width: 1em; +} \ No newline at end of file diff --git a/src/ui/BeatGroup/Beat/BeatSettings/BeatSettings.css b/src/ui/BeatGroup/Beat/BeatSettings/BeatSettings.css new file mode 100644 index 0000000..e275bd9 --- /dev/null +++ b/src/ui/BeatGroup/Beat/BeatSettings/BeatSettings.css @@ -0,0 +1,7 @@ +.beat-settings { + display: none; +} + +.beat-settings.visible { + display: block; +} \ No newline at end of file diff --git a/src/ui/BeatGroup/Beat/BeatSettings/BeatSettingsView.css b/src/ui/BeatGroup/Beat/BeatSettings/BeatSettingsView.css deleted file mode 100644 index 0cdd59d..0000000 --- a/src/ui/BeatGroup/Beat/BeatSettings/BeatSettingsView.css +++ /dev/null @@ -1,7 +0,0 @@ -.beatSettingsView { - display: none; -} - -.beatSettingsView.visible { - display: block; -} \ No newline at end of file diff --git a/src/ui/BeatGroup/Beat/BeatSettings/BeatSettingsView.ts b/src/ui/BeatGroup/Beat/BeatSettings/BeatSettingsView.ts index cb98fcc..51b3dbf 100644 --- a/src/ui/BeatGroup/Beat/BeatSettings/BeatSettingsView.ts +++ b/src/ui/BeatGroup/Beat/BeatSettings/BeatSettingsView.ts @@ -1,8 +1,8 @@ import UINode, {UINodeOptions} from "../../../UINode"; import Beat, {BeatEvents} from "../../../../Beat"; import {IPublisher} from "../../../../Publisher"; -import "./BeatSettingsView.css"; import ISubscriber from "../../../../Subscriber"; +import "./BeatSettings.css"; export type BeatSettingsViewUINodeOptions = UINodeOptions & { beat: Beat, @@ -22,7 +22,7 @@ export default class BeatSettingsView extends UINode implements ISubscriber { } private setupBindings() { - this.beat.addSubscriber(this, BeatEvents.NewName); + this.beat.addSubscriber(this, "all"); } notify(publisher: IPublisher, event: "all" | T[] | T) { @@ -34,7 +34,7 @@ export default class BeatSettingsView extends UINode implements ISubscriber { } } - toggleVisible() { + toggleVisible(): void { this.visible = !this.visible; if (this.visible) { this.node?.classList.add("visible"); @@ -43,26 +43,43 @@ export default class BeatSettingsView extends UINode implements ISubscriber { } } - isOpen() { + isOpen(): boolean { return this.visible; } rebuild(): HTMLElement { - this.timeSigUp = UINode.make("input", {}); - this.timeSigUp.addEventListener("input", - (event) => this.beat.setTimeSignature({ - up: Number((event.target as HTMLInputElement).value) })); - this.timeSigDown = UINode.make("input", {}); - this.timeSigDown.addEventListener("input", - (event) => this.beat.setTimeSignature({ - down: Number((event.target as HTMLInputElement).value) })); + this.timeSigUp = UINode.make("input", { + value: this.beat.getTimeSigUp().toString(), + oninput: (event) => { + this.beat.setTimeSignature({up: Number((event.target as HTMLInputElement).value) }); + }, + }); + this.timeSigDown = UINode.make("input", { + value: this.beat.getTimeSigDown().toString(), + oninput: (event) => { + this.beat.setTimeSignature({down: Number((event.target as HTMLInputElement).value) }); + }, + }); + this.barCountInput = UINode.make("input", { + value: this.beat.getBarCount().toString(), + oninput: (event) => { + this.beat.setBars(Number((event.target as HTMLInputElement).value)); + }, + }); this.node = UINode.make("div", { subs: [ - UINode.make("p", {innerText: `Settings for ${this.beat.getName()}`}), - this.timeSigUp, - this.timeSigDown, + UINode.make("div", { + classes: ["beat-settings-time-sig"], + subs: [ + UINode.make("label", {innerText: "Time Signature:"}), + this.timeSigUp, + this.timeSigDown, + ] + }), + UINode.make("label", {innerText: "Bars:"}), + this.barCountInput, ], - classes: ["beatSettingsView"] + classes: ["beat-settings"] }); return this.node; } diff --git a/src/ui/BeatGroup/Beat/BeatUnit/BeatUnit.css b/src/ui/BeatGroup/Beat/BeatUnit/BeatUnit.css new file mode 100644 index 0000000..c12c95c --- /dev/null +++ b/src/ui/BeatGroup/Beat/BeatUnit/BeatUnit.css @@ -0,0 +1,11 @@ +.beat-unit { + width: 2em; + height: 2em; + background-color: white; + border: 0.1em solid black; + display: inline-block; +} + +.beat-unit.on { + background-color: darksalmon; +} \ No newline at end of file diff --git a/src/ui/BeatGroup/Beat/BeatUnit/BeatUnitView.ts b/src/ui/BeatGroup/Beat/BeatUnit/BeatUnitView.ts new file mode 100644 index 0000000..40bd8e0 --- /dev/null +++ b/src/ui/BeatGroup/Beat/BeatUnit/BeatUnitView.ts @@ -0,0 +1,46 @@ +import BeatUnit, {BeatUnitEvents} from "../../../../BeatUnit"; +import ISubscriber from "../../../../Subscriber"; +import UINode, {UINodeOptions} from "../../../UINode"; +import {IPublisher} from "../../../../Publisher"; +import "./BeatUnit.css"; + +export type BeatUnitUINodeOptions = UINodeOptions & { + beatUnit: BeatUnit, +}; + +export default class BeatUnitView extends UINode implements ISubscriber { + private beatUnit: BeatUnit; + + constructor(options: BeatUnitUINodeOptions) { + super(options); + this.beatUnit = options.beatUnit; + this.setupBindings(); + this.rebuild(); + } + + private setupBindings() { + this.beatUnit.addSubscriber(this, "all"); + } + + notify(publisher: IPublisher, event: "all" | T[] | T) { + if (event === BeatUnitEvents.On) { + this.node?.classList.add("on"); + } else if (event === BeatUnitEvents.Off) { + this.node?.classList.remove("on"); + } + } + + rebuild(): HTMLElement { + const classes = ["beat-unit"]; + if (this.beatUnit.isOn()) { + classes.push("on"); + } + this.node = UINode.make("div", { + classes: classes, + onclick: () => { + this.beatUnit.toggle(); + } + }); + return this.node; + } +} diff --git a/src/ui/BeatGroup/Beat/BeatUnitView/BeatUnitView.css b/src/ui/BeatGroup/Beat/BeatUnitCollection/BeatUnitCollection.css similarity index 100% rename from src/ui/BeatGroup/Beat/BeatUnitView/BeatUnitView.css rename to src/ui/BeatGroup/Beat/BeatUnitCollection/BeatUnitCollection.css diff --git a/src/ui/BeatGroup/Beat/BeatUnitCollection/BeatUnitCollectionView.ts b/src/ui/BeatGroup/Beat/BeatUnitCollection/BeatUnitCollectionView.ts new file mode 100644 index 0000000..b9f9be6 --- /dev/null +++ b/src/ui/BeatGroup/Beat/BeatUnitCollection/BeatUnitCollectionView.ts @@ -0,0 +1,53 @@ +import ISubscriber from "../../../../Subscriber"; +import UINode, {UINodeOptions} from "../../../UINode"; +import BeatGroup, {BeatGroupEvents} from "../../../../BeatGroup"; +import {IPublisher} from "../../../../Publisher"; + +export type BeatUnitCollectionUINodeOptions = UINodeOptions & { + beatGroup: BeatGroup, +}; + +export default class BeatUnitCollectionView extends UINode implements ISubscriber { + private beatGroup: BeatGroup; + private barCountInput!: HTMLInputElement; + private timeSigUpInput!: HTMLInputElement; + + constructor(options: BeatUnitCollectionUINodeOptions) { + super(options); + this.beatGroup = options.beatGroup; + this.beatGroup.addSubscriber(this, []); + this.rebuild(); + } + + notify(publisher: IPublisher, event: "all" | T[] | T): void { + if (event === BeatGroupEvents.GlobalBarCountChanged) { + this.barCountInput.value = this.beatGroup.getBeatByIndex(0).getBarCount().toString(); + } else if (event === BeatGroupEvents.GlobalTimeSigUpChanged) { + this.barCountInput.value = this.beatGroup.getBeatByIndex(0).getBarCount().toString(); + } + } + + rebuild(): HTMLElement { + this.barCountInput = UINode.make("input", { + type: "text", + classes: ["beat-group-settings-view-bar-count"], + value: this.beatGroup.getBeatByIndex(0).getBarCount(), + oninput: () => { + this.beatGroup.setGlobalBarCount(Number(this.barCountInput.value)); + }, + }); + this.timeSigUpInput = UINode.make("input", { + type: "text", + value: this.beatGroup.getBeatByIndex(0).getTimeSigUp(), + classes: ["beat-group-settings-view-time-sig-up"], + oninput: () => { + this.beatGroup.setGlobalTimeSigUp(Number(this.timeSigUpInput.value)); + }, + }); + this.node = UINode.make("div", { + subs: [ + ], + }); + return this.node; + } +} \ No newline at end of file diff --git a/src/ui/BeatGroup/Beat/BeatUnitView/BeatUnitView.ts b/src/ui/BeatGroup/Beat/BeatUnitView/BeatUnitView.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/ui/BeatGroup/Beat/BeatView.ts b/src/ui/BeatGroup/Beat/BeatView.ts index d37bc76..b53b21a 100644 --- a/src/ui/BeatGroup/Beat/BeatView.ts +++ b/src/ui/BeatGroup/Beat/BeatView.ts @@ -3,6 +3,8 @@ import Beat, {BeatEvents} from "../../../Beat"; import {IPublisher} from "../../../Publisher"; import BeatSettingsView from "./BeatSettings/BeatSettingsView"; import ISubscriber from "../../../Subscriber"; +import BeatUnitView from "./BeatUnit/BeatUnitView"; +import "./Beat.css"; export type BeatUINodeOptions = UINodeOptions & { beat: Beat, @@ -13,6 +15,8 @@ export default class BeatView extends UINode implements ISubscriber { private title!: HTMLHeadingElement; private settingsView!: BeatSettingsView; private settingsToggleButton!: HTMLButtonElement; + private beatUnitViews: BeatUnitView[] = []; + private beatUnitViewBlock!: HTMLElement; constructor(options: BeatUINodeOptions) { super(options); @@ -22,12 +26,16 @@ export default class BeatView extends UINode implements ISubscriber { } private setupBindings() { - this.beat.addSubscriber(this, BeatEvents.NewName); + this.beat.addSubscriber(this, "all"); } - notify(publisher: IPublisher, event: "all" | T[] | T) { + notify(publisher: IPublisher, event: "all" | T[] | T): void { if (event === BeatEvents.NewName) { this.title.innerText = this.beat.getName(); + } else if (event === BeatEvents.NewTimeSig) { + this.render(); + } else if (event === BeatEvents.NewBarCount) { + this.render(); } } @@ -36,15 +44,65 @@ export default class BeatView extends UINode implements ISubscriber { this.settingsToggleButton.innerText = this.settingsView.isOpen() ? "Hide Settings" : "Show Settings"; } + private makeBeatUnits() { + const beatUnitCount = this.beat.getBarCount() * this.beat.getTimeSigUp(); + this.beatUnitViews = []; + for (let i = 0; i < beatUnitCount; i++) { + const beatUnit = this.beat.getUnitByIndex(i); + if (beatUnit) { + this.beatUnitViews.push(new BeatUnitView({beatUnit})); + } + } + } + + private respaceBeatUnits(): void { + 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; + } + } + } + rebuild(): HTMLElement { - this.title = UINode.make("h3", {innerText: this.beat.getName()}); + this.title = UINode.make("h3", { + innerText: this.beat.getName(), + classes: ["beat-title"], + }); + this.makeBeatUnits(); this.settingsView = new BeatSettingsView({beat: this.beat}); - this.settingsToggleButton = UINode.make("button", {innerText: this.settingsView.isOpen() ? "Hide Settings" : "Show Settings"}); + this.settingsToggleButton = UINode.make("button", { + classes: ["beat-settings-btn"], + innerText: this.settingsView.isOpen() ? "Hide Settings" : "Show Settings", + }); this.settingsToggleButton.addEventListener("click", () => this.toggleSettings()); + this.beatUnitViewBlock = UINode.make("div", { + classes: ["beat-unit-block"], + subs: [ + ...this.beatUnitViews.map(view => view.rebuild()), + ], + }); + this.respaceBeatUnits(); this.node = UINode.make("div", { + classes: ["beat"], subs: [ this.title, - UINode.make("p", {innerText: "I am a BeatGroup"}), + this.beatUnitViewBlock, this.settingsToggleButton, this.settingsView.rebuild(), ], diff --git a/src/ui/BeatGroup/BeatGroup.css b/src/ui/BeatGroup/BeatGroup.css index e69de29..c106977 100644 --- a/src/ui/BeatGroup/BeatGroup.css +++ b/src/ui/BeatGroup/BeatGroup.css @@ -0,0 +1,3 @@ +.beat-group { + +} \ No newline at end of file diff --git a/src/ui/BeatGroup/BeatGroupSettings/BeatGroupSettings.css b/src/ui/BeatGroup/BeatGroupSettings/BeatGroupSettings.css new file mode 100644 index 0000000..eb62264 --- /dev/null +++ b/src/ui/BeatGroup/BeatGroupSettings/BeatGroupSettings.css @@ -0,0 +1,3 @@ +.beat-group-settings { + +} \ No newline at end of file diff --git a/src/ui/BeatGroup/BeatGroupSettings/BeatGroupSettingsView.ts b/src/ui/BeatGroup/BeatGroupSettings/BeatGroupSettingsView.ts new file mode 100644 index 0000000..f5fd8ca --- /dev/null +++ b/src/ui/BeatGroup/BeatGroupSettings/BeatGroupSettingsView.ts @@ -0,0 +1,66 @@ +import BeatGroup from "../../../BeatGroup"; +import UINode, {UINodeOptions} from "../../UINode"; +import ISubscriber from "../../../Subscriber"; +import {IPublisher} from "../../../Publisher"; +import {BeatGroupEvents} from "../../../BeatGroup"; + +export type BeatGroupSettingsUINodeOptions = UINodeOptions & { + beatGroup: BeatGroup, +}; + +export default class BeatGroupSettingsView extends UINode implements ISubscriber { + private beatGroup: BeatGroup; + private barCountInput!: HTMLInputElement; + private timeSigUpInput!: HTMLInputElement; + + constructor(options: BeatGroupSettingsUINodeOptions) { + super(options); + this.beatGroup = options.beatGroup; + this.beatGroup.addSubscriber(this, [ + BeatGroupEvents.GlobalBarCountChanged, + BeatGroupEvents.GlobalTimeSigUpChanged + ]); + this.rebuild(); + } + + notify(publisher: IPublisher, event: "all" | T[] | T): void { + if (event === BeatGroupEvents.GlobalBarCountChanged) { + this.barCountInput.value = this.beatGroup.getBeatByIndex(0).getBarCount().toString(); + } else if (event === BeatGroupEvents.GlobalTimeSigUpChanged) { + this.barCountInput.value = this.beatGroup.getBeatByIndex(0).getBarCount().toString(); + } + } + + rebuild(): HTMLElement { + this.barCountInput = UINode.make("input", { + type: "text", + classes: ["beat-group-settings-view-bar-count"], + value: this.beatGroup.getBeatByIndex(0).getBarCount(), + oninput: () => { + this.beatGroup.setGlobalBarCount(Number(this.barCountInput.value)); + }, + }); + this.timeSigUpInput = UINode.make("input", { + type: "text", + value: this.beatGroup.getBeatByIndex(0).getTimeSigUp(), + classes: ["beat-group-settings-view-time-sig-up"], + oninput: () => { + this.beatGroup.setGlobalTimeSigUp(Number(this.timeSigUpInput.value)); + }, + }); + this.node = UINode.make("div", { + subs: [ + UINode.make("h4", {innerText: "Settings for beat"}), + UINode.make("label", { + innerText: "Bars:", + }), + this.barCountInput, + UINode.make("label", { + innerText: "Boxes per bar:", + }), + this.timeSigUpInput, + ], + }); + return this.node; + } +} \ No newline at end of file diff --git a/src/ui/BeatGroup/BeatGroupView.ts b/src/ui/BeatGroup/BeatGroupView.ts index 33c3987..4f5d621 100644 --- a/src/ui/BeatGroup/BeatGroupView.ts +++ b/src/ui/BeatGroup/BeatGroupView.ts @@ -1,6 +1,7 @@ import UINode, {UINodeOptions} from "../UINode"; import BeatGroup from "../../BeatGroup"; import BeatView from "./Beat/BeatView"; +import BeatGroupSettingsView from "./BeatGroupSettings/BeatGroupSettingsView"; export type BeatGroupUINodeOptions = UINodeOptions & { title: string, @@ -10,6 +11,7 @@ export type BeatGroupUINodeOptions = UINodeOptions & { export default class BeatGroupView extends UINode { private title: string; private beatGroup: BeatGroup; + private beatGroupSettingsView!: BeatGroupSettingsView; constructor(options: BeatGroupUINodeOptions) { super(options); @@ -22,9 +24,12 @@ export default class BeatGroupView extends UINode { for (let i = 0; i < this.beatGroup.getBeatCount(); i++) { beatViews.push(new BeatView({beat: this.beatGroup.getBeatByIndex(i)})); } + this.beatGroupSettingsView = new BeatGroupSettingsView({beatGroup: this.beatGroup}); return UINode.make("div", { + classes: ["beat-group"], subs: [ UINode.make("h3", {innerText: this.title}), + this.beatGroupSettingsView.rebuild(), ...beatViews.map(bv => bv.rebuild()) ], }); diff --git a/src/ui/Root/Root.css b/src/ui/Root/Root.css index 8ea5741..ebacdee 100644 --- a/src/ui/Root/Root.css +++ b/src/ui/Root/Root.css @@ -1,8 +1,12 @@ -.rootView { +.root { margin: auto; width: 80%; } -.rootView .title { +.root .title { text-align: center; +} + +.root input { + width: 5em; } \ No newline at end of file diff --git a/src/ui/Root/RootView.ts b/src/ui/Root/RootView.ts index 63e9f5b..7985701 100644 --- a/src/ui/Root/RootView.ts +++ b/src/ui/Root/RootView.ts @@ -11,7 +11,7 @@ export type RootUINodeOptions = UINodeOptions & { export default class RootView extends UINode { private title: string; - private parent: HTMLElement; + protected parent: HTMLElement; private beatGroupView: BeatGroupView; private mainBeatGroup: BeatGroup; @@ -24,23 +24,13 @@ export default class RootView extends UINode { this.rebuild(); } - render() { - const oldNode = this.node; - this.node = this.rebuild(); - if (oldNode) { - this.parent.replaceChild(oldNode, this.node); - } else { - this.parent.appendChild(this.node); - } - } - rebuild(): HTMLDivElement { return UINode.make("div", { + classes: ["root"], subs: [ UINode.make("h1", {innerText: this.title, classes: ["title"]}), this.beatGroupView.rebuild(), ], - classes: ["rootView"] }); } } \ No newline at end of file diff --git a/src/ui/UINode.ts b/src/ui/UINode.ts index 6c13908..aa73ad8 100644 --- a/src/ui/UINode.ts +++ b/src/ui/UINode.ts @@ -12,8 +12,21 @@ type IRenderAttributes< export default abstract class UINode { protected node: HTMLElement | null = null; + protected parent: HTMLElement | null = null; - constructor(options: UINodeOptions) { + constructor(options: UINodeOptions) {} + + render(): void { + const oldNode = this.node; + this.node = this.rebuild(); + if (oldNode) { + if (!this.parent) { + this.parent = oldNode.parentElement; + } + this.parent!.replaceChild(this.node, oldNode); + } else { + this.parent!.appendChild(this.node); + } } abstract rebuild(): HTMLElement;