Document boards
This commit is contained in:
parent
17b878e3be
commit
68b1401c2a
3 changed files with 121 additions and 38 deletions
|
@ -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<T>() {
|
||||
const selected = ref<T>();
|
||||
return {
|
||||
select: function (node: T) {
|
||||
selected.value = node;
|
||||
},
|
||||
deselect: function () {
|
||||
selected.value = undefined;
|
||||
},
|
||||
selected
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
||||
/**
|
||||
|
|
|
@ -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<T, R, S extends unknown[]>(
|
|||
: 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<T>() {
|
||||
const selected = ref<T>();
|
||||
return {
|
||||
select: function (node: T) {
|
||||
selected.value = node;
|
||||
},
|
||||
deselect: function () {
|
||||
selected.value = undefined;
|
||||
},
|
||||
selected
|
||||
};
|
||||
/** An object that configures a {@link DraggableNode}. */
|
||||
export interface DraggableNodeOptions<T> {
|
||||
/** 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 <Board> element. */
|
||||
board: Ref<ComponentPublicInstance<typeof Board> | 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<T, T[]>;
|
||||
/** The maximum distance (in pixels, before zoom) away a node can be and still drop onto a receiving node. */
|
||||
dropAreaRadius?: NodeComputable<T, number>;
|
||||
/** A callback for when a node gets dropped upon a receiving node. */
|
||||
onDrop?: (acceptingNode: T, draggingNode: T) => void;
|
||||
}
|
||||
|
||||
export function setupDraggableNode<T>(options: {
|
||||
board: Ref<ComponentPublicInstance<typeof Board> | undefined>;
|
||||
getPosition: (node: T) => NodePosition;
|
||||
setPosition: (node: T, position: NodePosition) => void;
|
||||
receivingNodes?: NodeComputable<T, T[]>;
|
||||
dropAreaRadius?: NodeComputable<T, number>;
|
||||
onDrop?: (acceptingNode: T, draggingNode: T) => void;
|
||||
}) {
|
||||
/** An object that represents a system for moving nodes on a board by dragging them. */
|
||||
export interface DraggableNode<T> {
|
||||
/** A ref to the node currently being moved. */
|
||||
nodeBeingDragged: Ref<T | undefined>;
|
||||
/** 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<T | undefined>;
|
||||
/** A ref to whether or not the node being dragged has actually been dragged away from its starting position. */
|
||||
hasDragged: Ref<boolean>;
|
||||
/** The position of the node being dragged relative to where it started at the beginning of the drag. */
|
||||
dragDelta: Ref<NodePosition>;
|
||||
/** The nodes that can receive the node currently being dragged. */
|
||||
receivingNodes: Ref<T[]>;
|
||||
/** 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<T>(options: DraggableNodeOptions<T>): DraggableNode<T> {
|
||||
const nodeBeingDragged = ref<T>();
|
||||
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 receivingNodes = computed(() =>
|
||||
nodeBeingDragged.value == null
|
||||
|
@ -78,6 +105,9 @@ export function setupDraggableNode<T>(options: {
|
|||
);
|
||||
const dropAreaRadius = options.dropAreaRadius ?? 50;
|
||||
|
||||
const mousePosition = ref<NodePosition>();
|
||||
const lastMousePosition = ref({ x: 0, y: 0 });
|
||||
|
||||
watchEffect(() => {
|
||||
const node = nodeBeingDragged.value;
|
||||
if (node == null) {
|
||||
|
@ -112,8 +142,6 @@ export function setupDraggableNode<T>(options: {
|
|||
nodeBeingDragged,
|
||||
receivingNode,
|
||||
hasDragged,
|
||||
mousePosition,
|
||||
lastMousePosition,
|
||||
dragDelta,
|
||||
receivingNodes,
|
||||
startDrag: function (e: MouseEvent | TouchEvent, node: T) {
|
||||
|
@ -206,19 +234,36 @@ export function setupDraggableNode<T>(options: {
|
|||
return result;
|
||||
}
|
||||
|
||||
/** An object that configures how to make a vue feature draggable using {@link makeDraggable}. */
|
||||
export interface MakeDraggableOptions<T> {
|
||||
/** 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<T | undefined>;
|
||||
/** A reference to whether or not the node being dragged has been moved away from its initial position. Typically from {@link setupDraggableNode}. */
|
||||
hasDragged: Ref<boolean>;
|
||||
/** A reference to how far the node being dragged is from its initial position. Typically from {@link setupDraggableNode}. */
|
||||
dragDelta: Ref<NodePosition>;
|
||||
/** 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<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;
|
||||
}
|
||||
options: MakeDraggableOptions<S>
|
||||
): asserts element is T & { position: Persistent<NodePosition> } {
|
||||
const position = persistent(options.initialPosition ?? { x: 0, y: 0 });
|
||||
(element as T & { position: Persistent<NodePosition> }).position = position;
|
||||
|
@ -264,13 +309,26 @@ export function makeDraggable<T extends VueFeature, S>(
|
|||
});
|
||||
}
|
||||
|
||||
export function setupActions<T extends NodePosition>(options: {
|
||||
/** An object that configures how to setup a list of actions using {@link setupActions}. */
|
||||
export interface SetupActionsOptions<T extends NodePosition> {
|
||||
/** The node to display actions upon, or undefined when the actions should be hidden. */
|
||||
node: Computable<T | undefined>;
|
||||
/** Whether or not to currently display the actions. */
|
||||
shouldShowActions?: NodeComputable<T, boolean>;
|
||||
/** The list of actions to display. Actions are arbitrary JSX elements. */
|
||||
actions: NodeComputable<T, ((position: NodePosition) => JSX.Element)[]>;
|
||||
/** The distance from the node to place the actions. */
|
||||
distance: NodeComputable<T, number>;
|
||||
/** The arc length to place between actions, in radians. */
|
||||
arcLength?: NodeComputable<T, number>;
|
||||
}) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<T extends NodePosition>(options: SetupActionsOptions<T>) {
|
||||
const node = convertComputable(options.node);
|
||||
return jsx(() => {
|
||||
const currNode = unref(node);
|
||||
|
@ -300,6 +358,13 @@ export function setupActions<T extends NodePosition>(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<T extends NodePosition>(
|
||||
nodeToPlace: T,
|
||||
nodes: T[],
|
||||
|
|
Loading…
Reference in a new issue