Added global loading indicator

This commit is contained in:
Dmitriy Shishkov 2021-07-16 15:33:30 +05:00
parent 3677abb2a7
commit f47c150146
No known key found for this signature in database
GPG Key ID: 14358F96FCDD8060
11 changed files with 109 additions and 60 deletions

View File

@ -1,6 +1,6 @@
{ {
"scripts": { "scripts": {
"dev": "SNOWPACK_PUBLIC_API_URL=http://localhost:5000 SNOWPACK_PUBLIC_BASE_URL=http://localhost:8080 snowpack dev", "dev": "SNOWPACK_PUBLIC_API_URL=https://publitebackend.dmitriy.icu SNOWPACK_PUBLIC_BASE_URL=http://localhost:8080 snowpack dev",
"build": "snowpack build", "build": "snowpack build",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },

View File

@ -3,3 +3,15 @@
width: 100vw; width: 100vw;
overflow: hidden; overflow: hidden;
} }
.loadingIndicator {
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 100vw;
background-color: white;
display: flex;
align-items: center;
justify-content: center;
}

View File

@ -1,23 +1,42 @@
import React from "react"; import React, { useState } from "react";
import { Route, Switch } from "wouter"; import { Route, Switch } from "wouter";
import { BookListContextProvider } from "~/context"; import { BookListContextProvider } from "~/context";
import { Bookshelf } from "~/pages/Bookshelf"; import { Bookshelf } from "~/pages/Bookshelf";
import { BookView } from "~/pages/BookView"; import { BookView } from "~/pages/BookView";
import { UploadForm } from "~/pages/UploadForm"; import { UploadForm } from "~/pages/UploadForm";
import { Dots } from "~/utils/Dots";
import styles from "./App.module.css"; import styles from "./App.module.css";
export const App = () => ( export const App = () => {
<div className={styles.container}> const [loading, setLoading] = useState(false);
<BookListContextProvider>
<Switch> return (
<Route path="/upload" component={UploadForm} /> <div className={styles.container}>
<Route path="/" component={Bookshelf} /> {loading && (
<Route path="/:hash" component={BookView} /> <div className={styles.loadingIndicator}>
</Switch> <h1>
</BookListContextProvider> Loading
</div> <Dots />
); </h1>
</div>
)}
<BookListContextProvider>
<Switch>
<Route path="/upload">
<UploadForm setLoading={setLoading} loading={loading} />
</Route>
<Route path="/">
<Bookshelf setLoading={setLoading} loading={loading} />
</Route>
<Route path="/:hash">
<BookView setLoading={setLoading} loading={loading} />
</Route>
</Switch>
</BookListContextProvider>
</div>
);
};
export default App; export default App;

View File

@ -10,13 +10,13 @@ import {
export type AddBookFT = (book: IBook) => void; export type AddBookFT = (book: IBook) => void;
export type UseLibraryReturnTuple = [ export type UseLibraryReturnTuple = [
Record<string, IBook>, Record<string, IBook> | null,
AddBookFT, AddBookFT,
string[] string[]
]; ];
export const useLibrary = (): UseLibraryReturnTuple => { export const useLibrary = (): UseLibraryReturnTuple => {
const [library, setLibrary] = useState<Record<string, IBook>>({}); const [library, setLibrary] = useState<Record<string, IBook> | null>(null);
const [hashList, setHashList] = useState<string[]>([]); const [hashList, setHashList] = useState<string[]>([]);
const addBook: AddBookFT = (book) => { const addBook: AddBookFT = (book) => {

View File

@ -25,7 +25,7 @@ export const usePagination = (
const [pages, setPages] = useState<number[]>([]); const [pages, setPages] = useState<number[]>([]);
const [currentPage, setCurrentPage] = useState(0); const [currentPage, setCurrentPage] = useState(0);
const computeStartPositionsOfElements = (root: HTMLDivElement) => { const computeStartPositionsOfElements = async (root: HTMLDivElement) => {
const positionToElement: PositionElement[] = []; const positionToElement: PositionElement[] = [];
const idPositions: IdPositions = {}; const idPositions: IdPositions = {};
@ -56,7 +56,7 @@ export const usePagination = (
setIdPositions(idPositions); setIdPositions(idPositions);
}; };
const findPages = (page: HTMLElement, pageContainer: HTMLElement) => { const findPages = async (page: HTMLElement, pageContainer: HTMLElement) => {
const pages = []; const pages = [];
pages.push(0); pages.push(0);
let jump = 100; let jump = 100;

View File

@ -17,16 +17,4 @@
img { img {
max-height: 98vh; max-height: 98vh;
max-width: 80vw; max-width: 80vw;
} }
.loadingIndicator {
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 100vw;
background-color: white;
display: flex;
align-items: center;
justify-content: center;
}

View File

@ -5,9 +5,12 @@ import styles from "./BookView.module.css";
import { BookListContext } from "~/context"; import { BookListContext } from "~/context";
import { usePagination } from "~/hooks/usePagination"; import { usePagination } from "~/hooks/usePagination";
import { IPageProps } from "~/types/page";
export const BookView = () => { export const BookView = ({ setLoading, loading }: IPageProps) => {
const [match, params] = useRoute("/:hash"); useEffect(() => setLoading(true), []);
const [_, params] = useRoute("/:hash");
const [books] = useContext(BookListContext); const [books] = useContext(BookListContext);
const contentRef = useRef<HTMLDivElement>(null); const contentRef = useRef<HTMLDivElement>(null);
@ -18,7 +21,7 @@ export const BookView = () => {
contentRef, contentRef,
pageContainerRef, pageContainerRef,
pageRef, pageRef,
params?.hash ? books[params.hash]?.content : undefined params?.hash && books && loading ? books[params.hash]?.content : undefined
); );
const currentPageRef = useRef(currentPage); const currentPageRef = useRef(currentPage);
@ -29,6 +32,8 @@ export const BookView = () => {
useEffect(() => { useEffect(() => {
if (ready) { if (ready) {
setLoading(false);
const handleKey = ({ key }: KeyboardEvent) => { const handleKey = ({ key }: KeyboardEvent) => {
switch (key) { switch (key) {
case "ArrowLeft": case "ArrowLeft":
@ -44,19 +49,16 @@ export const BookView = () => {
} }
}, [ready]); }, [ready]);
if (params?.hash && params.hash in books) if (books) {
return ( if (params?.hash && params.hash in books)
<> return (
{!ready && ( <>
<div className={styles.loadingIndicator}> <div className={styles.content} ref={contentRef} />
<h1>Loading</h1> <div className={styles.pageContainer} ref={pageContainerRef}>
<div ref={pageRef} />
</div> </div>
)} </>
<div className={styles.content} ref={contentRef} /> );
<div className={styles.pageContainer} ref={pageContainerRef}> return <Redirect to="/" />;
<div ref={pageRef} /> } else return <></>;
</div>
</>
);
return <Redirect to="/" />;
}; };

View File

@ -5,18 +5,27 @@ import styles from "./Bookshelf.module.css";
import { BookItem } from "./BookItem"; import { BookItem } from "./BookItem";
import { AddBook } from "./AddBook"; import { AddBook } from "./AddBook";
import { BookListContext } from "~/context"; import { BookListContext } from "~/context";
import { IPageProps } from "~/types/page";
export const Bookshelf = ({ setLoading }: IPageProps) => {
useEffect(() => setLoading(true), []);
export const Bookshelf = () => {
const [books] = useContext(BookListContext); const [books] = useContext(BookListContext);
return ( useEffect(() => {
<div className={styles.container}> if (books) setLoading(false);
<div className={styles.scrollContainer}> }, [books]);
<AddBook />
{Object.values(books).map((book, index) => ( if (books)
<BookItem key={book.hash} {...book} /> return (
))} <div className={styles.container}>
<div className={styles.scrollContainer}>
<AddBook />
{Object.values(books).map((book, index) => (
<BookItem key={book.hash} {...book} />
))}
</div>
</div> </div>
</div> );
); else return <></>;
}; };

View File

@ -1,14 +1,14 @@
import React, { useContext, useState } from "react"; import React, { useContext, useEffect, useState } from "react";
import { useLocation } from "wouter"; import { useLocation } from "wouter";
import plusIcon from "~/assets/plus.svg"; import plusIcon from "~/assets/plus.svg";
import styles from "./UploadForm.module.css"; import styles from "./UploadForm.module.css";
import { submitFile, validateResponse, validState } from "~/utils/api"; import { submitFile, validateResponse, validState } from "~/utils/api";
import { BookListContext } from "~/context"; import { BookListContext } from "~/context";
import { IPageProps } from "~/types/page";
export const UploadForm = () => { export const UploadForm = ({ setLoading }: IPageProps) => {
const [error, setError] = useState(""); const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const [_, setLocation] = useLocation(); const [_, setLocation] = useLocation();
const [__, saveBook] = useContext(BookListContext); const [__, saveBook] = useContext(BookListContext);

6
src/types/page.ts Normal file
View File

@ -0,0 +1,6 @@
import React from "react";
export interface IPageProps {
setLoading: React.Dispatch<React.SetStateAction<boolean>>;
loading: boolean;
}

13
src/utils/Dots.tsx Normal file
View File

@ -0,0 +1,13 @@
import React, { useEffect, useState } from "react";
export const Dots = () => {
const [n, setN] = useState(3);
useEffect(() => {
const timeout = setTimeout(() => setN((n + 1) % 4), 500);
return () => clearTimeout(timeout);
}, [n]);
return <span>{".".repeat(n)}</span>;
};