Repos / pytaku / 24c4e7599d
commit 24c4e7599d0eef22d98189b2a0dbb260a43d8e23
Author: Bùi Thành Nhân <hi@imnhan.com>
Date:   Sun Aug 23 19:23:14 2020 +0700

    allow reloading img on error

diff --git a/README.md b/README.md
index a9814ee..45726ef 100644
--- a/README.md
+++ b/README.md
@@ -17,8 +17,8 @@ # Pytaku
   architecture/tooling **F**etishi**S**ts! Oftentimes I have enough practice on
   industrial grade power tools at work so at home I want a change of pace.
   Flask + raw SQL has been surprisingly comfy. On the other side, mithril.js
-  seems to be a no-frills, stable SPA lib made by a person who knows what
-  they're doing so let's see how that goes.
+  provides a good baseline of SPA functionality without having to pull in the
+  Rube Goldberg machine that is """modern""" JS devtools.
 
 # Development
 
diff --git a/src/pytaku/static/js/routes/chapter.js b/src/pytaku/static/js/routes/chapter.js
index f336e9a..e188822 100644
--- a/src/pytaku/static/js/routes/chapter.js
+++ b/src/pytaku/static/js/routes/chapter.js
@@ -5,6 +5,32 @@ const LoadingPlaceholder = {
   view: () => m("h2", [m("i.icon.icon-loader.spin")]),
 };
 
+const RetryImgButton = {
+  view: (vnode) => {
+    return m(Button, {
+      text: "Errored. Try again?",
+      color: "red",
+      onclick: (ev) => {
+        const { page } = vnode.attrs;
+        page.status = ImgStatus.LOADING;
+        // Cheat: append to src so the element's key is
+        // different, forcing mithril to redraw.
+        // Chose `?` here because it will just be stripped by
+        // flask's path parser.
+        page.src = page.src.endsWith("?")
+          ? page.src.slice(0, -1)
+          : page.src + "?";
+      },
+    });
+  },
+};
+
+const ImgStatus = {
+  LOADING: "loading",
+  SUCCEEDED: "succeeded",
+  FAILED: "failed",
+};
+
 function Chapter(initialVNode) {
   let isLoading = false;
   let chapter = {};
@@ -12,10 +38,12 @@ function Chapter(initialVNode) {
   let pendingPages = [];
 
   function loadNextPage() {
-    loadedPages.push({
-      completed: false,
-      src: pendingPages.splice(0, 1)[0],
-    });
+    if (pendingPages.length > 0) {
+      loadedPages.push({
+        status: ImgStatus.LOADING,
+        src: pendingPages.splice(0, 1)[0],
+      });
+    }
   }
 
   return {
@@ -101,17 +129,35 @@ function Chapter(initialVNode) {
               (chapter.is_webtoon ? " chapter--webtoon" : ""),
           },
           [
-            loadedPages.map((page) => [
-              m("img", {
-                src: page.src,
-                style: { display: page.completed ? "block" : "none" },
-                onload: (ev) => {
-                  page.completed = true;
-                  loadNextPage();
-                },
-              }),
-              page.completed ? "" : m(LoadingPlaceholder),
-            ]),
+            loadedPages.map((page, pageIndex) =>
+              m("div", { key: page.src }, [
+                m("img", {
+                  src: page.src,
+                  style: {
+                    display:
+                      page.status === ImgStatus.SUCCEEDED ? "block" : "none",
+                  },
+                  onload: (ev) => {
+                    page.status = ImgStatus.SUCCEEDED;
+                    loadNextPage();
+                  },
+                  onerror: (ev) => {
+                    page.status = ImgStatus.FAILED;
+                    loadNextPage();
+                  },
+                }),
+                page.status === ImgStatus.LOADING
+                  ? m(LoadingPlaceholder)
+                  : null,
+                page.status === ImgStatus.FAILED
+                  ? m(
+                      "div",
+                      { style: { "margin-bottom": ".5rem" } },
+                      m(RetryImgButton, { page })
+                    )
+                  : null,
+              ])
+            ),
             pendingPages.map((page) => m(LoadingPlaceholder)),
           ]
         ),