feat: finished adding painting features, added eraser
This commit is contained in:
688
package-lock.json
generated
688
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,7 @@
|
||||
"@typescript-eslint/parser": "^5.26.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.16.0",
|
||||
"typescript": "^4.7.2",
|
||||
"vite": "^2.9.9"
|
||||
"typescript": "^4.9.4",
|
||||
"vite": "^3.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
import Track from "@/Track";
|
||||
import { IPublisher, ISubscriber, Publisher } from "@djledda/ladder";
|
||||
|
||||
export const enum TrackUnitType {
|
||||
Normal="tut-0",
|
||||
GhostNote="tut-1",
|
||||
Accent="tut-2",
|
||||
GhostNoteAccent="tut-3",
|
||||
}
|
||||
export const TrackUnitTypeList = [ "Normal", "GhostNote", "Accent", "GhostNoteAccent" ] as const;
|
||||
export type TrackUnitType = typeof TrackUnitTypeList[number];
|
||||
|
||||
export type TrackUnitStickingType =
|
||||
| "none"
|
||||
| "lh"
|
||||
| "rh"
|
||||
| "lf"
|
||||
| "rf";
|
||||
export const TrackUnitStickingTypeList = [ "none", "lh", "rh", "lf", "rf" ] as const;
|
||||
export type TrackUnitStickingType = typeof TrackUnitStickingTypeList[number];
|
||||
|
||||
export const enum TrackUnitEvent {
|
||||
Toggle="tue-0",
|
||||
@@ -23,12 +15,6 @@ export const enum TrackUnitEvent {
|
||||
}
|
||||
|
||||
export default class TrackUnit implements IPublisher<TrackUnitEvent> {
|
||||
private static readonly TypeRotation = [
|
||||
TrackUnitType.Normal,
|
||||
TrackUnitType.GhostNote,
|
||||
TrackUnitType.Accent,
|
||||
TrackUnitType.GhostNoteAccent,
|
||||
] as const;
|
||||
private publisher: Publisher<TrackUnitEvent, TrackUnit> = new Publisher<TrackUnitEvent, TrackUnit>(this);
|
||||
private on = false;
|
||||
private typeIndex = 0;
|
||||
@@ -42,7 +28,7 @@ export default class TrackUnit implements IPublisher<TrackUnitEvent> {
|
||||
}) {
|
||||
this.parent = options.parent;
|
||||
this.on = options.on ?? false;
|
||||
this.setType(options.type ?? TrackUnitType.Normal);
|
||||
this.setType(options.type ?? "Normal");
|
||||
}
|
||||
|
||||
addSubscriber(subscriber: ISubscriber<TrackUnitEvent>, eventType: TrackUnitEvent[]): { unbind: () => void } {
|
||||
@@ -67,13 +53,12 @@ export default class TrackUnit implements IPublisher<TrackUnitEvent> {
|
||||
}
|
||||
|
||||
setType(type: TrackUnitType): void {
|
||||
this.typeIndex = TrackUnit.TypeRotation.indexOf(type);
|
||||
this.typeIndex = TrackUnitTypeList.indexOf(type);
|
||||
this.publisher.notifySubs(TrackUnitEvent.TypeChange);
|
||||
this.parent.alertDeepChange();
|
||||
}
|
||||
|
||||
setStickingType(type: TrackUnitStickingType) {
|
||||
console.log('set stickin');
|
||||
this.stickingType = type;
|
||||
this.publisher.notifySubs(TrackUnitEvent.TypeChange);
|
||||
this.parent.alertDeepChange();
|
||||
@@ -84,11 +69,11 @@ export default class TrackUnit implements IPublisher<TrackUnitEvent> {
|
||||
}
|
||||
|
||||
getType(): TrackUnitType {
|
||||
return TrackUnit.TypeRotation[this.typeIndex];
|
||||
return TrackUnitTypeList[this.typeIndex];
|
||||
}
|
||||
|
||||
rotateType(): void {
|
||||
if (this.typeIndex === TrackUnit.TypeRotation.length - 1) {
|
||||
if (this.typeIndex === TrackUnitTypeList.length - 1) {
|
||||
this.typeIndex = 0;
|
||||
} else {
|
||||
this.typeIndex += 1;
|
||||
|
||||
@@ -138,23 +138,6 @@
|
||||
transition: background-color 200ms;
|
||||
}
|
||||
|
||||
.root-toolbox {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.root-toolbox .toolbox-button {
|
||||
padding: 1em;
|
||||
cursor: pointer;
|
||||
color: black;
|
||||
background-color: var(--color-ui-neutral-dark);
|
||||
}
|
||||
.root-toolbox .toolbox-button:hover {
|
||||
background-color: var(--color-ui-neutral-dark-hover);
|
||||
}
|
||||
.root-toolbox .toolbox-button.active {
|
||||
background-color: var(--color-ui-neutral-dark-active);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.sidebar-visible .root-sidebar {
|
||||
left: 0;
|
||||
|
||||
@@ -181,8 +181,8 @@ export default class RootView extends Rung<HTMLDivElement> {
|
||||
<div classes={["root", "sidebar-visible"]}>
|
||||
<this.Sidebar/>
|
||||
<div className={"root-beat-stage-container"}>
|
||||
{this.toolboxView}
|
||||
<div className={"root-beat-stage"}>
|
||||
{this.toolboxView}
|
||||
{this.beatView}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -24,7 +24,6 @@ export default class TrackView extends Rung implements ISubscriber<EventTypeSubs
|
||||
private title: HTMLHeadingElement;
|
||||
private trackUnitViews: TrackUnitView[] = [];
|
||||
private trackUnitViewBlock: HTMLElement | null = null;
|
||||
private lastHoveredTrackUnitView: TrackUnitView | null = null;
|
||||
private sub: ISubscription | null = null;
|
||||
private state: AppState;
|
||||
static deselectingUnits = false;
|
||||
@@ -79,8 +78,8 @@ export default class TrackView extends Rung implements ISubscriber<EventTypeSubs
|
||||
} else {
|
||||
view = new TrackUnitView({ trackUnit });
|
||||
this.trackUnitViews.push(view);
|
||||
view.onHover(() => this.onTrackUnitViewHover(view));
|
||||
view.onMouseDown((event: MouseEvent) => this.onTrackUnitClick(event.button, i));
|
||||
view.onHover(() => this.applyCurrentToolToTrackUnit(view));
|
||||
view.onMouseDown((event: MouseEvent) => this.onTrackUnitClick(event.button, view));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,28 +87,32 @@ export default class TrackView extends Rung implements ISubscriber<EventTypeSubs
|
||||
deadViews.forEach(trackUnitView => trackUnitView.setUnit(null));
|
||||
}
|
||||
|
||||
private onTrackUnitClick(button: number, index: number) {
|
||||
private onTrackUnitClick(button: number, view: TrackUnitView) {
|
||||
if (button === 0) {
|
||||
TrackView.selectingUnits = true;
|
||||
} else if (button === 2) {
|
||||
TrackView.deselectingUnits = true;
|
||||
}
|
||||
this.applyCurrentToolToTrackUnit(view);
|
||||
}
|
||||
|
||||
private onTrackUnitViewHover(trackUnitView: TrackUnitView) {
|
||||
this.lastHoveredTrackUnitView = trackUnitView;
|
||||
if (TrackView.selectingUnits) {
|
||||
if (this.state.selectedTool === 'sticking') {
|
||||
this.lastHoveredTrackUnitView.setStickingType(this.state.activeStickingType);
|
||||
} else if (this.state.selectedTool === 'track-unit-type') {
|
||||
this.lastHoveredTrackUnitView.turnOn();
|
||||
this.lastHoveredTrackUnitView.setType(this.state.activeTrackUnitType);
|
||||
private applyCurrentToolToTrackUnit(trackUnitView: TrackUnitView) {
|
||||
if (this.state.selectedTool === "sticking") {
|
||||
if (TrackView.selectingUnits) {
|
||||
trackUnitView.setStickingType(this.state.activeStickingType);
|
||||
} else if (TrackView.deselectingUnits) {
|
||||
trackUnitView.setStickingType("none");
|
||||
}
|
||||
} else if (TrackView.deselectingUnits) {
|
||||
if (this.state.selectedTool === 'sticking') {
|
||||
this.lastHoveredTrackUnitView.setStickingType("none");
|
||||
} else if (this.state.selectedTool === 'track-unit-type') {
|
||||
this.lastHoveredTrackUnitView.turnOff();
|
||||
} else if (this.state.selectedTool === "track-unit-type") {
|
||||
if (TrackView.selectingUnits) {
|
||||
trackUnitView.turnOn();
|
||||
trackUnitView.setType(this.state.activeTrackUnitType);
|
||||
} else if (TrackView.deselectingUnits) {
|
||||
trackUnitView.turnOff();
|
||||
}
|
||||
} else if (this.state.selectedTool === "eraser") {
|
||||
if (TrackView.selectingUnits || TrackView.deselectingUnits) {
|
||||
trackUnitView.turnOff();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.track-unit:hover {
|
||||
.track-unit.highlightable:hover {
|
||||
border-color: #5f5f5f;
|
||||
background-color: #5f5f5f;
|
||||
transition: none;
|
||||
@@ -22,34 +22,28 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.track-unit.track-unit-on {
|
||||
.track-unit.on {
|
||||
border-color: var(--color-ui-accent);
|
||||
background-color: var(--color-ui-accent);
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.track-unit.track-unit-on:hover {
|
||||
.track-unit.on.highlightable:hover {
|
||||
border-color: var(--color-ui-accent-hover);
|
||||
background-color: var(--color-ui-accent-hover);
|
||||
}
|
||||
|
||||
.track-unit.track-unit-on.track-unit-accent {
|
||||
.track-unit.on.Accent {
|
||||
border-color: var(--color-ui-neutral-light);
|
||||
}
|
||||
|
||||
.track-unit.track-unit-on.track-unit-ghost {
|
||||
.track-unit.on.Ghost {
|
||||
opacity: 60%;
|
||||
}
|
||||
|
||||
.track-unit.track-unit-on.track-unit-lh {
|
||||
content: 'hello';
|
||||
.track-unit .icon-view {
|
||||
display: none;
|
||||
}
|
||||
.track-unit.track-unit-on.track-unit-lf {
|
||||
content: 'world';
|
||||
}
|
||||
.track-unit.track-unit-on.track-unit-rh {
|
||||
content: 'world2';
|
||||
}
|
||||
.track-unit.track-unit-on.track-unit-rf {
|
||||
content: 'world';
|
||||
.track-unit.on.icon-visible .icon-view {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import TrackUnit, { TrackUnitEvent, TrackUnitStickingType, TrackUnitType } from "@/TrackUnit";
|
||||
import "./TrackUnit.css";
|
||||
import { h, IPublisher, ISubscriber, ISubscription, Publisher, Rung, RungOptions } from "@djledda/ladder";
|
||||
import { Capsule, h, IPublisher, ISubscriber, ISubscription, Publisher, Rung, RungOptions } from "@djledda/ladder";
|
||||
import IconView, { IconName } from "@/ui/Widgets/Icon/IconView";
|
||||
|
||||
export type TrackUnitUINodeOptions = RungOptions & {
|
||||
trackUnit: TrackUnit,
|
||||
@@ -13,7 +14,23 @@ const EventTypeSubscriptions = [
|
||||
];
|
||||
type EventTypeSubscriptions = typeof EventTypeSubscriptions[number];
|
||||
|
||||
export default class TrackUnitView extends Rung<HTMLElement> implements ISubscriber<EventTypeSubscriptions> {
|
||||
export const StickingTypeIconMap = {
|
||||
none: null,
|
||||
lf: 'lf',
|
||||
lh: 'lh',
|
||||
rf: 'rf',
|
||||
rh: 'rh',
|
||||
} as const satisfies Readonly<Record<TrackUnitStickingType, IconName | null>>;
|
||||
|
||||
const TypeClasses = [ "Ghost", "Accent" ] as const;
|
||||
export const TrackUnitTypeClassMap = {
|
||||
"Normal": [],
|
||||
"GhostNote": ["Ghost"],
|
||||
"Accent": ["Accent"],
|
||||
"GhostNoteAccent": ["Ghost", "Accent"],
|
||||
} as const satisfies Readonly<Record<TrackUnitType, Readonly<string[]>>>;
|
||||
|
||||
export default class TrackUnitView extends Rung<HTMLDivElement> implements ISubscriber<EventTypeSubscriptions> {
|
||||
private trackUnit: TrackUnit;
|
||||
private subscription: ISubscription | null = null;
|
||||
private publisher: IPublisher<TrackUnitEvent> = new Publisher<TrackUnitEvent, TrackUnitView>(this);
|
||||
@@ -21,6 +38,7 @@ export default class TrackUnitView extends Rung<HTMLElement> implements ISubscri
|
||||
private mouseDownListeners: ((ev: MouseEvent) => void)[] = [];
|
||||
private hoverListeners: ((ev: MouseEvent) => void)[] = [];
|
||||
private blockNextMouseUp = false;
|
||||
private icon: IconView = new IconView({ iconName: 'list' });
|
||||
|
||||
constructor(options: TrackUnitUINodeOptions) {
|
||||
super(options);
|
||||
@@ -57,12 +75,6 @@ export default class TrackUnitView extends Rung<HTMLElement> implements ISubscri
|
||||
private handleMouseDown(ev: MouseEvent): void {
|
||||
if (ev.button === 1) {
|
||||
this.trackUnit.rotateType();
|
||||
} else {
|
||||
this.rotationTimeout = this.rotationTimeout || setTimeout(() => {
|
||||
this.trackUnit.rotateType();
|
||||
this.blockNextMouseUp = true;
|
||||
this.rotationTimeout = null;
|
||||
}, 400);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,10 +86,6 @@ export default class TrackUnitView extends Rung<HTMLElement> implements ISubscri
|
||||
}
|
||||
|
||||
private handleMouseUp(ev: MouseEvent): void {
|
||||
if (this.rotationTimeout) {
|
||||
clearTimeout(this.rotationTimeout);
|
||||
this.rotationTimeout = null;
|
||||
}
|
||||
if (!this.blockNextMouseUp) {
|
||||
this.mouseDownListeners.forEach(listener => listener(ev));
|
||||
}
|
||||
@@ -103,7 +111,9 @@ export default class TrackUnitView extends Rung<HTMLElement> implements ISubscri
|
||||
}
|
||||
|
||||
setStickingType(type: TrackUnitStickingType): void {
|
||||
this.trackUnit.setStickingType(type);
|
||||
if (this.trackUnit.isOn()) {
|
||||
this.trackUnit.setStickingType(type);
|
||||
}
|
||||
}
|
||||
|
||||
setType(type: TrackUnitType): void {
|
||||
@@ -115,16 +125,17 @@ export default class TrackUnitView extends Rung<HTMLElement> implements ISubscri
|
||||
}
|
||||
|
||||
turnOff(): void {
|
||||
this.trackUnit.setStickingType("none");
|
||||
this.trackUnit.setOn(false);
|
||||
}
|
||||
|
||||
notify(publisher: unknown, event: EventTypeSubscriptions): void {
|
||||
switch (event) {
|
||||
case TrackUnitEvent.On:
|
||||
this.render().classList.add("track-unit-on");
|
||||
this.render().classList.add("on");
|
||||
break;
|
||||
case TrackUnitEvent.Off:
|
||||
this.render().classList.remove("track-unit-on");
|
||||
this.render().classList.remove("on");
|
||||
break;
|
||||
case TrackUnitEvent.TypeChange:
|
||||
this.syncTrackUnitType();
|
||||
@@ -133,43 +144,55 @@ export default class TrackUnitView extends Rung<HTMLElement> implements ISubscri
|
||||
}
|
||||
|
||||
private syncStickingType(type: TrackUnitStickingType) {
|
||||
const node = this.render();
|
||||
node.classList.remove("track-unit-lh");
|
||||
node.classList.remove("track-unit-rh");
|
||||
node.classList.remove("track-unit-lf");
|
||||
node.classList.remove("track-unit-rf");
|
||||
if (type !== "none") {
|
||||
node.classList.add(`track-unit-${ type }`);
|
||||
if (StickingTypeIconMap[this.trackUnit.getStickingType()]) {
|
||||
this.render().classList.add("icon-visible");
|
||||
} else {
|
||||
this.render().classList.remove("icon-visible");
|
||||
}
|
||||
const icon = StickingTypeIconMap[this.trackUnit.getStickingType()];
|
||||
if (icon) {
|
||||
this.icon.setIcon(icon);
|
||||
}
|
||||
}
|
||||
|
||||
private syncTrackUnitType() {
|
||||
switch (this.trackUnit.getType()) {
|
||||
case TrackUnitType.Normal:
|
||||
this.render().classList.remove("track-unit-ghost");
|
||||
this.render().classList.remove("track-unit-accent");
|
||||
break;
|
||||
case TrackUnitType.GhostNote:
|
||||
this.render().classList.add("track-unit-ghost");
|
||||
this.render().classList.remove("track-unit-accent");
|
||||
break;
|
||||
case TrackUnitType.Accent:
|
||||
this.render().classList.remove("track-unit-ghost");
|
||||
this.render().classList.add("track-unit-accent");
|
||||
break;
|
||||
case TrackUnitType.GhostNoteAccent:
|
||||
this.render().classList.add("track-unit-ghost");
|
||||
this.render().classList.add("track-unit-accent");
|
||||
break;
|
||||
for (const className of TypeClasses) {
|
||||
this.render().classList.remove(className);
|
||||
}
|
||||
for (const className of TrackUnitTypeClassMap[this.trackUnit.getType()]) {
|
||||
this.render().classList.add(className);
|
||||
}
|
||||
}
|
||||
|
||||
build(): HTMLElement {
|
||||
static getClasses(options: { on: boolean, stickingType: TrackUnitStickingType, type: TrackUnitType, highlightable?: boolean }) {
|
||||
const classes = ["track-unit"];
|
||||
if (this.trackUnit.isOn()) {
|
||||
classes.push("track-unit-on");
|
||||
if (options.on) {
|
||||
classes.push("on");
|
||||
}
|
||||
return <div classes={classes} oncontextmenu={() => false} /> as HTMLElement;
|
||||
if (StickingTypeIconMap[options.stickingType]) {
|
||||
classes.push("icon-visible");
|
||||
}
|
||||
if (options.type) {
|
||||
classes.push(...TrackUnitTypeClassMap[options.type]);
|
||||
}
|
||||
if (options.highlightable) {
|
||||
classes.push("highlightable");
|
||||
}
|
||||
return classes;
|
||||
}
|
||||
|
||||
build() {
|
||||
const classes = TrackUnitView.getClasses({
|
||||
on: this.trackUnit.isOn(),
|
||||
stickingType: this.trackUnit.getStickingType(),
|
||||
type: this.trackUnit.getType(),
|
||||
highlightable: true,
|
||||
});
|
||||
return (
|
||||
<div classes={classes} oncontextmenu={() => false}>
|
||||
{this.icon}
|
||||
</div>
|
||||
) as HTMLDivElement;
|
||||
}
|
||||
|
||||
onHover(cb: () => void): void {
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
import { h, Rung, RungOptions } from "@djledda/ladder";
|
||||
import "./Icon.css";
|
||||
|
||||
import List from "assets/svgs/list.svg";
|
||||
import ArrowClockwise from "assets/svgs/arrow-clockwise.svg";
|
||||
import Trash from "assets/svgs/trash.svg";
|
||||
import Snowflake from "assets/svgs/snowflake.svg";
|
||||
import { h, Rung, RungOptions } from "@djledda/ladder";
|
||||
import LeftHand from "assets/svgs/LH.png";
|
||||
import RightHand from "assets/svgs/RH.png";
|
||||
import LeftFoot from "assets/svgs/LF.png";
|
||||
import RightFoot from "assets/svgs/RF.png";
|
||||
|
||||
const IconUrlMap = {
|
||||
arrowClockwise: ArrowClockwise,
|
||||
list: List,
|
||||
trash: Trash,
|
||||
snowflake: Snowflake,
|
||||
lh: LeftHand,
|
||||
rh: RightHand,
|
||||
lf: LeftFoot,
|
||||
rf: RightFoot,
|
||||
} as const;
|
||||
|
||||
export type IconName = keyof typeof IconUrlMap;
|
||||
@@ -19,7 +28,7 @@ export type IconViewOptions = RungOptions & {
|
||||
color?: string,
|
||||
};
|
||||
|
||||
export default class IconView extends Rung {
|
||||
export default class IconView extends Rung<HTMLDivElement> {
|
||||
private iconUrl: string;
|
||||
private color: string | null;
|
||||
|
||||
@@ -29,10 +38,19 @@ export default class IconView extends Rung {
|
||||
this.iconUrl = IconUrlMap[options.iconName];
|
||||
}
|
||||
|
||||
build(): HTMLSpanElement {
|
||||
const icon = <div className={"icon-view"} /> as HTMLDivElement;
|
||||
setIcon(name: IconName) {
|
||||
this.iconUrl = IconUrlMap[name];
|
||||
this.render().style.cssText = this.cssText();
|
||||
}
|
||||
|
||||
cssText() {
|
||||
const colorString = this.color ? `--icon-bg:${this.color}` : "";
|
||||
icon.style.cssText = `-webkit-mask-image: url(${this.iconUrl}); mask-image: url(${this.iconUrl});${colorString}`;
|
||||
return `-webkit-mask-image: url(${this.iconUrl}); mask-image: url(${this.iconUrl});${colorString ?? ''}`;
|
||||
}
|
||||
|
||||
build() {
|
||||
const icon = <div className={"icon-view"} /> as HTMLDivElement;
|
||||
icon.style.cssText = this.cssText();
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user