Add loading view component #8
6
public/icons/rotate-clockwise.svg
Normal file
6
public/icons/rotate-clockwise.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-rotate-clockwise" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M4.05 11a8 8 0 1 1 .5 4m-.5 5v-5h5" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
After Width: | Height: | Size: 349 B |
@ -17,6 +17,7 @@
|
|||||||
--gap-large: 32px;
|
--gap-large: 32px;
|
||||||
|
|
||||||
--duration-short: 0.3s;
|
--duration-short: 0.3s;
|
||||||
|
--duration-long: 1.5s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grow {
|
.grow {
|
||||||
@ -208,6 +209,31 @@ i {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Loading */
|
||||||
|
|
||||||
|
.loading-icon {
|
||||||
|
animation: spin infinite linear var(--duration-long),
|
||||||
|
fadein linear var(--duration-short);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadein {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Icon buttons */
|
/* Icon buttons */
|
||||||
|
|
||||||
.icon-button {
|
.icon-button {
|
||||||
@ -306,6 +332,14 @@ i {
|
|||||||
|
|
||||||
/* Expression set page */
|
/* Expression set page */
|
||||||
|
|
||||||
|
.page-loading {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.page-with-padding {
|
.page-with-padding {
|
||||||
padding: var(--gap-small) var(--gap-medium) var(--gap-medium);
|
padding: var(--gap-small) var(--gap-medium) var(--gap-medium);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import { useAllExpressions } from "../../hooks";
|
|||||||
import { IndexedExpression } from "../../model";
|
import { IndexedExpression } from "../../model";
|
||||||
import { AppPath, AppRouting } from "../../model/routing";
|
import { AppPath, AppRouting } from "../../model/routing";
|
||||||
import { ErrorView } from "../ErrorView";
|
import { ErrorView } from "../ErrorView";
|
||||||
|
import { LoadingView } from "../LoadingView/LoadingView";
|
||||||
|
|
||||||
export function ExpressionCardListView() {
|
export function ExpressionCardListView() {
|
||||||
const { setRoute } = useContext(AppRouting);
|
const { setRoute } = useContext(AppRouting);
|
||||||
@ -13,7 +14,7 @@ export function ExpressionCardListView() {
|
|||||||
[expressions]
|
[expressions]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!expressions) return null; // LOADING
|
if (!expressions) return <LoadingView />;
|
||||||
if (!expressions.length) {
|
if (!expressions.length) {
|
||||||
return <ErrorView message="No expression cards yet" />;
|
return <ErrorView message="No expression cards yet" />;
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,13 @@ import { ExpressionDescription } from "../../components/ExpressionDescription";
|
|||||||
import { useExpressionById, useQueryExpressionId } from "../../hooks";
|
import { useExpressionById, useQueryExpressionId } from "../../hooks";
|
||||||
import { ItemNotFoundError } from "../../model";
|
import { ItemNotFoundError } from "../../model";
|
||||||
import { ErrorView } from "../ErrorView";
|
import { ErrorView } from "../ErrorView";
|
||||||
|
import { LoadingView } from "../LoadingView/LoadingView";
|
||||||
|
|
||||||
export function ExpressionCardView() {
|
export function ExpressionCardView() {
|
||||||
const expression_id = useQueryExpressionId();
|
const expression_id = useQueryExpressionId();
|
||||||
const expression = useExpressionById(expression_id);
|
const expression = useExpressionById(expression_id);
|
||||||
|
|
||||||
if (expression === undefined) return null; // LOADING
|
if (expression === undefined) return <LoadingView />;
|
||||||
if (expression === ItemNotFoundError)
|
if (expression === ItemNotFoundError)
|
||||||
return <ErrorView message="Expression card not found" />;
|
return <ErrorView message="Expression card not found" />;
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
import { IndexedExpression } from "../../model";
|
import { IndexedExpression } from "../../model";
|
||||||
import { sample } from "../../util";
|
import { sample } from "../../util";
|
||||||
import { ErrorView } from "../ErrorView";
|
import { ErrorView } from "../ErrorView";
|
||||||
|
import { LoadingView } from "../LoadingView/LoadingView";
|
||||||
import { ExpressionPracticeCardView } from "./ExpressionPracticeCardView";
|
import { ExpressionPracticeCardView } from "./ExpressionPracticeCardView";
|
||||||
|
|
||||||
export function ExpressionPracticeView() {
|
export function ExpressionPracticeView() {
|
||||||
@ -16,7 +17,7 @@ export function ExpressionPracticeView() {
|
|||||||
const expressions = useExpressionsByExpressionSetId(expression_set_id);
|
const expressions = useExpressionsByExpressionSetId(expression_set_id);
|
||||||
// TODO handle errors
|
// TODO handle errors
|
||||||
|
|
||||||
if (!expressions) return null; // LOADING
|
if (!expressions) return <LoadingView />;
|
||||||
|
|
||||||
const filtered_expressions = expressions.filter(
|
const filtered_expressions = expressions.filter(
|
||||||
(expression) => !filter_ids.includes(expression.id!)
|
(expression) => !filter_ids.includes(expression.id!)
|
||||||
|
@ -8,13 +8,14 @@ import {
|
|||||||
import { IndexedExpressionSet, ItemNotFoundError } from "../../model";
|
import { IndexedExpressionSet, ItemNotFoundError } from "../../model";
|
||||||
import { AppPath, AppRouting } from "../../model/routing";
|
import { AppPath, AppRouting } from "../../model/routing";
|
||||||
import { ErrorView } from "../ErrorView";
|
import { ErrorView } from "../ErrorView";
|
||||||
|
import { LoadingView } from "../LoadingView/LoadingView";
|
||||||
|
|
||||||
export function ExpressionSetDetailsView() {
|
export function ExpressionSetDetailsView() {
|
||||||
const expression_set_id = useQueryExpressionSetId();
|
const expression_set_id = useQueryExpressionSetId();
|
||||||
const expression_set = useExpressionSetById(expression_set_id);
|
const expression_set = useExpressionSetById(expression_set_id);
|
||||||
const expressions = useExpressionsByExpressionSetId(expression_set_id);
|
const expressions = useExpressionsByExpressionSetId(expression_set_id);
|
||||||
|
|
||||||
if (!expression_set || !expressions) return null; // LOADING
|
if (!expression_set || !expressions) return <LoadingView />;
|
||||||
if (expression_set === ItemNotFoundError) {
|
if (expression_set === ItemNotFoundError) {
|
||||||
return <ErrorView message="Expression set not found" />;
|
return <ErrorView message="Expression set not found" />;
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { useAllExpressionSets } from "../../hooks";
|
import { useAllExpressionSets } from "../../hooks";
|
||||||
import { ErrorView } from "../ErrorView";
|
import { ErrorView } from "../ErrorView";
|
||||||
|
import { LoadingView } from "../LoadingView/LoadingView";
|
||||||
import { ExpressionSetLink } from "./ExpressionSetLink";
|
import { ExpressionSetLink } from "./ExpressionSetLink";
|
||||||
|
|
||||||
export function ExpressionSetListView() {
|
export function ExpressionSetListView() {
|
||||||
const expression_sets = useAllExpressionSets();
|
const expression_sets = useAllExpressionSets();
|
||||||
|
|
||||||
if (!expression_sets) return null; // LOADING
|
if (!expression_sets) return <LoadingView />;
|
||||||
if (!expression_sets.length) {
|
if (!expression_sets.length) {
|
||||||
return <ErrorView message="No expression sets found" />;
|
return <ErrorView message="No expression sets found" />;
|
||||||
}
|
}
|
||||||
|
13
src/views/LoadingView/LoadingView.tsx
Normal file
13
src/views/LoadingView/LoadingView.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export function LoadingView() {
|
||||||
|
return (
|
||||||
|
<div className="page-loading">
|
||||||
|
<img
|
||||||
|
className="loading-icon"
|
||||||
|
src="/icons/rotate-clockwise.svg"
|
||||||
|
width="64"
|
||||||
|
height="64"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
1
src/views/LoadingView/index.ts
Normal file
1
src/views/LoadingView/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./LoadingView";
|
Loading…
Reference in New Issue
Block a user