forked from profectus/Profectus
Implemented most of the basic features for new Board component
This commit is contained in:
parent
c8651adb6b
commit
d47ce3525d
13 changed files with 490 additions and 10 deletions
83
package-lock.json
generated
83
package-lock.json
generated
|
@ -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",
|
||||||
|
|
|
@ -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"
|
||||||
|
|
80
src/components/board/Board.vue
Normal file
80
src/components/board/Board.vue
Normal 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>
|
203
src/components/board/BoardNode.vue
Normal file
203
src/components/board/BoardNode.vue
Normal 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>
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,3 +28,8 @@ export enum ImportingStatus {
|
||||||
WrongID = "WRONG_ID",
|
WrongID = "WRONG_ID",
|
||||||
Force = "FORCE"
|
Force = "FORCE"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ProgressDisplay {
|
||||||
|
Outline = "Outline",
|
||||||
|
Fill = "Fill"
|
||||||
|
}
|
||||||
|
|
|
@ -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
45
src/typings/features/board.d.ts
vendored
Normal 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[];
|
||||||
|
}
|
4
src/typings/layer.d.ts
vendored
4
src/typings/layer.d.ts
vendored
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
18
src/typings/player.d.ts
vendored
18
src/typings/player.d.ts
vendored
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
14
src/typings/theme.d.ts
vendored
14
src/typings/theme.d.ts
vendored
|
@ -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;
|
||||||
|
|
|
@ -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> => {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
Loading…
Reference in a new issue