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 @@ + + + + + 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 @@ + + + + + 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> = {}; @@ -70,6 +73,7 @@ export function addLayer(layer: RawLayer, player?: Partial): 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): void { layer.grids.data[id] = createGridProxy(layer.grids.data[id]) as Grid; } } + if (layer.boards) { + setupFeatures, 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; + height: string; + width: string; + types: Record; +} + +export type RawBoard = Omit, "types"> & { + startNodes: () => BoardNode[]; + types: Record>; +}; + +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 { challenges?: RawGridFeatures, Challenge>; clickables?: RawGridFeatures, Clickable>; grids?: RawFeatures, Grid>; + boards?: RawFeatures, Board, RawBoard>; hotkeys?: RawFeature[]; infoboxes?: RawFeatures, Infoboxe>; milestones?: RawFeatures, Milestone>; @@ -108,6 +110,7 @@ export interface Layer extends Feature { showMasterButton?: boolean; }; grids?: Features; + boards?: Features; hotkeys?: Hotkey[]; infoboxes?: Features; milestones?: Features; @@ -142,5 +145,6 @@ export interface ComponentStyles { "prestige-button"?: Partial; "respec-button"?: Partial; upgrade?: Partial; + board?: Partial; "tab-button"?: Partial; } 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; - achievements: Array; - milestones: Array; - infoboxes: Record; - buyables: Record; - clickables: Record; - challenges: Record; - grids: Record>; + upgrades: Array; + achievements: Array; + milestones: Array; + infoboxes: Record; + buyables: Record; + clickables: Record; + challenges: Record; + grids: Record>; + boards: Record>; 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 | Record | undefined +): Record> { + return boards + ? Object.keys(boards).reduce((acc: Record>, curr: string): Record< + string, + Array + > => { + acc[curr] = boards[curr].startNodes?.() || []; + return acc; + }, {}) + : {}; +} + export function resetLayerData(layer: string, keep: Array = []): void { keep.push("unlocked", "forceTooltip", "noRespecConfirm"); const keptData = keep.reduce((acc: Record, curr: string): Record => { 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): Record): Record { if (object.isProxy) { console.warn(