feat: new UI and build process
Some checks are pending
Gitea djledda.de/arne-drums/pipeline/head Build started...

This commit is contained in:
Daniel Ledda
2021-08-29 16:21:26 +02:00
parent f2bcc81330
commit ec4587bed5
30 changed files with 769 additions and 12596 deletions

View File

View File

@@ -0,0 +1,7 @@
.beatSettingsView {
display: none;
}
.beatSettingsView.visible {
display: block;
}

View File

@@ -0,0 +1,58 @@
import UINode, {UINodeOptions} from "../../../UINode";
import Beat, {BeatEvents} from "../../../../Beat";
import {IPublisher} from "../../../../Publisher";
import "./BeatSettingsView.css";
import ISubscriber from "../../../../Subscriber";
export type BeatSettingsViewUINodeOptions = UINodeOptions & {
beat: Beat,
};
export default class BeatSettingsView extends UINode implements ISubscriber {
private beat: Beat;
private visible = false;
private timeSigUp: HTMLInputElement | null = null;
private timeSigDown: HTMLInputElement | null = null;
constructor(options: BeatSettingsViewUINodeOptions) {
super(options);
this.beat = options.beat;
this.setupBindings();
}
private setupBindings() {
this.beat.addSubscriber(this, BeatEvents.NewName);
}
notify<T extends string | number>(publisher: IPublisher<T>, event: "all" | T[] | T) {
if (event === BeatEvents.NewTimeSig) {
if (this.timeSigUp && this.timeSigDown) {
this.timeSigUp.value = this.beat.getTimeSigUp().toString();
this.timeSigDown.value = this.beat.getTimeSigDown().toString();
}
}
}
toggleVisible() {
this.visible = !this.visible;
if (this.visible) {
this.node?.classList.add("visible");
} else {
this.node?.classList.remove("visible");
}
}
isOpen() {
return this.visible;
}
rebuild(): HTMLElement {
this.node = UINode.make("div", {
subs: [
UINode.make("p", {innerText: `Settings for ${this.beat.getName()}`}),
],
classes: ["beatSettingsView"]
});
return this.node;
}
}

View File

@@ -0,0 +1,54 @@
import UINode, {UINodeOptions} from "../../UINode";
import Beat, {BeatEvents} from "../../../Beat";
import {IPublisher} from "../../../Publisher";
import BeatSettingsView from "./BeatSettings/BeatSettingsView";
import ISubscriber from "../../../Subscriber";
export type BeatUINodeOptions = UINodeOptions & {
beat: Beat,
};
export default class BeatView extends UINode implements ISubscriber {
private beat: Beat;
private title!: HTMLHeadingElement;
private settingsView!: BeatSettingsView;
private settingsToggleButton!: HTMLButtonElement;
constructor(options: BeatUINodeOptions) {
super(options);
this.beat = options.beat;
this.setupBindings();
this.rebuild();
}
private setupBindings() {
this.beat.addSubscriber(this, BeatEvents.NewName);
}
notify<T extends string | number>(publisher: IPublisher<T>, event: "all" | T[] | T) {
if (event === BeatEvents.NewName) {
this.title.innerText = this.beat.getName();
}
}
private toggleSettings() {
this.settingsView.toggleVisible();
this.settingsToggleButton.innerText = this.settingsView.isOpen() ? "Hide Settings" : "Show Settings";
}
rebuild(): HTMLElement {
this.title = UINode.make("h3", {innerText: this.beat.getName()});
this.settingsView = new BeatSettingsView({beat: this.beat});
this.settingsToggleButton = UINode.make("button", {innerText: this.settingsView.isOpen() ? "Hide Settings" : "Show Settings"});
this.settingsToggleButton.addEventListener("click", () => this.toggleSettings());
this.node = UINode.make("div", {
subs: [
this.title,
UINode.make("p", {innerText: "I am a BeatGroup"}),
this.settingsToggleButton,
this.settingsView.rebuild(),
],
});
return this.node;
}
}

View File

View File

@@ -0,0 +1,32 @@
import UINode, {UINodeOptions} from "../UINode";
import BeatGroup from "../../BeatGroup";
import BeatView from "./Beat/BeatView";
export type BeatGroupUINodeOptions = UINodeOptions & {
title: string,
beatGroup: BeatGroup,
};
export default class BeatGroupView extends UINode {
private title: string;
private beatGroup: BeatGroup;
constructor(options: BeatGroupUINodeOptions) {
super(options);
this.beatGroup = options.beatGroup;
this.title = options.title;
}
rebuild(): HTMLDivElement {
const beatViews = [];
for (let i = 0; i < this.beatGroup.getBeatCount(); i++) {
beatViews.push(new BeatView({beat: this.beatGroup.getBeatByIndex(i)}));
}
return UINode.make("div", {
subs: [
UINode.make("h3", {innerText: this.title}),
...beatViews.map(bv => bv.rebuild())
],
});
}
}

8
src/ui/Root/Root.css Normal file
View File

@@ -0,0 +1,8 @@
.rootView {
margin: auto;
width: 80%;
}
.rootView .title {
text-align: center;
}

46
src/ui/Root/RootView.ts Normal file
View File

@@ -0,0 +1,46 @@
import UINode, {UINodeOptions} from "../UINode";
import BeatGroupView from "../BeatGroup/BeatGroupView";
import BeatGroup from "../../BeatGroup";
import "./Root.css";
export type RootUINodeOptions = UINodeOptions & {
title: string,
mainBeatGroup: BeatGroup,
parent: HTMLElement,
};
export default class RootView extends UINode {
private title: string;
private parent: HTMLElement;
private beatGroupView: BeatGroupView;
private mainBeatGroup: BeatGroup;
constructor(options: RootUINodeOptions) {
super(options);
this.beatGroupView = new BeatGroupView({title: "THE BEAT", beatGroup: options.mainBeatGroup});
this.mainBeatGroup = options.mainBeatGroup;
this.title = options.title;
this.parent = options.parent;
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", {
subs: [
UINode.make("h1", {innerText: this.title, classes: ["title"]}),
this.beatGroupView.rebuild(),
],
classes: ["rootView"]
});
}
}

41
src/ui/UINode.ts Normal file
View File

@@ -0,0 +1,41 @@
export type UINodeOptions = {
};
type IRenderAttributes<
T extends keyof HTMLElementTagNameMap,
K extends keyof HTMLElementTagNameMap[T]
> = Partial<Record<K, HTMLElementTagNameMap[T][K]> & {
classes: string[],
subs: HTMLElement[],
}>;
export default abstract class UINode {
protected node: HTMLElement | null = null;
constructor(options: UINodeOptions) {
}
abstract rebuild(): HTMLElement;
static make<
T extends keyof HTMLElementTagNameMap,
K extends keyof HTMLElementTagNameMap[T]>(
type: T,
attributes: IRenderAttributes<T, K>
): 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];
}
}
}
return element;
}
}