bigger map, legend floating

This commit is contained in:
2024-10-26 21:24:38 +02:00
parent a43c72fd9e
commit e02c08e19f
11 changed files with 201 additions and 27 deletions
View File
View File
+1 -1
View File
@@ -1,4 +1,4 @@
@import "_fonts.css"; @use "fonts";
$header-font: "Cormorant Garamond", sans-serif; $header-font: "Cormorant Garamond", sans-serif;
$font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; $font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+17
View File
@@ -0,0 +1,17 @@
import React from "react";
import {IconProps} from "./index.ts";
const DiscordIcon: React.FC<IconProps> = ({ color = "#ffffff" }) => (
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke={color}
stroke-width="0.00024000000000000003">
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
<g id="SVGRepo_iconCarrier">
<path
d="M20.317 4.4921C18.7873 3.80147 17.147 3.29265 15.4319 3.00122C15.4007 2.9956 15.3695 3.00965 15.3534 3.03777C15.1424 3.40697 14.9087 3.88862 14.7451 4.26719C12.9004 3.99545 11.0652 3.99545 9.25832 4.26719C9.09465 3.8802 8.85248 3.40697 8.64057 3.03777C8.62449 3.01059 8.59328 2.99654 8.56205 3.00122C6.84791 3.29172 5.20756 3.80054 3.67693 4.4921C3.66368 4.49772 3.65233 4.5071 3.64479 4.51928C0.533392 9.09311 -0.31895 13.5545 0.0991801 17.9606C0.101072 17.9822 0.11337 18.0028 0.130398 18.0159C2.18321 19.4993 4.17171 20.3998 6.12328 20.9967C6.15451 21.0061 6.18761 20.9949 6.20748 20.9695C6.66913 20.3492 7.08064 19.6952 7.43348 19.0073C7.4543 18.967 7.43442 18.9192 7.39186 18.9033C6.73913 18.6597 6.1176 18.3626 5.51973 18.0253C5.47244 17.9981 5.46865 17.9316 5.51216 17.8997C5.63797 17.8069 5.76382 17.7104 5.88396 17.613C5.90569 17.5952 5.93598 17.5914 5.96153 17.6026C9.88928 19.3672 14.1415 19.3672 18.023 17.6026C18.0485 17.5905 18.0788 17.5942 18.1015 17.612C18.2216 17.7095 18.3475 17.8069 18.4742 17.8997C18.5177 17.9316 18.5149 17.9981 18.4676 18.0253C17.8697 18.3692 17.2482 18.6597 16.5945 18.9024C16.552 18.9183 16.533 18.967 16.5538 19.0073C16.9143 19.6942 17.3258 20.3483 17.7789 20.9686C17.7978 20.9949 17.8319 21.0061 17.8631 20.9967C19.8241 20.3998 21.8126 19.4993 23.8654 18.0159C23.8834 18.0028 23.8948 17.9831 23.8967 17.9616C24.3971 12.8676 23.0585 8.4428 20.3482 4.52021C20.3416 4.5071 20.3303 4.49772 20.317 4.4921ZM8.02002 15.2778C6.8375 15.2778 5.86313 14.2095 5.86313 12.8976C5.86313 11.5857 6.8186 10.5175 8.02002 10.5175C9.23087 10.5175 10.1958 11.5951 10.1769 12.8976C10.1769 14.2095 9.22141 15.2778 8.02002 15.2778ZM15.9947 15.2778C14.8123 15.2778 13.8379 14.2095 13.8379 12.8976C13.8379 11.5857 14.7933 10.5175 15.9947 10.5175C17.2056 10.5175 18.1705 11.5951 18.1516 12.8976C18.1516 14.2095 17.2056 15.2778 15.9947 15.2778Z"
fill={color}></path>
</g>
</svg>
);
export default DiscordIcon;
+24
View File
@@ -0,0 +1,24 @@
import React from "react";
import {IconProps} from "./index.ts";
const GitHubIcon: React.FC<IconProps> = ({ color = "#ffffff"} ) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" version="1.1"
fill={color} stroke={color}>
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
<g id="SVGRepo_iconCarrier">
<defs></defs>
<g id="Page-1" stroke-width="0.0002" fill="none" fill-rule="evenodd">
<g id="Dribbble-Light-Preview" transform="translate(-140.000000, -7559.000000)" fill={color}>
<g id="icons" transform="translate(56.000000, 160.000000)">
<path
d="M94,7399 C99.523,7399 104,7403.59 104,7409.253 C104,7413.782 101.138,7417.624 97.167,7418.981 C96.66,7419.082 96.48,7418.762 96.48,7418.489 C96.48,7418.151 96.492,7417.047 96.492,7415.675 C96.492,7414.719 96.172,7414.095 95.813,7413.777 C98.04,7413.523 100.38,7412.656 100.38,7408.718 C100.38,7407.598 99.992,7406.684 99.35,7405.966 C99.454,7405.707 99.797,7404.664 99.252,7403.252 C99.252,7403.252 98.414,7402.977 96.505,7404.303 C95.706,7404.076 94.85,7403.962 94,7403.958 C93.15,7403.962 92.295,7404.076 91.497,7404.303 C89.586,7402.977 88.746,7403.252 88.746,7403.252 C88.203,7404.664 88.546,7405.707 88.649,7405.966 C88.01,7406.684 87.619,7407.598 87.619,7408.718 C87.619,7412.646 89.954,7413.526 92.175,7413.785 C91.889,7414.041 91.63,7414.493 91.54,7415.156 C90.97,7415.418 89.522,7415.871 88.63,7414.304 C88.63,7414.304 88.101,7413.319 87.097,7413.247 C87.097,7413.247 86.122,7413.234 87.029,7413.87 C87.029,7413.87 87.684,7414.185 88.139,7415.37 C88.139,7415.37 88.726,7417.2 91.508,7416.58 C91.513,7417.437 91.522,7418.245 91.522,7418.489 C91.522,7418.76 91.338,7419.077 90.839,7418.982 C86.865,7417.627 84,7413.783 84,7409.253 C84,7403.59 88.478,7399 94,7399"
id="github-[#fefefe]"></path>
</g>
</g>
</g>
</g>
</svg>
);
export default GitHubIcon;
+3
View File
@@ -0,0 +1,3 @@
export interface IconProps {
color?: string;
}
+51 -6
View File
@@ -1,18 +1,31 @@
.legend { .legend {
position: fixed;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 0 15px; padding: 0 25px 25px;
flex-grow: 1; flex-grow: 1;
max-width: 500px;
background-color: rgba(0, 0, 0, 0.8);
border-bottom-right-radius: 12px;
z-index: 1000;
@media (max-width: 1200px) {
position: relative;
max-width: 100%;
margin-top: 20px;
background-color: transparent;
}
h1 { h1 {
font-size: 3rem; font-size: 3rem;
font-weight: 700; font-weight: 800;
width: fit-content; width: fit-content;
margin-bottom: 12px; margin-bottom: 12px;
margin-top: 20px; margin-top: 20px;
@media (max-width: 768px) { @media (max-width: 768px) {
font-size: 1rem; font-size: 2.5rem;
} }
} }
@@ -26,9 +39,9 @@
border-bottom: 1px solid #a8a8a8; border-bottom: 1px solid #a8a8a8;
@media (max-width: 768px) { //@media (max-width: 768px) {
font-size: 1rem; // font-size: 1rem;
} //}
} }
.border { .border {
@@ -45,4 +58,36 @@
padding-left: 7px; padding-left: 7px;
} }
.platform-tooltip-info {
color: white;
z-index: 1;
.key {
background-color: rgba(255, 255, 255, 0.07);
padding: 7px 12px;
border-radius: 7px;
margin: 0 3px;
z-index: 0;
}
}
.links {
display: flex;
align-items: center;
gap: 15px;
a.link {
display: flex;
align-items: center;
gap: 7px;
img, svg {
--hw: 23px;
height: var(--hw);
width: var(--hw);
min-width: var(--hw);
}
}
}
} }
+52 -3
View File
@@ -1,18 +1,31 @@
import React from "react"; import React, {useEffect, useState} from "react";
import "./Legend.scss"; import "./Legend.scss";
import CityIcon from "../icons/CityIcon.tsx"; import CityIcon from "../icons/CityIcon.tsx";
import StarIcon from "../icons/StarIcon.tsx"; import StarIcon from "../icons/StarIcon.tsx";
import LegendCheckbox from "../common/LegendCheckbox.tsx"; import LegendCheckbox from "../common/LegendCheckbox.tsx";
import CapitalIcon from "../icons/CapitalIcon.tsx"; import CapitalIcon from "../icons/CapitalIcon.tsx";
import GitHubIcon from "../icons/GitHubIcon.tsx";
import DiscordIcon from "../icons/DiscordIcon.tsx";
import {Link} from "react-router-dom";
interface LegendProps { interface LegendProps {
hidden?: boolean;
showCapitals: boolean; showCapitals: boolean;
showSettlements: boolean; showSettlements: boolean;
showPOIs: boolean; showPOIs: boolean;
onUpdated: (showCapitals: boolean, showSettlements: boolean, showPOIs: boolean) => void; onUpdated: (showCapitals: boolean, showSettlements: boolean, showPOIs: boolean) => void;
} }
const Legend: React.FC<LegendProps> = ({ showCapitals, showSettlements, showPOIs, onUpdated }) => { const Legend: React.FC<LegendProps> = ({ showCapitals, showSettlements, showPOIs, onUpdated, hidden = false }) => {
const [platformText, setPlatformText] = useState(<></>);
useEffect(() => {
const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0;
const isWin = navigator.platform.toUpperCase().indexOf("WIN") >= 0;
if (isMac) setPlatformText(<p>Hold the <span className={"key"}> Command</span> key to keep the tooltip from moving.</p>);
else if (isWin) setPlatformText(<p>Hold the <span className={"key"}>Ctrl</span> key to keep the tooltip from moving.</p>);
}, []);
const onCheckboxUpdate = (type: "capitals" | "settlements" | "pois", checked: boolean) => { const onCheckboxUpdate = (type: "capitals" | "settlements" | "pois", checked: boolean) => {
onUpdated( onUpdated(
type === "capitals" ? checked : showCapitals, type === "capitals" ? checked : showCapitals,
@@ -21,13 +34,16 @@ const Legend: React.FC<LegendProps> = ({ showCapitals, showSettlements, showPOIs
); );
}; };
if (hidden) return null;
return ( return (
<div className="legend"> <div className="legend">
<h1>Interactive Map of the Magic Continent</h1> <h1>Interactive Map of the Magic Continent</h1>
<div className="border"></div> <div className="border"></div>
<h2>Filters</h2> <p>This is an interactive map of the world of Tensei Shitara Slime Datta Ken (That Time I Got Reincarnated as a Slime)</p>
<h2>Filters</h2>
<div className="form"> <div className="form">
<LegendCheckbox <LegendCheckbox
key="capital-checkbox" key="capital-checkbox"
@@ -53,6 +69,39 @@ const Legend: React.FC<LegendProps> = ({ showCapitals, showSettlements, showPOIs
onUpdated={(checked) => onCheckboxUpdate("pois", checked)} onUpdated={(checked) => onCheckboxUpdate("pois", checked)}
/> />
</div> </div>
{platformText && (
<p className="platform-tooltip-info">
{platformText}
</p>
)}
<div className="links">
<a
className={"link"}
href={"https://github.com/KartoffelChipss/TensuraMap"}
target={"_blank"}
rel={"noreferrer noopener"}
>
<GitHubIcon color={"#d78453"} />
GitHub
</a>
<a
className={"link"}
href={"https://strassburger.org/discord"}
target={"_blank"}
rel={"noreferrer noopener"}
>
<DiscordIcon color={"#d78453"} />
Discord
</a>
<Link
className={"link"}
to={"/impressum"}
>
Legal Notice
</Link>
</div>
</div> </div>
); );
} }
+24 -6
View File
@@ -13,9 +13,21 @@ interface MapProps {
capitals?: MapLocation[]; capitals?: MapLocation[];
cities?: MapLocation[]; cities?: MapLocation[];
pois?: MapLocation[]; pois?: MapLocation[];
onStartDragging?: () => void;
onStopDragging?: () => void;
onZoom?: (scale: number) => void;
} }
const Map: React.FC<MapProps> = ({ imageUrl, regions = [], cities = [], pois = [], capitals = [] }) => { const Map: React.FC<MapProps> = ({
imageUrl,
regions = [],
cities = [],
pois = [],
capitals = [],
onStartDragging = (() => {}),
onStopDragging = (() => {}),
onZoom = (() => {}),
}) => {
const svgRef = useRef<SVGSVGElement | null>(null); const svgRef = useRef<SVGSVGElement | null>(null);
const gRef = useRef<SVGGElement | null>(null); const gRef = useRef<SVGGElement | null>(null);
const tooltipRef = useRef<HTMLDivElement | null>(null); // Ref for the tooltip const tooltipRef = useRef<HTMLDivElement | null>(null); // Ref for the tooltip
@@ -32,7 +44,7 @@ const Map: React.FC<MapProps> = ({ imageUrl, regions = [], cities = [], pois = [
const [translate, setTranslate] = useState({ x: 0, y: 0 }); const [translate, setTranslate] = useState({ x: 0, y: 0 });
const [scale, setScale] = useState(1); const [scale, setScale] = useState(1);
const dragSensitivity = 2; const DRAG_SENSITIVITY = 2;
useEffect(() => { useEffect(() => {
const svgElement = svgRef.current; const svgElement = svgRef.current;
@@ -43,13 +55,14 @@ const Map: React.FC<MapProps> = ({ imageUrl, regions = [], cities = [], pois = [
setIsDragging(true); setIsDragging(true);
setStartX(e.clientX); setStartX(e.clientX);
setStartY(e.clientY); setStartY(e.clientY);
onStartDragging();
}; };
const handleMouseMove = (e: MouseEvent) => { const handleMouseMove = (e: MouseEvent) => {
if (!isDragging || scale <= 1) return; if (!isDragging || scale <= 1) return;
const deltaX = (e.clientX - startX) * dragSensitivity; const deltaX = (e.clientX - startX) * DRAG_SENSITIVITY;
const deltaY = (e.clientY - startY) * dragSensitivity; const deltaY = (e.clientY - startY) * DRAG_SENSITIVITY;
setTranslate(prev => ({ setTranslate(prev => ({
x: prev.x + deltaX, x: prev.x + deltaX,
y: prev.y + deltaY, y: prev.y + deltaY,
@@ -58,7 +71,11 @@ const Map: React.FC<MapProps> = ({ imageUrl, regions = [], cities = [], pois = [
setStartY(e.clientY); setStartY(e.clientY);
}; };
const handleMouseUp = () => setIsDragging(false); const handleMouseUp = () => {
setIsDragging(false);
onStopDragging();
};
const handleWheel = (e: WheelEvent) => { const handleWheel = (e: WheelEvent) => {
e.preventDefault(); e.preventDefault();
const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1; const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1;
@@ -75,6 +92,7 @@ const Map: React.FC<MapProps> = ({ imageUrl, regions = [], cities = [], pois = [
setTranslate({ x: newTranslateX, y: newTranslateY }); setTranslate({ x: newTranslateX, y: newTranslateY });
} }
setScale(newScale); setScale(newScale);
onZoom(newScale);
}; };
svgElement.addEventListener("mousedown", handleMouseDown); svgElement.addEventListener("mousedown", handleMouseDown);
@@ -200,7 +218,7 @@ const Map: React.FC<MapProps> = ({ imageUrl, regions = [], cities = [], pois = [
</svg> </svg>
{tooltip.visible && !isDragging && ( {tooltip.visible && !isDragging && (
<div <div
ref={tooltipRef} // Attach the ref here ref={tooltipRef}
id={"tooltip"} id={"tooltip"}
className={"tooltip"} className={"tooltip"}
style={{ style={{
+21 -9
View File
@@ -1,4 +1,8 @@
@import "variables"; @use "variables";
html {
overflow: hidden;
}
#root { #root {
padding: 0; padding: 0;
@@ -12,7 +16,6 @@
body { body {
height: 100vh; height: 100vh;
overflow: hidden;
padding: 0; padding: 0;
margin: 0; margin: 0;
@@ -23,14 +26,15 @@ body {
background-color: #1a1312; background-color: #1a1312;
color: #d78453; color: white;
font-size: 16px; font-size: 17px;
font-family: $font; font-family: variables.$font;
} }
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
font-family: "Cormorant Garamond", sans-serif; color: #d78453;
font-family: variables.$header-font;
font-weight: 700; font-weight: 700;
} }
@@ -60,12 +64,14 @@ a {
} }
} }
#map-container { #map {
display: flex; display: flex;
position: relative; position: relative;
max-height: 100vh; max-height: 100vh;
max-width: 100%; max-width: 100vw;
flex: 1; height: 100%;
width: 100%;
overflow: hidden;
} }
.region { .region {
@@ -84,7 +90,13 @@ a {
flex-direction: column; flex-direction: column;
} }
#map {
min-width: 100%;
min-height: 50vh;
}
.legend { .legend {
max-width: 100%; max-width: 100%;
position: relative;
} }
} }
+8 -2
View File
@@ -4,12 +4,14 @@ import nations from "../data/nations.ts";
import cities from "../data/cities.ts"; import cities from "../data/cities.ts";
import capitals from "../data/capitals.ts"; import capitals from "../data/capitals.ts";
import pois from "../data/pois.ts"; import pois from "../data/pois.ts";
import {useState} from "react"; import { useState } from "react";
const Main = () => { const Main = () => {
const [showCapitals, setShowCapitals] = useState(true); const [showCapitals, setShowCapitals] = useState(true);
const [showSettlements, setShowSettlements] = useState(true); const [showSettlements, setShowSettlements] = useState(true);
const [showPOIs, setShowPOIs] = useState(true); const [showPOIs, setShowPOIs] = useState(true);
const [isDragging, setIsDragging] = useState(false);
const [isZoomedIn, setIsZoomedIn] = useState(false);
const onUpdated = (showCapitals: boolean, showSettlements: boolean, showPOIs: boolean) => { const onUpdated = (showCapitals: boolean, showSettlements: boolean, showPOIs: boolean) => {
setShowCapitals(showCapitals); setShowCapitals(showCapitals);
@@ -25,8 +27,12 @@ const Main = () => {
capitals={showCapitals ? capitals : []} capitals={showCapitals ? capitals : []}
cities={showSettlements ? cities : []} cities={showSettlements ? cities : []}
pois={showPOIs ? pois : []} pois={showPOIs ? pois : []}
onStartDragging={() => setIsDragging(true)}
onStopDragging={() => setIsDragging(false)}
onZoom={(scale) => setIsZoomedIn(scale > 1)}
/> />
<Legend <Legend
hidden={isDragging || isZoomedIn}
showCapitals={showCapitals} showCapitals={showCapitals}
showSettlements={showSettlements} showSettlements={showSettlements}
showPOIs={showPOIs} showPOIs={showPOIs}
@@ -34,6 +40,6 @@ const Main = () => {
/> />
</> </>
); );
} };
export default Main; export default Main;