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 { format } from "date-fns";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
import styles from "../styles/CellModList.module.css";
|
import styles from "../styles/CellModList.module.css";
|
||||||
import type { Mod } from "./CellData";
|
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"]}>
|
<li key={mod.id} className={styles["mod-list-item"]}>
|
||||||
<div className={styles["mod-title"]}>
|
<div className={styles["mod-title"]}>
|
||||||
<strong>
|
<strong>
|
||||||
<a
|
<Link href={`/?mod=${mod.nexus_mod_id}`}>
|
||||||
href={`${NEXUS_MODS_URL}/mods/${mod.nexus_mod_id}`}
|
<a className={styles.link}>{mod.name}</a>
|
||||||
className={styles.link}
|
</Link>
|
||||||
>
|
|
||||||
{mod.name}
|
|
||||||
</a>
|
|
||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
href={`${NEXUS_MODS_URL}/mods/${mod.nexus_mod_id}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
className={styles.link}
|
||||||
|
>
|
||||||
|
View on Nexus Mods
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<strong>Category: </strong>
|
<strong>Category: </strong>
|
||||||
<a
|
<a
|
||||||
href={`${NEXUS_MODS_URL}/mods/categories/${mod.category_id}`}
|
href={`${NEXUS_MODS_URL}/mods/categories/${mod.category_id}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
className={styles.link}
|
className={styles.link}
|
||||||
>
|
>
|
||||||
{mod.category_name}
|
{mod.category_name}
|
||||||
@ -62,6 +72,8 @@ const CellModList: React.FC<Props> = ({ mods, counts }) => {
|
|||||||
<strong>Author: </strong>
|
<strong>Author: </strong>
|
||||||
<a
|
<a
|
||||||
href={`${NEXUS_MODS_URL}/users/${mod.author_id}`}
|
href={`${NEXUS_MODS_URL}/users/${mod.author_id}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
className={styles.link}
|
className={styles.link}
|
||||||
>
|
>
|
||||||
{mod.author_name}
|
{mod.author_name}
|
||||||
|
@ -45,6 +45,13 @@ const Map: React.FC = () => {
|
|||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
const [selectedCells, setSelectedCells] = useState<
|
||||||
|
| {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}[]
|
||||||
|
| null
|
||||||
|
>(null);
|
||||||
const sidebarOpen = selectedCell !== null || router.query.mod !== undefined;
|
const sidebarOpen = selectedCell !== null || router.query.mod !== undefined;
|
||||||
|
|
||||||
const { data: cellsData, error: cellsError } = useSWRImmutable(
|
const { data: cellsData, error: cellsError } = useSWRImmutable(
|
||||||
@ -149,6 +156,96 @@ const Map: React.FC = () => {
|
|||||||
[map]
|
[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(
|
const selectCell = useCallback(
|
||||||
(cell) => {
|
(cell) => {
|
||||||
router.push({ query: { cell: cell.x + "," + cell.y } });
|
router.push({ query: { cell: cell.x + "," + cell.y } });
|
||||||
@ -172,6 +269,19 @@ const Map: React.FC = () => {
|
|||||||
});
|
});
|
||||||
}, [map]);
|
}, [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(() => {
|
const clearSelectedMod = useCallback(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (map.current) map.current.resize();
|
if (map.current) map.current.resize();
|
||||||
@ -207,6 +317,27 @@ const Map: React.FC = () => {
|
|||||||
heatmapLoaded,
|
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(() => {
|
useEffect(() => {
|
||||||
if (!heatmapLoaded) return; // wait for all map layers to load
|
if (!heatmapLoaded) return; // wait for all map layers to load
|
||||||
if (!router.query.mod || typeof router.query.mod !== "string") {
|
if (!router.query.mod || typeof router.query.mod !== "string") {
|
||||||
@ -500,6 +631,7 @@ const Map: React.FC = () => {
|
|||||||
<Sidebar
|
<Sidebar
|
||||||
selectedCell={selectedCell}
|
selectedCell={selectedCell}
|
||||||
clearSelectedCell={() => router.push({ query: {} })}
|
clearSelectedCell={() => router.push({ query: {} })}
|
||||||
|
setSelectedCells={setSelectedCells}
|
||||||
map={map}
|
map={map}
|
||||||
counts={counts}
|
counts={counts}
|
||||||
countsError={countsError}
|
countsError={countsError}
|
||||||
|
@ -47,9 +47,14 @@ const jsonFetcher = async (url: string): Promise<Mod | null> => {
|
|||||||
type Props = {
|
type Props = {
|
||||||
selectedMod: number;
|
selectedMod: number;
|
||||||
counts: Record<number, [number, number, number]> | null;
|
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(
|
const { data, error } = useSWRImmutable(
|
||||||
`https://mods.modmapper.com/${selectedMod}.json`,
|
`https://mods.modmapper.com/${selectedMod}.json`,
|
||||||
jsonFetcher
|
jsonFetcher
|
||||||
@ -71,12 +76,16 @@ const ModData: React.FC<Props> = ({ selectedMod, counts }) => {
|
|||||||
const unique_downloads = modCounts ? modCounts[1] : 0;
|
const unique_downloads = modCounts ? modCounts[1] : 0;
|
||||||
const views = modCounts ? modCounts[2] : 0;
|
const views = modCounts ? modCounts[2] : 0;
|
||||||
|
|
||||||
|
setSelectedCells(data.cells);
|
||||||
|
|
||||||
if (selectedMod && data) {
|
if (selectedMod && data) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1>
|
<h1>
|
||||||
<a
|
<a
|
||||||
href={`${NEXUS_MODS_URL}/mods/${data.nexus_mod_id}`}
|
href={`${NEXUS_MODS_URL}/mods/${data.nexus_mod_id}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
className={`${styles.link} ${styles.name}`}
|
className={`${styles.link} ${styles.name}`}
|
||||||
>
|
>
|
||||||
{data.name}
|
{data.name}
|
||||||
@ -86,6 +95,8 @@ const ModData: React.FC<Props> = ({ selectedMod, counts }) => {
|
|||||||
<strong>Category: </strong>
|
<strong>Category: </strong>
|
||||||
<a
|
<a
|
||||||
href={`${NEXUS_MODS_URL}/mods/categories/${data.category_id}`}
|
href={`${NEXUS_MODS_URL}/mods/categories/${data.category_id}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
className={styles.link}
|
className={styles.link}
|
||||||
>
|
>
|
||||||
{data.category_name}
|
{data.category_name}
|
||||||
@ -95,6 +106,8 @@ const ModData: React.FC<Props> = ({ selectedMod, counts }) => {
|
|||||||
<strong>Author: </strong>
|
<strong>Author: </strong>
|
||||||
<a
|
<a
|
||||||
href={`${NEXUS_MODS_URL}/users/${data.author_id}`}
|
href={`${NEXUS_MODS_URL}/users/${data.author_id}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
className={styles.link}
|
className={styles.link}
|
||||||
>
|
>
|
||||||
{data.author_name}
|
{data.author_name}
|
||||||
|
@ -16,6 +16,7 @@ interface Cell {
|
|||||||
type Props = {
|
type Props = {
|
||||||
selectedCell: { x: number; y: number } | null;
|
selectedCell: { x: number; y: number } | null;
|
||||||
clearSelectedCell: () => void;
|
clearSelectedCell: () => void;
|
||||||
|
setSelectedCells: (cells: { x: number; y: number }[] | null) => void;
|
||||||
map: React.MutableRefObject<mapboxgl.Map | null>;
|
map: React.MutableRefObject<mapboxgl.Map | null>;
|
||||||
counts: Record<number, [number, number, number]> | null;
|
counts: Record<number, [number, number, number]> | null;
|
||||||
countsError: Error | null;
|
countsError: Error | null;
|
||||||
@ -24,6 +25,7 @@ type Props = {
|
|||||||
const Sidebar: React.FC<Props> = ({
|
const Sidebar: React.FC<Props> = ({
|
||||||
selectedCell,
|
selectedCell,
|
||||||
clearSelectedCell,
|
clearSelectedCell,
|
||||||
|
setSelectedCells,
|
||||||
counts,
|
counts,
|
||||||
countsError,
|
countsError,
|
||||||
map,
|
map,
|
||||||
@ -47,7 +49,13 @@ const Sidebar: React.FC<Props> = ({
|
|||||||
if (countsError) return renderLoadError(countsError);
|
if (countsError) return renderLoadError(countsError);
|
||||||
if (!counts) return renderLoading();
|
if (!counts) return renderLoading();
|
||||||
|
|
||||||
return <ModData selectedMod={selectedMod} counts={counts} />;
|
return (
|
||||||
|
<ModData
|
||||||
|
selectedMod={selectedMod}
|
||||||
|
counts={counts}
|
||||||
|
setSelectedCells={setSelectedCells}
|
||||||
|
/>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
|
@ -10,7 +10,3 @@
|
|||||||
.mod-title {
|
.mod-title {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.link {
|
|
||||||
color: #72030a;
|
|
||||||
}
|
|
||||||
|
@ -10,7 +10,3 @@
|
|||||||
.cell-title {
|
.cell-title {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.link {
|
|
||||||
color: #72030a;
|
|
||||||
}
|
|
||||||
|
@ -2,10 +2,6 @@
|
|||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.link {
|
|
||||||
color: #72030a;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.name {
|
a.name {
|
||||||
line-height: 1.75rem;
|
line-height: 1.75rem;
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,6 @@ div#__next {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user