WIP: Feature Rewrite #87
4 changed files with 225 additions and 167 deletions
|
@ -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<DecimalSource>(0, "prestige points");
|
||||
|
||||
const conversion = createCumulativeConversion(() => ({
|
||||
formula: x => x.div(10).sqrt(),
|
||||
baseResource: main.points,
|
||||
gainResource: points
|
||||
}));
|
||||
|
||||
const reset = createReset(() => ({
|
||||
thingsToReset: (): Record<string, unknown>[] => [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(() => (
|
||||
<>
|
||||
<MainDisplay resource={points} color={color} />
|
||||
{render(resetButton)}
|
||||
</>
|
||||
)),
|
||||
treeNode,
|
||||
hotkey
|
||||
};
|
||||
});
|
||||
|
||||
export default layer;
|
|
@ -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<NodePosition> };
|
||||
type NodeTypes = ANode | BNode;
|
||||
|
||||
const board = ref<ComponentPublicInstance<typeof Board>>();
|
||||
|
||||
const { select, deselect, selected } = setupSelectable<NodeTypes>();
|
||||
const { select, deselect, selected } = setupSelectable<number>();
|
||||
const {
|
||||
select: selectAction,
|
||||
deselect: deselectAction,
|
||||
|
@ -50,10 +56,15 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
|||
receivingNodes,
|
||||
receivingNode,
|
||||
dragDelta
|
||||
} = setupDraggableNode<NodeTypes /* | typeof cNode*/>({
|
||||
} = setupDraggableNode<number | "cnode">({
|
||||
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<NodeTypes[]>([{ 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<Record<string, NodeTypes>>(() =>
|
||||
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 (
|
||||
<SVGNode
|
||||
style={`transform: ${getTranslateString(node)}${getOpacityString(node)}`}
|
||||
style={`transform: ${getTranslateString(
|
||||
node,
|
||||
selected.value === node.id && nodeBeingDragged.value === node.id
|
||||
)}${getOpacityString(node)}`}
|
||||
onMouseDown={e => mouseDownNode(e, node)}
|
||||
onMouseUp={e => mouseUpNode(e, node)}
|
||||
>
|
||||
<g style={`transform: ${getScaleString(node)}; transition-duration: 0s`}>
|
||||
{receivingNodes.value.includes(node) && (
|
||||
<g style={`transform: ${getScaleString(node)}`}>
|
||||
{receivingNodes.value.includes(node.id) && (
|
||||
<circle
|
||||
r="58"
|
||||
fill="var(--background)"
|
||||
stroke={receivingNode.value === node ? "#0F0" : "#0F03"}
|
||||
stroke={receivingNode.value === node.id ? "#0F0" : "#0F03"}
|
||||
stroke-width="2"
|
||||
/>
|
||||
)}
|
||||
|
@ -132,7 +146,7 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
|||
stroke-width="4"
|
||||
/>
|
||||
</g>
|
||||
{selected.value === node && selectedAction.value === 0 && (
|
||||
{selected.value === node.id && selectedAction.value === 0 && (
|
||||
<text y="140" fill="var(--foreground)" class="node-text">
|
||||
Spawn B Node
|
||||
</text>
|
||||
|
@ -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 (
|
||||
<SVGNode
|
||||
style={`transform: ${getTranslateString(node)}${getOpacityString(node)}`}
|
||||
style={`transform: ${getTranslateString(
|
||||
node,
|
||||
selected.value === node.id && nodeBeingDragged.value === node.id
|
||||
)}${getOpacityString(node)}`}
|
||||
onMouseDown={e => mouseDownNode(e, node)}
|
||||
onMouseUp={e => mouseUpNode(e, node)}
|
||||
>
|
||||
<g
|
||||
style={`transform: ${getScaleString(node)}${getRotateString(
|
||||
45
|
||||
)}; transition-duration: 0s`}
|
||||
>
|
||||
{receivingNodes.value.includes(node) && (
|
||||
<g style={`transform: ${getScaleString(node)}${getRotateString(45)}`}>
|
||||
{receivingNodes.value.includes(node.id) && (
|
||||
<rect
|
||||
width={50 * sqrtTwo + 16}
|
||||
height={50 * sqrtTwo + 16}
|
||||
|
@ -193,7 +206,7 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
|||
(-50 * sqrtTwo + 16) / 2
|
||||
})`}
|
||||
fill="var(--background)"
|
||||
stroke={receivingNode.value === node ? "#0F0" : "#0F03"}
|
||||
stroke={receivingNode.value === node.id ? "#0F0" : "#0F03"}
|
||||
stroke-width="2"
|
||||
/>
|
||||
)}
|
||||
|
@ -213,7 +226,7 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
|||
stroke-width="4"
|
||||
/>
|
||||
</g>
|
||||
{selected.value === node && selectedAction.value === 0 && (
|
||||
{selected.value === node.id && selectedAction.value === 0 && (
|
||||
<text y="140" fill="var(--foreground)" class="node-text">
|
||||
Spawn A Node
|
||||
</text>
|
||||
|
@ -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: "<h1>C</h1>",
|
||||
// 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(() => (
|
||||
<>
|
||||
<Board
|
||||
|
@ -354,18 +390,20 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
|||
ref={board}
|
||||
>
|
||||
<SVGNode>{links()}</SVGNode>
|
||||
{nodes.value.filter(filterNodes).map(renderNode)}
|
||||
{nodes.value.filter(n => filterNodes(n.id)).map(renderNode)}
|
||||
{filterNodes("cnode") && render(cNode)}
|
||||
<SVGNode>
|
||||
{aActions()}
|
||||
{bActions()}
|
||||
</SVGNode>
|
||||
{renderNode(selected.value)}
|
||||
{renderNode(nodeBeingDragged.value)}
|
||||
{renderNodeById(selected.value)}
|
||||
{renderNodeById(nodeBeingDragged.value)}
|
||||
</Board>
|
||||
</>
|
||||
)),
|
||||
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<Player>
|
||||
): Array<GenericLayer> => [main, prestige];
|
||||
): Array<GenericLayer> => [main];
|
||||
|
||||
/**
|
||||
* A computed ref whose value is true whenever the game is over.
|
||||
|
|
37
src/features/boards/Draggable.vue
Normal file
37
src/features/boards/Draggable.vue
Normal file
|
@ -0,0 +1,37 @@
|
|||
<template>
|
||||
<div
|
||||
: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)"
|
||||
>
|
||||
<component v-if="comp" :is="comp" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { jsx } from "features/feature";
|
||||
import { VueFeature, coerceComponent, renderJSX } from "util/vue";
|
||||
import { Ref, shallowRef, unref } from "vue";
|
||||
import { NodePosition } from "./board";
|
||||
unref;
|
||||
|
||||
const props = defineProps<{
|
||||
element: VueFeature;
|
||||
mouseDown: (e: MouseEvent | TouchEvent) => void;
|
||||
mouseUp: (e: MouseEvent | TouchEvent) => void;
|
||||
position: Ref<NodePosition>;
|
||||
}>();
|
||||
|
||||
const comp = shallowRef(coerceComponent(jsx(() => renderJSX(props.element))));
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
div {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transition-duration: 0s;
|
||||
}
|
||||
</style>
|
|
@ -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<T>() {
|
|||
};
|
||||
}
|
||||
|
||||
export function setupDraggableNode<T extends NodePosition, S extends NodePosition = T>(options: {
|
||||
export function setupDraggableNode<T>(options: {
|
||||
board: Ref<ComponentPublicInstance<typeof Board> | undefined>;
|
||||
receivingNodes?: NodeComputable<T, S[]>;
|
||||
dropAreaRadius?: NodeComputable<S, number>;
|
||||
isDraggable?: NodeComputable<T, boolean>;
|
||||
onDrop?: (acceptingNode: S, draggingNode: T) => void;
|
||||
getPosition: (node: T) => NodePosition;
|
||||
setPosition: (node: T, position: NodePosition) => void;
|
||||
receivingNodes?: NodeComputable<T, T[]>;
|
||||
dropAreaRadius?: NodeComputable<T, number>;
|
||||
onDrop?: (acceptingNode: T, draggingNode: T) => void;
|
||||
}) {
|
||||
const nodeBeingDragged = ref<T>();
|
||||
const receivingNode = ref<S>();
|
||||
const receivingNode = ref<T>();
|
||||
const hasDragged = ref(false);
|
||||
const mousePosition = ref<NodePosition>();
|
||||
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<T extends NodePosition, S extends NodePositio
|
|||
);
|
||||
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 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<T extends NodePosition, S extends NodePositio
|
|||
lastMousePosition,
|
||||
dragDelta,
|
||||
receivingNodes,
|
||||
startDrag: function (e: MouseEvent | TouchEvent, node?: T) {
|
||||
startDrag: function (e: MouseEvent | TouchEvent, node: T) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
|
@ -141,17 +139,17 @@ export function setupDraggableNode<T extends NodePosition, S extends NodePositio
|
|||
dragDelta.value = { x: 0, y: 0 };
|
||||
hasDragged.value = false;
|
||||
|
||||
if (node != null && unwrapNodeRef(isDraggable, node)) {
|
||||
nodeBeingDragged.value = 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;
|
||||
const { x, y } = options.getPosition(nodeBeingDragged.value);
|
||||
const newX = x + Math.round(dragDelta.value.x / 25) * 25;
|
||||
const newY = y + Math.round(dragDelta.value.y / 25) * 25;
|
||||
options.setPosition(nodeBeingDragged.value, { x: newX, y: newY });
|
||||
}
|
||||
|
||||
if (receivingNode.value != null) {
|
||||
|
@ -210,6 +208,64 @@ export function setupDraggableNode<T extends NodePosition, S extends NodePositio
|
|||
return result;
|
||||
}
|
||||
|
||||
export function makeDraggable<T extends VueFeature, S>(
|
||||
element: T,
|
||||
options: {
|
||||
id: S;
|
||||
nodeBeingDragged: Ref<S | undefined>;
|
||||
hasDragged: Ref<boolean>;
|
||||
dragDelta: Ref<NodePosition>;
|
||||
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<NodePosition> } {
|
||||
const position = persistent(options.initialPosition ?? { x: 0, y: 0 });
|
||||
(element as T & { 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;
|
||||
});
|
||||
|
||||
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<T extends NodePosition>(options: {
|
||||
node: Computable<T | undefined>;
|
||||
shouldShowActions?: NodeComputable<T, boolean>;
|
||||
|
|
Loading…
Reference in a new issue