Updated dependencies.
Added prettier and formatted. Fixed bug causing back button to not show at all times.
This commit is contained in:
parent
ecd9f50029
commit
0528645838
89
.idea/codeStyles/Project.xml
generated
Normal file
89
.idea/codeStyles/Project.xml
generated
Normal 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
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal 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
7
.idea/prettier.xml
generated
Normal 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
7
.prettierrc
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-tailwindcss"]
|
||||
}
|
28
index.html
28
index.html
@ -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>
|
||||
|
25
package.json
25
package.json
@ -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
2160
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
packages:
|
||||
# include packages in subfolders (e.g. apps/ and packages/)
|
||||
- "apps/**"
|
||||
- 'packages/**'
|
||||
- "packages/**"
|
||||
# if required, exclude some directories
|
||||
- '!**/test/**'
|
||||
- "!**/test/**"
|
||||
|
@ -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: {}
|
||||
}
|
||||
}
|
||||
|
39
src/app.tsx
39
src/app.tsx
@ -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
|
||||
)
|
||||
|
@ -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>
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 på <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 på <Link to={"https://github.com/h600878/martials.no"}>GitHub</Link>
|
||||
</p>
|
||||
</footer>
|
||||
)
|
||||
|
||||
export default Footer;
|
||||
export default Footer
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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" }>
|
||||
Gå 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"}>
|
||||
Gå tilbake til forsiden
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
|
||||
export default PageNotFound;
|
||||
export default PageNotFound
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
10
src/types/env.d.ts
vendored
@ -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
84
src/types/types.d.ts
vendored
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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, " !")
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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: []
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user