diff --git a/config.json b/config.json index 49e6167..98be335 100644 --- a/config.json +++ b/config.json @@ -1,3 +1,4 @@ { - "development": true + "development": true, + "baseUrl": "/drum-slayer" } \ No newline at end of file diff --git a/public/index.html b/public/index.html index 5aca93a..f210032 100644 --- a/public/index.html +++ b/public/index.html @@ -7,9 +7,9 @@ Drum Slayer - + - +
diff --git a/src/Beat.ts b/src/Beat.ts index 0d01a4a..6bc8677 100644 --- a/src/Beat.ts +++ b/src/Beat.ts @@ -1,4 +1,4 @@ -import BeatUnit, {BeatUnitType} from "@/BeatUnit"; +import BeatUnit from "@/BeatUnit"; import {IPublisher, Publisher} from "@/Publisher"; import ISubscriber from "@/Subscriber"; import BeatLike from "@/BeatLike"; @@ -16,13 +16,13 @@ export type BeatInitOptions = { }; export const enum BeatEvents { - NewTimeSig="BE0", - NewBarCount="BE1", - NewName="BE2", - DisplayTypeChanged="BE3", - LoopLengthChanged="BE4", - WantsRemoval="BE5", - Baked="BE6", + NewTimeSig="be-0", + NewBarCount="be-1", + NewName="be-2", + DisplayTypeChanged="be-3", + LoopLengthChanged="be-4", + WantsRemoval="be-5", + Baked="be-6", } export default class Beat implements IPublisher, BeatLike { @@ -60,7 +60,7 @@ export default class Beat implements IPublisher, BeatLike { this.publisher.notifySubs(BeatEvents.DisplayTypeChanged); } - addSubscriber(subscriber: ISubscriber, eventType: BeatEvents | BeatEvents[] | "all"): { unbind: () => void } { + addSubscriber(subscriber: ISubscriber, eventType: BeatEvents | BeatEvents[]): { unbind: () => void } { return this.publisher.addSubscriber(subscriber, eventType); } diff --git a/src/BeatGroup.ts b/src/BeatGroup.ts index 8d85cc7..7b3f8b2 100644 --- a/src/BeatGroup.ts +++ b/src/BeatGroup.ts @@ -14,17 +14,25 @@ type BeatGroupInitOptions = { }; export const enum BeatGroupEvents { - BeatOrderChanged="BGE0", - BeatListChanged="BGE1", - BarCountChanged="BGE2", - TimeSigUpChanged="BGE3", - AutoBeatSettingsChanged="BGE4", - LockingChanged="BGE5", + BeatOrderChanged="bge-0", + BeatListChanged="bge-1", + BarCountChanged="bge-2", + TimeSigUpChanged="bge-3", + AutoBeatSettingsChanged="bge-4", + LockingChanged="bge-5", } -export default class BeatGroup implements IPublisher, BeatLike, ISubscriber { +type EventTypeSubscriptions = + | BeatEvents.LoopLengthChanged + | BeatEvents.DisplayTypeChanged + | BeatEvents.WantsRemoval + | BeatEvents.Baked; + +type EventTypePublications = BeatGroupEvents | BeatEvents; + +export default class BeatGroup implements IPublisher, BeatLike, ISubscriber { private beats: Beat[] = []; - private publisher: Publisher = new Publisher(this); + private publisher: Publisher = new Publisher(this); private barCount: number; private timeSigUp: number; private globalLoopLength: number; @@ -45,7 +53,7 @@ export default class BeatGroup implements IPublisher(publisher: IPublisher, event: "all" | T[] | T): void { + notify(publisher: unknown, event: EventTypeSubscriptions): void { switch (event) { case BeatEvents.LoopLengthChanged: case BeatEvents.DisplayTypeChanged: @@ -60,7 +68,7 @@ export default class BeatGroup implements IPublisher void } { + addSubscriber(subscriber: ISubscriber, eventType: SubscriptionEvent): { unbind: () => void } { return this.publisher.addSubscriber(subscriber, eventType); } diff --git a/src/BeatUnit.ts b/src/BeatUnit.ts index 54670c6..a8ac87c 100644 --- a/src/BeatUnit.ts +++ b/src/BeatUnit.ts @@ -1,28 +1,27 @@ -import {IPublisher, ISubscription, Publisher} from "./Publisher"; +import {IPublisher, Publisher} from "./Publisher"; import ISubscriber from "./Subscriber"; -export enum BeatUnitType { - Normal, - GhostNote, - Accent, +export const enum BeatUnitType { + Normal="but-0", + GhostNote="but-1", + Accent="but-2", +} + +export const enum BeatUnitEvent { + Toggle="bue-0", + On="bue-1", + Off="bue-2", + TypeChange="bue-3", } -export const enum BeatUnitEvents { - Toggle, - On, - Off, - TypeChange, -} - - -export default class BeatUnit implements IPublisher { +export default class BeatUnit implements IPublisher { private static readonly TypeRotation = [ BeatUnitType.Normal, BeatUnitType.GhostNote, BeatUnitType.Accent, ] as const; - private publisher: Publisher = new Publisher(this); + private publisher: Publisher = new Publisher(this); private on = false; private typeIndex = 0; @@ -31,28 +30,28 @@ export default class BeatUnit implements IPublisher { this.setType(type); } - addSubscriber(subscriber: ISubscriber, eventType: "all" | BeatUnitEvents | BeatUnitEvents[]): ISubscription { + addSubscriber(subscriber: ISubscriber, eventType: BeatUnitEvent[]): { unbind: () => void } { return this.publisher.addSubscriber(subscriber, eventType); } toggle(): void { this.on = !this.on; - this.publisher.notifySubs(BeatUnitEvents.Toggle); + this.publisher.notifySubs(BeatUnitEvent.Toggle); if (this.on) { - this.publisher.notifySubs(BeatUnitEvents.On); + this.publisher.notifySubs(BeatUnitEvent.On); } else { - this.publisher.notifySubs(BeatUnitEvents.Off); + this.publisher.notifySubs(BeatUnitEvent.Off); } } setOn(on: boolean): void { this.on = on; - this.publisher.notifySubs(this.on ? BeatUnitEvents.On : BeatUnitEvents.Off); + this.publisher.notifySubs(this.on ? BeatUnitEvent.On : BeatUnitEvent.Off); } setType(type: BeatUnitType): void { this.typeIndex = BeatUnit.TypeRotation.indexOf(type); - this.publisher.notifySubs(BeatUnitEvents.TypeChange); + this.publisher.notifySubs(BeatUnitEvent.TypeChange); } getType(): BeatUnitType { @@ -65,7 +64,7 @@ export default class BeatUnit implements IPublisher { } else { this.typeIndex += 1; } - this.publisher.notifySubs(BeatUnitEvents.TypeChange); + this.publisher.notifySubs(BeatUnitEvent.TypeChange); } isOn(): boolean { diff --git a/src/Publisher.ts b/src/Publisher.ts index 1c1d1a6..6a0c6cb 100644 --- a/src/Publisher.ts +++ b/src/Publisher.ts @@ -1,10 +1,10 @@ -import ISubscriber from "./Subscriber"; +import ISubscriber, {LEvent} from "./Subscriber"; -class Subscription implements ISubscription { - private subscriber: ISubscriber; - private readonly eventTypes: T[]; - private publisher: Publisher; - constructor(publisher: Publisher, subscriber: ISubscriber, eventTypes: T[]) { +class Subscription implements ISubscription { + private subscriber: ISubscriber; + private readonly eventTypes: EventType[]; + private publisher: Publisher; + constructor(publisher: Publisher, subscriber: ISubscriber, eventTypes: EventType[]) { this.subscriber = subscriber; this.publisher = publisher; this.eventTypes = eventTypes; @@ -14,32 +14,36 @@ class Subscription implements ISubscription { this.publisher.unbind(this); } - getEventTypes(): T[] { + getEventTypes(): EventType[] { return this.eventTypes; } - getSubscriber(): ISubscriber { + getSubscriber(): ISubscriber { return this.subscriber; } } +interface EventSubscriberRecord { + get(key: K): ISubscriber[]; + set(key: K, subscribers: ISubscriber[]): EventSubscriberRecord; +} -export class Publisher implements IPublisher { - private subscribers: Map; - private parent: P; - constructor(parent: P) { +export class Publisher implements IPublisher { + private subscribers: EventSubscriberRecord; + private parent: PublisherType; + + constructor(parent: PublisherType) { this.parent = parent; this.subscribers = new Map(); - this.subscribers.set("all", []); } - addSubscriber(subscriber: ISubscriber, eventType: (T | "all") | T[]): ISubscription { - let eventTypes: (T | "all")[] = []; - if (!Array.isArray(eventType)) { - eventTypes.push(eventType); + addSubscriber(subscriber: ISubscriber, subscribeTo: EventType | EventType[]): ISubscription { + let eventTypes: EventType[] = []; + if (!Array.isArray(subscribeTo)) { + eventTypes.push(subscribeTo); } else { - eventTypes = eventType as (T | "all")[]; + eventTypes = subscribeTo; } for (const key of eventTypes) { this.getSubscribers(key).push(subscriber); @@ -47,17 +51,17 @@ export class Publisher implements IPublisher return new Subscription(this, subscriber, eventTypes); } - unbind(subscription: Subscription): void { + unbind(subscription: Subscription): void { for (const key of subscription.getEventTypes()) { const subs = this.getSubscribers(key); subs.splice(subs.indexOf(subscription.getSubscriber()), 1); } } - private getSubscribers(key: T | "all"): ISubscriber[] { + private getSubscribers(key: K): ISubscriber[] { const subscribersList = this.subscribers.get(key); if (subscribersList === undefined) { - const newList: ISubscriber[] = []; + const newList: ISubscriber[] = []; this.subscribers.set(key, newList); return newList; } else { @@ -65,18 +69,15 @@ export class Publisher implements IPublisher } } - notifySubs(eventType: T): void { + notifySubs(eventType: K): void { for (const sub of this.getSubscribers(eventType)) { sub.notify(this.parent, eventType); } - for (const sub of this.getSubscribers("all")) { - sub.notify(this.parent, eventType); - } } } -export interface IPublisher { - addSubscriber(subscriber: ISubscriber, eventType: (T | "all") | T[]): {unbind: () => void}; +export interface IPublisher { + addSubscriber(subscriber: ISubscriber, subscribeTo: T | T[]): {unbind: () => void}; } export interface ISubscription { diff --git a/src/Subscriber.ts b/src/Subscriber.ts index 2b24242..35afd2b 100644 --- a/src/Subscriber.ts +++ b/src/Subscriber.ts @@ -1,3 +1,4 @@ -export default interface ISubscriber { - notify(publisher: unknown, event: T | "all" | T[]): void; +export type LEvent = string; +export default interface ISubscriber { + notify(publisher: unknown, event: T): void; } \ No newline at end of file diff --git a/src/ui/Beat/BeatView.ts b/src/ui/Beat/BeatView.ts index 6d9733d..88b3046 100644 --- a/src/ui/Beat/BeatView.ts +++ b/src/ui/Beat/BeatView.ts @@ -1,6 +1,5 @@ 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"; @@ -9,7 +8,17 @@ export type BeatUINodeOptions = UINodeOptions & { beat: Beat, }; -export default class BeatView extends UINode implements ISubscriber { +const EventTypeSubscriptions = [ + BeatEvents.NewName, + BeatEvents.NewTimeSig, + BeatEvents.NewBarCount, + BeatEvents.DisplayTypeChanged, + BeatEvents.LoopLengthChanged, +]; + +type EventTypeSubscriptions = FlatArray; + +export default class BeatView extends UINode implements ISubscriber { private beat: Beat; private title!: HTMLHeadingElement; private beatUnitViews: BeatUnitView[] = []; @@ -34,20 +43,20 @@ export default class BeatView extends UINode implements ISubscriber { } private setupBindings() { - this.beat.addSubscriber(this, "all"); + this.beat.addSubscriber(this, EventTypeSubscriptions); } - notify(publisher: IPublisher, event: "all" | T[] | T): void { - if (event === BeatEvents.NewName) { + notify(publisher: unknown, event: EventTypeSubscriptions): void { + switch (event) { + case 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) { + break; + case BeatEvents.NewTimeSig: + case BeatEvents.NewBarCount: + case BeatEvents.DisplayTypeChanged: + case BeatEvents.LoopLengthChanged: this.setupBeatUnits(); + break; } } @@ -91,8 +100,9 @@ export default class BeatView extends UINode implements ISubscriber { } else { this.beatUnitViewBlock = UINode.make("div", { classes: ["beat-unit-block"], - subs: [...beatUnitNodes], - }); + }, [ + ...beatUnitNodes + ]); } } @@ -140,16 +150,14 @@ export default class BeatView extends UINode implements ISubscriber { } return UINode.make("div", { classes: ["beat"], - subs: [ - UINode.make("div", { - classes: ["beat-main"], - subs: [ - this.title, - this.beatUnitViewBlock, - ] - }), - ], - }); + }, [ + UINode.make("div", { + classes: ["beat-main"], + }, [ + this.title, + this.beatUnitViewBlock, + ]), + ]); } } diff --git a/src/ui/BeatGroup/BeatGroupView.ts b/src/ui/BeatGroup/BeatGroupView.ts index 83d9b8f..c987196 100644 --- a/src/ui/BeatGroup/BeatGroupView.ts +++ b/src/ui/BeatGroup/BeatGroupView.ts @@ -3,14 +3,18 @@ import BeatGroup, {BeatGroupEvents} from "@/BeatGroup"; import BeatView from "@/ui/Beat/BeatView"; import "./BeatGroup.css"; import ISubscriber from "@/Subscriber"; -import {IPublisher} from "@/Publisher"; export type BeatGroupUINodeOptions = UINodeOptions & { title: string, beatGroup: BeatGroup, }; -export default class BeatGroupView extends UINode implements ISubscriber { +const EventTypeSubscriptions = [ + BeatGroupEvents.BeatListChanged +]; +type EventTypeSubscriptions = FlatArray; + +export default class BeatGroupView extends UINode implements ISubscriber { private title: string; private beatGroup: BeatGroup; private beatViews: BeatView[] = []; @@ -22,7 +26,7 @@ export default class BeatGroupView extends UINode implements ISubscriber { this.beatGroup.addSubscriber(this, BeatGroupEvents.BeatListChanged); } - notify(publisher: IPublisher, event: "all" | T[] | T): void { + notify(publisher: unknown, event: EventTypeSubscriptions): void { if (event === BeatGroupEvents.BeatListChanged) { this.redraw(); } @@ -41,9 +45,8 @@ export default class BeatGroupView extends UINode implements ISubscriber { } return UINode.make("div", { classes: ["beat-group"], - subs: [ - ...this.beatViews.map(bv => bv.render()) - ], - }); + },[ + ...this.beatViews.map(bv => bv.render()) + ]); } } \ No newline at end of file diff --git a/src/ui/BeatGroupSettings/BeatGroupSettingsView.ts b/src/ui/BeatGroupSettings/BeatGroupSettingsView.ts index f868d75..a4290bd 100644 --- a/src/ui/BeatGroupSettings/BeatGroupSettingsView.ts +++ b/src/ui/BeatGroupSettings/BeatGroupSettingsView.ts @@ -1,9 +1,8 @@ import "./BeatGroupSettings.css"; import UINode, {UINodeOptions} from "@/ui/UINode"; import NumberInputView from "@/ui/Widgets/NumberInput/NumberInputView"; -import ISubscriber from "@/Subscriber"; +import ISubscriber, {SubscriptionEvent} from "@/Subscriber"; import BeatGroup, {BeatGroupEvents} from "@/BeatGroup"; -import {IPublisher} from "@/Publisher"; import {BeatEvents} from "@/Beat"; import BoolBoxView from "@/ui/Widgets/BoolBox/BoolBoxView"; import BeatSettingsView from "@/ui/BeatSettings/BeatSettingsView"; @@ -13,21 +12,22 @@ export type BeatGroupSettingsUINodeOptions = UINodeOptions & { beatGroup: BeatGroup, }; -export default class BeatGroupSettingsView extends UINode implements ISubscriber { +const EventTypeSubscriptions = [ + BeatGroupEvents.BarCountChanged, + BeatGroupEvents.TimeSigUpChanged, + BeatEvents.DisplayTypeChanged, + BeatGroupEvents.BeatListChanged, + BeatGroupEvents.LockingChanged, + BeatGroupEvents.AutoBeatSettingsChanged, +]; + +export default class BeatGroupSettingsView extends UINode implements ISubscriber { private beatGroup: BeatGroup; private barCountInput!: NumberInputView; private timeSigUpInput!: NumberInputView; private autoBeatLengthCheckbox!: BoolBoxView; private beatSettingsViews: BeatSettingsView[] = []; private beatSettingsContainer!: HTMLDivElement; - private static readonly EventTypeSubscriptions = [ - BeatGroupEvents.BarCountChanged, - BeatGroupEvents.TimeSigUpChanged, - BeatEvents.DisplayTypeChanged, - BeatGroupEvents.BeatListChanged, - BeatGroupEvents.LockingChanged, - BeatGroupEvents.AutoBeatSettingsChanged, - ]; constructor(options: BeatGroupSettingsUINodeOptions) { super(options); @@ -38,14 +38,14 @@ export default class BeatGroupSettingsView extends UINode implements ISubscriber setBeatGroup(newBeatGroup: BeatGroup): void { this.beatGroup = newBeatGroup; this.setupBindings(); - BeatGroupSettingsView.EventTypeSubscriptions.forEach(eventType => this.notify(null, eventType)); + EventTypeSubscriptions.forEach(eventType => this.notify(null, eventType)); } setupBindings(): void { - this.beatGroup.addSubscriber(this, BeatGroupSettingsView.EventTypeSubscriptions); + this.beatGroup.addSubscriber(this, EventTypeSubscriptions); } - notify(publisher: IPublisher | null, event: "all" | T[] | T): void { + notify(publisher: unknown, event: SubscriptionEvent): void { switch(event) { case BeatGroupEvents.BarCountChanged: this.barCountInput.setValue(this.beatGroup.getBarCount()); @@ -66,6 +66,8 @@ export default class BeatGroupSettingsView extends UINode implements ISubscriber case BeatGroupEvents.AutoBeatSettingsChanged: this.autoBeatLengthCheckbox.setValue(this.beatGroup.autoBeatLengthOn()); break; + case BeatEvents.DisplayTypeChanged: + break; } } @@ -109,36 +111,32 @@ export default class BeatGroupSettingsView extends UINode implements ISubscriber this.remakeBeatSettingsViews(); return UINode.make("div", { classes: ["beat-group-settings"], - subs: [ + }, [ + UINode.make("div", { + classes: ["beat-group-settings-options"], + }, [ UINode.make("div", { - classes: ["beat-group-settings-options"], - subs: [ - UINode.make("div", { - classes: ["beat-group-settings-boxes", "beat-group-settings-option"], - subs: [ - this.timeSigUpInput.render(), - ], - }), - UINode.make("div", { - classes: ["beat-group-settings-bar-count", "beat-group-settings-option"], - subs: [ - this.barCountInput.render(), - ], - }), - UINode.make("div", { - classes: ["beat-group-settings-bar-count", "beat-group-settings-option"], - subs: [ - this.autoBeatLengthCheckbox.render(), - ], - }), - new ActionButtonView({ - label: "New Track", - onClick: () => this.beatGroup.addBeat(), - }).render(), - this.beatSettingsContainer, - ], - }), - ], - }); + classes: ["beat-group-settings-boxes", "beat-group-settings-option"], + }, [ + this.timeSigUpInput.render(), + ]), + UINode.make("div", { + classes: ["beat-group-settings-bar-count", "beat-group-settings-option"] + , + }, [ + this.barCountInput.render(), + ]), + UINode.make("div", { + classes: ["beat-group-settings-bar-count", "beat-group-settings-option"], + }, [ + this.autoBeatLengthCheckbox.render(), + ]), + new ActionButtonView({ + label: "New Track", + onClick: () => this.beatGroup.addBeat(), + }).render(), + this.beatSettingsContainer, + ]), + ]); } } \ No newline at end of file diff --git a/src/ui/BeatSettings/BeatSettingsView.ts b/src/ui/BeatSettings/BeatSettingsView.ts index b3076ae..c32e2a6 100644 --- a/src/ui/BeatSettings/BeatSettingsView.ts +++ b/src/ui/BeatSettings/BeatSettingsView.ts @@ -1,8 +1,8 @@ import "./BeatSettings.css"; import Beat, {BeatEvents} from "@/Beat"; import UINode, {UINodeOptions} from "@/ui/UINode"; -import ISubscriber from "@/Subscriber"; -import {IPublisher, ISubscription} from "@/Publisher"; +import ISubscriber, {SubscriptionEvent} from "@/Subscriber"; +import {ISubscription} from "@/Publisher"; import NumberInputView from "@/ui/Widgets/NumberInput/NumberInputView"; import BoolBoxView from "@/ui/Widgets/BoolBox/BoolBoxView"; import ActionButtonView from "@/ui/Widgets/ActionButton/ActionButtonView"; @@ -11,7 +11,13 @@ export type BeatSettingsViewUINodeOptions = UINodeOptions & { beat: Beat, }; -export default class BeatSettingsView extends UINode implements ISubscriber { +const EventTypeSubscriptions = [ + BeatEvents.NewName, + BeatEvents.LoopLengthChanged, + BeatEvents.DisplayTypeChanged, +]; + +export default class BeatSettingsView extends UINode implements ISubscriber { private beat: Beat; private loopLengthInput!: NumberInputView; private bakeButton!: ActionButtonView; @@ -37,18 +43,19 @@ export default class BeatSettingsView extends UINode implements ISubscriber { this.sub.unbind(); this.beat = beat; this.setupBindings(); - this.notify(null, BeatEvents.NewName); - this.notify(null, BeatEvents.LoopLengthChanged); - this.notify(null, BeatEvents.DisplayTypeChanged); + EventTypeSubscriptions.forEach(eventType => this.notify(null, eventType)); } - notify(publisher: IPublisher | null, event: "all" | T[] | T): void { - if (event === BeatEvents.NewName) { + notify(publisher: unknown, event: SubscriptionEvent): void { + switch(event) { + case BeatEvents.NewName: this.titleInput.value = this.beat.getName(); this.titleDisplay.innerText = this.beat.getName(); - } else if (event === BeatEvents.LoopLengthChanged) { + break; + case BeatEvents.LoopLengthChanged: this.loopLengthInput.setValue(this.beat.getLoopLength()); - } else if (event === BeatEvents.DisplayTypeChanged) { + break; + case BeatEvents.DisplayTypeChanged: this.loopCheckbox.setValue(this.beat.isLooping()); this.bakeButton.setDisabled(!this.beat.isLooping()); if (this.beat.isLooping()) { @@ -56,6 +63,7 @@ export default class BeatSettingsView extends UINode implements ISubscriber { } else { this.loopLengthSection.classList.add("hide"); } + break; } } @@ -102,10 +110,9 @@ export default class BeatSettingsView extends UINode implements ISubscriber { }); this.loopLengthSection = UINode.make("div", { classes: ["loop-settings-option"], - subs: [ - this.loopLengthInput.render(), - ], - }); + }, [ + this.loopLengthInput.render(), + ]); if (this.beat.isLooping()) { this.loopLengthSection.classList.remove("hide"); } else { @@ -113,28 +120,25 @@ export default class BeatSettingsView extends UINode implements ISubscriber { } return UINode.make("div", { classes: ["beat-settings"], - subs: [ - this.titleDisplay, + }, [ + this.titleDisplay, + UINode.make("div", { + classes: ["beat-settings-lower"], + }, [ + this.bakeButton.render(), + new ActionButtonView({ + icon: "trash", + type: "secondary", + alt: "Delete Track", + onClick: () => this.beat.delete(), + }).render(), UINode.make("div", { - classes: ["beat-settings-lower"], - subs: [ - this.bakeButton.render(), - new ActionButtonView({ - icon: "trash", - type: "secondary", - alt: "Delete Track", - onClick: () => this.beat.delete(), - }).render(), - UINode.make("div", { - classes: ["loop-settings"], - subs: [ - this.loopCheckbox.render(), - ] - }), - this.loopLengthSection, - ] - }), - ], - }); + classes: ["loop-settings"], + }, [ + this.loopCheckbox.render(), + ]), + this.loopLengthSection, + ]), + ]); } } diff --git a/src/ui/BeatUnit/BeatUnitView.ts b/src/ui/BeatUnit/BeatUnitView.ts index c4d452f..8107705 100644 --- a/src/ui/BeatUnit/BeatUnitView.ts +++ b/src/ui/BeatUnit/BeatUnitView.ts @@ -1,4 +1,4 @@ -import BeatUnit, {BeatUnitEvents, BeatUnitType} from "@/BeatUnit"; +import BeatUnit, {BeatUnitEvent, BeatUnitType} from "@/BeatUnit"; import ISubscriber from "@/Subscriber"; import UINode, {UINodeOptions} from "@/ui/UINode"; import {IPublisher, ISubscription, Publisher} from "@/Publisher"; @@ -8,10 +8,17 @@ export type BeatUnitUINodeOptions = UINodeOptions & { beatUnit: BeatUnit, }; -export default class BeatUnitView extends UINode implements ISubscriber { +const EventTypeSubscriptions = [ + BeatUnitEvent.On, + BeatUnitEvent.Off, + BeatUnitEvent.TypeChange, +]; +type EventTypeSubscriptions = FlatArray; + +export default class BeatUnitView extends UINode implements ISubscriber { private beatUnit: BeatUnit; private subscription: ISubscription | null = null; - private publisher: IPublisher = new Publisher(this); + private publisher: IPublisher = new Publisher(this); private touchTimeout: ReturnType | null = null; @@ -24,13 +31,13 @@ export default class BeatUnitView extends UINode implements ISubscriber { setUnit(beatUnit: BeatUnit): void { this.beatUnit = beatUnit; this.setupBindings(); - this.notify(this.publisher, beatUnit.isOn() ? BeatUnitEvents.On : BeatUnitEvents.Off); - this.notify(this.publisher, BeatUnitEvents.TypeChange); + this.notify(this.publisher, beatUnit.isOn() ? BeatUnitEvent.On : BeatUnitEvent.Off); + this.notify(this.publisher, BeatUnitEvent.TypeChange); } private setupBindings() { this.subscription?.unbind(); - this.subscription = this.beatUnit.addSubscriber(this, "all"); + this.subscription = this.beatUnit.addSubscriber(this, EventTypeSubscriptions); this.onMouseDown((ev: MouseEvent) => { if (ev.button === 1) { this.beatUnit.rotateType(); @@ -40,7 +47,7 @@ export default class BeatUnitView extends UINode implements ISubscriber { this.touchTimeout = setTimeout(() => { this.beatUnit.rotateType(); this.touchTimeout = null; - }, 600); + }, 400); }); this.getNode().addEventListener("touchend", () => { if (this.touchTimeout) { @@ -62,12 +69,15 @@ export default class BeatUnitView extends UINode implements ISubscriber { this.beatUnit.setOn(false); } - notify(publisher: IPublisher, event: "all" | T[] | T): void { - if (event === BeatUnitEvents.On) { + notify(publisher: unknown, event: EventTypeSubscriptions): void { + switch (event) { + case BeatUnitEvent.On: this.getNode().classList.add("beat-unit-on"); - } else if (event === BeatUnitEvents.Off) { + break; + case BeatUnitEvent.Off: this.getNode().classList.remove("beat-unit-on"); - } else if (event === BeatUnitEvents.TypeChange) { + break; + case BeatUnitEvent.TypeChange: switch (this.beatUnit.getType()) { case BeatUnitType.Normal: this.getNode().classList.remove("beat-unit-ghost"); @@ -82,6 +92,7 @@ export default class BeatUnitView extends UINode implements ISubscriber { this.getNode().classList.add("beat-unit-accent"); break; } + break; } } diff --git a/src/ui/Root/RootView.ts b/src/ui/Root/RootView.ts index ce4e038..a65b7c5 100644 --- a/src/ui/Root/RootView.ts +++ b/src/ui/Root/RootView.ts @@ -15,13 +15,13 @@ export default class RootView extends UINode { private beatGroupView: BeatGroupView; private mainBeatGroup: BeatGroup; private beatGroupSettingsView!: BeatGroupSettingsView; - private sidebar!: HTMLDivElement; constructor(options: RootUINodeOptions) { super(options); this.mainBeatGroup = options.mainBeatGroup ?? RootView.defaultMainBeatGroup(); this.beatGroupView = new BeatGroupView({title: options.title, beatGroup: this.mainBeatGroup}); + this.beatGroupSettingsView = new BeatGroupSettingsView({beatGroup: this.mainBeatGroup}); this.title = options.title; } @@ -47,68 +47,77 @@ export default class RootView extends UINode { this.getNode().classList.toggle("vertical-mode"); } - build(): HTMLElement { - this.beatGroupSettingsView = new BeatGroupSettingsView({beatGroup: this.mainBeatGroup}); - const sidebarMain = UINode.make("div", { - classes: ["root-settings"], - subs: [ - UINode.make("h1", {innerText: this.title, classes: ["root-title"]}), - this.beatGroupSettingsView.render(), - ] - }); - const sidebarStrip = UINode.make("div", { - classes: ["root-sidebar-toggle"], - subs: [ - UINode.make("div", { - classes: ["root-quick-access-button"], - subs: [new IconView({iconName: "list", color: "var(--color-ui-neutral-dark)"}).render()], - onclick: () => this.toggleSidebar(), - }), - UINode.make("div", { - classes: ["root-quick-access-button"], - subs: [new IconView({iconName: "arrowClockwise", color: "var(--color-ui-neutral-dark)"}).render()], - onclick: () => this.toggleOrientation(), - }), - UINode.make("div", { - classes: ["root-quick-access-button"], - subs: [new IconView({iconName: "snowflake", color: "var(--color-ui-neutral-dark)"}).render()], - onclick: () => this.mainBeatGroup.bakeLoops(), - }), - UINode.make("div", { - classes: ["root-quick-access-button"], - title: "Reset all", - subs: [new IconView({iconName: "trash", color: "var(--color-ui-neutral-dark)"}).render()], - onclick: () => { - this.mainBeatGroup = RootView.defaultMainBeatGroup(); - this.beatGroupSettingsView.setBeatGroup(this.mainBeatGroup); - this.beatGroupView.setBeatGroup(this.mainBeatGroup); - }, - }), - ] - }); - this.sidebar = UINode.make("div", { - classes: ["root-sidebar"], - subs: [ - sidebarMain, - sidebarStrip, - ] - }); + private buildSidebarStrip(): HTMLElement { return UINode.make("div", { - classes: ["root", "sidebar-visible"], - subs: [ - this.sidebar, - UINode.make("div", { - classes: ["root-beat-stage-container"], - subs: [ - UINode.make("div", { - classes: ["root-beat-stage"], - subs: [ - this.beatGroupView.render(), - ], - }) - ] - }) - ], - }); + classes: ["root-sidebar-toggle"], + }, [ + UINode.make("div", { + classes: ["root-quick-access-button"], + onclick: () => this.toggleSidebar(), + }, [ + new IconView({ + iconName: "list", + color: "var(--color-ui-neutral-dark)" + }).render() + ]), + UINode.make("div", { + classes: ["root-quick-access-button"], + onclick: () => this.toggleOrientation(), + }, [ + new IconView({ + iconName: "arrowClockwise", + color: "var(--color-ui-neutral-dark)" + }).render(), + ]), + UINode.make("div", { + classes: ["root-quick-access-button"], + onclick: () => this.mainBeatGroup.bakeLoops(), + }, [ + new IconView({ + iconName: "snowflake", + color: "var(--color-ui-neutral-dark)" + }).render(), + ]), + UINode.make("div", { + classes: ["root-quick-access-button"], + title: "Reset all", + onclick: () => { + this.mainBeatGroup = RootView.defaultMainBeatGroup(); + this.beatGroupSettingsView.setBeatGroup(this.mainBeatGroup); + this.beatGroupView.setBeatGroup(this.mainBeatGroup); + }, + }, [ + new IconView({ + iconName: "trash", + color: "var(--color-ui-neutral-dark)" + }).render() + ]), + ]); + } + + private buildSidebar(): HTMLElement { + return ( + UINode.make("div", {classes: ["root-sidebar"]}, [ + UINode.make("div", {classes: ["root-settings"]}, [ + UINode.make("h1", {classes: ["root-title"], innerText: this.title}, [ + this.beatGroupSettingsView.render(), + ]), + this.buildSidebarStrip(), + ]), + ]) + ); + } + + build(): HTMLElement { + return ( + UINode.make("div", {classes: ["root", "sidebar-visible"]}, [ + this.buildSidebar(), + UINode.make("div", {classes: ["root-beat-stage-container"]}, [ + UINode.make("div", {classes: ["root-beat-stage"]}, [ + this.beatGroupView.render(), + ]) + ]) + ]) + ); } } \ No newline at end of file diff --git a/src/ui/UINode.ts b/src/ui/UINode.ts index d8a4993..daaa393 100644 --- a/src/ui/UINode.ts +++ b/src/ui/UINode.ts @@ -50,20 +50,22 @@ export default abstract class UINode { T extends keyof HTMLElementTagNameMap, K extends keyof HTMLElementTagNameMap[T]>( type: T, - attributes: IRenderAttributes + attributes: IRenderAttributes, + subElements?: HTMLElement[], ): HTMLElementTagNameMap[T] { const element = document.createElement(type); if (attributes) { for (const key in attributes) { if (key === "classes") { element.classList.add(...attributes[key]!); - } else if (key === "subs") { - element.append(...attributes.subs!); } else { element[key as keyof HTMLElementTagNameMap[T]] = (attributes as any)[key]; } } } + if (subElements) { + element.append(...subElements); + } return element; } diff --git a/webpack.config.js b/webpack.config.js index 5d8c089..e40e320 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -50,14 +50,14 @@ const webpackConfig = { output: { filename: "bundle.js", - publicPath: "./static/", + publicPath: `${config.baseUrl}/static/`, path: path.resolve(__dirname, "./public/static"), }, devServer: { static: { directory: path.join(__dirname, "./public"), - publicPath: "/", + publicPath: `${config.baseUrl}/`, }, hot: true, compress: true,