kemono2/client/src/pages/importer_status.tsx
2024-11-26 00:11:49 +01:00

168 lines
4.5 KiB
TypeScript

import clsx from "clsx";
import { useState } from "react";
import {
LoaderFunctionArgs,
useLoaderData,
useNavigate,
} from "react-router-dom";
import { createImporterStatusPageURL } from "#lib/urls";
import { fetchHasPendingDMs } from "#api/dms";
import { fetchImportLogs } from "#api/imports";
import { getLocalStorageItem, setLocalStorageItem } from "#storage/local";
import { useInterval } from "#hooks";
import { PageSkeleton } from "#components/pages";
import { LoadingIcon } from "#components/loading";
import { Button } from "#components/buttons";
interface IProps {
importID: string;
isDMS?: boolean;
logs: string[];
}
export function ImporterStatusPage() {
const { importID, isDMS, logs } = useLoaderData() as IProps;
const navigate = useNavigate();
const [isReversed, switchReversed] = useState(false);
const title = `Import ${importID}`;
const heading = `Importer logs for ${importID}`;
const status = logs.length === 0 ? "Fetching" : "In Progress";
const cooldown = 120_000;
useInterval(() => {
navigate(String(createImporterStatusPageURL(importID)));
}, cooldown);
return (
<PageSkeleton name="importer-status" title={title} heading={heading}>
{isDMS && (
<div className="jumbo no-posts">
<strong>Hey!</strong>
<p>
You gave the importer permission to access your messages. To protect
your anonymity, you must manually approve each one. Wait until{" "}
<i>after</i> the importer says <code>Done importing DMs</code>, then
go <a href="/account/review_dms">here</a> to choose which ones you
wish to import.
</p>
</div>
)}
<div className="import">
<div className="import__info">
<div className="import__stats">
<div className="import__status">
<span>Status: </span>
<span>{status}</span>
</div>
<div
className={clsx(
"import__count",
logs.length === 0 && "import__count--invisible"
)}
>
<span>Total: </span>
<span>{logs.length}</span>
</div>
</div>
<div className="import__buttons">
<Button
className="import__reverse"
onClick={() => switchReversed((old) => !old)}
>
Reverse order
</Button>
</div>
</div>
<p
className={clsx(
"loading-placeholder",
logs.length !== 0 && "loading-placeholder--complete"
)}
>
<LoadingIcon /> <span>Wait until logs load...</span>
</p>
<ol
id="log-list"
className={clsx(
"log-list",
logs.length !== 0 && "log-list--loaded",
isReversed && "log-list--reversed"
)}
>
{logs.length !== 0 &&
logs.map((message, index) => (
<li key={`${index}-${message}`} className="log-list__item">
{message}
</li>
))}
</ol>
</div>
</PageSkeleton>
);
}
async function initPendingReviewDms(
forceReload = false,
minutesForRecheck = 30
) {
let hasPendingReviewDms =
getLocalStorageItem("has_pending_review_dms") === "true";
const lastCheckedHasPendingReviewDms = parseInt(
getLocalStorageItem("last_checked_has_pending_review_dms") ?? "0",
10
);
if (
forceReload ||
!lastCheckedHasPendingReviewDms ||
lastCheckedHasPendingReviewDms < Date.now() - minutesForRecheck * 60 * 1000
) {
/**
* @type {string}
*/
hasPendingReviewDms = await fetchHasPendingDMs();
setLocalStorageItem("has_pending_review_dms", String(hasPendingReviewDms));
localStorage.setItem(
"last_checked_has_pending_review_dms",
Date.now().toString()
);
}
}
export async function loader({
params,
request,
}: LoaderFunctionArgs): Promise<IProps> {
const searchparams = new URL(request.url).searchParams;
const importID = params.import_id?.trim();
if (!importID) {
throw new Error("Import ID is required.");
}
let isDMS: boolean | undefined = undefined;
{
const inputValue = Boolean(searchparams.get("dms")?.trim());
if (inputValue) {
isDMS = inputValue;
}
}
const logs = await fetchImportLogs(importID);
if (logs.length !== 0) {
initPendingReviewDms(true);
}
return {
importID,
isDMS,
logs,
};
}