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:
@@ -2,7 +2,7 @@ import UINode, {UINodeOptions} from "@/ui/UINode";
|
||||
import Beat, {BeatEvents} from "@/Beat";
|
||||
import {IPublisher} from "@/Publisher";
|
||||
import ISubscriber from "@/Subscriber";
|
||||
import BeatUnitView from "./BeatUnit/BeatUnitView";
|
||||
import BeatUnitView from "@/ui/BeatUnit/BeatUnitView";
|
||||
import "./Beat.css";
|
||||
|
||||
export type BeatUINodeOptions = UINodeOptions & {
|
||||
@@ -1,37 +0,0 @@
|
||||
.beat-unit {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
margin-right: 4px;
|
||||
background-color: var(--color-ui-neutral-dark);
|
||||
border-width: 0.1em 0.1em 0.1em 0.1em;
|
||||
border-color: var(--color-ui-neutral-dark);
|
||||
border-style: solid;
|
||||
display: inline-block;
|
||||
transition: background-color 150ms;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vertical-mode .beat-unit {
|
||||
margin-bottom: 4px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.beat-unit:hover {
|
||||
border-color: var(--color-ui-neutral-dark-hover);
|
||||
background-color: var(--color-ui-neutral-dark-hover);
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.beat-unit.beat-unit-ghost:hover {
|
||||
background-color: #c9e2c9;
|
||||
}
|
||||
|
||||
.beat-unit.beat-unit-on {
|
||||
background-color: var(--color-ui-accent);
|
||||
border-color: var(--color-ui-accent);
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.beat-unit.beat-unit-on.beat-unit-ghost {
|
||||
background-color: darkseagreen;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import UINode, {UINodeOptions} from "@/ui/UINode";
|
||||
import BeatGroup, {BeatGroupEvents} from "@/BeatGroup";
|
||||
import BeatView from "./Beat/BeatView";
|
||||
import BeatView from "@/ui/Beat/BeatView";
|
||||
import "./BeatGroup.css";
|
||||
import ISubscriber from "@/Subscriber";
|
||||
import {IPublisher} from "@/Publisher";
|
||||
@@ -28,6 +28,12 @@ export default class BeatGroupView extends UINode implements ISubscriber {
|
||||
}
|
||||
}
|
||||
|
||||
setBeatGroup(newBeatGroup: BeatGroup): void {
|
||||
this.beatGroup = newBeatGroup;
|
||||
this.beatGroup.addSubscriber(this, BeatGroupEvents.BeatListChanged);
|
||||
this.redraw();
|
||||
}
|
||||
|
||||
build(): HTMLDivElement {
|
||||
this.beatViews = [];
|
||||
for (let i = 0; i < this.beatGroup.getBeatCount(); i++) {
|
||||
|
||||
@@ -20,35 +20,52 @@ export default class BeatGroupSettingsView extends UINode implements ISubscriber
|
||||
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);
|
||||
this.beatGroup = options.beatGroup;
|
||||
this.beatGroup.addSubscriber(this, [
|
||||
BeatGroupEvents.BarCountChanged,
|
||||
BeatGroupEvents.TimeSigUpChanged,
|
||||
BeatEvents.DisplayTypeChanged,
|
||||
BeatGroupEvents.BeatListChanged,
|
||||
BeatGroupEvents.LockingChanged,
|
||||
BeatGroupEvents.AutoBeatSettingsChanged,
|
||||
]);
|
||||
this.setupBindings();
|
||||
}
|
||||
|
||||
notify<T extends string | number>(publisher: IPublisher<T>, event: "all" | T[] | T): void {
|
||||
if (event === BeatGroupEvents.BarCountChanged) {
|
||||
setBeatGroup(newBeatGroup: BeatGroup): void {
|
||||
this.beatGroup = newBeatGroup;
|
||||
this.setupBindings();
|
||||
BeatGroupSettingsView.EventTypeSubscriptions.forEach(eventType => this.notify(null, eventType));
|
||||
}
|
||||
|
||||
setupBindings(): void {
|
||||
this.beatGroup.addSubscriber(this, BeatGroupSettingsView.EventTypeSubscriptions);
|
||||
}
|
||||
|
||||
notify<T extends string | number>(publisher: IPublisher<T> | null, event: "all" | T[] | T): void {
|
||||
switch(event) {
|
||||
case BeatGroupEvents.BarCountChanged:
|
||||
this.barCountInput.setValue(this.beatGroup.getBarCount());
|
||||
} else if (event === BeatGroupEvents.TimeSigUpChanged) {
|
||||
break;
|
||||
case BeatGroupEvents.TimeSigUpChanged:
|
||||
this.timeSigUpInput.setValue(this.beatGroup.getTimeSigUp());
|
||||
} else if (event === BeatGroupEvents.BeatListChanged) {
|
||||
break;
|
||||
case BeatGroupEvents.BeatListChanged:
|
||||
this.remakeBeatSettingsViews();
|
||||
} else if (event === BeatGroupEvents.LockingChanged) {
|
||||
break;
|
||||
case BeatGroupEvents.LockingChanged:
|
||||
if (this.beatGroup.barsLocked()) {
|
||||
this.barCountInput.disable();
|
||||
} else {
|
||||
this.barCountInput.enable();
|
||||
}
|
||||
} else if (event === BeatGroupEvents.AutoBeatSettingsChanged) {
|
||||
break;
|
||||
case BeatGroupEvents.AutoBeatSettingsChanged:
|
||||
this.autoBeatLengthCheckbox.setValue(this.beatGroup.autoBeatLengthOn());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
55
src/ui/BeatUnit/BeatUnit.css
Normal file
55
src/ui/BeatUnit/BeatUnit.css
Normal file
@@ -0,0 +1,55 @@
|
||||
.beat-unit {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
margin-right: 4px;
|
||||
background-color: #464646;
|
||||
border-color: #464646;
|
||||
border-width: 0.1em 0.1em 0.1em 0.1em;
|
||||
border-style: solid;
|
||||
display: inline-block;
|
||||
transition: background-color 100ms, border-color 100ms;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.beat-unit:hover {
|
||||
border-color: #5f5f5f;
|
||||
background-color: #5f5f5f;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.vertical-mode .beat-unit {
|
||||
margin-bottom: 4px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.beat-unit.beat-unit-on {
|
||||
border-color: var(--color-ui-accent);
|
||||
background-color: var(--color-ui-accent);
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.beat-unit.beat-unit-on:hover {
|
||||
border-color: var(--color-ui-accent-hover);
|
||||
background-color: var(--color-ui-accent-hover);
|
||||
}
|
||||
|
||||
.beat-unit.beat-unit-on.beat-unit-ghost {
|
||||
border-color: var(--color-ui-neutral-light);
|
||||
background-color: var(--color-ui-accent);
|
||||
}
|
||||
|
||||
.beat-unit.beat-unit-on.beat-unit-ghost:hover {
|
||||
border-color: var(--color-ui-neutral-light);
|
||||
background-color: var(--color-ui-accent-hover);
|
||||
}
|
||||
|
||||
.beat-unit.beat-unit-on.beat-unit-accent {
|
||||
border-color: var(--color-ui-accent);
|
||||
background-color: var(--color-ui-accent);
|
||||
opacity: 60%;
|
||||
}
|
||||
|
||||
.beat-unit.beat-unit-on.beat-unit-accent:hover {
|
||||
border-color: var(--color-ui-accent-hover);
|
||||
background-color: var(--color-ui-accent-hover);
|
||||
}
|
||||
@@ -12,6 +12,8 @@ export default class BeatUnitView extends UINode implements ISubscriber {
|
||||
private beatUnit: BeatUnit;
|
||||
private subscription: ISubscription | null = null;
|
||||
private publisher: IPublisher<BeatUnitEvents> = new Publisher<BeatUnitEvents, BeatUnitView>(this);
|
||||
private touchTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
|
||||
constructor(options: BeatUnitUINodeOptions) {
|
||||
super(options);
|
||||
@@ -29,10 +31,21 @@ export default class BeatUnitView extends UINode implements ISubscriber {
|
||||
private setupBindings() {
|
||||
this.subscription?.unbind();
|
||||
this.subscription = this.beatUnit.addSubscriber(this, "all");
|
||||
this.onMouseUp((ev: MouseEvent) => {
|
||||
this.onMouseDown((ev: MouseEvent) => {
|
||||
if (ev.button === 1) {
|
||||
const currentType = this.beatUnit.getType();
|
||||
this.beatUnit.setType(currentType === BeatUnitType.GhostNote ? BeatUnitType.Normal : BeatUnitType.GhostNote);
|
||||
this.beatUnit.rotateType();
|
||||
}
|
||||
});
|
||||
this.getNode().addEventListener("touchstart", () => {
|
||||
this.touchTimeout = setTimeout(() => {
|
||||
this.beatUnit.rotateType();
|
||||
this.touchTimeout = null;
|
||||
}, 600);
|
||||
});
|
||||
this.getNode().addEventListener("touchend", () => {
|
||||
if (this.touchTimeout) {
|
||||
clearTimeout(this.touchTimeout);
|
||||
this.touchTimeout = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -55,12 +68,19 @@ export default class BeatUnitView extends UINode implements ISubscriber {
|
||||
} else if (event === BeatUnitEvents.Off) {
|
||||
this.getNode().classList.remove("beat-unit-on");
|
||||
} else if (event === BeatUnitEvents.TypeChange) {
|
||||
const showingAsGhost = this.getNode().classList.contains("beat-unit-ghost");
|
||||
const isGhost = this.beatUnit.getType() === BeatUnitType.GhostNote;
|
||||
if (isGhost && !showingAsGhost) {
|
||||
this.getNode().classList.add("beat-unit-ghost");
|
||||
} else if (!isGhost && showingAsGhost) {
|
||||
switch (this.beatUnit.getType()) {
|
||||
case BeatUnitType.Normal:
|
||||
this.getNode().classList.remove("beat-unit-ghost");
|
||||
this.getNode().classList.remove("beat-unit-accent");
|
||||
break;
|
||||
case BeatUnitType.GhostNote:
|
||||
this.getNode().classList.remove("beat-unit-accent");
|
||||
this.getNode().classList.add("beat-unit-ghost");
|
||||
break;
|
||||
case BeatUnitType.Accent:
|
||||
this.getNode().classList.remove("beat-unit-ghost");
|
||||
this.getNode().classList.add("beat-unit-accent");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,14 +97,14 @@ export default class BeatUnitView extends UINode implements ISubscriber {
|
||||
}
|
||||
|
||||
onHover(cb: () => void): void {
|
||||
this.getNode().onmouseover = cb;
|
||||
this.getNode().addEventListener("mouseover", cb);
|
||||
}
|
||||
|
||||
onMouseDown(cb: (ev: MouseEvent) => void): void {
|
||||
this.getNode().onmousedown = cb;
|
||||
this.getNode().addEventListener("mousedown", cb);
|
||||
}
|
||||
|
||||
onMouseUp(cb: (ev: MouseEvent) => void): void {
|
||||
this.getNode().onmouseup = cb;
|
||||
this.getNode().addEventListener("mouseup", cb);
|
||||
}
|
||||
}
|
||||
@@ -64,11 +64,12 @@
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.root-hamburger, .root-switch-mode {
|
||||
.root-quick-access-button {
|
||||
right: 0;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
cursor: pointer;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.root-beat-stage-container {
|
||||
|
||||
@@ -7,7 +7,7 @@ import IconView from "@/ui/Widgets/Icon/IconView";
|
||||
|
||||
export type RootUINodeOptions = UINodeOptions & {
|
||||
title: string,
|
||||
mainBeatGroup: BeatGroup,
|
||||
mainBeatGroup?: BeatGroup,
|
||||
};
|
||||
|
||||
export default class RootView extends UINode {
|
||||
@@ -20,11 +20,25 @@ export default class RootView extends UINode {
|
||||
|
||||
constructor(options: RootUINodeOptions) {
|
||||
super(options);
|
||||
this.mainBeatGroup = options.mainBeatGroup;
|
||||
this.mainBeatGroup = options.mainBeatGroup ?? RootView.defaultMainBeatGroup();
|
||||
this.beatGroupView = new BeatGroupView({title: options.title, beatGroup: this.mainBeatGroup});
|
||||
this.title = options.title;
|
||||
}
|
||||
|
||||
static defaultMainBeatGroup(): BeatGroup {
|
||||
const defaultSettings = {
|
||||
barCount: 2,
|
||||
isLooping: false,
|
||||
timeSigUp: 8,
|
||||
};
|
||||
const mainBeatGroup = new BeatGroup(defaultSettings);
|
||||
mainBeatGroup.addBeat({name: "LF"});
|
||||
mainBeatGroup.addBeat({name: "LH"});
|
||||
mainBeatGroup.addBeat({name: "RH"});
|
||||
mainBeatGroup.addBeat({name: "RF"});
|
||||
return mainBeatGroup;
|
||||
}
|
||||
|
||||
toggleSidebar(): void {
|
||||
this.getNode().classList.toggle("sidebar-visible");
|
||||
}
|
||||
@@ -46,15 +60,30 @@ export default class RootView extends UINode {
|
||||
classes: ["root-sidebar-toggle"],
|
||||
subs: [
|
||||
UINode.make("div", {
|
||||
classes: ["root-hamburger"],
|
||||
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-switch-mode"],
|
||||
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", {
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
background-color: var(--color-ui-accent);
|
||||
color: var(--color-p-light);
|
||||
}
|
||||
.action-button.action-button-primary:hover:not.disabled {
|
||||
.action-button.action-button-primary:hover {
|
||||
background-color: var(--color-ui-accent-hover);
|
||||
color: var(--color-p-light-hover);
|
||||
}
|
||||
.action-button.action-button-primary:active:not.disabled {
|
||||
.action-button.action-button-primary:active {
|
||||
background-color: var(--color-ui-accent-active);
|
||||
color: var(--color-p-light-active);
|
||||
}
|
||||
@@ -27,11 +27,11 @@
|
||||
background-color: var(--color-ui-neutral-dark);
|
||||
color: var(--color-p-light);
|
||||
}
|
||||
.action-button.action-button-secondary:hover:not.disabled {
|
||||
.action-button.action-button-secondary:hover:not(.disabled) {
|
||||
background-color: var(--color-ui-neutral-dark-hover);
|
||||
color: var(--color-p-light-hover);
|
||||
}
|
||||
.action-button.action-button-secondary:active:not.disabled {
|
||||
.action-button.action-button-secondary:active:not(.disabled) {
|
||||
background-color: var(--color-ui-neutral-dark-active);
|
||||
color: var(--color-p-light-active);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user