Set ts compiler to strict, refactored a bit

This commit is contained in:
Martin Berg Alstad 2023-04-08 19:24:10 +02:00
parent 292da46769
commit 7f6c405890
18 changed files with 391 additions and 330 deletions

View File

@ -49,13 +49,10 @@ export const Button: Component<ButtonProps> = (
onClick, onClick,
type = "button", type = "button",
} }
) => { ) => (
return ( <button title={ title } id={ id } type={ type }
<button title={ title } id={ id } type={ type } class={ `border-rounded bg-cyan-900 px-2 cursor-pointer ${ className }` }
class={ `border-rounded bg-cyan-900 px-2 cursor-pointer ${ className }` } onClick={ onClick }>
onClick={ onClick }> { children }
{ children } </button>
</button> );
);
};

View File

@ -3,21 +3,22 @@ import { type Component } from "solid-js";
import type { CardProps } from "../types/types"; import type { CardProps } from "../types/types";
import { Link } from "./link"; import { Link } from "./link";
const Card: Component<CardProps> = ({ children, className, title, to, newTab = false }) => { const Card: Component<CardProps> = (
return ( {
<> children,
<div className,
class={ `relative bg-gradient-to-r from-cyan-600 to-cyan-500 h-32 w-72 rounded-2xl ${ className }` }> title,
<div class="relative p-5"> to,
<Link className={ "text-white" } to={ to } newTab={ newTab }> newTab = false
<h3 class={ "text-center w-fit mx-auto before:content-['↗']" }>{ title }</h3> }) => (
</Link> <div class={ `relative bg-gradient-to-r from-cyan-600 to-cyan-500 h-32 w-72 rounded-2xl ${ className }` }>
{ children } <div class={ "relative p-5" }>
</div> <Link className={ "text-white" } to={ to } newTab={ newTab }>
</div> <h3 class={ "text-center w-fit mx-auto before:content-['↗']" }>{ title }</h3>
</Link>
</> { children }
); </div>
}; </div>
);
export default Card; export default Card;

View File

@ -1,22 +1,23 @@
/* @refresh reload */ /* @refresh reload */
import { Dialog, DialogDescription, DialogPanel, DialogTitle } from "solid-headless"; import { Dialog, DialogDescription, DialogPanel, DialogTitle } from "solid-headless";
import type { TitleProps } from "../types/types"; import type { TitleProps } from "../types/types";
import { createEffect, createSignal, JSX } from "solid-js"; import { Component, createEffect, createSignal, JSX } from "solid-js";
import { Button } from "./button"; import { Button } from "./button";
import { Portal } from "solid-js/web"; import { Portal } from "solid-js/web";
import { getElementById } from "../utils/dom";
interface MyDialog extends TitleProps { interface MyDialog extends TitleProps {
description?: string, description?: string,
button?: JSX.Element, button?: JSX.Element,
acceptButtonName?: string | null, acceptButtonName?: string,
acceptButtonId?: string, acceptButtonId?: string,
cancelButtonName?: string | null, cancelButtonName?: string,
callback?: () => void, callback?: () => void,
buttonClasses?: string, buttonClass?: string,
buttonTitle?: string | null, buttonTitle?: string | null,
} }
export default function MyDialog( const MyDialog: Component<MyDialog> = (
{ {
title, title,
description, description,
@ -26,10 +27,10 @@ export default function MyDialog(
children, children,
callback, callback,
className, className,
buttonClasses, buttonClass,
buttonTitle, buttonTitle,
acceptButtonId, acceptButtonId,
}: MyDialog): JSX.Element { }) => {
const [isOpen, setIsOpen] = createSignal(false); const [isOpen, setIsOpen] = createSignal(false);
@ -48,20 +49,21 @@ export default function MyDialog(
* @param e KeyboardEvent of keypress * @param e KeyboardEvent of keypress
*/ */
function click(e: KeyboardEvent): void { function click(e: KeyboardEvent): void {
if (isMounted && e.key === "Enter") { if (isMounted && e.key === "Enter" && acceptButtonId) {
(document.getElementById(acceptButtonId ?? "") as HTMLButtonElement | null)?.click(); getElementById<HTMLButtonElement>(acceptButtonId)?.click();
} }
} }
if (isOpen()) { if (isOpen()) {
const id = "cl-6" const id = "cl-6"
const el = document.getElementById(id); const el = getElementById(id);
el?.addEventListener("keypress", e => click(e)); el?.addEventListener("keypress", click);
return () => { return () => {
el?.removeEventListener("keypress", e => click(e)); el?.removeEventListener("keypress", click);
isMounted = false; isMounted = false;
} }
} }
else return () => undefined;
} }
createEffect(setupKeyPress, isOpen()); createEffect(setupKeyPress, isOpen());
@ -69,7 +71,7 @@ export default function MyDialog(
return ( return (
<div class={ "w-fit h-fit" }> <div class={ "w-fit h-fit" }>
<button onClick={ () => setIsOpen(true) } class={ buttonClasses } title={ buttonTitle ?? undefined }> <button onClick={ () => setIsOpen(true) } class={ buttonClass } title={ buttonTitle ?? undefined }>
{ button } { button }
</button> </button>
@ -99,3 +101,5 @@ export default function MyDialog(
</div> </div>
); );
} }
export default MyDialog;

View File

@ -3,12 +3,10 @@ import { type Component } from "solid-js";
import type { SimpleProps } from "../types/types"; import type { SimpleProps } from "../types/types";
import { Link } from "./link"; import { Link } from "./link";
const Footer: Component<SimpleProps> = ({ className }) => { const Footer: Component<SimpleProps> = ({ className }) => (
return ( <footer class={ `text-center py-5 absolute bottom-0 container ${ className }` }>
<footer class={ `text-center py-5 absolute bottom-0 container ${ className }` }> <p>Kildekode <Link to={ "https://github.com/h600878/martials.no" }>GitHub</Link></p>
<p>Kildekode <Link to={ "https://github.com/h600878/martials.no" }>GitHub</Link></p> </footer>
</footer> );
);
};
export default Footer; export default Footer;

View File

@ -5,24 +5,22 @@ import { Icon } from "solid-heroicons";
import { chevronLeft } from "solid-heroicons/solid"; import { chevronLeft } from "solid-heroicons/solid";
import { Link } from "./link"; import { Link } from "./link";
const Header: Component<TitleProps> = ({ className, title }) => { const Header: Component<TitleProps> = ({ className, title = "Title goes here" }) => (
return ( <header class={ className }>
<header class={ className }> <div class={ "flex-row-center mx-auto w-fit" }>
<div class={ "flex-row-center mx-auto w-fit" }>
<Show when={ typeof location !== "undefined" && location.pathname !== "/" } keyed> <Show when={ typeof location !== "undefined" && location.pathname !== "/" } keyed>
<Link to={ "/" } newTab={ false } title={ "Back to homepage" }> <Link to={ "/" } newTab={ false } title={ "Back to homepage" }>
<Icon path={ chevronLeft } class={ "text-cyan-500" } /> <Icon path={ chevronLeft } class={ "text-cyan-500" } />
</Link> </Link>
</Show> </Show>
<h1 class={ "text-center text-cyan-500" }>{ title }</h1> <h1 class={ "text-center text-cyan-500" }>{ title }</h1>
</div> </div>
<div class={ "mx-auto w-fit" }> <div class={ "mx-auto w-fit" }>
<p>Av Martin Berg Alstad</p> <p>Av Martin Berg Alstad</p>
</div> </div>
</header> </header>
); );
};
export default Header; export default Header;

View File

@ -1,7 +1,10 @@
/* @refresh reload */ /* @refresh reload */
import { type Component, createSignal, JSX, Setter } from "solid-js"; import { type Component, createSignal, JSX, onMount, Setter, Show } from "solid-js";
import type { InputProps } from "../types/types"; import type { InputProps } from "../types/types";
import Row from "./row"; import Row from "./row";
import { Icon } from "solid-heroicons";
import { magnifyingGlass, xMark } from "solid-heroicons/solid";
import { getElementById } from "../utils/dom";
function setupEventListener(id: string, setIsHover: Setter<boolean>): () => void { function setupEventListener(id: string, setIsHover: Setter<boolean>): () => void {
let isMounted = true; let isMounted = true;
@ -12,7 +15,7 @@ function setupEventListener(id: string, setIsHover: Setter<boolean>): () => void
} }
} }
const el = document.getElementById(id); const el = getElementById(id);
el?.addEventListener("pointerenter", () => hover(true)); el?.addEventListener("pointerenter", () => hover(true));
el?.addEventListener("pointerleave", () => hover(false)); el?.addEventListener("pointerleave", () => hover(false));
@ -29,7 +32,7 @@ function setupEventListener(id: string, setIsHover: Setter<boolean>): () => void
*/ */
function setSetIsText(id: string | undefined, isText: boolean, setIsText: Setter<boolean>): void { function setSetIsText(id: string | undefined, isText: boolean, setIsText: Setter<boolean>): void {
if (id) { if (id) {
const el = document.getElementById(id) as HTMLInputElement | HTMLTextAreaElement | null; const el = getElementById<HTMLInputElement | HTMLTextAreaElement>(id);
if (el && el.value !== "" !== isText) { if (el && el.value !== "" !== isText) {
setIsText(el.value !== ""); setIsText(el.value !== "");
} }
@ -43,7 +46,7 @@ interface Input<T> extends InputProps<T> {
inputClass?: string, inputClass?: string,
} }
export const Input: Component<Input<HTMLInputElement>> = ( export const Input: Component<Input<HTMLInputElement>> = ( // TODO remove leading and trailing from component
{ {
className, className,
id, id,
@ -71,7 +74,7 @@ export const Input: Component<Input<HTMLInputElement>> = (
*/ */
const [isText, setIsText] = createSignal(false); const [isText, setIsText] = createSignal(false);
document.addEventListener("DOMContentLoaded", () => { onMount(() => {
if (id && title) { if (id && title) {
setupEventListener(id, setIsHover); setupEventListener(id, setIsHover);
} }
@ -102,19 +105,71 @@ export const Input: Component<Input<HTMLInputElement>> = (
); );
} }
function HoverTitle( const HoverTitle: Component<{ title?: string, isActive?: boolean, htmlFor?: string }> = (
{ {
title, title,
isActive = false, isActive = false,
htmlFor htmlFor
}: { title?: string | null, isActive?: boolean, htmlFor?: string }): JSX.Element { }) => (
return ( <label class={ `absolute pointer-events-none
<label class={ `absolute pointer-events-none
${ isActive ? "-top-2 left-3 default-bg text-sm" : "left-2 top-1" } ${ isActive ? "-top-2 left-3 default-bg text-sm" : "left-2 top-1" }
transition-all duration-150 text-gray-600 dark:text-gray-400` } transition-all duration-150 text-gray-600 dark:text-gray-400` }
for={ htmlFor }> for={ htmlFor }>
<div class={ "z-50 relative" }>{ title }</div> <div class={ "z-50 relative" }>{ title }</div>
<div class={ "w-full h-2 default-bg absolute bottom-1/3 z-10" } /> <div class={ "w-full h-2 default-bg absolute bottom-1/3 z-10" } />
</label> </label>
);
interface SearchProps extends InputProps<HTMLInputElement> {
typingDefault?: boolean
}
export const Search: Component<SearchProps> = (
{
typingDefault = false,
id = "search",
className
}) => {
const [typing, setTyping] = createSignal(typingDefault);
function getInputElement() {
return getElementById<HTMLInputElement>(id);
}
function clearSearch(): void {
const el = getInputElement();
if (el) {
el.value = "";
setTyping(false);
history.replaceState(null, "", location.pathname);
el.focus();
}
}
function onChange(): void {
const el = getInputElement();
if (el && (el.value !== "") !== typing()) {
setTyping(el.value !== "");
}
}
return (
<Input inputClass={ `rounded-xl pl-7 h-10 w-full pr-8` } className={ `w-full ${ className }` }
id={ id }
placeholder={ "¬A & B -> C" }
type={ "text" }
onChange={ onChange }
leading={ <Icon path={ magnifyingGlass } aria-label={ "Magnifying glass" }
class={ "pl-2 absolute" } /> }
trailing={ <Show when={ typing() } keyed>
<button class={ "absolute right-2" }
title={ "Clear" }
type={ "reset" }
onClick={ clearSearch }>
<Icon path={ xMark } aria-label={ "The letter X" } />
</button>
</Show> }
/>
); );
} }

View File

@ -4,18 +4,21 @@ import type { TitleProps } from "../types/types";
import Header from "./header"; import Header from "./header";
import Footer from "./footer"; import Footer from "./footer";
const Layout: Component<TitleProps> = ({ children, title, className }) => { const Layout: Component<TitleProps> = (
return ( {
<div class={ `bg-default-bg text-white min-h-screen relative font-mono ${ className }` }> children,
<div class="container mx-auto"> title,
<Header className={ "py-3" } title={ title } /> className
<main> }) => (
<div class={ "pb-28" }>{ children }</div> <div class={ `bg-default-bg text-white min-h-screen relative font-mono ${ className }` }>
<Footer /> <div class="container mx-auto">
</main> <Header className={ "py-3" } title={ title } />
</div> <main>
<div class={ "pb-28" }>{ children }</div>
<Footer />
</main>
</div> </div>
); </div>
}; );
export default Layout; export default Layout;

View File

@ -11,14 +11,11 @@ export const Link: Component<LinkProps> = (
id, id,
newTab = true, newTab = true,
title, title,
}) => { }) => (
return ( <a href={ to } id={ id } title={ title }
<a href={ to } id={ id } title={ title } rel={ `${ rel } ${ newTab ? "noreferrer" : undefined }` }
rel={ `${ rel } ${ newTab ? "noreferrer" : undefined }` } target={ newTab ? "_blank" : undefined }
target={ newTab ? "_blank" : undefined } class={ `link ${ className }` }>
class={ `link ${ className }` }> { children }
{ children } </a>
</a> );
);
};

View File

@ -1,7 +1,8 @@
/* @refresh reload */
import { Disclosure, DisclosureButton, DisclosurePanel, Transition } from "solid-headless"; import { Disclosure, DisclosureButton, DisclosurePanel, Transition } from "solid-headless";
import { Icon } from "solid-heroicons"; import { Icon } from "solid-heroicons";
import type { ChildProps, TitleProps } from "../types/types"; import type { ChildProps, TitleProps } from "../types/types";
import { Component, JSX } from "solid-js"; import { type Component, JSX } from "solid-js";
import { chevronUp } from "solid-heroicons/solid"; import { chevronUp } from "solid-heroicons/solid";
interface InfoBoxProps extends TitleProps { interface InfoBoxProps extends TitleProps {
@ -10,18 +11,16 @@ interface InfoBoxProps extends TitleProps {
export const InfoBox: Component<InfoBoxProps> = ( export const InfoBox: Component<InfoBoxProps> = (
{ {
title = "", title,
children, children,
error = false, error = false,
className className
}: InfoBoxProps): JSX.Element => { }) => (
return ( <div class={ `border-rounded ${ error ? "border-red-500" : "border-gray-500" } ${ className }` }>
<div class={ `border-rounded ${ error ? "border-red-500" : "border-gray-500" } ${ className }` }> <p class={ `border-b px-2 ${ error ? "border-red-500" : "border-gray-500" }` }>{ title }</p>
<p class={ `border-b px-2 ${ error ? "border-red-500" : "border-gray-500" }` }>{ title }</p> <div class={ "mx-2" }>{ children }</div>
<div class={ "mx-2" }>{ children }</div> </div>
</div> );
);
}
interface MyDisclosureProps extends TitleProps { interface MyDisclosureProps extends TitleProps {
defaultOpen?: boolean, defaultOpen?: boolean,
@ -36,40 +35,41 @@ export const MyDisclosure: Component<MyDisclosureProps> = (
className, className,
id, id,
onClick onClick
}): JSX.Element => { }): JSX.Element => (
return ( <div id={ id } class={ `border-rounded bg-default-bg ${ className }` }>
<div id={ id } class={ `border-rounded bg-default-bg ${ className }` }> <Disclosure defaultOpen={ defaultOpen }>
<Disclosure defaultOpen={ defaultOpen }> { ({ isOpen }) =>
{ ({ isOpen }) => <>
<> <DisclosureButton onClick={ onClick }
<DisclosureButton onClick={ onClick } class={ `flex-row-center w-full justify-between px-2` }>
class={ `flex-row-center w-full justify-between px-2` }> <p class={ `py-1` }>{ title }</p>
<p class={ `py-1` }>{ title }</p> <Icon path={ chevronUp }
<Icon path={ chevronUp } class={ `w-5 ${ isOpen() && "transform rotate-180" } transition` } /> class={ `w-5 ${ isOpen() && "transform rotate-180" } transition` } />
</DisclosureButton> </DisclosureButton>
<Transition <Transition
enter="transition duration-100 ease-out" enter="transition duration-100 ease-out"
enterFrom="transform scale-95 opacity-0" enterFrom="transform scale-95 opacity-0"
enterTo="transform scale-100 opacity-100" enterTo="transform scale-100 opacity-100"
leave="transition duration-75 ease-out" leave="transition duration-75 ease-out"
leaveFrom="transform scale-100 opacity-100" leaveFrom="transform scale-100 opacity-100"
leaveTo="transform scale-95 opacity-0" show> leaveTo="transform scale-95 opacity-0" show>
<DisclosurePanel> <DisclosurePanel>
<div class={ "px-2 pb-2 text-gray-300" }>{ children }</div> <div class={ "px-2 pb-2 text-gray-300" }>{ children }</div>
</DisclosurePanel> </DisclosurePanel>
</Transition> </Transition>
</> </>
} }
</Disclosure> </Disclosure>
</div> </div>
); );
}
export const MyDisclosureContainer: Component<ChildProps> = ({ children, className }): JSX.Element => { export const MyDisclosureContainer: Component<ChildProps> = (
return ( {
<div class={ `bg-cyan-900 border-rounded dark:border-gray-800 p-2 mb-2 children,
className
}) => (
<div class={ `bg-cyan-900 border-rounded dark:border-gray-800 p-2 mb-2
flex flex-col gap-1 ${ className }` }> flex flex-col gap-1 ${ className }` }>
{ children } { children }
</div> </div>
); );
}

View File

@ -2,8 +2,14 @@
import { type Component } from "solid-js"; import { type Component } from "solid-js";
import type { ChildProps } from "../types/types"; import type { ChildProps } from "../types/types";
const Row: Component<ChildProps> = ({ children, className }) => { /**
return <div class={ `flex-row-center ${ className }` }>{ children }</div> * A row that centers its children
} * @param children The children of the row
* @param className The class name of the row
* @returns The row
*/
const Row: Component<ChildProps> = ({ children, className }) => (
<div class={ `flex-row-center ${ className }` }>{ children }</div>
);
export default Row; export default Row;

View File

@ -16,42 +16,38 @@ const TruthTable: Component<TruthTableProps> = (
className, className,
style, style,
id, id,
}) => { }) => (
<table class={ `border-2 border-gray-500 border-collapse table z-10 ${ className }` } id={ id } style={ style }>
return ( <thead>
<table class={ `border-2 border-gray-500 border-collapse table z-10 ${ className }` } id={ id } <tr>
style={ style }> <For each={ header }>
<thead> { (exp) => (
<tr> <th scope={ "col" }
<For each={ header }> class={ `bg-default-bg text-center sticky top-0 [position:-webkit-sticky;]
{ (exp) => (
<th scope={ "col" }
class={ `bg-default-bg text-center sticky top-0 [position:-webkit-sticky;]
outline outline-2 outline-offset-[-1px] outline-gray-500` /*TODO sticky header at the top of the screen */ }> outline outline-2 outline-offset-[-1px] outline-gray-500` /*TODO sticky header at the top of the screen */ }>
<p class={ "px-2 w-max" }>{ exp }</p> <p class={ "px-2 w-max" }>{ exp }</p>
</th> </th>
) } ) }
</For>
</tr>
</thead>
<tbody>
<For each={ table }>
{ (row) =>
<tr class={ "hover:text-black" }>
<For each={ row }>
{ (value) =>
<td class={ `text-center border border-gray-500 last:underline
${ value ? "bg-green-700" : "bg-red-700" }` }>
<p>{ value ? "T" : "F" }</p>
</td>
}
</For>
</tr>
}
</For> </For>
</tbody> </tr>
</table> </thead>
); <tbody>
} <For each={ table }>
{ (row) =>
<tr class={ "hover:text-black" }>
<For each={ row }>
{ (value) =>
<td class={ `text-center border border-gray-500 last:underline
${ value ? "bg-green-700" : "bg-red-700" }` }>
<p>{ value ? "T" : "F" }</p>
</td>
}
</For>
</tr>
}
</For>
</tbody>
</table>
);
export default TruthTable; export default TruthTable;

View File

@ -1,6 +1,6 @@
/* @refresh reload */ /* @refresh reload */
import Layout from "./components/layout"; import Layout from "./components/layout";
import { Input } from "./components/input"; import { Input, Search } from "./components/input";
import { Icon } from "solid-heroicons"; import { Icon } from "solid-heroicons";
import TruthTable from "./components/truth-table"; import TruthTable from "./components/truth-table";
import { InfoBox, MyDisclosure, MyDisclosureContainer } from "./components/output"; import { InfoBox, MyDisclosure, MyDisclosureContainer } from "./components/output";
@ -15,15 +15,15 @@ import {
check, check,
eye, eye,
eyeSlash, eyeSlash,
funnel, funnel
magnifyingGlass,
xMark
} from "solid-heroicons/solid"; } from "solid-heroicons/solid";
import { Button, MySwitch } from "./components/button"; import { Button, MySwitch } from "./components/button";
import MyDialog from "./components/dialog"; import MyDialog from "./components/dialog";
import { exportToExcel } from "./functions/export"; import { exportToExcel } from "./utils/export";
import { Link } from "./components/link"; import { Link } from "./components/link";
import { isTouch } from "./functions/touch"; import { isTouch } from "./utils/touch";
import { replaceOperators } from "./utils/expressionUtils";
import { getElementById } from "./utils/dom";
type Option = { name: string, value: "NONE" | "TRUE" | "FALSE" | "DEFAULT" | "TRUE_FIRST" | "FALSE_FIRST" }; type Option = { name: string, value: "NONE" | "TRUE" | "FALSE" | "DEFAULT" | "TRUE_FIRST" | "FALSE_FIRST" };
@ -60,10 +60,6 @@ const TruthTablePage: Component = () => {
* The state element used to store the simplified string, "empty string" by default * The state element used to store the simplified string, "empty string" by default
*/ */
const [fetchResult, setFetchResult] = createSignal<FetchResult | null>(null); const [fetchResult, setFetchResult] = createSignal<FetchResult | null>(null);
/**
* If the searchbar is empty, this state is 'false', otherwise 'true'
*/
const [typing, setTyping] = createSignal(inputContent);
const hideOptions: Option[] = [ const hideOptions: Option[] = [
{ name: "Show all result", value: "NONE" }, { name: "Show all result", value: "NONE" },
@ -98,7 +94,7 @@ const TruthTablePage: Component = () => {
let exp = getInputElement()?.value; let exp = getInputElement()?.value;
if (exp) { if (exp) {
exp = exp.replaceAll("|", ":").trimEnd(); exp = replaceOperators(exp);
history.pushState(null, "", `?exp=${ encodeURIComponent(exp) }&simplify=${ simplifyEnabled() }& history.pushState(null, "", `?exp=${ encodeURIComponent(exp) }&simplify=${ simplifyEnabled() }&
hide=${ hideValues().value }&sort=${ sortValues().value }&hideIntermediate=${ hideIntermediates() }`); hide=${ hideValues().value }&sort=${ sortValues().value }&hideIntermediate=${ hideIntermediates() }`);
@ -107,10 +103,10 @@ hide=${ hideValues().value }&sort=${ sortValues().value }&hideIntermediate=${ hi
} }
} }
function getFetchResult(exp: string): void { function getFetchResult(exp: string | null): void {
setFetchResult(null); setFetchResult(null);
if (exp !== "") { if (exp && exp !== "") {
setError(null); setError(null);
setIsLoaded(false); setIsLoaded(false);
@ -132,34 +128,17 @@ hideIntermediate=${ hideIntermediates() }`)
const inputId = "truth-input"; const inputId = "truth-input";
function getInputElement(): HTMLInputElement | null { function getInputElement(): HTMLInputElement | null {
return document.getElementById(inputId) as HTMLInputElement | null; return getElementById(inputId);
} }
function onTyping(): void {
const el = getInputElement();
if (el && (el.value !== "") !== typing()) {
setTyping(el.value !== "");
}
}
function clearSearch(): void {
const el = getInputElement();
if (el) {
el.value = "";
setTyping(false);
history.replaceState(null, "", location.pathname);
el.focus();
}
}
const tableId = "truth-table";
const filenameId = "excel-filename";
onMount((): void => { onMount((): void => {
const inputElement = getInputElement();
if (searchParams.has("exp")) { if (searchParams.has("exp")) {
const exp = searchParams.get("exp"); const exp = searchParams.get("exp");
if (exp !== "") { if (exp && inputElement) {
getInputElement().value = exp; inputElement.value = exp;
} }
const hide = searchParams.get("hide"); const hide = searchParams.get("hide");
if (hide) { if (hide) {
@ -175,12 +154,15 @@ hideIntermediate=${ hideIntermediates() }`)
// Focuses searchbar on load // Focuses searchbar on load
if (!isTouch()) { if (!isTouch()) {
getInputElement()?.focus(); inputElement?.focus();
} }
}); });
const tableId = "truth-table";
const filenameId = "excel-filename";
function _exportToExcel(): void { function _exportToExcel(): void {
const value = (document.getElementById(filenameId) as HTMLInputElement | null)?.value; const value = getElementById<HTMLInputElement>(filenameId)?.value;
exportToExcel({ exportToExcel({
name: value !== "" ? value : undefined, tableId name: value !== "" ? value : undefined, tableId
}); });
@ -197,40 +179,12 @@ hideIntermediate=${ hideIntermediates() }`)
<div id={ "truth-content" }> <div id={ "truth-content" }>
<div class={ "max-w-2xl mx-auto" }> <div class={ "max-w-2xl mx-auto" }>
<MyDisclosureContainer>
<MyDisclosure title={ "How to" }>
<p>Fill in a truth expression and it will be simplified for you as much as possible.
It will also genereate a truth table with all possible values. You can use a single
letter,
word or multiple words without spacing for each atomic value.
If you do not want to simplify the expression, simply turn off the toggle.
Keywords for operators are defined below. Parentheses is also allowed.</p>
<p>API docs can be found <Link to={ "https://api.martials.no/simplify-truths" }>here</Link>.
</p>
</MyDisclosure>
<KeywordsDisclosure /> <HowTo />
</MyDisclosureContainer>
<form class={ "flex-row-center" } onSubmit={ onClick } autocomplete={ "off" }> <form class={ "flex-row-center" } onSubmit={ onClick } autocomplete={ "off" }>
<Input inputClass={ `rounded-xl pl-7 h-10 w-full pr-8` } className={ "w-full" } <Search id={ inputId } typingDefault={ inputContent } />
id={ "truth-input" }
placeholder={ "¬A & B -> C" }
type={ "text" }
onChange={ onTyping }
leading={ <Icon path={ magnifyingGlass } aria-label={ "Magnifying glass" }
class={ "pl-2 absolute" } /> }
trailing={ <Show when={ typing() } keyed>
<button class={ "absolute right-2" }
title={ "Clear" }
type={ "reset" }
onClick={ clearSearch }>
<Icon path={ xMark } aria-label={ "The letter X" } />
</button>
</Show> }
/>
<Button id={ "truth-input-button" } <Button id={ "truth-input-button" }
title={ "Generate (Enter)" } title={ "Generate (Enter)" }
@ -292,7 +246,7 @@ hideIntermediate=${ hideIntermediates() }`)
onChange={ setHideIntermediates } onChange={ setHideIntermediates }
defaultValue={ hideIntermediates() } /> defaultValue={ hideIntermediates() } />
<Show when={ isLoaded() } keyed> <Show when={ isLoaded() && error() === null } keyed>
<MyDialog title={ "Download" } <MyDialog title={ "Download" }
description={ "Export current table (.xlsx)" } description={ "Export current table (.xlsx)" }
@ -303,7 +257,7 @@ hideIntermediate=${ hideIntermediates() }`)
callback={ _exportToExcel } callback={ _exportToExcel }
acceptButtonName={ "Download" } acceptButtonName={ "Download" }
cancelButtonName={ "Cancel" } cancelButtonName={ "Cancel" }
buttonClasses={ `float-right` } buttonClass={ `float-right` }
buttonTitle={ "Export current table" } buttonTitle={ "Export current table" }
acceptButtonId={ "download-accept" }> acceptButtonId={ "download-accept" }>
<p>{ "Filename" }:</p> <p>{ "Filename" }:</p>
@ -320,11 +274,11 @@ hideIntermediate=${ hideIntermediates() }`)
</Show> </Show>
<Show when={ error() && isLoaded() } keyed> <Show when={ error() && isLoaded() } keyed>
<ErrorBox title={ error().title ?? "Error" } <ErrorBox title={ error()?.title ?? "Error" }
error={ error().message ?? "Something went wrong" } /> error={ error()?.message ?? "Something went wrong" } />
</Show> </Show>
<Show when={ simplifyEnabled() && fetchResult()?.orderOperations?.length > 0 } keyed> <Show when={ simplifyEnabled() && (fetchResult()?.orderOperations?.length ?? 0) > 0 } keyed>
<ShowMeHow fetchResult={ fetchResult } /> <ShowMeHow fetchResult={ fetchResult } />
</Show> </Show>
@ -341,7 +295,7 @@ hideIntermediate=${ hideIntermediates() }`)
<div class={ "flex justify-center m-2" }> <div class={ "flex justify-center m-2" }>
<div id={ "table" } class={ "h-[45rem] overflow-auto" }> <div id={ "table" } class={ "h-[45rem] overflow-auto" }>
<TruthTable header={ fetchResult()?.header } <TruthTable header={ fetchResult()?.header ?? undefined }
table={ fetchResult()?.table?.truthMatrix } id={ tableId } /> table={ fetchResult()?.table?.truthMatrix } id={ tableId } />
</div> </div>
@ -362,8 +316,13 @@ interface SingleMenuItem {
onClick: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>, onClick: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>,
} }
const SingleMenuItem: Component<SingleMenuItem> = ({ option, currentValue, onClick }) => { const SingleMenuItem: Component<SingleMenuItem> = (
const isSelected = () => currentValue()?.value === option.value; {
option,
currentValue,
onClick
}) => {
const isSelected = () => currentValue && currentValue().value === option.value;
return ( return (
<button class={ `hover:underline cursor-pointer last:mb-1 flex-row-center` } <button class={ `hover:underline cursor-pointer last:mb-1 flex-row-center` }
onClick={ onClick }> onClick={ onClick }>
@ -374,91 +333,112 @@ const SingleMenuItem: Component<SingleMenuItem> = ({ option, currentValue, onCli
); );
} }
const ErrorBox: Component<{ title: string, error: string }> = ({ title, error }) => { const ErrorBox: Component<{ title: string, error: string }> = ({ title, error }) => (
return ( <InfoBox className={ "w-fit text-center mx-auto" }
<InfoBox className={ "w-fit text-center mx-auto" } title={ title }
title={ title } error={ true }>
error={ true }> <p>{ error }</p>
<p>{ error }</p> </InfoBox>
</InfoBox> );
)
}
interface ShowMeHowProps { interface ShowMeHowProps {
fetchResult: Accessor<FetchResult>, fetchResult: Accessor<FetchResult | null>,
} }
const ShowMeHow: Component<ShowMeHowProps> = ({ fetchResult }) => { const ShowMeHow: Component<ShowMeHowProps> = ({ fetchResult }) => (
return ( <MyDisclosureContainer>
<MyDisclosureContainer> <MyDisclosure title={ "Show me how it's done" }>
<MyDisclosure title={ "Show me how it's done" }> <table class={ "table" }>
<table class={ "table" }> <tbody>
<tbody>
<For each={ fetchResult()?.orderOperations }>{ <For each={ fetchResult()?.orderOperations }>{
(operation, index) => ( (operation, index) => (
<tr class={ "border-b border-dotted border-gray-500" }> <tr class={ "border-b border-dotted border-gray-500" }>
<td>{ index() + 1 }:</td> <td>{ index() + 1 }:</td>
<td class={ "px-2" }>{ <td class={ "px-2" }>{
<For each={ diffChars(operation.before, operation.after) }> <For each={ diffChars(operation.before, operation.after) }>
{ (part) => ( { (part) => (
<span class={ <span class={
`${ part.added && "bg-green-700" } `${ part.added && "bg-green-700" }
${ part.removed && "bg-red-700" }` }> ${ part.removed && "bg-red-700" }` }>
{ part.value } { part.value }
</span>) } </span>) }
</For> } </For> }
<Show <Show when={ typeof window !== "undefined" && window.outerWidth <= 640 } keyed>
when={ typeof window !== "undefined" && window.outerWidth <= 640 } <p>{ "using" }: { operation.law }</p>
keyed>
<p>{ "using" }: { operation.law }</p>
</Show>
</td>
<Show
when={ typeof window !== "undefined" && window.outerWidth > 640 }
keyed>
<td>{ "using" }: { operation.law }</td>
</Show> </Show>
</tr>
) }
</For>
</tbody> </td>
</table> <Show when={ typeof window !== "undefined" && window.outerWidth > 640 } keyed>
</MyDisclosure> <td>{ "using" }: { operation.law }</td>
</MyDisclosureContainer> </Show>
) </tr>
} ) }
</For>
const KeywordsDisclosure = () => {
return (
<MyDisclosure title={ "Keywords" }>
<table>
<tbody>
<tr>
<td>Not:</td>
<td>!</td>
</tr>
<tr>
<td>And:</td>
<td>&</td>
</tr>
<tr>
<td>Or:</td>
<td>|</td>
<td>/</td>
</tr>
<tr>
<td class={ "pr-2" }>Implication:</td>
<td>{ "->" }</td>
</tr>
</tbody> </tbody>
</table> </table>
</MyDisclosure> </MyDisclosure>
); </MyDisclosureContainer>
}; );
const HowTo: Component = () => (
<MyDisclosureContainer>
<MyDisclosure title={ "How to" }>
<p>Fill in a truth expression and it will be simplified for you as much as possible.
It will also genereate a truth table with all possible values. You can use a single
letter,
word or multiple words without spacing for each atomic value.
If you do not want to simplify the expression, simply turn off the toggle.
Keywords for operators are defined below. Parentheses is also allowed.</p>
<p>API docs can be found <Link to={ "https://api.martials.no/simplify-truths" }>here</Link>.
</p>
</MyDisclosure>
<KeywordsDisclosure />
</MyDisclosureContainer>
);
const KeywordsDisclosure: Component = () => (
<MyDisclosure title={ "Keywords" }>
<table>
<thead>
<tr class={ "text-left" }>
<th>Name</th>
<th class={ "pr-2" }>API</th>
<th>Other</th>
</tr>
</thead>
<tbody>
<tr>
<td>Not:</td>
<td>!</td>
<td>NOT</td>
</tr>
<tr>
<td>And:</td>
<td>&</td>
<td>AND</td>
</tr>
<tr>
<td>Or:</td>
<td>:</td>
<td>|</td>
<td>/</td>
<td>OR</td>
</tr>
<tr>
<td class={ "pr-2" }>Implication:</td>
<td>{ "->" }</td>
<td class={ "px-2" }>IMPLICATION</td>
<td>IMP</td>
</tr>
</tbody>
</table>
</MyDisclosure>
);
render(() => <TruthTablePage />, document.getElementById("root") as HTMLElement); render(() => <TruthTablePage />, document.getElementById("root") as HTMLElement);

View File

@ -29,7 +29,7 @@ interface ButtonProps extends TitleProps {
interface InputProps<T> extends TitleProps { interface InputProps<T> extends TitleProps {
onInput?: JSX.EventHandlerUnion<T, Event>, onInput?: JSX.EventHandlerUnion<T, Event>,
placeholder?: string | null, placeholder?: string,
required?: boolean, required?: boolean,
type?: string, type?: string,
} }

9
src/utils/dom.ts Normal file
View File

@ -0,0 +1,9 @@
/**
* Get an element by id
* @param id The id of the element
* @type T The type of the HTMLElement
* @returns The element with the given id, or null if it doesn't exist
*/
export function getElementById<T extends HTMLElement = HTMLElement>(id: string): T | null {
return document.getElementById(id) as T | null;
}

View File

@ -0,0 +1,16 @@
/**
* Replaces the operators in the expression with the ones used by the backend
* @param expression The expression to replace the operators in
* @returns The expression with the replaced operators
*/
export function replaceOperators(expression: string): string {
expression = expression.replaceAll(/[|/]/g, ":");
expression = expression.replaceAll(/¬/g, "!");
expression = expression.replaceAll(/\sOR\s/gi, " : ");
expression = expression.replaceAll(/\sAND\s/gi, " & ");
expression = expression.replaceAll(/\s(IMPLICATION|IMP)\s/gi, " -> ");
expression = expression.replaceAll(/\sNOT\s/gi, " !");
return expression;
}

View File

@ -9,6 +9,7 @@
"jsxImportSource": "solid-js", "jsxImportSource": "solid-js",
"types": ["vite/client"], "types": ["vite/client"],
"noEmit": true, "noEmit": true,
"isolatedModules": true "isolatedModules": true,
"strict": true
} }
} }