Multiple Base-Paths support

This commit is contained in:
glax 2025-03-16 16:48:38 +01:00
parent 42a1e1a2ce
commit d9bbbed1c0
7 changed files with 111 additions and 6 deletions

View File

@ -1,6 +1,7 @@
import {deleteData, getData, patchData, postData, putData} from '../App';
import IJob, {JobState, JobType} from "./interfaces/Jobs/IJob";
import IModifyJobRecord from "./interfaces/records/IModifyJobRecord";
import IDownloadAvailableJobsRecord from "./interfaces/records/IDownloadAvailableJobsRecord";
export default class Job
{
@ -117,16 +118,16 @@ export default class Job
});
}
static async CreateDownloadAvailableChaptersJob(apiUri: string, mangaId: string, recurrenceMs: number): Promise<string[]> {
static async CreateDownloadAvailableChaptersJob(apiUri: string, mangaId: string, data: IDownloadAvailableJobsRecord): Promise<string[]> {
if(mangaId === undefined || mangaId === null || mangaId.length < 1) {
console.error(`mangaId was not provided`);
return Promise.reject();
}
if(recurrenceMs === undefined || recurrenceMs === null || recurrenceMs < 0) {
if(data === undefined || data === null) {
console.error(`recurrenceMs was not provided`);
return Promise.reject();
}
return putData(`${apiUri}/v2/Job/DownloadAvailableChaptersJob/${mangaId}`, recurrenceMs)
return putData(`${apiUri}/v2/Job/DownloadAvailableChaptersJob/${mangaId}`, data)
.then((json) => {
//console.info(`Got Job ${jobId}`);
const ret = json as string[];

View File

@ -7,6 +7,8 @@ import '../styles/search.css';
import '../styles/ExtendedInfo.css'
import SearchFunctions from "./SearchFunctions";
import Job from "./Job";
import ILocalLibrary from "./interfaces/ILocalLibrary";
import LocalLibrary from "./interfaces/LocalLibrary";
export default function Search({apiUri, jobInterval, onJobsChanged, closeSearch} : {apiUri: string, jobInterval: Date, onJobsChanged: (internalId: string) => void, closeSearch(): void}) {
const [mangaConnectors, setConnectors] = useState<IMangaConnector[]>();
@ -86,6 +88,27 @@ export default function Search({apiUri, jobInterval, onJobsChanged, closeSearch}
}
const changeSelectedLanguage : ChangeEventHandler<HTMLSelectElement> = (event) => setSelectedLanguage(event.target.value);
let [selectedLibrary, setSelectedLibrary] = useState<ILocalLibrary | null>(null);
let [libraries, setLibraries] = useState<ILocalLibrary[] | null>(null);
useEffect(() => {
LocalLibrary.GetLibraries(apiUri).then(setLibraries);
}, []);
useEffect(() => {
if(libraries === null || libraries.length < 1)
setSelectedLibrary(null);
else
setSelectedLibrary(libraries[0]);
}, [libraries]);
const selectedLibraryChanged : ChangeEventHandler<HTMLSelectElement> = (event) => {
event.preventDefault();
if(libraries === null)
return;
const selectedLibrary = libraries.find((lib:ILocalLibrary) => lib.localLibraryId == event.target.value);
if(selectedLibrary === undefined)
return;
setSelectedLibrary(selectedLibrary);
}
return (<div id="Search">
<div id="SearchBox">
@ -110,8 +133,12 @@ export default function Search({apiUri, jobInterval, onJobsChanged, closeSearch}
? <p></p>
: searchResults.map(result =>
<ExtendedInfo key={"Searchresult-"+result.mangaId} apiUri={apiUri} manga={result} actions={[
<select defaultValue={selectedLibrary === null ? "" : selectedLibrary.localLibraryId} onChange={selectedLibraryChanged}>
{selectedLibrary === null || libraries === null ? <option value="">Loading</option>
: libraries.map(library => <option key={library.localLibraryId} value={library.localLibraryId}>{library.libraryName} ({library.basePath})</option>)}
</select>,
<button className="Manga-AddButton" onClick={() => {
Job.CreateDownloadAvailableChaptersJob(apiUri, result.mangaId, jobInterval.getTime()).then(() => onJobsChanged(result.mangaId));
Job.CreateDownloadAvailableChaptersJob(apiUri, result.mangaId, {recurrenceTimeMs: jobInterval.getTime(), localLibraryId: selectedLibrary!.localLibraryId}).then(() => onJobsChanged(result.mangaId));
}}>Monitor</button>
]}/>
)

View File

@ -0,0 +1,14 @@
import {ReactElement} from "react";
export default interface ILocalLibrary {
localLibraryId: string;
basePath: string;
libraryName: string;
}
export function LocalLibrary(library: ILocalLibrary) : ReactElement {
return (<div key={library.localLibraryId}>
<p className={"LocalLibrary-Name"}>{library.libraryName}</p>
<p className={"LocalLibrary-Path"}>{library.basePath}</p>
</div>);
}

View File

@ -0,0 +1,51 @@
import ILocalLibrary from "./ILocalLibrary";
import {deleteData, getData, patchData, putData} from "../../App";
import INewLibraryRecord from "./records/INewLibraryRecord";
export default class LocalLibrary
{
static async GetLibraries(apiUri: string): Promise<ILocalLibrary[]> {
return getData(`${apiUri}/v2/LocalLibraries`)
.then((json) => {
const ret = json as ILocalLibrary[];
console.debug(ret);
return (ret);
});
}
static async GetLibrary(apiUri: string, libraryId: string): Promise<ILocalLibrary> {
return getData(`${apiUri}/v2/LocalLibraries/${libraryId}`)
.then((json) => {
const ret = json as ILocalLibrary;
//console.debug(ret);
return (ret);
});
}
static async CreateLibrary(apiUri: string, data: INewLibraryRecord): Promise<ILocalLibrary> {
return putData(`${apiUri}/v2/LocalLibraries`, data)
.then((json) => {
const ret = json as ILocalLibrary;
//console.debug(ret);
return (ret);
});
}
static async DeleteLibrary(apiUri: string, libraryId: string): Promise<void> {
return deleteData(`${apiUri}/v2/LocalLibraries/${libraryId}`);
}
static async ChangeLibraryPath(apiUri: string, libraryId: string, newPath: string): Promise<void> {
return patchData(`${apiUri}/v2/LocalLibraries/${libraryId}/ChangeBasePath`, newPath)
.then(()=> {
return Promise.resolve()
});
}
static async ChangeLibraryName(apiUri: string, libraryId: string, newName: string): Promise<void> {
return patchData(`${apiUri}/v2/LocalLibraries/${libraryId}/ChangeName`, newName)
.then(()=> {
return Promise.resolve()
});
}
}

View File

@ -0,0 +1,4 @@
export default interface IDownloadAvailableJobsRecord {
recurrenceTimeMs: number;
localLibraryId: string;
}

View File

@ -0,0 +1,4 @@
export default interface INewLibraryRecord {
path: string;
name: string;
}

View File

@ -7,7 +7,7 @@
width: fit-content;
height: 328px;
display: grid;
grid-template-columns: 220px 600px 80px;
grid-template-columns: 220px 300px 380px;
grid-template-rows: 55px 55px 190px auto;
column-gap: 10px;
grid-template-areas:
@ -104,7 +104,7 @@
overflow-y: scroll;
}
.SearchResult > .Manga-actions button {
.SearchResult > .Manga-actions button, .Manga-actions select {
background-color: white;
border: 1px solid var(--primary-color);
border-radius: 4px;
@ -117,6 +117,10 @@
grid-area: button;
padding: 0;
margin: 5px 0 0 0;
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
}
.SearchResult > .Manga-AddButton:hover {