Refactor map actions into hook

This commit is contained in:
Mitchell McCaffrey 2021-07-21 08:08:58 +10:00
parent 54bc3502df
commit ef55f9c3a5
3 changed files with 157 additions and 131 deletions

149
src/hooks/useMapActions.ts Normal file
View File

@ -0,0 +1,149 @@
import { useState, useCallback } from "react";
import Action from "../actions/Action";
import { DrawingState } from "../types/Drawing";
import { FogState } from "../types/Fog";
import { MapState } from "../types/MapState";
import { Notes } from "../types/Note";
import { TokenStates } from "../types/TokenState";
export type DrawingsAction = {
type: "drawings";
action: Action<DrawingState>;
};
export type FogsAction = { type: "fogs"; action: Action<FogState> };
export type TokensAction = { type: "tokens"; action: Action<TokenStates> };
export type NotesAction = { type: "notes"; action: Action<Notes> };
export type MapAction =
| DrawingsAction
| FogsAction
| TokensAction
| NotesAction;
export type MapActions = {
actions: MapAction[][];
actionIndex: number;
};
export type AddActionsEventHandler = (actions: MapAction[]) => void;
export type UpdateActionIndexEventHandler = (change: number) => void;
export type ResetActionsEventHandler = () => void;
const defaultMapActions: MapActions = {
actions: [],
actionIndex: -1,
};
function useMapActions(
setCurrentMapState: React.Dispatch<React.SetStateAction<MapState | null>>
): [
MapActions,
AddActionsEventHandler,
UpdateActionIndexEventHandler,
ResetActionsEventHandler
] {
const [mapActions, setMapActions] = useState(defaultMapActions);
function applyMapActionsToState(
mapState: MapState,
actions: MapAction[]
): MapState {
for (let mapAction of actions) {
if (mapAction.type === "drawings") {
mapState.drawShapes = mapAction.action.execute(mapState.drawShapes);
} else if (mapAction.type === "fogs") {
mapState.fogShapes = mapAction.action.execute(mapState.fogShapes);
} else if (mapAction.type === "tokens") {
mapState.tokens = mapAction.action.execute(mapState.tokens);
} else if (mapAction.type === "notes") {
mapState.notes = mapAction.action.execute(mapState.notes);
}
}
return mapState;
}
function undoMapActionsToState(
mapState: MapState,
actions: MapAction[]
): MapState {
for (let mapAction of actions) {
if (mapAction.type === "drawings") {
mapState.drawShapes = mapAction.action.undo(mapState.drawShapes);
} else if (mapAction.type === "fogs") {
mapState.fogShapes = mapAction.action.undo(mapState.fogShapes);
} else if (mapAction.type === "tokens") {
mapState.tokens = mapAction.action.undo(mapState.tokens);
} else if (mapAction.type === "notes") {
mapState.notes = mapAction.action.undo(mapState.notes);
}
}
return mapState;
}
function addActions(actions: MapAction[]) {
setMapActions((prevActions) => {
const newActions = [
...prevActions.actions.slice(0, prevActions.actionIndex + 1),
actions,
];
const newIndex = newActions.length - 1;
return {
actions: newActions,
actionIndex: newIndex,
};
});
// Update map state by performing the actions on it
setCurrentMapState((prevMapState) => {
if (!prevMapState) {
return prevMapState;
}
let state = { ...prevMapState };
state = applyMapActionsToState(state, actions);
return state;
});
}
function updateActionIndex(change: number) {
const prevIndex = mapActions.actionIndex;
const newIndex = Math.min(
Math.max(mapActions.actionIndex + change, -1),
mapActions.actions.length - 1
);
setMapActions((prevMapActions) => ({
...prevMapActions,
actionIndex: newIndex,
}));
// Update map state by either performing the actions or undoing them
setCurrentMapState((prevMapState) => {
if (!prevMapState) {
return prevMapState;
}
let state = { ...prevMapState };
if (prevIndex < newIndex) {
// Redo
for (let i = prevIndex + 1; i < newIndex + 1; i++) {
const actions = mapActions.actions[i];
state = applyMapActionsToState(state, actions);
}
} else {
// Undo
for (let i = prevIndex; i > newIndex; i--) {
const actions = mapActions.actions[i];
state = undoMapActionsToState(state, actions);
}
}
return state;
});
}
const resetActions = useCallback(() => {
setMapActions(defaultMapActions);
}, []);
return [mapActions, addActions, updateActionIndex, resetActions];
}
export default useMapActions;

View File

@ -12,8 +12,8 @@ import { omit } from "../helpers/shared";
import useDebounce from "../hooks/useDebounce";
import useNetworkedState from "../hooks/useNetworkedState";
import useMapActions from "../hooks/useMapActions";
// Load session for auto complete
import Session, { PeerDataEvent, PeerDataProgressEvent } from "./Session";
import Action from "../actions/Action";
@ -23,7 +23,7 @@ import TokenBar from "../components/token/TokenBar";
import GlobalImageDrop from "../components/image/GlobalImageDrop";
import { Map as MapType, MapActions, MapAction } from "../types/Map";
import { Map as MapType } from "../types/Map";
import { MapState } from "../types/MapState";
import {
AssetManifest,
@ -35,11 +35,6 @@ import { DrawingState } from "../types/Drawing";
import { FogState } from "../types/Fog";
import { Note } from "../types/Note";
const defaultMapActions: MapActions = {
actions: [],
actionIndex: -1,
};
/**
* @typedef {object} NetworkedMapProps
* @property {Session} session
@ -219,106 +214,12 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
await loadAssetManifestFromMap(newMap, newMapState);
}
const [_, addActions, updateActionIndex, resetActions] =
useMapActions(setCurrentMapState);
function handleMapReset(newMapState: MapState) {
setCurrentMapState(newMapState, true, true);
setMapActions(defaultMapActions);
}
const [mapActions, setMapActions] = useState(defaultMapActions);
function applyMapActionsToState(
mapState: MapState,
actions: MapAction[]
): MapState {
for (let mapAction of actions) {
if (mapAction.type === "drawings") {
mapState.drawShapes = mapAction.action.execute(mapState.drawShapes);
} else if (mapAction.type === "fogs") {
mapState.fogShapes = mapAction.action.execute(mapState.fogShapes);
} else if (mapAction.type === "tokens") {
mapState.tokens = mapAction.action.execute(mapState.tokens);
} else if (mapAction.type === "notes") {
mapState.notes = mapAction.action.execute(mapState.notes);
}
}
return mapState;
}
function undoMapActionsToState(
mapState: MapState,
actions: MapAction[]
): MapState {
for (let mapAction of actions) {
if (mapAction.type === "drawings") {
mapState.drawShapes = mapAction.action.undo(mapState.drawShapes);
} else if (mapAction.type === "fogs") {
mapState.fogShapes = mapAction.action.undo(mapState.fogShapes);
} else if (mapAction.type === "tokens") {
mapState.tokens = mapAction.action.undo(mapState.tokens);
} else if (mapAction.type === "notes") {
mapState.notes = mapAction.action.undo(mapState.notes);
}
}
return mapState;
}
function addActions(actions: MapAction[]) {
setMapActions((prevActions) => {
const newActions = [
...prevActions.actions.slice(0, prevActions.actionIndex + 1),
actions,
];
const newIndex = newActions.length - 1;
return {
actions: newActions,
actionIndex: newIndex,
};
});
// Update map state by performing the actions on it
setCurrentMapState((prevMapState) => {
if (!prevMapState) {
return prevMapState;
}
let state = { ...prevMapState };
state = applyMapActionsToState(state, actions);
return state;
});
}
function updateActionIndex(change: number) {
const prevIndex = mapActions.actionIndex;
const newIndex = Math.min(
Math.max(mapActions.actionIndex + change, -1),
mapActions.actions.length - 1
);
setMapActions((prevMapActions) => ({
...prevMapActions,
actionIndex: newIndex,
}));
// Update map state by either performing the actions or undoing them
setCurrentMapState((prevMapState) => {
if (!prevMapState) {
return prevMapState;
}
let state = { ...prevMapState };
if (prevIndex < newIndex) {
// Redo
for (let i = prevIndex + 1; i < newIndex + 1; i++) {
const actions = mapActions.actions[i];
state = applyMapActionsToState(state, actions);
}
} else {
// Undo
for (let i = prevIndex; i > newIndex; i--) {
const actions = mapActions.actions[i];
state = undoMapActionsToState(state, actions);
}
}
return state;
});
resetActions();
}
function handleMapDraw(action: Action<DrawingState>) {
@ -341,10 +242,10 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
const previousMapIdRef = useRef<string>();
useEffect(() => {
if (currentMap && currentMap?.id !== previousMapIdRef.current) {
setMapActions(defaultMapActions);
resetActions();
previousMapIdRef.current = currentMap?.id;
}
}, [currentMap]);
}, [currentMap, resetActions]);
function handleNoteCreate(notes: Note[]) {
setCurrentMapState((prevMapState) => {

View File

@ -1,10 +1,5 @@
import React from "react";
import Action from "../actions/Action";
import { DrawingState } from "./Drawing";
import { FogState } from "./Fog";
import { Grid } from "./Grid";
import { Notes } from "./Note";
import { TokenStates } from "./TokenState";
export type MapToolId =
| "map"
@ -60,22 +55,3 @@ export type FileMap = BaseMap & {
};
export type Map = DefaultMap | FileMap;
export type DrawingsAction = {
type: "drawings";
action: Action<DrawingState>;
};
export type FogsAction = { type: "fogs"; action: Action<FogState> };
export type TokensAction = { type: "tokens"; action: Action<TokenStates> };
export type NotesAction = { type: "notes"; action: Action<Notes> };
export type MapAction =
| DrawingsAction
| FogsAction
| TokensAction
| NotesAction;
export type MapActions = {
actions: MapAction[][];
actionIndex: number;
};