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,
type = "button",
}
) => {
return (
<button title={ title } id={ id } type={ type }
class={ `border-rounded bg-cyan-900 px-2 cursor-pointer ${ className }` }
onClick={ onClick }>
{ children }
</button>
);
};
) => (
<button title={ title } id={ id } type={ type }
class={ `border-rounded bg-cyan-900 px-2 cursor-pointer ${ className }` }
onClick={ onClick }>
{ children }
</button>
);

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,10 @@
/* @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 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 {
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("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 {
if (id) {
const el = document.getElementById(id) as HTMLInputElement | HTMLTextAreaElement | null;
const el = getElementById<HTMLInputElement | HTMLTextAreaElement>(id);
if (el && el.value !== "" !== isText) {
setIsText(el.value !== "");
}
@ -43,7 +46,7 @@ interface Input<T> extends InputProps<T> {
inputClass?: string,
}
export const Input: Component<Input<HTMLInputElement>> = (
export const Input: Component<Input<HTMLInputElement>> = ( // TODO remove leading and trailing from component
{
className,
id,
@ -71,7 +74,7 @@ export const Input: Component<Input<HTMLInputElement>> = (
*/
const [isText, setIsText] = createSignal(false);
document.addEventListener("DOMContentLoaded", () => {
onMount(() => {
if (id && title) {
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,
isActive = false,
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" }
transition-all duration-150 text-gray-600 dark:text-gray-400` }
for={ htmlFor }>
<div class={ "z-50 relative" }>{ title }</div>
<div class={ "w-full h-2 default-bg absolute bottom-1/3 z-10" } />
</label>
for={ htmlFor }>
<div class={ "z-50 relative" }>{ title }</div>
<div class={ "w-full h-2 default-bg absolute bottom-1/3 z-10" } />
</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 Footer from "./footer";
const Layout: Component<TitleProps> = ({ children, title, className }) => {
return (
<div class={ `bg-default-bg text-white min-h-screen relative font-mono ${ className }` }>
<div class="container mx-auto">
<Header className={ "py-3" } title={ title } />
<main>
<div class={ "pb-28" }>{ children }</div>
<Footer />
</main>
</div>
const Layout: Component<TitleProps> = (
{
children,
title,
className
}) => (
<div class={ `bg-default-bg text-white min-h-screen relative font-mono ${ className }` }>
<div class="container mx-auto">
<Header className={ "py-3" } title={ title } />
<main>
<div class={ "pb-28" }>{ children }</div>
<Footer />
</main>
</div>
);
};
</div>
);
export default Layout;

View File

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

View File

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

View File

@ -2,8 +2,14 @@
import { type Component } from "solid-js";
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;

View File

@ -16,42 +16,38 @@ const TruthTable: Component<TruthTableProps> = (
className,
style,
id,
}) => {
return (
<table class={ `border-2 border-gray-500 border-collapse table z-10 ${ className }` } id={ id }
style={ style }>
<thead>
<tr>
<For each={ header }>
{ (exp) => (
<th scope={ "col" }
class={ `bg-default-bg text-center sticky top-0 [position:-webkit-sticky;]
}) => (
<table class={ `border-2 border-gray-500 border-collapse table z-10 ${ className }` } id={ id } style={ style }>
<thead>
<tr>
<For each={ header }>
{ (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 */ }>
<p class={ "px-2 w-max" }>{ exp }</p>
</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>
}
<p class={ "px-2 w-max" }>{ exp }</p>
</th>
) }
</For>
</tbody>
</table>
);
}
</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>
</tbody>
</table>
);
export default TruthTable;

View File

@ -1,6 +1,6 @@
/* @refresh reload */
import Layout from "./components/layout";
import { Input } from "./components/input";
import { Input, Search } from "./components/input";
import { Icon } from "solid-heroicons";
import TruthTable from "./components/truth-table";
import { InfoBox, MyDisclosure, MyDisclosureContainer } from "./components/output";
@ -15,15 +15,15 @@ import {
check,
eye,
eyeSlash,
funnel,
magnifyingGlass,
xMark
funnel
} from "solid-heroicons/solid";
import { Button, MySwitch } from "./components/button";
import MyDialog from "./components/dialog";
import { exportToExcel } from "./functions/export";
import { exportToExcel } from "./utils/export";
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" };
@ -60,10 +60,6 @@ const TruthTablePage: Component = () => {
* The state element used to store the simplified string, "empty string" by default
*/
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[] = [
{ name: "Show all result", value: "NONE" },
@ -98,7 +94,7 @@ const TruthTablePage: Component = () => {
let exp = getInputElement()?.value;
if (exp) {
exp = exp.replaceAll("|", ":").trimEnd();
exp = replaceOperators(exp);
history.pushState(null, "", `?exp=${ encodeURIComponent(exp) }&simplify=${ simplifyEnabled() }&
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);
if (exp !== "") {
if (exp && exp !== "") {
setError(null);
setIsLoaded(false);
@ -132,34 +128,17 @@ hideIntermediate=${ hideIntermediates() }`)
const inputId = "truth-input";
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 => {
const inputElement = getInputElement();
if (searchParams.has("exp")) {
const exp = searchParams.get("exp");
if (exp !== "") {
getInputElement().value = exp;
if (exp && inputElement) {
inputElement.value = exp;
}
const hide = searchParams.get("hide");
if (hide) {
@ -175,12 +154,15 @@ hideIntermediate=${ hideIntermediates() }`)
// Focuses searchbar on load
if (!isTouch()) {
getInputElement()?.focus();
inputElement?.focus();
}
});
const tableId = "truth-table";
const filenameId = "excel-filename";
function _exportToExcel(): void {
const value = (document.getElementById(filenameId) as HTMLInputElement | null)?.value;
const value = getElementById<HTMLInputElement>(filenameId)?.value;
exportToExcel({
name: value !== "" ? value : undefined, tableId
});
@ -197,40 +179,12 @@ hideIntermediate=${ hideIntermediates() }`)
<div id={ "truth-content" }>
<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 />
</MyDisclosureContainer>
<HowTo />
<form class={ "flex-row-center" } onSubmit={ onClick } autocomplete={ "off" }>
<Input inputClass={ `rounded-xl pl-7 h-10 w-full pr-8` } className={ "w-full" }
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> }
/>
<Search id={ inputId } typingDefault={ inputContent } />
<Button id={ "truth-input-button" }
title={ "Generate (Enter)" }
@ -292,7 +246,7 @@ hideIntermediate=${ hideIntermediates() }`)
onChange={ setHideIntermediates }
defaultValue={ hideIntermediates() } />
<Show when={ isLoaded() } keyed>
<Show when={ isLoaded() && error() === null } keyed>
<MyDialog title={ "Download" }
description={ "Export current table (.xlsx)" }
@ -303,7 +257,7 @@ hideIntermediate=${ hideIntermediates() }`)
callback={ _exportToExcel }
acceptButtonName={ "Download" }
cancelButtonName={ "Cancel" }
buttonClasses={ `float-right` }
buttonClass={ `float-right` }
buttonTitle={ "Export current table" }
acceptButtonId={ "download-accept" }>
<p>{ "Filename" }:</p>
@ -320,11 +274,11 @@ hideIntermediate=${ hideIntermediates() }`)
</Show>
<Show when={ error() && isLoaded() } keyed>
<ErrorBox title={ error().title ?? "Error" }
error={ error().message ?? "Something went wrong" } />
<ErrorBox title={ error()?.title ?? "Error" }
error={ error()?.message ?? "Something went wrong" } />
</Show>
<Show when={ simplifyEnabled() && fetchResult()?.orderOperations?.length > 0 } keyed>
<Show when={ simplifyEnabled() && (fetchResult()?.orderOperations?.length ?? 0) > 0 } keyed>
<ShowMeHow fetchResult={ fetchResult } />
</Show>
@ -341,7 +295,7 @@ hideIntermediate=${ hideIntermediates() }`)
<div class={ "flex justify-center m-2" }>
<div id={ "table" } class={ "h-[45rem] overflow-auto" }>
<TruthTable header={ fetchResult()?.header }
<TruthTable header={ fetchResult()?.header ?? undefined }
table={ fetchResult()?.table?.truthMatrix } id={ tableId } />
</div>
@ -362,8 +316,13 @@ interface SingleMenuItem {
onClick: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>,
}
const SingleMenuItem: Component<SingleMenuItem> = ({ option, currentValue, onClick }) => {
const isSelected = () => currentValue()?.value === option.value;
const SingleMenuItem: Component<SingleMenuItem> = (
{
option,
currentValue,
onClick
}) => {
const isSelected = () => currentValue && currentValue().value === option.value;
return (
<button class={ `hover:underline cursor-pointer last:mb-1 flex-row-center` }
onClick={ onClick }>
@ -374,91 +333,112 @@ const SingleMenuItem: Component<SingleMenuItem> = ({ option, currentValue, onCli
);
}
const ErrorBox: Component<{ title: string, error: string }> = ({ title, error }) => {
return (
<InfoBox className={ "w-fit text-center mx-auto" }
title={ title }
error={ true }>
<p>{ error }</p>
</InfoBox>
)
}
const ErrorBox: Component<{ title: string, error: string }> = ({ title, error }) => (
<InfoBox className={ "w-fit text-center mx-auto" }
title={ title }
error={ true }>
<p>{ error }</p>
</InfoBox>
);
interface ShowMeHowProps {
fetchResult: Accessor<FetchResult>,
fetchResult: Accessor<FetchResult | null>,
}
const ShowMeHow: Component<ShowMeHowProps> = ({ fetchResult }) => {
return (
<MyDisclosureContainer>
<MyDisclosure title={ "Show me how it's done" }>
<table class={ "table" }>
<tbody>
const ShowMeHow: Component<ShowMeHowProps> = ({ fetchResult }) => (
<MyDisclosureContainer>
<MyDisclosure title={ "Show me how it's done" }>
<table class={ "table" }>
<tbody>
<For each={ fetchResult()?.orderOperations }>{
(operation, index) => (
<tr class={ "border-b border-dotted border-gray-500" }>
<td>{ index() + 1 }:</td>
<td class={ "px-2" }>{
<For each={ fetchResult()?.orderOperations }>{
(operation, index) => (
<tr class={ "border-b border-dotted border-gray-500" }>
<td>{ index() + 1 }:</td>
<td class={ "px-2" }>{
<For each={ diffChars(operation.before, operation.after) }>
{ (part) => (
<span class={
`${ part.added && "bg-green-700" }
<For each={ diffChars(operation.before, operation.after) }>
{ (part) => (
<span class={
`${ part.added && "bg-green-700" }
${ part.removed && "bg-red-700" }` }>
{ part.value }
</span>) }
</For> }
</For> }
<Show
when={ typeof window !== "undefined" && window.outerWidth <= 640 }
keyed>
<p>{ "using" }: { operation.law }</p>
</Show>
</td>
<Show
when={ typeof window !== "undefined" && window.outerWidth > 640 }
keyed>
<td>{ "using" }: { operation.law }</td>
<Show when={ typeof window !== "undefined" && window.outerWidth <= 640 } keyed>
<p>{ "using" }: { operation.law }</p>
</Show>
</tr>
) }
</For>
</tbody>
</table>
</MyDisclosure>
</MyDisclosureContainer>
)
}
</td>
<Show when={ typeof window !== "undefined" && window.outerWidth > 640 } keyed>
<td>{ "using" }: { operation.law }</td>
</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>
</table>
</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);

View File

@ -29,7 +29,7 @@ interface ButtonProps extends TitleProps {
interface InputProps<T> extends TitleProps {
onInput?: JSX.EventHandlerUnion<T, Event>,
placeholder?: string | null,
placeholder?: string,
required?: boolean,
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",
"types": ["vite/client"],
"noEmit": true,
"isolatedModules": true
"isolatedModules": true,
"strict": true
}
}