Render plugin data from static server

This commit is contained in:
Tyler Hallada 2022-03-12 14:00:40 -05:00
parent 74c8920edf
commit 6a76bfac33
6 changed files with 57 additions and 41 deletions

View File

@ -2,24 +2,16 @@ import Head from "next/head";
import React from "react"; import React from "react";
import styles from "../styles/PluginData.module.css"; import styles from "../styles/PluginData.module.css";
import { Cell } from "./CellData"; import { formatBytes } from "../lib/plugins";
export interface Plugin { export interface Plugin {
id: number; hash: string;
name: string;
hash: bigint;
file_id: number;
mod_id: number;
version: number;
size: number; size: number;
author?: string; author?: string;
description?: string; description?: string;
masters: string[]; masters: string[];
file_name: string; file_name: string;
file_path: string; cell_count: number;
updated_at: Date;
created_at: Date;
cells: Omit<Cell, "mods">[];
} }
type Props = { type Props = {
@ -39,7 +31,7 @@ const PluginData: React.FC<Props> = ({ plugin, counts }) => {
<meta <meta
key="description" key="description"
name="description" name="description"
content={`Map of Skyrim showing ${plugin.cells.length} cell edits from the plugin: ${plugin.file_name}`} content={`Map of Skyrim showing ${plugin.cell_count} cell edits from the plugin: ${plugin.file_name}`}
/> />
<meta <meta
key="og:title" key="og:title"
@ -49,7 +41,7 @@ const PluginData: React.FC<Props> = ({ plugin, counts }) => {
<meta <meta
key="og:description" key="og:description"
property="og:description" property="og:description"
content={`Map of Skyrim showing ${plugin.cells.length} cell edits from the plugin: ${plugin.file_name}`} content={`Map of Skyrim showing ${plugin.cell_count} cell edits from the plugin: ${plugin.file_name}`}
/> />
<meta <meta
key="twitter:title" key="twitter:title"
@ -59,7 +51,7 @@ const PluginData: React.FC<Props> = ({ plugin, counts }) => {
<meta <meta
key="twitter:description" key="twitter:description"
name="twitter:description" name="twitter:description"
content={`Map of Skyrim showing ${plugin.cells.length} cell edits from the plugin: ${plugin.file_name}`} content={`Map of Skyrim showing ${plugin.cell_count} cell edits from the plugin: ${plugin.file_name}`}
/> />
<meta <meta
key="og:url" key="og:url"
@ -80,9 +72,13 @@ const PluginData: React.FC<Props> = ({ plugin, counts }) => {
{plugin.masters.join(", ")} {plugin.masters.join(", ")}
</div> </div>
)} )}
<div>
<strong>Size:&nbsp;</strong>
{formatBytes(plugin.size)}
</div>
<div> <div>
<strong>Cell edits:&nbsp;</strong> <strong>Cell edits:&nbsp;</strong>
{plugin.cells.length} {plugin.cell_count}
</div> </div>
{plugin.description && ( {plugin.description && (
<div> <div>

View File

@ -2,8 +2,11 @@ import React from "react";
import useSWRImmutable from "swr/immutable"; import useSWRImmutable from "swr/immutable";
import { useAppSelector } from "../lib/hooks"; import { useAppSelector } from "../lib/hooks";
import { PluginFile } from "../slices/plugins";
import { Mod } from "./ModData"; import { Mod } from "./ModData";
import PluginData from "./PluginData"; import { Cell } from "./CellData";
import CellModList from "./CellModList";
import PluginData, { Plugin as PluginProps } from "./PluginData";
import styles from "../styles/PluginData.module.css"; import styles from "../styles/PluginData.module.css";
export interface File { export interface File {
@ -40,13 +43,19 @@ export interface Plugin {
file_path: string; file_path: string;
updated_at: Date; updated_at: Date;
created_at: Date; created_at: Date;
file: File; }
mod: Omit<Mod, "cells">;
export interface PluginsByHashWithMods {
hash: number;
plugins: Plugin[];
files: File[];
mods: Mod[];
cells: Cell[];
} }
const NEXUS_MODS_URL = "https://www.nexusmods.com/skyrimspecialedition"; const NEXUS_MODS_URL = "https://www.nexusmods.com/skyrimspecialedition";
const jsonFetcher = async (url: string): Promise<Plugin | null> => { const jsonFetcher = async (url: string): Promise<PluginsByHashWithMods | null> => {
const res = await fetch(url); const res = await fetch(url);
if (!res.ok) { if (!res.ok) {
@ -59,6 +68,20 @@ const jsonFetcher = async (url: string): Promise<Plugin | null> => {
return res.json(); return res.json();
}; };
const buildPluginProps = (data?: PluginsByHashWithMods | null, plugin?: PluginFile): PluginProps => {
const dataPlugin = data && data.plugins.length > 0 && data.plugins[0];
return {
hash: (plugin && plugin.hash) || (dataPlugin && dataPlugin.hash.toString(36)) || "",
size: plugin?.size || (dataPlugin && dataPlugin.size) || 0,
author: plugin?.parsed?.header.author || (dataPlugin && dataPlugin.author) || undefined,
description: plugin?.parsed?.header.description || (dataPlugin && dataPlugin.description) || undefined,
masters: plugin?.parsed?.header.masters || (dataPlugin && dataPlugin.masters) || [],
file_name: plugin?.filename || (dataPlugin && dataPlugin.file_name) || "",
cell_count: plugin?.parsed?.cells.length || (data && data.cells.length) || 0,
}
}
type Props = { type Props = {
hash: string; hash: string;
counts: Record<number, [number, number, number]> | null; counts: Record<number, [number, number, number]> | null;
@ -86,27 +109,10 @@ const PluginDetail: React.FC<Props> = ({ hash, counts }) => {
return ( return (
<> <>
<PluginData <PluginData
plugin={ plugin={buildPluginProps(data, plugin)}
// TODO: merge into one common plugin object counts={counts}
data || {
id: plugin!.id,
name: plugin!.name,
hash: plugin!.hash,
file_id: plugin!.file_id,
mod_id: plugin!.mod_id,
version: plugin!.version,
size: plugin!.size,
author: plugin!.author,
description: plugin!.description,
masters: plugin!.masters,
file_name: plugin!.filename,
file_path: plugin!.filepath,
updated_at: plugin!.updated_at,
created_at: plugin!.created_at,
cells: plugin!.cells,
}
}
/> />
{data && <CellModList mods={data.mods} counts={counts} />}
</> </>
); );
}; };

View File

@ -42,7 +42,7 @@ export class WorkerPool {
return new Promise((resolve) => { return new Promise((resolve) => {
const worker = new Worker(); const worker = new Worker();
worker.onmessage = (evt: { worker.onmessage = (evt: {
data: string | PluginFile & { timeHashEnd: number }; data: string | PluginFile;
}) => { }) => {
const { data } = evt; const { data } = evt;
if (typeof data === "string" && data === "ready") { if (typeof data === "string" && data === "ready") {

View File

@ -42,4 +42,17 @@ export const parsePluginFiles = (pluginFiles: File[], workerPool: WorkerPool) =>
contents, contents,
}); });
}); });
}
// From: https://stackoverflow.com/a/18650828
export const formatBytes = (bytes: number, decimals = 2): string => {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
} }

View File

@ -38,6 +38,7 @@ export interface PluginFile {
hash: string; hash: string;
parseError?: string; parseError?: string;
enabled: boolean; enabled: boolean;
size: number;
} }
export type PluginsState = { export type PluginsState = {

View File

@ -19,7 +19,7 @@ self.addEventListener("message", async (event: MessageEvent<{ skipParsing?: bool
} }
} }
const hash = hash_plugin(contents).toString(36); const hash = hash_plugin(contents).toString(36);
self.postMessage({ filename, lastModified, parsed, hash, parseError, enabled: parsed && !parseError, timeHashEnd: Date.now() }); self.postMessage({ filename, lastModified, parsed, hash, parseError, enabled: parsed && !parseError, size: contents.length });
} catch (error) { } catch (error) {
console.error(error); console.error(error);
self.postMessage(error); self.postMessage(error);