WIP Render plugin data from static server
This commit is contained in:
parent
6a76bfac33
commit
1fa2e3e660
@ -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") {
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user