added root path alias, icons, improved framework semantics
This commit is contained in:
10
src/Beat.ts
10
src/Beat.ts
@@ -1,8 +1,8 @@
|
||||
import BeatUnit from "./BeatUnit";
|
||||
import {IPublisher, Publisher} from "./Publisher";
|
||||
import ISubscriber from "./Subscriber";
|
||||
import BeatLike from "./BeatLike";
|
||||
import {isPosInt} from "./utils";
|
||||
import BeatUnit from "@/BeatUnit";
|
||||
import {IPublisher, Publisher} from "@/Publisher";
|
||||
import ISubscriber from "@/Subscriber";
|
||||
import BeatLike from "@/BeatLike";
|
||||
import {isPosInt} from "@/utils";
|
||||
|
||||
export type BeatInitOptions = {
|
||||
timeSig?: {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import Beat, {BeatEvents, BeatInitOptions} from "./Beat";
|
||||
import {IPublisher, Publisher} from "./Publisher";
|
||||
import ISubscriber from "./Subscriber";
|
||||
import BeatLike from "./BeatLike";
|
||||
import {greatestCommonDivisor, isPosInt} from "./utils";
|
||||
import Beat, {BeatEvents, BeatInitOptions} from "@/Beat";
|
||||
import {IPublisher, Publisher} from "@/Publisher";
|
||||
import ISubscriber from "@/Subscriber";
|
||||
import BeatLike from "@/BeatLike";
|
||||
import {greatestCommonDivisor, isPosInt} from "@/utils";
|
||||
|
||||
type BeatGroupInitOptions = {
|
||||
barCount: number;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import BeatGroup from "./BeatGroup";
|
||||
import RootView from "./ui/Root/RootView";
|
||||
import "./ui/global.css";
|
||||
import BeatGroup from "@/BeatGroup";
|
||||
import RootView from "@/ui/Root/RootView";
|
||||
import "@/ui/global.css";
|
||||
|
||||
const defaultSettings = {
|
||||
barCount: 2,
|
||||
|
||||
12
src/types.d.ts
vendored
Normal file
12
src/types.d.ts
vendored
Normal 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;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import BeatUnit, {BeatUnitEvents, BeatUnitType} from "../../../../BeatUnit";
|
||||
import ISubscriber from "../../../../Subscriber";
|
||||
import UINode, {UINodeOptions} from "../../../UINode";
|
||||
import {IPublisher, ISubscription, Publisher} from "../../../../Publisher";
|
||||
import BeatUnit, {BeatUnitEvents, BeatUnitType} from "@/BeatUnit";
|
||||
import ISubscriber from "@/Subscriber";
|
||||
import UINode, {UINodeOptions} from "@/ui/UINode";
|
||||
import {IPublisher, ISubscription, Publisher} from "@/Publisher";
|
||||
import "./BeatUnit.css";
|
||||
|
||||
export type BeatUnitUINodeOptions = UINodeOptions & {
|
||||
@@ -29,6 +29,12 @@ export default class BeatUnitView extends UINode implements ISubscriber {
|
||||
private setupBindings() {
|
||||
this.subscription?.unbind();
|
||||
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 {
|
||||
@@ -59,22 +65,15 @@ export default class BeatUnitView extends UINode implements ISubscriber {
|
||||
}
|
||||
}
|
||||
|
||||
rebuild(): HTMLElement {
|
||||
build(): HTMLElement {
|
||||
const classes = ["beat-unit"];
|
||||
if (this.beatUnit.isOn()) {
|
||||
classes.push("beat-unit-on");
|
||||
}
|
||||
this.node = UINode.make("div", {
|
||||
return UINode.make("div", {
|
||||
classes: classes,
|
||||
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 {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import UINode, {UINodeOptions} from "../../UINode";
|
||||
import Beat, {BeatEvents} from "../../../Beat";
|
||||
import {IPublisher} from "../../../Publisher";
|
||||
import ISubscriber from "../../../Subscriber";
|
||||
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 "./Beat.css";
|
||||
|
||||
@@ -129,7 +129,7 @@ export default class BeatView extends UINode implements ISubscriber {
|
||||
this.respaceBeatUnits();
|
||||
}
|
||||
|
||||
rebuild(): HTMLElement {
|
||||
build(): HTMLElement {
|
||||
this.title = UINode.make("h3", {
|
||||
innerText: this.beat.getName(),
|
||||
classes: ["beat-title"],
|
||||
@@ -138,7 +138,7 @@ export default class BeatView extends UINode implements ISubscriber {
|
||||
if (!this.beatUnitViewBlock) {
|
||||
throw new Error("Beat unit block setup failed!");
|
||||
}
|
||||
this.node = UINode.make("div", {
|
||||
return UINode.make("div", {
|
||||
classes: ["beat"],
|
||||
subs: [
|
||||
UINode.make("div", {
|
||||
@@ -150,7 +150,6 @@ export default class BeatView extends UINode implements ISubscriber {
|
||||
}),
|
||||
],
|
||||
});
|
||||
return this.node;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import UINode, {UINodeOptions} from "../UINode";
|
||||
import BeatGroup, {BeatGroupEvents} from "../../BeatGroup";
|
||||
import UINode, {UINodeOptions} from "@/ui/UINode";
|
||||
import BeatGroup, {BeatGroupEvents} from "@/BeatGroup";
|
||||
import BeatView from "./Beat/BeatView";
|
||||
import "./BeatGroup.css";
|
||||
import ISubscriber from "../../Subscriber";
|
||||
import {IPublisher} from "../../Publisher";
|
||||
import ISubscriber from "@/Subscriber";
|
||||
import {IPublisher} from "@/Publisher";
|
||||
|
||||
export type BeatGroupUINodeOptions = UINodeOptions & {
|
||||
title: string,
|
||||
@@ -28,7 +28,7 @@ export default class BeatGroupView extends UINode implements ISubscriber {
|
||||
}
|
||||
}
|
||||
|
||||
rebuild(): HTMLDivElement {
|
||||
build(): HTMLDivElement {
|
||||
this.beatViews = [];
|
||||
for (let i = 0; i < this.beatGroup.getBeatCount(); i++) {
|
||||
this.beatViews.push(new BeatView({beat: this.beatGroup.getBeatByIndex(i)}));
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import "./BeatGroupSettings.css";
|
||||
import UINode, {UINodeOptions} from "../UINode";
|
||||
import NumberInputView from "../Widgets/NumberInput/NumberInputView";
|
||||
import ISubscriber from "../../Subscriber";
|
||||
import BeatGroup, {BeatGroupEvents} from "../../BeatGroup";
|
||||
import {IPublisher} from "../../Publisher";
|
||||
import {BeatEvents} from "../../Beat";
|
||||
import BoolBoxView from "../Widgets/BoolBox/BoolBoxView";
|
||||
import BeatSettingsView from "../BeatSettings/BeatSettingsView";
|
||||
import ActionButtonView from "../Widgets/ActionButton/ActionButtonView";
|
||||
import UINode, {UINodeOptions} from "@/ui/UINode";
|
||||
import NumberInputView from "@/ui/Widgets/NumberInput/NumberInputView";
|
||||
import ISubscriber 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";
|
||||
import ActionButtonView from "@/ui/Widgets/ActionButton/ActionButtonView";
|
||||
|
||||
export type BeatGroupSettingsUINodeOptions = UINodeOptions & {
|
||||
beatGroup: BeatGroup,
|
||||
@@ -71,7 +71,7 @@ export default class BeatGroupSettingsView extends UINode implements ISubscriber
|
||||
}
|
||||
}
|
||||
|
||||
rebuild(): HTMLElement {
|
||||
build(): HTMLElement {
|
||||
this.barCountInput = new NumberInputView({
|
||||
label: "Bars:",
|
||||
initialValue: this.beatGroup.getBarCount(),
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import "./BeatLikeLoopSettings.css";
|
||||
import BeatLike from "../../BeatLike";
|
||||
import NumberInputView from "../Widgets/NumberInput/NumberInputView";
|
||||
import ISubscriber from "../../Subscriber";
|
||||
import UINode, {UINodeOptions} from "../UINode";
|
||||
import {BeatEvents} from "../../Beat";
|
||||
import {IPublisher} from "../../Publisher";
|
||||
import BoolBoxView from "../Widgets/BoolBox/BoolBoxView";
|
||||
import BeatLike from "@/BeatLike";
|
||||
import NumberInputView from "@/ui/Widgets/NumberInput/NumberInputView";
|
||||
import ISubscriber from "@/Subscriber";
|
||||
import UINode, {UINodeOptions} from "@/ui/UINode";
|
||||
import {BeatEvents} from "@/Beat";
|
||||
import {IPublisher} from "@/Publisher";
|
||||
import BoolBoxView from "@/ui/Widgets/BoolBox/BoolBoxView";
|
||||
|
||||
export type BeatLikeLoopSettingsViewUINodeOptions = UINodeOptions & {
|
||||
beatLike: BeatLike,
|
||||
@@ -52,7 +52,7 @@ export default class BeatLikeLoopSettingsView extends UINode implements ISubscri
|
||||
this.notify(null, BeatEvents.DisplayTypeChanged);
|
||||
}
|
||||
|
||||
rebuild(): HTMLElement {
|
||||
build(): HTMLElement {
|
||||
this.loopLengthInput = new NumberInputView({
|
||||
initialValue: this.beatLike.getLoopLength(),
|
||||
label: "Length:",
|
||||
@@ -76,7 +76,7 @@ export default class BeatLikeLoopSettingsView extends UINode implements ISubscri
|
||||
} else {
|
||||
this.loopLengthSection.classList.add("hide");
|
||||
}
|
||||
this.node = UINode.make("div", {
|
||||
return UINode.make("div", {
|
||||
classes: ["loop-settings"],
|
||||
subs: [
|
||||
UINode.make("p", {innerText: this.title}),
|
||||
@@ -94,6 +94,5 @@ export default class BeatLikeLoopSettingsView extends UINode implements ISubscri
|
||||
}),
|
||||
]
|
||||
});
|
||||
return this.node;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import "./BeatSettings.css";
|
||||
import Beat, {BeatEvents} from "../../Beat";
|
||||
import UINode, {UINodeOptions} from "../UINode";
|
||||
import ISubscriber from "../../Subscriber";
|
||||
import {IPublisher, ISubscription} from "../../Publisher";
|
||||
import NumberInputView from "../Widgets/NumberInput/NumberInputView";
|
||||
import BoolBoxView from "../Widgets/BoolBox/BoolBoxView";
|
||||
import ActionButtonView from "../Widgets/ActionButton/ActionButtonView";
|
||||
import Beat, {BeatEvents} from "@/Beat";
|
||||
import UINode, {UINodeOptions} from "@/ui/UINode";
|
||||
import ISubscriber from "@/Subscriber";
|
||||
import {IPublisher, ISubscription} from "@/Publisher";
|
||||
import NumberInputView from "@/ui/Widgets/NumberInput/NumberInputView";
|
||||
import BoolBoxView from "@/ui/Widgets/BoolBox/BoolBoxView";
|
||||
import ActionButtonView from "@/ui/Widgets/ActionButton/ActionButtonView";
|
||||
|
||||
export type BeatSettingsViewUINodeOptions = UINodeOptions & {
|
||||
beat: Beat,
|
||||
@@ -54,7 +54,7 @@ export default class BeatSettingsView extends UINode implements ISubscriber {
|
||||
}
|
||||
}
|
||||
|
||||
rebuild(): HTMLElement {
|
||||
build(): HTMLElement {
|
||||
this.nameInput = UINode.make("input", {
|
||||
value: this.beat.getName(),
|
||||
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),
|
||||
});
|
||||
this.deleteButton = new ActionButtonView({
|
||||
label: "Delete",
|
||||
icon: "trash",
|
||||
type: "secondary",
|
||||
onClick: () => this.beat.delete(),
|
||||
});
|
||||
|
||||
@@ -64,28 +64,11 @@
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.root-hamburger {
|
||||
.root-hamburger, .root-switch-mode {
|
||||
right: 0;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
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 {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import UINode, {UINodeOptions} from "../UINode";
|
||||
import BeatGroupView from "../BeatGroup/BeatGroupView";
|
||||
import BeatGroup from "../../BeatGroup";
|
||||
import UINode, {UINodeOptions} from "@/ui/UINode";
|
||||
import BeatGroupView from "@/ui/BeatGroup/BeatGroupView";
|
||||
import BeatGroup from "@/BeatGroup";
|
||||
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 & {
|
||||
title: string,
|
||||
@@ -20,7 +21,7 @@ export default class RootView extends UINode {
|
||||
constructor(options: RootUINodeOptions) {
|
||||
super(options);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -32,7 +33,7 @@ export default class RootView extends UINode {
|
||||
this.getNode().classList.toggle("vertical-mode");
|
||||
}
|
||||
|
||||
rebuild(): HTMLElement {
|
||||
build(): HTMLElement {
|
||||
this.beatGroupSettingsView = new BeatGroupSettingsView({beatGroup: this.mainBeatGroup});
|
||||
const sidebarMain = UINode.make("div", {
|
||||
classes: ["root-settings"],
|
||||
@@ -41,15 +42,17 @@ export default class RootView extends UINode {
|
||||
this.beatGroupSettingsView.render(),
|
||||
]
|
||||
});
|
||||
const sidebarToggle = UINode.make("div", {
|
||||
const sidebarStrip = UINode.make("div", {
|
||||
classes: ["root-sidebar-toggle"],
|
||||
subs: [
|
||||
UINode.make("div", {
|
||||
classes: ["root-hamburger"],
|
||||
subs: [new IconView({iconName: "list"}).render()],
|
||||
onclick: () => this.toggleSidebar(),
|
||||
}),
|
||||
UINode.make("div", {
|
||||
classes: ["root-switch-mode"],
|
||||
subs: [new IconView({iconName: "arrowClockwise"}).render()],
|
||||
onclick: () => this.toggleOrientation(),
|
||||
})
|
||||
]
|
||||
@@ -58,10 +61,10 @@ export default class RootView extends UINode {
|
||||
classes: ["root-sidebar"],
|
||||
subs: [
|
||||
sidebarMain,
|
||||
sidebarToggle,
|
||||
sidebarStrip,
|
||||
]
|
||||
});
|
||||
this.node = UINode.make("div", {
|
||||
return UINode.make("div", {
|
||||
classes: ["root", "sidebar-visible"],
|
||||
subs: [
|
||||
this.sidebar,
|
||||
@@ -78,6 +81,5 @@ export default class RootView extends UINode {
|
||||
})
|
||||
],
|
||||
});
|
||||
return this.node;
|
||||
}
|
||||
}
|
||||
@@ -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 |
@@ -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 |
@@ -17,7 +17,7 @@ export default abstract class UINode {
|
||||
|
||||
render(): HTMLElement {
|
||||
if (!this.node) {
|
||||
this.node = this.rebuild();
|
||||
this.node = this.build();
|
||||
}
|
||||
return this.node;
|
||||
}
|
||||
@@ -37,12 +37,14 @@ export default abstract class UINode {
|
||||
}
|
||||
const parent = this.node.parentElement;
|
||||
if (parent) {
|
||||
this.node = this.rebuild();
|
||||
this.node = this.build();
|
||||
parent.replaceChild(this.node, oldNode);
|
||||
} else {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
abstract rebuild(): HTMLElement;
|
||||
protected abstract build(): HTMLElement;
|
||||
|
||||
static make<
|
||||
T extends keyof HTMLElementTagNameMap,
|
||||
@@ -64,4 +66,16 @@ export default abstract class UINode {
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,47 @@
|
||||
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 & {
|
||||
label: string,
|
||||
type?: "primary" | "secondary",
|
||||
onClick?: (isChecked: boolean) => void,
|
||||
};
|
||||
} & ({
|
||||
icon: IconName,
|
||||
label?: never,
|
||||
} | {
|
||||
label: string,
|
||||
icon?: never,
|
||||
});
|
||||
|
||||
export default class ActionButtonView extends UINode {
|
||||
private label: string | null;
|
||||
private label: string | null = null;
|
||||
private icon: IconName | null = null;
|
||||
private buttonElement!: HTMLButtonElement;
|
||||
private onClick: (isChecked: boolean) => void;
|
||||
private type: "primary" | "secondary";
|
||||
|
||||
constructor(options: ActionButtonUINodeOptions) {
|
||||
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.onClick = options.onClick ?? (() => { /* dummy */ });
|
||||
}
|
||||
|
||||
setLabel(newLabel: string | null): void {
|
||||
if (newLabel !== null) {
|
||||
this.buttonElement.innerText = newLabel;
|
||||
} else {
|
||||
this.buttonElement.innerText = "";
|
||||
}
|
||||
}
|
||||
|
||||
rebuild(): HTMLButtonElement {
|
||||
protected build(): HTMLButtonElement {
|
||||
this.buttonElement = UINode.make("button", {
|
||||
classes: ["action-button", `action-button-${this.type}`],
|
||||
innerText: this.label ?? "",
|
||||
onclick: this.onClick,
|
||||
subs: [
|
||||
this.icon !== null ? new IconView({
|
||||
iconName: this.icon
|
||||
}).render() : UINode.make("span", {
|
||||
innerText: this.label ?? ""
|
||||
}),
|
||||
],
|
||||
});
|
||||
return this.buttonElement;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import "./BoolBox.css";
|
||||
import UINode, {UINodeOptions} from "../../UINode";
|
||||
import UINode, {UINodeOptions} from "@/ui/UINode";
|
||||
|
||||
export type BoolBoxUINodeOptions = UINodeOptions & {
|
||||
label?: string,
|
||||
@@ -35,7 +35,7 @@ export default class BoolBoxView extends UINode {
|
||||
this.checkboxElement.checked = isChecked;
|
||||
}
|
||||
|
||||
rebuild(): HTMLDivElement {
|
||||
build(): HTMLDivElement {
|
||||
this.labelElement = UINode.make("label", {
|
||||
classes: ["bool-box-label"],
|
||||
innerText: this.label ?? "",
|
||||
|
||||
8
src/ui/Widgets/Icon/Icon.css
Normal file
8
src/ui/Widgets/Icon/Icon.css
Normal file
@@ -0,0 +1,8 @@
|
||||
.icon-view {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
-webkit-mask-size: 2em;
|
||||
mask-size: 2em;
|
||||
display: inline-block;
|
||||
background-color: black;
|
||||
}
|
||||
34
src/ui/Widgets/Icon/IconView.ts
Normal file
34
src/ui/Widgets/Icon/IconView.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
4
src/ui/Widgets/Icon/svgs/arrow-clockwise.svg
Normal file
4
src/ui/Widgets/Icon/svgs/arrow-clockwise.svg
Normal 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 |
3
src/ui/Widgets/Icon/svgs/list.svg
Normal file
3
src/ui/Widgets/Icon/svgs/list.svg
Normal 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 |
3
src/ui/Widgets/Icon/svgs/trash.svg
Normal file
3
src/ui/Widgets/Icon/svgs/trash.svg
Normal 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 |
@@ -1,4 +1,4 @@
|
||||
import UINode, { UINodeOptions } from "../../UINode";
|
||||
import UINode, { UINodeOptions } from "@/ui/UINode";
|
||||
import "./NumberInput.css";
|
||||
|
||||
type NumberInputUINodeOptionsBase = UINodeOptions & {
|
||||
@@ -27,7 +27,6 @@ export type NumberInputUINodeOptions = NumberInputUINodeOptionsGetSet | NumberIn
|
||||
|
||||
export default class NumberInputView extends UINode {
|
||||
private labelElement!: HTMLLabelElement;
|
||||
private mainElement!: HTMLDivElement;
|
||||
private inputElement!: HTMLInputElement;
|
||||
private labelPosition: "top" | "left";
|
||||
private value: number;
|
||||
@@ -63,12 +62,12 @@ export default class NumberInputView extends UINode {
|
||||
}
|
||||
|
||||
disable(): void {
|
||||
this.mainElement.classList.add("disabled");
|
||||
this.node?.classList.add("disabled");
|
||||
this.inputElement.disabled = true;
|
||||
}
|
||||
|
||||
enable(): void {
|
||||
this.mainElement.classList.remove("disabled");
|
||||
this.node?.classList.remove("disabled");
|
||||
this.inputElement.disabled = false;
|
||||
}
|
||||
|
||||
@@ -77,7 +76,7 @@ export default class NumberInputView extends UINode {
|
||||
this.inputElement.valueAsNumber = value;
|
||||
}
|
||||
|
||||
rebuild(): HTMLDivElement {
|
||||
build(): HTMLDivElement {
|
||||
this.labelElement = UINode.make("label", {
|
||||
classes: ["number-input-label", this.labelPosition],
|
||||
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"],
|
||||
subs: [
|
||||
this.labelElement,
|
||||
@@ -129,6 +128,5 @@ export default class NumberInputView extends UINode {
|
||||
}),
|
||||
],
|
||||
});
|
||||
return this.mainElement;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,11 @@
|
||||
"allowJs": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "Node",
|
||||
"resolveJsonModule": true
|
||||
"resolveJsonModule": true,
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["./src/**/*"]
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ const webpackConfig = {
|
||||
test: /\.(ts|tsx)$/,
|
||||
loader: "ts-loader",
|
||||
include: [path.resolve(__dirname, "src")],
|
||||
exclude: [/node_modules/]
|
||||
exclude: [/node_modules/],
|
||||
},
|
||||
{
|
||||
test: /.css$/,
|
||||
@@ -30,18 +30,21 @@ const webpackConfig = {
|
||||
}
|
||||
}]
|
||||
},
|
||||
// {
|
||||
// test: /\.(png|jpe?g|gif|ttf|woff2?|eot|svg)$/i,
|
||||
// use: [
|
||||
// {
|
||||
// loader: "file-loader",
|
||||
// },
|
||||
// ],
|
||||
// }
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|ttf|woff2?|eot|svg)$/i,
|
||||
use: [
|
||||
{
|
||||
loader: "file-loader",
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
extensions: [".tsx", ".ts", ".js"]
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user