From 424bde0cddc9b20f1d57e2ad5af94fefee1669b5 Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Wed, 28 Feb 2024 23:19:11 -0600 Subject: [PATCH 01/58] WIP on rewriting board --- src/data/common.css | 9 + src/data/projEntry.tsx | 417 +++++++++++++--- src/features/boards/Board.vue | 321 +++--------- src/features/boards/BoardLink.vue | 80 --- src/features/boards/BoardNode.vue | 339 ------------- src/features/boards/BoardNodeAction.vue | 109 ---- src/features/boards/CircleProgress.vue | 29 ++ src/features/boards/SVGNode.vue | 27 + src/features/boards/SquareProgress.vue | 30 ++ src/features/boards/board.ts | 631 ------------------------ src/features/boards/board.tsx | 317 ++++++++++++ 11 files changed, 820 insertions(+), 1489 deletions(-) delete mode 100644 src/features/boards/BoardLink.vue delete mode 100644 src/features/boards/BoardNode.vue delete mode 100644 src/features/boards/BoardNodeAction.vue create mode 100644 src/features/boards/CircleProgress.vue create mode 100644 src/features/boards/SVGNode.vue create mode 100644 src/features/boards/SquareProgress.vue delete mode 100644 src/features/boards/board.ts create mode 100644 src/features/boards/board.tsx diff --git a/src/data/common.css b/src/data/common.css index 728c160..1d13f96 100644 --- a/src/data/common.css +++ b/src/data/common.css @@ -7,3 +7,12 @@ .modifier-toggle.collapsed { transform: translate(-5px, -5px) rotate(-90deg); } + +.node-text { + text-anchor: middle; + dominant-baseline: middle; + font-family: monospace; + font-size: 200%; + pointer-events: none; + filter: drop-shadow(3px 3px 2px var(--tooltip-background)); +} diff --git a/src/data/projEntry.tsx b/src/data/projEntry.tsx index f69ac8b..d256f4e 100644 --- a/src/data/projEntry.tsx +++ b/src/data/projEntry.tsx @@ -1,92 +1,371 @@ -import Node from "components/Node.vue"; -import Spacer from "components/layout/Spacer.vue"; +import Board from "features/boards/Board.vue"; +import CircleProgress from "features/boards/CircleProgress.vue"; +import SVGNode from "features/boards/SVGNode.vue"; +import SquareProgress from "features/boards/SquareProgress.vue"; +import { + NodePosition, + placeInAvailableSpace, + setupActions, + setupDraggableNode, + setupSelectable, + setupUniqueIds +} from "features/boards/board"; import { jsx } from "features/feature"; -import { createResource, trackBest, trackOOMPS, trackTotal } from "features/resources/resource"; -import type { GenericTree } from "features/trees/tree"; -import { branchedResetPropagation, createTree } from "features/trees/tree"; -import { globalBus } from "game/events"; import type { BaseLayer, GenericLayer } from "game/layers"; import { createLayer } from "game/layers"; +import { persistent } from "game/persistence"; import type { Player } from "game/player"; -import player from "game/player"; -import type { DecimalSource } from "util/bignum"; -import Decimal, { format, formatTime } from "util/bignum"; -import { render } from "util/vue"; -import { computed, toRaw } from "vue"; +import { ComponentPublicInstance, computed, ref, watch } from "vue"; import prestige from "./layers/prestige"; +type ANode = NodePosition & { id: number; links: number[]; type: "anode" }; +type BNode = NodePosition & { id: number; links: number[]; type: "bnode" }; +type NodeTypes = ANode | BNode; + /** * @hidden */ export const main = createLayer("main", function (this: BaseLayer) { - const points = createResource(10); - const best = trackBest(points); - const total = trackTotal(points); + const board = ref>(); - const pointGain = computed(() => { - // eslint-disable-next-line prefer-const - let gain = new Decimal(1); - return gain; - }); - globalBus.on("update", diff => { - points.value = Decimal.add(points.value, Decimal.times(pointGain.value, diff)); - }); - const oomps = trackOOMPS(points, pointGain); + const { select, deselect, selected } = setupSelectable(); + const { + select: selectAction, + deselect: deselectAction, + selected: selectedAction + } = setupSelectable(); - const tree = createTree(() => ({ - nodes: [[prestige.treeNode]], - branches: [], - onReset() { - points.value = toRaw(this.resettingNode.value) === toRaw(prestige.treeNode) ? 0 : 10; - best.value = points.value; - total.value = points.value; + watch(selected, selected => { + if (selected == null) { + deselectAction(); + } + }); + + const { + startDrag, + endDrag, + drag, + nodeBeingDragged, + hasDragged, + receivingNodes, + receivingNode, + dragDelta + } = setupDraggableNode({ + board, + isDraggable: function (node) { + return nodes.value.includes(node); + } + }); + + // a nodes can be slotted into b nodes to draw a branch between them, with limited connections + // a nodes can be selected and have an action to spawn a b node, and vice versa + // Newly spawned nodes should find a safe spot to spawn, and display a link to their creator + // a nodes use all the stuff circles used to have, and b diamonds + // c node also exists but is a single Upgrade element that cannot be selected, but can be dragged + // d nodes are a performance test - 1000 simple nodes that have no interactions + // Make all nodes animate in (decorator? `fadeIn(feature)?) + const nodes = persistent([{ id: 0, x: 0, y: 0, links: [], type: "anode" }]); + const nodesById = computed>(() => + nodes.value.reduce((acc, curr) => ({ ...acc, [curr.id]: curr }), {}) + ); + function mouseDownNode(e: MouseEvent | TouchEvent, node: NodeTypes) { + if (nodeBeingDragged.value == null) { + startDrag(e, node); + } + deselect(); + } + function mouseUpNode(e: MouseEvent | TouchEvent, node: NodeTypes) { + if (!hasDragged.value) { + endDrag(); + select(node); + e.stopPropagation(); + } + } + function getTranslateString(node: NodePosition, overrideSelected?: boolean) { + const isSelected = overrideSelected == null ? selected.value === node : overrideSelected; + const isDragging = !isSelected && nodeBeingDragged.value === node; + let x = node.x; + let y = node.y; + if (isDragging) { + x += dragDelta.value.x; + y += dragDelta.value.y; + } + return ` translate(${x}px,${y}px)`; + } + function getRotateString(rotation: number) { + return ` rotate(${rotation}deg) `; + } + function getScaleString(node: NodePosition, overrideSelected?: boolean) { + const isSelected = overrideSelected == null ? selected.value === node : overrideSelected; + return isSelected ? " scale(1.2)" : ""; + } + function getOpacityString(node: NodePosition, overrideSelected?: boolean) { + const isSelected = overrideSelected == null ? selected.value === node : overrideSelected; + const isDragging = !isSelected && nodeBeingDragged.value === node; + if (isDragging) { + return "; opacity: 0.5;"; + } + return ""; + } + + const renderANode = function (node: ANode) { + return ( + mouseDownNode(e, node)} + onMouseUp={e => mouseUpNode(e, node)} + > + + {receivingNodes.value.includes(node) && ( + + )} + + + + {selected.value === node && selectedAction.value === 0 && ( + + Spawn B Node + + )} + + A + + + ); + }; + const aActions = setupActions({ + node: selected, + shouldShowActions: () => selected.value?.type === "anode", + actions(node) { + return [ + p => ( + { + if (selectedAction.value === 0) { + spawnBNode(node); + } else { + selectAction(0); + } + }} + > + + + add + + + ) + ]; }, - resetPropagation: branchedResetPropagation - })) as GenericTree; + distance: 100 + }); + const sqrtTwo = Math.sqrt(2); + const renderBNode = function (node: BNode) { + return ( + mouseDownNode(e, node)} + onMouseUp={e => mouseUpNode(e, node)} + > + + {receivingNodes.value.includes(node) && ( + + )} + + + + {selected.value === node && selectedAction.value === 0 && ( + + Spawn A Node + + )} + + B + + + ); + }; + const bActions = setupActions({ + node: selected, + shouldShowActions: () => selected.value?.type === "bnode", + actions(node) { + return [ + p => ( + { + if (selectedAction.value === 0) { + spawnANode(node); + } else { + selectAction(0); + } + }} + > + + + add + + + ) + ]; + }, + distance: 100 + }); + function spawnANode(parent: NodeTypes) { + const node: ANode = { + x: parent.x, + y: parent.y, + type: "anode", + links: [parent.id], + id: nextId.value + }; + placeInAvailableSpace(node, nodes.value); + nodes.value.push(node); + } + function spawnBNode(parent: NodeTypes) { + const node: BNode = { + x: parent.x, + y: parent.y, + type: "bnode", + links: [parent.id], + id: nextId.value + }; + placeInAvailableSpace(node, nodes.value); + nodes.value.push(node); + } + + // const cNode = createUpgrade(() => ({ + // requirements: createCostRequirement(() => ({ cost: 10, resource: points })), + // style: { + // x: "100px", + // y: "100px" + // } + // })); + // makeDraggable(cNode); // TODO make decorator + + // const dNodes; + + const links = jsx(() => ( + <> + {nodes.value + .reduce( + (acc, curr) => [ + ...acc, + ...curr.links.map(l => ({ from: curr, to: nodesById.value[l] })) + ], + [] as { from: NodeTypes; to: NodeTypes }[] + ) + .map(link => ( + + ))} + + )); + + const nextId = setupUniqueIds(() => nodes.value); + + function filterNodes(n: NodeTypes) { + return n !== nodeBeingDragged.value && n !== selected.value; + } + + function renderNode(node: NodeTypes | undefined) { + if (node == undefined) { + return undefined; + } else if (node.type === "anode") { + return renderANode(node); + } else if (node.type === "bnode") { + return renderBNode(node); + } + } return { name: "Tree", - links: tree.links, display: jsx(() => ( <> - {player.devSpeed === 0 ? ( -
- Game Paused - -
- ) : null} - {player.devSpeed != null && player.devSpeed !== 0 && player.devSpeed !== 1 ? ( -
- Dev Speed: {format(player.devSpeed)}x - -
- ) : null} - {player.offlineTime != null && player.offlineTime !== 0 ? ( -
- Offline Time: {formatTime(player.offlineTime)} - -
- ) : null} -
- {Decimal.lt(points.value, "1e1000") ? You have : null} -

{format(points.value)}

- {Decimal.lt(points.value, "1e1e6") ? points : null} -
- {Decimal.gt(pointGain.value, 0) ? ( -
- ({oomps.value}) - -
- ) : null} - - {render(tree)} + + {links()} + {nodes.value.filter(filterNodes).map(renderNode)} + + {aActions()} + {bActions()} + + {renderNode(selected.value)} + {renderNode(nodeBeingDragged.value)} + )), - points, - best, - total, - oomps, - tree + boardNodes: nodes + // cNode }; }); diff --git a/src/features/boards/Board.vue b/src/features/boards/Board.vue index 218b302..0ded9a1 100644 --- a/src/features/boards/Board.vue +++ b/src/features/boards/Board.vue @@ -1,278 +1,75 @@ + + diff --git a/src/features/boards/BoardLink.vue b/src/features/boards/BoardLink.vue deleted file mode 100644 index 5dacc66..0000000 --- a/src/features/boards/BoardLink.vue +++ /dev/null @@ -1,80 +0,0 @@ - - - - - diff --git a/src/features/boards/BoardNode.vue b/src/features/boards/BoardNode.vue deleted file mode 100644 index 6a32f37..0000000 --- a/src/features/boards/BoardNode.vue +++ /dev/null @@ -1,339 +0,0 @@ - - - - - - - diff --git a/src/features/boards/BoardNodeAction.vue b/src/features/boards/BoardNodeAction.vue deleted file mode 100644 index c65727a..0000000 --- a/src/features/boards/BoardNodeAction.vue +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - diff --git a/src/features/boards/CircleProgress.vue b/src/features/boards/CircleProgress.vue new file mode 100644 index 0000000..abe748d --- /dev/null +++ b/src/features/boards/CircleProgress.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/src/features/boards/SVGNode.vue b/src/features/boards/SVGNode.vue new file mode 100644 index 0000000..d36155b --- /dev/null +++ b/src/features/boards/SVGNode.vue @@ -0,0 +1,27 @@ + + + + + diff --git a/src/features/boards/SquareProgress.vue b/src/features/boards/SquareProgress.vue new file mode 100644 index 0000000..7e83c5d --- /dev/null +++ b/src/features/boards/SquareProgress.vue @@ -0,0 +1,30 @@ + + + + + diff --git a/src/features/boards/board.ts b/src/features/boards/board.ts deleted file mode 100644 index 8a9026f..0000000 --- a/src/features/boards/board.ts +++ /dev/null @@ -1,631 +0,0 @@ -import BoardComponent from "features/boards/Board.vue"; -import type { GenericComponent, OptionsFunc, Replace, StyleValue } from "features/feature"; -import { - Component, - findFeatures, - GatherProps, - getUniqueID, - setDefault, - Visibility -} from "features/feature"; -import { globalBus } from "game/events"; -import { DefaultValue, deletePersistent, Persistent, State } from "game/persistence"; -import { persistent } from "game/persistence"; -import type { Unsubscribe } from "nanoevents"; -import { Direction, isFunction } from "util/common"; -import type { - Computable, - GetComputableType, - GetComputableTypeWithDefault, - ProcessedComputable -} from "util/computed"; -import { processComputable } from "util/computed"; -import { createLazyProxy } from "util/proxies"; -import { computed, isRef, ref, Ref, unref } from "vue"; -import panZoom from "vue-panzoom"; -import type { Link } from "../links/links"; - -globalBus.on("setupVue", app => panZoom.install(app)); - -/** A symbol used to identify {@link Board} features. */ -export const BoardType = Symbol("Board"); - -/** - * A type representing a computable value for a node on the board. Used for node types to return different values based on the given node and the state of the board. - */ -export type NodeComputable = - | Computable - | ((node: BoardNode, ...args: S) => T); - -/** Ways to display progress of an action with a duration. */ -export enum ProgressDisplay { - Outline = "Outline", - Fill = "Fill" -} - -/** Node shapes. */ -export enum Shape { - Circle = "Circle", - Diamond = "Triangle" -} - -/** An object representing a node on the board. */ -export interface BoardNode { - id: number; - position: { - x: number; - y: number; - }; - type: string; - state?: State; - pinned?: boolean; -} - -/** An object representing a link between two nodes on the board. */ -export interface BoardNodeLink extends Omit { - startNode: BoardNode; - endNode: BoardNode; - stroke: string; - strokeWidth: number; - pulsing?: boolean; -} - -/** An object representing a label for a node. */ -export interface NodeLabel { - text: string; - color?: string; - pulsing?: boolean; -} - -/** The persistent data for a board. */ -export type BoardData = { - nodes: BoardNode[]; - selectedNode: number | null; - selectedAction: string | null; -}; - -/** - * An object that configures a {@link NodeType}. - */ -export interface NodeTypeOptions { - /** The title to display for the node. */ - title: NodeComputable; - /** An optional label for the node. */ - label?: NodeComputable; - /** The size of the node - diameter for circles, width and height for squares. */ - size: NodeComputable; - /** CSS to apply to this node. */ - style?: NodeComputable; - /** Dictionary of CSS classes to apply to this node. */ - classes?: NodeComputable>; - /** Whether the node is draggable or not. */ - draggable?: NodeComputable; - /** The shape of the node. */ - shape: NodeComputable; - /** Whether the node can accept another node being dropped upon it. */ - canAccept?: NodeComputable; - /** The progress value of the node, from 0 to 1. */ - progress?: NodeComputable; - /** How the progress should be displayed on the node. */ - progressDisplay?: NodeComputable; - /** The color of the progress indicator. */ - progressColor?: NodeComputable; - /** The fill color of the node. */ - fillColor?: NodeComputable; - /** The outline color of the node. */ - outlineColor?: NodeComputable; - /** The color of the title text. */ - titleColor?: NodeComputable; - /** The list of action options for the node. */ - actions?: BoardNodeActionOptions[]; - /** The arc between each action, in radians. */ - actionDistance?: NodeComputable; - /** A function that is called when the node is clicked. */ - onClick?: (node: BoardNode) => void; - /** A function that is called when a node is dropped onto this node. */ - onDrop?: (node: BoardNode, otherNode: BoardNode) => void; - /** A function that is called for each node of this type every tick. */ - update?: (node: BoardNode, diff: number) => void; -} - -/** - * The properties that are added onto a processed {@link NodeTypeOptions} to create a {@link NodeType}. - */ -export interface BaseNodeType { - /** The nodes currently on the board of this type. */ - nodes: Ref; -} - -/** An object that represents a type of node that can appear on a board. It will handle getting properties and callbacks for every node of that type. */ -export type NodeType = Replace< - T & BaseNodeType, - { - title: GetComputableType; - label: GetComputableType; - size: GetComputableTypeWithDefault; - style: GetComputableType; - classes: GetComputableType; - draggable: GetComputableTypeWithDefault; - shape: GetComputableTypeWithDefault; - canAccept: GetComputableTypeWithDefault; - progress: GetComputableType; - progressDisplay: GetComputableTypeWithDefault; - progressColor: GetComputableTypeWithDefault; - fillColor: GetComputableType; - outlineColor: GetComputableType; - titleColor: GetComputableType; - actions?: GenericBoardNodeAction[]; - actionDistance: GetComputableTypeWithDefault; - } ->; - -/** A type that matches any valid {@link NodeType} object. */ -export type GenericNodeType = Replace< - NodeType, - { - size: NodeComputable; - draggable: NodeComputable; - shape: NodeComputable; - canAccept: NodeComputable; - progressDisplay: NodeComputable; - progressColor: NodeComputable; - actionDistance: NodeComputable; - } ->; - -/** - * An object that configures a {@link BoardNodeAction}. - */ -export interface BoardNodeActionOptions { - /** A unique identifier for the action. */ - id: string; - /** Whether this action should be visible. */ - visibility?: NodeComputable; - /** The icon to display for the action. */ - icon: NodeComputable; - /** The fill color of the action. */ - fillColor?: NodeComputable; - /** The tooltip text to display for the action. */ - tooltip: NodeComputable; - /** The confirmation label that appears under the action. */ - confirmationLabel?: NodeComputable; - /** An array of board node links associated with the action. They appear when the action is focused. */ - links?: NodeComputable; - /** A function that is called when the action is clicked. */ - onClick: (node: BoardNode) => void; -} - -/** - * The properties that are added onto a processed {@link BoardNodeActionOptions} to create an {@link BoardNodeAction}. - */ -export interface BaseBoardNodeAction { - links?: Ref; -} - -/** An object that represents an action that can be taken upon a node. */ -export type BoardNodeAction = Replace< - T & BaseBoardNodeAction, - { - visibility: GetComputableTypeWithDefault; - icon: GetComputableType; - fillColor: GetComputableType; - tooltip: GetComputableType; - confirmationLabel: GetComputableTypeWithDefault; - links: GetComputableType; - } ->; - -/** A type that matches any valid {@link BoardNodeAction} object. */ -export type GenericBoardNodeAction = Replace< - BoardNodeAction, - { - visibility: NodeComputable; - confirmationLabel: NodeComputable; - } ->; - -/** - * An object that configures a {@link Board}. - */ -export interface BoardOptions { - /** Whether this board should be visible. */ - visibility?: Computable; - /** The height of the board. Defaults to 100% */ - height?: Computable; - /** The width of the board. Defaults to 100% */ - width?: Computable; - /** Dictionary of CSS classes to apply to this feature. */ - classes?: Computable>; - /** CSS to apply to this feature. */ - style?: Computable; - /** A function that returns an array of initial board nodes, without IDs. */ - startNodes: () => Omit[]; - /** A dictionary of node types that can appear on the board. */ - types: Record; - /** The persistent state of the board. */ - state?: Computable; - /** An array of board node links to display. */ - links?: Computable; -} - -/** - * The properties that are added onto a processed {@link BoardOptions} to create a {@link Board}. - */ -export interface BaseBoard { - /** An auto-generated ID for identifying features that appear in the DOM. Will not persist between refreshes or updates. */ - id: string; - /** All the nodes currently on the board. */ - nodes: Ref; - /** The currently selected node, if any. */ - selectedNode: Ref; - /** The currently selected action, if any. */ - selectedAction: Ref; - /** The currently being dragged node, if any. */ - draggingNode: Ref; - /** If dragging a node, the node it's currently being hovered over, if any. */ - receivingNode: Ref; - /** The current mouse position, if over the board. */ - mousePosition: Ref<{ x: number; y: number } | null>; - /** Places a node in the nearest empty space in the given direction with the specified space around it. */ - placeInAvailableSpace: (node: BoardNode, radius?: number, direction?: Direction) => void; - /** A symbol that helps identify features of the same type. */ - type: typeof BoardType; - /** The Vue component used to render this feature. */ - [Component]: GenericComponent; - /** A function to gather the props the vue component requires for this feature. */ - [GatherProps]: () => Record; -} - -/** An object that represents a feature that is a zoomable, pannable board with various nodes upon it. */ -export type Board = Replace< - T & BaseBoard, - { - visibility: GetComputableTypeWithDefault; - types: Record; - height: GetComputableType; - width: GetComputableType; - classes: GetComputableType; - style: GetComputableType; - state: GetComputableTypeWithDefault>; - links: GetComputableTypeWithDefault>; - } ->; - -/** A type that matches any valid {@link Board} object. */ -export type GenericBoard = Replace< - Board, - { - visibility: ProcessedComputable; - state: ProcessedComputable; - links: ProcessedComputable; - } ->; - -/** - * Lazily creates a board with the given options. - * @param optionsFunc Board options. - */ -export function createBoard( - optionsFunc: OptionsFunc -): Board { - const state = persistent( - { - nodes: [], - selectedNode: null, - selectedAction: null - }, - false - ); - - return createLazyProxy(feature => { - const board = optionsFunc.call(feature, feature); - board.id = getUniqueID("board-"); - board.type = BoardType; - board[Component] = BoardComponent as GenericComponent; - - if (board.state) { - deletePersistent(state); - processComputable(board as T, "state"); - } else { - state[DefaultValue] = { - nodes: board.startNodes().map((n, i) => { - (n as BoardNode).id = i; - return n as BoardNode; - }), - selectedNode: null, - selectedAction: null - }; - board.state = state; - } - - board.nodes = computed(() => unref(processedBoard.state).nodes); - board.selectedNode = computed({ - get() { - return ( - processedBoard.nodes.value.find( - node => node.id === unref(processedBoard.state).selectedNode - ) || null - ); - }, - set(node) { - if (isRef(processedBoard.state)) { - processedBoard.state.value = { - ...processedBoard.state.value, - selectedNode: node?.id ?? null - }; - } else { - processedBoard.state.selectedNode = node?.id ?? null; - } - } - }); - board.selectedAction = computed({ - get() { - const selectedNode = processedBoard.selectedNode.value; - if (selectedNode == null) { - return null; - } - const type = processedBoard.types[selectedNode.type]; - if (type.actions == null) { - return null; - } - return ( - type.actions.find( - action => action.id === unref(processedBoard.state).selectedAction - ) || null - ); - }, - set(action) { - if (isRef(processedBoard.state)) { - processedBoard.state.value = { - ...processedBoard.state.value, - selectedAction: action?.id ?? null - }; - } else { - processedBoard.state.selectedAction = action?.id ?? null; - } - } - }); - board.mousePosition = ref(null); - if (board.links) { - processComputable(board as T, "links"); - } else { - board.links = computed(() => { - if (processedBoard.selectedAction.value == null) { - return null; - } - if ( - processedBoard.selectedAction.value.links && - processedBoard.selectedNode.value - ) { - return getNodeProperty( - processedBoard.selectedAction.value.links, - processedBoard.selectedNode.value - ); - } - return null; - }); - } - board.draggingNode = ref(null); - board.receivingNode = ref(null); - processComputable(board as T, "visibility"); - setDefault(board, "visibility", Visibility.Visible); - processComputable(board as T, "width"); - setDefault(board, "width", "100%"); - processComputable(board as T, "height"); - setDefault(board, "height", "100%"); - processComputable(board as T, "classes"); - processComputable(board as T, "style"); - - for (const type in board.types) { - const nodeType: NodeTypeOptions & Partial = board.types[type]; - - processComputable(nodeType as NodeTypeOptions, "title"); - processComputable(nodeType as NodeTypeOptions, "label"); - processComputable(nodeType as NodeTypeOptions, "size"); - setDefault(nodeType, "size", 50); - processComputable(nodeType as NodeTypeOptions, "style"); - processComputable(nodeType as NodeTypeOptions, "classes"); - processComputable(nodeType as NodeTypeOptions, "draggable"); - setDefault(nodeType, "draggable", false); - processComputable(nodeType as NodeTypeOptions, "shape"); - setDefault(nodeType, "shape", Shape.Circle); - processComputable(nodeType as NodeTypeOptions, "canAccept"); - setDefault(nodeType, "canAccept", false); - processComputable(nodeType as NodeTypeOptions, "progress"); - processComputable(nodeType as NodeTypeOptions, "progressDisplay"); - setDefault(nodeType, "progressDisplay", ProgressDisplay.Fill); - processComputable(nodeType as NodeTypeOptions, "progressColor"); - setDefault(nodeType, "progressColor", "none"); - processComputable(nodeType as NodeTypeOptions, "fillColor"); - processComputable(nodeType as NodeTypeOptions, "outlineColor"); - processComputable(nodeType as NodeTypeOptions, "titleColor"); - processComputable(nodeType as NodeTypeOptions, "actionDistance"); - setDefault(nodeType, "actionDistance", Math.PI / 6); - nodeType.nodes = computed(() => - unref(processedBoard.state).nodes.filter(node => node.type === type) - ); - setDefault(nodeType, "onClick", function (node: BoardNode) { - unref(processedBoard.state).selectedNode = node.id; - }); - - if (nodeType.actions) { - for (const action of nodeType.actions) { - processComputable(action, "visibility"); - setDefault(action, "visibility", Visibility.Visible); - processComputable(action, "icon"); - processComputable(action, "fillColor"); - processComputable(action, "tooltip"); - processComputable(action, "confirmationLabel"); - setDefault(action, "confirmationLabel", { text: "Tap again to confirm" }); - processComputable(action, "links"); - } - } - } - - function setDraggingNode(node: BoardNode | null) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - board.draggingNode!.value = node; - } - function setReceivingNode(node: BoardNode | null) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - board.receivingNode!.value = node; - } - - board.placeInAvailableSpace = function ( - node: BoardNode, - radius = 100, - direction = Direction.Right - ) { - const nodes = processedBoard.nodes.value - .slice() - .filter(n => { - // Exclude self - if (n === node) { - return false; - } - - // Exclude nodes that aren't within the corridor we'll be moving within - if ( - (direction === Direction.Down || direction === Direction.Up) && - Math.abs(n.position.x - node.position.x) > radius - ) { - return false; - } - if ( - (direction === Direction.Left || direction === Direction.Right) && - Math.abs(n.position.y - node.position.y) > radius - ) { - return false; - } - - // Exclude nodes in the wrong direction - return !( - (direction === Direction.Right && - n.position.x < node.position.x - radius) || - (direction === Direction.Left && n.position.x > node.position.x + radius) || - (direction === Direction.Up && n.position.y > node.position.y + radius) || - (direction === Direction.Down && n.position.y < node.position.y - radius) - ); - }) - .sort( - direction === Direction.Right - ? (a, b) => a.position.x - b.position.x - : direction === Direction.Left - ? (a, b) => b.position.x - a.position.x - : direction === Direction.Up - ? (a, b) => b.position.y - a.position.y - : (a, b) => a.position.y - b.position.y - ); - for (let i = 0; i < nodes.length; i++) { - const nodeToCheck = nodes[i]; - const distance = - direction === Direction.Right || direction === Direction.Left - ? Math.abs(node.position.x - nodeToCheck.position.x) - : Math.abs(node.position.y - nodeToCheck.position.y); - - // If we're too close to this node, move further - if (distance < radius) { - if (direction === Direction.Right) { - node.position.x = nodeToCheck.position.x + radius; - } else if (direction === Direction.Left) { - node.position.x = nodeToCheck.position.x - radius; - } else if (direction === Direction.Up) { - node.position.y = nodeToCheck.position.y - radius; - } else if (direction === Direction.Down) { - node.position.y = nodeToCheck.position.y + radius; - } - } else if (i > 0 && distance > radius) { - // If we're further from this node than the radius, then the nodes are past us and we can early exit - break; - } - } - }; - - board[GatherProps] = function (this: GenericBoard) { - const { - nodes, - types, - state, - visibility, - width, - height, - style, - classes, - links, - selectedAction, - selectedNode, - mousePosition, - draggingNode, - receivingNode - } = this; - return { - nodes, - types, - state, - visibility, - width, - height, - style: unref(style), - classes, - links, - selectedAction, - selectedNode, - mousePosition, - draggingNode, - receivingNode, - setDraggingNode, - setReceivingNode - }; - }; - - // This is necessary because board.types is different from T and Board - const processedBoard = board as unknown as Board; - return processedBoard; - }); -} - -/** - * Gets the value of a property for a specified node. - * @param property The property to find the value of - * @param node The node to get the property of - */ -export function getNodeProperty( - property: NodeComputable, - node: BoardNode, - ...args: S -): T { - return isFunction>(property) - ? property(node, ...args) - : unref(property); -} - -/** - * Utility to get an ID for a node that is guaranteed unique. - * @param board The board feature to generate an ID for - */ -export function getUniqueNodeID(board: GenericBoard): number { - let id = 0; - board.nodes.value.forEach(node => { - if (node.id >= id) { - id = node.id + 1; - } - }); - return id; -} - -const listeners: Record = {}; -globalBus.on("addLayer", layer => { - const boards: GenericBoard[] = findFeatures(layer, BoardType) as GenericBoard[]; - listeners[layer.id] = layer.on("postUpdate", diff => { - boards.forEach(board => { - Object.values(board.types).forEach(type => - type.nodes.value.forEach(node => type.update?.(node, diff)) - ); - }); - }); -}); -globalBus.on("removeLayer", layer => { - // unsubscribe from postUpdate - listeners[layer.id]?.(); - listeners[layer.id] = undefined; -}); diff --git a/src/features/boards/board.tsx b/src/features/boards/board.tsx new file mode 100644 index 0000000..ac61615 --- /dev/null +++ b/src/features/boards/board.tsx @@ -0,0 +1,317 @@ +import Board from "features/boards/Board.vue"; +import { jsx } from "features/feature"; +import { globalBus } from "game/events"; +import type { PanZoom } from "panzoom"; +import { Direction, isFunction } from "util/common"; +import type { Computable, ProcessedComputable } from "util/computed"; +import { convertComputable } from "util/computed"; +import type { ComponentPublicInstance, Ref } from "vue"; +import { computed, ref, unref, watchEffect } from "vue"; +import panZoom from "vue-panzoom"; + +globalBus.on("setupVue", app => panZoom.install(app)); + +export type NodePosition = { x: number; y: number }; + +/** + * A type representing a computable value for a node on the board. Used for node types to return different values based on the given node and the state of the board. + */ +export type NodeComputable = + | Computable + | ((node: T, ...args: S) => R); + +/** + * Gets the value of a property for a specified node. + * @param property The property to find the value of + * @param node The node to get the property of + */ +export function unwrapNodeRef( + property: NodeComputable, + node: T, + ...args: S +): R { + return isFunction>(property) + ? property(node, ...args) + : unref(property); +} + +export function setupUniqueIds(nodes: Computable<{ id: number }[]>) { + const processedNodes = convertComputable(nodes); + return computed(() => Math.max(-1, ...unref(processedNodes).map(node => node.id)) + 1); +} + +export function setupSelectable() { + const selected = ref(); + return { + select: function (node: T) { + selected.value = node; + }, + deselect: function () { + selected.value = undefined; + }, + selected + }; +} + +export function setupDraggableNode(options: { + board: Ref | undefined>; + receivingNodes?: NodeComputable; + dropAreaRadius?: NodeComputable; + isDraggable?: NodeComputable; + onDrop?: (acceptingNode: S, draggingNode: T) => void; +}) { + const nodeBeingDragged = ref(); + const receivingNode = ref(); + const hasDragged = ref(false); + const mousePosition = ref(); + const lastMousePosition = ref({ x: 0, y: 0 }); + const dragDelta = ref({ x: 0, y: 0 }); + const isDraggable = options.isDraggable ?? true; + const receivingNodes = computed(() => + nodeBeingDragged.value == null + ? [] + : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + unwrapNodeRef(options.receivingNodes ?? [], nodeBeingDragged.value!) + ); + const dropAreaRadius = options.dropAreaRadius ?? 50; + + watchEffect(() => { + if (nodeBeingDragged.value != null && !unwrapNodeRef(isDraggable, nodeBeingDragged.value)) { + result.endDrag(); + } + }); + + watchEffect(() => { + const node = nodeBeingDragged.value; + if (node == null) { + return null; + } + + const position = { + x: node.x + dragDelta.value.x, + y: node.y + dragDelta.value.y + }; + let smallestDistance = Number.MAX_VALUE; + + receivingNode.value = unref(receivingNodes).reduce((smallest: S | undefined, curr: S) => { + if ((curr as S | T) === node) { + return smallest; + } + + const distanceSquared = + Math.pow(position.x - curr.x, 2) + Math.pow(position.y - curr.y, 2); + const size = unwrapNodeRef(dropAreaRadius, curr); + if (distanceSquared > smallestDistance || distanceSquared > size * size) { + return smallest; + } + + smallestDistance = distanceSquared; + return curr; + }, undefined); + }); + + const result = { + nodeBeingDragged, + receivingNode, + hasDragged, + mousePosition, + lastMousePosition, + dragDelta, + receivingNodes, + startDrag: function (e: MouseEvent | TouchEvent, node?: T) { + e.preventDefault(); + e.stopPropagation(); + + let clientX, clientY; + if ("touches" in e) { + if (e.touches.length === 1) { + clientX = e.touches[0].clientX; + clientY = e.touches[0].clientY; + } else { + return; + } + } else { + clientX = e.clientX; + clientY = e.clientY; + } + lastMousePosition.value = { + x: clientX, + y: clientY + }; + dragDelta.value = { x: 0, y: 0 }; + hasDragged.value = false; + + if (node != null && unwrapNodeRef(isDraggable, node)) { + nodeBeingDragged.value = node; + } + }, + endDrag: function () { + if (nodeBeingDragged.value == null) { + return; + } + if (receivingNode.value == null) { + nodeBeingDragged.value.x += Math.round(dragDelta.value.x / 25) * 25; + nodeBeingDragged.value.y += Math.round(dragDelta.value.y / 25) * 25; + } + + if (receivingNode.value != null) { + options.onDrop?.(receivingNode.value, nodeBeingDragged.value); + } + + nodeBeingDragged.value = undefined; + }, + drag: function (e: MouseEvent | TouchEvent) { + const panZoomInstance = options.board.value?.panZoomInstance as PanZoom | undefined; + if (panZoomInstance == null) { + return; + } + + const { x, y, scale } = panZoomInstance.getTransform(); + + let clientX, clientY; + if ("touches" in e) { + if (e.touches.length === 1) { + clientX = e.touches[0].clientX; + clientY = e.touches[0].clientY; + } else { + result.endDrag(); + mousePosition.value = undefined; + return; + } + } else { + clientX = e.clientX; + clientY = e.clientY; + } + + mousePosition.value = { + x: (clientX - x) / scale, + y: (clientY - y) / scale + }; + + dragDelta.value = { + x: dragDelta.value.x + (clientX - lastMousePosition.value.x) / scale, + y: dragDelta.value.y + (clientY - lastMousePosition.value.y) / scale + }; + lastMousePosition.value = { + x: clientX, + y: clientY + }; + + if (Math.abs(dragDelta.value.x) > 10 || Math.abs(dragDelta.value.y) > 10) { + hasDragged.value = true; + } + + if (nodeBeingDragged.value != null) { + e.preventDefault(); + e.stopPropagation(); + } + } + }; + return result; +} + +export function setupActions(options: { + node: Computable; + shouldShowActions?: NodeComputable; + actions: NodeComputable JSX.Element)[]>; + distance: NodeComputable; + arcLength?: NodeComputable; +}) { + const node = convertComputable(options.node); + return jsx(() => { + const currNode = unref(node); + if (currNode == null) { + return ""; + } + + const actions = unwrapNodeRef(options.actions, currNode); + const shouldShow = unwrapNodeRef(options.shouldShowActions, currNode) ?? true; + if (!shouldShow) { + return <>{actions.map(f => f(currNode))}; + } + + const distance = unwrapNodeRef(options.distance, currNode); + const arcLength = unwrapNodeRef(options.arcLength, currNode) ?? Math.PI / 6; + const firstAngle = Math.PI / 2 - ((actions.length - 1) / 2) * arcLength; + return ( + <> + {actions.map((f, index) => + f({ + x: currNode.x + Math.cos(firstAngle + index * arcLength) * distance, + y: currNode.y + Math.sin(firstAngle + index * arcLength) * distance + }) + )} + + ); + }); +} + +export function placeInAvailableSpace( + nodeToPlace: T, + nodes: T[], + radius = 100, + direction = Direction.Right +) { + nodes = nodes + .filter(n => { + // Exclude self + if (n === nodeToPlace) { + return false; + } + + // Exclude nodes that aren't within the corridor we'll be moving within + if ( + (direction === Direction.Down || direction === Direction.Up) && + Math.abs(n.x - nodeToPlace.x) > radius + ) { + return false; + } + if ( + (direction === Direction.Left || direction === Direction.Right) && + Math.abs(n.y - nodeToPlace.y) > radius + ) { + return false; + } + + // Exclude nodes in the wrong direction + return !( + (direction === Direction.Right && n.x < nodeToPlace.x - radius) || + (direction === Direction.Left && n.x > nodeToPlace.x + radius) || + (direction === Direction.Up && n.y > nodeToPlace.y + radius) || + (direction === Direction.Down && n.y < nodeToPlace.y - radius) + ); + }) + .sort( + direction === Direction.Right + ? (a, b) => a.x - b.x + : direction === Direction.Left + ? (a, b) => b.x - a.x + : direction === Direction.Up + ? (a, b) => b.y - a.y + : (a, b) => a.y - b.y + ); + + for (let i = 0; i < nodes.length; i++) { + const nodeToCheck = nodes[i]; + const distance = + direction === Direction.Right || direction === Direction.Left + ? Math.abs(nodeToPlace.x - nodeToCheck.x) + : Math.abs(nodeToPlace.y - nodeToCheck.y); + + // If we're too close to this node, move further + if (distance < radius) { + if (direction === Direction.Right) { + nodeToPlace.x = nodeToCheck.x + radius; + } else if (direction === Direction.Left) { + nodeToPlace.x = nodeToCheck.x - radius; + } else if (direction === Direction.Up) { + nodeToPlace.y = nodeToCheck.y - radius; + } else if (direction === Direction.Down) { + nodeToPlace.y = nodeToCheck.y + radius; + } + } else if (i > 0 && distance > radius) { + // If we're further from this node than the radius, then the nodes are past us and we can early exit + break; + } + } +} From 1acfde134b9e4923643a9ed1282797d4d040da5b Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Sun, 3 Mar 2024 19:59:26 -0600 Subject: [PATCH 02/58] Add support for rendering VueFeatures in boards --- src/data/layers/prestige.tsx | 73 ------------- src/data/projEntry.tsx | 172 ++++++++++++++++++------------ src/features/boards/Draggable.vue | 37 +++++++ src/features/boards/board.tsx | 110 ++++++++++++++----- 4 files changed, 225 insertions(+), 167 deletions(-) delete mode 100644 src/data/layers/prestige.tsx create mode 100644 src/features/boards/Draggable.vue diff --git a/src/data/layers/prestige.tsx b/src/data/layers/prestige.tsx deleted file mode 100644 index 6e3cb69..0000000 --- a/src/data/layers/prestige.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/** - * @module - * @hidden - */ -import { main } from "data/projEntry"; -import { createCumulativeConversion } from "features/conversion"; -import { jsx } from "features/feature"; -import { createHotkey } from "features/hotkey"; -import { createReset } from "features/reset"; -import MainDisplay from "features/resources/MainDisplay.vue"; -import { createResource } from "features/resources/resource"; -import { addTooltip } from "features/tooltips/tooltip"; -import { createResourceTooltip } from "features/trees/tree"; -import { BaseLayer, createLayer } from "game/layers"; -import type { DecimalSource } from "util/bignum"; -import { render } from "util/vue"; -import { createLayerTreeNode, createResetButton } from "../common"; - -const id = "p"; -const layer = createLayer(id, function (this: BaseLayer) { - const name = "Prestige"; - const color = "#4BDC13"; - const points = createResource(0, "prestige points"); - - const conversion = createCumulativeConversion(() => ({ - formula: x => x.div(10).sqrt(), - baseResource: main.points, - gainResource: points - })); - - const reset = createReset(() => ({ - thingsToReset: (): Record[] => [layer] - })); - - const treeNode = createLayerTreeNode(() => ({ - layerID: id, - color, - reset - })); - const tooltip = addTooltip(treeNode, { - display: createResourceTooltip(points), - pinnable: true - }); - - const resetButton = createResetButton(() => ({ - conversion, - tree: main.tree, - treeNode - })); - - const hotkey = createHotkey(() => ({ - description: "Reset for prestige points", - key: "p", - onPress: resetButton.onClick - })); - - return { - name, - color, - points, - tooltip, - display: jsx(() => ( - <> - - {render(resetButton)} - - )), - treeNode, - hotkey - }; -}); - -export default layer; diff --git a/src/data/projEntry.tsx b/src/data/projEntry.tsx index d256f4e..6762d45 100644 --- a/src/data/projEntry.tsx +++ b/src/data/projEntry.tsx @@ -4,6 +4,7 @@ import SVGNode from "features/boards/SVGNode.vue"; import SquareProgress from "features/boards/SquareProgress.vue"; import { NodePosition, + makeDraggable, placeInAvailableSpace, setupActions, setupDraggableNode, @@ -11,24 +12,29 @@ import { setupUniqueIds } from "features/boards/board"; import { jsx } from "features/feature"; +import { createResource } from "features/resources/resource"; +import { createUpgrade } from "features/upgrades/upgrade"; import type { BaseLayer, GenericLayer } from "game/layers"; import { createLayer } from "game/layers"; -import { persistent } from "game/persistence"; +import { Persistent, persistent } from "game/persistence"; import type { Player } from "game/player"; +import { createCostRequirement } from "game/requirements"; +import { render } from "util/vue"; import { ComponentPublicInstance, computed, ref, watch } from "vue"; -import prestige from "./layers/prestige"; - -type ANode = NodePosition & { id: number; links: number[]; type: "anode" }; -type BNode = NodePosition & { id: number; links: number[]; type: "bnode" }; -type NodeTypes = ANode | BNode; +import "./common.css"; /** * @hidden */ export const main = createLayer("main", function (this: BaseLayer) { + type ANode = NodePosition & { id: number; links: number[]; type: "anode" }; + type BNode = NodePosition & { id: number; links: number[]; type: "bnode" }; + type CNode = typeof cNode & { position: Persistent }; + type NodeTypes = ANode | BNode; + const board = ref>(); - const { select, deselect, selected } = setupSelectable(); + const { select, deselect, selected } = setupSelectable(); const { select: selectAction, deselect: deselectAction, @@ -50,10 +56,15 @@ export const main = createLayer("main", function (this: BaseLayer) { receivingNodes, receivingNode, dragDelta - } = setupDraggableNode({ + } = setupDraggableNode({ board, - isDraggable: function (node) { - return nodes.value.includes(node); + getPosition(id) { + return nodesById.value[id] ?? (cNode as CNode).position.value; + }, + setPosition(id, position) { + const node = nodesById.value[id] ?? (cNode as CNode).position.value; + node.x = position.x; + node.y = position.y; } }); @@ -64,26 +75,26 @@ export const main = createLayer("main", function (this: BaseLayer) { // c node also exists but is a single Upgrade element that cannot be selected, but can be dragged // d nodes are a performance test - 1000 simple nodes that have no interactions // Make all nodes animate in (decorator? `fadeIn(feature)?) - const nodes = persistent([{ id: 0, x: 0, y: 0, links: [], type: "anode" }]); + const nodes = persistent<(ANode | BNode)[]>([{ id: 0, x: 0, y: 0, links: [], type: "anode" }]); const nodesById = computed>(() => nodes.value.reduce((acc, curr) => ({ ...acc, [curr.id]: curr }), {}) ); function mouseDownNode(e: MouseEvent | TouchEvent, node: NodeTypes) { if (nodeBeingDragged.value == null) { - startDrag(e, node); + startDrag(e, node.id); } deselect(); } function mouseUpNode(e: MouseEvent | TouchEvent, node: NodeTypes) { if (!hasDragged.value) { endDrag(); - select(node); + if (typeof node.id === "number") { + select(node.id); + } e.stopPropagation(); } } - function getTranslateString(node: NodePosition, overrideSelected?: boolean) { - const isSelected = overrideSelected == null ? selected.value === node : overrideSelected; - const isDragging = !isSelected && nodeBeingDragged.value === node; + function getTranslateString(node: NodePosition, isDragging: boolean) { let x = node.x; let y = node.y; if (isDragging) { @@ -95,13 +106,13 @@ export const main = createLayer("main", function (this: BaseLayer) { function getRotateString(rotation: number) { return ` rotate(${rotation}deg) `; } - function getScaleString(node: NodePosition, overrideSelected?: boolean) { - const isSelected = overrideSelected == null ? selected.value === node : overrideSelected; + function getScaleString(nodeOrBool: NodeTypes | boolean) { + const isSelected = + typeof nodeOrBool === "boolean" ? nodeOrBool : selected.value === nodeOrBool.id; return isSelected ? " scale(1.2)" : ""; } - function getOpacityString(node: NodePosition, overrideSelected?: boolean) { - const isSelected = overrideSelected == null ? selected.value === node : overrideSelected; - const isDragging = !isSelected && nodeBeingDragged.value === node; + function getOpacityString(node: NodeTypes) { + const isDragging = selected.value !== node.id && nodeBeingDragged.value === node.id; if (isDragging) { return "; opacity: 0.5;"; } @@ -111,16 +122,19 @@ export const main = createLayer("main", function (this: BaseLayer) { const renderANode = function (node: ANode) { return ( mouseDownNode(e, node)} onMouseUp={e => mouseUpNode(e, node)} > - - {receivingNodes.value.includes(node) && ( + + {receivingNodes.value.includes(node.id) && ( )} @@ -132,7 +146,7 @@ export const main = createLayer("main", function (this: BaseLayer) { stroke-width="4" /> - {selected.value === node && selectedAction.value === 0 && ( + {selected.value === node.id && selectedAction.value === 0 && ( Spawn B Node @@ -144,8 +158,8 @@ export const main = createLayer("main", function (this: BaseLayer) { ); }; const aActions = setupActions({ - node: selected, - shouldShowActions: () => selected.value?.type === "anode", + node: () => nodesById.value[selected.value ?? ""], + shouldShowActions: node => node.type === "anode", actions(node) { return [ p => ( @@ -153,10 +167,10 @@ export const main = createLayer("main", function (this: BaseLayer) { style={`transform: ${getTranslateString( p, selectedAction.value === 0 - )}${getScaleString(p, selectedAction.value === 0)}`} + )}${getScaleString(selectedAction.value === 0)}`} onClick={() => { if (selectedAction.value === 0) { - spawnBNode(node); + spawnBNode(node as ANode); } else { selectAction(0); } @@ -176,16 +190,15 @@ export const main = createLayer("main", function (this: BaseLayer) { const renderBNode = function (node: BNode) { return ( mouseDownNode(e, node)} onMouseUp={e => mouseUpNode(e, node)} > - - {receivingNodes.value.includes(node) && ( + + {receivingNodes.value.includes(node.id) && ( )} @@ -213,7 +226,7 @@ export const main = createLayer("main", function (this: BaseLayer) { stroke-width="4" /> - {selected.value === node && selectedAction.value === 0 && ( + {selected.value === node.id && selectedAction.value === 0 && ( Spawn A Node @@ -225,8 +238,8 @@ export const main = createLayer("main", function (this: BaseLayer) { ); }; const bActions = setupActions({ - node: selected, - shouldShowActions: () => selected.value?.type === "bnode", + node: () => nodesById.value[selected.value ?? ""], + shouldShowActions: node => node.type === "bnode", actions(node) { return [ p => ( @@ -234,10 +247,10 @@ export const main = createLayer("main", function (this: BaseLayer) { style={`transform: ${getTranslateString( p, selectedAction.value === 0 - )}${getScaleString(p, selectedAction.value === 0)}`} + )}${getScaleString(selectedAction.value === 0)}`} onClick={() => { if (selectedAction.value === 0) { - spawnANode(node); + spawnANode(node as BNode); } else { selectAction(0); } @@ -253,7 +266,7 @@ export const main = createLayer("main", function (this: BaseLayer) { }, distance: 100 }); - function spawnANode(parent: NodeTypes) { + function spawnANode(parent: ANode | BNode) { const node: ANode = { x: parent.x, y: parent.y, @@ -264,7 +277,7 @@ export const main = createLayer("main", function (this: BaseLayer) { placeInAvailableSpace(node, nodes.value); nodes.value.push(node); } - function spawnBNode(parent: NodeTypes) { + function spawnBNode(parent: ANode | BNode) { const node: BNode = { x: parent.x, y: parent.y, @@ -276,14 +289,29 @@ export const main = createLayer("main", function (this: BaseLayer) { nodes.value.push(node); } - // const cNode = createUpgrade(() => ({ - // requirements: createCostRequirement(() => ({ cost: 10, resource: points })), - // style: { - // x: "100px", - // y: "100px" - // } - // })); - // makeDraggable(cNode); // TODO make decorator + const points = createResource(10); + const cNode = createUpgrade(() => ({ + display: "

C

", + // Purposefully not using noPersist + requirements: createCostRequirement(() => ({ cost: 10, resource: points })), + style: { + x: "100px", + y: "100px" + } + })); + makeDraggable(cNode, { + id: "cnode", + endDrag, + startDrag, + hasDragged, + nodeBeingDragged, + dragDelta, + onMouseUp() { + if (!hasDragged.value) { + cNode.purchase(); + } + } + }); // const dNodes; @@ -302,22 +330,22 @@ export const main = createLayer("main", function (this: BaseLayer) { stroke="white" stroke-width={4} x1={ - nodeBeingDragged.value === link.from + nodeBeingDragged.value === link.from.id ? dragDelta.value.x + link.from.x : link.from.x } y1={ - nodeBeingDragged.value === link.from + nodeBeingDragged.value === link.from.id ? dragDelta.value.y + link.from.y : link.from.y } x2={ - nodeBeingDragged.value === link.to + nodeBeingDragged.value === link.to.id ? dragDelta.value.x + link.to.x : link.to.x } y2={ - nodeBeingDragged.value === link.to + nodeBeingDragged.value === link.to.id ? dragDelta.value.y + link.to.y : link.to.y } @@ -328,22 +356,30 @@ export const main = createLayer("main", function (this: BaseLayer) { const nextId = setupUniqueIds(() => nodes.value); - function filterNodes(n: NodeTypes) { + function filterNodes(n: number | "cnode") { return n !== nodeBeingDragged.value && n !== selected.value; } - function renderNode(node: NodeTypes | undefined) { - if (node == undefined) { + function renderNodeById(id: number | "cnode" | undefined) { + if (id == null) { return undefined; - } else if (node.type === "anode") { + } + return renderNode(nodesById.value[id] ?? cNode); + } + + function renderNode(node: NodeTypes | typeof cNode) { + if (node.type === "anode") { return renderANode(node); } else if (node.type === "bnode") { return renderBNode(node); + } else { + return render(node); } } return { name: "Tree", + color: "var(--accent1)", display: jsx(() => ( <> {links()} - {nodes.value.filter(filterNodes).map(renderNode)} + {nodes.value.filter(n => filterNodes(n.id)).map(renderNode)} + {filterNodes("cnode") && render(cNode)} {aActions()} {bActions()} - {renderNode(selected.value)} - {renderNode(nodeBeingDragged.value)} + {renderNodeById(selected.value)} + {renderNodeById(nodeBeingDragged.value)} )), - boardNodes: nodes - // cNode + boardNodes: nodes, + cNode, + selected: persistent(selected) }; }); @@ -376,7 +414,7 @@ export const main = createLayer("main", function (this: BaseLayer) { export const getInitialLayers = ( /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ player: Partial -): Array => [main, prestige]; +): Array => [main]; /** * A computed ref whose value is true whenever the game is over. diff --git a/src/features/boards/Draggable.vue b/src/features/boards/Draggable.vue new file mode 100644 index 0000000..9d59db6 --- /dev/null +++ b/src/features/boards/Draggable.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/src/features/boards/board.tsx b/src/features/boards/board.tsx index ac61615..20501d8 100644 --- a/src/features/boards/board.tsx +++ b/src/features/boards/board.tsx @@ -1,12 +1,15 @@ import Board from "features/boards/Board.vue"; -import { jsx } from "features/feature"; +import Draggable from "features/boards/Draggable.vue"; +import { Component, GatherProps, GenericComponent, jsx } from "features/feature"; import { globalBus } from "game/events"; +import { Persistent, persistent } from "game/persistence"; import type { PanZoom } from "panzoom"; import { Direction, isFunction } from "util/common"; import type { Computable, ProcessedComputable } from "util/computed"; import { convertComputable } from "util/computed"; +import { VueFeature } from "util/vue"; import type { ComponentPublicInstance, Ref } from "vue"; -import { computed, ref, unref, watchEffect } from "vue"; +import { computed, nextTick, ref, unref, watchEffect } from "vue"; import panZoom from "vue-panzoom"; globalBus.on("setupVue", app => panZoom.install(app)); @@ -53,20 +56,20 @@ export function setupSelectable() { }; } -export function setupDraggableNode(options: { +export function setupDraggableNode(options: { board: Ref | undefined>; - receivingNodes?: NodeComputable; - dropAreaRadius?: NodeComputable; - isDraggable?: NodeComputable; - onDrop?: (acceptingNode: S, draggingNode: T) => void; + getPosition: (node: T) => NodePosition; + setPosition: (node: T, position: NodePosition) => void; + receivingNodes?: NodeComputable; + dropAreaRadius?: NodeComputable; + onDrop?: (acceptingNode: T, draggingNode: T) => void; }) { const nodeBeingDragged = ref(); - const receivingNode = ref(); + const receivingNode = ref(); const hasDragged = ref(false); const mousePosition = ref(); const lastMousePosition = ref({ x: 0, y: 0 }); const dragDelta = ref({ x: 0, y: 0 }); - const isDraggable = options.isDraggable ?? true; const receivingNodes = computed(() => nodeBeingDragged.value == null ? [] @@ -75,31 +78,26 @@ export function setupDraggableNode { - if (nodeBeingDragged.value != null && !unwrapNodeRef(isDraggable, nodeBeingDragged.value)) { - result.endDrag(); - } - }); - watchEffect(() => { const node = nodeBeingDragged.value; if (node == null) { return null; } + const originalPosition = options.getPosition(node); const position = { - x: node.x + dragDelta.value.x, - y: node.y + dragDelta.value.y + x: originalPosition.x + dragDelta.value.x, + y: originalPosition.y + dragDelta.value.y }; let smallestDistance = Number.MAX_VALUE; - receivingNode.value = unref(receivingNodes).reduce((smallest: S | undefined, curr: S) => { - if ((curr as S | T) === node) { + receivingNode.value = unref(receivingNodes).reduce((smallest: T | undefined, curr: T) => { + if ((curr as T) === node) { return smallest; } - const distanceSquared = - Math.pow(position.x - curr.x, 2) + Math.pow(position.y - curr.y, 2); + const { x, y } = options.getPosition(curr); + const distanceSquared = Math.pow(position.x - x, 2) + Math.pow(position.y - y, 2); const size = unwrapNodeRef(dropAreaRadius, curr); if (distanceSquared > smallestDistance || distanceSquared > size * size) { return smallest; @@ -118,7 +116,7 @@ export function setupDraggableNode( + element: T, + options: { + id: S; + nodeBeingDragged: Ref; + hasDragged: Ref; + dragDelta: Ref; + startDrag: (e: MouseEvent | TouchEvent, id: S) => void; + endDrag: VoidFunction; + onMouseDown?: (e: MouseEvent | TouchEvent) => boolean | void; + onMouseUp?: (e: MouseEvent | TouchEvent) => boolean | void; + initialPosition?: NodePosition; + } +): asserts element is T & { position: Persistent } { + const position = persistent(options.initialPosition ?? { x: 0, y: 0 }); + (element as T & { position: Persistent }).position = position; + const computedPosition = computed(() => { + if (options.nodeBeingDragged.value === options.id) { + return { + x: position.value.x + options.dragDelta.value.x, + y: position.value.y + options.dragDelta.value.y + }; + } + return position.value; + }); + + function handleMouseDown(e: MouseEvent | TouchEvent) { + if (options.onMouseDown?.(e) === false) { + return; + } + + if (options.nodeBeingDragged.value == null) { + options.startDrag(e, options.id); + } + } + + function handleMouseUp(e: MouseEvent | TouchEvent) { + options.onMouseUp?.(e); + } + + nextTick(() => { + const elementComponent = element[Component]; + const elementGatherProps = element[GatherProps].bind(element); + element[Component] = Draggable as GenericComponent; + element[GatherProps] = function gatherTooltipProps(this: typeof options) { + return { + element: { + [Component]: elementComponent, + [GatherProps]: elementGatherProps + }, + mouseDown: handleMouseDown, + mouseUp: handleMouseUp, + position: computedPosition + }; + }.bind(options); + }); +} + export function setupActions(options: { node: Computable; shouldShowActions?: NodeComputable; From f0e831ee8fea2aee319f37b722593dda259b95c3 Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Sun, 3 Mar 2024 20:26:00 -0600 Subject: [PATCH 03/58] Add cnodes --- src/data/projEntry.tsx | 20 +++++++++++++++++++- src/features/boards/SVGNode.vue | 1 + 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/data/projEntry.tsx b/src/data/projEntry.tsx index 6762d45..140fb55 100644 --- a/src/data/projEntry.tsx +++ b/src/data/projEntry.tsx @@ -313,6 +313,21 @@ export const main = createLayer("main", function (this: BaseLayer) { } }); + const dNodesPerAxis = 50; + const dNodes = jsx(() => + new Array(dNodesPerAxis * dNodesPerAxis).fill(0).map((_, i) => { + const x = (Math.floor(i / dNodesPerAxis) - dNodesPerAxis / 2) * 100; + const y = ((i % dNodesPerAxis) - dNodesPerAxis / 2) * 100; + return ( + + ); + }) + ); + // const dNodes; const links = jsx(() => ( @@ -389,7 +404,10 @@ export const main = createLayer("main", function (this: BaseLayer) { onMouseLeave={endDrag} ref={board} > - {links()} + + {dNodes()} + {links()} + {nodes.value.filter(n => filterNodes(n.id)).map(renderNode)} {filterNodes("cnode") && render(cNode)} diff --git a/src/features/boards/SVGNode.vue b/src/features/boards/SVGNode.vue index d36155b..a0d1b14 100644 --- a/src/features/boards/SVGNode.vue +++ b/src/features/boards/SVGNode.vue @@ -23,5 +23,6 @@ svg { cursor: pointer; transition-duration: 0s; overflow: visible; + position: absolute; } From aca56f6af6e8a05e019806d7d82c5e736c590dbd Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Sun, 3 Mar 2024 22:17:06 -0600 Subject: [PATCH 04/58] Use z-index to avoid changing render order --- src/data/projEntry.tsx | 106 ++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/src/data/projEntry.tsx b/src/data/projEntry.tsx index 140fb55..8a3f858 100644 --- a/src/data/projEntry.tsx +++ b/src/data/projEntry.tsx @@ -27,8 +27,8 @@ import "./common.css"; * @hidden */ export const main = createLayer("main", function (this: BaseLayer) { - type ANode = NodePosition & { id: number; links: number[]; type: "anode" }; - type BNode = NodePosition & { id: number; links: number[]; type: "bnode" }; + type ANode = NodePosition & { id: number; links: number[]; type: "anode"; z: number }; + type BNode = NodePosition & { id: number; links: number[]; type: "bnode"; z: number }; type CNode = typeof cNode & { position: Persistent }; type NodeTypes = ANode | BNode; @@ -75,11 +75,20 @@ export const main = createLayer("main", function (this: BaseLayer) { // c node also exists but is a single Upgrade element that cannot be selected, but can be dragged // d nodes are a performance test - 1000 simple nodes that have no interactions // Make all nodes animate in (decorator? `fadeIn(feature)?) - const nodes = persistent<(ANode | BNode)[]>([{ id: 0, x: 0, y: 0, links: [], type: "anode" }]); + const nodes = persistent<(ANode | BNode)[]>([ + { id: 0, x: 0, y: 0, z: 0, links: [], type: "anode" } + ]); const nodesById = computed>(() => nodes.value.reduce((acc, curr) => ({ ...acc, [curr.id]: curr }), {}) ); function mouseDownNode(e: MouseEvent | TouchEvent, node: NodeTypes) { + const oldZ = node.z; + nodes.value.forEach(node => { + if (node.z > oldZ) { + node.z--; + } + }); + node.z = nextId.value; if (nodeBeingDragged.value == null) { startDrag(e, node.id); } @@ -94,7 +103,7 @@ export const main = createLayer("main", function (this: BaseLayer) { e.stopPropagation(); } } - function getTranslateString(node: NodePosition, isDragging: boolean) { + function translate(node: NodePosition, isDragging: boolean) { let x = node.x; let y = node.y; if (isDragging) { @@ -103,33 +112,38 @@ export const main = createLayer("main", function (this: BaseLayer) { } return ` translate(${x}px,${y}px)`; } - function getRotateString(rotation: number) { + function rotate(rotation: number) { return ` rotate(${rotation}deg) `; } - function getScaleString(nodeOrBool: NodeTypes | boolean) { + function scale(nodeOrBool: NodeTypes | boolean) { const isSelected = typeof nodeOrBool === "boolean" ? nodeOrBool : selected.value === nodeOrBool.id; return isSelected ? " scale(1.2)" : ""; } - function getOpacityString(node: NodeTypes) { + function opacity(node: NodeTypes) { const isDragging = selected.value !== node.id && nodeBeingDragged.value === node.id; if (isDragging) { return "; opacity: 0.5;"; } return ""; } + function zIndex(node: NodeTypes) { + if (selected.value === node.id || nodeBeingDragged.value === node.id) { + return "; z-index: 100000000"; + } + return "; z-index: " + node.z; + } const renderANode = function (node: ANode) { return ( mouseDownNode(e, node)} onMouseUp={e => mouseUpNode(e, node)} > - + {receivingNodes.value.includes(node.id) && ( ( { if (selectedAction.value === 0) { spawnBNode(node as ANode); @@ -190,14 +203,13 @@ export const main = createLayer("main", function (this: BaseLayer) { const renderBNode = function (node: BNode) { return ( mouseDownNode(e, node)} onMouseUp={e => mouseUpNode(e, node)} > - + {receivingNodes.value.includes(node.id) && ( ( { if (selectedAction.value === 0) { spawnANode(node as BNode); @@ -270,6 +281,7 @@ export const main = createLayer("main", function (this: BaseLayer) { const node: ANode = { x: parent.x, y: parent.y, + z: nextId.value, type: "anode", links: [parent.id], id: nextId.value @@ -281,6 +293,7 @@ export const main = createLayer("main", function (this: BaseLayer) { const node: BNode = { x: parent.x, y: parent.y, + z: nextId.value, type: "bnode", links: [parent.id], id: nextId.value @@ -314,21 +327,21 @@ export const main = createLayer("main", function (this: BaseLayer) { }); const dNodesPerAxis = 50; - const dNodes = jsx(() => - new Array(dNodesPerAxis * dNodesPerAxis).fill(0).map((_, i) => { - const x = (Math.floor(i / dNodesPerAxis) - dNodesPerAxis / 2) * 100; - const y = ((i % dNodesPerAxis) - dNodesPerAxis / 2) * 100; - return ( - - ); - }) - ); - - // const dNodes; + const dNodes = jsx(() => ( + <> + {new Array(dNodesPerAxis * dNodesPerAxis).fill(0).map((_, i) => { + const x = (Math.floor(i / dNodesPerAxis) - dNodesPerAxis / 2) * 100; + const y = ((i % dNodesPerAxis) - dNodesPerAxis / 2) * 100; + return ( + + ); + })} + + )); const links = jsx(() => ( <> @@ -371,17 +384,6 @@ export const main = createLayer("main", function (this: BaseLayer) { const nextId = setupUniqueIds(() => nodes.value); - function filterNodes(n: number | "cnode") { - return n !== nodeBeingDragged.value && n !== selected.value; - } - - function renderNodeById(id: number | "cnode" | undefined) { - if (id == null) { - return undefined; - } - return renderNode(nodesById.value[id] ?? cNode); - } - function renderNode(node: NodeTypes | typeof cNode) { if (node.type === "anode") { return renderANode(node); @@ -408,14 +410,12 @@ export const main = createLayer("main", function (this: BaseLayer) { {dNodes()} {links()} - {nodes.value.filter(n => filterNodes(n.id)).map(renderNode)} - {filterNodes("cnode") && render(cNode)} + {nodes.value.map(renderNode)} + {render(cNode)} {aActions()} {bActions()} - {renderNodeById(selected.value)} - {renderNodeById(nodeBeingDragged.value)} )), From 3fd8375031de265f8cf6f307190c52986ec74fb9 Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Sun, 3 Mar 2024 22:17:16 -0600 Subject: [PATCH 05/58] Perf optimization --- src/features/boards/board.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/features/boards/board.tsx b/src/features/boards/board.tsx index 20501d8..b687998 100644 --- a/src/features/boards/board.tsx +++ b/src/features/boards/board.tsx @@ -160,7 +160,7 @@ export function setupDraggableNode(options: { }, drag: function (e: MouseEvent | TouchEvent) { const panZoomInstance = options.board.value?.panZoomInstance as PanZoom | undefined; - if (panZoomInstance == null) { + if (panZoomInstance == null || nodeBeingDragged.value == null) { return; } @@ -199,10 +199,8 @@ export function setupDraggableNode(options: { hasDragged.value = true; } - if (nodeBeingDragged.value != null) { - e.preventDefault(); - e.stopPropagation(); - } + e.preventDefault(); + e.stopPropagation(); } }; return result; From 17b878e3be553d491cfa3194be4f220d054c913b Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Sun, 3 Mar 2024 22:31:20 -0600 Subject: [PATCH 06/58] Fix upgrade purchasing on drag --- src/data/projEntry.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/data/projEntry.tsx b/src/data/projEntry.tsx index 8a3f858..2fa532d 100644 --- a/src/data/projEntry.tsx +++ b/src/data/projEntry.tsx @@ -309,7 +309,8 @@ export const main = createLayer("main", function (this: BaseLayer) { requirements: createCostRequirement(() => ({ cost: 10, resource: points })), style: { x: "100px", - y: "100px" + y: "100px", + pointerEvents: "none" } })); makeDraggable(cNode, { From 68b1401c2af072c789ed941979e3a15a8ef9d58e Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Fri, 8 Mar 2024 17:06:54 -0600 Subject: [PATCH 07/58] Document boards --- src/data/common.tsx | 20 ++++- src/data/projEntry.tsx | 2 +- src/features/boards/board.tsx | 137 +++++++++++++++++++++++++--------- 3 files changed, 121 insertions(+), 38 deletions(-) diff --git a/src/data/common.tsx b/src/data/common.tsx index c1286a2..da1a698 100644 --- a/src/data/common.tsx +++ b/src/data/common.tsx @@ -27,7 +27,7 @@ import type { import { convertComputable, processComputable } from "util/computed"; import { getFirstFeature, renderColJSX, renderJSX } from "util/vue"; import type { ComputedRef, Ref } from "vue"; -import { computed, unref } from "vue"; +import { computed, ref, unref } from "vue"; import "./common.css"; /** An object that configures a {@link ResetButton} */ @@ -505,3 +505,21 @@ export function isRendered(layer: BaseLayer, idOrFeature: string | { id: string const id = typeof idOrFeature === "string" ? idOrFeature : idOrFeature.id; return computed(() => id in layer.nodes.value); } + +/** + * Utility function for setting up a system where one of many things can be selected. + * It's recommended to use an ID or index rather than the object itself, so that you can wrap the ref in a persistent without breaking anything. + * @returns The ref containing the selection, as well as a select and deselect function + */ +export function setupSelectable() { + const selected = ref(); + return { + select: function (node: T) { + selected.value = node; + }, + deselect: function () { + selected.value = undefined; + }, + selected + }; +} diff --git a/src/data/projEntry.tsx b/src/data/projEntry.tsx index 2fa532d..96499b7 100644 --- a/src/data/projEntry.tsx +++ b/src/data/projEntry.tsx @@ -8,7 +8,6 @@ import { placeInAvailableSpace, setupActions, setupDraggableNode, - setupSelectable, setupUniqueIds } from "features/boards/board"; import { jsx } from "features/feature"; @@ -21,6 +20,7 @@ import type { Player } from "game/player"; import { createCostRequirement } from "game/requirements"; import { render } from "util/vue"; import { ComponentPublicInstance, computed, ref, watch } from "vue"; +import { setupSelectable } from "./common"; import "./common.css"; /** diff --git a/src/features/boards/board.tsx b/src/features/boards/board.tsx index b687998..845f96d 100644 --- a/src/features/boards/board.tsx +++ b/src/features/boards/board.tsx @@ -12,8 +12,10 @@ import type { ComponentPublicInstance, Ref } from "vue"; import { computed, nextTick, ref, unref, watchEffect } from "vue"; import panZoom from "vue-panzoom"; +// Register panzoom so it can be used in Board.vue globalBus.on("setupVue", app => panZoom.install(app)); +/** A type representing the position of a node. */ export type NodePosition = { x: number; y: number }; /** @@ -38,37 +40,62 @@ export function unwrapNodeRef( : unref(property); } +/** + * Create a computed ref that can assist in assigning new nodes an ID unique from all current nodes. + * @param nodes The list of current nodes with IDs as properties + * @returns A computed ref that will give the value of the next unique ID + */ export function setupUniqueIds(nodes: Computable<{ id: number }[]>) { const processedNodes = convertComputable(nodes); return computed(() => Math.max(-1, ...unref(processedNodes).map(node => node.id)) + 1); } -export function setupSelectable() { - const selected = ref(); - return { - select: function (node: T) { - selected.value = node; - }, - deselect: function () { - selected.value = undefined; - }, - selected - }; +/** An object that configures a {@link DraggableNode}. */ +export interface DraggableNodeOptions { + /** A ref to the specific instance of the Board vue component the node will be draggable on. Obtained by passing a suitable ref as the "ref" attribute to the element. */ + board: Ref | undefined>; + /** Getter function to go from the node (typically ID) to the position of said node. */ + getPosition: (node: T) => NodePosition; + /** Setter function to update the position of a node. */ + setPosition: (node: T, position: NodePosition) => void; + /** A list of nodes that the currently dragged node can be dropped upon. */ + receivingNodes?: NodeComputable; + /** The maximum distance (in pixels, before zoom) away a node can be and still drop onto a receiving node. */ + dropAreaRadius?: NodeComputable; + /** A callback for when a node gets dropped upon a receiving node. */ + onDrop?: (acceptingNode: T, draggingNode: T) => void; } -export function setupDraggableNode(options: { - board: Ref | undefined>; - getPosition: (node: T) => NodePosition; - setPosition: (node: T, position: NodePosition) => void; - receivingNodes?: NodeComputable; - dropAreaRadius?: NodeComputable; - onDrop?: (acceptingNode: T, draggingNode: T) => void; -}) { +/** An object that represents a system for moving nodes on a board by dragging them. */ +export interface DraggableNode { + /** A ref to the node currently being moved. */ + nodeBeingDragged: Ref; + /** A ref to the node the node being dragged could be dropped upon if let go, if any. The node closest to the node being dragged if there are more than one within the drop area radius. */ + receivingNode: Ref; + /** A ref to whether or not the node being dragged has actually been dragged away from its starting position. */ + hasDragged: Ref; + /** The position of the node being dragged relative to where it started at the beginning of the drag. */ + dragDelta: Ref; + /** The nodes that can receive the node currently being dragged. */ + receivingNodes: Ref; + /** A function to call whenever a drag should start, that takes the mouse event that triggered it. Typically attached to each node's onMouseDown listener. */ + startDrag: (e: MouseEvent | TouchEvent, node: T) => void; + /** A function to call whenever a drag should end, typically attached to the Board's onMouseUp and onMouseLeave listeners. */ + endDrag: VoidFunction; + /** A function to call when the mouse moves during a drag, typically attached to the Board's onDrag listener. */ + drag: (e: MouseEvent | TouchEvent) => void; +} + +/** + * Sets up a system to allow nodes to be moved within a board by dragging and dropping. + * Also allows for dropping nodes on other nodes to trigger code. + * @param options Draggable node options. + * @returns A DraggableNode object. + */ +export function setupDraggableNode(options: DraggableNodeOptions): DraggableNode { const nodeBeingDragged = ref(); const receivingNode = ref(); const hasDragged = ref(false); - const mousePosition = ref(); - const lastMousePosition = ref({ x: 0, y: 0 }); const dragDelta = ref({ x: 0, y: 0 }); const receivingNodes = computed(() => nodeBeingDragged.value == null @@ -78,6 +105,9 @@ export function setupDraggableNode(options: { ); const dropAreaRadius = options.dropAreaRadius ?? 50; + const mousePosition = ref(); + const lastMousePosition = ref({ x: 0, y: 0 }); + watchEffect(() => { const node = nodeBeingDragged.value; if (node == null) { @@ -112,8 +142,6 @@ export function setupDraggableNode(options: { nodeBeingDragged, receivingNode, hasDragged, - mousePosition, - lastMousePosition, dragDelta, receivingNodes, startDrag: function (e: MouseEvent | TouchEvent, node: T) { @@ -206,19 +234,36 @@ export function setupDraggableNode(options: { return result; } +/** An object that configures how to make a vue feature draggable using {@link makeDraggable}. */ +export interface MakeDraggableOptions { + /** The node ID to use for the vue feature. */ + id: T; + /** A reference to the current node being dragged, typically from {@link setupDraggableNode}. */ + nodeBeingDragged: Ref; + /** A reference to whether or not the node being dragged has been moved away from its initial position. Typically from {@link setupDraggableNode}. */ + hasDragged: Ref; + /** A reference to how far the node being dragged is from its initial position. Typically from {@link setupDraggableNode}. */ + dragDelta: Ref; + /** A function to call when a drag is supposed to start. Typically from {@link setupDraggableNode}. */ + startDrag: (e: MouseEvent | TouchEvent, id: T) => void; + /** A function to call when a drag is supposed to end. Typically from {@link setupDraggableNode}. */ + endDrag: VoidFunction; + /** A callback that's called when the element is pressed down. Fires before drag starts, and returning `false` will prevent the drag from happening. */ + onMouseDown?: (e: MouseEvent | TouchEvent) => boolean | void; + /** A callback that's called when the mouse is lifted off the element. */ + onMouseUp?: (e: MouseEvent | TouchEvent) => boolean | void; + /** The initial position of the node on the board. Defaults to (0, 0). */ + initialPosition?: NodePosition; +} + +/** + * Makes a vue feature draggable on a Board. + * @param element The vue feature to make draggable. + * @param options The options to configure the dragging behavior. + */ export function makeDraggable( element: T, - options: { - id: S; - nodeBeingDragged: Ref; - hasDragged: Ref; - dragDelta: Ref; - startDrag: (e: MouseEvent | TouchEvent, id: S) => void; - endDrag: VoidFunction; - onMouseDown?: (e: MouseEvent | TouchEvent) => boolean | void; - onMouseUp?: (e: MouseEvent | TouchEvent) => boolean | void; - initialPosition?: NodePosition; - } + options: MakeDraggableOptions ): asserts element is T & { position: Persistent } { const position = persistent(options.initialPosition ?? { x: 0, y: 0 }); (element as T & { position: Persistent }).position = position; @@ -264,13 +309,26 @@ export function makeDraggable( }); } -export function setupActions(options: { +/** An object that configures how to setup a list of actions using {@link setupActions}. */ +export interface SetupActionsOptions { + /** The node to display actions upon, or undefined when the actions should be hidden. */ node: Computable; + /** Whether or not to currently display the actions. */ shouldShowActions?: NodeComputable; + /** The list of actions to display. Actions are arbitrary JSX elements. */ actions: NodeComputable JSX.Element)[]>; + /** The distance from the node to place the actions. */ distance: NodeComputable; + /** The arc length to place between actions, in radians. */ arcLength?: NodeComputable; -}) { +} + +/** + * Sets up a system where a list of actions, which are arbitrary JSX elements, will get displayed around a node radially, under given conditions. The actions are radially centered around 3/2 PI (Down). + * @param options Setup actions options. + * @returns A JSX function to render the actions. + */ +export function setupActions(options: SetupActionsOptions) { const node = convertComputable(options.node); return jsx(() => { const currNode = unref(node); @@ -300,6 +358,13 @@ export function setupActions(options: { }); } +/** + * Moves a node so that it is sufficiently far away from any other nodes, to prevent overlapping. + * @param nodeToPlace The node to find a spot for, with it's current/preffered position. + * @param nodes The list of nodes to make sure nodeToPlace is far enough away from. + * @param radius How far away nodeToPlace must be from any other nodes. + * @param direction The direction to push the nodeToPlace until it finds an available spot. + */ export function placeInAvailableSpace( nodeToPlace: T, nodes: T[], From ad421ac7593594790f74db7a0ff3a62c38bbbe58 Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Sat, 9 Mar 2024 13:42:17 -0600 Subject: [PATCH 08/58] Add some tests for boards --- src/features/boards/board.tsx | 1 + tests/features/board.test.ts | 108 ++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 tests/features/board.test.ts diff --git a/src/features/boards/board.tsx b/src/features/boards/board.tsx index 845f96d..9912c2f 100644 --- a/src/features/boards/board.tsx +++ b/src/features/boards/board.tsx @@ -418,6 +418,7 @@ export function placeInAvailableSpace( : Math.abs(nodeToPlace.y - nodeToCheck.y); // If we're too close to this node, move further + // Keep in mind positions start at top right, so "down" means increasing Y if (distance < radius) { if (direction === Direction.Right) { nodeToPlace.x = nodeToCheck.x + radius; diff --git a/tests/features/board.test.ts b/tests/features/board.test.ts new file mode 100644 index 0000000..6aa0cad --- /dev/null +++ b/tests/features/board.test.ts @@ -0,0 +1,108 @@ +import { + NodePosition, + placeInAvailableSpace, + setupUniqueIds, + unwrapNodeRef +} from "features/boards/board"; +import { beforeEach, describe, expect, test } from "vitest"; +import { Ref, ref } from "vue"; +import "../utils"; +import { Direction } from "util/common"; + +describe("Unwraps node refs", () => { + test("Static value", () => expect(unwrapNodeRef(100, {})).toBe(100)); + test("Ref value", () => expect(unwrapNodeRef(ref(100), {})).toBe(100)); + test("0 param function value", () => expect(unwrapNodeRef(() => 100, {})).toBe(100)); + test("1 param function value", () => { + const actualNode = { foo: "bar" }; + expect( + unwrapNodeRef(function (node) { + if (node === actualNode) { + return true; + } + return false; + }, actualNode) + ).toBe(true); + }); +}); + +describe("Set up unique IDs", () => { + let nodes: Ref<{ id: number }[]>, nextId: Ref; + beforeEach(() => { + nodes = ref([]); + nextId = setupUniqueIds(nodes); + }); + test("Starts at 0", () => expect(nextId?.value).toBe(0)); + test("Calculates initial value properly", () => { + nodes.value = [{ id: 0 }, { id: 1 }, { id: 2 }]; + expect(nextId.value).toBe(3); + }); + test("Non consecutive IDs", () => { + nodes.value = [{ id: -5 }, { id: 0 }, { id: 200 }]; + expect(nextId.value).toBe(201); + }); + test("After modification", () => { + nodes.value = [{ id: 0 }, { id: 1 }, { id: 2 }]; + nodes.value.push({ id: nextId.value }); + expect(nextId.value).toBe(4); + }); +}); + +describe("Place in available space", () => { + let nodes: Ref, node: NodePosition; + beforeEach(() => { + nodes = ref([]); + node = { x: 10, y: 20 }; + }); + test("No nodes", () => { + placeInAvailableSpace(node, nodes.value); + expect(node).toMatchObject({ x: 10, y: 20 }); + }); + test("Moves node", () => { + nodes.value = [{ x: 10, y: 20 }]; + placeInAvailableSpace(node, nodes.value); + expect(node).not.toMatchObject({ x: 10, y: 20 }); + }); + describe("Respects radius", () => { + test("Positions radius away", () => { + nodes.value = [{ x: 10, y: 20 }]; + placeInAvailableSpace(node, nodes.value, 32); + expect(node).toMatchObject({ x: 42, y: 20 }); + }); + test("Ignores node already radius away", () => { + nodes.value = [{ x: 42, y: 20 }]; + placeInAvailableSpace(node, nodes.value, 32); + expect(node).toMatchObject({ x: 10, y: 20 }); + }); + test("Doesn't ignore node just under radius away", () => { + nodes.value = [{ x: 41, y: 20 }]; + placeInAvailableSpace(node, nodes.value, 32); + expect(node).not.toMatchObject({ x: 10, y: 20 }); + }); + }); + describe("Respects direction", () => { + test("Goes left", () => { + nodes.value = [{ x: 10, y: 20 }]; + placeInAvailableSpace(node, nodes.value, 10, Direction.Left); + expect(node).toMatchObject({ x: 0, y: 20 }); + }); + test("Goes up", () => { + nodes.value = [{ x: 10, y: 20 }]; + placeInAvailableSpace(node, nodes.value, 10, Direction.Up); + expect(node).toMatchObject({ x: 10, y: 10 }); + }); + test("Goes down", () => { + nodes.value = [{ x: 10, y: 20 }]; + placeInAvailableSpace(node, nodes.value, 10, Direction.Down); + expect(node).toMatchObject({ x: 10, y: 30 }); + }); + }); + test("Finds hole", () => { + nodes.value = [ + { x: 10, y: 20 }, + { x: 30, y: 20 } + ]; + placeInAvailableSpace(node, nodes.value, 10); + expect(node).toMatchObject({ x: 20, y: 20 }); + }); +}); From 2cfa450d968bcc6fd86bd125f22d39467d003d7c Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Sat, 9 Mar 2024 20:36:41 -0600 Subject: [PATCH 09/58] Move board to src/game --- src/data/projEntry.tsx | 10 +++++----- src/{features => game}/boards/Board.vue | 1 + src/{features => game}/boards/CircleProgress.vue | 0 src/{features => game}/boards/Draggable.vue | 0 src/{features => game}/boards/SVGNode.vue | 0 src/{features => game}/boards/SquareProgress.vue | 0 src/{features => game}/boards/board.tsx | 4 ++-- tests/features/board.test.ts | 2 +- 8 files changed, 9 insertions(+), 8 deletions(-) rename src/{features => game}/boards/Board.vue (99%) rename src/{features => game}/boards/CircleProgress.vue (100%) rename src/{features => game}/boards/Draggable.vue (100%) rename src/{features => game}/boards/SVGNode.vue (100%) rename src/{features => game}/boards/SquareProgress.vue (100%) rename src/{features => game}/boards/board.tsx (99%) diff --git a/src/data/projEntry.tsx b/src/data/projEntry.tsx index 96499b7..a52dcc3 100644 --- a/src/data/projEntry.tsx +++ b/src/data/projEntry.tsx @@ -1,7 +1,7 @@ -import Board from "features/boards/Board.vue"; -import CircleProgress from "features/boards/CircleProgress.vue"; -import SVGNode from "features/boards/SVGNode.vue"; -import SquareProgress from "features/boards/SquareProgress.vue"; +import Board from "game/boards/Board.vue"; +import CircleProgress from "game/boards/CircleProgress.vue"; +import SVGNode from "game/boards/SVGNode.vue"; +import SquareProgress from "game/boards/SquareProgress.vue"; import { NodePosition, makeDraggable, @@ -9,7 +9,7 @@ import { setupActions, setupDraggableNode, setupUniqueIds -} from "features/boards/board"; +} from "game/boards/board"; import { jsx } from "features/feature"; import { createResource } from "features/resources/resource"; import { createUpgrade } from "features/upgrades/upgrade"; diff --git a/src/features/boards/Board.vue b/src/game/boards/Board.vue similarity index 99% rename from src/features/boards/Board.vue rename to src/game/boards/Board.vue index 0ded9a1..8d33818 100644 --- a/src/features/boards/Board.vue +++ b/src/game/boards/Board.vue @@ -91,3 +91,4 @@ function onInit(panzoomInstance: PanZoom) { margin: -50px -10px; } +game/boards/board \ No newline at end of file diff --git a/src/features/boards/CircleProgress.vue b/src/game/boards/CircleProgress.vue similarity index 100% rename from src/features/boards/CircleProgress.vue rename to src/game/boards/CircleProgress.vue diff --git a/src/features/boards/Draggable.vue b/src/game/boards/Draggable.vue similarity index 100% rename from src/features/boards/Draggable.vue rename to src/game/boards/Draggable.vue diff --git a/src/features/boards/SVGNode.vue b/src/game/boards/SVGNode.vue similarity index 100% rename from src/features/boards/SVGNode.vue rename to src/game/boards/SVGNode.vue diff --git a/src/features/boards/SquareProgress.vue b/src/game/boards/SquareProgress.vue similarity index 100% rename from src/features/boards/SquareProgress.vue rename to src/game/boards/SquareProgress.vue diff --git a/src/features/boards/board.tsx b/src/game/boards/board.tsx similarity index 99% rename from src/features/boards/board.tsx rename to src/game/boards/board.tsx index 9912c2f..5b27d02 100644 --- a/src/features/boards/board.tsx +++ b/src/game/boards/board.tsx @@ -1,5 +1,5 @@ -import Board from "features/boards/Board.vue"; -import Draggable from "features/boards/Draggable.vue"; +import Board from "./Board.vue"; +import Draggable from "./Draggable.vue"; import { Component, GatherProps, GenericComponent, jsx } from "features/feature"; import { globalBus } from "game/events"; import { Persistent, persistent } from "game/persistence"; diff --git a/tests/features/board.test.ts b/tests/features/board.test.ts index 6aa0cad..c9e4859 100644 --- a/tests/features/board.test.ts +++ b/tests/features/board.test.ts @@ -3,7 +3,7 @@ import { placeInAvailableSpace, setupUniqueIds, unwrapNodeRef -} from "features/boards/board"; +} from "game/boards/board"; import { beforeEach, describe, expect, test } from "vitest"; import { Ref, ref } from "vue"; import "../utils"; From 0e7ca27e3b587b49864232863d453e60e0c69a51 Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Sat, 9 Mar 2024 20:52:02 -0600 Subject: [PATCH 10/58] Move common board node CSS to class --- src/game/boards/Board.vue | 11 +++++++++-- src/game/boards/Draggable.vue | 10 +--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/game/boards/Board.vue b/src/game/boards/Board.vue index 8d33818..c11a084 100644 --- a/src/game/boards/Board.vue +++ b/src/game/boards/Board.vue @@ -26,7 +26,7 @@ import type { PanZoom } from "panzoom"; import type { ComponentPublicInstance } from "vue"; import { computed, ref } from "vue"; // Required to make sure panzoom component gets registered: -import "features/boards/board"; +import "./board"; defineExpose({ panZoomInstance: computed(() => stage.value?.panZoomInstance) @@ -90,5 +90,12 @@ function onInit(panzoomInstance: PanZoom) { height: calc(100% + 100px); margin: -50px -10px; } + +.board-node { + position: absolute; + top: 0; + left: 50%; + transition-duration: 0s; +} -game/boards/board \ No newline at end of file +game/boards/board diff --git a/src/game/boards/Draggable.vue b/src/game/boards/Draggable.vue index 9d59db6..b181b8b 100644 --- a/src/game/boards/Draggable.vue +++ b/src/game/boards/Draggable.vue @@ -1,5 +1,6 @@ - diff --git a/src/features/trees/tree.ts b/src/features/trees/tree.ts deleted file mode 100644 index 1148cf0..0000000 --- a/src/features/trees/tree.ts +++ /dev/null @@ -1,387 +0,0 @@ -import { GenericDecorator } from "features/decorators/common"; -import type { - CoercableComponent, - GenericComponent, - OptionsFunc, - Replace, - StyleValue -} from "features/feature"; -import { Component, GatherProps, getUniqueID, setDefault, Visibility } from "features/feature"; -import type { Link } from "features/links/links"; -import type { GenericReset } from "features/reset"; -import type { Resource } from "features/resources/resource"; -import { displayResource } from "features/resources/resource"; -import TreeComponent from "features/trees/Tree.vue"; -import TreeNodeComponent from "features/trees/TreeNode.vue"; -import type { DecimalSource } from "util/bignum"; -import Decimal, { format, formatWhole } from "util/bignum"; -import type { - Computable, - GetComputableType, - GetComputableTypeWithDefault, - ProcessedComputable -} from "util/computed"; -import { convertComputable, processComputable } from "util/computed"; -import { createLazyProxy } from "util/proxies"; -import type { Ref } from "vue"; -import { computed, ref, shallowRef, unref } from "vue"; - -/** A symbol used to identify {@link TreeNode} features. */ -export const TreeNodeType = Symbol("TreeNode"); -/** A symbol used to identify {@link Tree} features. */ -export const TreeType = Symbol("Tree"); - -/** - * An object that configures a {@link TreeNode}. - */ -export interface TreeNodeOptions { - /** Whether this tree node should be visible. */ - visibility?: Computable; - /** Whether or not this tree node can be clicked. */ - canClick?: Computable; - /** The background color for this node. */ - color?: Computable; - /** The label to display on this tree node. */ - display?: Computable; - /** The color of the glow effect shown to notify the user there's something to do with this node. */ - glowColor?: Computable; - /** Dictionary of CSS classes to apply to this feature. */ - classes?: Computable>; - /** CSS to apply to this feature. */ - style?: Computable; - /** Shows a marker on the corner of the feature. */ - mark?: Computable; - /** A reset object attached to this node, used for propagating resets through the tree. */ - reset?: GenericReset; - /** A function that is called when the tree node is clicked. */ - onClick?: (e?: MouseEvent | TouchEvent) => void; - /** A function that is called when the tree node is held down. */ - onHold?: VoidFunction; -} - -/** - * The properties that are added onto a processed {@link TreeNodeOptions} to create an {@link TreeNode}. - */ -export interface BaseTreeNode { - /** An auto-generated ID for identifying features that appear in the DOM. Will not persist between refreshes or updates. */ - id: string; - /** A symbol that helps identify features of the same type. */ - type: typeof TreeNodeType; - /** The Vue component used to render this feature. */ - [Component]: GenericComponent; - /** A function to gather the props the vue component requires for this feature. */ - [GatherProps]: () => Record; -} - -/** An object that represents a node on a tree. */ -export type TreeNode = Replace< - T & BaseTreeNode, - { - visibility: GetComputableTypeWithDefault; - canClick: GetComputableTypeWithDefault; - color: GetComputableType; - display: GetComputableType; - glowColor: GetComputableType; - classes: GetComputableType; - style: GetComputableType; - mark: GetComputableType; - } ->; - -/** A type that matches any valid {@link TreeNode} object. */ -export type GenericTreeNode = Replace< - TreeNode, - { - visibility: ProcessedComputable; - canClick: ProcessedComputable; - } ->; - -/** - * Lazily creates a tree node with the given options. - * @param optionsFunc Tree Node options. - */ -export function createTreeNode( - optionsFunc?: OptionsFunc, - ...decorators: GenericDecorator[] -): TreeNode { - const decoratedData = decorators.reduce( - (current, next) => Object.assign(current, next.getPersistentData?.()), - {} - ); - return createLazyProxy(feature => { - const treeNode = - optionsFunc?.call(feature, feature) ?? - ({} as ReturnType>); - treeNode.id = getUniqueID("treeNode-"); - treeNode.type = TreeNodeType; - treeNode[Component] = TreeNodeComponent as GenericComponent; - - for (const decorator of decorators) { - decorator.preConstruct?.(treeNode); - } - - Object.assign(decoratedData); - - processComputable(treeNode as T, "visibility"); - setDefault(treeNode, "visibility", Visibility.Visible); - processComputable(treeNode as T, "canClick"); - setDefault(treeNode, "canClick", true); - processComputable(treeNode as T, "color"); - processComputable(treeNode as T, "display"); - processComputable(treeNode as T, "glowColor"); - processComputable(treeNode as T, "classes"); - processComputable(treeNode as T, "style"); - processComputable(treeNode as T, "mark"); - - for (const decorator of decorators) { - decorator.postConstruct?.(treeNode); - } - - if (treeNode.onClick) { - const onClick = treeNode.onClick.bind(treeNode); - treeNode.onClick = function (e) { - if ( - unref(treeNode.canClick as ProcessedComputable) !== false - ) { - onClick(e); - } - }; - } - if (treeNode.onHold) { - const onHold = treeNode.onHold.bind(treeNode); - treeNode.onHold = function () { - if ( - unref(treeNode.canClick as ProcessedComputable) !== false - ) { - onHold(); - } - }; - } - - const decoratedProps = decorators.reduce( - (current, next) => Object.assign(current, next.getGatheredProps?.(treeNode)), - {} - ); - treeNode[GatherProps] = function (this: GenericTreeNode) { - const { - display, - visibility, - style, - classes, - onClick, - onHold, - color, - glowColor, - canClick, - mark, - id - } = this; - return { - display, - visibility, - style, - classes, - onClick, - onHold, - color, - glowColor, - canClick, - mark, - id, - ...decoratedProps - }; - }; - - return treeNode as unknown as TreeNode; - }); -} - -/** Represents a branch between two nodes in a tree. */ -export interface TreeBranch extends Omit { - startNode: GenericTreeNode; - endNode: GenericTreeNode; -} - -/** - * An object that configures a {@link Tree}. - */ -export interface TreeOptions { - /** Whether this clickable should be visible. */ - visibility?: Computable; - /** The nodes within the tree, in a 2D array. */ - nodes: Computable; - /** Nodes to show on the left side of the tree. */ - leftSideNodes?: Computable; - /** Nodes to show on the right side of the tree. */ - rightSideNodes?: Computable; - /** The branches between nodes within this tree. */ - branches?: Computable; - /** How to propagate resets through the tree. */ - resetPropagation?: ResetPropagation; - /** A function that is called when a node within the tree is reset. */ - onReset?: (node: GenericTreeNode) => void; -} - -export interface BaseTree { - /** An auto-generated ID for identifying features that appear in the DOM. Will not persist between refreshes or updates. */ - id: string; - /** The link objects for each of the branches of the tree. */ - links: Ref; - /** Cause a reset on this node and propagate it through the tree according to {@link TreeOptions.resetPropagation}. */ - reset: (node: GenericTreeNode) => void; - /** A flag that is true while the reset is still propagating through the tree. */ - isResetting: Ref; - /** A reference to the node that caused the currently propagating reset. */ - resettingNode: Ref; - /** A symbol that helps identify features of the same type. */ - type: typeof TreeType; - /** The Vue component used to render this feature. */ - [Component]: GenericComponent; - /** A function to gather the props the vue component requires for this feature. */ - [GatherProps]: () => Record; -} - -/** An object that represents a feature that is a tree of nodes with branches between them. Contains support for reset mechanics that can propagate through the tree. */ -export type Tree = Replace< - T & BaseTree, - { - visibility: GetComputableTypeWithDefault; - nodes: GetComputableType; - leftSideNodes: GetComputableType; - rightSideNodes: GetComputableType; - branches: GetComputableType; - } ->; - -/** A type that matches any valid {@link Tree} object. */ -export type GenericTree = Replace< - Tree, - { - visibility: ProcessedComputable; - } ->; - -/** - * Lazily creates a tree with the given options. - * @param optionsFunc Tree options. - */ -export function createTree( - optionsFunc: OptionsFunc -): Tree { - return createLazyProxy(feature => { - const tree = optionsFunc.call(feature, feature); - tree.id = getUniqueID("tree-"); - tree.type = TreeType; - tree[Component] = TreeComponent as GenericComponent; - - tree.isResetting = ref(false); - tree.resettingNode = shallowRef(null); - - tree.reset = function (node) { - const genericTree = tree as GenericTree; - genericTree.isResetting.value = true; - genericTree.resettingNode.value = node; - genericTree.resetPropagation?.(genericTree, node); - genericTree.onReset?.(node); - genericTree.isResetting.value = false; - genericTree.resettingNode.value = null; - }; - tree.links = computed(() => { - const genericTree = tree as GenericTree; - return unref(genericTree.branches) ?? []; - }); - - processComputable(tree as T, "visibility"); - setDefault(tree, "visibility", Visibility.Visible); - processComputable(tree as T, "nodes"); - processComputable(tree as T, "leftSideNodes"); - processComputable(tree as T, "rightSideNodes"); - processComputable(tree as T, "branches"); - - tree[GatherProps] = function (this: GenericTree) { - const { nodes, leftSideNodes, rightSideNodes, branches } = this; - return { nodes, leftSideNodes, rightSideNodes, branches }; - }; - - return tree as unknown as Tree; - }); -} - -/** A function that is used to propagate resets through a tree. */ -export type ResetPropagation = { - (tree: GenericTree, resettingNode: GenericTreeNode): void; -}; - -/** Propagate resets down the tree by resetting every node in a lower row. */ -export const defaultResetPropagation = function ( - tree: GenericTree, - resettingNode: GenericTreeNode -): void { - const nodes = unref(tree.nodes); - const row = nodes.findIndex(nodes => nodes.includes(resettingNode)) - 1; - for (let x = row; x >= 0; x--) { - nodes[x].forEach(node => node.reset?.reset()); - } -}; - -/** Propagate resets down the tree by resetting every node in a lower row. */ -export const invertedResetPropagation = function ( - tree: GenericTree, - resettingNode: GenericTreeNode -): void { - const nodes = unref(tree.nodes); - const row = nodes.findIndex(nodes => nodes.includes(resettingNode)) + 1; - for (let x = row; x < nodes.length; x++) { - nodes[x].forEach(node => node.reset?.reset()); - } -}; - -/** Propagate resets down the branches of the tree. */ -export const branchedResetPropagation = function ( - tree: GenericTree, - resettingNode: GenericTreeNode -): void { - const links = unref(tree.branches); - if (links == null) return; - const reset: GenericTreeNode[] = []; - let current = [resettingNode]; - while (current.length !== 0) { - const next: GenericTreeNode[] = []; - for (const node of current) { - for (const link of links.filter(link => link.startNode === node)) { - if ([...reset, ...current].includes(link.endNode)) continue; - next.push(link.endNode); - link.endNode.reset?.reset(); - } - } - reset.push(...current); - current = next; - } -}; - -/** - * Utility for creating a tooltip for a tree node that displays a resource-based unlock requirement, and after unlock shows the amount of another resource. - * It sounds oddly specific, but comes up a lot. - */ -export function createResourceTooltip( - resource: Resource, - requiredResource: Resource | null = null, - requirement: Computable = 0 -): Ref { - const req = convertComputable(requirement); - return computed(() => { - if (requiredResource == null || Decimal.gte(resource.value, unref(req))) { - return displayResource(resource) + " " + resource.displayName; - } - return `Reach ${ - Decimal.eq(requiredResource.precision, 0) - ? formatWhole(unref(req)) - : format(unref(req), requiredResource.precision) - } ${requiredResource.displayName} to unlock (You have ${ - Decimal.eq(requiredResource.precision, 0) - ? formatWhole(requiredResource.value) - : format(requiredResource.value, requiredResource.precision) - })`; - }); -} diff --git a/src/features/trees/tree.tsx b/src/features/trees/tree.tsx new file mode 100644 index 0000000..7d2ded6 --- /dev/null +++ b/src/features/trees/tree.tsx @@ -0,0 +1,279 @@ +import type { OptionsFunc, Replace } from "features/feature"; +import { Link } from "features/links/links"; +import type { Reset } from "features/reset"; +import type { Resource } from "features/resources/resource"; +import { displayResource } from "features/resources/resource"; +import Tree from "features/trees/Tree.vue"; +import TreeNode from "features/trees/TreeNode.vue"; +import type { DecimalSource } from "util/bignum"; +import Decimal, { format, formatWhole } from "util/bignum"; +import { ProcessedRefOrGetter, processGetter } from "util/computed"; +import { createLazyProxy } from "util/proxies"; +import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue"; +import type { MaybeRef, MaybeRefOrGetter, Ref } from "vue"; +import { computed, ref, shallowRef, unref } from "vue"; + +/** A symbol used to identify {@link TreeNode} features. */ +export const TreeNodeType = Symbol("TreeNode"); +/** A symbol used to identify {@link Tree} features. */ +export const TreeType = Symbol("Tree"); + +/** + * An object that configures a {@link TreeNode}. + */ +export interface TreeNodeOptions extends VueFeatureOptions { + /** Whether or not this tree node can be clicked. */ + canClick?: MaybeRefOrGetter; + /** The background color for this node. */ + color?: MaybeRefOrGetter; + /** The label to display on this tree node. */ + display?: MaybeRefOrGetter; + /** The color of the glow effect shown to notify the user there's something to do with this node. */ + glowColor?: MaybeRefOrGetter; + /** A reset object attached to this node, used for propagating resets through the tree. */ + reset?: Reset; + /** A function that is called when the tree node is clicked. */ + onClick?: (e?: MouseEvent | TouchEvent) => void; + /** A function that is called when the tree node is held down. */ + onHold?: VoidFunction; +} + +/** + * The properties that are added onto a processed {@link TreeNodeOptions} to create an {@link TreeNode}. + */ +export interface BaseTreeNode extends VueFeature { + /** A symbol that helps identify features of the same type. */ + type: typeof TreeNodeType; +} + +/** An object that represents a node on a tree. */ +export type TreeNode = Replace< + TreeNodeOptions & BaseTreeNode, + { + canClick: MaybeRef; + color: ProcessedRefOrGetter; + display: ProcessedRefOrGetter; + glowColor: ProcessedRefOrGetter; + } +>; + +/** + * Lazily creates a tree node with the given options. + * @param optionsFunc Tree Node options. + */ +export function createTreeNode( + optionsFunc?: OptionsFunc +) { + return createLazyProxy(feature => { + const options = optionsFunc?.call(feature, feature as TreeNode) ?? ({} as T); + const { canClick, color, display, glowColor, onClick, onHold, ...props } = options; + + const treeNode = { + type: TreeNodeType, + ...(props as Omit), + ...vueFeatureMixin("treeNode", options, () => ( + + )), + canClick: processGetter(canClick) ?? true, + color: processGetter(color), + display: processGetter(display), + glowColor: processGetter(glowColor), + onClick: + onClick == null + ? undefined + : function (e) { + if (unref(treeNode.canClick) !== false) { + onClick.call(treeNode, e); + } + }, + onHold: + onHold == null + ? undefined + : function () { + if (unref(treeNode.canClick) !== false) { + onHold.call(treeNode); + } + } + } satisfies TreeNode; + + return treeNode; + }); +} + +/** Represents a branch between two nodes in a tree. */ +export interface TreeBranch extends Omit { + startNode: TreeNode; + endNode: TreeNode; +} + +/** + * An object that configures a {@link Tree}. + */ +export interface TreeOptions extends VueFeatureOptions { + /** The nodes within the tree, in a 2D array. */ + nodes: MaybeRefOrGetter; + /** Nodes to show on the left side of the tree. */ + leftSideNodes?: MaybeRefOrGetter; + /** Nodes to show on the right side of the tree. */ + rightSideNodes?: MaybeRefOrGetter; + /** The branches between nodes within this tree. */ + branches?: MaybeRefOrGetter; + /** How to propagate resets through the tree. */ + resetPropagation?: ResetPropagation; + /** A function that is called when a node within the tree is reset. */ + onReset?: (node: TreeNode) => void; +} + +export interface BaseTree extends VueFeature { + /** The link objects for each of the branches of the tree. */ + links: Ref; + /** Cause a reset on this node and propagate it through the tree according to {@link TreeOptions.resetPropagation}. */ + reset: (node: TreeNode) => void; + /** A flag that is true while the reset is still propagating through the tree. */ + isResetting: Ref; + /** A reference to the node that caused the currently propagating reset. */ + resettingNode: Ref; + /** A symbol that helps identify features of the same type. */ + type: typeof TreeType; +} + +/** An object that represents a feature that is a tree of nodes with branches between them. Contains support for reset mechanics that can propagate through the tree. */ +export type Tree = Replace< + TreeOptions & BaseTree, + { + nodes: ProcessedRefOrGetter; + leftSideNodes: ProcessedRefOrGetter; + rightSideNodes: ProcessedRefOrGetter; + branches: ProcessedRefOrGetter; + } +>; + +/** + * Lazily creates a tree with the given options. + * @param optionsFunc Tree options. + */ +export function createTree(optionsFunc: OptionsFunc) { + return createLazyProxy(feature => { + const options = optionsFunc.call(feature, feature as Tree); + const { + branches, + nodes, + leftSideNodes, + rightSideNodes, + reset, + resetPropagation, + onReset, + ...props + } = options; + + const tree = { + type: TreeType, + ...(props as Omit), + ...vueFeatureMixin("tree", options, () => ( + + )), + branches: processGetter(branches), + isResetting: ref(false), + resettingNode: shallowRef(null), + nodes: processGetter(nodes), + leftSideNodes: processGetter(leftSideNodes), + rightSideNodes: processGetter(rightSideNodes), + links: processGetter(branches) ?? [], + resetPropagation, + onReset, + reset: + reset ?? + function (node: TreeNode) { + tree.isResetting.value = true; + tree.resettingNode.value = node; + tree.resetPropagation?.(tree, node); + tree.onReset?.(node); + tree.isResetting.value = false; + tree.resettingNode.value = null; + } + } satisfies Tree; + + return tree; + }); +} + +/** A function that is used to propagate resets through a tree. */ +export type ResetPropagation = { + (tree: Tree, resettingNode: TreeNode): void; +}; + +/** Propagate resets down the tree by resetting every node in a lower row. */ +export const defaultResetPropagation = function (tree: Tree, resettingNode: TreeNode): void { + const nodes = unref(tree.nodes); + const row = nodes.findIndex(nodes => nodes.includes(resettingNode)) - 1; + for (let x = row; x >= 0; x--) { + nodes[x].forEach(node => node.reset?.reset()); + } +}; + +/** Propagate resets down the tree by resetting every node in a lower row. */ +export const invertedResetPropagation = function (tree: Tree, resettingNode: TreeNode): void { + const nodes = unref(tree.nodes); + const row = nodes.findIndex(nodes => nodes.includes(resettingNode)) + 1; + for (let x = row; x < nodes.length; x++) { + nodes[x].forEach(node => node.reset?.reset()); + } +}; + +/** Propagate resets down the branches of the tree. */ +export const branchedResetPropagation = function (tree: Tree, resettingNode: TreeNode): void { + const links = unref(tree.branches); + if (links == null) return; + const reset: TreeNode[] = []; + let current = [resettingNode]; + while (current.length !== 0) { + const next: TreeNode[] = []; + for (const node of current) { + for (const link of links.filter(link => link.startNode === node)) { + if ([...reset, ...current].includes(link.endNode)) continue; + next.push(link.endNode); + link.endNode.reset?.reset(); + } + } + reset.push(...current); + current = next; + } +}; + +/** + * Utility for creating a tooltip for a tree node that displays a resource-based unlock requirement, and after unlock shows the amount of another resource. + * It sounds oddly specific, but comes up a lot. + */ +export function createResourceTooltip( + resource: Resource, + requiredResource: Resource | null = null, + requirement: MaybeRefOrGetter = 0 +): Ref { + const req = processGetter(requirement); + return computed(() => { + if (requiredResource == null || Decimal.gte(resource.value, unref(req))) { + return displayResource(resource) + " " + resource.displayName; + } + return `Reach ${ + Decimal.eq(requiredResource.precision, 0) + ? formatWhole(unref(req)) + : format(unref(req), requiredResource.precision) + } ${requiredResource.displayName} to unlock (You have ${ + Decimal.eq(requiredResource.precision, 0) + ? formatWhole(requiredResource.value) + : format(requiredResource.value, requiredResource.precision) + })`; + }); +} diff --git a/src/features/upgrades/Upgrade.vue b/src/features/upgrades/Upgrade.vue deleted file mode 100644 index b2e3b5e..0000000 --- a/src/features/upgrades/Upgrade.vue +++ /dev/null @@ -1,98 +0,0 @@ - - - - - diff --git a/src/features/upgrades/upgrade.ts b/src/features/upgrades/upgrade.ts deleted file mode 100644 index a3075e7..0000000 --- a/src/features/upgrades/upgrade.ts +++ /dev/null @@ -1,227 +0,0 @@ -import { GenericDecorator } from "features/decorators/common"; -import type { - CoercableComponent, - GenericComponent, - OptionsFunc, - Replace, - StyleValue -} from "features/feature"; -import { - Component, - GatherProps, - Visibility, - findFeatures, - getUniqueID, - setDefault -} from "features/feature"; -import UpgradeComponent from "features/upgrades/Upgrade.vue"; -import type { GenericLayer } from "game/layers"; -import type { Persistent } from "game/persistence"; -import { persistent } from "game/persistence"; -import { - Requirements, - createVisibilityRequirement, - payRequirements, - requirementsMet -} from "game/requirements"; -import { isFunction } from "util/common"; -import type { - Computable, - GetComputableType, - GetComputableTypeWithDefault, - ProcessedComputable -} from "util/computed"; -import { processComputable } from "util/computed"; -import { createLazyProxy } from "util/proxies"; -import type { Ref } from "vue"; -import { computed, unref } from "vue"; - -/** A symbol used to identify {@link Upgrade} features. */ -export const UpgradeType = Symbol("Upgrade"); - -/** - * An object that configures a {@link Upgrade}. - */ -export interface UpgradeOptions { - /** Whether this clickable should be visible. */ - visibility?: Computable; - /** Dictionary of CSS classes to apply to this feature. */ - classes?: Computable>; - /** CSS to apply to this feature. */ - style?: Computable; - /** Shows a marker on the corner of the feature. */ - mark?: Computable; - /** The display to use for this clickable. */ - display?: Computable< - | CoercableComponent - | { - /** A header to appear at the top of the display. */ - title?: CoercableComponent; - /** The main text that appears in the display. */ - description: CoercableComponent; - /** A description of the current effect of the achievement. Useful when the effect changes dynamically. */ - effectDisplay?: CoercableComponent; - } - >; - /** The requirements to purchase this upgrade. */ - requirements: Requirements; - /** A function that is called when the upgrade is purchased. */ - onPurchase?: VoidFunction; -} - -/** - * The properties that are added onto a processed {@link UpgradeOptions} to create an {@link Upgrade}. - */ -export interface BaseUpgrade { - /** An auto-generated ID for identifying features that appear in the DOM. Will not persist between refreshes or updates. */ - id: string; - /** Whether or not this upgrade has been purchased. */ - bought: Persistent; - /** Whether or not the upgrade can currently be purchased. */ - canPurchase: Ref; - /** Purchase the upgrade */ - purchase: VoidFunction; - /** A symbol that helps identify features of the same type. */ - type: typeof UpgradeType; - /** The Vue component used to render this feature. */ - [Component]: GenericComponent; - /** A function to gather the props the vue component requires for this feature. */ - [GatherProps]: () => Record; -} - -/** An object that represents a feature that can be purchased a single time. */ -export type Upgrade = Replace< - T & BaseUpgrade, - { - visibility: GetComputableTypeWithDefault; - classes: GetComputableType; - style: GetComputableType; - display: GetComputableType; - requirements: GetComputableType; - mark: GetComputableType; - } ->; - -/** A type that matches any valid {@link Upgrade} object. */ -export type GenericUpgrade = Replace< - Upgrade, - { - visibility: ProcessedComputable; - } ->; - -/** - * Lazily creates an upgrade with the given options. - * @param optionsFunc Upgrade options. - */ -export function createUpgrade( - optionsFunc: OptionsFunc, - ...decorators: GenericDecorator[] -): Upgrade { - const bought = persistent(false, false); - const decoratedData = decorators.reduce( - (current, next) => Object.assign(current, next.getPersistentData?.()), - {} - ); - return createLazyProxy(feature => { - const upgrade = optionsFunc.call(feature, feature); - upgrade.id = getUniqueID("upgrade-"); - upgrade.type = UpgradeType; - upgrade[Component] = UpgradeComponent as GenericComponent; - - for (const decorator of decorators) { - decorator.preConstruct?.(upgrade); - } - - upgrade.bought = bought; - Object.assign(upgrade, decoratedData); - - upgrade.canPurchase = computed( - () => !bought.value && requirementsMet(upgrade.requirements) - ); - upgrade.purchase = function () { - const genericUpgrade = upgrade as GenericUpgrade; - if (!unref(genericUpgrade.canPurchase)) { - return; - } - payRequirements(upgrade.requirements); - bought.value = true; - genericUpgrade.onPurchase?.(); - }; - - const visibilityRequirement = createVisibilityRequirement(upgrade as GenericUpgrade); - if (Array.isArray(upgrade.requirements)) { - upgrade.requirements.unshift(visibilityRequirement); - } else { - upgrade.requirements = [visibilityRequirement, upgrade.requirements]; - } - - processComputable(upgrade as T, "visibility"); - setDefault(upgrade, "visibility", Visibility.Visible); - processComputable(upgrade as T, "classes"); - processComputable(upgrade as T, "style"); - processComputable(upgrade as T, "display"); - processComputable(upgrade as T, "mark"); - - for (const decorator of decorators) { - decorator.postConstruct?.(upgrade); - } - - const decoratedProps = decorators.reduce( - (current, next) => Object.assign(current, next.getGatheredProps?.(upgrade)), - {} - ); - upgrade[GatherProps] = function (this: GenericUpgrade) { - const { - display, - visibility, - style, - classes, - requirements, - canPurchase, - bought, - mark, - id, - purchase - } = this; - return { - display, - visibility, - style: unref(style), - classes, - requirements, - canPurchase, - bought, - mark, - id, - purchase, - ...decoratedProps - }; - }; - - return upgrade as unknown as Upgrade; - }); -} - -/** - * Utility to auto purchase a list of upgrades whenever they're affordable. - * @param layer The layer the upgrades are apart of - * @param autoActive Whether or not the upgrades should currently be auto-purchasing - * @param upgrades The specific upgrades to upgrade. If unspecified, uses all upgrades on the layer. - */ -export function setupAutoPurchase( - layer: GenericLayer, - autoActive: Computable, - upgrades: GenericUpgrade[] = [] -): void { - upgrades = - upgrades.length === 0 ? (findFeatures(layer, UpgradeType) as GenericUpgrade[]) : upgrades; - const isAutoActive: ProcessedComputable = isFunction(autoActive) - ? computed(autoActive) - : autoActive; - layer.on("update", () => { - if (unref(isAutoActive)) { - upgrades.forEach(upgrade => upgrade.purchase()); - } - }); -} diff --git a/src/game/boards/Draggable.vue b/src/game/boards/Draggable.vue index b181b8b..9b4a2fa 100644 --- a/src/game/boards/Draggable.vue +++ b/src/game/boards/Draggable.vue @@ -7,23 +7,17 @@ @mouseup="e => mouseUp(e)" @touchend.passive="e => mouseUp(e)" > - + diff --git a/src/game/boards/board.tsx b/src/game/boards/board.tsx index 5b27d02..15b9274 100644 --- a/src/game/boards/board.tsx +++ b/src/game/boards/board.tsx @@ -1,15 +1,13 @@ import Board from "./Board.vue"; import Draggable from "./Draggable.vue"; -import { Component, GatherProps, GenericComponent, jsx } from "features/feature"; import { globalBus } from "game/events"; import { Persistent, persistent } from "game/persistence"; import type { PanZoom } from "panzoom"; import { Direction, isFunction } from "util/common"; -import type { Computable, ProcessedComputable } from "util/computed"; -import { convertComputable } from "util/computed"; -import { VueFeature } from "util/vue"; -import type { ComponentPublicInstance, Ref } from "vue"; -import { computed, nextTick, ref, unref, watchEffect } from "vue"; +import { processGetter } from "util/computed"; +import { Renderable, VueFeature } from "util/vue"; +import type { ComponentPublicInstance, MaybeRef, MaybeRefOrGetter, Ref } from "vue"; +import { computed, ref, unref, watchEffect } from "vue"; import panZoom from "vue-panzoom"; // Register panzoom so it can be used in Board.vue @@ -19,10 +17,10 @@ globalBus.on("setupVue", app => panZoom.install(app)); export type NodePosition = { x: number; y: number }; /** - * A type representing a computable value for a node on the board. Used for node types to return different values based on the given node and the state of the board. + * A type representing a MaybeRefOrGetter value for a node on the board. Used for node types to return different values based on the given node and the state of the board. */ -export type NodeComputable = - | Computable +export type NodeMaybeRefOrGetter = + | MaybeRefOrGetter | ((node: T, ...args: S) => R); /** @@ -31,11 +29,11 @@ export type NodeComputable = * @param node The node to get the property of */ export function unwrapNodeRef( - property: NodeComputable, + property: NodeMaybeRefOrGetter, node: T, ...args: S ): R { - return isFunction>(property) + return isFunction>(property) ? property(node, ...args) : unref(property); } @@ -45,8 +43,8 @@ export function unwrapNodeRef( * @param nodes The list of current nodes with IDs as properties * @returns A computed ref that will give the value of the next unique ID */ -export function setupUniqueIds(nodes: Computable<{ id: number }[]>) { - const processedNodes = convertComputable(nodes); +export function setupUniqueIds(nodes: MaybeRefOrGetter<{ id: number }[]>) { + const processedNodes = processGetter(nodes); return computed(() => Math.max(-1, ...unref(processedNodes).map(node => node.id)) + 1); } @@ -59,9 +57,9 @@ export interface DraggableNodeOptions { /** Setter function to update the position of a node. */ setPosition: (node: T, position: NodePosition) => void; /** A list of nodes that the currently dragged node can be dropped upon. */ - receivingNodes?: NodeComputable; + receivingNodes?: NodeMaybeRefOrGetter; /** The maximum distance (in pixels, before zoom) away a node can be and still drop onto a receiving node. */ - dropAreaRadius?: NodeComputable; + dropAreaRadius?: NodeMaybeRefOrGetter; /** A callback for when a node gets dropped upon a receiving node. */ onDrop?: (acceptingNode: T, draggingNode: T) => void; } @@ -261,12 +259,12 @@ export interface MakeDraggableOptions { * @param element The vue feature to make draggable. * @param options The options to configure the dragging behavior. */ -export function makeDraggable( - element: T, - options: MakeDraggableOptions -): asserts element is T & { position: Persistent } { +export function makeDraggable( + element: VueFeature, + options: MakeDraggableOptions +): asserts element is VueFeature & { position: Persistent } { const position = persistent(options.initialPosition ?? { x: 0, y: 0 }); - (element as T & { position: Persistent }).position = position; + (element as VueFeature & { position: Persistent }).position = position; const computedPosition = computed(() => { if (options.nodeBeingDragged.value === options.id) { return { @@ -291,36 +289,25 @@ export function makeDraggable( options.onMouseUp?.(e); } - nextTick(() => { - const elementComponent = element[Component]; - const elementGatherProps = element[GatherProps].bind(element); - element[Component] = Draggable as GenericComponent; - element[GatherProps] = function gatherTooltipProps(this: typeof options) { - return { - element: { - [Component]: elementComponent, - [GatherProps]: elementGatherProps - }, - mouseDown: handleMouseDown, - mouseUp: handleMouseUp, - position: computedPosition - }; - }.bind(options); - }); + element.wrappers.push(el => ( + + {el} + + )); } /** An object that configures how to setup a list of actions using {@link setupActions}. */ export interface SetupActionsOptions { /** The node to display actions upon, or undefined when the actions should be hidden. */ - node: Computable; + node: MaybeRefOrGetter; /** Whether or not to currently display the actions. */ - shouldShowActions?: NodeComputable; + shouldShowActions?: NodeMaybeRefOrGetter; /** The list of actions to display. Actions are arbitrary JSX elements. */ - actions: NodeComputable JSX.Element)[]>; + actions: NodeMaybeRefOrGetter Renderable)[]>; /** The distance from the node to place the actions. */ - distance: NodeComputable; + distance: NodeMaybeRefOrGetter; /** The arc length to place between actions, in radians. */ - arcLength?: NodeComputable; + arcLength?: NodeMaybeRefOrGetter; } /** @@ -329,8 +316,8 @@ export interface SetupActionsOptions { * @returns A JSX function to render the actions. */ export function setupActions(options: SetupActionsOptions) { - const node = convertComputable(options.node); - return jsx(() => { + const node = processGetter(options.node) as MaybeRef; + return computed(() => { const currNode = unref(node); if (currNode == null) { return ""; @@ -404,10 +391,10 @@ export function placeInAvailableSpace( direction === Direction.Right ? (a, b) => a.x - b.x : direction === Direction.Left - ? (a, b) => b.x - a.x - : direction === Direction.Up - ? (a, b) => b.y - a.y - : (a, b) => a.y - b.y + ? (a, b) => b.x - a.x + : direction === Direction.Up + ? (a, b) => b.y - a.y + : (a, b) => a.y - b.y ); for (let i = 0; i < nodes.length; i++) { diff --git a/src/game/events.ts b/src/game/events.ts index 9e74714..c89cca3 100644 --- a/src/game/events.ts +++ b/src/game/events.ts @@ -1,7 +1,7 @@ import type { Settings } from "game/settings"; import { createNanoEvents } from "nanoevents"; import type { App } from "vue"; -import type { GenericLayer } from "./layers"; +import type { Layer } from "./layers"; import state from "./state"; /** All types of events able to be sent or emitted from the global event bus. */ @@ -11,12 +11,12 @@ export interface GlobalEvents { * @param layer The layer being added. * @param saveData The layer's save data object within player. */ - addLayer: (layer: GenericLayer, saveData: Record) => void; + addLayer: (layer: Layer, saveData: Record) => void; /** * Sent whenever a layer is removed. * @param layer The layer being removed. */ - removeLayer: (layer: GenericLayer) => void; + removeLayer: (layer: Layer) => void; /** * Sent every game tick. Runs the life cycle of the project. * @param diff The delta time since last tick, in ms. diff --git a/src/game/formulas/formulas.ts b/src/game/formulas/formulas.ts index d9b0a78..03a53a7 100644 --- a/src/game/formulas/formulas.ts +++ b/src/game/formulas/formulas.ts @@ -1,7 +1,7 @@ import { Resource } from "features/resources/resource"; import { NonPersistent } from "game/persistence"; import Decimal, { DecimalSource, format } from "util/bignum"; -import { Computable, ProcessedComputable, convertComputable } from "util/computed"; +import { MaybeRefOrGetter, MaybeRef, processGetter } from "util/computed"; import { Ref, computed, ref, unref } from "vue"; import * as ops from "./operations"; import type { @@ -60,7 +60,7 @@ export abstract class InternalFormula | undefined; + public readonly innermostVariable: MaybeRef | undefined; constructor(options: FormulaOptions) { let readonlyProperties; @@ -93,7 +93,7 @@ export abstract class InternalFormula; + variable: MaybeRef; }): InternalFormulaProperties { return { inputs: [variable] as T, @@ -207,7 +207,7 @@ export abstract class InternalFormula): InvertibleIntegralFormula { + public static constant(value: MaybeRef): InvertibleIntegralFormula { return new Formula({ inputs: [value] }); } @@ -215,7 +215,7 @@ export abstract class InternalFormula): InvertibleIntegralFormula { + public static variable(value: MaybeRef): InvertibleIntegralFormula { return new Formula({ variable: value }); } @@ -248,11 +248,11 @@ export abstract class InternalFormula, + start: MaybeRefOrGetter, formulaModifier: (value: InvertibleIntegralFormula) => GenericFormula ) { const formula = formulaModifier(Formula.variable(0)); - const processedStart = convertComputable(start); + const processedStart = processGetter(start); function evalStep(lhs: DecimalSource) { if (Decimal.lt(lhs, unref(processedStart))) { return lhs; @@ -293,7 +293,7 @@ export abstract class InternalFormula, + condition: MaybeRefOrGetter, formulaModifier: (value: InvertibleIntegralFormula) => GenericFormula, elseFormulaModifier?: (value: InvertibleIntegralFormula) => GenericFormula ) { @@ -301,7 +301,7 @@ export abstract class InternalFormula, + condition: MaybeRefOrGetter, formulaModifier: (value: InvertibleIntegralFormula) => GenericFormula, elseFormulaModifier?: (value: InvertibleIntegralFormula) => GenericFormula ) { @@ -909,20 +909,20 @@ export abstract class InternalFormula, + start: MaybeRefOrGetter, formulaModifier: (value: InvertibleIntegralFormula) => GenericFormula ) { return Formula.step(this, start, formulaModifier); } public if( - condition: Computable, + condition: MaybeRefOrGetter, formulaModifier: (value: InvertibleIntegralFormula) => GenericFormula ) { return Formula.if(this, condition, formulaModifier); } public conditional( - condition: Computable, + condition: MaybeRefOrGetter, formulaModifier: (value: InvertibleIntegralFormula) => GenericFormula ) { return Formula.if(this, condition, formulaModifier); @@ -1443,13 +1443,13 @@ export function findNonInvertible(formula: GenericFormula): GenericFormula | nul export function calculateMaxAffordable( formula: GenericFormula, resource: Resource, - cumulativeCost: Computable = true, - directSum?: Computable, - maxBulkAmount: Computable = Decimal.dInf + cumulativeCost: MaybeRefOrGetter = true, + directSum?: MaybeRefOrGetter, + maxBulkAmount: MaybeRefOrGetter = Decimal.dInf ) { - const computedCumulativeCost = convertComputable(cumulativeCost); - const computedDirectSum = convertComputable(directSum); - const computedmaxBulkAmount = convertComputable(maxBulkAmount); + const computedCumulativeCost = processGetter(cumulativeCost); + const computedDirectSum = processGetter(directSum); + const computedmaxBulkAmount = processGetter(maxBulkAmount); return computed(() => { const maxBulkAmount = unref(computedmaxBulkAmount); if (Decimal.eq(maxBulkAmount, 1)) { diff --git a/src/game/formulas/types.d.ts b/src/game/formulas/types.d.ts index cc185a9..3f98f55 100644 --- a/src/game/formulas/types.d.ts +++ b/src/game/formulas/types.d.ts @@ -1,10 +1,10 @@ import { InternalFormula } from "game/formulas/formulas"; import { DecimalSource } from "util/bignum"; -import { ProcessedComputable } from "util/computed"; +import { MaybeRef } from "util/computed"; // eslint-disable-next-line @typescript-eslint/no-explicit-any type GenericFormula = InternalFormula; -type FormulaSource = ProcessedComputable | GenericFormula; +type FormulaSource = MaybeRef | GenericFormula; type InvertibleFormula = GenericFormula & { invert: NonNullable; }; @@ -38,7 +38,7 @@ type SubstitutionFunction = ( ) => GenericFormula; type VariableFormulaOptions = { - variable: ProcessedComputable; + variable: MaybeRef; description?: string; }; type ConstantFormulaOptions = { @@ -67,7 +67,7 @@ type InternalFormulaProperties = { internalIntegrate?: IntegrateFunction; internalIntegrateInner?: IntegrateFunction; applySubstitution?: SubstitutionFunction; - innermostVariable?: ProcessedComputable; + innermostVariable?: MaybeRef; description?: string; }; diff --git a/src/game/layers.tsx b/src/game/layers.tsx index 7e82ead..a5f3fe2 100644 --- a/src/game/layers.tsx +++ b/src/game/layers.tsx @@ -1,28 +1,26 @@ import Modal from "components/modals/Modal.vue"; -import type { - CoercableComponent, - JSXFunction, - OptionsFunc, - Replace, - StyleValue -} from "features/feature"; -import { jsx, setDefault } from "features/feature"; +import type { OptionsFunc, Replace } from "features/feature"; import { globalBus } from "game/events"; import type { Persistent } from "game/persistence"; import { persistent } from "game/persistence"; import player from "game/player"; import type { Emitter } from "nanoevents"; import { createNanoEvents } from "nanoevents"; -import type { - Computable, - GetComputableType, - GetComputableTypeWithDefault, - ProcessedComputable -} from "util/computed"; -import { processComputable } from "util/computed"; +import { ProcessedRefOrGetter, processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; -import { computed, InjectionKey, Ref } from "vue"; -import { ref, shallowReactive, unref } from "vue"; +import { Renderable } from "util/vue"; +import { + computed, + type CSSProperties, + InjectionKey, + MaybeRef, + MaybeRefOrGetter, + Ref, + ref, + shallowReactive, + unref +} from "vue"; +import { JSX } from "vue/jsx-runtime"; /** A feature's node in the DOM that has its size tracked. */ export interface FeatureNode { @@ -74,12 +72,12 @@ export interface LayerEvents { * A reference to all the current layers. * It is shallow reactive so it will update when layers are added or removed, but not interfere with the existing refs within each layer. */ -export const layers: Record | undefined> = shallowReactive({}); +export const layers: Record> = shallowReactive({}); declare global { /** Augment the window object so the layers can be accessed from the console. */ interface Window { - layers: Record | undefined>; + layers: Record | undefined>; } } window.layers = layers; @@ -106,42 +104,42 @@ export interface Position { */ export interface LayerOptions { /** The color of the layer, used to theme the entire layer's display. */ - color?: Computable; + color?: MaybeRefOrGetter; /** * The layout of this layer's features. * When the layer is open in {@link game/player.PlayerData.tabs}, this is the content that is displayed. */ - display: Computable; + display: MaybeRefOrGetter; /** An object of classes that should be applied to the display. */ - classes?: Computable>; + classes?: MaybeRefOrGetter>; /** Styles that should be applied to the display. */ - style?: Computable; + style?: MaybeRefOrGetter; /** * The name of the layer, used on minimized tabs. * Defaults to {@link BaseLayer.id}. */ - name?: Computable; + name?: MaybeRefOrGetter; /** * Whether or not the layer can be minimized. * Defaults to true. */ - minimizable?: Computable; + minimizable?: MaybeRefOrGetter; /** * The layout of this layer's features. * When the layer is open in {@link game/player.PlayerData.tabs}, but the tab is {@link Layer.minimized} this is the content that is displayed. */ - minimizedDisplay?: Computable; + minimizedDisplay?: MaybeRefOrGetter; /** * Whether or not to force the go back button to be hidden. * If true, go back will be hidden regardless of {@link data/projInfo.allowGoBack}. */ - forceHideGoBack?: Computable; + forceHideGoBack?: MaybeRefOrGetter; /** * A CSS min-width value that is applied to the layer. * Can be a number, in which case the unit is assumed to be px. * Defaults to 600px. */ - minWidth?: Computable; + minWidth?: MaybeRefOrGetter; } /** The properties that are added onto a processed {@link LayerOptions} to create a {@link Layer} */ @@ -165,28 +163,18 @@ export interface BaseLayer { } /** An unit of game content. Displayed to the user as a tab or modal. */ -export type Layer = Replace< - T & BaseLayer, +export type Layer = Replace< + Replace, { - color: GetComputableType; - display: GetComputableType; - classes: GetComputableType; - style: GetComputableType; - name: GetComputableTypeWithDefault; - minWidth: GetComputableTypeWithDefault; - minimizable: GetComputableTypeWithDefault; - minimizedDisplay: GetComputableType; - forceHideGoBack: GetComputableType; - } ->; - -/** A type that matches any valid {@link Layer} object. */ -export type GenericLayer = Replace< - Layer, - { - name: ProcessedComputable; - minWidth: ProcessedComputable; - minimizable: ProcessedComputable; + color?: ProcessedRefOrGetter; + display: ProcessedRefOrGetter; + classes?: ProcessedRefOrGetter; + style?: ProcessedRefOrGetter; + name: MaybeRef; + minWidth: MaybeRef; + minimizable: MaybeRef; + minimizedDisplay?: ProcessedRefOrGetter; + forceHideGoBack?: ProcessedRefOrGetter; } >; @@ -206,72 +194,85 @@ export const addingLayers: string[] = []; export function createLayer( id: string, optionsFunc: OptionsFunc -): Layer { +) { return createLazyProxy(() => { - const layer = {} as T & Partial; - const emitter = (layer.emitter = createNanoEvents()); - layer.on = emitter.on.bind(emitter); - layer.emit = emitter.emit.bind(emitter) as ( - ...args: [K, ...Parameters] - ) => void; - layer.nodes = ref({}); - layer.id = id; - + const emitter = createNanoEvents(); addingLayers.push(id); persistentRefs[id] = new Set(); - layer.minimized = persistent(false, false); - Object.assign(layer, optionsFunc.call(layer, layer as BaseLayer)); + + const baseLayer = { + id, + emitter, + ...emitter, + nodes: ref({}), + minimized: persistent(false, false) + } satisfies BaseLayer; + + const options = optionsFunc.call(baseLayer, baseLayer); + const { + color, + display, + classes, + style: _style, + name, + forceHideGoBack, + minWidth, + minimizable, + minimizedDisplay, + ...props + } = options; if ( addingLayers[addingLayers.length - 1] == null || addingLayers[addingLayers.length - 1] !== id ) { throw new Error( - `Adding layers stack in invalid state. This should not happen\nStack: ${addingLayers}\nTrying to pop ${layer.id}` + `Adding layers stack in invalid state. This should not happen\nStack: ${addingLayers}\nTrying to pop ${id}` ); } addingLayers.pop(); - processComputable(layer as T, "color"); - processComputable(layer as T, "display"); - processComputable(layer as T, "classes"); - processComputable(layer as T, "style"); - processComputable(layer as T, "name"); - setDefault(layer, "name", layer.id); - processComputable(layer as T, "minWidth"); - setDefault(layer, "minWidth", 600); - processComputable(layer as T, "minimizable"); - setDefault(layer, "minimizable", true); - processComputable(layer as T, "minimizedDisplay"); + const style = processGetter(_style); - const style = layer.style as ProcessedComputable | undefined; - layer.style = computed(() => { - let width = unref(layer.minWidth as ProcessedComputable); - if (typeof width === "number" || !Number.isNaN(parseInt(width))) { - width = width + "px"; - } - return [ - unref(style) ?? "", - layer.minimized?.value - ? { - flexGrow: "0", - flexShrink: "0", - width: "60px", - minWidth: "", - flexBasis: "", - margin: "0" - } - : { - flexGrow: "", - flexShrink: "", - width: "", - minWidth: width, - flexBasis: width, - margin: "" - } - ]; - }) as Ref; + const layer = { + ...baseLayer, + ...(props as Omit), + color: processGetter(color), + display: processGetter(display), + classes: processGetter(classes), + style: computed((): CSSProperties => { + let width = unref(layer.minWidth); + if (typeof width === "number" || !Number.isNaN(parseInt(width))) { + width = width + "px"; + } + return { + ...unref(style), + ...(baseLayer.minimized.value + ? { + flexGrow: "0", + flexShrink: "0", + width: "60px", + minWidth: "", + flexBasis: "", + margin: "0" + } + : { + flexGrow: "", + flexShrink: "", + width: "", + minWidth: width, + flexBasis: width, + margin: "" + }) + }; + }), + name: processGetter(name) ?? id, + forceHideGoBack: processGetter(forceHideGoBack), + minWidth: processGetter(minWidth) ?? 600, + minimizable: processGetter(minimizable) ?? true, + minimizedDisplay: processGetter(minimizedDisplay) + } satisfies Layer; - return layer as unknown as Layer; + return layer; }); } @@ -284,11 +285,11 @@ export function createLayer( * @param player The player data object, which will have a data object for this layer. */ export function addLayer( - layer: GenericLayer, + layer: Layer, player: { layers?: Record> } ): void { console.info("Adding layer", layer.id); - if (layers[layer.id]) { + if (layers[layer.id] != null) { console.error( "Attempted to add layer with same ID as existing layer", layer.id, @@ -297,7 +298,7 @@ export function addLayer( return; } - setDefault(player, "layers", {}); + player.layers ??= {}; if (player.layers[layer.id] == null) { player.layers[layer.id] = {}; } @@ -310,7 +311,7 @@ export function addLayer( * Convenience method for getting a layer by its ID with correct typing. * @param layerID The ID of the layer to get. */ -export function getLayer(layerID: string): T { +export function getLayer(layerID: string): T { return layers[layerID] as T; } @@ -319,11 +320,11 @@ export function getLayer(layerID: string): T { * Note that accessing a layer/its properties does NOT require it to be enabled. * @param layer The layer to remove. */ -export function removeLayer(layer: GenericLayer): void { +export function removeLayer(layer: Layer): void { console.info("Removing layer", layer.id); globalBus.emit("removeLayer", layer); - layers[layer.id] = undefined; + delete layers[layer.id]; } /** @@ -331,7 +332,7 @@ export function removeLayer(layer: GenericLayer): void { * This is useful for layers with dynamic content, to ensure persistent refs are correctly configured. * @param layer Layer to remove and then re-add */ -export function reloadLayer(layer: GenericLayer): void { +export function reloadLayer(layer: Layer): void { removeLayer(layer); // Re-create layer @@ -343,14 +344,14 @@ export function reloadLayer(layer: GenericLayer): void { * Returns the modal itself, which can be rendered anywhere you need, as well as a function to open the modal. * @param layer The layer to display in the modal. */ -export function setupLayerModal(layer: GenericLayer): { +export function setupLayerModal(layer: Layer): { openModal: VoidFunction; - modal: JSXFunction; + modal: Ref; } { const showModal = ref(false); return { openModal: () => (showModal.value = true), - modal: jsx(() => ( + modal: computed(() => ( (showModal.value = value)} diff --git a/src/game/modifiers.tsx b/src/game/modifiers.tsx index 55efccb..995b300 100644 --- a/src/game/modifiers.tsx +++ b/src/game/modifiers.tsx @@ -1,15 +1,13 @@ import "components/common/modifiers.css"; -import type { CoercableComponent, OptionsFunc } from "features/feature"; -import { jsx } from "features/feature"; +import type { OptionsFunc } from "features/feature"; import settings from "game/settings"; import type { DecimalSource } from "util/bignum"; import Decimal, { formatSmall } from "util/bignum"; import type { RequiredKeys, WithRequired } from "util/common"; -import type { Computable, ProcessedComputable } from "util/computed"; -import { convertComputable } from "util/computed"; +import { processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; -import { renderJSX } from "util/vue"; -import { computed, unref } from "vue"; +import { render, Renderable } from "util/vue"; +import { computed, MaybeRef, MaybeRefOrGetter, unref } from "vue"; import Formula from "./formulas/formulas"; import { FormulaSource, GenericFormula } from "./formulas/types"; @@ -30,12 +28,12 @@ export interface Modifier { * Whether or not this modifier should be considered enabled. * Typically for use with modifiers passed into {@link createSequentialModifier}. */ - enabled?: ProcessedComputable; + enabled?: MaybeRef; /** * A description of this modifier. * @see {@link createModifierSection}. */ - description?: ProcessedComputable; + description?: MaybeRef; } /** Utility type that represents the output of all modifiers that represent a single operation. */ @@ -47,11 +45,11 @@ export type OperationModifier = WithRequired< /** An object that configures an additive modifier via {@link createAdditiveModifier}. */ export interface AdditiveModifierOptions { /** The amount to add to the input value. */ - addend: Computable; + addend: MaybeRefOrGetter; /** Description of what this modifier is doing. */ - description?: Computable; - /** A computable that will be processed and passed directly into the returned modifier. */ - enabled?: Computable; + description?: MaybeRefOrGetter; + /** A MaybeRefOrGetter that will be processed and passed directly into the returned modifier. */ + enabled?: MaybeRefOrGetter; /** Determines if numbers larger or smaller than 0 should be displayed as red. */ smallerIsBetter?: boolean; } @@ -69,25 +67,22 @@ export function createAdditiveModifier Decimal.add(gain, unref(processedAddend)), invert: (gain: DecimalSource) => Decimal.sub(gain, unref(processedAddend)), getFormula: (gain: FormulaSource) => Formula.add(gain, processedAddend), enabled: processedEnabled, description: - description == null + processedDescription == null ? undefined - : jsx(() => ( + : computed(() => (
- {unref(processedDescription) != null ? ( - - {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} - {renderJSX(unref(processedDescription)!)} - - ) : null} + + {render(processedDescription)} + ; + multiplier: MaybeRefOrGetter; /** Description of what this modifier is doing. */ - description?: Computable | undefined; - /** A computable that will be processed and passed directly into the returned modifier. */ - enabled?: Computable | undefined; + description?: MaybeRefOrGetter | undefined; + /** A MaybeRefOrGetter that will be processed and passed directly into the returned modifier. */ + enabled?: MaybeRefOrGetter | undefined; /** Determines if numbers larger or smaller than 1 should be displayed as red. */ smallerIsBetter?: boolean; } @@ -135,25 +130,22 @@ export function createMultiplicativeModifier< feature ); - const processedMultiplier = convertComputable(multiplier); - const processedDescription = convertComputable(description); - const processedEnabled = enabled == null ? undefined : convertComputable(enabled); + const processedMultiplier = processGetter(multiplier); + const processedDescription = processGetter(description); + const processedEnabled = enabled == null ? undefined : processGetter(enabled); return { apply: (gain: DecimalSource) => Decimal.times(gain, unref(processedMultiplier)), invert: (gain: DecimalSource) => Decimal.div(gain, unref(processedMultiplier)), getFormula: (gain: FormulaSource) => Formula.times(gain, processedMultiplier), enabled: processedEnabled, description: - description == null + processedDescription == null ? undefined - : jsx(() => ( + : computed(() => (
- {unref(processedDescription) != null ? ( - - {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} - {renderJSX(unref(processedDescription)!)} - - ) : null} + + {render(processedDescription)} + ; + exponent: MaybeRefOrGetter; /** Description of what this modifier is doing. */ - description?: Computable | undefined; - /** A computable that will be processed and passed directly into the returned modifier. */ - enabled?: Computable | undefined; + description?: MaybeRefOrGetter | undefined; + /** A MaybeRefOrGetter that will be processed and passed directly into the returned modifier. */ + enabled?: MaybeRefOrGetter | undefined; /** Add 1 before calculating, then remove it afterwards. This prevents low numbers from becoming lower. */ supportLowNumbers?: boolean; /** Determines if numbers larger or smaller than 1 should be displayed as red. */ @@ -200,9 +192,9 @@ export function createExponentialModifier< const { exponent, description, enabled, supportLowNumbers, smallerIsBetter } = optionsFunc.call(feature, feature); - const processedExponent = convertComputable(exponent); - const processedDescription = convertComputable(description); - const processedEnabled = enabled == null ? undefined : convertComputable(enabled); + const processedExponent = processGetter(exponent); + const processedDescription = processGetter(description); + const processedEnabled = enabled == null ? undefined : processGetter(enabled); return { apply: (gain: DecimalSource) => { let result = gain; @@ -232,17 +224,14 @@ export function createExponentialModifier< : Formula.pow(gain, processedExponent), enabled: processedEnabled, description: - description == null + processedDescription == null ? undefined - : jsx(() => ( + : computed(() => (
- {unref(processedDescription) != null ? ( - - {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} - {renderJSX(unref(processedDescription)!)} - {supportLowNumbers ? " (+1 effective)" : null} - - ) : null} + + {render(processedDescription)} + {supportLowNumbers ? " (+1 effective)" : null} + modifiers.filter(m => unref(m.enabled) !== false).length > 0) : undefined, description: modifiers.some(m => m.description != null) - ? jsx(() => ( - <> - {( - modifiers - .filter(m => unref(m.enabled) !== false) - .map(m => unref(m.description)) - .filter(d => d) as CoercableComponent[] - ).map(renderJSX)} - - )) + ? computed(() => + ( + modifiers + .filter(m => unref(m.enabled) !== false) + .map(m => unref(m.description)) + .filter(d => d) as MaybeRef[] + ).map(m => render(m)) + ) : undefined }; }) as S; @@ -332,7 +319,7 @@ export interface ModifierSectionOptions { /** The unit of the value being modified, if any. */ unit?: string; /** The label to use for the base value. Defaults to "Base". */ - baseText?: CoercableComponent; + baseText?: MaybeRefOrGetter; /** Determines if numbers larger or smaller than the base should be displayed as red. */ smallerIsBetter?: boolean; } @@ -352,6 +339,7 @@ export function createModifierSection({ smallerIsBetter }: ModifierSectionOptions) { const total = modifier.apply(base ?? 1); + const processedBaseText = processGetter(baseText); return (

@@ -360,13 +348,13 @@ export function createModifierSection({


- {renderJSX(baseText ?? "Base")} + {render(processedBaseText ?? "Base")} {formatSmall(base ?? 1)} {unit}
- {renderJSX(unref(modifier.description))} + {render(modifier.description)}
Total diff --git a/src/game/notifications.ts b/src/game/notifications.ts index 5a43c1a..46039b8 100644 --- a/src/game/notifications.ts +++ b/src/game/notifications.ts @@ -1,5 +1,5 @@ import { globalBus } from "game/events"; -import { convertComputable } from "util/computed"; +import { processGetter } from "util/computed"; import { trackHover, VueFeature } from "util/vue"; import { nextTick, Ref } from "vue"; import { ref, watch } from "vue"; @@ -37,7 +37,7 @@ export function createDismissableNotify( element: VueFeature, shouldNotify: Ref | (() => boolean) ): Ref { - const processedShouldNotify = convertComputable(shouldNotify) as Ref; + const processedShouldNotify = processGetter(shouldNotify) as Ref; const notifying = ref(false); nextTick(() => { notifying.value = processedShouldNotify.value; diff --git a/src/game/persistence.ts b/src/game/persistence.ts index 253481e..20c0534 100644 --- a/src/game/persistence.ts +++ b/src/game/persistence.ts @@ -1,14 +1,14 @@ import { globalBus } from "game/events"; -import type { GenericLayer } from "game/layers"; +import type { Layer } from "game/layers"; import { addingLayers, persistentRefs } from "game/layers"; import type { DecimalSource } from "util/bignum"; import Decimal from "util/bignum"; import { ProxyState } from "util/proxies"; import type { Ref, WritableComputedRef } from "vue"; import { computed, isReactive, isRef, ref } from "vue"; +import Formula from "./formulas/formulas"; import player from "./player"; import state from "./state"; -import Formula from "./formulas/formulas"; /** * A symbol used in {@link Persistent} objects. @@ -251,7 +251,7 @@ export function deletePersistent(persistent: Persistent) { persistent[Deleted] = true; } -globalBus.on("addLayer", (layer: GenericLayer, saveData: Record) => { +globalBus.on("addLayer", (layer: Layer, saveData: Record) => { const features: { type: typeof Symbol }[] = []; const handleObject = (obj: Record, path: string[] = []): boolean => { let foundPersistent = false; diff --git a/src/game/requirements.tsx b/src/game/requirements.tsx index 195efa8..a3b260f 100644 --- a/src/game/requirements.tsx +++ b/src/game/requirements.tsx @@ -1,26 +1,12 @@ -import { - CoercableComponent, - isVisible, - jsx, - OptionsFunc, - Replace, - setDefault, - Visibility -} from "features/feature"; +import { isVisible, OptionsFunc, Replace, Visibility } from "features/feature"; import { displayResource, Resource } from "features/resources/resource"; import Decimal, { DecimalSource } from "lib/break_eternity"; -import { - Computable, - convertComputable, - processComputable, - ProcessedComputable -} from "util/computed"; +import { processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; -import { joinJSX, renderJSX } from "util/vue"; -import { computed, unref } from "vue"; -import { JSX } from "vue/jsx-runtime"; +import { joinJSX, render, Renderable } from "util/vue"; +import { computed, MaybeRef, MaybeRefOrGetter, unref } from "vue"; import Formula, { calculateCost, calculateMaxAffordable } from "./formulas/formulas"; -import type { GenericFormula } from "./formulas/types"; +import type { GenericFormula, InvertibleIntegralFormula } from "./formulas/types"; import { DefaultValue, Persistent } from "./persistence"; /** @@ -31,27 +17,27 @@ export interface Requirement { /** * The display for this specific requirement. This is used for displays multiple requirements condensed. Required if {@link visibility} can be {@link Visibility.Visible}. */ - partialDisplay?: (amount?: DecimalSource) => JSX.Element; + partialDisplay?: (amount?: DecimalSource) => Renderable; /** * The display for this specific requirement. Required if {@link visibility} can be {@link Visibility.Visible}. */ - display?: (amount?: DecimalSource) => JSX.Element; + display?: (amount?: DecimalSource) => Renderable; /** * Whether or not this requirement should be displayed in Vue Features. {@link displayRequirements} will respect this property. */ - visibility: ProcessedComputable; + visibility: MaybeRef; /** * Whether or not this requirement has been met. */ - requirementMet: ProcessedComputable; + requirementMet: MaybeRef; /** * Whether or not this requirement will need to affect the game state when whatever is using this requirement gets triggered. */ - requiresPay: ProcessedComputable; + requiresPay: MaybeRef; /** * Whether or not this requirement can have multiple levels of requirements that can be met at once. Requirement is assumed to not have multiple levels if this property not present. */ - canMaximize?: ProcessedComputable; + canMaximize?: MaybeRef; /** * Perform any effects to the game state that should happen when the requirement gets triggered. * @param amount The amount of levels of requirements to pay for. @@ -73,28 +59,28 @@ export interface CostRequirementOptions { /** * The amount of {@link resource} that must be met for this requirement. You can pass a formula, in which case maximizing will work out of the box (assuming its invertible and, for more accurate calculations, its integral is invertible). If you don't pass a formula then you can still support maximizing by passing a custom {@link pay} function. */ - cost: Computable | GenericFormula; + cost: MaybeRefOrGetter | GenericFormula; /** * Pass-through to {@link Requirement.visibility}. */ - visibility?: Computable; + visibility?: MaybeRefOrGetter; /** * Pass-through to {@link Requirement.requiresPay}. If not set to false, the default {@link pay} function will remove {@link cost} from {@link resource}. */ - requiresPay?: Computable; + requiresPay?: MaybeRefOrGetter; /** * When calculating multiple levels to be handled at once, whether it should consider resources used for each level as spent. Setting this to false causes calculations to be faster with larger numbers and supports more math functions. * @see {Formula} */ - cumulativeCost?: Computable; + cumulativeCost?: MaybeRefOrGetter; /** * Upper limit on levels that can be performed at once. Defaults to 1. */ - maxBulkAmount?: Computable; + maxBulkAmount?: MaybeRefOrGetter; /** * When calculating requirement for multiple levels, how many should be directly summed for increase accuracy. High numbers can cause lag. Defaults to 10 if cumulative cost, 0 otherwise. */ - directSum?: Computable; + directSum?: MaybeRefOrGetter; /** * Pass-through to {@link Requirement.pay}. May be required for maximizing support. * @see {@link cost} for restrictions on maximizing support. @@ -105,11 +91,11 @@ export interface CostRequirementOptions { export type CostRequirement = Replace< Requirement & CostRequirementOptions, { - cost: ProcessedComputable | GenericFormula; - visibility: ProcessedComputable; - requiresPay: ProcessedComputable; - cumulativeCost: ProcessedComputable; - canMaximize: ProcessedComputable; + cost: MaybeRef | GenericFormula; + visibility: MaybeRef; + requiresPay: MaybeRef; + cumulativeCost: MaybeRef; + canMaximize: MaybeRef; } >; @@ -119,116 +105,123 @@ export type CostRequirement = Replace< */ export function createCostRequirement( optionsFunc: OptionsFunc -): CostRequirement { +) { return createLazyProxy(feature => { - const req = optionsFunc.call(feature, feature) as T & Partial; + const options = optionsFunc.call(feature, feature); + const { + visibility, + cost, + resource, + requiresPay, + cumulativeCost, + maxBulkAmount, + directSum, + pay + } = options; - req.partialDisplay = amount => ( - ) - ? "" - : "color: var(--danger)" + const requirement = { + resource, + visibility: processGetter(visibility) ?? Visibility.Visible, + cost: processGetter(cost), + requiresPay: processGetter(requiresPay) ?? true, + cumulativeCost: processGetter(cumulativeCost) ?? true, + maxBulkAmount: processGetter(maxBulkAmount) ?? 1, + directSum: processGetter(directSum), + partialDisplay: (amount?: DecimalSource) => ( + + {displayResource( + resource, + requirement.cost instanceof Formula + ? calculateCost( + requirement.cost as InvertibleIntegralFormula, + amount ?? 1, + unref(requirement.cumulativeCost), + unref(requirement.directSum) + ) + : unref(requirement.cost as MaybeRef) + )}{" "} + {resource.displayName} + + ), + display: (amount?: DecimalSource) => ( +
+ {unref(requirement.requiresPay as MaybeRef) ? "Costs: " : "Requires: "} + {displayResource( + resource, + requirement.cost instanceof Formula + ? calculateCost( + requirement.cost as InvertibleIntegralFormula, + amount ?? 1, + unref(requirement.cumulativeCost), + unref(requirement.directSum) + ) + : unref(requirement.cost as MaybeRef) + )}{" "} + {resource.displayName} +
+ ), + canMaximize: computed(() => { + if (!(requirement.cost instanceof Formula)) { + return false; + } + const maxBulkAmount = unref(requirement.maxBulkAmount); + if (Decimal.lte(maxBulkAmount, 1)) { + return false; + } + const cumulativeCost = unref(requirement.cumulativeCost); + const directSum = unref(requirement.directSum) ?? (cumulativeCost ? 10 : 0); + if (Decimal.lte(maxBulkAmount, directSum)) { + return true; + } + if (!requirement.cost.isInvertible()) { + return false; + } + if (cumulativeCost === true && !requirement.cost.isIntegrable()) { + return false; } - > - {displayResource( - req.resource, - req.cost instanceof Formula - ? calculateCost( - req.cost, - amount ?? 1, - unref(req.cumulativeCost) as boolean, - unref(req.directSum) as number - ) - : unref(req.cost as ProcessedComputable) - )}{" "} - {req.resource.displayName} -
- ); - req.display = amount => ( -
- {unref(req.requiresPay as ProcessedComputable) ? "Costs: " : "Requires: "} - {displayResource( - req.resource, - req.cost instanceof Formula - ? calculateCost( - req.cost, - amount ?? 1, - unref(req.cumulativeCost) as boolean, - unref(req.directSum) as number - ) - : unref(req.cost as ProcessedComputable) - )}{" "} - {req.resource.displayName} -
- ); - - processComputable(req as T, "visibility"); - setDefault(req, "visibility", Visibility.Visible); - processComputable(req as T, "cost"); - processComputable(req as T, "requiresPay"); - setDefault(req, "requiresPay", true); - processComputable(req as T, "cumulativeCost"); - setDefault(req, "cumulativeCost", true); - processComputable(req as T, "maxBulkAmount"); - setDefault(req, "maxBulkAmount", 1); - processComputable(req as T, "directSum"); - setDefault(req, "pay", function (amount?: DecimalSource) { - const cost = - req.cost instanceof Formula - ? calculateCost( - req.cost, - amount ?? 1, - unref(req.cumulativeCost as ProcessedComputable), - unref(req.directSum) as number - ) - : unref(req.cost as ProcessedComputable); - req.resource.value = Decimal.sub(req.resource.value, cost).max(0); - }); - - req.canMaximize = computed(() => { - if (!(req.cost instanceof Formula)) { - return false; - } - const maxBulkAmount = unref(req.maxBulkAmount as ProcessedComputable); - if (Decimal.lte(maxBulkAmount, 1)) { - return false; - } - const cumulativeCost = unref(req.cumulativeCost as ProcessedComputable); - const directSum = - unref(req.directSum as ProcessedComputable) ?? (cumulativeCost ? 10 : 0); - if (Decimal.lte(maxBulkAmount, directSum)) { return true; - } - if (!req.cost.isInvertible()) { - return false; - } - if (cumulativeCost === true && !req.cost.isIntegrable()) { - return false; - } - return true; - }); + }), + requirementMet: + cost instanceof Formula + ? calculateMaxAffordable( + cost, + resource, + cumulativeCost ?? true, + directSum, + maxBulkAmount + ) + : computed( + (): DecimalSource => + Decimal.gte( + resource.value, + unref(requirement.cost as MaybeRef) + ) + ? 1 + : 0 + ), + pay: + pay ?? + function (amount?: DecimalSource) { + const cost = + requirement.cost instanceof Formula + ? calculateCost( + requirement.cost, + amount ?? 1, + unref(requirement.cumulativeCost), + unref(requirement.directSum) + ) + : unref(requirement.cost as MaybeRef); + resource.value = Decimal.sub(resource.value, cost).max(0); + } + } satisfies CostRequirement; - if (req.cost instanceof Formula) { - req.requirementMet = calculateMaxAffordable( - req.cost, - req.resource, - req.cumulativeCost ?? true, - req.directSum, - req.maxBulkAmount - ); - } else { - req.requirementMet = computed(() => - Decimal.gte( - req.resource.value, - unref(req.cost as ProcessedComputable) - ) - ? 1 - : 0 - ); - } - - return req as CostRequirement; + return requirement; }); } @@ -236,11 +229,11 @@ export function createCostRequirement( * Utility function for creating a requirement that a specified vue feature is visible * @param feature The feature to check the visibility of */ -export function createVisibilityRequirement(feature: { - visibility: ProcessedComputable; -}): Requirement { +export function createVisibilityRequirement( + visibility: MaybeRef +): Requirement { return createLazyProxy(() => ({ - requirementMet: computed(() => isVisible(feature.visibility)), + requirementMet: computed(() => isVisible(visibility)), visibility: Visibility.None, requiresPay: false })); @@ -252,16 +245,20 @@ export function createVisibilityRequirement(feature: { * @param display How to display this requirement to the user */ export function createBooleanRequirement( - requirement: Computable, - display?: CoercableComponent + requirement: MaybeRefOrGetter, + display?: MaybeRefOrGetter ): Requirement { - return createLazyProxy(() => ({ - requirementMet: convertComputable(requirement), - partialDisplay: display == null ? undefined : jsx(() => renderJSX(display)), - display: display == null ? undefined : jsx(() => <>Req: {renderJSX(display)}), - visibility: display == null ? Visibility.None : Visibility.Visible, - requiresPay: false - })); + return createLazyProxy(() => { + const processedDisplay = processGetter(display); + return { + requirementMet: processGetter(requirement), + partialDisplay: processedDisplay == null ? undefined : () => render(processedDisplay), + display: + processedDisplay == null ? undefined : () => <>Req: {render(processedDisplay)}, + visibility: processedDisplay == null ? Visibility.None : Visibility.Visible, + requiresPay: false + }; + }); } /** @@ -300,7 +297,7 @@ export function maxRequirementsMet(requirements: Requirements): DecimalSource { */ export function displayRequirements(requirements: Requirements, amount: DecimalSource = 1) { if (Array.isArray(requirements)) { - requirements = requirements.filter(r => isVisible(r.visibility)); + requirements = requirements.filter(r => isVisible(r.visibility ?? true)); if (requirements.length === 1) { requirements = requirements[0]; } @@ -356,9 +353,9 @@ export function payByDivision(this: CostRequirement, amount?: DecimalSource) { ? calculateCost( this.cost, amount ?? 1, - unref(this.cumulativeCost as ProcessedComputable | undefined) ?? true + unref(this.cumulativeCost as MaybeRef | undefined) ?? true ) - : unref(this.cost as ProcessedComputable); + : unref(this.cost as MaybeRef); this.resource.value = Decimal.div(this.resource.value, cost); } diff --git a/src/game/settings.ts b/src/game/settings.ts index d24b810..9360001 100644 --- a/src/game/settings.ts +++ b/src/game/settings.ts @@ -1,10 +1,11 @@ import projInfo from "data/projInfo.json"; import { Themes } from "data/themes"; -import type { CoercableComponent } from "features/feature"; import { globalBus } from "game/events"; import LZString from "lz-string"; +import { processGetter } from "util/computed"; import { decodeSave, hardReset } from "util/save"; -import { reactive, watch } from "vue"; +import { Renderable } from "util/vue"; +import { MaybeRef, MaybeRefOrGetter, reactive, watch } from "vue"; /** The player's settings object. */ export interface Settings { @@ -100,22 +101,22 @@ export function loadSettings(): void { } /** A list of fields to append to the settings modal. */ -export const settingFields: CoercableComponent[] = reactive([]); +export const settingFields: MaybeRef[] = reactive([]); /** Register a field to be displayed in the settings modal. */ -export function registerSettingField(component: CoercableComponent) { - settingFields.push(component); +export function registerSettingField(component: MaybeRefOrGetter) { + settingFields.push(processGetter(component)); } /** A list of components to show in the info modal. */ -export const infoComponents: CoercableComponent[] = reactive([]); +export const infoComponents: MaybeRef[] = reactive([]); /** Register a component to be displayed in the info modal. */ -export function registerInfoComponent(component: CoercableComponent) { - infoComponents.push(component); +export function registerInfoComponent(component: MaybeRefOrGetter) { + infoComponents.push(processGetter(component)); } /** A list of components to add to the root of the page. */ -export const gameComponents: CoercableComponent[] = reactive([]); +export const gameComponents: MaybeRef[] = reactive([]); /** Register a component to be displayed at the root of the page. */ -export function registerGameComponent(component: CoercableComponent) { - gameComponents.push(component); +export function registerGameComponent(component: MaybeRefOrGetter) { + gameComponents.push(processGetter(component)); } diff --git a/src/mixins/bonusDecorator.ts b/src/mixins/bonusDecorator.ts new file mode 100644 index 0000000..b08954e --- /dev/null +++ b/src/mixins/bonusDecorator.ts @@ -0,0 +1,29 @@ +import Decimal, { DecimalSource } from "util/bignum"; +import { processGetter } from "util/computed"; +import { MaybeRefOrGetter, Ref, computed, unref } from "vue"; + +/** Allows the addition of "bonus levels" to a feature, with an accompanying "total amount". */ +export function bonusAmountMixin( + feature: { amount: Ref }, + bonusAmount: MaybeRefOrGetter +) { + const processedBonusAmount = processGetter(bonusAmount); + return { + bonusAmount, + totalAmount: computed(() => Decimal.add(unref(feature.amount), unref(processedBonusAmount))) + }; +} + +/** Allows the addition of "bonus completions" to a feature, with an accompanying "total completions". */ +export function bonusCompletionsMixin( + feature: { completions: Ref }, + bonusCompletions: MaybeRefOrGetter +) { + const processedBonusCompletions = processGetter(bonusCompletions); + return { + bonusCompletions, + totalCompletions: computed(() => + Decimal.add(unref(feature.completions), unref(processedBonusCompletions)) + ) + }; +} diff --git a/src/util/computed.ts b/src/util/computed.ts index ff11103..869b38f 100644 --- a/src/util/computed.ts +++ b/src/util/computed.ts @@ -1,57 +1,16 @@ -import type { JSXFunction } from "features/feature"; import { isFunction } from "util/common"; -import type { Ref } from "vue"; +import type { ComputedRef, MaybeRef, Ref, UnwrapRef } from "vue"; import { computed } from "vue"; -export const DoNotCache = Symbol("DoNotCache"); +export type ProcessedRefOrGetter = T extends () => infer S + ? Ref + : T extends undefined + ? undefined + : MaybeRef>>; -export type Computable = T | Ref | (() => T); -export type ProcessedComputable = T | Ref; -export type GetComputableType = T extends { [DoNotCache]: true } - ? T - : T extends () => infer S - ? Ref - : undefined extends T - ? undefined - : T; -export type GetComputableTypeWithDefault = undefined extends T - ? S - : GetComputableType>; -export type UnwrapComputableType = T extends Ref ? S : T extends () => infer S ? S : T; - -export type ComputableKeysOf = Pick< - T, - { - [K in keyof T]: T[K] extends Computable ? K : never; - }[keyof T] ->; - -// TODO fix the typing of this function, such that casting isn't necessary and can be used to -// detect if a createX function is validly written -export function processComputable>( - obj: T, - key: S -): asserts obj is T & { [K in S]: ProcessedComputable> } { - const computable = obj[key]; - if ( - isFunction(computable) && - computable.length === 0 && - !(computable as unknown as JSXFunction)[DoNotCache] - ) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - obj[key] = computed(computable.bind(obj)); - } else if (isFunction(computable)) { - obj[key] = computable.bind(obj) as unknown as T[S]; - (obj[key] as unknown as JSXFunction)[DoNotCache] = true; +export function processGetter(obj: T): T extends () => infer S ? ComputedRef : T { + if (isFunction(obj)) { + return computed(obj) as ReturnType>; } -} - -export function convertComputable(obj: Computable): ProcessedComputable { - if (isFunction(obj) && !(obj as unknown as JSXFunction)[DoNotCache]) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - obj = computed(obj); - } - return obj as ProcessedComputable; + return obj as ReturnType>; } diff --git a/src/util/galaxy.ts b/src/util/galaxy.ts index b2a4d04..b59ead5 100644 --- a/src/util/galaxy.ts +++ b/src/util/galaxy.ts @@ -82,7 +82,7 @@ function syncSaves( const saves = ( Object.keys(list) .map(slot => { - const { label, content } = list[slot as unknown as number]; + const { label, content } = list[parseInt(slot)]; try { return { slot: parseInt(slot), diff --git a/src/util/proxies.ts b/src/util/proxies.ts index 7476ba1..8447931 100644 --- a/src/util/proxies.ts +++ b/src/util/proxies.ts @@ -33,9 +33,9 @@ export type Proxied = // Takes a function that returns an object and pretends to be that object // Note that the object is lazily calculated export function createLazyProxy( - objectFunc: (this: S, baseObject: S) => T & S, + objectFunc: (this: S, baseObject: S) => T, baseObject: S = {} as S -): T { +): T & S { const obj: S & Partial = baseObject; let calculated = false; let calculating = false; diff --git a/src/util/save.ts b/src/util/save.ts index cb5c9b9..806c3fe 100644 --- a/src/util/save.ts +++ b/src/util/save.ts @@ -106,7 +106,7 @@ export async function loadSave(playerObj: Partial): Promise { for (const layer in layers) { const l = layers[layer]; - if (l) { + if (l != null) { removeLayer(l); } } diff --git a/src/util/vue.tsx b/src/util/vue.tsx index 1e2afb0..b59605d 100644 --- a/src/util/vue.tsx +++ b/src/util/vue.tsx @@ -4,122 +4,119 @@ // only apply to SFCs import Col from "components/layout/Column.vue"; import Row from "components/layout/Row.vue"; -import type { CoercableComponent, GenericComponent, JSXFunction } from "features/feature"; -import { - Component as ComponentKey, - GatherProps, - Visibility, - isVisible, - jsx -} from "features/feature"; -import type { ProcessedComputable } from "util/computed"; -import { DoNotCache } from "util/computed"; -import type { Component, DefineComponent, Ref, ShallowRef, UnwrapRef } from "vue"; -import { - computed, - defineComponent, - isRef, - onUnmounted, - ref, - shallowRef, - unref, - watchEffect -} from "vue"; +import { getUniqueID, Visibility } from "features/feature"; +import VueFeatureComponent from "features/VueFeature.vue"; +import { processGetter } from "util/computed"; +import type { CSSProperties, MaybeRef, MaybeRefOrGetter, Ref } from "vue"; +import { isRef, onUnmounted, ref, unref } from "vue"; import { JSX } from "vue/jsx-runtime"; import { camelToKebab } from "./common"; -export function coerceComponent( - component: CoercableComponent, - defaultWrapper = "span" -): DefineComponent { - if (typeof component === "function") { - return defineComponent({ render: component }); - } - if (typeof component === "string") { - if (component.length > 0) { - component = component.trim(); - if (component.charAt(0) !== "<") { - component = `<${defaultWrapper}>${component}`; - } +export const VueFeature = Symbol("VueFeature"); - return defineComponent({ template: component }); - } - return defineComponent({ render: () => ({}) }); - } - return component; +export type Renderable = JSX.Element | string; + +export interface VueFeatureOptions { + /** Whether this feature should be visible. */ + visibility?: MaybeRefOrGetter; + /** Dictionary of CSS classes to apply to this feature. */ + classes?: MaybeRefOrGetter>; + /** CSS to apply to this feature. */ + style?: MaybeRefOrGetter; } export interface VueFeature { - [ComponentKey]: GenericComponent; - [GatherProps]: () => Record; + /** An auto-generated ID for identifying features that appear in the DOM. Will not persist between refreshes or updates. */ + id: string; + /** Whether this feature should be visible. */ + visibility?: MaybeRef; + /** Dictionary of CSS classes to apply to this feature. */ + classes?: MaybeRef>; + /** CSS to apply to this feature. */ + style?: MaybeRef; + /** The components to render inside the vue feature */ + components: MaybeRef[]; + /** The components to render wrapped around the vue feature */ + wrappers: ((el: () => Renderable) => Renderable)[]; + /** Used to identify Vue Features */ + [VueFeature]: true; } -export function render(object: VueFeature | CoercableComponent): JSX.Element | DefineComponent { - if (isCoercableComponent(object)) { - if (typeof object === "function") { - return (object as JSXFunction)(); - } - return coerceComponent(object); +export function vueFeatureMixin( + featureName: string, + options: VueFeatureOptions, + component?: MaybeRefOrGetter +) { + return { + id: getUniqueID(featureName), + visibility: processGetter(options.visibility), + classes: processGetter(options.classes), + style: processGetter(options.style), + components: component == null ? [] : [processGetter(component)], + wrappers: [] as ((el: () => Renderable) => Renderable)[], + [VueFeature]: true + } satisfies VueFeature; +} + +export function render(object: VueFeature, wrapper?: (el: Renderable) => Renderable): JSX.Element; +export function render( + object: MaybeRef, + wrapper?: (el: Renderable) => T +): T; +export function render( + object: VueFeature | MaybeRef, + wrapper?: (el: Renderable) => Renderable +): Renderable; +export function render( + object: VueFeature | MaybeRef, + wrapper?: (el: Renderable) => Renderable +) { + if (typeof object === "object" && VueFeature in object) { + const { id, visibility, style, classes, components, wrappers } = object; + return ( + + ); } - const Component = object[ComponentKey]; - return ; + + object = unref(object); + return wrapper?.(object) ?? object; } -export function renderRow(...objects: (VueFeature | CoercableComponent)[]): JSX.Element { - return {objects.map(render)}; +export function renderRow(...objects: (VueFeature | MaybeRef)[]): JSX.Element { + return {objects.map(obj => render(obj))}; } -export function renderCol(...objects: (VueFeature | CoercableComponent)[]): JSX.Element { - return {objects.map(render)}; +export function renderCol(...objects: (VueFeature | MaybeRef)[]): JSX.Element { + return {objects.map(obj => render(obj))}; } -export function renderJSX(object: VueFeature | CoercableComponent): JSX.Element { - if (isCoercableComponent(object)) { - if (typeof object === "function") { - return (object as JSXFunction)(); - } - if (typeof object === "string") { - return <>{object}; - } - // TODO why is object typed as never? - const Comp = object as DefineComponent; - return ; - } - const Component = object[ComponentKey]; - return ; +export function joinJSX( + objects: (VueFeature | MaybeRef)[], + joiner: JSX.Element +): JSX.Element { + return objects.reduce( + (acc, curr) => ( + <> + {acc} + {joiner} + {render(curr)} + + ), + <> + ); } -export function renderRowJSX(...objects: (VueFeature | CoercableComponent)[]): JSX.Element { - return {objects.map(renderJSX)}; -} - -export function renderColJSX(...objects: (VueFeature | CoercableComponent)[]): JSX.Element { - return {objects.map(renderJSX)}; -} - -export function joinJSX(objects: JSX.Element[], joiner: JSX.Element): JSX.Element { - return objects.reduce((acc, curr) => ( - <> - {acc} - {joiner} - {curr} - - )); -} - -export function isCoercableComponent(component: unknown): component is CoercableComponent { - if (typeof component === "string") { - return true; - } else if (typeof component === "object") { - if (component == null) { - return false; - } - return "render" in component || "component" in component; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } else if (typeof component === "function" && (component as any)[DoNotCache] === true) { - return true; - } - return false; +export function isJSXElement(element: unknown): element is JSX.Element { + return ( + element != null && typeof element === "object" && "type" in element && "children" in element + ); } export function setupHoldToClick( @@ -158,61 +155,6 @@ export function setupHoldToClick( return { start, stop, handleHolding }; } -export function getFirstFeature< - T extends VueFeature & { visibility: ProcessedComputable } ->( - features: T[], - filter: (feature: T) => boolean -): { - firstFeature: Ref; - collapsedContent: JSXFunction; - hasCollapsedContent: Ref; -} { - const filteredFeatures = computed(() => - features.filter(feature => isVisible(feature.visibility) && filter(feature)) - ); - return { - firstFeature: computed(() => filteredFeatures.value[0]), - collapsedContent: jsx(() => renderCol(...filteredFeatures.value.slice(1))), - hasCollapsedContent: computed(() => filteredFeatures.value.length > 1) - }; -} - -export function computeComponent( - component: Ref, - defaultWrapper = "div" -): ShallowRef { - const comp = shallowRef(); - watchEffect(() => { - comp.value = coerceComponent(unref(component), defaultWrapper); - }); - return comp as ShallowRef; -} -export function computeOptionalComponent( - component: Ref, - defaultWrapper = "div" -): ShallowRef { - const comp = shallowRef(null); - watchEffect(() => { - const currComponent = unref(component); - comp.value = - currComponent === "" || currComponent == null - ? null - : coerceComponent(currComponent, defaultWrapper); - }); - return comp; -} - -export function deepUnref(refObject: T): { [K in keyof T]: UnwrapRef } { - return (Object.keys(refObject) as (keyof T)[]).reduce( - (acc, curr) => { - acc[curr] = unref(refObject[curr]) as UnwrapRef; - return acc; - }, - {} as { [K in keyof T]: UnwrapRef } - ); -} - export function setRefValue(ref: Ref>, value: T) { if (isRef(ref.value)) { ref.value.value = value; @@ -232,12 +174,10 @@ export type PropTypes = export function trackHover(element: VueFeature): Ref { const isHovered = ref(false); - const elementGatherProps = element[GatherProps].bind(element); - element[GatherProps] = () => ({ - ...elementGatherProps(), - onPointerenter: () => (isHovered.value = true), - onPointerleave: () => (isHovered.value = false) - }); + (element as unknown as { onPointerenter: VoidFunction }).onPointerenter = () => + (isHovered.value = true); + (element as unknown as { onPointerleave: VoidFunction }).onPointerleave = () => + (isHovered.value = true); return isHovered; } diff --git a/src/components/MarkNode.vue b/src/wrappers/marks/MarkNode.vue similarity index 81% rename from src/components/MarkNode.vue rename to src/wrappers/marks/MarkNode.vue index c730279..98f5218 100644 --- a/src/components/MarkNode.vue +++ b/src/wrappers/marks/MarkNode.vue @@ -1,12 +1,13 @@ diff --git a/src/features/grids/grid.tsx b/src/features/grids/grid.tsx index ddbbb3c..4d8c7e9 100644 --- a/src/features/grids/grid.tsx +++ b/src/features/grids/grid.tsx @@ -1,16 +1,16 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { OptionsFunc, Replace } from "features/feature"; -import { Visibility } from "features/feature"; -import Grid from "features/grids/Grid.vue"; +import { getUniqueID, Visibility } from "features/feature"; import type { Persistent, State } from "game/persistence"; import { persistent } from "game/persistence"; import { isFunction } from "util/common"; -import { ProcessedRefOrGetter, processGetter } from "util/computed"; +import { processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; -import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue"; +import { isJSXElement, render, Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue"; import type { CSSProperties, MaybeRef, MaybeRefOrGetter, Ref } from "vue"; -import { computed, unref } from "vue"; -import GridCell from "./GridCell.vue"; +import { computed, isRef, unref } from "vue"; +import Column from "components/layout/Column.vue"; +import Row from "components/layout/Row.vue"; +import Clickable from "features/clickables/Clickable.vue"; /** A symbol used to identify {@link Grid} features. */ export const GridType = Symbol("Grid"); @@ -38,8 +38,6 @@ export interface GridCell extends VueFeature { startState: State; /** The persistent state of this cell. */ state: State; - /** A header to appear at the top of the display. */ - title?: MaybeRef; /** The main text that appears in the display. */ display: MaybeRef; /** A function that is called when the cell is clicked. */ @@ -56,30 +54,49 @@ export interface GridOptions extends VueFeatureOptions { rows: MaybeRefOrGetter; /** The number of columns in the grid. */ cols: MaybeRefOrGetter; - /** A MaybeRefOrGetter to determine the visibility of a cell. */ + /** A getter for the visibility of a cell. */ getVisibility?: CellMaybeRefOrGetter; - /** A MaybeRefOrGetter to determine if a cell can be clicked. */ + /** A getter for if a cell can be clicked. */ getCanClick?: CellMaybeRefOrGetter; - /** A MaybeRefOrGetter to get the initial persistent state of a cell. */ + /** A getter for the initial persistent state of a cell. */ getStartState: MaybeRefOrGetter | ((row: number, col: number) => State); - /** A MaybeRefOrGetter to get the CSS styles for a cell. */ + /** A getter for the CSS styles for a cell. */ getStyle?: CellMaybeRefOrGetter; - /** A MaybeRefOrGetter to get the CSS classes for a cell. */ + /** A getter for the CSS classes for a cell. */ getClasses?: CellMaybeRefOrGetter>; - /** A MaybeRefOrGetter to get the title component for a cell. */ - getTitle?: CellMaybeRefOrGetter>; - /** A MaybeRefOrGetter to get the display component for a cell. */ - getDisplay: CellMaybeRefOrGetter>; + /** A getter for the display component for a cell. */ + getDisplay: CellMaybeRefOrGetter | { + getTitle?: CellMaybeRefOrGetter; + getDescription: CellMaybeRefOrGetter + }; /** A function that is called when a cell is clicked. */ onClick?: (row: number, col: number, state: State, e?: MouseEvent | TouchEvent) => void; /** A function that is called when a cell is held down. */ onHold?: (row: number, col: number, state: State) => void; } -/** - * The properties that are added onto a processed {@link BoardOptions} to create a {@link Board}. - */ -export interface BaseGrid extends VueFeature { +/** An object that represents a feature that is a grid of cells that all behave according to the same rules. */ +export interface Grid extends VueFeature { + /** A function that is called when a cell is clicked. */ + onClick?: (row: number, col: number, state: State, e?: MouseEvent | TouchEvent) => void; + /** A function that is called when a cell is held down. */ + onHold?: (row: number, col: number, state: State) => void; + /** A getter for determine the visibility of a cell. */ + getVisibility?: ProcessedCellRefOrGetter; + /** A getter for determine if a cell can be clicked. */ + getCanClick?: ProcessedCellRefOrGetter; + /** The number of rows in the grid. */ + rows: MaybeRef; + /** The number of columns in the grid. */ + cols: MaybeRef; + /** A getter for the initial persistent state of a cell. */ + getStartState: MaybeRef | ((row: number, col: number) => State); + /** A getter for the CSS styles for a cell. */ + getStyle?: ProcessedCellRefOrGetter; + /** A getter for the CSS classes for a cell. */ + getClasses?: ProcessedCellRefOrGetter>; + /** A getter for the display component for a cell. */ + getDisplay: ProcessedCellRefOrGetter; /** Get the auto-generated ID for identifying a specific cell of this grid that appears in the DOM. Will not persist between refreshes or updates. */ getID: (row: number, col: number, state: State) => string; /** Get the persistent state of the given cell. */ @@ -94,39 +111,18 @@ export interface BaseGrid extends VueFeature { type: typeof GridType; } -/** An object that represents a feature that is a grid of cells that all behave according to the same rules. */ -export type Grid = Replace< - Replace, - { - getVisibility: ProcessedCellRefOrGetter; - getCanClick: ProcessedCellRefOrGetter; - rows: ProcessedRefOrGetter; - cols: ProcessedRefOrGetter; - getStartState: MaybeRef | ((row: number, col: number) => State); - getStyle: ProcessedCellRefOrGetter; - getClasses: ProcessedCellRefOrGetter; - getTitle: ProcessedCellRefOrGetter; - getDisplay: ProcessedCellRefOrGetter; - } ->; - function getCellRowHandler(grid: Grid, row: number) { return new Proxy({} as GridCell[], { get(target, key) { - if (key === "isProxy") { - return true; - } - - if (typeof key !== "string") { - return; - } - if (key === "length") { return unref(grid.cols); } + if (typeof key !== "number" && typeof key !== "string") { + return; + } - const keyNum = parseInt(key); - if (!Number.isFinite(keyNum) || keyNum >= unref(grid.cols)) { + const keyNum = typeof key === "number" ? key : parseInt(key); + if (Number.isFinite(keyNum) && keyNum < unref(grid.cols)) { if (keyNum in target) { return target[keyNum]; } @@ -144,20 +140,20 @@ function getCellRowHandler(grid: Grid, row: number) { if (key === "length") { return true; } - if (typeof key !== "string") { + if (typeof key !== "number" && typeof key !== "string") { return false; } - const keyNum = parseInt(key); + const keyNum = typeof key === "number" ? key : parseInt(key); if (!Number.isFinite(keyNum) || keyNum >= unref(grid.cols)) { return false; } return true; }, getOwnPropertyDescriptor(target, key) { - if (typeof key !== "string") { + if (typeof key !== "number" && typeof key !== "string") { return; } - const keyNum = parseInt(key); + const keyNum = typeof key === "number" ? key : parseInt(key); if (key !== "length" && (!Number.isFinite(keyNum) || keyNum >= unref(grid.cols))) { return; } @@ -200,8 +196,6 @@ function getCellHandler(grid: Grid, row: number, col: number): GridCell { // The typing in this function is absolutely atrocious in order to support custom properties get(target, key, receiver) { switch (key) { - case "isProxy": - return true; case "wrappers": return []; case VueFeature: @@ -219,32 +213,28 @@ function getCellHandler(grid: Grid, row: number, col: number): GridCell { case "state": { return grid.getState(row, col); } + case "id": + return target.id = target.id ?? getUniqueID("gridcell"); case "components": return [ computed(() => ( - )) ]; } - - let prop = (grid as any)[key]; - - if (isFunction(prop)) { - return () => prop.call(receiver, row, col, grid.getState(row, col)); - } - if (prop != null || typeof key === "symbol") { - return prop; + + if (typeof key === "symbol") { + return (grid as any)[key]; } key = key.slice(0, 1).toUpperCase() + key.slice(1); - prop = (grid as any)[`get${key}`]; + let prop = (grid as any)[`get${key}`]; if (isFunction(prop)) { if (!(key in cache)) { cache[key] = computed(() => @@ -263,14 +253,24 @@ function getCellHandler(grid: Grid, row: number, col: number): GridCell { return prop; } + // Revert key change + key = key.slice(0, 1).toLowerCase() + key.slice(1); + prop = (grid as any)[key]; + + if (isFunction(prop)) { + return () => prop.call(receiver, row, col, grid.getState(row, col)); + } + return (grid as any)[key]; }, set(target, key, value) { + console.log("!!?", key, value) if (typeof key !== "string") { return false; } key = `set${key.slice(0, 1).toUpperCase() + key.slice(1)}`; - if (key in grid && isFunction((grid as any)[key]) && (grid as any)[key].length < 3) { + console.log(key, grid[key]) + if (key in grid && isFunction((grid as any)[key]) && (grid as any)[key].length <= 3) { (grid as any)[key].call(grid, row, col, value); return true; } else { @@ -296,6 +296,12 @@ function getCellHandler(grid: Grid, row: number, col: number): GridCell { }); } +function convertCellMaybeRefOrGetter( + value: NonNullable> +): ProcessedCellRefOrGetter; +function convertCellMaybeRefOrGetter( + value: CellMaybeRefOrGetter | undefined +): ProcessedCellRefOrGetter | undefined; function convertCellMaybeRefOrGetter( value: CellMaybeRefOrGetter ): ProcessedCellRefOrGetter { @@ -309,10 +315,10 @@ function convertCellMaybeRefOrGetter( * Lazily creates a grid with the given options. * @param optionsFunc Grid options. */ -export function createGrid(optionsFunc: OptionsFunc) { +export function createGrid(optionsFunc: () => T) { const cellState = persistent>>({}, false); - return createLazyProxy(feature => { - const options = optionsFunc.call(feature, feature as Grid); + return createLazyProxy(() => { + const options = optionsFunc(); const { rows, cols, @@ -321,40 +327,57 @@ export function createGrid(optionsFunc: OptionsFunc + {title} + {description} + ; + } + } else { + getDisplay = convertCellMaybeRefOrGetter(_getDisplay); + } + const grid = { type: GridType, ...(props as Omit), ...vueFeatureMixin("grid", options, () => ( - - )), + + {new Array(unref(grid.rows)).fill(0).map((_, row) => ( + + {new Array(unref(grid.cols)).fill(0).map((_, col) => + render(grid.cells[row][col]))} + ))} + )), cellState, - cells: new Proxy({} as Record, { + cells: new Proxy({} as GridCell[][], { get(target, key: PropertyKey) { - if (key === "isProxy") { - return true; - } - if (key === "length") { return unref(grid.rows); } - if (typeof key !== "string") { + if (typeof key !== "number" && typeof key !== "string") { return; } - const keyNum = parseInt(key); - if (!Number.isFinite(keyNum) || keyNum >= unref(grid.rows)) { - if (keyNum in target) { - return target[keyNum]; + const keyNum = typeof key === "number" ? key : parseInt(key); + if (Number.isFinite(keyNum) && keyNum < unref(grid.rows)) { + if (!(keyNum in target)) { + target[keyNum] = getCellRowHandler(grid, keyNum); } - return (target[keyNum] = getCellRowHandler(grid, keyNum)); + return target[keyNum]; } }, set(target, key, value) { @@ -368,20 +391,20 @@ export function createGrid(optionsFunc: OptionsFunc= unref(grid.rows)) { return false; } return true; }, getOwnPropertyDescriptor(target, key) { - if (typeof key !== "string") { + if (typeof key !== "number" && typeof key !== "string") { return; } - const keyNum = parseInt(key); + const keyNum = typeof key === "number" ? key : parseInt(key); if ( key !== "length" && (!Number.isFinite(keyNum) || keyNum >= unref(grid.rows)) @@ -399,15 +422,16 @@ export function createGrid(optionsFunc: OptionsFunc 0 ? + getStartState : processGetter(getStartState), getStyle: convertCellMaybeRefOrGetter(getStyle), getClasses: convertCellMaybeRefOrGetter(getClasses), - getTitle: convertCellMaybeRefOrGetter(getTitle), - getDisplay: convertCellMaybeRefOrGetter(getDisplay), + getDisplay, getID: function (row: number, col: number): string { return grid.id + "-" + row + "-" + col; }, getState: function (row: number, col: number): State { + cellState.value[row] ??= {}; if (cellState.value[row][col] != null) { return cellState.value[row][col]; } @@ -421,7 +445,7 @@ export function createGrid(optionsFunc: OptionsFunc(optionsFunc: OptionsFunc = shallowReactive({}); @@ -33,34 +27,29 @@ export interface HotkeyOptions { onPress: (e?: MouseEvent | TouchEvent) => void; } -/** - * The properties that are added onto a processed {@link HotkeyOptions} to create an {@link Hotkey}. - */ -export interface BaseHotkey { +/** An object that represents a hotkey shortcut that performs an action upon a key sequence being pressed. */ +export interface Hotkey { + /** Whether or not this hotkey is currently enabled. */ + enabled: MaybeRef; + /** The key tied to this hotkey */ + key: string; + /** The description of this hotkey, to display in the settings. */ + description: MaybeRef; + /** What to do upon pressing the key. */ + onPress: (e?: MouseEvent | TouchEvent) => void; /** A symbol that helps identify features of the same type. */ type: typeof HotkeyType; } -/** An object that represents a hotkey shortcut that performs an action upon a key sequence being pressed. */ -export type Hotkey = Replace< - Replace, - { - enabled: MaybeRef; - description: UnwrapRef; - } ->; - const uppercaseNumbers = [")", "!", "@", "#", "$", "%", "^", "&", "*", "("]; /** * Lazily creates a hotkey with the given options. * @param optionsFunc Hotkey options. */ -export function createHotkey( - optionsFunc: OptionsFunc -) { - return createLazyProxy(feature => { - const options = optionsFunc.call(feature, feature as Hotkey); +export function createHotkey(optionsFunc: () => T) { + return createLazyProxy(() => { + const options = optionsFunc(); const { enabled, description, key, onPress, ...props } = options; const hotkey = { @@ -119,7 +108,7 @@ document.onkeydown = function (e) { keysToCheck.push("ctrl+" + e.key); } const hotkey = hotkeys[keysToCheck.find(key => key in hotkeys) ?? ""]; - if (hotkey != null && unref(hotkey.enabled)) { + if (hotkey != null && unref(hotkey.enabled) !== false) { e.preventDefault(); hotkey.onPress(); } diff --git a/src/features/infoboxes/Infobox.vue b/src/features/infoboxes/Infobox.vue index 8f4d018..39f875c 100644 --- a/src/features/infoboxes/Infobox.vue +++ b/src/features/infoboxes/Infobox.vue @@ -15,8 +15,8 @@ </button> <CollapseTransition> - <div v-if="!unref(collapsed)" class="body" :style="{ backgroundColor: unref(color) }"> - <Body :style="unref(bodyStyle)" /> + <div v-if="!unref(collapsed)" class="body" :style="unref(bodyStyle)"> + <Body /> </div> </CollapseTransition> </div> @@ -79,6 +79,8 @@ const stacked = computed(() => themes[settings.theme].mergeAdjacent); width: auto; text-align: left; padding-left: 30px; + border-radius: 0; + margin: 00; } .infobox:not(.stacked) .title { @@ -117,21 +119,15 @@ const stacked = computed(() => themes[settings.theme].mergeAdjacent); .body { transition-duration: 0.5s; - border-radius: 5px; - border-top-left-radius: 0; + padding: 8px; + width: 100%; + display: block; + box-sizing: border-box; + background-color: var(--background); + border-radius: 0 0 var(--feature-margin) var(--feature-margin); } .infobox:not(.stacked) .body { padding: 4px; } - -.body > * { - padding: 8px; - width: 100%; - display: block; - box-sizing: border-box; - border-radius: 5px; - border-top-left-radius: 0; - background-color: var(--background); -} </style> diff --git a/src/features/infoboxes/infobox.tsx b/src/features/infoboxes/infobox.tsx index d9aec4c..ccf676c 100644 --- a/src/features/infoboxes/infobox.tsx +++ b/src/features/infoboxes/infobox.tsx @@ -1,11 +1,10 @@ -import type { OptionsFunc, Replace } from "features/feature"; import Infobox from "features/infoboxes/Infobox.vue"; import type { Persistent } from "game/persistence"; import { persistent } from "game/persistence"; -import { ProcessedRefOrGetter, processGetter } from "util/computed"; +import { processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue"; -import { CSSProperties, MaybeRefOrGetter } from "vue"; +import { CSSProperties, MaybeRef, MaybeRefOrGetter } from "vue"; /** A symbol used to identify {@link Infobox} features. */ export const InfoboxType = Symbol("Infobox"); @@ -14,7 +13,7 @@ export const InfoboxType = Symbol("Infobox"); * An object that configures an {@link Infobox}. */ export interface InfoboxOptions extends VueFeatureOptions { - /** The background color of the Infobox. */ + /** The background color of the Infobox. Defaults to the layer color. */ color?: MaybeRefOrGetter<string>; /** CSS to apply to the title of the infobox. */ titleStyle?: MaybeRefOrGetter<CSSProperties>; @@ -26,38 +25,32 @@ export interface InfoboxOptions extends VueFeatureOptions { display: MaybeRefOrGetter<Renderable>; } -/** - * The properties that are added onto a processed {@link InfoboxOptions} to create an {@link Infobox}. - */ -export interface BaseInfobox extends VueFeature { +/** An object that represents a feature that displays information in a collapsible way. */ +export interface Infobox extends VueFeature { + /** The background color of the Infobox. */ + color?: MaybeRef<string>; + /** CSS to apply to the title of the infobox. */ + titleStyle?: MaybeRef<CSSProperties>; + /** CSS to apply to the body of the infobox. */ + bodyStyle?: MaybeRef<CSSProperties>; + /** A header to appear at the top of the display. */ + title: MaybeRef<Renderable>; + /** The main text that appears in the display. */ + display: MaybeRef<Renderable>; /** Whether or not this infobox is collapsed. */ collapsed: Persistent<boolean>; /** A symbol that helps identify features of the same type. */ type: typeof InfoboxType; } -/** An object that represents a feature that displays information in a collapsible way. */ -export type Infobox = Replace< - Replace<InfoboxOptions, BaseInfobox>, - { - color: ProcessedRefOrGetter<InfoboxOptions["color"]>; - titleStyle: ProcessedRefOrGetter<InfoboxOptions["titleStyle"]>; - bodyStyle: ProcessedRefOrGetter<InfoboxOptions["bodyStyle"]>; - title: ProcessedRefOrGetter<InfoboxOptions["title"]>; - display: ProcessedRefOrGetter<InfoboxOptions["display"]>; - } ->; - /** * Lazily creates an infobox with the given options. * @param optionsFunc Infobox options. */ -export function createInfobox<T extends InfoboxOptions>( - optionsFunc: OptionsFunc<T, BaseInfobox, Infobox> -) { +export function createInfobox<T extends InfoboxOptions>(optionsFunc: () => T) { const collapsed = persistent<boolean>(false, false); - return createLazyProxy(feature => { - const options = optionsFunc.call(feature, feature as Infobox); + return createLazyProxy(() => { + const options = optionsFunc(); const { color, titleStyle, bodyStyle, title, display, ...props } = options; const infobox = { @@ -74,7 +67,7 @@ export function createInfobox<T extends InfoboxOptions>( /> )), collapsed, - color: processGetter(color), + color: processGetter(color) ?? "--layer-color", titleStyle: processGetter(titleStyle), bodyStyle: processGetter(bodyStyle), title: processGetter(title), diff --git a/src/features/links/Links.vue b/src/features/links/Links.vue index 7053da4..c33b8d7 100644 --- a/src/features/links/Links.vue +++ b/src/features/links/Links.vue @@ -15,22 +15,30 @@ <script setup lang="ts"> import type { FeatureNode } from "game/layers"; import { BoundsInjectionKey, NodesInjectionKey } from "game/layers"; -import { computed, inject, onMounted, ref, unref, watch } from "vue"; +import { computed, inject, onMounted, ref, shallowRef, unref, watch } from "vue"; import LinkVue from "./Link.vue"; import { Links } from "./links"; const props = defineProps<{ links: Links["links"] }>(); -const resizeListener = ref<Element | null>(null); +function updateBounds() { + boundingRect.value = resizeListener.value?.getBoundingClientRect(); +} + +const resizeObserver = new ResizeObserver(updateBounds); +const resizeListener = shallowRef<HTMLElement | null>(null); const nodes = inject(NodesInjectionKey, ref<Record<string, FeatureNode | undefined>>({})); const outerBoundingRect = inject(BoundsInjectionKey, ref<DOMRect | undefined>(undefined)); const boundingRect = ref<DOMRect | undefined>(resizeListener.value?.getBoundingClientRect()); -watch( - outerBoundingRect, - () => (boundingRect.value = resizeListener.value?.getBoundingClientRect()) -); -onMounted(() => (boundingRect.value = resizeListener.value?.getBoundingClientRect())); +watch(outerBoundingRect, updateBounds); +onMounted(() => { + const resListener = resizeListener.value; + if (resListener != null) { + resizeObserver.observe(resListener); + } + updateBounds(); +}); const validLinks = computed(() => { const n = nodes.value; @@ -42,23 +50,14 @@ const validLinks = computed(() => { </script> <style scoped> -.resize-listener { - position: absolute; - top: 0px; - left: 0; - right: -4px; - bottom: 5px; - z-index: -10; - pointer-events: none; -} - -svg { +.resize-listener, svg { position: absolute; top: 0; left: 0; - width: 100%; - height: 100%; z-index: -10; pointer-events: none; + margin: 0; + width: 100%; + height: 100%; } </style> diff --git a/src/features/links/links.tsx b/src/features/links/links.tsx index 73ae7d0..ef6bdcb 100644 --- a/src/features/links/links.tsx +++ b/src/features/links/links.tsx @@ -1,16 +1,15 @@ -import type { OptionsFunc, Replace } from "features/feature"; import type { Position } from "game/layers"; import { processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; -import { VueFeature, vueFeatureMixin } from "util/vue"; -import type { MaybeRef, MaybeRefOrGetter, SVGAttributes } from "vue"; +import { VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue"; +import { unref, type MaybeRef, type MaybeRefOrGetter, type SVGAttributes } from "vue"; import Links from "./Links.vue"; /** A symbol used to identify {@link Links} features. */ export const LinksType = Symbol("Links"); /** Represents a link between two nodes. It will be displayed as an SVG line, and can take any appropriate properties for an SVG line element. */ -export interface Link extends SVGAttributes { +export interface Link extends /* @vue-ignore */ SVGAttributes { startNode: { id: string }; endNode: { id: string }; offsetStart?: Position; @@ -18,40 +17,35 @@ export interface Link extends SVGAttributes { } /** An object that configures a {@link Links}. */ -export interface LinksOptions { +export interface LinksOptions extends VueFeatureOptions { /** The list of links to display. */ links: MaybeRefOrGetter<Link[]>; } -/** - * The properties that are added onto a processed {@link LinksOptions} to create an {@link Links}. - */ -export interface BaseLinks extends VueFeature { +/** An object that represents a list of links between nodes, which are the elements in the DOM for any renderable feature. */ +export interface Links extends VueFeature { + /** The list of links to display. */ + links: MaybeRef<Link[]>; /** A symbol that helps identify features of the same type. */ type: typeof LinksType; } -/** An object that represents a list of links between nodes, which are the elements in the DOM for any renderable feature. */ -export type Links = Replace< - Replace<LinksOptions, BaseLinks>, - { - links: MaybeRef<Link[]>; - } ->; - /** * Lazily creates links with the given options. * @param optionsFunc Links options. */ -export function createLinks<T extends LinksOptions>(optionsFunc: OptionsFunc<T, BaseLinks, Links>) { - return createLazyProxy(feature => { - const options = optionsFunc?.call(feature, feature as Links); - const { links, ...props } = options; +export function createLinks<T extends LinksOptions>(optionsFunc: () => T) { + return createLazyProxy(() => { + const options = optionsFunc?.(); + const { links, style: _style, ...props } = options; + + const style = processGetter(_style); + options.style = () => ({ position: "static", ...(unref(style) ?? {}) }); const retLinks = { type: LinksType, ...(props as Omit<typeof props, keyof VueFeature | keyof LinksOptions>), - ...vueFeatureMixin("links", {}, () => <Links links={retLinks.links} />), + ...vueFeatureMixin("links", options, () => <Links links={retLinks.links} />), links: processGetter(links) } satisfies Links; diff --git a/src/features/particles/Particles.vue b/src/features/particles/Particles.vue index a4c64ad..b7426f2 100644 --- a/src/features/particles/Particles.vue +++ b/src/features/particles/Particles.vue @@ -24,7 +24,6 @@ const resizeObserver = new ResizeObserver(updateBounds); const resizeListener = shallowRef<HTMLElement | null>(null); onMounted(() => { - // ResizeListener exists because ResizeObserver's don't work when told to observe an SVG element const resListener = resizeListener.value; if (resListener != null) { resizeObserver.observe(resListener); diff --git a/src/features/particles/particles.tsx b/src/features/particles/particles.tsx index be4480a..a933a20 100644 --- a/src/features/particles/particles.tsx +++ b/src/features/particles/particles.tsx @@ -1,8 +1,6 @@ import { Application } from "@pixi/app"; import type { EmitterConfigV3 } from "@pixi/particle-emitter"; import { Emitter, upgradeConfig } from "@pixi/particle-emitter"; -import type { OptionsFunc, Replace } from "features/feature"; -import { ProcessedRefOrGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; import { VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue"; import { Ref, shallowRef } from "vue"; @@ -22,9 +20,14 @@ export interface ParticlesOptions extends VueFeatureOptions { } /** - * The properties that are added onto a processed {@link ParticlesOptions} to create an {@link Particles}. + * An object that represents a feature that display particle effects on the screen. + * The config should typically be gotten by designing the effect using the [online particle effect editor](https://pixijs.io/pixi-particles-editor/) and passing it into the {@link upgradeConfig} from @pixi/particle-emitter. */ -export interface BaseParticles extends VueFeature { +export interface Particles extends VueFeature { + /** A function that is called when the particles canvas is resized. */ + onContainerResized?: (boundingRect: DOMRect) => void; + /** A function that is called whenever the particles element is reloaded during development. For restarting particle effects. */ + onHotReload?: VoidFunction; /** The Pixi.JS Application powering this particles canvas. */ app: Ref<null | Application>; /** @@ -37,27 +40,13 @@ export interface BaseParticles extends VueFeature { type: typeof ParticlesType; } -/** - * An object that represents a feature that display particle effects on the screen. - * The config should typically be gotten by designing the effect using the [online particle effect editor](https://pixijs.io/pixi-particles-editor/) and passing it into the {@link upgradeConfig} from @pixi/particle-emitter. - */ -export type Particles = Replace< - Replace<ParticlesOptions, BaseParticles>, - { - classes: ProcessedRefOrGetter<ParticlesOptions["classes"]>; - style: ProcessedRefOrGetter<ParticlesOptions["style"]>; - } ->; - /** * Lazily creates particles with the given options. * @param optionsFunc Particles options. */ -export function createParticles<T extends ParticlesOptions>( - optionsFunc?: OptionsFunc<T, BaseParticles, Particles> -) { - return createLazyProxy(feature => { - const options = optionsFunc?.call(feature, feature as Particles) ?? ({} as T); +export function createParticles<T extends ParticlesOptions>(optionsFunc?: () => T) { + return createLazyProxy(() => { + const options = optionsFunc?.() ?? ({} as T); const { onContainerResized, onHotReload, ...props } = options; let emittersToAdd: { diff --git a/src/features/reset.ts b/src/features/reset.ts index c075ec3..c9c7f06 100644 --- a/src/features/reset.ts +++ b/src/features/reset.ts @@ -1,4 +1,3 @@ -import type { OptionsFunc, Replace } from "features/feature"; import { globalBus } from "game/events"; import Formula from "game/formulas/formulas"; import type { BaseLayer } from "game/layers"; @@ -11,9 +10,9 @@ import { } from "game/persistence"; import type { Unsubscribe } from "nanoevents"; import Decimal from "util/bignum"; -import { processGetter, type MaybeRefOrGetter, type UnwrapRef } from "util/computed"; +import { processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; -import { isRef, unref } from "vue"; +import { isRef, MaybeRef, MaybeRefOrGetter, unref } from "vue"; /** A symbol used to identify {@link Reset} features. */ export const ResetType = Symbol("Reset"); @@ -28,31 +27,25 @@ export interface ResetOptions { onReset?: VoidFunction; } -/** - * The properties that are added onto a processed {@link ResetOptions} to create an {@link Reset}. - */ -export interface BaseReset { +/** An object that represents a reset mechanic, which resets progress back to its initial state. */ +export interface Reset { + /** List of things to reset. Can include objects which will be recursed over for persistent values. */ + thingsToReset: MaybeRef<unknown[]>; + /** A function that is called when the reset is performed. */ + onReset?: VoidFunction; /** Trigger the reset. */ reset: VoidFunction; /** A symbol that helps identify features of the same type. */ type: typeof ResetType; } -/** An object that represents a reset mechanic, which resets progress back to its initial state. */ -export type Reset = Replace< - Replace<ResetOptions, BaseReset>, - { - thingsToReset: UnwrapRef<ResetOptions["thingsToReset"]>; - } ->; - /** * Lazily creates a reset with the given options. * @param optionsFunc Reset options. */ -export function createReset<T extends ResetOptions>(optionsFunc: OptionsFunc<T, BaseReset, Reset>) { - return createLazyProxy(feature => { - const options = optionsFunc.call(feature, feature as Reset); +export function createReset<T extends ResetOptions>(optionsFunc: () => T) { + return createLazyProxy(() => { + const options = optionsFunc(); const { thingsToReset, onReset, ...props } = options; const reset = { diff --git a/src/features/resources/MainDisplay.vue b/src/features/resources/MainDisplay.vue index 2421420..d8225f7 100644 --- a/src/features/resources/MainDisplay.vue +++ b/src/features/resources/MainDisplay.vue @@ -3,16 +3,12 @@ <div class="main-display-container" :class="classes ?? {}" - :style="[{ height: `${(effectRef?.$el.clientHeight ?? 0) + 50}px` }, style ?? {}]" - > - <div class="main-display"> + :style="[{ height: `${(displayRef?.clientHeight ?? 0) + 20}px` }, style ?? {}]"> + <div class="main-display" ref="displayRef"> <span v-if="showPrefix">You have </span> <ResourceVue :resource="resource" :color="color || 'white'" /> - {{ resource.displayName - }}<!-- remove whitespace --> - <span v-if="effectDisplay" - >, <Effect ref="effectRef" - /></span> + {{ resource.displayName }}<!-- remove whitespace --> + <span v-if="effectDisplay">, <Effect /></span> </div> </div> </Sticky> @@ -24,7 +20,7 @@ import type { Resource } from "features/resources/resource"; import ResourceVue from "features/resources/Resource.vue"; import Decimal from "util/bignum"; import { Renderable } from "util/vue"; -import { ComponentPublicInstance, computed, MaybeRefOrGetter, ref, StyleValue, toValue } from "vue"; +import { computed, MaybeRefOrGetter, ref, StyleValue, toValue } from "vue"; const props = defineProps<{ resource: Resource; @@ -34,7 +30,7 @@ const props = defineProps<{ effectDisplay?: MaybeRefOrGetter<Renderable>; }>(); -const effectRef = ref<ComponentPublicInstance | null>(null); +const displayRef = ref<Element | null>(null); const Effect = () => toValue(props.effectDisplay); diff --git a/src/features/tabs/TabButton.vue b/src/features/tabs/TabButton.vue index 6611c8f..05f5a7f 100644 --- a/src/features/tabs/TabButton.vue +++ b/src/features/tabs/TabButton.vue @@ -9,12 +9,13 @@ import { getNotifyStyle } from "game/notifications"; import { render } from "util/vue"; import { computed, unref } from "vue"; import { TabButton } from "./tabFamily"; +import themes from "data/themes"; +import settings from "game/settings"; const props = defineProps<{ display: TabButton["display"]; glowColor: TabButton["glowColor"]; active?: boolean; - floating?: boolean; }>(); const emit = defineEmits<{ @@ -28,12 +29,16 @@ const glowColorStyle = computed(() => { if (color == null || color === "") { return {}; } - if (props.floating) { + if (floating.value) { return getNotifyStyle(color); } return { boxShadow: `0px 9px 5px -6px ${color}` }; }); +const floating = computed(() => { + return themes[settings.theme].floatingTabs; +}); + function selectTab() { emit("selectTab"); } diff --git a/src/features/tabs/TabFamily.vue b/src/features/tabs/TabFamily.vue index cf7694a..ec13c01 100644 --- a/src/features/tabs/TabFamily.vue +++ b/src/features/tabs/TabFamily.vue @@ -6,15 +6,7 @@ :style="unref(buttonContainerStyle)" > <div class="tab-buttons" :class="{ floating }"> - <TabButton - v-for="(button, id) in unref(tabs)" - @selectTab="selected.value = id" - :floating="floating" - :key="id" - :active="unref(button.tab) === unref(activeTab)" - :display="button.display" - :glowColor="button.glowColor" - /> + <TabButtons /> </div> </Sticky> <Component v-if="unref(activeTab) != null" /> @@ -23,28 +15,22 @@ <script setup lang="ts"> import Sticky from "components/layout/Sticky.vue"; -import themes from "data/themes"; -import TabButton from "features/tabs/TabButton.vue"; -import settings from "game/settings"; +import { isType } from "features/feature"; import { render } from "util/vue"; import type { Component } from "vue"; import { computed, unref } from "vue"; -import { TabFamily } from "./tabFamily"; import { TabType } from "./tab"; -import { isType } from "features/feature"; +import { TabFamily } from "./tabFamily"; +import themes from "data/themes"; +import settings from "game/settings"; const props = defineProps<{ activeTab: TabFamily["activeTab"]; - selected: TabFamily["selected"]; tabs: TabFamily["tabs"]; buttonContainerClasses: TabFamily["buttonContainerClasses"]; buttonContainerStyle: TabFamily["buttonContainerStyle"]; }>(); -const floating = computed(() => { - return themes[settings.theme].floatingTabs; -}); - const Component = () => { const activeTab = unref(props.activeTab); if (activeTab == null) { @@ -53,6 +39,12 @@ const Component = () => { return render(activeTab); }; +const floating = computed(() => { + return themes[settings.theme].floatingTabs; +}); + +const TabButtons = () => Object.values(props.tabs).map(tab => render(tab)); + const tabClasses = computed(() => { const activeTab = unref(props.activeTab); if (isType(activeTab, TabType)) { @@ -136,6 +128,10 @@ const tabStyle = computed(() => { z-index: 4; } +.tab-buttons > * { + margin: 0; +} + .layer-tab > .tab-family-container:first-child:nth-last-child(3) > .tab-buttons-container diff --git a/src/features/tabs/tab.ts b/src/features/tabs/tab.ts index bcd26e0..104b9c2 100644 --- a/src/features/tabs/tab.ts +++ b/src/features/tabs/tab.ts @@ -1,8 +1,7 @@ -import type { OptionsFunc, Replace } from "features/feature"; -import { ProcessedRefOrGetter, processGetter } from "util/computed"; +import { processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; import { render, Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue"; -import { MaybeRefOrGetter } from "vue"; +import { MaybeRef, MaybeRefOrGetter } from "vue"; import { JSX } from "vue/jsx-runtime"; /** A symbol used to identify {@link Tab} features. */ @@ -17,31 +16,23 @@ export interface TabOptions extends VueFeatureOptions { } /** - * The properties that are added onto a processed {@link TabOptions} to create an {@link Tab}. + * An object representing a tab of content in a tabbed interface. + * @see {@link TabFamily} */ -export interface BaseTab extends VueFeature { +export interface Tab extends VueFeature { + /** The display to use for this tab. */ + display: MaybeRef<Renderable>; /** A symbol that helps identify features of the same type. */ type: typeof TabType; } -/** - * An object representing a tab of content in a tabbed interface. - * @see {@link TabFamily} - */ -export type Tab = Replace< - Replace<TabOptions, BaseTab>, - { - display: ProcessedRefOrGetter<TabOptions["display"]>; - } ->; - /** * Lazily creates a tab with the given options. * @param optionsFunc Tab options. */ -export function createTab<T extends TabOptions>(optionsFunc: OptionsFunc<T, BaseTab, Tab>) { - return createLazyProxy(feature => { - const options = optionsFunc?.call(feature, feature as Tab) ?? ({} as T); +export function createTab<T extends TabOptions>(optionsFunc: () => T) { + return createLazyProxy(() => { + const options = optionsFunc?.() ?? ({} as T); const { display, ...props } = options; const tab = { diff --git a/src/features/tabs/tabFamily.tsx b/src/features/tabs/tabFamily.tsx index e77a3d1..66cd590 100644 --- a/src/features/tabs/tabFamily.tsx +++ b/src/features/tabs/tabFamily.tsx @@ -1,11 +1,10 @@ -import type { OptionsFunc, Replace } from "features/feature"; import { isVisible } from "features/feature"; import { Tab } from "features/tabs/tab"; import TabButton from "features/tabs/TabButton.vue"; import TabFamily from "features/tabs/TabFamily.vue"; import type { Persistent } from "game/persistence"; import { persistent } from "game/persistence"; -import { ProcessedRefOrGetter, processGetter } from "util/computed"; +import { processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue"; import type { CSSProperties, MaybeRef, MaybeRefOrGetter, Ref } from "vue"; @@ -28,26 +27,20 @@ export interface TabButtonOptions extends VueFeatureOptions { glowColor?: MaybeRefOrGetter<string>; } -/** - * The properties that are added onto a processed {@link TabButtonOptions} to create an {@link TabButton}. - */ -export interface BaseTabButton extends VueFeature { - /** A symbol that helps identify features of the same type. */ - type: typeof TabButtonType; -} - /** * An object that represents a button that can be clicked to change tabs in a tabbed interface. * @see {@link TabFamily} */ -export type TabButton = Replace< - Replace<TabButtonOptions, BaseTabButton>, - { - tab: Tab | MaybeRef<Renderable>; - display: ProcessedRefOrGetter<TabButtonOptions["display"]>; - glowColor: ProcessedRefOrGetter<TabButtonOptions["glowColor"]>; - } ->; +export interface TabButton extends VueFeature { + /** The tab to display when this button is clicked. */ + tab: Tab | MaybeRef<Renderable>; + /** The label on this button. */ + display: MaybeRef<Renderable>; + /** The color of the glow effect to display when this button is active. */ + glowColor?: MaybeRef<string>; + /** A symbol that helps identify features of the same type. */ + type: typeof TabButtonType; +} /** * An object that configures a {@link TabFamily}. @@ -60,11 +53,16 @@ export interface TabFamilyOptions extends VueFeatureOptions { } /** - * The properties that are added onto a processed {@link TabFamilyOptions} to create an {@link TabFamily}. + * An object that represents a tabbed interface. + * @see {@link TabFamily} */ -export interface BaseTabFamily extends VueFeature { +export interface TabFamily extends VueFeature { + /** A dictionary of CSS classes to apply to the list of buttons for changing tabs. */ + buttonContainerClasses?: MaybeRef<Record<string, boolean>>; + /** CSS to apply to the list of buttons for changing tabs. */ + buttonContainerStyle?: MaybeRef<CSSProperties>; /** All the tabs within this family. */ - tabs: Record<string, TabButtonOptions>; + tabs: Record<string, TabButton>; /** The currently active tab, if any. */ activeTab: Ref<Tab | MaybeRef<Renderable> | null>; /** The name of the tab that is currently active. */ @@ -73,32 +71,21 @@ export interface BaseTabFamily extends VueFeature { type: typeof TabFamilyType; } -/** - * An object that represents a tabbed interface. - * @see {@link TabFamily} - */ -export type TabFamily = Replace< - Replace<TabFamilyOptions, BaseTabFamily>, - { - tabs: Record<string, TabButton>; - } ->; - /** * Lazily creates a tab family with the given options. * @param optionsFunc Tab family options. */ export function createTabFamily<T extends TabFamilyOptions>( tabs: Record<string, () => TabButtonOptions>, - optionsFunc?: OptionsFunc<T, BaseTabFamily, TabFamily> + optionsFunc?: () => T ) { if (Object.keys(tabs).length === 0) { console.error("Cannot create tab family with 0 tabs"); } const selected = persistent(Object.keys(tabs)[0], false); - return createLazyProxy(feature => { - const options = optionsFunc?.call(feature, feature as TabFamily) ?? ({} as T); + return createLazyProxy(() => { + const options = optionsFunc?.() ?? ({} as T); const { buttonContainerClasses, buttonContainerStyle, ...props } = options; const tabFamily = { @@ -107,7 +94,6 @@ export function createTabFamily<T extends TabFamilyOptions>( ...vueFeatureMixin("tabFamily", options, () => ( <TabFamily activeTab={tabFamily.activeTab} - selected={tabFamily.selected} tabs={tabFamily.tabs} buttonContainerClasses={tabFamily.buttonContainerClasses} buttonContainerStyle={tabFamily.buttonContainerStyle} @@ -120,7 +106,13 @@ export function createTabFamily<T extends TabFamilyOptions>( const tabButton = { type: TabButtonType, ...(props as Omit<typeof props, keyof VueFeature | keyof TabButtonOptions>), - ...vueFeatureMixin("tabButton", options), + ...vueFeatureMixin("tabButton", options, () => + <TabButton + display={tabButton.display} + glowColor={tabButton.glowColor} + active={unref(tabButton.tab) === unref(tabFamily.activeTab)} + onSelectTab={() => tabFamily.selected.value = tab} + />), tab: processGetter(buttonTab), glowColor: processGetter(glowColor), display: processGetter(display) diff --git a/src/features/trees/Tree.vue b/src/features/trees/Tree.vue index 678108d..22f2c72 100644 --- a/src/features/trees/Tree.vue +++ b/src/features/trees/Tree.vue @@ -9,7 +9,7 @@ import "components/common/table.css"; import Links from "features/links/Links.vue"; import type { Tree } from "features/trees/tree"; -import { joinJSX, render } from "util/vue"; +import { render } from "util/vue"; import { unref } from "vue"; const props = defineProps<{ diff --git a/src/features/trees/TreeNode.vue b/src/features/trees/TreeNode.vue index f52b632..22ed704 100644 --- a/src/features/trees/TreeNode.vue +++ b/src/features/trees/TreeNode.vue @@ -1,5 +1,5 @@ <template> - <div + <button :style="{ backgroundColor: unref(color), boxShadow: `-4px -4px 4px rgba(0, 0, 0, 0.25) inset, 0 0 20px ${unref( @@ -19,7 +19,7 @@ @touchcancel.passive="stop" > <Component /> - </div> + </button> </template> <script setup lang="tsx"> @@ -53,7 +53,6 @@ const { start, stop } = setupHoldToClick(toRef(props, "onClick"), toRef(props, " font-size: 40px; color: rgba(0, 0, 0, 0.5); text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.25); - box-shadow: -4px -4px 4px rgba(0, 0, 0, 0.25) inset, 0px 0px 20px var(--background); display: flex; } diff --git a/src/features/trees/tree.tsx b/src/features/trees/tree.tsx index 7d2ded6..8c37923 100644 --- a/src/features/trees/tree.tsx +++ b/src/features/trees/tree.tsx @@ -1,13 +1,13 @@ -import type { OptionsFunc, Replace } from "features/feature"; import { Link } from "features/links/links"; import type { Reset } from "features/reset"; import type { Resource } from "features/resources/resource"; import { displayResource } from "features/resources/resource"; import Tree from "features/trees/Tree.vue"; import TreeNode from "features/trees/TreeNode.vue"; +import { noPersist } from "game/persistence"; import type { DecimalSource } from "util/bignum"; import Decimal, { format, formatWhole } from "util/bignum"; -import { ProcessedRefOrGetter, processGetter } from "util/computed"; +import { processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue"; import type { MaybeRef, MaybeRefOrGetter, Ref } from "vue"; @@ -41,31 +41,32 @@ export interface TreeNodeOptions extends VueFeatureOptions { /** * The properties that are added onto a processed {@link TreeNodeOptions} to create an {@link TreeNode}. */ -export interface BaseTreeNode extends VueFeature { +export interface TreeNode extends VueFeature { + /** Whether or not this tree node can be clicked. */ + canClick?: MaybeRef<boolean>; + /** The background color for this node. */ + color?: MaybeRef<string>; + /** The label to display on this tree node. */ + display?: MaybeRef<Renderable>; + /** The color of the glow effect shown to notify the user there's something to do with this node. */ + glowColor?: MaybeRef<string>; + /** A reset object attached to this node, used for propagating resets through the tree. */ + reset?: Reset; + /** A function that is called when the tree node is clicked. */ + onClick?: (e?: MouseEvent | TouchEvent) => void; + /** A function that is called when the tree node is held down. */ + onHold?: VoidFunction; /** A symbol that helps identify features of the same type. */ type: typeof TreeNodeType; } -/** An object that represents a node on a tree. */ -export type TreeNode = Replace< - TreeNodeOptions & BaseTreeNode, - { - canClick: MaybeRef<boolean>; - color: ProcessedRefOrGetter<TreeNodeOptions["color"]>; - display: ProcessedRefOrGetter<TreeNodeOptions["display"]>; - glowColor: ProcessedRefOrGetter<TreeNodeOptions["glowColor"]>; - } ->; - /** * Lazily creates a tree node with the given options. * @param optionsFunc Tree Node options. */ -export function createTreeNode<T extends TreeNodeOptions>( - optionsFunc?: OptionsFunc<T, BaseTreeNode, TreeNode> -) { - return createLazyProxy(feature => { - const options = optionsFunc?.call(feature, feature as TreeNode) ?? ({} as T); +export function createTreeNode<T extends TreeNodeOptions>(optionsFunc?: () => T) { + return createLazyProxy(() => { + const options = optionsFunc?.() ?? ({} as T); const { canClick, color, display, glowColor, onClick, onHold, ...props } = options; const treeNode = { @@ -131,9 +132,21 @@ export interface TreeOptions extends VueFeatureOptions { onReset?: (node: TreeNode) => void; } -export interface BaseTree extends VueFeature { +export interface Tree extends VueFeature { + /** The nodes within the tree, in a 2D array. */ + nodes: MaybeRef<TreeNode[][]>; + /** Nodes to show on the left side of the tree. */ + leftSideNodes?: MaybeRef<TreeNode[]>; + /** Nodes to show on the right side of the tree. */ + rightSideNodes?: MaybeRef<TreeNode[]>; + /** The branches between nodes within this tree. */ + branches?: MaybeRef<TreeBranch[]>; + /** How to propagate resets through the tree. */ + resetPropagation?: ResetPropagation; + /** A function that is called when a node within the tree is reset. */ + onReset?: (node: TreeNode) => void; /** The link objects for each of the branches of the tree. */ - links: Ref<Link[]>; + links: MaybeRef<Link[]>; /** Cause a reset on this node and propagate it through the tree according to {@link TreeOptions.resetPropagation}. */ reset: (node: TreeNode) => void; /** A flag that is true while the reset is still propagating through the tree. */ @@ -144,35 +157,29 @@ export interface BaseTree extends VueFeature { type: typeof TreeType; } -/** An object that represents a feature that is a tree of nodes with branches between them. Contains support for reset mechanics that can propagate through the tree. */ -export type Tree = Replace< - TreeOptions & BaseTree, - { - nodes: ProcessedRefOrGetter<TreeOptions["nodes"]>; - leftSideNodes: ProcessedRefOrGetter<TreeOptions["leftSideNodes"]>; - rightSideNodes: ProcessedRefOrGetter<TreeOptions["rightSideNodes"]>; - branches: ProcessedRefOrGetter<TreeOptions["branches"]>; - } ->; - /** * Lazily creates a tree with the given options. * @param optionsFunc Tree options. */ -export function createTree<T extends TreeOptions>(optionsFunc: OptionsFunc<T, BaseTree, Tree>) { - return createLazyProxy(feature => { - const options = optionsFunc.call(feature, feature as Tree); +export function createTree<T extends TreeOptions>(optionsFunc: () => T) { + return createLazyProxy(() => { + const options = optionsFunc(); const { - branches, + branches: _branches, nodes, leftSideNodes, rightSideNodes, - reset, resetPropagation, onReset, + style: _style, ...props } = options; + const style = processGetter(_style); + options.style = () => ({ position: "static", ...(unref(style) ?? {}) }); + + const branches = _branches == null ? undefined : processGetter(_branches); + const tree = { type: TreeType, ...(props as Omit<typeof props, keyof VueFeature | keyof TreeOptions>), @@ -184,25 +191,23 @@ export function createTree<T extends TreeOptions>(optionsFunc: OptionsFunc<T, Ba branches={tree.branches} /> )), - branches: processGetter(branches), + branches, isResetting: ref(false), resettingNode: shallowRef<TreeNode | null>(null), nodes: processGetter(nodes), leftSideNodes: processGetter(leftSideNodes), rightSideNodes: processGetter(rightSideNodes), - links: processGetter(branches) ?? [], + links: branches == null ? [] : noPersist(branches), resetPropagation, onReset, - reset: - reset ?? - function (node: TreeNode) { - tree.isResetting.value = true; - tree.resettingNode.value = node; - tree.resetPropagation?.(tree, node); - tree.onReset?.(node); - tree.isResetting.value = false; - tree.resettingNode.value = null; - } + reset: function (node: TreeNode) { + tree.isResetting.value = true; + tree.resettingNode.value = node; + tree.resetPropagation?.(tree, node); + tree.onReset?.(node); + tree.isResetting.value = false; + tree.resettingNode.value = null; + } } satisfies Tree; return tree; diff --git a/src/game/boards/CircleProgress.vue b/src/game/boards/CircleProgress.vue index abe748d..6886fa0 100644 --- a/src/game/boards/CircleProgress.vue +++ b/src/game/boards/CircleProgress.vue @@ -12,7 +12,7 @@ <script setup lang="ts"> import type { SVGAttributes } from "vue"; -interface CircleProgressProps extends SVGAttributes { +interface CircleProgressProps extends /* @vue-ignore */ SVGAttributes { r: number; progress: number; stroke: string; diff --git a/src/game/boards/Draggable.vue b/src/game/boards/Draggable.vue index 9b4a2fa..a052513 100644 --- a/src/game/boards/Draggable.vue +++ b/src/game/boards/Draggable.vue @@ -2,10 +2,11 @@ <div class="board-node" :style="`transform: translate(calc(${unref(position).x}px - 50%), ${unref(position).y}px);`" - @mousedown="e => mouseDown(e)" - @touchstart.passive="e => mouseDown(e)" - @mouseup="e => mouseUp(e)" - @touchend.passive="e => mouseUp(e)" + @click.capture.stop="() => {}" + @mousedown="mouseDown" + @touchstart.passive="mouseDown" + @mouseup.capture="mouseUp" + @touchend.passive="mouseUp" > <slot /> </div> diff --git a/src/game/boards/SquareProgress.vue b/src/game/boards/SquareProgress.vue index 7e83c5d..9db9917 100644 --- a/src/game/boards/SquareProgress.vue +++ b/src/game/boards/SquareProgress.vue @@ -14,7 +14,7 @@ <script setup lang="ts"> import type { SVGAttributes } from "vue"; -interface SquareProgressProps extends SVGAttributes { +interface SquareProgressProps extends /* @vue-ignore */ SVGAttributes { size: number; progress: number; stroke: string; diff --git a/src/game/boards/board.tsx b/src/game/boards/board.tsx index 15b9274..80d7a08 100644 --- a/src/game/boards/board.tsx +++ b/src/game/boards/board.tsx @@ -1,14 +1,15 @@ -import Board from "./Board.vue"; -import Draggable from "./Draggable.vue"; import { globalBus } from "game/events"; -import { Persistent, persistent } from "game/persistence"; +import { DefaultValue, Persistent, persistent } from "game/persistence"; import type { PanZoom } from "panzoom"; import { Direction, isFunction } from "util/common"; import { processGetter } from "util/computed"; +import { createLazyProxy, runAfterEvaluation } from "util/proxies"; import { Renderable, VueFeature } from "util/vue"; -import type { ComponentPublicInstance, MaybeRef, MaybeRefOrGetter, Ref } from "vue"; +import type { ComponentPublicInstance, ComputedRef, MaybeRef, MaybeRefOrGetter, Ref } from "vue"; import { computed, ref, unref, watchEffect } from "vue"; import panZoom from "vue-panzoom"; +import Board from "./Board.vue"; +import Draggable from "./Draggable.vue"; // Register panzoom so it can be used in Board.vue globalBus.on("setupVue", app => panZoom.install(app)); @@ -254,46 +255,85 @@ export interface MakeDraggableOptions<T> { initialPosition?: NodePosition; } +/** Contains all the data tied to making a vue feature draggable */ +export interface Draggable<T> extends MakeDraggableOptions<T> { + /** The current position of the node on the board. */ + position: Persistent<NodePosition>; + /** The current position, plus the current offset from being dragged. */ + computedPosition: ComputedRef<NodePosition>; +} + /** * Makes a vue feature draggable on a Board. * @param element The vue feature to make draggable. * @param options The options to configure the dragging behavior. */ -export function makeDraggable<T>( +export function makeDraggable<T, S extends MakeDraggableOptions<T>>( element: VueFeature, - options: MakeDraggableOptions<T> -): asserts element is VueFeature & { position: Persistent<NodePosition> } { - const position = persistent(options.initialPosition ?? { x: 0, y: 0 }); - (element as VueFeature & { position: Persistent<NodePosition> }).position = position; - const computedPosition = computed(() => { - if (options.nodeBeingDragged.value === options.id) { - return { - x: position.value.x + options.dragDelta.value.x, - y: position.value.y + options.dragDelta.value.y - }; - } - return position.value; + optionsFunc: () => S +): asserts element is VueFeature & { draggable: Draggable<T> } { + const position = persistent<NodePosition>({ x: 0, y: 0 }); + const draggable = createLazyProxy(() => { + const options = optionsFunc(); + const { id, nodeBeingDragged, hasDragged, dragDelta, startDrag, endDrag, onMouseDown, onMouseUp, initialPosition, ...props } = options; + + position[DefaultValue] = initialPosition ?? position[DefaultValue]; + + const draggable = { + ...(props as Omit<typeof props, keyof VueFeature | keyof MakeDraggableOptions<S>>), + id, + nodeBeingDragged, + hasDragged, + dragDelta, + startDrag, + endDrag, + onMouseDown(e: MouseEvent | TouchEvent) { + if (onMouseDown?.(e) === false) { + return; + } + + if (nodeBeingDragged.value == null) { + startDrag(e, id); + } + }, + onMouseUp(e: MouseEvent | TouchEvent) { + // The element we're mapping may have their own click listeners, so we need to stop + // the propagation regardless, and can't rely on them passing through to the board. + endDrag(); + if (!hasDragged.value) { + onMouseUp?.(e); + } + e.stopPropagation(); + }, + initialPosition, + position, + computedPosition: computed(() => { + if (nodeBeingDragged.value === id) { + return { + x: position.value.x + dragDelta.value.x, + y: position.value.y + dragDelta.value.y + }; + } + return position.value; + }) + } satisfies Draggable<T>; + + return draggable; }); - function handleMouseDown(e: MouseEvent | TouchEvent) { - if (options.onMouseDown?.(e) === false) { - return; - } - - if (options.nodeBeingDragged.value == null) { - options.startDrag(e, options.id); - } - } - - function handleMouseUp(e: MouseEvent | TouchEvent) { - options.onMouseUp?.(e); - } - - element.wrappers.push(el => ( - <Draggable mouseDown={handleMouseDown} mouseUp={handleMouseUp} position={computedPosition}> - {el} - </Draggable> - )); + runAfterEvaluation(element, el => { + draggable.id; // Ensure draggable gets evaluated + (el as VueFeature & { draggable: Draggable<T> }).draggable = draggable; + element.wrappers.push(el => ( + <Draggable + mouseDown={draggable.onMouseDown} + mouseUp={draggable.onMouseUp} + position={draggable.computedPosition} + > + {el} + </Draggable> + )); + }); } /** An object that configures how to setup a list of actions using {@link setupActions}. */ diff --git a/src/game/formulas/formulas.ts b/src/game/formulas/formulas.ts index 03a53a7..7ffe6ec 100644 --- a/src/game/formulas/formulas.ts +++ b/src/game/formulas/formulas.ts @@ -1,8 +1,8 @@ import { Resource } from "features/resources/resource"; import { NonPersistent } from "game/persistence"; import Decimal, { DecimalSource, format } from "util/bignum"; -import { MaybeRefOrGetter, MaybeRef, processGetter } from "util/computed"; -import { Ref, computed, ref, unref } from "vue"; +import { processGetter } from "util/computed"; +import { MaybeRef, MaybeRefOrGetter, Ref, computed, ref, unref } from "vue"; import * as ops from "./operations"; import type { EvaluateFunction, diff --git a/src/game/layers.tsx b/src/game/layers.tsx index a5f3fe2..e6ab118 100644 --- a/src/game/layers.tsx +++ b/src/game/layers.tsx @@ -1,14 +1,13 @@ import Modal from "components/modals/Modal.vue"; -import type { OptionsFunc, Replace } from "features/feature"; import { globalBus } from "game/events"; import type { Persistent } from "game/persistence"; import { persistent } from "game/persistence"; import player from "game/player"; import type { Emitter } from "nanoevents"; import { createNanoEvents } from "nanoevents"; -import { ProcessedRefOrGetter, processGetter } from "util/computed"; +import { processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; -import { Renderable } from "util/vue"; +import { render, Renderable } from "util/vue"; import { computed, type CSSProperties, @@ -163,20 +162,45 @@ export interface BaseLayer { } /** An unit of game content. Displayed to the user as a tab or modal. */ -export type Layer = Replace< - Replace<LayerOptions, BaseLayer>, - { - color?: ProcessedRefOrGetter<LayerOptions["color"]>; - display: ProcessedRefOrGetter<LayerOptions["display"]>; - classes?: ProcessedRefOrGetter<LayerOptions["classes"]>; - style?: ProcessedRefOrGetter<LayerOptions["style"]>; - name: MaybeRef<string>; - minWidth: MaybeRef<string | number>; - minimizable: MaybeRef<boolean>; - minimizedDisplay?: ProcessedRefOrGetter<LayerOptions["minimizedDisplay"]>; - forceHideGoBack?: ProcessedRefOrGetter<LayerOptions["forceHideGoBack"]>; - } ->; +export interface Layer extends BaseLayer { + /** The color of the layer, used to theme the entire layer's display. */ + color?: MaybeRef<string>; + /** + * The layout of this layer's features. + * When the layer is open in {@link game/player.PlayerData.tabs}, this is the content that is displayed. + */ + display: MaybeRef<Renderable>; + /** An object of classes that should be applied to the display. */ + classes?: MaybeRef<Record<string, boolean>>; + /** Styles that should be applied to the display. */ + style?: MaybeRef<CSSProperties>; + /** + * The name of the layer, used on minimized tabs. + * Defaults to {@link BaseLayer.id}. + */ + name?: MaybeRef<string>; + /** + * Whether or not the layer can be minimized. + * Defaults to true. + */ + minimizable?: MaybeRef<boolean>; + /** + * The layout of this layer's features. + * When the layer is open in {@link game/player.PlayerData.tabs}, but the tab is {@link Layer.minimized} this is the content that is displayed. + */ + minimizedDisplay?: MaybeRef<Renderable>; + /** + * Whether or not to force the go back button to be hidden. + * If true, go back will be hidden regardless of {@link data/projInfo.allowGoBack}. + */ + forceHideGoBack?: MaybeRef<boolean>; + /** + * A CSS min-width value that is applied to the layer. + * Can be a number, in which case the unit is assumed to be px. + * Defaults to 600px. + */ + minWidth?: MaybeRef<number | string>; +} /** * When creating layers, this object a map of layer ID to a set of any created persistent refs in order to check they're all included in the final layer object. @@ -193,7 +217,7 @@ export const addingLayers: string[] = []; */ export function createLayer<T extends LayerOptions>( id: string, - optionsFunc: OptionsFunc<T, BaseLayer> + optionsFunc: (layer: BaseLayer) => T & ThisType<Layer & Omit<T, keyof Layer>> ) { return createLazyProxy(() => { const emitter = createNanoEvents<LayerEvents>(); @@ -208,7 +232,7 @@ export function createLayer<T extends LayerOptions>( minimized: persistent(false, false) } satisfies BaseLayer; - const options = optionsFunc.call(baseLayer, baseLayer); + const options = optionsFunc(baseLayer); const { color, display, @@ -357,7 +381,7 @@ export function setupLayerModal(layer: Layer): { onUpdate:modelValue={value => (showModal.value = value)} v-slots={{ header: () => <h2>{unref(layer.name)}</h2>, - body: unref(layer.display) + body: () => render(layer.display) }} /> )) diff --git a/src/game/modifiers.tsx b/src/game/modifiers.tsx index 995b300..286c18c 100644 --- a/src/game/modifiers.tsx +++ b/src/game/modifiers.tsx @@ -1,5 +1,4 @@ import "components/common/modifiers.css"; -import type { OptionsFunc } from "features/feature"; import settings from "game/settings"; import type { DecimalSource } from "util/bignum"; import Decimal, { formatSmall } from "util/bignum"; @@ -59,13 +58,10 @@ export interface AdditiveModifierOptions { * @param optionsFunc Additive modifier options. */ export function createAdditiveModifier<T extends AdditiveModifierOptions, S = OperationModifier<T>>( - optionsFunc: OptionsFunc<T> + optionsFunc: () => T ) { - return createLazyProxy(feature => { - const { addend, description, enabled, smallerIsBetter } = optionsFunc.call( - feature, - feature - ); + return createLazyProxy(() => { + const { addend, description, enabled, smallerIsBetter } = optionsFunc(); const processedAddend = processGetter(addend); const processedDescription = processGetter(description); @@ -123,12 +119,9 @@ export interface MultiplicativeModifierOptions { export function createMultiplicativeModifier< T extends MultiplicativeModifierOptions, S = OperationModifier<T> ->(optionsFunc: OptionsFunc<T>) { - return createLazyProxy(feature => { - const { multiplier, description, enabled, smallerIsBetter } = optionsFunc.call( - feature, - feature - ); +>(optionsFunc: () => T) { + return createLazyProxy(() => { + const { multiplier, description, enabled, smallerIsBetter } = optionsFunc(); const processedMultiplier = processGetter(multiplier); const processedDescription = processGetter(description); @@ -187,10 +180,10 @@ export interface ExponentialModifierOptions { export function createExponentialModifier< T extends ExponentialModifierOptions, S = OperationModifier<T> ->(optionsFunc: OptionsFunc<T>) { - return createLazyProxy(feature => { +>(optionsFunc: () => T) { + return createLazyProxy(() => { const { exponent, description, enabled, supportLowNumbers, smallerIsBetter } = - optionsFunc.call(feature, feature); + optionsFunc(); const processedExponent = processGetter(exponent); const processedDescription = processGetter(description); diff --git a/src/game/persistence.ts b/src/game/persistence.ts index 20c0534..e4904c4 100644 --- a/src/game/persistence.ts +++ b/src/game/persistence.ts @@ -258,7 +258,7 @@ globalBus.on("addLayer", (layer: Layer, saveData: Record<string, unknown>) => { Object.keys(obj).forEach(key => { let value = obj[key]; if (value != null && typeof value === "object") { - if ((value as Record<PropertyKey, unknown>)[SkipPersistence] === true) { + if (SkipPersistence in value && value[SkipPersistence] === true) { return; } if (ProxyState in value) { @@ -364,7 +364,7 @@ globalBus.on("addLayer", (layer: Layer, saveData: Record<string, unknown>) => { return; } console.error( - `Created persistent ref in ${layer.id} without registering it to the layer!`, + `Created persistent ref in "${layer.id}" without registering it to the layer!`, "Make sure to include everything persistent in the returned object.\n\nCreated at:\n" + persistent[StackTrace] ); diff --git a/src/game/requirements.tsx b/src/game/requirements.tsx index a3b260f..ba3088e 100644 --- a/src/game/requirements.tsx +++ b/src/game/requirements.tsx @@ -1,4 +1,4 @@ -import { isVisible, OptionsFunc, Replace, Visibility } from "features/feature"; +import { isVisible, Visibility } from "features/feature"; import { displayResource, Resource } from "features/resources/resource"; import Decimal, { DecimalSource } from "lib/break_eternity"; import { processGetter } from "util/computed"; @@ -65,7 +65,7 @@ export interface CostRequirementOptions { */ visibility?: MaybeRefOrGetter<Visibility.Visible | Visibility.None | boolean>; /** - * Pass-through to {@link Requirement.requiresPay}. If not set to false, the default {@link pay} function will remove {@link cost} from {@link resource}. + * Pass-through to {@link Requirement["requiresPay"]}. If not set to false, the default {@link pay} function will remove {@link cost} from {@link resource}. */ requiresPay?: MaybeRefOrGetter<boolean>; /** @@ -88,26 +88,42 @@ export interface CostRequirementOptions { pay?: (amount?: DecimalSource) => void; } -export type CostRequirement = Replace< - Requirement & CostRequirementOptions, - { - cost: MaybeRef<DecimalSource> | GenericFormula; - visibility: MaybeRef<Visibility.Visible | Visibility.None | boolean>; - requiresPay: MaybeRef<boolean>; - cumulativeCost: MaybeRef<boolean>; - canMaximize: MaybeRef<boolean>; - } ->; +export interface CostRequirement extends Requirement { + /** + * The resource that will be checked for meeting the {@link cost}. + */ + resource: Resource; + /** + * The amount of {@link resource} that must be met for this requirement. You can pass a formula, in which case maximizing will work out of the box (assuming its invertible and, for more accurate calculations, its integral is invertible). If you don't pass a formula then you can still support maximizing by passing a custom {@link pay} function. + */ + cost: MaybeRef<DecimalSource> | GenericFormula; + /** + * When calculating multiple levels to be handled at once, whether it should consider resources used for each level as spent. Setting this to false causes calculations to be faster with larger numbers and supports more math functions. + * @see {Formula} + */ + cumulativeCost: MaybeRef<boolean>; + /** + * Upper limit on levels that can be performed at once. Defaults to 1. + */ + maxBulkAmount?: MaybeRef<DecimalSource>; + /** + * When calculating requirement for multiple levels, how many should be directly summed for increase accuracy. High numbers can cause lag. Defaults to 10 if cumulative cost, 0 otherwise. + */ + directSum?: MaybeRef<number>; + /** + * Pass-through to {@link Requirement.pay}. May be required for maximizing support. + * @see {@link cost} for restrictions on maximizing support. + */ + pay?: (amount?: DecimalSource) => void; +} /** * Lazily creates a requirement with the given options, that is based on meeting an amount of a resource. * @param optionsFunc Cost requirement options. */ -export function createCostRequirement<T extends CostRequirementOptions>( - optionsFunc: OptionsFunc<T> -) { +export function createCostRequirement<T extends CostRequirementOptions>(optionsFunc: () => T) { return createLazyProxy(feature => { - const options = optionsFunc.call(feature, feature); + const options = optionsFunc.call(feature); const { visibility, cost, diff --git a/src/lib/pixi.ts b/src/lib/pixi.ts index 9db8382..ad62daa 100644 --- a/src/lib/pixi.ts +++ b/src/lib/pixi.ts @@ -1,7 +1,4 @@ -import { Application } from "@pixi/app"; -import { BatchRenderer, Renderer } from "@pixi/core"; +import { BatchRenderer, extensions } from "@pixi/core"; import { TickerPlugin } from "@pixi/ticker"; -Application.registerPlugin(TickerPlugin); - -Renderer.registerPlugin("batch", BatchRenderer); +extensions.add(TickerPlugin, BatchRenderer); diff --git a/src/util/computed.ts b/src/util/computed.ts index 869b38f..c1ad5d3 100644 --- a/src/util/computed.ts +++ b/src/util/computed.ts @@ -1,13 +1,7 @@ import { isFunction } from "util/common"; -import type { ComputedRef, MaybeRef, Ref, UnwrapRef } from "vue"; +import type { ComputedRef } from "vue"; import { computed } from "vue"; -export type ProcessedRefOrGetter<T> = T extends () => infer S - ? Ref<S> - : T extends undefined - ? undefined - : MaybeRef<NonNullable<UnwrapRef<T>>>; - export function processGetter<T>(obj: T): T extends () => infer S ? ComputedRef<S> : T { if (isFunction(obj)) { return computed(obj) as ReturnType<typeof processGetter<T>>; diff --git a/src/util/proxies.ts b/src/util/proxies.ts index 8447931..31984e8 100644 --- a/src/util/proxies.ts +++ b/src/util/proxies.ts @@ -1,34 +1,7 @@ -import type { Persistent } from "game/persistence"; import { NonPersistent } from "game/persistence"; -import Decimal from "util/bignum"; export const ProxyState = Symbol("ProxyState"); -export const ProxyPath = Symbol("ProxyPath"); - -export type ProxiedWithState<T> = - NonNullable<T> extends Record<PropertyKey, unknown> - ? NonNullable<T> extends Decimal - ? T - : { - [K in keyof T]: ProxiedWithState<T[K]>; - } & { - [ProxyState]: T; - [ProxyPath]: string[]; - } - : T; - -export type Proxied<T> = - NonNullable<T> extends Record<PropertyKey, unknown> - ? NonNullable<T> extends Persistent<infer S> - ? NonPersistent<S> - : NonNullable<T> extends Decimal - ? T - : { - [K in keyof T]: Proxied<T[K]>; - } & { - [ProxyState]: T; - } - : T; +export const AfterEvaluation = Symbol("AfterEvaluation"); // Takes a function that returns an object and pretends to be that object // Note that the object is lazily calculated @@ -39,23 +12,36 @@ export function createLazyProxy<T extends object, S extends T>( const obj: S & Partial<T> = baseObject; let calculated = false; let calculating = false; + const toBeEvaluated: ((proxy: S & T) => void)[] = []; function calculateObj(): T { if (!calculated) { if (calculating) { - console.error("Cyclical dependency detected. Cannot evaluate lazy proxy."); + throw new Error("Cyclical dependency detected. Cannot evaluate lazy proxy."); } calculating = true; Object.assign(obj, objectFunc.call(obj, obj)); calculated = true; + toBeEvaluated.forEach(cb => cb(obj)); } return obj as S & T; } + function runAfterEvaluation(cb: (proxy: S & T) => void) { + if (calculated) { + cb(obj); + } else { + toBeEvaluated.push(cb); + } + } + return new Proxy(obj, { get(target, key) { if (key === ProxyState) { return calculateObj(); } + if (key === AfterEvaluation) { + return runAfterEvaluation; + } // eslint-disable-next-line @typescript-eslint/no-explicit-any const val = (calculateObj() as any)[key]; if (val != null && typeof val === "object" && NonPersistent in val) { @@ -70,7 +56,7 @@ export function createLazyProxy<T extends object, S extends T>( return true; }, has(target, key) { - if (key === ProxyState) { + if (key === ProxyState || key === AfterEvaluation) { return true; } return Reflect.has(calculateObj(), key); @@ -87,3 +73,11 @@ export function createLazyProxy<T extends object, S extends T>( } }) as S & T; } + +export function runAfterEvaluation<T extends object>(maybeProxy: T, callback: (object: T) => void) { + if (AfterEvaluation in maybeProxy) { + (maybeProxy[AfterEvaluation] as (callback: (object: T) => void) => void)(callback); + } else { + callback(maybeProxy); + } +} diff --git a/src/wrappers/marks/mark.tsx b/src/wrappers/marks/mark.tsx index ac85c33..ac0e810 100644 --- a/src/wrappers/marks/mark.tsx +++ b/src/wrappers/marks/mark.tsx @@ -1,6 +1,6 @@ import { type OptionsFunc } from "features/feature"; import { processGetter } from "util/computed"; -import { createLazyProxy } from "util/proxies"; +import { createLazyProxy, runAfterEvaluation } from "util/proxies"; import type { VueFeature } from "util/vue"; import { MaybeRef, MaybeRefOrGetter, unref } from "vue"; import MarkNode from "./MarkNode.vue"; @@ -25,10 +25,10 @@ export interface Mark { */ export function addMark<T extends MarkOptions>( element: VueFeature, - optionsFunc: OptionsFunc<T, Mark, Mark> -) { - const mark = createLazyProxy(feature => { - const options = optionsFunc.call(feature, feature as Mark); + optionsFunc: OptionsFunc<T, Mark> +): asserts element is VueFeature & { mark: Mark } { + const mark = createLazyProxy(() => { + const options = optionsFunc(); const { mark, ...props } = options; return { @@ -37,9 +37,11 @@ export function addMark<T extends MarkOptions>( } satisfies Mark; }); - element.wrappers.push(el => - Boolean(unref(mark.mark)) ? <MarkNode mark={mark.mark}>{el}</MarkNode> : <>{el}</> - ); - - return mark; + runAfterEvaluation(element, el => { + mark.mark; // Ensure mark gets evaluated + (element as VueFeature & { mark: Mark }).mark = mark; + el.wrappers.push(el => + Boolean(unref(mark.mark)) ? <MarkNode mark={mark.mark}>{el}</MarkNode> : <>{el}</> + ); + }); } diff --git a/src/wrappers/tooltips/tooltip.tsx b/src/wrappers/tooltips/tooltip.tsx index 1d582c5..453d317 100644 --- a/src/wrappers/tooltips/tooltip.tsx +++ b/src/wrappers/tooltips/tooltip.tsx @@ -1,8 +1,8 @@ -import { isVisible, type OptionsFunc, type Replace } from "features/feature"; +import { isVisible, type OptionsFunc } from "features/feature"; import { deletePersistent, persistent } from "game/persistence"; import { Direction } from "util/common"; -import { ProcessedRefOrGetter, processGetter } from "util/computed"; -import { createLazyProxy } from "util/proxies"; +import { processGetter } from "util/computed"; +import { createLazyProxy, runAfterEvaluation } from "util/proxies"; import { Renderable, vueFeatureMixin, type VueFeature, type VueFeatureOptions } from "util/vue"; import { MaybeRef, MaybeRefOrGetter, type Ref } from "vue"; import Tooltip from "wrappers/tooltips/Tooltip.vue"; @@ -30,26 +30,22 @@ export interface TooltipOptions extends VueFeatureOptions { yoffset?: MaybeRefOrGetter<string>; } -/** - * The properties that are added onto a processed {@link TooltipOptions} to create an {@link Tooltip}. - */ -export interface BaseTooltip extends VueFeature { +/** An object that represents a tooltip that appears when hovering over an element. */ +export interface Tooltip extends VueFeature { + /** Whether or not this tooltip can be pinned, meaning it'll stay visible even when not hovered. */ + pinnable?: boolean; + /** The text to display inside the tooltip. */ + display: MaybeRef<Renderable>; + /** The direction in which to display the tooltip */ + direction?: MaybeRef<Direction>; + /** The x offset of the tooltip, in px. */ + xoffset?: MaybeRef<string>; + /** The y offset of the tooltip, in px. */ + yoffset?: MaybeRef<string>; + /** Whether or not this tooltip is currently pinned. Undefined if {@link pinnable} is false. */ pinned?: Ref<boolean>; } -/** An object that represents a tooltip that appears when hovering over an element. */ -export type Tooltip = Replace< - Replace<TooltipOptions, BaseTooltip>, - { - pinnable: boolean; - pinned?: Ref<boolean>; - display: MaybeRef<Renderable>; - direction: MaybeRef<Direction>; - xoffset?: ProcessedRefOrGetter<TooltipOptions["xoffset"]>; - yoffset?: ProcessedRefOrGetter<TooltipOptions["yoffset"]>; - } ->; - /** * Creates a tooltip on the given element with the given options. * @param element The renderable feature to display the tooltip on. @@ -57,11 +53,11 @@ export type Tooltip = Replace< */ export function addTooltip<T extends TooltipOptions>( element: VueFeature, - optionsFunc: OptionsFunc<T, BaseTooltip, Tooltip> -) { + optionsFunc: OptionsFunc<T, Tooltip> +): asserts element is VueFeature & { tooltip: Tooltip } { const pinned = persistent<boolean>(false, false); - const tooltip = createLazyProxy(feature => { - const options = optionsFunc.call(feature, feature as Tooltip); + const tooltip = createLazyProxy(() => { + const options = optionsFunc(); const { pinnable, display, direction, xoffset, yoffset, ...props } = options; if (pinnable === false) { @@ -82,23 +78,25 @@ export function addTooltip<T extends TooltipOptions>( return tooltip; }); - element.wrappers.push(el => - isVisible(tooltip.visibility ?? true) ? ( - <Tooltip - pinned={tooltip.pinned} - display={tooltip.display} - classes={tooltip.classes} - style={tooltip.style} - direction={tooltip.direction} - xoffset={tooltip.xoffset} - yoffset={tooltip.yoffset} - > - {el} - </Tooltip> - ) : ( - <>{el}</> - ) - ); - - return tooltip; + runAfterEvaluation(element, el => { + tooltip.id; // Ensure tooltip gets evaluated + (el as VueFeature & { tooltip: Tooltip }).tooltip = tooltip; + el.wrappers.push(el => + isVisible(tooltip.visibility ?? true) ? ( + <Tooltip + pinned={tooltip.pinned} + display={tooltip.display} + classes={tooltip.classes} + style={tooltip.style} + direction={tooltip.direction} + xoffset={tooltip.xoffset} + yoffset={tooltip.yoffset} + > + {el} + </Tooltip> + ) : ( + <>{el}</> + ) + ); + }); } diff --git a/tests/game/__snapshots__/modifiers.test.ts.snap b/tests/game/__snapshots__/modifiers.test.ts.snap index 2d23e1f..b2257dd 100644 --- a/tests/game/__snapshots__/modifiers.test.ts.snap +++ b/tests/game/__snapshots__/modifiers.test.ts.snap @@ -13,37 +13,7 @@ exports[`Additive Modifiers > applies description correctly > with description 1 "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "test", ], "component": null, "ctx": null, @@ -146,37 +116,7 @@ exports[`Additive Modifiers > applies smallerIsBetter correctly > with smallerIs "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "test", ], "component": null, "ctx": null, @@ -279,37 +219,7 @@ exports[`Additive Modifiers > applies smallerIsBetter correctly > with smallerIs "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "test", ], "component": null, "ctx": null, @@ -412,37 +322,7 @@ exports[`Additive Modifiers > applies smallerIsBetter correctly > with smallerIs "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "test", ], "component": null, "ctx": null, @@ -545,37 +425,7 @@ exports[`Additive Modifiers > applies smallerIsBetter correctly > without smalle "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "test", ], "component": null, "ctx": null, @@ -678,37 +528,7 @@ exports[`Additive Modifiers > applies smallerIsBetter correctly > without smalle "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "test", ], "component": null, "ctx": null, @@ -811,37 +631,7 @@ exports[`Additive Modifiers > applies smallerIsBetter correctly > without smalle "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "test", ], "component": null, "ctx": null, @@ -1011,37 +801,7 @@ exports[`Create modifier sections > No optional values 1`] = ` "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Base", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Base", ], "component": null, "ctx": null, @@ -1140,37 +900,7 @@ exports[`Create modifier sections > No optional values 1`] = ` "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Test Desc", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Test Desc", ], "component": null, "ctx": null, @@ -1525,37 +1255,7 @@ exports[`Create modifier sections > With base 1`] = ` "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Base", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Base", ], "component": null, "ctx": null, @@ -1654,37 +1354,7 @@ exports[`Create modifier sections > With base 1`] = ` "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Test Desc", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Test Desc", ], "component": null, "ctx": null, @@ -2039,37 +1709,7 @@ exports[`Create modifier sections > With base 2`] = ` "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Based on", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Based on", ], "component": null, "ctx": null, @@ -2168,37 +1808,7 @@ exports[`Create modifier sections > With base 2`] = ` "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Test Desc", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Test Desc", ], "component": null, "ctx": null, @@ -2553,37 +2163,7 @@ exports[`Create modifier sections > With baseText 1`] = ` "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Based on", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Based on", ], "component": null, "ctx": null, @@ -2682,37 +2262,7 @@ exports[`Create modifier sections > With baseText 1`] = ` "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Test Desc", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Test Desc", ], "component": null, "ctx": null, @@ -3157,37 +2707,7 @@ exports[`Create modifier sections > With everything 1`] = ` "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Based on", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Based on", ], "component": null, "ctx": null, @@ -3286,37 +2806,7 @@ exports[`Create modifier sections > With everything 1`] = ` "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Test Desc", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Test Desc", ], "component": null, "ctx": null, @@ -3671,37 +3161,7 @@ exports[`Create modifier sections > With smallerIsBetter > smallerIsBetter = fal "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Base", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Base", ], "component": null, "ctx": null, @@ -3800,37 +3260,7 @@ exports[`Create modifier sections > With smallerIsBetter > smallerIsBetter = fal "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Test Desc", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Test Desc", ], "component": null, "ctx": null, @@ -4185,37 +3615,7 @@ exports[`Create modifier sections > With smallerIsBetter > smallerIsBetter = fal "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Base", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Base", ], "component": null, "ctx": null, @@ -4314,37 +3714,7 @@ exports[`Create modifier sections > With smallerIsBetter > smallerIsBetter = fal "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Test Desc", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Test Desc", ], "component": null, "ctx": null, @@ -4699,37 +4069,7 @@ exports[`Create modifier sections > With smallerIsBetter > smallerIsBetter = fal "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Base", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Base", ], "component": null, "ctx": null, @@ -4828,37 +4168,7 @@ exports[`Create modifier sections > With smallerIsBetter > smallerIsBetter = fal "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Test Desc", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Test Desc", ], "component": null, "ctx": null, @@ -5213,37 +4523,7 @@ exports[`Create modifier sections > With smallerIsBetter > smallerIsBetter = tru "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Base", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Base", ], "component": null, "ctx": null, @@ -5342,37 +4622,7 @@ exports[`Create modifier sections > With smallerIsBetter > smallerIsBetter = tru "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Test Desc", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Test Desc", ], "component": null, "ctx": null, @@ -5727,37 +4977,7 @@ exports[`Create modifier sections > With smallerIsBetter > smallerIsBetter = tru "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Base", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Base", ], "component": null, "ctx": null, @@ -5856,37 +5076,7 @@ exports[`Create modifier sections > With smallerIsBetter > smallerIsBetter = tru "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Test Desc", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Test Desc", ], "component": null, "ctx": null, @@ -6241,37 +5431,7 @@ exports[`Create modifier sections > With smallerIsBetter > smallerIsBetter = tru "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Base", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Base", ], "component": null, "ctx": null, @@ -6370,37 +5530,7 @@ exports[`Create modifier sections > With smallerIsBetter > smallerIsBetter = tru "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Test Desc", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Test Desc", ], "component": null, "ctx": null, @@ -6845,37 +5975,7 @@ exports[`Create modifier sections > With subtitle 1`] = ` "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Base", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Base", ], "component": null, "ctx": null, @@ -6974,37 +6074,7 @@ exports[`Create modifier sections > With subtitle 1`] = ` "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Test Desc", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Test Desc", ], "component": null, "ctx": null, @@ -7359,37 +6429,7 @@ exports[`Create modifier sections > With unit 1`] = ` "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Base", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Base", ], "component": null, "ctx": null, @@ -7488,37 +6528,7 @@ exports[`Create modifier sections > With unit 1`] = ` "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "Test Desc", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "Test Desc", ], "component": null, "ctx": null, @@ -7806,37 +6816,7 @@ exports[`Exponential Modifiers > applies description correctly > with descriptio "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "test", null, ], "component": null, @@ -7968,37 +6948,7 @@ exports[`Exponential Modifiers > applies smallerIsBetter correctly > with smalle "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "test", null, ], "component": null, @@ -8130,37 +7080,7 @@ exports[`Exponential Modifiers > applies smallerIsBetter correctly > with smalle "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "test", null, ], "component": null, @@ -8292,37 +7212,7 @@ exports[`Exponential Modifiers > applies smallerIsBetter correctly > with smalle "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "test", null, ], "component": null, @@ -8454,37 +7344,7 @@ exports[`Exponential Modifiers > applies smallerIsBetter correctly > without sma "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "test", null, ], "component": null, @@ -8616,37 +7476,7 @@ exports[`Exponential Modifiers > applies smallerIsBetter correctly > without sma "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "test", null, ], "component": null, @@ -8778,37 +7608,7 @@ exports[`Exponential Modifiers > applies smallerIsBetter correctly > without sma "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "test", null, ], "component": null, @@ -8940,37 +7740,7 @@ exports[`Multiplicative Modifiers > applies description correctly > with descrip "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "test", ], "component": null, "ctx": null, @@ -9101,37 +7871,7 @@ exports[`Multiplicative Modifiers > applies smallerIsBetter correctly > with sma "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "test", ], "component": null, "ctx": null, @@ -9262,37 +8002,7 @@ exports[`Multiplicative Modifiers > applies smallerIsBetter correctly > with sma "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "test", ], "component": null, "ctx": null, @@ -9423,37 +8133,7 @@ exports[`Multiplicative Modifiers > applies smallerIsBetter correctly > with sma "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "test", ], "component": null, "ctx": null, @@ -9584,37 +8264,7 @@ exports[`Multiplicative Modifiers > applies smallerIsBetter correctly > without "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "test", ], "component": null, "ctx": null, @@ -9745,37 +8395,7 @@ exports[`Multiplicative Modifiers > applies smallerIsBetter correctly > without "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "test", ], "component": null, "ctx": null, @@ -9906,37 +8526,7 @@ exports[`Multiplicative Modifiers > applies smallerIsBetter correctly > without "anchor": null, "appContext": null, "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, + "test", ], "component": null, "ctx": null, @@ -10055,5104 +8645,3283 @@ exports[`Multiplicative Modifiers > applies smallerIsBetter correctly > without `; exports[`Sequential Modifiers > applies description correctly > with both 1`] = ` -{ - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), +[ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": "×", - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 8, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-txt), + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": "×", + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": null, + "ref": null, + "scopeId": null, + "shapeFlag": 8, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": Symbol(v-txt), + }, + "0.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "color: var(--danger)", }, - "0.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "color: var(--danger)", + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", -} +] `; exports[`Sequential Modifiers > applies description correctly > with description 1`] = ` -{ - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "+", - "0.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", +[ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": null, - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 0, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": "×", - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 8, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-txt), - }, - "0.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "color: var(--danger)", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": null, - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 0, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - null, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": "^", - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 8, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-txt), - }, - "0.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "color: var(--danger)", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "+", + "0.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), -} + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": "×", + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": null, + "ref": null, + "scopeId": null, + "shapeFlag": 8, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": Symbol(v-txt), + }, + "0.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "color: var(--danger)", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + null, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": "^", + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": null, + "ref": null, + "scopeId": null, + "shapeFlag": 8, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": Symbol(v-txt), + }, + "0.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "color: var(--danger)", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, +] `; exports[`Sequential Modifiers > applies smallerIsBetter correctly > with both > negative value 1`] = ` -{ - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", +[ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "", - "-5.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": null, - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 0, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "", + "-5.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "", }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "", - "-5.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "color: var(--danger)", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), -} + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "", + "-5.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "color: var(--danger)", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, +] `; exports[`Sequential Modifiers > applies smallerIsBetter correctly > with both > positive value 1`] = ` -{ - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", +[ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "+", - "5.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "color: var(--danger)", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": null, - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 0, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "+", + "5.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "color: var(--danger)", }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "+", - "5.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), -} + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "+", + "5.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, +] `; exports[`Sequential Modifiers > applies smallerIsBetter correctly > with both > zero value 1`] = ` -{ - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", +[ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "+", - "0.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": null, - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 0, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "+", + "0.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "", }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "+", - "0.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), -} + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "+", + "0.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, +] `; exports[`Sequential Modifiers > applies smallerIsBetter correctly > with smallerIsBetter true > negative value 1`] = ` -{ - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "", - "-5.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", +[ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": null, - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 0, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": "×", - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 8, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-txt), - }, - "-5.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": null, - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 0, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - null, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": "^", - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 8, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-txt), - }, - "-5.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "", + "-5.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), -} + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": "×", + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": null, + "ref": null, + "scopeId": null, + "shapeFlag": 8, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": Symbol(v-txt), + }, + "-5.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + null, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": "^", + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": null, + "ref": null, + "scopeId": null, + "shapeFlag": 8, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": Symbol(v-txt), + }, + "-5.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, +] `; exports[`Sequential Modifiers > applies smallerIsBetter correctly > with smallerIsBetter true > positive value 1`] = ` -{ - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "+", - "5.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "color: var(--danger)", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", +[ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": null, - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 0, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": "×", - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 8, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-txt), - }, - "5.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "color: var(--danger)", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": null, - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 0, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - null, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": "^", - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 8, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-txt), - }, - "5.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "color: var(--danger)", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "+", + "5.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "color: var(--danger)", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), -} + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": "×", + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": null, + "ref": null, + "scopeId": null, + "shapeFlag": 8, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": Symbol(v-txt), + }, + "5.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "color: var(--danger)", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + null, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": "^", + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": null, + "ref": null, + "scopeId": null, + "shapeFlag": 8, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": Symbol(v-txt), + }, + "5.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "color: var(--danger)", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, +] `; exports[`Sequential Modifiers > applies smallerIsBetter correctly > with smallerIsBetter true > zero value 1`] = ` -{ - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "+", - "0.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", +[ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": null, - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 0, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": "×", - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 8, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-txt), - }, - "0.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": null, - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 0, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - null, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": "^", - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 8, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-txt), - }, - "0.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "+", + "0.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), -} + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": "×", + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": null, + "ref": null, + "scopeId": null, + "shapeFlag": 8, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": Symbol(v-txt), + }, + "0.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + null, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": "^", + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": null, + "ref": null, + "scopeId": null, + "shapeFlag": 8, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": Symbol(v-txt), + }, + "0.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, +] `; exports[`Sequential Modifiers > applies smallerIsBetter correctly > without smallerIsBetter false > negative value 1`] = ` -{ - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "", - "-5.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "color: var(--danger)", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", +[ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": null, - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 0, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": "×", - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 8, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-txt), - }, - "-5.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "color: var(--danger)", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": null, - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 0, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - null, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": "^", - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 8, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-txt), - }, - "-5.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "color: var(--danger)", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "", + "-5.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "color: var(--danger)", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), -} + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": "×", + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": null, + "ref": null, + "scopeId": null, + "shapeFlag": 8, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": Symbol(v-txt), + }, + "-5.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "color: var(--danger)", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + null, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": "^", + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": null, + "ref": null, + "scopeId": null, + "shapeFlag": 8, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": Symbol(v-txt), + }, + "-5.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "color: var(--danger)", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, +] `; exports[`Sequential Modifiers > applies smallerIsBetter correctly > without smallerIsBetter false > positive value 1`] = ` -{ - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "+", - "5.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", +[ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": null, - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 0, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": "×", - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 8, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-txt), - }, - "5.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": null, - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 0, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - null, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": "^", - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 8, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-txt), - }, - "5.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "+", + "5.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), -} + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": "×", + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": null, + "ref": null, + "scopeId": null, + "shapeFlag": 8, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": Symbol(v-txt), + }, + "5.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + null, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": "^", + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": null, + "ref": null, + "scopeId": null, + "shapeFlag": 8, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": Symbol(v-txt), + }, + "5.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, +] `; exports[`Sequential Modifiers > applies smallerIsBetter correctly > without smallerIsBetter false > zero value 1`] = ` -{ - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "+", - "0.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", +[ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": null, - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 0, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": "×", - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 8, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-txt), - }, - "0.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "color: var(--danger)", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": null, - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 0, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - "test", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), - }, - null, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-description", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": [ - { - "__v_isVNode": true, - "__v_skip": true, - "anchor": null, - "appContext": null, - "children": "^", - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 8, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-txt), - }, - "0.00", - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-amount", - "style": "color: var(--danger)", - }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "span", - }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": { - "class": "modifier-container", + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", }, - "ref": null, - "scopeId": null, - "shapeFlag": 17, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": "div", + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "+", + "0.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", }, - ], - "component": null, - "ctx": null, - "dirs": null, - "dynamicChildren": null, - "dynamicProps": null, - "el": null, - "key": null, - "patchFlag": 0, - "props": null, - "ref": null, - "scopeId": null, - "shapeFlag": 16, - "slotScopeIds": null, - "ssContent": null, - "ssFallback": null, - "staticCount": 0, - "suspense": null, - "target": null, - "targetAnchor": null, - "targetStart": null, - "transition": null, - "type": Symbol(v-fgt), -} + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": "×", + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": null, + "ref": null, + "scopeId": null, + "shapeFlag": 8, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": Symbol(v-txt), + }, + "0.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "color: var(--danger)", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + "test", + null, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-description", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": [ + { + "__v_isVNode": true, + "__v_skip": true, + "anchor": null, + "appContext": null, + "children": "^", + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": null, + "ref": null, + "scopeId": null, + "shapeFlag": 8, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": Symbol(v-txt), + }, + "0.00", + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-amount", + "style": "color: var(--danger)", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "span", + }, + ], + "component": null, + "ctx": null, + "dirs": null, + "dynamicChildren": null, + "dynamicProps": null, + "el": null, + "key": null, + "patchFlag": 0, + "props": { + "class": "modifier-container", + }, + "ref": null, + "scopeId": null, + "shapeFlag": 17, + "slotScopeIds": null, + "ssContent": null, + "ssFallback": null, + "staticCount": 0, + "suspense": null, + "target": null, + "targetAnchor": null, + "targetStart": null, + "transition": null, + "type": "div", + }, +] `; diff --git a/tests/game/modifiers.test.ts b/tests/game/modifiers.test.ts index be3726a..d7ef786 100644 --- a/tests/game/modifiers.test.ts +++ b/tests/game/modifiers.test.ts @@ -9,11 +9,10 @@ import { } from "game/modifiers"; import Decimal, { DecimalSource } from "util/bignum"; import { WithRequired } from "util/common"; -import { MaybeRefOrGetter } from "util/computed"; import { beforeAll, describe, expect, test } from "vitest"; -import { Ref, ref, unref } from "vue"; +import { MaybeRefOrGetter, Ref, ref, unref } from "vue"; import "../utils"; -import { MaybeRefOrGetter<Renderable>, render } from "util/vue"; +import { render, Renderable } from "util/vue"; export type ModifierConstructorOptions = { [S in "addend" | "multiplier" | "exponent"]: MaybeRefOrGetter<DecimalSource>; From 68da6c352e729a26c7ca25a44517fd9e66452606 Mon Sep 17 00:00:00 2001 From: thepaperpilot <thepaperpilot@gmail.com> Date: Wed, 11 Dec 2024 13:58:14 -0600 Subject: [PATCH 50/58] Complete the rewrite Renderables no longer get wrapped in computed refs, because JSX.Elements don't like that (desyncs with the DOM) Relatedly, a lot of display functions got fairly simplified, removing unnecessary local components Added `MaybeGetter` utility type for something that may be a getter function or a static value (but not a ref) Made Achievement.vue use a Renderable for the display. The object of components can still be passed to `createAchievement` Made Challenge.vue use a Renderable for the display. The object of components can still be passed to `createChallenge` Fixed some issues introduced by the rewrite that broke particles systems --- src/components/fields/Select.vue | 5 +- src/components/fields/Text.vue | 5 +- src/components/fields/Toggle.vue | 5 +- src/components/layout/Collapsible.vue | 7 +- src/data/common.tsx | 78 ++-- src/data/layers/board.tsx | 424 ---------------------- src/features/VueFeature.vue | 3 +- src/features/achievements/Achievement.vue | 30 +- src/features/achievements/achievement.tsx | 93 +++-- src/features/bars/bar.tsx | 8 +- src/features/challenges/Challenge.vue | 30 +- src/features/challenges/challenge.tsx | 91 +++-- src/features/clickables/action.tsx | 66 ++-- src/features/clickables/clickable.tsx | 42 ++- src/features/clickables/repeatable.tsx | 48 +-- src/features/clickables/upgrade.tsx | 55 +-- src/features/conversion.ts | 4 +- src/features/feature.ts | 27 +- src/features/grids/grid.tsx | 84 +++-- src/features/infoboxes/infobox.tsx | 14 +- src/features/particles/Particles.vue | 1 + src/features/particles/particles.tsx | 9 +- src/features/resources/MainDisplay.vue | 5 +- src/features/tabs/tab.ts | 14 +- src/features/tabs/tabFamily.tsx | 25 +- src/features/trees/tree.tsx | 16 +- src/game/boards/board.tsx | 15 +- src/game/layers.tsx | 24 +- src/game/modifiers.tsx | 51 ++- src/game/requirements.tsx | 16 +- src/game/settings.ts | 22 +- src/util/computed.ts | 2 + src/util/vue.tsx | 28 +- src/wrappers/marks/mark.tsx | 5 +- src/wrappers/tooltips/tooltip.tsx | 14 +- tests/game/modifiers.test.ts | 5 +- 36 files changed, 478 insertions(+), 893 deletions(-) delete mode 100644 src/data/layers/board.tsx diff --git a/src/components/fields/Select.vue b/src/components/fields/Select.vue index eda049a..30f4eb5 100644 --- a/src/components/fields/Select.vue +++ b/src/components/fields/Select.vue @@ -15,15 +15,16 @@ <script setup lang="tsx"> import "components/common/fields.css"; +import { MaybeGetter } from "util/computed"; import { render, Renderable } from "util/vue"; -import { MaybeRef, ref, toRef, unref, watch } from "vue"; +import { ref, toRef, unref, watch } from "vue"; import VueNextSelect from "vue-next-select"; import "vue-next-select/dist/index.css"; export type SelectOption = { label: string; value: unknown }; const props = defineProps<{ - title?: MaybeRef<Renderable>; + title?: MaybeGetter<Renderable>; modelValue?: unknown; options: SelectOption[]; placeholder?: string; diff --git a/src/components/fields/Text.vue b/src/components/fields/Text.vue index c7a6f5f..75def9e 100644 --- a/src/components/fields/Text.vue +++ b/src/components/fields/Text.vue @@ -27,12 +27,13 @@ <script setup lang="tsx"> import "components/common/fields.css"; +import { MaybeGetter } from "util/computed"; import { render, Renderable } from "util/vue"; -import { computed, MaybeRef, onMounted, shallowRef, unref } from "vue"; +import { computed, onMounted, shallowRef, unref } from "vue"; import VueTextareaAutosize from "vue-textarea-autosize"; const props = defineProps<{ - title?: MaybeRef<Renderable>; + title?: MaybeGetter<Renderable>; modelValue?: string; textArea?: boolean; placeholder?: string; diff --git a/src/components/fields/Toggle.vue b/src/components/fields/Toggle.vue index 6e77332..d9a16e9 100644 --- a/src/components/fields/Toggle.vue +++ b/src/components/fields/Toggle.vue @@ -7,11 +7,12 @@ <script setup lang="tsx"> import "components/common/fields.css"; +import { MaybeGetter } from "util/computed"; import { render, Renderable } from "util/vue"; -import { computed, MaybeRef } from "vue"; +import { computed } from "vue"; const props = defineProps<{ - title?: MaybeRef<Renderable>; + title?: MaybeGetter<Renderable>; modelValue?: boolean; }>(); const emit = defineEmits<{ diff --git a/src/components/layout/Collapsible.vue b/src/components/layout/Collapsible.vue index 3ea2033..c5fda03 100644 --- a/src/components/layout/Collapsible.vue +++ b/src/components/layout/Collapsible.vue @@ -8,14 +8,15 @@ </template> <script setup lang="ts"> +import { MaybeGetter } from "util/computed"; import { render, Renderable } from "util/vue"; -import type { MaybeRef, Ref } from "vue"; +import type { Ref } from "vue"; import Col from "./Column.vue"; const props = defineProps<{ collapsed: Ref<boolean>; - display: MaybeRef<Renderable>; - content: MaybeRef<Renderable>; + display: MaybeGetter<Renderable>; + content: MaybeGetter<Renderable>; }>(); const Display = () => render(props.display); diff --git a/src/data/common.tsx b/src/data/common.tsx index 289e111..2d0bf30 100644 --- a/src/data/common.tsx +++ b/src/data/common.tsx @@ -17,7 +17,7 @@ import settings from "game/settings"; import type { DecimalSource } from "util/bignum"; import Decimal, { format, formatSmall, formatTime } from "util/bignum"; import { WithRequired } from "util/common"; -import { processGetter } from "util/computed"; +import { MaybeGetter, processGetter } from "util/computed"; import { render, Renderable, renderCol } from "util/vue"; import type { ComputedRef, MaybeRef, MaybeRefOrGetter } from "vue"; import { computed, ref, unref } from "vue"; @@ -43,7 +43,7 @@ export interface ResetButtonOptions extends ClickableOptions { * The content to display on the button. * By default, this includes the reset description, and amount of currency to be gained. */ - display?: MaybeRefOrGetter<Renderable>; + display?: MaybeGetter<Renderable>; /** * Whether or not this button can currently be clicked. * Defaults to checking the current gain amount is greater than {@link minimumGain} @@ -126,38 +126,36 @@ export function createResetButton<T extends ClickableOptions & ResetButtonOption Decimal.gte(unref(conversion.actualGain), unref(resetButton.minimumGain)) ), display: - processGetter(display) ?? - computed( - (): JSX.Element => ( - <span> - {unref(resetButton.resetDescription)} - <b> + display ?? + ((): JSX.Element => ( + <span> + {unref(resetButton.resetDescription)} + <b> + {displayResource( + conversion.gainResource, + Decimal.max( + unref(conversion.actualGain), + unref(resetButton.minimumGain) + ) + )} + </b>{" "} + {conversion.gainResource.displayName} + {unref(resetButton.showNextAt) != null ? ( + <div> + <br /> + {unref(conversion.buyMax) ? "Next:" : "Req:"}{" "} {displayResource( - conversion.gainResource, - Decimal.max( - unref(conversion.actualGain), - unref(resetButton.minimumGain) - ) - )} - </b>{" "} - {conversion.gainResource.displayName} - {unref(resetButton.showNextAt) != null ? ( - <div> - <br /> - {unref(conversion.buyMax) ? "Next:" : "Req:"}{" "} - {displayResource( - conversion.baseResource, - !unref<boolean>(conversion.buyMax) && - Decimal.gte(unref(conversion.actualGain), 1) - ? unref(conversion.currentAt) - : unref(conversion.nextAt) - )}{" "} - {conversion.baseResource.displayName} - </div> - ) : null} - </span> - ) - ), + conversion.baseResource, + !unref<boolean>(conversion.buyMax) && + Decimal.gte(unref(conversion.actualGain), 1) + ? unref(conversion.currentAt) + : unref(conversion.nextAt) + )}{" "} + {conversion.baseResource.displayName} + </div> + ) : null} + </span> + )), onClick: function (e?: MouseEvent | TouchEvent) { if (unref(resetButton.canClick) === false) { return; @@ -211,7 +209,7 @@ export function createLayerTreeNode<T extends LayerTreeNodeOptions>(optionsFunc: return { ...(props as Omit<typeof props, keyof LayerTreeNodeOptions>), layerID, - display: processGetter(display) ?? layerID, + display: display ?? layerID, append: processGetter(append) ?? true, onClick() { if (unref<boolean>(layerTreeNode.append)) { @@ -244,7 +242,7 @@ export interface Section { /** The unit of measurement for the base. **/ unit?: string; /** The label to call the base amount. Defaults to "Base". **/ - baseText?: MaybeRefOrGetter<Renderable>; + baseText?: MaybeGetter<Renderable>; /** Whether or not this section should be currently visible to the player. **/ visible?: MaybeRefOrGetter<boolean>; /** Determines if numbers larger or smaller than the base should be displayed as red. */ @@ -258,12 +256,12 @@ export interface Section { */ export function createCollapsibleModifierSections( sectionsFunc: () => Section[] -): [MaybeRef<Renderable>, Persistent<Record<number, boolean>>] { +): [() => Renderable, Persistent<Record<number, boolean>>] { const sections: Section[] = []; const processed: | { base: MaybeRef<DecimalSource | undefined>[]; - baseText: (MaybeRef<Renderable> | undefined)[]; + baseText: (MaybeGetter<Renderable> | undefined)[]; visible: MaybeRef<boolean | undefined>[]; title: MaybeRef<string | undefined>[]; subtitle: MaybeRef<string | undefined>[]; @@ -274,7 +272,7 @@ export function createCollapsibleModifierSections( if (!calculated) { sections.push(...sectionsFunc()); processed.base = sections.map(s => processGetter(s.base)); - processed.baseText = sections.map(s => processGetter(s.baseText)); + processed.baseText = sections.map(s => s.baseText); processed.visible = sections.map(s => processGetter(s.visible)); processed.title = sections.map(s => processGetter(s.title)); processed.subtitle = sections.map(s => processGetter(s.subtitle)); @@ -284,7 +282,7 @@ export function createCollapsibleModifierSections( } const collapsed = persistent<Record<number, boolean>>({}, false); - const jsxFunc = computed(() => { + const jsxFunc = () => { const sections = calculateSections(); let firstVisibleSection = true; @@ -364,7 +362,7 @@ export function createCollapsibleModifierSections( ); }); return <>{sectionJSX}</>; - }); + }; return [jsxFunc, collapsed]; } diff --git a/src/data/layers/board.tsx b/src/data/layers/board.tsx deleted file mode 100644 index d663598..0000000 --- a/src/data/layers/board.tsx +++ /dev/null @@ -1,424 +0,0 @@ -import { createUpgrade } from "features/clickables/upgrade"; -import { createResource } from "features/resources/resource"; -import Board from "game/boards/Board.vue"; -import CircleProgress from "game/boards/CircleProgress.vue"; -import SVGNode from "game/boards/SVGNode.vue"; -import SquareProgress from "game/boards/SquareProgress.vue"; -import { - Draggable, - MakeDraggableOptions, - NodePosition, - makeDraggable, - placeInAvailableSpace, - setupActions, - setupDraggableNode, - setupUniqueIds -} from "game/boards/board"; -import type { BaseLayer } from "game/layers"; -import { createLayer } from "game/layers"; -import { persistent } from "game/persistence"; -import { createCostRequirement } from "game/requirements"; -import { render } from "util/vue"; -import { ComponentPublicInstance, computed, ref, watch } from "vue"; -import { setupSelectable } from "../common"; - -const board = createLayer("board", function (this: BaseLayer) { - type ANode = NodePosition & { id: number; links: number[]; type: "anode"; z: number }; - type BNode = NodePosition & { id: number; links: number[]; type: "bnode"; z: number }; - type CNode = typeof cNode & { draggable: Draggable<number | "cNode"> }; - type NodeTypes = ANode | BNode; - - const board = ref<ComponentPublicInstance<typeof Board>>(); - - const { select, deselect, selected } = setupSelectable<number>(); - const { - select: selectAction, - deselect: deselectAction, - selected: selectedAction - } = setupSelectable<number>(); - - watch(selected, selected => { - if (selected == null) { - deselectAction(); - } - }); - - const { - startDrag, - endDrag, - drag, - nodeBeingDragged, - hasDragged, - receivingNodes, - receivingNode, - dragDelta - } = setupDraggableNode<number | "cnode">({ - board, - getPosition(id) { - return nodesById.value[id] ?? (cNode as CNode).draggable.position.value; - }, - setPosition(id, position) { - const node = nodesById.value[id] ?? (cNode as CNode).draggable.position.value; - node.x = position.x; - node.y = position.y; - } - }); - - // a nodes can be slotted into b nodes to draw a branch between them, with limited connections - // a nodes can be selected and have an action to spawn a b node, and vice versa - // Newly spawned nodes should find a safe spot to spawn, and display a link to their creator - // a nodes use all the stuff circles used to have, and b diamonds - // c node also exists but is a single Upgrade element that cannot be selected, but can be dragged - // d nodes are a performance test - 1000 simple nodes that have no interactions - // Make all nodes animate in (decorator? `fadeIn(feature)?) - const nodes = persistent<(ANode | BNode)[]>([ - { id: 0, x: 0, y: 0, z: 0, links: [], type: "anode" } - ]); - const nodesById = computed<Record<string, NodeTypes>>(() => - nodes.value.reduce((acc, curr) => ({ ...acc, [curr.id]: curr }), {}) - ); - function mouseDownNode(e: MouseEvent | TouchEvent, node: NodeTypes) { - const oldZ = node.z; - nodes.value.forEach(node => { - if (node.z > oldZ) { - node.z--; - } - }); - node.z = nextId.value; - if (nodeBeingDragged.value == null) { - startDrag(e, node.id); - } - deselect(); - } - function mouseUpNode(e: MouseEvent | TouchEvent, node: NodeTypes) { - if (!hasDragged.value) { - endDrag(); - if (typeof node.id === "number") { - select(node.id); - } - e.stopPropagation(); - } - } - function translate(node: NodePosition, isDragging: boolean) { - let x = node.x; - let y = node.y; - if (isDragging) { - x += dragDelta.value.x; - y += dragDelta.value.y; - } - return ` translate(${x}px,${y}px)`; - } - function rotate(rotation: number) { - return ` rotate(${rotation}deg) `; - } - function scale(nodeOrBool: NodeTypes | boolean) { - const isSelected = - typeof nodeOrBool === "boolean" ? nodeOrBool : selected.value === nodeOrBool.id; - return isSelected ? " scale(1.2)" : ""; - } - function opacity(node: NodeTypes) { - const isDragging = selected.value !== node.id && nodeBeingDragged.value === node.id; - if (isDragging) { - return "; opacity: 0.5;"; - } - return ""; - } - function zIndex(node: NodeTypes) { - if (selected.value === node.id || nodeBeingDragged.value === node.id) { - return "; z-index: 100000000"; - } - return "; z-index: " + node.z; - } - - const renderANode = function (node: ANode) { - return ( - <SVGNode - style={`transform: ${translate(node, nodeBeingDragged.value === node.id)}${opacity( - node - )}${zIndex(node)}`} - onMouseDown={e => mouseDownNode(e, node)} - onMouseUp={e => mouseUpNode(e, node)} - > - <g style={`transform: ${scale(node)}`}> - {receivingNodes.value.includes(node.id) && ( - <circle - r="58" - fill="var(--background)" - stroke={receivingNode.value === node.id ? "#0F0" : "#0F03"} - stroke-width="2" - /> - )} - <CircleProgress r={54.5} progress={0.5} stroke="var(--accent2)" /> - <circle - r="50" - fill="var(--raised-background)" - stroke="var(--outline)" - stroke-width="4" - /> - </g> - {selected.value === node.id && selectedAction.value === 0 && ( - <text y="140" fill="var(--foreground)" class="node-text"> - Spawn B Node - </text> - )} - <text fill="var(--foreground)" class="node-text"> - A - </text> - </SVGNode> - ); - }; - const aActions = setupActions({ - node: () => nodesById.value[selected.value ?? ""], - shouldShowActions: node => node.type === "anode", - actions(node) { - return [ - p => ( - <g - style={`transform: ${translate(p, selectedAction.value === 0)}${scale( - selectedAction.value === 0 - )}`} - onClick={() => { - if (selectedAction.value === 0) { - spawnBNode(node as ANode); - } else { - selectAction(0); - } - }} - > - <circle fill="black" r="20"></circle> - <text fill="white" class="material-icons" x="-12" y="12"> - add - </text> - </g> - ) - ]; - }, - distance: 100 - }); - const sqrtTwo = Math.sqrt(2); - const renderBNode = function (node: BNode) { - return ( - <SVGNode - style={`transform: ${translate(node, nodeBeingDragged.value === node.id)}${opacity( - node - )}${zIndex(node)}`} - onMouseDown={e => mouseDownNode(e, node)} - onMouseUp={e => mouseUpNode(e, node)} - > - <g style={`transform: ${scale(node)}${rotate(45)}`}> - {receivingNodes.value.includes(node.id) && ( - <rect - width={50 * sqrtTwo + 16} - height={50 * sqrtTwo + 16} - style={`translate(${(-50 * sqrtTwo + 16) / 2}, ${ - (-50 * sqrtTwo + 16) / 2 - })`} - fill="var(--background)" - stroke={receivingNode.value === node.id ? "#0F0" : "#0F03"} - stroke-width="2" - /> - )} - <SquareProgress - size={50 * sqrtTwo + 9} - progress={0.5} - stroke="var(--accent2)" - /> - <rect - width={50 * sqrtTwo} - height={50 * sqrtTwo} - style={`transform: translate(${(-50 * sqrtTwo) / 2}px, ${ - (-50 * sqrtTwo) / 2 - }px)`} - fill="var(--raised-background)" - stroke="var(--outline)" - stroke-width="4" - /> - </g> - {selected.value === node.id && selectedAction.value === 0 && ( - <text y="140" fill="var(--foreground)" class="node-text"> - Spawn A Node - </text> - )} - <text fill="var(--foreground)" class="node-text"> - B - </text> - </SVGNode> - ); - }; - const bActions = setupActions({ - node: () => nodesById.value[selected.value ?? ""], - shouldShowActions: node => node.type === "bnode", - actions(node) { - return [ - p => ( - <g - style={`transform: ${translate(p, selectedAction.value === 0)}${scale( - selectedAction.value === 0 - )}`} - onClick={() => { - if (selectedAction.value === 0) { - spawnANode(node as BNode); - } else { - selectAction(0); - } - }} - > - <circle fill="white" r="20"></circle> - <text fill="black" class="material-icons" x="-12" y="12"> - add - </text> - </g> - ) - ]; - }, - distance: 100 - }); - function spawnANode(parent: ANode | BNode) { - const node: ANode = { - x: parent.x, - y: parent.y, - z: nextId.value, - type: "anode", - links: [parent.id], - id: nextId.value - }; - placeInAvailableSpace(node, nodes.value); - nodes.value.push(node); - } - function spawnBNode(parent: ANode | BNode) { - const node: BNode = { - x: parent.x, - y: parent.y, - z: nextId.value, - type: "bnode", - links: [parent.id], - id: nextId.value - }; - placeInAvailableSpace(node, nodes.value); - nodes.value.push(node); - } - - const points = createResource(10); - const cNode = createUpgrade(() => ({ - display: <h1>C</h1>, - // Purposefully not using noPersist - requirements: createCostRequirement(() => ({ cost: 10, resource: points })), - style: { - x: "100px", - y: "100px", - "--layer-color": "var(--accent1)" - }, - // no-op to prevent purchasing while dragging - onHold: () => {} - })); - makeDraggable<number | "cnode", MakeDraggableOptions<number | "cnode">>(cNode, () => ({ - id: "cnode", - endDrag, - startDrag, - hasDragged, - nodeBeingDragged, - dragDelta, - onMouseUp: cNode.purchase - })); - - const dNodesPerAxis = 50; - const dNodes = ( - <> - {new Array(dNodesPerAxis * dNodesPerAxis).fill(0).map((_, i) => { - const x = (Math.floor(i / dNodesPerAxis) - dNodesPerAxis / 2) * 100; - const y = ((i % dNodesPerAxis) - dNodesPerAxis / 2) * 100; - return ( - <path - fill="var(--bought)" - style={`transform: translate(${x}px, ${y}px) scale(0.05)`} - d="M62.43,122.88h-1.98c0-16.15-6.04-30.27-18.11-42.34C30.27,68.47,16.16,62.43,0,62.43v-1.98 c16.16,0,30.27-6.04,42.34-18.14C54.41,30.21,60.45,16.1,60.45,0h1.98c0,16.15,6.04,30.27,18.11,42.34 c12.07,12.07,26.18,18.11,42.34,18.11v1.98c-16.15,0-30.27,6.04-42.34,18.11C68.47,92.61,62.43,106.72,62.43,122.88L62.43,122.88z" - /> - ); - })} - </> - ); - - const links = computed(() => ( - <> - {nodes.value - .reduce( - (acc, curr) => [ - ...acc, - ...curr.links.map(l => ({ from: curr, to: nodesById.value[l] })) - ], - [] as { from: NodeTypes; to: NodeTypes }[] - ) - .map(link => ( - <line - stroke="white" - stroke-width={4} - x1={ - nodeBeingDragged.value === link.from.id - ? dragDelta.value.x + link.from.x - : link.from.x - } - y1={ - nodeBeingDragged.value === link.from.id - ? dragDelta.value.y + link.from.y - : link.from.y - } - x2={ - nodeBeingDragged.value === link.to.id - ? dragDelta.value.x + link.to.x - : link.to.x - } - y2={ - nodeBeingDragged.value === link.to.id - ? dragDelta.value.y + link.to.y - : link.to.y - } - /> - ))} - </> - )); - - const nextId = setupUniqueIds(() => nodes.value); - - function renderNode(node: NodeTypes | typeof cNode) { - if (node.type === "anode") { - return renderANode(node); - } else if (node.type === "bnode") { - return renderBNode(node); - } else { - return render(node); - } - } - - return { - name: "Board", - color: "var(--accent1)", - display: () => ( - <> - <Board - onDrag={drag} - onMouseDown={deselect} - onMouseUp={endDrag} - onMouseLeave={endDrag} - ref={board} - style={{ height: "600px" }} - > - <SVGNode> - {dNodes} - {links.value} - </SVGNode> - {nodes.value.map(renderNode)} - {render(cNode)} - <SVGNode> - {aActions.value} - {bActions.value} - </SVGNode> - </Board> - </> - ), - boardNodes: nodes, - cNode, - selected: persistent(selected) - }; -}); - -export default board; diff --git a/src/features/VueFeature.vue b/src/features/VueFeature.vue index a19cfa1..ad787ad 100644 --- a/src/features/VueFeature.vue +++ b/src/features/VueFeature.vue @@ -18,12 +18,13 @@ import "components/common/features.css"; import Node from "components/Node.vue"; import type { Visibility } from "features/feature"; import { isHidden, isVisible } from "features/feature"; +import { MaybeGetter } from "util/computed"; import { render, Renderable } from "util/vue"; import { MaybeRef, unref, type CSSProperties } from "vue"; const props = withDefaults(defineProps<{ id: string; - components: MaybeRef<Renderable>[]; + components: MaybeGetter<Renderable>[]; wrappers: ((el: () => Renderable) => Renderable)[]; visibility?: MaybeRef<Visibility | boolean>; style?: MaybeRef<CSSProperties>; diff --git a/src/features/achievements/Achievement.vue b/src/features/achievements/Achievement.vue index 3184822..bba4b35 100644 --- a/src/features/achievements/Achievement.vue +++ b/src/features/achievements/Achievement.vue @@ -29,35 +29,7 @@ const props = defineProps<{ small: Achievement["small"]; }>(); -const Component = () => { - if (props.display == null) { - return null; - } else if ( - isRef(props.display) || - typeof props.display === "string" || - isJSXElement(props.display) - ) { - return render(props.display); - } else { - const { requirement, effectDisplay, optionsDisplay } = props.display; - return ( - <span> - {requirement ? - render(requirement, el => <h3>{el}</h3>) : - displayRequirements(props.requirements ?? [])} - {effectDisplay ? ( - <div> - {render(effectDisplay, el => <b>{el}</b>)} - </div> - ) : null} - {optionsDisplay != null ? ( - <div class="equal-spaced"> - {render(optionsDisplay)} - </div> - ) : null} - </span>); - } -}; +const Component = () => props.display == null ? <></> : render(props.display); </script> <style scoped> diff --git a/src/features/achievements/achievement.tsx b/src/features/achievements/achievement.tsx index 1ecae42..ed97ffe 100644 --- a/src/features/achievements/achievement.tsx +++ b/src/features/achievements/achievement.tsx @@ -14,7 +14,7 @@ import { } from "game/requirements"; import settings, { registerSettingField } from "game/settings"; import { camelToTitle } from "util/common"; -import { processGetter } from "util/computed"; +import { MaybeGetter, processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; import { isJSXElement, @@ -24,7 +24,7 @@ import { vueFeatureMixin, VueFeatureOptions } from "util/vue"; -import { computed, isRef, MaybeRef, MaybeRefOrGetter, unref, watchEffect } from "vue"; +import { computed, MaybeRef, MaybeRefOrGetter, unref, watchEffect } from "vue"; import { useToast } from "vue-toastification"; import Achievement from "./Achievement.vue"; @@ -50,14 +50,15 @@ export interface AchievementOptions extends VueFeatureOptions { requirements?: Requirements; /** The display to use for this achievement. */ display?: - | MaybeRefOrGetter<Renderable> + | Renderable + | (() => Renderable) | { /** Description of the requirement(s) for this achievement. If unspecified then the requirements will be displayed automatically based on {@link requirements}. */ - requirement?: MaybeRefOrGetter<Renderable>; + requirement?: MaybeGetter<Renderable>; /** Description of what will change (if anything) for achieving this. */ - effectDisplay?: MaybeRefOrGetter<Renderable>; + effectDisplay?: MaybeGetter<Renderable>; /** Any additional things to display on this achievement, such as a toggle for it's effect. */ - optionsDisplay?: MaybeRefOrGetter<Renderable>; + optionsDisplay?: MaybeGetter<Renderable>; }; /** Toggles a smaller design for the feature. */ small?: MaybeRefOrGetter<boolean>; @@ -76,13 +77,7 @@ export interface Achievement extends VueFeature { /** A function that is called when the achievement is completed. */ onComplete?: VoidFunction; /** The display to use for this achievement. */ - display?: - | MaybeRef<Renderable> - | { - requirement?: MaybeRef<Renderable>; - effectDisplay?: MaybeRef<Renderable>; - optionsDisplay?: MaybeRef<Renderable>; - }; + display?: MaybeGetter<Renderable>; /** Toggles a smaller design for the feature. */ small?: MaybeRef<boolean>; /** An image to display as the background for this achievement. */ @@ -105,7 +100,15 @@ export function createAchievement<T extends AchievementOptions>(optionsFunc?: () const earned = persistent<boolean>(false, false); return createLazyProxy(() => { const options = optionsFunc?.() ?? ({} as T); - const { requirements, display, small, image, showPopups, onComplete, ...props } = options; + const { + requirements, + display: _display, + small, + image, + showPopups, + onComplete, + ...props + } = options; const vueFeature = vueFeatureMixin("achievement", options, () => ( <Achievement @@ -117,12 +120,35 @@ export function createAchievement<T extends AchievementOptions>(optionsFunc?: () /> )); + let display: MaybeGetter<Renderable> | undefined = undefined; + if (typeof _display === "object" && !isJSXElement(_display)) { + const { requirement, effectDisplay, optionsDisplay } = _display; + display = () => ( + <span> + {requirement == null + ? displayRequirements(requirements ?? []) + : render(requirement, el => <h3>{el}</h3>)} + {effectDisplay == null ? null : ( + <div> + {render(effectDisplay, el => ( + <b>{el}</b> + ))} + </div> + )} + {optionsDisplay != null ? ( + <div class="equal-spaced">{render(optionsDisplay)}</div> + ) : null} + </span> + ); + } else if (_display != null) { + display = _display; + } + const achievement = { type: AchievementType, ...(props as Omit<typeof props, keyof VueFeature | keyof AchievementOptions>), ...vueFeature, visibility: computed(() => { - const display = unref((achievement as Achievement).display); switch (settings.msDisplay) { default: case AchievementDisplay.All: @@ -131,9 +157,9 @@ export function createAchievement<T extends AchievementOptions>(optionsFunc?: () if ( unref(earned) && !( - display != null && - typeof display === "object" && - "optionsDisplay" in display + _display != null && + typeof _display === "object" && + !isJSXElement(_display) ) ) { return Visibility.None; @@ -153,19 +179,7 @@ export function createAchievement<T extends AchievementOptions>(optionsFunc?: () small: processGetter(small), image: processGetter(image), showPopups: processGetter(showPopups) ?? true, - display: - display == null - ? undefined - : isRef(display) || - typeof display === "string" || - typeof display === "function" || - isJSXElement(display) - ? processGetter(display) - : { - requirement: processGetter(display.requirement), - effectDisplay: processGetter(display.effectDisplay), - optionsDisplay: processGetter(display.optionsDisplay) - }, + display, requirements: requirements == null ? undefined @@ -181,19 +195,18 @@ export function createAchievement<T extends AchievementOptions>(optionsFunc?: () earned.value = true; achievement.onComplete?.(); if (achievement.display != null && unref(achievement.showPopups) === true) { - const display = achievement.display; - let Display; - if (isRef(display) || typeof display === "string" || isJSXElement(display)) { - Display = () => render(display); - } else if (display.requirement != null) { - Display = () => render(display.requirement!); - } else { - Display = () => displayRequirements(achievement.requirements ?? []); + let display = achievement.display; + if (typeof _display === "object" && !isJSXElement(_display)) { + if (_display.requirement != null) { + display = _display.requirement; + } else { + display = displayRequirements(requirements ?? []); + } } toast.info( <div> <h3>Achievement earned!</h3> - <div>{Display()}</div> + <div>{render(display)}</div> </div> ); } diff --git a/src/features/bars/bar.tsx b/src/features/bars/bar.tsx index b498518..cb86253 100644 --- a/src/features/bars/bar.tsx +++ b/src/features/bars/bar.tsx @@ -1,7 +1,7 @@ import Bar from "features/bars/Bar.vue"; import type { DecimalSource } from "util/bignum"; import { Direction } from "util/common"; -import { processGetter } from "util/computed"; +import { MaybeGetter, processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue"; import { CSSProperties, MaybeRef, MaybeRefOrGetter } from "vue"; @@ -30,7 +30,7 @@ export interface BarOptions extends VueFeatureOptions { /** The progress value of the bar, from 0 to 1. */ progress: MaybeRefOrGetter<DecimalSource>; /** The display to use for this bar. */ - display?: MaybeRefOrGetter<Renderable>; + display?: MaybeGetter<Renderable>; } /** An object that represents a feature that displays some sort of progress or completion or resource with a cap. */ @@ -52,7 +52,7 @@ export interface Bar extends VueFeature { /** The progress value of the bar, from 0 to 1. */ progress: MaybeRef<DecimalSource>; /** The display to use for this bar. */ - display?: MaybeRef<Renderable>; + display?: MaybeGetter<Renderable>; /** A symbol that helps identify features of the same type. */ type: typeof BarType; } @@ -101,7 +101,7 @@ export function createBar<T extends BarOptions>(optionsFunc: () => T) { textStyle: processGetter(textStyle), fillStyle: processGetter(fillStyle), progress: processGetter(progress), - display: processGetter(display) + display } satisfies Bar; return bar; diff --git a/src/features/challenges/Challenge.vue b/src/features/challenges/Challenge.vue index 23dcb55..915bd1e 100644 --- a/src/features/challenges/Challenge.vue +++ b/src/features/challenges/Challenge.vue @@ -22,7 +22,6 @@ <script setup lang="tsx"> import "components/common/features.css"; import { getHighNotifyStyle, getNotifyStyle } from "game/notifications"; -import { displayRequirements } from "game/requirements"; import { render } from "util/vue"; import type { Component } from "vue"; import { computed, unref } from "vue"; @@ -61,34 +60,7 @@ const notifyStyle = computed(() => { return {}; }); -const Component = () => { - if (props.display == null) { - return null; - } - if (typeof props.display === "object" && "description" in props.display) { - const { title, description, goal, reward, effectDisplay } = props.display; - return <span> - {title != null ? (<div>{render(title, el => <h3>{el}</h3>)}</div>) : null} - {render(description, el => <div>{el}</div>)} - <div> - <br /> - Goal: {goal == null ? displayRequirements(props.requirements) : render(goal, el => <h3>{el}</h3>)} - </div> - {reward != null ? ( - <div> - <br /> - Reward: {render(reward)} - </div> - ) : null} - {effectDisplay != null ? ( - <div> - Currently: {render(effectDisplay)} - </div> - ) : null} - </span>; - } - return render(props.display); -} +const Component = () => props.display == null ? <></> : render(props.display); </script> <style scoped> diff --git a/src/features/challenges/challenge.tsx b/src/features/challenges/challenge.tsx index b277e26..3d18f01 100644 --- a/src/features/challenges/challenge.tsx +++ b/src/features/challenges/challenge.tsx @@ -4,13 +4,20 @@ import type { Reset } from "features/reset"; import { globalBus } from "game/events"; import type { Persistent } from "game/persistence"; import { persistent } from "game/persistence"; -import { Requirements, maxRequirementsMet } from "game/requirements"; +import { Requirements, displayRequirements, maxRequirementsMet } from "game/requirements"; import settings, { registerSettingField } from "game/settings"; import type { DecimalSource } from "util/bignum"; import Decimal from "util/bignum"; -import { processGetter } from "util/computed"; +import { MaybeGetter, processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; -import { Renderable, VueFeature, VueFeatureOptions, vueFeatureMixin } from "util/vue"; +import { + Renderable, + VueFeature, + VueFeatureOptions, + isJSXElement, + render, + vueFeatureMixin +} from "util/vue"; import type { MaybeRef, MaybeRefOrGetter, Ref, WatchStopHandle } from "vue"; import { computed, unref, watch } from "vue"; import Challenge from "./Challenge.vue"; @@ -32,18 +39,19 @@ export interface ChallengeOptions extends VueFeatureOptions { completionLimit?: MaybeRefOrGetter<DecimalSource>; /** The display to use for this challenge. */ display?: - | MaybeRefOrGetter<Renderable> + | Renderable + | (() => Renderable) | { /** A header to appear at the top of the display. */ - title?: MaybeRefOrGetter<Renderable>; + title?: MaybeGetter<Renderable>; /** The main text that appears in the display. */ - description: MaybeRefOrGetter<Renderable>; + description: MaybeGetter<Renderable>; /** A description of the current goal for this challenge. If unspecified then the requirements will be displayed automatically based on {@link requirements}. */ - goal?: MaybeRefOrGetter<Renderable>; + goal?: MaybeGetter<Renderable>; /** A description of what will change upon completing this challenge. */ - reward?: MaybeRefOrGetter<Renderable>; + reward?: MaybeGetter<Renderable>; /** A description of the current effect of this challenge. */ - effectDisplay?: MaybeRefOrGetter<Renderable>; + effectDisplay?: MaybeGetter<Renderable>; }; /** A function that is called when the challenge is completed. */ onComplete?: VoidFunction; @@ -70,20 +78,7 @@ export interface Challenge extends VueFeature { /** The maximum number of times the challenge can be completed. */ completionLimit?: MaybeRef<DecimalSource>; /** The display to use for this challenge. */ - display?: - | MaybeRef<Renderable> - | { - /** A header to appear at the top of the display. */ - title?: MaybeRef<Renderable>; - /** The main text that appears in the display. */ - description: MaybeRef<Renderable>; - /** A description of the current goal for this challenge. If unspecified then the requirements will be displayed automatically based on {@link requirements}. */ - goal?: MaybeRef<Renderable>; - /** A description of what will change upon completing this challenge. */ - reward?: MaybeRef<Renderable>; - /** A description of the current effect of this challenge. */ - effectDisplay?: MaybeRef<Renderable>; - }; + display?: MaybeGetter<Renderable>; /** The current amount of times this challenge can be completed. */ canComplete: Ref<DecimalSource>; /** The current number of times this challenge has been completed. */ @@ -118,7 +113,7 @@ export function createChallenge<T extends ChallengeOptions>(optionsFunc: () => T requirements, canStart, completionLimit, - display, + display: _display, reset, onComplete, onEnter, @@ -139,6 +134,41 @@ export function createChallenge<T extends ChallengeOptions>(optionsFunc: () => T /> )); + let display: MaybeGetter<Renderable> | undefined = undefined; + if (typeof _display === "object" && !isJSXElement(_display)) { + const { title, description, goal, reward, effectDisplay } = _display; + display = () => ( + <span> + {title != null ? ( + <div> + {render(title, el => ( + <h3>{el}</h3> + ))} + </div> + ) : null} + {render(description, el => ( + <div>{el}</div> + ))} + <div> + <br /> + Goal:{" "} + {goal == null + ? displayRequirements(challenge.requirements) + : render(goal, el => <h3>{el}</h3>)} + </div> + {reward != null ? ( + <div> + <br /> + Reward: {render(reward)} + </div> + ) : null} + {effectDisplay != null ? <div>Currently: {render(effectDisplay)}</div> : null} + </span> + ); + } else if (_display != null) { + display = _display; + } + const challenge = { type: ChallengeType, ...(props as Omit<typeof props, keyof VueFeature | keyof ChallengeOptions>), @@ -157,18 +187,7 @@ export function createChallenge<T extends ChallengeOptions>(optionsFunc: () => T onComplete, onEnter, onExit, - display: - display == null - ? undefined - : typeof display === "object" && "description" in display - ? { - title: processGetter(display.title), - description: processGetter(display.description), - goal: processGetter(display.goal), - reward: processGetter(display.reward), - effectDisplay: processGetter(display.effectDisplay) - } - : processGetter(display), + display, toggle: function () { if (active.value) { if ( diff --git a/src/features/clickables/action.tsx b/src/features/clickables/action.tsx index 4ec16cc..c05b91d 100644 --- a/src/features/clickables/action.tsx +++ b/src/features/clickables/action.tsx @@ -5,11 +5,10 @@ import { persistent } from "game/persistence"; import Decimal, { DecimalSource } from "lib/break_eternity"; import { Unsubscribe } from "nanoevents"; import { Direction } from "util/common"; -import { processGetter } from "util/computed"; +import { MaybeGetter, processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; -import { render, Renderable, VueFeature, vueFeatureMixin } from "util/vue"; +import { isJSXElement, render, Renderable, VueFeature, vueFeatureMixin } from "util/vue"; import { computed, MaybeRef, MaybeRefOrGetter, Ref, ref, unref } from "vue"; -import { JSX } from "vue/jsx-runtime"; import { Bar, BarOptions, createBar } from "../bars/bar"; import { type Clickable, ClickableOptions } from "./clickable"; @@ -39,7 +38,7 @@ export interface Action extends VueFeature { /** Whether or not the action may be performed. */ canClick: MaybeRef<boolean>; /** The display to use for this action. */ - display?: MaybeRef<Renderable>; + display?: MaybeGetter<Renderable>; /** A function that is called when the action is clicked. */ onClick: (amount: DecimalSource) => void; /** Whether or not the player is holding down the action. Actions will be considered clicked as soon as the cooldown completes when being held down. */ @@ -62,8 +61,16 @@ export function createAction<T extends ActionOptions>(optionsFunc?: () => T) { const progress = persistent<DecimalSource>(0); return createLazyProxy(() => { const options = optionsFunc?.() ?? ({} as T); - const { style, duration, canClick, autoStart, display, barOptions, onClick, ...props } = - options; + const { + style, + duration, + canClick, + autoStart, + display: _display, + barOptions, + onClick, + ...props + } = options; const processedCanClick = processGetter(canClick) ?? true; const processedStyle = processGetter(style); @@ -78,29 +85,24 @@ export function createAction<T extends ActionOptions>(optionsFunc?: () => T) { ...(barOptions as Omit<typeof barOptions, keyof VueFeature>) })); - let Component: () => JSX.Element; - if (typeof display === "object" && "description" in display) { - const title = processGetter(display.title); - const description = processGetter(display.description); - - const Title = () => (title == null ? <></> : render(title, el => <h3>{el}</h3>)); - const Description = () => render(description, el => <div>{el}</div>); - - Component = () => { - return ( - <span> - {title != null ? ( - <div> - <Title /> - </div> - ) : null} - <Description /> - </span> - ); - }; - } else if (display != null) { - const processedDisplay = processGetter(display); - Component = () => render(processedDisplay); + let display: MaybeGetter<Renderable>; + if (typeof _display === "object" && !isJSXElement(_display)) { + display = () => ( + <span> + {_display.title != null ? ( + <div> + {render(_display.title, el => ( + <h3>{el}</h3> + ))} + </div> + ) : null} + {render(_display.description, el => ( + <div>{el}</div> + ))} + </span> + ); + } else if (_display != null) { + display = _display; } const action = { @@ -135,14 +137,14 @@ export function createAction<T extends ActionOptions>(optionsFunc?: () => T) { unref(processedCanClick) && Decimal.gte(progress.value, unref(action.duration)) ), autoStart: processGetter(autoStart) ?? false, - display: computed(() => ( + display: () => ( <> <div style="flex-grow: 1" /> - {display == null ? null : <Component />} + {display == null ? null : render(display)} <div style="flex-grow: 1" /> {render(progressBar)} </> - )), + ), progressBar, onClick: function () { if (unref(action.canClick) === false) { diff --git a/src/features/clickables/clickable.tsx b/src/features/clickables/clickable.tsx index 54e392c..98dab9a 100644 --- a/src/features/clickables/clickable.tsx +++ b/src/features/clickables/clickable.tsx @@ -1,9 +1,16 @@ import Clickable from "features/clickables/Clickable.vue"; import type { BaseLayer } from "game/layers"; import type { Unsubscribe } from "nanoevents"; -import { processGetter } from "util/computed"; +import { MaybeGetter, processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; -import { render, Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue"; +import { + isJSXElement, + render, + Renderable, + VueFeature, + vueFeatureMixin, + VueFeatureOptions +} from "util/vue"; import { computed, MaybeRef, MaybeRefOrGetter, unref } from "vue"; /** A symbol used to identify {@link Clickable} features. */ @@ -17,12 +24,13 @@ export interface ClickableOptions extends VueFeatureOptions { canClick?: MaybeRefOrGetter<boolean>; /** The display to use for this clickable. */ display?: - | MaybeRefOrGetter<Renderable> + | Renderable + | (() => Renderable) | { /** A header to appear at the top of the display. */ - title?: MaybeRefOrGetter<Renderable>; + title?: MaybeGetter<Renderable>; /** The main text that appears in the display. */ - description: MaybeRefOrGetter<Renderable>; + description: MaybeGetter<Renderable>; }; /** A function that is called when the clickable is clicked. */ onClick?: (e?: MouseEvent | TouchEvent) => void; @@ -39,7 +47,7 @@ export interface Clickable extends VueFeature { /** Whether or not the clickable may be clicked. */ canClick: MaybeRef<boolean>; /** The display to use for this clickable. */ - display?: MaybeRef<Renderable>; + display?: MaybeGetter<Renderable>; /** A symbol that helps identify features of the same type. */ type: typeof ClickableType; } @@ -53,26 +61,20 @@ export function createClickable<T extends ClickableOptions>(optionsFunc?: () => const options = optionsFunc?.() ?? ({} as T); const { canClick, display: _display, onClick: onClick, onHold: onHold, ...props } = options; - let display: MaybeRef<Renderable> | undefined = undefined; - if (typeof _display === "object" && "description" in _display) { - const title = processGetter(_display.title); - const description = processGetter(_display.description); - - const Title = () => (title == null ? <></> : render(title, el => <h3>{el}</h3>)); - const Description = () => render(description, el => <div>{el}</div>); - - display = computed(() => ( + let display: MaybeGetter<Renderable> | undefined = undefined; + if (typeof _display === "object" && !isJSXElement(_display)) { + display = () => ( <span> - {title != null ? ( + {_display.title != null ? ( <div> - <Title /> + {render(_display.title, el => <h3>{el}</h3>)} </div> ) : null} - <Description /> + {render(_display.description, el => <div>{el}</div>)} </span> - )); + ); } else if (_display != null) { - display = processGetter(_display); + display = _display; } const clickable = { diff --git a/src/features/clickables/repeatable.tsx b/src/features/clickables/repeatable.tsx index a4112db..c4e6c0f 100644 --- a/src/features/clickables/repeatable.tsx +++ b/src/features/clickables/repeatable.tsx @@ -11,11 +11,11 @@ import { } from "game/requirements"; import type { DecimalSource } from "util/bignum"; import Decimal, { formatWhole } from "util/bignum"; -import { processGetter } from "util/computed"; +import { MaybeGetter, processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; import { isJSXElement, render, Renderable, VueFeature, vueFeatureMixin } from "util/vue"; import type { MaybeRef, MaybeRefOrGetter, Ref } from "vue"; -import { computed, isRef, unref } from "vue"; +import { computed, unref } from "vue"; import { ClickableOptions } from "./clickable"; /** A symbol used to identify {@link Repeatable} features. */ @@ -31,14 +31,15 @@ export interface RepeatableOptions extends ClickableOptions { initialAmount?: DecimalSource; /** The display to use for this repeatable. */ display?: - | MaybeRefOrGetter<Renderable> + | Renderable + | (() => Renderable) | { /** A header to appear at the top of the display. */ - title?: MaybeRefOrGetter<Renderable>; + title?: MaybeGetter<Renderable>; /** The main text that appears in the display. */ - description: MaybeRefOrGetter<Renderable>; + description: MaybeGetter<Renderable>; /** A description of the current effect of this repeatable, based off its amount. */ - effectDisplay?: MaybeRefOrGetter<Renderable>; + effectDisplay?: MaybeGetter<Renderable>; /** Whether or not to show the current amount of this repeatable at the bottom of the display. */ showAmount?: boolean; }; @@ -53,7 +54,7 @@ export interface Repeatable extends VueFeature { /** The initial amount this repeatable has on a new save / after reset. */ initialAmount?: DecimalSource; /** The display to use for this repeatable. */ - display?: MaybeRef<Renderable>; + display?: MaybeGetter<Renderable>; /** Whether or not the repeatable may be clicked. */ canClick: Ref<boolean>; /** A function that is called when the repeatable is clicked. */ @@ -119,25 +120,19 @@ export function createRepeatable<T extends RepeatableOptions>(optionsFunc: () => } let display; - if (typeof _display === "object" && !isRef(_display) && !isJSXElement(_display)) { - const title = processGetter(_display.title); - const description = processGetter(_display.description); - const effectDisplay = processGetter(_display.effectDisplay); - const showAmount = processGetter(_display.showAmount); + if (typeof _display === "object" && !isJSXElement(_display)) { + const { title, description, effectDisplay, showAmount } = _display; - const Title = title == null ? null : () => render(title, el => <h3>{el}</h3>); - const Description = () => render(description, el => <>{el}</>); - const EffectDisplay = - effectDisplay == null ? null : () => render(effectDisplay, el => <>{el}</>); - - display = computed(() => ( + display = () => ( <span> - {Title == null ? null : ( + {title == null ? null : ( <div> - <Title /> + {render(title, el => ( + <h3>{el}</h3> + ))} </div> )} - <Description /> + {render(description)} {showAmount === false ? null : ( <div> <br /> @@ -147,10 +142,10 @@ export function createRepeatable<T extends RepeatableOptions>(optionsFunc: () => ) : undefined} </div> )} - {EffectDisplay == null ? null : ( + {effectDisplay == null ? null : ( <div> <br /> - Currently: <EffectDisplay /> + Currently: {render(effectDisplay)} </div> )} {unref(repeatable.maxed) ? null : ( @@ -160,12 +155,9 @@ export function createRepeatable<T extends RepeatableOptions>(optionsFunc: () => </div> )} </span> - )); + ); } else if (_display != null) { - const processedDisplay = processGetter(_display); - display = computed(() => render(processedDisplay)); - } else { - display = undefined; + display = _display; } amount[DefaultValue] = initialAmount ?? 0; diff --git a/src/features/clickables/upgrade.tsx b/src/features/clickables/upgrade.tsx index 18df857..4c299cd 100644 --- a/src/features/clickables/upgrade.tsx +++ b/src/features/clickables/upgrade.tsx @@ -10,9 +10,16 @@ import { requirementsMet } from "game/requirements"; import { isFunction } from "util/common"; -import { processGetter } from "util/computed"; +import { MaybeGetter, processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; -import { Renderable, VueFeature, VueFeatureOptions, render, vueFeatureMixin } from "util/vue"; +import { + Renderable, + VueFeature, + VueFeatureOptions, + isJSXElement, + render, + vueFeatureMixin +} from "util/vue"; import type { MaybeRef, MaybeRefOrGetter, Ref } from "vue"; import { computed, unref } from "vue"; import Clickable from "./Clickable.vue"; @@ -27,14 +34,15 @@ export const UpgradeType = Symbol("Upgrade"); export interface UpgradeOptions extends VueFeatureOptions, ClickableOptions { /** The display to use for this upgrade. */ display?: - | MaybeRefOrGetter<Renderable> + | Renderable + | (() => Renderable) | { /** A header to appear at the top of the display. */ - title?: MaybeRefOrGetter<Renderable>; + title?: MaybeGetter<Renderable>; /** The main text that appears in the display. */ - description: MaybeRefOrGetter<Renderable>; + description: MaybeGetter<Renderable>; /** A description of the current effect of the achievement. Useful when the effect changes dynamically. */ - effectDisplay?: MaybeRefOrGetter<Renderable>; + effectDisplay?: MaybeGetter<Renderable>; }; /** The requirements to purchase this upgrade. */ requirements: Requirements; @@ -47,7 +55,7 @@ export interface Upgrade extends VueFeature { /** The requirements to purchase this upgrade. */ requirements: Requirements; /** The display to use for this upgrade. */ - display?: MaybeRef<Renderable>; + display?: MaybeGetter<Renderable>; /** Whether or not this upgrade has been purchased. */ bought: Persistent<boolean>; /** Whether or not the upgrade can currently be purchased. */ @@ -92,30 +100,23 @@ export function createUpgrade<T extends UpgradeOptions>(optionsFunc: () => T) { requirements.push(createVisibilityRequirement(vueFeature.visibility)); } - let display: MaybeRef<Renderable> | undefined = undefined; - if (typeof _display === "object" && "description" in _display) { - const title = processGetter(_display.title); - const description = processGetter(_display.description); - const effectDisplay = processGetter(_display.effectDisplay); + let display; + if (typeof _display === "object" && !isJSXElement(_display)) { + const { title, description, effectDisplay } = _display; - const Title = () => (title == null ? <></> : render(title, el => <h3>{el}</h3>)); - const Description = () => render(description, el => <div>{el}</div>); - const EffectDisplay = () => - effectDisplay == null ? <></> : render(effectDisplay, el => <>{el}</>); - - display = computed(() => ( + display = () => ( <span> {title != null ? ( <div> - <Title /> - </div> - ) : null} - <Description /> - {effectDisplay != null ? ( - <div> - Currently: <EffectDisplay /> + {render(title, el => ( + <h3>{el}</h3> + ))} </div> ) : null} + {render(description, el => ( + <div>{el}</div> + ))} + {effectDisplay != null ? <div>Currently: {render(effectDisplay)}</div> : null} {bought.value ? null : ( <> <br /> @@ -123,9 +124,9 @@ export function createUpgrade<T extends UpgradeOptions>(optionsFunc: () => T) { </> )} </span> - )); + ); } else if (_display != null) { - display = processGetter(_display); + display = _display; } const upgrade = { diff --git a/src/features/conversion.ts b/src/features/conversion.ts index ea7f0db..4969f3d 100644 --- a/src/features/conversion.ts +++ b/src/features/conversion.ts @@ -5,7 +5,7 @@ import type { BaseLayer } from "game/layers"; import { createBooleanRequirement } from "game/requirements"; import type { DecimalSource } from "util/bignum"; import Decimal from "util/bignum"; -import { processGetter } from "util/computed"; +import { MaybeGetter, processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; import { Renderable } from "util/vue"; import { computed, MaybeRef, MaybeRefOrGetter, unref } from "vue"; @@ -310,7 +310,7 @@ export function setupPassiveGeneration( export function createCanConvertRequirement( conversion: Conversion, minGainAmount: MaybeRefOrGetter<DecimalSource> = 1, - display?: MaybeRefOrGetter<Renderable> + display?: MaybeGetter<Renderable> ) { const computedMinGainAmount = processGetter(minGainAmount); return createBooleanRequirement( diff --git a/src/features/feature.ts b/src/features/feature.ts index 2d13953..e669f0d 100644 --- a/src/features/feature.ts +++ b/src/features/feature.ts @@ -47,9 +47,12 @@ export function findFeatures(obj: object, ...types: symbol[]): unknown[] { const handleObject = (obj: object) => { Object.keys(obj).forEach(key => { const value: unknown = obj[key as keyof typeof obj]; - if (value != null && typeof value === "object") { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (types.includes((value as Record<string, any>).type)) { + if ( + value != null && + typeof value === "object" && + (value as Record<string, unknown>).__v_isVNode !== true + ) { + if (types.includes((value as Record<string, unknown>).type as symbol)) { objects.push(value); } else if (!(value instanceof Decimal) && !isRef(value)) { handleObject(value as Record<string, unknown>); @@ -66,7 +69,7 @@ export function getFirstFeature<T extends VueFeature>( filter: (feature: T) => boolean ): { firstFeature: Ref<T | undefined>; - collapsedContent: MaybeRef<Renderable>; + collapsedContent: () => Renderable; hasCollapsedContent: Ref<boolean>; } { const filteredFeatures = computed(() => @@ -74,7 +77,7 @@ export function getFirstFeature<T extends VueFeature>( ); return { firstFeature: computed(() => filteredFeatures.value[0]), - collapsedContent: computed(() => renderCol(...filteredFeatures.value.slice(1))), + collapsedContent: () => renderCol(...filteredFeatures.value.slice(1)), hasCollapsedContent: computed(() => filteredFeatures.value.length > 1) }; } @@ -90,13 +93,13 @@ export function excludeFeatures(obj: Record<string, unknown>, ...types: symbol[] const handleObject = (obj: Record<string, unknown>) => { Object.keys(obj).forEach(key => { const value = obj[key]; - if (value != null && typeof value === "object") { - if ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - typeof (value as Record<string, any>).type === "symbol" && - // eslint-disable-next-line @typescript-eslint/no-explicit-any - !types.includes((value as Record<string, any>).type) - ) { + if ( + value != null && + typeof value === "object" && + (value as Record<string, unknown>).__v_isVNode !== true + ) { + const type = (value as Record<string, unknown>).type; + if (typeof type === "symbol" && !types.includes(type)) { objects.push(value); } else if (!(value instanceof Decimal) && !isRef(value)) { handleObject(value as Record<string, unknown>); diff --git a/src/features/grids/grid.tsx b/src/features/grids/grid.tsx index 4d8c7e9..97b94d1 100644 --- a/src/features/grids/grid.tsx +++ b/src/features/grids/grid.tsx @@ -1,16 +1,23 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import Column from "components/layout/Column.vue"; +import Row from "components/layout/Row.vue"; +import Clickable from "features/clickables/Clickable.vue"; import { getUniqueID, Visibility } from "features/feature"; import type { Persistent, State } from "game/persistence"; import { persistent } from "game/persistence"; import { isFunction } from "util/common"; -import { processGetter } from "util/computed"; +import { MaybeGetter, processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; -import { isJSXElement, render, Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue"; +import { + isJSXElement, + render, + Renderable, + VueFeature, + vueFeatureMixin, + VueFeatureOptions +} from "util/vue"; import type { CSSProperties, MaybeRef, MaybeRefOrGetter, Ref } from "vue"; -import { computed, isRef, unref } from "vue"; -import Column from "components/layout/Column.vue"; -import Row from "components/layout/Row.vue"; -import Clickable from "features/clickables/Clickable.vue"; +import { computed, unref } from "vue"; /** A symbol used to identify {@link Grid} features. */ export const GridType = Symbol("Grid"); @@ -39,7 +46,7 @@ export interface GridCell extends VueFeature { /** The persistent state of this cell. */ state: State; /** The main text that appears in the display. */ - display: MaybeRef<Renderable>; + display: MaybeGetter<Renderable>; /** A function that is called when the cell is clicked. */ onClick?: (e?: MouseEvent | TouchEvent) => void; /** A function that is called when the cell is held down. */ @@ -65,10 +72,13 @@ export interface GridOptions extends VueFeatureOptions { /** A getter for the CSS classes for a cell. */ getClasses?: CellMaybeRefOrGetter<Record<string, boolean>>; /** A getter for the display component for a cell. */ - getDisplay: CellMaybeRefOrGetter<Renderable> | { - getTitle?: CellMaybeRefOrGetter<Renderable>; - getDescription: CellMaybeRefOrGetter<Renderable> - }; + getDisplay: + | Renderable + | ((row: number, col: number, state: State) => Renderable) + | { + getTitle?: Renderable | ((row: number, col: number, state: State) => Renderable); + getDescription: Renderable | ((row: number, col: number, state: State) => Renderable); + }; /** A function that is called when a cell is clicked. */ onClick?: (row: number, col: number, state: State, e?: MouseEvent | TouchEvent) => void; /** A function that is called when a cell is held down. */ @@ -96,7 +106,7 @@ export interface Grid extends VueFeature { /** A getter for the CSS classes for a cell. */ getClasses?: ProcessedCellRefOrGetter<Record<string, boolean>>; /** A getter for the display component for a cell. */ - getDisplay: ProcessedCellRefOrGetter<Renderable>; + getDisplay: Renderable | ((row: number, col: number, state: State) => Renderable); /** Get the auto-generated ID for identifying a specific cell of this grid that appears in the DOM. Will not persist between refreshes or updates. */ getID: (row: number, col: number, state: State) => string; /** Get the persistent state of the given cell. */ @@ -214,7 +224,7 @@ function getCellHandler(grid: Grid, row: number, col: number): GridCell { return grid.getState(row, col); } case "id": - return target.id = target.id ?? getUniqueID("gridcell"); + return (target.id = target.id ?? getUniqueID("gridcell")); case "components": return [ computed(() => ( @@ -227,7 +237,7 @@ function getCellHandler(grid: Grid, row: number, col: number): GridCell { )) ]; } - + if (typeof key === "symbol") { return (grid as any)[key]; } @@ -264,12 +274,10 @@ function getCellHandler(grid: Grid, row: number, col: number): GridCell { return (grid as any)[key]; }, set(target, key, value) { - console.log("!!?", key, value) if (typeof key !== "string") { return false; } key = `set${key.slice(0, 1).toUpperCase() + key.slice(1)}`; - console.log(key, grid[key]) if (key in grid && isFunction((grid as any)[key]) && (grid as any)[key].length <= 3) { (grid as any)[key].call(grid, row, col, value); return true; @@ -334,20 +342,23 @@ export function createGrid<T extends GridOptions>(optionsFunc: () => T) { } = options; let getDisplay; - if (typeof _getDisplay === "object" && !isRef(_getDisplay) && !isJSXElement(_getDisplay)) { + if (typeof _getDisplay === "object" && !isJSXElement(_getDisplay)) { const { getTitle, getDescription } = _getDisplay; - const getProcessedTitle = convertCellMaybeRefOrGetter(getTitle); - const getProcessedDescription = convertCellMaybeRefOrGetter(getDescription); - getDisplay = function(row: number, col: number, state: State) { - const title = typeof getProcessedTitle === "function" ? getProcessedTitle(row, col, state) : unref(getProcessedTitle); - const description = typeof getProcessedDescription === "function" ? getProcessedDescription(row, col, state) : unref(getProcessedDescription); - return <> - {title} - {description} - </>; - } + getDisplay = function (row: number, col: number, state: State) { + const title = typeof getTitle === "function" ? getTitle(row, col, state) : getTitle; + const description = + typeof getDescription === "function" + ? getDescription(row, col, state) + : getDescription; + return ( + <> + {title} + {description} + </> + ); + }; } else { - getDisplay = convertCellMaybeRefOrGetter(_getDisplay); + getDisplay = _getDisplay; } const grid = { @@ -357,10 +368,13 @@ export function createGrid<T extends GridOptions>(optionsFunc: () => T) { <Column> {new Array(unref(grid.rows)).fill(0).map((_, row) => ( <Row> - {new Array(unref(grid.cols)).fill(0).map((_, col) => - render(grid.cells[row][col]))} - </Row>))} - </Column>)), + {new Array(unref(grid.cols)) + .fill(0) + .map((_, col) => render(grid.cells[row][col]))} + </Row> + ))} + </Column> + )), cellState, cells: new Proxy({} as GridCell[][], { get(target, key: PropertyKey) { @@ -422,8 +436,10 @@ export function createGrid<T extends GridOptions>(optionsFunc: () => T) { cols: processGetter(cols), getVisibility: convertCellMaybeRefOrGetter(getVisibility ?? true), getCanClick: convertCellMaybeRefOrGetter(getCanClick ?? true), - getStartState: typeof getStartState === "function" && getStartState.length > 0 ? - getStartState : processGetter(getStartState), + getStartState: + typeof getStartState === "function" && getStartState.length > 0 + ? getStartState + : processGetter(getStartState), getStyle: convertCellMaybeRefOrGetter(getStyle), getClasses: convertCellMaybeRefOrGetter(getClasses), getDisplay, diff --git a/src/features/infoboxes/infobox.tsx b/src/features/infoboxes/infobox.tsx index ccf676c..1a0405c 100644 --- a/src/features/infoboxes/infobox.tsx +++ b/src/features/infoboxes/infobox.tsx @@ -1,7 +1,7 @@ import Infobox from "features/infoboxes/Infobox.vue"; import type { Persistent } from "game/persistence"; import { persistent } from "game/persistence"; -import { processGetter } from "util/computed"; +import { MaybeGetter, processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue"; import { CSSProperties, MaybeRef, MaybeRefOrGetter } from "vue"; @@ -20,9 +20,9 @@ export interface InfoboxOptions extends VueFeatureOptions { /** CSS to apply to the body of the infobox. */ bodyStyle?: MaybeRefOrGetter<CSSProperties>; /** A header to appear at the top of the display. */ - title: MaybeRefOrGetter<Renderable>; + title: MaybeGetter<Renderable>; /** The main text that appears in the display. */ - display: MaybeRefOrGetter<Renderable>; + display: MaybeGetter<Renderable>; } /** An object that represents a feature that displays information in a collapsible way. */ @@ -34,9 +34,9 @@ export interface Infobox extends VueFeature { /** CSS to apply to the body of the infobox. */ bodyStyle?: MaybeRef<CSSProperties>; /** A header to appear at the top of the display. */ - title: MaybeRef<Renderable>; + title: MaybeGetter<Renderable>; /** The main text that appears in the display. */ - display: MaybeRef<Renderable>; + display: MaybeGetter<Renderable>; /** Whether or not this infobox is collapsed. */ collapsed: Persistent<boolean>; /** A symbol that helps identify features of the same type. */ @@ -70,8 +70,8 @@ export function createInfobox<T extends InfoboxOptions>(optionsFunc: () => T) { color: processGetter(color) ?? "--layer-color", titleStyle: processGetter(titleStyle), bodyStyle: processGetter(bodyStyle), - title: processGetter(title), - display: processGetter(display) + title, + display } satisfies Infobox; return infobox; diff --git a/src/features/particles/Particles.vue b/src/features/particles/Particles.vue index b7426f2..2ec2f0b 100644 --- a/src/features/particles/Particles.vue +++ b/src/features/particles/Particles.vue @@ -41,6 +41,7 @@ onMounted(() => { }); onBeforeUnmount(() => { app.value?.destroy(); + app.value = null; }); let isDirty = true; diff --git a/src/features/particles/particles.tsx b/src/features/particles/particles.tsx index a933a20..eaf33ec 100644 --- a/src/features/particles/particles.tsx +++ b/src/features/particles/particles.tsx @@ -3,8 +3,9 @@ import type { EmitterConfigV3 } from "@pixi/particle-emitter"; import { Emitter, upgradeConfig } from "@pixi/particle-emitter"; import { createLazyProxy } from "util/proxies"; import { VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue"; -import { Ref, shallowRef } from "vue"; +import { Ref, shallowRef, unref } from "vue"; import Particles from "./Particles.vue"; +import { processGetter } from "util/computed"; /** A symbol used to identify {@link Particles} features. */ export const ParticlesType = Symbol("Particles"); @@ -47,7 +48,10 @@ export interface Particles extends VueFeature { export function createParticles<T extends ParticlesOptions>(optionsFunc?: () => T) { return createLazyProxy(() => { const options = optionsFunc?.() ?? ({} as T); - const { onContainerResized, onHotReload, ...props } = options; + const { onContainerResized, onHotReload, style: _style, ...props } = options; + + const style = processGetter(_style); + options.style = () => ({ position: "static", ...(unref(style) ?? {}) }); let emittersToAdd: { resolve: (value: Emitter | PromiseLike<Emitter>) => void; @@ -57,6 +61,7 @@ export function createParticles<T extends ParticlesOptions>(optionsFunc?: () => function onInit(app: Application) { emittersToAdd.forEach(({ resolve, config }) => resolve(new Emitter(app.stage, config))); emittersToAdd = []; + particles.app.value = app; } const particles = { diff --git a/src/features/resources/MainDisplay.vue b/src/features/resources/MainDisplay.vue index d8225f7..3dab4d6 100644 --- a/src/features/resources/MainDisplay.vue +++ b/src/features/resources/MainDisplay.vue @@ -19,15 +19,16 @@ import Sticky from "components/layout/Sticky.vue"; import type { Resource } from "features/resources/resource"; import ResourceVue from "features/resources/Resource.vue"; import Decimal from "util/bignum"; +import { MaybeGetter } from "util/computed"; import { Renderable } from "util/vue"; -import { computed, MaybeRefOrGetter, ref, StyleValue, toValue } from "vue"; +import { computed, ref, StyleValue, toValue } from "vue"; const props = defineProps<{ resource: Resource; color?: string; classes?: Record<string, boolean>; style?: StyleValue; - effectDisplay?: MaybeRefOrGetter<Renderable>; + effectDisplay?: MaybeGetter<Renderable>; }>(); const displayRef = ref<Element | null>(null); diff --git a/src/features/tabs/tab.ts b/src/features/tabs/tab.ts index 104b9c2..1441ff7 100644 --- a/src/features/tabs/tab.ts +++ b/src/features/tabs/tab.ts @@ -1,8 +1,6 @@ -import { processGetter } from "util/computed"; +import { MaybeGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; -import { render, Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue"; -import { MaybeRef, MaybeRefOrGetter } from "vue"; -import { JSX } from "vue/jsx-runtime"; +import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue"; /** A symbol used to identify {@link Tab} features. */ export const TabType = Symbol("Tab"); @@ -12,7 +10,7 @@ export const TabType = Symbol("Tab"); */ export interface TabOptions extends VueFeatureOptions { /** The display to use for this tab. */ - display: MaybeRefOrGetter<Renderable>; + display: MaybeGetter<Renderable>; } /** @@ -21,7 +19,7 @@ export interface TabOptions extends VueFeatureOptions { */ export interface Tab extends VueFeature { /** The display to use for this tab. */ - display: MaybeRef<Renderable>; + display: MaybeGetter<Renderable>; /** A symbol that helps identify features of the same type. */ type: typeof TabType; } @@ -38,8 +36,8 @@ export function createTab<T extends TabOptions>(optionsFunc: () => T) { const tab = { type: TabType, ...(props as Omit<typeof props, keyof VueFeature | keyof TabOptions>), - ...vueFeatureMixin("tab", options, (): JSX.Element => render(tab.display)), - display: processGetter(display) + ...vueFeatureMixin("tab", options, display), + display } satisfies Tab; return tab; diff --git a/src/features/tabs/tabFamily.tsx b/src/features/tabs/tabFamily.tsx index 66cd590..88a9f4e 100644 --- a/src/features/tabs/tabFamily.tsx +++ b/src/features/tabs/tabFamily.tsx @@ -4,7 +4,7 @@ import TabButton from "features/tabs/TabButton.vue"; import TabFamily from "features/tabs/TabFamily.vue"; import type { Persistent } from "game/persistence"; import { persistent } from "game/persistence"; -import { processGetter } from "util/computed"; +import { MaybeGetter, processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue"; import type { CSSProperties, MaybeRef, MaybeRefOrGetter, Ref } from "vue"; @@ -20,9 +20,9 @@ export const TabFamilyType = Symbol("TabFamily"); */ export interface TabButtonOptions extends VueFeatureOptions { /** The tab to display when this button is clicked. */ - tab: Tab | MaybeRefOrGetter<Renderable>; + tab: Tab | MaybeGetter<Renderable>; /** The label on this button. */ - display: MaybeRefOrGetter<Renderable>; + display: MaybeGetter<Renderable>; /** The color of the glow effect to display when this button is active. */ glowColor?: MaybeRefOrGetter<string>; } @@ -33,9 +33,9 @@ export interface TabButtonOptions extends VueFeatureOptions { */ export interface TabButton extends VueFeature { /** The tab to display when this button is clicked. */ - tab: Tab | MaybeRef<Renderable>; + tab: Tab | MaybeGetter<Renderable>; /** The label on this button. */ - display: MaybeRef<Renderable>; + display: MaybeGetter<Renderable>; /** The color of the glow effect to display when this button is active. */ glowColor?: MaybeRef<string>; /** A symbol that helps identify features of the same type. */ @@ -64,7 +64,7 @@ export interface TabFamily extends VueFeature { /** All the tabs within this family. */ tabs: Record<string, TabButton>; /** The currently active tab, if any. */ - activeTab: Ref<Tab | MaybeRef<Renderable> | null>; + activeTab: Ref<Tab | MaybeGetter<Renderable> | null>; /** The name of the tab that is currently active. */ selected: Persistent<string>; /** A symbol that helps identify features of the same type. */ @@ -106,16 +106,17 @@ export function createTabFamily<T extends TabFamilyOptions>( const tabButton = { type: TabButtonType, ...(props as Omit<typeof props, keyof VueFeature | keyof TabButtonOptions>), - ...vueFeatureMixin("tabButton", options, () => + ...vueFeatureMixin("tabButton", options, () => ( <TabButton display={tabButton.display} glowColor={tabButton.glowColor} active={unref(tabButton.tab) === unref(tabFamily.activeTab)} - onSelectTab={() => tabFamily.selected.value = tab} - />), - tab: processGetter(buttonTab), + onSelectTab={() => (tabFamily.selected.value = tab)} + /> + )), + tab: buttonTab, glowColor: processGetter(glowColor), - display: processGetter(display) + display } satisfies TabButton; parsedTabs[tab] = tabButton; @@ -124,7 +125,7 @@ export function createTabFamily<T extends TabFamilyOptions>( buttonContainerClasses: processGetter(buttonContainerClasses), buttonContainerStyle: processGetter(buttonContainerStyle), selected, - activeTab: computed((): Tab | MaybeRef<Renderable> | null => { + activeTab: computed((): Tab | MaybeGetter<Renderable> | null => { if ( selected.value in tabFamily.tabs && isVisible(tabFamily.tabs[selected.value].visibility ?? true) diff --git a/src/features/trees/tree.tsx b/src/features/trees/tree.tsx index 8c37923..c517c3c 100644 --- a/src/features/trees/tree.tsx +++ b/src/features/trees/tree.tsx @@ -7,11 +7,11 @@ import TreeNode from "features/trees/TreeNode.vue"; import { noPersist } from "game/persistence"; import type { DecimalSource } from "util/bignum"; import Decimal, { format, formatWhole } from "util/bignum"; -import { processGetter } from "util/computed"; +import { MaybeGetter, processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue"; import type { MaybeRef, MaybeRefOrGetter, Ref } from "vue"; -import { computed, ref, shallowRef, unref } from "vue"; +import { ref, shallowRef, unref } from "vue"; /** A symbol used to identify {@link TreeNode} features. */ export const TreeNodeType = Symbol("TreeNode"); @@ -27,7 +27,7 @@ export interface TreeNodeOptions extends VueFeatureOptions { /** The background color for this node. */ color?: MaybeRefOrGetter<string>; /** The label to display on this tree node. */ - display?: MaybeRefOrGetter<Renderable>; + display?: MaybeGetter<Renderable>; /** The color of the glow effect shown to notify the user there's something to do with this node. */ glowColor?: MaybeRefOrGetter<string>; /** A reset object attached to this node, used for propagating resets through the tree. */ @@ -47,7 +47,7 @@ export interface TreeNode extends VueFeature { /** The background color for this node. */ color?: MaybeRef<string>; /** The label to display on this tree node. */ - display?: MaybeRef<Renderable>; + display?: MaybeGetter<Renderable>; /** The color of the glow effect shown to notify the user there's something to do with this node. */ glowColor?: MaybeRef<string>; /** A reset object attached to this node, used for propagating resets through the tree. */ @@ -84,7 +84,7 @@ export function createTreeNode<T extends TreeNodeOptions>(optionsFunc?: () => T) )), canClick: processGetter(canClick) ?? true, color: processGetter(color), - display: processGetter(display), + display, glowColor: processGetter(glowColor), onClick: onClick == null @@ -265,9 +265,9 @@ export function createResourceTooltip( resource: Resource, requiredResource: Resource | null = null, requirement: MaybeRefOrGetter<DecimalSource> = 0 -): Ref<string> { +): () => string { const req = processGetter(requirement); - return computed(() => { + return () => { if (requiredResource == null || Decimal.gte(resource.value, unref(req))) { return displayResource(resource) + " " + resource.displayName; } @@ -280,5 +280,5 @@ export function createResourceTooltip( ? formatWhole(requiredResource.value) : format(requiredResource.value, requiredResource.precision) })`; - }); + }; } diff --git a/src/game/boards/board.tsx b/src/game/boards/board.tsx index 80d7a08..4ef9080 100644 --- a/src/game/boards/board.tsx +++ b/src/game/boards/board.tsx @@ -275,7 +275,18 @@ export function makeDraggable<T, S extends MakeDraggableOptions<T>>( const position = persistent<NodePosition>({ x: 0, y: 0 }); const draggable = createLazyProxy(() => { const options = optionsFunc(); - const { id, nodeBeingDragged, hasDragged, dragDelta, startDrag, endDrag, onMouseDown, onMouseUp, initialPosition, ...props } = options; + const { + id, + nodeBeingDragged, + hasDragged, + dragDelta, + startDrag, + endDrag, + onMouseDown, + onMouseUp, + initialPosition, + ...props + } = options; position[DefaultValue] = initialPosition ?? position[DefaultValue]; @@ -323,7 +334,7 @@ export function makeDraggable<T, S extends MakeDraggableOptions<T>>( runAfterEvaluation(element, el => { draggable.id; // Ensure draggable gets evaluated - (el as VueFeature & { draggable: Draggable<T> }).draggable = draggable; + (el as VueFeature & { draggable: Draggable<T> }).draggable = draggable; element.wrappers.push(el => ( <Draggable mouseDown={draggable.onMouseDown} diff --git a/src/game/layers.tsx b/src/game/layers.tsx index e6ab118..07c5656 100644 --- a/src/game/layers.tsx +++ b/src/game/layers.tsx @@ -5,9 +5,9 @@ import { persistent } from "game/persistence"; import player from "game/player"; import type { Emitter } from "nanoevents"; import { createNanoEvents } from "nanoevents"; -import { processGetter } from "util/computed"; +import { MaybeGetter, processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; -import { render, Renderable } from "util/vue"; +import { Renderable } from "util/vue"; import { computed, type CSSProperties, @@ -108,7 +108,7 @@ export interface LayerOptions { * The layout of this layer's features. * When the layer is open in {@link game/player.PlayerData.tabs}, this is the content that is displayed. */ - display: MaybeRefOrGetter<Renderable>; + display: MaybeGetter<Renderable>; /** An object of classes that should be applied to the display. */ classes?: MaybeRefOrGetter<Record<string, boolean>>; /** Styles that should be applied to the display. */ @@ -127,7 +127,7 @@ export interface LayerOptions { * The layout of this layer's features. * When the layer is open in {@link game/player.PlayerData.tabs}, but the tab is {@link Layer.minimized} this is the content that is displayed. */ - minimizedDisplay?: MaybeRefOrGetter<Renderable>; + minimizedDisplay?: MaybeGetter<Renderable>; /** * Whether or not to force the go back button to be hidden. * If true, go back will be hidden regardless of {@link data/projInfo.allowGoBack}. @@ -169,7 +169,7 @@ export interface Layer extends BaseLayer { * The layout of this layer's features. * When the layer is open in {@link game/player.PlayerData.tabs}, this is the content that is displayed. */ - display: MaybeRef<Renderable>; + display: MaybeGetter<Renderable>; /** An object of classes that should be applied to the display. */ classes?: MaybeRef<Record<string, boolean>>; /** Styles that should be applied to the display. */ @@ -188,7 +188,7 @@ export interface Layer extends BaseLayer { * The layout of this layer's features. * When the layer is open in {@link game/player.PlayerData.tabs}, but the tab is {@link Layer.minimized} this is the content that is displayed. */ - minimizedDisplay?: MaybeRef<Renderable>; + minimizedDisplay?: MaybeGetter<Renderable>; /** * Whether or not to force the go back button to be hidden. * If true, go back will be hidden regardless of {@link data/projInfo.allowGoBack}. @@ -261,7 +261,7 @@ export function createLayer<T extends LayerOptions>( ...baseLayer, ...(props as Omit<typeof props, keyof LayerOptions>), color: processGetter(color), - display: processGetter(display), + display, classes: processGetter(classes), style: computed((): CSSProperties => { let width = unref(layer.minWidth); @@ -293,7 +293,7 @@ export function createLayer<T extends LayerOptions>( forceHideGoBack: processGetter(forceHideGoBack), minWidth: processGetter(minWidth) ?? 600, minimizable: processGetter(minimizable) ?? true, - minimizedDisplay: processGetter(minimizedDisplay) + minimizedDisplay } satisfies Layer; return layer; @@ -370,21 +370,21 @@ export function reloadLayer(layer: Layer): void { */ export function setupLayerModal(layer: Layer): { openModal: VoidFunction; - modal: Ref<JSX.Element>; + modal: () => JSX.Element; } { const showModal = ref(false); return { openModal: () => (showModal.value = true), - modal: computed(() => ( + modal: () => ( <Modal modelValue={showModal.value} onUpdate:modelValue={value => (showModal.value = value)} v-slots={{ header: () => <h2>{unref(layer.name)}</h2>, - body: () => render(layer.display) + body: typeof layer.display ? layer.display : () => layer.display }} /> - )) + ) }; } diff --git a/src/game/modifiers.tsx b/src/game/modifiers.tsx index 286c18c..ec2a8d7 100644 --- a/src/game/modifiers.tsx +++ b/src/game/modifiers.tsx @@ -3,7 +3,7 @@ import settings from "game/settings"; import type { DecimalSource } from "util/bignum"; import Decimal, { formatSmall } from "util/bignum"; import type { RequiredKeys, WithRequired } from "util/common"; -import { processGetter } from "util/computed"; +import { MaybeGetter, processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; import { render, Renderable } from "util/vue"; import { computed, MaybeRef, MaybeRefOrGetter, unref } from "vue"; @@ -32,7 +32,7 @@ export interface Modifier { * A description of this modifier. * @see {@link createModifierSection}. */ - description?: MaybeRef<Renderable>; + description?: MaybeGetter<Renderable>; } /** Utility type that represents the output of all modifiers that represent a single operation. */ @@ -46,7 +46,7 @@ export interface AdditiveModifierOptions { /** The amount to add to the input value. */ addend: MaybeRefOrGetter<DecimalSource>; /** Description of what this modifier is doing. */ - description?: MaybeRefOrGetter<Renderable>; + description?: MaybeGetter<Renderable>; /** A MaybeRefOrGetter that will be processed and passed directly into the returned modifier. */ enabled?: MaybeRefOrGetter<boolean>; /** Determines if numbers larger or smaller than 0 should be displayed as red. */ @@ -64,7 +64,6 @@ export function createAdditiveModifier<T extends AdditiveModifierOptions, S = Op const { addend, description, enabled, smallerIsBetter } = optionsFunc(); const processedAddend = processGetter(addend); - const processedDescription = processGetter(description); const processedEnabled = enabled == null ? undefined : processGetter(enabled); return { apply: (gain: DecimalSource) => Decimal.add(gain, unref(processedAddend)), @@ -72,13 +71,11 @@ export function createAdditiveModifier<T extends AdditiveModifierOptions, S = Op getFormula: (gain: FormulaSource) => Formula.add(gain, processedAddend), enabled: processedEnabled, description: - processedDescription == null + description == null ? undefined - : computed(() => ( + : () => ( <div class="modifier-container"> - <span class="modifier-description"> - {render(processedDescription)} - </span> + <span class="modifier-description">{render(description)}</span> <span class="modifier-amount" style={ @@ -95,7 +92,7 @@ export function createAdditiveModifier<T extends AdditiveModifierOptions, S = Op {formatSmall(unref(processedAddend))} </span> </div> - )) + ) }; }) as S; } @@ -105,7 +102,7 @@ export interface MultiplicativeModifierOptions { /** The amount to multiply the input value by. */ multiplier: MaybeRefOrGetter<DecimalSource>; /** Description of what this modifier is doing. */ - description?: MaybeRefOrGetter<Renderable> | undefined; + description?: MaybeGetter<Renderable> | undefined; /** A MaybeRefOrGetter that will be processed and passed directly into the returned modifier. */ enabled?: MaybeRefOrGetter<boolean> | undefined; /** Determines if numbers larger or smaller than 1 should be displayed as red. */ @@ -124,7 +121,6 @@ export function createMultiplicativeModifier< const { multiplier, description, enabled, smallerIsBetter } = optionsFunc(); const processedMultiplier = processGetter(multiplier); - const processedDescription = processGetter(description); const processedEnabled = enabled == null ? undefined : processGetter(enabled); return { apply: (gain: DecimalSource) => Decimal.times(gain, unref(processedMultiplier)), @@ -132,13 +128,11 @@ export function createMultiplicativeModifier< getFormula: (gain: FormulaSource) => Formula.times(gain, processedMultiplier), enabled: processedEnabled, description: - processedDescription == null + description == null ? undefined - : computed(() => ( + : () => ( <div class="modifier-container"> - <span class="modifier-description"> - {render(processedDescription)} - </span> + <span class="modifier-description">{render(description)}</span> <span class="modifier-amount" style={ @@ -154,7 +148,7 @@ export function createMultiplicativeModifier< ×{formatSmall(unref(processedMultiplier))} </span> </div> - )) + ) }; }) as S; } @@ -164,7 +158,7 @@ export interface ExponentialModifierOptions { /** The amount to raise the input value to the power of. */ exponent: MaybeRefOrGetter<DecimalSource>; /** Description of what this modifier is doing. */ - description?: MaybeRefOrGetter<Renderable> | undefined; + description?: MaybeGetter<Renderable> | undefined; /** A MaybeRefOrGetter that will be processed and passed directly into the returned modifier. */ enabled?: MaybeRefOrGetter<boolean> | undefined; /** Add 1 before calculating, then remove it afterwards. This prevents low numbers from becoming lower. */ @@ -186,7 +180,6 @@ export function createExponentialModifier< optionsFunc(); const processedExponent = processGetter(exponent); - const processedDescription = processGetter(description); const processedEnabled = enabled == null ? undefined : processGetter(enabled); return { apply: (gain: DecimalSource) => { @@ -217,12 +210,12 @@ export function createExponentialModifier< : Formula.pow(gain, processedExponent), enabled: processedEnabled, description: - processedDescription == null + description == null ? undefined - : computed(() => ( + : () => ( <div class="modifier-container"> <span class="modifier-description"> - {render(processedDescription)} + {render(description)} {supportLowNumbers ? " (+1 effective)" : null} </span> <span @@ -240,7 +233,7 @@ export function createExponentialModifier< ^{formatSmall(unref(processedExponent))} </span> </div> - )) + ) }; }) as S; } @@ -286,14 +279,13 @@ export function createSequentialModifier< ? computed(() => modifiers.filter(m => unref(m.enabled) !== false).length > 0) : undefined, description: modifiers.some(m => m.description != null) - ? computed(() => + ? () => ( modifiers .filter(m => unref(m.enabled) !== false) .map(m => unref(m.description)) - .filter(d => d) as MaybeRef<Renderable>[] + .filter(d => d) as MaybeGetter<Renderable>[] ).map(m => render(m)) - ) : undefined }; }) as S; @@ -312,7 +304,7 @@ export interface ModifierSectionOptions { /** The unit of the value being modified, if any. */ unit?: string; /** The label to use for the base value. Defaults to "Base". */ - baseText?: MaybeRefOrGetter<Renderable>; + baseText?: MaybeGetter<Renderable>; /** Determines if numbers larger or smaller than the base should be displayed as red. */ smallerIsBetter?: boolean; } @@ -332,7 +324,6 @@ export function createModifierSection({ smallerIsBetter }: ModifierSectionOptions) { const total = modifier.apply(base ?? 1); - const processedBaseText = processGetter(baseText); return ( <div style={{ "--unit": settings.alignUnits && unit != null ? "'" + unit + "'" : "" }}> <h3> @@ -341,7 +332,7 @@ export function createModifierSection({ </h3> <br /> <div class="modifier-container"> - <span class="modifier-description">{render(processedBaseText ?? "Base")}</span> + <span class="modifier-description">{render(baseText ?? "Base")}</span> <span class="modifier-amount"> {formatSmall(base ?? 1)} {unit} diff --git a/src/game/requirements.tsx b/src/game/requirements.tsx index ba3088e..15d6368 100644 --- a/src/game/requirements.tsx +++ b/src/game/requirements.tsx @@ -1,9 +1,9 @@ import { isVisible, Visibility } from "features/feature"; import { displayResource, Resource } from "features/resources/resource"; import Decimal, { DecimalSource } from "lib/break_eternity"; -import { processGetter } from "util/computed"; +import { MaybeGetter, processGetter } from "util/computed"; import { createLazyProxy } from "util/proxies"; -import { joinJSX, render, Renderable } from "util/vue"; +import { joinJSX, Renderable } from "util/vue"; import { computed, MaybeRef, MaybeRefOrGetter, unref } from "vue"; import Formula, { calculateCost, calculateMaxAffordable } from "./formulas/formulas"; import type { GenericFormula, InvertibleIntegralFormula } from "./formulas/types"; @@ -262,16 +262,16 @@ export function createVisibilityRequirement( */ export function createBooleanRequirement( requirement: MaybeRefOrGetter<boolean>, - display?: MaybeRefOrGetter<Renderable> + display?: MaybeGetter<Renderable> ): Requirement { return createLazyProxy(() => { - const processedDisplay = processGetter(display); + const partialDisplay = + display == null ? undefined : typeof display === "function" ? display : () => display; return { requirementMet: processGetter(requirement), - partialDisplay: processedDisplay == null ? undefined : () => render(processedDisplay), - display: - processedDisplay == null ? undefined : () => <>Req: {render(processedDisplay)}</>, - visibility: processedDisplay == null ? Visibility.None : Visibility.Visible, + partialDisplay, + display: display == null ? undefined : () => <>Req: {partialDisplay}</>, + visibility: display == null ? Visibility.None : Visibility.Visible, requiresPay: false }; }); diff --git a/src/game/settings.ts b/src/game/settings.ts index 9360001..8b0c464 100644 --- a/src/game/settings.ts +++ b/src/game/settings.ts @@ -2,10 +2,10 @@ import projInfo from "data/projInfo.json"; import { Themes } from "data/themes"; import { globalBus } from "game/events"; import LZString from "lz-string"; -import { processGetter } from "util/computed"; +import { MaybeGetter } from "util/computed"; import { decodeSave, hardReset } from "util/save"; import { Renderable } from "util/vue"; -import { MaybeRef, MaybeRefOrGetter, reactive, watch } from "vue"; +import { reactive, watch } from "vue"; /** The player's settings object. */ export interface Settings { @@ -101,22 +101,22 @@ export function loadSettings(): void { } /** A list of fields to append to the settings modal. */ -export const settingFields: MaybeRef<Renderable>[] = reactive([]); +export const settingFields: MaybeGetter<Renderable>[] = reactive([]); /** Register a field to be displayed in the settings modal. */ -export function registerSettingField(component: MaybeRefOrGetter<Renderable>) { - settingFields.push(processGetter(component)); +export function registerSettingField(component: MaybeGetter<Renderable>) { + settingFields.push(component); } /** A list of components to show in the info modal. */ -export const infoComponents: MaybeRef<Renderable>[] = reactive([]); +export const infoComponents: MaybeGetter<Renderable>[] = reactive([]); /** Register a component to be displayed in the info modal. */ -export function registerInfoComponent(component: MaybeRefOrGetter<Renderable>) { - infoComponents.push(processGetter(component)); +export function registerInfoComponent(component: MaybeGetter<Renderable>) { + infoComponents.push(component); } /** A list of components to add to the root of the page. */ -export const gameComponents: MaybeRef<Renderable>[] = reactive([]); +export const gameComponents: MaybeGetter<Renderable>[] = reactive([]); /** Register a component to be displayed at the root of the page. */ -export function registerGameComponent(component: MaybeRefOrGetter<Renderable>) { - gameComponents.push(processGetter(component)); +export function registerGameComponent(component: MaybeGetter<Renderable>) { + gameComponents.push(component); } diff --git a/src/util/computed.ts b/src/util/computed.ts index c1ad5d3..56051cd 100644 --- a/src/util/computed.ts +++ b/src/util/computed.ts @@ -2,6 +2,8 @@ import { isFunction } from "util/common"; import type { ComputedRef } from "vue"; import { computed } from "vue"; +export type MaybeGetter<T> = T | (() => T); + export function processGetter<T>(obj: T): T extends () => infer S ? ComputedRef<S> : T { if (isFunction(obj)) { return computed(obj) as ReturnType<typeof processGetter<T>>; diff --git a/src/util/vue.tsx b/src/util/vue.tsx index b59605d..2ced50e 100644 --- a/src/util/vue.tsx +++ b/src/util/vue.tsx @@ -6,9 +6,9 @@ import Col from "components/layout/Column.vue"; import Row from "components/layout/Row.vue"; import { getUniqueID, Visibility } from "features/feature"; import VueFeatureComponent from "features/VueFeature.vue"; -import { processGetter } from "util/computed"; +import { MaybeGetter, processGetter } from "util/computed"; import type { CSSProperties, MaybeRef, MaybeRefOrGetter, Ref } from "vue"; -import { isRef, onUnmounted, ref, unref } from "vue"; +import { isRef, onUnmounted, ref, toValue } from "vue"; import { JSX } from "vue/jsx-runtime"; import { camelToKebab } from "./common"; @@ -35,7 +35,7 @@ export interface VueFeature { /** CSS to apply to this feature. */ style?: MaybeRef<CSSProperties>; /** The components to render inside the vue feature */ - components: MaybeRef<Renderable>[]; + components: MaybeGetter<Renderable>[]; /** The components to render wrapped around the vue feature */ wrappers: ((el: () => Renderable) => Renderable)[]; /** Used to identify Vue Features */ @@ -45,14 +45,14 @@ export interface VueFeature { export function vueFeatureMixin( featureName: string, options: VueFeatureOptions, - component?: MaybeRefOrGetter<Renderable> + component?: MaybeGetter<Renderable> ) { return { id: getUniqueID(featureName), visibility: processGetter(options.visibility), classes: processGetter(options.classes), style: processGetter(options.style), - components: component == null ? [] : [processGetter(component)], + components: component == null ? [] : [component], wrappers: [] as ((el: () => Renderable) => Renderable)[], [VueFeature]: true } satisfies VueFeature; @@ -60,15 +60,15 @@ export function vueFeatureMixin( export function render(object: VueFeature, wrapper?: (el: Renderable) => Renderable): JSX.Element; export function render<T extends Renderable>( - object: MaybeRef<Renderable>, + object: MaybeGetter<Renderable>, wrapper?: (el: Renderable) => T ): T; export function render( - object: VueFeature | MaybeRef<Renderable>, + object: VueFeature | MaybeGetter<Renderable>, wrapper?: (el: Renderable) => Renderable ): Renderable; export function render( - object: VueFeature | MaybeRef<Renderable>, + object: VueFeature | MaybeGetter<Renderable>, wrapper?: (el: Renderable) => Renderable ) { if (typeof object === "object" && VueFeature in object) { @@ -85,20 +85,24 @@ export function render( ); } - object = unref(object); + object = toValue(object); return wrapper?.(object) ?? object; } -export function renderRow(...objects: (VueFeature | MaybeRef<Renderable>)[]): JSX.Element { +export function renderRow( + ...objects: (VueFeature | MaybeGetter<Renderable>)[] +): JSX.Element { return <Row>{objects.map(obj => render(obj))}</Row>; } -export function renderCol(...objects: (VueFeature | MaybeRef<Renderable>)[]): JSX.Element { +export function renderCol( + ...objects: (VueFeature | MaybeGetter<Renderable>)[] +): JSX.Element { return <Col>{objects.map(obj => render(obj))}</Col>; } export function joinJSX( - objects: (VueFeature | MaybeRef<Renderable>)[], + objects: (VueFeature | MaybeGetter<Renderable>)[], joiner: JSX.Element ): JSX.Element { return objects.reduce<JSX.Element>( diff --git a/src/wrappers/marks/mark.tsx b/src/wrappers/marks/mark.tsx index ac0e810..d269353 100644 --- a/src/wrappers/marks/mark.tsx +++ b/src/wrappers/marks/mark.tsx @@ -1,4 +1,3 @@ -import { type OptionsFunc } from "features/feature"; import { processGetter } from "util/computed"; import { createLazyProxy, runAfterEvaluation } from "util/proxies"; import type { VueFeature } from "util/vue"; @@ -23,9 +22,9 @@ export interface Mark { * @param element The renderable feature to display the tooltip on. * @param options Mark options. */ -export function addMark<T extends MarkOptions>( +export function addMark( element: VueFeature, - optionsFunc: OptionsFunc<T, Mark> + optionsFunc: () => MarkOptions ): asserts element is VueFeature & { mark: Mark } { const mark = createLazyProxy(() => { const options = optionsFunc(); diff --git a/src/wrappers/tooltips/tooltip.tsx b/src/wrappers/tooltips/tooltip.tsx index 453d317..f736688 100644 --- a/src/wrappers/tooltips/tooltip.tsx +++ b/src/wrappers/tooltips/tooltip.tsx @@ -1,7 +1,7 @@ -import { isVisible, type OptionsFunc } from "features/feature"; +import { isVisible } from "features/feature"; import { deletePersistent, persistent } from "game/persistence"; import { Direction } from "util/common"; -import { processGetter } from "util/computed"; +import { MaybeGetter, processGetter } from "util/computed"; import { createLazyProxy, runAfterEvaluation } from "util/proxies"; import { Renderable, vueFeatureMixin, type VueFeature, type VueFeatureOptions } from "util/vue"; import { MaybeRef, MaybeRefOrGetter, type Ref } from "vue"; @@ -21,7 +21,7 @@ export interface TooltipOptions extends VueFeatureOptions { /** Whether or not this tooltip can be pinned, meaning it'll stay visible even when not hovered. */ pinnable?: boolean; /** The text to display inside the tooltip. */ - display: MaybeRefOrGetter<Renderable>; + display: MaybeGetter<Renderable>; /** The direction in which to display the tooltip */ direction?: MaybeRefOrGetter<Direction>; /** The x offset of the tooltip, in px. */ @@ -35,7 +35,7 @@ export interface Tooltip extends VueFeature { /** Whether or not this tooltip can be pinned, meaning it'll stay visible even when not hovered. */ pinnable?: boolean; /** The text to display inside the tooltip. */ - display: MaybeRef<Renderable>; + display: MaybeGetter<Renderable>; /** The direction in which to display the tooltip */ direction?: MaybeRef<Direction>; /** The x offset of the tooltip, in px. */ @@ -51,9 +51,9 @@ export interface Tooltip extends VueFeature { * @param element The renderable feature to display the tooltip on. * @param options Tooltip options. */ -export function addTooltip<T extends TooltipOptions>( +export function addTooltip( element: VueFeature, - optionsFunc: OptionsFunc<T, Tooltip> + optionsFunc: () => TooltipOptions ): asserts element is VueFeature & { tooltip: Tooltip } { const pinned = persistent<boolean>(false, false); const tooltip = createLazyProxy(() => { @@ -69,7 +69,7 @@ export function addTooltip<T extends TooltipOptions>( ...vueFeatureMixin("tooltip", options), pinnable: pinnable ?? true, pinned: pinnable === false ? undefined : pinned, - display: processGetter(display), + display, direction: processGetter(direction ?? Direction.Up), xoffset: processGetter(xoffset), yoffset: processGetter(yoffset) diff --git a/tests/game/modifiers.test.ts b/tests/game/modifiers.test.ts index d7ef786..5383523 100644 --- a/tests/game/modifiers.test.ts +++ b/tests/game/modifiers.test.ts @@ -9,15 +9,16 @@ import { } from "game/modifiers"; import Decimal, { DecimalSource } from "util/bignum"; import { WithRequired } from "util/common"; +import { MaybeGetter } from "util/computed"; +import { render, Renderable } from "util/vue"; import { beforeAll, describe, expect, test } from "vitest"; import { MaybeRefOrGetter, Ref, ref, unref } from "vue"; import "../utils"; -import { render, Renderable } from "util/vue"; export type ModifierConstructorOptions = { [S in "addend" | "multiplier" | "exponent"]: MaybeRefOrGetter<DecimalSource>; } & { - description?: MaybeRefOrGetter<Renderable>; + description?: MaybeGetter<Renderable>; enabled?: MaybeRefOrGetter<boolean>; smallerIsBetter?: boolean; }; From c61bf64b1564d1e8cfe85f42796061df8de06d39 Mon Sep 17 00:00:00 2001 From: thepaperpilot <thepaperpilot@gmail.com> Date: Wed, 11 Dec 2024 14:29:10 -0600 Subject: [PATCH 51/58] Remove Grid --- src/features/grids/grid.tsx | 480 ------------------------------------ 1 file changed, 480 deletions(-) delete mode 100644 src/features/grids/grid.tsx diff --git a/src/features/grids/grid.tsx b/src/features/grids/grid.tsx deleted file mode 100644 index 97b94d1..0000000 --- a/src/features/grids/grid.tsx +++ /dev/null @@ -1,480 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import Column from "components/layout/Column.vue"; -import Row from "components/layout/Row.vue"; -import Clickable from "features/clickables/Clickable.vue"; -import { getUniqueID, Visibility } from "features/feature"; -import type { Persistent, State } from "game/persistence"; -import { persistent } from "game/persistence"; -import { isFunction } from "util/common"; -import { MaybeGetter, processGetter } from "util/computed"; -import { createLazyProxy } from "util/proxies"; -import { - isJSXElement, - render, - Renderable, - VueFeature, - vueFeatureMixin, - VueFeatureOptions -} from "util/vue"; -import type { CSSProperties, MaybeRef, MaybeRefOrGetter, Ref } from "vue"; -import { computed, unref } from "vue"; - -/** A symbol used to identify {@link Grid} features. */ -export const GridType = Symbol("Grid"); - -/** A type representing a MaybeRefOrGetter value for a cell in the grid. */ -export type CellMaybeRefOrGetter<T> = - | MaybeRefOrGetter<T> - | ((row: number, col: number, state: State) => T); -export type ProcessedCellRefOrGetter<T> = - | MaybeRef<T> - | ((row: number, col: number, state: State) => T); - -/** - * Represents a cell within a grid. These properties will typically be accessed via a cell proxy that calls functions on the grid to get the properties for a specific cell. - * @see {@link createGridProxy} - */ -export interface GridCell extends VueFeature { - /** Which roe in the grid this cell is from. */ - row: number; - /** Which col in the grid this cell is from. */ - col: number; - /** Whether this cell can be clicked. */ - canClick: boolean; - /** The initial persistent state of this cell. */ - startState: State; - /** The persistent state of this cell. */ - state: State; - /** The main text that appears in the display. */ - display: MaybeGetter<Renderable>; - /** A function that is called when the cell is clicked. */ - onClick?: (e?: MouseEvent | TouchEvent) => void; - /** A function that is called when the cell is held down. */ - onHold?: VoidFunction; -} - -/** - * An object that configures a {@link Grid}. - */ -export interface GridOptions extends VueFeatureOptions { - /** The number of rows in the grid. */ - rows: MaybeRefOrGetter<number>; - /** The number of columns in the grid. */ - cols: MaybeRefOrGetter<number>; - /** A getter for the visibility of a cell. */ - getVisibility?: CellMaybeRefOrGetter<Visibility | boolean>; - /** A getter for if a cell can be clicked. */ - getCanClick?: CellMaybeRefOrGetter<boolean>; - /** A getter for the initial persistent state of a cell. */ - getStartState: MaybeRefOrGetter<State> | ((row: number, col: number) => State); - /** A getter for the CSS styles for a cell. */ - getStyle?: CellMaybeRefOrGetter<CSSProperties>; - /** A getter for the CSS classes for a cell. */ - getClasses?: CellMaybeRefOrGetter<Record<string, boolean>>; - /** A getter for the display component for a cell. */ - getDisplay: - | Renderable - | ((row: number, col: number, state: State) => Renderable) - | { - getTitle?: Renderable | ((row: number, col: number, state: State) => Renderable); - getDescription: Renderable | ((row: number, col: number, state: State) => Renderable); - }; - /** A function that is called when a cell is clicked. */ - onClick?: (row: number, col: number, state: State, e?: MouseEvent | TouchEvent) => void; - /** A function that is called when a cell is held down. */ - onHold?: (row: number, col: number, state: State) => void; -} - -/** An object that represents a feature that is a grid of cells that all behave according to the same rules. */ -export interface Grid extends VueFeature { - /** A function that is called when a cell is clicked. */ - onClick?: (row: number, col: number, state: State, e?: MouseEvent | TouchEvent) => void; - /** A function that is called when a cell is held down. */ - onHold?: (row: number, col: number, state: State) => void; - /** A getter for determine the visibility of a cell. */ - getVisibility?: ProcessedCellRefOrGetter<Visibility | boolean>; - /** A getter for determine if a cell can be clicked. */ - getCanClick?: ProcessedCellRefOrGetter<boolean>; - /** The number of rows in the grid. */ - rows: MaybeRef<number>; - /** The number of columns in the grid. */ - cols: MaybeRef<number>; - /** A getter for the initial persistent state of a cell. */ - getStartState: MaybeRef<State> | ((row: number, col: number) => State); - /** A getter for the CSS styles for a cell. */ - getStyle?: ProcessedCellRefOrGetter<CSSProperties>; - /** A getter for the CSS classes for a cell. */ - getClasses?: ProcessedCellRefOrGetter<Record<string, boolean>>; - /** A getter for the display component for a cell. */ - getDisplay: Renderable | ((row: number, col: number, state: State) => Renderable); - /** Get the auto-generated ID for identifying a specific cell of this grid that appears in the DOM. Will not persist between refreshes or updates. */ - getID: (row: number, col: number, state: State) => string; - /** Get the persistent state of the given cell. */ - getState: (row: number, col: number) => State; - /** Set the persistent state of the given cell. */ - setState: (row: number, col: number, state: State) => void; - /** A dictionary of cells within this grid. */ - cells: GridCell[][]; - /** The persistent state of this grid, which is a dictionary of cell states. */ - cellState: Persistent<Record<number, Record<number, State>>>; - /** A symbol that helps identify features of the same type. */ - type: typeof GridType; -} - -function getCellRowHandler(grid: Grid, row: number) { - return new Proxy({} as GridCell[], { - get(target, key) { - if (key === "length") { - return unref(grid.cols); - } - if (typeof key !== "number" && typeof key !== "string") { - return; - } - - const keyNum = typeof key === "number" ? key : parseInt(key); - if (Number.isFinite(keyNum) && keyNum < unref(grid.cols)) { - if (keyNum in target) { - return target[keyNum]; - } - return (target[keyNum] = getCellHandler(grid, row, keyNum)); - } - }, - set(target, key, value) { - console.warn("Cannot set grid cells", target, key, value); - return false; - }, - ownKeys() { - return [...new Array(unref(grid.cols)).fill(0).map((_, i) => "" + i), "length"]; - }, - has(target, key) { - if (key === "length") { - return true; - } - if (typeof key !== "number" && typeof key !== "string") { - return false; - } - const keyNum = typeof key === "number" ? key : parseInt(key); - if (!Number.isFinite(keyNum) || keyNum >= unref(grid.cols)) { - return false; - } - return true; - }, - getOwnPropertyDescriptor(target, key) { - if (typeof key !== "number" && typeof key !== "string") { - return; - } - const keyNum = typeof key === "number" ? key : parseInt(key); - if (key !== "length" && (!Number.isFinite(keyNum) || keyNum >= unref(grid.cols))) { - return; - } - return { - configurable: true, - enumerable: true, - writable: false - }; - } - }); -} - -/** - * Returns traps for a proxy that will get the properties for the specified cell - * @param id The grid cell ID to get properties from. - * @see {@link getGridHandler} - * @see {@link createGridProxy} - */ -function getCellHandler(grid: Grid, row: number, col: number): GridCell { - const keys = [ - "id", - "visibility", - "classes", - "style", - "components", - "wrappers", - VueFeature, - "row", - "col", - "canClick", - "startState", - "state", - "title", - "display", - "onClick", - "onHold" - ] as const; - const cache: Record<string, Ref<unknown>> = {}; - return new Proxy({} as GridCell, { - // The typing in this function is absolutely atrocious in order to support custom properties - get(target, key, receiver) { - switch (key) { - case "wrappers": - return []; - case VueFeature: - return true; - case "row": - return row; - case "col": - return col; - case "startState": { - if (typeof grid.getStartState === "function") { - return grid.getStartState(row, col); - } - return unref(grid.getStartState); - } - case "state": { - return grid.getState(row, col); - } - case "id": - return (target.id = target.id ?? getUniqueID("gridcell")); - case "components": - return [ - computed(() => ( - <Clickable - onClick={receiver.onClick} - onHold={receiver.onHold} - display={receiver.display} - canClick={receiver.canClick} - /> - )) - ]; - } - - if (typeof key === "symbol") { - return (grid as any)[key]; - } - - key = key.slice(0, 1).toUpperCase() + key.slice(1); - - let prop = (grid as any)[`get${key}`]; - if (isFunction(prop)) { - if (!(key in cache)) { - cache[key] = computed(() => - prop.call(receiver, row, col, grid.getState(row, col)) - ); - } - return cache[key].value; - } else if (prop != null) { - return unref(prop); - } - - prop = (grid as any)[`on${key}`]; - if (isFunction(prop)) { - return () => prop.call(receiver, row, col, grid.getState(row, col)); - } else if (prop != null) { - return prop; - } - - // Revert key change - key = key.slice(0, 1).toLowerCase() + key.slice(1); - prop = (grid as any)[key]; - - if (isFunction(prop)) { - return () => prop.call(receiver, row, col, grid.getState(row, col)); - } - - return (grid as any)[key]; - }, - set(target, key, value) { - if (typeof key !== "string") { - return false; - } - key = `set${key.slice(0, 1).toUpperCase() + key.slice(1)}`; - if (key in grid && isFunction((grid as any)[key]) && (grid as any)[key].length <= 3) { - (grid as any)[key].call(grid, row, col, value); - return true; - } else { - console.warn(`No setter for "${key}".`, target); - return false; - } - }, - ownKeys() { - return keys; - }, - has(target, key) { - return (keys as readonly (string | symbol)[]).includes(key); - }, - getOwnPropertyDescriptor(target, key) { - if ((keys as readonly (string | symbol)[]).includes(key)) { - return { - configurable: true, - enumerable: true, - writable: false - }; - } - } - }); -} - -function convertCellMaybeRefOrGetter<T>( - value: NonNullable<CellMaybeRefOrGetter<T>> -): ProcessedCellRefOrGetter<T>; -function convertCellMaybeRefOrGetter<T>( - value: CellMaybeRefOrGetter<T> | undefined -): ProcessedCellRefOrGetter<T> | undefined; -function convertCellMaybeRefOrGetter<T>( - value: CellMaybeRefOrGetter<T> -): ProcessedCellRefOrGetter<T> { - if (typeof value === "function" && value.length > 0) { - return value; - } - return processGetter(value) as MaybeRef<T>; -} - -/** - * Lazily creates a grid with the given options. - * @param optionsFunc Grid options. - */ -export function createGrid<T extends GridOptions>(optionsFunc: () => T) { - const cellState = persistent<Record<number, Record<number, State>>>({}, false); - return createLazyProxy(() => { - const options = optionsFunc(); - const { - rows, - cols, - getVisibility, - getCanClick, - getStartState, - getStyle, - getClasses, - getDisplay: _getDisplay, - onClick, - onHold, - ...props - } = options; - - let getDisplay; - if (typeof _getDisplay === "object" && !isJSXElement(_getDisplay)) { - const { getTitle, getDescription } = _getDisplay; - getDisplay = function (row: number, col: number, state: State) { - const title = typeof getTitle === "function" ? getTitle(row, col, state) : getTitle; - const description = - typeof getDescription === "function" - ? getDescription(row, col, state) - : getDescription; - return ( - <> - {title} - {description} - </> - ); - }; - } else { - getDisplay = _getDisplay; - } - - const grid = { - type: GridType, - ...(props as Omit<typeof props, keyof VueFeature | keyof GridOptions>), - ...vueFeatureMixin("grid", options, () => ( - <Column> - {new Array(unref(grid.rows)).fill(0).map((_, row) => ( - <Row> - {new Array(unref(grid.cols)) - .fill(0) - .map((_, col) => render(grid.cells[row][col]))} - </Row> - ))} - </Column> - )), - cellState, - cells: new Proxy({} as GridCell[][], { - get(target, key: PropertyKey) { - if (key === "length") { - return unref(grid.rows); - } - - if (typeof key !== "number" && typeof key !== "string") { - return; - } - - const keyNum = typeof key === "number" ? key : parseInt(key); - if (Number.isFinite(keyNum) && keyNum < unref(grid.rows)) { - if (!(keyNum in target)) { - target[keyNum] = getCellRowHandler(grid, keyNum); - } - return target[keyNum]; - } - }, - set(target, key, value) { - console.warn("Cannot set grid cells", target, key, value); - return false; - }, - ownKeys(): string[] { - return [...new Array(unref(grid.rows)).fill(0).map((_, i) => "" + i), "length"]; - }, - has(target, key) { - if (key === "length") { - return true; - } - if (typeof key !== "number" && typeof key !== "string") { - return false; - } - const keyNum = typeof key === "number" ? key : parseInt(key); - if (!Number.isFinite(keyNum) || keyNum >= unref(grid.rows)) { - return false; - } - return true; - }, - getOwnPropertyDescriptor(target, key) { - if (typeof key !== "number" && typeof key !== "string") { - return; - } - const keyNum = typeof key === "number" ? key : parseInt(key); - if ( - key !== "length" && - (!Number.isFinite(keyNum) || keyNum >= unref(grid.rows)) - ) { - return; - } - return { - configurable: true, - enumerable: true, - writable: false - }; - } - }), - rows: processGetter(rows), - cols: processGetter(cols), - getVisibility: convertCellMaybeRefOrGetter(getVisibility ?? true), - getCanClick: convertCellMaybeRefOrGetter(getCanClick ?? true), - getStartState: - typeof getStartState === "function" && getStartState.length > 0 - ? getStartState - : processGetter(getStartState), - getStyle: convertCellMaybeRefOrGetter(getStyle), - getClasses: convertCellMaybeRefOrGetter(getClasses), - getDisplay, - getID: function (row: number, col: number): string { - return grid.id + "-" + row + "-" + col; - }, - getState: function (row: number, col: number): State { - cellState.value[row] ??= {}; - if (cellState.value[row][col] != null) { - return cellState.value[row][col]; - } - return grid.cells[row][col].startState; - }, - setState: function (row: number, col: number, state: State) { - cellState.value[row] ??= {}; - cellState.value[row][col] = state; - }, - onClick: - onClick == null - ? undefined - : function (row, col, state, e) { - if (grid.cells[row][col].canClick !== false) { - onClick.call(grid, row, col, state, e); - } - }, - onHold: - onHold == null - ? undefined - : function (row, col, state) { - if (grid.cells[row][col].canClick !== false) { - onHold.call(grid, row, col, state); - } - } - } satisfies Grid; - - return grid; - }); -} From 3a696030314fb7e2d8df0e4ee7668b62757df0dc Mon Sep 17 00:00:00 2001 From: thepaperpilot <thepaperpilot@gmail.com> Date: Wed, 11 Dec 2024 15:12:27 -0600 Subject: [PATCH 52/58] Lint --- src/data/layers/prestige.tsx | 10 ++-------- src/features/clickables/clickable.tsx | 8 ++++++-- src/util/vue.tsx | 8 ++------ 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/data/layers/prestige.tsx b/src/data/layers/prestige.tsx index b9a01d3..f136c19 100644 --- a/src/data/layers/prestige.tsx +++ b/src/data/layers/prestige.tsx @@ -5,20 +5,14 @@ import { main } from "data/projEntry"; import { createCumulativeConversion } from "features/conversion"; import { createHotkey } from "features/hotkey"; -import { bonusAmountMixin } from "mixins/bonusDecorator"; -import { createRepeatable } from "features/clickables/repeatable"; import { createReset } from "features/reset"; import MainDisplay from "features/resources/MainDisplay.vue"; import { createResource } from "features/resources/resource"; -import { addTooltip } from "wrappers/tooltips/tooltip"; import { createResourceTooltip } from "features/trees/tree"; -import { createUpgrade } from "features/clickables/upgrade"; import { BaseLayer, createLayer } from "game/layers"; -import { noPersist, persistent } from "game/persistence"; -import { createCostRequirement } from "game/requirements"; import type { DecimalSource } from "util/bignum"; -import { render, renderCol, renderRow } from "util/vue"; -import { computed } from "vue"; +import { render } from "util/vue"; +import { addTooltip } from "wrappers/tooltips/tooltip"; import { createLayerTreeNode, createResetButton } from "../common"; const id = "p"; diff --git a/src/features/clickables/clickable.tsx b/src/features/clickables/clickable.tsx index 98dab9a..309a3b4 100644 --- a/src/features/clickables/clickable.tsx +++ b/src/features/clickables/clickable.tsx @@ -67,10 +67,14 @@ export function createClickable<T extends ClickableOptions>(optionsFunc?: () => <span> {_display.title != null ? ( <div> - {render(_display.title, el => <h3>{el}</h3>)} + {render(_display.title, el => ( + <h3>{el}</h3> + ))} </div> ) : null} - {render(_display.description, el => <div>{el}</div>)} + {render(_display.description, el => ( + <div>{el}</div> + ))} </span> ); } else if (_display != null) { diff --git a/src/util/vue.tsx b/src/util/vue.tsx index 2ced50e..ecc99bf 100644 --- a/src/util/vue.tsx +++ b/src/util/vue.tsx @@ -89,15 +89,11 @@ export function render( return wrapper?.(object) ?? object; } -export function renderRow( - ...objects: (VueFeature | MaybeGetter<Renderable>)[] -): JSX.Element { +export function renderRow(...objects: (VueFeature | MaybeGetter<Renderable>)[]): JSX.Element { return <Row>{objects.map(obj => render(obj))}</Row>; } -export function renderCol( - ...objects: (VueFeature | MaybeGetter<Renderable>)[] -): JSX.Element { +export function renderCol(...objects: (VueFeature | MaybeGetter<Renderable>)[]): JSX.Element { return <Col>{objects.map(obj => render(obj))}</Col>; } From 5718abc01345412a23059a911b4a0a6f9e3af76e Mon Sep 17 00:00:00 2001 From: thepaperpilot <thepaperpilot@gmail.com> Date: Thu, 12 Dec 2024 07:39:10 -0600 Subject: [PATCH 53/58] Cleanup --- src/data/layers/prestige.tsx | 4 ++-- src/data/projEntry.tsx | 6 +++--- src/features/achievements/achievement.tsx | 3 +-- src/features/challenges/challenge.tsx | 3 +-- src/features/clickables/clickable.tsx | 3 +-- src/features/clickables/repeatable.tsx | 3 +-- src/features/clickables/upgrade.tsx | 3 +-- 7 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/data/layers/prestige.tsx b/src/data/layers/prestige.tsx index f136c19..a065db1 100644 --- a/src/data/layers/prestige.tsx +++ b/src/data/layers/prestige.tsx @@ -9,14 +9,14 @@ import { createReset } from "features/reset"; import MainDisplay from "features/resources/MainDisplay.vue"; import { createResource } from "features/resources/resource"; import { createResourceTooltip } from "features/trees/tree"; -import { BaseLayer, createLayer } from "game/layers"; +import { createLayer } from "game/layers"; import type { DecimalSource } from "util/bignum"; import { render } from "util/vue"; import { addTooltip } from "wrappers/tooltips/tooltip"; import { createLayerTreeNode, createResetButton } from "../common"; const id = "p"; -const layer = createLayer(id, function (this: BaseLayer) { +const layer = createLayer(id, () => { const name = "Prestige"; const color = "#4BDC13"; const points = createResource<DecimalSource>(0, "prestige points"); diff --git a/src/data/projEntry.tsx b/src/data/projEntry.tsx index b129c2f..e1a5d9d 100644 --- a/src/data/projEntry.tsx +++ b/src/data/projEntry.tsx @@ -3,7 +3,7 @@ import Spacer from "components/layout/Spacer.vue"; import { createResource, trackBest, trackOOMPS, trackTotal } from "features/resources/resource"; import { branchedResetPropagation, createTree, Tree } from "features/trees/tree"; import { globalBus } from "game/events"; -import type { BaseLayer, Layer } from "game/layers"; +import type { Layer } from "game/layers"; import { createLayer } from "game/layers"; import player, { Player } from "game/player"; import type { DecimalSource } from "util/bignum"; @@ -15,7 +15,7 @@ import prestige from "./layers/prestige"; /** * @hidden */ -export const main = createLayer("main", function (this: BaseLayer) { +export const main = createLayer("main", layer => { const points = createResource<DecimalSource>(10); const best = trackBest(points); const total = trackTotal(points); @@ -25,7 +25,7 @@ export const main = createLayer("main", function (this: BaseLayer) { let gain = new Decimal(1); return gain; }); - globalBus.on("update", diff => { + layer.on("update", diff => { points.value = Decimal.add(points.value, Decimal.times(pointGain.value, diff)); }); const oomps = trackOOMPS(points, pointGain); diff --git a/src/features/achievements/achievement.tsx b/src/features/achievements/achievement.tsx index ed97ffe..5997ff8 100644 --- a/src/features/achievements/achievement.tsx +++ b/src/features/achievements/achievement.tsx @@ -50,8 +50,7 @@ export interface AchievementOptions extends VueFeatureOptions { requirements?: Requirements; /** The display to use for this achievement. */ display?: - | Renderable - | (() => Renderable) + | MaybeGetter<Renderable> | { /** Description of the requirement(s) for this achievement. If unspecified then the requirements will be displayed automatically based on {@link requirements}. */ requirement?: MaybeGetter<Renderable>; diff --git a/src/features/challenges/challenge.tsx b/src/features/challenges/challenge.tsx index 3d18f01..213ff6d 100644 --- a/src/features/challenges/challenge.tsx +++ b/src/features/challenges/challenge.tsx @@ -39,8 +39,7 @@ export interface ChallengeOptions extends VueFeatureOptions { completionLimit?: MaybeRefOrGetter<DecimalSource>; /** The display to use for this challenge. */ display?: - | Renderable - | (() => Renderable) + | MaybeGetter<Renderable> | { /** A header to appear at the top of the display. */ title?: MaybeGetter<Renderable>; diff --git a/src/features/clickables/clickable.tsx b/src/features/clickables/clickable.tsx index 309a3b4..12e9022 100644 --- a/src/features/clickables/clickable.tsx +++ b/src/features/clickables/clickable.tsx @@ -24,8 +24,7 @@ export interface ClickableOptions extends VueFeatureOptions { canClick?: MaybeRefOrGetter<boolean>; /** The display to use for this clickable. */ display?: - | Renderable - | (() => Renderable) + | MaybeGetter<Renderable> | { /** A header to appear at the top of the display. */ title?: MaybeGetter<Renderable>; diff --git a/src/features/clickables/repeatable.tsx b/src/features/clickables/repeatable.tsx index c4e6c0f..4f36c5f 100644 --- a/src/features/clickables/repeatable.tsx +++ b/src/features/clickables/repeatable.tsx @@ -31,8 +31,7 @@ export interface RepeatableOptions extends ClickableOptions { initialAmount?: DecimalSource; /** The display to use for this repeatable. */ display?: - | Renderable - | (() => Renderable) + | MaybeGetter<Renderable> | { /** A header to appear at the top of the display. */ title?: MaybeGetter<Renderable>; diff --git a/src/features/clickables/upgrade.tsx b/src/features/clickables/upgrade.tsx index 4c299cd..1eed690 100644 --- a/src/features/clickables/upgrade.tsx +++ b/src/features/clickables/upgrade.tsx @@ -34,8 +34,7 @@ export const UpgradeType = Symbol("Upgrade"); export interface UpgradeOptions extends VueFeatureOptions, ClickableOptions { /** The display to use for this upgrade. */ display?: - | Renderable - | (() => Renderable) + | MaybeGetter<Renderable> | { /** A header to appear at the top of the display. */ title?: MaybeGetter<Renderable>; From 5bdb5ceed1373c65b88df0b609de432ad56744a2 Mon Sep 17 00:00:00 2001 From: thepaperpilot <thepaperpilot@gmail.com> Date: Sun, 15 Dec 2024 19:39:11 -0600 Subject: [PATCH 54/58] Change bonus mixin --- src/features/clickables/clickable.tsx | 2 +- src/mixins/bonusAmount.ts | 15 ++++++++++++++ src/mixins/bonusDecorator.ts | 29 --------------------------- 3 files changed, 16 insertions(+), 30 deletions(-) create mode 100644 src/mixins/bonusAmount.ts delete mode 100644 src/mixins/bonusDecorator.ts diff --git a/src/features/clickables/clickable.tsx b/src/features/clickables/clickable.tsx index 12e9022..1268f46 100644 --- a/src/features/clickables/clickable.tsx +++ b/src/features/clickables/clickable.tsx @@ -108,7 +108,7 @@ export function createClickable<T extends ClickableOptions>(optionsFunc?: () => onHold.call(clickable); } } - } satisfies Clickable & { onClick: T["onClick"] }; + } satisfies Clickable; return clickable; }); diff --git a/src/mixins/bonusAmount.ts b/src/mixins/bonusAmount.ts new file mode 100644 index 0000000..5ed7c7d --- /dev/null +++ b/src/mixins/bonusAmount.ts @@ -0,0 +1,15 @@ +import Decimal, { DecimalSource } from "util/bignum"; +import { processGetter } from "util/computed"; +import { MaybeRefOrGetter, Ref, computed, unref } from "vue"; + +/** Allows the addition of "bonus levels" to a feature, with an accompanying "total amount". */ +export function bonusAmountMixin( + baseAmount: Ref<DecimalSource>, + bonusAmount: MaybeRefOrGetter<DecimalSource> +) { + const processedBonusAmount = processGetter(bonusAmount); + return { + bonusAmount: processedBonusAmount, + totalAmount: computed(() => Decimal.add(unref(baseAmount), unref(processedBonusAmount))) + }; +} diff --git a/src/mixins/bonusDecorator.ts b/src/mixins/bonusDecorator.ts deleted file mode 100644 index b08954e..0000000 --- a/src/mixins/bonusDecorator.ts +++ /dev/null @@ -1,29 +0,0 @@ -import Decimal, { DecimalSource } from "util/bignum"; -import { processGetter } from "util/computed"; -import { MaybeRefOrGetter, Ref, computed, unref } from "vue"; - -/** Allows the addition of "bonus levels" to a feature, with an accompanying "total amount". */ -export function bonusAmountMixin( - feature: { amount: Ref<DecimalSource> }, - bonusAmount: MaybeRefOrGetter<DecimalSource> -) { - const processedBonusAmount = processGetter(bonusAmount); - return { - bonusAmount, - totalAmount: computed(() => Decimal.add(unref(feature.amount), unref(processedBonusAmount))) - }; -} - -/** Allows the addition of "bonus completions" to a feature, with an accompanying "total completions". */ -export function bonusCompletionsMixin( - feature: { completions: Ref<DecimalSource> }, - bonusCompletions: MaybeRefOrGetter<DecimalSource> -) { - const processedBonusCompletions = processGetter(bonusCompletions); - return { - bonusCompletions, - totalCompletions: computed(() => - Decimal.add(unref(feature.completions), unref(processedBonusCompletions)) - ) - }; -} From 0a5f63ff048227e74ad95e9e23ab41a6646c7d62 Mon Sep 17 00:00:00 2001 From: thepaperpilot <thepaperpilot@gmail.com> Date: Wed, 25 Dec 2024 11:13:29 -0600 Subject: [PATCH 55/58] Don't convert functions with parameters --- src/util/computed.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/computed.ts b/src/util/computed.ts index 56051cd..29f0c8f 100644 --- a/src/util/computed.ts +++ b/src/util/computed.ts @@ -5,7 +5,7 @@ import { computed } from "vue"; export type MaybeGetter<T> = T | (() => T); export function processGetter<T>(obj: T): T extends () => infer S ? ComputedRef<S> : T { - if (isFunction(obj)) { + if (isFunction(obj) && obj.length === 0) { return computed(obj) as ReturnType<typeof processGetter<T>>; } return obj as ReturnType<typeof processGetter<T>>; From 9e65adee95473d7784109b090c60cc75ebd5f287 Mon Sep 17 00:00:00 2001 From: thepaperpilot <thepaperpilot@gmail.com> Date: Wed, 25 Dec 2024 11:26:34 -0600 Subject: [PATCH 56/58] Lint --- src/data/projEntry.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/data/projEntry.tsx b/src/data/projEntry.tsx index e1a5d9d..f6b4c20 100644 --- a/src/data/projEntry.tsx +++ b/src/data/projEntry.tsx @@ -2,7 +2,6 @@ import Node from "components/Node.vue"; import Spacer from "components/layout/Spacer.vue"; import { createResource, trackBest, trackOOMPS, trackTotal } from "features/resources/resource"; import { branchedResetPropagation, createTree, Tree } from "features/trees/tree"; -import { globalBus } from "game/events"; import type { Layer } from "game/layers"; import { createLayer } from "game/layers"; import player, { Player } from "game/player"; From 595a4170b2a2c6a704fd82e90b07a175bfa241af Mon Sep 17 00:00:00 2001 From: thepaperpilot <thepaperpilot@gmail.com> Date: Wed, 25 Dec 2024 19:30:23 -0600 Subject: [PATCH 57/58] Fix error about pinned tooltips --- src/data/projEntry.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/data/projEntry.tsx b/src/data/projEntry.tsx index f6b4c20..abba816 100644 --- a/src/data/projEntry.tsx +++ b/src/data/projEntry.tsx @@ -4,6 +4,7 @@ import { createResource, trackBest, trackOOMPS, trackTotal } from "features/reso import { branchedResetPropagation, createTree, Tree } from "features/trees/tree"; import type { Layer } from "game/layers"; import { createLayer } from "game/layers"; +import { noPersist } from "game/persistence"; import player, { Player } from "game/player"; import type { DecimalSource } from "util/bignum"; import Decimal, { format, formatTime } from "util/bignum"; @@ -31,7 +32,7 @@ export const main = createLayer("main", layer => { // Note: Casting as generic tree to avoid recursive type definitions const tree = createTree(() => ({ - nodes: [[prestige.treeNode]], + nodes: noPersist([[prestige.treeNode]]), branches: [], onReset() { points.value = toRaw(tree.resettingNode.value) === toRaw(prestige.treeNode) ? 0 : 10; From 44cdb7091983ae217fba9f50efc302cde3afcb61 Mon Sep 17 00:00:00 2001 From: thepaperpilot <thepaperpilot@gmail.com> Date: Thu, 26 Dec 2024 05:12:16 -0600 Subject: [PATCH 58/58] Cleanup --- src/components/modals/Info.vue | 4 ++-- src/features/achievements/Achievement.vue | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/modals/Info.vue b/src/components/modals/Info.vue index db3ab51..6370482 100644 --- a/src/components/modals/Info.vue +++ b/src/components/modals/Info.vue @@ -53,7 +53,7 @@ </div> <br /> <div>Time Played: {{ timePlayed }}</div> - <Info /> + <InfoComponents /> </div> </template> </Modal> @@ -77,7 +77,7 @@ const isOpen = ref(false); const timePlayed = computed(() => formatTime(player.timePlayed)); -const Info = () => infoComponents.map(f => render(f)); +const InfoComponents = () => infoComponents.map(f => render(f)); defineExpose({ open() { diff --git a/src/features/achievements/Achievement.vue b/src/features/achievements/Achievement.vue index bba4b35..3cced62 100644 --- a/src/features/achievements/Achievement.vue +++ b/src/features/achievements/Achievement.vue @@ -16,10 +16,9 @@ <script setup lang="tsx"> import "components/common/features.css"; -import { isJSXElement, render } from "util/vue"; -import { Component, isRef, unref } from "vue"; +import { render } from "util/vue"; +import { Component, unref } from "vue"; import { Achievement } from "./achievement"; -import { displayRequirements } from "game/requirements"; const props = defineProps<{ display: Achievement["display"];