Implemented most of the basic features for new Board component

This commit is contained in:
thepaperpilot 2021-08-19 00:25:49 -05:00
parent fbd01497e7
commit 0dcf417a31
13 changed files with 490 additions and 10 deletions

83
package-lock.json generated
View file

@ -13,6 +13,7 @@
"vue": "^3.2.2", "vue": "^3.2.2",
"vue-class-component": "^8.0.0-rc.1", "vue-class-component": "^8.0.0-rc.1",
"vue-next-select": "^2.9.0", "vue-next-select": "^2.9.0",
"vue-panzoom": "^1.1.6",
"vue-sortable": "github:Netbel/vue-sortable#master-fix", "vue-sortable": "github:Netbel/vue-sortable#master-fix",
"vue-textarea-autosize": "^1.1.1", "vue-textarea-autosize": "^1.1.1",
"vue-transition-expand": "^0.1.0" "vue-transition-expand": "^0.1.0"
@ -14925,6 +14926,14 @@
"integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=",
"dev": true "dev": true
}, },
"node_modules/amator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/amator/-/amator-1.1.0.tgz",
"integrity": "sha1-CMa2C8k67Cthu/wMTWd9MDI8wPE=",
"dependencies": {
"bezier-easing": "^2.0.3"
}
},
"node_modules/ansi-colors": { "node_modules/ansi-colors": {
"version": "3.2.4", "version": "3.2.4",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz",
@ -15495,6 +15504,11 @@
"tweetnacl": "^0.14.3" "tweetnacl": "^0.14.3"
} }
}, },
"node_modules/bezier-easing": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz",
"integrity": "sha1-wE3+i5JtbsrKGBPWn/F5t8ICXYY="
},
"node_modules/bfj": { "node_modules/bfj": {
"version": "6.1.2", "version": "6.1.2",
"resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz", "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz",
@ -22071,6 +22085,11 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true "dev": true
}, },
"node_modules/ngraph.events": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ngraph.events/-/ngraph.events-1.2.1.tgz",
"integrity": "sha512-D4C+nXH/RFxioGXQdHu8ELDtC6EaCiNsZtih0IvyGN81OZSUby4jXoJ5+RNWasfsd0FnKxxpAROyUMzw64QNsw=="
},
"node_modules/nice-try": { "node_modules/nice-try": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
@ -22736,6 +22755,16 @@
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"dev": true "dev": true
}, },
"node_modules/panzoom": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/panzoom/-/panzoom-9.4.2.tgz",
"integrity": "sha512-sQLr0t6EmNFXpZHag0HQVtOKqF9xjF7iZdgWg3Ss1o7uh2QZLvcrz7S0Cl8M0d2TkPZ69JfPJdknXN3I0e/2aQ==",
"dependencies": {
"amator": "^1.1.0",
"ngraph.events": "^1.2.1",
"wheel": "^1.0.0"
}
},
"node_modules/parallel-transform": { "node_modules/parallel-transform": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
@ -27245,6 +27274,14 @@
"vue": "^3.0.0" "vue": "^3.0.0"
} }
}, },
"node_modules/vue-panzoom": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/vue-panzoom/-/vue-panzoom-1.1.6.tgz",
"integrity": "sha512-yEE60C/gnc5NGL6YBD++CErD820va7fkBJE5dCWZZzXX2aMGklj/UKmtqu1u5xDkuOIjnGUr412LNHwOOE711w==",
"dependencies": {
"panzoom": "^9.4.1"
}
},
"node_modules/vue-sortable": { "node_modules/vue-sortable": {
"version": "0.1.3", "version": "0.1.3",
"resolved": "git+ssh://git@github.com/Netbel/vue-sortable.git#f4d4870ace71ea59bd79252eb2ec1cf6bfb02fe7", "resolved": "git+ssh://git@github.com/Netbel/vue-sortable.git#f4d4870ace71ea59bd79252eb2ec1cf6bfb02fe7",
@ -28083,6 +28120,11 @@
"node": ">=0.8.0" "node": ">=0.8.0"
} }
}, },
"node_modules/wheel": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wheel/-/wheel-1.0.0.tgz",
"integrity": "sha512-XiCMHibOiqalCQ+BaNSwRoZ9FDTAvOsXxGHXChBugewDj7HC8VBIER71dEOiRH1fSdLbRCQzngKTSiZ06ZQzeA=="
},
"node_modules/which": { "node_modules/which": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
@ -31140,6 +31182,14 @@
"integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=",
"dev": true "dev": true
}, },
"amator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/amator/-/amator-1.1.0.tgz",
"integrity": "sha1-CMa2C8k67Cthu/wMTWd9MDI8wPE=",
"requires": {
"bezier-easing": "^2.0.3"
}
},
"ansi-colors": { "ansi-colors": {
"version": "3.2.4", "version": "3.2.4",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz",
@ -31576,6 +31626,11 @@
"tweetnacl": "^0.14.3" "tweetnacl": "^0.14.3"
} }
}, },
"bezier-easing": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz",
"integrity": "sha1-wE3+i5JtbsrKGBPWn/F5t8ICXYY="
},
"bfj": { "bfj": {
"version": "6.1.2", "version": "6.1.2",
"resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz", "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz",
@ -36803,6 +36858,11 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true "dev": true
}, },
"ngraph.events": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ngraph.events/-/ngraph.events-1.2.1.tgz",
"integrity": "sha512-D4C+nXH/RFxioGXQdHu8ELDtC6EaCiNsZtih0IvyGN81OZSUby4jXoJ5+RNWasfsd0FnKxxpAROyUMzw64QNsw=="
},
"nice-try": { "nice-try": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
@ -37323,6 +37383,16 @@
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"dev": true "dev": true
}, },
"panzoom": {
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/panzoom/-/panzoom-9.4.2.tgz",
"integrity": "sha512-sQLr0t6EmNFXpZHag0HQVtOKqF9xjF7iZdgWg3Ss1o7uh2QZLvcrz7S0Cl8M0d2TkPZ69JfPJdknXN3I0e/2aQ==",
"requires": {
"amator": "^1.1.0",
"ngraph.events": "^1.2.1",
"wheel": "^1.0.0"
}
},
"parallel-transform": { "parallel-transform": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
@ -41002,6 +41072,14 @@
"integrity": "sha512-GjX4pHqZXXitquDeSAtLaf85jXdMUOKyCNzo+EF3xRr4DebGwbST4CtmRvL0TX3EhwLHQjUlAc3JcJX+azpLHg==", "integrity": "sha512-GjX4pHqZXXitquDeSAtLaf85jXdMUOKyCNzo+EF3xRr4DebGwbST4CtmRvL0TX3EhwLHQjUlAc3JcJX+azpLHg==",
"requires": {} "requires": {}
}, },
"vue-panzoom": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/vue-panzoom/-/vue-panzoom-1.1.6.tgz",
"integrity": "sha512-yEE60C/gnc5NGL6YBD++CErD820va7fkBJE5dCWZZzXX2aMGklj/UKmtqu1u5xDkuOIjnGUr412LNHwOOE711w==",
"requires": {
"panzoom": "^9.4.1"
}
},
"vue-sortable": { "vue-sortable": {
"version": "git+ssh://git@github.com/Netbel/vue-sortable.git#f4d4870ace71ea59bd79252eb2ec1cf6bfb02fe7", "version": "git+ssh://git@github.com/Netbel/vue-sortable.git#f4d4870ace71ea59bd79252eb2ec1cf6bfb02fe7",
"from": "vue-sortable@github:Netbel/vue-sortable#master-fix", "from": "vue-sortable@github:Netbel/vue-sortable#master-fix",
@ -41687,6 +41765,11 @@
"integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
"dev": true "dev": true
}, },
"wheel": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wheel/-/wheel-1.0.0.tgz",
"integrity": "sha512-XiCMHibOiqalCQ+BaNSwRoZ9FDTAvOsXxGHXChBugewDj7HC8VBIER71dEOiRH1fSdLbRCQzngKTSiZ06ZQzeA=="
},
"which": { "which": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",

View file

@ -13,6 +13,7 @@
"vue": "^3.2.2", "vue": "^3.2.2",
"vue-class-component": "^8.0.0-rc.1", "vue-class-component": "^8.0.0-rc.1",
"vue-next-select": "^2.9.0", "vue-next-select": "^2.9.0",
"vue-panzoom": "^1.1.6",
"vue-sortable": "github:Netbel/vue-sortable#master-fix", "vue-sortable": "github:Netbel/vue-sortable#master-fix",
"vue-textarea-autosize": "^1.1.1", "vue-textarea-autosize": "^1.1.1",
"vue-transition-expand": "^0.1.0" "vue-transition-expand": "^0.1.0"

View file

@ -0,0 +1,80 @@
<template>
<panZoom
:style="style"
selector="#g1"
@init="onInit"
:options="{ initialZoom: 1, minZoom: 0.1, maxZoom: 10 }"
ref="stage"
>
<svg class="stage" width="100%" height="100%">
<g id="g1">
<BoardNode
v-for="(node, nodeIndex) in nodes"
:key="nodeIndex"
:index="nodeIndex"
:node="node"
:nodeType="board.types[node.type]"
/>
</g>
</svg>
</panZoom>
</template>
<script lang="ts">
import { layers } from "@/game/layers";
import player from "@/game/player";
import { Board } from "@/typings/features/board";
import { InjectLayerMixin } from "@/util/vue";
import { defineComponent } from "vue";
export default defineComponent({
name: "Board",
mixins: [InjectLayerMixin],
props: {
id: {
type: [Number, String],
required: true
}
},
provide() {
return {
getZoomLevel: () => (this.$refs.stage as any).$panZoomInstance.getTransform().scale
};
},
computed: {
board(): Board {
return layers[this.layer].boards!.data[this.id];
},
style(): Array<Partial<CSSStyleDeclaration> | undefined> {
return [
{
width: this.board.width,
height: this.board.height
},
layers[this.layer].componentStyles?.board,
this.board.style
];
},
nodes() {
return player.layers[this.layer].boards[this.id];
}
},
methods: {
onInit: function(panzoomInstance) {
panzoomInstance.setTransformOrigin(null);
}
}
});
</script>
<style>
.vue-pan-zoom-scene {
width: 100%;
height: 100%;
cursor: move;
}
#g1 {
transition-duration: 0s;
}
</style>

View file

@ -0,0 +1,203 @@
<template>
<g
class="boardnode"
:style="{ opacity: dragging ? 0.5 : 1 }"
:transform="`translate(${position.x},${position.y})`"
@mousedown="mouseDown"
>
<circle :r="size + 8" :fill="backgroundColor" stroke="#0F03" :stroke-width="2" />
<circle :r="size" :fill="fillColor" :stroke="outlineColor" :stroke-width="4" />
<circle
v-if="progressDisplay === ProgressDisplay.Fill"
:r="size * progress"
:fill="progressColor"
/>
<circle
v-else
:r="size + 4.5"
class="progressRing"
fill="transparent"
:stroke-dasharray="(size + 4.5) * 2 * Math.PI"
:stroke-width="5"
:stroke-dashoffset="(size + 4.5) * 2 * Math.PI - progress * (size + 4.5) * 2 * Math.PI"
:stroke="progressColor"
/>
<text :fill="titleColor" class="node-title">{{ title }}</text>
</g>
</template>
<script lang="ts">
import themes from "@/data/themes";
import { ProgressDisplay } from "@/game/enums";
import player from "@/game/player";
import { BoardNode, NodeType } from "@/typings/features/board";
import { InjectLayerMixin } from "@/util/vue";
import { defineComponent, PropType } from "vue";
// TODO will blindly use any T given (can't restrict it to S[R] because I can't figure out how
// to make it support narrowing the return type)
function getTypeProperty<T, S extends NodeType, R extends keyof S>(
nodeType: S,
node: BoardNode,
property: R
): S[R] extends Pick<
S,
{
[K in keyof S]-?: undefined extends S[K] ? never : K;
}[keyof S]
>
? T
: T | undefined {
return typeof nodeType[property] === "function"
? (nodeType[property] as (node: BoardNode) => T)(node)
: (nodeType[property] as T);
}
export default defineComponent({
name: "BoardNode",
mixins: [InjectLayerMixin],
inject: ["getZoomLevel"],
data() {
return {
ProgressDisplay,
lastMousePosition: { x: 0, y: 0 },
dragged: { x: 0, y: 0 },
dragging: false
};
},
props: {
index: {
type: Number,
required: true
},
node: {
type: Object as PropType<BoardNode>,
required: true
},
nodeType: {
type: Object as PropType<NodeType>,
required: true
}
},
computed: {
draggable(): boolean {
return getTypeProperty(this.nodeType, this.node, "draggable");
},
position(): { x: number; y: number } {
return this.draggable && this.dragging
? {
x: this.node.position.x + Math.round(this.dragged.x / 25) * 25,
y: this.node.position.y + Math.round(this.dragged.y / 25) * 25
}
: this.node.position;
},
size(): number {
return getTypeProperty(this.nodeType, this.node, "size");
},
title(): string {
return getTypeProperty(this.nodeType, this.node, "title");
},
progress(): number {
return getTypeProperty(this.nodeType, this.node, "progress") || 0;
},
backgroundColor(): string {
return themes[player.theme].variables["--background"];
},
outlineColor(): string {
return (
getTypeProperty(this.nodeType, this.node, "outlineColor") ||
themes[player.theme].variables["--separator"]
);
},
fillColor(): string {
return (
getTypeProperty(this.nodeType, this.node, "fillColor") ||
themes[player.theme].variables["--secondary-background"]
);
},
progressColor(): string {
return getTypeProperty(this.nodeType, this.node, "progressColor") || "none";
},
titleColor(): string {
return (
getTypeProperty(this.nodeType, this.node, "titleColor") ||
themes[player.theme].variables["--color"]
);
},
progressDisplay(): ProgressDisplay {
return (
getTypeProperty(this.nodeType, this.node, "progressDisplay") ||
ProgressDisplay.Outline
);
}
},
methods: {
mouseDown(e: MouseEvent) {
if (this.draggable) {
e.preventDefault();
e.stopPropagation();
this.lastMousePosition = {
x: e.clientX,
y: e.clientY
};
this.dragged = { x: 0, y: 0 };
this.dragging = true;
document.onmouseup = this.mouseUp;
document.onmousemove = this.mouseMove;
}
},
mouseMove(e: MouseEvent) {
if (this.draggable && this.dragging) {
e.preventDefault();
e.stopPropagation();
const zoom = (this.getZoomLevel as () => number)();
console.log(zoom);
this.dragged.x += (e.clientX - this.lastMousePosition.x) / zoom;
this.dragged.y += (e.clientY - this.lastMousePosition.y) / zoom;
this.lastMousePosition = {
x: e.clientX,
y: e.clientY
};
}
},
mouseUp(e: MouseEvent) {
if (this.draggable && this.dragging) {
e.preventDefault();
e.stopPropagation();
let node = player.layers[this.nodeType.layer].boards[this.nodeType.id][this.index];
node.position.x += Math.round(this.dragged.x / 25) * 25;
node.position.y += Math.round(this.dragged.y / 25) * 25;
this.dragging = false;
document.onmouseup = null;
document.onmousemove = null;
}
}
}
});
</script>
<style scoped>
.boardnode {
cursor: pointer;
transition-duration: 0s;
}
.node-title {
text-anchor: middle;
dominant-baseline: middle;
font-family: monospace;
font-size: 200%;
}
.progressRing {
transform: rotate(-90deg);
}
</style>

View file

@ -6,6 +6,7 @@ import VueTextareaAutosize from "vue-textarea-autosize";
import Sortable from "vue-sortable"; import Sortable from "vue-sortable";
import VueNextSelect from "vue-next-select"; import VueNextSelect from "vue-next-select";
import "vue-next-select/dist/index.css"; import "vue-next-select/dist/index.css";
import panZoom from "vue-panzoom";
import { App } from "vue"; import { App } from "vue";
export function registerComponents(vue: App): void { export function registerComponents(vue: App): void {
@ -23,4 +24,5 @@ export function registerComponents(vue: App): void {
vue.use(VueTextareaAutosize); vue.use(VueTextareaAutosize);
vue.use(Sortable); vue.use(Sortable);
vue.component("vue-select", VueNextSelect); vue.component("vue-select", VueNextSelect);
vue.use(panZoom);
} }

View file

@ -28,3 +28,8 @@ export enum ImportingStatus {
WrongID = "WRONG_ID", WrongID = "WRONG_ID",
Force = "FORCE" Force = "FORCE"
} }
export enum ProgressDisplay {
Outline = "Outline",
Fill = "Fill"
}

View file

@ -1,5 +1,6 @@
import { CacheableFunction } from "@/typings/cacheableFunction"; import { CacheableFunction } from "@/typings/cacheableFunction";
import { Achievement } from "@/typings/features/achievement"; import { Achievement } from "@/typings/features/achievement";
import { Board } from "@/typings/features/board";
import { Buyable } from "@/typings/features/buyable"; import { Buyable } from "@/typings/features/buyable";
import { Challenge } from "@/typings/features/challenge"; import { Challenge } from "@/typings/features/challenge";
import { Clickable } from "@/typings/features/clickable"; import { Clickable } from "@/typings/features/clickable";
@ -23,6 +24,7 @@ import Decimal, { DecimalSource } from "@/util/bignum";
import { isFunction } from "@/util/common"; import { isFunction } from "@/util/common";
import { import {
defaultLayerProperties, defaultLayerProperties,
getStartingBoards,
getStartingBuyables, getStartingBuyables,
getStartingChallenges, getStartingChallenges,
getStartingClickables, getStartingClickables,
@ -32,6 +34,7 @@ import { createGridProxy, createLayerProxy } from "@/util/proxies";
import { applyPlayerData } from "@/util/save"; import { applyPlayerData } from "@/util/save";
import clone from "lodash.clonedeep"; import clone from "lodash.clonedeep";
import { isRef } from "vue"; import { isRef } from "vue";
import { ProgressDisplay } from "./enums";
import { default as playerProxy } from "./player"; import { default as playerProxy } from "./player";
export const layers: Record<string, Readonly<Layer>> = {}; export const layers: Record<string, Readonly<Layer>> = {};
@ -70,6 +73,7 @@ export function addLayer(layer: RawLayer, player?: Partial<PlayerData>): void {
buyables: getStartingBuyables(layer.buyables?.data), buyables: getStartingBuyables(layer.buyables?.data),
clickables: getStartingClickables(layer.clickables?.data), clickables: getStartingClickables(layer.clickables?.data),
challenges: getStartingChallenges(layer.challenges?.data), challenges: getStartingChallenges(layer.challenges?.data),
boards: getStartingBoards(layer.boards?.data),
grids: {}, grids: {},
confirmRespecBuyables: false, confirmRespecBuyables: false,
...(layer.startData?.() || {}) ...(layer.startData?.() || {})
@ -423,6 +427,31 @@ export function addLayer(layer: RawLayer, player?: Partial<PlayerData>): void {
layer.grids.data[id] = createGridProxy(layer.grids.data[id]) as Grid; layer.grids.data[id] = createGridProxy(layer.grids.data[id]) as Grid;
} }
} }
if (layer.boards) {
setupFeatures<NonNullable<RawLayer["boards"]>, Board>(layer.id, layer.boards);
for (const id in layer.boards.data) {
setDefault(layer.boards.data[id], "width", "100%");
setDefault(layer.boards.data[id], "height", "400px");
for (const nodeType in layer.boards.data[id].types) {
layer.boards.data[id].types[nodeType].layer = layer.id;
layer.boards.data[id].types[nodeType].id = id;
layer.boards.data[id].types[nodeType].type = nodeType;
setDefault(layer.boards.data[id].types[nodeType], "size", 50);
setDefault(layer.boards.data[id].types[nodeType], "draggable", false);
setDefault(layer.boards.data[id].types[nodeType], "canAccept", false);
setDefault(
layer.boards.data[id].types[nodeType],
"progressDisplay",
ProgressDisplay.Fill
);
setDefault(layer.boards.data[id].types[nodeType], "nodes", function() {
return playerProxy.layers[this.layer].boards[this.id].filter(
node => node.type === this.type
);
});
}
}
}
if (layer.subtabs) { if (layer.subtabs) {
layer.activeSubtab = function() { layer.activeSubtab = function() {
if ( if (

45
src/typings/features/board.d.ts vendored Normal file
View file

@ -0,0 +1,45 @@
import { State } from "../state";
import { Feature, RawFeature } from "./feature";
export interface BoardNode {
position: {
x: number;
y: number;
};
type: string;
data?: State;
}
export interface CardOption {
text: string;
selected: (node: BoardNode) => void;
}
export interface Board extends Feature {
startNodes: () => BoardNode[];
style?: Partial<CSSStyleDeclaration>;
height: string;
width: string;
types: Record<string, NodeType>;
}
export type RawBoard = Omit<RawFeature<Board>, "types"> & {
startNodes: () => BoardNode[];
types: Record<string, RawFeature<NodeType>>;
};
export interface NodeType extends Feature {
tooltip?: string | ((node: BoardNode) => string);
title: string | ((node: BoardNode) => string);
size: number | ((node: BoardNode) => number);
draggable: boolean | ((node: BoardNode) => boolean);
canAccept: boolean | ((node: BoardNode, otherNode: BoardNode) => boolean);
progress?: number | ((node: BoardNode) => number);
progressDisplay: ProgressDisplay | ((node: BoardNode) => ProgressDisplay);
progressColor: string | ((node: BoardNode) => string);
fillColor?: string | ((node: BoardNode) => string);
outlineColor?: string | ((node: BoardNode) => string);
titleColor?: string | ((node: BoardNode) => string);
onClick: (node: BoardNode) => void;
nodes: BoardNode[];
}

View file

@ -3,6 +3,7 @@ import Decimal, { DecimalSource } from "@/util/bignum";
import { CoercableComponent } from "./component"; import { CoercableComponent } from "./component";
import { Achievement } from "./features/achievement"; import { Achievement } from "./features/achievement";
import { Bar } from "./features/bar"; import { Bar } from "./features/bar";
import { Board, RawBoard } from "./features/board";
import { Buyable } from "./features/buyable"; import { Buyable } from "./features/buyable";
import { Challenge } from "./features/challenge"; import { Challenge } from "./features/challenge";
import { Clickable } from "./features/clickable"; import { Clickable } from "./features/clickable";
@ -31,6 +32,7 @@ export interface RawLayer extends RawFeature<Layer> {
challenges?: RawGridFeatures<NonNullable<Layer["challenges"]>, Challenge>; challenges?: RawGridFeatures<NonNullable<Layer["challenges"]>, Challenge>;
clickables?: RawGridFeatures<NonNullable<Layer["clickables"]>, Clickable>; clickables?: RawGridFeatures<NonNullable<Layer["clickables"]>, Clickable>;
grids?: RawFeatures<NonNullable<Layer["grids"]>, Grid>; grids?: RawFeatures<NonNullable<Layer["grids"]>, Grid>;
boards?: RawFeatures<NonNullable<Layer["boards"]>, Board, RawBoard>;
hotkeys?: RawFeature<Hotkey>[]; hotkeys?: RawFeature<Hotkey>[];
infoboxes?: RawFeatures<NonNullable<Layer["infoboxes"]>, Infoboxe>; infoboxes?: RawFeatures<NonNullable<Layer["infoboxes"]>, Infoboxe>;
milestones?: RawFeatures<NonNullable<Layer["milestones"]>, Milestone>; milestones?: RawFeatures<NonNullable<Layer["milestones"]>, Milestone>;
@ -108,6 +110,7 @@ export interface Layer extends Feature {
showMasterButton?: boolean; showMasterButton?: boolean;
}; };
grids?: Features<Grid>; grids?: Features<Grid>;
boards?: Features<Board>;
hotkeys?: Hotkey[]; hotkeys?: Hotkey[];
infoboxes?: Features<Infobox>; infoboxes?: Features<Infobox>;
milestones?: Features<Milestone>; milestones?: Features<Milestone>;
@ -142,5 +145,6 @@ export interface ComponentStyles {
"prestige-button"?: Partial<CSSStyleDeclaration>; "prestige-button"?: Partial<CSSStyleDeclaration>;
"respec-button"?: Partial<CSSStyleDeclaration>; "respec-button"?: Partial<CSSStyleDeclaration>;
upgrade?: Partial<CSSStyleDeclaration>; upgrade?: Partial<CSSStyleDeclaration>;
board?: Partial<CSSStyleDeclaration>;
"tab-button"?: Partial<CSSStyleDeclaration>; "tab-button"?: Partial<CSSStyleDeclaration>;
} }

View file

@ -1,6 +1,7 @@
import { Themes } from "@/data/themes"; import { Themes } from "@/data/themes";
import { DecimalSource } from "@/lib/break_eternity"; import { DecimalSource } from "@/lib/break_eternity";
import Decimal from "@/util/bignum"; import Decimal from "@/util/bignum";
import { BoardNode } from "./features/board";
import { MilestoneDisplay } from "./features/milestone"; import { MilestoneDisplay } from "./features/milestone";
import { State } from "./state"; import { State } from "./state";
@ -53,14 +54,15 @@ export interface LayerSaveData {
unlockOrder?: number; unlockOrder?: number;
forceTooltip?: boolean; forceTooltip?: boolean;
resetTime: Decimal; resetTime: Decimal;
upgrades: Array<string | number>; upgrades: Array<string>;
achievements: Array<string | number>; achievements: Array<string>;
milestones: Array<string | number>; milestones: Array<string>;
infoboxes: Record<string | number, boolean>; infoboxes: Record<string, boolean>;
buyables: Record<string | number, Decimal>; buyables: Record<string, Decimal>;
clickables: Record<string | number, State>; clickables: Record<string, State>;
challenges: Record<string | number, Decimal>; challenges: Record<string, Decimal>;
grids: Record<string | number, Record<string, number, State>>; grids: Record<string, Record<string, State>>;
boards: Record<string, Array<BoardNode>>;
confirmRespecBuyables: boolean; confirmRespecBuyables: boolean;
[index: string]: unknown; [index: string]: unknown;
} }

View file

@ -1,6 +1,18 @@
export interface Theme { export interface Theme {
variables: { variables: {
[index: string]: string; "--background": string;
"--background-tooltip": string;
"--secondary-background": string;
"--color": string;
"--points": string;
"--locked": string;
"--bought": string;
"--link": string;
"--separator": string;
"--border-radius": string;
"--danger": string;
"--modal-border": string;
"--feature-margin": string;
}; };
stackedInfoboxes: boolean; stackedInfoboxes: boolean;
floatingTabs: boolean; floatingTabs: boolean;

View file

@ -1,6 +1,7 @@
import { hotkeys, layers } from "@/game/layers"; import { hotkeys, layers } from "@/game/layers";
import player from "@/game/player"; import player from "@/game/player";
import { CacheableFunction } from "@/typings/cacheableFunction"; import { CacheableFunction } from "@/typings/cacheableFunction";
import { Board, BoardNode, RawBoard } from "@/typings/features/board";
import { Buyable } from "@/typings/features/buyable"; import { Buyable } from "@/typings/features/buyable";
import { Challenge } from "@/typings/features/challenge"; import { Challenge } from "@/typings/features/challenge";
import { Clickable } from "@/typings/features/clickable"; import { Clickable } from "@/typings/features/clickable";
@ -70,6 +71,20 @@ export function getStartingChallenges(
: {}; : {};
} }
export function getStartingBoards(
boards?: Record<string, Board> | Record<string, RawBoard> | undefined
): Record<string, Array<BoardNode>> {
return boards
? Object.keys(boards).reduce((acc: Record<string, Array<BoardNode>>, curr: string): Record<
string,
Array<BoardNode>
> => {
acc[curr] = boards[curr].startNodes?.() || [];
return acc;
}, {})
: {};
}
export function resetLayerData(layer: string, keep: Array<string> = []): void { export function resetLayerData(layer: string, keep: Array<string> = []): void {
keep.push("unlocked", "forceTooltip", "noRespecConfirm"); keep.push("unlocked", "forceTooltip", "noRespecConfirm");
const keptData = keep.reduce((acc: Record<string, any>, curr: string): Record<string, any> => { const keptData = keep.reduce((acc: Record<string, any>, curr: string): Record<string, any> => {

View file

@ -13,7 +13,6 @@ export function createLayerProxy(object: Record<string, any>): Record<string, an
return objectProxy; return objectProxy;
} }
// TODO cache grid values? Currently they'll be calculated every render they're visible
export function createGridProxy(object: Record<string, any>): Record<string, any> { export function createGridProxy(object: Record<string, any>): Record<string, any> {
if (object.isProxy) { if (object.isProxy) {
console.warn( console.warn(