30 Commits

Author SHA1 Message Date
d64e2497a8 Updated deps.
Fixed type errors.
2024-07-18 16:30:03 +02:00
2376eadea9 Changed operators before sending to backend 2024-07-04 13:29:28 +02:00
fea9b8324d Updated name of law 2024-06-27 19:23:47 +02:00
ed1fc8ef59 IgnoreCase and changed imply symbol 2024-06-21 18:06:54 +02:00
b75cbba462 Merge remote-tracking branch 'origin/master' into simplify-truths-v2 2024-06-21 17:54:17 +02:00
06ce4eb784 SimpleAnalytics 2024-06-19 13:28:33 +02:00
2c92ef38df Fixed hideIntermediateSteps 2024-06-17 23:12:44 +02:00
5043a7b4bd Updated types and adapted to new API 2024-06-17 00:47:23 +02:00
e834476526 Updated types and adapted to new API 2024-06-13 18:44:02 +02:00
0528645838 Updated dependencies.
Added prettier and formatted.
Fixed bug causing back button to not show at all times.
2024-02-25 00:18:02 +01:00
ecd9f50029 Implemented failure function algorithm with a simple interface 2023-09-10 19:46:41 +02:00
1ae2757573 Updated path to portfolio 2023-08-14 16:39:16 +02:00
7c77a11d44 Migrated to pnpm 2023-07-30 14:25:49 +02:00
10f07ac525 Migrated to pnpm 2023-07-30 13:58:27 +02:00
de8305e797 Refactor replaceOperators function to use chain methods 2023-07-24 22:21:43 +02:00
449673800d Fixed loading icon loading at mount 2023-07-24 20:55:49 +02:00
7656d35227 Changed the main entry file in Vite's rollupOptions from 'app.html' to 'index.html'. 2023-07-24 20:47:55 +02:00
bfcb860cfc Remove static HTML pages in favor of dynamic routing
Deleted static HTML files (404.html and simplify-truths.html) and implemented dynamic routing with '@solidjs/router'; website pages are now rendered dynamically via routing in app.tsx, which reduces unnecessary file redundancy. Moved the truth-table.tsx component to a new pages directory, adjusting the import statements accordingly. Updated vite.config.ts to point to the new app.html instead of the removed files. Finally, the esbuild packages were updated to their latest versions in package-lock.json.
2023-07-24 20:44:32 +02:00
58fb93fef0 Updated dependencies 2023-06-03 18:12:13 +02:00
ae1ec571f8 Removed .d imports 2023-05-15 23:26:23 +02:00
fb391a5808 Changed api path 2023-04-22 22:26:15 +02:00
f8ff336ec3 Updated : to | in keywords 2023-04-22 22:12:15 +02:00
122789c72a Updated : to | 2023-04-15 23:25:17 +02:00
d58e1a454c Fixed input not encoding on load 2023-04-15 23:06:01 +02:00
fbc6611137 The actual input will be stored in the url instead of the api call, moved some code to it's own component 2023-04-09 11:42:13 +02:00
7f6c405890 Set ts compiler to strict, refactored a bit 2023-04-08 19:24:10 +02:00
292da46769 Added version to fetch props. Renamed interfaces to a declaration file 2023-04-08 12:05:08 +02:00
3245769977 Added switch between fetchurls on DEV, updated dependencies 2023-04-07 15:57:35 +02:00
f6197fd142 Changed OR char, and added environmental variabels for urls 2023-04-06 23:40:51 +02:00
f13b74a94f Updated to next version of backend 2023-03-19 23:13:09 +01:00
51 changed files with 3472 additions and 3885 deletions

3
.env.development Normal file
View File

@ -0,0 +1,3 @@
VITE_FETCH_URL=http://localhost:8080/
VITE_FETCH_PATH=simplify/table/
VITE_FETCH_FULL=$VITE_FETCH_URL$VITE_FETCH_PATH

3
.env.production Normal file
View File

@ -0,0 +1,3 @@
VITE_FETCH_URL=https://api.martials.no/
VITE_FETCH_PATH=simplify-truths/simplify/table/
VITE_FETCH_FULL=$VITE_FETCH_URL$VITE_FETCH_PATH

View File

@ -15,16 +15,20 @@ jobs:
- name: Checkout repo
uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
with:
version: 8
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 18
- name: Install dependencies
uses: bahmutov/npm-install@v1
run: pnpm install
- name: Build project
run: npm run build
run: pnpm build
- name: Upload production-ready build files
uses: actions/upload-artifact@v3

View File

@ -1,4 +1,4 @@
ErrorDocument 404 /404.html
ErrorDocument 404 /index.html
<IfModule mod_headers.c>
Header add Access-Control-Allow-Origin 'https://api.martials.no/'

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

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

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

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

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<includedPredefinedLibrary name="Node.js Core" />
<file url="PROJECT" libraries="{latest}" />
</component>
</project>

1
.idea/martials.no.iml generated
View File

@ -8,5 +8,6 @@
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="latest" level="application" />
</component>
</module>

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

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

7
.prettierrc Normal file
View File

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

View File

@ -1,18 +0,0 @@
<!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>Siden ble ikke funnet | Martials.no</title>
<meta name="description" content="Hjemmeside for API og diverse">
<link rel="icon" type="image/x-icon" href="resources/code.svg">
<link rel="stylesheet" href="src/index.css">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="/src/404.tsx" type="module"></script>
</body>
</html>

View File

@ -1,17 +1,22 @@
<!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">
<meta name="description" content="Hjemmeside for API og diverse" />
<link rel="icon" type="image/x-icon" href="resources/code.svg" />
<!-- 100% privacy-first analytics -->
<script data-collect-dnt="true" async defer src="https://scripts.simpleanalyticscdn.com/latest.js"></script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<noscript>
You need to enable JavaScript to run this app.
<img src="https://queue.simpleanalyticscdn.com/noscript.gif?collect-dnt=true" alt="" referrerpolicy="no-referrer-when-downgrade" />
</noscript>
<div id="root"></div>
<script src="/src/index.tsx" type="module"></script>
<script src="/src/app.tsx" type="module"></script>
</body>
</html>

2558
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,25 +3,30 @@
"version": "1.0",
"description": "Landing page Martin Berg Alstad's APIs and other things",
"scripts": {
"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.21",
"tailwindcss": "^3.3.0",
"typescript": "^5.0.2",
"vite": "^4.2.1",
"vite-plugin-solid": "^2.6.1"
"autoprefixer": "^10.4.19",
"postcss": "^8.4.39",
"prettier": "3.3.3",
"prettier-plugin-tailwindcss": "^0.6.5",
"tailwindcss": "^3.4.6",
"typescript": "^5.5.3",
"vite": "^5.3.4",
"vite-plugin-solid": "^2.10.2"
},
"dependencies": {
"@types/diff": "^5.0.3",
"diff": "^5.1.0",
"@solidjs/router": "^0.14.1",
"@types/diff": "^5.2.1",
"diff": "^5.2.0",
"solid-headless": "^0.13.1",
"solid-heroicons": "^3.1.1",
"solid-js": "^1.6.16",
"solid-heroicons": "^3.2.4",
"solid-js": "^1.8.18",
"xlsx": "^0.18.5"
}
}

1927
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

6
pnpm-workspace.yaml Normal file
View File

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

View File

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

View File

@ -1,18 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#181a1b" />
<title>Simplify | Martials.no</title>
<meta name="description" content="Simplify truth values and generate truth tables">
<link rel="icon" type="image/x-icon" href="resources/code.svg">
<link rel="stylesheet" href="src/index.css">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="/src/truth-table.tsx" type="module"></script>
</body>
</html>

View File

@ -1,22 +0,0 @@
/* @refresh reload */
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 = () => {
return (
<Layout title={ "Siden ble ikke funnet" }>
<div class={ "text-center" }>
<h4>Error 404 - Siden ble ikke funnet</h4>
<p>
<Link to={ "/" } newTab={ false } className={ "mx-auto relative w-max" }>
tilbake til forsiden
</Link>
</p>
</div>
</Layout>
);
};
render(() => <PageNotFound />, document.getElementById("root") as HTMLElement);

18
src/app.tsx Normal file
View File

@ -0,0 +1,18 @@
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>
<Route path={"/"} component={HomePage} />
<Route path={"/simplify-truths"} component={TruthTablePage} />
<Route path={"/failure-function"} component={FailureFunctionPage} />
<Route path={"*"} component={PageNotFound} />
</Router>
),
document.getElementById("root") as HTMLElement
)

View File

@ -1,14 +1,12 @@
/* @refresh reload */
import { type Component, createSignal } from "solid-js";
import type { ButtonProps, TitleProps } from "../types/interfaces";
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> = (
{
export const MySwitch: Component<SwitchProps> = ({
defaultValue = false,
title,
onChange,
@ -16,46 +14,46 @@ export const MySwitch: Component<SwitchProps> = (
name,
id
}) => {
const [checked, setChecked] = createSignal(defaultValue);
const [checked, setChecked] = createSignal(defaultValue)
function handleChange() {
const newChecked = !checked();
setChecked(newChecked);
const newChecked = !checked()
setChecked(newChecked)
if (onChange) {
onChange(newChecked);
onChange(newChecked)
}
}
return (
<button id={ id }
<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 }` }>
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` } />
<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> = (
{
export const Button: Component<ButtonProps> = ({
className,
title,
children,
id,
onClick,
type = "button",
}
) => {
return (
<button title={ title } id={ id } type={ type }
class={ `border-rounded bg-cyan-900 px-2 cursor-pointer ${ className }` }
onClick={ onClick }>
type = "button"
}) => (
<button
title={title}
id={id}
type={type}
class={`border-rounded cursor-pointer bg-cyan-900 px-2 ${className}`}
onClick={onClick}
>
{children}
</button>
);
};
)

View File

@ -1,23 +1,18 @@
/* @refresh reload */
import { type Component } from "solid-js";
import type { CardProps } from "../types/interfaces";
import { Link } from "./link";
import { type Component } from "solid-js"
import { Link } from "./link"
const Card: Component<CardProps> = ({ children, className, title, to, newTab = false }) => {
return (
<>
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">
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={ "text-center w-fit mx-auto before:content-['↗']" }>{ title }</h3>
<h3 class={"mx-auto w-fit text-center before:content-['↗']"}>{title}</h3>
</Link>
{children}
</div>
</div>
)
</>
);
};
export default Card;
export default Card

View File

@ -1,23 +1,22 @@
/* @refresh reload */
import { Dialog, DialogDescription, DialogPanel, DialogTitle } from "solid-headless";
import type { TitleProps } from "../types/interfaces";
import { createEffect, createSignal, JSX } from "solid-js";
import { Button } from "./button";
import { Portal } from "solid-js/web";
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 | null,
acceptButtonId?: string,
cancelButtonName?: string | null,
callback?: () => void,
buttonClasses?: string,
buttonTitle?: string | null,
description?: string
button?: JSX.Element
acceptButtonName?: string
acceptButtonId?: string
cancelButtonName?: string
callback?: () => void
buttonClass?: string
buttonTitle?: string | null
}
export default function MyDialog(
{
const MyDialog: Component<MyDialog> = ({
title,
description,
button,
@ -26,76 +25,76 @@ export default function MyDialog(
children,
callback,
className,
buttonClasses,
buttonClass,
buttonTitle,
acceptButtonId,
}: MyDialog): JSX.Element {
const [isOpen, setIsOpen] = createSignal(false);
acceptButtonId
}) => {
const [isOpen, setIsOpen] = createSignal(false)
function callbackAndClose(): void {
if (callback) {
callback();
}
setIsOpen(false);
callback?.()
setIsOpen(false)
}
function setupKeyPress(): () => void {
let isMounted = true;
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") {
(document.getElementById(acceptButtonId ?? "") as HTMLButtonElement | null)?.click();
if (isMounted && e.key === "Enter" && acceptButtonId) {
getElementById<HTMLButtonElement>(acceptButtonId)?.click()
}
}
if (isOpen()) {
const id = "cl-6"
const el = document.getElementById(id);
el?.addEventListener("keypress", e => click(e));
const el = getElementById(id)
el?.addEventListener("keypress", click)
return () => {
el?.removeEventListener("keypress", e => click(e));
isMounted = false;
}
el?.removeEventListener("keypress", click)
isMounted = false
}
} else return () => undefined
}
createEffect(setupKeyPress, isOpen());
createEffect(setupKeyPress, isOpen())
return (
<div class={ "w-fit h-fit" }>
<button onClick={ () => setIsOpen(true) } class={ buttonClasses } title={ buttonTitle ?? undefined }>
<div class={"h-fit w-fit"}>
<button onClick={() => setIsOpen(true)} class={buttonClass} title={buttonTitle ?? undefined}>
{button}
</button>
<Portal>
<Dialog isOpen={ isOpen() } onClose={ () => setIsOpen(false) }
class={ `fixed inset-0 flex-row-center justify-center z-50 overflow-auto text-white ${ className }` }>
<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} />
<DialogPanel class={ "w-fit relative bg-default-bg border-rounded border-gray-500 p-2" }>
<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>
{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>
<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

View File

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

View File

@ -1,16 +1,17 @@
/* @refresh reload */
import { type Component, Show } from "solid-js";
import type { TitleProps } from "../types/interfaces";
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" }) => {
const location = useLocation()
const Header: Component<TitleProps> = ({ className, title }) => {
return (
<header class={className}>
<div class={"flex-row-center mx-auto w-fit"}>
<Show when={ typeof location !== "undefined" && location.pathname !== "/" } keyed>
<Show when={location.pathname !== "/"} keyed>
<Link to={"/"} newTab={false} title={"Back to homepage"}>
<Icon path={chevronLeft} class={"text-cyan-500"} />
</Link>
@ -22,7 +23,7 @@ const Header: Component<TitleProps> = ({ className, title }) => {
<p>Av Martin Berg Alstad</p>
</div>
</header>
);
};
)
}
export default Header;
export default Header

View File

@ -1,25 +1,27 @@
/* @refresh reload */
import { type Component, createSignal, JSX, Setter } from "solid-js";
import type { InputProps } from "../types/interfaces";
import Row from "./row";
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);
setIsHover(hover)
}
}
const el = document.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;
el?.removeEventListener("pointerenter", () => hover(true))
el?.removeEventListener("pointerleave", () => hover(false))
isMounted = false
}
}
@ -29,21 +31,22 @@ function setupEventListener(id: string, setIsHover: Setter<boolean>): () => void
*/
function setSetIsText(id: string | undefined, isText: boolean, setIsText: Setter<boolean>): void {
if (id) {
const el = document.getElementById(id) as HTMLInputElement | HTMLTextAreaElement | null;
if (el && el.value !== "" !== isText) {
setIsText(el.value !== "");
const el = getElementById<HTMLInputElement | HTMLTextAreaElement>(id)
if (el && (el.value !== "") !== isText) {
setIsText(el.value !== "")
}
}
}
interface Input<T> extends InputProps<T> {
leading?: JSX.Element,
trailing?: JSX.Element,
onChange?: () => void,
inputClass?: string,
interface Input<T extends HTMLElement> extends InputProps<T> {
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,
@ -55,36 +58,38 @@ export const Input: Component<Input<HTMLInputElement>> = (
onChange,
leading,
trailing,
inputClass
}): JSX.Element => {
inputClass,
ref
}
): JSX.Element => {
/**
* Is 'true' if the input element is in focus
*/
const [isFocused, setIsFocused] = createSignal(false);
const [isFocused, setIsFocused] = createSignal(false)
/**
* Is 'true' if the user is hovering over the input element
*/
const [isHover, setIsHover] = createSignal(false);
const [isHover, setIsHover] = createSignal(false)
/**
* Is 'true' if the input element contains any characters
*/
const [isText, setIsText] = createSignal(false);
const [isText, setIsText] = createSignal(false)
document.addEventListener("DOMContentLoaded", () => {
onMount(() => {
if (id && title) {
setupEventListener(id, setIsHover);
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 }` }
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}
@ -92,29 +97,85 @@ export const Input: Component<Input<HTMLInputElement>> = (
placeholder={placeholder ?? undefined}
required={required}
onInput={() => {
setSetIsText(id, isText(), setIsText);
setSetIsText(id, isText(), setIsText)
if (onChange) {
onChange();
onChange()
}
} } />
}}
/>
{trailing}
</Row>
);
)
}
function HoverTitle(
{
const HoverTitle: Component<{ title?: string; isActive?: boolean; htmlFor?: string }> = ({
title,
isActive = false,
htmlFor
}: { title?: string | null, isActive?: boolean, htmlFor?: string }): JSX.Element {
return (
<label class={ `absolute pointer-events-none
${ 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
class={`pointer-events-none absolute
${isActive ? "default-bg -top-2 left-3 text-sm" : "left-2 top-1"}
text-gray-600 transition-all duration-150 dark:text-gray-400`}
for={htmlFor}
>
<div class={"relative z-50"}>{title}</div>
<div class={"default-bg absolute bottom-1/3 z-10 h-2 w-full"} />
</label>
);
)
interface SearchProps extends InputProps {
typingDefault?: boolean
}
export const Search: Component<SearchProps> = ({
typingDefault = false,
id = "search",
className,
ref
}) => {
const [typing, setTyping] = createSignal(typingDefault)
function getInputElement() {
return getElementById<HTMLInputElement>(id)
}
function clearSearch(): void {
const el = getInputElement()
if (el) {
el.value = ""
setTyping(false)
history.replaceState(null, "", location.pathname)
el.focus()
}
}
function onChange(): void {
const el = getInputElement()
if (el && (el.value !== "") !== typing()) {
setTyping(el.value !== "")
}
}
return (
<Input
inputClass={`rounded-xl pl-7 h-10 w-full pr-8`}
className={`w-full ${className}`}
id={id}
ref={ref}
placeholder={"¬A & B -> C"}
type={"text"}
onChange={onChange}
leading={
<Icon path={magnifyingGlass} aria-label={"Magnifying glass"} class={"absolute pl-2"} />
}
trailing={
<Show when={typing()} keyed>
<button class={"absolute right-2"} title={"Clear"} type={"reset"} onClick={clearSearch}>
<Icon path={xMark} aria-label={"The letter X"} />
</button>
</Show>
}
/>
)
}

View File

@ -1,12 +1,10 @@
/* @refresh reload */
import { type Component } from "solid-js";
import type { TitleProps } from "../types/interfaces";
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 }) => {
return (
<div class={ `bg-default-bg text-white min-h-screen relative font-mono ${ className }` }>
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>
@ -15,7 +13,6 @@ const Layout: Component<TitleProps> = ({ children, title, className }) => {
</main>
</div>
</div>
);
};
)
export default Layout;
export default Layout

View File

@ -1,24 +1,17 @@
/* @refresh reload */
import { type Component } from "solid-js";
import type { LinkProps } from "../types/interfaces";
import { type Component } from "solid-js"
export const Link: Component<LinkProps> = (
{
to,
rel,
children,
className,
id,
newTab = true,
title,
}) => {
return (
<a href={ to } id={ id } title={ title }
{ 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 }` }>
class={`link ${className}`}
>
{children}
</a>
);
};
)

View File

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

View File

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

View File

@ -1,9 +1,14 @@
/* @refresh reload */
import { type Component } from "solid-js";
import type { ChildProps } from "../types/interfaces";
import { type Component } from "solid-js"
const Row: Component<ChildProps> = ({ children, className }) => {
return <div class={ `flex-row-center ${ className }` }>{ children }</div>
}
/**
* A row that centers its children
* @param children The children of the row
* @param className The class name of the row
* @returns The row
*/
const Row: Component<ChildProps> = ({ children, className }) => (
<div class={`flex-row-center ${className}`}>{children}</div>
)
export default Row;
export default Row

View File

@ -1,34 +1,29 @@
/* @refresh reload */
import type { SimpleProps } from "../types/interfaces";
import type { Table } from "../types/interfaces";
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?: TruthMatrix
header?: string[]
}
const TruthTable: Component<TruthTableProps> = (
{
table,
header,
className,
style,
id,
}) => {
return (
<table class={ `border-2 border-gray-500 border-collapse table z-10 ${ className }` } id={ id }
style={ style }>
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={ `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
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>
@ -36,22 +31,22 @@ const TruthTable: Component<TruthTableProps> = (
</thead>
<tbody>
<For each={table}>
{ (row) =>
{(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" }` }>
{(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>
</tr>
}
)}
</For>
</tbody>
</table>
);
}
)
export default TruthTable;
export default TruthTable

View File

@ -1,27 +0,0 @@
/* @refresh reload */
import type { FetchResultsProps, WebWorkerProps } from '../types/interfaces';
onmessage = async (e: MessageEvent<WebWorkerProps>) => {
console.log("Worker: Message received from main script")
const {
expression,
simplifyEnabled,
hideValue,
sortValue,
hideIntermediates
} = e.data;
const result: FetchResultsProps = {
fetchResult: null,
error: null,
};
await fetch(`https://api.martials.no/simplify-truths/do/simplify/table?exp=${ encodeURIComponent(expression) }&
simplify=${ simplifyEnabled }&hide=${ hideValue }&sort=${ sortValue }&caseSensitive=false&
hideIntermediate=${ hideIntermediates }`)
.then(res => res.json())
.then(res => result.fetchResult = res)
.catch(err => result.error = err.toString());
postMessage(result);
};

View File

@ -3,10 +3,9 @@
@tailwind utilities;
@layer components {
.debug {
@apply border border-red-500;
@apply after:content-['DEBUG'] after:absolute;
@apply after:absolute after:content-['DEBUG'];
}
.flex-row-center {
@ -14,7 +13,7 @@
}
.border-rounded {
@apply border rounded-2xl border-gray-700;
@apply rounded-2xl border border-gray-700;
}
h1 {
@ -34,15 +33,14 @@
}
a {
@apply hover:underline text-cyan-500;
@apply text-cyan-500 hover:underline;
}
li {
@apply list-disc ml-4;
@apply ml-4 list-disc;
}
svg {
@apply pointer-events-none h-6 w-6;
}
}

View File

@ -1,54 +0,0 @@
/* @refresh reload */
import { For, render } from "solid-js/web";
import "./index.css";
import { type Component } from "solid-js";
import Layout from "./components/layout";
import Card from "./components/card";
import type { CardProps } from "./types/interfaces";
import { Link } from "./components/link";
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,
},
{
title: "Hjemmeside",
children: <p>Sjekk ut mine andre prosjekter</p>,
to: "https://h600878.github.io/",
},
{
title: "Forenkle sannhetsverdier",
children: <p>Implementering av API</p>,
to: `/simplify-truths.html`,
}
] satisfies CardProps[];
const HomePage: Component = () => {
return (
<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 }>{ card.children }</Card>
}
</For>
</div>
</Layout>
);
};
render(() => <HomePage />, document.getElementById("root") as HTMLElement);

20
src/pages/404.tsx Normal file
View File

@ -0,0 +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"
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={"relative mx-auto w-max"}>
tilbake til forsiden
</Link>
</p>
</div>
</Layout>
)
export default PageNotFound

View File

@ -0,0 +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"
type FFProps = { char: string; index: number }
const FailureFunctionPage: Component = () => {
let inputRef: HTMLInputElement | undefined = undefined
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)
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>
)
}
export default FailureFunctionPage

57
src/pages/home.tsx Normal file
View File

@ -0,0 +1,57 @@
/* @refresh reload */
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"
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[]
const HomePage: Component = () => (
<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

462
src/pages/truth-table.tsx Normal file
View File

@ -0,0 +1,462 @@
/* @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"
type Option = {
name: string
value: "NONE" | "TRUE" | "FALSE" | "DEFAULT" | "TRUE_FIRST" | "FALSE_FIRST"
}
const fetchUrls = [
"http://localhost:8000/simplify/table/",
"https://api.martials.no/simplify-truths/v2/simplify/table/"
]
// TODO move some code to new components
// TODO option to ignore case
// TODO more user friendly options
const TruthTablePage: Component = () => {
const [searchParams, setSearchParams] = useSearchParams()
let inputElement!: HTMLInputElement
let simplifyDefault = searchParams.simplify === undefined || searchParams.simplify === "true",
inputContent = !!searchParams.exp,
hideIntermediate = searchParams.hideIntermediateSteps === "true"
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 [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 [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
if (exp) {
setSearchParams({
exp,
simplify: simplifyEnabled(),
hide: hideValues().value,
sort: sortValues().value,
hideIntermediateSteps: hideIntermediates()
})
void getFetchResult(exp)
}
}
async function getFetchResult(exp: string | null): Promise<void> {
setFetchResult(null)
if (exp && exp !== "") {
exp = replaceOperators(exp)
setError(null)
setIsLoaded(false)
try {
const response =
await fetch(`${fetchUrls[useLocalhost() ? 0 : 1]}${encodeURIComponent(exp)}?
simplify=${simplifyEnabled()}&hide=${hideValues().value}&sort=${sortValues().value}&ignoreCase=false&
hideIntermediateSteps=${hideIntermediates()}`)
const body = await response.json()
if (!response.ok) {
setError({
title: "Input error",
message: body.message
})
} else {
const fetchResult: FetchResult = body
setFetchResult(fetchResult)
}
} catch (e: any) {
setError({
title: "Error",
message: e.message
})
} 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])
}
void 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={"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"}`}
/>
}
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={"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()?.operations?.length ?? 0) > 0} keyed>
<ShowMeHow fetchResult={fetchResult} />
</Show>
</div>
<Show when={isLoaded() && error() === null && fetchResult()?.truthTable} 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()!.truthTable!.header}
table={fetchResult()!.truthTable!.truthMatrix}
id={tableId}
/>
</div>
</div>
</Show>
</div>
</Layout>
)
}
export default TruthTablePage
interface SingleMenuItem {
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={`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={"mx-auto w-fit text-center"} title={title} error={true}>
<p>{error}</p>
</InfoBox>
)
interface ShowMeHowProps {
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()?.operations}>{operationRow()}</For>
</tbody>
</table>
</MyDisclosure>
</MyDisclosureContainer>
)
const HowTo: Component = () => (
<MyDisclosureContainer>
<MyDisclosure title={"How to"}>
<p>
Fill in a truth expression and it will be simplified for you as much as possible. It will
also genereate a truth table with all possible values. You can use a single letter, word or
multiple words without spacing for each atomic value. If you do not want to simplify the
expression, simply turn off the toggle. Keywords for operators are defined below.
Parentheses is also allowed.
</p>
<p>
API docs can be found{" "}
<Link to={"https://api.martials.no/simplify-truths/v2/openapi"}>here</Link>.
</p>
</MyDisclosure>
<KeywordsDisclosure />
</MyDisclosureContainer>
)
const operationRow = () => (operation: Operation, index: Accessor<number>) => (
<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>
}
<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>
)

View File

@ -1,476 +0,0 @@
/* @refresh reload */
import Layout from "./components/layout";
import { Input } 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 { FetchResultsProps, FetchResult, WebWorkerProps } from "./types/interfaces";
import { type Accessor, type Component, createSignal, JSX, onMount, Show } from "solid-js";
import { For, render } from "solid-js/web";
import Row from "./components/row";
import {
arrowDownTray, arrowPath,
check,
eye,
eyeSlash,
funnel,
magnifyingGlass,
xMark
} from "solid-heroicons/solid";
import { Button, MySwitch } from "./components/button";
import MyDialog from "./components/dialog";
import { exportToExcel } from "./functions/export";
import { Link } from "./components/link";
import { isTouch } from "./functions/touch";
type Option = { name: string, value: "NONE" | "TRUE" | "FALSE" | "DEFAULT" | "TRUE_FIRST" | "FALSE_FIRST" };
// TODO move some code to new components
const TruthTablePage: Component = () => {
let searchParams: URLSearchParams;
let simplifyDefault = true, inputContent = false, hideIntermediate = false;
if (typeof location !== "undefined") {
searchParams = new URLSearchParams(location.search);
if (searchParams.has("simplify")) {
simplifyDefault = searchParams.get("simplify") === "true";
}
if (searchParams.has("exp")) {
inputContent = true;
}
if (searchParams.has("hideIntermediate")) {
hideIntermediate = searchParams.get("hideIntermediate") === "true";
}
}
/**
* Stores the boolean value of the simplify toggle
*/
const [simplifyEnabled, setSimplifyEnabled] = createSignal(simplifyDefault);
/**
* The state element used to store the simplified string, "empty string" by default
*/
const [fetchResult, setFetchResult] = createSignal<FetchResult | null>(null);
/**
* If the searchbar is empty, this state is 'false', otherwise 'true'
*/
const [typing, setTyping] = createSignal(inputContent);
const hideOptions: Option[] = [
{ name: "Show all result", value: "NONE" },
{ name: "Hide true results", value: "TRUE" },
{ name: "Hide false results", value: "FALSE" },
];
const [hideValues, setHideValues] = createSignal(hideOptions[0]);
const sortOptions: Option[] = [
{ name: "Sort by default", value: "DEFAULT" },
{ name: "Sort by true first", value: "TRUE_FIRST" },
{ name: "Sort by false first", value: "FALSE_FIRST" },
];
const [sortValues, setSortValues] = createSignal(sortOptions[0]);
const [hideIntermediates, setHideIntermediates] = createSignal(hideIntermediate);
const [isLoaded, setIsLoaded] = createSignal<boolean | null>(null);
const [error, setError] = createSignal<string | null>(null);
/**
* 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: { preventDefault: () => void; }): void {
e.preventDefault(); // Stops the page from reloading onClick
let exp = getInputElement()?.value;
if (exp) {
exp = exp.replaceAll("|", "/").trimEnd();
history.pushState(null, "", `?exp=${ encodeURIComponent(exp) }&simplify=${ simplifyEnabled() }&
hide=${ hideValues().value }&sort=${ sortValues().value }&hideIntermediate=${ hideIntermediates() }`);
getFetchResult(exp);
}
}
function getFetchResult(exp: string): void {
setFetchResult(null);
if (exp !== "") {
setError(null);
setIsLoaded(false);
if (window.Worker) {
const worker = new Worker(new URL("./functions/fetch.ts", import.meta.url));
const input: WebWorkerProps = {
expression: exp,
simplifyEnabled: simplifyEnabled(),
hideValue: hideValues().value,
sortValue: sortValues().value,
hideIntermediates: hideIntermediates()
};
worker.postMessage(input);
worker.onmessage = (e: MessageEvent<FetchResultsProps>): void => {
const data = e.data;
setIsLoaded(true);
if (data.fetchResult) {
setFetchResult(data.fetchResult);
}
else if (data.error) {
setError(data.error);
}
worker.terminate();
};
}
else {
fetch(`https://api.martials.no/simplify-truths/do/simplify/table?exp=${ encodeURIComponent(exp) }&
simplify=${ simplifyEnabled() }&hide=${ hideValues().value }&sort=${ sortValues().value }&caseSensitive=false&
hideIntermediate=${ hideIntermediates() }`)
.then(res => res.json())
.then(res => setFetchResult(res))
.catch(err => setError(err.toString()))
.finally(() => setIsLoaded(true));
}
}
}
const inputId = "truth-input";
function getInputElement(): HTMLInputElement | null {
return document.getElementById(inputId) as HTMLInputElement | null;
}
function onTyping(): void {
const el = getInputElement();
if (el && (el.value !== "") !== typing()) {
setTyping(el.value !== "");
}
}
function clearSearch(): void {
const el = getInputElement();
if (el) {
el.value = "";
setTyping(false);
history.replaceState(null, "", location.pathname);
el.focus();
}
}
const tableId = "truth-table";
const filenameId = "excel-filename";
onMount((): void => {
if (searchParams.has("exp")) {
const exp = searchParams.get("exp");
if (exp !== "") {
getInputElement().value = exp;
}
const hide = searchParams.get("hide");
if (hide) {
setHideValues(hideOptions.find(o => o.value === hide) ?? hideOptions[0]);
}
const sort = searchParams.get("sort");
if (sort) {
setSortValues(sortOptions.find(o => o.value === sort) ?? sortOptions[0]);
}
getFetchResult(exp);
}
// Focuses searchbar on load
if (!isTouch()) {
getInputElement()?.focus();
}
});
function _exportToExcel(): void {
const value = (document.getElementById(filenameId) as HTMLInputElement | null)?.value;
exportToExcel({
name: value !== "" ? value : undefined, tableId
});
}
return (
<Layout title={ "Truth tables" }>
<div id={ "truth-content" }>
<div class={ "max-w-2xl mx-auto" }>
<MyDisclosureContainer>
<MyDisclosure title={ "How to" }>
<p>Fill in a truth expression and it will be simplified for you as much as possible.
It will also genereate a truth table with all possible values. You can use a single
letter,
word or multiple words without spacing for each atomic value.
If you do not want to simplify the expression, simply turn off the toggle.
Keywords for operators are defined below. Parentheses is also allowed.</p>
<p>API docs can be found <Link to={ "https://api.martials.no/simplify-truths" }>here</Link>.
</p>
</MyDisclosure>
<KeywordsDisclosure />
</MyDisclosureContainer>
<form class={ "flex-row-center" } onSubmit={ onClick } autocomplete={ "off" }>
<Input inputClass={ `rounded-xl pl-7 h-10 w-full pr-8` } className={ "w-full" }
id={ "truth-input" }
placeholder={ "¬A & B -> C" }
type={ "text" }
onChange={ onTyping }
leading={ <Icon path={ magnifyingGlass } aria-label={ "Magnifying glass" }
class={ "pl-2 absolute" } /> }
trailing={ <Show when={ typing() } keyed>
<button class={ "absolute right-2" }
title={ "Clear" }
type={ "reset" }
onClick={ clearSearch }>
<Icon path={ xMark } aria-label={ "The letter X" } />
</button>
</Show> }
/>
<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() } 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" }
buttonClasses={ `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={ isLoaded() === false } keyed>
<Icon path={ arrowPath } aria-label={ "Loading indicator" } class={ "animate-spin mx-auto" } />
</Show>
<Show when={ error() } keyed>
<ErrorBox title={ "Fetch error" } error={ error() } />
</Show>
<Show when={ error() === null && isLoaded() && fetchResult()?.status.code !== 200 } keyed>
<ErrorBox title={ "Input error" } error={ fetchResult()?.status.message } />
</Show>
<Show when={ simplifyEnabled() && fetchResult()?.orderOperations?.length > 0 } keyed>
<ShowMeHow fetchResult={ fetchResult } />
</Show>
</div>
<Show when={ isLoaded() && fetchResult()?.status?.code === 200 } 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 }
table={ fetchResult()?.table?.truthMatrix } id={ tableId } />
</div>
</div>
</Show>
</div>
</Layout>
);
}
export default TruthTablePage;
interface SingleMenuItem {
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 ErrorBox: Component<{ title: string, error: string }> = ({ title, error }) => {
return (
<InfoBox className={ "w-fit text-center mx-auto" }
title={ title }
error={ true }>
<p>{ error }</p>
</InfoBox>
)
}
interface ShowMeHowProps {
fetchResult: Accessor<FetchResult>,
}
const ShowMeHow: Component<ShowMeHowProps> = ({ fetchResult }) => {
return (
<MyDisclosureContainer>
<MyDisclosure title={ "Show me how it's done" }>
<table class={ "table" }>
<tbody>
<For each={ fetchResult()?.orderOperations }>{
(operation, index) => (
<tr class={ "border-b border-dotted border-gray-500" }>
<td>{ index() + 1 }:</td>
<td class={ "px-2" }>{
<For each={ 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>
) }
</For>
</tbody>
</table>
</MyDisclosure>
</MyDisclosureContainer>
)
}
const KeywordsDisclosure = () => {
return (
<MyDisclosure title={ "Keywords" }>
<table>
<tbody>
<tr>
<td>Not:</td>
<td>!</td>
</tr>
<tr>
<td>And:</td>
<td>&</td>
</tr>
<tr>
<td>Or:</td>
<td>|</td>
<td>/</td>
</tr>
<tr>
<td class={ "pr-2" }>Implication:</td>
<td>{ "->" }</td>
</tr>
</tbody>
</table>
</MyDisclosure>
);
};
render(() => <TruthTablePage />, document.getElementById("root") as HTMLElement);

11
src/types/env.d.ts vendored Normal file
View File

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

View File

@ -1,86 +0,0 @@
import { JSX } from "solid-js";
export interface SimpleProps {
name?: string;
className?: string,
style?: JSX.CSSProperties,
id?: string,
title?: string,
}
export interface ChildProps extends SimpleProps {
children?: JSX.Element,
}
export interface LinkProps extends ChildProps {
to?: string,
rel?: string,
newTab?: boolean,
}
export interface TitleProps extends ChildProps {
title?: string,
}
export interface ButtonProps extends TitleProps {
onClick?: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>,
type?: "button" | "submit" | "reset",
}
export interface InputProps<T> extends TitleProps {
onInput?: JSX.EventHandlerUnion<T, Event>,
placeholder?: string | null,
required?: boolean,
type?: string,
}
export interface CardProps extends LinkProps {
title?: string;
}
export type Expression = {
leading: string,
left: Expression | null,
operator: Operator | null,
right: Expression | null,
trailing: string,
atomic: string | null,
};
export type Operator = "AND" | "OR" | "NOT" | "IMPLICATION";
export type Table = boolean[][];
export type OrderOfOperations = {
before: string,
after: string,
law: string,
}[];
export type FetchResult = {
status: {
code: number,
message: string,
},
before: string,
after: string,
orderOperations: OrderOfOperations | null,
expression: Expression | null,
header: string[] | null,
table: {
truthMatrix: Table,
} | null,
};
export type WebWorkerProps = {
expression: string,
simplifyEnabled: boolean,
hideValue: string,
sortValue: string,
hideIntermediates: boolean
};
export type FetchResultsProps = {
fetchResult: FetchResult | null,
error: string | null,
}

87
src/types/types.d.ts vendored Normal file
View File

@ -0,0 +1,87 @@
interface SimpleProps<T extends HTMLElement = HTMLElement> {
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
}
interface LinkProps extends ChildProps {
to?: string
rel?: string
newTab?: boolean
}
interface TitleProps<T extends HTMLElement = HTMLElement> extends ChildProps<T> {
title?: string
}
interface ButtonProps extends TitleProps {
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
}
interface CardProps extends LinkProps {
title?: string
}
type AtomicExpression = {
atomic: string
}
type NotExpression = {
not: Expression
}
type BinaryExpression = {
left: Expression
operator: BinaryOperator
right: Expression
}
type Expression = AtomicExpression | NotExpression | BinaryExpression
type BinaryOperator = "AND" | "OR" | "IMPLICATION"
type Law =
| "ELIMINATION_OF_IMPLICATION"
| "DE_MORGANS_LAWS"
| "ABSORPTION_LAW"
| "ASSOCIATIVE_LAW"
| "DISTRIBUTIVE_LAW"
| "DOUBLE_NEGATION_ELIMINATION"
| "COMMUTATIVE_LAW"
type TruthMatrix = boolean[][]
type Operation = {
before: string
after: string
law: Law
}
type Table = {
header: string[]
truthMatrix: TruthMatrix
}
type FetchResult = {
version: string
before: string
after: string
operations: Operation[]
expression: Expression | null
truthTable?: Table | null
}

9
src/utils/dom.ts Normal file
View File

@ -0,0 +1,9 @@
/**
* Get an element by id
* @param id The id of the element
* @type T The type of the HTMLElement
* @returns The element with the given id, or null if it doesn't exist
*/
export function getElementById<T extends HTMLElement = HTMLElement>(id: string): T | null {
return <T>document.getElementById(id)
}

View File

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

View File

@ -0,0 +1,14 @@
/**
* Replaces the operators in the expression with the ones used by the backend
* @param expression The expression to replace the operators in
* @returns The expression with the replaced operators
*/
export function replaceOperators(expression: string): string {
return expression
.replaceAll(/\//g, "|")
.replaceAll(/¬/g, "!")
.replaceAll(/\s(OR|)\s/gi, " | ")
.replaceAll(/\s(AND|⋀)\s/gi, " & ")
.replaceAll(/\s(IMPLICATION|IMP|->)\s/gi, " => ")
.replaceAll(/\sNOT\s/gi, " !")
}

View File

@ -0,0 +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
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]
}
}
return arr
}

View File

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

View File

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

View File

@ -9,6 +9,7 @@
"jsxImportSource": "solid-js",
"types": ["vite/client"],
"noEmit": true,
"isolatedModules": true
"isolatedModules": true,
"strict": true
}
}

View File

@ -1,19 +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,
port: 3000
},
build: {
target: 'esnext',
target: "esnext",
rollupOptions: {
input: {
main: "index.html",
"404": "404.html",
simplifyTruths: "simplify-truths.html",
main: "index.html"
}
}
},
});
}
})