Add layer to show cells a selected cell edits
Also removes the link color to make links clearer.
This commit is contained in:
parent
6005028439
commit
6d27db3122
@ -1,5 +1,6 @@
|
||||
import { format } from "date-fns";
|
||||
import React from "react";
|
||||
import Link from "next/link";
|
||||
|
||||
import styles from "../styles/CellModList.module.css";
|
||||
import type { Mod } from "./CellData";
|
||||
@ -41,18 +42,27 @@ const CellModList: React.FC<Props> = ({ mods, counts }) => {
|
||||
<li key={mod.id} className={styles["mod-list-item"]}>
|
||||
<div className={styles["mod-title"]}>
|
||||
<strong>
|
||||
<Link href={`/?mod=${mod.nexus_mod_id}`}>
|
||||
<a className={styles.link}>{mod.name}</a>
|
||||
</Link>
|
||||
</strong>
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
href={`${NEXUS_MODS_URL}/mods/${mod.nexus_mod_id}`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className={styles.link}
|
||||
>
|
||||
{mod.name}
|
||||
View on Nexus Mods
|
||||
</a>
|
||||
</strong>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Category: </strong>
|
||||
<a
|
||||
href={`${NEXUS_MODS_URL}/mods/categories/${mod.category_id}`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className={styles.link}
|
||||
>
|
||||
{mod.category_name}
|
||||
@ -62,6 +72,8 @@ const CellModList: React.FC<Props> = ({ mods, counts }) => {
|
||||
<strong>Author: </strong>
|
||||
<a
|
||||
href={`${NEXUS_MODS_URL}/users/${mod.author_id}`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className={styles.link}
|
||||
>
|
||||
{mod.author_name}
|
||||
|
@ -45,6 +45,13 @@ const Map: React.FC = () => {
|
||||
x: number;
|
||||
y: number;
|
||||
} | null>(null);
|
||||
const [selectedCells, setSelectedCells] = useState<
|
||||
| {
|
||||
x: number;
|
||||
y: number;
|
||||
}[]
|
||||
| null
|
||||
>(null);
|
||||
const sidebarOpen = selectedCell !== null || router.query.mod !== undefined;
|
||||
|
||||
const { data: cellsData, error: cellsError } = useSWRImmutable(
|
||||
@ -149,6 +156,96 @@ const Map: React.FC = () => {
|
||||
[map]
|
||||
);
|
||||
|
||||
const selectCells = useCallback(
|
||||
(cells: { x: number; y: number }[]) => {
|
||||
if (!map.current) return;
|
||||
if (map.current && !map.current.getSource("grid-source")) return;
|
||||
|
||||
var zoom = map.current.getZoom();
|
||||
var viewportNW = map.current.project([-180, 85.051129]);
|
||||
var cellSize = Math.pow(2, zoom + 2);
|
||||
|
||||
const selectedCellsLines: GeoJSON.FeatureCollection<
|
||||
GeoJSON.Geometry,
|
||||
GeoJSON.GeoJsonProperties
|
||||
> = {
|
||||
type: "FeatureCollection",
|
||||
features: [],
|
||||
};
|
||||
let bounds: mapboxgl.LngLatBounds | null = null;
|
||||
|
||||
for (const cell of cells) {
|
||||
const x = cell.x + 57;
|
||||
const y = 50 - cell.y;
|
||||
let nw = map.current.unproject([
|
||||
x * cellSize + viewportNW.x,
|
||||
y * cellSize + viewportNW.y,
|
||||
]);
|
||||
let ne = map.current.unproject([
|
||||
x * cellSize + viewportNW.x + cellSize,
|
||||
y * cellSize + viewportNW.y,
|
||||
]);
|
||||
let se = map.current.unproject([
|
||||
x * cellSize + viewportNW.x + cellSize,
|
||||
y * cellSize + viewportNW.y + cellSize,
|
||||
]);
|
||||
let sw = map.current.unproject([
|
||||
x * cellSize + viewportNW.x,
|
||||
y * cellSize + viewportNW.y + cellSize,
|
||||
]);
|
||||
if (bounds) {
|
||||
bounds.extend(new mapboxgl.LngLatBounds(sw, ne));
|
||||
} else {
|
||||
bounds = new mapboxgl.LngLatBounds(sw, ne);
|
||||
}
|
||||
selectedCellsLines.features.push({
|
||||
type: "Feature",
|
||||
geometry: {
|
||||
type: "LineString",
|
||||
coordinates: [
|
||||
[nw.lng, nw.lat],
|
||||
[ne.lng, ne.lat],
|
||||
[se.lng, se.lat],
|
||||
[sw.lng, sw.lat],
|
||||
[nw.lng, nw.lat],
|
||||
],
|
||||
},
|
||||
properties: { x: x, y: y },
|
||||
});
|
||||
}
|
||||
|
||||
if (map.current.getLayer("selected-cells-layer")) {
|
||||
map.current.removeLayer("selected-cells-layer");
|
||||
}
|
||||
if (map.current.getSource("selected-cells-source")) {
|
||||
map.current.removeSource("selected-cells-source");
|
||||
}
|
||||
map.current.addSource("selected-cells-source", {
|
||||
type: "geojson",
|
||||
data: selectedCellsLines,
|
||||
});
|
||||
map.current.addLayer({
|
||||
id: "selected-cells-layer",
|
||||
type: "line",
|
||||
source: "selected-cells-source",
|
||||
paint: {
|
||||
"line-color": "purple",
|
||||
"line-width": 4,
|
||||
},
|
||||
});
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (map.current) {
|
||||
map.current.resize();
|
||||
if (bounds) {
|
||||
map.current.fitBounds(bounds, { padding: 20 });
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
[map]
|
||||
);
|
||||
|
||||
const selectCell = useCallback(
|
||||
(cell) => {
|
||||
router.push({ query: { cell: cell.x + "," + cell.y } });
|
||||
@ -172,6 +269,19 @@ const Map: React.FC = () => {
|
||||
});
|
||||
}, [map]);
|
||||
|
||||
const clearSelectedCells = useCallback(() => {
|
||||
setSelectedCells(null);
|
||||
if (map.current && map.current.getLayer("selected-cells-layer")) {
|
||||
map.current.removeLayer("selected-cells-layer");
|
||||
}
|
||||
if (map.current && map.current.getSource("selected-cells-source")) {
|
||||
map.current.removeSource("selected-cells-source");
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
if (map.current) map.current.resize();
|
||||
});
|
||||
}, [map]);
|
||||
|
||||
const clearSelectedMod = useCallback(() => {
|
||||
requestAnimationFrame(() => {
|
||||
if (map.current) map.current.resize();
|
||||
@ -207,6 +317,27 @@ const Map: React.FC = () => {
|
||||
heatmapLoaded,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!heatmapLoaded) return; // wait for all map layers to load
|
||||
if (
|
||||
router.query.mod &&
|
||||
typeof router.query.mod === "string" &&
|
||||
selectedCells
|
||||
) {
|
||||
selectCells(selectedCells);
|
||||
} else {
|
||||
if (selectedCells) {
|
||||
clearSelectedCells();
|
||||
}
|
||||
}
|
||||
}, [
|
||||
selectedCells,
|
||||
router.query.mod,
|
||||
selectCells,
|
||||
clearSelectedCells,
|
||||
heatmapLoaded,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!heatmapLoaded) return; // wait for all map layers to load
|
||||
if (!router.query.mod || typeof router.query.mod !== "string") {
|
||||
@ -500,6 +631,7 @@ const Map: React.FC = () => {
|
||||
<Sidebar
|
||||
selectedCell={selectedCell}
|
||||
clearSelectedCell={() => router.push({ query: {} })}
|
||||
setSelectedCells={setSelectedCells}
|
||||
map={map}
|
||||
counts={counts}
|
||||
countsError={countsError}
|
||||
|
@ -47,9 +47,14 @@ const jsonFetcher = async (url: string): Promise<Mod | null> => {
|
||||
type Props = {
|
||||
selectedMod: number;
|
||||
counts: Record<number, [number, number, number]> | null;
|
||||
setSelectedCells: (cells: { x: number; y: number }[] | null) => void;
|
||||
};
|
||||
|
||||
const ModData: React.FC<Props> = ({ selectedMod, counts }) => {
|
||||
const ModData: React.FC<Props> = ({
|
||||
selectedMod,
|
||||
counts,
|
||||
setSelectedCells,
|
||||
}) => {
|
||||
const { data, error } = useSWRImmutable(
|
||||
`https://mods.modmapper.com/${selectedMod}.json`,
|
||||
jsonFetcher
|
||||
@ -71,12 +76,16 @@ const ModData: React.FC<Props> = ({ selectedMod, counts }) => {
|
||||
const unique_downloads = modCounts ? modCounts[1] : 0;
|
||||
const views = modCounts ? modCounts[2] : 0;
|
||||
|
||||
setSelectedCells(data.cells);
|
||||
|
||||
if (selectedMod && data) {
|
||||
return (
|
||||
<>
|
||||
<h1>
|
||||
<a
|
||||
href={`${NEXUS_MODS_URL}/mods/${data.nexus_mod_id}`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className={`${styles.link} ${styles.name}`}
|
||||
>
|
||||
{data.name}
|
||||
@ -86,6 +95,8 @@ const ModData: React.FC<Props> = ({ selectedMod, counts }) => {
|
||||
<strong>Category: </strong>
|
||||
<a
|
||||
href={`${NEXUS_MODS_URL}/mods/categories/${data.category_id}`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className={styles.link}
|
||||
>
|
||||
{data.category_name}
|
||||
@ -95,6 +106,8 @@ const ModData: React.FC<Props> = ({ selectedMod, counts }) => {
|
||||
<strong>Author: </strong>
|
||||
<a
|
||||
href={`${NEXUS_MODS_URL}/users/${data.author_id}`}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className={styles.link}
|
||||
>
|
||||
{data.author_name}
|
||||
|
@ -16,6 +16,7 @@ interface Cell {
|
||||
type Props = {
|
||||
selectedCell: { x: number; y: number } | null;
|
||||
clearSelectedCell: () => void;
|
||||
setSelectedCells: (cells: { x: number; y: number }[] | null) => void;
|
||||
map: React.MutableRefObject<mapboxgl.Map | null>;
|
||||
counts: Record<number, [number, number, number]> | null;
|
||||
countsError: Error | null;
|
||||
@ -24,6 +25,7 @@ type Props = {
|
||||
const Sidebar: React.FC<Props> = ({
|
||||
selectedCell,
|
||||
clearSelectedCell,
|
||||
setSelectedCells,
|
||||
counts,
|
||||
countsError,
|
||||
map,
|
||||
@ -47,7 +49,13 @@ const Sidebar: React.FC<Props> = ({
|
||||
if (countsError) return renderLoadError(countsError);
|
||||
if (!counts) return renderLoading();
|
||||
|
||||
return <ModData selectedMod={selectedMod} counts={counts} />;
|
||||
return (
|
||||
<ModData
|
||||
selectedMod={selectedMod}
|
||||
counts={counts}
|
||||
setSelectedCells={setSelectedCells}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
|
@ -10,7 +10,3 @@
|
||||
.mod-title {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
a.link {
|
||||
color: #72030a;
|
||||
}
|
||||
|
@ -10,7 +10,3 @@
|
||||
.cell-title {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
a.link {
|
||||
color: #72030a;
|
||||
}
|
||||
|
@ -2,10 +2,6 @@
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
a.link {
|
||||
color: #72030a;
|
||||
}
|
||||
|
||||
a.name {
|
||||
line-height: 1.75rem;
|
||||
}
|
||||
|
@ -9,11 +9,6 @@ div#__next {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user