Complete rewrite #2

Merged
glax merged 30 commits from cuttingedge into master 2023-09-19 17:02:12 +02:00
11 changed files with 830 additions and 714 deletions

123
README.md Normal file
View File

@ -0,0 +1,123 @@
<!-- PROJECT SHIELDS -->
<!--
*** I'm using markdown "reference style" links for readability.
*** Reference links are enclosed in brackets [ ] instead of parentheses ( ).
*** See the bottom of this document for the declaration of the reference variables
*** for contributors-url, forks-url, etc. This is an optional, concise syntax you may use.
*** https://www.markdownguide.org/basic-syntax/#reference-style-links
-->
<!-- PROJECT LOGO -->
<br />
<div align="center">
<h3 align="center">Tranga-Website</h3>
<p align="center">
Automatic Manga and Metadata downloader
</p>
<p align="center">
This is the Website for <a href="https://github.com/C9Glax/tranga">Tranga</a> (API)
</p>
</div>
<!-- TABLE OF CONTENTS -->
<details>
<summary>Table of Contents</summary>
<ol>
<li>
<a href="#about-the-project">About The Project</a>
<ul>
<li><a href="#built-with">Built With</a></li>
</ul>
</li>
<li>
<a href="#getting-started">Getting Started</a>
</li>
<li><a href="#roadmap">Roadmap</a></li>
<li><a href="#contributing">Contributing</a></li>
<li><a href="#license">License</a></li>
<li><a href="#acknowledgments">Acknowledgments</a></li>
</ol>
</details>
<!-- ABOUT THE PROJECT -->
## About The Project
Tranga-Website is the Web-frontend to [Tranga](https://github.com/C9Glax/tranga) (the API). It displays information aquired from Tranga and can create Jobs (Manga-Downloads).
### What this does do (and nothing else)
This repo makes HTTP-requests to the [Tranga-API](https://github.com/C9Glax/tranga) to display it's present configuration.
### Built With
- nginx
- HTML, CSS, and barebones Javascript
- 💙 Blåhaj 🦈
<p align="right">(<a href="#readme-top">back to top</a>)</p>
<!-- GETTING STARTED -->
## Getting Started
There is a single release:
### Docker
Download [docker-compose.yaml](https://github.com/C9Glax/tranga-website/blob/cuttingedge/docker-compose.yaml) and configure to your needs.
The `docker-compose` also includes [Tranga](https://github.com/C9Glax/tranga) as backend. For its configuration refer to the repo README.
<!-- ROADMAP -->
## Roadmap
- [ ] ❓
See the [open issues](https://github.com/C9Glax/tranga-website/issues) for a full list of proposed features (and known issues).
<p align="right">(<a href="#readme-top">back to top</a>)</p>
<!-- CONTRIBUTING -->
## Contributing
The following is copy & pasted:
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement".
Don't forget to give the project a star! Thanks again!
1. Fork the Project
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
4. Push to the Branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request
<p align="right">(<a href="#readme-top">back to top</a>)</p>
<!-- LICENSE -->
## License
Distributed under the GNU GPLv3 License. See `LICENSE.txt` for more information.
<p align="right">(<a href="#readme-top">back to top</a>)</p>
<!-- ACKNOWLEDGMENTS -->
## Acknowledgments
* [Choose an Open Source License](https://choosealicense.com)
* [Font Awesome](https://fontawesome.com)
* [Best-README-Template](https://github.com/othneildrew/Best-README-Template/tree/master)
<p align="right">(<a href="#readme-top">back to top</a>)</p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

@ -3,6 +3,15 @@
if(getCookie("apiUri") != ""){
apiUri = getCookie("apiUri");
}
setCookie("apiUri", apiUri);
function setCookie(cname, cvalue) {
const d = new Date();
d.setTime(d.getTime() + (365*24*60*60*1000));
let expires = "expires="+ d.toUTCString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/;samesite=strict";
}
function getCookie(cname) {
let name = cname + "=";
let decodedCookie = decodeURIComponent(document.cookie);
@ -42,127 +51,144 @@ function DeleteData(uri){
});
}
async function Ping(){
let ret = await GetData(`${apiUri}/Ping`);
return ret;
}
async function GetAvailableControllers(){
var uri = apiUri + "/Connectors";
let json = await GetData(uri);
return json;
}
async function GetPublicationFromConnector(connectorName, title){
var uri = apiUri + `/Publications/FromConnector?connectorName=${connectorName}&title=${title}`;
async function GetPublicationFromConnector(connector, title){
var uri;
if(title.includes("http")){
uri = `${apiUri}/Manga/FromConnector?connector=${connector}&url=${title}`;
}else{
uri = `${apiUri}/Manga/FromConnector?connector=${connector}&title=${title}`;
}
let json = await GetData(uri);
return json;
}
async function GetChapters(connector, internalId, language){
var uri = `${apiUri}/Manga/Chapters?connector=${connector}&internalId=${internalId}&translatedLanguage=${language}`;
let json = await GetData(uri);
return json;
}
async function GetKnownPublications(){
var uri = apiUri + "/Publications/Known";
function GetCoverUrl(internalId){
return `${apiUri}/Manga/Cover?internalId=${internalId}`;
}
async function GetAllJobs(){
var uri = `${apiUri}/Jobs`;
let json = await GetData(uri);
return json;
}
async function GetRunningJobs(){
var uri = `${apiUri}/Jobs/Running`;
let json = await GetData(uri);
return json;
}
async function GetPublication(internalId){
var uri = apiUri + `/Publications/Known?internalId=${internalId}`;
async function GetWaitingJobs(){
var uri = `${apiUri}/Jobs/Waiting`;
let json = await GetData(uri);
return json;
}
async function GetChapters(internalId, connectorName, onlyNew, language){
var uri = apiUri + `/Publications/Chapters?internalId=${internalId}&connectorName=${connectorName}&onlyNew=${onlyNew}&language=${language}`;
let json = await GetData(uri);
return json;
async function GetMonitorJobs(){
var uri = `${apiUri}/Jobs/MonitorJobs`;
let json = await GetData(uri);
return json;
}
async function GetTaskTypes(){
var uri = apiUri + "/Tasks/Types";
let json = await GetData(uri);
return json;
}
async function GetRunningTasks(){
var uri = apiUri + "/Tasks/RunningTasks";
let json = await GetData(uri);
return json;
}
async function GetDownloadTasks(){
var uri = apiUri + "/Tasks?taskType=MonitorPublication";
async function GetProgress(jobId){
var uri = `${apiUri}/Jobs/Progress?jobId=${jobId}`;
let json = await GetData(uri);
return json;
}
async function GetSettings(){
var uri = apiUri + "/Settings";
var uri = `${apiUri}/Settings`;
let json = await GetData(uri);
return json;
}
async function GetKomgaTask(){
var uri = apiUri + "/Tasks?taskType=UpdateLibraries";
let json = await GetData(uri);
return json;
async function GetAvailableNotificationConnectors(){
var uri = `${apiUri}/NotificationConnectors/Types`;
let json = await GetData(uri);
return json;
}
function CreateMonitorTask(connectorName, internalId, reoccurrence, language){
var uri = apiUri + `/Tasks/CreateMonitorTask?connectorName=${connectorName}&internalId=${internalId}&reoccurrenceTime=${reoccurrence}&language=${language}`;
async function GetNotificationConnectors(){
var uri = `${apiUri}/NotificationConnectors`;
let json = await GetData(uri);
return json;
}
async function GetAvailableLibraryConnectors(){
var uri = `${apiUri}/LibraryConnectors/Types`;
let json = await GetData(uri);
return json;
}
async function GetLibraryConnectors(){
var uri = `${apiUri}/LibraryConnectors`;
let json = await GetData(uri);
return json;
}
function CreateMonitorJob(connector, internalId, language){
var uri = `${apiUri}/Jobs/MonitorManga?connector=${connector}&internalId=${internalId}&interval=03:00:00&translatedLanguage=${language}`;
PostData(uri);
}
function CreateDownloadChaptersTask(connectorName, internalId, chapters, language){
var uri = apiUri + `/Tasks/CreateDownloadChaptersTask?connectorName=${connectorName}&internalId=${internalId}&chapters=${chapters}&language=${language}`;
function CreateDownloadNewChaptersJob(connector, internalId, language){
var uri = `${apiUri}/Jobs/DownloadNewChapters?connector=${connector}&internalId=${internalId}&translatedLanguage=${language}`;
PostData(uri);
}
function StartTask(taskType, connectorName, internalId){
var uri = apiUri + `/Tasks/Start?taskType=${taskType}&connectorName=${connectorName}&internalId=${internalId}`;
PostData(uri);
}
function EnqueueTask(taskType, connectorName, publicationId){
var uri = apiUri + `/Queue/Enqueue?taskType=${taskType}&connectorName=${connectorName}&publicationId=${publicationId}`;
function StartJob(jobId){
var uri = `${apiUri}/Jobs/StartNow?jobId=${jobId}`;
PostData(uri);
}
function UpdateDownloadLocation(downloadLocation){
var uri = apiUri + "/Settings/Update?"
uri += "&downloadLocation="+downloadLocation;
PostData(uri);
var uri = `${apiUri}/Settings/UpdateDownloadLocation?downloadLocation=${downloadLocation}`;
PostData(uri);
}
function UpdateKomga(komgaUrl, komgaAuth){
var uri = apiUri + "/Settings/Update?"
uri += `&komgaUrl=${komgaUrl}&komgaAuth=${komgaAuth}`;
var uri = `${apiUri}/LibraryConnectors/Update?libraryConnector=Komga&komgaUrl=${komgaUrl}&komgaAuth=${komgaAuth}`;
PostData(uri);
}
function UpdateKavita(kavitaUrl, kavitaUser, kavitaPass){
var uri = apiUri + "/Settings/Update?"
uri += `&kavitaUrl=${kavitaUrl}&kavitaUsername=${kavitaUser}&kavitaPassword=${kavitaPass}`;
function UpdateKavita(kavitaUrl, kavitaUsername, kavitaPassword){
var uri = `${apiUri}/LibraryConnectors/Update?libraryConnector=Kavita&kavitaUrl=${kavitaUrl}&kavitaUsername=${kavitaUsername}&kavitaPassword={kavitaPassword}`;
PostData(uri);
}
function UpdateGotify(gotifyUrl, gotifyAppToken){
var uri = apiUri + "/Settings/Update?"
uri += `&gotifyUrl=${gotifyUrl}&gotifyAppToken=${gotifyAppToken}`;
var uri = `${apiUri}/NotificationConnectors/Update?notificationConnector=Gotify&gotifyUrl=${gotifyUrl}&gotifyAppToken=${gotifyAppToken}`;
PostData(uri);
}
function UpdateLunaSea(lunaseaWebhook){
var uri = apiUri + "/Settings/Update?"
uri += `&lunaseaWebhook=${lunaseaWebhook}`;
var uri = `${apiUri}/NotificationConnectors/Update?notificationConnector=LunaSea&lunaseaWebhook=${lunaseaWebhook}`;
PostData(uri);
}
function DeleteTask(taskType, connectorName, publicationId){
var uri = apiUri + `/Tasks?taskType=${taskType}&connectorName=${connectorName}&publicationId=${publicationId}`;
function RemoveJob(jobId){
var uri = `${apiUri}/Jobs?jobId=${jobId}`;
DeleteData(uri);
}
function DequeueTask(taskType, connectorName, publicationId){
var uri = apiUri + `/Queue/Dequeue?taskType=${taskType}&connectorName=${connectorName}&publicationId=${publicationId}`;
DeleteData(uri);
}
async function GetQueue(){
var uri = apiUri + "/Queue/List";
let json = await GetData(uri);
return json;
function CancelJob(jobId){
var uri = `${apiUri}/Jobs/Cancel?jobId=${jobId}`;
PostData(uri);
}

View File

@ -20,94 +20,41 @@
<img id="settingscog" src="media/settings-cogwheel.svg" height="100%" alt="settingscog">
</topbar>
<viewport>
<div id="loaderdiv">
<blur-background></blur-background>
<div id="loader"></div>
<p id="loaderText">Check your Settings > API-URI</p>
</div>
<content>
<div id="addPublication">
<p>+</p>
</div>
<publication>
<publication onclick="ShowNewMangaSearch()">
<img alt="cover" src="media/cover.jpg">
<publication-information>
<connector-name class="pill">MangaDex</connector-name>
<publication-name>Tensei Pandemic</publication-name>
<connector-name class="pill">Sample</connector-name>
<publication-name>Best Manga there is</publication-name>
</publication-information>
</publication>
</content>
<popup id="selectPublicationPopup">
<blur-background id="blurBackgroundTaskPopup"></blur-background>
<popup-window>
<popup-title>Select Publication</popup-title>
<popup-content>
<div>
<label for="connectors">Connector</label>
<select id="connectors">
<option value=""></option>
</select>
</div>
<div>
<label for="searchPublicationQuery">Search Title</label><input id="searchPublicationQuery" type="text"></addtask-setting>
</div>
<input type="submit" value="Search" style="font-weight: bolder" onclick="NewSearch();">
</popup-content>
<div id="taskSelectOutput"></div>
</popup-window>
</popup>
<popup id="createMonitorTaskPopup">
<blur-background id="blurBackgroundCreateMonitorTaskPopup"></blur-background>
<popup-window>
<popup-title>Create Task: Monitor Publication</popup-title>
<popup-content>
<div>
<span>Run every</span>
<label for="hours"></label><input id="hours" type="number" value="3" min="0" max="23"><span>hours</span>
<label for="minutes"></label><input id="minutes" type="number" value="0" min="0" max="59"><span>minutes</span>
<input type="submit" value="Create" onclick="AddMonitorTask()">
</div>
</popup-content>
</popup-window>
</popup>
<popup id="createDownloadChaptersTask">
<blur-background id="blurBackgroundCreateDownloadChaptersTask"></blur-background>
<popup-window>
<popup-title>Create Task: Download Chapter(s)</popup-title>
<popup-content>
<div>
<label for="selectedChapters">Chapters:</label><input id="selectedChapters" placeholder="Select"><input type="submit" value="Select" onclick="DownloadChapterTaskClick()">
</div>
<div id="chapterOutput">
</div>
</popup-content>
</popup-window>
</popup>
<popup id="publicationViewerPopup">
<blur-background id="blurBackgroundPublicationPopup"></blur-background>
<publication-viewer>
<img id="pubviewcover" src="media/cover.jpg" alt="cover">
<publication-information>
<publication-name id="publicationViewerName">Tensei Pandemic</publication-name>
<publication-tags id="publicationViewerTags"></publication-tags>
<publication-author id="publicationViewerAuthor">Imamura Hinata</publication-author>
<publication-description id="publicationViewerDescription">Imamura Hinata is a high school boy with a cute appearance.
Since his trauma with the first love, he wanted to be more manly than anybody else. But one day he woke up to something different…
The total opposite of his ideal male body!
Pandemic love comedy!
</publication-description>
<publication-interactions>
<publication-starttask>Start Task ▶️</publication-starttask>
<publication-delete>Delete Task ❌</publication-delete>
<publication-add id="createMonitorTaskButton">Monitor </publication-add>
<publication-add id="createDownloadChapterTaskButton">Download Chapter </publication-add>
</publication-interactions>
</publication-information>
</publication-viewer>
<popup id="newMangaPopup">
<blur-background id="blurBackgroundNewMangaPopup" onclick="newMangaPopup.style.display = 'none';"></blur-background>
<div id="newMangaPopupSelector">
<select id="newMangaConnector" />
<input type="text" placeholder="Title" id="newMangaTitle" />
<select id="newMangaTranslatedLanguage">
<option selected="selected">en</option>
<option>it</option>
<option>de</option>
</select>
</div>
<div id="newMangaResult"></div>
</popup>
<popup id="settingsPopup">
<blur-background id="blurBackgroundSettingsPopup"></blur-background>
<blur-background id="blurBackgroundSettingsPopup" onclick="
settingsPopup.style.display = 'none';"></blur-background>
<popup-window>
<popup-title>Settings</popup-title>
<popup-content>
@ -145,29 +92,53 @@
<label for="lunaseaWebhook"></label><input placeholder="device/:id or user/:id" id="lunaseaWebhook" type="text">
</div>
<div>
<label for="libraryUpdateTime" style="margin-right: 5px;">Update Time</label><input id="libraryUpdateTime" type="time" value="00:01:00" step="10">
<input type="submit" value="Update" onclick="UpdateLibrarySettings()">
<input type="submit" value="Update" onclick="UpdateSettings()">
</div>
</popup-content>
</popup-window>
</popup>
<popup id="publicationViewerPopup">
<blur-background id="blurBackgroundPublicationPopup" onclick="publicationViewerPopup.style.display= 'none';"></blur-background>
<publication-viewer>
<img id="pubviewcover" src="media/cover.jpg" alt="cover">
<publication-information>
<publication-name id="publicationViewerName">Best Manga there is</publication-name>
<publication-tags id="publicationViewerTags">A Manga</publication-tags>
<publication-author id="publicationViewerAuthor">Glax</publication-author>
<publication-description id="publicationViewerDescription">
An interesting description. The description is very intriguing, yet wholesome.
</publication-description>
<publication-interactions>
<publication-starttask id="startJobButton">Start Job ▶️</publication-starttask>
<publication-canceltask id="cancelJobButton">Cancel Job ❌</publication-canceltask>
<publication-delete id="deleteJobButton">Delete Job 🗑️</publication-delete>
<publication-add id="createMonitorJobButton">Monitor </publication-add>
<publication-add id="createDownloadChapterJobButton">Download Chapter 📥</publication-add>
</publication-interactions>
</publication-information>
</publication-viewer>
</popup>
<popup id="downloadTasksPopup">
<blur-background id="blurBackgroundTasksQueuePopup"></blur-background>
<popup id="jobStatusView">
<blur-background id="blurBackgroundJobStatus" onclick="jobStatusView.style.display= 'none';"></blur-background>
<popup-window>
<popup-title>Task Progress</popup-title>
<popup-content>
</popup-content>
<div>
<div id="jobStatusRunning" style="border-right: 1px solid gray;"></div>
</div>
<div>
<div id="jobStatusWaiting" style="border-left: 1px solid gray;"></div>
</div>
</popup-window>
</popup>
</viewport>
<footer>
<div onclick="ShowTasksQueue();">
<img src="media/running.svg" alt="running"><div id="tasksRunningTag">0</div>
<div onclick="ShowJobQueue();">
<img src="media/running.svg" alt="running"><div id="jobsRunningTag">0</div>
</div>
<div onclick="ShowTasksQueue();">
<img src="media/queue.svg" alt="queue"><div id="tasksQueuedTag">0</div>
<div onclick="ShowJobQueue();">
<img src="media/queue.svg" alt="queue"><div id="jobsQueuedTag">0</div>
</div>
<p id="madeWith">Made with Blåhaj 🦈</p>
</footer>

View File

@ -1,31 +1,26 @@
let publications = [];
let tasks = [];
let toEditId;
let runningJobs = [];
let waitingJobs = [];
let notificationConnectorTypes = [];
let libraryConnectorTypes = [];
let selectedManga;
let selectedJob;
const searchBox = document.querySelector("#searchbox");
const searchPublicationQuery = document.querySelector("#searchPublicationQuery");
const selectPublication = document.querySelector("#taskSelectOutput");
const connectorSelect = document.querySelector("#connectors");
const settingsPopup = document.querySelector("#settingsPopup");
const settingsCog = document.querySelector("#settingscog");
const selectRecurrence = document.querySelector("#selectReccurrence");
const tasksContent = document.querySelector("content");
const selectPublicationPopup = document.querySelector("#selectPublicationPopup");
const createMonitorTaskButton = document.querySelector("#createMonitorTaskButton");
const createDownloadChapterTaskButton = document.querySelector("#createDownloadChapterTaskButton");
const createMonitorTaskPopup = document.querySelector("#createMonitorTaskPopup");
const createDownloadChaptersTask = document.querySelector("#createDownloadChaptersTask");
const chapterOutput = document.querySelector("#chapterOutput");
const selectedChapters = document.querySelector("#selectedChapters");
const publicationViewerPopup = document.querySelector("#publicationViewerPopup");
const publicationViewerWindow = document.querySelector("publication-viewer");
const publicationViewerDescription = document.querySelector("#publicationViewerDescription");
const publicationViewerName = document.querySelector("#publicationViewerName");
const publicationViewerTags = document.querySelector("#publicationViewerTags");
const publicationViewerAuthor = document.querySelector("#publicationViewerAuthor");
const pubviewcover = document.querySelector("#pubviewcover");
const publicationDelete = document.querySelector("publication-delete");
const publicationTaskStart = document.querySelector("publication-starttask");
const createMonitorTaskButton = document.querySelector("#createMonitoJobButton");
const createDownloadChapterTaskButton = document.querySelector("#createDownloadChapterJobButton");
const startJobButton = document.querySelector("#startJobButton");
const cancelJobButton = document.querySelector("#cancelJobButton");
const deleteJobButton = document.querySelector("#deleteJobButton");
const mangaViewerPopup = document.querySelector("#publicationViewerPopup");
const mangaViewerWindow = document.querySelector("publication-viewer");
const mangaViewerDescription = document.querySelector("#publicationViewerDescription");
const mangaViewerName = document.querySelector("#publicationViewerName");
const mangaViewerTags = document.querySelector("#publicationViewerTags");
const mangaViewerAuthor = document.querySelector("#publicationViewerAuthor");
const mangaViewCover = document.querySelector("#pubviewcover");
const settingDownloadLocation = document.querySelector("#downloadLocation");
const settingKomgaUrl = document.querySelector("#komgaUrl");
const settingKomgaUser = document.querySelector("#komgaUsername");
@ -36,166 +31,55 @@ const settingKavitaPass = document.querySelector("#kavitaPassword");
const settingGotifyUrl = document.querySelector("#gotifyUrl");
const settingGotifyAppToken = document.querySelector("#gotifyAppToken");
const settingLunaseaWebhook = document.querySelector("#lunaseaWebhook");
const libraryUpdateTime = document.querySelector("#libraryUpdateTime");
const settingKomgaConfigured = document.querySelector("#komgaConfigured");
const settingKavitaConfigured = document.querySelector("#kavitaConfigured");
const settingGotifyConfigured = document.querySelector("#gotifyConfigured");
const settingLunaseaConfigured = document.querySelector("#lunaseaConfigured");
const settingApiUri = document.querySelector("#settingApiUri");
const tagTasksRunning = document.querySelector("#tasksRunningTag");
const tagTasksQueued = document.querySelector("#tasksQueuedTag");
const downloadTasksPopup = document.querySelector("#downloadTasksPopup");
const downloadTasksOutput = downloadTasksPopup.querySelector("popup-content");
const newMangaPopup = document.querySelector("#newMangaPopup");
const newMangaConnector = document.querySelector("#newMangaConnector");
const newMangaTitle = document.querySelector("#newMangaTitle");
const newMangaResult = document.querySelector("#newMangaResult");
const newMangaTranslatedLanguage = document.querySelector("#newMangaTranslatedLanguage");
const jobsRunningTag = document.querySelector("#jobsRunningTag");
const jobsQueuedTag = document.querySelector("#jobsQueuedTag");
const loaderdiv = document.querySelector('#loaderdiv');
const jobStatusView = document.querySelector("#jobStatusView");
const jobStatusRunning = document.querySelector("#jobStatusRunning");
const jobStatusWaiting = document.querySelector("#jobStatusWaiting");
searchBox.addEventListener("keyup", () => FilterResults());
settingsCog.addEventListener("click", () => OpenSettings());
document.querySelector("#blurBackgroundSettingsPopup").addEventListener("click", () => settingsPopup.style.display = "none");
document.querySelector("#blurBackgroundTaskPopup").addEventListener("click", () => selectPublicationPopup.style.display = "none");
document.querySelector("#blurBackgroundPublicationPopup").addEventListener("click", () => HidePublicationPopup());
document.querySelector("#blurBackgroundCreateMonitorTaskPopup").addEventListener("click", () => createMonitorTaskPopup.style.display = "none");
document.querySelector("#blurBackgroundCreateDownloadChaptersTask").addEventListener("click", () => createDownloadChaptersTask.style.display = "none");
document.querySelector("#blurBackgroundTasksQueuePopup").addEventListener("click", () => downloadTasksPopup.style.display = "none");
selectedChapters.addEventListener("keypress", (event) => {
if(event.key === "Enter"){
DownloadChapterTaskClick();
}
})
publicationDelete.addEventListener("click", () => DeleteTaskClick());
createMonitorTaskButton.addEventListener("click", () => {
HidePublicationPopup();
createMonitorTaskPopup.style.display = "block";
});
createDownloadChapterTaskButton.addEventListener("click", () => {
HidePublicationPopup();
OpenDownloadChapterTaskPopup();
});
publicationTaskStart.addEventListener("click", () => StartTaskClick());
searchPublicationQuery.addEventListener("keypress", (event) => {
if(event.key === "Enter"){
NewSearch();
}
});
GetAvailableControllers()
.then(json => availableConnectors = json)
.then(json =>
json.forEach(connector => {
var option = document.createElement('option');
option.value = connector;
option.innerText = connector;
connectorSelect.appendChild(option);
})
);
function NewSearch(){
//Disable inputs
connectorSelect.disabled = true;
searchPublicationQuery.disabled = true;
//Waitcursor
document.body.style.cursor = "wait";
//Empty previous results
selectPublication.replaceChildren();
GetPublicationFromConnector(connectorSelect.value, searchPublicationQuery.value)
.then(json =>
json.forEach(publication => {
var option = CreatePublication(publication, connectorSelect.value);
option.addEventListener("click", (mouseEvent) => {
ShowPublicationViewerWindow(publication.internalId, mouseEvent, true);
});
selectPublication.appendChild(option);
}
))
.then(() => {
//Re-enable inputs
connectorSelect.disabled = false;
searchPublicationQuery.disabled = false;
//Cursor
document.body.style.cursor = "initial";
});
}
//Returns a new "Publication" Item to display in the tasks section
function CreatePublication(publication, connector){
var publicationElement = document.createElement('publication');
publicationElement.setAttribute("id", publication.internalId);
var img = document.createElement('img');
img.src = `imageCache/${publication.coverFileNameInCache}`;
publicationElement.appendChild(img);
var info = document.createElement('publication-information');
var connectorName = document.createElement('connector-name');
connectorName.innerText = connector;
connectorName.className = "pill";
info.appendChild(connectorName);
var publicationName = document.createElement('publication-name');
publicationName.innerText = publication.sortName;
info.appendChild(publicationName);
publicationElement.appendChild(info);
if(publications.filter(pub => pub.internalId === publication.internalId) < 1)
publications.push(publication);
return publicationElement;
}
function AddMonitorTask(){
var hours = document.querySelector("#hours").value;
var minutes = document.querySelector("#minutes").value;
CreateMonitorTask(connectorSelect.value, toEditId, `${hours}:${minutes}:00`, "en");
HidePublicationPopup();
createMonitorTaskPopup.style.display = "none";
selectPublicationPopup.style.display = "none";
}
function OpenDownloadChapterTaskPopup(){
selectedChapters.value = "";
chapterOutput.replaceChildren();
createDownloadChaptersTask.style.display = "block";
GetChapters(toEditId, connectorSelect.value, true, "en").then((json) => {
var i = 0;
json.forEach(chapter => {
var chapterDom = document.createElement("div");
var indexDom = document.createElement("span");
indexDom.className = "index";
indexDom.innerText = i++;
chapterDom.appendChild(indexDom);
var volDom = document.createElement("span");
volDom.className = "vol";
volDom.innerText = chapter.volumeNumber;
chapterDom.appendChild(volDom);
var chDom = document.createElement("span");
chDom.className = "ch";
chDom.innerText = chapter.chapterNumber;
chapterDom.appendChild(chDom);
var titleDom = document.createElement("span");
titleDom.innerText = chapter.name;
chapterDom.appendChild(titleDom);
chapterOutput.appendChild(chapterDom);
});
function Setup(){
Ping().then((ret) => {
loaderdiv.style.display = 'none';
GetAvailableNotificationConnectors().then((json) => {
json.forEach(connector => {
notificationConnectorTypes[connector.Key] = connector.Value;
});
});
}
function DownloadChapterTaskClick(){
CreateDownloadChaptersTask(connectorSelect.value, toEditId, selectedChapters.value, "en");
HidePublicationPopup();
createDownloadChaptersTask.style.display = "none";
selectPublicationPopup.style.display = "none";
}
GetAvailableLibraryConnectors().then((json) => {
json.forEach(connector => {
libraryConnectorTypes[connector.Key] = connector.Value;
});
});
function DeleteTaskClick(){
taskToDelete = tasks.filter(tTask => tTask.publication.internalId === toEditId)[0];
DeleteTask("MonitorPublication", taskToDelete.connectorName, toEditId);
HidePublicationPopup();
}
function StartTaskClick(){
var toEditTask = tasks.filter(task => task.publication.internalId == toEditId)[0];
StartTask("MonitorPublication", toEditTask.connectorName, toEditId);
HidePublicationPopup();
GetAvailableControllers().then((json) => {
json.forEach(connector => {
var option = document.createElement('option');
option.value = connector;
option.innerText = connector;
newMangaConnector.appendChild(option);
});
});
UpdateJobs();
setInterval(() => {
UpdateJobs();
}, 1000);
});
}
Setup();
function ResetContent(){
//Delete everything
@ -207,45 +91,120 @@ function ResetContent(){
var plus = document.createElement("p");
plus.innerText = "+";
add.appendChild(plus);
add.addEventListener("click", () => ShowNewTaskWindow());
add.addEventListener("click", () => ShowNewMangaSearch());
tasksContent.appendChild(add);
}
function ShowPublicationViewerWindow(publicationId, event, add){
function ShowNewMangaSearch(){
newMangaTitle.value = "";
newMangaPopup.style.display = "block";
newMangaResult.replaceChildren();
}
newMangaTitle.addEventListener("keypress", (event) => { if(event.key === "Enter") GetNewMangaItems();})
function GetNewMangaItems(){
if(newMangaTitle.value.length < 4)
return;
newMangaResult.replaceChildren();
newMangaConnector.disabled = true;
newMangaTitle.disabled = true;
newMangaTranslatedLanguage.disabled = true;
GetPublicationFromConnector(newMangaConnector.value, newMangaTitle.value).then((json) => {
//console.log(json);
if(json.length > 0)
newMangaResult.style.display = "flex";
json.forEach(result => {
var mangaElement = CreateManga(result, newMangaConnector.value)
newMangaResult.appendChild(mangaElement);
mangaElement.addEventListener("click", (event) => {
ShowMangaWindow(null, result, event, true);
});
});
newMangaConnector.disabled = false;
newMangaTitle.disabled = false;
newMangaTranslatedLanguage.disabled = false;
});
}
//Returns a new "Publication" Item to display in the jobs section
function CreateManga(manga, connector){
var mangaElement = document.createElement('publication');
mangaElement.id = GetValidSelector(manga.internalId);
var mangaImage = document.createElement('img');
mangaImage.src = GetCoverUrl(manga.internalId);
mangaElement.appendChild(mangaImage);
var info = document.createElement('publication-information');
var connectorName = document.createElement('connector-name');
connectorName.innerText = connector;
connectorName.className = "pill";
info.appendChild(connectorName);
var mangaName = document.createElement('publication-name');
mangaName.innerText = manga.sortName;
info.appendChild(mangaName);
mangaElement.appendChild(info);
return mangaElement;
}
createMonitorJobButton.addEventListener("click", () => {
CreateMonitorJob(newMangaConnector.value, selectedManga.internalId, newMangaTranslatedLanguage.value);
UpdateJobs();
mangaViewerPopup.style.display = "none";
});
startJobButton.addEventListener("click", () => {
StartJob(selectedJob.id);
mangaViewerPopup.style.display = "none";
});
cancelJobButton.addEventListener("click", () => {
CancelJob(selectedJob.id);
mangaViewerPopup.style.display = "none";
});
deleteJobButton.addEventListener("click", () => {
RemoveJob(selectedJob.id);
UpdateJobs();
mangaViewerPopup.style.display = "none";
});
function ShowMangaWindow(job, manga, event, add){
selectedManga = manga;
selectedJob = job;
//Show popup
publicationViewerPopup.style.display = "block";
mangaViewerPopup.style.display = "block";
//Set position to mouse-position
if(event.clientY < window.innerHeight - publicationViewerWindow.offsetHeight)
publicationViewerWindow.style.top = `${event.clientY}px`;
if(event.clientY < window.innerHeight - mangaViewerWindow.offsetHeight)
mangaViewerWindow.style.top = `${event.clientY}px`;
else
publicationViewerWindow.style.top = `${event.clientY - publicationViewerWindow.offsetHeight}px`;
mangaViewerWindow.style.top = `${event.clientY - mangaViewerWindow.offsetHeight}px`;
if(event.clientX < window.innerWidth - publicationViewerWindow.offsetWidth)
publicationViewerWindow.style.left = `${event.clientX}px`;
if(event.clientX < window.innerWidth - mangaViewerWindow.offsetWidth)
mangaViewerWindow.style.left = `${event.clientX}px`;
else
publicationViewerWindow.style.left = `${event.clientX - publicationViewerWindow.offsetWidth}px`;
mangaViewerWindow.style.left = `${event.clientX - mangaViewerWindow.offsetWidth}px`;
//Edit information inside the window
var publication = publications.filter(pub => pub.internalId === publicationId)[0];
publicationViewerName.innerText = publication.sortName;
publicationViewerTags.innerText = publication.tags.join(", ");
publicationViewerDescription.innerText = publication.description;
publicationViewerAuthor.innerText = publication.authors.join(',');
pubviewcover.src = `imageCache/${publication.coverFileNameInCache}`;
toEditId = publicationId;
mangaViewerName.innerText = manga.sortName;
mangaViewerTags.innerText = manga.tags.join(", ");
mangaViewerDescription.innerText = manga.description;
mangaViewerAuthor.innerText = manga.authors.join(',');
mangaViewCover.src = GetCoverUrl(manga.internalId);
toEditId = manga.internalId;
//Check what action should be listed
if(add){
createMonitorTaskButton.style.display = "initial";
createDownloadChapterTaskButton.style.display = "initial";
publicationDelete.style.display = "none";
publicationTaskStart.style.display = "none";
createMonitorJobButton.style.display = "initial";
createDownloadChapterJobButton.style.display = "none";
cancelJobButton.style.display = "none";
startJobButton.style.display = "none";
deleteJobButton.style.display = "none";
}
else{
createMonitorTaskButton.style.display = "none";
createDownloadChapterTaskButton.style.display = "none";
publicationDelete.style.display = "initial";
publicationTaskStart.style.display = "initial";
createMonitorJobButton.style.display = "none";
createDownloadChapterJobButton.style.display = "none";
cancelJobButton.style.display = "initial";
startJobButton.style.display = "initial";
deleteJobButton.style.display = "initial";
}
}
@ -253,108 +212,8 @@ function HidePublicationPopup(){
publicationViewerPopup.style.display = "none";
}
function ShowNewTaskWindow(){
selectPublication.replaceChildren();
searchPublicationQuery.value = "";
selectPublicationPopup.style.display = "flex";
}
const fadeIn = [
{ opacity: "0" },
{ opacity: "1" }
];
const fadeInTiming = {
duration: 50,
iterations: 1,
fill: "forwards"
}
function OpenSettings(){
GetSettingsClick();
settingsPopup.style.display = "flex";
}
function GetSettingsClick(){
settingApiUri.value = "";
settingKomgaUrl.value = "";
settingKomgaUser.value = "";
settingKomgaPass.value = "";
settingKomgaConfigured.innerText = "❌";
settingKavitaUrl.value = "";
settingKavitaUser.value = "";
settingKavitaPass.value = "";
settingKavitaConfigured.innerText = "❌";
settingGotifyUrl.value = "";
settingGotifyAppToken.value = "";
settingGotifyConfigured.innerText = "❌";
settingLunaseaWebhook.value = "";
settingLunaseaConfigured.innerText = "❌";
settingApiUri.placeholder = apiUri;
GetSettings().then(json => {
settingDownloadLocation.innerText = json.downloadLocation;
json.libraryManagers.forEach(lm => {
if(lm.libraryType == 0){
settingKomgaUrl.placeholder = lm.baseUrl;
settingKomgaUser.placeholder = "User";
settingKomgaPass.placeholder = "***";
settingKomgaConfigured.innerText = "✅";
} else if(lm.libraryType == 1){
settingKavitaUrl.placeholder = lm.baseUrl;
settingKavitaUser.placeholder = "User";
settingKavitaPass.placeholder = "***";
settingKavitaConfigured.innerText = "✅";
}
});
json.notificationManagers.forEach(nm => {
if(nm.notificationManagerType == 0){
settingGotifyConfigured.innerText = "✅";
} else if(nm.notificationManagerType == 1){
settingLunaseaConfigured.innerText = "✅";
}
});
});
GetKomgaTask().then(json => {
if(json.length > 0)
libraryUpdateTime.value = json[0].reoccurrence;
});
}
function UpdateLibrarySettings(){
if(settingKomgaUrl.value != "" && settingKomgaUser.value != "" && settingKomgaPass.value != ""){
var auth = utf8_to_b64(`${settingKomgaUser.value}:${settingKomgaPass.value}`);
console.log(auth);
UpdateKomga(settingKomgaUrl.value, auth);
}
if(settingKavitaUrl.value != "" && settingKavitaUser.value != "" && settingKavitaPass.value != ""){
UpdateKavita(settingKavitaUrl.value, settingKavitaUser.value, settingKavitaPass.value);
}
if(settingGotifyUrl.value != "" && settingGotifyAppToken.value != ""){
UpdateGotify(settingGotifyUrl.value, settingGotifyAppToken.value);
}
if(settingLunaseaWebhook.value != ""){
UpdateLunaSea(settingLunaseaWebhook.value);
}
if(settingApiUri.value != ""){
apiUri = settingApiUri.value;
document.cookie = `apiUri=${apiUri};`;
}
setTimeout(() => GetSettingsClick(), 200);
}
function utf8_to_b64( str ) {
return window.btoa(unescape(encodeURIComponent( str )));
}
searchBox.addEventListener("keyup", () => FilterResults());
//Filter shown jobs
function FilterResults(){
if(searchBox.value.length > 0){
tasksContent.childNodes.forEach(publication => {
@ -377,147 +236,249 @@ function FilterResults(){
}
}
function ShowTasksQueue(){
settingsCog.addEventListener("click", () => {
OpenSettings();
settingsPopup.style.display = "flex";
});
downloadTasksOutput.replaceChildren();
GetRunningTasks()
.then(json => {
tagTasksRunning.innerText = json.length;
json.forEach(task => {
if(task.task == 2 || task.task == 4) {
downloadTasksOutput.appendChild(CreateProgressChild(task));
document.querySelector(`#progress${GetValidSelector(task.taskId)}`).value = task.progress;
var finishedHours = task.executionApproximatelyRemaining.split(':')[0];
var finishedMinutes = task.executionApproximatelyRemaining.split(':')[1];
var finishedSeconds = task.executionApproximatelyRemaining.split(':')[2].split('.')[0];
document.querySelector(`#progressStr${GetValidSelector(task.taskId)}`).innerText = `${finishedHours}:${finishedMinutes}:${finishedSeconds}`;
}
});
});
settingKomgaUrl.addEventListener("keypress", (event) => { { if(event.key === "Enter") UpdateSettings(); } });
settingKomgaUser.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); });
settingKomgaPass.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); });
settingKavitaUrl.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); });
settingKavitaUser.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); });
settingKavitaPass.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); });
settingGotifyUrl.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); });
settingGotifyAppToken.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); });
settingLunaseaWebhook.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); });
settingApiUri.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); });
GetQueue()
.then(json => {
tagTasksQueued.innerText = json.length;
json.forEach(task => {
downloadTasksOutput.appendChild(CreateProgressChild(task));
});
});
downloadTasksPopup.style.display = "flex";
function OpenSettings(){
settingGotifyConfigured.innerText = "❌";
settingLunaseaConfigured.innerText = "❌";
settingKavitaConfigured.innerText = "❌";
settingKomgaConfigured.innerText = "❌";
settingKomgaUrl.value = "";
settingKomgaUser.value = "";
settingKomgaPass.value = "";
settingKavitaUrl.value = "";
settingKavitaUser.value = "";
settingKavitaPass.value = "";
settingGotifyUrl.value = "";
settingGotifyAppToken.value = "";
settingLunaseaWebhook.value = "";
settingApiUri.value = "";
GetSettings().then((json) => {
//console.log(json);
settingDownloadLocation.innerText = json.downloadLocation;
settingApiUri.placeholder = apiUri;
});
GetLibraryConnectors().then((json) => {
//console.log(json);
json.forEach(connector => {
switch(libraryConnectorTypes[connector.libraryType]){
case "Kavita":
settingKavitaConfigured.innerText = "✅";
settingKavitaUrl.placeholder = connector.baseUrl;
settingKavitaUser.placeholder = "***";
settingKavitaPass.placeholder = "***";
break;
case "Komga":
settingKomgaConfigured.innerText = "✅";
settingKomgaUrl.placeholder = connector.baseUrl;
settingKomgaUser.placeholder = "***";
settingKomgaPass.placeholder = "***";
break;
default:
console.log("Unknown type");
console.log(connector);
break;
}
});
});
GetNotificationConnectors().then((json) => {
//console.log(json);
json.forEach(connector => {
switch(notificationConnectorTypes[connector.notificationConnectorType]){
case "Gotify":
settingGotifyUrl.placeholder = connector.endpoint;
settingGotifyAppToken.placeholder = "***";
settingGotifyConfigured.innerText = "✅";
break;
case "LunaSea":
settingLunaseaConfigured.innerText = "✅";
settingLunaseaWebhook.placeholder = connector.id;
break;
default:
console.log("Unknown type");
console.log(connector);
break;
}
});
});
}
function CreateProgressChild(task){
var child = document.createElement("div");
var img = document.createElement('img');
img.src = `imageCache/${task.publication.coverFileNameInCache}`;
child.appendChild(img);
var name = document.createElement("span");
name.innerText = task.publication.sortName;
name.className = "pubTitle";
child.appendChild(name);
function UpdateSettings(){
if(settingApiUri.value != ""){
apiUri = settingApiUri.value;
setCookie("apiUri", apiUri);
Setup();
}
if(settingKomgaUrl.value != "" &&
settingKomgaUser.value != "" &&
settingKomgaPass.value != ""){
UpdateKomga(settingKomgaUrl.value, utf8_to_b64(`${settingKomgaUser.value}:${settingKomgaPass.value}`));
}
if(settingKavitaUrl.value != "" &&
settingKavitaUser.value != "" &&
settingKavitaPass.value != ""){
UpdateKavita(settingKavitaUrl.value, settingKavitaUser.value, settingKavitaPass.value);
}
if(settingGotifyUrl.value != "" &&
settingGotifyAppToken.value != ""){
UpdateGotify(settingGotifyUrl.value, settingGotifyAppToken.value);
}
if(settingLunaseaWebhook.value != ""){
UpdateLunaSea(settingLunaseaWebhook.value);
}
OpenSettings();
Setup();
}
function utf8_to_b64(str) {
return window.btoa(unescape(encodeURIComponent( str )));
}
var progress = document.createElement("progress");
progress.id = `progress${GetValidSelector(task.taskId)}`;
child.appendChild(progress);
function UpdateJobs(){
GetMonitorJobs().then((json) => {
ResetContent();
//console.log(json);
json.forEach(job => {
var mangaView = CreateManga(job.manga, job.mangaConnector.name);
mangaView.addEventListener("click", (event) => {
ShowMangaWindow(job, job.manga, event, false);
});
tasksContent.appendChild(mangaView);
});
});
var progressStr = document.createElement("span");
progressStr.innerText = " \t∞";
progressStr.className = "progressStr";
progressStr.id = `progressStr${GetValidSelector(task.taskId)}`;
child.appendChild(progressStr);
GetWaitingJobs().then((json) => {
jobsQueuedTag.innerText = json.length;
if(task.chapter != undefined){
var chapterNumber = document.createElement("span");
chapterNumber.className = "chapterNumber";
chapterNumber.innerText = `Vol.${task.chapter.volumeNumber} Ch.${task.chapter.chapterNumber}`;
child.appendChild(chapterNumber);
var chapterName = document.createElement("span");
chapterName.className = "chapterName";
chapterName.innerText = task.chapter.name;
child.appendChild(chapterName);
var nowWaitingJobs = [];
json.forEach(job => {
if(!waitingJobs.includes(GetValidSelector(job.id))){
var jobDom = createJob(job);
jobStatusWaiting.appendChild(jobDom);
}
nowWaitingJobs.push(GetValidSelector(job.id));
});
waitingJobs = nowWaitingJobs;
});
jobStatusWaiting.childNodes.forEach(child => {
if(!waitingJobs.includes(child.id))
jobStatusWaiting.removeChild(child);
});
GetRunningJobs().then((json) => {
jobsRunningTag.innerText = json.length;
var nowRunningJobs = [];
json.forEach(job => {
if(!runningJobs.includes(GetValidSelector(job.id))){
var jobDom = createJob(job);
jobStatusRunning.appendChild(jobDom);
}
nowRunningJobs.push(GetValidSelector(job.id));
UpdateJobProgress(job.id);
});
runningJobs = nowRunningJobs;
});
jobStatusRunning.childNodes.forEach(child => {
if(!runningJobs.includes(child.id))
jobStatusRunning.removeChild(child);
});
}
function createJob(jobjson){
var manga;
if(jobjson.chapter != null)
manga = jobjson.chapter.parentManga;
else if(jobjson.manga != null)
manga = jobjson.manga;
else return null;
var wrapper = document.createElement("div");
wrapper.className = "jobWrapper";
wrapper.id = GetValidSelector(jobjson.id);
var image = document.createElement("img");
image.className = "jobImage";
image.src = GetCoverUrl(manga.internalId);
wrapper.appendChild(image);
var title = document.createElement("span");
title.className = "jobTitle";
if(jobjson.chapter != null)
title.innerText = `${manga.sortName} - ${jobjson.chapter.fileName}`;
else if(jobjson.manga != null)
title.innerText = manga.sortName;
wrapper.appendChild(title);
var progressBar = document.createElement("progress");
progressBar.className = "jobProgressBar";
progressBar.id = `jobProgressBar${GetValidSelector(jobjson.id)}`;
wrapper.appendChild(progressBar);
var progressSpan = document.createElement("span");
progressSpan.className = "jobProgressSpan";
progressSpan.id = `jobProgressSpan${GetValidSelector(jobjson.id)}`;
progressSpan.innerText = "0% 00:00:00";
wrapper.appendChild(progressSpan);
var cancelSpan = document.createElement("span");
cancelSpan.className = "jobCancel";
cancelSpan.innerText = "Cancel";
cancelSpan.addEventListener("click", () => CancelJob(jobjson.id));
wrapper.appendChild(cancelSpan);
return wrapper;
}
function ShowJobQueue(){
jobStatusView.style.display = "initial";
}
function UpdateJobProgress(jobId){
GetProgress(jobId).then((json) => {
var progressBar = document.querySelector(`#jobProgressBar${GetValidSelector(jobId)}`);
var progressSpan = document.querySelector(`#jobProgressSpan${GetValidSelector(jobId)}`);
if(progressBar != null && json.progress != 0){
progressBar.value = json.progress;
}
return child;
if(progressSpan != null){
var percentageStr = "0%";
var timeleftStr = "00:00:00";
if(json.progress != 0){
percentageStr = Intl.NumberFormat("en-US", { style: "percent"}).format(json.progress);
timeleftStr = json.timeRemaining.split('.')[0];
}
progressSpan.innerText = `${percentageStr} ${timeleftStr}`;
}
});
}
//Resets the tasks shown
ResetContent();
downloadTasksOutput.replaceChildren();
//Get Tasks and show them
GetDownloadTasks()
.then(json => json.forEach(task => {
var publication = CreatePublication(task.publication, task.connectorName);
publication.addEventListener("click", (event) => ShowPublicationViewerWindow(task.publication.internalId, event, false));
tasksContent.appendChild(publication);
tasks.push(task);
}));
GetRunningTasks()
.then(json => {
tagTasksRunning.innerText = json.length;
json.forEach(task => {
downloadTasksOutput.appendChild(CreateProgressChild(task));
});
});
GetQueue()
.then(json => {
tagTasksQueued.innerText = json.length;
json.forEach(task => {
downloadTasksOutput.appendChild(CreateProgressChild(task));
});
})
setInterval(() => {
//Tasks from API
var cTasks = [];
GetDownloadTasks()
.then(json => json.forEach(task => cTasks.push(task)))
.then(() => {
//Only update view if tasks-amount has changed
if(tasks.length != cTasks.length) {
//Resets the tasks shown
ResetContent();
//Add all currenttasks to view
cTasks.forEach(task => {
var publication = CreatePublication(task.publication, task.connectorName);
publication.addEventListener("click", (event) => ShowPublicationViewerWindow(task.publication.internalId, event, false));
tasksContent.appendChild(publication);
})
tasks = cTasks;
}
}
);
GetRunningTasks()
.then(json => {
tagTasksRunning.innerText = json.length;
});
GetQueue()
.then(json => {
tagTasksQueued.innerText = json.length;
});
}, 1000);
setInterval(() => {
GetRunningTasks().then((json) => {
json.forEach(task => {
if(task.task == 2 || task.task == 4){
document.querySelector(`#progress${GetValidSelector(task.taskId)}`).value = task.progress;
var finishedHours = task.executionApproximatelyRemaining.split(':')[0];
var finishedMinutes = task.executionApproximatelyRemaining.split(':')[1];
var finishedSeconds = task.executionApproximatelyRemaining.split(':')[2].split('.')[0];
document.querySelector(`#progressStr${GetValidSelector(task.taskId)}`).innerText = `${finishedHours}:${finishedMinutes}:${finishedSeconds}`;
}
});
});
},500);
function GetValidSelector(str){
var clean = [...str.matchAll(/[a-zA-Z0-9]*-*_*/g)];
return clean.join('');

View File

@ -148,7 +148,7 @@ content {
}
#settingsPopup{
z-index: 10;
z-index: 300;
}
#settingsPopup popup-content{
@ -205,6 +205,7 @@ publication{
margin: 10px 10px;
padding: 15px 20px;
position: relative;
flex-shrink: 0;
}
publication::after{
@ -305,138 +306,57 @@ popup popup-window popup-content input, select {
border-radius: 3px;
}
#selectPublicationPopup publication {
width: 150px;
height: 250px;
#newMangaPopup > div {
z-index: 3;
position: relative;
}
#createTaskPopup {
z-index: 7;
#newMangaPopup > #newMangaPopupSelector {
width: 600px;
height: 40px;
margin: 80px auto 0;
}
#createTaskPopup input {
height: 30px;
width: 200px;
#newMangaPopup > div > #newMangaConnector, #newMangaTitle, #newMangaTranslatedLanguage {
margin: 0;
display: inline-block;
height: 40px;
}
#createMonitorTaskPopup, #createDownloadChaptersTask {
z-index: 9;
#newMangaPopup #newMangaConnector {
width: 100px;
padding: 0 0 0 5px;
border-radius: 5px 0 0 5px;
border: 0;
border-right: 1px solid darkgray;
}
#createMonitorTaskPopup input[type="number"] {
width: 40px;
#newMangaPopup #newMangaTitle{
width: 445px;
padding: 0 5px 0 5px;
border: 0;
}
#createDownloadChaptersTask popup-content {
flex-direction: column;
align-items: start;
#newMangaPopup #newMangaTranslatedLanguage {
width: 45px;
border-radius: 0 5px 5px 0;
border: 0;
border-left: 1px solid darkgray;
margin-left: -5px;
}
#createDownloadChaptersTask popup-content > * {
margin: 3px 0;
}
#createDownloadChaptersTask #chapterOutput {
max-height: 50vh;
overflow-y: scroll;
}
#createDownloadChaptersTask #chapterOutput .index{
display: inline-block;
width: 25px;
}
#createDownloadChaptersTask #chapterOutput .index::after{
content: ':';
}
#createDownloadChaptersTask #chapterOutput .vol::before{
content: 'Vol.';
}
#createDownloadChaptersTask #chapterOutput .vol{
display: inline-block;
width: 45px;
}
#createDownloadChaptersTask #chapterOutput .ch::before{
content: 'Ch.';
}
#createDownloadChaptersTask #chapterOutput .ch {
display: inline-block;
width: 60px;
}
#downloadTasksPopup popup-window {
left: 0;
top: 80px;
margin: 0 0 0 10px;
height: calc(100vh - 140px);
width: 400px;
max-width: 95vw;
overflow-y: scroll;
}
#downloadTasksPopup popup-content {
flex-direction: column;
align-items: start;
margin: 5px;
}
#downloadTasksPopup popup-content > div {
display: block;
height: 80px;
position: relative;
margin: 5px 0;
}
#downloadTasksPopup popup-content > div > img {
display: block;
position: absolute;
height: 100%;
width: 60px;
left: 0;
top: 0;
object-fit: cover;
border-radius: 4px;
}
#downloadTasksPopup popup-content > div > span {
display: block;
position: absolute;
width: max-content;
}
#downloadTasksPopup popup-content > div > .pubTitle {
left: 70px;
top: 0;
}
#downloadTasksPopup popup-content > div > .chapterName {
left: 70px;
top: 28pt;
}
#downloadTasksPopup popup-content > div > .chapterNumber {
left: 70px;
top: 14pt;
}
#downloadTasksPopup popup-content > div > progress {
display: block;
position: absolute;
left: 150px;
bottom: 0;
width: 200px;
}
#downloadTasksPopup popup-content > div > .progressStr {
display: block;
position: absolute;
left: 70px;
bottom: 0;
width: 70px;
#newMangaResult {
display: none;
flex-direction: row;
justify-content: flex-start;
margin: 5px auto 0;
border-radius: 5px;
padding: 5px;
width: min-content;
max-width: 98%;
max-height: 400px;
overflow-x: scroll;
overflow-y: hidden;
}
blur-background {
@ -444,18 +364,10 @@ blur-background {
height: 100%;
position: absolute;
left: 0;
background-color: black;
opacity: 0.5;
}
#taskSelectOutput{
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: start;
align-content: start;
max-height: 70vh;
overflow-y: scroll;
background: rgba(245, 169, 184, 0.58);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(4.5px);
-webkit-backdrop-filter: blur(4.5px);
}
#publicationViewerPopup{
@ -464,7 +376,7 @@ blur-background {
publication-viewer{
display: block;
width: 450px;
width: 460px;
position: absolute;
top: 200px;
left: 400px;
@ -559,6 +471,10 @@ publication-viewer publication-information publication-interactions publication-
color: red;
}
publication-view publication-information publication-interactions publication-canceltask {
color: yellow;
}
publication-viewer publication-information publication-interactions publication-add {
color: limegreen;
}
@ -600,4 +516,123 @@ footer-tag-popup::before{
left: 0;
bottom: -17px;
border-radius: 0 0 0 5px;
}
#loaderdiv {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
z-index: 200;
}
#loader {
border: 16px solid transparent;
border-top: 16px solid var(--secondary-color);
border-bottom: 16px solid var(--primary-color);
border-radius: 50%;
width: 120px;
height: 120px;
animation: spin 2s linear infinite;
position: absolute;
left: calc(50% - 60px);
top: calc(50% - 120px);
z-index: 201;
}
#loaderText {
position: relative;
margin: 0 auto;
top: calc(50% + 80px);
z-index: 201;
text-align: center;
color: var(--second-background-color);
font-size: 20pt;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
#jobStatusView {
z-index: 50;
}
#jobStatusView > popup-window {
top: 80px;
width: 50%;
max-height: calc(100% - 140px);
display: flex;
flex-direction: row;
flex-wrap: nowrap;
background-color: transparent;
}
#jobStatusView > popup-window > div {
overflow-y: scroll;
overflow-x: hidden;
width: 50%;
margin: 0;
max-height: 100%;
}
#jobStatusView > popup-window > div > div {
overflow-x: hidden;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
width: 100%;
margin: 0;
}
.jobWrapper {
width: 90%;
margin: 2px 5%;
height: 100px;
position: relative;
flex-shrink: 0;
background-color: rgba(187,187,187,0.4);
border-radius: 3px;
}
.jobWrapper > .jobImage {
height: 90%;
width: 20%;
left: 5px;
object-fit: contain;
position: absolute;
top: 5%;
}
.jobWrapper > .jobTitle {
position: absolute;
left: calc(20% + 10px);
top: 5px;
}
.jobWrapper > .jobProgressBar {
position: absolute;
left: calc(20% + 10px);
bottom: calc(12pt + 10px);
width: calc(80% - 20px);
height: 10px;
}
.jobWrapper > .jobProgressSpan {
position: absolute;
right: 10px;
bottom: calc(12pt + 20px);
width: 60%;
text-align: right;
}
.jobWrapper > .jobCancel {
position: absolute;
right: 10px;
bottom: 5px;
font-size: 12pt;
color: var(--secondary-color);
cursor: pointer;
}

View File

@ -1,7 +1,7 @@
version: '3'
services:
tranga-api:
image: glax/tranga-api:latest
image: glax/tranga-api:cuttingedge
container_name: tranga-api
volumes:
- ./tranga:/usr/share/Tranga-API #1 when replacing ./tranga replace #2 with same value
@ -10,7 +10,7 @@ services:
- "6531:6531"
restart: unless-stopped
tranga-website:
image: glax/tranga-website:latest
image: glax/tranga-website:cuttingedge
container_name: tranga-website
volumes:
- ./tranga/imageCache:/usr/share/nginx/html/imageCache:ro #2 when replacing Point to same value as #1/imageCache