Updated dependencies.
Added prettier and formatted. Fixed bug causing back button to not show at all times.
This commit is contained in:
parent
ecd9f50029
commit
0528645838
89
.idea/codeStyles/Project.xml
generated
Normal file
89
.idea/codeStyles/Project.xml
generated
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<HTMLCodeStyleSettings>
|
||||||
|
<option name="HTML_ALIGN_TEXT" value="true" />
|
||||||
|
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||||
|
<option name="HTML_DO_NOT_INDENT_CHILDREN_OF" value="" />
|
||||||
|
</HTMLCodeStyleSettings>
|
||||||
|
<JSCodeStyleSettings version="0">
|
||||||
|
<option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
|
||||||
|
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||||
|
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||||
|
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||||
|
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||||
|
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||||
|
<option name="SPACES_WITHIN_INTERPOLATION_EXPRESSIONS" value="true" />
|
||||||
|
</JSCodeStyleSettings>
|
||||||
|
<JetCodeStyleSettings>
|
||||||
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
|
</JetCodeStyleSettings>
|
||||||
|
<Objective-C>
|
||||||
|
<option name="KEEP_STRUCTURES_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="KEEP_CASE_EXPRESSIONS_IN_ONE_LINE" value="true" />
|
||||||
|
</Objective-C>
|
||||||
|
<TypeScriptCodeStyleSettings version="0">
|
||||||
|
<option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
|
||||||
|
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||||
|
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||||
|
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||||
|
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||||
|
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||||
|
<option name="SPACES_WITHIN_INTERPOLATION_EXPRESSIONS" value="true" />
|
||||||
|
</TypeScriptCodeStyleSettings>
|
||||||
|
<VueCodeStyleSettings>
|
||||||
|
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
|
||||||
|
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
|
||||||
|
</VueCodeStyleSettings>
|
||||||
|
<codeStyleSettings language="HTML">
|
||||||
|
<option name="SOFT_MARGINS" value="100" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="JAVA">
|
||||||
|
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||||
|
<option name="CATCH_ON_NEW_LINE" value="true" />
|
||||||
|
<option name="FINALLY_ON_NEW_LINE" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
|
||||||
|
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="JavaScript">
|
||||||
|
<option name="SOFT_MARGINS" value="100" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="ObjectiveC">
|
||||||
|
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||||
|
<option name="CATCH_ON_NEW_LINE" value="true" />
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="TypeScript">
|
||||||
|
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||||
|
<option name="CATCH_ON_NEW_LINE" value="true" />
|
||||||
|
<option name="FINALLY_ON_NEW_LINE" value="true" />
|
||||||
|
<option name="SOFT_MARGINS" value="100" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="Vue">
|
||||||
|
<option name="SOFT_MARGINS" value="100" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="kotlin">
|
||||||
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
|
</state>
|
||||||
|
</component>
|
7
.idea/prettier.xml
generated
Normal file
7
.idea/prettier.xml
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="PrettierConfiguration">
|
||||||
|
<option name="myConfigurationMode" value="AUTOMATIC" />
|
||||||
|
<option name="myRunOnSave" value="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
7
.prettierrc
Normal file
7
.prettierrc
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": false,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"printWidth": 100,
|
||||||
|
"plugins": ["prettier-plugin-tailwindcss"]
|
||||||
|
}
|
28
index.html
28
index.html
@ -1,17 +1,17 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="nb">
|
<html lang="nb">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#181a1b" />
|
<meta name="theme-color" content="#181a1b" />
|
||||||
<title>Hjem | Martials.no</title>
|
<title>Hjem | Martials.no</title>
|
||||||
<meta name="description" content="Hjemmeside for API og diverse">
|
<meta name="description" content="Hjemmeside for API og diverse" />
|
||||||
<link rel="icon" type="image/x-icon" href="resources/code.svg">
|
<link rel="icon" type="image/x-icon" href="resources/code.svg" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
||||||
<script src="/src/app.tsx" type="module"></script>
|
<script src="/src/app.tsx" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
25
package.json
25
package.json
@ -6,24 +6,27 @@
|
|||||||
"prestart": "npx only-allow pnpm",
|
"prestart": "npx only-allow pnpm",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build && sh build_extra.sh",
|
"build": "vite build && sh build_extra.sh",
|
||||||
"serve": "vite preview"
|
"serve": "vite preview",
|
||||||
|
"format": "prettier --write ."
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.17",
|
||||||
"postcss": "^8.4.27",
|
"postcss": "^8.4.35",
|
||||||
"tailwindcss": "^3.3.3",
|
"prettier": "3.2.5",
|
||||||
"typescript": "^5.1.6",
|
"prettier-plugin-tailwindcss": "^0.5.11",
|
||||||
"vite": "^4.4.7",
|
"tailwindcss": "^3.4.1",
|
||||||
"vite-plugin-solid": "^2.7.0"
|
"typescript": "^5.3.3",
|
||||||
|
"vite": "^5.1.4",
|
||||||
|
"vite-plugin-solid": "^2.10.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@solidjs/router": "^0.8.2",
|
"@solidjs/router": "^0.12.4",
|
||||||
"@types/diff": "^5.0.3",
|
"@types/diff": "^5.0.9",
|
||||||
"diff": "^5.1.0",
|
"diff": "^5.2.0",
|
||||||
"solid-headless": "^0.13.1",
|
"solid-headless": "^0.13.1",
|
||||||
"solid-heroicons": "^3.2.4",
|
"solid-heroicons": "^3.2.4",
|
||||||
"solid-js": "^1.7.8",
|
"solid-js": "^1.8.15",
|
||||||
"xlsx": "^0.18.5"
|
"xlsx": "^0.18.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2160
pnpm-lock.yaml
generated
2160
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
packages:
|
packages:
|
||||||
# include packages in subfolders (e.g. apps/ and packages/)
|
# include packages in subfolders (e.g. apps/ and packages/)
|
||||||
- "apps/**"
|
- "apps/**"
|
||||||
- 'packages/**'
|
- "packages/**"
|
||||||
# if required, exclude some directories
|
# if required, exclude some directories
|
||||||
- '!**/test/**'
|
- "!**/test/**"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
purge: ['./*.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
|
purge: ["./*.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {}
|
||||||
},
|
}
|
||||||
};
|
}
|
||||||
|
39
src/app.tsx
39
src/app.tsx
@ -1,25 +1,18 @@
|
|||||||
import { Route, Router, Routes } from "@solidjs/router";
|
import { Route, Router } from "@solidjs/router"
|
||||||
import HomePage from "./pages/home";
|
import HomePage from "./pages/home"
|
||||||
import TruthTablePage from "./pages/truth-table";
|
import TruthTablePage from "./pages/truth-table"
|
||||||
import PageNotFound from "./pages/404";
|
import PageNotFound from "./pages/404"
|
||||||
import { render } from "solid-js/web";
|
import { render } from "solid-js/web"
|
||||||
import { type Component } from "solid-js";
|
import FailureFunctionPage from "./pages/failureFunction"
|
||||||
import FailureFunctionPage from "./pages/failureFunction";
|
|
||||||
|
|
||||||
const App: Component = () => (
|
|
||||||
<Routes>
|
|
||||||
<Route path={ "/" } element={ <HomePage /> } />
|
|
||||||
<Route path={ "/simplify-truths" } element={ <TruthTablePage /> } />
|
|
||||||
<Route path={ "/failure-function" } element={ <FailureFunctionPage /> } />
|
|
||||||
<Route path={ "*" } element={ <PageNotFound /> } />
|
|
||||||
</Routes>
|
|
||||||
);
|
|
||||||
|
|
||||||
render(
|
render(
|
||||||
() => (
|
() => (
|
||||||
<Router>
|
<Router>
|
||||||
<App />
|
<Route path={"/"} component={HomePage} />
|
||||||
</Router>
|
<Route path={"/simplify-truths"} component={TruthTablePage} />
|
||||||
),
|
<Route path={"/failure-function"} component={FailureFunctionPage} />
|
||||||
document.getElementById("root") as HTMLElement
|
<Route path={"*"} component={PageNotFound} />
|
||||||
);
|
</Router>
|
||||||
|
),
|
||||||
|
document.getElementById("root") as HTMLElement
|
||||||
|
)
|
||||||
|
@ -1,57 +1,59 @@
|
|||||||
/* @refresh reload */
|
/* @refresh reload */
|
||||||
import { type Component, createSignal } from "solid-js";
|
import { type Component, createSignal } from "solid-js"
|
||||||
|
|
||||||
interface SwitchProps extends TitleProps {
|
interface SwitchProps extends TitleProps {
|
||||||
defaultValue?: boolean,
|
defaultValue?: boolean
|
||||||
onChange?: (value: boolean) => void,
|
onChange?: (value: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MySwitch: Component<SwitchProps> = (
|
export const MySwitch: Component<SwitchProps> = ({
|
||||||
{
|
defaultValue = false,
|
||||||
defaultValue = false,
|
title,
|
||||||
title,
|
onChange,
|
||||||
onChange,
|
className,
|
||||||
className,
|
name,
|
||||||
name,
|
id
|
||||||
id
|
}) => {
|
||||||
}) => {
|
const [checked, setChecked] = createSignal(defaultValue)
|
||||||
|
|
||||||
const [checked, setChecked] = createSignal(defaultValue);
|
function handleChange() {
|
||||||
|
const newChecked = !checked()
|
||||||
function handleChange() {
|
setChecked(newChecked)
|
||||||
const newChecked = !checked();
|
if (onChange) {
|
||||||
setChecked(newChecked);
|
onChange(newChecked)
|
||||||
if (onChange) {
|
|
||||||
onChange(newChecked);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button id={ id }
|
<button
|
||||||
onClick={ handleChange }
|
id={id}
|
||||||
title={ title }
|
onClick={handleChange}
|
||||||
class={ `${ checked() ? "bg-cyan-900" : "bg-gray-500" }
|
title={title}
|
||||||
relative inline-flex h-6 w-11 items-center rounded-full my-2 ${ className }` }>
|
class={`${checked() ? "bg-cyan-900" : "bg-gray-500"} relative my-2 inline-flex h-6 w-11 items-center rounded-full ${className}`}
|
||||||
<span class={ "sr-only" }>{ name }</span>
|
>
|
||||||
<span class={ `${ checked() ? 'translate-x-6' : 'translate-x-1' }
|
<span class={"sr-only"}>{name}</span>
|
||||||
inline-block h-4 w-4 transform rounded-full bg-white transition-all` } />
|
<span
|
||||||
</button>
|
class={`${checked() ? "translate-x-6" : "translate-x-1"} inline-block h-4 w-4 transform rounded-full bg-white transition-all`}
|
||||||
);
|
/>
|
||||||
};
|
|
||||||
|
|
||||||
export const Button: Component<ButtonProps> = (
|
|
||||||
{
|
|
||||||
className,
|
|
||||||
title,
|
|
||||||
children,
|
|
||||||
id,
|
|
||||||
onClick,
|
|
||||||
type = "button",
|
|
||||||
}
|
|
||||||
) => (
|
|
||||||
<button title={ title } id={ id } type={ type }
|
|
||||||
class={ `border-rounded bg-cyan-900 px-2 cursor-pointer ${ className }` }
|
|
||||||
onClick={ onClick }>
|
|
||||||
{ children }
|
|
||||||
</button>
|
</button>
|
||||||
);
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Button: Component<ButtonProps> = ({
|
||||||
|
className,
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
id,
|
||||||
|
onClick,
|
||||||
|
type = "button"
|
||||||
|
}) => (
|
||||||
|
<button
|
||||||
|
title={title}
|
||||||
|
id={id}
|
||||||
|
type={type}
|
||||||
|
class={`border-rounded cursor-pointer bg-cyan-900 px-2 ${className}`}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
@ -1,23 +1,18 @@
|
|||||||
/* @refresh reload */
|
/* @refresh reload */
|
||||||
import { type Component } from "solid-js";
|
import { type Component } from "solid-js"
|
||||||
import { Link } from "./link";
|
import { Link } from "./link"
|
||||||
|
|
||||||
const Card: Component<CardProps> = (
|
const Card: Component<CardProps> = ({ children, className, title, to, newTab = false }) => (
|
||||||
{
|
<div
|
||||||
children,
|
class={`relative h-32 w-72 rounded-2xl bg-gradient-to-r from-cyan-600 to-cyan-500 ${className}`}
|
||||||
className,
|
>
|
||||||
title,
|
<div class={"relative p-5"}>
|
||||||
to,
|
<Link className={"text-white"} to={to} newTab={newTab}>
|
||||||
newTab = false
|
<h3 class={"mx-auto w-fit text-center before:content-['↗']"}>{title}</h3>
|
||||||
}) => (
|
</Link>
|
||||||
<div class={ `relative bg-gradient-to-r from-cyan-600 to-cyan-500 h-32 w-72 rounded-2xl ${ className }` }>
|
{children}
|
||||||
<div class={ "relative p-5" }>
|
|
||||||
<Link className={ "text-white" } to={ to } newTab={ newTab }>
|
|
||||||
<h3 class={ "text-center w-fit mx-auto before:content-['↗']" }>{ title }</h3>
|
|
||||||
</Link>
|
|
||||||
{ children }
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
export default Card;
|
export default Card
|
||||||
|
@ -1,104 +1,100 @@
|
|||||||
/* @refresh reload */
|
/* @refresh reload */
|
||||||
import { Dialog, DialogDescription, DialogPanel, DialogTitle } from "solid-headless";
|
import { Dialog, DialogDescription, DialogPanel, DialogTitle } from "solid-headless"
|
||||||
import { Component, createEffect, createSignal, JSX } from "solid-js";
|
import { Component, createEffect, createSignal, JSX } from "solid-js"
|
||||||
import { Button } from "./button";
|
import { Button } from "./button"
|
||||||
import { Portal } from "solid-js/web";
|
import { Portal } from "solid-js/web"
|
||||||
import { getElementById } from "../utils/dom";
|
import { getElementById } from "../utils/dom"
|
||||||
|
|
||||||
interface MyDialog extends TitleProps {
|
interface MyDialog extends TitleProps {
|
||||||
description?: string,
|
description?: string
|
||||||
button?: JSX.Element,
|
button?: JSX.Element
|
||||||
acceptButtonName?: string,
|
acceptButtonName?: string
|
||||||
acceptButtonId?: string,
|
acceptButtonId?: string
|
||||||
cancelButtonName?: string,
|
cancelButtonName?: string
|
||||||
callback?: () => void,
|
callback?: () => void
|
||||||
buttonClass?: string,
|
buttonClass?: string
|
||||||
buttonTitle?: string | null,
|
buttonTitle?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
const MyDialog: Component<MyDialog> = (
|
const MyDialog: Component<MyDialog> = ({
|
||||||
{
|
title,
|
||||||
title,
|
description,
|
||||||
description,
|
button,
|
||||||
button,
|
acceptButtonName = "Ok",
|
||||||
acceptButtonName = "Ok",
|
cancelButtonName = "Cancel",
|
||||||
cancelButtonName = "Cancel",
|
children,
|
||||||
children,
|
callback,
|
||||||
callback,
|
className,
|
||||||
className,
|
buttonClass,
|
||||||
buttonClass,
|
buttonTitle,
|
||||||
buttonTitle,
|
acceptButtonId
|
||||||
acceptButtonId,
|
}) => {
|
||||||
}) => {
|
const [isOpen, setIsOpen] = createSignal(false)
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = createSignal(false);
|
function callbackAndClose(): void {
|
||||||
|
callback?.()
|
||||||
|
setIsOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
function callbackAndClose(): void {
|
function setupKeyPress(): () => void {
|
||||||
if (callback) {
|
let isMounted = true
|
||||||
callback();
|
|
||||||
}
|
/**
|
||||||
setIsOpen(false);
|
* Pressing "Enter" when the modal is open, will click the accept button
|
||||||
|
* @param e KeyboardEvent of keypress
|
||||||
|
*/
|
||||||
|
function click(e: KeyboardEvent): void {
|
||||||
|
if (isMounted && e.key === "Enter" && acceptButtonId) {
|
||||||
|
getElementById<HTMLButtonElement>(acceptButtonId)?.click()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupKeyPress(): () => void {
|
if (isOpen()) {
|
||||||
let isMounted = true;
|
const id = "cl-6"
|
||||||
|
const el = getElementById(id)
|
||||||
|
el?.addEventListener("keypress", click)
|
||||||
|
return () => {
|
||||||
|
el?.removeEventListener("keypress", click)
|
||||||
|
isMounted = false
|
||||||
|
}
|
||||||
|
} else return () => undefined
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
createEffect(setupKeyPress, isOpen())
|
||||||
* Pressing "Enter" when the modal is open, will click the accept button
|
|
||||||
* @param e KeyboardEvent of keypress
|
|
||||||
*/
|
|
||||||
function click(e: KeyboardEvent): void {
|
|
||||||
if (isMounted && e.key === "Enter" && acceptButtonId) {
|
|
||||||
getElementById<HTMLButtonElement>(acceptButtonId)?.click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isOpen()) {
|
return (
|
||||||
const id = "cl-6"
|
<div class={"h-fit w-fit"}>
|
||||||
const el = getElementById(id);
|
<button onClick={() => setIsOpen(true)} class={buttonClass} title={buttonTitle ?? undefined}>
|
||||||
el?.addEventListener("keypress", click);
|
{button}
|
||||||
return () => {
|
</button>
|
||||||
el?.removeEventListener("keypress", click);
|
|
||||||
isMounted = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else return () => undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
createEffect(setupKeyPress, isOpen());
|
<Portal>
|
||||||
|
<Dialog
|
||||||
|
isOpen={isOpen()}
|
||||||
|
onClose={() => setIsOpen(false)}
|
||||||
|
class={`flex-row-center fixed inset-0 z-50 justify-center overflow-auto text-white ${className}`}
|
||||||
|
>
|
||||||
|
<div class={"fixed inset-0 bg-black/40" /*Backdrop*/} aria-hidden={true} />
|
||||||
|
|
||||||
return (
|
<DialogPanel class={"border-rounded relative w-fit border-gray-500 bg-default-bg p-2"}>
|
||||||
<div class={ "w-fit h-fit" }>
|
<DialogTitle class={"border-b"}>{title}</DialogTitle>
|
||||||
|
<DialogDescription class={"mb-4 mt-1"}>{description}</DialogDescription>
|
||||||
|
|
||||||
<button onClick={ () => setIsOpen(true) } class={ buttonClass } title={ buttonTitle ?? undefined }>
|
{children}
|
||||||
{ button }
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<Portal>
|
<div class={"my-3"}>
|
||||||
<Dialog isOpen={ isOpen() } onClose={ () => setIsOpen(false) }
|
<Button onClick={callbackAndClose} className={"mr-2 h-10"} id={acceptButtonId}>
|
||||||
class={ `fixed inset-0 flex-row-center justify-center z-50 overflow-auto text-white ${ className }` }>
|
{acceptButtonName}
|
||||||
|
</Button>
|
||||||
<div class={ "fixed inset-0 bg-black/40" /*Backdrop*/ } aria-hidden={ true } />
|
<Button onClick={() => setIsOpen(false)} className={"h-10"}>
|
||||||
|
{cancelButtonName}
|
||||||
<DialogPanel class={ "w-fit relative bg-default-bg border-rounded border-gray-500 p-2" }>
|
</Button>
|
||||||
<DialogTitle class={ "border-b" }>{ title }</DialogTitle>
|
</div>
|
||||||
<DialogDescription class={ "mb-4 mt-1" }>{ description }</DialogDescription>
|
</DialogPanel>
|
||||||
|
</Dialog>
|
||||||
{ children }
|
</Portal>
|
||||||
|
</div>
|
||||||
<div class={ "my-3" }>
|
)
|
||||||
<Button onClick={ callbackAndClose } className={ "h-10 mr-2" }
|
|
||||||
id={ acceptButtonId }>{ acceptButtonName }</Button>
|
|
||||||
<Button onClick={ () => setIsOpen(false) }
|
|
||||||
className={ "h-10" }>{ cancelButtonName }</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</DialogPanel>
|
|
||||||
|
|
||||||
</Dialog>
|
|
||||||
</Portal>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MyDialog;
|
export default MyDialog
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
/* @refresh reload */
|
/* @refresh reload */
|
||||||
import { type Component } from "solid-js";
|
import { type Component } from "solid-js"
|
||||||
import { Link } from "./link";
|
import { Link } from "./link"
|
||||||
|
|
||||||
const Footer: Component<SimpleProps> = ({ className }) => (
|
const Footer: Component<SimpleProps> = ({ className }) => (
|
||||||
<footer class={ `text-center py-5 absolute bottom-0 container ${ className }` }>
|
<footer class={`container absolute bottom-0 py-5 text-center ${className}`}>
|
||||||
<p>Kildekode på <Link to={ "https://github.com/h600878/martials.no" }>GitHub</Link></p>
|
<p>
|
||||||
</footer>
|
Kildekode på <Link to={"https://github.com/h600878/martials.no"}>GitHub</Link>
|
||||||
);
|
</p>
|
||||||
|
</footer>
|
||||||
|
)
|
||||||
|
|
||||||
export default Footer;
|
export default Footer
|
||||||
|
@ -1,25 +1,29 @@
|
|||||||
/* @refresh reload */
|
/* @refresh reload */
|
||||||
import { type Component, Show } from "solid-js";
|
import { type Component, Show } from "solid-js"
|
||||||
import { Icon } from "solid-heroicons";
|
import { Icon } from "solid-heroicons"
|
||||||
import { chevronLeft } from "solid-heroicons/solid";
|
import { chevronLeft } from "solid-heroicons/solid"
|
||||||
import { Link } from "./link";
|
import { Link } from "./link"
|
||||||
|
import { useLocation } from "@solidjs/router"
|
||||||
|
|
||||||
const Header: Component<TitleProps> = ({ className, title = "Title goes here" }) => (
|
const Header: Component<TitleProps> = ({ className, title = "Title goes here" }) => {
|
||||||
<header class={ className }>
|
const location = useLocation()
|
||||||
<div class={ "flex-row-center mx-auto w-fit" }>
|
|
||||||
|
|
||||||
<Show when={ typeof location !== "undefined" && location.pathname !== "/" } keyed>
|
return (
|
||||||
<Link to={ "/" } newTab={ false } title={ "Back to homepage" }>
|
<header class={className}>
|
||||||
<Icon path={ chevronLeft } class={ "text-cyan-500" } />
|
<div class={"flex-row-center mx-auto w-fit"}>
|
||||||
</Link>
|
<Show when={location.pathname !== "/"} keyed>
|
||||||
</Show>
|
<Link to={"/"} newTab={false} title={"Back to homepage"}>
|
||||||
|
<Icon path={chevronLeft} class={"text-cyan-500"} />
|
||||||
|
</Link>
|
||||||
|
</Show>
|
||||||
|
|
||||||
<h1 class={ "text-center text-cyan-500" }>{ title }</h1>
|
<h1 class={"text-center text-cyan-500"}>{title}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class={ "mx-auto w-fit" }>
|
<div class={"mx-auto w-fit"}>
|
||||||
<p>Av Martin Berg Alstad</p>
|
<p>Av Martin Berg Alstad</p>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
);
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default Header;
|
export default Header
|
||||||
|
@ -1,28 +1,28 @@
|
|||||||
/* @refresh reload */
|
/* @refresh reload */
|
||||||
import { type Component, createSignal, JSX, onMount, Setter, Show } from "solid-js";
|
import { type Component, createSignal, JSX, onMount, Setter, Show } from "solid-js"
|
||||||
import Row from "./row";
|
import Row from "./row"
|
||||||
import { Icon } from "solid-heroicons";
|
import { Icon } from "solid-heroicons"
|
||||||
import { magnifyingGlass, xMark } from "solid-heroicons/solid";
|
import { magnifyingGlass, xMark } from "solid-heroicons/solid"
|
||||||
import { getElementById } from "../utils/dom";
|
import { getElementById } from "../utils/dom"
|
||||||
|
|
||||||
function setupEventListener(id: string, setIsHover: Setter<boolean>): () => void {
|
function setupEventListener(id: string, setIsHover: Setter<boolean>): () => void {
|
||||||
let isMounted = true;
|
let isMounted = true
|
||||||
|
|
||||||
function hover(hover: boolean): void {
|
function hover(hover: boolean): void {
|
||||||
if (isMounted) {
|
if (isMounted) {
|
||||||
setIsHover(hover);
|
setIsHover(hover)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const el = getElementById(id);
|
const el = getElementById(id)
|
||||||
el?.addEventListener("pointerenter", () => hover(true));
|
el?.addEventListener("pointerenter", () => hover(true))
|
||||||
el?.addEventListener("pointerleave", () => hover(false));
|
el?.addEventListener("pointerleave", () => hover(false))
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
el?.removeEventListener("pointerenter", () => hover(true));
|
el?.removeEventListener("pointerenter", () => hover(true))
|
||||||
el?.removeEventListener("pointerleave", () => hover(false));
|
el?.removeEventListener("pointerleave", () => hover(false))
|
||||||
isMounted = false;
|
isMounted = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -30,149 +30,152 @@ function setupEventListener(id: string, setIsHover: Setter<boolean>): () => void
|
|||||||
* if the value of the input element is not empty and it's different from the current value
|
* if the value of the input element is not empty and it's different from the current value
|
||||||
*/
|
*/
|
||||||
function setSetIsText(id: string | undefined, isText: boolean, setIsText: Setter<boolean>): void {
|
function setSetIsText(id: string | undefined, isText: boolean, setIsText: Setter<boolean>): void {
|
||||||
if (id) {
|
if (id) {
|
||||||
const el = getElementById<HTMLInputElement | HTMLTextAreaElement>(id);
|
const el = getElementById<HTMLInputElement | HTMLTextAreaElement>(id)
|
||||||
if (el && el.value !== "" !== isText) {
|
if (el && (el.value !== "") !== isText) {
|
||||||
setIsText(el.value !== "");
|
setIsText(el.value !== "")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Input<T extends HTMLElement> extends InputProps<T> {
|
interface Input<T extends HTMLElement> extends InputProps<T> {
|
||||||
leading?: JSX.Element,
|
leading?: JSX.Element
|
||||||
trailing?: JSX.Element,
|
trailing?: JSX.Element
|
||||||
onChange?: () => void,
|
onChange?: () => void
|
||||||
inputClass?: string,
|
inputClass?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Input: Component<Input<HTMLInputElement>> = ( // TODO remove leading and trailing from component
|
export const Input: Component<Input<HTMLInputElement>> = (
|
||||||
{
|
// TODO remove leading and trailing from component
|
||||||
className,
|
{
|
||||||
id,
|
className,
|
||||||
name,
|
id,
|
||||||
type = "text",
|
name,
|
||||||
title,
|
type = "text",
|
||||||
placeholder,
|
title,
|
||||||
required = false,
|
placeholder,
|
||||||
onChange,
|
required = false,
|
||||||
leading,
|
onChange,
|
||||||
trailing,
|
leading,
|
||||||
inputClass,
|
trailing,
|
||||||
ref
|
inputClass,
|
||||||
}): JSX.Element => {
|
ref
|
||||||
|
}
|
||||||
|
): JSX.Element => {
|
||||||
|
/**
|
||||||
|
* Is 'true' if the input element is in focus
|
||||||
|
*/
|
||||||
|
const [isFocused, setIsFocused] = createSignal(false)
|
||||||
|
/**
|
||||||
|
* Is 'true' if the user is hovering over the input element
|
||||||
|
*/
|
||||||
|
const [isHover, setIsHover] = createSignal(false)
|
||||||
|
/**
|
||||||
|
* Is 'true' if the input element contains any characters
|
||||||
|
*/
|
||||||
|
const [isText, setIsText] = createSignal(false)
|
||||||
|
|
||||||
/**
|
onMount(() => {
|
||||||
* Is 'true' if the input element is in focus
|
if (id && title) {
|
||||||
*/
|
setupEventListener(id, setIsHover)
|
||||||
const [isFocused, setIsFocused] = createSignal(false);
|
|
||||||
/**
|
|
||||||
* Is 'true' if the user is hovering over the input element
|
|
||||||
*/
|
|
||||||
const [isHover, setIsHover] = createSignal(false);
|
|
||||||
/**
|
|
||||||
* Is 'true' if the input element contains any characters
|
|
||||||
*/
|
|
||||||
const [isText, setIsText] = createSignal(false);
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if (id && title) {
|
|
||||||
setupEventListener(id, setIsHover);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Row className={ `relative ${ className }` }>
|
|
||||||
{ leading }
|
|
||||||
<HoverTitle title={ title } isActive={ isFocused() || isHover() || isText() } htmlFor={ id } />
|
|
||||||
<input
|
|
||||||
class={ `bg-default-bg focus:border-cyan-500 outline-none border-2 border-gray-500
|
|
||||||
hover:border-t-cyan-400 ${ inputClass }` }
|
|
||||||
id={ id }
|
|
||||||
ref={ ref }
|
|
||||||
onFocus={ () => setIsFocused(true) }
|
|
||||||
onBlur={ () => setIsFocused(false) }
|
|
||||||
name={ name ?? undefined }
|
|
||||||
type={ type }
|
|
||||||
placeholder={ placeholder ?? undefined }
|
|
||||||
required={ required }
|
|
||||||
onInput={ () => {
|
|
||||||
setSetIsText(id, isText(), setIsText);
|
|
||||||
if (onChange) {
|
|
||||||
onChange();
|
|
||||||
}
|
|
||||||
} } />
|
|
||||||
{ trailing }
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const HoverTitle: Component<{ title?: string, isActive?: boolean, htmlFor?: string }> = (
|
|
||||||
{
|
|
||||||
title,
|
|
||||||
isActive = false,
|
|
||||||
htmlFor
|
|
||||||
}) => (
|
|
||||||
<label class={ `absolute pointer-events-none
|
|
||||||
${ isActive ? "-top-2 left-3 default-bg text-sm" : "left-2 top-1" }
|
|
||||||
transition-all duration-150 text-gray-600 dark:text-gray-400` }
|
|
||||||
for={ htmlFor }>
|
|
||||||
<div class={ "z-50 relative" }>{ title }</div>
|
|
||||||
<div class={ "w-full h-2 default-bg absolute bottom-1/3 z-10" } />
|
|
||||||
</label>
|
|
||||||
);
|
|
||||||
|
|
||||||
interface SearchProps extends InputProps<HTMLInputElement> {
|
|
||||||
typingDefault?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Search: Component<SearchProps> = (
|
|
||||||
{
|
|
||||||
typingDefault = false,
|
|
||||||
id = "search",
|
|
||||||
className,
|
|
||||||
ref
|
|
||||||
}) => {
|
|
||||||
|
|
||||||
const [typing, setTyping] = createSignal(typingDefault);
|
|
||||||
|
|
||||||
function getInputElement() {
|
|
||||||
return getElementById<HTMLInputElement>(id);
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function clearSearch(): void {
|
return (
|
||||||
const el = getInputElement();
|
<Row className={`relative ${className}`}>
|
||||||
if (el) {
|
{leading}
|
||||||
el.value = "";
|
<HoverTitle title={title} isActive={isFocused() || isHover() || isText()} htmlFor={id} />
|
||||||
setTyping(false);
|
<input
|
||||||
history.replaceState(null, "", location.pathname);
|
class={`border-2 border-gray-500 bg-default-bg outline-none hover:border-t-cyan-400
|
||||||
el.focus();
|
focus:border-cyan-500 ${inputClass}`}
|
||||||
}
|
id={id}
|
||||||
}
|
ref={ref}
|
||||||
|
onFocus={() => setIsFocused(true)}
|
||||||
function onChange(): void {
|
onBlur={() => setIsFocused(false)}
|
||||||
const el = getInputElement();
|
name={name ?? undefined}
|
||||||
if (el && (el.value !== "") !== typing()) {
|
type={type}
|
||||||
setTyping(el.value !== "");
|
placeholder={placeholder ?? undefined}
|
||||||
}
|
required={required}
|
||||||
}
|
onInput={() => {
|
||||||
|
setSetIsText(id, isText(), setIsText)
|
||||||
return (
|
if (onChange) {
|
||||||
<Input inputClass={ `rounded-xl pl-7 h-10 w-full pr-8` } className={ `w-full ${ className }` }
|
onChange()
|
||||||
id={ id }
|
}
|
||||||
ref={ ref }
|
}}
|
||||||
placeholder={ "¬A & B -> C" }
|
/>
|
||||||
type={ "text" }
|
{trailing}
|
||||||
onChange={ onChange }
|
</Row>
|
||||||
leading={ <Icon path={ magnifyingGlass } aria-label={ "Magnifying glass" }
|
)
|
||||||
class={ "pl-2 absolute" } /> }
|
}
|
||||||
trailing={ <Show when={ typing() } keyed>
|
|
||||||
<button class={ "absolute right-2" }
|
const HoverTitle: Component<{ title?: string; isActive?: boolean; htmlFor?: string }> = ({
|
||||||
title={ "Clear" }
|
title,
|
||||||
type={ "reset" }
|
isActive = false,
|
||||||
onClick={ clearSearch }>
|
htmlFor
|
||||||
<Icon path={ xMark } aria-label={ "The letter X" } />
|
}) => (
|
||||||
</button>
|
<label
|
||||||
</Show> }
|
class={`pointer-events-none absolute
|
||||||
/>
|
${isActive ? "default-bg -top-2 left-3 text-sm" : "left-2 top-1"}
|
||||||
);
|
text-gray-600 transition-all duration-150 dark:text-gray-400`}
|
||||||
|
for={htmlFor}
|
||||||
|
>
|
||||||
|
<div class={"relative z-50"}>{title}</div>
|
||||||
|
<div class={"default-bg absolute bottom-1/3 z-10 h-2 w-full"} />
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
|
||||||
|
interface SearchProps extends InputProps {
|
||||||
|
typingDefault?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Search: Component<SearchProps> = ({
|
||||||
|
typingDefault = false,
|
||||||
|
id = "search",
|
||||||
|
className,
|
||||||
|
ref
|
||||||
|
}) => {
|
||||||
|
const [typing, setTyping] = createSignal(typingDefault)
|
||||||
|
|
||||||
|
function getInputElement() {
|
||||||
|
return getElementById<HTMLInputElement>(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSearch(): void {
|
||||||
|
const el = getInputElement()
|
||||||
|
if (el) {
|
||||||
|
el.value = ""
|
||||||
|
setTyping(false)
|
||||||
|
history.replaceState(null, "", location.pathname)
|
||||||
|
el.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChange(): void {
|
||||||
|
const el = getInputElement()
|
||||||
|
if (el && (el.value !== "") !== typing()) {
|
||||||
|
setTyping(el.value !== "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
inputClass={`rounded-xl pl-7 h-10 w-full pr-8`}
|
||||||
|
className={`w-full ${className}`}
|
||||||
|
id={id}
|
||||||
|
ref={ref}
|
||||||
|
placeholder={"¬A & B -> C"}
|
||||||
|
type={"text"}
|
||||||
|
onChange={onChange}
|
||||||
|
leading={
|
||||||
|
<Icon path={magnifyingGlass} aria-label={"Magnifying glass"} class={"absolute pl-2"} />
|
||||||
|
}
|
||||||
|
trailing={
|
||||||
|
<Show when={typing()} keyed>
|
||||||
|
<button class={"absolute right-2"} title={"Clear"} type={"reset"} onClick={clearSearch}>
|
||||||
|
<Icon path={xMark} aria-label={"The letter X"} />
|
||||||
|
</button>
|
||||||
|
</Show>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,18 @@
|
|||||||
/* @refresh reload */
|
/* @refresh reload */
|
||||||
import { type Component } from "solid-js";
|
import { type Component } from "solid-js"
|
||||||
import Header from "./header";
|
import Header from "./header"
|
||||||
import Footer from "./footer";
|
import Footer from "./footer"
|
||||||
|
|
||||||
const Layout: Component<TitleProps> = (
|
const Layout: Component<TitleProps> = ({ children, title, className }) => (
|
||||||
{
|
<div class={`relative min-h-screen bg-default-bg font-mono text-white ${className}`}>
|
||||||
children,
|
<div class="container mx-auto">
|
||||||
title,
|
<Header className={"py-3"} title={title} />
|
||||||
className
|
<main>
|
||||||
}) => (
|
<div class={"pb-28"}>{children}</div>
|
||||||
<div class={ `bg-default-bg text-white min-h-screen relative font-mono ${ className }` }>
|
<Footer />
|
||||||
<div class="container mx-auto">
|
</main>
|
||||||
<Header className={ "py-3" } title={ title } />
|
|
||||||
<main>
|
|
||||||
<div class={ "pb-28" }>{ children }</div>
|
|
||||||
<Footer />
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
export default Layout;
|
export default Layout
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
/* @refresh reload */
|
/* @refresh reload */
|
||||||
import { type Component } from "solid-js";
|
import { type Component } from "solid-js"
|
||||||
|
|
||||||
export const Link: Component<LinkProps> = (
|
export const Link: Component<LinkProps> = (
|
||||||
{
|
{ to, rel, children, className, id, newTab = true, title } // TODO <A/> throws exception
|
||||||
to,
|
) => (
|
||||||
rel,
|
<a
|
||||||
children,
|
href={to}
|
||||||
className,
|
id={id}
|
||||||
id,
|
title={title}
|
||||||
newTab = true,
|
rel={`${rel} ${newTab ? "noreferrer" : undefined}`}
|
||||||
title,
|
target={newTab ? "_blank" : undefined}
|
||||||
}) => ( // TODO <A/> throws exception
|
class={`link ${className}`}
|
||||||
<a href={ to } id={ id } title={ title }
|
>
|
||||||
rel={ `${ rel } ${ newTab ? "noreferrer" : undefined }` }
|
{children}
|
||||||
target={ newTab ? "_blank" : undefined }
|
</a>
|
||||||
class={ `link ${ className }` }>
|
)
|
||||||
{ children }
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
|
@ -1,78 +1,72 @@
|
|||||||
/* @refresh reload */
|
/* @refresh reload */
|
||||||
import { type Component, createEffect, createSignal, JSX, Show } from "solid-js";
|
import { type Component, createEffect, createSignal, JSX, Show } from "solid-js"
|
||||||
import { Button } from "./button";
|
import { Button } from "./button"
|
||||||
|
|
||||||
interface MenuProps extends TitleProps {
|
interface MenuProps extends TitleProps {
|
||||||
button?: JSX.Element,
|
button?: JSX.Element
|
||||||
buttonClassName?: string,
|
buttonClassName?: string
|
||||||
itemsClassName?: string,
|
itemsClassName?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const MyMenu: Component<MenuProps> = (
|
const MyMenu: Component<MenuProps> = ({
|
||||||
{
|
title,
|
||||||
title,
|
button,
|
||||||
button,
|
children,
|
||||||
children,
|
id,
|
||||||
id,
|
className,
|
||||||
className,
|
buttonClassName,
|
||||||
buttonClassName,
|
itemsClassName
|
||||||
itemsClassName,
|
}) => {
|
||||||
}) => {
|
const [isOpen, setIsOpen] = createSignal(false)
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = createSignal(false);
|
function closeMenu(): void {
|
||||||
|
setIsOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
function closeMenu(): void {
|
function toggleMenu(): void {
|
||||||
setIsOpen(false);
|
setIsOpen(!isOpen())
|
||||||
|
}
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
function click(e: MouseEvent): void {
|
||||||
|
if (e.target instanceof HTMLElement) {
|
||||||
|
if (e.target.closest(`#${id}`) === null) {
|
||||||
|
closeMenu()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleMenu(): void {
|
function keypress(e: KeyboardEvent): void {
|
||||||
setIsOpen(!isOpen());
|
if (e.key === "Escape") {
|
||||||
|
closeMenu()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createEffect(() => {
|
if (isOpen()) {
|
||||||
|
document.addEventListener("click", click)
|
||||||
|
document.addEventListener("keyup", keypress)
|
||||||
|
} else {
|
||||||
|
document.removeEventListener("click", click)
|
||||||
|
document.removeEventListener("keyup", keypress)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function click(e: MouseEvent): void {
|
return (
|
||||||
if (e.target instanceof HTMLElement) {
|
// TODO transition
|
||||||
if (e.target.closest(`#${ id }`) === null) {
|
<div class={`${className}`} id={id}>
|
||||||
closeMenu();
|
<Button title={title} onClick={toggleMenu} className={`flex-row-center ${buttonClassName}`}>
|
||||||
}
|
{button}
|
||||||
}
|
</Button>
|
||||||
}
|
|
||||||
|
|
||||||
function keypress(e: KeyboardEvent): void {
|
|
||||||
if (e.key === "Escape") {
|
|
||||||
closeMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isOpen()) {
|
|
||||||
document.addEventListener("click", click);
|
|
||||||
document.addEventListener("keyup", keypress);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
document.removeEventListener("click", click);
|
|
||||||
document.removeEventListener("keyup", keypress);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return ( // TODO transition
|
|
||||||
<div class={ `${ className }` } id={ id }>
|
|
||||||
|
|
||||||
<Button title={ title }
|
|
||||||
onClick={ toggleMenu }
|
|
||||||
className={ `flex-row-center ${ buttonClassName }` }>
|
|
||||||
{ button }
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Show when={ isOpen() } keyed>
|
|
||||||
<div
|
|
||||||
class={ `absolute bg-default-bg border border-gray-500 rounded-b-xl mt-1 w-max z-50 ${ itemsClassName }` }>
|
|
||||||
<div class={ "mx-1" }>{ children }</div>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
|
<Show when={isOpen()} keyed>
|
||||||
|
<div
|
||||||
|
class={`absolute z-50 mt-1 w-max rounded-b-xl border border-gray-500 bg-default-bg ${itemsClassName}`}
|
||||||
|
>
|
||||||
|
<div class={"mx-1"}>{children}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
</Show>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MyMenu;
|
export default MyMenu
|
||||||
|
@ -1,74 +1,65 @@
|
|||||||
/* @refresh reload */
|
/* @refresh reload */
|
||||||
import { Disclosure, DisclosureButton, DisclosurePanel, Transition } from "solid-headless";
|
import { Disclosure, DisclosureButton, DisclosurePanel, Transition } from "solid-headless"
|
||||||
import { Icon } from "solid-heroicons";
|
import { Icon } from "solid-heroicons"
|
||||||
import { type Component, JSX } from "solid-js";
|
import { type Component, JSX } from "solid-js"
|
||||||
import { chevronUp } from "solid-heroicons/solid";
|
import { chevronUp } from "solid-heroicons/solid"
|
||||||
|
|
||||||
interface InfoBoxProps extends TitleProps {
|
interface InfoBoxProps extends TitleProps {
|
||||||
error?: boolean,
|
error?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const InfoBox: Component<InfoBoxProps> = (
|
export const InfoBox: Component<InfoBoxProps> = ({ title, children, error = false, className }) => (
|
||||||
{
|
<div class={`border-rounded ${error ? "border-red-500" : "border-gray-500"} ${className}`}>
|
||||||
title,
|
<p class={`border-b px-2 ${error ? "border-red-500" : "border-gray-500"}`}>{title}</p>
|
||||||
children,
|
<div class={"mx-2"}>{children}</div>
|
||||||
error = false,
|
</div>
|
||||||
className
|
)
|
||||||
}) => (
|
|
||||||
<div class={ `border-rounded ${ error ? "border-red-500" : "border-gray-500" } ${ className }` }>
|
|
||||||
<p class={ `border-b px-2 ${ error ? "border-red-500" : "border-gray-500" }` }>{ title }</p>
|
|
||||||
<div class={ "mx-2" }>{ children }</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
interface MyDisclosureProps extends TitleProps {
|
interface MyDisclosureProps extends TitleProps {
|
||||||
defaultOpen?: boolean,
|
defaultOpen?: boolean
|
||||||
onClick?: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>,
|
onClick?: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MyDisclosure: Component<MyDisclosureProps> = (
|
export const MyDisclosure: Component<MyDisclosureProps> = ({
|
||||||
{
|
title,
|
||||||
title,
|
children,
|
||||||
children,
|
defaultOpen = false,
|
||||||
defaultOpen = false,
|
className,
|
||||||
className,
|
id,
|
||||||
id,
|
onClick
|
||||||
onClick
|
}): JSX.Element => (
|
||||||
}): JSX.Element => (
|
<div id={id} class={`border-rounded bg-default-bg ${className}`}>
|
||||||
<div id={ id } class={ `border-rounded bg-default-bg ${ className }` }>
|
<Disclosure defaultOpen={defaultOpen}>
|
||||||
<Disclosure defaultOpen={ defaultOpen }>
|
{({ isOpen }) => (
|
||||||
{ ({ isOpen }) =>
|
<>
|
||||||
<>
|
<DisclosureButton onClick={onClick} class={`flex-row-center w-full justify-between px-2`}>
|
||||||
<DisclosureButton onClick={ onClick }
|
<p class={`py-1`}>{title}</p>
|
||||||
class={ `flex-row-center w-full justify-between px-2` }>
|
<Icon path={chevronUp} class={`w-5 ${isOpen() && "rotate-180 transform"} transition`} />
|
||||||
<p class={ `py-1` }>{ title }</p>
|
</DisclosureButton>
|
||||||
<Icon path={ chevronUp }
|
<Transition
|
||||||
class={ `w-5 ${ isOpen() && "transform rotate-180" } transition` } />
|
enter="transition duration-100 ease-out"
|
||||||
</DisclosureButton>
|
enterFrom="transform scale-95 opacity-0"
|
||||||
<Transition
|
enterTo="transform scale-100 opacity-100"
|
||||||
enter="transition duration-100 ease-out"
|
leave="transition duration-75 ease-out"
|
||||||
enterFrom="transform scale-95 opacity-0"
|
leaveFrom="transform scale-100 opacity-100"
|
||||||
enterTo="transform scale-100 opacity-100"
|
leaveTo="transform scale-95 opacity-0"
|
||||||
leave="transition duration-75 ease-out"
|
show
|
||||||
leaveFrom="transform scale-100 opacity-100"
|
>
|
||||||
leaveTo="transform scale-95 opacity-0" show>
|
<DisclosurePanel>
|
||||||
<DisclosurePanel>
|
<div class={"px-2 pb-2 text-gray-300"}>{children}</div>
|
||||||
<div class={ "px-2 pb-2 text-gray-300" }>{ children }</div>
|
</DisclosurePanel>
|
||||||
</DisclosurePanel>
|
</Transition>
|
||||||
</Transition>
|
</>
|
||||||
</>
|
)}
|
||||||
}
|
</Disclosure>
|
||||||
</Disclosure>
|
</div>
|
||||||
</div>
|
)
|
||||||
);
|
|
||||||
|
|
||||||
export const MyDisclosureContainer: Component<ChildProps> = (
|
export const MyDisclosureContainer: Component<ChildProps> = ({ children, className }) => (
|
||||||
{
|
<div
|
||||||
children,
|
class={`border-rounded mb-2 flex flex-col gap-1
|
||||||
className
|
bg-cyan-900 p-2 dark:border-gray-800 ${className}`}
|
||||||
}) => (
|
>
|
||||||
<div class={ `bg-cyan-900 border-rounded dark:border-gray-800 p-2 mb-2
|
{children}
|
||||||
flex flex-col gap-1 ${ className }` }>
|
</div>
|
||||||
{ children }
|
)
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/* @refresh reload */
|
/* @refresh reload */
|
||||||
import { type Component } from "solid-js";
|
import { type Component } from "solid-js"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A row that centers its children
|
* A row that centers its children
|
||||||
@ -8,7 +8,7 @@ import { type Component } from "solid-js";
|
|||||||
* @returns The row
|
* @returns The row
|
||||||
*/
|
*/
|
||||||
const Row: Component<ChildProps> = ({ children, className }) => (
|
const Row: Component<ChildProps> = ({ children, className }) => (
|
||||||
<div class={ `flex-row-center ${ className }` }>{ children }</div>
|
<div class={`flex-row-center ${className}`}>{children}</div>
|
||||||
);
|
)
|
||||||
|
|
||||||
export default Row;
|
export default Row
|
||||||
|
@ -1,51 +1,54 @@
|
|||||||
/* @refresh reload */
|
/* @refresh reload */
|
||||||
import { For } from "solid-js/web";
|
import { For } from "solid-js/web"
|
||||||
import { type Component } from "solid-js";
|
import { type Component } from "solid-js"
|
||||||
|
|
||||||
interface TruthTableProps extends SimpleProps {
|
interface TruthTableProps extends SimpleProps {
|
||||||
table?: Table,
|
table?: Table
|
||||||
header?: string[],
|
header?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const TruthTable: Component<TruthTableProps> = (
|
const TruthTable: Component<TruthTableProps> = ({ table, header, className, style, id }) => (
|
||||||
{
|
<table
|
||||||
table,
|
class={`z-10 table border-collapse border-2 border-gray-500 ${className}`}
|
||||||
header,
|
id={id}
|
||||||
className,
|
style={style}
|
||||||
style,
|
>
|
||||||
id,
|
<thead>
|
||||||
}) => (
|
<tr>
|
||||||
<table class={ `border-2 border-gray-500 border-collapse table z-10 ${ className }` } id={ id } style={ style }>
|
<For each={header}>
|
||||||
<thead>
|
{(exp) => (
|
||||||
<tr>
|
<th
|
||||||
<For each={ header }>
|
scope={"col"}
|
||||||
{ (exp) => (
|
class={
|
||||||
<th scope={ "col" }
|
`sticky top-0 bg-default-bg text-center outline
|
||||||
class={ `bg-default-bg text-center sticky top-0 [position:-webkit-sticky;]
|
outline-2 outline-offset-[-1px] outline-gray-500 [position:-webkit-sticky;]` /*TODO sticky header at the top of the screen */
|
||||||
outline outline-2 outline-offset-[-1px] outline-gray-500` /*TODO sticky header at the top of the screen */ }>
|
}
|
||||||
<p class={ "px-2 w-max" }>{ exp }</p>
|
>
|
||||||
</th>
|
<p class={"w-max px-2"}>{exp}</p>
|
||||||
) }
|
</th>
|
||||||
</For>
|
)}
|
||||||
</tr>
|
</For>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody>
|
</thead>
|
||||||
<For each={ table }>
|
<tbody>
|
||||||
{ (row) =>
|
<For each={table}>
|
||||||
<tr class={ "hover:text-black" }>
|
{(row) => (
|
||||||
<For each={ row }>
|
<tr class={"hover:text-black"}>
|
||||||
{ (value) =>
|
<For each={row}>
|
||||||
<td class={ `text-center border border-gray-500 last:underline
|
{(value) => (
|
||||||
${ value ? "bg-green-700" : "bg-red-700" }` }>
|
<td
|
||||||
<p>{ value ? "T" : "F" }</p>
|
class={`border border-gray-500 text-center last:underline
|
||||||
</td>
|
${value ? "bg-green-700" : "bg-red-700"}`}
|
||||||
}
|
>
|
||||||
</For>
|
<p>{value ? "T" : "F"}</p>
|
||||||
</tr>
|
</td>
|
||||||
}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</tbody>
|
</tr>
|
||||||
</table>
|
)}
|
||||||
);
|
</For>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)
|
||||||
|
|
||||||
export default TruthTable;
|
export default TruthTable
|
||||||
|
@ -3,46 +3,44 @@
|
|||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
|
.debug {
|
||||||
|
@apply border border-red-500;
|
||||||
|
@apply after:absolute after:content-['DEBUG'];
|
||||||
|
}
|
||||||
|
|
||||||
.debug {
|
.flex-row-center {
|
||||||
@apply border border-red-500;
|
@apply flex flex-row items-center;
|
||||||
@apply after:content-['DEBUG'] after:absolute;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.flex-row-center {
|
.border-rounded {
|
||||||
@apply flex flex-row items-center;
|
@apply rounded-2xl border border-gray-700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.border-rounded {
|
h1 {
|
||||||
@apply border rounded-2xl border-gray-700;
|
@apply text-4xl;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h2 {
|
||||||
@apply text-4xl;
|
@apply text-3xl;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h3 {
|
||||||
@apply text-3xl;
|
@apply text-2xl;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h4 {
|
||||||
@apply text-2xl;
|
@apply text-xl;
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
a {
|
||||||
@apply text-xl;
|
@apply text-cyan-500 hover:underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
li {
|
||||||
@apply hover:underline text-cyan-500;
|
@apply ml-4 list-disc;
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
|
||||||
@apply list-disc ml-4;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
@apply pointer-events-none h-6 w-6;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
svg {
|
||||||
|
@apply pointer-events-none h-6 w-6;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
/* @refresh reload */
|
/* @refresh reload */
|
||||||
import { type Component } from "solid-js";
|
import { type Component } from "solid-js"
|
||||||
import { render } from "solid-js/web";
|
import { render } from "solid-js/web"
|
||||||
import Layout from "../components/layout";
|
import Layout from "../components/layout"
|
||||||
import { Link } from "../components/link";
|
import { Link } from "../components/link"
|
||||||
|
|
||||||
const PageNotFound: Component = () => (
|
const PageNotFound: Component = () => (
|
||||||
<Layout title={ "Siden ble ikke funnet" }>
|
<Layout title={"Siden ble ikke funnet"}>
|
||||||
<div class={ "text-center" }>
|
<div class={"text-center"}>
|
||||||
<h4>Error 404 - Siden ble ikke funnet</h4>
|
<h4>Error 404 - Siden ble ikke funnet</h4>
|
||||||
<p>
|
<p>
|
||||||
<Link to={ "/" } newTab={ false } className={ "mx-auto relative w-max" }>
|
<Link to={"/"} newTab={false} className={"relative mx-auto w-max"}>
|
||||||
Gå tilbake til forsiden
|
Gå tilbake til forsiden
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
)
|
||||||
|
|
||||||
export default PageNotFound;
|
export default PageNotFound
|
||||||
|
@ -1,66 +1,59 @@
|
|||||||
/* @refresh reload */
|
/* @refresh reload */
|
||||||
|
|
||||||
import { Component, createSignal } from "solid-js";
|
import { Component, createSignal } from "solid-js"
|
||||||
import { Input } from "../components/input";
|
import { Input } from "../components/input"
|
||||||
import Layout from "../components/layout";
|
import Layout from "../components/layout"
|
||||||
import { failureFunction } from "../utils/failureFunction";
|
import { failureFunction } from "../utils/failureFunction"
|
||||||
import { For } from "solid-js/web";
|
import { For } from "solid-js/web"
|
||||||
|
|
||||||
type FFProps = { char: string, index: number }
|
type FFProps = { char: string; index: number }
|
||||||
|
|
||||||
const FailureFunctionPage: Component = () => {
|
const FailureFunctionPage: Component = () => {
|
||||||
let inputRef: HTMLInputElement | undefined = undefined;
|
let inputRef: HTMLInputElement | undefined = undefined
|
||||||
|
|
||||||
const [result, setResult] = createSignal<ReadonlyArray<FFProps>>()
|
const [result, setResult] = createSignal<ReadonlyArray<FFProps>>()
|
||||||
|
|
||||||
function onSubmit(e: Event) {
|
function onSubmit(e: Event) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (inputRef) {
|
if (inputRef) {
|
||||||
const input = inputRef.value;
|
const input = inputRef.value
|
||||||
const splitInput = input.split("")
|
const splitInput = input.split("")
|
||||||
const output = failureFunction(input)
|
const output = failureFunction(input)
|
||||||
|
|
||||||
if (output.length !== splitInput.length) {
|
if (output.length !== splitInput.length) {
|
||||||
console.error("Something went wrong")
|
console.error("Something went wrong")
|
||||||
}
|
} else {
|
||||||
else {
|
setResult(output.map((value, index) => ({ char: splitInput[index], index: value })))
|
||||||
setResult(output.map((value, index) => {
|
}
|
||||||
return { char: splitInput[index], index: value } as FFProps
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout title={ "Failure function" }>
|
<Layout title={"Failure function"}>
|
||||||
<div class={ "container mx-auto max-w-2xl overflow-auto" }>
|
<div class={"container mx-auto max-w-2xl overflow-auto"}>
|
||||||
<p>Failure Function</p>
|
<p>Failure Function</p>
|
||||||
<form onsubmit={ onSubmit }>
|
<form onsubmit={onSubmit}>
|
||||||
<Input ref={ inputRef } inputClass={ "rounded-2xl w-full h-10 px-3" } />
|
<Input ref={inputRef} inputClass={"rounded-2xl w-full h-10 px-3"} />
|
||||||
</form>
|
</form>
|
||||||
<table class={ "mb-3" }>
|
<table class={"mb-3"}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<For each={ result() }>
|
<For each={result()}>
|
||||||
{ ({ char }) =>
|
{({ char }) => <th class={"border border-black"}>{char}</th>}
|
||||||
<th class={ "border border-black" }>{ char }</th>
|
</For>
|
||||||
}
|
</tr>
|
||||||
</For>
|
</thead>
|
||||||
</tr>
|
<tbody>
|
||||||
</thead>
|
<tr>
|
||||||
<tbody>
|
<For each={result()}>
|
||||||
<tr>
|
{({ index }) => <td class={"border border-black"}>{index}</td>}
|
||||||
<For each={ result() }>
|
</For>
|
||||||
{ ({ index }) =>
|
</tr>
|
||||||
<td class={ "border border-black" }>{ index }</td>
|
</tbody>
|
||||||
}
|
</table>
|
||||||
</For>
|
</div>
|
||||||
</tr>
|
</Layout>
|
||||||
</tbody>
|
)
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FailureFunctionPage;
|
export default FailureFunctionPage
|
||||||
|
@ -1,55 +1,57 @@
|
|||||||
/* @refresh reload */
|
/* @refresh reload */
|
||||||
import { For } from "solid-js/web";
|
import { For } from "solid-js/web"
|
||||||
|
|
||||||
import "../index.css";
|
import "../index.css"
|
||||||
import { type Component } from "solid-js";
|
import { type Component } from "solid-js"
|
||||||
import Layout from "../components/layout";
|
import Layout from "../components/layout"
|
||||||
import Card from "../components/card";
|
import Card from "../components/card"
|
||||||
import { Link } from "../components/link";
|
import { Link } from "../components/link"
|
||||||
|
|
||||||
const apiRoot = "https://api.martials.no";
|
const apiRoot = "https://api.martials.no"
|
||||||
|
|
||||||
const cards = [
|
const cards = [
|
||||||
{
|
{
|
||||||
title: "API-er",
|
title: "API-er",
|
||||||
children: <>
|
children: (
|
||||||
<p>Sjekk ut mine API-er</p>
|
<>
|
||||||
<ul>
|
<p>Sjekk ut mine API-er</p>
|
||||||
<li>
|
<ul>
|
||||||
<Link className={ "text-white" } to={ `${ apiRoot }/simplify-truths` }>
|
<li>
|
||||||
Forenkle sannhetsverdier
|
<Link className={"text-white"} to={`${apiRoot}/simplify-truths`}>
|
||||||
</Link>
|
Forenkle sannhetsverdier
|
||||||
</li>
|
</Link>
|
||||||
</ul>
|
</li>
|
||||||
</>,
|
</ul>
|
||||||
to: apiRoot,
|
</>
|
||||||
newTab: true,
|
),
|
||||||
},
|
to: apiRoot,
|
||||||
{
|
newTab: true
|
||||||
title: "Hjemmeside",
|
},
|
||||||
children: <p>Sjekk ut mine andre prosjekter</p>,
|
{
|
||||||
to: "https://emberal.github.io/",
|
title: "Hjemmeside",
|
||||||
newTab: true,
|
children: <p>Sjekk ut mine andre prosjekter</p>,
|
||||||
},
|
to: "https://emberal.github.io/",
|
||||||
{
|
newTab: true
|
||||||
title: "Forenkle sannhetsverdier",
|
},
|
||||||
children: <p>Implementering av API</p>,
|
{
|
||||||
to: `/simplify-truths`,
|
title: "Forenkle sannhetsverdier",
|
||||||
}
|
children: <p>Implementering av API</p>,
|
||||||
] satisfies CardProps[];
|
to: `/simplify-truths`
|
||||||
|
}
|
||||||
|
] satisfies CardProps[]
|
||||||
|
|
||||||
const HomePage: Component = () => (
|
const HomePage: Component = () => (
|
||||||
<Layout title={ "Velkommen!" }>
|
<Layout title={"Velkommen!"}>
|
||||||
<div class={ "flex flex-wrap justify-center mt-10" }>
|
<div class={"mt-10 flex flex-wrap justify-center"}>
|
||||||
<For each={ cards }>
|
<For each={cards}>
|
||||||
{ card =>
|
{(card) => (
|
||||||
<Card title={ card.title } className={ "m-4" } to={ card.to } newTab={ card.newTab }>
|
<Card title={card.title} className={"m-4"} to={card.to} newTab={card.newTab}>
|
||||||
{ card.children }
|
{card.children}
|
||||||
</Card>
|
</Card>
|
||||||
}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
)
|
||||||
|
|
||||||
export default HomePage;
|
export default HomePage
|
||||||
|
@ -1,420 +1,447 @@
|
|||||||
/* @refresh reload */
|
/* @refresh reload */
|
||||||
import Layout from "../components/layout";
|
import Layout from "../components/layout"
|
||||||
import { Input, Search } from "../components/input";
|
import { Input, Search } from "../components/input"
|
||||||
import { Icon } from "solid-heroicons";
|
import { Icon } from "solid-heroicons"
|
||||||
import TruthTable from "../components/truth-table";
|
import TruthTable from "../components/truth-table"
|
||||||
import { InfoBox, MyDisclosure, MyDisclosureContainer } from "../components/output";
|
import { InfoBox, MyDisclosure, MyDisclosureContainer } from "../components/output"
|
||||||
import { diffChars } from "diff";
|
import { diffChars } from "diff"
|
||||||
import MyMenu from "../components/menu";
|
import MyMenu from "../components/menu"
|
||||||
import { type Accessor, type Component, createSignal, JSX, onMount, Show } from "solid-js";
|
import { type Accessor, type Component, createSignal, JSX, onMount, Show } from "solid-js"
|
||||||
import { For } from "solid-js/web";
|
import { For } from "solid-js/web"
|
||||||
import Row from "../components/row";
|
import Row from "../components/row"
|
||||||
import {
|
import { arrowDownTray, arrowPath, check, eye, eyeSlash, funnel } from "solid-heroicons/solid"
|
||||||
arrowDownTray, arrowPath,
|
import { Button, MySwitch } from "../components/button"
|
||||||
check,
|
import MyDialog from "../components/dialog"
|
||||||
eye,
|
import { exportToExcel } from "../utils/export"
|
||||||
eyeSlash,
|
import { Link } from "../components/link"
|
||||||
funnel
|
import { isTouch } from "../utils/touch"
|
||||||
} from "solid-heroicons/solid";
|
import { replaceOperators } from "../utils/expressionUtils"
|
||||||
import { Button, MySwitch } from "../components/button";
|
import { getElementById } from "../utils/dom"
|
||||||
import MyDialog from "../components/dialog";
|
import { useSearchParams } from "@solidjs/router"
|
||||||
import { exportToExcel } from "../utils/export";
|
|
||||||
import { Link } from "../components/link";
|
|
||||||
import { isTouch } from "../utils/touch";
|
|
||||||
import { replaceOperators } from "../utils/expressionUtils";
|
|
||||||
import { getElementById } from "../utils/dom";
|
|
||||||
import { useSearchParams } from "@solidjs/router";
|
|
||||||
|
|
||||||
type Option = { name: string, value: "NONE" | "TRUE" | "FALSE" | "DEFAULT" | "TRUE_FIRST" | "FALSE_FIRST" };
|
type Option = {
|
||||||
|
name: string
|
||||||
|
value: "NONE" | "TRUE" | "FALSE" | "DEFAULT" | "TRUE_FIRST" | "FALSE_FIRST"
|
||||||
|
}
|
||||||
|
|
||||||
const fetchUrls = [
|
const fetchUrls = [
|
||||||
"http://localhost:8080/simplify/table/",
|
"http://localhost:8080/simplify/table/",
|
||||||
"https://api.martials.no/simplify-truths/simplify/table/"
|
"https://api.martials.no/simplify-truths/simplify/table/"
|
||||||
];
|
]
|
||||||
|
|
||||||
// TODO move some code to new components
|
// TODO move some code to new components
|
||||||
const TruthTablePage: Component = () => {
|
const TruthTablePage: Component = () => {
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams()
|
||||||
|
let inputElement: HTMLInputElement | undefined = undefined
|
||||||
|
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
let simplifyDefault = searchParams.simplify === undefined || searchParams.simplify === "true",
|
||||||
let inputElement: HTMLInputElement | undefined = undefined;
|
inputContent = !!searchParams.exp,
|
||||||
|
hideIntermediate = searchParams.hideIntermediate === "true"
|
||||||
|
|
||||||
let simplifyDefault = searchParams.simplify === undefined || searchParams.simplify === "true",
|
const [simplifyEnabled, setSimplifyEnabled] = createSignal(simplifyDefault)
|
||||||
inputContent = !!searchParams.exp,
|
const [fetchResult, setFetchResult] = createSignal<FetchResult | null>(null)
|
||||||
hideIntermediate = searchParams.hideIntermediate === "true";
|
|
||||||
|
|
||||||
const [simplifyEnabled, setSimplifyEnabled] = createSignal(simplifyDefault);
|
const hideOptions: Option[] = [
|
||||||
const [fetchResult, setFetchResult] = createSignal<FetchResult | null>(null);
|
{ name: "Show all result", value: "NONE" },
|
||||||
|
{ name: "Hide true results", value: "TRUE" },
|
||||||
|
{ name: "Hide false results", value: "FALSE" }
|
||||||
|
]
|
||||||
|
|
||||||
const hideOptions: Option[] = [
|
const [hideValues, setHideValues] = createSignal(hideOptions[0])
|
||||||
{ name: "Show all result", value: "NONE" },
|
|
||||||
{ name: "Hide true results", value: "TRUE" },
|
|
||||||
{ name: "Hide false results", value: "FALSE" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const [hideValues, setHideValues] = createSignal(hideOptions[0]);
|
const sortOptions: Option[] = [
|
||||||
|
{ name: "Sort by default", value: "DEFAULT" },
|
||||||
|
{ name: "Sort by true first", value: "TRUE_FIRST" },
|
||||||
|
{ name: "Sort by false first", value: "FALSE_FIRST" }
|
||||||
|
]
|
||||||
|
|
||||||
const sortOptions: Option[] = [
|
const [sortValues, setSortValues] = createSignal(sortOptions[0])
|
||||||
{ name: "Sort by default", value: "DEFAULT" },
|
const [hideIntermediates, setHideIntermediates] = createSignal(hideIntermediate)
|
||||||
{ name: "Sort by true first", value: "TRUE_FIRST" },
|
const [isLoaded, setIsLoaded] = createSignal<boolean | null>(null)
|
||||||
{ name: "Sort by false first", value: "FALSE_FIRST" },
|
const [error, setError] = createSignal<{ title: string; message: string } | null>(null)
|
||||||
];
|
const [useLocalhost, setUseLocalhost] = createSignal(false)
|
||||||
|
|
||||||
const [sortValues, setSortValues] = createSignal(sortOptions[0]);
|
/**
|
||||||
const [hideIntermediates, setHideIntermediates] = createSignal(hideIntermediate);
|
* Updates the state of the current expression to the new search with all whitespace removed.
|
||||||
const [isLoaded, setIsLoaded] = createSignal<boolean | null>(null);
|
* If the element is not found, reset.
|
||||||
const [error, setError] = createSignal<{ title: string, message: string } | null>(null);
|
*/
|
||||||
const [useLocalhost, setUseLocalhost] = createSignal(false);
|
function onClick(e: Event): void {
|
||||||
|
e.preventDefault() // Stops the page from reloading onClick
|
||||||
|
const exp = inputElement?.value
|
||||||
|
|
||||||
/**
|
if (exp) {
|
||||||
* Updates the state of the current expression to the new search with all whitespace removed.
|
setSearchParams({
|
||||||
* If the element is not found, reset.
|
exp,
|
||||||
*/
|
simplify: simplifyEnabled(),
|
||||||
function onClick(e: Event): void {
|
hide: hideValues().value,
|
||||||
e.preventDefault(); // Stops the page from reloading onClick
|
sort: sortValues().value,
|
||||||
const exp = inputElement?.value;
|
hideIntermediate: hideIntermediates()
|
||||||
|
})
|
||||||
|
|
||||||
if (exp) {
|
getFetchResult(exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setSearchParams({
|
function getFetchResult(exp: string | null): void {
|
||||||
exp,
|
setFetchResult(null)
|
||||||
simplify: simplifyEnabled(),
|
|
||||||
hide: hideValues().value,
|
|
||||||
sort: sortValues().value,
|
|
||||||
hideIntermediate: hideIntermediates()
|
|
||||||
});
|
|
||||||
|
|
||||||
getFetchResult(exp);
|
if (exp && exp !== "") {
|
||||||
}
|
exp = replaceOperators(exp)
|
||||||
|
setError(null)
|
||||||
|
setIsLoaded(false)
|
||||||
|
|
||||||
|
fetch(`${fetchUrls[useLocalhost() ? 0 : 1]}${encodeURIComponent(exp)}?
|
||||||
|
simplify=${simplifyEnabled()}&hide=${hideValues().value}&sort=${sortValues().value}&caseSensitive=false&
|
||||||
|
hideIntermediate=${hideIntermediates()}`)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status !== "OK" && !res.ok) {
|
||||||
|
return setError({ title: "Input error", message: res.message })
|
||||||
|
}
|
||||||
|
return setFetchResult(res)
|
||||||
|
})
|
||||||
|
.catch((err) => setError({ title: "Fetch error", message: err.toString() }))
|
||||||
|
.finally(() => setIsLoaded(true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount((): void => {
|
||||||
|
if (searchParams.exp) {
|
||||||
|
const exp = searchParams.exp
|
||||||
|
if (exp && inputElement) {
|
||||||
|
inputElement.value = exp
|
||||||
|
}
|
||||||
|
const hide = searchParams.hide
|
||||||
|
if (hide) {
|
||||||
|
setHideValues(hideOptions.find((o) => o.value === hide) ?? hideOptions[0])
|
||||||
|
}
|
||||||
|
const sort = searchParams.sort
|
||||||
|
if (sort) {
|
||||||
|
setSortValues(sortOptions.find((o) => o.value === sort) ?? sortOptions[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
getFetchResult(exp)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFetchResult(exp: string | null): void {
|
// Focuses searchbar on load
|
||||||
setFetchResult(null);
|
if (!isTouch()) {
|
||||||
|
inputElement?.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if (exp && exp !== "") {
|
const tableId = "truth-table"
|
||||||
exp = replaceOperators(exp);
|
const filenameId = "excel-filename"
|
||||||
setError(null);
|
|
||||||
setIsLoaded(false);
|
|
||||||
|
|
||||||
fetch(`${ fetchUrls[useLocalhost() ? 0 : 1] }${ encodeURIComponent(exp) }?
|
function _exportToExcel(): void {
|
||||||
simplify=${ simplifyEnabled() }&hide=${ hideValues().value }&sort=${ sortValues().value }&caseSensitive=false&
|
const value = getElementById<HTMLInputElement>(filenameId)?.value
|
||||||
hideIntermediate=${ hideIntermediates() }`)
|
exportToExcel({
|
||||||
.then(res => res.json())
|
name: value !== "" ? value : undefined,
|
||||||
.then(res => {
|
tableId
|
||||||
if (res.status !== "OK" && !res.ok) {
|
})
|
||||||
return setError({ title: "Input error", message: res.message });
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout title={"Truth tables"}>
|
||||||
|
<Show when={import.meta.env.DEV ?? false} keyed>
|
||||||
|
(DEV) Use localhost:
|
||||||
|
<MySwitch title={"Use localhost"} defaultValue={false} onChange={setUseLocalhost} />
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<div id={"truth-content"}>
|
||||||
|
<div class={"mx-auto max-w-2xl"}>
|
||||||
|
<HowTo />
|
||||||
|
|
||||||
|
<form class={"flex-row-center"} onSubmit={onClick} autocomplete={"off"}>
|
||||||
|
<Search ref={inputElement} typingDefault={inputContent} />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
id={"truth-input-button"}
|
||||||
|
title={"Generate (Enter)"}
|
||||||
|
type={"submit"}
|
||||||
|
className={"min-w-50px ml-2 h-10"}
|
||||||
|
children={"Generate"}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{/* Options row */}
|
||||||
|
<Row className={"my-1 gap-2"}>
|
||||||
|
<span class={"h-min"}>{"Simplify"}: </span>
|
||||||
|
|
||||||
|
<MySwitch
|
||||||
|
onChange={setSimplifyEnabled}
|
||||||
|
defaultValue={simplifyEnabled()}
|
||||||
|
title={"Simplify"}
|
||||||
|
name={"Turn on/off simplify expressions"}
|
||||||
|
className={"mx-1"}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class={"relative h-min"}>
|
||||||
|
<MyMenu
|
||||||
|
title={"Filter results"}
|
||||||
|
id={"filter-results"}
|
||||||
|
button={
|
||||||
|
<Show
|
||||||
|
when={hideValues().value !== "NONE"}
|
||||||
|
children={
|
||||||
|
<Icon
|
||||||
|
path={eyeSlash}
|
||||||
|
aria-label={"An eye with a slash through it"}
|
||||||
|
class={`mx-1 ${hideValues().value === "TRUE" ? "text-green-500" : "text-red-500"}`}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
return setFetchResult(res);
|
fallback={<Icon path={eye} aria-label={"An eye"} class={"mx-1"} />}
|
||||||
})
|
keyed
|
||||||
.catch(err => setError({ title: "Fetch error", message: err.toString() }))
|
/>
|
||||||
.finally(() => setIsLoaded(true));
|
}
|
||||||
}
|
children={
|
||||||
}
|
<For each={hideOptions}>
|
||||||
|
{(option) => (
|
||||||
onMount((): void => {
|
<SingleMenuItem
|
||||||
|
onClick={() => setHideValues(option)}
|
||||||
if (searchParams.exp) {
|
option={option}
|
||||||
const exp = searchParams.exp;
|
currentValue={hideValues}
|
||||||
if (exp && inputElement) {
|
/>
|
||||||
inputElement.value = exp;
|
)}
|
||||||
}
|
</For>
|
||||||
const hide = searchParams.hide;
|
}
|
||||||
if (hide) {
|
itemsClassName={"right-0"}
|
||||||
setHideValues(hideOptions.find(o => o.value === hide) ?? hideOptions[0]);
|
/>
|
||||||
}
|
|
||||||
const sort = searchParams.sort;
|
|
||||||
if (sort) {
|
|
||||||
setSortValues(sortOptions.find(o => o.value === sort) ?? sortOptions[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
getFetchResult(exp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Focuses searchbar on load
|
|
||||||
if (!isTouch()) {
|
|
||||||
inputElement?.focus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const tableId = "truth-table";
|
|
||||||
const filenameId = "excel-filename";
|
|
||||||
|
|
||||||
function _exportToExcel(): void {
|
|
||||||
const value = getElementById<HTMLInputElement>(filenameId)?.value;
|
|
||||||
exportToExcel({
|
|
||||||
name: value !== "" ? value : undefined, tableId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Layout title={ "Truth tables" }>
|
|
||||||
|
|
||||||
<Show when={ import.meta.env.DEV ?? false } keyed>
|
|
||||||
(DEV) Use localhost:
|
|
||||||
<MySwitch title={ "Use localhost" } defaultValue={ false }
|
|
||||||
onChange={ setUseLocalhost } />
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<div id={ "truth-content" }>
|
|
||||||
<div class={ "max-w-2xl mx-auto" }>
|
|
||||||
|
|
||||||
<HowTo />
|
|
||||||
|
|
||||||
<form class={ "flex-row-center" } onSubmit={ onClick } autocomplete={ "off" }>
|
|
||||||
|
|
||||||
<Search ref={ inputElement } typingDefault={ inputContent } />
|
|
||||||
|
|
||||||
<Button id={ "truth-input-button" }
|
|
||||||
title={ "Generate (Enter)" }
|
|
||||||
type={ "submit" }
|
|
||||||
className={ "min-w-50px h-10 ml-2" }
|
|
||||||
children={ "Generate" } />
|
|
||||||
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{ /* Options row */ }
|
|
||||||
<Row className={ "my-1 gap-2" }>
|
|
||||||
<span class={ "h-min" }>{ "Simplify" }: </span>
|
|
||||||
|
|
||||||
<MySwitch onChange={ setSimplifyEnabled } defaultValue={ simplifyEnabled() }
|
|
||||||
title={ "Simplify" }
|
|
||||||
name={ "Turn on/off simplify expressions" } className={ "mx-1" } />
|
|
||||||
|
|
||||||
<div class={ "h-min relative" }>
|
|
||||||
<MyMenu title={ "Filter results" } id={ "filter-results" }
|
|
||||||
button={
|
|
||||||
<Show when={ hideValues().value !== "NONE" } children={
|
|
||||||
<Icon path={ eyeSlash } aria-label={ "An eye with a slash through it" }
|
|
||||||
class={ `mx-1 ${ hideValues().value === "TRUE" ?
|
|
||||||
"text-green-500" : "text-red-500" }` } />
|
|
||||||
} fallback={
|
|
||||||
<Icon path={ eye } aria-label={ "An eye" } class={ "mx-1" } />
|
|
||||||
} keyed />
|
|
||||||
}
|
|
||||||
children={
|
|
||||||
<For each={ hideOptions }>
|
|
||||||
{ (option) => (
|
|
||||||
<SingleMenuItem onClick={ () => setHideValues(option) }
|
|
||||||
option={ option }
|
|
||||||
currentValue={ hideValues } />
|
|
||||||
) }
|
|
||||||
</For>
|
|
||||||
} itemsClassName={ "right-0" }
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class={ "h-min relative" }>
|
|
||||||
<MyMenu title={ "Sort results" } id={ "sort-results" }
|
|
||||||
button={ <Icon path={ funnel } aria-label={ "Filter" }
|
|
||||||
class={ `h-6 w-6 ${ sortValues().value === "TRUE_FIRST" ? "text-green-500" :
|
|
||||||
sortValues().value === "FALSE_FIRST" && "text-red-500" }` } /> }
|
|
||||||
children={
|
|
||||||
<For each={ sortOptions }>
|
|
||||||
{ (option) => (
|
|
||||||
<SingleMenuItem option={ option } currentValue={ sortValues }
|
|
||||||
onClick={ () => setSortValues(option) } />
|
|
||||||
) }
|
|
||||||
</For>
|
|
||||||
}
|
|
||||||
itemsClassName={ "right-0" }
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<MySwitch title={ "Hide intermediate values" }
|
|
||||||
onChange={ setHideIntermediates }
|
|
||||||
defaultValue={ hideIntermediates() } />
|
|
||||||
|
|
||||||
<Show when={ isLoaded() && error() === null } keyed>
|
|
||||||
|
|
||||||
<MyDialog title={ "Download" }
|
|
||||||
description={ "Export current table (.xlsx)" }
|
|
||||||
button={ <>
|
|
||||||
<p class={ "sr-only" }>{ "Download" }</p>
|
|
||||||
<Icon aria-label={ "Download" } path={ arrowDownTray } />
|
|
||||||
</> }
|
|
||||||
callback={ _exportToExcel }
|
|
||||||
acceptButtonName={ "Download" }
|
|
||||||
cancelButtonName={ "Cancel" }
|
|
||||||
buttonClass={ `float-right` }
|
|
||||||
buttonTitle={ "Export current table" }
|
|
||||||
acceptButtonId={ "download-accept" }>
|
|
||||||
<p>{ "Filename" }:</p>
|
|
||||||
<Input className={ "border-rounded h-10 px-2" } id={ filenameId }
|
|
||||||
placeholder={ "Truth Table" } />
|
|
||||||
</MyDialog>
|
|
||||||
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
<Show when={ error() } keyed>
|
|
||||||
<ErrorBox title={ error()?.title ?? "Error" }
|
|
||||||
error={ error()?.message ?? "Something went wrong" } />
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<Show when={ isLoaded() === false } keyed>
|
|
||||||
<Icon path={ arrowPath } aria-label={ "Loading indicator" } class={ "animate-spin mx-auto" } />
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<Show when={ simplifyEnabled() && (fetchResult()?.orderOperations?.length ?? 0) > 0 } keyed>
|
|
||||||
<ShowMeHow fetchResult={ fetchResult } />
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Show when={ isLoaded() && error() === null } keyed>
|
|
||||||
<Show when={ simplifyEnabled() } keyed>
|
|
||||||
<InfoBox className={ "w-fit mx-auto pb-1 text-lg text-center" }
|
|
||||||
title={ "Output:" } id={ "expression-output" }>
|
|
||||||
<p>{ fetchResult()?.after }</p>
|
|
||||||
</InfoBox>
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<div class={ "flex justify-center m-2" }>
|
|
||||||
<div id={ "table" } class={ "h-[45rem] overflow-auto" }>
|
|
||||||
|
|
||||||
<TruthTable header={ fetchResult()?.header ?? undefined }
|
|
||||||
table={ fetchResult()?.table?.truthMatrix } id={ tableId } />
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</Layout>
|
<div class={"relative h-min"}>
|
||||||
);
|
<MyMenu
|
||||||
|
title={"Sort results"}
|
||||||
|
id={"sort-results"}
|
||||||
|
button={
|
||||||
|
<Icon
|
||||||
|
path={funnel}
|
||||||
|
aria-label={"Filter"}
|
||||||
|
class={`h-6 w-6 ${
|
||||||
|
sortValues().value === "TRUE_FIRST"
|
||||||
|
? "text-green-500"
|
||||||
|
: sortValues().value === "FALSE_FIRST" && "text-red-500"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
children={
|
||||||
|
<For each={sortOptions}>
|
||||||
|
{(option) => (
|
||||||
|
<SingleMenuItem
|
||||||
|
option={option}
|
||||||
|
currentValue={sortValues}
|
||||||
|
onClick={() => setSortValues(option)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
}
|
||||||
|
itemsClassName={"right-0"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<MySwitch
|
||||||
|
title={"Hide intermediate values"}
|
||||||
|
onChange={setHideIntermediates}
|
||||||
|
defaultValue={hideIntermediates()}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Show when={isLoaded() && error() === null} keyed>
|
||||||
|
<MyDialog
|
||||||
|
title={"Download"}
|
||||||
|
description={"Export current table (.xlsx)"}
|
||||||
|
button={
|
||||||
|
<>
|
||||||
|
<p class={"sr-only"}>{"Download"}</p>
|
||||||
|
<Icon aria-label={"Download"} path={arrowDownTray} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
callback={_exportToExcel}
|
||||||
|
acceptButtonName={"Download"}
|
||||||
|
cancelButtonName={"Cancel"}
|
||||||
|
buttonClass={`float-right`}
|
||||||
|
buttonTitle={"Export current table"}
|
||||||
|
acceptButtonId={"download-accept"}
|
||||||
|
>
|
||||||
|
<p>{"Filename"}:</p>
|
||||||
|
<Input
|
||||||
|
className={"border-rounded h-10 px-2"}
|
||||||
|
id={filenameId}
|
||||||
|
placeholder={"Truth Table"}
|
||||||
|
/>
|
||||||
|
</MyDialog>
|
||||||
|
</Show>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Show when={error()} keyed>
|
||||||
|
<ErrorBox
|
||||||
|
title={error()?.title ?? "Error"}
|
||||||
|
error={error()?.message ?? "Something went wrong"}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<Show when={isLoaded() === false} keyed>
|
||||||
|
<Icon
|
||||||
|
path={arrowPath}
|
||||||
|
aria-label={"Loading indicator"}
|
||||||
|
class={"mx-auto animate-spin"}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<Show when={simplifyEnabled() && (fetchResult()?.orderOperations?.length ?? 0) > 0} keyed>
|
||||||
|
<ShowMeHow fetchResult={fetchResult} />
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Show when={isLoaded() && error() === null} keyed>
|
||||||
|
<Show when={simplifyEnabled()} keyed>
|
||||||
|
<InfoBox
|
||||||
|
className={"mx-auto w-fit pb-1 text-center text-lg"}
|
||||||
|
title={"Output:"}
|
||||||
|
id={"expression-output"}
|
||||||
|
>
|
||||||
|
<p>{fetchResult()?.after}</p>
|
||||||
|
</InfoBox>
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<div class={"m-2 flex justify-center"}>
|
||||||
|
<div id={"table"} class={"h-[45rem] overflow-auto"}>
|
||||||
|
<TruthTable
|
||||||
|
header={fetchResult()?.header ?? undefined}
|
||||||
|
table={fetchResult()?.table?.truthMatrix}
|
||||||
|
id={tableId}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TruthTablePage;
|
export default TruthTablePage
|
||||||
|
|
||||||
interface SingleMenuItem {
|
interface SingleMenuItem {
|
||||||
option: Option,
|
option: Option
|
||||||
currentValue?: Accessor<Option>,
|
currentValue?: Accessor<Option>
|
||||||
onClick: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>,
|
onClick: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>
|
||||||
}
|
}
|
||||||
|
|
||||||
const SingleMenuItem: Component<SingleMenuItem> = (
|
const SingleMenuItem: Component<SingleMenuItem> = ({ option, currentValue, onClick }) => {
|
||||||
{
|
const isSelected = () => currentValue?.().value === option.value
|
||||||
option,
|
return (
|
||||||
currentValue,
|
<button class={`flex-row-center cursor-pointer last:mb-1 hover:underline`} onClick={onClick}>
|
||||||
onClick
|
<Icon
|
||||||
}) => {
|
path={check}
|
||||||
const isSelected = () => currentValue?.().value === option.value;
|
aria-label={isSelected() ? "A checkmark" : "Nothing"}
|
||||||
return (
|
class={`text-white ${!isSelected() && "invisible"}`}
|
||||||
<button class={ `hover:underline cursor-pointer last:mb-1 flex-row-center` }
|
/>
|
||||||
onClick={ onClick }>
|
{option.name}
|
||||||
<Icon path={ check } aria-label={ isSelected() ? "A checkmark" : "Nothing" }
|
</button>
|
||||||
class={ `text-white ${ !isSelected() && "invisible" }` } />
|
)
|
||||||
{ option.name }
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ErrorBox: Component<{ title: string, error: string }> = ({ title, error }) => (
|
const ErrorBox: Component<{ title: string; error: string }> = ({ title, error }) => (
|
||||||
<InfoBox className={ "w-fit text-center mx-auto" }
|
<InfoBox className={"mx-auto w-fit text-center"} title={title} error={true}>
|
||||||
title={ title }
|
<p>{error}</p>
|
||||||
error={ true }>
|
</InfoBox>
|
||||||
<p>{ error }</p>
|
)
|
||||||
</InfoBox>
|
|
||||||
);
|
|
||||||
|
|
||||||
interface ShowMeHowProps {
|
interface ShowMeHowProps {
|
||||||
fetchResult: Accessor<FetchResult | null>,
|
fetchResult: Accessor<FetchResult | null>
|
||||||
}
|
}
|
||||||
|
|
||||||
const ShowMeHow: Component<ShowMeHowProps> = ({ fetchResult }) => (
|
const ShowMeHow: Component<ShowMeHowProps> = ({ fetchResult }) => (
|
||||||
<MyDisclosureContainer>
|
<MyDisclosureContainer>
|
||||||
<MyDisclosure title={ "Show me how it's done" }>
|
<MyDisclosure title={"Show me how it's done"}>
|
||||||
<table class={ "table" }>
|
<table class={"table"}>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
<For each={fetchResult()?.orderOperations}>{orderOperationRow()}</For>
|
||||||
<For each={ fetchResult()?.orderOperations }>
|
</tbody>
|
||||||
{ orderOperationRow() }
|
</table>
|
||||||
</For>
|
</MyDisclosure>
|
||||||
|
</MyDisclosureContainer>
|
||||||
</tbody>
|
)
|
||||||
</table>
|
|
||||||
</MyDisclosure>
|
|
||||||
</MyDisclosureContainer>
|
|
||||||
);
|
|
||||||
|
|
||||||
const HowTo: Component = () => (
|
const HowTo: Component = () => (
|
||||||
<MyDisclosureContainer>
|
<MyDisclosureContainer>
|
||||||
|
<MyDisclosure title={"How to"}>
|
||||||
|
<p>
|
||||||
|
Fill in a truth expression and it will be simplified for you as much as possible. It will
|
||||||
|
also genereate a truth table with all possible values. You can use a single letter, word or
|
||||||
|
multiple words without spacing for each atomic value. If you do not want to simplify the
|
||||||
|
expression, simply turn off the toggle. Keywords for operators are defined below.
|
||||||
|
Parentheses is also allowed.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
API docs can be found <Link to={"https://api.martials.no/simplify-truths"}>here</Link>.
|
||||||
|
</p>
|
||||||
|
</MyDisclosure>
|
||||||
|
|
||||||
<MyDisclosure title={ "How to" }>
|
<KeywordsDisclosure />
|
||||||
<p>Fill in a truth expression and it will be simplified for you as much as possible.
|
</MyDisclosureContainer>
|
||||||
It will also genereate a truth table with all possible values. You can use a single
|
)
|
||||||
letter,
|
|
||||||
word or multiple words without spacing for each atomic value.
|
|
||||||
If you do not want to simplify the expression, simply turn off the toggle.
|
|
||||||
Keywords for operators are defined below. Parentheses is also allowed.</p>
|
|
||||||
<p>API docs can be found <Link to={ "https://api.martials.no/simplify-truths" }>here</Link>.
|
|
||||||
</p>
|
|
||||||
</MyDisclosure>
|
|
||||||
|
|
||||||
<KeywordsDisclosure />
|
|
||||||
|
|
||||||
</MyDisclosureContainer>
|
|
||||||
);
|
|
||||||
|
|
||||||
const orderOperationRow = () => (operation: OrderOfOperation, index: Accessor<number>) => (
|
const orderOperationRow = () => (operation: OrderOfOperation, index: Accessor<number>) => (
|
||||||
<tr class={ "border-b border-dotted border-gray-500" }>
|
<tr class={"border-b border-dotted border-gray-500"}>
|
||||||
<td>{ index() + 1 }:</td>
|
<td>{index() + 1}:</td>
|
||||||
<td class={ "px-2" }>{
|
<td class={"px-2"}>
|
||||||
|
{
|
||||||
|
<For each={diffChars(operation.before, operation.after)}>
|
||||||
|
{(part) => (
|
||||||
|
<span class={`${part.added && "bg-green-700"} ${part.removed && "bg-red-700"}`}>
|
||||||
|
{part.value}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
}
|
||||||
|
|
||||||
<For each={ diffChars(operation.before, operation.after) }>
|
<Show when={typeof window !== "undefined" && window.outerWidth <= 640} keyed>
|
||||||
{ (part) => (
|
<p>
|
||||||
<span class={ `${ part.added && "bg-green-700" } ${ part.removed && "bg-red-700" }` }>
|
{"using"}: {operation.law}
|
||||||
{ part.value }
|
</p>
|
||||||
</span>
|
</Show>
|
||||||
) }
|
</td>
|
||||||
</For> }
|
<Show when={typeof window !== "undefined" && window.outerWidth > 640} keyed>
|
||||||
|
<td>
|
||||||
<Show when={ typeof window !== "undefined" && window.outerWidth <= 640 } keyed>
|
{"using"}: {operation.law}
|
||||||
<p>{ "using" }: { operation.law }</p>
|
</td>
|
||||||
</Show>
|
</Show>
|
||||||
|
</tr>
|
||||||
</td>
|
)
|
||||||
<Show when={ typeof window !== "undefined" && window.outerWidth > 640 } keyed>
|
|
||||||
<td>{ "using" }: { operation.law }</td>
|
|
||||||
</Show>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
|
|
||||||
const KeywordsDisclosure: Component = () => (
|
const KeywordsDisclosure: Component = () => (
|
||||||
<MyDisclosure title={ "Keywords" }>
|
<MyDisclosure title={"Keywords"}>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr class={ "text-left" }>
|
<tr class={"text-left"}>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th class={ "pr-2" }>API</th>
|
<th class={"pr-2"}>API</th>
|
||||||
<th>Other</th>
|
<th>Other</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Not:</td>
|
<td>Not:</td>
|
||||||
<td>!</td>
|
<td>!</td>
|
||||||
<td>NOT</td>
|
<td>NOT</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>And:</td>
|
<td>And:</td>
|
||||||
<td>&</td>
|
<td>&</td>
|
||||||
<td>AND</td>
|
<td>AND</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Or:</td>
|
<td>Or:</td>
|
||||||
<td>|</td>
|
<td>|</td>
|
||||||
<td>/</td>
|
<td>/</td>
|
||||||
<td>OR</td>
|
<td>OR</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class={ "pr-2" }>Implication:</td>
|
<td class={"pr-2"}>Implication:</td>
|
||||||
<td>{ "->" }</td>
|
<td>{"->"}</td>
|
||||||
<td class={ "px-2" }>IMPLICATION</td>
|
<td class={"px-2"}>IMPLICATION</td>
|
||||||
<td>IMP</td>
|
<td>IMP</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</MyDisclosure>
|
</MyDisclosure>
|
||||||
);
|
)
|
||||||
|
10
src/types/env.d.ts
vendored
10
src/types/env.d.ts
vendored
@ -1,11 +1,11 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
readonly VITE_FETCH_URL: string,
|
readonly VITE_FETCH_URL: string
|
||||||
readonly VITE_FETCH_PATH: string,
|
readonly VITE_FETCH_PATH: string
|
||||||
readonly VITE_FETCH_FULL: string,
|
readonly VITE_FETCH_FULL: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
readonly env: ImportMetaEnv
|
readonly env: ImportMetaEnv
|
||||||
}
|
}
|
||||||
|
84
src/types/types.d.ts
vendored
84
src/types/types.d.ts
vendored
@ -1,70 +1,70 @@
|
|||||||
interface SimpleProps<T extends HTMLElement = HTMLElement> {
|
interface SimpleProps<T extends HTMLElement = HTMLElement> {
|
||||||
name?: string;
|
name?: string
|
||||||
className?: string,
|
className?: string
|
||||||
style?: import("solid-js").JSX.CSSProperties,
|
style?: import("solid-js").JSX.CSSProperties
|
||||||
id?: string,
|
id?: string
|
||||||
title?: string,
|
title?: string
|
||||||
ref?: T,
|
ref?: T
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ChildProps<T extends HTMLElement = HTMLInputElement> extends SimpleProps<T> {
|
interface ChildProps<T extends HTMLElement = HTMLInputElement> extends SimpleProps<T> {
|
||||||
children?: import("solid-js").JSX.Element,
|
children?: import("solid-js").JSX.Element
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LinkProps extends ChildProps {
|
interface LinkProps extends ChildProps {
|
||||||
to?: string,
|
to?: string
|
||||||
rel?: string,
|
rel?: string
|
||||||
newTab?: boolean,
|
newTab?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TitleProps<T extends HTMLElement = HTMLElement> extends ChildProps<T> {
|
interface TitleProps<T extends HTMLElement = HTMLElement> extends ChildProps<T> {
|
||||||
title?: string,
|
title?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ButtonProps extends TitleProps {
|
interface ButtonProps extends TitleProps {
|
||||||
onClick?: import("solid-js").JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>,
|
onClick?: import("solid-js").JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>
|
||||||
type?: "button" | "submit" | "reset",
|
type?: "button" | "submit" | "reset"
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InputProps<T extends HTMLElement = HTMLInputElement> extends TitleProps<T> {
|
interface InputProps<T extends HTMLElement = HTMLInputElement> extends TitleProps<T> {
|
||||||
onInput?: import("solid-js").JSX.EventHandlerUnion<T, Event>,
|
onInput?: import("solid-js").JSX.EventHandlerUnion<T, Event>
|
||||||
placeholder?: string,
|
placeholder?: string
|
||||||
required?: boolean,
|
required?: boolean
|
||||||
type?: string,
|
type?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CardProps extends LinkProps {
|
interface CardProps extends LinkProps {
|
||||||
title?: string;
|
title?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Expression = {
|
type Expression = {
|
||||||
leading: string,
|
leading: string
|
||||||
left: Expression | null,
|
left: Expression | null
|
||||||
operator: Operator | null,
|
operator: Operator | null
|
||||||
right: Expression | null,
|
right: Expression | null
|
||||||
trailing: string,
|
trailing: string
|
||||||
atomic: string | null,
|
atomic: string | null
|
||||||
};
|
}
|
||||||
|
|
||||||
type Operator = "AND" | "OR" | "NOT" | "IMPLICATION";
|
type Operator = "AND" | "OR" | "NOT" | "IMPLICATION"
|
||||||
|
|
||||||
type Table = boolean[][];
|
type Table = boolean[][]
|
||||||
|
|
||||||
type OrderOfOperation = {
|
type OrderOfOperation = {
|
||||||
before: string,
|
before: string
|
||||||
after: string,
|
after: string
|
||||||
law: string,
|
law: string
|
||||||
};
|
}
|
||||||
|
|
||||||
type FetchResult = {
|
type FetchResult = {
|
||||||
status: string,
|
status: string
|
||||||
version: string | null,
|
version: string | null
|
||||||
before: string,
|
before: string
|
||||||
after: string,
|
after: string
|
||||||
orderOperations: OrderOfOperation[] | null,
|
orderOperations: OrderOfOperation[] | null
|
||||||
expression: Expression | null,
|
expression: Expression | null
|
||||||
header: string[] | null,
|
header: string[] | null
|
||||||
table: {
|
table: {
|
||||||
truthMatrix: Table,
|
truthMatrix: Table
|
||||||
} | null,
|
} | null
|
||||||
};
|
}
|
||||||
|
@ -5,5 +5,5 @@
|
|||||||
* @returns The element with the given id, or null if it doesn't exist
|
* @returns The element with the given id, or null if it doesn't exist
|
||||||
*/
|
*/
|
||||||
export function getElementById<T extends HTMLElement = HTMLElement>(id: string): T | null {
|
export function getElementById<T extends HTMLElement = HTMLElement>(id: string): T | null {
|
||||||
return <T>document.getElementById(id);
|
return <T>document.getElementById(id)
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { type BookType, utils, write, writeFile } from "xlsx";
|
import { type BookType, utils, write, writeFile } from "xlsx"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exports the generated truth table to an excel (.xlsx) file
|
* Exports the generated truth table to an excel (.xlsx) file
|
||||||
@ -27,17 +27,20 @@ import { type BookType, utils, write, writeFile } from "xlsx";
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
export function exportToExcel(
|
export function exportToExcel({
|
||||||
{
|
type = "xlsx",
|
||||||
type = "xlsx",
|
name = "Truth Table",
|
||||||
name = "Truth Table",
|
dl = false,
|
||||||
dl = false,
|
tableId
|
||||||
tableId,
|
}: {
|
||||||
}: { type?: BookType, name?: string, dl?: boolean, tableId: string }): any {
|
type?: BookType
|
||||||
|
name?: string
|
||||||
const element = document.getElementById(tableId);
|
dl?: boolean
|
||||||
const wb = utils.table_to_book(element, { sheet: "sheet1" });
|
tableId: string
|
||||||
return dl ?
|
}): any {
|
||||||
write(wb, { bookType: type, bookSST: true, type: 'base64' }) :
|
const element = document.getElementById(tableId)
|
||||||
writeFile(wb, name + "." + type);
|
const wb = utils.table_to_book(element, { sheet: "sheet1" })
|
||||||
|
return dl
|
||||||
|
? write(wb, { bookType: type, bookSST: true, type: "base64" })
|
||||||
|
: writeFile(wb, name + "." + type)
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,11 @@
|
|||||||
* @returns The expression with the replaced operators
|
* @returns The expression with the replaced operators
|
||||||
*/
|
*/
|
||||||
export function replaceOperators(expression: string): string {
|
export function replaceOperators(expression: string): string {
|
||||||
return expression
|
return expression
|
||||||
.replaceAll(/\//g, "|")
|
.replaceAll(/\//g, "|")
|
||||||
.replaceAll(/¬/g, "!")
|
.replaceAll(/¬/g, "!")
|
||||||
.replaceAll(/\sOR\s/gi, " | ")
|
.replaceAll(/\sOR\s/gi, " | ")
|
||||||
.replaceAll(/\sAND\s/gi, " & ")
|
.replaceAll(/\sAND\s/gi, " & ")
|
||||||
.replaceAll(/\s(IMPLICATION|IMP)\s/gi, " -> ")
|
.replaceAll(/\s(IMPLICATION|IMP)\s/gi, " -> ")
|
||||||
.replaceAll(/\sNOT\s/gi, " !");
|
.replaceAll(/\sNOT\s/gi, " !")
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,24 @@
|
|||||||
export function failureFunction(P: String, m = P.length): number[] {
|
export function failureFunction(P: String, m = P.length): number[] {
|
||||||
// No proper prefix for string of length 1:
|
// No proper prefix for string of length 1:
|
||||||
const arr = [0]
|
const arr = [0]
|
||||||
let i = 0, j = 1
|
let i = 0,
|
||||||
|
j = 1
|
||||||
|
|
||||||
while (j < m) {
|
while (j < m) {
|
||||||
if (P[i] == P[j]) {
|
if (P[i] == P[j]) {
|
||||||
i++
|
i++
|
||||||
arr.push(i)
|
arr.push(i)
|
||||||
j++;
|
j++
|
||||||
}
|
|
||||||
// The first character didn't match:
|
|
||||||
else if (i == 0) {
|
|
||||||
arr.push(0)
|
|
||||||
j++
|
|
||||||
}
|
|
||||||
// Mismatch after at least one matching character:
|
|
||||||
else {
|
|
||||||
i = arr[i - 1]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return arr
|
// The first character didn't match:
|
||||||
|
else if (i == 0) {
|
||||||
|
arr.push(0)
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
// Mismatch after at least one matching character:
|
||||||
|
else {
|
||||||
|
i = arr[i - 1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arr
|
||||||
}
|
}
|
||||||
|
@ -3,5 +3,5 @@
|
|||||||
* @returns {boolean} True if the device is touch enabled, otherwise false
|
* @returns {boolean} True if the device is touch enabled, otherwise false
|
||||||
*/
|
*/
|
||||||
export function isTouch(): boolean {
|
export function isTouch(): boolean {
|
||||||
return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
return "ontouchstart" in window || navigator.maxTouchPoints > 0
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: [
|
content: ["./index.html", "./404.html", "./src/**/*.{js,ts,jsx,tsx,css,md,mdx,html,json,scss}"],
|
||||||
'./index.html', "./404.html",
|
darkMode: "class",
|
||||||
'./src/**/*.{js,ts,jsx,tsx,css,md,mdx,html,json,scss}',
|
theme: {
|
||||||
],
|
extend: {
|
||||||
darkMode: 'class',
|
colors: {
|
||||||
theme: {
|
"default-bg": "#181a1b"
|
||||||
extend: {
|
}
|
||||||
colors: {
|
}
|
||||||
"default-bg": "#181a1b",
|
},
|
||||||
}
|
plugins: []
|
||||||
},
|
}
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
};
|
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from "vite"
|
||||||
import solidPlugin from 'vite-plugin-solid';
|
import solidPlugin from "vite-plugin-solid"
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [solidPlugin()],
|
plugins: [solidPlugin()],
|
||||||
server: {
|
server: {
|
||||||
port: 3000,
|
port: 3000
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
target: 'esnext',
|
target: "esnext",
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: {
|
input: {
|
||||||
main: "index.html",
|
main: "index.html"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user