MH - Journal Log Tracker

Tracks when your journal log is going to show up next and shows a button to access your last journal log

当前为 2024-10-15 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name MH - Journal Log Tracker
  3. // @version 1.4.0
  4. // @description Tracks when your journal log is going to show up next and shows a button to access your last journal log
  5. // @author hannadDev
  6. // @namespace https://greasyfork.org/en/users/1238393-hannaddev
  7. // @match https://www.mousehuntgame.com/*
  8. // @icon https://www.mousehuntgame.com/images/ui/journal/themes/classic_thumb.gif
  9. // @require https://cdn.jsdelivr.net/npm/mh-assets@1.0.8/scripts/utils.js
  10. // @require https://cdn.jsdelivr.net/npm/mousehunt-utils@1.10.5/mousehunt-utils.js
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. 'use strict';
  16.  
  17. // #region Variables
  18. let isDebug = false;
  19.  
  20. const localStorageKey = `mh-journal-log-tracker`;
  21. const PAGE_SIZE = 10;
  22.  
  23. let storedData = {};
  24. //#endregion
  25.  
  26. //#region Loading external assets
  27. const mainStylesheetUrl = "https://cdn.jsdelivr.net/npm/mh-assets@1.0.8/stylesheets/main.css";
  28. const scriptSpecificStylesheetUrl = "https://cdn.jsdelivr.net/npm/mh-assets@1.0.8/stylesheets/journal-log-tracker.css";
  29.  
  30. hd_utils.addStyleElement(mainStylesheetUrl);
  31. hd_utils.addStyleElement(scriptSpecificStylesheetUrl);
  32. //#endregion
  33.  
  34. //#region Initialization
  35. const journalContainerObserver = new MutationObserver(function (mutations) {
  36. if (isDebug) {
  37. console.log('[Journal Container Observer] Mutation');
  38. for (const mutation of mutations) {
  39. console.log({ mutation });
  40. console.log(mutation.target);
  41. }
  42. }
  43.  
  44. // Only save if something was added.
  45. if (mutations.some(v => v.type === 'childList' && v.addedNodes.length > 0 && v.target.className !== 'journaldate')) {
  46. tryToScrapeJournal();
  47. }
  48. });
  49.  
  50. const mousehuntContainerObserver = new MutationObserver(function (mutations) {
  51. if (isDebug) {
  52. console.log('[Mousehunt Container Observer] Mutation');
  53. for (const mutation of mutations) {
  54. console.log({ mutation });
  55. console.log(mutation.target);
  56. }
  57. }
  58.  
  59. // Check if camp or journal pages
  60. const mhContainer = document.getElementById('mousehuntContainer');
  61. if (mhContainer && mhContainer.classList && (mhContainer.classList.contains('PageCamp') || mhContainer.classList.contains('PageJournal'))) {
  62. showButton();
  63. }
  64. });
  65.  
  66. function activateJournalMutationObserver() {
  67. let journalContainerObserverTarget = document.querySelector(`#journalContainer[data-owner="${user.user_id}"] .content`);
  68.  
  69. if (journalContainerObserverTarget) {
  70. journalContainerObserver.observe(journalContainerObserverTarget, {
  71. childList: true,
  72. subtree: true
  73. });
  74. }
  75. }
  76.  
  77. function activateMousehuntContainerMutationObserver() {
  78. let mousehuntContainerObserverTarget = document.getElementById('mousehuntContainer');
  79.  
  80. if (mousehuntContainerObserverTarget) {
  81. mousehuntContainerObserver.observe(mousehuntContainerObserverTarget, {
  82. attributes: true,
  83. attributeFilter: ['class']
  84. });
  85. }
  86. }
  87.  
  88. function Initialize() {
  89. if (isDebug) console.log(`Initializing.`);
  90.  
  91. storedData = getStoredData();
  92.  
  93. activateMousehuntContainerMutationObserver();
  94. activateJournalMutationObserver();
  95. showButton();
  96.  
  97. onRequest(() => { tryToScrapeJournal(); }, 'managers/ajax/turns/activeturn.php');
  98. }
  99.  
  100. Initialize();
  101. //#endregion
  102.  
  103. // #region LocalStorage Methods
  104. function getStoredData() {
  105. const savedData = localStorage.getItem(localStorageKey);
  106.  
  107. if (savedData !== null) {
  108. return JSON.parse(savedData);
  109. }
  110.  
  111. return {
  112. logs: {},
  113. lastSavedEntryId: -1
  114. };
  115. }
  116.  
  117. function setData(stats) {
  118. localStorage.setItem(localStorageKey, JSON.stringify(stats));
  119. }
  120.  
  121. function deleteLog(logId) {
  122. delete storedData.logs[logId];
  123. if (Number.parseInt(storedData.lastSavedEntryId) === Number.parseInt(logId)) {
  124. const keys = Object.keys(storedData.logs);
  125.  
  126. storedData.lastSavedEntryId = -1;
  127. if (keys.length > 0) {
  128. for (let k = 0; k < keys.length; k++) {
  129. if (storedData.lastSavedEntryId < Number.parseInt(keys[k])) {
  130. storedData.lastSavedEntryId = Number.parseInt(keys[k]);
  131. }
  132. }
  133. }
  134. }
  135. }
  136. // #endregion
  137.  
  138. // #region Journal Scraping Methods
  139. function tryToScrapeJournal() {
  140. if (!hd_utils.mh.isOwnJournal()) {
  141. return;
  142. }
  143.  
  144. scrapeJournal();
  145. }
  146.  
  147. function scrapeJournal() {
  148. const entries = document.querySelectorAll('.entry');
  149.  
  150. let addedNewEntries = false;
  151. for (const entry of entries) {
  152. let entryId = entry.dataset.entryId
  153.  
  154. if (!entryId) return;
  155.  
  156. entryId = Number.parseInt(entryId);
  157.  
  158. if (entry.className.search(/(log_summary)/) !== -1) {
  159. if (storedData.logs.hasOwnProperty(entryId)) {
  160. if (isDebug) console.log(`Entry ${entryId} already stored`);
  161. }
  162. else {
  163. if (isDebug) console.log(`New entry ${entryId}`);
  164. const entryInfo = extractInfoFromEntry(entry);
  165.  
  166. if (entryInfo != null) {
  167. storedData.logs[entryId] = entryInfo;
  168.  
  169. if (storedData.lastSavedEntryId < entryId) {
  170. storedData.lastSavedEntryId = entryId;
  171. }
  172.  
  173. addedNewEntries = true;
  174. }
  175. }
  176. }
  177. }
  178.  
  179. if (addedNewEntries) {
  180. setData(storedData);
  181. showButton();
  182. }
  183. }
  184.  
  185. function extractInfoFromEntry(entry) {
  186. const entryInfo = {};
  187.  
  188. const splitStringDate = entry.querySelector(".journaldate").innerHTML.split("-")[0].trim().split(" ");
  189.  
  190. try {
  191. const date = new Date();
  192. date.setMilliseconds(0);
  193. date.setSeconds(0);
  194.  
  195. date.setHours(splitStringDate[0].split(":")[0]);
  196. date.setMinutes(splitStringDate[0].split(":")[1]);
  197.  
  198. if (date.getHours() !== 12 && splitStringDate[1] === "pm") {
  199. date.setHours(date.getHours() + 12);
  200. } else if (date.getHours() === 12 && splitStringDate[1] === "am") {
  201. date.setHours(date.getHours() - 12);
  202. }
  203.  
  204. if (date.getTime() > Date.now()) {
  205. date.setDate(date.getDate() - 1);
  206. }
  207.  
  208. entryInfo.Timestamp = date.getTime();
  209. } catch (e) {
  210. console.log(e);
  211. return null;
  212. }
  213.  
  214. entryInfo.Duration = entry.querySelector(".reportSubtitle").innerHTML.replace("Last ", "");
  215.  
  216. const tableBody = entry.querySelector(".journalbody .journaltext table tbody");
  217. const tdElements = tableBody.querySelectorAll(".leftSide, .rightSide");
  218. for (let i = 0; i < tdElements.length; ++i) {
  219. if (tdElements[i].innerHTML.includes("Catches:")) {
  220. entryInfo.Catches = Number.parseInt(tdElements[i].nextSibling.innerHTML);
  221. } else if (tdElements[i].innerHTML.includes("Misses:")) {
  222. entryInfo.Ftc = Number.parseInt(tdElements[i].nextSibling.innerHTML);
  223. } else if (tdElements[i].innerHTML.includes("Fail to Attract:")) {
  224. entryInfo.Fta = Number.parseInt(tdElements[i].nextSibling.innerHTML);
  225. } else if (tdElements[i].innerHTML.includes("Gained:")) {
  226. // Left is gold. Right is points
  227. if (tdElements[i].classList.contains('leftSide')) {
  228. if (tdElements[i].nextSibling) {
  229. entryInfo.GoldGained = Number.parseInt(tdElements[i].nextSibling.innerHTML.replaceAll(',', ''));
  230. }
  231. } else {
  232. if (tdElements[i].nextSibling) {
  233. entryInfo.PointsGained = Number.parseInt(tdElements[i].nextSibling.innerHTML.replaceAll(',', ''));
  234. }
  235. }
  236. } else if (tdElements[i].innerHTML.includes("Lost:")) {
  237. // Left is gold. Right is points
  238. if (tdElements[i].classList.contains('leftSide')) {
  239. if (tdElements[i].nextSibling) {
  240. entryInfo.GoldLost = Number.parseInt(tdElements[i].nextSibling.innerHTML.replaceAll(',', ''));
  241. }
  242. } else {
  243. if (tdElements[i].nextSibling) {
  244. entryInfo.PointsLost = Number.parseInt(tdElements[i].nextSibling.innerHTML.replaceAll(',', ''));
  245. }
  246. }
  247. } else if (tdElements[i].innerHTML.includes("Total:")) {
  248. // Left is gold. Right is points
  249. if (tdElements[i].classList.contains('leftSide')) {
  250. if (tdElements[i].nextSibling) {
  251. entryInfo.GoldTotal = Number.parseInt(tdElements[i].nextSibling.innerHTML.replaceAll(',', ''));
  252. }
  253. } else {
  254. if (tdElements[i].nextSibling) {
  255. entryInfo.PointsTotal = Number.parseInt(tdElements[i].nextSibling.innerHTML.replaceAll(',', ''));
  256. }
  257. }
  258. }
  259. }
  260.  
  261. const link = tableBody.querySelector("a");
  262.  
  263. entryInfo.OpenSummaryMethod = link.onclick.toString().split("onclick(event) {")[1].split("return false;")[0].trim();
  264.  
  265. return entryInfo;
  266. }
  267. // #endregion
  268.  
  269. // #region Export/Import Methods
  270. function exportData() {
  271. let filename = `${user.user_id}_${Date.now()}.json`;
  272. let contentType = "application/json;charset=utf-8;";
  273.  
  274. if (window.navigator && window.navigator.msSaveOrOpenBlob) {
  275. var blob = new Blob([decodeURIComponent(encodeURI(JSON.stringify(storedData)))], { type: contentType });
  276. navigator.msSaveOrOpenBlob(blob, filename);
  277. } else {
  278. var a = document.createElement('a');
  279. a.download = filename;
  280. a.href = 'data:' + contentType + ',' + encodeURIComponent(JSON.stringify(storedData));
  281. a.target = '_blank';
  282. document.body.appendChild(a);
  283. a.click();
  284. document.body.removeChild(a);
  285. }
  286. }
  287.  
  288. function importData() {
  289. let input = document.createElement('input');
  290. input.type = 'file';
  291. input.onchange = _ => {
  292. let files = Array.from(input.files);
  293.  
  294. if (files.length > 0 && files[0].type === "application/json") {
  295. let fr = new FileReader();
  296.  
  297. fr.onload = function () {
  298. const importedData = JSON.parse(this.result);
  299. filterAndSaveImportedData(importedData);
  300. }
  301.  
  302. fr.readAsText(files[0]);
  303. } else {
  304. console.log("Invalid file imported");
  305. }
  306. };
  307. input.click();
  308. }
  309.  
  310. function filterAndSaveImportedData(importedData) {
  311. let importedLogCount = 0;
  312. for (const key in importedData.logs) {
  313. if (!storedData.logs.hasOwnProperty(key)) {
  314. storedData.logs[key] = importedData.logs[key];
  315.  
  316. if (Number.parseInt(storedData.lastSavedEntryId) < Number.parseInt(key)) {
  317. storedData.lastSavedEntryId = Number.parseInt(key);
  318. }
  319.  
  320. importedLogCount++;
  321. }
  322. }
  323.  
  324. console.log(`Imported ${importedLogCount} logs`);
  325.  
  326. if (importedLogCount > 0) {
  327. setData(storedData);
  328. showLogs();
  329. showButton();
  330. }
  331. }
  332. // #endregion
  333.  
  334. // #region UI
  335. function showButton() {
  336. if (!hd_utils.mh.isOwnJournal()) {
  337. return;
  338. }
  339.  
  340. const olderButton = document.querySelector("#journal-log-button");
  341. if (olderButton) {
  342. olderButton.remove();
  343. }
  344.  
  345. const target = document.querySelector("#journalContainer .top");
  346. if (target) {
  347. const link = document.createElement("a");
  348. link.id = "journal-log-button";
  349.  
  350. link.innerText = `Next Log: ${getNextLogTimer()}`;
  351. link.href = "#";
  352. link.classList.add("hd-journal-log-button");
  353. link.addEventListener("click", function () {
  354. showLogs();
  355. });
  356. target.append(link);
  357. }
  358. }
  359.  
  360. function showLogs(page = 1, enableDeleteLogs = false) {
  361. document.querySelectorAll("#journal-logs-popup-div").forEach(el => el.remove());
  362.  
  363. const journalLogsPopup = document.createElement("div");
  364. journalLogsPopup.id = ("journal-logs-popup-div");
  365. journalLogsPopup.classList.add("hd-popup");
  366.  
  367. // Journal Logs Division
  368. const journalLogs = document.createElement("div");
  369.  
  370. // Title
  371. const title = document.createElement("h2");
  372. title.innerText = `Journal Log Tracker`
  373. title.classList.add("hd-bold");
  374. journalLogs.appendChild(title);
  375.  
  376. // Subtitle
  377. let nextLogDateString = "N/A";
  378. if (storedData.lastSavedEntryId !== undefined && storedData.logs[storedData.lastSavedEntryId] !== undefined) {
  379. const logDate = new Date(storedData.logs[storedData.lastSavedEntryId].Timestamp);
  380. do {
  381. logDate.setHours(logDate.getHours() + 36);
  382. } while (Date.now() - logDate.getTime() > 4 * (1000 * 60 * 60));
  383.  
  384. nextLogDateString = `${logDate.toLocaleString([], { year: 'numeric', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit' })} - (${getNextLogTimer()})`;
  385. }
  386.  
  387. const subtitle = document.createElement("h4");
  388. subtitle.innerText = `Next Estimated Log: ${nextLogDateString}`;
  389. journalLogs.appendChild(subtitle);
  390.  
  391. journalLogs.appendChild(document.createElement("br"));
  392.  
  393. // Table for journal logs
  394. const journalLogsTable = document.createElement("table");
  395. journalLogsTable.id = "journal-logs-table"
  396. journalLogsTable.classList.add("hd-table");
  397.  
  398. const headings = ["#", "Date & Time", "Duration", "Catches", "FTC", "FTA", "Gold", "Points", "#"];
  399. const keys = ["", "Timestamp", "Duration", "Catches", "Ftc", "Fta", "GoldTotal", "PointsTotal"];
  400.  
  401. // Create headings
  402. for (let i = 0; i < headings.length; ++i) {
  403. if (!enableDeleteLogs && i == headings.length - 1) {
  404. continue;
  405. }
  406.  
  407. const headingElement = document.createElement("th");
  408. headingElement.id = `journal-logs-${headings[i].toLowerCase()}-heading`;
  409. headingElement.innerText = headings[i];
  410. headingElement.classList.add("hd-table-heading");
  411.  
  412. journalLogsTable.appendChild(headingElement);
  413. }
  414.  
  415. // Table Body
  416. const tableBody = document.createElement("tbody");
  417.  
  418. const logIDs = Object.keys(storedData.logs);
  419. logIDs.sort((a, b) => b - a);
  420.  
  421. let j = 0;
  422. for (const logId of logIDs) {
  423. if (j < PAGE_SIZE * (page - 1)) {
  424. ++j;
  425. continue;
  426. }
  427.  
  428. if (j >= PAGE_SIZE * page) {
  429. break;
  430. }
  431.  
  432. const tableRow = document.createElement("tr");
  433. tableRow.id = "journal-logs-table-row-" + j
  434.  
  435. for (let i = 0; i < headings.length; ++i) {
  436. if (!enableDeleteLogs && i == headings.length - 1) {
  437. continue;
  438. }
  439.  
  440. const tdElement = document.createElement("td");
  441. if (i == 0) {
  442. tdElement.innerText = j + 1;
  443. } else if (i == 1) {
  444. // Link element
  445. const link = document.createElement("a");
  446. link.innerText = new Date(storedData.logs[logId][keys[i]]).toLocaleString([], { year: 'numeric', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit' });
  447. link.href = "#";
  448. link.addEventListener("click", function () {
  449. document.querySelector("#journal-logs-popup-div").remove();
  450. eval(storedData.logs[logId]["OpenSummaryMethod"]);
  451. return false;
  452. });
  453.  
  454. tdElement.append(link);
  455. } else if (i == headings.length - 1) {
  456. // Delete Log element
  457. const link = document.createElement("a");
  458. link.innerText = "X";
  459. link.href = "#";
  460. link.addEventListener("click", function () {
  461. deleteLog(logId);
  462. const pagesCount = Math.ceil(Object.keys(storedData.logs).length / PAGE_SIZE);
  463. showLogs(page > pagesCount ? pagesCount : page, true);
  464. return false;
  465. });
  466.  
  467. tdElement.append(link);
  468. } else {
  469. if (storedData.logs[logId][keys[i]] !== undefined) {
  470. tdElement.innerText = storedData.logs[logId][keys[i]];
  471.  
  472. if ('GoldTotal' === keys[i] || 'PointsTotal' === keys[i]) {
  473. tdElement.innerText = Number.parseInt(tdElement.innerText).toLocaleString();
  474. }
  475. } else {
  476. tdElement.innerText = '-';
  477. }
  478. }
  479.  
  480. tdElement.classList.add("hd-table-td");
  481.  
  482. tableRow.appendChild(tdElement);
  483. }
  484.  
  485. tableBody.appendChild(tableRow);
  486.  
  487. j++;
  488. }
  489.  
  490. // Final append
  491. journalLogsTable.appendChild(tableBody)
  492. journalLogs.appendChild(journalLogsTable);
  493.  
  494. // Pagination links
  495. journalLogs.appendChild(document.createElement("br"));
  496. const pagesCount = Math.ceil(Object.keys(storedData.logs).length / PAGE_SIZE);
  497.  
  498. const paginationDiv = document.createElement("div");
  499. const firstPageLink = document.createElement("a");
  500. firstPageLink.innerText = "<< First";
  501. firstPageLink.classList.add("hd-mx-2");
  502. paginationDiv.appendChild(firstPageLink);
  503.  
  504. const previousPageLink = document.createElement("a");
  505. previousPageLink.innerText = "< Prev";
  506. previousPageLink.classList.add("hd-mx-2");
  507. paginationDiv.appendChild(previousPageLink);
  508.  
  509. if (page > 1) {
  510. firstPageLink.href = "#";
  511.  
  512. firstPageLink.addEventListener("click", function () {
  513. showLogs(1, enableDeleteLogs);
  514. return false;
  515. });
  516.  
  517. previousPageLink.href = "#";
  518.  
  519. previousPageLink.addEventListener("click", function () {
  520. showLogs(page - 1, enableDeleteLogs);
  521. return false;
  522. });
  523. }
  524.  
  525. const currentPageText = document.createElement("span");
  526. currentPageText.innerText = `${page} of ${pagesCount}`;
  527. currentPageText.classList.add("hd-mx-2");
  528. paginationDiv.appendChild(currentPageText);
  529.  
  530. const nextPageLink = document.createElement("a");
  531. nextPageLink.innerText = "Next >";
  532. nextPageLink.classList.add("hd-mx-2");
  533. paginationDiv.appendChild(nextPageLink);
  534.  
  535. const lastPageLink = document.createElement("a");
  536. lastPageLink.innerText = "Last >>";
  537. lastPageLink.classList.add("hd-mx-2");
  538. paginationDiv.appendChild(lastPageLink);
  539.  
  540. if (page < pagesCount) {
  541. nextPageLink.href = "#";
  542. nextPageLink.addEventListener("click", function () {
  543. showLogs(page + 1, enableDeleteLogs);
  544. return false;
  545. });
  546.  
  547. lastPageLink.href = "#";
  548. lastPageLink.addEventListener("click", function () {
  549. showLogs(pagesCount, enableDeleteLogs);
  550. return false;
  551. });
  552. }
  553.  
  554. journalLogs.appendChild(paginationDiv);
  555.  
  556. // Manual fetch link. Remove to other tab later
  557. journalLogs.appendChild(document.createElement("br"));
  558. const manualFetchLink = document.createElement("a");
  559. manualFetchLink.innerText = "Manual Fetch";
  560. manualFetchLink.href = "#";
  561. manualFetchLink.classList.add("hd-button");
  562. manualFetchLink.addEventListener("click", function () {
  563. tryToScrapeJournal();
  564. showLogs();
  565. return false;
  566. });
  567.  
  568. journalLogs.appendChild(manualFetchLink);
  569.  
  570. // Export link. Remove to other tab later
  571. journalLogs.appendChild(document.createElement("br"));
  572. journalLogs.appendChild(document.createElement("br"));
  573. const exportLink = document.createElement("a");
  574. exportLink.innerText = "Export";
  575. exportLink.href = "#";
  576. exportLink.classList.add("hd-button");
  577. exportLink.addEventListener("click", exportData);
  578.  
  579. journalLogs.appendChild(exportLink);
  580.  
  581. // Import link. Remove to other tab later
  582. const importLink = document.createElement("a");
  583. importLink.innerText = "Import";
  584. importLink.href = "#";
  585. importLink.classList.add("hd-button");
  586. importLink.addEventListener("click", importData);
  587.  
  588. journalLogs.appendChild(importLink);
  589.  
  590. // Toggle Log Deletion. Remove to other tab later
  591. journalLogs.appendChild(document.createElement("br"));
  592. journalLogs.appendChild(document.createElement("br"));
  593. if (enableDeleteLogs) {
  594. const confirmDeleteLogsLink = document.createElement("a");
  595. confirmDeleteLogsLink.innerText = "Confirm Deletion";
  596. confirmDeleteLogsLink.href = "#";
  597. confirmDeleteLogsLink.classList.add("hd-button");
  598. confirmDeleteLogsLink.addEventListener("click", function () {
  599. setData(storedData);
  600. showLogs(1, !enableDeleteLogs);
  601. return false;
  602. });
  603.  
  604. journalLogs.appendChild(confirmDeleteLogsLink);
  605.  
  606. const discardDeleteLogsLink = document.createElement("a");
  607. discardDeleteLogsLink.innerText = "Discard Deletion";
  608. discardDeleteLogsLink.href = "#";
  609. discardDeleteLogsLink.classList.add("hd-button");
  610. discardDeleteLogsLink.addEventListener("click", function () {
  611. storedData = getStoredData();
  612. showLogs(1, !enableDeleteLogs);
  613. return false;
  614. });
  615.  
  616. journalLogs.appendChild(discardDeleteLogsLink);
  617.  
  618. } else {
  619. const toggleDeleteLogsLink = document.createElement("a");
  620. toggleDeleteLogsLink.innerText = "Toggle Delete Logs";
  621. toggleDeleteLogsLink.href = "#";
  622. toggleDeleteLogsLink.classList.add("hd-button");
  623. toggleDeleteLogsLink.addEventListener("click", function () {
  624. showLogs(page, !enableDeleteLogs);
  625. return false;
  626. });
  627.  
  628. journalLogs.appendChild(toggleDeleteLogsLink);
  629. }
  630.  
  631. journalLogsPopup.appendChild(journalLogs);
  632.  
  633. // Close button
  634. const closeButton = document.createElement("button");
  635. closeButton.id = "close-button";
  636. closeButton.textContent = "Close";
  637. closeButton.classList.add("hd-button");
  638. closeButton.onclick = function () {
  639. document.body.removeChild(journalLogsPopup);
  640. }
  641.  
  642. // Append
  643. journalLogsPopup.appendChild(closeButton);
  644.  
  645. // Final Append
  646. document.body.appendChild(journalLogsPopup);
  647. hd_utils.dragElement(journalLogsPopup, journalLogs);
  648. }
  649. // #endregion
  650.  
  651. // #region Utils
  652. function getNextLogTimer() {
  653. let timerString = "N/A";
  654. if (storedData.lastSavedEntryId !== undefined && storedData.logs[storedData.lastSavedEntryId] !== undefined) {
  655. const lastLogDate = new Date(storedData.logs[storedData.lastSavedEntryId].Timestamp);
  656.  
  657. const nextLogTimestamp = lastLogDate.setHours(lastLogDate.getHours() + 36);
  658. const timestampDifference = Math.abs(nextLogTimestamp - Date.now());
  659. let hasPassed = nextLogTimestamp < Date.now();
  660. let logsMissed = 0;
  661.  
  662. let hours = Math.floor(timestampDifference / 1000 / 60 / 60);
  663. let minutes = Math.round((timestampDifference - (hours * 1000 * 60 * 60)) / 1000 / 60);
  664.  
  665. if (minutes === 60) {
  666. minutes = 0;
  667. hours += 1;
  668. }
  669.  
  670. timerString = "";
  671.  
  672. if (hours > 0) {
  673. if (hasPassed && hours >= 8) {
  674. logsMissed = Math.floor(hours / 36) + 1;
  675. hours = Math.abs(hours - 36 * logsMissed);
  676.  
  677. if (minutes > 0) {
  678. hours--;
  679. minutes = 60 - minutes;
  680. }
  681. }
  682.  
  683. timerString = `${hours}h`;
  684. }
  685.  
  686. if (minutes > 0) {
  687. timerString += `${hours > 0 ? " " : ""}${minutes}m`;
  688. }
  689.  
  690. if (logsMissed > 0) {
  691. timerString = `~${timerString}`;
  692. }
  693.  
  694. if (hasPassed && logsMissed === 0) {
  695. timerString += " ago";
  696. }
  697.  
  698. if (hours == 0 && minutes == 0) {
  699. timerString = "Almost ready!"
  700. }
  701. }
  702.  
  703. return timerString;
  704. }
  705. // #endregion
  706. })();