From 0dcf417a314fdf439380510cf76e53d01941b7d1 Mon Sep 17 00:00:00 2001 From: thepaperpilot <thepaperpilot@gmail.com> Date: Thu, 19 Aug 2021 00:25:49 -0500 Subject: [PATCH] Implemented most of the basic features for new Board component --- package-lock.json | 83 ++++++++++++ package.json | 1 + src/components/board/Board.vue | 80 ++++++++++++ src/components/board/BoardNode.vue | 203 +++++++++++++++++++++++++++++ src/components/index.ts | 2 + src/game/enums.ts | 5 + src/game/layers.ts | 29 +++++ src/typings/features/board.d.ts | 45 +++++++ src/typings/layer.d.ts | 4 + src/typings/player.d.ts | 18 +-- src/typings/theme.d.ts | 14 +- src/util/layers.ts | 15 +++ src/util/proxies.ts | 1 - 13 files changed, 490 insertions(+), 10 deletions(-) create mode 100644 src/components/board/Board.vue create mode 100644 src/components/board/BoardNode.vue create mode 100644 src/typings/features/board.d.ts diff --git a/package-lock.json b/package-lock.json index 82536ac..b496392 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "vue": "^3.2.2", "vue-class-component": "^8.0.0-rc.1", "vue-next-select": "^2.9.0", + "vue-panzoom": "^1.1.6", "vue-sortable": "github:Netbel/vue-sortable#master-fix", "vue-textarea-autosize": "^1.1.1", "vue-transition-expand": "^0.1.0" @@ -14925,6 +14926,14 @@ "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", "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": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", @@ -15495,6 +15504,11 @@ "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": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz", @@ -22071,6 +22085,11 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "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": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -22736,6 +22755,16 @@ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "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": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", @@ -27245,6 +27274,14 @@ "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": { "version": "0.1.3", "resolved": "git+ssh://git@github.com/Netbel/vue-sortable.git#f4d4870ace71ea59bd79252eb2ec1cf6bfb02fe7", @@ -28083,6 +28120,11 @@ "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": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -31140,6 +31182,14 @@ "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", "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": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", @@ -31576,6 +31626,11 @@ "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": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz", @@ -36803,6 +36858,11 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "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": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -37323,6 +37383,16 @@ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "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": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", @@ -41002,6 +41072,14 @@ "integrity": "sha512-GjX4pHqZXXitquDeSAtLaf85jXdMUOKyCNzo+EF3xRr4DebGwbST4CtmRvL0TX3EhwLHQjUlAc3JcJX+azpLHg==", "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": { "version": "git+ssh://git@github.com/Netbel/vue-sortable.git#f4d4870ace71ea59bd79252eb2ec1cf6bfb02fe7", "from": "vue-sortable@github:Netbel/vue-sortable#master-fix", @@ -41687,6 +41765,11 @@ "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true }, + "wheel": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wheel/-/wheel-1.0.0.tgz", + "integrity": "sha512-XiCMHibOiqalCQ+BaNSwRoZ9FDTAvOsXxGHXChBugewDj7HC8VBIER71dEOiRH1fSdLbRCQzngKTSiZ06ZQzeA==" + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/package.json b/package.json index e65641f..d09e8ea 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "vue": "^3.2.2", "vue-class-component": "^8.0.0-rc.1", "vue-next-select": "^2.9.0", + "vue-panzoom": "^1.1.6", "vue-sortable": "github:Netbel/vue-sortable#master-fix", "vue-textarea-autosize": "^1.1.1", "vue-transition-expand": "^0.1.0" diff --git a/src/components/board/Board.vue b/src/components/board/Board.vue new file mode 100644 index 0000000..f490b60 --- /dev/null +++ b/src/components/board/Board.vue @@ -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> diff --git a/src/components/board/BoardNode.vue b/src/components/board/BoardNode.vue new file mode 100644 index 0000000..bec3081 --- /dev/null +++ b/src/components/board/BoardNode.vue @@ -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> diff --git a/src/components/index.ts b/src/components/index.ts index 9619597..408338f 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -6,6 +6,7 @@ import VueTextareaAutosize from "vue-textarea-autosize"; import Sortable from "vue-sortable"; import VueNextSelect from "vue-next-select"; import "vue-next-select/dist/index.css"; +import panZoom from "vue-panzoom"; import { App } from "vue"; export function registerComponents(vue: App): void { @@ -23,4 +24,5 @@ export function registerComponents(vue: App): void { vue.use(VueTextareaAutosize); vue.use(Sortable); vue.component("vue-select", VueNextSelect); + vue.use(panZoom); } diff --git a/src/game/enums.ts b/src/game/enums.ts index f175079..a9047d3 100644 --- a/src/game/enums.ts +++ b/src/game/enums.ts @@ -28,3 +28,8 @@ export enum ImportingStatus { WrongID = "WRONG_ID", Force = "FORCE" } + +export enum ProgressDisplay { + Outline = "Outline", + Fill = "Fill" +} diff --git a/src/game/layers.ts b/src/game/layers.ts index 41c688e..d20d094 100644 --- a/src/game/layers.ts +++ b/src/game/layers.ts @@ -1,5 +1,6 @@ import { CacheableFunction } from "@/typings/cacheableFunction"; import { Achievement } from "@/typings/features/achievement"; +import { Board } from "@/typings/features/board"; import { Buyable } from "@/typings/features/buyable"; import { Challenge } from "@/typings/features/challenge"; import { Clickable } from "@/typings/features/clickable"; @@ -23,6 +24,7 @@ import Decimal, { DecimalSource } from "@/util/bignum"; import { isFunction } from "@/util/common"; import { defaultLayerProperties, + getStartingBoards, getStartingBuyables, getStartingChallenges, getStartingClickables, @@ -32,6 +34,7 @@ import { createGridProxy, createLayerProxy } from "@/util/proxies"; import { applyPlayerData } from "@/util/save"; import clone from "lodash.clonedeep"; import { isRef } from "vue"; +import { ProgressDisplay } from "./enums"; import { default as playerProxy } from "./player"; 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), clickables: getStartingClickables(layer.clickables?.data), challenges: getStartingChallenges(layer.challenges?.data), + boards: getStartingBoards(layer.boards?.data), grids: {}, confirmRespecBuyables: false, ...(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; } } + 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) { layer.activeSubtab = function() { if ( diff --git a/src/typings/features/board.d.ts b/src/typings/features/board.d.ts new file mode 100644 index 0000000..520411e --- /dev/null +++ b/src/typings/features/board.d.ts @@ -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[]; +} diff --git a/src/typings/layer.d.ts b/src/typings/layer.d.ts index 9059174..9dfd56a 100644 --- a/src/typings/layer.d.ts +++ b/src/typings/layer.d.ts @@ -3,6 +3,7 @@ import Decimal, { DecimalSource } from "@/util/bignum"; import { CoercableComponent } from "./component"; import { Achievement } from "./features/achievement"; import { Bar } from "./features/bar"; +import { Board, RawBoard } from "./features/board"; import { Buyable } from "./features/buyable"; import { Challenge } from "./features/challenge"; import { Clickable } from "./features/clickable"; @@ -31,6 +32,7 @@ export interface RawLayer extends RawFeature<Layer> { challenges?: RawGridFeatures<NonNullable<Layer["challenges"]>, Challenge>; clickables?: RawGridFeatures<NonNullable<Layer["clickables"]>, Clickable>; grids?: RawFeatures<NonNullable<Layer["grids"]>, Grid>; + boards?: RawFeatures<NonNullable<Layer["boards"]>, Board, RawBoard>; hotkeys?: RawFeature<Hotkey>[]; infoboxes?: RawFeatures<NonNullable<Layer["infoboxes"]>, Infoboxe>; milestones?: RawFeatures<NonNullable<Layer["milestones"]>, Milestone>; @@ -108,6 +110,7 @@ export interface Layer extends Feature { showMasterButton?: boolean; }; grids?: Features<Grid>; + boards?: Features<Board>; hotkeys?: Hotkey[]; infoboxes?: Features<Infobox>; milestones?: Features<Milestone>; @@ -142,5 +145,6 @@ export interface ComponentStyles { "prestige-button"?: Partial<CSSStyleDeclaration>; "respec-button"?: Partial<CSSStyleDeclaration>; upgrade?: Partial<CSSStyleDeclaration>; + board?: Partial<CSSStyleDeclaration>; "tab-button"?: Partial<CSSStyleDeclaration>; } diff --git a/src/typings/player.d.ts b/src/typings/player.d.ts index f45c2cc..b75d2f3 100644 --- a/src/typings/player.d.ts +++ b/src/typings/player.d.ts @@ -1,6 +1,7 @@ import { Themes } from "@/data/themes"; import { DecimalSource } from "@/lib/break_eternity"; import Decimal from "@/util/bignum"; +import { BoardNode } from "./features/board"; import { MilestoneDisplay } from "./features/milestone"; import { State } from "./state"; @@ -53,14 +54,15 @@ export interface LayerSaveData { unlockOrder?: number; forceTooltip?: boolean; resetTime: Decimal; - upgrades: Array<string | number>; - achievements: Array<string | number>; - milestones: Array<string | number>; - infoboxes: Record<string | number, boolean>; - buyables: Record<string | number, Decimal>; - clickables: Record<string | number, State>; - challenges: Record<string | number, Decimal>; - grids: Record<string | number, Record<string, number, State>>; + upgrades: Array<string>; + achievements: Array<string>; + milestones: Array<string>; + infoboxes: Record<string, boolean>; + buyables: Record<string, Decimal>; + clickables: Record<string, State>; + challenges: Record<string, Decimal>; + grids: Record<string, Record<string, State>>; + boards: Record<string, Array<BoardNode>>; confirmRespecBuyables: boolean; [index: string]: unknown; } diff --git a/src/typings/theme.d.ts b/src/typings/theme.d.ts index d05ddbb..b3236e4 100644 --- a/src/typings/theme.d.ts +++ b/src/typings/theme.d.ts @@ -1,6 +1,18 @@ export interface Theme { 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; floatingTabs: boolean; diff --git a/src/util/layers.ts b/src/util/layers.ts index 5bbb2e2..519ca43 100644 --- a/src/util/layers.ts +++ b/src/util/layers.ts @@ -1,6 +1,7 @@ import { hotkeys, layers } from "@/game/layers"; import player from "@/game/player"; import { CacheableFunction } from "@/typings/cacheableFunction"; +import { Board, BoardNode, RawBoard } from "@/typings/features/board"; import { Buyable } from "@/typings/features/buyable"; import { Challenge } from "@/typings/features/challenge"; 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 { keep.push("unlocked", "forceTooltip", "noRespecConfirm"); const keptData = keep.reduce((acc: Record<string, any>, curr: string): Record<string, any> => { diff --git a/src/util/proxies.ts b/src/util/proxies.ts index 57fa533..83c1c58 100644 --- a/src/util/proxies.ts +++ b/src/util/proxies.ts @@ -13,7 +13,6 @@ export function createLayerProxy(object: Record<string, any>): Record<string, an 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> { if (object.isProxy) { console.warn(