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

View File

@ -6,24 +6,27 @@
"prestart": "npx only-allow pnpm", "prestart": "npx only-allow pnpm",
"dev": "vite", "dev": "vite",
"build": "vite build && sh build_extra.sh", "build": "vite build && sh build_extra.sh",
"serve": "vite preview" "serve": "vite preview",
"format": "prettier --write ."
}, },
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.17",
"postcss": "^8.4.27", "postcss": "^8.4.35",
"tailwindcss": "^3.3.3", "prettier": "3.2.5",
"typescript": "^5.1.6", "prettier-plugin-tailwindcss": "^0.5.11",
"vite": "^4.4.7", "tailwindcss": "^3.4.1",
"vite-plugin-solid": "^2.7.0" "typescript": "^5.3.3",
"vite": "^5.1.4",
"vite-plugin-solid": "^2.10.1"
}, },
"dependencies": { "dependencies": {
"@solidjs/router": "^0.8.2", "@solidjs/router": "^0.12.4",
"@types/diff": "^5.0.3", "@types/diff": "^5.0.9",
"diff": "^5.1.0", "diff": "^5.2.0",
"solid-headless": "^0.13.1", "solid-headless": "^0.13.1",
"solid-heroicons": "^3.2.4", "solid-heroicons": "^3.2.4",
"solid-js": "^1.7.8", "solid-js": "^1.8.15",
"xlsx": "^0.18.5" "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: packages:
# include packages in subfolders (e.g. apps/ and packages/) # include packages in subfolders (e.g. apps/ and packages/)
- "apps/**" - "apps/**"
- 'packages/**' - "packages/**"
# if required, exclude some directories # if required, exclude some directories
- '!**/test/**' - "!**/test/**"

View File

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

View File

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

View File

@ -1,57 +1,59 @@
/* @refresh reload */ /* @refresh reload */
import { type Component, createSignal } from "solid-js"; import { type Component, createSignal } from "solid-js"
interface SwitchProps extends TitleProps { interface SwitchProps extends TitleProps {
defaultValue?: boolean, defaultValue?: boolean
onChange?: (value: boolean) => void, onChange?: (value: boolean) => void
} }
export const MySwitch: Component<SwitchProps> = ( export const MySwitch: Component<SwitchProps> = ({
{ defaultValue = false,
defaultValue = false, title,
title, onChange,
onChange, className,
className, name,
name, id
id }) => {
}) => { const [checked, setChecked] = createSignal(defaultValue)
const [checked, setChecked] = createSignal(defaultValue); function handleChange() {
const newChecked = !checked()
function handleChange() { setChecked(newChecked)
const newChecked = !checked(); if (onChange) {
setChecked(newChecked); onChange(newChecked)
if (onChange) {
onChange(newChecked);
}
} }
}
return ( return (
<button id={ id } <button
onClick={ handleChange } id={id}
title={ title } onClick={handleChange}
class={ `${ checked() ? "bg-cyan-900" : "bg-gray-500" } title={title}
relative inline-flex h-6 w-11 items-center rounded-full my-2 ${ className }` }> 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' } <span class={"sr-only"}>{name}</span>
inline-block h-4 w-4 transform rounded-full bg-white transition-all` } /> <span
</button> class={`${checked() ? "translate-x-6" : "translate-x-1"} inline-block h-4 w-4 transform rounded-full bg-white transition-all`}
); />
};
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 }
</button> </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 */ /* @refresh reload */
import { type Component } from "solid-js"; import { type Component } from "solid-js"
import { Link } from "./link"; import { Link } from "./link"
const Card: Component<CardProps> = ( const Card: Component<CardProps> = ({ children, className, title, to, newTab = false }) => (
{ <div
children, class={`relative h-32 w-72 rounded-2xl bg-gradient-to-r from-cyan-600 to-cyan-500 ${className}`}
className, >
title, <div class={"relative p-5"}>
to, <Link className={"text-white"} to={to} newTab={newTab}>
newTab = false <h3 class={"mx-auto w-fit text-center before:content-['↗']"}>{title}</h3>
}) => ( </Link>
<div class={ `relative bg-gradient-to-r from-cyan-600 to-cyan-500 h-32 w-72 rounded-2xl ${ className }` }> {children}
<div class={ "relative p-5" }>
<Link className={ "text-white" } to={ to } newTab={ newTab }>
<h3 class={ "text-center w-fit mx-auto before:content-['↗']" }>{ title }</h3>
</Link>
{ children }
</div>
</div> </div>
); </div>
)
export default Card; export default Card

View File

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

View File

@ -1,11 +1,13 @@
/* @refresh reload */ /* @refresh reload */
import { type Component } from "solid-js"; import { type Component } from "solid-js"
import { Link } from "./link"; import { Link } from "./link"
const Footer: Component<SimpleProps> = ({ className }) => ( const Footer: Component<SimpleProps> = ({ className }) => (
<footer class={ `text-center py-5 absolute bottom-0 container ${ className }` }> <footer class={`container absolute bottom-0 py-5 text-center ${className}`}>
<p>Kildekode <Link to={ "https://github.com/h600878/martials.no" }>GitHub</Link></p> <p>
</footer> 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 */ /* @refresh reload */
import { type Component, Show } from "solid-js"; import { type Component, Show } from "solid-js"
import { Icon } from "solid-heroicons"; import { Icon } from "solid-heroicons"
import { chevronLeft } from "solid-heroicons/solid"; import { chevronLeft } from "solid-heroicons/solid"
import { Link } from "./link"; import { Link } from "./link"
import { useLocation } from "@solidjs/router"
const Header: Component<TitleProps> = ({ className, title = "Title goes here" }) => ( const Header: Component<TitleProps> = ({ className, title = "Title goes here" }) => {
<header class={ className }> const location = useLocation()
<div class={ "flex-row-center mx-auto w-fit" }>
<Show when={ typeof location !== "undefined" && location.pathname !== "/" } keyed> return (
<Link to={ "/" } newTab={ false } title={ "Back to homepage" }> <header class={className}>
<Icon path={ chevronLeft } class={ "text-cyan-500" } /> <div class={"flex-row-center mx-auto w-fit"}>
</Link> <Show when={location.pathname !== "/"} keyed>
</Show> <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> <h1 class={"text-center text-cyan-500"}>{title}</h1>
</div> </div>
<div class={ "mx-auto w-fit" }> <div class={"mx-auto w-fit"}>
<p>Av Martin Berg Alstad</p> <p>Av Martin Berg Alstad</p>
</div> </div>
</header> </header>
); )
}
export default Header; export default Header

View File

@ -1,28 +1,28 @@
/* @refresh reload */ /* @refresh reload */
import { type Component, createSignal, JSX, onMount, Setter, Show } from "solid-js"; import { type Component, createSignal, JSX, onMount, Setter, Show } from "solid-js"
import Row from "./row"; import Row from "./row"
import { Icon } from "solid-heroicons"; import { Icon } from "solid-heroicons"
import { magnifyingGlass, xMark } from "solid-heroicons/solid"; import { magnifyingGlass, xMark } from "solid-heroicons/solid"
import { getElementById } from "../utils/dom"; import { getElementById } from "../utils/dom"
function setupEventListener(id: string, setIsHover: Setter<boolean>): () => void { function setupEventListener(id: string, setIsHover: Setter<boolean>): () => void {
let isMounted = true; let isMounted = true
function hover(hover: boolean): void { function hover(hover: boolean): void {
if (isMounted) { if (isMounted) {
setIsHover(hover); setIsHover(hover)
}
} }
}
const el = getElementById(id); const el = getElementById(id)
el?.addEventListener("pointerenter", () => hover(true)); el?.addEventListener("pointerenter", () => hover(true))
el?.addEventListener("pointerleave", () => hover(false)); el?.addEventListener("pointerleave", () => hover(false))
return () => { return () => {
el?.removeEventListener("pointerenter", () => hover(true)); el?.removeEventListener("pointerenter", () => hover(true))
el?.removeEventListener("pointerleave", () => hover(false)); el?.removeEventListener("pointerleave", () => hover(false))
isMounted = 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 * 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 { function setSetIsText(id: string | undefined, isText: boolean, setIsText: Setter<boolean>): void {
if (id) { if (id) {
const el = getElementById<HTMLInputElement | HTMLTextAreaElement>(id); const el = getElementById<HTMLInputElement | HTMLTextAreaElement>(id)
if (el && el.value !== "" !== isText) { if (el && (el.value !== "") !== isText) {
setIsText(el.value !== ""); setIsText(el.value !== "")
}
} }
}
} }
interface Input<T extends HTMLElement> extends InputProps<T> { interface Input<T extends HTMLElement> extends InputProps<T> {
leading?: JSX.Element, leading?: JSX.Element
trailing?: JSX.Element, trailing?: JSX.Element
onChange?: () => void, onChange?: () => void
inputClass?: string, inputClass?: string
} }
export const Input: Component<Input<HTMLInputElement>> = ( // TODO remove leading and trailing from component export const Input: Component<Input<HTMLInputElement>> = (
{ // TODO remove leading and trailing from component
className, {
id, className,
name, id,
type = "text", name,
title, type = "text",
placeholder, title,
required = false, placeholder,
onChange, required = false,
leading, onChange,
trailing, leading,
inputClass, trailing,
ref inputClass,
}): JSX.Element => { 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)
/** onMount(() => {
* Is 'true' if the input element is in focus if (id && title) {
*/ setupEventListener(id, setIsHover)
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);
} }
})
function clearSearch(): void { return (
const el = getInputElement(); <Row className={`relative ${className}`}>
if (el) { {leading}
el.value = ""; <HoverTitle title={title} isActive={isFocused() || isHover() || isText()} htmlFor={id} />
setTyping(false); <input
history.replaceState(null, "", location.pathname); class={`border-2 border-gray-500 bg-default-bg outline-none hover:border-t-cyan-400
el.focus(); focus:border-cyan-500 ${inputClass}`}
} id={id}
} ref={ref}
onFocus={() => setIsFocused(true)}
function onChange(): void { onBlur={() => setIsFocused(false)}
const el = getInputElement(); name={name ?? undefined}
if (el && (el.value !== "") !== typing()) { type={type}
setTyping(el.value !== ""); placeholder={placeholder ?? undefined}
} required={required}
} onInput={() => {
setSetIsText(id, isText(), setIsText)
return ( if (onChange) {
<Input inputClass={ `rounded-xl pl-7 h-10 w-full pr-8` } className={ `w-full ${ className }` } onChange()
id={ id } }
ref={ ref } }}
placeholder={ "¬A & B -> C" } />
type={ "text" } {trailing}
onChange={ onChange } </Row>
leading={ <Icon path={ magnifyingGlass } aria-label={ "Magnifying glass" } )
class={ "pl-2 absolute" } /> } }
trailing={ <Show when={ typing() } keyed>
<button class={ "absolute right-2" } const HoverTitle: Component<{ title?: string; isActive?: boolean; htmlFor?: string }> = ({
title={ "Clear" } title,
type={ "reset" } isActive = false,
onClick={ clearSearch }> htmlFor
<Icon path={ xMark } aria-label={ "The letter X" } /> }) => (
</button> <label
</Show> } 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 */ /* @refresh reload */
import { type Component } from "solid-js"; import { type Component } from "solid-js"
import Header from "./header"; import Header from "./header"
import Footer from "./footer"; import Footer from "./footer"
const Layout: Component<TitleProps> = ( const Layout: Component<TitleProps> = ({ children, title, className }) => (
{ <div class={`relative min-h-screen bg-default-bg font-mono text-white ${className}`}>
children, <div class="container mx-auto">
title, <Header className={"py-3"} title={title} />
className <main>
}) => ( <div class={"pb-28"}>{children}</div>
<div class={ `bg-default-bg text-white min-h-screen relative font-mono ${ className }` }> <Footer />
<div class="container mx-auto"> </main>
<Header className={ "py-3" } title={ title } />
<main>
<div class={ "pb-28" }>{ children }</div>
<Footer />
</main>
</div>
</div> </div>
); </div>
)
export default Layout; export default Layout

View File

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

View File

@ -1,78 +1,72 @@
/* @refresh reload */ /* @refresh reload */
import { type Component, createEffect, createSignal, JSX, Show } from "solid-js"; import { type Component, createEffect, createSignal, JSX, Show } from "solid-js"
import { Button } from "./button"; import { Button } from "./button"
interface MenuProps extends TitleProps { interface MenuProps extends TitleProps {
button?: JSX.Element, button?: JSX.Element
buttonClassName?: string, buttonClassName?: string
itemsClassName?: string, itemsClassName?: string
} }
const MyMenu: Component<MenuProps> = ( const MyMenu: Component<MenuProps> = ({
{ title,
title, button,
button, children,
children, id,
id, className,
className, buttonClassName,
buttonClassName, itemsClassName
itemsClassName, }) => {
}) => { const [isOpen, setIsOpen] = createSignal(false)
const [isOpen, setIsOpen] = createSignal(false); function closeMenu(): void {
setIsOpen(false)
}
function closeMenu(): void { function toggleMenu(): void {
setIsOpen(false); setIsOpen(!isOpen())
}
createEffect(() => {
function click(e: MouseEvent): void {
if (e.target instanceof HTMLElement) {
if (e.target.closest(`#${id}`) === null) {
closeMenu()
}
}
} }
function toggleMenu(): void { function keypress(e: KeyboardEvent): void {
setIsOpen(!isOpen()); 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 { return (
if (e.target instanceof HTMLElement) { // TODO transition
if (e.target.closest(`#${ id }`) === null) { <div class={`${className}`} id={id}>
closeMenu(); <Button title={title} onClick={toggleMenu} className={`flex-row-center ${buttonClassName}`}>
} {button}
} </Button>
}
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>
<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> </div>
); </Show>
</div>
)
} }
export default MyMenu; export default MyMenu

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,420 +1,447 @@
/* @refresh reload */ /* @refresh reload */
import Layout from "../components/layout"; import Layout from "../components/layout"
import { Input, Search } from "../components/input"; import { Input, Search } from "../components/input"
import { Icon } from "solid-heroicons"; import { Icon } from "solid-heroicons"
import TruthTable from "../components/truth-table"; import TruthTable from "../components/truth-table"
import { InfoBox, MyDisclosure, MyDisclosureContainer } from "../components/output"; import { InfoBox, MyDisclosure, MyDisclosureContainer } from "../components/output"
import { diffChars } from "diff"; import { diffChars } from "diff"
import MyMenu from "../components/menu"; import MyMenu from "../components/menu"
import { type Accessor, type Component, createSignal, JSX, onMount, Show } from "solid-js"; import { type Accessor, type Component, createSignal, JSX, onMount, Show } from "solid-js"
import { For } from "solid-js/web"; import { For } from "solid-js/web"
import Row from "../components/row"; import Row from "../components/row"
import { import { arrowDownTray, arrowPath, check, eye, eyeSlash, funnel } from "solid-heroicons/solid"
arrowDownTray, arrowPath, import { Button, MySwitch } from "../components/button"
check, import MyDialog from "../components/dialog"
eye, import { exportToExcel } from "../utils/export"
eyeSlash, import { Link } from "../components/link"
funnel import { isTouch } from "../utils/touch"
} from "solid-heroicons/solid"; import { replaceOperators } from "../utils/expressionUtils"
import { Button, MySwitch } from "../components/button"; import { getElementById } from "../utils/dom"
import MyDialog from "../components/dialog"; import { useSearchParams } from "@solidjs/router"
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 = [ const fetchUrls = [
"http://localhost:8080/simplify/table/", "http://localhost:8080/simplify/table/",
"https://api.martials.no/simplify-truths/simplify/table/" "https://api.martials.no/simplify-truths/simplify/table/"
]; ]
// TODO move some code to new components // TODO move some code to new components
const TruthTablePage: Component = () => { const TruthTablePage: Component = () => {
const [searchParams, setSearchParams] = useSearchParams()
let inputElement: HTMLInputElement | undefined = undefined
const [searchParams, setSearchParams] = useSearchParams(); let simplifyDefault = searchParams.simplify === undefined || searchParams.simplify === "true",
let inputElement: HTMLInputElement | undefined = undefined; inputContent = !!searchParams.exp,
hideIntermediate = searchParams.hideIntermediate === "true"
let simplifyDefault = searchParams.simplify === undefined || searchParams.simplify === "true", const [simplifyEnabled, setSimplifyEnabled] = createSignal(simplifyDefault)
inputContent = !!searchParams.exp, const [fetchResult, setFetchResult] = createSignal<FetchResult | null>(null)
hideIntermediate = searchParams.hideIntermediate === "true";
const [simplifyEnabled, setSimplifyEnabled] = createSignal(simplifyDefault); const hideOptions: Option[] = [
const [fetchResult, setFetchResult] = createSignal<FetchResult | null>(null); { name: "Show all result", value: "NONE" },
{ name: "Hide true results", value: "TRUE" },
{ name: "Hide false results", value: "FALSE" }
]
const hideOptions: Option[] = [ const [hideValues, setHideValues] = createSignal(hideOptions[0])
{ 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 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[] = [ const [sortValues, setSortValues] = createSignal(sortOptions[0])
{ name: "Sort by default", value: "DEFAULT" }, const [hideIntermediates, setHideIntermediates] = createSignal(hideIntermediate)
{ name: "Sort by true first", value: "TRUE_FIRST" }, const [isLoaded, setIsLoaded] = createSignal<boolean | null>(null)
{ name: "Sort by false first", value: "FALSE_FIRST" }, 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); * Updates the state of the current expression to the new search with all whitespace removed.
const [isLoaded, setIsLoaded] = createSignal<boolean | null>(null); * If the element is not found, reset.
const [error, setError] = createSignal<{ title: string, message: string } | null>(null); */
const [useLocalhost, setUseLocalhost] = createSignal(false); function onClick(e: Event): void {
e.preventDefault() // Stops the page from reloading onClick
const exp = inputElement?.value
/** if (exp) {
* Updates the state of the current expression to the new search with all whitespace removed. setSearchParams({
* If the element is not found, reset. exp,
*/ simplify: simplifyEnabled(),
function onClick(e: Event): void { hide: hideValues().value,
e.preventDefault(); // Stops the page from reloading onClick sort: sortValues().value,
const exp = inputElement?.value; hideIntermediate: hideIntermediates()
})
if (exp) { getFetchResult(exp)
}
}
setSearchParams({ function getFetchResult(exp: string | null): void {
exp, setFetchResult(null)
simplify: simplifyEnabled(),
hide: hideValues().value,
sort: sortValues().value,
hideIntermediate: hideIntermediates()
});
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 { // Focuses searchbar on load
setFetchResult(null); if (!isTouch()) {
inputElement?.focus()
}
})
if (exp && exp !== "") { const tableId = "truth-table"
exp = replaceOperators(exp); const filenameId = "excel-filename"
setError(null);
setIsLoaded(false);
fetch(`${ fetchUrls[useLocalhost() ? 0 : 1] }${ encodeURIComponent(exp) }? function _exportToExcel(): void {
simplify=${ simplifyEnabled() }&hide=${ hideValues().value }&sort=${ sortValues().value }&caseSensitive=false& const value = getElementById<HTMLInputElement>(filenameId)?.value
hideIntermediate=${ hideIntermediates() }`) exportToExcel({
.then(res => res.json()) name: value !== "" ? value : undefined,
.then(res => { tableId
if (res.status !== "OK" && !res.ok) { })
return setError({ title: "Input error", message: res.message }); }
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); fallback={<Icon path={eye} aria-label={"An eye"} class={"mx-1"} />}
}) keyed
.catch(err => setError({ title: "Fetch error", message: err.toString() })) />
.finally(() => setIsLoaded(true)); }
} children={
} <For each={hideOptions}>
{(option) => (
onMount((): void => { <SingleMenuItem
onClick={() => setHideValues(option)}
if (searchParams.exp) { option={option}
const exp = searchParams.exp; currentValue={hideValues}
if (exp && inputElement) { />
inputElement.value = exp; )}
} </For>
const hide = searchParams.hide; }
if (hide) { itemsClassName={"right-0"}
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>
</div> </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 { interface SingleMenuItem {
option: Option, option: Option
currentValue?: Accessor<Option>, currentValue?: Accessor<Option>
onClick: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>, onClick: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>
} }
const SingleMenuItem: Component<SingleMenuItem> = ( const SingleMenuItem: Component<SingleMenuItem> = ({ option, currentValue, onClick }) => {
{ const isSelected = () => currentValue?.().value === option.value
option, return (
currentValue, <button class={`flex-row-center cursor-pointer last:mb-1 hover:underline`} onClick={onClick}>
onClick <Icon
}) => { path={check}
const isSelected = () => currentValue?.().value === option.value; aria-label={isSelected() ? "A checkmark" : "Nothing"}
return ( class={`text-white ${!isSelected() && "invisible"}`}
<button class={ `hover:underline cursor-pointer last:mb-1 flex-row-center` } />
onClick={ onClick }> {option.name}
<Icon path={ check } aria-label={ isSelected() ? "A checkmark" : "Nothing" } </button>
class={ `text-white ${ !isSelected() && "invisible" }` } /> )
{ option.name }
</button>
);
} }
const ErrorBox: Component<{ title: string, error: string }> = ({ title, error }) => ( const ErrorBox: Component<{ title: string; error: string }> = ({ title, error }) => (
<InfoBox className={ "w-fit text-center mx-auto" } <InfoBox className={"mx-auto w-fit text-center"} title={title} error={true}>
title={ title } <p>{error}</p>
error={ true }> </InfoBox>
<p>{ error }</p> )
</InfoBox>
);
interface ShowMeHowProps { interface ShowMeHowProps {
fetchResult: Accessor<FetchResult | null>, fetchResult: Accessor<FetchResult | null>
} }
const ShowMeHow: Component<ShowMeHowProps> = ({ fetchResult }) => ( const ShowMeHow: Component<ShowMeHowProps> = ({ fetchResult }) => (
<MyDisclosureContainer> <MyDisclosureContainer>
<MyDisclosure title={ "Show me how it's done" }> <MyDisclosure title={"Show me how it's done"}>
<table class={ "table" }> <table class={"table"}>
<tbody> <tbody>
<For each={fetchResult()?.orderOperations}>{orderOperationRow()}</For>
<For each={ fetchResult()?.orderOperations }> </tbody>
{ orderOperationRow() } </table>
</For> </MyDisclosure>
</MyDisclosureContainer>
</tbody> )
</table>
</MyDisclosure>
</MyDisclosureContainer>
);
const HowTo: Component = () => ( 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" }> <KeywordsDisclosure />
<p>Fill in a truth expression and it will be simplified for you as much as possible. </MyDisclosureContainer>
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 orderOperationRow = () => (operation: OrderOfOperation, index: Accessor<number>) => ( const orderOperationRow = () => (operation: OrderOfOperation, index: Accessor<number>) => (
<tr class={ "border-b border-dotted border-gray-500" }> <tr class={"border-b border-dotted border-gray-500"}>
<td>{ index() + 1 }:</td> <td>{index() + 1}:</td>
<td class={ "px-2" }>{ <td class={"px-2"}>
{
<For each={diffChars(operation.before, operation.after)}>
{(part) => (
<span class={`${part.added && "bg-green-700"} ${part.removed && "bg-red-700"}`}>
{part.value}
</span>
)}
</For>
}
<For each={ diffChars(operation.before, operation.after) }> <Show when={typeof window !== "undefined" && window.outerWidth <= 640} keyed>
{ (part) => ( <p>
<span class={ `${ part.added && "bg-green-700" } ${ part.removed && "bg-red-700" }` }> {"using"}: {operation.law}
{ part.value } </p>
</span> </Show>
) } </td>
</For> } <Show when={typeof window !== "undefined" && window.outerWidth > 640} keyed>
<td>
<Show when={ typeof window !== "undefined" && window.outerWidth <= 640 } keyed> {"using"}: {operation.law}
<p>{ "using" }: { operation.law }</p> </td>
</Show> </Show>
</tr>
</td> )
<Show when={ typeof window !== "undefined" && window.outerWidth > 640 } keyed>
<td>{ "using" }: { operation.law }</td>
</Show>
</tr>
);
const KeywordsDisclosure: Component = () => ( const KeywordsDisclosure: Component = () => (
<MyDisclosure title={ "Keywords" }> <MyDisclosure title={"Keywords"}>
<table> <table>
<thead> <thead>
<tr class={ "text-left" }> <tr class={"text-left"}>
<th>Name</th> <th>Name</th>
<th class={ "pr-2" }>API</th> <th class={"pr-2"}>API</th>
<th>Other</th> <th>Other</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td>Not:</td> <td>Not:</td>
<td>!</td> <td>!</td>
<td>NOT</td> <td>NOT</td>
</tr> </tr>
<tr> <tr>
<td>And:</td> <td>And:</td>
<td>&</td> <td>&</td>
<td>AND</td> <td>AND</td>
</tr> </tr>
<tr> <tr>
<td>Or:</td> <td>Or:</td>
<td>|</td> <td>|</td>
<td>/</td> <td>/</td>
<td>OR</td> <td>OR</td>
</tr> </tr>
<tr> <tr>
<td class={ "pr-2" }>Implication:</td> <td class={"pr-2"}>Implication:</td>
<td>{ "->" }</td> <td>{"->"}</td>
<td class={ "px-2" }>IMPLICATION</td> <td class={"px-2"}>IMPLICATION</td>
<td>IMP</td> <td>IMP</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</MyDisclosure> </MyDisclosure>
); )

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

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

View File

@ -5,5 +5,5 @@
* @returns The element with the given id, or null if it doesn't exist * @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 { 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 * 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 * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
export function exportToExcel( export function exportToExcel({
{ type = "xlsx",
type = "xlsx", name = "Truth Table",
name = "Truth Table", dl = false,
dl = false, tableId
tableId, }: {
}: { type?: BookType, name?: string, dl?: boolean, tableId: string }): any { type?: BookType
name?: string
const element = document.getElementById(tableId); dl?: boolean
const wb = utils.table_to_book(element, { sheet: "sheet1" }); tableId: string
return dl ? }): any {
write(wb, { bookType: type, bookSST: true, type: 'base64' }) : const element = document.getElementById(tableId)
writeFile(wb, name + "." + type); 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 * @returns The expression with the replaced operators
*/ */
export function replaceOperators(expression: string): string { export function replaceOperators(expression: string): string {
return expression return expression
.replaceAll(/\//g, "|") .replaceAll(/\//g, "|")
.replaceAll(/¬/g, "!") .replaceAll(/¬/g, "!")
.replaceAll(/\sOR\s/gi, " | ") .replaceAll(/\sOR\s/gi, " | ")
.replaceAll(/\sAND\s/gi, " & ") .replaceAll(/\sAND\s/gi, " & ")
.replaceAll(/\s(IMPLICATION|IMP)\s/gi, " -> ") .replaceAll(/\s(IMPLICATION|IMP)\s/gi, " -> ")
.replaceAll(/\sNOT\s/gi, " !"); .replaceAll(/\sNOT\s/gi, " !")
} }

View File

@ -1,23 +1,24 @@
export function failureFunction(P: String, m = P.length): number[] { export function failureFunction(P: String, m = P.length): number[] {
// No proper prefix for string of length 1: // No proper prefix for string of length 1:
const arr = [0] const arr = [0]
let i = 0, j = 1 let i = 0,
j = 1
while (j < m) { while (j < m) {
if (P[i] == P[j]) { if (P[i] == P[j]) {
i++ i++
arr.push(i) arr.push(i)
j++; 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]
}
} }
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 * @returns {boolean} True if the device is touch enabled, otherwise false
*/ */
export function isTouch(): boolean { 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} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
content: [ content: ["./index.html", "./404.html", "./src/**/*.{js,ts,jsx,tsx,css,md,mdx,html,json,scss}"],
'./index.html', "./404.html", darkMode: "class",
'./src/**/*.{js,ts,jsx,tsx,css,md,mdx,html,json,scss}', theme: {
], extend: {
darkMode: 'class', colors: {
theme: { "default-bg": "#181a1b"
extend: { }
colors: { }
"default-bg": "#181a1b", },
} plugins: []
}, }
},
plugins: [],
};

View File

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