Added global loading indicator
This commit is contained in:
parent
3677abb2a7
commit
f47c150146
@ -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"
|
||||||
},
|
},
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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) => {
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
|
||||||
}
|
|
@ -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="/" />;
|
|
||||||
};
|
};
|
||||||
|
@ -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 <></>;
|
||||||
};
|
};
|
||||||
|
@ -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
6
src/types/page.ts
Normal 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
13
src/utils/Dots.tsx
Normal 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>;
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user