From 17f00c696062d81c0fbd1e0d1c25cdfae765c6fe Mon Sep 17 00:00:00 2001 From: Ash Keel Date: Thu, 24 Nov 2022 19:49:25 +0100 Subject: [PATCH] feat: (incomplete) logging window --- frontend/package-lock.json | 118 +++++------ frontend/package.json | 6 +- frontend/package.json.md5 | 2 +- frontend/src/index.tsx | 2 - frontend/src/locale/en/translation.json | 9 + frontend/src/ui/App.tsx | 57 ++--- frontend/src/ui/components/LogViewer.tsx | 194 ++++++++++++++++++ frontend/src/ui/components/Sidebar.tsx | 17 +- .../src/ui/components/utils/Scrollbar.tsx | 64 ++++++ frontend/src/ui/theme/forms.ts | 36 +++- modules/http/safebool.go | 25 --- 11 files changed, 404 insertions(+), 126 deletions(-) create mode 100644 frontend/src/ui/components/LogViewer.tsx create mode 100644 frontend/src/ui/components/utils/Scrollbar.tsx delete mode 100644 modules/http/safebool.go diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 26cfb0a..9053c50 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,7 +16,10 @@ "@radix-ui/react-dialog": "^1.0.2", "@radix-ui/react-icons": "^1.1.1", "@radix-ui/react-label": "^2.0.0", + "@radix-ui/react-scroll-area": "^1.0.2", "@radix-ui/react-tabs": "^1.0.1", + "@radix-ui/react-toggle": "^1.0.1", + "@radix-ui/react-toggle-group": "^1.0.1", "@radix-ui/react-toolbar": "^1.0.1", "@redux-devtools/extension": "^3.2.3", "@reduxjs/toolkit": "^1.9.0", @@ -29,15 +32,12 @@ "i18next": "^22.0.6", "inter-ui": "^3.19.3", "normalize.css": "^8.0.1", - "overlayscrollbars": "^2.0.1", - "overlayscrollbars-react": "^0.5.0", "postcss-import": "^15.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^12.0.0", "react-redux": "^8.0.5", "react-router-dom": "^6.4.3", - "react-toastify": "^9.1.1", "redux-thunk": "^2.4.2", "sass": "^1.56.1", "typescript": "^4.9.3", @@ -708,6 +708,14 @@ "resolved": "https://registry.npmjs.org/@radix-ui/colors/-/colors-0.1.8.tgz", "integrity": "sha512-jwRMXYwC0hUo0mv6wGpuw254Pd9p/R6Td5xsRpOmaWkUHlooNWqVcadgyzlRumMq3xfOTXwJReU0Jv+EIy4Jbw==" }, + "node_modules/@radix-ui/number": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.0.tgz", + "integrity": "sha512-Ofwh/1HX69ZfJRiRBMTy7rgjAzHmwe4kW9C9Y99HTRUcYLUuVT0KESFj15rPjRgKJs20GPq8Bm5aEDJ8DuA3vA==", + "dependencies": { + "@babel/runtime": "^7.13.10" + } + }, "node_modules/@radix-ui/primitive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz", @@ -966,6 +974,27 @@ "react-dom": "^16.8 || ^17.0 || ^18.0" } }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.0.2.tgz", + "integrity": "sha512-k8VseTxI26kcKJaX0HPwkvlNBPTs56JRdYzcZ/vzrNUkDlvXBy8sMc7WvCpYzZkHgb+hd72VW9MqkqecGtuNgg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/number": "1.0.0", + "@radix-ui/primitive": "1.0.0", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-context": "1.0.0", + "@radix-ui/react-direction": "1.0.0", + "@radix-ui/react-presence": "1.0.0", + "@radix-ui/react-primitive": "1.0.1", + "@radix-ui/react-use-callback-ref": "1.0.0", + "@radix-ui/react-use-layout-effect": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, "node_modules/@radix-ui/react-separator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.0.1.tgz", @@ -1811,14 +1840,6 @@ "node": ">= 6" } }, - "node_modules/clsx": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", - "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==", - "engines": { - "node": ">=6" - } - }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -3921,20 +3942,6 @@ "node": ">= 0.8.0" } }, - "node_modules/overlayscrollbars": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/overlayscrollbars/-/overlayscrollbars-2.0.1.tgz", - "integrity": "sha512-tER9iKasFqcJiYdtspHbzlhJJg8kicyj/Oag/GRAK658rDouat2BGFfSFg3AgIw/Yc9CQ78AuX6ieHgg1wQw7Q==" - }, - "node_modules/overlayscrollbars-react": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/overlayscrollbars-react/-/overlayscrollbars-react-0.5.0.tgz", - "integrity": "sha512-uCNTnkfWW74veoiEv3kSwoLelKt4e8gTNv65D771X3il0x5g5Yo0fUbro7SpQzR9yNgi23cvB2mQHTTdQH96pA==", - "peerDependencies": { - "overlayscrollbars": "^2.0.0", - "react": ">=16.8.0" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -4350,18 +4357,6 @@ } } }, - "node_modules/react-toastify": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.1.tgz", - "integrity": "sha512-pkFCla1z3ve045qvjEmn2xOJOy4ZciwRXm1oMPULVkELi5aJdHCN/FHnuqXq8IwGDLB7PPk2/J6uP9D8ejuiRw==", - "dependencies": { - "clsx": "^1.1.1" - }, - "peerDependencies": { - "react": ">=16", - "react-dom": ">=16" - } - }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -5634,6 +5629,14 @@ "resolved": "https://registry.npmjs.org/@radix-ui/colors/-/colors-0.1.8.tgz", "integrity": "sha512-jwRMXYwC0hUo0mv6wGpuw254Pd9p/R6Td5xsRpOmaWkUHlooNWqVcadgyzlRumMq3xfOTXwJReU0Jv+EIy4Jbw==" }, + "@radix-ui/number": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.0.tgz", + "integrity": "sha512-Ofwh/1HX69ZfJRiRBMTy7rgjAzHmwe4kW9C9Y99HTRUcYLUuVT0KESFj15rPjRgKJs20GPq8Bm5aEDJ8DuA3vA==", + "requires": { + "@babel/runtime": "^7.13.10" + } + }, "@radix-ui/primitive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz", @@ -5831,6 +5834,23 @@ "@radix-ui/react-use-controllable-state": "1.0.0" } }, + "@radix-ui/react-scroll-area": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.0.2.tgz", + "integrity": "sha512-k8VseTxI26kcKJaX0HPwkvlNBPTs56JRdYzcZ/vzrNUkDlvXBy8sMc7WvCpYzZkHgb+hd72VW9MqkqecGtuNgg==", + "requires": { + "@babel/runtime": "^7.13.10", + "@radix-ui/number": "1.0.0", + "@radix-ui/primitive": "1.0.0", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-context": "1.0.0", + "@radix-ui/react-direction": "1.0.0", + "@radix-ui/react-presence": "1.0.0", + "@radix-ui/react-primitive": "1.0.1", + "@radix-ui/react-use-callback-ref": "1.0.0", + "@radix-ui/react-use-layout-effect": "1.0.0" + } + }, "@radix-ui/react-separator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.0.1.tgz", @@ -6403,11 +6423,6 @@ } } }, - "clsx": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", - "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==" - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -7859,17 +7874,6 @@ "word-wrap": "^1.2.3" } }, - "overlayscrollbars": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/overlayscrollbars/-/overlayscrollbars-2.0.1.tgz", - "integrity": "sha512-tER9iKasFqcJiYdtspHbzlhJJg8kicyj/Oag/GRAK658rDouat2BGFfSFg3AgIw/Yc9CQ78AuX6ieHgg1wQw7Q==" - }, - "overlayscrollbars-react": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/overlayscrollbars-react/-/overlayscrollbars-react-0.5.0.tgz", - "integrity": "sha512-uCNTnkfWW74veoiEv3kSwoLelKt4e8gTNv65D771X3il0x5g5Yo0fUbro7SpQzR9yNgi23cvB2mQHTTdQH96pA==", - "requires": {} - }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -8100,14 +8104,6 @@ "tslib": "^2.0.0" } }, - "react-toastify": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.1.tgz", - "integrity": "sha512-pkFCla1z3ve045qvjEmn2xOJOy4ZciwRXm1oMPULVkELi5aJdHCN/FHnuqXq8IwGDLB7PPk2/J6uP9D8ejuiRw==", - "requires": { - "clsx": "^1.1.1" - } - }, "read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 6ab3cdf..b464137 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,7 +11,10 @@ "@radix-ui/react-dialog": "^1.0.2", "@radix-ui/react-icons": "^1.1.1", "@radix-ui/react-label": "^2.0.0", + "@radix-ui/react-scroll-area": "^1.0.2", "@radix-ui/react-tabs": "^1.0.1", + "@radix-ui/react-toggle": "^1.0.1", + "@radix-ui/react-toggle-group": "^1.0.1", "@radix-ui/react-toolbar": "^1.0.1", "@redux-devtools/extension": "^3.2.3", "@reduxjs/toolkit": "^1.9.0", @@ -24,15 +27,12 @@ "i18next": "^22.0.6", "inter-ui": "^3.19.3", "normalize.css": "^8.0.1", - "overlayscrollbars": "^2.0.1", - "overlayscrollbars-react": "^0.5.0", "postcss-import": "^15.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^12.0.0", "react-redux": "^8.0.5", "react-router-dom": "^6.4.3", - "react-toastify": "^9.1.1", "redux-thunk": "^2.4.2", "sass": "^1.56.1", "typescript": "^4.9.3", diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 index 4c51e53..e70c688 100644 --- a/frontend/package.json.md5 +++ b/frontend/package.json.md5 @@ -1 +1 @@ -1de5a15d31eda16cf49dfabfe8142773 \ No newline at end of file +b120abf6c619728b1d1a33e9dda709c0 \ No newline at end of file diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 0a91fc1..11909b3 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -6,8 +6,6 @@ import { HashRouter } from 'react-router-dom'; import 'inter-ui/inter.css'; import '@fontsource/space-mono/index.css'; import 'normalize.css/normalize.css'; -import 'react-toastify/dist/ReactToastify.css'; -import 'overlayscrollbars/overlayscrollbars.css'; import './locale/setup'; diff --git a/frontend/src/locale/en/translation.json b/frontend/src/locale/en/translation.json index fdcf21a..5baa221 100644 --- a/frontend/src/locale/en/translation.json +++ b/frontend/src/locale/en/translation.json @@ -286,5 +286,14 @@ "text": "This page is still under construction, apologies for the lackluster view :(" }, "loading": "{{APPNAME}} is starting up, please wait!" + }, + "logging": { + "dialog-title": "Application logs", + "levelFilter": "Filter per log severity", + "level": { + "info": "Info", + "warn": "Warning", + "error": "Error" + } } } diff --git a/frontend/src/ui/App.tsx b/frontend/src/ui/App.tsx index 2be610d..5c39e38 100644 --- a/frontend/src/ui/App.tsx +++ b/frontend/src/ui/App.tsx @@ -13,7 +13,6 @@ import { t } from 'i18next'; import React, { useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import { Route, Routes } from 'react-router-dom'; -import { ToastContainer } from 'react-toastify'; import { GetKilovoltBind, @@ -43,6 +42,8 @@ import { APPNAME, styled } from './theme'; // @ts-expect-error Asset import import spinner from '../assets/icon-loading.svg'; +import Scrollbar from './components/utils/Scrollbar'; +import LogViewer from './components/LogViewer'; const LoadingDiv = styled('div', { display: 'flex', @@ -218,31 +219,37 @@ export default function App(): JSX.Element { return ( + - - - - } /> - } /> - } /> - } /> - } /> - } - /> - } - /> - } /> - } /> - } /> - } /> - - - - + + + + + } /> + } /> + } /> + } /> + } /> + } + /> + } + /> + } /> + } /> + } /> + } /> + + + + ); } diff --git a/frontend/src/ui/components/LogViewer.tsx b/frontend/src/ui/components/LogViewer.tsx new file mode 100644 index 0000000..789fbe4 --- /dev/null +++ b/frontend/src/ui/components/LogViewer.tsx @@ -0,0 +1,194 @@ +import { Cross2Icon } from '@radix-ui/react-icons'; +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; +import { RootState } from 'src/store'; +import * as DialogPrimitive from '@radix-ui/react-dialog'; +import { + Dialog, + DialogContainer, + DialogOverlay, + DialogTitle, + IconButton, + MultiToggle, + MultiToggleItem, + styled, +} from '../theme'; + +const Floating = styled('div', { + position: 'fixed', + top: '6px', + right: '10px', + display: 'flex', + gap: '3px', + zIndex: 10, +}); + +const LogBubble = styled('div', { + borderRadius: '6px', + minWidth: '10px', + minHeight: '10px', + backgroundColor: '$gray6', + color: '$gray11', + padding: '4px 5px 3px', + lineHeight: '0.7rem', + fontSize: '0.7rem', + cursor: 'pointer', + variants: { + level: { + info: {}, + warn: { + backgroundColor: '$yellow6', + color: '$yellow11', + }, + error: { + backgroundColor: '$red6', + color: '$red11', + }, + }, + }, +}); + +const emptyFilter = { + info: false, + warn: false, + error: false, +}; +type LogLevel = keyof typeof emptyFilter; +const levels: LogLevel[] = ['info', 'warn', 'error']; + +interface LogDialogProps { + initialFilter: LogLevel; +} + +const LevelToggle = styled(MultiToggleItem, { + variants: { + level: { + info: {}, + warn: { + backgroundColor: '$yellow4', + '&:hover': { + backgroundColor: '$yellow5', + }, + "&[data-state='on']": { + backgroundColor: '$yellow8', + }, + }, + error: { + backgroundColor: '$red4', + '&:hover': { + backgroundColor: '$red5', + }, + "&[data-state='on']": { + backgroundColor: '$red8', + }, + }, + }, + }, +}); + +function LogDialog({ initialFilter }: LogDialogProps) { + const logEntries = useSelector((state: RootState) => state.logging.messages); + const [filter, setFilter] = useState({ + ...emptyFilter, + [initialFilter]: true, + }); + const { t } = useTranslation(); + const enabled = levels.filter((level) => filter[level]); + + const count = logEntries.reduce((acc, entry) => { + if (entry.level in acc) { + acc[entry.level] += 1; + } else { + acc[entry.level] = 1; + } + return acc; + }, {} as Record); + + return ( + + + + + {t('logging.dialog-title')} + { + const newFilter = { ...emptyFilter }; + values.forEach((level) => { + newFilter[level] = true; + }); + setFilter(newFilter); + }} + > + {levels.map((level) => ( + + {t(`logging.level.${level}`)} ({count[level] ?? 0}) + + ))} + + + + + + + +

+
+
+ ); +} + +function LogViewer() { + const logEntries = useSelector((state: RootState) => state.logging.messages); + const [activeDialog, setActiveDialog] = useState(null); + + const count = logEntries.reduce((acc, entry) => { + if (entry.level in acc) { + acc[entry.level] += 1; + } else { + acc[entry.level] = 1; + } + return acc; + }, {} as Record); + + return ( +
+ + {levels.map((level) => + level in count && count[level] > 0 ? ( + setActiveDialog(level)} + > + {count[level]} + + ) : null, + )} + + + { + if (!open) { + // Reset dialog status on dialog close + setActiveDialog(null); + } + }} + > + {activeDialog ? : null} + +
+ ); +} + +export default React.memo(LogViewer); diff --git a/frontend/src/ui/components/Sidebar.tsx b/frontend/src/ui/components/Sidebar.tsx index cbf8f8d..bcff485 100644 --- a/frontend/src/ui/components/Sidebar.tsx +++ b/frontend/src/ui/components/Sidebar.tsx @@ -3,13 +3,14 @@ import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; import { Link, useMatch, useResolvedPath } from 'react-router-dom'; -import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; + import { RootState } from '../../store'; import { APPNAME, APPREPO } from '../theme'; +import BrowserLink from './BrowserLink'; +import Scrollbar from './utils/Scrollbar'; // @ts-expect-error Asset import import logo from '../../assets/icon-logo.svg'; -import BrowserLink from './BrowserLink'; export interface RouteSection { title: string; @@ -38,6 +39,7 @@ const Header = styled('div', { }); const AppName = styled('h1', { + userSelect: 'none', display: 'flex', alignItems: 'center', justifyContent: 'center', @@ -49,6 +51,7 @@ const AppName = styled('h1', { }); const AppLink = styled(Link, { + userSelect: 'none', all: 'unset', cursor: 'pointer', color: '$teal12', @@ -69,6 +72,7 @@ const AppLink = styled(Link, { }); const VersionLabel = styled('div', { + userSelect: 'none', textTransform: 'uppercase', fontSize: '0.75rem', fontWeight: 'bold', @@ -106,8 +110,10 @@ const MenuHeader = styled('header', { fontWeight: 'bold', padding: '0.5rem 0 0.5rem 0.8rem', color: '$teal9', + userSelect: 'none', }); const MenuLink = styled(Link, { + userSelect: 'none', color: '$teal13 !important', display: 'flex', alignItems: 'center', @@ -199,10 +205,7 @@ export default function Sidebar({ return ( - +
@@ -230,7 +233,7 @@ export default function Sidebar({ ))} ))} - + ); } diff --git a/frontend/src/ui/components/utils/Scrollbar.tsx b/frontend/src/ui/components/utils/Scrollbar.tsx new file mode 100644 index 0000000..4635643 --- /dev/null +++ b/frontend/src/ui/components/utils/Scrollbar.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import * as ScrollArea from '@radix-ui/react-scroll-area'; +import { styled } from '../../theme'; + +export interface ScrollbarProps { + vertical?: boolean; + horizontal?: boolean; + root?: React.CSSProperties; + viewport?: React.CSSProperties; +} + +const StyledScrollbar = styled(ScrollArea.Scrollbar, { + display: 'flex', + userSelect: 'none', + touchAction: 'none', + padding: '2px', + background: '$blackA6', + transition: 'background 160ms ease-out', + '&:hover': { + background: '$blackA8', + }, +}); + +const StyledThumb = styled(ScrollArea.Thumb, { + flex: '1', + background: '$teal6', + borderRadius: '10px', + position: 'relative', + '&:hover': { + background: '$teal8', + }, +}); + +function Scrollbar({ + vertical, + horizontal, + root, + viewport, + children, +}: React.PropsWithChildren): React.ReactElement { + return ( + + + {children} + + {vertical ? ( + + + + ) : null} + {horizontal ? ( + + + + ) : null} + + + ); +} + +export default React.memo(Scrollbar); diff --git a/frontend/src/ui/theme/forms.ts b/frontend/src/ui/theme/forms.ts index 50a85c4..472f269 100644 --- a/frontend/src/ui/theme/forms.ts +++ b/frontend/src/ui/theme/forms.ts @@ -1,5 +1,6 @@ import * as UnstyledLabel from '@radix-ui/react-label'; import * as CheckboxPrimitive from '@radix-ui/react-checkbox'; +import * as ToggleGroup from '@radix-ui/react-toggle-group'; import { styled } from './theme'; import { theme } from '.'; @@ -115,15 +116,15 @@ export const MultiButton = styled('div', { display: 'flex', }); -export const Button = styled('button', { +const button = { all: 'unset', cursor: 'pointer', userSelect: 'none', color: '$gray12', fontWeight: '300', - padding: '0.5rem 1rem', borderRadius: theme.borderRadius.form, fontSize: '1.1rem', + padding: '0.5rem 1rem', border: '1px solid $gray6', backgroundColor: '$gray4', display: 'flex', @@ -220,6 +221,37 @@ export const Button = styled('button', { }, }, }, +}; + +export const MultiToggle = styled(ToggleGroup.Root, { + display: 'inline-flex', + borderRadius: theme.borderRadius.form, + backgroundColor: '$gray4', +}); + +export const MultiToggleItem = styled(ToggleGroup.Item, { + ...button, + borderRadius: 0, + border: 0, + '&:first-child': { + borderTopLeftRadius: theme.borderRadius.form, + borderBottomLeftRadius: theme.borderRadius.form, + }, + '&:last-child': { + borderTopRightRadius: theme.borderRadius.form, + borderBottomRightRadius: theme.borderRadius.form, + }, + '&:hover': { + ...button['&:hover'], + }, + "&[data-state='on']": { + ...button['&:active'], + background: '$gray8', + }, +}); + +export const Button = styled('button', { + ...button, }); export const ComboBox = styled('select', { diff --git a/modules/http/safebool.go b/modules/http/safebool.go deleted file mode 100644 index 3084a98..0000000 --- a/modules/http/safebool.go +++ /dev/null @@ -1,25 +0,0 @@ -package http - -import "sync" - -type SafeBool struct { - val bool - mux sync.RWMutex -} - -func newSafeBool(val bool) *SafeBool { - return &SafeBool{val: val, mux: sync.RWMutex{}} -} - -func (s *SafeBool) Set(val bool) { - s.mux.Lock() - s.val = val - s.mux.Unlock() -} - -func (s *SafeBool) Get() bool { - s.mux.RLock() - val := s.val - s.mux.RUnlock() - return val -}