/* * Copyright 2020 James Lyne * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import {MutationTree} from "vuex"; import {MutationTypes} from "@/store/mutation-types"; import {State} from "@/store/state"; import { DynmapArea, DynmapCircle, DynmapComponentConfig, DynmapLine, Coordinate, DynmapMarker, DynmapMarkerSet, DynmapMarkerSetUpdates, DynmapPlayer, DynmapServerConfig, DynmapTileUpdate, DynmapWorld, DynmapWorldState, DynmapParsedUrl, DynmapChat } from "@/dynmap"; import {DynmapProjection} from "@/leaflet/projection/DynmapProjection"; import {LiveAtlasMessageConfig, LiveAtlasServerDefinition, LiveAtlasSidebarSection, LiveAtlasUIElement} from "@/index"; export type CurrentMapPayload = { worldName: string; mapName: string; } export type Mutations = { [MutationTypes.SET_SERVERS](state: S, servers: Map): void [MutationTypes.SET_CONFIGURATION](state: S, config: DynmapServerConfig): void [MutationTypes.SET_CONFIGURATION_HASH](state: S, hash: number): void [MutationTypes.CLEAR_CONFIGURATION_HASH](state: S): void [MutationTypes.SET_MESSAGES](state: S, messages: LiveAtlasMessageConfig): void [MutationTypes.SET_WORLDS](state: S, worlds: Array): void [MutationTypes.CLEAR_WORLDS](state: S): void [MutationTypes.SET_COMPONENTS](state: S, worlds: DynmapComponentConfig): void [MutationTypes.SET_MARKER_SETS](state: S, worlds: Map): void [MutationTypes.CLEAR_MARKER_SETS](state: S): void [MutationTypes.ADD_WORLD](state: S, world: DynmapWorld): void [MutationTypes.SET_WORLD_STATE](state: S, worldState: DynmapWorldState): void [MutationTypes.SET_UPDATE_TIMESTAMP](state: S, time: Date): void [MutationTypes.ADD_MARKER_SET_UPDATES](state: S, updates: Map): void [MutationTypes.ADD_TILE_UPDATES](state: S, updates: Array): void [MutationTypes.ADD_CHAT](state: State, chat: Array): void [MutationTypes.POP_MARKER_UPDATES](state: S, payload: {markerSet: string, amount: number}): void [MutationTypes.POP_AREA_UPDATES](state: S, payload: {markerSet: string, amount: number}): void [MutationTypes.POP_CIRCLE_UPDATES](state: S, payload: {markerSet: string, amount: number}): void [MutationTypes.POP_LINE_UPDATES](state: S, payload: {markerSet: string, amount: number}): void [MutationTypes.POP_TILE_UPDATES](state: S, amount: number): void [MutationTypes.INCREMENT_REQUEST_ID](state: S): void [MutationTypes.SET_PLAYERS_ASYNC](state: S, players: Set): Set [MutationTypes.SYNC_PLAYERS](state: S, keep: Set): void [MutationTypes.CLEAR_PLAYERS](state: S): void [MutationTypes.SET_CURRENT_SERVER](state: S, server: string): void [MutationTypes.SET_CURRENT_MAP](state: S, payload: CurrentMapPayload): void [MutationTypes.SET_CURRENT_PROJECTION](state: S, payload: DynmapProjection): void [MutationTypes.SET_CURRENT_LOCATION](state: S, payload: Coordinate): void [MutationTypes.SET_CURRENT_ZOOM](state: S, payload: number): void [MutationTypes.SET_PARSED_URL](state: S, payload: DynmapParsedUrl): void [MutationTypes.CLEAR_PARSED_URL](state: S): void [MutationTypes.CLEAR_CURRENT_MAP](state: S): void [MutationTypes.SET_FOLLOW_TARGET](state: S, payload: DynmapPlayer): void [MutationTypes.SET_PAN_TARGET](state: S, payload: DynmapPlayer): void [MutationTypes.CLEAR_FOLLOW_TARGET](state: S, a?: void): void [MutationTypes.CLEAR_PAN_TARGET](state: S, a?: void): void [MutationTypes.SET_SMALL_SCREEN](state: S, payload: boolean): void [MutationTypes.TOGGLE_UI_ELEMENT_VISIBILITY](state: S, payload: LiveAtlasUIElement): void [MutationTypes.SET_UI_ELEMENT_VISIBILITY](state: S, payload: {element: LiveAtlasUIElement, state: boolean}): void [MutationTypes.TOGGLE_SIDEBAR_SECTION_COLLAPSED_STATE](state: S, section: LiveAtlasSidebarSection): void [MutationTypes.SET_SIDEBAR_SECTION_COLLAPSED_STATE](state: S, payload: {section: LiveAtlasSidebarSection, state: boolean}): void [MutationTypes.SET_LOGGED_IN](state: S, payload: boolean): void } export const mutations: MutationTree & Mutations = { // Sets configuration options from the initial config fetch [MutationTypes.SET_SERVERS](state: State, config: Map) { state.servers = config; if(state.currentServer && !state.servers.has(state.currentServer.id)) { state.currentServer = undefined; } }, // Sets configuration options from the initial config fetch [MutationTypes.SET_CONFIGURATION](state: State, config: DynmapServerConfig) { state.configuration = Object.assign(state.configuration, config); state.configurationHash = config.hash; }, // Sets configuration hash [MutationTypes.SET_CONFIGURATION_HASH](state: State, hash: number) { state.configurationHash = hash; }, // Sets configuration hash [MutationTypes.CLEAR_CONFIGURATION_HASH](state: State) { state.configurationHash = undefined; }, //Set messsages from the initial config fetch [MutationTypes.SET_MESSAGES](state: State, messages: LiveAtlasMessageConfig) { state.messages = Object.assign(state.messages, messages); }, //Sets the list of worlds, and their settings, from the initial config fetch [MutationTypes.SET_WORLDS](state: State, worlds: Array) { state.worlds.clear(); state.maps.clear(); state.followTarget = undefined; state.panTarget = undefined; state.currentWorldState.timeOfDay = 0; state.currentWorldState.raining = false; state.currentWorldState.thundering = false; worlds.forEach(world => { state.worlds.set(world.name, world); world.maps.forEach(map => state.maps.set([world.name, map.name].join('_'), map)); }); //Update current world if a world with the same name still exists, otherwise clear if(state.currentWorld && state.worlds.has(state.currentWorld.name)) { state.currentWorld = state.worlds.get(state.currentWorld.name); } else { state.currentWorld = undefined; } //Update current map if a map with the same name still exists, otherwise clear if(state.currentWorld && state.currentMap && state.maps.has([state.currentWorld.name, state.currentMap.name].join('_'))) { state.currentMap = state.maps.get([state.currentWorld.name, state.currentMap.name].join('_')); } else { state.currentMap = undefined; } }, [MutationTypes.CLEAR_WORLDS](state: State) { state.worlds.clear(); state.maps.clear(); state.followTarget = undefined; state.panTarget = undefined; state.currentWorldState.timeOfDay = 0; state.currentWorldState.raining = false; state.currentWorldState.thundering = false; }, //Sets the state and settings of optional components, from the initial config fetch [MutationTypes.SET_COMPONENTS](state: State, components: DynmapComponentConfig) { state.components = Object.assign(state.components, components); }, //Sets the existing marker sets from the last marker fetch [MutationTypes.SET_MARKER_SETS](state: State, markerSets: Map) { state.markerSets.clear(); state.pendingSetUpdates.clear(); for(const entry of markerSets) { state.markerSets.set(entry[0], entry[1]); state.pendingSetUpdates.set(entry[0], { markerUpdates: [], areaUpdates: [], circleUpdates: [], lineUpdates: [], }); } }, [MutationTypes.CLEAR_MARKER_SETS](state: State) { state.markerSets.clear(); state.pendingSetUpdates.clear(); }, [MutationTypes.ADD_WORLD](state: State, world: DynmapWorld) { state.worlds.set(world.name, world); }, //Sets the current world state an update fetch [MutationTypes.SET_WORLD_STATE](state: State, worldState: DynmapWorldState) { state.currentWorldState = Object.assign(state.currentWorldState, worldState); }, //Sets the timestamp of the last update fetch [MutationTypes.SET_UPDATE_TIMESTAMP](state: State, timestamp: Date) { state.updateTimestamp = timestamp; }, //Adds markerset related updates from an update fetch to the pending updates list [MutationTypes.ADD_MARKER_SET_UPDATES](state: State, updates: Map) { for(const entry of updates) { if(!state.markerSets.has(entry[0])) { //Create marker set if it doesn't exist if(entry[1].payload) { state.markerSets.set(entry[0], { id: entry[0], showLabels: entry[1].payload.showLabels, minZoom: entry[1].payload.minZoom, maxZoom: entry[1].payload.maxZoom, priority: entry[1].payload.priority, label: entry[1].payload.label, hidden: entry[1].payload.hidden, markers: Object.freeze(new Map()) as Map, areas: Object.freeze(new Map()) as Map, circles: Object.freeze(new Map()) as Map, lines: Object.freeze(new Map()) as Map, }); state.pendingSetUpdates.set(entry[0], { markerUpdates: [], areaUpdates: [], circleUpdates: [], lineUpdates: [], }); } else { console.warn(`ADD_MARKER_SET_UPDATES: Marker set ${entry[0]} doesn't exist`); continue; } } const set = state.markerSets.get(entry[0]) as DynmapMarkerSet, setUpdates = state.pendingSetUpdates.get(entry[0]) as DynmapMarkerSetUpdates; //Delete the set if it has been deleted if(entry[1].removed) { state.markerSets.delete(entry[0]); state.pendingSetUpdates.delete(entry[0]); continue; } //Update the set itself if a payload exists if(entry[1].payload) { set.showLabels = entry[1].payload.showLabels; set.minZoom = entry[1].payload.minZoom; set.maxZoom = entry[1].payload.maxZoom; set.priority = entry[1].payload.priority; set.label = entry[1].payload.label; set.hidden = entry[1].payload.hidden; } //Update non-reactive lists for(const update of entry[1].markerUpdates) { if(update.removed) { set.markers.delete(update.id); } else { set.markers.set(update.id, update.payload as DynmapMarker); } } for(const update of entry[1].areaUpdates) { if(update.removed) { set.areas.delete(update.id); } else { set.areas.set(update.id, update.payload as DynmapArea); } } for(const update of entry[1].circleUpdates) { if(update.removed) { set.circles.delete(update.id); } else { set.circles.set(update.id, update.payload as DynmapCircle); } } for(const update of entry[1].lineUpdates) { if(update.removed) { set.lines.delete(update.id); } else { set.lines.set(update.id, update.payload as DynmapLine); } } //Add to reactive pending updates lists setUpdates.markerUpdates = setUpdates.markerUpdates.concat(entry[1].markerUpdates); setUpdates.areaUpdates = setUpdates.areaUpdates.concat(entry[1].areaUpdates); setUpdates.circleUpdates = setUpdates.circleUpdates.concat(entry[1].circleUpdates); setUpdates.lineUpdates = setUpdates.lineUpdates.concat(entry[1].lineUpdates); } }, //Adds tile updates from an update fetch to the pending updates list [MutationTypes.ADD_TILE_UPDATES](state: State, updates: Array) { state.pendingTileUpdates = state.pendingTileUpdates.concat(updates); }, //Adds chat messages from an update fetch to the chat history [MutationTypes.ADD_CHAT](state: State, chat: Array) { state.chat.messages.unshift(...chat); }, //Pops the specified number of marker updates from the pending updates list [MutationTypes.POP_MARKER_UPDATES](state: State, {markerSet, amount}) { if(!state.markerSets.has(markerSet)) { console.warn(`POP_MARKER_UPDATES: Marker set ${markerSet} doesn't exist`); return; } state.pendingSetUpdates.get(markerSet)!.markerUpdates.splice(0, amount); }, //Pops the specified number of area updates from the pending updates list [MutationTypes.POP_AREA_UPDATES](state: State, {markerSet, amount}) { if(!state.markerSets.has(markerSet)) { console.warn(`POP_AREA_UPDATES: Marker set ${markerSet} doesn't exist`); return; } state.pendingSetUpdates.get(markerSet)!.areaUpdates.splice(0, amount); }, //Pops the specified number of circle updates from the pending updates list [MutationTypes.POP_CIRCLE_UPDATES](state: State, {markerSet, amount}) { if(!state.markerSets.has(markerSet)) { console.warn(`POP_CIRCLE_UPDATES: Marker set ${markerSet} doesn't exist`); return; } state.pendingSetUpdates.get(markerSet)!.circleUpdates.splice(0, amount); }, //Pops the specified number of line updates from the pending updates list [MutationTypes.POP_LINE_UPDATES](state: State, {markerSet, amount}) { if(!state.markerSets.has(markerSet)) { console.warn(`POP_LINE_UPDATES: Marker set ${markerSet} doesn't exist`); return; } state.pendingSetUpdates.get(markerSet)!.lineUpdates.splice(0, amount); }, //Pops the specified number of tile updates from the pending updates list [MutationTypes.POP_TILE_UPDATES](state: State, amount: number) { state.pendingTileUpdates.splice(0, amount); }, //Increments the request id for the next update fetch [MutationTypes.INCREMENT_REQUEST_ID](state: State) { state.updateRequestId++; }, // Set up to 10 players at once [MutationTypes.SET_PLAYERS_ASYNC](state: State, players: Set): Set { let count = 0; for(const player of players) { if(state.players.has(player.account)) { const existing = state.players.get(player.account); existing!.health = player.health; existing!.armor = player.armor; existing!.location = Object.assign(existing!.location, player.location); existing!.hidden = player.hidden; existing!.name = player.name; existing!.sort = player.sort; } else { state.players.set(player.account, { account: player.account, health: player.health, armor: player.armor, location: player.location, name: player.name, sort: player.sort, hidden: player.hidden, }); } players.delete(player); if(++count >= 10) { break; } } return players; }, //Removes all players not found in the provided keep set [MutationTypes.SYNC_PLAYERS](state: State, keep: Set) { for(const [key, player] of state.players) { if(!keep.has(player.account)) { state.players.delete(key); } } }, //Removes all players not found in the provided keep set [MutationTypes.CLEAR_PLAYERS](state: State) { state.followTarget = undefined; state.panTarget = undefined; state.players.clear(); }, //Sets the currently active server [MutationTypes.SET_CURRENT_SERVER](state: State, serverName) { if(!state.servers.has(serverName)) { throw new RangeError(`Unknown server ${serverName}`); } state.currentServer = state.servers.get(serverName); }, //Sets the currently active map/world [MutationTypes.SET_CURRENT_MAP](state: State, {worldName, mapName}) { mapName = [worldName, mapName].join('_'); if(!state.worlds.has(worldName)) { throw new RangeError(`Unknown world ${worldName}`); } if(!state.maps.has(mapName)) { throw new RangeError(`Unknown map ${mapName}`); } const newWorld = state.worlds.get(worldName); if(state.currentWorld !== newWorld) { state.currentWorld = state.worlds.get(worldName); state.markerSets.clear(); state.pendingSetUpdates.clear(); state.pendingTileUpdates = []; } state.currentMap = state.maps.get(mapName); }, //Sets the projection to use for coordinate conversion in the current map [MutationTypes.SET_CURRENT_PROJECTION](state: State, projection) { state.currentProjection = projection; }, //Sets the current location the map is showing. This is called by the map itself, and calling elsewhere will not update the map. [MutationTypes.SET_CURRENT_LOCATION](state: State, payload: Coordinate) { state.currentLocation = payload; }, //Sets the current zoom level of the map. This is called by the map itself, and calling elsewhere will not update the map. [MutationTypes.SET_CURRENT_ZOOM](state: State, payload: number) { state.currentZoom = payload; }, //Sets the result of parsing the current map url, if present and valid [MutationTypes.SET_PARSED_URL](state: State, payload: DynmapParsedUrl) { state.parsedUrl = payload; }, //Clear the current map/world [MutationTypes.CLEAR_CURRENT_MAP](state: State) { state.markerSets.clear(); state.pendingSetUpdates.clear(); state.pendingTileUpdates = []; state.currentWorld = undefined; state.currentMap = undefined; }, //Clear any existing parsed url [MutationTypes.CLEAR_PARSED_URL](state: State) { state.parsedUrl = { world: undefined, map: undefined, location: undefined, zoom: undefined, legacy: false, }; }, //Set the follow target, which the map will automatically pan to keep in view [MutationTypes.SET_FOLLOW_TARGET](state: State, player: DynmapPlayer) { state.followTarget = player; }, //Set the pan target, which the map will immediately pan to once [MutationTypes.SET_PAN_TARGET](state: State, player: DynmapPlayer) { state.panTarget = player; }, //Clear the follow target [MutationTypes.CLEAR_FOLLOW_TARGET](state: State) { state.followTarget = undefined; }, //Clear the pan target [MutationTypes.CLEAR_PAN_TARGET](state: State) { state.panTarget = undefined; }, [MutationTypes.SET_SMALL_SCREEN](state: State, smallScreen: boolean): void { if(!state.ui.smallScreen && smallScreen && state.ui.visibleElements.size > 1) { state.ui.visibleElements.clear(); } state.ui.smallScreen = smallScreen; }, [MutationTypes.TOGGLE_UI_ELEMENT_VISIBILITY](state: State, element: LiveAtlasUIElement): void { const newState = !state.ui.visibleElements.has(element); if(newState && state.ui.smallScreen) { state.ui.visibleElements.clear(); } state.ui.previouslyVisibleElements.add(element); newState ? state.ui.visibleElements.add(element) : state.ui.visibleElements.delete(element); }, [MutationTypes.SET_UI_ELEMENT_VISIBILITY](state: State, payload: {element: LiveAtlasUIElement, state: boolean}): void { if(payload.state && state.ui.smallScreen) { state.ui.visibleElements.clear(); } if(payload.state || state.ui.visibleElements.has(payload.element)) { state.ui.previouslyVisibleElements.add(payload.element); } payload.state ? state.ui.visibleElements.add(payload.element) : state.ui.visibleElements.delete(payload.element); }, [MutationTypes.TOGGLE_SIDEBAR_SECTION_COLLAPSED_STATE](state: State, section: LiveAtlasSidebarSection): void { if(state.ui.sidebar.collapsedSections.has(section)) { state.ui.sidebar.collapsedSections.delete(section); } else { state.ui.sidebar.collapsedSections.add(section); } }, [MutationTypes.SET_SIDEBAR_SECTION_COLLAPSED_STATE](state: State, payload: {section: LiveAtlasSidebarSection, state: boolean}): void { if (payload.state) { state.ui.sidebar.collapsedSections.delete(payload.section); } else { state.ui.sidebar.collapsedSections.add(payload.section); } }, [MutationTypes.SET_LOGGED_IN](state: State, payload: boolean): void { state.loggedIn = payload; } }