Compare commits

...

9 Commits

Author SHA1 Message Date
Piotr Rogowski 4f9afdefe9
Bump dependencies 2023-09-22 19:10:07 +02:00
Piotr Rogowski 43d55dc71e
Fix lint 2023-09-22 19:06:30 +02:00
Piotr Rogowski 8a513dfae8
Prompt user to reload when new version is present 2023-09-22 19:04:05 +02:00
Piotr Rogowski 6af0f62fc9
Adjust autocomplete batch size 2023-09-19 13:29:50 +02:00
Piotr Rogowski 54b71dea19
Update dependencies 2023-09-19 13:26:19 +02:00
Piotr Rogowski badc20db2e
Switch to Biome 2023-09-14 21:58:19 +02:00
Piotr Rogowski 4c7da032af
Update internal deps 2023-09-11 17:05:31 +02:00
Piotr Rogowski a42d9a2bcf
Bump other deps 2023-09-11 16:58:15 +02:00
Piotr Rogowski 572be519cb
Update internal dependencies 2023-09-11 16:37:25 +02:00
46 changed files with 1725 additions and 1353 deletions

View File

@ -19,7 +19,7 @@
"editorconfig.editorconfig",
"davidanson.vscode-markdownlint",
"streetsidesoftware.code-spell-checker",
"rome.rome"
"biomejs.biome"
]
}
},

View File

@ -1,10 +1,8 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"editorconfig.editorconfig",
"davidanson.vscode-markdownlint",
"streetsidesoftware.code-spell-checker",
"rome.rome"
"biomejs.biome"
]
}

29
.vscode/settings.json vendored
View File

@ -1,8 +1,23 @@
{
"typescript.tsdk": "./node_modules/typescript/lib",
"[jsonc]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[less]": {
"editor.defaultFormatter": "vscode.css-language-features"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"editor.formatOnSave": true,
"cSpell.words": [
"baro",
"biomejs",
"Codespaces",
"devcontainer",
"devcontainers",
"FOME",
"hypertuner",
@ -18,17 +33,5 @@
"typegen",
"vite",
"vitejs"
],
"[jsonc]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[less]": {
"editor.defaultFormatter": "vscode.css-language-features"
},
"[typescript]": {
"editor.defaultFormatter": "rome.rome"
},
"[javascript]": {
"editor.defaultFormatter": "rome.rome"
}
]
}

View File

@ -1,13 +1,13 @@
{
"$schema": "https://biomejs.dev/schemas/1.1.2/schema.json",
"organizeImports": {
"enabled": true
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"lineWidth": 100,
"indentSize": 2,
"ignore": [
"node_modules",
"build"
]
"indentSize": 2
},
"javascript": {
"formatter": {
@ -15,6 +15,13 @@
"trailingComma": "all"
}
},
"files": {
"ignore": [
".devcontainer",
".vscode",
"node_modules"
]
},
"linter": {
"enabled": true,
"rules": {
@ -27,7 +34,8 @@
"all": true,
"noImplicitBoolean": "off",
"useEnumInitializers": "off",
"noNonNullAssertion": "off"
"noNonNullAssertion": "off",
"useNamingConvention": "off"
},
"suspicious": {
"all": true,
@ -36,6 +44,11 @@
"nursery": {
"all": true,
"useExhaustiveDependencies": "off",
"useImportRestrictions": "off",
"noExcessiveComplexity": "off",
"noConfusingVoidType": "off"
},
"complexity": {
"noForEach": "off"
}
}

1917
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,27 +12,27 @@
},
"scripts": {
"start": "vite",
"build": "tsc && vite build",
"build": "tsc && vite build && generate-version",
"serve": "vite preview",
"lint": "tsc && npm run lint:rome",
"lint:rome": "rome ci src",
"lint:fix": "rome format --write src && rome check --apply src",
"lint:fix:unsafe": "rome check --apply-unsafe src",
"lint": "npm run lint:biome && tsc",
"lint:biome": "biome check src",
"lint:fix": "biome format --write src && biome check --apply src",
"lint:fix:unsafe": "biome check src --apply-unsafe src",
"analyze": "npm run build && open stats.html",
"typegen": "pocketbase-typegen --json ../cloud-backend/pb_schema.json --out src/@types/pocketbase-types.ts"
},
"dependencies": {
"@hyper-tuner/ini": "git+https://github.com/hyper-tuner/ini.git",
"@hyper-tuner/types": "git+https://github.com/hyper-tuner/types.git",
"@hyper-tuner/ini": "github:hyper-tuner/ini",
"@hyper-tuner/types": "github:hyper-tuner/types",
"@reduxjs/toolkit": "^1.9.5",
"@sentry/react": "^7.68.0",
"@sentry/tracing": "^7.68.0",
"@sentry/react": "^7.70.0",
"@sentry/tracing": "^7.70.0",
"antd": "^4.24.14",
"fuse.js": "^6.6.2",
"kbar": "^0.1.0-beta.43",
"lodash.debounce": "^4.0.8",
"mlg-converter": "^0.9.0",
"nanoid": "^4.0.2",
"nanoid": "^5.0.1",
"pako": "^2.1.0",
"pocketbase": "^0.18.0",
"react": "^18.2.0",
@ -41,17 +41,19 @@
"react-markdown": "^8.0.7",
"react-perfect-scrollbar": "^1.5.8",
"react-redux": "^8.1.2",
"react-router-dom": "^6.15.0",
"uplot": "^1.6.25",
"react-router-dom": "^6.16.0",
"react-update-notification": "^1.2.0",
"uplot": "^1.6.26",
"uplot-react": "^1.1.5",
"vite": "^4.4.9"
},
"devDependencies": {
"@biomejs/biome": "1.2.2",
"@total-typescript/ts-reset": "^0.5.1",
"@types/lodash.debounce": "^4.0.7",
"@types/node": "^20.6.0",
"@types/node": "^20.6.3",
"@types/pako": "^2.0.0",
"@types/react": "^18.2.21",
"@types/react": "^18.2.22",
"@types/react-dom": "^18.2.7",
"@types/react-redux": "^7.1.26",
"@types/react-router-dom": "^5.3.3",
@ -59,7 +61,6 @@
"less": "^4.2.0",
"pocketbase-typegen": "^1.1.13",
"rollup-plugin-visualizer": "^5.9.2",
"rome": "^12.1.3",
"typescript": "^5.2.2",
"vite-plugin-html": "^3.2.0",
"vite-plugin-pwa": "^0.16.5"

View File

@ -1,36 +1,38 @@
import { ClockCircleOutlined, ReloadOutlined } from '@ant-design/icons';
import { INI } from '@hyper-tuner/ini';
import { Layout, Modal, Result } from 'antd';
import { ReactNode, Suspense, lazy, useCallback, useEffect, useState } from 'react';
import { connect } from 'react-redux';
import {
Routes as ReactRoutes,
Route,
Routes as ReactRoutes,
generatePath,
useMatch,
useNavigate,
generatePath,
} from 'react-router-dom';
import { Layout, Result } from 'antd';
import { connect } from 'react-redux';
import { INI } from '@hyper-tuner/ini';
import { lazy, ReactNode, Suspense, useCallback, useEffect, useState } from 'react';
import TopBar from './components/TopBar';
import { useUpdateCheck } from 'react-update-notification';
import { TunesResponse } from './@types/pocketbase-types';
import Loader from './components/Loader';
import StatusBar from './components/StatusBar';
import TopBar from './components/TopBar';
import { collapsedSidebarWidth, sidebarWidth } from './components/Tune/SideBar';
import { divider } from './data/constants';
import help from './data/help';
import standardDialogs from './data/standardDialogs';
import useDb from './hooks/useDb';
import useServerStorage from './hooks/useServerStorage';
import Hub from './pages/Hub';
import Info from './pages/Info';
import { FormRoles } from './pages/auth/Login';
import { iniLoadingError, tuneNotFound, tuneParsingError } from './pages/auth/notifications';
import { Routes } from './routes';
import store from './store';
import Loader from './components/Loader';
import { AppState, TuneDataState, UIState } from './types/state';
import useDb from './hooks/useDb';
import Info from './pages/Info';
import Hub from './pages/Hub';
import { FormRoles } from './pages/auth/Login';
import useServerStorage from './hooks/useServerStorage';
import TuneParser from './utils/tune/TuneParser';
import standardDialogs from './data/standardDialogs';
import help from './data/help';
import { iniLoadingError, tuneNotFound, tuneParsingError } from './pages/auth/notifications';
import { divider } from './data/constants';
import { collapsedSidebarWidth, sidebarWidth } from './components/Tune/SideBar';
import 'uplot/dist/uPlot.min.css';
import 'react-perfect-scrollbar/dist/css/styles.css';
import 'uplot/dist/uPlot.min.css';
import './css/App.less';
import { TunesResponse } from './@types/pocketbase-types';
const Tune = lazy(() => import('./pages/Tune'));
const Logs = lazy(() => import('./pages/Logs'));
@ -53,6 +55,41 @@ const mapStateToProps = (state: AppState) => ({
tuneData: state.tuneData,
});
const NewVersionPrompt = () => {
// fetch /version.json?v=timestamp every 10s
const { status, reloadPage } = useUpdateCheck({
type: 'interval',
interval: 10000,
ignoreServerCache: true,
});
const [open, setOpen] = useState(true);
if (status === 'checking' || status === 'current') {
return null;
}
return (
<Modal
title="New Version Available! 🚀"
open={open}
centered
maskClosable={false}
closable={false}
keyboard={false}
onOk={reloadPage}
okText="Reload the page"
okButtonProps={{ icon: <ReloadOutlined /> }}
onCancel={() => setOpen(false)}
cancelText="I'll do it later"
cancelButtonProps={{ icon: <ClockCircleOutlined /> }}
destroyOnClose
>
<p>To enjoy the new features, it's time to refresh the page!</p>
<p>You can refresh later at your convenience.</p>
</Modal>
);
};
const App = ({ ui, tuneData }: { ui: UIState; tuneData: TuneDataState }) => {
const margin = ui.sidebarCollapsed ? collapsedSidebarWidth : sidebarWidth;
const { getTune } = useDb();
@ -159,7 +196,7 @@ const App = ({ ui, tuneData }: { ui: UIState; tuneData: TuneDataState }) => {
return (
<Layout style={{ marginLeft }}>
<Layout className='app-content'>
<Layout className="app-content">
<Content>
<Suspense fallback={<Loader />}>{element}</Suspense>
</Content>
@ -174,6 +211,7 @@ const App = ({ ui, tuneData }: { ui: UIState; tuneData: TuneDataState }) => {
return (
<>
<NewVersionPrompt />
<Layout>
<TopBar tuneData={tuneData} />
<ReactRoutes>
@ -231,11 +269,11 @@ const App = ({ ui, tuneData }: { ui: UIState; tuneData: TuneDataState }) => {
/>
<Route
path='*'
path="*"
element={
<ContentFor
element={
<Result status='warning' title='Page not found' style={{ marginTop: 50 }} />
<Result status="warning" title="Page not found" style={{ marginTop: 50 }} />
}
/>
}

View File

@ -1,12 +1,12 @@
import { Space, Tooltip } from 'antd';
import { CheckCircleFilled } from '@ant-design/icons';
import { Space, Tooltip } from 'antd';
import { UsersResponse } from '../@types/pocketbase-types';
import { Colors } from '../utils/colors';
const AuthorName = ({ author }: { author: UsersResponse }) => (
<Space>
{author.verifiedAuthor === true && (
<Tooltip title='Verified author'>
<Tooltip title="Verified author">
<CheckCircleFilled style={{ color: Colors.ACCENT }} />
</Tooltip>
)}

View File

@ -1,59 +1,51 @@
import React, {
CSSProperties,
forwardRef,
Fragment,
Ref,
useMemo,
ReactNode,
useCallback,
} from 'react';
import {
ActionId,
KBarAnimator,
KBarProvider,
KBarPortal,
KBarPositioner,
KBarSearch,
KBarResults,
useMatches,
ActionImpl,
Action,
useRegisterActions,
} from 'kbar';
import { connect } from 'react-redux';
import {
CloudUploadOutlined,
LoginOutlined,
UserAddOutlined,
LogoutOutlined,
InfoCircleOutlined,
FundOutlined,
SettingOutlined,
CarOutlined,
CloudUploadOutlined,
FundOutlined,
InfoCircleOutlined,
LoginOutlined,
LogoutOutlined,
SettingOutlined,
UserAddOutlined,
} from '@ant-design/icons';
import { useNavigate, generatePath } from 'react-router';
import {
Config as ConfigType,
Tune as TuneType,
Menus as MenusType,
Menu as MenuType,
SubMenu as SubMenuType,
GroupMenu as GroupMenuType,
GroupChildMenu as GroupChildMenuType,
GroupMenu as GroupMenuType,
Menu as MenuType,
Menus as MenusType,
SubMenu as SubMenuType,
Tune as TuneType,
} from '@hyper-tuner/types';
import { Routes } from '../routes';
import {
Action,
ActionId,
ActionImpl,
KBarAnimator,
KBarPortal,
KBarPositioner,
KBarProvider,
KBarResults,
KBarSearch,
useMatches,
useRegisterActions,
} from 'kbar';
import { CSSProperties, Fragment, ReactNode, Ref, forwardRef, useCallback, useMemo } from 'react';
import { connect } from 'react-redux';
import { generatePath, useNavigate } from 'react-router';
import { useAuth } from '../contexts/AuthContext';
import { logOutSuccessful } from '../pages/auth/notifications';
import { Routes } from '../routes';
import store from '../store';
import { isMac } from '../utils/env';
import { AppState, NavigationState } from '../types/state';
import { isMac } from '../utils/env';
import Icon from './SideBar/Icon';
import {
buildDialogUrl,
buildGroupMenuDialogUrl,
SKIP_MENUS,
SKIP_SUB_MENUS,
buildDialogUrl,
buildGroupMenuDialogUrl,
} from './Tune/SideBar';
import Icon from './SideBar/Icon';
enum Sections {
NAVIGATION = 'Navigation',
@ -172,7 +164,7 @@ const ResultItem = forwardRef(
</div>
{action.shortcut?.length ? (
<div aria-hidden style={{ display: 'grid', gridAutoFlow: 'column', gap: '4px' }}>
{action.shortcut.map((sc: React.Key) => (
{action.shortcut.map((sc) => (
<kbd
key={sc}
style={{

View File

@ -1,7 +1,7 @@
import { Skeleton } from 'antd';
const Loader = () => (
<div className='small-container'>
<div className="small-container">
<Skeleton active />
</div>
);

View File

@ -1,12 +1,12 @@
import { useCallback } from 'react';
import { Logs } from '@hyper-tuner/types';
import { Space } from 'antd';
import UplotReact from 'uplot-react';
import { useCallback } from 'react';
import uPlot from 'uplot';
import { colorHsl, formatNumberMs } from '../../utils/numbers';
import UplotReact from 'uplot-react';
import { Colors } from '../../utils/colors';
import touchZoomPlugin from '../../utils/uPlot/touchZoomPlugin';
import { colorHsl, formatNumberMs } from '../../utils/numbers';
import { isNumber } from '../../utils/tune/expression';
import touchZoomPlugin from '../../utils/uPlot/touchZoomPlugin';
export interface SelectedField {
name: string;
@ -174,7 +174,7 @@ const LogCanvas = ({
}
return (
<Space direction='vertical' size='large'>
<Space direction="vertical" size="large">
<UplotReact key={`first-${selectedFields1.join('-')}`} options={options1} data={plotData1} />
{!showSingleGraph && (
<UplotReact

View File

@ -1,12 +1,12 @@
import { useEffect, useState } from 'react';
import { StarFilled, StarOutlined } from '@ant-design/icons';
import { Badge, Button, Space, Tooltip } from 'antd';
import { StarOutlined, StarFilled } from '@ant-design/icons';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Colors } from '../utils/colors';
import { TuneDataState } from '../types/state';
import useDb from '../hooks/useDb';
import { useAuth } from '../contexts/AuthContext';
import useDb from '../hooks/useDb';
import { Routes } from '../routes';
import { TuneDataState } from '../types/state';
import { Colors } from '../utils/colors';
const StarButton = ({ tuneData }: { tuneData: TuneDataState }) => {
const navigate = useNavigate();
@ -65,8 +65,8 @@ const StarButton = ({ tuneData }: { tuneData: TuneDataState }) => {
return (
<div style={{ textAlign: 'right' }}>
<Tooltip
title='You must be signed in to star a tune'
placement='bottom'
title="You must be signed in to star a tune"
placement="bottom"
trigger={currentUserToken ? 'none' : 'hover'}
>
<Button

View File

@ -1,10 +1,10 @@
import { GithubOutlined, InfoCircleOutlined } from '@ant-design/icons';
import { Col, Layout, Row, Space, Typography } from 'antd';
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { Layout, Space, Row, Col, Typography } from 'antd';
import { InfoCircleOutlined, GithubOutlined } from '@ant-design/icons';
import { connect } from 'react-redux';
import { AppState, TuneState } from '../types/state';
import { Link } from 'react-router-dom';
import { Routes } from '../routes';
import { AppState, TuneState } from '../types/state';
const { Footer } = Layout;
@ -36,7 +36,7 @@ const Firmware = ({ tune }: { tune: TuneState }) => {
};
const StatusBar = ({ tune }: { tune: TuneState }) => (
<Footer className='app-status-bar'>
<Footer className="app-status-bar">
<Row>
<Col span={20}>{tune?.details?.author && <Firmware tune={tune} />}</Col>
<Col span={4} style={{ textAlign: 'right' }}>

View File

@ -1,39 +1,39 @@
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import { useLocation, useNavigate, Link, generatePath, useMatch } from 'react-router-dom';
import { Layout, Space, Button, Row, Col, Tooltip, Grid, Dropdown, Typography, Radio } from 'antd';
import {
UserOutlined,
CloudUploadOutlined,
CarOutlined,
CloudDownloadOutlined,
SettingOutlined,
LoginOutlined,
LineChartOutlined,
SlidersOutlined,
FileZipOutlined,
CloudUploadOutlined,
DesktopOutlined,
DownOutlined,
SearchOutlined,
ToolOutlined,
FundOutlined,
UserAddOutlined,
LogoutOutlined,
InfoCircleOutlined,
CarOutlined,
FileTextOutlined,
FileExcelOutlined,
FileTextOutlined,
FileZipOutlined,
FundOutlined,
InfoCircleOutlined,
LineChartOutlined,
LoginOutlined,
LogoutOutlined,
SearchOutlined,
SettingOutlined,
SlidersOutlined,
ToolOutlined,
UserAddOutlined,
UserOutlined,
} from '@ant-design/icons';
import { Button, Col, Dropdown, Grid, Layout, Radio, Row, Space, Tooltip, Typography } from 'antd';
import { useKBar } from 'kbar';
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import { Link, generatePath, useLocation, useMatch, useNavigate } from 'react-router-dom';
import { Collections } from '../@types/pocketbase-types';
import { useAuth } from '../contexts/AuthContext';
import useDb from '../hooks/useDb';
import useServerStorage from '../hooks/useServerStorage';
import { logOutSuccessful } from '../pages/auth/notifications';
import { removeFilenameSuffix } from '../pocketbase';
import { Routes } from '../routes';
import store from '../store';
import { TuneDataState } from '../types/state';
import { isMac } from '../utils/env';
import { isToggleSidebar } from '../utils/keyboard/shortcuts';
import { Routes } from '../routes';
import { useAuth } from '../contexts/AuthContext';
import { logOutSuccessful } from '../pages/auth/notifications';
import { TuneDataState } from '../types/state';
import { removeFilenameSuffix } from '../pocketbase';
import useServerStorage from '../hooks/useServerStorage';
import useDb from '../hooks/useDb';
import { Collections } from '../@types/pocketbase-types';
import { buildHyperTunerAppLink } from '../utils/url';
const { Header } = Layout;
@ -179,8 +179,8 @@ const TopBar = ({
tuneRootMatch?.pathname ||
hubPathMatch?.pathname
}
optionType='button'
buttonStyle='solid'
optionType="button"
buttonStyle="solid"
onChange={(e) => navigate(e.target.value)}
>
<Radio.Button value={buildTuneUrl(Routes.HUB)}>
@ -254,11 +254,11 @@ const TopBar = ({
const list = [];
if (lg) {
list.push(<span key='download-text'>Download</span>);
list.push(<span key="download-text">Download</span>);
}
if (sm) {
list.push(<DownOutlined key='download-icon' />);
list.push(<DownOutlined key="download-icon" />);
}
return list.length ? list : null;
@ -311,7 +311,7 @@ const TopBar = ({
];
return (
<Header className='app-top-bar' style={xs ? { padding: '0 5px' } : {}}>
<Header className="app-top-bar" style={xs ? { padding: '0 5px' } : {}}>
<Row>
{tuneData?.tuneId ? (
tabs
@ -344,15 +344,15 @@ const TopBar = ({
</Button>
</Link>
{tuneData?.tuneId && (
<Dropdown menu={{ items: downloadItems }} placement='bottom' trigger={['click']}>
<Dropdown menu={{ items: downloadItems }} placement="bottom" trigger={['click']}>
<Button icon={<CloudDownloadOutlined />}>{downloadButton}</Button>
</Dropdown>
)}
<Dropdown menu={{ items: userMenuItems }} placement='bottomRight' trigger={['click']}>
<Dropdown menu={{ items: userMenuItems }} placement="bottomRight" trigger={['click']}>
<Button icon={<UserOutlined />}>{sm && <DownOutlined />}</Button>
</Dropdown>
{/* dummy anchor for file download */}
<a href='#download' ref={downloadAnchorRef} style={{ display: 'none' }}>
<a href="#download" ref={downloadAnchorRef} style={{ display: 'none' }}>
download
</a>
</Space>

View File

@ -1,9 +1,9 @@
import { useEffect, useState } from 'react';
import UplotReact from 'uplot-react';
import uPlot from 'uplot';
import touchZoomPlugin from '../../utils/uPlot/touchZoomPlugin';
import { CompositeLogEntry } from '../../utils/logs/TriggerLogsParser';
import UplotReact from 'uplot-react';
import { Colors } from '../../utils/colors';
import { CompositeLogEntry } from '../../utils/logs/TriggerLogsParser';
import touchZoomPlugin from '../../utils/uPlot/touchZoomPlugin';
import LogsPagination from './LogsPagination';
const scale = 2;

View File

@ -1,9 +1,9 @@
import { useEffect, useState } from 'react';
import UplotReact from 'uplot-react';
import uPlot from 'uplot';
import touchZoomPlugin from '../../utils/uPlot/touchZoomPlugin';
import { ToothLogEntry, EntryType } from '../../utils/logs/TriggerLogsParser';
import UplotReact from 'uplot-react';
import { Colors } from '../../utils/colors';
import { EntryType, ToothLogEntry } from '../../utils/logs/TriggerLogsParser';
import touchZoomPlugin from '../../utils/uPlot/touchZoomPlugin';
import LogsPagination from './LogsPagination';
const { bars } = uPlot.paths;

View File

@ -1,29 +1,29 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { connect } from 'react-redux';
import { Form, Divider, Col, Row, Popover, Space, Result } from 'antd';
import { QuestionCircleOutlined } from '@ant-design/icons';
import {
Dialogs as DialogsType,
Dialog as DialogType,
Config as ConfigType,
Field as FieldType,
Curve as CurveType,
Table as TableType,
ScalarConstant as ScalarConstantType,
ConstantTypes,
Curve as CurveType,
Dialog as DialogType,
Dialogs as DialogsType,
Field as FieldType,
ScalarConstant as ScalarConstantType,
Table as TableType,
Tune as TuneType,
} from '@hyper-tuner/types';
import { AppState, UIState } from '../../types/state';
import SmartSelect from './Dialog/SmartSelect';
import SmartNumber from './Dialog/SmartNumber';
import TextField from './Dialog/TextField';
import Curve from './Dialog/Curve';
import { parseXy, parseZ } from '../../utils/tune/table';
import Map3D from './Dialog/Map3D';
import { evaluateExpression } from '../../utils/tune/expression';
import { Col, Divider, Form, Popover, Result, Row, Space } from 'antd';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { connect } from 'react-redux';
import useBrowserStorage from '../../hooks/useBrowserStorage';
import useConfig from '../../hooks/useConfig';
import { AppState, UIState } from '../../types/state';
import { evaluateExpression } from '../../utils/tune/expression';
import { parseXy, parseZ } from '../../utils/tune/table';
import Loader from '../Loader';
import Curve from './Dialog/Curve';
import Map3D from './Dialog/Map3D';
import SmartNumber from './Dialog/SmartNumber';
import SmartSelect from './Dialog/SmartSelect';
import TextField from './Dialog/TextField';
interface DialogsAndCurves {
[name: string]: DialogType | CurveType | TableType;
@ -88,11 +88,11 @@ const Dialog = ({
link && (
<Popover
content={
<a href={`${link}`} target='__blank' rel='noopener noreferrer'>
<a href={`${link}`} target="__blank" rel="noopener noreferrer">
{link}
</a>
}
placement='right'
placement="right"
>
<QuestionCircleOutlined />
</Popover>
@ -353,7 +353,7 @@ const Dialog = ({
if (!dialogConfig) {
if (curveConfig) {
return (
<div ref={containerRef} className='large-container'>
<div ref={containerRef} className="large-container">
<Divider>{curveConfig.title}</Divider>
{renderCurve(curveConfig)}
</div>
@ -362,7 +362,7 @@ const Dialog = ({
if (tableConfig) {
return (
<div ref={containerRef} className='large-container'>
<div ref={containerRef} className="large-container">
{renderHelp(tableConfig.help)}
<Divider>{tableConfig.title}</Divider>
{renderTable(tableConfig)}
@ -370,11 +370,11 @@ const Dialog = ({
);
}
return <Result status='warning' title='Dialog not found' style={{ marginTop: 50 }} />;
return <Result status="warning" title="Dialog not found" style={{ marginTop: 50 }} />;
}
return (
<div ref={containerRef} className='large-container'>
<div ref={containerRef} className="large-container">
{renderHelp(dialogConfig?.help)}
<Form labelCol={{ span: 10 }} wrapperCol={{ span: 10 }}>
<Row gutter={20}>{panelsComponents}</Row>

View File

@ -1,10 +1,10 @@
import { Typography, Grid } from 'antd';
import { Grid, Typography } from 'antd';
import { useCallback, useEffect, useRef, useState } from 'react';
import UplotReact from 'uplot-react';
import uPlot from 'uplot';
import UplotReact from 'uplot-react';
import { Colors } from '../../../utils/colors';
import LandscapeNotice from './LandscapeNotice';
import Table from './Curve/Table';
import LandscapeNotice from './LandscapeNotice';
const { useBreakpoint } = Grid;
@ -87,7 +87,7 @@ const Curve = ({
return (
<>
<Typography.Paragraph>
<Typography.Text type='secondary'>{help}</Typography.Text>
<Typography.Text type="secondary">{help}</Typography.Text>
</Typography.Paragraph>
<UplotReact options={options!} data={plotData!} />
<div ref={containerRef}>

View File

@ -27,7 +27,7 @@ const Table = ({
return (
<td
className='value'
className="value"
key={`${axis}-${index}-${value}-${hue}${sat}${light}`}
style={{ backgroundColor: `hsl(${hue}, ${sat}%, ${light}%)` }}
>
@ -39,15 +39,15 @@ const Table = ({
);
return (
<div className='table'>
<div className="table">
<table>
<tbody>
<tr>
<td {...titleProps} className='title-curve' key={yLabel}>{`${yLabel} (${yUnits})`}</td>
<td {...titleProps} className="title-curve" key={yLabel}>{`${yLabel} (${yUnits})`}</td>
{renderRow('y', yData)}
</tr>
<tr>
<td {...titleProps} className='title-curve' key={xLabel}>{`${xLabel} (${xUnits})`}</td>
<td {...titleProps} className="title-curve" key={xLabel}>{`${xLabel} (${xUnits})`}</td>
{renderRow('x', xData)}
</tr>
</tbody>

View File

@ -1,8 +1,8 @@
import { Result } from 'antd';
import { RotateLeftOutlined } from '@ant-design/icons';
import { Result } from 'antd';
const LandscapeNotice = () => (
<Result title='Turn your device to landscape mode' icon={<RotateLeftOutlined />} />
<Result title="Turn your device to landscape mode" icon={<RotateLeftOutlined />} />
);
export default LandscapeNotice;

View File

@ -1,6 +1,6 @@
import { Grid } from 'antd';
import LandscapeNotice from './LandscapeNotice';
import { colorHsl, formatNumber } from '../../../utils/numbers';
import LandscapeNotice from './LandscapeNotice';
const { useBreakpoint } = Grid;
@ -33,7 +33,7 @@ const Map3D = ({
if (index === 0) {
result.push(
<td {...titleProps} className='title-map' key={`y-${yValue}`}>
<td {...titleProps} className="title-map" key={`y-${yValue}`}>
{`${yValue}`}
</td>,
);
@ -41,7 +41,7 @@ const Map3D = ({
result.push(
<td
className='value'
className="value"
key={`${rowIndex}-${index}-${value}-${hue}${sat}${light}`}
style={{ backgroundColor: `hsl(${hue}, ${sat}%, ${light}%)` }}
>
@ -57,14 +57,14 @@ const Map3D = ({
}
return (
<div className='table'>
<div className="table">
<table>
<tbody>
{zData.map((row, i) => (
<tr key={`row-${i}`}>{renderRow(i, row)}</tr>
))}
<tr>
<td {...titleProps} className='title-map'>
<td {...titleProps} className="title-map">
{yUnits} / {xUnits}
</td>
{xData.map((xValue, l) => (

View File

@ -1,6 +1,6 @@
import { Radio, Select, Switch } from 'antd';
import { CheckOutlined, CloseOutlined } from '@ant-design/icons';
import { Switches } from '@hyper-tuner/types';
import { Radio, Select, Switch } from 'antd';
const SmartSelect = ({
values,
@ -29,8 +29,8 @@ const SmartSelect = ({
return (
<Radio.Group
value={values.indexOf(defaultValue)}
optionType='button'
buttonStyle='solid'
optionType="button"
buttonStyle="solid"
disabled={disabled}
>
{values.map((val: string, index) => (
@ -45,7 +45,7 @@ const SmartSelect = ({
return (
<Select
value={values.indexOf(defaultValue)}
optionFilterProp='label'
optionFilterProp="label"
disabled={disabled}
style={{ maxWidth: 250 }}
>

View File

@ -1,4 +1,4 @@
import { Typography, Alert } from 'antd';
import { Alert, Typography } from 'antd';
const TextField = ({ title }: { title: string }) => {
const types: { [char: string]: 'info' | 'warning' } = {
@ -20,7 +20,7 @@ const TextField = ({ title }: { title: string }) => {
const parts = matches.map((part) => {
if (urlPattern.test(part)) {
return (
<a href={part} target='_blank' rel='noreferrer' style={{ color: 'inherit' }}>
<a href={part} target="_blank" rel="noreferrer" style={{ color: 'inherit' }}>
{part}
</a>
);
@ -36,7 +36,7 @@ const TextField = ({ title }: { title: string }) => {
{type ? (
<Alert message={messageTag} type={type} showIcon style={{ width: '100%', maxWidth: 700 }} />
) : (
<Typography.Text type='secondary'>{title}</Typography.Text>
<Typography.Text type="secondary">{title}</Typography.Text>
)}
</Typography.Paragraph>
);

View File

@ -1,21 +1,21 @@
import { Layout, Menu } from 'antd';
import { ItemType } from 'antd/lib/menu/hooks/useItems';
import { connect } from 'react-redux';
import { generatePath, PathMatch, useNavigate } from 'react-router-dom';
import PerfectScrollbar from 'react-perfect-scrollbar';
import { useCallback, useEffect, useState } from 'react';
import {
Config as ConfigType,
Menus as MenusType,
Tune as TuneType,
SubMenu as SubMenuType,
GroupMenu as GroupMenuType,
GroupChildMenu as GroupChildMenuType,
GroupMenu as GroupMenuType,
Menus as MenusType,
SubMenu as SubMenuType,
Tune as TuneType,
} from '@hyper-tuner/types';
import store from '../../store';
import Icon from '../SideBar/Icon';
import { Layout, Menu } from 'antd';
import { ItemType } from 'antd/lib/menu/hooks/useItems';
import { useCallback, useEffect, useState } from 'react';
import PerfectScrollbar from 'react-perfect-scrollbar';
import { connect } from 'react-redux';
import { PathMatch, generatePath, useNavigate } from 'react-router-dom';
import { Routes } from '../../routes';
import store from '../../store';
import { AppState, NavigationState, UIState } from '../../types/state';
import Icon from '../SideBar/Icon';
const { Sider } = Layout;
@ -196,7 +196,7 @@ const SideBar = ({
};
return (
<Sider {...siderProps} className='app-sidebar'>
<Sider {...siderProps} className="app-sidebar">
<PerfectScrollbar options={{ suppressScrollX: true }}>
<Menu
defaultSelectedKeys={[
@ -205,7 +205,7 @@ const SideBar = ({
: matchedPath!.pathname,
]}
defaultOpenKeys={ui.sidebarCollapsed ? [] : defaultOpenSubmenus()}
mode='inline'
mode="inline"
style={{ height: '100%' }}
key={
matchedGroupMenuDialogPath ? matchedGroupMenuDialogPath.pathname : matchedPath!.pathname

View File

@ -1,8 +1,8 @@
import { createContext, ReactNode, useContext, useEffect, useMemo, useState } from 'react';
import { client, formatError, AuthMethodsList, ClientResponseError } from '../pocketbase';
import { buildRedirectUrl } from '../utils/url';
import { ReactNode, createContext, useContext, useEffect, useMemo, useState } from 'react';
import { Collections, UsersResponse } from '../@types/pocketbase-types';
import { AuthMethodsList, ClientResponseError, client, formatError } from '../pocketbase';
import { Routes } from '../routes';
import { buildRedirectUrl } from '../utils/url';
export enum OAuthProviders {
GOOGLE = 'google',
@ -27,7 +27,7 @@ interface AuthValue {
}
const AuthContext = createContext<AuthValue | null>(null);
// rome-ignore lint/nursery/useHookAtTopLevel: <explanation>
// biome-ignore lint/nursery/useHookAtTopLevel: False positive
const useAuth = () => useContext<AuthValue>(AuthContext as any);
const users = client.collection(Collections.Users);

View File

@ -1,12 +1,12 @@
import { useMemo } from 'react';
import {
Config as ConfigType,
Page as PageType,
Constant,
OutputChannel,
SimpleConstant,
DatalogEntry,
OutputChannel,
Page as PageType,
SimpleConstant,
} from '@hyper-tuner/types';
import { useMemo } from 'react';
const findConstantOnPage = (config: ConfigType, fieldName: string): Constant => {
const foundPage =
@ -50,7 +50,7 @@ const findDatalog = (config: ConfigType, name: string): DatalogEntry => {
};
const useConfig = (config: ConfigType | null) =>
// rome-ignore lint/nursery/useHookAtTopLevel: <explanation>
// biome-ignore lint/nursery/useHookAtTopLevel: False positive
useMemo(
() => ({
isConfigReady: !!config?.constants,

View File

@ -1,6 +1,4 @@
import * as Sentry from '@sentry/browser';
import { client, formatError, ClientResponseError, API_URL } from '../pocketbase';
import { databaseGenericError } from '../pages/auth/notifications';
import {
Collections,
IniFilesResponse,
@ -8,6 +6,8 @@ import {
TunesResponse,
UsersResponse,
} from '../@types/pocketbase-types';
import { databaseGenericError } from '../pages/auth/notifications';
import { API_URL, ClientResponseError, client, formatError } from '../pocketbase';
type Partial<T> = {
[A in keyof T]?: T[A];
@ -170,7 +170,7 @@ const useDb = () => {
const autocomplete = async (attribute: string, search: string): Promise<TunesResponse[]> => {
try {
const items = await tunesCollection.getFullList<TunesResponse>(10, {
const items = await tunesCollection.getFullList<TunesResponse>({
filter: `${attribute} ~ "${search}"`,
});

View File

@ -1,10 +1,10 @@
import * as Sentry from '@sentry/browser';
import { API_URL, removeFilenameSuffix } from '../pocketbase';
import { Collections } from '../@types/pocketbase-types';
import useDb from './useDb';
import { fetchWithProgress, OnProgress } from '../utils/http';
import { downloading } from '../pages/auth/notifications';
import { API_URL, removeFilenameSuffix } from '../pocketbase';
import { decompress } from '../utils/compression';
import { OnProgress, fetchWithProgress } from '../utils/http';
import useDb from './useDb';
const useServerStorage = () => {
const { getIni } = useDb();

View File

@ -1,14 +1,14 @@
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import { HashRouter } from 'react-router-dom';
import * as Sentry from '@sentry/react';
import { Integrations } from '@sentry/tracing';
import { createRoot } from 'react-dom/client';
import ReactGA from 'react-ga4';
import { Provider } from 'react-redux';
import { HashRouter } from 'react-router-dom';
import App from './App';
import CommandPalette from './components/CommandPalette';
import { AuthProvider } from './contexts/AuthContext';
import store from './store';
import { environment, isProduction, sentryDsn } from './utils/env';
import { AuthProvider } from './contexts/AuthContext';
import CommandPalette from './components/CommandPalette';
import '@total-typescript/ts-reset';

View File

@ -1,16 +1,16 @@
import { GithubOutlined, HeartOutlined } from '@ant-design/icons';
import { Button, Result } from 'antd';
import { HeartOutlined, GithubOutlined } from '@ant-design/icons';
import hyperIcon from '../assets/img/hypertuner-logo.png';
const About = () => (
<div className='large-container'>
<div className="large-container">
<Result
status='success'
icon={<img src={hyperIcon} alt='HyperTuner' style={{ maxWidth: 100 }} />}
status="success"
icon={<img src={hyperIcon} alt="HyperTuner" style={{ maxWidth: 100 }} />}
title={
<>
Powered by{' '}
<a href='https://github.com/hyper-tuner' target='_blank' rel='noreferrer'>
<a href="https://github.com/hyper-tuner" target="_blank" rel="noreferrer">
HyperTuner
</a>
</>
@ -18,14 +18,14 @@ const About = () => (
subTitle={
<>
Created with <HeartOutlined /> by{' '}
<a href='https://github.com/karniv00l' target='_blank' rel='noreferrer'>
<a href="https://github.com/karniv00l" target="_blank" rel="noreferrer">
Piotr Rogowski
</a>
, licensed under{' '}
<a
href='https://github.com/hyper-tuner/hypertuner-cloud/blob/master/LICENSE'
target='_blank'
rel='noreferrer'
href="https://github.com/hyper-tuner/hypertuner-cloud/blob/master/LICENSE"
target="_blank"
rel="noreferrer"
>
MIT
</a>
@ -34,16 +34,16 @@ const About = () => (
}
extra={[
<Button
type='primary'
key='sponsor'
className='sponsor-button'
type="primary"
key="sponsor"
className="sponsor-button"
icon={<HeartOutlined />}
onClick={() => window.open('https://github.com/sponsors/karniv00l', '_blank')}
>
Sponsor
</Button>,
<Button
key='source'
key="source"
icon={<GithubOutlined />}
onClick={() => window.open('https://github.com/hyper-tuner/hypertuner-cloud', '_blank')}
>

View File

@ -1,26 +1,26 @@
import { generatePath, Link, useMatch, useNavigate } from 'react-router-dom';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Layout, Tabs, Progress, Steps, Space, Divider, Typography, Badge, Grid } from 'antd';
import { FileTextOutlined, GlobalOutlined } from '@ant-design/icons';
import { connect } from 'react-redux';
import { Badge, Divider, Grid, Layout, Progress, Space, Steps, Tabs, Typography } from 'antd';
import { useCallback, useEffect, useRef, useState } from 'react';
import PerfectScrollbar from 'react-perfect-scrollbar';
import { AppState, ToothLogsState, TuneDataState, UIState } from '../types/state';
import store from '../store';
import { formatBytes } from '../utils/numbers';
import { connect } from 'react-redux';
import { Link, generatePath, useMatch, useNavigate } from 'react-router-dom';
import Loader from '../components/Loader';
import CompositeCanvas from '../components/TriggerLogs/CompositeCanvas';
import ToothCanvas from '../components/TriggerLogs/ToothCanvas';
import { collapsedSidebarWidth, sidebarWidth } from '../components/Tune/SideBar';
import useServerStorage from '../hooks/useServerStorage';
import { removeFilenameSuffix } from '../pocketbase';
import { Routes } from '../routes';
import store from '../store';
import { AppState, ToothLogsState, TuneDataState, UIState } from '../types/state';
import { Colors } from '../utils/colors';
import { decompress } from '../utils/compression';
import { isAbortedRequest } from '../utils/error';
import TriggerLogsParser, {
CompositeLogEntry,
ToothLogEntry,
} from '../utils/logs/TriggerLogsParser';
import ToothCanvas from '../components/TriggerLogs/ToothCanvas';
import Loader from '../components/Loader';
import { Colors } from '../utils/colors';
import { Routes } from '../routes';
import { removeFilenameSuffix } from '../pocketbase';
import useServerStorage from '../hooks/useServerStorage';
import { isAbortedRequest } from '../utils/error';
import { collapsedSidebarWidth, sidebarWidth } from '../components/Tune/SideBar';
import { decompress } from '../utils/compression';
import { formatBytes } from '../utils/numbers';
const { Content } = Layout;
@ -205,17 +205,17 @@ const Diagnose = ({
return (
<>
<Sider {...siderProps} className='app-sidebar'>
<Sider {...siderProps} className="app-sidebar">
{loadedToothLogs.type ? (
!ui.sidebarCollapsed && (
<Tabs
defaultActiveKey='files'
defaultActiveKey="files"
style={{ marginLeft: 20 }}
items={[
{
label: (
<Badge
size='small'
size="small"
style={badgeStyle}
count={tuneData?.toothLogFiles?.length}
offset={[10, -3]}
@ -252,14 +252,14 @@ const Diagnose = ({
<Loader />
)}
</Sider>
<Layout className='logs-container'>
<Layout className="logs-container">
<Content>
<div ref={contentRef}>
{loadedToothLogs.type ? (
<Graph />
) : (
<Space direction='vertical' size='large'>
<Progress type='circle' percent={progress} className='logs-progress' />
<Space direction="vertical" size="large">
<Progress type="circle" percent={progress} className="logs-progress" />
<Divider />
<Steps
current={step}

View File

@ -1,21 +1,21 @@
import { Link } from 'react-router-dom';
import { ArrowRightOutlined, CopyOutlined, EditOutlined, StarFilled } from '@ant-design/icons';
import { Button, Grid, Input, InputRef, Pagination, Space, Table, Typography } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import { CopyOutlined, StarFilled, ArrowRightOutlined, EditOutlined } from '@ant-design/icons';
import debounce from 'lodash.debounce';
import { useCallback, useEffect, useRef, useState } from 'react';
import { generatePath, useNavigate } from 'react-router';
import debounce from 'lodash.debounce';
import { Link } from 'react-router-dom';
import { TunesResponse } from '../@types/pocketbase-types';
import AuthorName from '../components/AuthorName';
import TuneTag from '../components/TuneTag';
import { useAuth } from '../contexts/AuthContext';
import useDb, { TunesResponseExpand } from '../hooks/useDb';
import { Routes } from '../routes';
import { buildFullUrl } from '../utils/url';
import { aspirationMapper } from '../utils/tune/mappers';
import { copyToClipboard, isClipboardSupported } from '../utils/clipboard';
import { isEscape } from '../utils/keyboard/shortcuts';
import { formatTime } from '../utils/time';
import { useAuth } from '../contexts/AuthContext';
import { TunesResponse } from '../@types/pocketbase-types';
import TuneTag from '../components/TuneTag';
import AuthorName from '../components/AuthorName';
import { aspirationMapper } from '../utils/tune/mappers';
import { buildFullUrl } from '../utils/url';
const { useBreakpoint } = Grid;
const { Text, Title } = Typography;
@ -97,8 +97,8 @@ const Hub = () => {
<TuneTag tag={tune.tags} />
</Space>
</Title>
<Space direction='vertical'>
<Text type='secondary'>
<Space direction="vertical">
<Text type="secondary">
<Link to={generatePath(Routes.USER_ROOT, { userId: tune.author })}>
<AuthorName author={tune.expand!.author} />
</Link>
@ -120,7 +120,7 @@ const Hub = () => {
key: 'vehicleName',
responsive: ['sm'],
render: (vehicleName: string, tune: TunesResponse) => (
<Space direction='vertical'>
<Space direction="vertical">
{vehicleName}
<TuneTag tag={tune.tags} />
</Space>
@ -190,6 +190,7 @@ const Hub = () => {
{
dataIndex: 'tuneId',
fixed: 'right',
align: 'right',
render: (tuneId: string, record: TunesResponse) => {
const isOwner = currentUser?.id === record.author;
const size = isOwner ? 'small' : 'middle';
@ -208,7 +209,7 @@ const Hub = () => {
)}
<Button
size={size}
type='primary'
type="primary"
icon={<ArrowRightOutlined />}
onClick={() => navigate(tunePath(tuneId))}
/>
@ -220,14 +221,14 @@ const Hub = () => {
];
return (
<div className='large-container'>
<div className="large-container">
<Input
// rome-ignore lint: make search input first in tab order
// biome-ignore lint: make search input first in tab order
tabIndex={1}
ref={searchRef}
style={{ marginBottom: 10, height: 40 }}
value={searchQuery}
placeholder='Search by anything...'
placeholder="Search by anything..."
onChange={({ target }) => debounceLoadData(target.value)}
allowClear
/>

View File

@ -1,15 +1,15 @@
import { connect } from 'react-redux';
import ReactMarkdown from 'react-markdown';
import { Button, Col, Divider, Form, Input, Row, Select } from 'antd';
import { EditOutlined } from '@ant-design/icons';
import { Button, Col, Divider, Form, Input, Row, Select } from 'antd';
import ReactMarkdown from 'react-markdown';
import { connect } from 'react-redux';
import { generatePath, useNavigate } from 'react-router-dom';
import { AppState, TuneDataState } from '../types/state';
import Loader from '../components/Loader';
import { Routes } from '../routes';
import { useAuth } from '../contexts/AuthContext';
import { formatTime } from '../utils/time';
import { TunesAspirationOptions } from '../@types/pocketbase-types';
import Loader from '../components/Loader';
import StarButton from '../components/StarButton';
import { useAuth } from '../contexts/AuthContext';
import { Routes } from '../routes';
import { AppState, TuneDataState } from '../types/state';
import { formatTime } from '../utils/time';
const { Item } = Form;
const rowProps = { gutter: 10 };
@ -37,7 +37,7 @@ const Info = ({ tuneData }: { tuneData: TuneDataState }) => {
<Divider>Manage</Divider>
<Row style={{ marginTop: 10 }}>
<Item style={{ width: '100%' }}>
<Button type='primary' block onClick={goToEdit} icon={<EditOutlined />}>
<Button type="primary" block onClick={goToEdit} icon={<EditOutlined />}>
Edit
</Button>
</Item>
@ -50,59 +50,59 @@ const Info = ({ tuneData }: { tuneData: TuneDataState }) => {
}
return (
<div className='small-container'>
<div className="small-container">
<StarButton tuneData={tuneData} />
<Divider>Details</Divider>
<Form>
<Row {...rowProps}>
<Col {...colProps}>
<Item>
<Input value={tuneData.expand!.author.username} addonBefore='Author' />
<Input value={tuneData.expand!.author.username} addonBefore="Author" />
</Item>
</Col>
<Col {...colProps}>
<Item>
<Input value={tuneData.signature} addonBefore='Signature' />
<Input value={tuneData.signature} addonBefore="Signature" />
</Item>
</Col>
</Row>
<Row {...rowProps}>
<Col span={24} sm={24}>
<Item>
<Input value={formatTime(tuneData.updated)} addonBefore='Published at' />
<Input value={formatTime(tuneData.updated)} addonBefore="Published at" />
</Item>
</Col>
</Row>
<Row {...rowProps}>
<Col span={24} sm={24}>
<Item>
<Input value={tuneData.vehicleName!} addonBefore='Vehicle name' />
<Input value={tuneData.vehicleName!} addonBefore="Vehicle name" />
</Item>
</Col>
</Row>
<Row {...rowProps}>
<Col {...colProps}>
<Item>
<Input value={tuneData.engineMake!} addonBefore='Engine make' />
<Input value={tuneData.engineMake!} addonBefore="Engine make" />
</Item>
</Col>
<Col {...colProps}>
<Item>
<Input value={tuneData.engineCode!} addonBefore='Engine code' />
<Input value={tuneData.engineCode!} addonBefore="Engine code" />
</Item>
</Col>
</Row>
<Row {...rowProps}>
<Col {...colProps}>
<Item>
<Input value={tuneData.displacement!} addonBefore='Displacement' addonAfter='l' />
<Input value={tuneData.displacement!} addonBefore="Displacement" addonAfter="l" />
</Item>
</Col>
<Col {...colProps}>
<Item>
<Input
value={tuneData.cylindersCount!}
addonBefore='Cylinders'
addonBefore="Cylinders"
style={{ width: '100%' }}
/>
</Item>
@ -112,7 +112,7 @@ const Info = ({ tuneData }: { tuneData: TuneDataState }) => {
<Col {...colProps}>
<Item>
<Select
placeholder='Aspiration'
placeholder="Aspiration"
style={{ width: '100%' }}
value={tuneData.aspiration}
>
@ -130,9 +130,9 @@ const Info = ({ tuneData }: { tuneData: TuneDataState }) => {
<Item>
<Input
value={tuneData.compression!}
addonBefore='Compression'
addonBefore="Compression"
style={{ width: '100%' }}
addonAfter=':1'
addonAfter=":1"
/>
</Item>
</Col>
@ -140,42 +140,42 @@ const Info = ({ tuneData }: { tuneData: TuneDataState }) => {
<Row {...rowProps}>
<Col {...colProps}>
<Item>
<Input value={tuneData.fuel!} addonBefore='Fuel' />
<Input value={tuneData.fuel!} addonBefore="Fuel" />
</Item>
</Col>
<Col {...colProps}>
<Item>
<Input value={tuneData.ignition!} addonBefore='Ignition' />
<Input value={tuneData.ignition!} addonBefore="Ignition" />
</Item>
</Col>
</Row>
<Row {...rowProps}>
<Col {...colProps}>
<Item>
<Input value={tuneData.injectorsSize!} addonBefore='Injectors size' addonAfter='cc' />
<Input value={tuneData.injectorsSize!} addonBefore="Injectors size" addonAfter="cc" />
</Item>
</Col>
<Col {...colProps}>
<Item>
<Input value={tuneData.year!} addonBefore='Year' />
<Input value={tuneData.year!} addonBefore="Year" />
</Item>
</Col>
</Row>
<Row {...rowProps}>
<Col {...colProps}>
<Item>
<Input value={tuneData.hp!} addonBefore='HP' style={{ width: '100%' }} />
<Input value={tuneData.hp!} addonBefore="HP" style={{ width: '100%' }} />
</Item>
</Col>
<Col {...colProps}>
<Item>
<Input value={tuneData.stockHp!} addonBefore='Stock HP' style={{ width: '100%' }} />
<Input value={tuneData.stockHp!} addonBefore="Stock HP" style={{ width: '100%' }} />
</Item>
</Col>
</Row>
</Form>
<Divider>README</Divider>
<div className='markdown-preview' style={{ height: '100%' }}>
<div className="markdown-preview" style={{ height: '100%' }}>
{tuneData.readme && <ReactMarkdown>{`${tuneData.readme}`}</ReactMarkdown>}
</div>
{canManage && <ManageSection />}

View File

@ -1,42 +1,42 @@
import { Link, generatePath, useMatch, useNavigate } from 'react-router-dom';
import { useCallback, useEffect, useRef, useState } from 'react';
import { EditOutlined, FileTextOutlined, GlobalOutlined } from '@ant-design/icons';
import { DatalogEntry, Logs as LogsType, OutputChannel } from '@hyper-tuner/types';
import {
Layout,
Tabs,
Checkbox,
Row,
Progress,
Steps,
Space,
Divider,
Badge,
Typography,
Checkbox,
Divider,
Grid,
Input,
Layout,
Progress,
Row,
Space,
Steps,
Tabs,
Typography,
} from 'antd';
import { FileTextOutlined, EditOutlined, GlobalOutlined } from '@ant-design/icons';
import { CheckboxValueType } from 'antd/es/checkbox/Group';
import { connect } from 'react-redux';
import { Result as ParserResult } from 'mlg-converter/dist/types';
import PerfectScrollbar from 'react-perfect-scrollbar';
import { OutputChannel, Logs as LogsType, DatalogEntry } from '@hyper-tuner/types';
import LogParserWorker from '../workers/logParserWorker?worker';
import LogCanvas, { SelectedField } from '../components/Logs/LogCanvas';
import store from '../store';
import { formatBytes, msToTime } from '../utils/numbers';
import useConfig from '../hooks/useConfig';
import { isExpression, stripExpression } from '../utils/tune/expression';
import { AppState, ConfigState, LogsState, TuneDataState, UIState } from '../types/state';
import Loader from '../components/Loader';
import { Colors } from '../utils/colors';
import useServerStorage from '../hooks/useServerStorage';
import { Routes } from '../routes';
import { removeFilenameSuffix } from '../pocketbase';
import { isAbortedRequest } from '../utils/error';
import { WorkerOutput } from '../workers/logParserWorker';
import { collapsedSidebarWidth, sidebarWidth } from '../components/Tune/SideBar';
import Fuse from 'fuse.js';
import debounce from 'lodash.debounce';
import { Result as ParserResult } from 'mlg-converter/dist/types';
import { useCallback, useEffect, useRef, useState } from 'react';
import PerfectScrollbar from 'react-perfect-scrollbar';
import { connect } from 'react-redux';
import { Link, generatePath, useMatch, useNavigate } from 'react-router-dom';
import Loader from '../components/Loader';
import LogCanvas, { SelectedField } from '../components/Logs/LogCanvas';
import { collapsedSidebarWidth, sidebarWidth } from '../components/Tune/SideBar';
import useConfig from '../hooks/useConfig';
import useServerStorage from '../hooks/useServerStorage';
import { removeFilenameSuffix } from '../pocketbase';
import { Routes } from '../routes';
import store from '../store';
import { AppState, ConfigState, LogsState, TuneDataState, UIState } from '../types/state';
import { Colors } from '../utils/colors';
import { isAbortedRequest } from '../utils/error';
import { formatBytes, msToTime } from '../utils/numbers';
import { isExpression, stripExpression } from '../utils/tune/expression';
import { WorkerOutput } from '../workers/logParserWorker';
import LogParserWorker from '../workers/logParserWorker?worker';
const { Content } = Layout;
const edgeUnknown = 'Unknown';
@ -318,16 +318,16 @@ const Logs = ({
return (
<>
<Sider {...siderProps} className='app-sidebar'>
<Sider {...siderProps} className="app-sidebar">
{(loadedLogs?.logs || []).length ? (
!ui.sidebarCollapsed && (
<Tabs
defaultActiveKey='fields'
defaultActiveKey="fields"
style={{ marginLeft: 20 }}
items={[
{
label: (
<Badge size='small' style={badgeStyle} count={fields.length} offset={[10, -3]}>
<Badge size="small" style={badgeStyle} count={fields.length} offset={[10, -3]}>
<EditOutlined />
Fields
</Badge>
@ -338,7 +338,7 @@ const Logs = ({
<Input
onChange={({ target }) => debounceSearch1(target.value)}
style={searchInputStyle}
placeholder='Search fields...'
placeholder="Search fields..."
allowClear
/>
<div
@ -369,7 +369,7 @@ const Logs = ({
<Input
onChange={({ target }) => debounceSearch2(target.value)}
style={searchInputStyle}
placeholder='Search fields...'
placeholder="Search fields..."
allowClear
/>
<div style={fieldsSectionStyle}>
@ -398,7 +398,7 @@ const Logs = ({
{
label: (
<Badge
size='small'
size="small"
style={badgeStyle}
count={tuneData?.logFiles?.length}
offset={[10, -3]}
@ -435,7 +435,7 @@ const Logs = ({
<Loader />
)}
</Sider>
<Layout className='logs-container'>
<Layout className="logs-container">
<Content>
<div ref={contentRef}>
{logs || !!(loadedLogs.logs || []).length ? (
@ -448,12 +448,12 @@ const Logs = ({
showSingleGraph={showSingleGraph}
/>
) : (
<Space direction='vertical' size='large'>
<Space direction="vertical" size="large">
<Progress
type='circle'
type="circle"
percent={progress}
status={(fetchError || parseError) && 'exception'}
className='logs-progress'
className="logs-progress"
/>
<Divider />
<Steps

View File

@ -1,13 +1,13 @@
import { generatePath, useMatch, useNavigate } from 'react-router-dom';
import { connect } from 'react-redux';
import { useEffect } from 'react';
import { Config as ConfigType } from '@hyper-tuner/types';
import { useEffect } from 'react';
import { connect } from 'react-redux';
import { generatePath, useMatch, useNavigate } from 'react-router-dom';
import Loader from '../components/Loader';
import Dialog from '../components/Tune/Dialog';
import SideBar from '../components/Tune/SideBar';
import { Routes } from '../routes';
import useConfig from '../hooks/useConfig';
import { Routes } from '../routes';
import { AppState, TuneState } from '../types/state';
import Loader from '../components/Loader';
const mapStateToProps = (state: AppState) => ({
navigation: state.navigation,

View File

@ -1,60 +1,42 @@
import { useCallback, useEffect, useState } from 'react';
import {
CheckOutlined,
CopyOutlined,
EditOutlined,
EyeOutlined,
FileTextOutlined,
FundOutlined,
GlobalOutlined,
PlusOutlined,
SendOutlined,
SettingOutlined,
ShareAltOutlined,
ToolOutlined,
} from '@ant-design/icons';
import { INI } from '@hyper-tuner/ini';
import {
AutoComplete,
Button,
Col,
Divider,
Form,
Input,
InputNumber,
Row,
Select,
Space,
Tabs,
Tag,
Tooltip,
Typography,
Upload,
Form,
AutoComplete,
Tag,
} from 'antd';
import {
PlusOutlined,
ToolOutlined,
FundOutlined,
SettingOutlined,
CopyOutlined,
ShareAltOutlined,
FileTextOutlined,
EditOutlined,
CheckOutlined,
SendOutlined,
GlobalOutlined,
EyeOutlined,
} from '@ant-design/icons';
import debounce from 'lodash.debounce';
import { INI } from '@hyper-tuner/ini';
import { UploadRequestOption } from 'rc-upload/lib/interface';
import { UploadFile } from 'antd/lib/upload/interface';
import { generatePath, useMatch, useNavigate } from 'react-router-dom';
import ReactMarkdown from 'react-markdown';
import debounce from 'lodash.debounce';
import { nanoid } from 'nanoid';
import {
emailNotVerified,
error,
restrictedPage,
signatureNotSupportedWarning,
} from './auth/notifications';
import { useAuth } from '../contexts/AuthContext';
import { Routes } from '../routes';
import TuneParser from '../utils/tune/TuneParser';
import TriggerLogsParser from '../utils/logs/TriggerLogsParser';
import LogValidator from '../utils/logs/LogValidator';
import useDb, { TunesRecordPartial } from '../hooks/useDb';
import useServerStorage from '../hooks/useServerStorage';
import { buildFullUrl } from '../utils/url';
import Loader from '../components/Loader';
import { requiredTextRules, requiredRules } from '../utils/form';
import { aspirationMapper } from '../utils/tune/mappers';
import { copyToClipboard } from '../utils/clipboard';
import { UploadRequestOption } from 'rc-upload/lib/interface';
import { useCallback, useEffect, useState } from 'react';
import ReactMarkdown from 'react-markdown';
import { generatePath, useMatch, useNavigate } from 'react-router-dom';
import {
TunesAspirationOptions,
TunesRecord,
@ -63,9 +45,27 @@ import {
TunesTagsOptions,
TunesVisibilityOptions,
} from '../@types/pocketbase-types';
import Loader from '../components/Loader';
import { useAuth } from '../contexts/AuthContext';
import useDb, { TunesRecordPartial } from '../hooks/useDb';
import useServerStorage from '../hooks/useServerStorage';
import { removeFilenameSuffix } from '../pocketbase';
import { bufferToFile } from '../utils/files';
import { Routes } from '../routes';
import { copyToClipboard } from '../utils/clipboard';
import { compress } from '../utils/compression';
import { bufferToFile } from '../utils/files';
import { requiredRules, requiredTextRules } from '../utils/form';
import LogValidator from '../utils/logs/LogValidator';
import TriggerLogsParser from '../utils/logs/TriggerLogsParser';
import TuneParser from '../utils/tune/TuneParser';
import { aspirationMapper } from '../utils/tune/mappers';
import { buildFullUrl } from '../utils/url';
import {
emailNotVerified,
error,
restrictedPage,
signatureNotSupportedWarning,
} from './auth/notifications';
const { Item, useForm } = Form;
@ -148,8 +148,6 @@ const UploadPage = () => {
}
const options = (await autocomplete(attribute, search)).map((record) => record[attribute]);
// TODO: order by occurrence (more common - higher in the list)
const unique = [...new Set(options)].map((value) => ({ value }));
setAutocompleteOptions((prev) => ({ ...prev, [attribute]: unique }));
@ -611,7 +609,7 @@ const UploadPage = () => {
}, [routeMatch?.params.tuneId]);
const UploadButton = () => (
<Space direction='vertical'>
<Space direction="vertical">
<PlusOutlined />
Upload
</Space>
@ -628,7 +626,7 @@ const UploadPage = () => {
const PublishButton = () => (
<Row style={{ marginTop: 10 }} {...rowProps}>
<Col {...colProps}>
<Item name='visibility'>
<Item name="visibility">
<Select>
<Select.Option value={TunesVisibilityOptions.public}>
<Space>
@ -648,10 +646,10 @@ const UploadPage = () => {
<Col {...colProps}>
<Item style={{ width: '100%' }}>
<Button
type='primary'
type="primary"
block
loading={isLoading}
htmlType='submit'
htmlType="submit"
icon={isEditMode ? <EditOutlined /> : <CheckOutlined />}
disabled={customIniRequired}
>
@ -666,11 +664,11 @@ const UploadPage = () => {
<>
<Row>
<Input style={{ width: `calc(100% - ${shareSupported ? 65 : 35}px)` }} value={shareUrl!} />
<Tooltip title='Copy URL'>
<Tooltip title="Copy URL">
<Button icon={<CopyOutlined />} onClick={() => copyToClipboard(shareUrl!)} />
</Tooltip>
{shareSupported && (
<Tooltip title='Share'>
<Tooltip title="Share">
<Button
icon={<ShareAltOutlined />}
onClick={() => navigator.share({ url: shareUrl! })}
@ -680,7 +678,7 @@ const UploadPage = () => {
</Row>
<Row style={{ marginTop: 10 }}>
<Item style={{ width: '100%' }}>
<Button type='primary' block onClick={goToNewTune} icon={<SendOutlined />}>
<Button type="primary" block onClick={goToNewTune} icon={<SendOutlined />}>
Open
</Button>
</Item>
@ -700,29 +698,29 @@ const UploadPage = () => {
<Divider>Details</Divider>
<Row {...rowProps}>
<Col {...colProps}>
<Item name='vehicleName' rules={requiredTextRules}>
<Item name="vehicleName" rules={requiredTextRules}>
<AutoComplete
options={autocompleteOptions.vehicleName}
onSearch={(search) => searchAutocomplete('vehicleName', search)}
backfill
>
<Input addonBefore='Name' />
<Input addonBefore="Name" />
</AutoComplete>
</Item>
</Col>
<Col {...colProps}>
<Item name='tags'>
<Item name="tags">
<Select
placeholder='Tags'
placeholder="Tags"
allowClear
style={{ width: '100%' }}
options={[
{
label: <Tag color='green'>base map</Tag>,
label: <Tag color="green">base map</Tag>,
value: TunesTagsOptions['base map'],
},
{
label: <Tag color='red'>help needed</Tag>,
label: <Tag color="red">help needed</Tag>,
value: TunesTagsOptions['help needed'],
},
]}
@ -732,44 +730,44 @@ const UploadPage = () => {
</Row>
<Row {...rowProps}>
<Col {...colProps}>
<Item name='engineMake' rules={requiredTextRules}>
<Item name="engineMake" rules={requiredTextRules}>
<AutoComplete
options={autocompleteOptions.engineMake}
onSearch={(search) => searchAutocomplete('engineMake', search)}
backfill
>
<Input addonBefore='Engine make' />
<Input addonBefore="Engine make" />
</AutoComplete>
</Item>
</Col>
<Col {...colProps}>
<Item name='engineCode' rules={requiredTextRules}>
<Item name="engineCode" rules={requiredTextRules}>
<AutoComplete
options={autocompleteOptions.engineCode}
onSearch={(search) => searchAutocomplete('engineCode', search)}
backfill
>
<Input addonBefore='Engine code' />
<Input addonBefore="Engine code" />
</AutoComplete>
</Item>
</Col>
</Row>
<Row {...rowProps}>
<Col {...colProps}>
<Item name='displacement' rules={requiredRules}>
<InputNumber addonBefore='Displacement' addonAfter='l' min={0} max={100} />
<Item name="displacement" rules={requiredRules}>
<InputNumber addonBefore="Displacement" addonAfter="l" min={0} max={100} />
</Item>
</Col>
<Col {...colProps}>
<Item name='cylindersCount' rules={requiredRules}>
<InputNumber addonBefore='Cylinders' style={{ width: '100%' }} min={0} max={16} />
<Item name="cylindersCount" rules={requiredRules}>
<InputNumber addonBefore="Cylinders" style={{ width: '100%' }} min={0} max={16} />
</Item>
</Col>
</Row>
<Row {...rowProps}>
<Col {...colProps}>
<Item name='aspiration'>
<Select placeholder='Aspiration' style={{ width: '100%' }}>
<Item name="aspiration">
<Select placeholder="Aspiration" style={{ width: '100%' }}>
<Select.Option value={TunesAspirationOptions.na}>Naturally aspirated</Select.Option>
<Select.Option value={TunesAspirationOptions.turbocharged}>
Turbocharged
@ -781,75 +779,75 @@ const UploadPage = () => {
</Item>
</Col>
<Col {...colProps}>
<Item name='compression'>
<Item name="compression">
<InputNumber
addonBefore='Compression'
addonBefore="Compression"
style={{ width: '100%' }}
min={0}
max={100}
step={0.1}
addonAfter=':1'
addonAfter=":1"
/>
</Item>
</Col>
</Row>
<Row {...rowProps}>
<Col {...colProps}>
<Item name='fuel'>
<Item name="fuel">
<AutoComplete
options={autocompleteOptions.fuel}
onSearch={(search) => searchAutocomplete('fuel', search)}
backfill
>
<Input addonBefore='Fuel' />
<Input addonBefore="Fuel" />
</AutoComplete>
</Item>
</Col>
<Col {...colProps}>
<Item name='ignition'>
<Item name="ignition">
<AutoComplete
options={autocompleteOptions.ignition}
onSearch={(search) => searchAutocomplete('ignition', search)}
backfill
>
<Input addonBefore='Ignition' />
<Input addonBefore="Ignition" />
</AutoComplete>
</Item>
</Col>
</Row>
<Row {...rowProps}>
<Col {...colProps}>
<Item name='injectorsSize'>
<InputNumber addonBefore='Injectors size' addonAfter='cc' min={0} max={100_000} />
<Item name="injectorsSize">
<InputNumber addonBefore="Injectors size" addonAfter="cc" min={0} max={100_000} />
</Item>
</Col>
<Col {...colProps}>
<Item name='year'>
<InputNumber addonBefore='Year' style={{ width: '100%' }} min={1886} max={thisYear} />
<Item name="year">
<InputNumber addonBefore="Year" style={{ width: '100%' }} min={1886} max={thisYear} />
</Item>
</Col>
</Row>
<Row {...rowProps}>
<Col {...colProps}>
<Item name='hp'>
<InputNumber addonBefore='HP' style={{ width: '100%' }} min={0} max={100_000} />
<Item name="hp">
<InputNumber addonBefore="HP" style={{ width: '100%' }} min={0} max={100_000} />
</Item>
</Col>
<Col {...colProps}>
<Item name='stockHp'>
<InputNumber addonBefore='Stock HP' style={{ width: '100%' }} min={0} max={100_000} />
<Item name="stockHp">
<InputNumber addonBefore="Stock HP" style={{ width: '100%' }} min={0} max={100_000} />
</Item>
</Col>
</Row>
<Divider style={{ marginTop: 40 }}>
<Space>
README
<Typography.Text type='secondary'>(markdown)</Typography.Text>
<Typography.Text type="secondary">(markdown)</Typography.Text>
</Space>
</Divider>
<Tabs
defaultActiveKey='source'
className='upload-readme'
defaultActiveKey="source"
className="upload-readme"
items={[
{
label: 'Edit',
@ -870,7 +868,7 @@ const UploadPage = () => {
key: 'preview',
style: { height: descriptionEditorHeight },
children: (
<div className='markdown-preview'>
<div className="markdown-preview">
<ReactMarkdown>{readme}</ReactMarkdown>
</div>
),
@ -885,12 +883,12 @@ const UploadPage = () => {
<Divider>
<Space>
Upload Logs
<Typography.Text type='secondary'>(.mlg, .csv, .msl)</Typography.Text>
<Typography.Text type="secondary">(.mlg, .csv, .msl)</Typography.Text>
</Space>
</Divider>
<Upload
key={defaultLogFilesList.map((file) => file.uid).join('-') || 'logs'}
listType='picture-card'
listType="picture-card"
customRequest={uploadLogs}
onRemove={removeLogFile}
iconRender={logIcon}
@ -899,19 +897,19 @@ const UploadPage = () => {
disabled={isPublished}
onPreview={noop}
defaultFileList={defaultLogFilesList}
accept='.mlg,.csv,.msl'
accept=".mlg,.csv,.msl"
>
{logFiles.length < MaxFiles.LOG_FILES && <UploadButton />}
</Upload>
<Divider>
<Space>
Upload Tooth and Composite logs
<Typography.Text type='secondary'>(.csv)</Typography.Text>
<Typography.Text type="secondary">(.csv)</Typography.Text>
</Space>
</Divider>
<Upload
key={defaultToothLogFilesList.map((file) => file.uid).join('-') || 'toothLogs'}
listType='picture-card'
listType="picture-card"
customRequest={uploadToothLogs}
onRemove={removeToothLogFile}
iconRender={toothLogIcon}
@ -919,26 +917,26 @@ const UploadPage = () => {
maxCount={MaxFiles.TOOTH_LOG_FILES}
onPreview={noop}
defaultFileList={defaultToothLogFilesList}
accept='.csv'
accept=".csv"
>
{toothLogFiles.length < MaxFiles.TOOTH_LOG_FILES && <UploadButton />}
</Upload>
<Divider>
<Space>
Upload Custom INI
<Typography.Text type='secondary'>(.ini)</Typography.Text>
<Typography.Text type="secondary">(.ini)</Typography.Text>
</Space>
</Divider>
<Upload
key={defaultCustomIniFileList[0]?.uid || 'customIni'}
listType='picture-card'
listType="picture-card"
customRequest={uploadCustomIni}
onRemove={removeCustomIniFile}
iconRender={iniIcon}
disabled={isPublished}
onPreview={noop}
defaultFileList={defaultCustomIniFileList}
accept='.ini'
accept=".ini"
>
{!customIniFile && <UploadButton />}
</Upload>
@ -949,7 +947,7 @@ const UploadPage = () => {
if (isPublished) {
return (
<div className='small-container'>
<div className="small-container">
<ShareSection />
</div>
);
@ -964,7 +962,7 @@ const UploadPage = () => {
}
return (
<div className='small-container'>
<div className="small-container">
<Form
initialValues={
{
@ -982,19 +980,19 @@ const UploadPage = () => {
<Divider>
<Space>
Upload Tune
<Typography.Text type='secondary'>(.msq)</Typography.Text>
<Typography.Text type="secondary">(.msq)</Typography.Text>
</Space>
</Divider>
<Upload
key={defaultTuneFileList[0]?.uid || 'tuneFile'}
listType='picture-card'
listType="picture-card"
customRequest={uploadTune}
onRemove={removeTuneFile}
iconRender={tuneIcon}
disabled={isPublished}
onPreview={noop}
defaultFileList={defaultTuneFileList}
accept='.msq'
accept=".msq"
>
{tuneFile === undefined && <UploadButton />}
</Upload>

View File

@ -1,14 +1,14 @@
import { ArrowRightOutlined } from '@ant-design/icons';
import { Button, Divider, List, Pagination, Space, Typography } from 'antd';
import { useEffect, useState } from 'react';
import { generatePath, useMatch, useNavigate } from 'react-router-dom';
import { Button, Divider, List, Pagination, Space, Typography } from 'antd';
import { ArrowRightOutlined } from '@ant-design/icons';
import { TunesResponse, UsersResponse } from '../@types/pocketbase-types';
import AuthorName from '../components/AuthorName';
import TuneTag from '../components/TuneTag';
import useDb from '../hooks/useDb';
import { Routes } from '../routes';
import { formatTime } from '../utils/time';
import useDb from '../hooks/useDb';
import { aspirationMapper } from '../utils/tune/mappers';
import { TunesResponse, UsersResponse } from '../@types/pocketbase-types';
import TuneTag from '../components/TuneTag';
import AuthorName from '../components/AuthorName';
const tunePath = (tuneId: string) => generatePath(Routes.TUNE_TUNE, { tuneId });
@ -42,7 +42,7 @@ const Profile = () => {
}, [page]);
return (
<div className='small-container'>
<div className="small-container">
<Divider>
{author ? (
<>
@ -66,10 +66,10 @@ const Profile = () => {
]}
className={tune.visibility}
>
<Space direction='vertical'>
<Space direction="vertical">
<List.Item.Meta
title={
<Space direction='vertical'>
<Space direction="vertical">
{tune.vehicleName}
<TuneTag tag={tune.tags} />
<Typography.Text italic>{tune.signature}</Typography.Text>

View File

@ -1,19 +1,20 @@
import { ReactNode, useCallback, useEffect, useState } from 'react';
import { Form, Input, Button, Divider, Space } from 'antd';
import {
MailOutlined,
LockOutlined,
UnlockOutlined,
GoogleOutlined,
GithubOutlined,
FacebookOutlined,
GithubOutlined,
GoogleOutlined,
LockOutlined,
MailOutlined,
UnlockOutlined,
UserAddOutlined,
UserOutlined,
} from '@ant-design/icons';
import { Button, Divider, Form, Input, Space } from 'antd';
import { ReactNode, useCallback, useEffect, useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { OAuthProviders, useAuth } from '../../contexts/AuthContext';
import { Routes } from '../../routes';
import validateMessages from './validateMessages';
import { emailRules, requiredRules, usernameRules } from '../../utils/form';
import { buildRedirectUrl } from '../../utils/url';
import {
emailNotVerified,
logInFailed,
@ -21,8 +22,7 @@ import {
signUpFailed,
signUpSuccessful,
} from './notifications';
import { emailRules, requiredRules, usernameRules } from '../../utils/form';
import { buildRedirectUrl } from '../../utils/url';
import validateMessages from './validateMessages';
const { Item } = Form;
@ -95,7 +95,11 @@ const Login = ({ formRole }: { formRole: FormRoles }) => {
};
const oauthMethods: {
[provider: string]: { label: string; icon: ReactNode; onClick: () => void };
[provider: string]: {
label: string;
icon: ReactNode;
onClick: () => void;
};
} = {
google: {
label: 'Google',
@ -186,7 +190,7 @@ const Login = ({ formRole }: { formRole: FormRoles }) => {
const OauthSection = () => {
return isOauthEnabled ? (
<>
<Space direction='horizontal' style={{ width: '100%', justifyContent: 'center' }}>
<Space direction="horizontal" style={{ width: '100%', justifyContent: 'center' }}>
{providersReady &&
Object.keys(oauthMethods).map(
(provider) =>
@ -224,8 +228,8 @@ const Login = ({ formRole }: { formRole: FormRoles }) => {
const submitLogin = (
<Button
type='primary'
htmlType='submit'
type="primary"
htmlType="submit"
style={{ width: '100%' }}
loading={isEmailLoading}
disabled={isAnythingLoading}
@ -237,8 +241,8 @@ const Login = ({ formRole }: { formRole: FormRoles }) => {
const submitSignUp = (
<Button
type='primary'
htmlType='submit'
type="primary"
htmlType="submit"
style={{ width: '100%' }}
loading={isEmailLoading}
disabled={isAnythingLoading}
@ -249,7 +253,7 @@ const Login = ({ formRole }: { formRole: FormRoles }) => {
);
return (
<div className='auth-container'>
<div className="auth-container">
<Divider>{formRole}</Divider>
{providersReady && <OauthSection />}
<Form
@ -257,23 +261,23 @@ const Login = ({ formRole }: { formRole: FormRoles }) => {
validateMessages={validateMessages}
form={formEmail}
>
<Item name='email' rules={emailRules} hasFeedback>
<Item name="email" rules={emailRules} hasFeedback>
<Input
prefix={<MailOutlined />}
placeholder='Email'
autoComplete='email'
placeholder="Email"
autoComplete="email"
disabled={isAnythingLoading}
/>
</Item>
{!isLogin && (
<Item name='username' rules={usernameRules} hasFeedback>
<Input prefix={<UserOutlined />} placeholder='Username' autoComplete='name' />
<Item name="username" rules={usernameRules} hasFeedback>
<Input prefix={<UserOutlined />} placeholder="Username" autoComplete="name" />
</Item>
)}
<Item name='password' rules={requiredRules} hasFeedback>
<Item name="password" rules={requiredRules} hasFeedback>
<Input.Password
placeholder='Password'
autoComplete='current-password'
placeholder="Password"
autoComplete="current-password"
prefix={<LockOutlined />}
disabled={isAnythingLoading}
/>

View File

@ -1,8 +1,8 @@
import { useEffect } from 'react';
import { useMatch, useNavigate, useSearchParams } from 'react-router-dom';
import { AuthProviderInfo } from '../../pocketbase';
import Loader from '../../components/Loader';
import { OAuthProviders, useAuth } from '../../contexts/AuthContext';
import { AuthProviderInfo } from '../../pocketbase';
import { Routes } from '../../routes';
import { logInSuccessful } from './notifications';

View File

@ -1,30 +1,30 @@
import { useEffect, useState } from 'react';
import { generatePath, useNavigate } from 'react-router-dom';
import { Form, Input, Button, Divider, Alert, Space, List, Pagination, Typography } from 'antd';
import {
UserOutlined,
MailOutlined,
ArrowRightOutlined,
EditOutlined,
GlobalOutlined,
EyeOutlined,
GlobalOutlined,
MailOutlined,
UserOutlined,
} from '@ant-design/icons';
import validateMessages from './validateMessages';
import { Alert, Button, Divider, Form, Input, List, Pagination, Space, Typography } from 'antd';
import { useEffect, useState } from 'react';
import { generatePath, useNavigate } from 'react-router-dom';
import { TunesResponse, TunesVisibilityOptions } from '../../@types/pocketbase-types';
import TuneTag from '../../components/TuneTag';
import { useAuth } from '../../contexts/AuthContext';
import {
restrictedPage,
sendingEmailVerificationFailed,
emailVerificationSent,
profileUpdateSuccess,
profileUpdateFailed,
} from './notifications';
import useDb from '../../hooks/useDb';
import { Routes } from '../../routes';
import { usernameRules } from '../../utils/form';
import { formatTime } from '../../utils/time';
import useDb from '../../hooks/useDb';
import { aspirationMapper } from '../../utils/tune/mappers';
import { TunesResponse, TunesVisibilityOptions } from '../../@types/pocketbase-types';
import TuneTag from '../../components/TuneTag';
import {
emailVerificationSent,
profileUpdateFailed,
profileUpdateSuccess,
restrictedPage,
sendingEmailVerificationFailed,
} from './notifications';
import validateMessages from './validateMessages';
const { Item } = Form;
@ -111,14 +111,14 @@ const Profile = () => {
return (
<>
<div className='auth-container'>
<div className="auth-container">
{!currentUser?.verified && (
<>
<Divider>Email verification</Divider>
<Space direction='vertical' style={{ width: '100%' }} size='large'>
<Alert message='Your email address is not verified!' type='error' showIcon />
<Space direction="vertical" style={{ width: '100%' }} size="large">
<Alert message="Your email address is not verified!" type="error" showIcon />
<Button
type='primary'
type="primary"
style={{ width: '100%' }}
icon={<MailOutlined />}
disabled={isVerificationSent}
@ -131,7 +131,7 @@ const Profile = () => {
</>
)}
<Divider>Your Profile</Divider>
<Space direction='vertical' style={{ width: '100%' }} size='large'>
<Space direction="vertical" style={{ width: '100%' }} size="large">
<Form
validateMessages={validateMessages}
form={formProfile}
@ -147,16 +147,16 @@ const Profile = () => {
},
]}
>
<Item name='username' rules={usernameRules} hasFeedback>
<Input prefix={<UserOutlined />} placeholder='Username' autoComplete='name' />
<Item name="username" rules={usernameRules} hasFeedback>
<Input prefix={<UserOutlined />} placeholder="Username" autoComplete="name" />
</Item>
<Item name='email'>
<Input prefix={<MailOutlined />} placeholder='Email' disabled />
<Item name="email">
<Input prefix={<MailOutlined />} placeholder="Email" disabled />
</Item>
<Item>
<Button
type='primary'
htmlType='submit'
type="primary"
htmlType="submit"
style={{ width: '100%' }}
icon={<UserOutlined />}
loading={isProfileLoading}
@ -167,7 +167,7 @@ const Profile = () => {
</Form>
</Space>
</div>
<div className='small-container'>
<div className="small-container">
<Divider>Your tunes</Divider>
<List
dataSource={tunesDataSource}
@ -187,10 +187,10 @@ const Profile = () => {
/>,
]}
>
<Space direction='vertical'>
<Space direction="vertical">
<List.Item.Meta
title={
<Space direction='vertical'>
<Space direction="vertical">
{tune.vehicleName}
<TuneTag tag={tune.tags} />
<Typography.Text italic>{tune.signature}</Typography.Text>

View File

@ -1,12 +1,12 @@
import { useState } from 'react';
import { Form, Input, Button, Divider } from 'antd';
import { MailOutlined } from '@ant-design/icons';
import { Button, Divider, Form, Input } from 'antd';
import { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useAuth } from '../../contexts/AuthContext';
import { Routes } from '../../routes';
import validateMessages from './validateMessages';
import { resetFailed, resetSuccessful } from './notifications';
import { emailRules } from '../../utils/form';
import { resetFailed, resetSuccessful } from './notifications';
import validateMessages from './validateMessages';
const { Item } = Form;
@ -30,7 +30,7 @@ const ResetPassword = () => {
};
return (
<div className='auth-container'>
<div className="auth-container">
<Divider>Reset password</Divider>
<Form
initialValues={{ remember: true }}
@ -38,11 +38,11 @@ const ResetPassword = () => {
validateMessages={validateMessages}
form={form}
>
<Item name='email' rules={emailRules} hasFeedback>
<Input prefix={<MailOutlined />} placeholder='Email' autoComplete='email' />
<Item name="email" rules={emailRules} hasFeedback>
<Input prefix={<MailOutlined />} placeholder="Email" autoComplete="email" />
</Item>
<Item>
<Button type='primary' htmlType='submit' style={{ width: '100%' }} loading={isLoading}>
<Button type="primary" htmlType="submit" style={{ width: '100%' }} loading={isLoading}>
Reset password
</Button>
</Item>

View File

@ -1,11 +1,11 @@
import { Button, Divider, Form, Input } from 'antd';
import { LockOutlined } from '@ant-design/icons';
import { Button, Divider, Form, Input } from 'antd';
import { useState } from 'react';
import { Link, useMatch, useNavigate } from 'react-router-dom';
import { useAuth } from '../../contexts/AuthContext';
import { Routes } from '../../routes';
import { passwordUpdateFailed, passwordUpdateSuccess } from './notifications';
import { passwordRules } from '../../utils/form';
import { passwordUpdateFailed, passwordUpdateSuccess } from './notifications';
import validateMessages from './validateMessages';
const { Item } = Form;
@ -31,26 +31,26 @@ const ResetPasswordConfirmation = () => {
};
return (
<div className='auth-container'>
<div className="auth-container">
<Divider>Change password</Divider>
<Form
initialValues={{ remember: true }}
onFinish={changePassword}
validateMessages={validateMessages}
autoComplete='off'
autoComplete="off"
form={form}
>
<Item name='password' rules={passwordRules} hasFeedback>
<Item name="password" rules={passwordRules} hasFeedback>
<Input.Password
placeholder='New password'
autoComplete='new-password'
placeholder="New password"
autoComplete="new-password"
prefix={<LockOutlined />}
/>
</Item>
<Item>
<Button
type='primary'
htmlType='submit'
type="primary"
htmlType="submit"
style={{ width: '100%' }}
icon={<LockOutlined />}
loading={isLoading}

View File

@ -1,4 +1,4 @@
import PocketBase, { ClientResponseError, AuthMethodsList, AuthProviderInfo } from 'pocketbase';
import PocketBase, { AuthMethodsList, AuthProviderInfo, ClientResponseError } from 'pocketbase';
import { fetchEnv } from './utils/env';
const API_URL = fetchEnv('VITE_POCKETBASE_API_URL');

View File

@ -32,9 +32,10 @@ class LogValidator implements ParserInterface {
}
private checkMLG() {
const fileFormat = new TextDecoder('utf8').decode(this.buffer.slice(0, this.mlgFormatLength))
.replace(/\x00/gu, '');
const fileFormat = new TextDecoder('utf8')
.decode(this.buffer.slice(0, this.mlgFormatLength))
// biome-ignore lint/suspicious/noControlCharactersInRegex: false positive
.replace(/\x00/gu, '');
if (fileFormat === 'MLVLG') {
this.isMLGLogs = true;

View File

@ -1,4 +1,4 @@
import { Record, Result, BlockType } from 'mlg-converter/dist/types';
import { BlockType, Record, Result } from 'mlg-converter/dist/types';
import { ParserInterface } from '../ParserInterface';
class MslLogParser implements ParserInterface {

View File

@ -1,8 +1,8 @@
import { Parser } from 'mlg-converter';
import { Result } from 'mlg-converter/dist/types';
import { decompress } from '../utils/compression';
import LogValidator from '../utils/logs/LogValidator';
import MslLogParser from '../utils/logs/MslLogParser';
import { decompress } from '../utils/compression';
const ctx: Worker = self as any;