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";