Converted draw actions storage to collapsed representation
Moved to command pattern for action application
This commit is contained in:
parent
2fc7f4f162
commit
fa1f6fe18f
@ -24,6 +24,7 @@
|
||||
"fuse.js": "^6.4.1",
|
||||
"interactjs": "^1.9.7",
|
||||
"konva": "^7.1.8",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.set": "^4.3.2",
|
||||
"normalize-wheel": "^1.0.1",
|
||||
|
59
src/actions/Action.js
Normal file
59
src/actions/Action.js
Normal file
@ -0,0 +1,59 @@
|
||||
import { Diff } from "deep-diff";
|
||||
|
||||
import { diff, revertChanges } from "../helpers/diff";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
|
||||
/**
|
||||
* @callback ActionUpdate
|
||||
* @param {any} state
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implementation of the Command Pattern
|
||||
* Wraps an update function with internal state to support undo
|
||||
*/
|
||||
class Action {
|
||||
/**
|
||||
* The update function called with the current state and should return the updated state
|
||||
* This is implemented in the child class
|
||||
*
|
||||
* @type {ActionUpdate}
|
||||
*/
|
||||
update;
|
||||
|
||||
/**
|
||||
* The changes caused by the last state update
|
||||
* @type {Diff}
|
||||
*/
|
||||
changes;
|
||||
|
||||
/**
|
||||
* Executes the action update on the state
|
||||
* @param {any} state The current state to update
|
||||
* @returns {any} The updated state
|
||||
*/
|
||||
execute(state) {
|
||||
if (state && this.update) {
|
||||
let newState = this.update(cloneDeep(state));
|
||||
this.changes = diff(state, newState);
|
||||
return newState;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts the changes caused by the last call of `execute`
|
||||
* @param {any} state The current state to perform the undo on
|
||||
* @returns {any} The state with the last changes reverted
|
||||
*/
|
||||
undo(state) {
|
||||
if (state && this.changes) {
|
||||
let revertedState = cloneDeep(state);
|
||||
revertChanges(revertedState, this.changes);
|
||||
return revertedState;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default Action;
|
15
src/actions/AddShapeAction.js
Normal file
15
src/actions/AddShapeAction.js
Normal file
@ -0,0 +1,15 @@
|
||||
import Action from "./Action";
|
||||
|
||||
class AddShapeAction extends Action {
|
||||
constructor(shapes) {
|
||||
super();
|
||||
this.update = (shapesById) => {
|
||||
for (let shape of shapes) {
|
||||
shapesById[shape.id] = shape;
|
||||
}
|
||||
return shapesById;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default AddShapeAction;
|
40
src/actions/CutShapeAction.js
Normal file
40
src/actions/CutShapeAction.js
Normal file
@ -0,0 +1,40 @@
|
||||
import polygonClipping from "polygon-clipping";
|
||||
|
||||
import Action from "./Action";
|
||||
import {
|
||||
addPolygonDifferenceToShapes,
|
||||
addPolygonIntersectionToShapes,
|
||||
} from "../helpers/drawing";
|
||||
|
||||
class CutShapeAction extends Action {
|
||||
constructor(shapes) {
|
||||
super();
|
||||
this.update = (shapesById) => {
|
||||
const actionGeom = shapes.map((actionShape) => [
|
||||
actionShape.data.points.map(({ x, y }) => [x, y]),
|
||||
]);
|
||||
let cutShapes = {};
|
||||
for (let shape of Object.values(shapesById)) {
|
||||
const shapePoints = shape.data.points.map(({ x, y }) => [x, y]);
|
||||
const shapeHoles = shape.data.holes.map((hole) =>
|
||||
hole.map(({ x, y }) => [x, y])
|
||||
);
|
||||
let shapeGeom = [[shapePoints, ...shapeHoles]];
|
||||
try {
|
||||
const difference = polygonClipping.difference(shapeGeom, actionGeom);
|
||||
const intersection = polygonClipping.intersection(
|
||||
shapeGeom,
|
||||
actionGeom
|
||||
);
|
||||
addPolygonDifferenceToShapes(shape, difference, cutShapes);
|
||||
addPolygonIntersectionToShapes(shape, intersection, cutShapes);
|
||||
} catch {
|
||||
console.error("Unable to find intersection for shapes");
|
||||
}
|
||||
}
|
||||
return cutShapes;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default CutShapeAction;
|
17
src/actions/EditShapeAction.js
Normal file
17
src/actions/EditShapeAction.js
Normal file
@ -0,0 +1,17 @@
|
||||
import Action from "./Action";
|
||||
|
||||
class EditShapeAction extends Action {
|
||||
constructor(shapes) {
|
||||
super();
|
||||
this.update = (shapesById) => {
|
||||
for (let edit of shapes) {
|
||||
if (edit.id in shapesById) {
|
||||
shapesById[edit.id] = { ...shapesById[edit.id], ...edit };
|
||||
}
|
||||
}
|
||||
return shapesById;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default EditShapeAction;
|
13
src/actions/RemoveShapeAction.js
Normal file
13
src/actions/RemoveShapeAction.js
Normal file
@ -0,0 +1,13 @@
|
||||
import Action from "./Action";
|
||||
import { omit } from "../helpers/shared";
|
||||
|
||||
class RemoveShapeAction extends Action {
|
||||
constructor(shapeIds) {
|
||||
super();
|
||||
this.update = (shapesById) => {
|
||||
return omit(shapesById, shapeIds);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default RemoveShapeAction;
|
32
src/actions/SubtractShapeAction.js
Normal file
32
src/actions/SubtractShapeAction.js
Normal file
@ -0,0 +1,32 @@
|
||||
import polygonClipping from "polygon-clipping";
|
||||
|
||||
import Action from "./Action";
|
||||
import { addPolygonDifferenceToShapes } from "../helpers/drawing";
|
||||
|
||||
class SubtractShapeAction extends Action {
|
||||
constructor(shapes) {
|
||||
super();
|
||||
this.update = (shapesById) => {
|
||||
const actionGeom = shapes.map((actionShape) => [
|
||||
actionShape.data.points.map(({ x, y }) => [x, y]),
|
||||
]);
|
||||
let subtractedShapes = {};
|
||||
for (let shape of Object.values(shapesById)) {
|
||||
const shapePoints = shape.data.points.map(({ x, y }) => [x, y]);
|
||||
const shapeHoles = shape.data.holes.map((hole) =>
|
||||
hole.map(({ x, y }) => [x, y])
|
||||
);
|
||||
let shapeGeom = [[shapePoints, ...shapeHoles]];
|
||||
try {
|
||||
const difference = polygonClipping.difference(shapeGeom, actionGeom);
|
||||
addPolygonDifferenceToShapes(shape, difference, subtractedShapes);
|
||||
} catch {
|
||||
console.error("Unable to find difference for shapes");
|
||||
}
|
||||
}
|
||||
return subtractedShapes;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default SubtractShapeAction;
|
50
src/actions/index.js
Normal file
50
src/actions/index.js
Normal file
@ -0,0 +1,50 @@
|
||||
import AddShapeAction from "./AddShapeAction";
|
||||
import CutShapeAction from "./CutShapeAction";
|
||||
import EditShapeAction from "./EditShapeAction";
|
||||
import RemoveShapeAction from "./RemoveShapeAction";
|
||||
import SubtractShapeAction from "./SubtractShapeAction";
|
||||
|
||||
/**
|
||||
* Convert from the previous representation of actions (1.7.0) to the new representation (1.8.0)
|
||||
* and combine into shapes
|
||||
* @param {Array} actions
|
||||
* @param {number} actionIndex
|
||||
*/
|
||||
export function convertOldActionsToShapes(actions, actionIndex) {
|
||||
let newShapes = {};
|
||||
for (let i = 0; i <= actionIndex; i++) {
|
||||
const action = actions[i];
|
||||
if (!action) {
|
||||
continue;
|
||||
}
|
||||
let newAction;
|
||||
if (action.shapes) {
|
||||
if (action.type === "add") {
|
||||
newAction = new AddShapeAction(action.shapes);
|
||||
} else if (action.type === "edit") {
|
||||
newAction = new EditShapeAction(action.shapes);
|
||||
} else if (action.type === "remove") {
|
||||
newAction = new RemoveShapeAction(action.shapes);
|
||||
} else if (action.type === "subtract") {
|
||||
newAction = new SubtractShapeAction(action.shapes);
|
||||
} else if (action.type === "cut") {
|
||||
newAction = new CutShapeAction(action.shapes);
|
||||
}
|
||||
} else if (action.type === "remove" && action.shapeIds) {
|
||||
newAction = new RemoveShapeAction(action.shapeIds);
|
||||
}
|
||||
|
||||
if (newAction) {
|
||||
newShapes = newAction.execute(newShapes);
|
||||
}
|
||||
}
|
||||
return newShapes;
|
||||
}
|
||||
|
||||
export {
|
||||
AddShapeAction,
|
||||
CutShapeAction,
|
||||
EditShapeAction,
|
||||
RemoveShapeAction,
|
||||
SubtractShapeAction,
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useContext, useEffect } from "react";
|
||||
import React, { useState, useContext } from "react";
|
||||
import { Group } from "react-konva";
|
||||
|
||||
import MapControls from "./MapControls";
|
||||
@ -19,11 +19,17 @@ import TokenDragOverlay from "../token/TokenDragOverlay";
|
||||
import NoteMenu from "../note/NoteMenu";
|
||||
import NoteDragOverlay from "../note/NoteDragOverlay";
|
||||
|
||||
import { drawActionsToShapes } from "../../helpers/drawing";
|
||||
import {
|
||||
AddShapeAction,
|
||||
CutShapeAction,
|
||||
EditShapeAction,
|
||||
RemoveShapeAction,
|
||||
} from "../../actions";
|
||||
|
||||
function Map({
|
||||
map,
|
||||
mapState,
|
||||
mapActions,
|
||||
onMapTokenStateChange,
|
||||
onMapTokenStateRemove,
|
||||
onMapChange,
|
||||
@ -67,13 +73,12 @@ function Map({
|
||||
}));
|
||||
}
|
||||
|
||||
const drawShapes = Object.values(mapState?.drawShapes || {});
|
||||
const fogShapes = Object.values(mapState?.fogShapes || {});
|
||||
|
||||
function handleToolAction(action) {
|
||||
if (action === "eraseAll") {
|
||||
onMapDraw({
|
||||
type: "remove",
|
||||
shapeIds: mapShapes.map((s) => s.id),
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
onMapDraw(new RemoveShapeAction(drawShapes.map((s) => s.id)));
|
||||
}
|
||||
if (action === "mapUndo") {
|
||||
onMapDrawUndo();
|
||||
@ -89,47 +94,30 @@ function Map({
|
||||
}
|
||||
}
|
||||
|
||||
const [mapShapes, setMapShapes] = useState([]);
|
||||
|
||||
function handleMapShapeAdd(shape) {
|
||||
onMapDraw({ type: "add", shapes: [shape] });
|
||||
onMapDraw(new AddShapeAction([shape]));
|
||||
}
|
||||
|
||||
function handleMapShapesRemove(shapeIds) {
|
||||
onMapDraw({ type: "remove", shapeIds });
|
||||
onMapDraw(new RemoveShapeAction(shapeIds));
|
||||
}
|
||||
|
||||
const [fogShapes, setFogShapes] = useState([]);
|
||||
|
||||
function handleFogShapeAdd(shape) {
|
||||
onFogDraw({ type: "add", shapes: [shape] });
|
||||
onFogDraw(new AddShapeAction([shape]));
|
||||
}
|
||||
|
||||
function handleFogShapeCut(shape) {
|
||||
onFogDraw({ type: "cut", shapes: [shape] });
|
||||
onFogDraw(new CutShapeAction([shape]));
|
||||
}
|
||||
|
||||
function handleFogShapesRemove(shapeIds) {
|
||||
onFogDraw({ type: "remove", shapeIds });
|
||||
onFogDraw(new RemoveShapeAction(shapeIds));
|
||||
}
|
||||
|
||||
function handleFogShapesEdit(shapes) {
|
||||
onFogDraw({ type: "edit", shapes });
|
||||
onFogDraw(new EditShapeAction(shapes));
|
||||
}
|
||||
|
||||
// Replay the draw actions and convert them to shapes for the map drawing
|
||||
useEffect(() => {
|
||||
if (!mapState) {
|
||||
return;
|
||||
}
|
||||
setMapShapes(
|
||||
drawActionsToShapes(mapState.mapDrawActions, mapState.mapDrawActionIndex)
|
||||
);
|
||||
setFogShapes(
|
||||
drawActionsToShapes(mapState.fogDrawActions, mapState.fogDrawActionIndex)
|
||||
);
|
||||
}, [mapState]);
|
||||
|
||||
const disabledControls = [];
|
||||
if (!allowMapDrawing) {
|
||||
disabledControls.push("drawing");
|
||||
@ -150,24 +138,24 @@ function Map({
|
||||
}
|
||||
|
||||
const disabledSettings = { fog: [], drawing: [] };
|
||||
if (mapShapes.length === 0) {
|
||||
if (drawShapes.length === 0) {
|
||||
disabledSettings.drawing.push("erase");
|
||||
}
|
||||
if (!mapState || mapState.mapDrawActionIndex < 0) {
|
||||
if (!mapState || mapActions.mapDrawActionIndex < 0) {
|
||||
disabledSettings.drawing.push("undo");
|
||||
}
|
||||
if (
|
||||
!mapState ||
|
||||
mapState.mapDrawActionIndex === mapState.mapDrawActions.length - 1
|
||||
mapActions.mapDrawActionIndex === mapActions.mapDrawActions.length - 1
|
||||
) {
|
||||
disabledSettings.drawing.push("redo");
|
||||
}
|
||||
if (!mapState || mapState.fogDrawActionIndex < 0) {
|
||||
if (!mapState || mapActions.fogDrawActionIndex < 0) {
|
||||
disabledSettings.fog.push("undo");
|
||||
}
|
||||
if (
|
||||
!mapState ||
|
||||
mapState.fogDrawActionIndex === mapState.fogDrawActions.length - 1
|
||||
mapActions.fogDrawActionIndex === mapActions.fogDrawActions.length - 1
|
||||
) {
|
||||
disabledSettings.fog.push("redo");
|
||||
}
|
||||
@ -313,7 +301,7 @@ function Map({
|
||||
const mapDrawing = (
|
||||
<MapDrawing
|
||||
map={map}
|
||||
shapes={mapShapes}
|
||||
shapes={drawShapes}
|
||||
onShapeAdd={handleMapShapeAdd}
|
||||
onShapesRemove={handleMapShapesRemove}
|
||||
active={selectedToolId === "drawing"}
|
||||
|
@ -39,8 +39,8 @@ function MapTiles({
|
||||
for (let state of selectedMapStates) {
|
||||
if (
|
||||
Object.values(state.tokens).length > 0 ||
|
||||
state.mapDrawActions.length > 0 ||
|
||||
state.fogDrawActions.length > 0 ||
|
||||
Object.values(state.drawShapes).length > 0 ||
|
||||
Object.values(state.fogShapes).length > 0 ||
|
||||
Object.values(state.notes).length > 0
|
||||
) {
|
||||
hasMapState = true;
|
||||
|
@ -22,12 +22,8 @@ const cachedMapMax = 15;
|
||||
|
||||
const defaultMapState = {
|
||||
tokens: {},
|
||||
// An index into the draw actions array to which only actions before the
|
||||
// index will be performed (used in undo and redo)
|
||||
mapDrawActionIndex: -1,
|
||||
mapDrawActions: [],
|
||||
fogDrawActionIndex: -1,
|
||||
fogDrawActions: [],
|
||||
drawShapes: {},
|
||||
fogShapes: {},
|
||||
// Flags to determine what other people can edit
|
||||
editFlags: ["drawing", "tokens", "notes"],
|
||||
notes: {},
|
||||
|
@ -2,6 +2,7 @@ import Dexie from "dexie";
|
||||
|
||||
import blobToBuffer from "./helpers/blobToBuffer";
|
||||
import { getMapDefaultInset } from "./helpers/map";
|
||||
import { convertOldActionsToShapes } from "./actions";
|
||||
|
||||
function loadVersions(db) {
|
||||
// v1.2.0
|
||||
@ -305,7 +306,7 @@ function loadVersions(db) {
|
||||
});
|
||||
});
|
||||
|
||||
// 1.7.1 - Added note text only mode
|
||||
// 1.8.0 - Added note text only mode, converted draw and fog representations
|
||||
db.version(18)
|
||||
.stores({})
|
||||
.upgrade((tx) => {
|
||||
@ -316,6 +317,20 @@ function loadVersions(db) {
|
||||
for (let id in state.notes) {
|
||||
state.notes[id].textOnly = false;
|
||||
}
|
||||
|
||||
state.drawShapes = convertOldActionsToShapes(
|
||||
state.mapDrawActions,
|
||||
state.mapDrawActionIndex
|
||||
);
|
||||
state.fogShapes = convertOldActionsToShapes(
|
||||
state.fogDrawActions,
|
||||
state.fogDrawActionIndex
|
||||
);
|
||||
|
||||
delete state.mapDrawActions;
|
||||
delete state.mapDrawActionIndex;
|
||||
delete state.fogDrawActions;
|
||||
delete state.fogDrawActionIndex;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { applyChange, diff as deepDiff } from "deep-diff";
|
||||
import { applyChange, revertChange, diff as deepDiff } from "deep-diff";
|
||||
|
||||
export function applyChanges(target, changes) {
|
||||
for (let change of changes) {
|
||||
@ -6,4 +6,10 @@ export function applyChanges(target, changes) {
|
||||
}
|
||||
}
|
||||
|
||||
export function revertChanges(target, changes) {
|
||||
for (let change of changes) {
|
||||
revertChange(target, true, change);
|
||||
}
|
||||
}
|
||||
|
||||
export const diff = deepDiff;
|
||||
|
@ -2,7 +2,7 @@ import simplify from "simplify-js";
|
||||
import polygonClipping from "polygon-clipping";
|
||||
|
||||
import * as Vector2 from "./vector2";
|
||||
import { toDegrees, omit } from "./shared";
|
||||
import { toDegrees } from "./shared";
|
||||
import { getRelativePointerPositionNormalized } from "./konva";
|
||||
import { logError } from "./logging";
|
||||
|
||||
@ -219,80 +219,7 @@ export function simplifyPoints(points, gridSize, scale) {
|
||||
);
|
||||
}
|
||||
|
||||
export function drawActionsToShapes(actions, actionIndex) {
|
||||
let shapesById = {};
|
||||
for (let i = 0; i <= actionIndex; i++) {
|
||||
const action = actions[i];
|
||||
if (!action) {
|
||||
continue;
|
||||
}
|
||||
if (action.type === "add") {
|
||||
for (let shape of action.shapes) {
|
||||
shapesById[shape.id] = shape;
|
||||
}
|
||||
}
|
||||
if (action.type === "edit") {
|
||||
for (let edit of action.shapes) {
|
||||
if (edit.id in shapesById) {
|
||||
shapesById[edit.id] = { ...shapesById[edit.id], ...edit };
|
||||
}
|
||||
}
|
||||
}
|
||||
if (action.type === "remove") {
|
||||
shapesById = omit(shapesById, action.shapeIds);
|
||||
}
|
||||
if (action.type === "subtract") {
|
||||
const actionGeom = action.shapes.map((actionShape) => [
|
||||
actionShape.data.points.map(({ x, y }) => [x, y]),
|
||||
]);
|
||||
let subtractedShapes = {};
|
||||
for (let shape of Object.values(shapesById)) {
|
||||
const shapePoints = shape.data.points.map(({ x, y }) => [x, y]);
|
||||
const shapeHoles = shape.data.holes.map((hole) =>
|
||||
hole.map(({ x, y }) => [x, y])
|
||||
);
|
||||
let shapeGeom = [[shapePoints, ...shapeHoles]];
|
||||
const difference = polygonClipping.difference(shapeGeom, actionGeom);
|
||||
addPolygonDifferenceToShapes(shape, difference, subtractedShapes);
|
||||
}
|
||||
shapesById = subtractedShapes;
|
||||
}
|
||||
if (action.type === "cut") {
|
||||
const actionGeom = action.shapes.map((actionShape) => [
|
||||
actionShape.data.points.map(({ x, y }) => [x, y]),
|
||||
]);
|
||||
let cutShapes = {};
|
||||
for (let shape of Object.values(shapesById)) {
|
||||
const shapePoints = shape.data.points.map(({ x, y }) => [x, y]);
|
||||
const shapeHoles = shape.data.holes.map((hole) =>
|
||||
hole.map(({ x, y }) => [x, y])
|
||||
);
|
||||
let shapeGeom = [[shapePoints, ...shapeHoles]];
|
||||
try {
|
||||
const difference = polygonClipping.difference(shapeGeom, actionGeom);
|
||||
const intersection = polygonClipping.intersection(
|
||||
shapeGeom,
|
||||
actionGeom
|
||||
);
|
||||
addPolygonDifferenceToShapes(shape, difference, cutShapes);
|
||||
addPolygonIntersectionToShapes(shape, intersection, cutShapes);
|
||||
} catch {
|
||||
logError(
|
||||
new Error(
|
||||
`Unable to find segment for shapes ${JSON.stringify(
|
||||
shape
|
||||
)} and ${JSON.stringify(action)}`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
shapesById = cutShapes;
|
||||
}
|
||||
}
|
||||
return Object.values(shapesById);
|
||||
}
|
||||
|
||||
function addPolygonDifferenceToShapes(shape, difference, shapes) {
|
||||
export function addPolygonDifferenceToShapes(shape, difference, shapes) {
|
||||
for (let i = 0; i < difference.length; i++) {
|
||||
let newId = `${shape.id}-dif-${i}`;
|
||||
// Holes detected
|
||||
@ -314,7 +241,7 @@ function addPolygonDifferenceToShapes(shape, difference, shapes) {
|
||||
}
|
||||
}
|
||||
|
||||
function addPolygonIntersectionToShapes(shape, intersection, shapes) {
|
||||
export function addPolygonIntersectionToShapes(shape, intersection, shapes) {
|
||||
for (let i = 0; i < intersection.length; i++) {
|
||||
let newId = `${shape.id}-int-${i}`;
|
||||
shapes[newId] = {
|
||||
|
@ -17,6 +17,13 @@ import Session from "./Session";
|
||||
import Map from "../components/map/Map";
|
||||
import Tokens from "../components/token/Tokens";
|
||||
|
||||
const defaultMapActions = {
|
||||
mapDrawActions: [],
|
||||
mapDrawActionIndex: -1,
|
||||
fogDrawActions: [],
|
||||
fogDrawActionIndex: -1,
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {object} NetworkedMapProps
|
||||
* @property {Session} session
|
||||
@ -225,58 +232,115 @@ function NetworkedMapAndTokens({ session }) {
|
||||
setCurrentMapState(newMapState, true, true);
|
||||
}
|
||||
|
||||
function addMapDrawActions(actions, indexKey, actionsKey) {
|
||||
setCurrentMapState((prevMapState) => {
|
||||
const [mapActions, setMapActions] = useState(defaultMapActions);
|
||||
|
||||
function addMapActions(actions, indexKey, actionsKey, shapesKey) {
|
||||
setMapActions((prevMapActions) => {
|
||||
const newActions = [
|
||||
...prevMapState[actionsKey].slice(0, prevMapState[indexKey] + 1),
|
||||
...prevMapActions[actionsKey].slice(0, prevMapActions[indexKey] + 1),
|
||||
...actions,
|
||||
];
|
||||
const newIndex = newActions.length - 1;
|
||||
return {
|
||||
...prevMapState,
|
||||
...prevMapActions,
|
||||
[actionsKey]: newActions,
|
||||
[indexKey]: newIndex,
|
||||
};
|
||||
});
|
||||
// Update map state by performing the actions on it
|
||||
setCurrentMapState((prevMapState) => {
|
||||
if (prevMapState) {
|
||||
let shapes = prevMapState[shapesKey];
|
||||
for (let action of actions) {
|
||||
shapes = action.execute(shapes);
|
||||
}
|
||||
return {
|
||||
...prevMapState,
|
||||
[shapesKey]: shapes,
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateDrawActionIndex(change, indexKey, actionsKey) {
|
||||
function updateActionIndex(change, indexKey, actionsKey, shapesKey) {
|
||||
const prevIndex = mapActions[indexKey];
|
||||
const newIndex = Math.min(
|
||||
Math.max(currentMapState[indexKey] + change, -1),
|
||||
currentMapState[actionsKey].length - 1
|
||||
Math.max(mapActions[indexKey] + change, -1),
|
||||
mapActions[actionsKey].length - 1
|
||||
);
|
||||
|
||||
setCurrentMapState((prevMapState) => ({
|
||||
...prevMapState,
|
||||
setMapActions((prevMapActions) => ({
|
||||
...prevMapActions,
|
||||
[indexKey]: newIndex,
|
||||
}));
|
||||
|
||||
// Update map state by either performing the actions or undoing them
|
||||
setCurrentMapState((prevMapState) => {
|
||||
if (prevMapState) {
|
||||
let shapes = prevMapState[shapesKey];
|
||||
if (prevIndex < newIndex) {
|
||||
// Redo
|
||||
for (let i = prevIndex + 1; i < newIndex + 1; i++) {
|
||||
let action = mapActions[actionsKey][i];
|
||||
shapes = action.execute(shapes);
|
||||
}
|
||||
} else {
|
||||
// Undo
|
||||
for (let i = prevIndex; i > newIndex; i--) {
|
||||
let action = mapActions[actionsKey][i];
|
||||
shapes = action.undo(shapes);
|
||||
}
|
||||
}
|
||||
return {
|
||||
...prevMapState,
|
||||
[shapesKey]: shapes,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return newIndex;
|
||||
}
|
||||
|
||||
function handleMapDraw(action) {
|
||||
addMapDrawActions([action], "mapDrawActionIndex", "mapDrawActions");
|
||||
addMapActions(
|
||||
[action],
|
||||
"mapDrawActionIndex",
|
||||
"mapDrawActions",
|
||||
"drawShapes"
|
||||
);
|
||||
}
|
||||
|
||||
function handleMapDrawUndo() {
|
||||
updateDrawActionIndex(-1, "mapDrawActionIndex", "mapDrawActions");
|
||||
updateActionIndex(-1, "mapDrawActionIndex", "mapDrawActions", "drawShapes");
|
||||
}
|
||||
|
||||
function handleMapDrawRedo() {
|
||||
updateDrawActionIndex(1, "mapDrawActionIndex", "mapDrawActions");
|
||||
updateActionIndex(1, "mapDrawActionIndex", "mapDrawActions", "drawShapes");
|
||||
}
|
||||
|
||||
function handleFogDraw(action) {
|
||||
addMapDrawActions([action], "fogDrawActionIndex", "fogDrawActions");
|
||||
addMapActions(
|
||||
[action],
|
||||
"fogDrawActionIndex",
|
||||
"fogDrawActions",
|
||||
"fogShapes"
|
||||
);
|
||||
}
|
||||
|
||||
function handleFogDrawUndo() {
|
||||
updateDrawActionIndex(-1, "fogDrawActionIndex", "fogDrawActions");
|
||||
updateActionIndex(-1, "fogDrawActionIndex", "fogDrawActions", "fogShapes");
|
||||
}
|
||||
|
||||
function handleFogDrawRedo() {
|
||||
updateDrawActionIndex(1, "fogDrawActionIndex", "fogDrawActions");
|
||||
updateActionIndex(1, "fogDrawActionIndex", "fogDrawActions", "fogShapes");
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentMapState) {
|
||||
setMapActions(defaultMapActions);
|
||||
}
|
||||
}, [currentMapState]);
|
||||
|
||||
function handleNoteChange(note) {
|
||||
setCurrentMapState((prevMapState) => ({
|
||||
...prevMapState,
|
||||
@ -495,6 +559,7 @@ function NetworkedMapAndTokens({ session }) {
|
||||
<Map
|
||||
map={currentMap}
|
||||
mapState={currentMapState}
|
||||
mapActions={mapActions}
|
||||
onMapTokenStateChange={handleMapTokenStateChange}
|
||||
onMapTokenStateRemove={handleMapTokenStateRemove}
|
||||
onMapChange={handleMapChange}
|
||||
|
@ -7697,6 +7697,11 @@ lodash.camelcase@^4.3.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
||||
integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
|
||||
|
||||
lodash.clonedeep@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
|
||||
|
||||
lodash.debounce@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||
|
Loading…
x
Reference in New Issue
Block a user