Repos / pytaku / c9ca67a387
commit c9ca67a387843e041f8f5c4db55d4e4ef0bfb752
Author: Bùi Thành Nhân <hi@imnhan.com>
Date:   Sun Aug 30 13:59:04 2020 +0700

    cache chapter data; preload next chapter's pages

diff --git a/src/pytaku/static/js/models.js b/src/pytaku/static/js/models.js
index e28f991..221681f 100644
--- a/src/pytaku/static/js/models.js
+++ b/src/pytaku/static/js/models.js
@@ -120,4 +120,37 @@ const SearchModel = {
   },
 };
 
-export { Auth, SearchModel };
+const ChapterModel = {
+  cache: {},
+
+  cacheGet: (site, titleId, chapterId) => {
+    const key = [site, titleId, chapterId].join(",");
+    return ChapterModel.cache[key] || null;
+  },
+  cacheSet: (site, titleId, chapterId, value) => {
+    const key = [site, titleId, chapterId].join(",");
+    ChapterModel.cache[key] = value;
+  },
+
+  get: ({ site, titleId, chapterId }) => {
+    // Returns a promise.
+    // Tries to return cached data first, if that fails then send http request
+    // and save cache on success.
+
+    const cached = ChapterModel.cacheGet(site, titleId, chapterId);
+    if (cached) {
+      return Promise.resolve(cached);
+    }
+
+    return Auth.request({
+      method: "GET",
+      url: "/api/chapter/:site/:titleId/:chapterId",
+      params: { site, titleId, chapterId },
+    }).then((chapter) => {
+      ChapterModel.cacheSet(site, titleId, chapterId, chapter);
+      return chapter;
+    });
+  },
+};
+
+export { Auth, SearchModel, ChapterModel };
diff --git a/src/pytaku/static/js/routes/chapter.js b/src/pytaku/static/js/routes/chapter.js
index a5782f6..987086e 100644
--- a/src/pytaku/static/js/routes/chapter.js
+++ b/src/pytaku/static/js/routes/chapter.js
@@ -1,4 +1,4 @@
-import { Auth } from "../models.js";
+import { Auth, ChapterModel } from "../models.js";
 import { LoadingMessage, fullChapterName, Button } from "../utils.js";
 
 const LoadingPlaceholder = {
@@ -41,35 +41,64 @@ function Chapter(initialVNode) {
   let loadedPages = [];
   let pendingPages = [];
 
+  let site, titleId; // these are written on init
+  let nextChapterPromise = null;
+  let nextChapterPendingPages = null;
+  let nextChapterLoadedPage = "";
+
   function loadNextPage() {
     if (pendingPages.length > 0) {
       loadedPages.push({
         status: ImgStatus.LOADING,
         src: pendingPages.splice(0, 1)[0],
       });
+    } else if (chapter.next_chapter && nextChapterPromise === null) {
+      /* Once all pages of this chapter have been loaded,
+       * preload the next chapter one page at a time
+       */
+      nextChapterPromise = ChapterModel.get({
+        site,
+        titleId,
+        chapterId: chapter.next_chapter.id,
+      }).then((nextChapter) => {
+        console.log("Preloading next chapter:", fullChapterName(nextChapter));
+        nextChapterPendingPages = nextChapter.pages.slice();
+        preloadNextChapterPage();
+      });
+    }
+  }
+
+  function preloadNextChapterPage() {
+    if (nextChapterPendingPages !== null) {
+      if (nextChapterPendingPages.length > 0) {
+        nextChapterLoadedPage = nextChapterPendingPages.splice(0, 1)[0];
+      } else {
+        console.log("Completely preloaded next chapter.");
+      }
     }
   }
 
   return {
     oninit: (vnode) => {
       document.title = "Manga chapter";
+      site = vnode.attrs.site;
+      titleId = vnode.attrs.titleId;
 
       isLoading = true;
       m.redraw();
 
-      Auth.request({
-        method: "GET",
-        url: "/api/chapter/:site/:titleId/:chapterId",
-        params: {
-          site: vnode.attrs.site,
-          titleId: vnode.attrs.titleId,
-          chapterId: vnode.attrs.chapterId,
-        },
+      ChapterModel.get({
+        site: vnode.attrs.site,
+        titleId: vnode.attrs.titleId,
+        chapterId: vnode.attrs.chapterId,
       })
         .then((resp) => {
           chapter = resp;
           document.title = fullChapterName(chapter);
-          pendingPages = chapter.pages;
+
+          // Clone array here to avoid mutating the model
+          pendingPages = chapter.pages.slice();
+
           // start loading pages, 3 at a time:
           loadNextPage();
           loadNextPage();
@@ -186,6 +215,12 @@ function Chapter(initialVNode) {
           ]
         ),
         buttons,
+        m("img.chapter--preloader", {
+          style: { display: "none" },
+          onload: preloadNextChapterPage,
+          onerror: preloadNextChapterPage,
+          src: nextChapterLoadedPage,
+        }),
       ]);
     },
   };