From b7f5a0eb553704b5a7d46db48b1ad6d748e22fb8 Mon Sep 17 00:00:00 2001
From: dm1sh <me@dmitriy.icu>
Date: Sat, 24 Jul 2021 20:06:40 +0300
Subject: [PATCH] Added pages persistance (ugly, but temporary)

---
 src/context.tsx                        |  4 +--
 src/hooks/useBookState.ts              | 48 ++++++++++++++++++++++++++
 src/hooks/useLibrary.ts                |  8 ++---
 src/pages/BookView/index.tsx           | 14 ++++++--
 src/pages/Bookshelf/BookItem/index.tsx |  4 +--
 src/types/book.ts                      |  6 +++-
 src/utils/api.ts                       |  4 +--
 src/utils/localStorage.ts              | 40 +++++++++++++++++++--
 8 files changed, 111 insertions(+), 17 deletions(-)
 create mode 100644 src/hooks/useBookState.ts

diff --git a/src/context.tsx b/src/context.tsx
index f5c5243..a1cae71 100644
--- a/src/context.tsx
+++ b/src/context.tsx
@@ -1,10 +1,10 @@
 import React from "react";
 import { useLibrary, UseLibraryReturnTuple } from "./hooks/useLibrary";
-import { IBook } from "./types/book";
+import { BookT } from "./types/book";
 
 export const BookListContext = React.createContext<UseLibraryReturnTuple>([
   {},
-  (book: IBook) => {},
+  (book: BookT) => {},
   [],
 ]);
 
diff --git a/src/hooks/useBookState.ts b/src/hooks/useBookState.ts
new file mode 100644
index 0000000..633dd1c
--- /dev/null
+++ b/src/hooks/useBookState.ts
@@ -0,0 +1,48 @@
+import React, { useEffect, useState } from "react";
+import { BookState } from "~/types/book";
+import { loadBookState, saveBookState } from "~/utils/localStorage";
+
+export const useBookState = (
+  pagesReady: boolean,
+  hash: string | undefined,
+  goToPage: (pageNum: number) => void,
+  currentPage: React.RefObject<number>
+): [boolean, BookState | undefined] => {
+  const [state, setState] = useState<BookState>();
+  const [ready, setReady] = useState(false);
+
+  useEffect(() => {
+    if (hash)
+      loadBookState(
+        hash,
+        (obj) => setState(obj),
+        () => setState({ currentPage: 0 })
+      );
+  }, [hash]);
+
+  useEffect(() => {
+    console.log(Boolean(!ready && state?.currentPage && goToPage));
+    if (!ready && state?.currentPage && pagesReady) {
+      console.log("Go to", state.currentPage);
+      goToPage(state.currentPage);
+      console.log("Ready");
+      setReady(true);
+    } else if (hash && !ready && pagesReady && typeof state === "object") {
+      saveBookState(hash, { currentPage: 0 });
+      setReady(true);
+    }
+  }, [ready, state, goToPage]);
+
+  useEffect(
+    () => () => {
+      if (hash) {
+        console.log(currentPage);
+        if (ready && state) saveBookState(hash, state);
+        else saveBookState(hash, { currentPage: currentPage.current || 0 });
+      }
+    },
+    []
+  );
+
+  return [ready, state];
+};
diff --git a/src/hooks/useLibrary.ts b/src/hooks/useLibrary.ts
index db501b3..0c29713 100644
--- a/src/hooks/useLibrary.ts
+++ b/src/hooks/useLibrary.ts
@@ -1,5 +1,5 @@
 import { useEffect, useState } from "react";
-import { IBook } from "~/types/book";
+import { BookT } from "~/types/book";
 import {
   getBookHT,
   getHashList,
@@ -7,16 +7,16 @@ import {
   updateHashList,
 } from "~/utils/localStorage";
 
-export type AddBookFT = (book: IBook) => void;
+export type AddBookFT = (book: BookT) => void;
 
 export type UseLibraryReturnTuple = [
-  Record<string, IBook> | null,
+  Record<string, BookT> | null,
   AddBookFT,
   string[]
 ];
 
 export const useLibrary = (): UseLibraryReturnTuple => {
-  const [library, setLibrary] = useState<Record<string, IBook> | null>(null);
+  const [library, setLibrary] = useState<Record<string, BookT> | null>(null);
   const [hashList, setHashList] = useState<string[]>([]);
 
   const addBook: AddBookFT = (book) => {
diff --git a/src/pages/BookView/index.tsx b/src/pages/BookView/index.tsx
index d4e6c0b..65fb059 100644
--- a/src/pages/BookView/index.tsx
+++ b/src/pages/BookView/index.tsx
@@ -6,6 +6,7 @@ import styles from "./BookView.module.css";
 import { BookListContext } from "~/context";
 import { usePagination } from "~/hooks/usePagination";
 import { IPageProps } from "~/types/page";
+import { useBookState } from "~/hooks/useBookState";
 
 export const BookView = ({ setLoading, loading }: IPageProps) => {
   useEffect(() => setLoading(true), []);
@@ -17,7 +18,7 @@ export const BookView = ({ setLoading, loading }: IPageProps) => {
   const pageContainerRef = useRef<HTMLDivElement>(null);
   const pageRef = useRef<HTMLDivElement>(null);
 
-  const [ready, goToPage, currentPage, pagesNumber] = usePagination(
+  const [pagesReady, goToPage, currentPage, pagesNumber] = usePagination(
     contentRef,
     pageContainerRef,
     pageRef,
@@ -31,8 +32,15 @@ export const BookView = ({ setLoading, loading }: IPageProps) => {
     currentPageRef.current = currentPage;
   }, [currentPage]);
 
+  const [bookStateReady, bs] = useBookState(
+    pagesReady,
+    params?.hash,
+    goToPage,
+    currentPageRef
+  );
+
   useEffect(() => {
-    if (ready) {
+    if (bookStateReady) {
       setLoading(false);
 
       const handleKey = ({ key }: KeyboardEvent) => {
@@ -48,7 +56,7 @@ export const BookView = ({ setLoading, loading }: IPageProps) => {
 
       window.addEventListener("keydown", handleKey);
     }
-  }, [ready]);
+  }, [bookStateReady]);
 
   const goPrev = () => goToPage(currentPage - 1);
   const goNext = () => goToPage(currentPage + 1);
diff --git a/src/pages/Bookshelf/BookItem/index.tsx b/src/pages/Bookshelf/BookItem/index.tsx
index f2cb3c4..bc672da 100644
--- a/src/pages/Bookshelf/BookItem/index.tsx
+++ b/src/pages/Bookshelf/BookItem/index.tsx
@@ -2,10 +2,10 @@ import React from "react";
 
 import styles from "./BookItem.module.css";
 
-import { IBook } from "~/types/book";
+import { BookT } from "~/types/book";
 import { Link } from "wouter";
 
-interface IBookItemProps extends IBook {}
+interface IBookItemProps extends BookT {}
 
 export const BookItem = ({ author, title, cover, hash }: IBookItemProps) => {
   return (
diff --git a/src/types/book.ts b/src/types/book.ts
index 3463afe..6f2f4e8 100644
--- a/src/types/book.ts
+++ b/src/types/book.ts
@@ -1,9 +1,13 @@
 export const requiredBookProps = ["title", "author", "content"] as const;
 export const optionalBookProps = ["cover", "hash"] as const;
 
-export type IBook = {
+export type BookT = {
   [key in typeof requiredBookProps[number]]: string;
 } &
   {
     [key in typeof optionalBookProps[number]]: string | undefined;
   };
+
+export type BookState = {
+  currentPage: number;
+};
diff --git a/src/utils/api.ts b/src/utils/api.ts
index b8bb602..13f6fde 100644
--- a/src/utils/api.ts
+++ b/src/utils/api.ts
@@ -1,4 +1,4 @@
-import { IBook, requiredBookProps } from "~/types/book";
+import { BookT, requiredBookProps } from "~/types/book";
 
 import { API_URL } from "~/constants";
 
@@ -39,7 +39,7 @@ export const submitFile = async (
   }
 };
 
-export const validateResponse = (content: unknown): content is IBook => {
+export const validateResponse = (content: unknown): content is BookT => {
   if (content && typeof content === "object")
     for (const key of requiredBookProps)
       if (!(key in content)) {
diff --git a/src/utils/localStorage.ts b/src/utils/localStorage.ts
index 9f0e71e..5c3ef53 100644
--- a/src/utils/localStorage.ts
+++ b/src/utils/localStorage.ts
@@ -1,4 +1,4 @@
-import { IBook } from "~/types/book";
+import { BookState, BookT } from "~/types/book";
 import { isArrOfStr } from "~/types/utils";
 import { validateResponse } from "~/utils/api";
 
@@ -17,7 +17,7 @@ export const getHashList = () => {
 };
 
 export const getBookHT = (hashList: string[]) => {
-  const bookHT: Record<string, IBook> = {};
+  const bookHT: Record<string, BookT> = {};
 
   hashList.forEach((hash) => {
     try {
@@ -31,7 +31,7 @@ export const getBookHT = (hashList: string[]) => {
   return bookHT;
 };
 
-export const saveBook = (key: string, book: IBook) =>
+export const saveBook = (key: string, book: BookT) =>
   localStorage.setItem(key, JSON.stringify(book));
 
 export const updateHashList = (hashList: string[]) =>
@@ -78,3 +78,37 @@ export const savePages = (
   width: number,
   pages: number[]
 ) => localStorage.setItem(hashStr(hash, height, width), JSON.stringify(pages));
+
+export const validateBookState = (obj: unknown): obj is BookState =>
+  Boolean(
+    obj &&
+      typeof obj === "object" &&
+      !Array.isArray(obj) &&
+      "currentPage" in obj
+  );
+
+export const loadBookState = (
+  hash: string,
+  cb: (bookState: BookState) => void,
+  ecb: () => void
+) => {
+  const str = localStorage.getItem(hash + "-state");
+
+  if (str) {
+    try {
+      const obj: unknown = JSON.parse(str);
+
+      if (validateBookState(obj)) {
+        cb(obj);
+        return true;
+      }
+    } catch (e) {
+      console.error(e);
+    }
+  }
+
+  ecb();
+};
+
+export const saveBookState = (hash: string, state: BookState) =>
+  localStorage.setItem(hash + "-state", JSON.stringify(state));