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; | ||||
| 
 | ||||
|   --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); | ||||
| } | ||||
|  | ||||
| @ -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 <LoadingView />; | ||||
|   if (!expressions.length) { | ||||
|     return <ErrorView message="No expression cards yet" />; | ||||
|   } | ||||
|  | ||||
| @ -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 <LoadingView />; | ||||
|   if (expression === ItemNotFoundError) | ||||
|     return <ErrorView message="Expression card not found" />; | ||||
| 
 | ||||
|  | ||||
| @ -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 <LoadingView />; | ||||
| 
 | ||||
|   const filtered_expressions = expressions.filter( | ||||
|     (expression) => !filter_ids.includes(expression.id!) | ||||
|  | ||||
| @ -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 <LoadingView />; | ||||
|   if (expression_set === ItemNotFoundError) { | ||||
|     return <ErrorView message="Expression set not found" />; | ||||
|   } | ||||
|  | ||||
| @ -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 <LoadingView />; | ||||
|   if (!expression_sets.length) { | ||||
|     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