Updated dependencies.

Added prettier and formatted.
Fixed bug causing back button to not show at all times.
This commit is contained in:
Martin Berg Alstad 2024-02-25 00:18:02 +01:00
parent ecd9f50029
commit 0528645838
36 changed files with 2815 additions and 1938 deletions

89
.idea/codeStyles/Project.xml generated Normal file
View File

@ -0,0 +1,89 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<HTMLCodeStyleSettings>
<option name="HTML_ALIGN_TEXT" value="true" />
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
<option name="HTML_DO_NOT_INDENT_CHILDREN_OF" value="" />
</HTMLCodeStyleSettings>
<JSCodeStyleSettings version="0">
<option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
<option name="SPACES_WITHIN_INTERPOLATION_EXPRESSIONS" value="true" />
</JSCodeStyleSettings>
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<Objective-C>
<option name="KEEP_STRUCTURES_IN_ONE_LINE" value="true" />
<option name="KEEP_CASE_EXPRESSIONS_IN_ONE_LINE" value="true" />
</Objective-C>
<TypeScriptCodeStyleSettings version="0">
<option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
<option name="SPACES_WITHIN_INTERPOLATION_EXPRESSIONS" value="true" />
</TypeScriptCodeStyleSettings>
<VueCodeStyleSettings>
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
</VueCodeStyleSettings>
<codeStyleSettings language="HTML">
<option name="SOFT_MARGINS" value="100" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JAVA">
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="CATCH_ON_NEW_LINE" value="true" />
<option name="FINALLY_ON_NEW_LINE" value="true" />
<option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="SOFT_MARGINS" value="100" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="ObjectiveC">
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="CATCH_ON_NEW_LINE" value="true" />
</codeStyleSettings>
<codeStyleSettings language="TypeScript">
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="CATCH_ON_NEW_LINE" value="true" />
<option name="FINALLY_ON_NEW_LINE" value="true" />
<option name="SOFT_MARGINS" value="100" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Vue">
<option name="SOFT_MARGINS" value="100" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

7
.idea/prettier.xml generated Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PrettierConfiguration">
<option name="myConfigurationMode" value="AUTOMATIC" />
<option name="myRunOnSave" value="true" />
</component>
</project>

7
.prettierrc Normal file
View File

@ -0,0 +1,7 @@
{
"semi": false,
"singleQuote": false,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-tailwindcss"]
}

View File

@ -1,17 +1,17 @@
<!DOCTYPE html>
<!doctype html>
<html lang="nb">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#181a1b" />
<title>Hjem | Martials.no</title>
<meta name="description" content="Hjemmeside for API og diverse">
<link rel="icon" type="image/x-icon" href="resources/code.svg">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#181a1b" />
<title>Hjem | Martials.no</title>
<meta name="description" content="Hjemmeside for API og diverse" />
<link rel="icon" type="image/x-icon" href="resources/code.svg" />
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="/src/app.tsx" type="module"></script>
</body>
<script src="/src/app.tsx" type="module"></script>
</body>
</html>

View File

@ -6,24 +6,27 @@
"prestart": "npx only-allow pnpm",
"dev": "vite",
"build": "vite build && sh build_extra.sh",
"serve": "vite preview"
"serve": "vite preview",
"format": "prettier --write ."
},
"license": "MIT",
"devDependencies": {
"autoprefixer": "^10.4.14",
"postcss": "^8.4.27",
"tailwindcss": "^3.3.3",
"typescript": "^5.1.6",
"vite": "^4.4.7",
"vite-plugin-solid": "^2.7.0"
"autoprefixer": "^10.4.17",
"postcss": "^8.4.35",
"prettier": "3.2.5",
"prettier-plugin-tailwindcss": "^0.5.11",
"tailwindcss": "^3.4.1",
"typescript": "^5.3.3",
"vite": "^5.1.4",
"vite-plugin-solid": "^2.10.1"
},
"dependencies": {
"@solidjs/router": "^0.8.2",
"@types/diff": "^5.0.3",
"diff": "^5.1.0",
"@solidjs/router": "^0.12.4",
"@types/diff": "^5.0.9",
"diff": "^5.2.0",
"solid-headless": "^0.13.1",
"solid-heroicons": "^3.2.4",
"solid-js": "^1.7.8",
"solid-js": "^1.8.15",
"xlsx": "^0.18.5"
}
}

2160
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
packages:
# include packages in subfolders (e.g. apps/ and packages/)
- "apps/**"
- 'packages/**'
- "packages/**"
# if required, exclude some directories
- '!**/test/**'
- "!**/test/**"

View File

@ -1,7 +1,7 @@
module.exports = {
purge: ['./*.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
purge: ["./*.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
plugins: {
tailwindcss: {},
autoprefixer: {}
}
}

View File

@ -1,25 +1,18 @@
import { Route, Router, Routes } from "@solidjs/router";
import HomePage from "./pages/home";
import TruthTablePage from "./pages/truth-table";
import PageNotFound from "./pages/404";
import { render } from "solid-js/web";
import { type Component } from "solid-js";
import FailureFunctionPage from "./pages/failureFunction";
const App: Component = () => (
<Routes>
<Route path={ "/" } element={ <HomePage /> } />
<Route path={ "/simplify-truths" } element={ <TruthTablePage /> } />
<Route path={ "/failure-function" } element={ <FailureFunctionPage /> } />
<Route path={ "*" } element={ <PageNotFound /> } />
</Routes>
);
import { Route, Router } from "@solidjs/router"
import HomePage from "./pages/home"
import TruthTablePage from "./pages/truth-table"
import PageNotFound from "./pages/404"
import { render } from "solid-js/web"
import FailureFunctionPage from "./pages/failureFunction"
render(
() => (
<Router>
<App />
</Router>
),
document.getElementById("root") as HTMLElement
);
() => (
<Router>
<Route path={"/"} component={HomePage} />
<Route path={"/simplify-truths"} component={TruthTablePage} />
<Route path={"/failure-function"} component={FailureFunctionPage} />
<Route path={"*"} component={PageNotFound} />
</Router>
),
document.getElementById("root") as HTMLElement
)

View File

@ -1,57 +1,59 @@
/* @refresh reload */
import { type Component, createSignal } from "solid-js";
import { type Component, createSignal } from "solid-js"
interface SwitchProps extends TitleProps {
defaultValue?: boolean,
onChange?: (value: boolean) => void,
defaultValue?: boolean
onChange?: (value: boolean) => void
}
export const MySwitch: Component<SwitchProps> = (
{
defaultValue = false,
title,
onChange,
className,
name,
id
}) => {
export const MySwitch: Component<SwitchProps> = ({
defaultValue = false,
title,
onChange,
className,
name,
id
}) => {
const [checked, setChecked] = createSignal(defaultValue)
const [checked, setChecked] = createSignal(defaultValue);
function handleChange() {
const newChecked = !checked();
setChecked(newChecked);
if (onChange) {
onChange(newChecked);
}
function handleChange() {
const newChecked = !checked()
setChecked(newChecked)
if (onChange) {
onChange(newChecked)
}
}
return (
<button id={ id }
onClick={ handleChange }
title={ title }
class={ `${ checked() ? "bg-cyan-900" : "bg-gray-500" }
relative inline-flex h-6 w-11 items-center rounded-full my-2 ${ className }` }>
<span class={ "sr-only" }>{ name }</span>
<span class={ `${ checked() ? 'translate-x-6' : 'translate-x-1' }
inline-block h-4 w-4 transform rounded-full bg-white transition-all` } />
</button>
);
};
export const Button: Component<ButtonProps> = (
{
className,
title,
children,
id,
onClick,
type = "button",
}
) => (
<button title={ title } id={ id } type={ type }
class={ `border-rounded bg-cyan-900 px-2 cursor-pointer ${ className }` }
onClick={ onClick }>
{ children }
return (
<button
id={id}
onClick={handleChange}
title={title}
class={`${checked() ? "bg-cyan-900" : "bg-gray-500"} relative my-2 inline-flex h-6 w-11 items-center rounded-full ${className}`}
>
<span class={"sr-only"}>{name}</span>
<span
class={`${checked() ? "translate-x-6" : "translate-x-1"} inline-block h-4 w-4 transform rounded-full bg-white transition-all`}
/>
</button>
);
)
}
export const Button: Component<ButtonProps> = ({
className,
title,
children,
id,
onClick,
type = "button"
}) => (
<button
title={title}
id={id}
type={type}
class={`border-rounded cursor-pointer bg-cyan-900 px-2 ${className}`}
onClick={onClick}
>
{children}
</button>
)

View File

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

View File

@ -1,104 +1,100 @@
/* @refresh reload */
import { Dialog, DialogDescription, DialogPanel, DialogTitle } from "solid-headless";
import { Component, createEffect, createSignal, JSX } from "solid-js";
import { Button } from "./button";
import { Portal } from "solid-js/web";
import { getElementById } from "../utils/dom";
import { Dialog, DialogDescription, DialogPanel, DialogTitle } from "solid-headless"
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,
acceptButtonId?: string,
cancelButtonName?: string,
callback?: () => void,
buttonClass?: string,
buttonTitle?: string | null,
description?: string
button?: JSX.Element
acceptButtonName?: string
acceptButtonId?: string
cancelButtonName?: string
callback?: () => void
buttonClass?: string
buttonTitle?: string | null
}
const MyDialog: Component<MyDialog> = (
{
title,
description,
button,
acceptButtonName = "Ok",
cancelButtonName = "Cancel",
children,
callback,
className,
buttonClass,
buttonTitle,
acceptButtonId,
}) => {
const MyDialog: Component<MyDialog> = ({
title,
description,
button,
acceptButtonName = "Ok",
cancelButtonName = "Cancel",
children,
callback,
className,
buttonClass,
buttonTitle,
acceptButtonId
}) => {
const [isOpen, setIsOpen] = createSignal(false)
const [isOpen, setIsOpen] = createSignal(false);
function callbackAndClose(): void {
callback?.()
setIsOpen(false)
}
function callbackAndClose(): void {
if (callback) {
callback();
}
setIsOpen(false);
function setupKeyPress(): () => void {
let isMounted = true
/**
* Pressing "Enter" when the modal is open, will click the accept button
* @param e KeyboardEvent of keypress
*/
function click(e: KeyboardEvent): void {
if (isMounted && e.key === "Enter" && acceptButtonId) {
getElementById<HTMLButtonElement>(acceptButtonId)?.click()
}
}
function setupKeyPress(): () => void {
let isMounted = true;
if (isOpen()) {
const id = "cl-6"
const el = getElementById(id)
el?.addEventListener("keypress", click)
return () => {
el?.removeEventListener("keypress", click)
isMounted = false
}
} else return () => undefined
}
/**
* Pressing "Enter" when the modal is open, will click the accept button
* @param e KeyboardEvent of keypress
*/
function click(e: KeyboardEvent): void {
if (isMounted && e.key === "Enter" && acceptButtonId) {
getElementById<HTMLButtonElement>(acceptButtonId)?.click();
}
}
createEffect(setupKeyPress, isOpen())
if (isOpen()) {
const id = "cl-6"
const el = getElementById(id);
el?.addEventListener("keypress", click);
return () => {
el?.removeEventListener("keypress", click);
isMounted = false;
}
}
else return () => undefined;
}
return (
<div class={"h-fit w-fit"}>
<button onClick={() => setIsOpen(true)} class={buttonClass} title={buttonTitle ?? undefined}>
{button}
</button>
createEffect(setupKeyPress, isOpen());
<Portal>
<Dialog
isOpen={isOpen()}
onClose={() => setIsOpen(false)}
class={`flex-row-center fixed inset-0 z-50 justify-center overflow-auto text-white ${className}`}
>
<div class={"fixed inset-0 bg-black/40" /*Backdrop*/} aria-hidden={true} />
return (
<div class={ "w-fit h-fit" }>
<DialogPanel class={"border-rounded relative w-fit border-gray-500 bg-default-bg p-2"}>
<DialogTitle class={"border-b"}>{title}</DialogTitle>
<DialogDescription class={"mb-4 mt-1"}>{description}</DialogDescription>
<button onClick={ () => setIsOpen(true) } class={ buttonClass } title={ buttonTitle ?? undefined }>
{ button }
</button>
{children}
<Portal>
<Dialog isOpen={ isOpen() } onClose={ () => setIsOpen(false) }
class={ `fixed inset-0 flex-row-center justify-center z-50 overflow-auto text-white ${ className }` }>
<div class={ "fixed inset-0 bg-black/40" /*Backdrop*/ } aria-hidden={ true } />
<DialogPanel class={ "w-fit relative bg-default-bg border-rounded border-gray-500 p-2" }>
<DialogTitle class={ "border-b" }>{ title }</DialogTitle>
<DialogDescription class={ "mb-4 mt-1" }>{ description }</DialogDescription>
{ children }
<div class={ "my-3" }>
<Button onClick={ callbackAndClose } className={ "h-10 mr-2" }
id={ acceptButtonId }>{ acceptButtonName }</Button>
<Button onClick={ () => setIsOpen(false) }
className={ "h-10" }>{ cancelButtonName }</Button>
</div>
</DialogPanel>
</Dialog>
</Portal>
</div>
);
<div class={"my-3"}>
<Button onClick={callbackAndClose} className={"mr-2 h-10"} id={acceptButtonId}>
{acceptButtonName}
</Button>
<Button onClick={() => setIsOpen(false)} className={"h-10"}>
{cancelButtonName}
</Button>
</div>
</DialogPanel>
</Dialog>
</Portal>
</div>
)
}
export default MyDialog;
export default MyDialog

View File

@ -1,11 +1,13 @@
/* @refresh reload */
import { type Component } from "solid-js";
import { Link } from "./link";
import { type Component } from "solid-js"
import { Link } from "./link"
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>
);
<footer class={`container absolute bottom-0 py-5 text-center ${className}`}>
<p>
Kildekode <Link to={"https://github.com/h600878/martials.no"}>GitHub</Link>
</p>
</footer>
)
export default Footer;
export default Footer

View File

@ -1,25 +1,29 @@
/* @refresh reload */
import { type Component, Show } from "solid-js";
import { Icon } from "solid-heroicons";
import { chevronLeft } from "solid-heroicons/solid";
import { Link } from "./link";
import { type Component, Show } from "solid-js"
import { Icon } from "solid-heroicons"
import { chevronLeft } from "solid-heroicons/solid"
import { Link } from "./link"
import { useLocation } from "@solidjs/router"
const Header: Component<TitleProps> = ({ className, title = "Title goes here" }) => (
<header class={ className }>
<div class={ "flex-row-center mx-auto w-fit" }>
const Header: Component<TitleProps> = ({ className, title = "Title goes here" }) => {
const location = useLocation()
<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>
return (
<header class={className}>
<div class={"flex-row-center mx-auto w-fit"}>
<Show when={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>
<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;
export default Header

View File

@ -1,28 +1,28 @@
/* @refresh reload */
import { type Component, createSignal, JSX, onMount, Setter, Show } from "solid-js";
import Row from "./row";
import { Icon } from "solid-heroicons";
import { magnifyingGlass, xMark } from "solid-heroicons/solid";
import { getElementById } from "../utils/dom";
import { type Component, createSignal, JSX, onMount, Setter, Show } from "solid-js"
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;
let isMounted = true
function hover(hover: boolean): void {
if (isMounted) {
setIsHover(hover);
}
function hover(hover: boolean): void {
if (isMounted) {
setIsHover(hover)
}
}
const el = getElementById(id);
el?.addEventListener("pointerenter", () => hover(true));
el?.addEventListener("pointerleave", () => hover(false));
const el = getElementById(id)
el?.addEventListener("pointerenter", () => hover(true))
el?.addEventListener("pointerleave", () => hover(false))
return () => {
el?.removeEventListener("pointerenter", () => hover(true));
el?.removeEventListener("pointerleave", () => hover(false));
isMounted = false;
}
return () => {
el?.removeEventListener("pointerenter", () => hover(true))
el?.removeEventListener("pointerleave", () => hover(false))
isMounted = false
}
}
/**
@ -30,149 +30,152 @@ function setupEventListener(id: string, setIsHover: Setter<boolean>): () => void
* if the value of the input element is not empty and it's different from the current value
*/
function setSetIsText(id: string | undefined, isText: boolean, setIsText: Setter<boolean>): void {
if (id) {
const el = getElementById<HTMLInputElement | HTMLTextAreaElement>(id);
if (el && el.value !== "" !== isText) {
setIsText(el.value !== "");
}
if (id) {
const el = getElementById<HTMLInputElement | HTMLTextAreaElement>(id)
if (el && (el.value !== "") !== isText) {
setIsText(el.value !== "")
}
}
}
interface Input<T extends HTMLElement> extends InputProps<T> {
leading?: JSX.Element,
trailing?: JSX.Element,
onChange?: () => void,
inputClass?: string,
leading?: JSX.Element
trailing?: JSX.Element
onChange?: () => void
inputClass?: string
}
export const Input: Component<Input<HTMLInputElement>> = ( // TODO remove leading and trailing from component
{
className,
id,
name,
type = "text",
title,
placeholder,
required = false,
onChange,
leading,
trailing,
inputClass,
ref
}): JSX.Element => {
export const Input: Component<Input<HTMLInputElement>> = (
// TODO remove leading and trailing from component
{
className,
id,
name,
type = "text",
title,
placeholder,
required = false,
onChange,
leading,
trailing,
inputClass,
ref
}
): JSX.Element => {
/**
* Is 'true' if the input element is in focus
*/
const [isFocused, setIsFocused] = createSignal(false)
/**
* Is 'true' if the user is hovering over the input element
*/
const [isHover, setIsHover] = createSignal(false)
/**
* Is 'true' if the input element contains any characters
*/
const [isText, setIsText] = createSignal(false)
/**
* Is 'true' if the input element is in focus
*/
const [isFocused, setIsFocused] = createSignal(false);
/**
* Is 'true' if the user is hovering over the input element
*/
const [isHover, setIsHover] = createSignal(false);
/**
* Is 'true' if the input element contains any characters
*/
const [isText, setIsText] = createSignal(false);
onMount(() => {
if (id && title) {
setupEventListener(id, setIsHover);
}
});
return (
<Row className={ `relative ${ className }` }>
{ leading }
<HoverTitle title={ title } isActive={ isFocused() || isHover() || isText() } htmlFor={ id } />
<input
class={ `bg-default-bg focus:border-cyan-500 outline-none border-2 border-gray-500
hover:border-t-cyan-400 ${ inputClass }` }
id={ id }
ref={ ref }
onFocus={ () => setIsFocused(true) }
onBlur={ () => setIsFocused(false) }
name={ name ?? undefined }
type={ type }
placeholder={ placeholder ?? undefined }
required={ required }
onInput={ () => {
setSetIsText(id, isText(), setIsText);
if (onChange) {
onChange();
}
} } />
{ trailing }
</Row>
);
}
const HoverTitle: Component<{ title?: string, isActive?: boolean, htmlFor?: string }> = (
{
title,
isActive = false,
htmlFor
}) => (
<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>
);
interface SearchProps extends InputProps<HTMLInputElement> {
typingDefault?: boolean
}
export const Search: Component<SearchProps> = (
{
typingDefault = false,
id = "search",
className,
ref
}) => {
const [typing, setTyping] = createSignal(typingDefault);
function getInputElement() {
return getElementById<HTMLInputElement>(id);
onMount(() => {
if (id && title) {
setupEventListener(id, setIsHover)
}
})
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 }
ref={ ref }
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> }
/>
);
return (
<Row className={`relative ${className}`}>
{leading}
<HoverTitle title={title} isActive={isFocused() || isHover() || isText()} htmlFor={id} />
<input
class={`border-2 border-gray-500 bg-default-bg outline-none hover:border-t-cyan-400
focus:border-cyan-500 ${inputClass}`}
id={id}
ref={ref}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
name={name ?? undefined}
type={type}
placeholder={placeholder ?? undefined}
required={required}
onInput={() => {
setSetIsText(id, isText(), setIsText)
if (onChange) {
onChange()
}
}}
/>
{trailing}
</Row>
)
}
const HoverTitle: Component<{ title?: string; isActive?: boolean; htmlFor?: string }> = ({
title,
isActive = false,
htmlFor
}) => (
<label
class={`pointer-events-none absolute
${isActive ? "default-bg -top-2 left-3 text-sm" : "left-2 top-1"}
text-gray-600 transition-all duration-150 dark:text-gray-400`}
for={htmlFor}
>
<div class={"relative z-50"}>{title}</div>
<div class={"default-bg absolute bottom-1/3 z-10 h-2 w-full"} />
</label>
)
interface SearchProps extends InputProps {
typingDefault?: boolean
}
export const Search: Component<SearchProps> = ({
typingDefault = false,
id = "search",
className,
ref
}) => {
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}
ref={ref}
placeholder={"¬A & B -> C"}
type={"text"}
onChange={onChange}
leading={
<Icon path={magnifyingGlass} aria-label={"Magnifying glass"} class={"absolute pl-2"} />
}
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

@ -1,23 +1,18 @@
/* @refresh reload */
import { type Component } from "solid-js";
import Header from "./header";
import Footer from "./footer";
import { type Component } from "solid-js"
import Header from "./header"
import Footer from "./footer"
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>
const Layout: Component<TitleProps> = ({ children, title, className }) => (
<div class={`relative min-h-screen bg-default-bg font-mono text-white ${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;
export default Layout

View File

@ -1,20 +1,17 @@
/* @refresh reload */
import { type Component } from "solid-js";
import { type Component } from "solid-js"
export const Link: Component<LinkProps> = (
{
to,
rel,
children,
className,
id,
newTab = true,
title,
}) => ( // TODO <A/> throws exception
<a href={ to } id={ id } title={ title }
rel={ `${ rel } ${ newTab ? "noreferrer" : undefined }` }
target={ newTab ? "_blank" : undefined }
class={ `link ${ className }` }>
{ children }
</a>
);
{ to, rel, children, className, id, newTab = true, title } // TODO <A/> throws exception
) => (
<a
href={to}
id={id}
title={title}
rel={`${rel} ${newTab ? "noreferrer" : undefined}`}
target={newTab ? "_blank" : undefined}
class={`link ${className}`}
>
{children}
</a>
)

View File

@ -1,78 +1,72 @@
/* @refresh reload */
import { type Component, createEffect, createSignal, JSX, Show } from "solid-js";
import { Button } from "./button";
import { type Component, createEffect, createSignal, JSX, Show } from "solid-js"
import { Button } from "./button"
interface MenuProps extends TitleProps {
button?: JSX.Element,
buttonClassName?: string,
itemsClassName?: string,
button?: JSX.Element
buttonClassName?: string
itemsClassName?: string
}
const MyMenu: Component<MenuProps> = (
{
title,
button,
children,
id,
className,
buttonClassName,
itemsClassName,
}) => {
const MyMenu: Component<MenuProps> = ({
title,
button,
children,
id,
className,
buttonClassName,
itemsClassName
}) => {
const [isOpen, setIsOpen] = createSignal(false)
const [isOpen, setIsOpen] = createSignal(false);
function closeMenu(): void {
setIsOpen(false)
}
function closeMenu(): void {
setIsOpen(false);
function toggleMenu(): void {
setIsOpen(!isOpen())
}
createEffect(() => {
function click(e: MouseEvent): void {
if (e.target instanceof HTMLElement) {
if (e.target.closest(`#${id}`) === null) {
closeMenu()
}
}
}
function toggleMenu(): void {
setIsOpen(!isOpen());
function keypress(e: KeyboardEvent): void {
if (e.key === "Escape") {
closeMenu()
}
}
createEffect(() => {
if (isOpen()) {
document.addEventListener("click", click)
document.addEventListener("keyup", keypress)
} else {
document.removeEventListener("click", click)
document.removeEventListener("keyup", keypress)
}
})
function click(e: MouseEvent): void {
if (e.target instanceof HTMLElement) {
if (e.target.closest(`#${ id }`) === null) {
closeMenu();
}
}
}
function keypress(e: KeyboardEvent): void {
if (e.key === "Escape") {
closeMenu();
}
}
if (isOpen()) {
document.addEventListener("click", click);
document.addEventListener("keyup", keypress);
}
else {
document.removeEventListener("click", click);
document.removeEventListener("keyup", keypress);
}
});
return ( // TODO transition
<div class={ `${ className }` } id={ id }>
<Button title={ title }
onClick={ toggleMenu }
className={ `flex-row-center ${ buttonClassName }` }>
{ button }
</Button>
<Show when={ isOpen() } keyed>
<div
class={ `absolute bg-default-bg border border-gray-500 rounded-b-xl mt-1 w-max z-50 ${ itemsClassName }` }>
<div class={ "mx-1" }>{ children }</div>
</div>
</Show>
return (
// TODO transition
<div class={`${className}`} id={id}>
<Button title={title} onClick={toggleMenu} className={`flex-row-center ${buttonClassName}`}>
{button}
</Button>
<Show when={isOpen()} keyed>
<div
class={`absolute z-50 mt-1 w-max rounded-b-xl border border-gray-500 bg-default-bg ${itemsClassName}`}
>
<div class={"mx-1"}>{children}</div>
</div>
);
</Show>
</div>
)
}
export default MyMenu;
export default MyMenu

View File

@ -1,74 +1,65 @@
/* @refresh reload */
import { Disclosure, DisclosureButton, DisclosurePanel, Transition } from "solid-headless";
import { Icon } from "solid-heroicons";
import { type Component, JSX } from "solid-js";
import { chevronUp } from "solid-heroicons/solid";
import { Disclosure, DisclosureButton, DisclosurePanel, Transition } from "solid-headless"
import { Icon } from "solid-heroicons"
import { type Component, JSX } from "solid-js"
import { chevronUp } from "solid-heroicons/solid"
interface InfoBoxProps extends TitleProps {
error?: boolean,
error?: boolean
}
export const InfoBox: Component<InfoBoxProps> = (
{
title,
children,
error = false,
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>
<div class={ "mx-2" }>{ children }</div>
</div>
);
export const InfoBox: Component<InfoBoxProps> = ({ title, children, error = false, 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>
<div class={"mx-2"}>{children}</div>
</div>
)
interface MyDisclosureProps extends TitleProps {
defaultOpen?: boolean,
onClick?: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>,
defaultOpen?: boolean
onClick?: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>
}
export const MyDisclosure: Component<MyDisclosureProps> = (
{
title,
children,
defaultOpen = false,
className,
id,
onClick
}): 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 MyDisclosure: Component<MyDisclosureProps> = ({
title,
children,
defaultOpen = false,
className,
id,
onClick
}): 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() && "rotate-180 transform"} 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
}) => (
<div class={ `bg-cyan-900 border-rounded dark:border-gray-800 p-2 mb-2
flex flex-col gap-1 ${ className }` }>
{ children }
</div>
);
export const MyDisclosureContainer: Component<ChildProps> = ({ children, className }) => (
<div
class={`border-rounded mb-2 flex flex-col gap-1
bg-cyan-900 p-2 dark:border-gray-800 ${className}`}
>
{children}
</div>
)

View File

@ -1,5 +1,5 @@
/* @refresh reload */
import { type Component } from "solid-js";
import { type Component } from "solid-js"
/**
* A row that centers its children
@ -8,7 +8,7 @@ import { type Component } from "solid-js";
* @returns The row
*/
const Row: Component<ChildProps> = ({ children, className }) => (
<div class={ `flex-row-center ${ className }` }>{ children }</div>
);
<div class={`flex-row-center ${className}`}>{children}</div>
)
export default Row;
export default Row

View File

@ -1,51 +1,54 @@
/* @refresh reload */
import { For } from "solid-js/web";
import { type Component } from "solid-js";
import { For } from "solid-js/web"
import { type Component } from "solid-js"
interface TruthTableProps extends SimpleProps {
table?: Table,
header?: string[],
table?: Table
header?: string[]
}
const TruthTable: Component<TruthTableProps> = (
{
table,
header,
className,
style,
id,
}) => (
<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>
}
const TruthTable: Component<TruthTableProps> = ({ table, header, className, style, id }) => (
<table
class={`z-10 table border-collapse border-2 border-gray-500 ${className}`}
id={id}
style={style}
>
<thead>
<tr>
<For each={header}>
{(exp) => (
<th
scope={"col"}
class={
`sticky top-0 bg-default-bg text-center outline
outline-2 outline-offset-[-1px] outline-gray-500 [position:-webkit-sticky;]` /*TODO sticky header at the top of the screen */
}
>
<p class={"w-max px-2"}>{exp}</p>
</th>
)}
</For>
</tr>
</thead>
<tbody>
<For each={table}>
{(row) => (
<tr class={"hover:text-black"}>
<For each={row}>
{(value) => (
<td
class={`border border-gray-500 text-center last:underline
${value ? "bg-green-700" : "bg-red-700"}`}
>
<p>{value ? "T" : "F"}</p>
</td>
)}
</For>
</tbody>
</table>
);
</tr>
)}
</For>
</tbody>
</table>
)
export default TruthTable;
export default TruthTable

View File

@ -3,46 +3,44 @@
@tailwind utilities;
@layer components {
.debug {
@apply border border-red-500;
@apply after:absolute after:content-['DEBUG'];
}
.debug {
@apply border border-red-500;
@apply after:content-['DEBUG'] after:absolute;
}
.flex-row-center {
@apply flex flex-row items-center;
}
.flex-row-center {
@apply flex flex-row items-center;
}
.border-rounded {
@apply rounded-2xl border border-gray-700;
}
.border-rounded {
@apply border rounded-2xl border-gray-700;
}
h1 {
@apply text-4xl;
}
h1 {
@apply text-4xl;
}
h2 {
@apply text-3xl;
}
h2 {
@apply text-3xl;
}
h3 {
@apply text-2xl;
}
h3 {
@apply text-2xl;
}
h4 {
@apply text-xl;
}
h4 {
@apply text-xl;
}
a {
@apply text-cyan-500 hover:underline;
}
a {
@apply hover:underline text-cyan-500;
}
li {
@apply list-disc ml-4;
}
svg {
@apply pointer-events-none h-6 w-6;
}
li {
@apply ml-4 list-disc;
}
svg {
@apply pointer-events-none h-6 w-6;
}
}

View File

@ -1,20 +1,20 @@
/* @refresh reload */
import { type Component } from "solid-js";
import { render } from "solid-js/web";
import Layout from "../components/layout";
import { Link } from "../components/link";
import { type Component } from "solid-js"
import { render } from "solid-js/web"
import Layout from "../components/layout"
import { Link } from "../components/link"
const PageNotFound: Component = () => (
<Layout title={ "Siden ble ikke funnet" }>
<div class={ "text-center" }>
<h4>Error 404 - Siden ble ikke funnet</h4>
<p>
<Link to={ "/" } newTab={ false } className={ "mx-auto relative w-max" }>
tilbake til forsiden
</Link>
</p>
</div>
</Layout>
);
<Layout title={"Siden ble ikke funnet"}>
<div class={"text-center"}>
<h4>Error 404 - Siden ble ikke funnet</h4>
<p>
<Link to={"/"} newTab={false} className={"relative mx-auto w-max"}>
tilbake til forsiden
</Link>
</p>
</div>
</Layout>
)
export default PageNotFound;
export default PageNotFound

View File

@ -1,66 +1,59 @@
/* @refresh reload */
import { Component, createSignal } from "solid-js";
import { Input } from "../components/input";
import Layout from "../components/layout";
import { failureFunction } from "../utils/failureFunction";
import { For } from "solid-js/web";
import { Component, createSignal } from "solid-js"
import { Input } from "../components/input"
import Layout from "../components/layout"
import { failureFunction } from "../utils/failureFunction"
import { For } from "solid-js/web"
type FFProps = { char: string, index: number }
type FFProps = { char: string; index: number }
const FailureFunctionPage: Component = () => {
let inputRef: HTMLInputElement | undefined = undefined;
let inputRef: HTMLInputElement | undefined = undefined
const [result, setResult] = createSignal<ReadonlyArray<FFProps>>()
const [result, setResult] = createSignal<ReadonlyArray<FFProps>>()
function onSubmit(e: Event) {
e.preventDefault()
if (inputRef) {
const input = inputRef.value;
const splitInput = input.split("")
const output = failureFunction(input)
function onSubmit(e: Event) {
e.preventDefault()
if (inputRef) {
const input = inputRef.value
const splitInput = input.split("")
const output = failureFunction(input)
if (output.length !== splitInput.length) {
console.error("Something went wrong")
}
else {
setResult(output.map((value, index) => {
return { char: splitInput[index], index: value } as FFProps
}))
}
}
if (output.length !== splitInput.length) {
console.error("Something went wrong")
} else {
setResult(output.map((value, index) => ({ char: splitInput[index], index: value })))
}
}
}
return (
<Layout title={ "Failure function" }>
<div class={ "container mx-auto max-w-2xl overflow-auto" }>
<p>Failure Function</p>
<form onsubmit={ onSubmit }>
<Input ref={ inputRef } inputClass={ "rounded-2xl w-full h-10 px-3" } />
</form>
<table class={ "mb-3" }>
<thead>
<tr>
<For each={ result() }>
{ ({ char }) =>
<th class={ "border border-black" }>{ char }</th>
}
</For>
</tr>
</thead>
<tbody>
<tr>
<For each={ result() }>
{ ({ index }) =>
<td class={ "border border-black" }>{ index }</td>
}
</For>
</tr>
</tbody>
</table>
</div>
</Layout>
);
return (
<Layout title={"Failure function"}>
<div class={"container mx-auto max-w-2xl overflow-auto"}>
<p>Failure Function</p>
<form onsubmit={onSubmit}>
<Input ref={inputRef} inputClass={"rounded-2xl w-full h-10 px-3"} />
</form>
<table class={"mb-3"}>
<thead>
<tr>
<For each={result()}>
{({ char }) => <th class={"border border-black"}>{char}</th>}
</For>
</tr>
</thead>
<tbody>
<tr>
<For each={result()}>
{({ index }) => <td class={"border border-black"}>{index}</td>}
</For>
</tr>
</tbody>
</table>
</div>
</Layout>
)
}
export default FailureFunctionPage;
export default FailureFunctionPage

View File

@ -1,55 +1,57 @@
/* @refresh reload */
import { For } from "solid-js/web";
import { For } from "solid-js/web"
import "../index.css";
import { type Component } from "solid-js";
import Layout from "../components/layout";
import Card from "../components/card";
import { Link } from "../components/link";
import "../index.css"
import { type Component } from "solid-js"
import Layout from "../components/layout"
import Card from "../components/card"
import { Link } from "../components/link"
const apiRoot = "https://api.martials.no";
const apiRoot = "https://api.martials.no"
const cards = [
{
title: "API-er",
children: <>
<p>Sjekk ut mine API-er</p>
<ul>
<li>
<Link className={ "text-white" } to={ `${ apiRoot }/simplify-truths` }>
Forenkle sannhetsverdier
</Link>
</li>
</ul>
</>,
to: apiRoot,
newTab: true,
},
{
title: "Hjemmeside",
children: <p>Sjekk ut mine andre prosjekter</p>,
to: "https://emberal.github.io/",
newTab: true,
},
{
title: "Forenkle sannhetsverdier",
children: <p>Implementering av API</p>,
to: `/simplify-truths`,
}
] satisfies CardProps[];
{
title: "API-er",
children: (
<>
<p>Sjekk ut mine API-er</p>
<ul>
<li>
<Link className={"text-white"} to={`${apiRoot}/simplify-truths`}>
Forenkle sannhetsverdier
</Link>
</li>
</ul>
</>
),
to: apiRoot,
newTab: true
},
{
title: "Hjemmeside",
children: <p>Sjekk ut mine andre prosjekter</p>,
to: "https://emberal.github.io/",
newTab: true
},
{
title: "Forenkle sannhetsverdier",
children: <p>Implementering av API</p>,
to: `/simplify-truths`
}
] satisfies CardProps[]
const HomePage: Component = () => (
<Layout title={ "Velkommen!" }>
<div class={ "flex flex-wrap justify-center mt-10" }>
<For each={ cards }>
{ card =>
<Card title={ card.title } className={ "m-4" } to={ card.to } newTab={ card.newTab }>
{ card.children }
</Card>
}
</For>
</div>
</Layout>
);
<Layout title={"Velkommen!"}>
<div class={"mt-10 flex flex-wrap justify-center"}>
<For each={cards}>
{(card) => (
<Card title={card.title} className={"m-4"} to={card.to} newTab={card.newTab}>
{card.children}
</Card>
)}
</For>
</div>
</Layout>
)
export default HomePage;
export default HomePage

View File

@ -1,420 +1,447 @@
/* @refresh reload */
import Layout from "../components/layout";
import { Input, Search } from "../components/input";
import { Icon } from "solid-heroicons";
import TruthTable from "../components/truth-table";
import { InfoBox, MyDisclosure, MyDisclosureContainer } from "../components/output";
import { diffChars } from "diff";
import MyMenu from "../components/menu";
import { type Accessor, type Component, createSignal, JSX, onMount, Show } from "solid-js";
import { For } from "solid-js/web";
import Row from "../components/row";
import {
arrowDownTray, arrowPath,
check,
eye,
eyeSlash,
funnel
} from "solid-heroicons/solid";
import { Button, MySwitch } from "../components/button";
import MyDialog from "../components/dialog";
import { exportToExcel } from "../utils/export";
import { Link } from "../components/link";
import { isTouch } from "../utils/touch";
import { replaceOperators } from "../utils/expressionUtils";
import { getElementById } from "../utils/dom";
import { useSearchParams } from "@solidjs/router";
import Layout from "../components/layout"
import { Input, Search } from "../components/input"
import { Icon } from "solid-heroicons"
import TruthTable from "../components/truth-table"
import { InfoBox, MyDisclosure, MyDisclosureContainer } from "../components/output"
import { diffChars } from "diff"
import MyMenu from "../components/menu"
import { type Accessor, type Component, createSignal, JSX, onMount, Show } from "solid-js"
import { For } from "solid-js/web"
import Row from "../components/row"
import { arrowDownTray, arrowPath, check, eye, eyeSlash, funnel } from "solid-heroicons/solid"
import { Button, MySwitch } from "../components/button"
import MyDialog from "../components/dialog"
import { exportToExcel } from "../utils/export"
import { Link } from "../components/link"
import { isTouch } from "../utils/touch"
import { replaceOperators } from "../utils/expressionUtils"
import { getElementById } from "../utils/dom"
import { useSearchParams } from "@solidjs/router"
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"
}
const fetchUrls = [
"http://localhost:8080/simplify/table/",
"https://api.martials.no/simplify-truths/simplify/table/"
];
"http://localhost:8080/simplify/table/",
"https://api.martials.no/simplify-truths/simplify/table/"
]
// TODO move some code to new components
const TruthTablePage: Component = () => {
const [searchParams, setSearchParams] = useSearchParams()
let inputElement: HTMLInputElement | undefined = undefined
const [searchParams, setSearchParams] = useSearchParams();
let inputElement: HTMLInputElement | undefined = undefined;
let simplifyDefault = searchParams.simplify === undefined || searchParams.simplify === "true",
inputContent = !!searchParams.exp,
hideIntermediate = searchParams.hideIntermediate === "true"
let simplifyDefault = searchParams.simplify === undefined || searchParams.simplify === "true",
inputContent = !!searchParams.exp,
hideIntermediate = searchParams.hideIntermediate === "true";
const [simplifyEnabled, setSimplifyEnabled] = createSignal(simplifyDefault)
const [fetchResult, setFetchResult] = createSignal<FetchResult | null>(null)
const [simplifyEnabled, setSimplifyEnabled] = createSignal(simplifyDefault);
const [fetchResult, setFetchResult] = createSignal<FetchResult | null>(null);
const hideOptions: Option[] = [
{ name: "Show all result", value: "NONE" },
{ name: "Hide true results", value: "TRUE" },
{ name: "Hide false results", value: "FALSE" }
]
const hideOptions: Option[] = [
{ name: "Show all result", value: "NONE" },
{ name: "Hide true results", value: "TRUE" },
{ name: "Hide false results", value: "FALSE" },
];
const [hideValues, setHideValues] = createSignal(hideOptions[0])
const [hideValues, setHideValues] = createSignal(hideOptions[0]);
const sortOptions: Option[] = [
{ name: "Sort by default", value: "DEFAULT" },
{ name: "Sort by true first", value: "TRUE_FIRST" },
{ name: "Sort by false first", value: "FALSE_FIRST" }
]
const sortOptions: Option[] = [
{ name: "Sort by default", value: "DEFAULT" },
{ name: "Sort by true first", value: "TRUE_FIRST" },
{ name: "Sort by false first", value: "FALSE_FIRST" },
];
const [sortValues, setSortValues] = createSignal(sortOptions[0])
const [hideIntermediates, setHideIntermediates] = createSignal(hideIntermediate)
const [isLoaded, setIsLoaded] = createSignal<boolean | null>(null)
const [error, setError] = createSignal<{ title: string; message: string } | null>(null)
const [useLocalhost, setUseLocalhost] = createSignal(false)
const [sortValues, setSortValues] = createSignal(sortOptions[0]);
const [hideIntermediates, setHideIntermediates] = createSignal(hideIntermediate);
const [isLoaded, setIsLoaded] = createSignal<boolean | null>(null);
const [error, setError] = createSignal<{ title: string, message: string } | null>(null);
const [useLocalhost, setUseLocalhost] = createSignal(false);
/**
* Updates the state of the current expression to the new search with all whitespace removed.
* If the element is not found, reset.
*/
function onClick(e: Event): void {
e.preventDefault() // Stops the page from reloading onClick
const exp = inputElement?.value
/**
* Updates the state of the current expression to the new search with all whitespace removed.
* If the element is not found, reset.
*/
function onClick(e: Event): void {
e.preventDefault(); // Stops the page from reloading onClick
const exp = inputElement?.value;
if (exp) {
setSearchParams({
exp,
simplify: simplifyEnabled(),
hide: hideValues().value,
sort: sortValues().value,
hideIntermediate: hideIntermediates()
})
if (exp) {
getFetchResult(exp)
}
}
setSearchParams({
exp,
simplify: simplifyEnabled(),
hide: hideValues().value,
sort: sortValues().value,
hideIntermediate: hideIntermediates()
});
function getFetchResult(exp: string | null): void {
setFetchResult(null)
getFetchResult(exp);
}
if (exp && exp !== "") {
exp = replaceOperators(exp)
setError(null)
setIsLoaded(false)
fetch(`${fetchUrls[useLocalhost() ? 0 : 1]}${encodeURIComponent(exp)}?
simplify=${simplifyEnabled()}&hide=${hideValues().value}&sort=${sortValues().value}&caseSensitive=false&
hideIntermediate=${hideIntermediates()}`)
.then((res) => res.json())
.then((res) => {
if (res.status !== "OK" && !res.ok) {
return setError({ title: "Input error", message: res.message })
}
return setFetchResult(res)
})
.catch((err) => setError({ title: "Fetch error", message: err.toString() }))
.finally(() => setIsLoaded(true))
}
}
onMount((): void => {
if (searchParams.exp) {
const exp = searchParams.exp
if (exp && inputElement) {
inputElement.value = exp
}
const hide = searchParams.hide
if (hide) {
setHideValues(hideOptions.find((o) => o.value === hide) ?? hideOptions[0])
}
const sort = searchParams.sort
if (sort) {
setSortValues(sortOptions.find((o) => o.value === sort) ?? sortOptions[0])
}
getFetchResult(exp)
}
function getFetchResult(exp: string | null): void {
setFetchResult(null);
// Focuses searchbar on load
if (!isTouch()) {
inputElement?.focus()
}
})
if (exp && exp !== "") {
exp = replaceOperators(exp);
setError(null);
setIsLoaded(false);
const tableId = "truth-table"
const filenameId = "excel-filename"
fetch(`${ fetchUrls[useLocalhost() ? 0 : 1] }${ encodeURIComponent(exp) }?
simplify=${ simplifyEnabled() }&hide=${ hideValues().value }&sort=${ sortValues().value }&caseSensitive=false&
hideIntermediate=${ hideIntermediates() }`)
.then(res => res.json())
.then(res => {
if (res.status !== "OK" && !res.ok) {
return setError({ title: "Input error", message: res.message });
function _exportToExcel(): void {
const value = getElementById<HTMLInputElement>(filenameId)?.value
exportToExcel({
name: value !== "" ? value : undefined,
tableId
})
}
return (
<Layout title={"Truth tables"}>
<Show when={import.meta.env.DEV ?? false} keyed>
(DEV) Use localhost:
<MySwitch title={"Use localhost"} defaultValue={false} onChange={setUseLocalhost} />
</Show>
<div id={"truth-content"}>
<div class={"mx-auto max-w-2xl"}>
<HowTo />
<form class={"flex-row-center"} onSubmit={onClick} autocomplete={"off"}>
<Search ref={inputElement} typingDefault={inputContent} />
<Button
id={"truth-input-button"}
title={"Generate (Enter)"}
type={"submit"}
className={"min-w-50px ml-2 h-10"}
children={"Generate"}
/>
</form>
{/* Options row */}
<Row className={"my-1 gap-2"}>
<span class={"h-min"}>{"Simplify"}: </span>
<MySwitch
onChange={setSimplifyEnabled}
defaultValue={simplifyEnabled()}
title={"Simplify"}
name={"Turn on/off simplify expressions"}
className={"mx-1"}
/>
<div class={"relative h-min"}>
<MyMenu
title={"Filter results"}
id={"filter-results"}
button={
<Show
when={hideValues().value !== "NONE"}
children={
<Icon
path={eyeSlash}
aria-label={"An eye with a slash through it"}
class={`mx-1 ${hideValues().value === "TRUE" ? "text-green-500" : "text-red-500"}`}
/>
}
return setFetchResult(res);
})
.catch(err => setError({ title: "Fetch error", message: err.toString() }))
.finally(() => setIsLoaded(true));
}
}
onMount((): void => {
if (searchParams.exp) {
const exp = searchParams.exp;
if (exp && inputElement) {
inputElement.value = exp;
}
const hide = searchParams.hide;
if (hide) {
setHideValues(hideOptions.find(o => o.value === hide) ?? hideOptions[0]);
}
const sort = searchParams.sort;
if (sort) {
setSortValues(sortOptions.find(o => o.value === sort) ?? sortOptions[0]);
}
getFetchResult(exp);
}
// Focuses searchbar on load
if (!isTouch()) {
inputElement?.focus();
}
});
const tableId = "truth-table";
const filenameId = "excel-filename";
function _exportToExcel(): void {
const value = getElementById<HTMLInputElement>(filenameId)?.value;
exportToExcel({
name: value !== "" ? value : undefined, tableId
});
}
return (
<Layout title={ "Truth tables" }>
<Show when={ import.meta.env.DEV ?? false } keyed>
(DEV) Use localhost:
<MySwitch title={ "Use localhost" } defaultValue={ false }
onChange={ setUseLocalhost } />
</Show>
<div id={ "truth-content" }>
<div class={ "max-w-2xl mx-auto" }>
<HowTo />
<form class={ "flex-row-center" } onSubmit={ onClick } autocomplete={ "off" }>
<Search ref={ inputElement } typingDefault={ inputContent } />
<Button id={ "truth-input-button" }
title={ "Generate (Enter)" }
type={ "submit" }
className={ "min-w-50px h-10 ml-2" }
children={ "Generate" } />
</form>
{ /* Options row */ }
<Row className={ "my-1 gap-2" }>
<span class={ "h-min" }>{ "Simplify" }: </span>
<MySwitch onChange={ setSimplifyEnabled } defaultValue={ simplifyEnabled() }
title={ "Simplify" }
name={ "Turn on/off simplify expressions" } className={ "mx-1" } />
<div class={ "h-min relative" }>
<MyMenu title={ "Filter results" } id={ "filter-results" }
button={
<Show when={ hideValues().value !== "NONE" } children={
<Icon path={ eyeSlash } aria-label={ "An eye with a slash through it" }
class={ `mx-1 ${ hideValues().value === "TRUE" ?
"text-green-500" : "text-red-500" }` } />
} fallback={
<Icon path={ eye } aria-label={ "An eye" } class={ "mx-1" } />
} keyed />
}
children={
<For each={ hideOptions }>
{ (option) => (
<SingleMenuItem onClick={ () => setHideValues(option) }
option={ option }
currentValue={ hideValues } />
) }
</For>
} itemsClassName={ "right-0" }
/>
</div>
<div class={ "h-min relative" }>
<MyMenu title={ "Sort results" } id={ "sort-results" }
button={ <Icon path={ funnel } aria-label={ "Filter" }
class={ `h-6 w-6 ${ sortValues().value === "TRUE_FIRST" ? "text-green-500" :
sortValues().value === "FALSE_FIRST" && "text-red-500" }` } /> }
children={
<For each={ sortOptions }>
{ (option) => (
<SingleMenuItem option={ option } currentValue={ sortValues }
onClick={ () => setSortValues(option) } />
) }
</For>
}
itemsClassName={ "right-0" }
/>
</div>
<MySwitch title={ "Hide intermediate values" }
onChange={ setHideIntermediates }
defaultValue={ hideIntermediates() } />
<Show when={ isLoaded() && error() === null } keyed>
<MyDialog title={ "Download" }
description={ "Export current table (.xlsx)" }
button={ <>
<p class={ "sr-only" }>{ "Download" }</p>
<Icon aria-label={ "Download" } path={ arrowDownTray } />
</> }
callback={ _exportToExcel }
acceptButtonName={ "Download" }
cancelButtonName={ "Cancel" }
buttonClass={ `float-right` }
buttonTitle={ "Export current table" }
acceptButtonId={ "download-accept" }>
<p>{ "Filename" }:</p>
<Input className={ "border-rounded h-10 px-2" } id={ filenameId }
placeholder={ "Truth Table" } />
</MyDialog>
</Show>
</Row>
<Show when={ error() } keyed>
<ErrorBox title={ error()?.title ?? "Error" }
error={ error()?.message ?? "Something went wrong" } />
</Show>
<Show when={ isLoaded() === false } keyed>
<Icon path={ arrowPath } aria-label={ "Loading indicator" } class={ "animate-spin mx-auto" } />
</Show>
<Show when={ simplifyEnabled() && (fetchResult()?.orderOperations?.length ?? 0) > 0 } keyed>
<ShowMeHow fetchResult={ fetchResult } />
</Show>
</div>
<Show when={ isLoaded() && error() === null } keyed>
<Show when={ simplifyEnabled() } keyed>
<InfoBox className={ "w-fit mx-auto pb-1 text-lg text-center" }
title={ "Output:" } id={ "expression-output" }>
<p>{ fetchResult()?.after }</p>
</InfoBox>
</Show>
<div class={ "flex justify-center m-2" }>
<div id={ "table" } class={ "h-[45rem] overflow-auto" }>
<TruthTable header={ fetchResult()?.header ?? undefined }
table={ fetchResult()?.table?.truthMatrix } id={ tableId } />
</div>
</div>
</Show>
fallback={<Icon path={eye} aria-label={"An eye"} class={"mx-1"} />}
keyed
/>
}
children={
<For each={hideOptions}>
{(option) => (
<SingleMenuItem
onClick={() => setHideValues(option)}
option={option}
currentValue={hideValues}
/>
)}
</For>
}
itemsClassName={"right-0"}
/>
</div>
</Layout>
);
<div class={"relative h-min"}>
<MyMenu
title={"Sort results"}
id={"sort-results"}
button={
<Icon
path={funnel}
aria-label={"Filter"}
class={`h-6 w-6 ${
sortValues().value === "TRUE_FIRST"
? "text-green-500"
: sortValues().value === "FALSE_FIRST" && "text-red-500"
}`}
/>
}
children={
<For each={sortOptions}>
{(option) => (
<SingleMenuItem
option={option}
currentValue={sortValues}
onClick={() => setSortValues(option)}
/>
)}
</For>
}
itemsClassName={"right-0"}
/>
</div>
<MySwitch
title={"Hide intermediate values"}
onChange={setHideIntermediates}
defaultValue={hideIntermediates()}
/>
<Show when={isLoaded() && error() === null} keyed>
<MyDialog
title={"Download"}
description={"Export current table (.xlsx)"}
button={
<>
<p class={"sr-only"}>{"Download"}</p>
<Icon aria-label={"Download"} path={arrowDownTray} />
</>
}
callback={_exportToExcel}
acceptButtonName={"Download"}
cancelButtonName={"Cancel"}
buttonClass={`float-right`}
buttonTitle={"Export current table"}
acceptButtonId={"download-accept"}
>
<p>{"Filename"}:</p>
<Input
className={"border-rounded h-10 px-2"}
id={filenameId}
placeholder={"Truth Table"}
/>
</MyDialog>
</Show>
</Row>
<Show when={error()} keyed>
<ErrorBox
title={error()?.title ?? "Error"}
error={error()?.message ?? "Something went wrong"}
/>
</Show>
<Show when={isLoaded() === false} keyed>
<Icon
path={arrowPath}
aria-label={"Loading indicator"}
class={"mx-auto animate-spin"}
/>
</Show>
<Show when={simplifyEnabled() && (fetchResult()?.orderOperations?.length ?? 0) > 0} keyed>
<ShowMeHow fetchResult={fetchResult} />
</Show>
</div>
<Show when={isLoaded() && error() === null} keyed>
<Show when={simplifyEnabled()} keyed>
<InfoBox
className={"mx-auto w-fit pb-1 text-center text-lg"}
title={"Output:"}
id={"expression-output"}
>
<p>{fetchResult()?.after}</p>
</InfoBox>
</Show>
<div class={"m-2 flex justify-center"}>
<div id={"table"} class={"h-[45rem] overflow-auto"}>
<TruthTable
header={fetchResult()?.header ?? undefined}
table={fetchResult()?.table?.truthMatrix}
id={tableId}
/>
</div>
</div>
</Show>
</div>
</Layout>
)
}
export default TruthTablePage;
export default TruthTablePage
interface SingleMenuItem {
option: Option,
currentValue?: Accessor<Option>,
onClick: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>,
option: Option
currentValue?: Accessor<Option>
onClick: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>
}
const SingleMenuItem: Component<SingleMenuItem> = (
{
option,
currentValue,
onClick
}) => {
const isSelected = () => currentValue?.().value === option.value;
return (
<button class={ `hover:underline cursor-pointer last:mb-1 flex-row-center` }
onClick={ onClick }>
<Icon path={ check } aria-label={ isSelected() ? "A checkmark" : "Nothing" }
class={ `text-white ${ !isSelected() && "invisible" }` } />
{ option.name }
</button>
);
const SingleMenuItem: Component<SingleMenuItem> = ({ option, currentValue, onClick }) => {
const isSelected = () => currentValue?.().value === option.value
return (
<button class={`flex-row-center cursor-pointer last:mb-1 hover:underline`} onClick={onClick}>
<Icon
path={check}
aria-label={isSelected() ? "A checkmark" : "Nothing"}
class={`text-white ${!isSelected() && "invisible"}`}
/>
{option.name}
</button>
)
}
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>
);
const ErrorBox: Component<{ title: string; error: string }> = ({ title, error }) => (
<InfoBox className={"mx-auto w-fit text-center"} title={title} error={true}>
<p>{error}</p>
</InfoBox>
)
interface ShowMeHowProps {
fetchResult: Accessor<FetchResult | null>,
fetchResult: Accessor<FetchResult | null>
}
const ShowMeHow: Component<ShowMeHowProps> = ({ fetchResult }) => (
<MyDisclosureContainer>
<MyDisclosure title={ "Show me how it's done" }>
<table class={ "table" }>
<tbody>
<For each={ fetchResult()?.orderOperations }>
{ orderOperationRow() }
</For>
</tbody>
</table>
</MyDisclosure>
</MyDisclosureContainer>
);
<MyDisclosureContainer>
<MyDisclosure title={"Show me how it's done"}>
<table class={"table"}>
<tbody>
<For each={fetchResult()?.orderOperations}>{orderOperationRow()}</For>
</tbody>
</table>
</MyDisclosure>
</MyDisclosureContainer>
)
const HowTo: Component = () => (
<MyDisclosureContainer>
<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>
<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>
);
<KeywordsDisclosure />
</MyDisclosureContainer>
)
const orderOperationRow = () => (operation: OrderOfOperation, index: Accessor<number>) => (
<tr class={ "border-b border-dotted border-gray-500" }>
<td>{ index() + 1 }:</td>
<td class={ "px-2" }>{
<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"} ${part.removed && "bg-red-700"}`}>
{part.value}
</span>
)}
</For>
}
<For each={ diffChars(operation.before, operation.after) }>
{ (part) => (
<span class={ `${ part.added && "bg-green-700" } ${ part.removed && "bg-red-700" }` }>
{ part.value }
</span>
) }
</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>
</tr>
);
<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>
</tr>
)
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>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>
);
<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>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>
)

10
src/types/env.d.ts vendored
View File

@ -1,11 +1,11 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_FETCH_URL: string,
readonly VITE_FETCH_PATH: string,
readonly VITE_FETCH_FULL: string,
readonly VITE_FETCH_URL: string
readonly VITE_FETCH_PATH: string
readonly VITE_FETCH_FULL: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
readonly env: ImportMetaEnv
}

84
src/types/types.d.ts vendored
View File

@ -1,70 +1,70 @@
interface SimpleProps<T extends HTMLElement = HTMLElement> {
name?: string;
className?: string,
style?: import("solid-js").JSX.CSSProperties,
id?: string,
title?: string,
ref?: T,
name?: string
className?: string
style?: import("solid-js").JSX.CSSProperties
id?: string
title?: string
ref?: T
}
interface ChildProps<T extends HTMLElement = HTMLInputElement> extends SimpleProps<T> {
children?: import("solid-js").JSX.Element,
children?: import("solid-js").JSX.Element
}
interface LinkProps extends ChildProps {
to?: string,
rel?: string,
newTab?: boolean,
to?: string
rel?: string
newTab?: boolean
}
interface TitleProps<T extends HTMLElement = HTMLElement> extends ChildProps<T> {
title?: string,
title?: string
}
interface ButtonProps extends TitleProps {
onClick?: import("solid-js").JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>,
type?: "button" | "submit" | "reset",
onClick?: import("solid-js").JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>
type?: "button" | "submit" | "reset"
}
interface InputProps<T extends HTMLElement = HTMLInputElement> extends TitleProps<T> {
onInput?: import("solid-js").JSX.EventHandlerUnion<T, Event>,
placeholder?: string,
required?: boolean,
type?: string,
onInput?: import("solid-js").JSX.EventHandlerUnion<T, Event>
placeholder?: string
required?: boolean
type?: string
}
interface CardProps extends LinkProps {
title?: string;
title?: string
}
type Expression = {
leading: string,
left: Expression | null,
operator: Operator | null,
right: Expression | null,
trailing: string,
atomic: string | null,
};
leading: string
left: Expression | null
operator: Operator | null
right: Expression | null
trailing: string
atomic: string | null
}
type Operator = "AND" | "OR" | "NOT" | "IMPLICATION";
type Operator = "AND" | "OR" | "NOT" | "IMPLICATION"
type Table = boolean[][];
type Table = boolean[][]
type OrderOfOperation = {
before: string,
after: string,
law: string,
};
before: string
after: string
law: string
}
type FetchResult = {
status: string,
version: string | null,
before: string,
after: string,
orderOperations: OrderOfOperation[] | null,
expression: Expression | null,
header: string[] | null,
table: {
truthMatrix: Table,
} | null,
};
status: string
version: string | null
before: string
after: string
orderOperations: OrderOfOperation[] | null
expression: Expression | null
header: string[] | null
table: {
truthMatrix: Table
} | null
}

View File

@ -5,5 +5,5 @@
* @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 <T>document.getElementById(id);
return <T>document.getElementById(id)
}

View File

@ -1,4 +1,4 @@
import { type BookType, utils, write, writeFile } from "xlsx";
import { type BookType, utils, write, writeFile } from "xlsx"
/**
* Exports the generated truth table to an excel (.xlsx) file
@ -27,17 +27,20 @@ import { type BookType, utils, write, writeFile } from "xlsx";
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export function exportToExcel(
{
type = "xlsx",
name = "Truth Table",
dl = false,
tableId,
}: { type?: BookType, name?: string, dl?: boolean, tableId: string }): any {
const element = document.getElementById(tableId);
const wb = utils.table_to_book(element, { sheet: "sheet1" });
return dl ?
write(wb, { bookType: type, bookSST: true, type: 'base64' }) :
writeFile(wb, name + "." + type);
export function exportToExcel({
type = "xlsx",
name = "Truth Table",
dl = false,
tableId
}: {
type?: BookType
name?: string
dl?: boolean
tableId: string
}): any {
const element = document.getElementById(tableId)
const wb = utils.table_to_book(element, { sheet: "sheet1" })
return dl
? write(wb, { bookType: type, bookSST: true, type: "base64" })
: writeFile(wb, name + "." + type)
}

View File

@ -4,11 +4,11 @@
* @returns The expression with the replaced operators
*/
export function replaceOperators(expression: string): string {
return expression
.replaceAll(/\//g, "|")
.replaceAll(/¬/g, "!")
.replaceAll(/\sOR\s/gi, " | ")
.replaceAll(/\sAND\s/gi, " & ")
.replaceAll(/\s(IMPLICATION|IMP)\s/gi, " -> ")
.replaceAll(/\sNOT\s/gi, " !");
}
return expression
.replaceAll(/\//g, "|")
.replaceAll(/¬/g, "!")
.replaceAll(/\sOR\s/gi, " | ")
.replaceAll(/\sAND\s/gi, " & ")
.replaceAll(/\s(IMPLICATION|IMP)\s/gi, " -> ")
.replaceAll(/\sNOT\s/gi, " !")
}

View File

@ -1,23 +1,24 @@
export function failureFunction(P: String, m = P.length): number[] {
// No proper prefix for string of length 1:
const arr = [0]
let i = 0, j = 1
// No proper prefix for string of length 1:
const arr = [0]
let i = 0,
j = 1
while (j < m) {
if (P[i] == P[j]) {
i++
arr.push(i)
j++;
}
// The first character didn't match:
else if (i == 0) {
arr.push(0)
j++
}
// Mismatch after at least one matching character:
else {
i = arr[i - 1]
}
while (j < m) {
if (P[i] == P[j]) {
i++
arr.push(i)
j++
}
return arr
// The first character didn't match:
else if (i == 0) {
arr.push(0)
j++
}
// Mismatch after at least one matching character:
else {
i = arr[i - 1]
}
}
return arr
}

View File

@ -3,5 +3,5 @@
* @returns {boolean} True if the device is touch enabled, otherwise false
*/
export function isTouch(): boolean {
return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
return "ontouchstart" in window || navigator.maxTouchPoints > 0
}

View File

@ -1,16 +1,13 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./index.html', "./404.html",
'./src/**/*.{js,ts,jsx,tsx,css,md,mdx,html,json,scss}',
],
darkMode: 'class',
theme: {
extend: {
colors: {
"default-bg": "#181a1b",
}
},
},
plugins: [],
};
content: ["./index.html", "./404.html", "./src/**/*.{js,ts,jsx,tsx,css,md,mdx,html,json,scss}"],
darkMode: "class",
theme: {
extend: {
colors: {
"default-bg": "#181a1b"
}
}
},
plugins: []
}

View File

@ -1,17 +1,17 @@
import { defineConfig } from 'vite';
import solidPlugin from 'vite-plugin-solid';
import { defineConfig } from "vite"
import solidPlugin from "vite-plugin-solid"
export default defineConfig({
plugins: [solidPlugin()],
server: {
port: 3000,
},
build: {
target: 'esnext',
rollupOptions: {
input: {
main: "index.html",
}
}
},
});
plugins: [solidPlugin()],
server: {
port: 3000
},
build: {
target: "esnext",
rollupOptions: {
input: {
main: "index.html"
}
}
}
})