Compare commits
9 Commits
745f4fb59a
...
4f9afdefe9
Author | SHA1 | Date |
---|---|---|
Piotr Rogowski | 4f9afdefe9 | |
Piotr Rogowski | 43d55dc71e | |
Piotr Rogowski | 8a513dfae8 | |
Piotr Rogowski | 6af0f62fc9 | |
Piotr Rogowski | 54b71dea19 | |
Piotr Rogowski | badc20db2e | |
Piotr Rogowski | 4c7da032af | |
Piotr Rogowski | a42d9a2bcf | |
Piotr Rogowski | 572be519cb |
|
@ -19,7 +19,7 @@
|
|||
"editorconfig.editorconfig",
|
||||
"davidanson.vscode-markdownlint",
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"rome.rome"
|
||||
"biomejs.biome"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
31
package.json
31
package.json
|
@ -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"
|
||||
|
|
84
src/App.tsx
84
src/App.tsx
|
@ -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 }} />
|
||||
}
|
||||
/>
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
@ -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={{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Skeleton } from 'antd';
|
||||
|
||||
const Loader = () => (
|
||||
<div className='small-container'>
|
||||
<div className="small-container">
|
||||
<Skeleton active />
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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' }}>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) => (
|
||||
|
|
|
@ -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 }}
|
||||
>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}"`,
|
||||
});
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
10
src/main.tsx
10
src/main.tsx
|
@ -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';
|
||||
|
||||
|
|
|
@ -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')}
|
||||
>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
/>
|
||||
|
|
|
@ -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 />}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue