added root path alias, icons, improved framework semantics

This commit is contained in:
Daniel Ledda
2022-02-27 22:59:30 +01:00
parent 352f6d6b9a
commit 7fca44f6c0
25 changed files with 207 additions and 284 deletions

View File

@@ -1,8 +1,8 @@
import BeatUnit from "./BeatUnit"; import BeatUnit from "@/BeatUnit";
import {IPublisher, Publisher} from "./Publisher"; import {IPublisher, Publisher} from "@/Publisher";
import ISubscriber from "./Subscriber"; import ISubscriber from "@/Subscriber";
import BeatLike from "./BeatLike"; import BeatLike from "@/BeatLike";
import {isPosInt} from "./utils"; import {isPosInt} from "@/utils";
export type BeatInitOptions = { export type BeatInitOptions = {
timeSig?: { timeSig?: {

View File

@@ -1,8 +1,8 @@
import Beat, {BeatEvents, BeatInitOptions} from "./Beat"; import Beat, {BeatEvents, BeatInitOptions} from "@/Beat";
import {IPublisher, Publisher} from "./Publisher"; import {IPublisher, Publisher} from "@/Publisher";
import ISubscriber from "./Subscriber"; import ISubscriber from "@/Subscriber";
import BeatLike from "./BeatLike"; import BeatLike from "@/BeatLike";
import {greatestCommonDivisor, isPosInt} from "./utils"; import {greatestCommonDivisor, isPosInt} from "@/utils";
type BeatGroupInitOptions = { type BeatGroupInitOptions = {
barCount: number; barCount: number;

View File

@@ -1,6 +1,6 @@
import BeatGroup from "./BeatGroup"; import BeatGroup from "@/BeatGroup";
import RootView from "./ui/Root/RootView"; import RootView from "@/ui/Root/RootView";
import "./ui/global.css"; import "@/ui/global.css";
const defaultSettings = { const defaultSettings = {
barCount: 2, barCount: 2,

12
src/types.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
declare module "*.gif" {
const value: string;
export = value;
}
declare module "*.png" {
const value: string;
export = value;
}
declare module "*.svg" {
const value: string;
export = value;
}

View File

@@ -1,7 +1,7 @@
import BeatUnit, {BeatUnitEvents, BeatUnitType} from "../../../../BeatUnit"; import BeatUnit, {BeatUnitEvents, BeatUnitType} from "@/BeatUnit";
import ISubscriber from "../../../../Subscriber"; import ISubscriber from "@/Subscriber";
import UINode, {UINodeOptions} from "../../../UINode"; import UINode, {UINodeOptions} from "@/ui/UINode";
import {IPublisher, ISubscription, Publisher} from "../../../../Publisher"; import {IPublisher, ISubscription, Publisher} from "@/Publisher";
import "./BeatUnit.css"; import "./BeatUnit.css";
export type BeatUnitUINodeOptions = UINodeOptions & { export type BeatUnitUINodeOptions = UINodeOptions & {
@@ -29,6 +29,12 @@ export default class BeatUnitView extends UINode implements ISubscriber {
private setupBindings() { private setupBindings() {
this.subscription?.unbind(); this.subscription?.unbind();
this.subscription = this.beatUnit.addSubscriber(this, "all"); this.subscription = this.beatUnit.addSubscriber(this, "all");
this.onMouseUp((ev: MouseEvent) => {
if (ev.button === 1) {
const currentType = this.beatUnit.getType();
this.beatUnit.setType(currentType === BeatUnitType.GhostNote ? BeatUnitType.Normal : BeatUnitType.GhostNote);
}
});
} }
toggle(): void { toggle(): void {
@@ -59,22 +65,15 @@ export default class BeatUnitView extends UINode implements ISubscriber {
} }
} }
rebuild(): HTMLElement { build(): HTMLElement {
const classes = ["beat-unit"]; const classes = ["beat-unit"];
if (this.beatUnit.isOn()) { if (this.beatUnit.isOn()) {
classes.push("beat-unit-on"); classes.push("beat-unit-on");
} }
this.node = UINode.make("div", { return UINode.make("div", {
classes: classes, classes: classes,
oncontextmenu: () => false, oncontextmenu: () => false,
}); });
this.onMouseUp((ev: MouseEvent) => {
if (ev.button === 1) {
const currentType = this.beatUnit.getType();
this.beatUnit.setType(currentType === BeatUnitType.GhostNote ? BeatUnitType.Normal : BeatUnitType.GhostNote);
}
});
return this.node;
} }
onHover(cb: () => void): void { onHover(cb: () => void): void {

View File

@@ -1,7 +1,7 @@
import UINode, {UINodeOptions} from "../../UINode"; import UINode, {UINodeOptions} from "@/ui/UINode";
import Beat, {BeatEvents} from "../../../Beat"; import Beat, {BeatEvents} from "@/Beat";
import {IPublisher} from "../../../Publisher"; import {IPublisher} from "@/Publisher";
import ISubscriber from "../../../Subscriber"; import ISubscriber from "@/Subscriber";
import BeatUnitView from "./BeatUnit/BeatUnitView"; import BeatUnitView from "./BeatUnit/BeatUnitView";
import "./Beat.css"; import "./Beat.css";
@@ -129,7 +129,7 @@ export default class BeatView extends UINode implements ISubscriber {
this.respaceBeatUnits(); this.respaceBeatUnits();
} }
rebuild(): HTMLElement { build(): HTMLElement {
this.title = UINode.make("h3", { this.title = UINode.make("h3", {
innerText: this.beat.getName(), innerText: this.beat.getName(),
classes: ["beat-title"], classes: ["beat-title"],
@@ -138,7 +138,7 @@ export default class BeatView extends UINode implements ISubscriber {
if (!this.beatUnitViewBlock) { if (!this.beatUnitViewBlock) {
throw new Error("Beat unit block setup failed!"); throw new Error("Beat unit block setup failed!");
} }
this.node = UINode.make("div", { return UINode.make("div", {
classes: ["beat"], classes: ["beat"],
subs: [ subs: [
UINode.make("div", { UINode.make("div", {
@@ -150,7 +150,6 @@ export default class BeatView extends UINode implements ISubscriber {
}), }),
], ],
}); });
return this.node;
} }
} }

View File

@@ -1,9 +1,9 @@
import UINode, {UINodeOptions} from "../UINode"; import UINode, {UINodeOptions} from "@/ui/UINode";
import BeatGroup, {BeatGroupEvents} from "../../BeatGroup"; import BeatGroup, {BeatGroupEvents} from "@/BeatGroup";
import BeatView from "./Beat/BeatView"; import BeatView from "./Beat/BeatView";
import "./BeatGroup.css"; import "./BeatGroup.css";
import ISubscriber from "../../Subscriber"; import ISubscriber from "@/Subscriber";
import {IPublisher} from "../../Publisher"; import {IPublisher} from "@/Publisher";
export type BeatGroupUINodeOptions = UINodeOptions & { export type BeatGroupUINodeOptions = UINodeOptions & {
title: string, title: string,
@@ -28,7 +28,7 @@ export default class BeatGroupView extends UINode implements ISubscriber {
} }
} }
rebuild(): HTMLDivElement { build(): HTMLDivElement {
this.beatViews = []; this.beatViews = [];
for (let i = 0; i < this.beatGroup.getBeatCount(); i++) { for (let i = 0; i < this.beatGroup.getBeatCount(); i++) {
this.beatViews.push(new BeatView({beat: this.beatGroup.getBeatByIndex(i)})); this.beatViews.push(new BeatView({beat: this.beatGroup.getBeatByIndex(i)}));

View File

@@ -1,13 +1,13 @@
import "./BeatGroupSettings.css"; import "./BeatGroupSettings.css";
import UINode, {UINodeOptions} from "../UINode"; import UINode, {UINodeOptions} from "@/ui/UINode";
import NumberInputView from "../Widgets/NumberInput/NumberInputView"; import NumberInputView from "@/ui/Widgets/NumberInput/NumberInputView";
import ISubscriber from "../../Subscriber"; import ISubscriber from "@/Subscriber";
import BeatGroup, {BeatGroupEvents} from "../../BeatGroup"; import BeatGroup, {BeatGroupEvents} from "@/BeatGroup";
import {IPublisher} from "../../Publisher"; import {IPublisher} from "@/Publisher";
import {BeatEvents} from "../../Beat"; import {BeatEvents} from "@/Beat";
import BoolBoxView from "../Widgets/BoolBox/BoolBoxView"; import BoolBoxView from "@/ui/Widgets/BoolBox/BoolBoxView";
import BeatSettingsView from "../BeatSettings/BeatSettingsView"; import BeatSettingsView from "@/ui/BeatSettings/BeatSettingsView";
import ActionButtonView from "../Widgets/ActionButton/ActionButtonView"; import ActionButtonView from "@/ui/Widgets/ActionButton/ActionButtonView";
export type BeatGroupSettingsUINodeOptions = UINodeOptions & { export type BeatGroupSettingsUINodeOptions = UINodeOptions & {
beatGroup: BeatGroup, beatGroup: BeatGroup,
@@ -71,7 +71,7 @@ export default class BeatGroupSettingsView extends UINode implements ISubscriber
} }
} }
rebuild(): HTMLElement { build(): HTMLElement {
this.barCountInput = new NumberInputView({ this.barCountInput = new NumberInputView({
label: "Bars:", label: "Bars:",
initialValue: this.beatGroup.getBarCount(), initialValue: this.beatGroup.getBarCount(),

View File

@@ -1,11 +1,11 @@
import "./BeatLikeLoopSettings.css"; import "./BeatLikeLoopSettings.css";
import BeatLike from "../../BeatLike"; import BeatLike from "@/BeatLike";
import NumberInputView from "../Widgets/NumberInput/NumberInputView"; import NumberInputView from "@/ui/Widgets/NumberInput/NumberInputView";
import ISubscriber from "../../Subscriber"; import ISubscriber from "@/Subscriber";
import UINode, {UINodeOptions} from "../UINode"; import UINode, {UINodeOptions} from "@/ui/UINode";
import {BeatEvents} from "../../Beat"; import {BeatEvents} from "@/Beat";
import {IPublisher} from "../../Publisher"; import {IPublisher} from "@/Publisher";
import BoolBoxView from "../Widgets/BoolBox/BoolBoxView"; import BoolBoxView from "@/ui/Widgets/BoolBox/BoolBoxView";
export type BeatLikeLoopSettingsViewUINodeOptions = UINodeOptions & { export type BeatLikeLoopSettingsViewUINodeOptions = UINodeOptions & {
beatLike: BeatLike, beatLike: BeatLike,
@@ -52,7 +52,7 @@ export default class BeatLikeLoopSettingsView extends UINode implements ISubscri
this.notify(null, BeatEvents.DisplayTypeChanged); this.notify(null, BeatEvents.DisplayTypeChanged);
} }
rebuild(): HTMLElement { build(): HTMLElement {
this.loopLengthInput = new NumberInputView({ this.loopLengthInput = new NumberInputView({
initialValue: this.beatLike.getLoopLength(), initialValue: this.beatLike.getLoopLength(),
label: "Length:", label: "Length:",
@@ -76,7 +76,7 @@ export default class BeatLikeLoopSettingsView extends UINode implements ISubscri
} else { } else {
this.loopLengthSection.classList.add("hide"); this.loopLengthSection.classList.add("hide");
} }
this.node = UINode.make("div", { return UINode.make("div", {
classes: ["loop-settings"], classes: ["loop-settings"],
subs: [ subs: [
UINode.make("p", {innerText: this.title}), UINode.make("p", {innerText: this.title}),
@@ -94,6 +94,5 @@ export default class BeatLikeLoopSettingsView extends UINode implements ISubscri
}), }),
] ]
}); });
return this.node;
} }
} }

View File

@@ -1,11 +1,11 @@
import "./BeatSettings.css"; import "./BeatSettings.css";
import Beat, {BeatEvents} from "../../Beat"; import Beat, {BeatEvents} from "@/Beat";
import UINode, {UINodeOptions} from "../UINode"; import UINode, {UINodeOptions} from "@/ui/UINode";
import ISubscriber from "../../Subscriber"; import ISubscriber from "@/Subscriber";
import {IPublisher, ISubscription} from "../../Publisher"; import {IPublisher, ISubscription} from "@/Publisher";
import NumberInputView from "../Widgets/NumberInput/NumberInputView"; import NumberInputView from "@/ui/Widgets/NumberInput/NumberInputView";
import BoolBoxView from "../Widgets/BoolBox/BoolBoxView"; import BoolBoxView from "@/ui/Widgets/BoolBox/BoolBoxView";
import ActionButtonView from "../Widgets/ActionButton/ActionButtonView"; import ActionButtonView from "@/ui/Widgets/ActionButton/ActionButtonView";
export type BeatSettingsViewUINodeOptions = UINodeOptions & { export type BeatSettingsViewUINodeOptions = UINodeOptions & {
beat: Beat, beat: Beat,
@@ -54,7 +54,7 @@ export default class BeatSettingsView extends UINode implements ISubscriber {
} }
} }
rebuild(): HTMLElement { build(): HTMLElement {
this.nameInput = UINode.make("input", { this.nameInput = UINode.make("input", {
value: this.beat.getName(), value: this.beat.getName(),
classes: ["beat-settings-name-field"], classes: ["beat-settings-name-field"],
@@ -62,7 +62,7 @@ export default class BeatSettingsView extends UINode implements ISubscriber {
oninput: (event: Event) => this.beat.setName((event.target as HTMLInputElement).value), oninput: (event: Event) => this.beat.setName((event.target as HTMLInputElement).value),
}); });
this.deleteButton = new ActionButtonView({ this.deleteButton = new ActionButtonView({
label: "Delete", icon: "trash",
type: "secondary", type: "secondary",
onClick: () => this.beat.delete(), onClick: () => this.beat.delete(),
}); });

View File

@@ -64,28 +64,11 @@
left: 0; left: 0;
} }
.root-hamburger { .root-hamburger, .root-switch-mode {
right: 0; right: 0;
width: 2em; width: 2em;
height: 2em; height: 2em;
cursor: pointer; cursor: pointer;
-webkit-mask-image: url(./drawing.svg);
mask-image: url(./drawing.svg);
-webkit-mask-size: 2em;
mask-size: 2em;
background-color: var(--color-ui-neutral-dark);
}
.root-switch-mode {
right: 0;
width: 2em;
height: 2em;
cursor: pointer;
-webkit-mask-image: url(./rotate.svg);
mask-image: url(./rotate.svg);
-webkit-mask-size: 2em;
mask-size: 2em;
background-color: var(--color-ui-neutral-dark);
} }
.root-beat-stage-container { .root-beat-stage-container {

View File

@@ -1,8 +1,9 @@
import UINode, {UINodeOptions} from "../UINode"; import UINode, {UINodeOptions} from "@/ui/UINode";
import BeatGroupView from "../BeatGroup/BeatGroupView"; import BeatGroupView from "@/ui/BeatGroup/BeatGroupView";
import BeatGroup from "../../BeatGroup"; import BeatGroup from "@/BeatGroup";
import "./Root.css"; import "./Root.css";
import BeatGroupSettingsView from "../BeatGroupSettings/BeatGroupSettingsView"; import BeatGroupSettingsView from "@/ui/BeatGroupSettings/BeatGroupSettingsView";
import IconView from "@/ui/Widgets/Icon/IconView";
export type RootUINodeOptions = UINodeOptions & { export type RootUINodeOptions = UINodeOptions & {
title: string, title: string,
@@ -20,7 +21,7 @@ export default class RootView extends UINode {
constructor(options: RootUINodeOptions) { constructor(options: RootUINodeOptions) {
super(options); super(options);
this.mainBeatGroup = options.mainBeatGroup; this.mainBeatGroup = options.mainBeatGroup;
this.beatGroupView = new BeatGroupView({title: "THE BEAT", beatGroup: this.mainBeatGroup}); this.beatGroupView = new BeatGroupView({title: options.title, beatGroup: this.mainBeatGroup});
this.title = options.title; this.title = options.title;
} }
@@ -32,7 +33,7 @@ export default class RootView extends UINode {
this.getNode().classList.toggle("vertical-mode"); this.getNode().classList.toggle("vertical-mode");
} }
rebuild(): HTMLElement { build(): HTMLElement {
this.beatGroupSettingsView = new BeatGroupSettingsView({beatGroup: this.mainBeatGroup}); this.beatGroupSettingsView = new BeatGroupSettingsView({beatGroup: this.mainBeatGroup});
const sidebarMain = UINode.make("div", { const sidebarMain = UINode.make("div", {
classes: ["root-settings"], classes: ["root-settings"],
@@ -41,15 +42,17 @@ export default class RootView extends UINode {
this.beatGroupSettingsView.render(), this.beatGroupSettingsView.render(),
] ]
}); });
const sidebarToggle = UINode.make("div", { const sidebarStrip = UINode.make("div", {
classes: ["root-sidebar-toggle"], classes: ["root-sidebar-toggle"],
subs: [ subs: [
UINode.make("div", { UINode.make("div", {
classes: ["root-hamburger"], classes: ["root-hamburger"],
subs: [new IconView({iconName: "list"}).render()],
onclick: () => this.toggleSidebar(), onclick: () => this.toggleSidebar(),
}), }),
UINode.make("div", { UINode.make("div", {
classes: ["root-switch-mode"], classes: ["root-switch-mode"],
subs: [new IconView({iconName: "arrowClockwise"}).render()],
onclick: () => this.toggleOrientation(), onclick: () => this.toggleOrientation(),
}) })
] ]
@@ -58,10 +61,10 @@ export default class RootView extends UINode {
classes: ["root-sidebar"], classes: ["root-sidebar"],
subs: [ subs: [
sidebarMain, sidebarMain,
sidebarToggle, sidebarStrip,
] ]
}); });
this.node = UINode.make("div", { return UINode.make("div", {
classes: ["root", "sidebar-visible"], classes: ["root", "sidebar-visible"],
subs: [ subs: [
this.sidebar, this.sidebar,
@@ -78,6 +81,5 @@ export default class RootView extends UINode {
}) })
], ],
}); });
return this.node;
} }
} }

View File

@@ -1,73 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="64"
height="64"
viewBox="0 0 16.933333 16.933333"
version="1.1"
id="svg5"
inkscape:version="1.1 (ce6663b3b7, 2021-05-25)"
sodipodi:docname="drawing.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="true"
inkscape:zoom="6.0150393"
inkscape:cx="24.355618"
inkscape:cy="41.562488"
inkscape:window-width="1920"
inkscape:window-height="1043"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
width="64mm"
units="px">
<inkscape:grid
type="xygrid"
id="grid25"
empspacing="4" />
</sodipodi:namedview>
<defs
id="defs2" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#1a1a1a;fill-rule:evenodd;stroke-width:0.634;stop-color:#000000"
id="rect49"
width="12.7"
height="2.1166666"
x="2.1166666"
y="3.1750002"
ry="1.0583333" />
<rect
style="fill:#1a1a1a;fill-rule:evenodd;stroke-width:0.634;stop-color:#000000"
id="rect49-3"
width="12.7"
height="2.1166666"
x="2.1166666"
y="7.4083338"
ry="1.0583333" />
<rect
style="fill:#1a1a1a;fill-rule:evenodd;stroke-width:0.634;stop-color:#000000"
id="rect49-6"
width="12.7"
height="2.1166666"
x="2.1166668"
y="11.641666"
ry="1.0583333" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1,78 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="64"
height="64"
viewBox="0 0 16.933333 16.933334"
version="1.1"
id="svg5"
inkscape:version="1.1 (ce6663b3b7, 2021-05-25)"
sodipodi:docname="rotate.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:pageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="true"
units="px"
width="64px"
inkscape:snap-bbox="true"
inkscape:zoom="6.0150393"
inkscape:cx="26.433743"
inkscape:cy="34.330615"
inkscape:window-width="1920"
inkscape:window-height="1016"
inkscape:window-x="1920"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="layer1">
<inkscape:grid
type="xygrid"
id="grid113"
empspacing="4"
snapvisiblegridlinesonly="true"
visible="true"
enabled="true" />
</sodipodi:namedview>
<defs
id="defs2" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<g
id="g1725"
transform="rotate(-39.669761,8.2863595,8.2940605)">
<path
id="path217"
style="fill:#000000;fill-rule:evenodd;stroke-width:0.55878;stop-color:#000000"
d="M 8.2923774,2.0552887 A 6.5293784,6.5293784 0 0 0 1.762999,8.5846671 6.5293784,6.5293784 0 0 0 8.2923774,15.114045 6.5293784,6.5293784 0 0 0 12.031194,13.937153 L 11.496948,13.171992 A 5.5966101,5.5966101 0 0 1 8.2923774,14.181277 5.5966101,5.5966101 0 0 1 2.6957673,8.5846671 5.5966101,5.5966101 0 0 1 8.2923774,2.988057 5.5966101,5.5966101 0 0 1 13.888988,8.5846671 h 0.932768 A 6.5293784,6.5293784 0 0 0 8.2923774,2.0552887 Z m 6.5293786,6.5293784 a 6.5293784,6.5293784 0 0 1 -0.05967,0.8649058 6.5293784,6.5293784 0 0 0 0.05967,-0.8649058 z m -0.06558,0.9136394 a 6.5293784,6.5293784 0 0 1 -0.173077,0.8284685 6.5293784,6.5293784 0 0 0 0.173073,-0.8284685 z m -0.18947,0.8863115 a 6.5293784,6.5293784 0 0 1 -0.286936,0.802964 6.5293784,6.5293784 0 0 0 0.286936,-0.802964 z m -0.300599,0.832113 a 6.5293784,6.5293784 0 0 1 -0.393967,0.755597 6.5293784,6.5293784 0 0 0 0.393967,-0.755597 z m -0.412641,0.787478 a 6.5293784,6.5293784 0 0 1 -0.493255,0.694567 6.5293784,6.5293784 0 0 0 0.493255,-0.694567 z m -0.529237,0.738746 a 6.5293784,6.5293784 0 0 1 -0.583435,0.619417 6.5293784,6.5293784 0 0 0 0.583435,-0.619417 z m -0.614862,0.647654 a 6.5293784,6.5293784 0 0 1 -0.667238,0.538801 6.5293784,6.5293784 0 0 0 0.667238,-0.538801 z" />
<path
sodipodi:type="star"
style="fill:#000000;fill-rule:evenodd;stroke-width:2.39622;stop-color:#000000"
id="path1641"
inkscape:flatsided="true"
sodipodi:sides="3"
sodipodi:cx="47"
sodipodi:cy="50"
sodipodi:r1="7"
sodipodi:r2="3.5"
sodipodi:arg1="1.5707963"
sodipodi:arg2="2.6179939"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 47,57 -6.062178,-10.5 12.124356,0 z"
transform="matrix(0.23319209,0,0,0.23319209,3.3953438,-2.6085529)"
inkscape:transform-center-y="0.40808613" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -17,7 +17,7 @@ export default abstract class UINode {
render(): HTMLElement { render(): HTMLElement {
if (!this.node) { if (!this.node) {
this.node = this.rebuild(); this.node = this.build();
} }
return this.node; return this.node;
} }
@@ -37,12 +37,14 @@ export default abstract class UINode {
} }
const parent = this.node.parentElement; const parent = this.node.parentElement;
if (parent) { if (parent) {
this.node = this.rebuild(); this.node = this.build();
parent.replaceChild(this.node, oldNode); parent.replaceChild(this.node, oldNode);
} else {
this.render();
} }
} }
abstract rebuild(): HTMLElement; protected abstract build(): HTMLElement;
static make< static make<
T extends keyof HTMLElementTagNameMap, T extends keyof HTMLElementTagNameMap,
@@ -64,4 +66,16 @@ export default abstract class UINode {
} }
return element; return element;
} }
static q(text: string): Text {
return document.createTextNode(text);
}
static frag(subs?: Node[]): DocumentFragment {
const frag = document.createDocumentFragment();
if (subs) {
frag.append(...subs);
}
return frag;
}
} }

View File

@@ -1,39 +1,48 @@
import "./ActionButton.css"; import "./ActionButton.css";
import UINode, {UINodeOptions} from "../../UINode"; import UINode, {UINodeOptions} from "@/ui/UINode";
import IconView, {IconName} from "@/ui/Widgets/Icon/IconView";
export type ActionButtonUINodeOptions = UINodeOptions & { export type ActionButtonUINodeOptions = UINodeOptions & {
label: string,
type?: "primary" | "secondary", type?: "primary" | "secondary",
onClick?: (isChecked: boolean) => void, onClick?: (isChecked: boolean) => void,
}; } & ({
icon: IconName,
label?: never,
} | {
label: string,
icon?: never,
});
export default class ActionButtonView extends UINode { export default class ActionButtonView extends UINode {
private label: string | null; private label: string | null = null;
private icon: IconName | null = null;
private buttonElement!: HTMLButtonElement; private buttonElement!: HTMLButtonElement;
private onClick: (isChecked: boolean) => void; private onClick: (isChecked: boolean) => void;
private type: "primary" | "secondary"; private type: "primary" | "secondary";
constructor(options: ActionButtonUINodeOptions) { constructor(options: ActionButtonUINodeOptions) {
super(options); super(options);
this.label = options.label ?? ""; if (typeof options.icon !== "undefined") {
this.icon = options.icon;
} else if (typeof options.label !== "undefined") {
this.label = options.label;
}
this.type = options.type ?? "primary"; this.type = options.type ?? "primary";
this.onClick = options.onClick ?? (() => { /* dummy */ }); this.onClick = options.onClick ?? (() => { /* dummy */ });
} }
setLabel(newLabel: string | null): void { protected build(): HTMLButtonElement {
if (newLabel !== null) {
this.buttonElement.innerText = newLabel;
} else {
this.buttonElement.innerText = "";
}
}
rebuild(): HTMLButtonElement {
this.buttonElement = UINode.make("button", { this.buttonElement = UINode.make("button", {
classes: ["action-button", `action-button-${this.type}`], classes: ["action-button", `action-button-${this.type}`],
innerText: this.label ?? "",
onclick: this.onClick, onclick: this.onClick,
subs: [
this.icon !== null ? new IconView({
iconName: this.icon
}).render() : UINode.make("span", {
innerText: this.label ?? ""
}),
],
}); });
return this.buttonElement; return this.buttonElement;
} }
} }

View File

@@ -1,5 +1,5 @@
import "./BoolBox.css"; import "./BoolBox.css";
import UINode, {UINodeOptions} from "../../UINode"; import UINode, {UINodeOptions} from "@/ui/UINode";
export type BoolBoxUINodeOptions = UINodeOptions & { export type BoolBoxUINodeOptions = UINodeOptions & {
label?: string, label?: string,
@@ -35,7 +35,7 @@ export default class BoolBoxView extends UINode {
this.checkboxElement.checked = isChecked; this.checkboxElement.checked = isChecked;
} }
rebuild(): HTMLDivElement { build(): HTMLDivElement {
this.labelElement = UINode.make("label", { this.labelElement = UINode.make("label", {
classes: ["bool-box-label"], classes: ["bool-box-label"],
innerText: this.label ?? "", innerText: this.label ?? "",

View File

@@ -0,0 +1,8 @@
.icon-view {
width: 2em;
height: 2em;
-webkit-mask-size: 2em;
mask-size: 2em;
display: inline-block;
background-color: black;
}

View File

@@ -0,0 +1,34 @@
import UINode, {UINodeOptions} from "@/ui/UINode";
import "./Icon.css";
import List from "./svgs/list.svg";
import ArrowClockwise from "./svgs/arrow-clockwise.svg";
import Trash from "./svgs/trash.svg";
const IconUrlMap = {
arrowClockwise: ArrowClockwise,
list: List,
trash: Trash,
} as const;
export type IconName = keyof typeof IconUrlMap;
export type IconViewOptions = UINodeOptions & {
iconName: IconName,
};
export default class IconView extends UINode {
private iconUrl: string;
constructor(options: IconViewOptions) {
super(options);
this.iconUrl = IconUrlMap[options.iconName];
}
build(): HTMLSpanElement {
const icon = UINode.make("div", {
classes: ["icon-view"],
});
icon.style.cssText = `-webkit-mask-image: url(${this.iconUrl}); mask-image: url(${this.iconUrl});`;
return icon;
}
}

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/>
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/>
</svg>

After

Width:  |  Height:  |  Size: 352 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-list" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 344 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash3-fill" viewBox="0 0 16 16">
<path d="M11 1.5v1h3.5a.5.5 0 0 1 0 1h-.538l-.853 10.66A2 2 0 0 1 11.115 16h-6.23a2 2 0 0 1-1.994-1.84L2.038 3.5H1.5a.5.5 0 0 1 0-1H5v-1A1.5 1.5 0 0 1 6.5 0h3A1.5 1.5 0 0 1 11 1.5Zm-5 0v1h4v-1a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5ZM4.5 5.029l.5 8.5a.5.5 0 1 0 .998-.06l-.5-8.5a.5.5 0 1 0-.998.06Zm6.53-.528a.5.5 0 0 0-.528.47l-.5 8.5a.5.5 0 0 0 .998.058l.5-8.5a.5.5 0 0 0-.47-.528ZM8 4.5a.5.5 0 0 0-.5.5v8.5a.5.5 0 0 0 1 0V5a.5.5 0 0 0-.5-.5Z"/>
</svg>

After

Width:  |  Height:  |  Size: 582 B

View File

@@ -1,4 +1,4 @@
import UINode, { UINodeOptions } from "../../UINode"; import UINode, { UINodeOptions } from "@/ui/UINode";
import "./NumberInput.css"; import "./NumberInput.css";
type NumberInputUINodeOptionsBase = UINodeOptions & { type NumberInputUINodeOptionsBase = UINodeOptions & {
@@ -27,7 +27,6 @@ export type NumberInputUINodeOptions = NumberInputUINodeOptionsGetSet | NumberIn
export default class NumberInputView extends UINode { export default class NumberInputView extends UINode {
private labelElement!: HTMLLabelElement; private labelElement!: HTMLLabelElement;
private mainElement!: HTMLDivElement;
private inputElement!: HTMLInputElement; private inputElement!: HTMLInputElement;
private labelPosition: "top" | "left"; private labelPosition: "top" | "left";
private value: number; private value: number;
@@ -63,12 +62,12 @@ export default class NumberInputView extends UINode {
} }
disable(): void { disable(): void {
this.mainElement.classList.add("disabled"); this.node?.classList.add("disabled");
this.inputElement.disabled = true; this.inputElement.disabled = true;
} }
enable(): void { enable(): void {
this.mainElement.classList.remove("disabled"); this.node?.classList.remove("disabled");
this.inputElement.disabled = false; this.inputElement.disabled = false;
} }
@@ -77,7 +76,7 @@ export default class NumberInputView extends UINode {
this.inputElement.valueAsNumber = value; this.inputElement.valueAsNumber = value;
} }
rebuild(): HTMLDivElement { build(): HTMLDivElement {
this.labelElement = UINode.make("label", { this.labelElement = UINode.make("label", {
classes: ["number-input-label", this.labelPosition], classes: ["number-input-label", this.labelPosition],
innerText: this.label ?? "", innerText: this.label ?? "",
@@ -100,7 +99,7 @@ export default class NumberInputView extends UINode {
} }
}, },
}); });
this.mainElement = UINode.make("div", { return UINode.make("div", {
classes: ["number-input"], classes: ["number-input"],
subs: [ subs: [
this.labelElement, this.labelElement,
@@ -129,6 +128,5 @@ export default class NumberInputView extends UINode {
}), }),
], ],
}); });
return this.mainElement;
} }
} }

View File

@@ -7,7 +7,11 @@
"allowJs": true, "allowJs": true,
"strict": true, "strict": true,
"moduleResolution": "Node", "moduleResolution": "Node",
"resolveJsonModule": true "resolveJsonModule": true,
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
}, },
"include": ["./src/**/*"] "include": ["./src/**/*"]
} }

View File

@@ -15,7 +15,7 @@ const webpackConfig = {
test: /\.(ts|tsx)$/, test: /\.(ts|tsx)$/,
loader: "ts-loader", loader: "ts-loader",
include: [path.resolve(__dirname, "src")], include: [path.resolve(__dirname, "src")],
exclude: [/node_modules/] exclude: [/node_modules/],
}, },
{ {
test: /.css$/, test: /.css$/,
@@ -30,18 +30,21 @@ const webpackConfig = {
} }
}] }]
}, },
// { {
// test: /\.(png|jpe?g|gif|ttf|woff2?|eot|svg)$/i, test: /\.(png|jpe?g|gif|ttf|woff2?|eot|svg)$/i,
// use: [ use: [
// { {
// loader: "file-loader", loader: "file-loader",
// }, },
// ], ],
// } }
] ]
}, },
resolve: { resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
extensions: [".tsx", ".ts", ".js"] extensions: [".tsx", ".ts", ".js"]
}, },