Adding a removing jobs.

Having a list of running and waiting jobs
This commit is contained in:
glax 2025-03-14 00:41:07 +01:00
parent 84520d8e18
commit 1eef710efc
21 changed files with 224 additions and 92 deletions

View File

@ -0,0 +1,19 @@
import {getData} from "../App";
import IChapter from "./interfaces/IChapter";
export default class Chapter{
static async GetChapterFromId(apiUri: string, chapterId: string): Promise<IChapter> {
if(chapterId === undefined || chapterId === null) {
console.error(`chapterId was not provided`);
return Promise.reject();
}
return getData(`${apiUri}/v2/Query/Chapter/${chapterId}`)
.then((json) => {
//console.info("Got all Manga");
const ret = json as IChapter;
//console.debug(ret);
return (ret);
});
}
}

View File

@ -4,7 +4,7 @@ import Job from './Job';
import Icon from '@mdi/react'; import Icon from '@mdi/react';
import {mdiCounter, mdiEyeCheck, mdiRun, mdiTrayFull} from '@mdi/js'; import {mdiCounter, mdiEyeCheck, mdiRun, mdiTrayFull} from '@mdi/js';
import QueuePopUp from "./QueuePopUp"; import QueuePopUp from "./QueuePopUp";
import {JobState, JobType} from "./interfaces/IJob"; import {JobState, JobType} from "./interfaces/Jobs/IJob";
export default function Footer({connectedToBackend, apiUri} : {connectedToBackend: boolean, apiUri: string}) { export default function Footer({connectedToBackend, apiUri} : {connectedToBackend: boolean, apiUri: string}) {
const [MonitoringJobsCount, setMonitoringJobsCount] = React.useState(0); const [MonitoringJobsCount, setMonitoringJobsCount] = React.useState(0);

View File

@ -1,5 +1,5 @@
import {deleteData, getData, patchData, postData, putData} from '../App'; import {deleteData, getData, patchData, postData, putData} from '../App';
import IJob, {JobState, JobType} from "./interfaces/IJob"; import IJob, {JobState, JobType} from "./interfaces/Jobs/IJob";
import IModifyJobRecord from "./interfaces/records/IModifyJobRecord"; import IModifyJobRecord from "./interfaces/records/IModifyJobRecord";
export default class Job export default class Job

View File

@ -1,6 +1,5 @@
import IManga from './interfaces/IManga'; import IManga from './interfaces/IManga';
import {deleteData, getData, patchData, postData} from '../App'; import {deleteData, getData, patchData, postData} from '../App';
import {RefObject} from "react";
import IChapter from "./interfaces/IChapter"; import IChapter from "./interfaces/IChapter";
export default class Manga export default class Manga

View File

@ -1,19 +1,15 @@
import React, {EventHandler, MouseEventHandler, ReactElement, useEffect, useState} from 'react'; import React, {EventHandler, ReactElement, useEffect, useState} from 'react';
import Job from './Job'; import Job from './Job';
import '../styles/monitorMangaList.css'; import '../styles/monitorMangaList.css';
import IJob, {JobType} from "./interfaces/IJob"; import {JobType} from "./interfaces/Jobs/IJob";
import IManga from "./interfaces/IManga";
import '../styles/MangaCoverCard.css' import '../styles/MangaCoverCard.css'
import DownloadAvailableChaptersJob from "./interfaces/Jobs/DownloadAvailableChaptersJob";
import {CoverCard} from "./interfaces/IManga";
export default function MonitorJobsList({onStartSearch, onJobsChanged, connectedToBackend, apiUri, updateList} : {onStartSearch() : void, onJobsChanged: EventHandler<any>, connectedToBackend: boolean, apiUri: string, updateList: Date}) { export default function MonitorJobsList({onStartSearch, onJobsChanged, connectedToBackend, apiUri, updateList} : {onStartSearch() : void, onJobsChanged: EventHandler<any>, connectedToBackend: boolean, apiUri: string, updateList: Date}) {
const [MonitoringJobs, setMonitoringJobs] = useState<IJob[]>([]); const [MonitoringJobs, setMonitoringJobs] = useState<DownloadAvailableChaptersJob[]>([]);
const [AllManga, setAllManga] = useState<IManga[]>([]);
const [joblistUpdateInterval, setJoblistUpdateInterval] = React.useState<number>(); const [joblistUpdateInterval, setJoblistUpdateInterval] = React.useState<number>();
useEffect(() => {
}, [MonitoringJobs]);
useEffect(() => { useEffect(() => {
if(connectedToBackend){ if(connectedToBackend){
UpdateMonitoringJobsList(); UpdateMonitoringJobsList();
@ -35,35 +31,24 @@ export default function MonitorJobsList({onStartSearch, onJobsChanged, connected
return; return;
//console.debug("Updating MonitoringJobsList"); //console.debug("Updating MonitoringJobsList");
Job.GetJobsWithType(apiUri, JobType.DownloadAvailableChaptersJob) Job.GetJobsWithType(apiUri, JobType.DownloadAvailableChaptersJob)
.then((jobs) => setMonitoringJobs(jobs)); .then((jobs) => setMonitoringJobs(jobs as DownloadAvailableChaptersJob[]));
} }
function StartSearchMangaEntry() : ReactElement { function StartSearchMangaEntry() : ReactElement {
return (<div key="monitorMangaEntry.StartSearch" className="monitorMangaEntry" onClick={onStartSearch}> return (<div key="monitorMangaEntry.StartSearch" className="startSearchEntry Manga" onClick={onStartSearch}>
<div className="Manga" key="StartSearch.Manga">
<img src="../media/blahaj.png" alt="Blahaj"></img> <img src="../media/blahaj.png" alt="Blahaj"></img>
<div> <div>
<p style={{textAlign: "center", width: "100%"}} className="Manga-name">Add new Manga</p> <p style={{textAlign: "center", width: "100%"}} className="Manga-name">Add new Manga</p>
<p style={{fontSize: "42pt", textAlign: "center"}}>+</p> <p style={{fontSize: "42pt", textAlign: "center"}}>+</p>
</div> </div>
</div>
</div>); </div>);
} }
const DeleteJob : MouseEventHandler = (e) => {
const jobId = e.currentTarget.id.slice(e.currentTarget.id.indexOf("-")+1);
//console.info(`Pressed ${e.currentTarget.id} => ${jobId}`);
Job.DeleteJob(apiUri, jobId).then(() => onJobsChanged(jobId));
}
const StartJob : MouseEventHandler = (e) => {
const jobId = e.currentTarget.id.slice(e.currentTarget.id.indexOf("-")+1);
//console.info(`Pressed ${e.currentTarget.id} => ${jobId}`);
Job.StartJob(apiUri, jobId);
}
return ( return (
<div id="MonitorMangaList"> <div id="MonitorMangaList">
{StartSearchMangaEntry()} <StartSearchMangaEntry />
</div>) {MonitoringJobs.map((MonitoringJob) =>
<CoverCard apiUri={apiUri} mangaId={MonitoringJob.mangaId} key={MonitoringJob.mangaId} />
)}
</div>);
} }

View File

@ -1,10 +1,10 @@
import React, {useEffect, useState} from 'react'; import React, {useEffect, useState} from 'react';
import IJob, {JobState} from "./interfaces/IJob"; import IJob, {JobState, JobType} from "./interfaces/Jobs/IJob";
import '../styles/queuePopUp.css'; import '../styles/queuePopUp.css';
import '../styles/popup.css'; import '../styles/popup.css';
import Job from "./Job"; import Job from "./Job";
import IManga, {QueueItem} from "./interfaces/IManga"; import DownloadSingleChapterJob from "./interfaces/Jobs/DownloadSingleChapterJob";
import Manga from "./Manga"; import { ItemDownloadSingleChapterJob } from "./interfaces/IManga";
export default function QueuePopUp({connectedToBackend, children, apiUri} : {connectedToBackend: boolean, children: JSX.Element[], apiUri: string}) { export default function QueuePopUp({connectedToBackend, children, apiUri} : {connectedToBackend: boolean, children: JSX.Element[], apiUri: string}) {
@ -34,16 +34,16 @@ export default function QueuePopUp({connectedToBackend, children, apiUri} : {con
function UpdateMonitoringJobsList(){ function UpdateMonitoringJobsList(){
Job.GetJobsInState(apiUri, JobState.Waiting) Job.GetJobsInState(apiUri, JobState.Waiting)
.then((jobs: IJob[]) => { .then((jobs: IJob[]) => {
//console.log(StandbyJobs) //console.log(jobs);
setWaitingJobs(jobs); return jobs;
//console.log(StandbyJobs) })
}); .then(setWaitingJobs);
Job.GetJobsInState(apiUri, JobState.Running) Job.GetJobsInState(apiUri, JobState.Running)
.then((jobs: IJob[]) => { .then((jobs: IJob[]) => {
//console.log(StandbyJobs) //console.log(jobs);
setRunningJobs(jobs); return jobs;
//console.log(StandbyJobs) })
}); .then(setRunningJobs);
} }
return (<> return (<>
@ -58,6 +58,12 @@ export default function QueuePopUp({connectedToBackend, children, apiUri} : {con
onClick={() => setShowQueuePopup(false)}/> onClick={() => setShowQueuePopup(false)}/>
</div> </div>
<div id="QueuePopUpBody" className="popupBody"> <div id="QueuePopUpBody" className="popupBody">
<div>
{RunningJobs.filter(j => j.jobType == JobType.DownloadSingleChapterJob).map(j => <ItemDownloadSingleChapterJob apiUri={apiUri} job={j as DownloadSingleChapterJob} key={j.jobId} />)}
</div>
<div>
{WaitingJobs.filter(j => j.jobType == JobType.DownloadSingleChapterJob).map(j => <ItemDownloadSingleChapterJob apiUri={apiUri} job={j as DownloadSingleChapterJob} key={j.jobId} />)}
</div>
</div> </div>
</div> </div>
: <></> : <></>

View File

@ -2,10 +2,11 @@ import React, {ChangeEventHandler, EventHandler, useEffect, useState} from 'reac
import {MangaConnector} from "./MangaConnector"; import {MangaConnector} from "./MangaConnector";
import IMangaConnector from "./interfaces/IMangaConnector"; import IMangaConnector from "./interfaces/IMangaConnector";
import {isValidUri} from "../App"; import {isValidUri} from "../App";
import IManga, {SearchResult} from "./interfaces/IManga"; import IManga, {ExtendedInfo} from "./interfaces/IManga";
import '../styles/search.css'; import '../styles/search.css';
import '../styles/MangaSearchResult.css' import '../styles/ExtendedInfo.css'
import SearchFunctions from "./SearchFunctions"; import SearchFunctions from "./SearchFunctions";
import Job from "./Job";
export default function Search({apiUri, jobInterval, onJobsChanged, closeSearch} : {apiUri: string, jobInterval: Date, onJobsChanged: (internalId: string) => void, closeSearch(): void}) { export default function Search({apiUri, jobInterval, onJobsChanged, closeSearch} : {apiUri: string, jobInterval: Date, onJobsChanged: (internalId: string) => void, closeSearch(): void}) {
const [mangaConnectors, setConnectors] = useState<IMangaConnector[]>(); const [mangaConnectors, setConnectors] = useState<IMangaConnector[]>();
@ -90,14 +91,15 @@ export default function Search({apiUri, jobInterval, onJobsChanged, closeSearch}
<div id="SearchBox"> <div id="SearchBox">
<input type="text" placeholder="Manganame" id="Searchbox-Manganame" onKeyDown={(e) => {if(e.key == "Enter") ExecuteSearch(null);}} onChange={searchBoxValueChanged}></input> <input type="text" placeholder="Manganame" id="Searchbox-Manganame" onKeyDown={(e) => {if(e.key == "Enter") ExecuteSearch(null);}} onChange={searchBoxValueChanged}></input>
<select id="Searchbox-Connector" value={selectedConnector === undefined ? "" : selectedConnector.name} onChange={selectedConnectorChanged}> <select id="Searchbox-Connector" value={selectedConnector === undefined ? "" : selectedConnector.name} onChange={selectedConnectorChanged}>
<option value="" disabled hidden>Select</option> {mangaConnectors === undefined ? <option value="Loading">Loading</option> : <option value="" disabled hidden>Select</option>}
{mangaConnectors === undefined {mangaConnectors === undefined
? <option value="Loading">Loading</option> ? null
: mangaConnectors.map(con => <option value={con.name} key={con.name}>{con.name}</option>)} : mangaConnectors.map(con => <option value={con.name} key={con.name}>{con.name}</option>)}
</select> </select>
<select id="Searchbox-language" onChange={changeSelectedLanguage} value={selectedLanguage === null ? "" : selectedLanguage}> <select id="Searchbox-language" onChange={changeSelectedLanguage} value={selectedLanguage === null ? "" : selectedLanguage}>
{mangaConnectors === undefined ? <option value="Loading">Loading</option> : <option value="" disabled hidden>Select Connector</option>}
{selectedConnector === undefined {selectedConnector === undefined
? <option value="" disabled hidden>Select Connector</option> ? null
: selectedConnector.supportedLanguages.map(language => <option value={language} key={language}>{language}</option>)} : selectedConnector.supportedLanguages.map(language => <option value={language} key={language}>{language}</option>)}
</select> </select>
<button id="Searchbox-button" type="submit" onClick={ExecuteSearch}>Search</button> <button id="Searchbox-button" type="submit" onClick={ExecuteSearch}>Search</button>
@ -106,7 +108,14 @@ export default function Search({apiUri, jobInterval, onJobsChanged, closeSearch}
<div id="SearchResults"> <div id="SearchResults">
{searchResults === undefined {searchResults === undefined
? <p></p> ? <p></p>
: searchResults.map(result => SearchResult(apiUri, result, jobInterval, onJobsChanged))} : searchResults.map(result =>
<ExtendedInfo key={"Searchresult-"+result.mangaId} apiUri={apiUri} manga={result} actions={[
<button className="Manga-AddButton" onClick={() => {
Job.CreateDownloadAvailableChaptersJob(apiUri, result.mangaId, jobInterval.getTime()).then(() => onJobsChanged(result.mangaId));
}}>Monitor</button>
]}/>
)
}
</div> </div>
</div>); </div>);
} }

View File

@ -9,7 +9,7 @@ export function LoadFrontendSettings(): IFrontendSettings {
const cookies = new Cookies(); const cookies = new Cookies();
return { return {
jobInterval: cookies.get('jobInterval') === undefined jobInterval: cookies.get('jobInterval') === undefined
? new Date(0,0,0,3) ? new Date(Date.parse("1970-01-01T03:00:00.000Z"))
: cookies.get('jobInterval'), : cookies.get('jobInterval'),
apiUri: cookies.get('apiUri') === undefined apiUri: cookies.get('apiUri') === undefined
? `${window.location.protocol}//${window.location.host}/api` ? `${window.location.protocol}//${window.location.host}/api`

View File

@ -1,12 +1,13 @@
import Manga from "../Manga"; import Manga from "../Manga";
import React, {ReactElement, ReactEventHandler} from "react"; import React, {ReactElement, ReactEventHandler, useEffect} from "react";
import Icon from '@mdi/react'; import Icon from '@mdi/react';
import { mdiTagTextOutline, mdiAccountEdit, mdiLinkVariant } from '@mdi/js'; import { mdiTagTextOutline, mdiAccountEdit, mdiLinkVariant } from '@mdi/js';
import MarkdownPreview from '@uiw/react-markdown-preview'; import MarkdownPreview from '@uiw/react-markdown-preview';
import IJob from "./IJob";
import {AuthorElement} from "./IAuthor"; import {AuthorElement} from "./IAuthor";
import Job from "../Job";
import {LinkElement} from "./ILink"; import {LinkElement} from "./ILink";
import DownloadSingleChapterJob from "./Jobs/DownloadSingleChapterJob";
import IChapter from "./IChapter";
import Chapter from "../Chapter";
export default interface IManga{ export default interface IManga{
mangaId: string; mangaId: string;
@ -34,28 +35,66 @@ export enum MangaReleaseStatus {
Unreleased = "Unreleased", Unreleased = "Unreleased",
} }
export function CoverCard(apiUri: string, manga: IManga) : ReactElement { export const defaultManga: IManga = {
altTitleIds: [],
authorIds: [],
connectorId: "",
description: "",
folderName: "",
ignoreChapterBefore: 0,
linkIds: [],
mangaConnectorId: "",
name: "",
originalLanguage: "",
releaseStatus: MangaReleaseStatus.Unreleased,
tags: [],
websiteUrl: "",
year: 0,
mangaId: ""
}
export function CoverCard({apiUri, mangaId} : {apiUri: string, mangaId: string}) : ReactElement {
let [manga, setContent] = React.useState<IManga>(defaultManga);
let [extendedInfo, setExtendedInfo] = React.useState(false);
useEffect(() => {
Manga.GetMangaById(apiUri, mangaId).then(setContent);
}, []);
const MangaCover : ReactEventHandler<HTMLImageElement> = (e) => {
if(e.currentTarget.src != Manga.GetMangaCoverImageUrl(apiUri, manga.mangaId, e.currentTarget))
e.currentTarget.src = Manga.GetMangaCoverImageUrl(apiUri, manga.mangaId, e.currentTarget);
}
return ( return (
<div className="Manga" key={manga.mangaId}> <div className="Manga" key={manga.mangaId} onClick={(e) => {
<img src="../../media/blahaj.png" alt="Manga Cover"></img> setExtendedInfo(!extendedInfo);
<div> }}>
<img src={Manga.GetMangaCoverImageUrl(apiUri, manga.mangaId, undefined)} alt="Manga Cover" onLoad={MangaCover} onResize={MangaCover}></img>
<div className="SimpleCover">
<p className="pill connector-name">{manga.mangaConnectorId}</p> <p className="pill connector-name">{manga.mangaConnectorId}</p>
<div className="Manga-status" release-status={manga.releaseStatus}></div> <div className="Manga-status" release-status={manga.releaseStatus}></div>
<p className="Manga-name">{manga.name}</p> <p className="Manga-name">{manga.name}</p>
</div> </div>
{extendedInfo ? <div extended-info={extendedInfo ? "yes" : "no"}>
<ExtendedInfo apiUri={apiUri} manga={manga} actions={[
<button className="Manga-DeleteButton" onClick={() => {
Manga.DeleteManga(apiUri, manga.mangaId);
}}>Delete</button>
]} />
</div> : null}
</div>); </div>);
} }
export function SearchResult(apiUri: string, manga: IManga, interval: Date, onJobsChanged: (internalId: string) => void) : ReactElement { export function ExtendedInfo({apiUri, manga, actions} : {apiUri: string, manga: IManga, actions: ReactElement[]}) : ReactElement {
const MangaCover : ReactEventHandler<HTMLImageElement> = (e) => { const MangaCover : ReactEventHandler<HTMLImageElement> = (e) => {
console.log(manga.mangaId);
if(e.currentTarget.src != Manga.GetMangaCoverImageUrl(apiUri, manga.mangaId, e.currentTarget)) if(e.currentTarget.src != Manga.GetMangaCoverImageUrl(apiUri, manga.mangaId, e.currentTarget))
e.currentTarget.src = Manga.GetMangaCoverImageUrl(apiUri, manga.mangaId, e.currentTarget); e.currentTarget.src = Manga.GetMangaCoverImageUrl(apiUri, manga.mangaId, e.currentTarget);
} }
return( return(
<div className="SearchResult" key={manga.mangaId}> <div className="SearchResult" key={manga.mangaId}>
<img src={Manga.GetMangaCoverImageUrl(apiUri, manga.mangaId, undefined)} alt="Manga Cover" onLoad={MangaCover}></img> <img src={Manga.GetMangaCoverImageUrl(apiUri, manga.mangaId, undefined)} alt="Manga Cover" onLoad={MangaCover} onResize={MangaCover}></img>
<p className="connector-name">{manga.mangaConnectorId}</p> <p className="connector-name">{manga.mangaConnectorId}</p>
<div className="Manga-status" release-status={manga.releaseStatus}></div> <div className="Manga-status" release-status={manga.releaseStatus}></div>
<p className="Manga-name"><a href={manga.websiteUrl}>{manga.name}<img src="../../media/link.svg" <p className="Manga-name"><a href={manga.websiteUrl}>{manga.name}<img src="../../media/link.svg"
@ -79,30 +118,45 @@ export function SearchResult(apiUri: string, manga: IManga, interval: Date, onJo
</div> </div>
<MarkdownPreview className="Manga-description" source={manga.description} <MarkdownPreview className="Manga-description" source={manga.description}
style={{backgroundColor: "transparent", color: "black"}}/> style={{backgroundColor: "transparent", color: "black"}}/>
<button className="Manga-AddButton" onClick={() => { <div className="Manga-actions">
Job.CreateDownloadAvailableChaptersJob(apiUri, manga.mangaId, interval.getMilliseconds()).then(() => onJobsChanged(manga.mangaId)); {actions.map((p, i) => <div key={i}>{p}</div>)}
}}>Monitor </div>
</button>
</div>); </div>);
} }
export function QueueItem(apiUri: string, manga: IManga, job: IJob, triggerUpdate: () => void){ export function ItemDownloadSingleChapterJob({apiUri, job} : {apiUri: string, job: DownloadSingleChapterJob}){
return ( const MangaCover : ReactEventHandler<HTMLImageElement> = (e) => {
<div className="QueueJob" key={"QueueJob-" + job.jobId}> if(manga === null)
<img src="../../media/blahaj.png" alt="Manga Cover"></img> return;
<p className="QueueJob-Name">{manga.name}</p> if(e.currentTarget.src != Manga.GetMangaCoverImageUrl(apiUri, manga.mangaId, e.currentTarget))
<p className="QueueJob-JobType">{job.jobType}</p> e.currentTarget.src = Manga.GetMangaCoverImageUrl(apiUri, manga.mangaId, e.currentTarget);
<div className="QueueJob-actions">
<button className="QueueJob-Cancel"
onClick={() => Job.StopJob(apiUri, job.jobId).then(triggerUpdate)}>Cancel
</button>
{job.parentJobId != null
? <button className="QueueJob-Cancel"
onClick={() => Job.StopJob(apiUri, job.parentJobId!).then(triggerUpdate)}>Cancel all
related</button>
: <></>
} }
</div>
let [chapter, setChapter] = React.useState<IChapter|null>(null);
let [manga, setManga] = React.useState<IManga|null>(null);
useEffect(() => {
Chapter.GetChapterFromId(apiUri, job.chapterId).then(setChapter);
}, []);
useEffect(() => {
if(chapter === null){
setManga(null);
return;
}
Manga.GetMangaById(apiUri, chapter.parentMangaId).then(setManga);
}, [chapter]);
return (
<div className="DownloadSingleChapterJob" key={"DownloadSingleChapterJob-" + job.jobId}>
<img src={manga ? Manga.GetMangaCoverImageUrl(apiUri, manga.mangaId, undefined) : ""} alt="Manga Cover" onLoad={MangaCover} onResize={MangaCover}></img>
<p className="DownloadSingleChapterJob-Name">{manga ? manga.name : job.chapterId}</p>
<p className="DownloadSingleChapterJob-Title">
{chapter ? "Vol." + chapter.volumeNumber + " Ch." + chapter.chapterNumber + ": " + chapter.title : "loading"}
<a href={chapter ? chapter.url : ""}>
<img src="../../media/link.svg" alt=""/>
</a>
</p>
</div> </div>
); );
} }

View File

@ -0,0 +1,5 @@
import IJob from "./IJob";
export default interface DownloadAvailableChaptersJob extends IJob {
mangaId: string;
}

View File

@ -0,0 +1,5 @@
import IJob from "./IJob";
export default interface DownloadMangaCoverJob extends IJob {
mangaId: string;
}

View File

@ -0,0 +1,5 @@
import IJob from "./IJob";
export default interface DownloadSingleChapterJob extends IJob {
chapterId: string;
}

View File

@ -0,0 +1,6 @@
import IJob from "./IJob";
export default interface MoveFileOrFolderJob extends IJob {
fromLocation: string;
toLocation: string;
}

View File

@ -0,0 +1,5 @@
import IJob from "./IJob";
export default interface RetrieveChaptersJob extends IJob {
mangaId: string;
}

View File

@ -0,0 +1,5 @@
import IJob from "./IJob";
export default interface UpdateFilesDownloadedJob extends IJob {
mangaId: string;
}

View File

@ -0,0 +1,5 @@
import IJob from "./IJob";
export default interface UpdateMetadataJob extends IJob {
mangaId: string;
}

View File

@ -66,10 +66,12 @@
padding: 0; padding: 0;
margin: 0; margin: 0;
white-space: nowrap; white-space: nowrap;
max-height: 100%;
overflow-y: scroll;
} }
.SearchResult > .Manga-tags p { .SearchResult > .Manga-tags p {
margin: 0 2px; margin: 2px;
padding: 5px; padding: 5px;
font-size: 10pt; font-size: 10pt;
height: fit-content; height: fit-content;
@ -102,8 +104,7 @@
overflow-y: scroll; overflow-y: scroll;
} }
.SearchResult > .Manga-AddButton { .SearchResult > .Manga-actions button {
grid-area: button;
background-color: white; background-color: white;
border: 1px solid var(--primary-color); border: 1px solid var(--primary-color);
border-radius: 4px; border-radius: 4px;
@ -112,6 +113,12 @@
padding: 5px 10px; padding: 5px 10px;
} }
.SearchResult > .Manga-actions {
grid-area: button;
padding: 0;
margin: 5px 0 0 0;
}
.SearchResult > .Manga-AddButton:hover { .SearchResult > .Manga-AddButton:hover {
background-color: #eee; background-color: #eee;
} }
@ -126,6 +133,6 @@
bottom: 7px; bottom: 7px;
} }
.monitorMangaEntry { .startSearchEntry {
position: relative; position: relative;
} }

View File

@ -32,6 +32,10 @@
z-index: 0; z-index: 0;
} }
.startSearchEntry::after{
background: initial !important;
}
.Manga-name{ .Manga-name{
width: fit-content; width: fit-content;
font-size: 16pt; font-size: 16pt;
@ -102,7 +106,7 @@
background-color: gray; background-color: gray;
} }
.Manga img { .Manga > img {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
@ -117,7 +121,7 @@
margin: 2px 0; margin: 2px 0;
} }
.Manga > div { .Manga > .SimpleCover {
position: relative; position: relative;
z-index: 1; z-index: 1;
width: 100%; width: 100%;
@ -125,3 +129,15 @@
left: 0; left: 0;
top: 0; top: 0;
} }
div[extended-info="no"]{
display: none;
}
div[extended-info="yes"]{
display: block;
position: absolute;
left: 0;
top: 0;
z-index: 2;
}

View File

@ -4,7 +4,7 @@
flex-flow: row; flex-flow: row;
flex-wrap: nowrap; flex-wrap: nowrap;
flex-grow: 1; flex-grow: 1;
height: 100%; height: calc(100vh - 100px);
overflow-y: scroll; overflow-y: scroll;
scrollbar-color: var(--accent-color) var(--primary-color); scrollbar-color: var(--accent-color) var(--primary-color);
scrollbar-width: thin; scrollbar-width: thin;

View File

@ -1,5 +1,6 @@
#QueuePopUp #QueuePopUpBody { #QueuePopUp #QueuePopUpBody {
display: flex; display: flex;
color: black;
} }
#QueuePopUp #QueuePopUpBody > * { #QueuePopUp #QueuePopUpBody > * {