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 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 "./cell.css"; | ||||
| import {State} from "./state/state.ts"; | ||||
| import {ActionDispatch} from "react"; | ||||
| 
 | ||||
| function isMarked(x: number, y: number, marked: Coordinates[]): boolean { | ||||
|     return !!marked.find(e => e.x === x && e.y === y); | ||||
| } | ||||
| 
 | ||||
| export default function Cell({x, y, state, dispatch}) { | ||||
|     const cell: MazeCell = state.maze.grid[y][x]; | ||||
| export default function Cell({x, y, state, dispatch}: | ||||
|                              { | ||||
|                                  x: number, | ||||
|                                  y: number, | ||||
|                                  state: State, | ||||
|                                  dispatch: ActionDispatch<[Action]> | ||||
|                              }) { | ||||
|     const cell: MazeCell = state.maze!.grid[y][x]; | ||||
|     let classes = " r" + y + " c" + x; | ||||
|     if (cell.top) classes += " top"; | ||||
|     if (cell.right) classes += " right"; | ||||
|  | @ -26,7 +34,7 @@ export default function Cell({x, y, state, dispatch}) { | |||
|                      dispatch(actionClickedCell(x, y)); | ||||
|                  } | ||||
|              }} | ||||
|              onClick={(e) => { | ||||
|              onClick={() => { | ||||
|                  dispatch(actionClickedCell(x, y)); | ||||
|              }}> | ||||
|         </div> | ||||
|  |  | |||
|  | @ -1,16 +1,20 @@ | |||
| import {useState} from 'react'; | ||||
| import ValidatingInputNumberField from "./validating-input-number-field.tsx"; | ||||
| import {actionLoadedMaze, actionLoadingFailed, actionStartedLoading} from "./state/action.ts"; | ||||
| import {ActionDispatch, FormEvent, useState} from 'react'; | ||||
| import ValidatingInputNumberField, {ValidatorFunction} from "./validating-input-number-field.tsx"; | ||||
| import {Action, actionLoadedMaze, actionLoadingFailed, actionStartedLoading} from "./state/action.ts"; | ||||
| import styles from "./input-form.module.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 [height, setHeight] = useState(10); | ||||
|     const [id, setId] = useState(null as number); | ||||
|     const [id, setId] = useState<number>(); | ||||
|     const [algorithm, setAlgorithm] = useState('wilson'); | ||||
| 
 | ||||
|     const handleSubmit = (e) => { | ||||
|     const handleSubmit = (e: FormEvent) => { | ||||
|         e.preventDefault(); | ||||
|         dispatch(actionStartedLoading()); | ||||
|         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)); | ||||
|             }); | ||||
|     }; | ||||
|     const validateWidthHeightInput = value => { | ||||
|         if (isNaN(value) || "" === value || (Math.floor(value) !== Number(value))) { | ||||
|     const validateWidthHeightInput: ValidatorFunction<string, number> = value => { | ||||
|         const numberValue = Number(value); | ||||
|         if (isNaN(numberValue) || "" === value || (Math.floor(numberValue) !== numberValue)) { | ||||
|             return { | ||||
|                 valid: false, | ||||
|                 message: "Must be an integer greater than 1.", | ||||
|                 value | ||||
|                 message: "Must be an integer greater than 1." | ||||
|             }; | ||||
|         } | ||||
|         if (value < 1) { | ||||
|         if (numberValue < 1) { | ||||
|             return { | ||||
|                 valid: false, | ||||
|                 message: "Must be greater than 1.", | ||||
|                 value | ||||
|                 message: "Must be greater than 1." | ||||
|             }; | ||||
|         } | ||||
|         return { | ||||
|             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").
 | ||||
|         if (isNaN(value) || ("" !== value && ((Math.floor(value) !== Number(value))))) { | ||||
|         if (isNaN(numberValue) || Math.floor(numberValue) !== numberValue) { | ||||
|             return { | ||||
|                 valid: false, | ||||
|                 message: "Must be empty or an integer", | ||||
|                 value | ||||
|                 message: "Must be empty or an integer" | ||||
|             }; | ||||
|         } | ||||
|         return { | ||||
|             valid: true, | ||||
|             value | ||||
|             value: numberValue | ||||
|         } | ||||
|     }; | ||||
|     return ( | ||||
|  |  | |||
|  | @ -1,14 +1,21 @@ | |||
| import Cell from "./cell.tsx"; | ||||
| 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) { | ||||
|         return <div>No valid maze.</div> | ||||
|     } | ||||
| 
 | ||||
|     let maze: JSX.Element[] = []; | ||||
|     const maze: JSX.Element[] = []; | ||||
|     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++) { | ||||
|             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 {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() { | ||||
|         dispatch(actionClosedMessageBanner()); | ||||
|     } | ||||
|  |  | |||
|  | @ -22,8 +22,8 @@ export default function Home() { | |||
|                        dispatch={dispatch}/> | ||||
|             {hasValidMaze && | ||||
|                 <> | ||||
|                     <h1>The Maze ({state.maze.width}x{state.maze.height}, Algorithm: {state.maze.algorithm}, | ||||
|                         ID: {state.maze.id})</h1> | ||||
|                     <h1>The Maze ({state.maze!.width}x{state.maze!.height}, Algorithm: {state.maze!.algorithm}, | ||||
|                         ID: {state.maze!.id})</h1> | ||||
|                     <input type={"checkbox"} | ||||
|                            onChange={(e) => { | ||||
|                                dispatch(actionToggledShowSolution(e.target.checked)); | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ import Maze from "../model/maze.ts"; | |||
| export interface Action { | ||||
|     type: string, | ||||
| 
 | ||||
|     [key: string]: any | ||||
|     [key: string]: boolean | number | string | object | null | undefined; | ||||
| } | ||||
| 
 | ||||
| export const ID_ACTION_STARTED_LOADING = 'started_loading'; | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ import { | |||
|     ID_ACTION_STARTED_LOADING, | ||||
|     ID_ACTION_TOGGLED_SHOW_SOLUTION | ||||
| } from "./action.ts"; | ||||
| import Maze from "@/app/model/maze.ts"; | ||||
| 
 | ||||
| export default function reduce(state: State, action: Action): State { | ||||
|     switch (action.type) { | ||||
|  | @ -24,7 +25,7 @@ export default function reduce(state: State, action: Action): State { | |||
|             return { | ||||
|                 ...state, | ||||
|                 loading: false, | ||||
|                 maze: action.maze, | ||||
|                 maze: action.maze as Maze, | ||||
|                 userPath: [] | ||||
|             } | ||||
|         } | ||||
|  | @ -38,7 +39,7 @@ export default function reduce(state: State, action: Action): State { | |||
|         case ID_ACTION_TOGGLED_SHOW_SOLUTION: { | ||||
|             return { | ||||
|                 ...state, | ||||
|                 showSolution: action.value | ||||
|                 showSolution: action.value as boolean | ||||
|             } | ||||
|         } | ||||
|         case ID_ACTION_CLOSED_MESSAGE_BANNER: { | ||||
|  | @ -49,7 +50,7 @@ export default function reduce(state: State, action: Action): State { | |||
|         } | ||||
|         case ID_ACTION_CLICKED_CELL: { | ||||
|             // 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: { | ||||
|             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 { | ||||
|     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)
 | ||||
|         // and that's not blocked by a wall. Now let's see.
 | ||||
|         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({ | ||||
|                                                        id, | ||||
|                                                        label, | ||||
|                                                        value = 0, | ||||
|                                                        constraints = {}, | ||||
|                                                        constraints = undefined, | ||||
|                                                        validatorFn = (value) => { | ||||
|                                                            return {valid: true, value}; | ||||
|                                                            return {valid: true, value: Number(value)}; | ||||
|                                                        }, | ||||
|                                                        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 validation = validatorFn(value); | ||||
|         if (!validation.valid) { | ||||
|             setError(validation.message); | ||||
|         } else { | ||||
|             setError(null); | ||||
|             setError(undefined); | ||||
|         } | ||||
|         if (validation.value) { | ||||
|             onChange(validation.value); | ||||
|         } | ||||
|         onChange(validation.value); | ||||
|     }; | ||||
|     return ( | ||||
|         <> | ||||
|  | @ -31,11 +42,19 @@ export default function ValidatingInputNumberField({ | |||
|                    type={"number"} | ||||
|                    onChange={handleValueChange} | ||||
|                    value={value || ""} | ||||
|                    min={constraints.min || null} | ||||
|                    max={constraints.max || null} | ||||
|                    min={constraints?.min} | ||||
|                    max={constraints?.max} | ||||
|                    disabled={disabled} | ||||
|             /> | ||||
|             <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