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") != ""){ if(getCookie("apiUri") != ""){
apiUri = 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) { function getCookie(cname) {
let name = cname + "="; let name = cname + "=";
let decodedCookie = decodeURIComponent(document.cookie); 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(){ async function GetAvailableControllers(){
var uri = apiUri + "/Connectors"; var uri = apiUri + "/Connectors";
let json = await GetData(uri); let json = await GetData(uri);
return json; return json;
} }
async function GetPublicationFromConnector(connectorName, title){ async function GetPublicationFromConnector(connector, title){
var uri = apiUri + `/Publications/FromConnector?connectorName=${connectorName}&title=${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); let json = await GetData(uri);
return json; return json;
} }
async function GetKnownPublications(){ function GetCoverUrl(internalId){
var uri = apiUri + "/Publications/Known"; 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); let json = await GetData(uri);
return json; return json;
} }
async function GetPublication(internalId){ async function GetWaitingJobs(){
var uri = apiUri + `/Publications/Known?internalId=${internalId}`; var uri = `${apiUri}/Jobs/Waiting`;
let json = await GetData(uri); let json = await GetData(uri);
return json; return json;
} }
async function GetChapters(internalId, connectorName, onlyNew, language){ async function GetMonitorJobs(){
var uri = apiUri + `/Publications/Chapters?internalId=${internalId}&connectorName=${connectorName}&onlyNew=${onlyNew}&language=${language}`; var uri = `${apiUri}/Jobs/MonitorJobs`;
let json = await GetData(uri); let json = await GetData(uri);
return json; return json;
} }
async function GetTaskTypes(){ async function GetProgress(jobId){
var uri = apiUri + "/Tasks/Types"; var uri = `${apiUri}/Jobs/Progress?jobId=${jobId}`;
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";
let json = await GetData(uri); let json = await GetData(uri);
return json; return json;
} }
async function GetSettings(){ async function GetSettings(){
var uri = apiUri + "/Settings"; var uri = `${apiUri}/Settings`;
let json = await GetData(uri); let json = await GetData(uri);
return json; return json;
} }
async function GetKomgaTask(){ async function GetAvailableNotificationConnectors(){
var uri = apiUri + "/Tasks?taskType=UpdateLibraries"; var uri = `${apiUri}/NotificationConnectors/Types`;
let json = await GetData(uri); let json = await GetData(uri);
return json; return json;
} }
function CreateMonitorTask(connectorName, internalId, reoccurrence, language){ async function GetNotificationConnectors(){
var uri = apiUri + `/Tasks/CreateMonitorTask?connectorName=${connectorName}&internalId=${internalId}&reoccurrenceTime=${reoccurrence}&language=${language}`; 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); PostData(uri);
} }
function CreateDownloadChaptersTask(connectorName, internalId, chapters, language){ function CreateDownloadNewChaptersJob(connector, internalId, language){
var uri = apiUri + `/Tasks/CreateDownloadChaptersTask?connectorName=${connectorName}&internalId=${internalId}&chapters=${chapters}&language=${language}`; var uri = `${apiUri}/Jobs/DownloadNewChapters?connector=${connector}&internalId=${internalId}&translatedLanguage=${language}`;
PostData(uri); PostData(uri);
} }
function StartTask(taskType, connectorName, internalId){ function StartJob(jobId){
var uri = apiUri + `/Tasks/Start?taskType=${taskType}&connectorName=${connectorName}&internalId=${internalId}`; var uri = `${apiUri}/Jobs/StartNow?jobId=${jobId}`;
PostData(uri);
}
function EnqueueTask(taskType, connectorName, publicationId){
var uri = apiUri + `/Queue/Enqueue?taskType=${taskType}&connectorName=${connectorName}&publicationId=${publicationId}`;
PostData(uri); PostData(uri);
} }
function UpdateDownloadLocation(downloadLocation){ function UpdateDownloadLocation(downloadLocation){
var uri = apiUri + "/Settings/Update?" var uri = `${apiUri}/Settings/UpdateDownloadLocation?downloadLocation=${downloadLocation}`;
uri += "&downloadLocation="+downloadLocation; PostData(uri);
PostData(uri);
} }
function UpdateKomga(komgaUrl, komgaAuth){ function UpdateKomga(komgaUrl, komgaAuth){
var uri = apiUri + "/Settings/Update?" var uri = `${apiUri}/LibraryConnectors/Update?libraryConnector=Komga&komgaUrl=${komgaUrl}&komgaAuth=${komgaAuth}`;
uri += `&komgaUrl=${komgaUrl}&komgaAuth=${komgaAuth}`;
PostData(uri); PostData(uri);
} }
function UpdateKavita(kavitaUrl, kavitaUser, kavitaPass){ function UpdateKavita(kavitaUrl, kavitaUsername, kavitaPassword){
var uri = apiUri + "/Settings/Update?" var uri = `${apiUri}/LibraryConnectors/Update?libraryConnector=Kavita&kavitaUrl=${kavitaUrl}&kavitaUsername=${kavitaUsername}&kavitaPassword={kavitaPassword}`;
uri += `&kavitaUrl=${kavitaUrl}&kavitaUsername=${kavitaUser}&kavitaPassword=${kavitaPass}`;
PostData(uri); PostData(uri);
} }
function UpdateGotify(gotifyUrl, gotifyAppToken){ function UpdateGotify(gotifyUrl, gotifyAppToken){
var uri = apiUri + "/Settings/Update?" var uri = `${apiUri}/NotificationConnectors/Update?notificationConnector=Gotify&gotifyUrl=${gotifyUrl}&gotifyAppToken=${gotifyAppToken}`;
uri += `&gotifyUrl=${gotifyUrl}&gotifyAppToken=${gotifyAppToken}`;
PostData(uri); PostData(uri);
} }
function UpdateLunaSea(lunaseaWebhook){ function UpdateLunaSea(lunaseaWebhook){
var uri = apiUri + "/Settings/Update?" var uri = `${apiUri}/NotificationConnectors/Update?notificationConnector=LunaSea&lunaseaWebhook=${lunaseaWebhook}`;
uri += `&lunaseaWebhook=${lunaseaWebhook}`;
PostData(uri); PostData(uri);
} }
function DeleteTask(taskType, connectorName, publicationId){ function RemoveJob(jobId){
var uri = apiUri + `/Tasks?taskType=${taskType}&connectorName=${connectorName}&publicationId=${publicationId}`; var uri = `${apiUri}/Jobs?jobId=${jobId}`;
DeleteData(uri); DeleteData(uri);
} }
function DequeueTask(taskType, connectorName, publicationId){ function CancelJob(jobId){
var uri = apiUri + `/Queue/Dequeue?taskType=${taskType}&connectorName=${connectorName}&publicationId=${publicationId}`; var uri = `${apiUri}/Jobs/Cancel?jobId=${jobId}`;
DeleteData(uri); PostData(uri);
}
async function GetQueue(){
var uri = apiUri + "/Queue/List";
let json = await GetData(uri);
return json;
} }

View File

@ -20,94 +20,41 @@
<img id="settingscog" src="media/settings-cogwheel.svg" height="100%" alt="settingscog"> <img id="settingscog" src="media/settings-cogwheel.svg" height="100%" alt="settingscog">
</topbar> </topbar>
<viewport> <viewport>
<div id="loaderdiv">
<blur-background></blur-background>
<div id="loader"></div>
<p id="loaderText">Check your Settings > API-URI</p>
</div>
<content> <content>
<div id="addPublication"> <div id="addPublication">
<p>+</p> <p>+</p>
</div> </div>
<publication> <publication onclick="ShowNewMangaSearch()">
<img alt="cover" src="media/cover.jpg"> <img alt="cover" src="media/cover.jpg">
<publication-information> <publication-information>
<connector-name class="pill">MangaDex</connector-name> <connector-name class="pill">Sample</connector-name>
<publication-name>Tensei Pandemic</publication-name> <publication-name>Best Manga there is</publication-name>
</publication-information> </publication-information>
</publication> </publication>
</content> </content>
<popup id="selectPublicationPopup"> <popup id="newMangaPopup">
<blur-background id="blurBackgroundTaskPopup"></blur-background> <blur-background id="blurBackgroundNewMangaPopup" onclick="newMangaPopup.style.display = 'none';"></blur-background>
<popup-window> <div id="newMangaPopupSelector">
<popup-title>Select Publication</popup-title> <select id="newMangaConnector" />
<popup-content> <input type="text" placeholder="Title" id="newMangaTitle" />
<div> <select id="newMangaTranslatedLanguage">
<label for="connectors">Connector</label> <option selected="selected">en</option>
<select id="connectors"> <option>it</option>
<option value=""></option> <option>de</option>
</select> </select>
</div> </div>
<div> <div id="newMangaResult"></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> </popup>
<popup id="settingsPopup"> <popup id="settingsPopup">
<blur-background id="blurBackgroundSettingsPopup"></blur-background> <blur-background id="blurBackgroundSettingsPopup" onclick="
settingsPopup.style.display = 'none';"></blur-background>
<popup-window> <popup-window>
<popup-title>Settings</popup-title> <popup-title>Settings</popup-title>
<popup-content> <popup-content>
@ -145,29 +92,53 @@
<label for="lunaseaWebhook"></label><input placeholder="device/:id or user/:id" id="lunaseaWebhook" type="text"> <label for="lunaseaWebhook"></label><input placeholder="device/:id or user/:id" id="lunaseaWebhook" type="text">
</div> </div>
<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="UpdateSettings()">
<input type="submit" value="Update" onclick="UpdateLibrarySettings()">
</div> </div>
</popup-content> </popup-content>
</popup-window> </popup-window>
</popup> </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"> <popup id="jobStatusView">
<blur-background id="blurBackgroundTasksQueuePopup"></blur-background> <blur-background id="blurBackgroundJobStatus" onclick="jobStatusView.style.display= 'none';"></blur-background>
<popup-window> <popup-window>
<popup-title>Task Progress</popup-title> <div>
<popup-content> <div id="jobStatusRunning" style="border-right: 1px solid gray;"></div>
</div>
</popup-content> <div>
<div id="jobStatusWaiting" style="border-left: 1px solid gray;"></div>
</div>
</popup-window> </popup-window>
</popup> </popup>
</viewport> </viewport>
<footer> <footer>
<div onclick="ShowTasksQueue();"> <div onclick="ShowJobQueue();">
<img src="media/running.svg" alt="running"><div id="tasksRunningTag">0</div> <img src="media/running.svg" alt="running"><div id="jobsRunningTag">0</div>
</div> </div>
<div onclick="ShowTasksQueue();"> <div onclick="ShowJobQueue();">
<img src="media/queue.svg" alt="queue"><div id="tasksQueuedTag">0</div> <img src="media/queue.svg" alt="queue"><div id="jobsQueuedTag">0</div>
</div> </div>
<p id="madeWith">Made with Blåhaj 🦈</p> <p id="madeWith">Made with Blåhaj 🦈</p>
</footer> </footer>

View File

@ -1,31 +1,26 @@
let publications = []; let runningJobs = [];
let tasks = []; let waitingJobs = [];
let toEditId; let notificationConnectorTypes = [];
let libraryConnectorTypes = [];
let selectedManga;
let selectedJob;
const searchBox = document.querySelector("#searchbox"); 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 settingsPopup = document.querySelector("#settingsPopup");
const settingsCog = document.querySelector("#settingscog"); const settingsCog = document.querySelector("#settingscog");
const selectRecurrence = document.querySelector("#selectReccurrence");
const tasksContent = document.querySelector("content"); const tasksContent = document.querySelector("content");
const selectPublicationPopup = document.querySelector("#selectPublicationPopup"); const createMonitorTaskButton = document.querySelector("#createMonitoJobButton");
const createMonitorTaskButton = document.querySelector("#createMonitorTaskButton"); const createDownloadChapterTaskButton = document.querySelector("#createDownloadChapterJobButton");
const createDownloadChapterTaskButton = document.querySelector("#createDownloadChapterTaskButton"); const startJobButton = document.querySelector("#startJobButton");
const createMonitorTaskPopup = document.querySelector("#createMonitorTaskPopup"); const cancelJobButton = document.querySelector("#cancelJobButton");
const createDownloadChaptersTask = document.querySelector("#createDownloadChaptersTask"); const deleteJobButton = document.querySelector("#deleteJobButton");
const chapterOutput = document.querySelector("#chapterOutput"); const mangaViewerPopup = document.querySelector("#publicationViewerPopup");
const selectedChapters = document.querySelector("#selectedChapters"); const mangaViewerWindow = document.querySelector("publication-viewer");
const publicationViewerPopup = document.querySelector("#publicationViewerPopup"); const mangaViewerDescription = document.querySelector("#publicationViewerDescription");
const publicationViewerWindow = document.querySelector("publication-viewer"); const mangaViewerName = document.querySelector("#publicationViewerName");
const publicationViewerDescription = document.querySelector("#publicationViewerDescription"); const mangaViewerTags = document.querySelector("#publicationViewerTags");
const publicationViewerName = document.querySelector("#publicationViewerName"); const mangaViewerAuthor = document.querySelector("#publicationViewerAuthor");
const publicationViewerTags = document.querySelector("#publicationViewerTags"); const mangaViewCover = document.querySelector("#pubviewcover");
const publicationViewerAuthor = document.querySelector("#publicationViewerAuthor");
const pubviewcover = document.querySelector("#pubviewcover");
const publicationDelete = document.querySelector("publication-delete");
const publicationTaskStart = document.querySelector("publication-starttask");
const settingDownloadLocation = document.querySelector("#downloadLocation"); const settingDownloadLocation = document.querySelector("#downloadLocation");
const settingKomgaUrl = document.querySelector("#komgaUrl"); const settingKomgaUrl = document.querySelector("#komgaUrl");
const settingKomgaUser = document.querySelector("#komgaUsername"); const settingKomgaUser = document.querySelector("#komgaUsername");
@ -36,166 +31,55 @@ const settingKavitaPass = document.querySelector("#kavitaPassword");
const settingGotifyUrl = document.querySelector("#gotifyUrl"); const settingGotifyUrl = document.querySelector("#gotifyUrl");
const settingGotifyAppToken = document.querySelector("#gotifyAppToken"); const settingGotifyAppToken = document.querySelector("#gotifyAppToken");
const settingLunaseaWebhook = document.querySelector("#lunaseaWebhook"); const settingLunaseaWebhook = document.querySelector("#lunaseaWebhook");
const libraryUpdateTime = document.querySelector("#libraryUpdateTime");
const settingKomgaConfigured = document.querySelector("#komgaConfigured"); const settingKomgaConfigured = document.querySelector("#komgaConfigured");
const settingKavitaConfigured = document.querySelector("#kavitaConfigured"); const settingKavitaConfigured = document.querySelector("#kavitaConfigured");
const settingGotifyConfigured = document.querySelector("#gotifyConfigured"); const settingGotifyConfigured = document.querySelector("#gotifyConfigured");
const settingLunaseaConfigured = document.querySelector("#lunaseaConfigured"); const settingLunaseaConfigured = document.querySelector("#lunaseaConfigured");
const settingApiUri = document.querySelector("#settingApiUri"); const settingApiUri = document.querySelector("#settingApiUri");
const tagTasksRunning = document.querySelector("#tasksRunningTag"); const newMangaPopup = document.querySelector("#newMangaPopup");
const tagTasksQueued = document.querySelector("#tasksQueuedTag"); const newMangaConnector = document.querySelector("#newMangaConnector");
const downloadTasksPopup = document.querySelector("#downloadTasksPopup"); const newMangaTitle = document.querySelector("#newMangaTitle");
const downloadTasksOutput = downloadTasksPopup.querySelector("popup-content"); 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()); function Setup(){
settingsCog.addEventListener("click", () => OpenSettings()); Ping().then((ret) => {
document.querySelector("#blurBackgroundSettingsPopup").addEventListener("click", () => settingsPopup.style.display = "none"); loaderdiv.style.display = 'none';
document.querySelector("#blurBackgroundTaskPopup").addEventListener("click", () => selectPublicationPopup.style.display = "none");
document.querySelector("#blurBackgroundPublicationPopup").addEventListener("click", () => HidePublicationPopup()); GetAvailableNotificationConnectors().then((json) => {
document.querySelector("#blurBackgroundCreateMonitorTaskPopup").addEventListener("click", () => createMonitorTaskPopup.style.display = "none"); json.forEach(connector => {
document.querySelector("#blurBackgroundCreateDownloadChaptersTask").addEventListener("click", () => createDownloadChaptersTask.style.display = "none"); notificationConnectorTypes[connector.Key] = connector.Value;
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 DownloadChapterTaskClick(){ GetAvailableLibraryConnectors().then((json) => {
CreateDownloadChaptersTask(connectorSelect.value, toEditId, selectedChapters.value, "en"); json.forEach(connector => {
HidePublicationPopup(); libraryConnectorTypes[connector.Key] = connector.Value;
createDownloadChaptersTask.style.display = "none"; });
selectPublicationPopup.style.display = "none"; });
}
function DeleteTaskClick(){ GetAvailableControllers().then((json) => {
taskToDelete = tasks.filter(tTask => tTask.publication.internalId === toEditId)[0]; json.forEach(connector => {
DeleteTask("MonitorPublication", taskToDelete.connectorName, toEditId); var option = document.createElement('option');
HidePublicationPopup(); option.value = connector;
} option.innerText = connector;
newMangaConnector.appendChild(option);
function StartTaskClick(){ });
var toEditTask = tasks.filter(task => task.publication.internalId == toEditId)[0]; });
StartTask("MonitorPublication", toEditTask.connectorName, toEditId);
HidePublicationPopup(); UpdateJobs();
setInterval(() => {
UpdateJobs();
}, 1000);
});
} }
Setup();
function ResetContent(){ function ResetContent(){
//Delete everything //Delete everything
@ -207,45 +91,120 @@ function ResetContent(){
var plus = document.createElement("p"); var plus = document.createElement("p");
plus.innerText = "+"; plus.innerText = "+";
add.appendChild(plus); add.appendChild(plus);
add.addEventListener("click", () => ShowNewTaskWindow()); add.addEventListener("click", () => ShowNewMangaSearch());
tasksContent.appendChild(add); 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 //Show popup
publicationViewerPopup.style.display = "block"; mangaViewerPopup.style.display = "block";
//Set position to mouse-position //Set position to mouse-position
if(event.clientY < window.innerHeight - publicationViewerWindow.offsetHeight) if(event.clientY < window.innerHeight - mangaViewerWindow.offsetHeight)
publicationViewerWindow.style.top = `${event.clientY}px`; mangaViewerWindow.style.top = `${event.clientY}px`;
else else
publicationViewerWindow.style.top = `${event.clientY - publicationViewerWindow.offsetHeight}px`; mangaViewerWindow.style.top = `${event.clientY - mangaViewerWindow.offsetHeight}px`;
if(event.clientX < window.innerWidth - publicationViewerWindow.offsetWidth) if(event.clientX < window.innerWidth - mangaViewerWindow.offsetWidth)
publicationViewerWindow.style.left = `${event.clientX}px`; mangaViewerWindow.style.left = `${event.clientX}px`;
else else
publicationViewerWindow.style.left = `${event.clientX - publicationViewerWindow.offsetWidth}px`; mangaViewerWindow.style.left = `${event.clientX - mangaViewerWindow.offsetWidth}px`;
//Edit information inside the window //Edit information inside the window
var publication = publications.filter(pub => pub.internalId === publicationId)[0]; mangaViewerName.innerText = manga.sortName;
publicationViewerName.innerText = publication.sortName; mangaViewerTags.innerText = manga.tags.join(", ");
publicationViewerTags.innerText = publication.tags.join(", "); mangaViewerDescription.innerText = manga.description;
publicationViewerDescription.innerText = publication.description; mangaViewerAuthor.innerText = manga.authors.join(',');
publicationViewerAuthor.innerText = publication.authors.join(','); mangaViewCover.src = GetCoverUrl(manga.internalId);
pubviewcover.src = `imageCache/${publication.coverFileNameInCache}`; toEditId = manga.internalId;
toEditId = publicationId;
//Check what action should be listed //Check what action should be listed
if(add){ if(add){
createMonitorTaskButton.style.display = "initial"; createMonitorJobButton.style.display = "initial";
createDownloadChapterTaskButton.style.display = "initial"; createDownloadChapterJobButton.style.display = "none";
publicationDelete.style.display = "none"; cancelJobButton.style.display = "none";
publicationTaskStart.style.display = "none"; startJobButton.style.display = "none";
deleteJobButton.style.display = "none";
} }
else{ else{
createMonitorTaskButton.style.display = "none"; createMonitorJobButton.style.display = "none";
createDownloadChapterTaskButton.style.display = "none"; createDownloadChapterJobButton.style.display = "none";
publicationDelete.style.display = "initial"; cancelJobButton.style.display = "initial";
publicationTaskStart.style.display = "initial"; startJobButton.style.display = "initial";
deleteJobButton.style.display = "initial";
} }
} }
@ -253,108 +212,8 @@ function HidePublicationPopup(){
publicationViewerPopup.style.display = "none"; publicationViewerPopup.style.display = "none";
} }
function ShowNewTaskWindow(){ searchBox.addEventListener("keyup", () => FilterResults());
selectPublication.replaceChildren(); //Filter shown jobs
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 )));
}
function FilterResults(){ function FilterResults(){
if(searchBox.value.length > 0){ if(searchBox.value.length > 0){
tasksContent.childNodes.forEach(publication => { tasksContent.childNodes.forEach(publication => {
@ -377,147 +236,249 @@ function FilterResults(){
} }
} }
function ShowTasksQueue(){ settingsCog.addEventListener("click", () => {
OpenSettings();
settingsPopup.style.display = "flex";
});
downloadTasksOutput.replaceChildren(); settingKomgaUrl.addEventListener("keypress", (event) => { { if(event.key === "Enter") UpdateSettings(); } });
GetRunningTasks() settingKomgaUser.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); });
.then(json => { settingKomgaPass.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); });
tagTasksRunning.innerText = json.length; settingKavitaUrl.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); });
json.forEach(task => { settingKavitaUser.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); });
if(task.task == 2 || task.task == 4) { settingKavitaPass.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); });
downloadTasksOutput.appendChild(CreateProgressChild(task)); settingGotifyUrl.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); });
document.querySelector(`#progress${GetValidSelector(task.taskId)}`).value = task.progress; settingGotifyAppToken.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); });
var finishedHours = task.executionApproximatelyRemaining.split(':')[0]; settingLunaseaWebhook.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); });
var finishedMinutes = task.executionApproximatelyRemaining.split(':')[1]; settingApiUri.addEventListener("keypress", (event) => { if(event.key === "Enter") UpdateSettings(); });
var finishedSeconds = task.executionApproximatelyRemaining.split(':')[2].split('.')[0];
document.querySelector(`#progressStr${GetValidSelector(task.taskId)}`).innerText = `${finishedHours}:${finishedMinutes}:${finishedSeconds}`;
}
});
});
GetQueue() function OpenSettings(){
.then(json => { settingGotifyConfigured.innerText = "❌";
tagTasksQueued.innerText = json.length; settingLunaseaConfigured.innerText = "❌";
json.forEach(task => { settingKavitaConfigured.innerText = "❌";
downloadTasksOutput.appendChild(CreateProgressChild(task)); settingKomgaConfigured.innerText = "❌";
}); settingKomgaUrl.value = "";
}); settingKomgaUser.value = "";
downloadTasksPopup.style.display = "flex"; 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){ function UpdateSettings(){
var child = document.createElement("div"); if(settingApiUri.value != ""){
var img = document.createElement('img'); apiUri = settingApiUri.value;
img.src = `imageCache/${task.publication.coverFileNameInCache}`; setCookie("apiUri", apiUri);
child.appendChild(img); Setup();
}
var name = document.createElement("span");
name.innerText = task.publication.sortName; if(settingKomgaUrl.value != "" &&
name.className = "pubTitle"; settingKomgaUser.value != "" &&
child.appendChild(name); 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"); function UpdateJobs(){
progress.id = `progress${GetValidSelector(task.taskId)}`; GetMonitorJobs().then((json) => {
child.appendChild(progress); 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"); GetWaitingJobs().then((json) => {
progressStr.innerText = " \t∞"; jobsQueuedTag.innerText = json.length;
progressStr.className = "progressStr";
progressStr.id = `progressStr${GetValidSelector(task.taskId)}`;
child.appendChild(progressStr);
if(task.chapter != undefined){ var nowWaitingJobs = [];
var chapterNumber = document.createElement("span");
chapterNumber.className = "chapterNumber"; json.forEach(job => {
chapterNumber.innerText = `Vol.${task.chapter.volumeNumber} Ch.${task.chapter.chapterNumber}`; if(!waitingJobs.includes(GetValidSelector(job.id))){
child.appendChild(chapterNumber); var jobDom = createJob(job);
jobStatusWaiting.appendChild(jobDom);
var chapterName = document.createElement("span"); }
chapterName.className = "chapterName"; nowWaitingJobs.push(GetValidSelector(job.id));
chapterName.innerText = task.chapter.name; });
child.appendChild(chapterName); 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;
} }
if(progressSpan != null){
var percentageStr = "0%";
return child; 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){ function GetValidSelector(str){
var clean = [...str.matchAll(/[a-zA-Z0-9]*-*_*/g)]; var clean = [...str.matchAll(/[a-zA-Z0-9]*-*_*/g)];
return clean.join(''); return clean.join('');

View File

@ -148,7 +148,7 @@ content {
} }
#settingsPopup{ #settingsPopup{
z-index: 10; z-index: 300;
} }
#settingsPopup popup-content{ #settingsPopup popup-content{
@ -205,6 +205,7 @@ publication{
margin: 10px 10px; margin: 10px 10px;
padding: 15px 20px; padding: 15px 20px;
position: relative; position: relative;
flex-shrink: 0;
} }
publication::after{ publication::after{
@ -305,138 +306,57 @@ popup popup-window popup-content input, select {
border-radius: 3px; border-radius: 3px;
} }
#selectPublicationPopup publication { #newMangaPopup > div {
width: 150px; z-index: 3;
height: 250px; position: relative;
} }
#createTaskPopup { #newMangaPopup > #newMangaPopupSelector {
z-index: 7; width: 600px;
height: 40px;
margin: 80px auto 0;
} }
#createTaskPopup input { #newMangaPopup > div > #newMangaConnector, #newMangaTitle, #newMangaTranslatedLanguage {
height: 30px; margin: 0;
width: 200px; display: inline-block;
height: 40px;
} }
#createMonitorTaskPopup, #createDownloadChaptersTask { #newMangaPopup #newMangaConnector {
z-index: 9; width: 100px;
padding: 0 0 0 5px;
border-radius: 5px 0 0 5px;
border: 0;
border-right: 1px solid darkgray;
} }
#createMonitorTaskPopup input[type="number"] { #newMangaPopup #newMangaTitle{
width: 40px; width: 445px;
padding: 0 5px 0 5px;
border: 0;
} }
#createDownloadChaptersTask popup-content { #newMangaPopup #newMangaTranslatedLanguage {
flex-direction: column; width: 45px;
align-items: start; border-radius: 0 5px 5px 0;
border: 0;
border-left: 1px solid darkgray;
margin-left: -5px;
} }
#createDownloadChaptersTask popup-content > * { #newMangaResult {
margin: 3px 0; display: none;
} flex-direction: row;
justify-content: flex-start;
#createDownloadChaptersTask #chapterOutput { margin: 5px auto 0;
max-height: 50vh; border-radius: 5px;
overflow-y: scroll; padding: 5px;
} width: min-content;
max-width: 98%;
#createDownloadChaptersTask #chapterOutput .index{ max-height: 400px;
display: inline-block; overflow-x: scroll;
width: 25px; overflow-y: hidden;
}
#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;
} }
blur-background { blur-background {
@ -444,18 +364,10 @@ blur-background {
height: 100%; height: 100%;
position: absolute; position: absolute;
left: 0; left: 0;
background-color: black; background: rgba(245, 169, 184, 0.58);
opacity: 0.5; box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
} backdrop-filter: blur(4.5px);
-webkit-backdrop-filter: blur(4.5px);
#taskSelectOutput{
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: start;
align-content: start;
max-height: 70vh;
overflow-y: scroll;
} }
#publicationViewerPopup{ #publicationViewerPopup{
@ -464,7 +376,7 @@ blur-background {
publication-viewer{ publication-viewer{
display: block; display: block;
width: 450px; width: 460px;
position: absolute; position: absolute;
top: 200px; top: 200px;
left: 400px; left: 400px;
@ -559,6 +471,10 @@ publication-viewer publication-information publication-interactions publication-
color: red; color: red;
} }
publication-view publication-information publication-interactions publication-canceltask {
color: yellow;
}
publication-viewer publication-information publication-interactions publication-add { publication-viewer publication-information publication-interactions publication-add {
color: limegreen; color: limegreen;
} }
@ -600,4 +516,123 @@ footer-tag-popup::before{
left: 0; left: 0;
bottom: -17px; bottom: -17px;
border-radius: 0 0 0 5px; 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' version: '3'
services: services:
tranga-api: tranga-api:
image: glax/tranga-api:latest image: glax/tranga-api:cuttingedge
container_name: tranga-api container_name: tranga-api
volumes: volumes:
- ./tranga:/usr/share/Tranga-API #1 when replacing ./tranga replace #2 with same value - ./tranga:/usr/share/Tranga-API #1 when replacing ./tranga replace #2 with same value
@ -10,7 +10,7 @@ services:
- "6531:6531" - "6531:6531"
restart: unless-stopped restart: unless-stopped
tranga-website: tranga-website:
image: glax/tranga-website:latest image: glax/tranga-website:cuttingedge
container_name: tranga-website container_name: tranga-website
volumes: volumes:
- ./tranga/imageCache:/usr/share/nginx/html/imageCache:ro #2 when replacing Point to same value as #1/imageCache - ./tranga/imageCache:/usr/share/nginx/html/imageCache:ro #2 when replacing Point to same value as #1/imageCache