Virtualize remote mods

This commit is contained in:
Ben C 2023-02-07 22:49:50 -05:00
parent 26324a1dbd
commit 827cee3f0b
10 changed files with 98 additions and 43 deletions

12
Cargo.lock generated
View File

@ -38,17 +38,6 @@ version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
[[package]]
name = "async-recursion"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b015a331cc64ebd1774ba119538573603427eaace0a1950c423ab971f903796"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "atk"
version = "0.15.1"
@ -1918,7 +1907,6 @@ name = "owmods_core"
version = "0.1.0"
dependencies = [
"anyhow",
"async-recursion",
"directories",
"futures",
"glob",

View File

@ -17,7 +17,6 @@ directories = "4.0.1"
reqwest = { version = "0.11.14", features = ["blocking"] }
glob = "0.3.1"
version-compare = "0.1.1"
async-recursion = "1.0.2"
anyhow = "1.0.68"
zip = { version = "0.6.3", default-features = false, features = ["deflate", "zstd"] }
tempdir = "0.3.7"

View File

@ -68,8 +68,8 @@ pub fn get_remote_mods(state: tauri::State<'_, State>) -> Vec<String> {
let db = state.remote_db.read().unwrap();
let mut mods: Vec<&RemoteMod> = db.mods.values().collect();
mods.sort_by(|a, b| b.download_count.cmp(&a.download_count));
mods
.into_iter().map(|m| m.unique_name.clone())
mods.into_iter()
.map(|m| m.unique_name.clone())
.filter(|m| m != "Alek.OWML")
.collect::<Vec<String>>()
}

View File

@ -19,3 +19,4 @@ plugins:
- "@typescript-eslint"
rules:
"react/display-name": off
"@typescript-eslint/no-non-null-assertion": off

View File

@ -11,11 +11,11 @@ interface DownloadPayload {
}
const ActiveDownload = (props: ActiveDownloadProps) => {
// Temp state for rn, will use useSyncExternalStore later.
const [progress, setProgress] = useState<DownloadPayload>({
// Temp state for rn, will use tauri later.
const progress = useState<DownloadPayload>({
id: props.id,
message: "Downloading xen.NewHorizons"
});
})[0];
return (
<div className="downloads-row">

View File

@ -2,11 +2,37 @@ import Icon from "@components/Icon";
import ModActionButton from "@components/mods/ModActionButton";
import ModHeader from "@components/mods/ModHeader";
import { useTauri } from "@hooks";
import { memo } from "react";
import { CSSProperties, memo } from "react";
import { FaArrowDown, FaGlobe } from "react-icons/fa";
import { RemoteMod } from "src/types";
const RemoteModRow = memo((props: { uniqueName: string }) => {
// Stolen from mods website, Rai will never catch me!
const magnitudeMap = [
{ value: 1, symbol: "" },
{ value: 1e3, symbol: "k" },
{ value: 1e6, symbol: "M" },
{ value: 1e9, symbol: "G" },
{ value: 1e12, symbol: "T" },
{ value: 1e15, symbol: "P" },
{ value: 1e18, symbol: "E" }
];
const numberFormatRegex = /\.0+$|(\.[0-9]*[1-9])0+$/;
export const formatNumber = (value: number, digits = 1) => {
const magnitude = magnitudeMap
.slice()
.reverse()
.find((item) => {
return value >= item.value;
});
return magnitude
? (value / magnitude.value).toFixed(digits).replace(numberFormatRegex, "$1") +
magnitude.symbol
: "0";
};
const RemoteModRow = memo((props: { uniqueName: string; style?: CSSProperties }) => {
const [status, mod, err] = useTauri<RemoteMod, { uniqueName: string }>(
"REMOTE-REFRESH",
"get_remote_mod",
@ -14,15 +40,17 @@ const RemoteModRow = memo((props: { uniqueName: string }) => {
);
if (status === "Loading") {
return <p>Loading...</p>;
return <div className="mod-row center-loading" aria-busy style={props.style}></div>;
} else if (status === "Error") {
return <p>{err}</p>;
return <p style={props.style}>{err}</p>;
} else {
const remote_mod = mod!;
let desc = remote_mod.description ?? "No Description Provided";
if (desc.trim() === "") desc = "No Description Provided";
return (
<details>
<ModHeader {...remote_mod}>
<small>{remote_mod.downloadCount}</small>
<div style={props.style} className="mod-row">
<ModHeader {...remote_mod} author={remote_mod.authorDisplay ?? remote_mod.author}>
<small>{formatNumber(remote_mod.downloadCount)}</small>
<ModActionButton ariaLabel="Install With Dependencies">
<Icon iconType={FaArrowDown} />
</ModActionButton>
@ -30,8 +58,8 @@ const RemoteModRow = memo((props: { uniqueName: string }) => {
<Icon iconType={FaGlobe} />
</ModActionButton>
</ModHeader>
<small>{remote_mod.description}</small>
</details>
<small className="mod-description">{desc}</small>
</div>
);
}
});

View File

@ -1,23 +1,36 @@
import { useTauri } from "@hooks";
import { memo } from "react";
import AutoSizer from "react-virtualized-auto-sizer";
import { FixedSizeList } from "react-window";
import RemoteModRow from "./RemoteModRow";
const RemoteMods = () => {
const RemoteMods = memo(() => {
const [status, mods, err] = useTauri<string[], undefined>("REMOTE-REFRESH", "get_remote_mods");
if (status === "Loading") {
return <p>Loading...</p>;
return <div className="mod-list center-loading" aria-busy></div>;
} else if (status === "Error") {
return <p>{err}</p>;
} else {
const remote_mods = mods!;
return (
<div className="mod-list">
{remote_mods.map((m) => (
<RemoteModRow key={m} uniqueName={m} />
))}
</div>
<AutoSizer>
{({ width, height }) => (
<FixedSizeList
itemCount={remote_mods.length}
itemSize={120}
width={width}
height={height}
className="mod-list"
>
{({ index, style }) => (
<RemoteModRow style={style} uniqueName={remote_mods[index]} />
)}
</FixedSizeList>
)}
</AutoSizer>
);
}
};
});
export default RemoteMods;

View File

@ -1,4 +1,4 @@
import { FaPlay, FaQuestion, FaArrowDown, FaCog, FaInfoCircle } from "react-icons/fa";
import { FaPlay, FaQuestion, FaCog, FaInfoCircle } from "react-icons/fa";
import { TbRefresh } from "react-icons/tb";
import { RiInstallFill } from "react-icons/ri";
import { RxActivityLog } from "react-icons/rx";
@ -11,8 +11,6 @@ import { useRef } from "react";
import SettingsModal from "@components/modals/SettingsModal";
import InstallFromModal from "@components/modals/InstallFromModal";
import AboutModal from "@components/modals/AboutModal";
import DownloadsBadge from "./DownloadsBadge";
import DownloadsPopout from "./DownloadsPopout";
import Downloads from "../downloads/Downloads";
const Nav = () => {

View File

@ -26,12 +26,9 @@ export const useTauri = <T, P>(
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let u = () => {};
if (status !== "Loading") {
console.debug(`Begin subscribe to ${eventName}`);
subscribeTauri(eventName)(() => setStatus("Loading")).then((unsubscribe) => {
u = unsubscribe;
});
subscribeTauri(eventName)(() => setStatus("Loading"));
} else {
console.debug(`Invoking ${commandName} with args ${commandPayload ?? "null"}`);
getTauriSnapshot(commandName, commandPayload)()
@ -44,7 +41,6 @@ export const useTauri = <T, P>(
setStatus("Error");
});
}
return u;
}, [status]);
return [status, data, error];

View File

@ -2,6 +2,17 @@
margin-top: $margin-lg;
display: grid;
grid-auto-flow: row;
min-width: 0;
width: 100%;
}
$row-border: 1px solid $accent-bg;
.mod-row {
border-bottom: $row-border;
padding: $margin;
width: 100%;
min-width: 0;
}
.mod-header {
@ -41,3 +52,24 @@
justify-self: right;
column-gap: $margin;
}
.mod-actions svg {
cursor: pointer;
}
.mod-authors {
color: $primary-muted;
}
.mod-description {
width: 100%;
display: block;
padding-right: 6.5em;
}
.center-loading {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}