🏗️ Cleaning up forms and cards

This commit is contained in:
2023-12-13 12:30:14 -05:00
parent 72a308801d
commit 81b590157e
5 changed files with 198 additions and 92 deletions

View File

@@ -11,7 +11,7 @@ import {
getComicBooks,
} from "../../actions/fileops.actions";
import { getLibraryStatistics } from "../../actions/comicinfo.actions";
import { isEmpty, isNil } from "lodash";
import { isEmpty, isNil, isUndefined } from "lodash";
import Header from "../shared/Header";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import axios from "axios";
@@ -20,6 +20,10 @@ import {
LIBRARY_SERVICE_BASE_URI,
LIBRARY_SERVICE_HOST,
} from "../../constants/endpoints";
import {
determineCoverFile,
determineExternalMetadata,
} from "../../shared/utils/metadata.utils";
export const Dashboard = (): ReactElement => {
const { data: recentComics } = useQuery({
@@ -86,71 +90,97 @@ export const Dashboard = (): ReactElement => {
<section>
<h1>Dashboard</h1>
<div className="grid grid-cols-5 gap-6">
{recentComics?.data.docs.map((recentComic, idx) => (
<Card
orientation="horizontal-2-medium"
key={idx}
imageUrl={`${LIBRARY_SERVICE_HOST}/${recentComic.rawFileDetails.cover.filePath}`}
title={recentComic.inferredMetadata.issue.name}
hasDetails
>
<div>
<dt className="sr-only">Address</dt>
<dd className="text-sm my-1 flex flex-row gap-1">
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-0.5 rounded-md dark:text-slate-900 dark:bg-slate-400">
<span className="pr-1 pt-1">
<i className="icon-[solar--hashtag-outline]"></i>
</span>
<span className="text-md text-slate-500">
{recentComic.inferredMetadata.issue.number}
</span>
</span>
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-0.5 rounded-md dark:text-slate-900 dark:bg-slate-400">
<span className="pr-1 pt-1">
<i className="icon-[solar--file-bold-duotone] w-4 h-4"></i>
</span>
{recentComics?.data.docs.map(
(
{
_id,
rawFileDetails,
sourcedMetadata: { comicvine, comicInfo, locg },
inferredMetadata,
acquisition: {
source: { name },
},
},
idx,
) => {
const { issueName, url } = determineCoverFile({
rawFileDetails,
comicvine,
comicInfo,
locg,
});
const { issue, coverURL, icon } = determineExternalMetadata(
name,
{
comicvine,
comicInfo,
locg,
},
);
const isComicVineMetadataAvailable =
!isUndefined(comicvine) &&
!isUndefined(comicvine.volumeInformation);
<span className="text-md text-slate-500">
{recentComic.rawFileDetails.extension}
</span>
</span>
</dd>
</div>
return (
<Card
orientation="vertical-2"
key={idx}
imageUrl={`${LIBRARY_SERVICE_HOST}/${rawFileDetails.cover.filePath}`}
title={inferredMetadata.issue.name}
hasDetails
>
<div>
<dt className="sr-only">Address</dt>
<dd className="text-sm my-1 flex flex-row gap-1">
{/* Issue number */}
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-0.5 rounded-md dark:text-slate-900 dark:bg-slate-400">
<span className="pr-1 pt-1">
<i className="icon-[solar--hashtag-outline]"></i>
</span>
<span className="text-md text-slate-900">
{inferredMetadata.issue.number}
</span>
</span>
{/* File extension */}
<span className="inline-flex items-center bg-slate-50 text-slate-800 text-xs font-medium px-2.5 py-0.5 rounded-md dark:text-slate-900 dark:bg-slate-400">
<span className="pr-1 pt-1">
<i className="icon-[solar--file-bold-duotone] w-4 h-4"></i>
</span>
<div className="flex flex-row items-center gap-4 my-2">
<div className="sm:inline-flex sm:shrink-0 sm:items-center sm:gap-2">
<i className="h-7 w-7 icon-[solar--code-file-bold-duotone] text-orange-400 dark:text-orange"></i>
<span className="text-md text-slate-500 dark:text-slate-900">
{rawFileDetails.extension}
</span>
</span>
</dd>
</div>
{/* <div className="">
<p className="text-gray-500">Parking</p>
<p className="font-medium">2 spaces</p>
</div> */}
</div>
<div className="sm:inline-flex sm:shrink-0 sm:items-center sm:gap-2">
<svg
className="h-4 w-4 text-indigo-700"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z"
/>
</svg>
{/* <div className="mt-1.5 sm:mt-0">
<p className="text-slate-500">Bathroom</p>
<p className="font-medium">2 rooms</p>
</div> */}
</div>
</div>
</Card>
))}
<div className="flex flex-row items-center gap-1 my-2">
<div className="sm:inline-flex sm:shrink-0 sm:items-center sm:gap-2">
{/* ComicInfo.xml presence */}
{!isNil(comicInfo) && !isEmpty(comicInfo) && (
<i className="h-7 w-7 icon-[solar--code-file-bold-duotone] text-slate-500 dark:text-slate-300"></i>
)}
{/* ComicVine metadata presence */}
{isComicVineMetadataAvailable && (
<span className="w-7 h-7">
<img
src="/src/client/assets/img/cvlogo.svg"
alt={"ComicVine metadata detected."}
/>
</span>
)}
</div>
{/* Raw file presence */}
{isNil(rawFileDetails) && (
<span className="h-6 w-5 sm:shrink-0 sm:items-center sm:gap-2">
<i className="icon-[solar--file-corrupted-outline] h-5 w-5" />
</span>
)}
</div>
</Card>
);
},
)}
</div>
</section>
</div>

View File

@@ -71,7 +71,6 @@ export const AirDCPPSettingsForm = (): ReactElement => {
const initFormData = !isUndefined(airDCPPClientConfiguration)
? airDCPPClientConfiguration
: {};
console.log(airDCPPClientConfiguration);
return (
<>
<ConnectionForm

View File

@@ -50,24 +50,42 @@ export const Settings = (props: ISettingsProps): ReactElement => {
},
];
return (
<section className="container">
<div className="columns">
<div className="section column is-one-quarter">
<h1 className="title">Settings</h1>
<aside className="menu">
<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">
Settings
</h1>
<p className="mt-1.5 text-sm text-gray-500 dark:text-white">
Import comics into the ThreeTwo library.
</p>
</div>
</div>
</div>
</header>
<div className="flex flex-cols max-w-screen-xl mx-auto">
<aside className="px-4 py-4 sm:px-6 sm:py-8 lg:px-8">
{map(settingsObject, (settingObject, idx) => {
return (
<div key={idx}>
<p className="menu-label">{settingObject.category}</p>
<div className="w-64 py-2 text-slate" key={idx}>
<h3 className="text-l pb-2">
{settingObject.category.toUpperCase()}
</h3>
{/* First level children */}
{!isUndefined(settingObject.children) ? (
<ul className="menu-list" key={settingObject.id}>
<ul key={settingObject.id}>
{map(settingObject.children, (item, idx) => {
return (
<li key={idx}>
<li key={idx} className="mb-2">
<a
className={
item.id.toString() === active ? "is-active" : ""
item.id.toString() === active
? "is-active flex items-center"
: "flex items-center"
}
onClick={() => setActive(item.id.toString())}
>
@@ -75,14 +93,14 @@ export const Settings = (props: ISettingsProps): ReactElement => {
</a>
{/* Second level children */}
{!isUndefined(item.children) ? (
<ul>
<ul className="pl-4">
{map(item.children, (item, idx) => (
<li key={item.id}>
<li key={item.id} className="mb-2">
<a
className={
item.id.toString() === active
? "is-active"
: ""
? "is-active flex items-center"
: "flex items-center"
}
onClick={() =>
setActive(item.id.toString())
@@ -103,18 +121,18 @@ export const Settings = (props: ISettingsProps): ReactElement => {
);
})}
</aside>
</div>
{/* content for settings */}
<div className="section column is-half mt-6">
<div className="content">
{map(settingsContent, ({ id, content }) =>
active === id ? content : null,
)}
{/* content for settings */}
<div className="max-w-screen-xl">
<div className="content">
{map(settingsContent, ({ id, content }) =>
active === id ? content : null,
)}
</div>
</div>
</div>
</div>
</section>
</section>
</div>
);
};

View File

@@ -104,24 +104,23 @@ const renderCard = (props: ICardProps): ReactElement => {
</div>
);
case "horizontal-2-small":
case "horizontal-small":
return (
<>
<div className="flex flex-row justify-start align-top gap-3 bg-slate-200 h-fit rounded-md shadow-md shadow-white-400">
{/* thumbnail */}
<div className="rounded-l-md overflow-hidden">
<div className="rounded-md overflow-hidden">
<img src={props.imageUrl} className="object-cover h-20 w-20" />
</div>
{/* details */}
<div className="w-fit h-fit pl-1 pr-2 py-1">
<p className="text-sm">{props.title}</p>
nothin
</div>
</div>
</>
);
case "horizontal-2-medium":
case "horizontal-medium":
return (
<>
<div className="flex flex-row items-center align-top gap-3 bg-slate-200 h-fit p-2 rounded-md shadow-md shadow-white-400">
@@ -137,6 +136,17 @@ const renderCard = (props: ICardProps): ReactElement => {
</div>
</>
);
case "cover-only":
return (
<>
{/* thumbnail */}
<div className="rounded-md overflow-hidden w-fit h-fit">
<img src={props.imageUrl} />
</div>
</>
);
default:
return <></>;
}

View File

@@ -15,7 +15,56 @@ export const ConnectionForm = ({
initialValues={initialData}
render={({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<h2>{formHeading}</h2>
<h2 className="text-xl">{formHeading}</h2>
<div className="relative flex w-full max-w-[24rem]">
<Field name="hostname" validate={hostNameValidator}>
{({ input, meta }) => (
<div className="flex items-center rounded-md border border-gray-300">
<div className="relative">
{/* <select
id="dropdown"
className="appearance-none h-11 bg-transparent rounded-none border-r border-gray-300 text-gray-700 dark:text-slate-200 py-1 px-3 sm:text-sm sm:leading-5 focus:outline-none focus:shadow-outline-blue focus:border-blue-300"
>
<option>Protocol</option>
<option value="http">http://</option>
<option value="https">https://</option>
</select> */}
<Field
name="protocol"
component="select"
className="appearance-none h-11 bg-transparent rounded-none border-r border-gray-300 text-gray-700 dark:text-slate-200 py-1 px-3 sm:text-sm sm:leading-5 focus:outline-none focus:shadow-outline-blue focus:border-blue-300"
>
<option>Protocol</option>
<option value="http">http://</option>
<option value="https">https://</option>
</Field>
<div className="absolute right-0 inset-y-0 flex items-center px-0 pointer-events-none">
<svg
className="h-5 w-5 text-gray-400"
fill="currentColor"
viewBox="0 0 20 20"
>
<path d="M7 7l3-3 3 3m0 6l-3 3-3-3"></path>
</svg>
</div>
</div>
<input
{...input}
type="text"
placeholder="hostname"
className="ml-2 bg-transparent py-2 px-2 block w-full rounded-md sm:text-sm sm:leading-5 focus:outline-none focus:shadow-outline-blue focus:border-blue-300"
/>
{meta.error && meta.touched && (
<span className="is-size-7 has-text-danger">
{meta.error}
</span>
)}
</div>
)}
</Field>
</div>
<label className="label">Hostname</label>
<div className="field has-addons">
<p className="control">