251 lines
6.9 KiB
JavaScript
251 lines
6.9 KiB
JavaScript
import React, { useEffect, useState, useContext, useCallback } from "react";
|
|
|
|
import { useUserId } from "./UserIdContext";
|
|
import { useDatabase } from "./DatabaseContext";
|
|
|
|
import { applyObservableChange } from "../helpers/dexie";
|
|
import { removeGroupsItems } from "../helpers/group";
|
|
|
|
const MapDataContext = React.createContext();
|
|
|
|
const defaultMapState = {
|
|
tokens: {},
|
|
drawShapes: {},
|
|
fogShapes: {},
|
|
// Flags to determine what other people can edit
|
|
editFlags: ["drawing", "tokens", "notes"],
|
|
notes: {},
|
|
};
|
|
|
|
export function MapDataProvider({ children }) {
|
|
const { database, databaseStatus } = useDatabase();
|
|
const userId = useUserId();
|
|
|
|
const [maps, setMaps] = useState([]);
|
|
const [mapStates, setMapStates] = useState([]);
|
|
const [mapsLoading, setMapsLoading] = useState(true);
|
|
const [mapGroups, setMapGroups] = useState([]);
|
|
|
|
// Load maps from the database and ensure state is properly setup
|
|
useEffect(() => {
|
|
if (!userId || !database || databaseStatus === "loading") {
|
|
return;
|
|
}
|
|
|
|
async function loadMaps() {
|
|
const storedMaps = await database.table("maps").toArray();
|
|
setMaps(storedMaps);
|
|
const storedStates = await database.table("states").toArray();
|
|
setMapStates(storedStates);
|
|
const group = await database.table("groups").get("maps");
|
|
const storedGroups = group.items;
|
|
setMapGroups(storedGroups);
|
|
setMapsLoading(false);
|
|
}
|
|
|
|
loadMaps();
|
|
}, [userId, database, databaseStatus]);
|
|
|
|
const getMap = useCallback(
|
|
async (mapId) => {
|
|
let map = await database.table("maps").get(mapId);
|
|
return map;
|
|
},
|
|
[database]
|
|
);
|
|
|
|
const getMapState = useCallback(
|
|
async (mapId) => {
|
|
let mapState = await database.table("states").get(mapId);
|
|
return mapState;
|
|
},
|
|
[database]
|
|
);
|
|
|
|
/**
|
|
* Adds a map to the database, also adds an assosiated state and group for that map
|
|
* @param {Object} map map to add
|
|
*/
|
|
const addMap = useCallback(
|
|
async (map) => {
|
|
// Just update map database as react state will be updated with an Observable
|
|
const state = { ...defaultMapState, mapId: map.id };
|
|
await database.table("maps").add(map);
|
|
await database.table("states").add(state);
|
|
const group = await database.table("groups").get("maps");
|
|
await database.table("groups").update("maps", {
|
|
items: [{ id: map.id, type: "item" }, ...group.items],
|
|
});
|
|
},
|
|
[database]
|
|
);
|
|
|
|
const removeMaps = useCallback(
|
|
async (ids) => {
|
|
const maps = await database.table("maps").bulkGet(ids);
|
|
// Remove assets linked with maps
|
|
let assetIds = [];
|
|
for (let map of maps) {
|
|
if (map.type === "file") {
|
|
assetIds.push(map.file);
|
|
assetIds.push(map.thumbnail);
|
|
for (let res of Object.values(map.resolutions)) {
|
|
assetIds.push(res);
|
|
}
|
|
}
|
|
}
|
|
|
|
const group = await database.table("groups").get("maps");
|
|
let items = removeGroupsItems(group.items, ids);
|
|
await database.table("groups").update("maps", { items });
|
|
|
|
await database.table("maps").bulkDelete(ids);
|
|
await database.table("states").bulkDelete(ids);
|
|
await database.table("assets").bulkDelete(assetIds);
|
|
},
|
|
[database]
|
|
);
|
|
|
|
const resetMap = useCallback(
|
|
async (id) => {
|
|
const state = { ...defaultMapState, mapId: id };
|
|
await database.table("states").put(state);
|
|
return state;
|
|
},
|
|
[database]
|
|
);
|
|
|
|
const updateMap = useCallback(
|
|
async (id, update) => {
|
|
await database.table("maps").update(id, update);
|
|
},
|
|
[database]
|
|
);
|
|
|
|
const updateMapState = useCallback(
|
|
async (id, update) => {
|
|
await database.table("states").update(id, update);
|
|
},
|
|
[database]
|
|
);
|
|
|
|
const updateMapGroups = useCallback(
|
|
async (groups) => {
|
|
// Update group state immediately to avoid animation delay
|
|
setMapGroups(groups);
|
|
await database.table("groups").update("maps", { items: groups });
|
|
},
|
|
[database]
|
|
);
|
|
|
|
// Create DB observable to sync creating and deleting
|
|
useEffect(() => {
|
|
if (!database || databaseStatus === "loading") {
|
|
return;
|
|
}
|
|
|
|
function handleMapChanges(changes) {
|
|
for (let change of changes) {
|
|
if (change.table === "maps") {
|
|
if (change.type === 1) {
|
|
// Created
|
|
const map = change.obj;
|
|
const state = { ...defaultMapState, mapId: map.id };
|
|
setMaps((prevMaps) => [map, ...prevMaps]);
|
|
setMapStates((prevStates) => [state, ...prevStates]);
|
|
} else if (change.type === 2) {
|
|
const map = change.obj;
|
|
setMaps((prevMaps) => {
|
|
const newMaps = [...prevMaps];
|
|
const i = newMaps.findIndex((m) => m.id === map.id);
|
|
if (i > -1) {
|
|
newMaps[i] = map;
|
|
}
|
|
return newMaps;
|
|
});
|
|
} else if (change.type === 3) {
|
|
// Deleted
|
|
const id = change.key;
|
|
setMaps((prevMaps) => {
|
|
const filtered = prevMaps.filter((map) => map.id !== id);
|
|
return filtered;
|
|
});
|
|
setMapStates((prevMapsStates) => {
|
|
const filtered = prevMapsStates.filter(
|
|
(state) => state.mapId !== id
|
|
);
|
|
return filtered;
|
|
});
|
|
}
|
|
}
|
|
if (change.table === "states") {
|
|
if (change.type === 2) {
|
|
// Update map state
|
|
const state = change.obj;
|
|
setMapStates((prevMapStates) => {
|
|
const newStates = [...prevMapStates];
|
|
const i = newStates.findIndex((s) => s.mapId === state.mapId);
|
|
if (i > -1) {
|
|
newStates[i] = state;
|
|
}
|
|
return newStates;
|
|
});
|
|
}
|
|
}
|
|
if (change.table === "groups") {
|
|
if (change.type === 2 && change.key === "maps") {
|
|
const group = applyObservableChange(change);
|
|
const groups = group.items.filter((item) => item !== null);
|
|
setMapGroups(groups);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
database.on("changes", handleMapChanges);
|
|
|
|
return () => {
|
|
database.on("changes").unsubscribe(handleMapChanges);
|
|
};
|
|
}, [database, databaseStatus]);
|
|
|
|
const [mapsById, setMapsById] = useState({});
|
|
useEffect(() => {
|
|
setMapsById(
|
|
maps.reduce((obj, map) => {
|
|
obj[map.id] = map;
|
|
return obj;
|
|
}, {})
|
|
);
|
|
}, [maps]);
|
|
|
|
const value = {
|
|
maps,
|
|
mapStates,
|
|
mapGroups,
|
|
addMap,
|
|
removeMaps,
|
|
resetMap,
|
|
updateMap,
|
|
updateMapState,
|
|
getMap,
|
|
mapsLoading,
|
|
getMapState,
|
|
updateMapGroups,
|
|
mapsById,
|
|
};
|
|
return (
|
|
<MapDataContext.Provider value={value}>{children}</MapDataContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useMapData() {
|
|
const context = useContext(MapDataContext);
|
|
if (context === undefined) {
|
|
throw new Error("useMapData must be used within a MapDataProvider");
|
|
}
|
|
return context;
|
|
}
|
|
|
|
export default MapDataContext;
|