Compare commits

...

No commits in common. "master" and "build" have entirely different histories.

57 changed files with 133 additions and 4257 deletions

View File

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

View File

@ -1,3 +0,0 @@
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

@ -1,45 +0,0 @@
name: Deploy
on:
pull_request:
branches:
- master
push:
branches:
- master
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Install dependencies
run: echo y | npm exec -- pnpm install
- name: Build project
run: npm exec -- pnpm build
- name: Upload production-ready build files
uses: actions/upload-artifact@v3
with:
name: dist
path: ./dist
deploy:
name: Deploy
needs: build
runs-on: host
steps:
- name: Download artifact
uses: actions/download-artifact@v3
with:
name: dist
path: ./dist
# Deploy to local repo
- name: Move files to server
run: |
rm -rf /var/www/martials.no/*
cp -r dist/* /var/www/martials.no

2
.gitignore vendored
View File

@ -1,2 +0,0 @@
node_modules
dist

5
.idea/.gitignore generated vendored
View File

@ -1,5 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

View File

@ -1,89 +0,0 @@
<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>

View File

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

View File

@ -1,12 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="GrazieInspection" enabled="false" level="TYPO" enabled_by_default="false" />
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

View File

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

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

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="latest" level="application" />
</component>
</module>

8
.idea/modules.xml generated
View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/martials.no.iml" filepath="$PROJECT_DIR$/.idea/martials.no.iml" />
</modules>
</component>
</project>

7
.idea/prettier.xml generated
View File

@ -1,7 +0,0 @@
<?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>

View File

@ -1,12 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="build" type="js.build_tools.npm" nameIsGenerated="true">
<package-json value="$PROJECT_DIR$/package.json" />
<command value="run" />
<scripts>
<script value="build" />
</scripts>
<node-interpreter value="project" />
<envs />
<method v="2" />
</configuration>
</component>

View File

@ -1,12 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="dev" type="js.build_tools.npm" nameIsGenerated="true">
<package-json value="$PROJECT_DIR$/package.json" />
<command value="run" />
<scripts>
<script value="dev" />
</scripts>
<node-interpreter value="project" />
<envs />
<method v="2" />
</configuration>
</component>

View File

@ -1,12 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="serve" type="js.build_tools.npm" nameIsGenerated="true">
<package-json value="$PROJECT_DIR$/package.json" />
<command value="run" />
<scripts>
<script value="serve" />
</scripts>
<node-interpreter value="project" />
<envs />
<method v="2" />
</configuration>
</component>

6
.idea/vcs.xml generated
View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

14
.idea/webResources.xml generated
View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="WebResourcesPaths">
<contentEntries>
<entry url="file://$PROJECT_DIR$">
<entryData>
<resourceRoots>
<path value="file://$PROJECT_DIR$/resources" />
</resourceRoots>
</entryData>
</entry>
</contentEntries>
</component>
</project>

View File

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

View File

@ -1,34 +0,0 @@
## Usage
Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`.
This is the reason you see a `pnpm-lock.yaml`. That being said, any package manager will work. This file can be safely be removed once you clone a template.
```bash
$ npm install # or pnpm install or yarn install
```
### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs)
## Available Scripts
In the project directory, you can run:
### `npm dev` or `npm start`
Runs the app in the development mode.<br>
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br>
### `npm run build`
Builds the app for production to the `dist` folder.<br>
It correctly bundles Solid in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br>
Your app is ready to be deployed!
## Deployment
You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.)

View File

Before

Width:  |  Height:  |  Size: 307 B

After

Width:  |  Height:  |  Size: 307 B

129
assets/main-7gPkv783.js Normal file

File diff suppressed because one or more lines are too long

1
assets/main-CQ3K2vor.css Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,21 +0,0 @@
#!/bin/sh
if [ ! -e dist ]; then
echo "dist directory not found, running 'vite bulid'"
vite build
fi
# Copying the file ($1) to the dist directory, or to subdirectory ($2)
copy() {
if [ -e "$1" ]; then
cp "$1" dist/"$2"
else
echo "'$1' not found, skipping"
fi
}
mkdir -p "dist/.well-known"
copy robots.txt
copy security.txt .well-known
copy .htaccess

View File

@ -6,9 +6,11 @@
<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" />
<link rel="icon" type="image/x-icon" href="/assets/code-nzJlNpcV.svg" />
<!-- 100% privacy-first analytics -->
<script data-collect-dnt="true" async defer src="https://scripts.simpleanalyticscdn.com/latest.js"></script>
<script type="module" crossorigin src="/assets/main-7gPkv783.js"></script>
<link rel="stylesheet" crossorigin href="/assets/main-CQ3K2vor.css">
</head>
<body>
<noscript>
@ -17,6 +19,5 @@
</noscript>
<div id="root"></div>
<script src="/src/app.tsx" type="module"></script>
</body>
</html>

View File

@ -1,32 +0,0 @@
{
"name": "martials-no",
"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",
"format": "prettier --write ."
},
"license": "MIT",
"devDependencies": {
"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.12.4",
"@types/diff": "^5.0.9",
"diff": "^5.2.0",
"solid-headless": "^0.13.1",
"solid-heroicons": "^3.2.4",
"solid-js": "^1.8.15",
"xlsx": "^0.18.5"
}
}

2381
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -1,18 +0,0 @@
/* @refresh reload */
import { type Component } from "solid-js"
import { Link } from "./link"
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

View File

@ -1,100 +0,0 @@
/* @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"
interface MyDialog extends TitleProps {
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 [isOpen, setIsOpen] = createSignal(false)
function callbackAndClose(): void {
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()
}
}
if (isOpen()) {
const id = "cl-6"
const el = getElementById(id)
el?.addEventListener("keypress", click)
return () => {
el?.removeEventListener("keypress", click)
isMounted = false
}
} else return () => undefined
}
createEffect(setupKeyPress, isOpen())
return (
<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={`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={"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={"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,13 +0,0 @@
/* @refresh reload */
import { type Component } from "solid-js"
import { Link } from "./link"
const Footer: Component<SimpleProps> = ({ className }) => (
<footer class={`container absolute bottom-0 py-5 text-center ${className}`}>
<p>
Kildekode <Link to={"https://git.martials.no/martials/old.martials.no"}>Gitea</Link>
</p>
</footer>
)
export default Footer

View File

@ -1,29 +0,0 @@
/* @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 { useLocation } from "@solidjs/router"
const Header: Component<TitleProps> = ({ className, title = "Title goes here" }) => {
const location = useLocation()
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>
</header>
)
}
export default Header

View File

@ -1,181 +0,0 @@
/* @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"
function setupEventListener(id: string, setIsHover: Setter<boolean>): () => void {
let isMounted = true
function hover(hover: boolean): void {
if (isMounted) {
setIsHover(hover)
}
}
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
}
}
/**
* Sets isText to 'true' or 'false' using the setIsText function.
* 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 !== "")
}
}
}
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,
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)
onMount(() => {
if (id && title) {
setupEventListener(id, setIsHover)
}
})
return (
<Row className={`relative ${className}`}>
{leading}
<HoverTitle title={title} isActive={isFocused() || isHover() || isText()} htmlFor={id} />
<input
class={`border-2 border-gray-500 bg-default-bg outline-none hover:border-t-cyan-400
focus:border-cyan-500 ${inputClass}`}
id={id}
ref={ref}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
name={name ?? undefined}
type={type}
placeholder={placeholder ?? undefined}
required={required}
onInput={() => {
setSetIsText(id, isText(), setIsText)
if (onChange) {
onChange()
}
}}
/>
{trailing}
</Row>
)
}
const HoverTitle: Component<{ title?: string; isActive?: boolean; htmlFor?: string }> = ({
title,
isActive = false,
htmlFor
}) => (
<label
class={`pointer-events-none absolute
${isActive ? "default-bg -top-2 left-3 text-sm" : "left-2 top-1"}
text-gray-600 transition-all duration-150 dark:text-gray-400`}
for={htmlFor}
>
<div class={"relative z-50"}>{title}</div>
<div class={"default-bg absolute bottom-1/3 z-10 h-2 w-full"} />
</label>
)
interface SearchProps extends InputProps {
typingDefault?: boolean
}
export const Search: Component<SearchProps> = ({
typingDefault = false,
id = "search",
className,
ref
}) => {
const [typing, setTyping] = createSignal(typingDefault)
function getInputElement() {
return getElementById<HTMLInputElement>(id)
}
function clearSearch(): void {
const el = getInputElement()
if (el) {
el.value = ""
setTyping(false)
history.replaceState(null, "", location.pathname)
el.focus()
}
}
function onChange(): void {
const el = getInputElement()
if (el && (el.value !== "") !== typing()) {
setTyping(el.value !== "")
}
}
return (
<Input
inputClass={`rounded-xl pl-7 h-10 w-full pr-8`}
className={`w-full ${className}`}
id={id}
ref={ref}
placeholder={"¬A & B -> C"}
type={"text"}
onChange={onChange}
leading={
<Icon path={magnifyingGlass} aria-label={"Magnifying glass"} class={"absolute pl-2"} />
}
trailing={
<Show when={typing()} keyed>
<button class={"absolute right-2"} title={"Clear"} type={"reset"} onClick={clearSearch}>
<Icon path={xMark} aria-label={"The letter X"} />
</button>
</Show>
}
/>
)
}

View File

@ -1,18 +0,0 @@
/* @refresh reload */
import { type Component } from "solid-js"
import Header from "./header"
import Footer from "./footer"
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

View File

@ -1,17 +0,0 @@
/* @refresh reload */
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>
)

View File

@ -1,72 +0,0 @@
/* @refresh reload */
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
}
const MyMenu: Component<MenuProps> = ({
title,
button,
children,
id,
className,
buttonClassName,
itemsClassName
}) => {
const [isOpen, setIsOpen] = createSignal(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 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 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

View File

@ -1,65 +0,0 @@
/* @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
}
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>
}
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={`border-rounded mb-2 flex flex-col gap-1
bg-cyan-900 p-2 dark:border-gray-800 ${className}`}
>
{children}
</div>
)

View File

@ -1,14 +0,0 @@
/* @refresh reload */
import { type Component } from "solid-js"
/**
* 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

View File

@ -1,54 +0,0 @@
/* @refresh reload */
import { For } from "solid-js/web"
import { type Component } from "solid-js"
interface TruthTableProps extends SimpleProps {
table?: Table
header?: string[]
}
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>
</tr>
)}
</For>
</tbody>
</table>
)
export default TruthTable

View File

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

View File

@ -1,20 +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 = () => (
<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

@ -1,59 +0,0 @@
/* @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

View File

@ -1,57 +0,0 @@
/* @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

View File

@ -1,447 +0,0 @@
/* @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: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
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 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,
hideIntermediate: hideIntermediates()
})
getFetchResult(exp)
}
}
function getFetchResult(exp: string | null): void {
setFetchResult(null)
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)
}
// 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()?.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
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()?.orderOperations}>{orderOperationRow()}</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"}>here</Link>.
</p>
</MyDisclosure>
<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"}>
{
<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>
)

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

@ -1,11 +0,0 @@
/// <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
}

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

@ -1,70 +0,0 @@
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 Expression = {
leading: string
left: Expression | null
operator: Operator | null
right: Expression | null
trailing: string
atomic: string | null
}
type Operator = "AND" | "OR" | "NOT" | "IMPLICATION"
type Table = boolean[][]
type OrderOfOperation = {
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
}

View File

@ -1,9 +0,0 @@
/**
* 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,46 +0,0 @@
import { type BookType, utils, write, writeFile } from "xlsx"
/**
* Exports the generated truth table to an excel (.xlsx) file
*
* @param type The downloaded files extension. Default is "xlsx"
* @param name The name of the file, excluding the extension. Default is "Truth Table"
* @param dl
* @param tableId The id of the table to export
* @returns {any}
* @author SheetJS
* @link https://cdn.sheetjs.com/
* @license Apache 2.0 License
* SheetJS Community Edition -- https://sheetjs.com/
*
* Copyright (C) 2012-present SheetJS LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* 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)
}

View File

@ -1,14 +0,0 @@
/**
* 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(/\sOR\s/gi, " | ")
.replaceAll(/\sAND\s/gi, " & ")
.replaceAll(/\s(IMPLICATION|IMP)\s/gi, " -> ")
.replaceAll(/\sNOT\s/gi, " !")
}

View File

@ -1,24 +0,0 @@
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

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

View File

@ -1,13 +0,0 @@
/** @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: []
}

View File

@ -1,15 +0,0 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"types": ["vite/client"],
"noEmit": true,
"isolatedModules": true,
"strict": true
}
}

View File

@ -1,17 +0,0 @@
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"
}
}
}
})