diff --git a/eslint.config.js b/eslint.config.js
index b19330b..cd2d447 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -1,23 +1,38 @@
-import js from '@eslint/js'
-import globals from 'globals'
-import reactHooks from 'eslint-plugin-react-hooks'
-import reactRefresh from 'eslint-plugin-react-refresh'
-import tseslint from 'typescript-eslint'
-import { defineConfig, globalIgnores } from 'eslint/config'
+import js from "@eslint/js"
+import globals from "globals"
+import reactHooks from "eslint-plugin-react-hooks"
+import reactRefresh from "eslint-plugin-react-refresh"
+import tseslint from "typescript-eslint"
+import { defineConfig, globalIgnores } from "eslint/config"
export default defineConfig([
- globalIgnores(['dist']),
+ globalIgnores(["dist"]),
{
- files: ['**/*.{ts,tsx}'],
+ files: ["**/*.{ts,tsx}"],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
- reactHooks.configs['recommended-latest'],
+ reactHooks.configs["recommended-latest"],
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
+ rules: {
+ "@typescript-eslint/no-unused-vars": [
+ "warn",
+ {
+ argsIgnorePattern: "^_",
+ varsIgnorePattern: "^_",
+ },
+ ],
+ },
+ },
+ {
+ files: ["test/**/*.{ts,tsx}"],
+ rules: {
+ "@typescript-eslint/no-explicit-any": "off",
+ },
},
])
diff --git a/src/App.css b/src/App.css
index ab28aa0..8ce14c8 100644
--- a/src/App.css
+++ b/src/App.css
@@ -82,6 +82,10 @@ button.movePage:hover{
}
+#root {
+ display: flex;
+ flex-direction: column;
+}
header {
position: sticky;
@@ -96,6 +100,7 @@ header {
align-items: center;
justify-content: center;
+ background-color: var(--accent-color);
backdrop-filter: blur(10px);
z-index: 1; /* Otherwise any translated elements render above the blur?? */
}
@@ -121,6 +126,14 @@ main {
flex-wrap: wrap;
}
+.min-height-0 {
+ min-height: 0;
+}
+
+.scroll-y {
+ overflow-y: scroll;
+}
+
.align-center {
align-items: center;
}
@@ -141,6 +154,10 @@ main {
gap: 1rem;
}
+.margin-0 {
+ margin: 0;
+}
+
.padding-sm {
padding: .25rem;
}
@@ -150,7 +167,19 @@ main {
.padding-lg {
padding: 1rem;
}
+.padding-b-sm {
+ padding-bottom: .25rem;
+}
+.padding-b-md {
+ padding-bottom: .5rem;
+}
+.padding-b-lg {
+ padding-bottom: 1rem;
+}
+.round-sm, .round-md, .round-lg {
+ overflow: hidden;
+}
.round-sm {
border-radius: .25rem;
}
@@ -159,4 +188,59 @@ main {
}
.round-lg {
border-radius: 1rem;
-}
\ No newline at end of file
+}
+
+.border-sm {
+ border: 1px solid canvastext;
+}
+.border-md {
+ border: 2px solid canvastext;
+}
+.border-lg {
+ border: 3px solid canvastext;
+}
+
+.font-small {
+ font-size: .75rem;
+}
+.font-medium {
+ font-size: 1rem;
+}
+.font-large {
+ font-size: 1.25rem;
+}
+.mono {
+ font-family: ui-monospace, monospace;
+}
+.bold {
+ font-weight: bold;
+}
+
+
+.clickable {
+ cursor: pointer;
+
+ &:hover {
+ text-decoration: underline;
+ }
+}
+.user-select-all {
+ -webkit-user-select: all;
+ user-select: all;
+}
+.user-select-none {
+ -webkit-user-select: none;
+ user-select: none;
+}
+button.no-button {
+ background: none;
+ border: none;
+ padding: 0;
+ cursor: pointer;
+ color: inherit;
+ text-decoration: none;
+
+ &:hover {
+ text-decoration: underline;
+ }
+}
diff --git a/src/App.tsx b/src/App.tsx
index 968c979..70fc815 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -4,23 +4,31 @@ import TemplatePage from './pages/TemplatePage/Template.tsx'
import Home from './pages/Home/Home.tsx'
import Robot from './pages/Robot/Robot.tsx';
import VisProg from "./pages/VisProgPage/VisProg.tsx";
+import {useState} from "react";
+import Logging from "./components/Logging/Logging.tsx";
function App(){
+ const [showLogs, setShowLogs] = useState(false);
+
return (
-
+ <>
Home
+
-
-
- } />
- } />
- } />
- } />
-
-
-
- )
+
+
+
+ } />
+ } />
+ } />
+ } />
+
+
+ {showLogs && }
+
+ >
+ );
}
export default App
diff --git a/src/components/Logging/Filters.module.css b/src/components/Logging/Filters.module.css
new file mode 100644
index 0000000..405560c
--- /dev/null
+++ b/src/components/Logging/Filters.module.css
@@ -0,0 +1,34 @@
+.filter-root {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.filter-panel {
+ position: absolute;
+ display: flex;
+ flex-direction: column;
+ gap: .25rem;
+ top: 0;
+ right: 0;
+ z-index: 1;
+ background: canvas;
+ box-shadow: 0 0 1rem rgba(0, 0, 0, 0.5);
+ width: 300px;
+
+ *:first-child {
+ margin-top: 0;
+ }
+ *:last-child {
+ margin-bottom: 0;
+ }
+}
+
+button.deletable {
+ cursor: pointer;
+
+ &:hover {
+ text-decoration: line-through;
+ }
+}
diff --git a/src/components/Logging/Filters.tsx b/src/components/Logging/Filters.tsx
new file mode 100644
index 0000000..446a9c6
--- /dev/null
+++ b/src/components/Logging/Filters.tsx
@@ -0,0 +1,200 @@
+import {useEffect, useRef, useState} from "react";
+
+import type {LogFilterPredicate} from "./useLogs.ts";
+
+import styles from "./Filters.module.css";
+
+type Setter = (value: T | ((prev: T) => T)) => void;
+
+const optionMapping = new Map([
+ ["ALL", 0],
+ ["DEBUG", 10],
+ ["INFO", 20],
+ ["WARNING", 30],
+ ["ERROR", 40],
+ ["CRITICAL", 50],
+ ["NONE", 999_999_999_999], // It is technically possible to have a higher level, but this is fine
+]);
+
+function LevelPredicateElement({
+ name,
+ level,
+ setLevel,
+ onDelete,
+}: {
+ name: string;
+ level: string;
+ setLevel: (level: string) => void;
+ onDelete?: () => void;
+}) {
+ const normalizedName = name.split(".").pop() || name;
+
+ return
+
+
+
+}
+
+const GLOBAL_LOG_LEVEL_PREDICATE_KEY = "global_log_level";
+
+function GlobalLevelFilter({
+ filterPredicates,
+ setFilterPredicates,
+}: {
+ filterPredicates: Map;
+ setFilterPredicates: Setter