WIP Render plugin data from static server

This commit is contained in:
Tyler Hallada 2022-03-12 18:34:45 -05:00
parent 6a76bfac33
commit 1fa2e3e660
3 changed files with 128 additions and 64 deletions

View File

@ -4,8 +4,8 @@ import Gradient from "javascript-color-gradient";
import mapboxgl from "mapbox-gl"; import mapboxgl from "mapbox-gl";
import useSWRImmutable from "swr/immutable"; import useSWRImmutable from "swr/immutable";
import { useAppSelector } from "../lib/hooks"; import { useAppDispatch, useAppSelector } from "../lib/hooks";
import { PluginFile } from "../slices/plugins"; import { PluginFile, setFetchedPlugin } from "../slices/plugins";
import styles from "../styles/Map.module.css"; import styles from "../styles/Map.module.css";
import Sidebar from "./Sidebar"; import Sidebar from "./Sidebar";
import ToggleLayersControl from "./ToggleLayersControl"; import ToggleLayersControl from "./ToggleLayersControl";
@ -59,9 +59,11 @@ const Map: React.FC = () => {
| null | null
>(null); >(null);
const [sidebarOpen, setSidebarOpen] = useState(true); const [sidebarOpen, setSidebarOpen] = useState(true);
const dispatch = useAppDispatch();
const plugins = useAppSelector((state) => state.plugins.plugins); const plugins = useAppSelector((state) => state.plugins.plugins);
const pluginsPending = useAppSelector((state) => state.plugins.pending); const pluginsPending = useAppSelector((state) => state.plugins.pending);
const fetchedPlugin = useAppSelector((state) => state.plugins.fetchedPlugin);
const { data: cellsData, error: cellsError } = useSWRImmutable( const { data: cellsData, error: cellsError } = useSWRImmutable(
"https://cells.modmapper.com/edits.json", "https://cells.modmapper.com/edits.json",
@ -246,6 +248,10 @@ const Map: React.FC = () => {
const clearSelectedCells = useCallback(() => { const clearSelectedCells = useCallback(() => {
setSelectedCells(null); setSelectedCells(null);
if (router.query.plugin !== fetchedPlugin?.hash.toString(36)) {
console.log("clearing fetched plugin");
dispatch(setFetchedPlugin(undefined));
}
if (map.current) { if (map.current) {
map.current.removeFeatureState({ source: "selected-cell-source" }); map.current.removeFeatureState({ source: "selected-cell-source" });
map.current.removeFeatureState({ source: "conflicted-cell-source" }); map.current.removeFeatureState({ source: "conflicted-cell-source" });
@ -253,7 +259,7 @@ const Map: React.FC = () => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
if (map.current) map.current.resize(); if (map.current) map.current.resize();
}); });
}, [map]); }, [map, fetchedPlugin, dispatch, router.query.plugin]);
const clearSelectedMod = useCallback(() => { const clearSelectedMod = useCallback(() => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
@ -305,7 +311,7 @@ const Map: React.FC = () => {
if (plugin && plugin.parsed) { if (plugin && plugin.parsed) {
const cells = []; const cells = [];
const cellSet = new Set<number>(); const cellSet = new Set<number>();
for (const cell of plugin.parsed.cells) { for (const cell of plugin?.parsed?.cells) {
if ( if (
cell.x !== undefined && cell.x !== undefined &&
cell.y !== undefined && cell.y !== undefined &&
@ -361,6 +367,25 @@ const Map: React.FC = () => {
pluginsPending, pluginsPending,
]); ]);
useEffect(() => {
if (router.query.plugin && typeof router.query.plugin === "string" && fetchedPlugin && fetchedPlugin.cells) {
console.log("selecting fetchedPlugin cells");
const cells = [];
const cellSet = new Set<number>();
for (const cell of fetchedPlugin.cells) {
if (
cell.x !== undefined &&
cell.y !== undefined &&
cellSet.has(cell.x + cell.y * 1000) === false
) {
cells.push({ x: cell.x, y: cell.y });
cellSet.add(cell.x + cell.y * 1000);
}
}
selectCells(cells);
}
}, [fetchedPlugin, selectCells, router.query.plugin]);
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") {

View File

@ -1,60 +1,12 @@
import React from "react"; import React, { useEffect } from "react";
import useSWRImmutable from "swr/immutable"; import useSWRImmutable from "swr/immutable";
import { useAppSelector } from "../lib/hooks"; import { useAppDispatch, useAppSelector } from "../lib/hooks";
import { PluginFile } from "../slices/plugins"; import { setFetchedPlugin, PluginFile, PluginsByHashWithMods, Cell } from "../slices/plugins";
import { Mod } from "./ModData";
import { Cell } from "./CellData";
import CellModList from "./CellModList"; import CellModList from "./CellModList";
import PluginData, { Plugin as PluginProps } from "./PluginData"; import PluginData, { Plugin as PluginProps } from "./PluginData";
import styles from "../styles/PluginData.module.css"; import styles from "../styles/PluginData.module.css";
export interface File {
id: number;
name: string;
file_name: string;
nexus_file_id: number;
mod_id: number;
category?: string;
version?: string;
mod_version?: string;
size: number;
uploaded_at?: Date;
has_download_link: boolean;
updated_at: Date;
created_at: Date;
downloaded_at?: Date;
has_plugin: boolean;
unable_to_extract_plugins: boolean;
}
export interface Plugin {
id: number;
name: string;
hash: bigint;
file_id: number;
mod_id: number;
version: number;
size: number;
author?: string;
description?: string;
masters: string[];
file_name: string;
file_path: string;
updated_at: Date;
created_at: Date;
}
export interface PluginsByHashWithMods {
hash: number;
plugins: Plugin[];
files: File[];
mods: Mod[];
cells: Cell[];
}
const NEXUS_MODS_URL = "https://www.nexusmods.com/skyrimspecialedition";
const jsonFetcher = async (url: string): Promise<PluginsByHashWithMods | null> => { const jsonFetcher = async (url: string): Promise<PluginsByHashWithMods | null> => {
const res = await fetch(url); const res = await fetch(url);
@ -81,7 +33,6 @@ const buildPluginProps = (data?: PluginsByHashWithMods | null, plugin?: PluginFi
} }
} }
type Props = { type Props = {
hash: string; hash: string;
counts: Record<number, [number, number, number]> | null; counts: Record<number, [number, number, number]> | null;
@ -93,9 +44,18 @@ const PluginDetail: React.FC<Props> = ({ hash, counts }) => {
jsonFetcher jsonFetcher
); );
const dispatch = useAppDispatch();
const plugins = useAppSelector((state) => state.plugins.plugins); const plugins = useAppSelector((state) => state.plugins.plugins);
const fetchedPlugin = useAppSelector((state) => state.plugins.fetchedPlugin);
const plugin = plugins.find((plugin) => plugin.hash === hash); const plugin = plugins.find((plugin) => plugin.hash === hash);
useEffect(() => {
if (data && !fetchedPlugin) {
console.log("setting fetched plugins from PluginDetail", data);
dispatch(setFetchedPlugin(data));
}
}, [dispatch, data, fetchedPlugin])
if (!plugin && error && error.status === 404) { if (!plugin && error && error.status === 404) {
return <h3>Plugin could not be found.</h3>; return <h3>Plugin could not be found.</h3>;
} else if (!plugin && error) { } else if (!plugin && error) {

View File

@ -1,6 +1,7 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit" import { createSlice, PayloadAction } from "@reduxjs/toolkit"
import type { AppState, AppThunk } from "../lib/store" import type { AppState, AppThunk } from "../lib/store"
import { Mod } from "../components/ModData";
export interface Header { export interface Header {
author?: string; author?: string;
@ -41,8 +42,53 @@ export interface PluginFile {
size: number; size: number;
} }
export interface File {
id: number;
name: string;
file_name: string;
nexus_file_id: number;
mod_id: number;
category?: string;
version?: string;
mod_version?: string;
size: number;
uploaded_at?: Date;
has_download_link: boolean;
updated_at: Date;
created_at: Date;
downloaded_at?: Date;
has_plugin: boolean;
unable_to_extract_plugins: boolean;
}
export interface FetchedPlugin {
id: number;
name: string;
hash: bigint;
file_id: number;
mod_id: number;
version: number;
size: number;
author?: string;
description?: string;
masters: string[];
file_name: string;
file_path: string;
updated_at: Date;
created_at: Date;
}
export interface PluginsByHashWithMods {
hash: number;
plugins: FetchedPlugin[];
files: File[];
mods: Mod[];
cells: Cell[];
}
export type PluginsState = { export type PluginsState = {
plugins: PluginFile[]; plugins: PluginFile[];
fetchedPlugin?: PluginsByHashWithMods;
pending: number; pending: number;
} }
@ -52,17 +98,50 @@ export const pluginsSlice = createSlice({
name: "plugins", name: "plugins",
initialState, initialState,
reducers: { reducers: {
addPlugin: (state, action: PayloadAction<PluginFile>) => ({ plugins: [...state.plugins, action.payload], pending: state.pending }), addPlugin: (state, action: PayloadAction<PluginFile>) => ({
updatePlugin: (state, action: PayloadAction<PluginFile>) => ({ plugins: [...state.plugins.filter(plugin => plugin.hash !== action.payload.hash), action.payload], pending: state.pending }), plugins: [...state.plugins, action.payload],
setPlugins: (state, action: PayloadAction<PluginFile[]>) => ({ plugins: action.payload, pending: state.pending }), pending: state.pending,
setPending: (state, action: PayloadAction<number>) => ({ plugins: state.plugins, pending: action.payload }), fetchedPlugin: state.fetchedPlugin,
decrementPending: (state, action: PayloadAction<number>) => ({ plugins: state.plugins, pending: state.pending - action.payload }), }),
togglePlugin: (state, action: PayloadAction<string>) => ({ plugins: state.plugins.map((plugin) => (plugin.filename === action.payload ? { ...plugin, enabled: !plugin.enabled } : plugin)), pending: state.pending }), updatePlugin: (state, action: PayloadAction<PluginFile>) => ({
clearPlugins: () => ({ plugins: [], pending: 0 }), plugins: [...state.plugins.filter(plugin => plugin.hash !== action.payload.hash), action.payload],
pending: state.pending,
fetchedPlugin: state.fetchedPlugin,
}),
setPlugins: (state, action: PayloadAction<PluginFile[]>) => ({
plugins: action.payload,
pending: state.pending,
fetchedPlugin: state.fetchedPlugin,
}),
setPending: (state, action: PayloadAction<number>) => ({
plugins: state.plugins,
pending: action.payload,
fetchedPlugin: state.fetchedPlugin,
}),
decrementPending: (state, action: PayloadAction<number>) => ({
plugins: state.plugins,
pending: state.pending - action.payload,
fetchedPlugin: state.fetchedPlugin,
}),
togglePlugin: (state, action: PayloadAction<string>) => ({
plugins: state.plugins.map((plugin) => (plugin.filename === action.payload ? { ...plugin, enabled: !plugin.enabled } : plugin)),
pending: state.pending,
fetchedPlugin: state.fetchedPlugin,
}),
setFetchedPlugin: (state, action: PayloadAction<PluginsByHashWithMods | undefined>) => ({
plugins: state.plugins,
pending: state.pending,
fetchedPlugin: action.payload,
}),
clearPlugins: () => ({
plugins: [],
pending: 0,
loadedPluginCells: [],
}),
}, },
}) })
export const { addPlugin, setPlugins, setPending, decrementPending, togglePlugin, clearPlugins } = pluginsSlice.actions export const { addPlugin, setPlugins, setPending, decrementPending, togglePlugin, setFetchedPlugin, clearPlugins } = pluginsSlice.actions
export const selectPlugins = (state: AppState) => state.plugins export const selectPlugins = (state: AppState) => state.plugins