]> git.localhorst.tv Git - alttp.git/commitdiff
interactive map
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Mon, 25 Mar 2024 10:24:12 +0000 (11:24 +0100)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Mon, 25 Mar 2024 10:24:12 +0000 (11:24 +0100)
resources/js/components/tracker/Map.js
resources/js/components/tracker/index.js
resources/js/helpers/tracker.js
resources/sass/tracker.scss

index 311263116a4c197f7af63378ecac1d793b2f6f1a..520ad938ab2faaafb61fd3774626af442104aa5b 100644 (file)
@@ -2,6 +2,14 @@ import PropTypes from 'prop-types';
 import React from 'react';
 import { useTranslation } from 'react-i18next';
 
+import {
+       clearAll,
+       decrement,
+       hasDungeonBoss,
+       increment,
+       toggleBoolean,
+       unclearAll,
+} from '../../helpers/tracker';
 import { useTracker } from '../../hooks/tracker';
 
 const LW_DUNGEONS = [
@@ -608,8 +616,24 @@ const Location = ({ number, l, size }) => {
        if (size) {
                classNames.push(`size-${size}`);
        }
+       if (l.handlePrimary) {
+               classNames.push('clickable');
+       }
 
-       return <g className={classNames.join(' ')} transform={`translate(${l.x} ${l.y})`}>
+       return <g
+               className={classNames.join(' ')}
+               onClick={(e) => {
+                       l.handlePrimary();
+                       e.preventDefault();
+                       e.stopPropagation();
+               }}
+               onContextMenu={(e) => {
+                       l.handleSecondary();
+                       e.preventDefault();
+                       e.stopPropagation();
+               }}
+               transform={`translate(${l.x} ${l.y})`}
+       >
                <title>{t(`tracker.location.${l.id}`)}</title>
                <rect className="box" x="0" y="0" />
                {number && l.cleared < l.total ?
@@ -627,12 +651,14 @@ Location.propTypes = {
                cleared: PropTypes.number,
                total: PropTypes.number,
                status: PropTypes.string,
+               handlePrimary: PropTypes.func,
+               handleSecondary: PropTypes.func,
        }),
        size: PropTypes.string,
 };
 
 const Map = () => {
-       const { dungeons, state } = useTracker();
+       const { dungeons, setState, state } = useTracker();
 
        const mapDungeon = React.useCallback(dungeon => {
                const definition = dungeons.find(d => d.id === dungeon.id);
@@ -641,7 +667,7 @@ const Map = () => {
                let status = 'available';
                if (cleared === total) {
                        if (['ct', 'gt'].includes(dungeon.id)) {
-                               if (state[`${dungeon.id}-boss-defeated`]) {
+                               if (hasDungeonBoss(state, dungeon)) {
                                        status = 'cleared';
                                }
                        } else {
@@ -653,8 +679,41 @@ const Map = () => {
                        status,
                        cleared,
                        total,
+                       handlePrimary: () => {
+                               if (['ct', 'gt'].includes(dungeon.id) && cleared === total) {
+                                       if (hasDungeonBoss(state, dungeon)) {
+                                               // reset
+                                               setState(s => ({
+                                                       ...s,
+                                                       [`${dungeon.id}-checks`]: 0,
+                                                       [`${dungeon.id}-boss-defeated`]: false,
+                                               }));
+                                       } else {
+                                               setState(toggleBoolean(`${dungeon.id}-boss-defeated`));
+                                       }
+                               } else {
+                                       setState(increment(`${dungeon.id}-checks`, total));
+                               }
+                       },
+                       handleSecondary: () => {
+                               if (['ct', 'gt'].includes(dungeon.id) &&
+                                       (hasDungeonBoss(state, dungeon) || !cleared)
+                               ) {
+                                       if (hasDungeonBoss(state, dungeon)) {
+                                               setState(toggleBoolean(`${dungeon.id}-boss-defeated`));
+                                       } else {
+                                               setState(s => ({
+                                                       ...s,
+                                                       [`${dungeon.id}-checks`]: total,
+                                                       [`${dungeon.id}-boss-defeated`]: true,
+                                               }));
+                                       }
+                               } else {
+                                       setState(decrement(`${dungeon.id}-checks`, total));
+                               }
+                       },
                };
-       }, [dungeons, state]);
+       }, [dungeons, setState, state]);
 
        const mapLocation = React.useCallback(loc => {
                const cleared = loc.checks.reduce((acc, cur) => state[cur] ? acc + 1 : acc, 0);
@@ -668,8 +727,22 @@ const Map = () => {
                        cleared,
                        total,
                        status,
+                       handlePrimary: () => {
+                               if (cleared < total) {
+                                       setState(clearAll(loc.checks));
+                               } else {
+                                       setState(unclearAll(loc.checks));
+                               }
+                       },
+                       handleSecondary: () => {
+                               if (cleared < total) {
+                                       setState(clearAll(loc.checks));
+                               } else {
+                                       setState(unclearAll(loc.checks));
+                               }
+                       },
                };
-       }, [state]);
+       }, [setState, state]);
 
        const lwDungeons = React.useMemo(() => LW_DUNGEONS.map(mapDungeon), [mapDungeon]);
        const lwLocations = React.useMemo(() => LW_LOCATIONS.map(mapLocation), [mapLocation]);
@@ -684,6 +757,10 @@ const Map = () => {
                        width="2"
                        height="1"
                        viewBox="0 0 2 1"
+                       onContextMenu={(e) => {
+                               e.preventDefault();
+                               e.stopPropagation();
+                       }}
                >
                        <g className="light-world">
                                <g className="background">
index 0dd7cc5ed95d6d3fa7443c905c78b6a4cd2c689f..9cf88b497f3d998b9cc6bda37e888e92fa651301 100644 (file)
@@ -15,7 +15,7 @@ const Tracker = () => {
                                <Equipment />
                                <Dungeons />
                        </div>
-                       <div>
+                       <div className="flex-fill">
                                <Map />
                        </div>
                </div>
index f2be2434a3eb69944685b5fd503c765d75089196..902a1bfc9ba9d11f84d744d12936a538fb7ba137 100644 (file)
@@ -1588,6 +1588,16 @@ export const highestActive = (state, names) => {
        return null;
 };
 
+export const clearAll = names => state => {
+       const changes = names.reduce((acc, cur) => ({ ...acc, [cur]: true }), {});
+       return { ...state, ...changes };
+};
+
+export const unclearAll = names => state => {
+       const changes = names.reduce((acc, cur) => ({ ...acc, [cur]: false }), {});
+       return { ...state, ...changes };
+};
+
 export const hasDungeonBoss = (state, dungeon) => !!state[`${dungeon.id}-boss-defeated`];
 
 export const getDungeonBoss = (state, dungeon) =>
index 96da0ae2594f35aa4ab52f0e7886f9b251547fff..d916d339642b6e6ded7967678fdade4fa015051d 100644 (file)
        }
        .tracker-map {
                .canvas {
-                       width: 50em;
+                       width: 100%;
                        height: auto;
                        .location {
                                .box {
                                        width: 0.04px;
                                        height: 0.04px;
+                                       transform: translate(-0.02px, -0.02px);
                                        fill: green;
                                        stroke: black;
-                                       stroke-width: 0.003px;
-                                       transform: translate(-0.02px, -0.02px);
+                                       stroke-width: 1px;
+                                       vector-effect: non-scaling-stroke;
                                }
                                .text {
                                        fill: black;
                                        text-anchor: middle;
                                        dominant-baseline: middle;
                                        pointer-events: none;
+                                       user-select: none;
                                }
                                &.status-cleared {
                                        .box {
                                                font-size: 0.04px;
                                        }
                                }
+                               &.clickable {
+                                       .box:hover {
+                                               stroke: white;
+                                       }
+                               }
                        }
                }
        }