Some fiddling, but also version upgrades and a new button for downloading a pdf version of the current maze.
Some checks reported errors
continuous-integration/drone/push Build encountered an error
Some checks reported errors
continuous-integration/drone/push Build encountered an error
This commit is contained in:
parent
79467e29f2
commit
b6359bcb5d
21 changed files with 2002 additions and 927 deletions
1
next-env.d.ts
vendored
1
next-env.d.ts
vendored
|
|
@ -1,5 +1,6 @@
|
|||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
|
|
|||
1963
package-lock.json
generated
1963
package-lock.json
generated
File diff suppressed because it is too large
Load diff
13
package.json
13
package.json
|
|
@ -9,17 +9,18 @@
|
|||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"next": "15.1.2"
|
||||
"next": "^16.1.4",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.1.2",
|
||||
"@eslint/eslintrc": "^3"
|
||||
"eslint-config-next": "^16.1.4",
|
||||
"sass": "^1.85.1",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
109
src/app/cell.css
109
src/app/cell.css
|
|
@ -1,109 +0,0 @@
|
|||
.solution {
|
||||
background-color: var(--color-maze-cell-solution);
|
||||
}
|
||||
|
||||
.solution:hover {
|
||||
background-color: var(--color-maze-cell-solution-highlight);
|
||||
}
|
||||
|
||||
.top {
|
||||
border-top-color: var(--color-maze-border);
|
||||
}
|
||||
|
||||
.right {
|
||||
border-right-color: var(--color-maze-border);
|
||||
}
|
||||
|
||||
.bottom {
|
||||
border-bottom-color: var(--color-maze-border);
|
||||
}
|
||||
|
||||
.left {
|
||||
border-left-color: var(--color-maze-border);
|
||||
}
|
||||
|
||||
.userSELF {
|
||||
background: radial-gradient(
|
||||
ellipse 16% 16% at center,
|
||||
var(--color-maze-cell-user) 0,
|
||||
var(--color-maze-cell-user) 100%,
|
||||
#0000 100%
|
||||
);
|
||||
}
|
||||
|
||||
.userSELF:hover {
|
||||
background: radial-gradient(
|
||||
ellipse 33% 33% at center,
|
||||
var(--color-maze-cell-user) 0,
|
||||
var(--color-maze-cell-user) 80%,
|
||||
#0000 100%
|
||||
);
|
||||
}
|
||||
|
||||
.marker {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.marker:hover {
|
||||
background: #fc08;
|
||||
}
|
||||
|
||||
.userUP .marker.UP {
|
||||
height: 50%;
|
||||
width: 100%;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
#0000 0,
|
||||
#0000 33%,
|
||||
var(--color-maze-cell-user) 33%,
|
||||
var(--color-maze-cell-user) 66%,
|
||||
#0000 66%,
|
||||
#0000 100%
|
||||
);
|
||||
}
|
||||
|
||||
.userRIGHT .marker.RIGHT {
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
left: 50%;
|
||||
background: linear-gradient(
|
||||
0deg,
|
||||
#0000 0,
|
||||
#0000 33%,
|
||||
var(--color-maze-cell-user) 33%,
|
||||
var(--color-maze-cell-user) 66%,
|
||||
#0000 66%,
|
||||
#0000 100%
|
||||
);
|
||||
}
|
||||
|
||||
.userDOWN .marker.DOWN {
|
||||
height: 50%;
|
||||
width: 100%;
|
||||
top: 50%;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
#0000 0,
|
||||
#0000 33%,
|
||||
var(--color-maze-cell-user) 33%,
|
||||
var(--color-maze-cell-user) 66%,
|
||||
#0000 66%,
|
||||
#0000 100%
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
.userLEFT .marker.LEFT {
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
background: linear-gradient(
|
||||
0deg,
|
||||
#0000 0,
|
||||
#0000 33%,
|
||||
var(--color-maze-cell-user) 33%,
|
||||
var(--color-maze-cell-user) 66%,
|
||||
#0000 66%,
|
||||
#0000 100%
|
||||
);
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
.cell {
|
||||
display: table-cell;
|
||||
border: 1px solid transparent;
|
||||
height: 2em;
|
||||
width: 2em;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.cell:hover {
|
||||
background-color: var(--color-background-highlight);
|
||||
}
|
||||
12
src/app/cell.module.scss
Normal file
12
src/app/cell.module.scss
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
.cell {
|
||||
display: table-cell;
|
||||
border: 1px solid transparent;
|
||||
height: 2em;
|
||||
width: 2em;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-background-highlight);
|
||||
}
|
||||
}
|
||||
186
src/app/cell.scss
Normal file
186
src/app/cell.scss
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
.solution {
|
||||
background-color: var(--color-maze-cell-solution);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-maze-cell-solution-highlight);
|
||||
}
|
||||
}
|
||||
|
||||
.top {
|
||||
border-top-color: var(--color-maze-border);
|
||||
}
|
||||
|
||||
.right {
|
||||
border-right-color: var(--color-maze-border);
|
||||
}
|
||||
|
||||
.bottom {
|
||||
border-bottom-color: var(--color-maze-border);
|
||||
}
|
||||
|
||||
.left {
|
||||
border-left-color: var(--color-maze-border);
|
||||
}
|
||||
|
||||
.userSELF {
|
||||
background: radial-gradient(
|
||||
ellipse 16% 16% at center,
|
||||
var(--color-maze-cell-user) 0,
|
||||
var(--color-maze-cell-user) 100%,
|
||||
#0000 100%
|
||||
);
|
||||
|
||||
&.solution {
|
||||
background: radial-gradient(
|
||||
ellipse 16% 16% at center,
|
||||
var(--color-maze-cell-user-solution) 0,
|
||||
var(--color-maze-cell-user-solution) 100%,
|
||||
#0000 100%
|
||||
);
|
||||
|
||||
&:hover {
|
||||
background: radial-gradient(
|
||||
ellipse 33% 33% at center,
|
||||
var(--color-maze-cell-user-solution) 0,
|
||||
var(--color-maze-cell-user-solution) 80%,
|
||||
#0000 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: radial-gradient(
|
||||
ellipse 33% 33% at center,
|
||||
var(--color-maze-cell-user) 0,
|
||||
var(--color-maze-cell-user) 80%,
|
||||
#0000 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.marker {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
|
||||
&:hover {
|
||||
background: #fc08;
|
||||
}
|
||||
}
|
||||
|
||||
.userUP .marker.UP {
|
||||
height: 50%;
|
||||
width: 100%;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
#0000 0,
|
||||
#0000 33%,
|
||||
var(--color-maze-cell-user) 33%,
|
||||
var(--color-maze-cell-user) 66%,
|
||||
#0000 66%,
|
||||
#0000 100%
|
||||
);
|
||||
}
|
||||
|
||||
.solution.userUP .marker.UP {
|
||||
height: 50%;
|
||||
width: 100%;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
#0000 0,
|
||||
#0000 33%,
|
||||
var(--color-maze-cell-user-solution) 33%,
|
||||
var(--color-maze-cell-user-solution) 66%,
|
||||
#0000 66%,
|
||||
#0000 100%
|
||||
);
|
||||
}
|
||||
|
||||
.userRIGHT .marker.RIGHT {
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
left: 50%;
|
||||
background: linear-gradient(
|
||||
0deg,
|
||||
#0000 0,
|
||||
#0000 33%,
|
||||
var(--color-maze-cell-user) 33%,
|
||||
var(--color-maze-cell-user) 66%,
|
||||
#0000 66%,
|
||||
#0000 100%
|
||||
);
|
||||
}
|
||||
|
||||
.solution.userRIGHT .marker.RIGHT {
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
left: 50%;
|
||||
background: linear-gradient(
|
||||
0deg,
|
||||
#0000 0,
|
||||
#0000 33%,
|
||||
var(--color-maze-cell-user-solution) 33%,
|
||||
var(--color-maze-cell-user-solution) 66%,
|
||||
#0000 66%,
|
||||
#0000 100%
|
||||
);
|
||||
}
|
||||
|
||||
.userDOWN .marker.DOWN {
|
||||
height: 50%;
|
||||
width: 100%;
|
||||
top: 50%;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
#0000 0,
|
||||
#0000 33%,
|
||||
var(--color-maze-cell-user) 33%,
|
||||
var(--color-maze-cell-user) 66%,
|
||||
#0000 66%,
|
||||
#0000 100%
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
.solution.userDOWN .marker.DOWN {
|
||||
height: 50%;
|
||||
width: 100%;
|
||||
top: 50%;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
#0000 0,
|
||||
#0000 33%,
|
||||
var(--color-maze-cell-user-solution) 33%,
|
||||
var(--color-maze-cell-user-solution) 66%,
|
||||
#0000 66%,
|
||||
#0000 100%
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
.userLEFT .marker.LEFT {
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
background: linear-gradient(
|
||||
0deg,
|
||||
#0000 0,
|
||||
#0000 33%,
|
||||
var(--color-maze-cell-user) 33%,
|
||||
var(--color-maze-cell-user) 66%,
|
||||
#0000 66%,
|
||||
#0000 100%
|
||||
);
|
||||
}
|
||||
|
||||
.solution.userLEFT .marker.LEFT {
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
background: linear-gradient(
|
||||
0deg,
|
||||
#0000 0,
|
||||
#0000 33%,
|
||||
var(--color-maze-cell-user-solution) 33%,
|
||||
var(--color-maze-cell-user-solution) 66%,
|
||||
#0000 66%,
|
||||
#0000 100%
|
||||
);
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import {MazeCell} from "./model/maze.ts";
|
||||
import Coordinates from "./model/coordinates.ts";
|
||||
import {Action, actionClickedCell} from "./state/action.ts";
|
||||
import styles from "./cell.module.css";
|
||||
import "./cell.css";
|
||||
import styles from "./cell.module.scss";
|
||||
import "./cell.scss";
|
||||
import {State} from "./state/state.ts";
|
||||
import {ActionDispatch} from "react";
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@
|
|||
--color-maze-cell-solution: #b1d5b1;
|
||||
--color-maze-cell-solution-highlight: #b9e8b9;
|
||||
--color-maze-cell-user: #ffcc00;
|
||||
--color-maze-cell-user-highlight: #ffdd22;
|
||||
--color-maze-cell-user-solution: #47e147;
|
||||
--color-accent: #ffcc00;
|
||||
--color-accent-inverse: #000000;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
|
|
@ -29,7 +31,7 @@
|
|||
--color-maze-cell-solution: #213d21;
|
||||
--color-maze-cell-solution-highlight: #3d6e3d;
|
||||
--color-maze-cell-user: #ffcc00;
|
||||
--color-maze-cell-user-highlight: #ffdd22;
|
||||
--color-maze-cell-user-solution: #00a421;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
form {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
input, select {
|
||||
background-color: var(--color-background);
|
||||
border: 1px solid var(--color-border);
|
||||
color: var(--color-foreground);
|
||||
}
|
||||
|
||||
input:hover, input:focus, select:hover, select:focus {
|
||||
background-color: var(--color-background-highlight);
|
||||
border-color: var(--color-border-highlight);
|
||||
color: var(--color-foreground-highlight);
|
||||
}
|
||||
|
||||
input:invalid {
|
||||
border-color: var(--color-form-error);
|
||||
}
|
||||
|
|
@ -5,8 +5,9 @@
|
|||
}
|
||||
|
||||
.submitbutton {
|
||||
background-color: #fc0;
|
||||
background-color: var(--color-accent);
|
||||
border: 1px solid var(--color-foreground);
|
||||
color: var(--color-accent-inverse);
|
||||
padding: 0.5em;
|
||||
border-radius: 0.5em;
|
||||
margin-top: 2em;
|
||||
21
src/app/input-form.scss
Normal file
21
src/app/input-form.scss
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
form {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
input, select {
|
||||
background-color: var(--color-background);
|
||||
border: 1px solid var(--color-border);
|
||||
color: var(--color-foreground);
|
||||
|
||||
&:hover, &:focus {
|
||||
background-color: var(--color-background-highlight);
|
||||
border-color: var(--color-border-highlight);
|
||||
color: var(--color-foreground-highlight);
|
||||
}
|
||||
}
|
||||
|
||||
input:invalid {
|
||||
border-color: var(--color-form-error);
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import {ActionDispatch, FormEvent, useState} from 'react';
|
||||
import ValidatingInputNumberField, {ValidatorFunction} from "./validating-input-number-field.tsx";
|
||||
import {
|
||||
Action,
|
||||
actionLoadedMaze,
|
||||
|
|
@ -7,10 +6,14 @@ import {
|
|||
actionStartedLoading,
|
||||
actionToggledShowSolution
|
||||
} from "./state/action.ts";
|
||||
import styles from "./input-form.module.css";
|
||||
import "./input-form.css";
|
||||
import styles from "./input-form.module.scss";
|
||||
import "./input-form.scss";
|
||||
import {State} from "@/app/state/state.ts";
|
||||
import ValidatingInputBigIntField from "@/app/validating-input-bigint-field.tsx";
|
||||
import {
|
||||
ValidatingInputNumberField,
|
||||
ValidatingInputRegExpField,
|
||||
ValidatorFunction
|
||||
} from "@/app/validating-input-field.tsx";
|
||||
|
||||
export default function InputForm({state, dispatch}: {
|
||||
state: State,
|
||||
|
|
@ -18,7 +21,7 @@ export default function InputForm({state, dispatch}: {
|
|||
}) {
|
||||
const [width, setWidth] = useState(10);
|
||||
const [height, setHeight] = useState(10);
|
||||
const [id, setId] = useState<bigint>();
|
||||
const [id, setId] = useState<string>();
|
||||
const [algorithm, setAlgorithm] = useState('wilson');
|
||||
|
||||
const handleSubmit = (e: FormEvent) => {
|
||||
|
|
@ -36,7 +39,7 @@ export default function InputForm({state, dispatch}: {
|
|||
dispatch(actionLoadingFailed(reason));
|
||||
});
|
||||
};
|
||||
const validateWidthHeightInput: ValidatorFunction<string, number> = value => {
|
||||
const validateSizeInput: ValidatorFunction<string, number> = value => {
|
||||
const numberValue = Number(value);
|
||||
if (isNaN(numberValue) || "" === value || (Math.floor(numberValue) !== numberValue)) {
|
||||
return {
|
||||
|
|
@ -55,24 +58,6 @@ export default function InputForm({state, dispatch}: {
|
|||
value: numberValue
|
||||
};
|
||||
};
|
||||
const validateIdInput: ValidatorFunction<string, bigint> = value => {
|
||||
if ("" === value) {
|
||||
return {
|
||||
valid: true
|
||||
};
|
||||
}
|
||||
const numberValue = BigInt(value);
|
||||
if (numberValue.toString() !== value.trim()) {
|
||||
return {
|
||||
valid: false,
|
||||
message: "Must be empty or an integer"
|
||||
};
|
||||
}
|
||||
return {
|
||||
valid: true,
|
||||
value: numberValue
|
||||
}
|
||||
};
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className={styles.inputform}>
|
||||
|
|
@ -82,7 +67,7 @@ export default function InputForm({state, dispatch}: {
|
|||
constraints={{
|
||||
min: 2
|
||||
}}
|
||||
validatorFn={validateWidthHeightInput}
|
||||
validatorFn={validateSizeInput}
|
||||
disabled={state.loading}
|
||||
onChange={setWidth}
|
||||
/>
|
||||
|
|
@ -92,16 +77,30 @@ export default function InputForm({state, dispatch}: {
|
|||
constraints={{
|
||||
min: 2
|
||||
}}
|
||||
validatorFn={validateWidthHeightInput}
|
||||
validatorFn={validateSizeInput}
|
||||
disabled={state.loading}
|
||||
onChange={setHeight}
|
||||
/>
|
||||
<ValidatingInputBigIntField id={"id"}
|
||||
{/*<ValidatingInputBigIntField id={"id"}*/}
|
||||
{/* label={"ID (optional)"}*/}
|
||||
{/* value={id}*/}
|
||||
{/* validatorFn={validateIdInput}*/}
|
||||
{/* disabled={state.loading}*/}
|
||||
{/* onChange={setId}*/}
|
||||
{/* constraints={{*/}
|
||||
{/* min: -9223372036854775808n,*/}
|
||||
{/* max: 9223372036854775807n*/}
|
||||
{/* }}*/}
|
||||
{/*/>*/}
|
||||
<ValidatingInputRegExpField id={"id"}
|
||||
label={"ID (optional)"}
|
||||
value={id}
|
||||
validatorFn={validateIdInput}
|
||||
disabled={state.loading}
|
||||
onChange={setId}
|
||||
constraints={[
|
||||
/^[0-9a-fA-F]{0,16}$/
|
||||
]}
|
||||
placeholder={"Hex-Number (without 0x prefix)"}
|
||||
/>
|
||||
<label htmlFor="algorithm">Algorithm:</label>
|
||||
<select id={"algorithm"}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type {Metadata} from "next";
|
||||
import "./globals.css";
|
||||
import "./globals.scss";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "A-Maze-R! Create your own Maze!",
|
||||
|
|
|
|||
|
|
@ -1,179 +0,0 @@
|
|||
.page {
|
||||
}
|
||||
|
||||
.page h1.mainheading, .page h2.mainheading {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.page h2.mainheading {
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
/*.page {*/
|
||||
/* --gray-rgb: 0, 0, 0;*/
|
||||
/* --gray-alpha-200: rgba(var(--gray-rgb), 0.08);*/
|
||||
/* --gray-alpha-100: rgba(var(--gray-rgb), 0.05);*/
|
||||
|
||||
/* --button-primary-hover: #383838;*/
|
||||
/* --button-secondary-hover: #f2f2f2;*/
|
||||
|
||||
/* display: grid;*/
|
||||
/* grid-template-rows: 20px 1fr 20px;*/
|
||||
/* align-items: center;*/
|
||||
/* justify-items: center;*/
|
||||
/* min-height: 100svh;*/
|
||||
/* padding: 80px;*/
|
||||
/* gap: 64px;*/
|
||||
/* font-family: var(--font-geist-sans);*/
|
||||
/*}*/
|
||||
|
||||
/*@media (prefers-color-scheme: dark) {*/
|
||||
/* .page {*/
|
||||
/* --gray-rgb: 255, 255, 255;*/
|
||||
/* --gray-alpha-200: rgba(var(--gray-rgb), 0.145);*/
|
||||
/* --gray-alpha-100: rgba(var(--gray-rgb), 0.06);*/
|
||||
|
||||
/* --button-primary-hover: #ccc;*/
|
||||
/* --button-secondary-hover: #1a1a1a;*/
|
||||
/* }*/
|
||||
/*}*/
|
||||
|
||||
/*.main {*/
|
||||
/* display: flex;*/
|
||||
/* flex-direction: column;*/
|
||||
/* gap: 32px;*/
|
||||
/* grid-row-start: 2;*/
|
||||
/*}*/
|
||||
|
||||
/*.main ol {*/
|
||||
/* font-family: var(--font-geist-mono);*/
|
||||
/* padding-left: 0;*/
|
||||
/* margin: 0;*/
|
||||
/* font-size: 14px;*/
|
||||
/* line-height: 24px;*/
|
||||
/* letter-spacing: -0.01em;*/
|
||||
/* list-style-position: inside;*/
|
||||
/*}*/
|
||||
|
||||
/*.main li:not(:last-of-type) {*/
|
||||
/* margin-bottom: 8px;*/
|
||||
/*}*/
|
||||
|
||||
/*.main code {*/
|
||||
/* font-family: inherit;*/
|
||||
/* background: var(--gray-alpha-100);*/
|
||||
/* padding: 2px 4px;*/
|
||||
/* border-radius: 4px;*/
|
||||
/* font-weight: 600;*/
|
||||
/*}*/
|
||||
|
||||
/*.ctas {*/
|
||||
/* display: flex;*/
|
||||
/* gap: 16px;*/
|
||||
/*}*/
|
||||
|
||||
/*.ctas a {*/
|
||||
/* appearance: none;*/
|
||||
/* border-radius: 128px;*/
|
||||
/* height: 48px;*/
|
||||
/* padding: 0 20px;*/
|
||||
/* border: none;*/
|
||||
/* border: 1px solid transparent;*/
|
||||
/* transition:*/
|
||||
/* background 0.2s,*/
|
||||
/* color 0.2s,*/
|
||||
/* border-color 0.2s;*/
|
||||
/* cursor: pointer;*/
|
||||
/* display: flex;*/
|
||||
/* align-items: center;*/
|
||||
/* justify-content: center;*/
|
||||
/* font-size: 16px;*/
|
||||
/* line-height: 20px;*/
|
||||
/* font-weight: 500;*/
|
||||
/*}*/
|
||||
|
||||
/*a.primary {*/
|
||||
/* background: var(--foreground);*/
|
||||
/* color: var(--background);*/
|
||||
/* gap: 8px;*/
|
||||
/*}*/
|
||||
|
||||
/*a.secondary {*/
|
||||
/* border-color: var(--gray-alpha-200);*/
|
||||
/* min-width: 180px;*/
|
||||
/*}*/
|
||||
|
||||
/*.footer {*/
|
||||
/* grid-row-start: 3;*/
|
||||
/* display: flex;*/
|
||||
/* gap: 24px;*/
|
||||
/*}*/
|
||||
|
||||
/*.footer a {*/
|
||||
/* display: flex;*/
|
||||
/* align-items: center;*/
|
||||
/* gap: 8px;*/
|
||||
/*}*/
|
||||
|
||||
/*.footer img {*/
|
||||
/* flex-shrink: 0;*/
|
||||
/*}*/
|
||||
|
||||
/*!* Enable hover only on non-touch devices *!*/
|
||||
/*@media (hover: hover) and (pointer: fine) {*/
|
||||
/* a.primary:hover {*/
|
||||
/* background: var(--button-primary-hover);*/
|
||||
/* border-color: transparent;*/
|
||||
/* }*/
|
||||
|
||||
/* a.secondary:hover {*/
|
||||
/* background: var(--button-secondary-hover);*/
|
||||
/* border-color: transparent;*/
|
||||
/* }*/
|
||||
|
||||
/* .footer a:hover {*/
|
||||
/* text-decoration: underline;*/
|
||||
/* text-underline-offset: 4px;*/
|
||||
/* }*/
|
||||
/*}*/
|
||||
|
||||
/*@media (max-width: 600px) {*/
|
||||
/* .page {*/
|
||||
/* padding: 32px;*/
|
||||
/* padding-bottom: 80px;*/
|
||||
/* }*/
|
||||
|
||||
/* .main {*/
|
||||
/* align-items: center;*/
|
||||
/* }*/
|
||||
|
||||
/* .main ol {*/
|
||||
/* text-align: center;*/
|
||||
/* }*/
|
||||
|
||||
/* .ctas {*/
|
||||
/* flex-direction: column;*/
|
||||
/* }*/
|
||||
|
||||
/* .ctas a {*/
|
||||
/* font-size: 14px;*/
|
||||
/* height: 40px;*/
|
||||
/* padding: 0 16px;*/
|
||||
/* }*/
|
||||
|
||||
/* a.secondary {*/
|
||||
/* min-width: auto;*/
|
||||
/* }*/
|
||||
|
||||
/* .footer {*/
|
||||
/* flex-wrap: wrap;*/
|
||||
/* align-items: center;*/
|
||||
/* justify-content: center;*/
|
||||
/* }*/
|
||||
/*}*/
|
||||
|
||||
/*@media (prefers-color-scheme: dark) {*/
|
||||
/* .logo {*/
|
||||
/* filter: invert();*/
|
||||
/* }*/
|
||||
/*}*/
|
||||
22
src/app/page.module.scss
Normal file
22
src/app/page.module.scss
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
.page {
|
||||
h1.mainheading, h2.mainheading {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h2.mainheading {
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
a.downloadlink {
|
||||
background-color: var(--color-accent);
|
||||
border: 1px solid var(--color-foreground);
|
||||
border-radius: 0.5em;
|
||||
color: var(--color-accent-inverse);
|
||||
display: block;
|
||||
margin-bottom: 1em;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
padding: 0.5em;
|
||||
width: 11em;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
'use client'
|
||||
|
||||
import styles from "./page.module.css";
|
||||
import styles from "./page.module.scss";
|
||||
import {useReducer} from "react";
|
||||
import reduce from "./state/reducer.ts";
|
||||
import {INITIAL_STATE} from "./state/state.ts";
|
||||
|
|
@ -24,6 +24,12 @@ export default function Home() {
|
|||
<>
|
||||
<h1>The Maze ({state.maze!.width}x{state.maze!.height}, Algorithm: {state.maze!.algorithm},
|
||||
ID: {state.maze!.id})</h1>
|
||||
<a href={"https://manuel.friedli.info/labyrinth/create/pdffile?w="
|
||||
+ state.maze!.width
|
||||
+ "&h=" + state.maze!.height
|
||||
+ "&id=" + state.maze!.id
|
||||
+ "&a=" + state.maze!.algorithm}
|
||||
className={styles.downloadlink}>Download as PDF file</a>
|
||||
<input type={"checkbox"}
|
||||
checked={state.showSolution}
|
||||
onChange={(e) => {
|
||||
|
|
|
|||
|
|
@ -1,60 +0,0 @@
|
|||
import React, {ChangeEventHandler, useState} from 'react';
|
||||
|
||||
export default function ValidatingInputBigIntField({
|
||||
id,
|
||||
label,
|
||||
value = 0n,
|
||||
constraints = undefined,
|
||||
validatorFn = (value) => {
|
||||
return {valid: true, value: BigInt(value)};
|
||||
},
|
||||
disabled = false,
|
||||
onChange = () => {
|
||||
}
|
||||
}:
|
||||
{
|
||||
id: string;
|
||||
label: string;
|
||||
value?: bigint;
|
||||
constraints?: { min?: bigint; max?: bigint; };
|
||||
validatorFn: ValidatorFunction<string, bigint>;
|
||||
disabled: boolean;
|
||||
onChange: (v: bigint) => void;
|
||||
}) {
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
const handleValueChange: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
const value = e.target.value;
|
||||
const validation = validatorFn(value);
|
||||
if (!validation.valid) {
|
||||
setError(validation.message);
|
||||
} else {
|
||||
setError(undefined);
|
||||
}
|
||||
if (validation.value !== undefined) {
|
||||
onChange(validation.value);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<label htmlFor={id}>{label}: </label>
|
||||
<input id={id}
|
||||
type={"number"}
|
||||
onChange={handleValueChange}
|
||||
value={value?.toString() || ""}
|
||||
min={constraints?.min?.toString()}
|
||||
max={constraints?.max?.toString()}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<span>{error}</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export interface Validation<T> {
|
||||
valid: boolean;
|
||||
message?: string;
|
||||
value?: T;
|
||||
}
|
||||
|
||||
export type ValidatorFunction<I, T> = (v: I) => Validation<T>;
|
||||
173
src/app/validating-input-field.tsx
Normal file
173
src/app/validating-input-field.tsx
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
import React, {ChangeEventHandler, useState} from 'react';
|
||||
|
||||
export function ValidatingInputBigIntField({
|
||||
id,
|
||||
label,
|
||||
value = 0n,
|
||||
constraints = undefined,
|
||||
validatorFn = (value) => {
|
||||
return {valid: true, value: BigInt(value)};
|
||||
},
|
||||
disabled = false,
|
||||
onChange = () => {
|
||||
}
|
||||
}:
|
||||
{
|
||||
id: string;
|
||||
label: string;
|
||||
value?: bigint;
|
||||
constraints?: { min?: bigint; max?: bigint; };
|
||||
validatorFn: ValidatorFunction<string, bigint>;
|
||||
disabled: boolean;
|
||||
onChange: (v: bigint) => void;
|
||||
}) {
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
const handleValueChange: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
const value = e.target.value;
|
||||
const validation = validatorFn(value);
|
||||
if (!validation.valid) {
|
||||
setError(validation.message);
|
||||
} else {
|
||||
setError(undefined);
|
||||
}
|
||||
if (validation.value !== undefined) {
|
||||
onChange(validation.value);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<label htmlFor={id}>{label}: </label>
|
||||
<input id={id}
|
||||
type={"number"}
|
||||
onChange={handleValueChange}
|
||||
value={value?.toString() || ""}
|
||||
min={constraints?.min?.toString()}
|
||||
max={constraints?.max?.toString()}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<span>{error}</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function ValidatingInputNumberField({
|
||||
id,
|
||||
label,
|
||||
value = 0,
|
||||
constraints = undefined,
|
||||
validatorFn = (value) => {
|
||||
return {valid: true, value: Number(value)};
|
||||
},
|
||||
disabled = false,
|
||||
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<string>();
|
||||
|
||||
const handleValueChange: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
const value = e.target.value;
|
||||
const validation = validatorFn(value);
|
||||
if (!validation.valid) {
|
||||
setError(validation.message);
|
||||
} else {
|
||||
setError(undefined);
|
||||
}
|
||||
if (validation.value) {
|
||||
onChange(validation.value);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<label htmlFor={id}>{label}: </label>
|
||||
<input id={id}
|
||||
type={"number"}
|
||||
onChange={handleValueChange}
|
||||
value={value || ""}
|
||||
min={constraints?.min}
|
||||
max={constraints?.max}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<span>{error}</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function ValidatingInputRegExpField({
|
||||
id,
|
||||
label,
|
||||
value = undefined,
|
||||
constraints = undefined,
|
||||
validatorFn = (value) => {
|
||||
if (constraints === undefined) {
|
||||
console.log("no constraints, returning VALID")
|
||||
return {valid: true, value};
|
||||
}
|
||||
const allValid = constraints
|
||||
.map(expr => expr.test(value))
|
||||
.reduce((prev, curr) => prev && curr)
|
||||
?? true;
|
||||
console.log("valid?", allValid);
|
||||
return {valid: allValid, value: allValid ? value : undefined};
|
||||
},
|
||||
disabled = false,
|
||||
onChange = () => {
|
||||
},
|
||||
placeholder = ""
|
||||
}:
|
||||
{
|
||||
id: string;
|
||||
label: string;
|
||||
value?: string;
|
||||
constraints?: RegExp[];
|
||||
validatorFn?: ValidatorFunction<string, string>;
|
||||
disabled: boolean;
|
||||
onChange: (v: string) => void;
|
||||
placeholder?: string;
|
||||
}) {
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
const handleValueChange: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
const value = e.target.value;
|
||||
const validation = validatorFn(value);
|
||||
if (!validation.valid) {
|
||||
setError(validation.message);
|
||||
} else {
|
||||
setError(undefined);
|
||||
}
|
||||
if (validation.value !== undefined) {
|
||||
console.log("setting value to:", validation.value);
|
||||
onChange(validation.value);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<label htmlFor={id}>{label}: </label>
|
||||
<input id={id}
|
||||
type={"text"}
|
||||
placeholder={placeholder}
|
||||
onChange={handleValueChange}
|
||||
value={value?.toString() || ""}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<span>{error}</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export interface Validation<T> {
|
||||
valid: boolean;
|
||||
message?: string;
|
||||
value?: T;
|
||||
}
|
||||
|
||||
export type ValidatorFunction<I, T> = (v: I) => Validation<T>;
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
import React, {ChangeEventHandler, useState} from 'react';
|
||||
|
||||
export default function ValidatingInputNumberField({
|
||||
id,
|
||||
label,
|
||||
value = 0,
|
||||
constraints = undefined,
|
||||
validatorFn = (value) => {
|
||||
return {valid: true, value: Number(value)};
|
||||
},
|
||||
disabled = false,
|
||||
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<string>();
|
||||
|
||||
const handleValueChange: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||
const value = e.target.value;
|
||||
const validation = validatorFn(value);
|
||||
if (!validation.valid) {
|
||||
setError(validation.message);
|
||||
} else {
|
||||
setError(undefined);
|
||||
}
|
||||
if (validation.value) {
|
||||
onChange(validation.value);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<label htmlFor={id}>{label}: </label>
|
||||
<input id={id}
|
||||
type={"number"}
|
||||
onChange={handleValueChange}
|
||||
value={value || ""}
|
||||
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>;
|
||||
|
|
@ -1,7 +1,11 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"skipLibCheck": true,
|
||||
|
|
@ -12,7 +16,7 @@
|
|||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
|
|
@ -20,11 +24,18 @@
|
|||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"a-maze-r/node_modules"
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue