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…
Reference in a new issue