diff --git a/public/icons/rotate-clockwise.svg b/public/icons/rotate-clockwise.svg new file mode 100644 index 0000000..21df873 --- /dev/null +++ b/public/icons/rotate-clockwise.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/styles/components.css b/src/styles/components.css index fc91c98..73ca269 100644 --- a/src/styles/components.css +++ b/src/styles/components.css @@ -17,6 +17,7 @@ --gap-large: 32px; --duration-short: 0.3s; + --duration-long: 1.5s; } .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-button { @@ -306,6 +332,14 @@ i { /* Expression set page */ +.page-loading { + height: 100%; + width: 100%; + display: flex; + align-items: center; + justify-content: center; +} + .page-with-padding { padding: var(--gap-small) var(--gap-medium) var(--gap-medium); } diff --git a/src/views/ExpressionCardListView/ExpressionCardListView.tsx b/src/views/ExpressionCardListView/ExpressionCardListView.tsx index e2e0603..36aa0e7 100644 --- a/src/views/ExpressionCardListView/ExpressionCardListView.tsx +++ b/src/views/ExpressionCardListView/ExpressionCardListView.tsx @@ -4,6 +4,7 @@ import { useAllExpressions } from "../../hooks"; import { IndexedExpression } from "../../model"; import { AppPath, AppRouting } from "../../model/routing"; import { ErrorView } from "../ErrorView"; +import { LoadingView } from "../LoadingView/LoadingView"; export function ExpressionCardListView() { const { setRoute } = useContext(AppRouting); @@ -13,7 +14,7 @@ export function ExpressionCardListView() { [expressions] ); - if (!expressions) return null; // LOADING + if (!expressions) return ; if (!expressions.length) { return ; } diff --git a/src/views/ExpressionCardView/ExpressionCardView.tsx b/src/views/ExpressionCardView/ExpressionCardView.tsx index 2bc545a..2db6e00 100644 --- a/src/views/ExpressionCardView/ExpressionCardView.tsx +++ b/src/views/ExpressionCardView/ExpressionCardView.tsx @@ -3,12 +3,13 @@ import { ExpressionDescription } from "../../components/ExpressionDescription"; import { useExpressionById, useQueryExpressionId } from "../../hooks"; import { ItemNotFoundError } from "../../model"; import { ErrorView } from "../ErrorView"; +import { LoadingView } from "../LoadingView/LoadingView"; export function ExpressionCardView() { const expression_id = useQueryExpressionId(); const expression = useExpressionById(expression_id); - if (expression === undefined) return null; // LOADING + if (expression === undefined) return ; if (expression === ItemNotFoundError) return ; diff --git a/src/views/ExpressionPracticeView/ExpressionPracticeView.tsx b/src/views/ExpressionPracticeView/ExpressionPracticeView.tsx index 9a90839..e76bc3b 100644 --- a/src/views/ExpressionPracticeView/ExpressionPracticeView.tsx +++ b/src/views/ExpressionPracticeView/ExpressionPracticeView.tsx @@ -8,6 +8,7 @@ import { import { IndexedExpression } from "../../model"; import { sample } from "../../util"; import { ErrorView } from "../ErrorView"; +import { LoadingView } from "../LoadingView/LoadingView"; import { ExpressionPracticeCardView } from "./ExpressionPracticeCardView"; export function ExpressionPracticeView() { @@ -16,7 +17,7 @@ export function ExpressionPracticeView() { const expressions = useExpressionsByExpressionSetId(expression_set_id); // TODO handle errors - if (!expressions) return null; // LOADING + if (!expressions) return ; const filtered_expressions = expressions.filter( (expression) => !filter_ids.includes(expression.id!) diff --git a/src/views/ExpressionSetDetailsView/ExpressionSetDetailsView.tsx b/src/views/ExpressionSetDetailsView/ExpressionSetDetailsView.tsx index ab6c218..1e9ea48 100644 --- a/src/views/ExpressionSetDetailsView/ExpressionSetDetailsView.tsx +++ b/src/views/ExpressionSetDetailsView/ExpressionSetDetailsView.tsx @@ -8,13 +8,14 @@ import { import { IndexedExpressionSet, ItemNotFoundError } from "../../model"; import { AppPath, AppRouting } from "../../model/routing"; import { ErrorView } from "../ErrorView"; +import { LoadingView } from "../LoadingView/LoadingView"; export function ExpressionSetDetailsView() { const expression_set_id = useQueryExpressionSetId(); const expression_set = useExpressionSetById(expression_set_id); const expressions = useExpressionsByExpressionSetId(expression_set_id); - if (!expression_set || !expressions) return null; // LOADING + if (!expression_set || !expressions) return ; if (expression_set === ItemNotFoundError) { return ; } diff --git a/src/views/ExpressionSetListView/ExpressionSetListView.tsx b/src/views/ExpressionSetListView/ExpressionSetListView.tsx index 38e3214..c6a7ab6 100644 --- a/src/views/ExpressionSetListView/ExpressionSetListView.tsx +++ b/src/views/ExpressionSetListView/ExpressionSetListView.tsx @@ -1,11 +1,12 @@ import { useAllExpressionSets } from "../../hooks"; import { ErrorView } from "../ErrorView"; +import { LoadingView } from "../LoadingView/LoadingView"; import { ExpressionSetLink } from "./ExpressionSetLink"; export function ExpressionSetListView() { const expression_sets = useAllExpressionSets(); - if (!expression_sets) return null; // LOADING + if (!expression_sets) return ; if (!expression_sets.length) { return ; } diff --git a/src/views/LoadingView/LoadingView.tsx b/src/views/LoadingView/LoadingView.tsx new file mode 100644 index 0000000..097437c --- /dev/null +++ b/src/views/LoadingView/LoadingView.tsx @@ -0,0 +1,13 @@ +export function LoadingView() { + return ( +
+ +
+ ); +} diff --git a/src/views/LoadingView/index.ts b/src/views/LoadingView/index.ts new file mode 100644 index 0000000..6818542 --- /dev/null +++ b/src/views/LoadingView/index.ts @@ -0,0 +1 @@ +export * from "./LoadingView";