This changes a lot of things. It's actually only the second time that I've merged from the dev branch. The main new additions are the Mapper and more tools in the utils/dex to get items (Field, Scripted, and Hidden), shops (PokeMart, Special, and Heart Scale), and trainers (Scripted, and Placedata).
Mapper Components
The Mapper Itself
There are a lot of moving parts to this that I'll cover in multiple sections. The overall summary of this is that draws a canvas and updates that canvas according to several inputs given to it. The way the canvas is updated in broad terms is that it's initialized in it's entirety with the image and the overlay. From there, to optimize performance, the canvas only updates small relevant chunks.
States
This first section may be a bit confusing at first having let variables and react states co-mingling, but this is a necessary feature. The let variables are responsible for updating the actual canvas and the react states are for updating the UI elements like the Encounters dropdowns. You may be asking, "Why do I need separate states for the canvas?" And that's a valid question. The answer to that question is that a vanilla <canvas/> element does not have the same life cycles as React does. The canvas elements are updated before React states change since react uses an asynchronous update life cycle and canvas is synchronous.
Setting up the Canvas
The next chunk of this file is responsible for the setup for the canvas. The canvasRef is initialized and then mounted only on the initial render with just the image and the overlay which is handled by the drawOverlay. This set up process will never happen again for as long as the browser isn't refreshed. To updated the canvas, the drawRect and clearRect functions are called when needed.
Updating the Canvas
In order to update the Canvas in an efficient manner, we use the drawRect and clearRect functions.
drawRect:
We'll go over the drawRect function first as that is the easier of the two to understand. In order to draw on the canvas, you need the location that you want to draw in and the specific mode defined in the CLEAR_MODE constant. If the previous rectangle hadn't been cleared before calling this function (and it wasn't called for encounters), it will clear that previous location.
From there, it will store the current location's position and image data in the previousRectangle and originalImageData variables respectively. It will then set the fillStyle according to the mode and draw that rectangle in the given location.
clearRect:
This function is a bit more complex as it has 2 main branches.
The first branch is if you're not clearing an Encounter. The way this works is by taking the layers that have been drawn onto the canvas and removing them one by one before replacing the resulting blank spot with the original image data. So, if this is in SELECT mode, it will clear the area of the image. Then it will take the hover highlighted section and put it's original data back in place. Finally it will put the select highlighted original image data back in place. This may be an extra step, but it works....
The second branch is if you are clearing and encounter highlight. For this mode, you need to provide a location that you're trying to clear. It will then get the index of the given location inside of the previous encounter rectangles array. It will then clear that location of the encounter highlight and either replace it with the original image data or draw a select highlight depending on if the location is currently selected.
Listener Events for the Canvas
Because the <canvas /> element is synchronous, custom listener events are required to be dispatched so React elements can interact with the canvas. There are 6 listeners in total that the canvas is listening to:
Location Dropdown
Handled by updateLocationDataFromDropdown
This is a custom listener attached to the canvas to update the selected location when the dropdown is used.
It grabs the coordinates for the selected area, clears the hover highlight (if any) in that area, resets any previous selected area highlights, draws the new highlight, and sets all of the map data.
Pokemon Name Dropdown
Handled by updatePokemonLocationsFromDropdown
This is a custom listener that receives a pokemon object when the respective dropdown is updated.
It takes the pokemonId of the pokemon object and searches for all of the locations that pokemon can be found.
It checks if there are any previous encounter highlighted areas and clears them as long as it isn't the selected location
It then draws all of the areas detailed that aren't hovered on or selected as encounter highlights
Color Settings
Handled by updateColorSettings
This is a custom listener that waits for the Save Changes button to be pressed in the Setting Modal.
It takes the colorSettings and clears out any highlighted areas and sets the pokemon dropdown to the default of "Bulbasaur".
If there was a selected area, it is reselected.
Click
Handled by handleClick
The mouse's position is being calculated by this whenever a click occurs in the canvas bounds.
This acts very similarly to the Location Dropdown where it will clear the selected location of hover highlights and then select highlight it.
Mouse Move
Handled by handleMouseMove.
This is the fastest updating listener and it needs to be in order to keep up.
In order to update this listener, it tracks the mouse cursor inside of the canvas.
If the mouse cursor lines up with an actual map location, it will hover highlight that location by drawing on top of the canvas and storing the original canvas image.
When the mouse cursor either doesn't line up with a map location or it changes which map location it is hovering over, it will clear the previous hover highlight, place to original image data back and perform the previous step of highlighting and storing.
Mouse Leave
This handles when the mouse leaves the bounds of the canvas. And now that I'm looking at it closer, it basically does nothing. Setting the HoveredZone to null is already handled by the mouse move and we don't actually want to clear the encounter locations when you leave the canvas.
Will likely delete this in a future PR.
useEffect hooks
This first useEffect hook is to update the listeners whenever the canvasRef.current is called/updated. This happens whenever the conditions are met to update the event listeners. It seems backwards and would be updated infinitely. That is what the return statement is for the remove the event listeners and cleanup. Not too sure how it works but I know that it does.
The other useEffects are to update the Encounter list whenever the encounter options are updated in the Encounter component, and update the location list that matches up with the current selected pokemon name.
Setting Up Encounters
This next section which is handled by setAllEncounters is to take all of the inputs from the Encounter component and replace and/or move around encounters according to those options.
Encounters
This is the main component that is used for displaying the Encounters. It gives the user access to change the time of day, fishing rod, swarm, radar, and incense methods at will. This also gives dropdowns to visualize the changes to the options given in real time. The PokemonAccordion was extended to allow the use of strict colors that don't change in dark/light mode.
With 3.0, this will need to be updated so the Incense with have a Toggle similar to the TOD and Rods. A future will include Special (Static/Gift) encounters in an additional dropdown.
This currently has a bug where the encounter method will not carry over to a new route when that is selected.
EncounterTable
This is a very basic and generalized component that displays a table containing all of the encounters for a specific list of encounters. The only special conditionals are:
Displaying the min and max levels or just the min level (This only applies to Resort Area afaik)
If the encounterRate is "morning", then change the encounter type to "Morning" and change the encounter rate to be "10%".
Buttons
This is to keep the complex buttons for the EncounterTable in one place so it's easier to debug in the future. They function very similarly where only one option can be selected at a time. Their states are handled in the Mapper.jsx component and are passed in via the Encounters.jsx component. These can probably be updated to be html structure calls, but they work for now.
Settings Modal
This is a Modal that handles settings (obviously lol). This is currently only handling the highlight colors, but this will be the area to add any potential customization to the Mapper. To do that , the handleSubmit function will need to be refactored to handle more than just the highlight colors and probably pass those different settings through a new CustomEvent.
Highlight Colors
This is a form component that is used by the Settings Modal to display and change the highlight colors within the rgba color spectrum. The state is handled in part in the SettingsModal.jsx component and mainly in the Mapper.jsx component. This form uses MUI's form inputs in order to display the numbers with a label.
Currently there is a bug in the TextField component from MUI when using type="number" that doesn't allow for incremental steps that are less than 1 but greater than 0 ( 0 > 0 < 1). This will be solved in a future update by changing the TextField to be a NumberInput, when the NumberInput is no longer in beta. This means that the transparency value (a) is unable to change effectively and will therefore be disabled for the time being.
SearchBar
This component is tricky as it performs 2 tasks. The obvious function is giving access to the Pokemon Search Bar and Location Dropdown. The not so obvious function is overlaying on top of the Mapper's canvas to position the dropdowns in the correct spot on the page. Each of these uses CustomEvent to dispatch an event that the canvas can see and update to. They also have a normal state that is handled by the parent Mapper.jsx as well.
The trick to overlay on top of the canvas was to combine a few css tricks. The div that houses the dropdowns are placed in an absolute position from the top and then given the pointer-events: none; property to allow all events like clicking or hovering to pass through it. And yes this includes all of the children. So in order to allow the children to have pointer-events, they were given their own css class that turns pointer-events to all.
As for the Pokemon Search Bar and Location Dropdown, these are a lot more straightforward. There was an attempt at debouncing the Pokemon Search Input to give a time buffer, but it doesn't seem like this is necessary at this time. The only way to select a Pokemon is to click on it in the dropdown. The Location Dropdown is the same functionally except it doesn't have the debounced value. The options for the allPokemons are provided by the custom plugin pokedex-data-plugin. The Location Dropdown just uses all of the location names detailed in coordinates.js as the options to select.
I see that there are some errors in some cases because of the utility functions that are not called with the correct parameters. As we work on #113 we should be able to solve at least some of these as well.
Summary
This changes a lot of things. It's actually only the second time that I've merged from the dev branch. The main new additions are the Mapper and more tools in the
utils/dex
to get items (Field, Scripted, and Hidden), shops (PokeMart, Special, and Heart Scale), and trainers (Scripted, and Placedata).Mapper Components
The Mapper Itself
There are a lot of moving parts to this that I'll cover in multiple sections. The overall summary of this is that draws a canvas and updates that canvas according to several inputs given to it. The way the canvas is updated in broad terms is that it's initialized in it's entirety with the image and the overlay. From there, to optimize performance, the canvas only updates small relevant chunks.
States
This first section may be a bit confusing at first having
let
variables and react states co-mingling, but this is a necessary feature. Thelet
variables are responsible for updating the actual canvas and the react states are for updating the UI elements like the Encounters dropdowns. You may be asking, "Why do I need separate states for the canvas?" And that's a valid question. The answer to that question is that a vanilla<canvas/>
element does not have the same life cycles as React does. The canvas elements are updated before React states change since react uses an asynchronous update life cycle and canvas is synchronous.Setting up the Canvas
The next chunk of this file is responsible for the setup for the canvas. The
canvasRef
is initialized and then mounted only on the initial render with just the image and the overlay which is handled by thedrawOverlay
. This set up process will never happen again for as long as the browser isn't refreshed. To updated the canvas, thedrawRect
andclearRect
functions are called when needed.Updating the Canvas
In order to update the Canvas in an efficient manner, we use the
drawRect
andclearRect
functions.drawRect:
We'll go over the drawRect function first as that is the easier of the two to understand. In order to draw on the canvas, you need the location that you want to draw in and the specific mode defined in the
CLEAR_MODE
constant. If the previous rectangle hadn't been cleared before calling this function (and it wasn't called for encounters), it will clear that previous location.From there, it will store the current location's position and image data in the
previousRectangle
andoriginalImageData
variables respectively. It will then set thefillStyle
according to the mode and draw that rectangle in the given location.clearRect:
This function is a bit more complex as it has 2 main branches.
The first branch is if you're not clearing an Encounter. The way this works is by taking the layers that have been drawn onto the canvas and removing them one by one before replacing the resulting blank spot with the original image data. So, if this is in SELECT mode, it will clear the area of the image. Then it will take the hover highlighted section and put it's original data back in place. Finally it will put the select highlighted original image data back in place. This may be an extra step, but it works....
The second branch is if you are clearing and encounter highlight. For this mode, you need to provide a location that you're trying to clear. It will then get the index of the given location inside of the previous encounter rectangles array. It will then clear that location of the encounter highlight and either replace it with the original image data or draw a select highlight depending on if the location is currently selected.
Listener Events for the Canvas
Because the
<canvas />
element is synchronous, custom listener events are required to be dispatched so React elements can interact with the canvas. There are 6 listeners in total that the canvas is listening to:Location Dropdown
updateLocationDataFromDropdown
Pokemon Name Dropdown
updatePokemonLocationsFromDropdown
Color Settings
updateColorSettings
Save Changes
button to be pressed in the Setting Modal.Click
handleClick
Mouse Move
handleMouseMove
.Mouse Leave
HoveredZone
to null is already handled by the mouse move and we don't actually want to clear the encounter locations when you leave the canvas.useEffect hooks
This first useEffect hook is to update the listeners whenever the canvasRef.current is called/updated. This happens whenever the conditions are met to update the event listeners. It seems backwards and would be updated infinitely. That is what the return statement is for the remove the event listeners and cleanup. Not too sure how it works but I know that it does.
The other useEffects are to update the Encounter list whenever the encounter options are updated in the
Encounter
component, and update the location list that matches up with the current selected pokemon name.Setting Up Encounters
This next section which is handled by
setAllEncounters
is to take all of the inputs from theEncounter
component and replace and/or move around encounters according to those options.Encounters
This is the main component that is used for displaying the Encounters. It gives the user access to change the time of day, fishing rod, swarm, radar, and incense methods at will. This also gives dropdowns to visualize the changes to the options given in real time. The PokemonAccordion was extended to allow the use of strict colors that don't change in dark/light mode.
With 3.0, this will need to be updated so the Incense with have a Toggle similar to the TOD and Rods. A future will include Special (Static/Gift) encounters in an additional dropdown.
This currently has a bug where the encounter method will not carry over to a new route when that is selected.
EncounterTable
This is a very basic and generalized component that displays a table containing all of the encounters for a specific list of encounters. The only special conditionals are:
Buttons
This is to keep the complex buttons for the EncounterTable in one place so it's easier to debug in the future. They function very similarly where only one option can be selected at a time. Their states are handled in the
Mapper.jsx
component and are passed in via theEncounters.jsx
component. These can probably be updated to be html structure calls, but they work for now.Settings Modal
This is a Modal that handles settings (obviously lol). This is currently only handling the highlight colors, but this will be the area to add any potential customization to the Mapper. To do that , the
handleSubmit
function will need to be refactored to handle more than just the highlight colors and probably pass those different settings through a newCustomEvent
.Highlight Colors
This is a form component that is used by the Settings Modal to display and change the highlight colors within the rgba color spectrum. The state is handled in part in the
SettingsModal.jsx
component and mainly in theMapper.jsx
component. This form uses MUI's form inputs in order to display the numbers with a label.Currently there is a bug in the
TextField
component from MUI when usingtype="number"
that doesn't allow for incremental steps that are less than 1 but greater than 0 ( 0 > 0 < 1). This will be solved in a future update by changing theTextField
to be aNumberInput
, when the NumberInput is no longer in beta. This means that the transparency value (a) is unable to change effectively and will therefore be disabled for the time being.SearchBar
This component is tricky as it performs 2 tasks. The obvious function is giving access to the Pokemon Search Bar and Location Dropdown. The not so obvious function is overlaying on top of the Mapper's canvas to position the dropdowns in the correct spot on the page. Each of these uses
CustomEvent
to dispatch an event that the canvas can see and update to. They also have a normal state that is handled by the parentMapper.jsx
as well.The trick to overlay on top of the canvas was to combine a few css tricks. The div that houses the dropdowns are placed in an absolute position from the top and then given the
pointer-events: none;
property to allow all events like clicking or hovering to pass through it. And yes this includes all of the children. So in order to allow the children to have pointer-events, they were given their own css class that turnspointer-events
to all.As for the Pokemon Search Bar and Location Dropdown, these are a lot more straightforward. There was an attempt at debouncing the Pokemon Search Input to give a time buffer, but it doesn't seem like this is necessary at this time. The only way to select a Pokemon is to click on it in the dropdown. The Location Dropdown is the same functionally except it doesn't have the debounced value. The options for the
allPokemons
are provided by the custom pluginpokedex-data-plugin
. The Location Dropdown just uses all of the location names detailed incoordinates.js
as the options to select.Util Functions
...To Be Continued (I'm not finished lol)