From: Daniel Karbach Date: Mon, 25 Mar 2024 10:24:12 +0000 (+0100) Subject: interactive map X-Git-Url: https://git.localhorst.tv/?a=commitdiff_plain;h=3603709307e0777958cd18134ad4178cef410d14;p=alttp.git interactive map --- diff --git a/resources/js/components/tracker/Map.js b/resources/js/components/tracker/Map.js index 3112631..520ad93 100644 --- a/resources/js/components/tracker/Map.js +++ b/resources/js/components/tracker/Map.js @@ -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 + return { + l.handlePrimary(); + e.preventDefault(); + e.stopPropagation(); + }} + onContextMenu={(e) => { + l.handleSecondary(); + e.preventDefault(); + e.stopPropagation(); + }} + transform={`translate(${l.x} ${l.y})`} + > {t(`tracker.location.${l.id}`)} {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(); + }} > diff --git a/resources/js/components/tracker/index.js b/resources/js/components/tracker/index.js index 0dd7cc5..9cf88b4 100644 --- a/resources/js/components/tracker/index.js +++ b/resources/js/components/tracker/index.js @@ -15,7 +15,7 @@ const Tracker = () => { -
+
diff --git a/resources/js/helpers/tracker.js b/resources/js/helpers/tracker.js index f2be243..902a1bf 100644 --- a/resources/js/helpers/tracker.js +++ b/resources/js/helpers/tracker.js @@ -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) => diff --git a/resources/sass/tracker.scss b/resources/sass/tracker.scss index 96da0ae..d916d33 100644 --- a/resources/sass/tracker.scss +++ b/resources/sass/tracker.scss @@ -120,16 +120,17 @@ } .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; @@ -138,6 +139,7 @@ text-anchor: middle; dominant-baseline: middle; pointer-events: none; + user-select: none; } &.status-cleared { .box { @@ -155,6 +157,11 @@ font-size: 0.04px; } } + &.clickable { + .box:hover { + stroke: white; + } + } } } }