Dark mode refactor (#98)

* 🏗️ Acquisition Panel refactor WIP

* 🔧 Formatted the search query box

* 🔧 Implementing download method

* 🏗️ Refactored the AirDC++ download panel

* 🌜 Initial Dark Mode support

* 🌜 Trying dark mode on the react-select

* Update App.scss

* 🏗️ Migrating Navbar to TailwindCSS

* 🖼️ Added solar icons

* 🔧 Added solar icons

* 🔧 Added code for dark mode toggle

* 🏗️ Wiring up the dark mode toggle

* 🌜 Added Dark mode to the body

* 🏗️ Building out the import page

* 🪑 Cleaning up the table styles

* 🏗️ Cleaned up past imports table

* 🏗️ Refactored Import socket events

* 🏗️ Refactored the card grid on dashboard

* 🏗️ Building variants for Cards

* 🏗️ Added a horizontal medium variant

* 🏗️ Cleaning up forms and cards

* 🔧 Styling form inputs

* 🏗️ Form refactor

* 🔠 Added a monospace font

* 🪑 Refactoring the table

* 🧹 Formatting in connection confirmation panels

* 🏗️ Refactoring table for library

* 🏗️ Added icons and details to metadata

* 🏗️ Cleaned the table further

* 🏗️ Fixed fonts, and comic detail page first draft

*  Removing yarn.lockfile
This commit was merged in pull request #98.
This commit is contained in:
2023-12-21 16:23:29 -05:00
committed by GitHub
parent 68442894d0
commit 4f49e538a8
35 changed files with 1405 additions and 12089 deletions

View File

@@ -53,25 +53,6 @@ export const Import = (props: IProps): ReactElement => {
}),
});
// 1a. Act on each comic issue successfully imported/failed, as indicated
// by the LS_COVER_EXTRACTED/LS_COVER_EXTRACTION_FAILED events
socketIOInstance.on("LS_COVER_EXTRACTED", (data) => {
const { completedJobCount, importResult } = data;
importJobQueue.setJobCount("successful", completedJobCount);
importJobQueue.setMostRecentImport(importResult.rawFileDetails.name);
});
socketIOInstance.on("LS_COVER_EXTRACTION_FAILED", (data) => {
const { failedJobCount } = data;
importJobQueue.setJobCount("failed", failedJobCount);
});
// 1b. Clear the localStorage sessionId upon receiving the
// LS_IMPORT_QUEUE_DRAINED event
socketIOInstance.on("LS_IMPORT_QUEUE_DRAINED", (data) => {
localStorage.removeItem("sessionId");
importJobQueue.setStatus("drained");
queryClient.invalidateQueries({ queryKey: ["allImportJobResults"] });
});
const toggleQueue = (queueAction: string, queueStatus: string) => {
socketIOInstance.emit(
"call",
@@ -94,29 +75,35 @@ export const Import = (props: IProps): ReactElement => {
switch (status) {
case "running":
return (
<div className="control">
<div>
<button
className="button is-warning is-light"
className="flex space-x-1 sm:mt-0 sm:flex-row sm:items-center rounded-lg border border-green-400 dark:border-green-200 bg-green-200 px-3 py-1 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
onClick={() => {
toggleQueue("pause", "paused");
importJobQueue.setStatus("paused");
}}
>
<i className="fa-solid fa-pause mr-2"></i> Pause
<span className="text-md">Pause</span>
<span className="w-5 h-5">
<i className="h-5 w-5 icon-[solar--pause-bold]"></i>
</span>
</button>
</div>
);
case "paused":
return (
<div className="control">
<div>
<button
className="button is-success is-light"
className="flex space-x-1 sm:mt-0 sm:flex-row sm:items-center rounded-lg border border-green-400 dark:border-green-200 bg-green-200 px-3 py-1 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
onClick={() => {
toggleQueue("resume", "running");
importJobQueue.setStatus("running");
}}
>
<i className="fa-solid fa-play mr-2"></i> Resume
<span className="text-md">Resume</span>
<span className="w-5 h-5">
<i className="h-5 w-5 icon-[solar--play-bold]"></i>
</span>
</button>
</div>
);
@@ -129,145 +116,180 @@ export const Import = (props: IProps): ReactElement => {
}
};
return (
<div className="container">
<section className="section is-small">
<h1 className="title">Import Comics</h1>
<article className="message is-dark">
<div className="message-body">
<p className="mb-2">
<span className="tag is-medium is-info is-light">
Import Comics
</span>
will add comics identified from the mapped folder into ThreeTwo's
database.
</p>
<p>
Metadata from ComicInfo.xml, if present, will also be extracted.
</p>
<p>
This process could take a while, if you have a lot of comics, or
are importing over a network connection.
</p>
<div>
<section>
<header className="bg-slate-200 dark:bg-slate-500">
<div className="mx-auto max-w-screen-xl px-2 py-2 sm:px-6 sm:py-8 lg:px-8 lg:py-4">
<div className="sm:flex sm:items-center sm:justify-between">
<div className="text-center sm:text-left">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white sm:text-3xl">
Import
</h1>
<p className="mt-1.5 text-sm text-gray-500 dark:text-white">
Import comics into the ThreeTwo library.
</p>
</div>
</div>
</div>
</article>
<p className="buttons">
<button
className={
importJobQueue.status === "drained" ||
importJobQueue.status === undefined
? "button is-medium"
: "button is-loading is-medium"
}
onClick={() => {
initiateImport();
importJobQueue.setStatus("running");
}}
</header>
<div className="mx-auto max-w-screen-xl px-4 py-4 sm:px-6 sm:py-8 lg:px-8">
<article
role="alert"
className="rounded-lg max-w-screen-md border-s-4 border-blue-500 bg-blue-50 p-4 dark:border-s-4 dark:border-blue-600 dark:bg-blue-300 dark:text-slate-600"
>
<span className="icon">
<i className="fas fa-file-import"></i>
</span>
<span>Start Import</span>
</button>
</p>
<div>
<p>
Importing will add comics identified from the mapped folder into
ThreeTwo's database.
</p>
<p>
Metadata from ComicInfo.xml, if present, will also be extracted.
</p>
<p>
This process could take a while, if you have a lot of comics, or
are importing over a network connection.
</p>
</div>
</article>
{importJobQueue.status !== "drained" &&
!isUndefined(importJobQueue.status) && (
<div className="my-4">
{importJobQueue.status === "drained" ||
(importJobQueue.status === undefined && (
<button
className="flex space-x-1 sm:mt-0 sm:flex-row sm:items-center rounded-lg border border-green-400 dark:border-green-200 bg-green-200 px-5 py-3 text-gray-500 hover:bg-transparent hover:text-green-600 focus:outline-none focus:ring active:text-indigo-500"
onClick={() => {
initiateImport();
importJobQueue.setStatus("running");
}}
>
<span className="text-md">Start Import</span>
<span className="w-6 h-6">
<i className="h-6 w-6 icon-[solar--file-left-bold-duotone]"></i>
</span>
</button>
))}
</div>
{/* Activity */}
{(importJobQueue.status === "running" ||
importJobQueue.status === "paused") && (
<>
<table className="table">
<thead>
<tr>
<th>Completed Jobs</th>
<th>Failed Jobs</th>
<th>Queue Controls</th>
<th>Queue Status</th>
</tr>
</thead>
<tbody>
<tr>
<th>
{importJobQueue.successfulJobCount > 0 && (
<div className="box has-background-success-light has-text-centered">
<span className="is-size-2 has-text-weight-bold">
{importJobQueue.successfulJobCount}
</span>
</div>
)}
</th>
<td>
{importJobQueue.failedJobCount > 0 && (
<div className="box has-background-danger has-text-centered">
<span className="is-size-2 has-text-weight-bold">
{importJobQueue.failedJobCount}
</span>
</div>
)}
</td>
<td>{renderQueueControls(importJobQueue.status)}</td>
<td>
{importJobQueue.status !== undefined ? (
<span className="tag is-warning">
{importJobQueue.status}
</span>
) : null}
</td>
</tr>
</tbody>
</table>
Imported{" "}
<span className="has-text-weight-bold">
{importJobQueue.mostRecentImport}
<span className="flex items-center my-5 max-w-screen-lg">
<span className="text-xl text-slate-500 dark:text-slate-200 pr-5">
Import Activity
</span>
<span className="h-px flex-1 bg-slate-200 dark:bg-slate-400"></span>
</span>
<div className="mt-5 flex flex-col gap-4 sm:mt-0 sm:flex-row sm:items-center">
<dl className="grid grid-cols-2 gap-4 sm:grid-cols-2">
{/* Successful import counts */}
<div className="flex flex-col rounded-lg bg-green-100 dark:bg-green-200 px-4 py-6 text-center">
<dd className="text-3xl text-green-600 md:text-5xl">
{importJobQueue.successfulJobCount}
</dd>
<dt className="text-lg font-medium text-gray-500">
imported
</dt>
</div>
{/* Failed job counts */}
<div className="flex flex-col rounded-lg bg-red-100 dark:bg-red-200 px-4 py-6 text-center">
<dd className="text-3xl text-red-600 md:text-5xl">
{importJobQueue.failedJobCount}
</dd>
<dt className="text-lg font-medium text-gray-500">
failed
</dt>
</div>
<div className="flex flex-col dark:text-slate-200 text-slate-400">
<dd>{renderQueueControls(importJobQueue.status)}</dd>
</div>
</dl>
</div>
<div className="flex">
<span className="mt-2 dark:text-slate-200 text-slate-400">
Imported: <span>{importJobQueue.mostRecentImport}</span>
</span>
</div>
</>
)}
{/* Past imports */}
{!isLoading && !isEmpty(data?.data) && (
<>
<h3 className="subtitle is-4 mt-5">Past Imports</h3>
<table className="table">
<thead>
<tr>
<th>Time Started</th>
<th>Session Id</th>
<th>Imported</th>
<th>Failed</th>
</tr>
</thead>
{/* Past imports */}
{!isLoading && !isEmpty(data?.data) && (
<div className="max-w-screen-lg">
<span className="flex items-center mt-6">
<span className="text-xl text-slate-500 dark:text-slate-200 pr-5">
Past Imports
</span>
<span className="h-px flex-1 bg-slate-200 dark:bg-slate-400"></span>
</span>
<tbody>
{data?.data.map((jobResult, id) => {
return (
<tr key={id}>
<td>
{format(
new Date(jobResult.earliestTimestamp),
"EEEE, hh:mma, do LLLL Y",
)}
</td>
<td>
<span className="tag is-warning">
{jobResult.sessionId}
</span>
</td>
<td>
<span className="tag is-success">
{jobResult.completedJobs}
</span>
</td>
<td>
<span className="tag is-danger">
{jobResult.failedJobs}
</span>
</td>
<div className="overflow-x-auto mt-4 rounded-lg border border-gray-200">
<table className="min-w-full divide-y-2 divide-gray-200 dark:divide-gray-200 text-md">
<thead className="ltr:text-left rtl:text-right">
<tr>
<th className="whitespace-nowrap px-4 py-2 font-medium text-gray-900 dark:text-slate-200">
Time Started
</th>
<th className="whitespace-nowrap px-4 py-2 font-medium text-gray-900 dark:text-slate-200">
Session Id
</th>
<th className="whitespace-nowrap px-4 py-2 font-medium text-gray-900 dark:text-slate-200">
Imported
</th>
<th className="whitespace-nowrap px-4 py-2 font-medium text-gray-900 dark:text-slate-200">
Failed
</th>
</tr>
);
})}
</tbody>
</table>
</>
)}
</thead>
<tbody className="divide-y divide-gray-200">
{data?.data.map((jobResult, id) => {
return (
<tr key={id}>
<td className="whitespace-nowrap px-2 py-2 text-gray-700 dark:text-slate-300">
{format(
new Date(jobResult.earliestTimestamp),
"EEEE, hh:mma, do LLLL Y",
)}
</td>
<td className="whitespace-nowrap px-2 py-2 text-gray-700 dark:text-slate-300">
<span className="tag is-warning">
{jobResult.sessionId}
</span>
</td>
<td className="whitespace-nowrap px-4 py-2 text-gray-700 dark:text-slate-300">
<span className="inline-flex items-center justify-center rounded-full bg-emerald-100 px-2 py-0.5 text-emerald-700">
<span className="h-5 w-6">
<i className="icon-[solar--check-circle-line-duotone] h-5 w-5"></i>
</span>
<p className="whitespace-nowrap text-sm">
{jobResult.completedJobs}
</p>
</span>
</td>
<td className="whitespace-nowrap px-4 py-2 text-gray-700 dark:text-slate-300">
<span className="inline-flex items-center justify-center rounded-full bg-red-100 px-2 py-0.5 text-red-700">
<span className="h-5 w-6">
<i className="icon-[solar--close-circle-line-duotone] h-5 w-5"></i>
</span>
<p className="whitespace-nowrap text-sm">
{jobResult.failedJobs}
</p>
</span>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
)}
</div>
</section>
</div>
);