Fix linting and build errors.
This commit is contained in:
		
							parent
							
								
									d8d05589e7
								
							
						
					
					
						commit
						36e1ea45d5
					
				
					 9 changed files with 94 additions and 45 deletions
				
			
		|  | @ -1,15 +1,23 @@ | ||||||
| import {MazeCell} from "./model/maze.ts"; | import {MazeCell} from "./model/maze.ts"; | ||||||
| import Coordinates from "./model/coordinates.ts"; | import Coordinates from "./model/coordinates.ts"; | ||||||
| import {actionClickedCell} from "./state/action.ts"; | import {Action, actionClickedCell} from "./state/action.ts"; | ||||||
| import styles from "./cell.module.css"; | import styles from "./cell.module.css"; | ||||||
| import "./cell.css"; | import "./cell.css"; | ||||||
|  | import {State} from "./state/state.ts"; | ||||||
|  | import {ActionDispatch} from "react"; | ||||||
| 
 | 
 | ||||||
| function isMarked(x: number, y: number, marked: Coordinates[]): boolean { | function isMarked(x: number, y: number, marked: Coordinates[]): boolean { | ||||||
|     return !!marked.find(e => e.x === x && e.y === y); |     return !!marked.find(e => e.x === x && e.y === y); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default function Cell({x, y, state, dispatch}) { | export default function Cell({x, y, state, dispatch}: | ||||||
|     const cell: MazeCell = state.maze.grid[y][x]; |                              { | ||||||
|  |                                  x: number, | ||||||
|  |                                  y: number, | ||||||
|  |                                  state: State, | ||||||
|  |                                  dispatch: ActionDispatch<[Action]> | ||||||
|  |                              }) { | ||||||
|  |     const cell: MazeCell = state.maze!.grid[y][x]; | ||||||
|     let classes = " r" + y + " c" + x; |     let classes = " r" + y + " c" + x; | ||||||
|     if (cell.top) classes += " top"; |     if (cell.top) classes += " top"; | ||||||
|     if (cell.right) classes += " right"; |     if (cell.right) classes += " right"; | ||||||
|  | @ -26,7 +34,7 @@ export default function Cell({x, y, state, dispatch}) { | ||||||
|                      dispatch(actionClickedCell(x, y)); |                      dispatch(actionClickedCell(x, y)); | ||||||
|                  } |                  } | ||||||
|              }} |              }} | ||||||
|              onClick={(e) => { |              onClick={() => { | ||||||
|                  dispatch(actionClickedCell(x, y)); |                  dispatch(actionClickedCell(x, y)); | ||||||
|              }}> |              }}> | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
|  | @ -1,16 +1,20 @@ | ||||||
| import {useState} from 'react'; | import {ActionDispatch, FormEvent, useState} from 'react'; | ||||||
| import ValidatingInputNumberField from "./validating-input-number-field.tsx"; | import ValidatingInputNumberField, {ValidatorFunction} from "./validating-input-number-field.tsx"; | ||||||
| import {actionLoadedMaze, actionLoadingFailed, actionStartedLoading} from "./state/action.ts"; | import {Action, actionLoadedMaze, actionLoadingFailed, actionStartedLoading} from "./state/action.ts"; | ||||||
| import styles from "./input-form.module.css"; | import styles from "./input-form.module.css"; | ||||||
| import "./input-form.css"; | import "./input-form.css"; | ||||||
|  | import {State} from "@/app/state/state.ts"; | ||||||
| 
 | 
 | ||||||
| export default function InputForm({state, dispatch}) { | export default function InputForm({state, dispatch}: { | ||||||
|  |     state: State, | ||||||
|  |     dispatch: ActionDispatch<[Action]> | ||||||
|  | }) { | ||||||
|     const [width, setWidth] = useState(10); |     const [width, setWidth] = useState(10); | ||||||
|     const [height, setHeight] = useState(10); |     const [height, setHeight] = useState(10); | ||||||
|     const [id, setId] = useState(null as number); |     const [id, setId] = useState<number>(); | ||||||
|     const [algorithm, setAlgorithm] = useState('wilson'); |     const [algorithm, setAlgorithm] = useState('wilson'); | ||||||
| 
 | 
 | ||||||
|     const handleSubmit = (e) => { |     const handleSubmit = (e: FormEvent) => { | ||||||
|         e.preventDefault(); |         e.preventDefault(); | ||||||
|         dispatch(actionStartedLoading()); |         dispatch(actionStartedLoading()); | ||||||
|         const url = `https://manuel.friedli.info/labyrinth/create/json?w=${width}&h=${height}&id=${id || ''}&algorithm=${algorithm}`; |         const url = `https://manuel.friedli.info/labyrinth/create/json?w=${width}&h=${height}&id=${id || ''}&algorithm=${algorithm}`; | ||||||
|  | @ -25,38 +29,42 @@ export default function InputForm({state, dispatch}) { | ||||||
|                 dispatch(actionLoadingFailed(reason)); |                 dispatch(actionLoadingFailed(reason)); | ||||||
|             }); |             }); | ||||||
|     }; |     }; | ||||||
|     const validateWidthHeightInput = value => { |     const validateWidthHeightInput: ValidatorFunction<string, number> = value => { | ||||||
|         if (isNaN(value) || "" === value || (Math.floor(value) !== Number(value))) { |         const numberValue = Number(value); | ||||||
|  |         if (isNaN(numberValue) || "" === value || (Math.floor(numberValue) !== numberValue)) { | ||||||
|             return { |             return { | ||||||
|                 valid: false, |                 valid: false, | ||||||
|                 message: "Must be an integer greater than 1.", |                 message: "Must be an integer greater than 1." | ||||||
|                 value |  | ||||||
|             }; |             }; | ||||||
|         } |         } | ||||||
|         if (value < 1) { |         if (numberValue < 1) { | ||||||
|             return { |             return { | ||||||
|                 valid: false, |                 valid: false, | ||||||
|                 message: "Must be greater than 1.", |                 message: "Must be greater than 1." | ||||||
|                 value |  | ||||||
|             }; |             }; | ||||||
|         } |         } | ||||||
|         return { |         return { | ||||||
|             valid: true, |             valid: true, | ||||||
|             value |             value: numberValue | ||||||
|         }; |         }; | ||||||
|     }; |     }; | ||||||
|     const validateIdInput = value => { |     const validateIdInput: ValidatorFunction<string, number> = value => { | ||||||
|  |         if ("" === value) { | ||||||
|  |             return { | ||||||
|  |                 valid: true | ||||||
|  |             }; | ||||||
|  |         } | ||||||
|  |         const numberValue = Number(value); | ||||||
|         // FIXME doesn't handle strings with characters correctly (e.g. "asdf" yields an empty value, due to "type=number").
 |         // FIXME doesn't handle strings with characters correctly (e.g. "asdf" yields an empty value, due to "type=number").
 | ||||||
|         if (isNaN(value) || ("" !== value && ((Math.floor(value) !== Number(value))))) { |         if (isNaN(numberValue) || Math.floor(numberValue) !== numberValue) { | ||||||
|             return { |             return { | ||||||
|                 valid: false, |                 valid: false, | ||||||
|                 message: "Must be empty or an integer", |                 message: "Must be empty or an integer" | ||||||
|                 value |  | ||||||
|             }; |             }; | ||||||
|         } |         } | ||||||
|         return { |         return { | ||||||
|             valid: true, |             valid: true, | ||||||
|             value |             value: numberValue | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|     return ( |     return ( | ||||||
|  |  | ||||||
|  | @ -1,14 +1,21 @@ | ||||||
| import Cell from "./cell.tsx"; | import Cell from "./cell.tsx"; | ||||||
| import styles from "./maze.module.css"; | import styles from "./maze.module.css"; | ||||||
|  | import {State} from "@/app/state/state.ts"; | ||||||
|  | import {ActionDispatch, JSX} from "react"; | ||||||
|  | import {Action} from "@/app/state/action.ts"; | ||||||
| 
 | 
 | ||||||
| export default function Maze({state, dispatch}) { | export default function Maze({state, dispatch}: | ||||||
|  |                              { | ||||||
|  |                                  state: State, | ||||||
|  |                                  dispatch: ActionDispatch<[Action]> | ||||||
|  |                              }) { | ||||||
|     if (!state.maze) { |     if (!state.maze) { | ||||||
|         return <div>No valid maze.</div> |         return <div>No valid maze.</div> | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     let maze: JSX.Element[] = []; |     const maze: JSX.Element[] = []; | ||||||
|     for (let y = 0; y < state.maze.height; y++) { |     for (let y = 0; y < state.maze.height; y++) { | ||||||
|         let row: JSX.Element[] = []; |         const row: JSX.Element[] = []; | ||||||
|         for (let x = 0; x < state.maze.width; x++) { |         for (let x = 0; x < state.maze.width; x++) { | ||||||
|             row.push(<Cell key={`${x}x${y}`} x={x} y={y} state={state} dispatch={dispatch}/>) |             row.push(<Cell key={`${x}x${y}`} x={x} y={y} state={state} dispatch={dispatch}/>) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -1,7 +1,13 @@ | ||||||
| import {actionClosedMessageBanner} from "./state/action.ts"; | import {Action, actionClosedMessageBanner} from "./state/action.ts"; | ||||||
| import styles from "./message-banner.module.css"; | import styles from "./message-banner.module.css"; | ||||||
|  | import {State} from "@/app/state/state.ts"; | ||||||
|  | import {ActionDispatch} from "react"; | ||||||
| 
 | 
 | ||||||
| export default function MessageBanner({state, dispatch}) { | export default function MessageBanner({state, dispatch}: | ||||||
|  |                                       { | ||||||
|  |                                           state: State; | ||||||
|  |                                           dispatch: ActionDispatch<[Action]> | ||||||
|  |                                       }) { | ||||||
|     function handleClose() { |     function handleClose() { | ||||||
|         dispatch(actionClosedMessageBanner()); |         dispatch(actionClosedMessageBanner()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -22,8 +22,8 @@ export default function Home() { | ||||||
|                        dispatch={dispatch}/> |                        dispatch={dispatch}/> | ||||||
|             {hasValidMaze && |             {hasValidMaze && | ||||||
|                 <> |                 <> | ||||||
|                     <h1>The Maze ({state.maze.width}x{state.maze.height}, Algorithm: {state.maze.algorithm}, |                     <h1>The Maze ({state.maze!.width}x{state.maze!.height}, Algorithm: {state.maze!.algorithm}, | ||||||
|                         ID: {state.maze.id})</h1> |                         ID: {state.maze!.id})</h1> | ||||||
|                     <input type={"checkbox"} |                     <input type={"checkbox"} | ||||||
|                            onChange={(e) => { |                            onChange={(e) => { | ||||||
|                                dispatch(actionToggledShowSolution(e.target.checked)); |                                dispatch(actionToggledShowSolution(e.target.checked)); | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import Maze from "../model/maze.ts"; | ||||||
| export interface Action { | export interface Action { | ||||||
|     type: string, |     type: string, | ||||||
| 
 | 
 | ||||||
|     [key: string]: any |     [key: string]: boolean | number | string | object | null | undefined; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const ID_ACTION_STARTED_LOADING = 'started_loading'; | export const ID_ACTION_STARTED_LOADING = 'started_loading'; | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ import { | ||||||
|     ID_ACTION_STARTED_LOADING, |     ID_ACTION_STARTED_LOADING, | ||||||
|     ID_ACTION_TOGGLED_SHOW_SOLUTION |     ID_ACTION_TOGGLED_SHOW_SOLUTION | ||||||
| } from "./action.ts"; | } from "./action.ts"; | ||||||
|  | import Maze from "@/app/model/maze.ts"; | ||||||
| 
 | 
 | ||||||
| export default function reduce(state: State, action: Action): State { | export default function reduce(state: State, action: Action): State { | ||||||
|     switch (action.type) { |     switch (action.type) { | ||||||
|  | @ -24,7 +25,7 @@ export default function reduce(state: State, action: Action): State { | ||||||
|             return { |             return { | ||||||
|                 ...state, |                 ...state, | ||||||
|                 loading: false, |                 loading: false, | ||||||
|                 maze: action.maze, |                 maze: action.maze as Maze, | ||||||
|                 userPath: [] |                 userPath: [] | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | @ -38,7 +39,7 @@ export default function reduce(state: State, action: Action): State { | ||||||
|         case ID_ACTION_TOGGLED_SHOW_SOLUTION: { |         case ID_ACTION_TOGGLED_SHOW_SOLUTION: { | ||||||
|             return { |             return { | ||||||
|                 ...state, |                 ...state, | ||||||
|                 showSolution: action.value |                 showSolution: action.value as boolean | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         case ID_ACTION_CLOSED_MESSAGE_BANNER: { |         case ID_ACTION_CLOSED_MESSAGE_BANNER: { | ||||||
|  | @ -49,7 +50,7 @@ export default function reduce(state: State, action: Action): State { | ||||||
|         } |         } | ||||||
|         case ID_ACTION_CLICKED_CELL: { |         case ID_ACTION_CLICKED_CELL: { | ||||||
|             // There's so much logic involved, externalize that into its own file.
 |             // There's so much logic involved, externalize that into its own file.
 | ||||||
|             return handleUserClicked(state, action.x, action.y); |             return handleUserClicked(state, action.x as number, action.y as number); | ||||||
|         } |         } | ||||||
|         default: { |         default: { | ||||||
|             throw new Error(`Unknown action: ${action.type}`); |             throw new Error(`Unknown action: ${action.type}`); | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import {MazeCell} from "../model/maze.ts"; | ||||||
| 
 | 
 | ||||||
| export default function handleUserClicked(state: State, x: number, y: number): State { | export default function handleUserClicked(state: State, x: number, y: number): State { | ||||||
|     if (isClickAllowed(x, y, state)) { |     if (isClickAllowed(x, y, state)) { | ||||||
|         let maze = state.maze!; |         const maze = state.maze!; | ||||||
|         // Okay, we clicked a cell that's adjacent to the end of the userpath (or which IS the end of the userpath)
 |         // Okay, we clicked a cell that's adjacent to the end of the userpath (or which IS the end of the userpath)
 | ||||||
|         // and that's not blocked by a wall. Now let's see.
 |         // and that's not blocked by a wall. Now let's see.
 | ||||||
|         if (-1 === state.userPath.findIndex(step => step.x === x && step.y === y)) { |         if (-1 === state.userPath.findIndex(step => step.x === x && step.y === y)) { | ||||||
|  |  | ||||||
|  | @ -1,28 +1,39 @@ | ||||||
| import React, {useState} from 'react'; | import React, {ChangeEventHandler, useState} from 'react'; | ||||||
| 
 | 
 | ||||||
| export default function ValidatingInputNumberField({ | export default function ValidatingInputNumberField({ | ||||||
|                                                        id, |                                                        id, | ||||||
|                                                        label, |                                                        label, | ||||||
|                                                        value = 0, |                                                        value = 0, | ||||||
|                                                        constraints = {}, |                                                        constraints = undefined, | ||||||
|                                                        validatorFn = (value) => { |                                                        validatorFn = (value) => { | ||||||
|                                                            return {valid: true, value}; |                                                            return {valid: true, value: Number(value)}; | ||||||
|                                                        }, |                                                        }, | ||||||
|                                                        disabled = false, |                                                        disabled = false, | ||||||
|                                                        onChange = _ => { |                                                        onChange = () => { | ||||||
|                                                        } |                                                        } | ||||||
|  |                                                    }: | ||||||
|  |                                                    { | ||||||
|  |                                                        id: string; | ||||||
|  |                                                        label: string; | ||||||
|  |                                                        value?: number; | ||||||
|  |                                                        constraints?: { min?: number; max?: number; }; | ||||||
|  |                                                        validatorFn: ValidatorFunction<string, number>; | ||||||
|  |                                                        disabled: boolean; | ||||||
|  |                                                        onChange: (v: number) => void; | ||||||
|                                                    }) { |                                                    }) { | ||||||
|     const [error, setError] = useState(null); |     const [error, setError] = useState<string>(); | ||||||
| 
 | 
 | ||||||
|     const handleValueChange = (e) => { |     const handleValueChange: ChangeEventHandler<HTMLInputElement> = (e) => { | ||||||
|         const value = e.target.value; |         const value = e.target.value; | ||||||
|         const validation = validatorFn(value); |         const validation = validatorFn(value); | ||||||
|         if (!validation.valid) { |         if (!validation.valid) { | ||||||
|             setError(validation.message); |             setError(validation.message); | ||||||
|         } else { |         } else { | ||||||
|             setError(null); |             setError(undefined); | ||||||
|         } |         } | ||||||
|  |         if (validation.value) { | ||||||
|             onChange(validation.value); |             onChange(validation.value); | ||||||
|  |         } | ||||||
|     }; |     }; | ||||||
|     return ( |     return ( | ||||||
|         <> |         <> | ||||||
|  | @ -31,11 +42,19 @@ export default function ValidatingInputNumberField({ | ||||||
|                    type={"number"} |                    type={"number"} | ||||||
|                    onChange={handleValueChange} |                    onChange={handleValueChange} | ||||||
|                    value={value || ""} |                    value={value || ""} | ||||||
|                    min={constraints.min || null} |                    min={constraints?.min} | ||||||
|                    max={constraints.max || null} |                    max={constraints?.max} | ||||||
|                    disabled={disabled} |                    disabled={disabled} | ||||||
|             /> |             /> | ||||||
|             <span>{error}</span> |             <span>{error}</span> | ||||||
|         </> |         </> | ||||||
|     ); |     ); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export interface Validation<T> { | ||||||
|  |     valid: boolean; | ||||||
|  |     message?: string; | ||||||
|  |     value?: T; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export type ValidatorFunction<I, T> = (v: I) => Validation<T>; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue