Set ts compiler to strict, refactored a bit
This commit is contained in:
parent
292da46769
commit
7f6c405890
@ -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>
|
||||
);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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 på <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 på <Link to={ "https://github.com/h600878/martials.no" }>GitHub</Link></p>
|
||||
</footer>
|
||||
);
|
||||
|
||||
export default Footer;
|
||||
|
@ -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;
|
||||
|
@ -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> }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
2
src/types/types.d.ts
vendored
2
src/types/types.d.ts
vendored
@ -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
9
src/utils/dom.ts
Normal 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;
|
||||
}
|
16
src/utils/expressionUtils.ts
Normal file
16
src/utils/expressionUtils.ts
Normal 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;
|
||||
}
|
@ -9,6 +9,7 @@
|
||||
"jsxImportSource": "solid-js",
|
||||
"types": ["vite/client"],
|
||||
"noEmit": true,
|
||||
"isolatedModules": true
|
||||
"isolatedModules": true,
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user