Load user tooth logs (#816)
This commit is contained in:
parent
9c746cdf43
commit
2fbc2be2e0
3
.env
3
.env
|
@ -4,7 +4,4 @@ VITE_WEB_URL=http://localhost:5173
|
||||||
VITE_SENTRY_DSN=
|
VITE_SENTRY_DSN=
|
||||||
VITE_GTM_ID=
|
VITE_GTM_ID=
|
||||||
|
|
||||||
# TODO: remove this later
|
|
||||||
VITE_CDN_URL="https://public-bucket.speedytuner.app"
|
|
||||||
|
|
||||||
VITE_POCKETBASE_API_URL="https://api.hypertuner.cloud"
|
VITE_POCKETBASE_API_URL="https://api.hypertuner.cloud"
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import Pako from 'pako';
|
import Pako from 'pako';
|
||||||
import * as Sentry from '@sentry/browser';
|
import * as Sentry from '@sentry/browser';
|
||||||
import { fetchEnv } from '../utils/env';
|
|
||||||
import { API_URL } from '../pocketbase';
|
import { API_URL } from '../pocketbase';
|
||||||
import { Collections } from '../@types/pocketbase-types';
|
import { Collections } from '../@types/pocketbase-types';
|
||||||
import useDb from './useDb';
|
import useDb from './useDb';
|
||||||
|
@ -9,8 +8,6 @@ import {
|
||||||
OnProgress,
|
OnProgress,
|
||||||
} from '../utils/http';
|
} from '../utils/http';
|
||||||
|
|
||||||
export const CDN_URL = fetchEnv('VITE_CDN_URL');
|
|
||||||
|
|
||||||
const useServerStorage = () => {
|
const useServerStorage = () => {
|
||||||
const { getIni } = useDb();
|
const { getIni } = useDb();
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
/* eslint-disable import/no-webpack-loader-syntax */
|
import {
|
||||||
|
generatePath,
|
||||||
|
Link,
|
||||||
|
useMatch,
|
||||||
|
useNavigate,
|
||||||
|
} from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
@ -19,20 +24,16 @@ import {
|
||||||
FileTextOutlined,
|
FileTextOutlined,
|
||||||
GlobalOutlined,
|
GlobalOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import * as Sentry from '@sentry/browser';
|
|
||||||
import useBreakpoint from 'antd/lib/grid/hooks/useBreakpoint';
|
import useBreakpoint from 'antd/lib/grid/hooks/useBreakpoint';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PerfectScrollbar from 'react-perfect-scrollbar';
|
import PerfectScrollbar from 'react-perfect-scrollbar';
|
||||||
|
import Pako from 'pako';
|
||||||
import {
|
import {
|
||||||
AppState,
|
AppState,
|
||||||
ConfigState,
|
ToothLogsState,
|
||||||
LogsState,
|
TuneDataState,
|
||||||
UIState,
|
UIState,
|
||||||
} from '../types/state';
|
} from '../types/state';
|
||||||
import {
|
|
||||||
loadCompositeLogs,
|
|
||||||
loadToothLogs,
|
|
||||||
} from '../utils/api';
|
|
||||||
import store from '../store';
|
import store from '../store';
|
||||||
import { formatBytes } from '../utils/numbers';
|
import { formatBytes } from '../utils/numbers';
|
||||||
import CompositeCanvas from '../components/TriggerLogs/CompositeCanvas';
|
import CompositeCanvas from '../components/TriggerLogs/CompositeCanvas';
|
||||||
|
@ -43,6 +44,10 @@ import TriggerLogsParser, {
|
||||||
import ToothCanvas from '../components/TriggerLogs/ToothCanvas';
|
import ToothCanvas from '../components/TriggerLogs/ToothCanvas';
|
||||||
import Loader from '../components/Loader';
|
import Loader from '../components/Loader';
|
||||||
import { Colors } from '../utils/colors';
|
import { Colors } from '../utils/colors';
|
||||||
|
import { Routes } from '../routes';
|
||||||
|
import { removeFilenameSuffix } from '../pocketbase';
|
||||||
|
import useServerStorage from '../hooks/useServerStorage';
|
||||||
|
import { isAbortedRequest } from '../utils/error';
|
||||||
|
|
||||||
const { Content } = Layout;
|
const { Content } = Layout;
|
||||||
const { Step } = Steps;
|
const { Step } = Steps;
|
||||||
|
@ -51,18 +56,27 @@ const edgeUnknown = 'Unknown';
|
||||||
|
|
||||||
const badgeStyle = { backgroundColor: Colors.TEXT };
|
const badgeStyle = { backgroundColor: Colors.TEXT };
|
||||||
|
|
||||||
const mapStateToProps = (state: AppState) => ({
|
|
||||||
ui: state.ui,
|
|
||||||
status: state.status,
|
|
||||||
config: state.config,
|
|
||||||
loadedLogs: state.logs,
|
|
||||||
});
|
|
||||||
|
|
||||||
const margin = 30;
|
const margin = 30;
|
||||||
const sidebarWidth = 250;
|
const sidebarWidth = 250;
|
||||||
const minCanvasHeightInner = 600;
|
const minCanvasHeightInner = 600;
|
||||||
|
|
||||||
const Diagnose = ({ ui, config, loadedLogs }: { ui: UIState, config: ConfigState, loadedLogs: LogsState }) => {
|
const mapStateToProps = (state: AppState) => ({
|
||||||
|
ui: state.ui,
|
||||||
|
status: state.status,
|
||||||
|
config: state.config,
|
||||||
|
loadedToothLogs: state.toothLogs,
|
||||||
|
tuneData: state.tuneData,
|
||||||
|
});
|
||||||
|
|
||||||
|
const Diagnose = ({
|
||||||
|
ui,
|
||||||
|
loadedToothLogs,
|
||||||
|
tuneData,
|
||||||
|
}: {
|
||||||
|
ui: UIState;
|
||||||
|
loadedToothLogs: ToothLogsState;
|
||||||
|
tuneData: TuneDataState;
|
||||||
|
}) => {
|
||||||
const { lg } = useBreakpoint();
|
const { lg } = useBreakpoint();
|
||||||
const { Sider } = Layout;
|
const { Sider } = Layout;
|
||||||
const [progress, setProgress] = useState(0);
|
const [progress, setProgress] = useState(0);
|
||||||
|
@ -73,11 +87,15 @@ const Diagnose = ({ ui, config, loadedLogs }: { ui: UIState, config: ConfigState
|
||||||
const contentRef = useRef<HTMLDivElement | null>(null);
|
const contentRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [canvasWidth, setCanvasWidth] = useState(0);
|
const [canvasWidth, setCanvasWidth] = useState(0);
|
||||||
const [canvasHeight, setCanvasHeight] = useState(0);
|
const [canvasHeight, setCanvasHeight] = useState(0);
|
||||||
|
const routeMatch = useMatch(Routes.TUNE_DIAGNOSE_FILE);
|
||||||
|
const { fetchLogFileWithProgress } = useServerStorage();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const calculateCanvasSize = useCallback(() => {
|
const calculateCanvasSize = useCallback(() => {
|
||||||
setCanvasWidth((contentRef.current?.clientWidth || 0) - margin);
|
setCanvasWidth((contentRef.current?.clientWidth || 0) - margin);
|
||||||
|
|
||||||
if (window.innerHeight > minCanvasHeightInner) {
|
if (window.innerHeight > minCanvasHeightInner) {
|
||||||
setCanvasHeight(Math.round((window.innerHeight - 250) / 2));
|
setCanvasHeight(Math.round(window.innerHeight - 250));
|
||||||
} else {
|
} else {
|
||||||
setCanvasHeight(minCanvasHeightInner / 2);
|
setCanvasHeight(minCanvasHeightInner / 2);
|
||||||
}
|
}
|
||||||
|
@ -91,47 +109,92 @@ const Diagnose = ({ ui, config, loadedLogs }: { ui: UIState, config: ConfigState
|
||||||
store.dispatch({ type: 'ui/sidebarCollapsed', payload: collapsed });
|
store.dispatch({ type: 'ui/sidebarCollapsed', payload: collapsed });
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const [logs, setLogs] = useState<CompositeLogEntry[]>();
|
|
||||||
const [toothLogs, setToothLogs] = useState<ToothLogEntry[]>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const { signal } = controller;
|
const { signal } = controller;
|
||||||
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
const pako = await import('pako');
|
const logFileName = routeMatch?.params.fileName;
|
||||||
|
|
||||||
|
if (!logFileName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// user didn't upload any logs
|
||||||
|
if (tuneData && (tuneData.toothLogFiles || []).length === 0) {
|
||||||
|
navigate(Routes.HUB);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const compositeRaw = await loadCompositeLogs((percent, total, edge) => {
|
const raw = await fetchLogFileWithProgress(tuneData.id, logFileName, (percent, total, edge) => {
|
||||||
setProgress(percent);
|
setProgress(percent);
|
||||||
setFileSize(formatBytes(total));
|
setFileSize(formatBytes(total));
|
||||||
setEdgeLocation(edge || edgeUnknown);
|
setEdgeLocation(edge || edgeUnknown);
|
||||||
}, signal);
|
}, signal);
|
||||||
|
|
||||||
const toothRaw = await loadToothLogs(undefined, signal);
|
setFileSize(formatBytes(raw.byteLength));
|
||||||
|
|
||||||
setFileSize(formatBytes(compositeRaw.byteLength));
|
|
||||||
setStep(1);
|
setStep(1);
|
||||||
|
|
||||||
const resultComposite = (new TriggerLogsParser(pako.inflate(new Uint8Array(compositeRaw))))
|
const parser = new TriggerLogsParser(Pako.inflate(new Uint8Array(raw))).parse();
|
||||||
.parse()
|
|
||||||
.getCompositeLogs();
|
|
||||||
const resultTooth = (new TriggerLogsParser(pako.inflate(new Uint8Array(toothRaw))))
|
|
||||||
.parse()
|
|
||||||
.getToothLogs();
|
|
||||||
|
|
||||||
setLogs(resultComposite);
|
let type = '';
|
||||||
setToothLogs(resultTooth);
|
let result: CompositeLogEntry[] | ToothLogEntry[] = [];
|
||||||
|
|
||||||
|
if (parser.isComposite()) {
|
||||||
|
type = 'composite';
|
||||||
|
result = parser.getCompositeLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parser.isTooth()) {
|
||||||
|
type = 'tooth';
|
||||||
|
result = parser.getToothLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
store.dispatch({
|
||||||
|
type: 'toothLogs/load', payload: {
|
||||||
|
fileName: logFileName,
|
||||||
|
logs: result,
|
||||||
|
type,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
setStep(2);
|
setStep(2);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (isAbortedRequest(error as Error)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setFetchError(error as Error);
|
setFetchError(error as Error);
|
||||||
Sentry.captureException(error);
|
|
||||||
console.error(error);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadData();
|
// first visit, logs are not loaded yet
|
||||||
|
if (!loadedToothLogs.type && tuneData?.tuneId) {
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// file changed, reload
|
||||||
|
if (loadedToothLogs.type && loadedToothLogs.fileName !== routeMatch?.params.fileName) {
|
||||||
|
// setToothLogs(undefined);
|
||||||
|
// setCompositeLogs(undefined);
|
||||||
|
store.dispatch({ type: 'toothLogs/load', payload: {} });
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// user navigated to logs root page
|
||||||
|
if (!routeMatch?.params.fileName && tuneData.toothLogFiles?.length) {
|
||||||
|
// either redirect to the first log or to the latest selected
|
||||||
|
if (loadedToothLogs.fileName) {
|
||||||
|
navigate(generatePath(Routes.TUNE_DIAGNOSE_FILE, { tuneId: tuneData.tuneId, fileName: loadedToothLogs.fileName }));
|
||||||
|
} else {
|
||||||
|
const firstLogFile = (tuneData.toothLogFiles || [])[0];
|
||||||
|
navigate(generatePath(Routes.TUNE_DIAGNOSE_FILE, { tuneId: tuneData.tuneId, fileName: firstLogFile }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
calculateCanvasSize();
|
calculateCanvasSize();
|
||||||
|
|
||||||
window.addEventListener('resize', calculateCanvasSize);
|
window.addEventListener('resize', calculateCanvasSize);
|
||||||
|
@ -140,12 +203,32 @@ const Diagnose = ({ ui, config, loadedLogs }: { ui: UIState, config: ConfigState
|
||||||
controller.abort();
|
controller.abort();
|
||||||
window.removeEventListener('resize', calculateCanvasSize);
|
window.removeEventListener('resize', calculateCanvasSize);
|
||||||
};
|
};
|
||||||
}, [calculateCanvasSize, loadedLogs, ui.sidebarCollapsed]);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [calculateCanvasSize, routeMatch?.params.fileName, ui.sidebarCollapsed, tuneData?.tuneId]);
|
||||||
|
|
||||||
|
const graphSection = () => {
|
||||||
|
switch (loadedToothLogs.type) {
|
||||||
|
case 'composite':
|
||||||
|
return <CompositeCanvas
|
||||||
|
data={loadedToothLogs.logs as CompositeLogEntry[]}
|
||||||
|
width={canvasWidth}
|
||||||
|
height={canvasHeight}
|
||||||
|
/>;
|
||||||
|
case 'tooth':
|
||||||
|
return <ToothCanvas
|
||||||
|
data={loadedToothLogs.logs}
|
||||||
|
width={canvasWidth}
|
||||||
|
height={canvasHeight}
|
||||||
|
/>;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Sider {...(siderProps as any)} className="app-sidebar">
|
<Sider {...(siderProps as any)} className="app-sidebar">
|
||||||
{!logs && !(loadedLogs.logs || []).length ?
|
{!loadedToothLogs.type ?
|
||||||
<Loader />
|
<Loader />
|
||||||
:
|
:
|
||||||
!ui.sidebarCollapsed &&
|
!ui.sidebarCollapsed &&
|
||||||
|
@ -155,15 +238,26 @@ const Diagnose = ({ ui, config, loadedLogs }: { ui: UIState, config: ConfigState
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
label: (
|
label: (
|
||||||
<Badge size="small" style={badgeStyle} count={1} offset={[10, -3]}>
|
<Badge size="small" style={badgeStyle} count={tuneData?.toothLogFiles?.length} offset={[10, -3]}>
|
||||||
<FileTextOutlined />Files
|
<FileTextOutlined />Files
|
||||||
</Badge>
|
</Badge>
|
||||||
),
|
),
|
||||||
key: 'files',
|
key: 'files',
|
||||||
children: (
|
children: (
|
||||||
<PerfectScrollbar options={{ suppressScrollX: true }}>
|
<PerfectScrollbar options={{ suppressScrollX: true }}>
|
||||||
<Typography.Paragraph>tooth.csv</Typography.Paragraph>
|
{tuneData?.toothLogFiles?.map((fileName) => (
|
||||||
<Typography.Paragraph>composite.csv</Typography.Paragraph>
|
<Typography.Paragraph key={fileName} ellipsis>
|
||||||
|
<Link
|
||||||
|
to={generatePath(Routes.TUNE_DIAGNOSE_FILE, { tuneId: tuneData.tuneId, fileName })}
|
||||||
|
style={
|
||||||
|
routeMatch?.params.fileName === fileName ?
|
||||||
|
{} : { color: 'inherit' }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{removeFilenameSuffix(fileName)}
|
||||||
|
</Link>
|
||||||
|
</Typography.Paragraph>
|
||||||
|
))}
|
||||||
</PerfectScrollbar>
|
</PerfectScrollbar>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -174,22 +268,9 @@ const Diagnose = ({ ui, config, loadedLogs }: { ui: UIState, config: ConfigState
|
||||||
<Layout style={{ width: '100%', textAlign: 'center', marginTop: 50 }}>
|
<Layout style={{ width: '100%', textAlign: 'center', marginTop: 50 }}>
|
||||||
<Content>
|
<Content>
|
||||||
<div ref={contentRef} style={{ width: '100%', marginRight: margin }}>
|
<div ref={contentRef} style={{ width: '100%', marginRight: margin }}>
|
||||||
{toothLogs && logs
|
{loadedToothLogs.type
|
||||||
?
|
?
|
||||||
(
|
graphSection()
|
||||||
<Space direction="vertical" size="large">
|
|
||||||
<ToothCanvas
|
|
||||||
data={toothLogs!}
|
|
||||||
width={canvasWidth}
|
|
||||||
height={canvasHeight}
|
|
||||||
/>
|
|
||||||
<CompositeCanvas
|
|
||||||
data={logs!}
|
|
||||||
width={canvasWidth}
|
|
||||||
height={canvasHeight}
|
|
||||||
/>
|
|
||||||
</Space>
|
|
||||||
)
|
|
||||||
:
|
:
|
||||||
<Space
|
<Space
|
||||||
direction="vertical"
|
direction="vertical"
|
||||||
|
|
|
@ -61,6 +61,8 @@ import Loader from '../components/Loader';
|
||||||
import { Colors } from '../utils/colors';
|
import { Colors } from '../utils/colors';
|
||||||
import useServerStorage from '../hooks/useServerStorage';
|
import useServerStorage from '../hooks/useServerStorage';
|
||||||
import { Routes } from '../routes';
|
import { Routes } from '../routes';
|
||||||
|
import { removeFilenameSuffix } from '../pocketbase';
|
||||||
|
import { isAbortedRequest } from '../utils/error';
|
||||||
|
|
||||||
const { Content } = Layout;
|
const { Content } = Layout;
|
||||||
const { Step } = Steps;
|
const { Step } = Steps;
|
||||||
|
@ -84,10 +86,10 @@ const Logs = ({
|
||||||
loadedLogs,
|
loadedLogs,
|
||||||
tuneData,
|
tuneData,
|
||||||
}: {
|
}: {
|
||||||
ui: UIState,
|
ui: UIState;
|
||||||
config: ConfigState,
|
config: ConfigState;
|
||||||
loadedLogs: LogsState,
|
loadedLogs: LogsState;
|
||||||
tuneData: TuneDataState,
|
tuneData: TuneDataState;
|
||||||
}) => {
|
}) => {
|
||||||
const { lg } = useBreakpoint();
|
const { lg } = useBreakpoint();
|
||||||
const { Sider } = Layout;
|
const { Sider } = Layout;
|
||||||
|
@ -188,8 +190,7 @@ const Logs = ({
|
||||||
|
|
||||||
setFileSize(formatBytes(raw.byteLength));
|
setFileSize(formatBytes(raw.byteLength));
|
||||||
|
|
||||||
const pako = await import('pako');
|
worker.postMessage(raw);
|
||||||
worker.postMessage(pako.inflate(new Uint8Array(raw)).buffer);
|
|
||||||
|
|
||||||
worker.onmessage = ({ data }) => {
|
worker.onmessage = ({ data }) => {
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
|
@ -221,6 +222,10 @@ const Logs = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (isAbortedRequest(error as Error)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setFetchError(error as Error);
|
setFetchError(error as Error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -264,12 +269,12 @@ const Logs = ({
|
||||||
window.removeEventListener('resize', calculateCanvasSize);
|
window.removeEventListener('resize', calculateCanvasSize);
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [calculateCanvasSize, config?.datalog, config?.outputChannels, loadedLogs, ui.sidebarCollapsed, routeMatch?.params.fileName]);
|
}, [calculateCanvasSize, config?.datalog, config?.outputChannels, ui.sidebarCollapsed, routeMatch?.params.fileName]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Sider {...(siderProps as any)} className="app-sidebar">
|
<Sider {...(siderProps as any)} className="app-sidebar">
|
||||||
{!logs && !(loadedLogs.logs || []).length ?
|
{!(loadedLogs.logs || []).length ?
|
||||||
<Loader />
|
<Loader />
|
||||||
:
|
:
|
||||||
!ui.sidebarCollapsed &&
|
!ui.sidebarCollapsed &&
|
||||||
|
@ -318,15 +323,15 @@ const Logs = ({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: (
|
label: (
|
||||||
<Badge size="small" style={badgeStyle} count={tuneData.logFiles?.length} offset={[10, -3]}>
|
<Badge size="small" style={badgeStyle} count={tuneData?.logFiles?.length} offset={[10, -3]}>
|
||||||
<FileTextOutlined />Files
|
<FileTextOutlined />Files
|
||||||
</Badge>
|
</Badge>
|
||||||
),
|
),
|
||||||
key: 'files',
|
key: 'files',
|
||||||
children: (
|
children: (
|
||||||
<PerfectScrollbar options={{ suppressScrollX: true }}>
|
<PerfectScrollbar options={{ suppressScrollX: true }}>
|
||||||
{tuneData.logFiles?.map((fileName) => (
|
{tuneData?.logFiles?.map((fileName) => (
|
||||||
<Typography.Paragraph key={fileName}>
|
<Typography.Paragraph key={fileName} ellipsis>
|
||||||
<Link
|
<Link
|
||||||
to={generatePath(Routes.TUNE_LOGS_FILE, { tuneId: tuneData.tuneId, fileName })}
|
to={generatePath(Routes.TUNE_LOGS_FILE, { tuneId: tuneData.tuneId, fileName })}
|
||||||
style={
|
style={
|
||||||
|
@ -334,7 +339,7 @@ const Logs = ({
|
||||||
{} : { color: 'inherit' }
|
{} : { color: 'inherit' }
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{fileName}
|
{removeFilenameSuffix(fileName)}
|
||||||
</Link>
|
</Link>
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -69,6 +69,7 @@ import {
|
||||||
TunesRecordFull,
|
TunesRecordFull,
|
||||||
TunesRecordPartial,
|
TunesRecordPartial,
|
||||||
} from '../types/dbData';
|
} from '../types/dbData';
|
||||||
|
import { removeFilenameSuffix } from '../pocketbase';
|
||||||
|
|
||||||
const { Item, useForm } = Form;
|
const { Item, useForm } = Form;
|
||||||
|
|
||||||
|
@ -139,8 +140,6 @@ const UploadPage = () => {
|
||||||
|
|
||||||
const noop = () => { };
|
const noop = () => { };
|
||||||
|
|
||||||
const removeFilenameSuffix = (filename: string) => filename.replace(/(.+)(_\w{10})(\.\w+)$/, '$1$3');
|
|
||||||
|
|
||||||
const goToNewTune = () => navigate(generatePath(Routes.TUNE_TUNE, {
|
const goToNewTune = () => navigate(generatePath(Routes.TUNE_TUNE, {
|
||||||
tuneId: newTuneId!,
|
tuneId: newTuneId!,
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -17,6 +17,8 @@ const formatError = (error: any) => {
|
||||||
return message;
|
return message;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const removeFilenameSuffix = (filename: string) => filename.replace(/(.+)(_\w{10})(\.\w+)$/, '$1$3');
|
||||||
|
|
||||||
// NOTE: PocketBase doesn't return ISO time, this may change here: https://github.com/pocketbase/pocketbase/issues/376
|
// NOTE: PocketBase doesn't return ISO time, this may change here: https://github.com/pocketbase/pocketbase/issues/376
|
||||||
const formatTime = (time: string) => new Date(`${time}Z`).toLocaleString();
|
const formatTime = (time: string) => new Date(`${time}Z`).toLocaleString();
|
||||||
|
|
||||||
|
@ -25,4 +27,5 @@ export {
|
||||||
client,
|
client,
|
||||||
formatError,
|
formatError,
|
||||||
formatTime,
|
formatTime,
|
||||||
|
removeFilenameSuffix,
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
AppState,
|
AppState,
|
||||||
ConfigState,
|
ConfigState,
|
||||||
LogsState,
|
LogsState,
|
||||||
|
ToothLogsState,
|
||||||
TuneDataState,
|
TuneDataState,
|
||||||
TuneState,
|
TuneState,
|
||||||
UpdateTunePayload,
|
UpdateTunePayload,
|
||||||
|
@ -25,6 +26,7 @@ const setTuneId = createAction<string>('navigation/tuneId');
|
||||||
|
|
||||||
// logs
|
// logs
|
||||||
const loadLogs = createAction<LogsState>('logs/load');
|
const loadLogs = createAction<LogsState>('logs/load');
|
||||||
|
const loadToothLogs = createAction<ToothLogsState>('toothLogs/load');
|
||||||
|
|
||||||
// status bar
|
// status bar
|
||||||
const setStatus = createAction<string>('status');
|
const setStatus = createAction<string>('status');
|
||||||
|
@ -40,6 +42,7 @@ const initialState: AppState = {
|
||||||
},
|
},
|
||||||
tuneData: {} as any,
|
tuneData: {} as any,
|
||||||
logs: {} as any,
|
logs: {} as any,
|
||||||
|
toothLogs: {} as any,
|
||||||
config: {} as any,
|
config: {} as any,
|
||||||
ui: {
|
ui: {
|
||||||
sidebarCollapsed: false,
|
sidebarCollapsed: false,
|
||||||
|
@ -66,6 +69,9 @@ const rootReducer = createReducer(initialState, (builder) => {
|
||||||
.addCase(loadLogs, (state: AppState, action) => {
|
.addCase(loadLogs, (state: AppState, action) => {
|
||||||
state.logs = action.payload;
|
state.logs = action.payload;
|
||||||
})
|
})
|
||||||
|
.addCase(loadToothLogs, (state: AppState, action) => {
|
||||||
|
state.toothLogs = action.payload;
|
||||||
|
})
|
||||||
.addCase(updateTune, (state: AppState, action) => {
|
.addCase(updateTune, (state: AppState, action) => {
|
||||||
state.tune.constants[action.payload.name].value = action.payload.value;
|
state.tune.constants[action.payload.name].value = action.payload.value;
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,6 +3,10 @@ import {
|
||||||
Logs,
|
Logs,
|
||||||
TuneWithDetails,
|
TuneWithDetails,
|
||||||
} from '@hyper-tuner/types';
|
} from '@hyper-tuner/types';
|
||||||
|
import {
|
||||||
|
CompositeLogEntry,
|
||||||
|
ToothLogEntry,
|
||||||
|
} from '../utils/logs/TriggerLogsParser';
|
||||||
import { TunesRecordFull } from './dbData';
|
import { TunesRecordFull } from './dbData';
|
||||||
|
|
||||||
export interface ConfigState extends Config {}
|
export interface ConfigState extends Config {}
|
||||||
|
@ -16,6 +20,12 @@ export interface LogsState {
|
||||||
logs: Logs;
|
logs: Logs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ToothLogsState {
|
||||||
|
fileName: string;
|
||||||
|
type: 'tooth' | 'composite';
|
||||||
|
logs: CompositeLogEntry[] | ToothLogEntry[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface UIState {
|
export interface UIState {
|
||||||
sidebarCollapsed: boolean;
|
sidebarCollapsed: boolean;
|
||||||
}
|
}
|
||||||
|
@ -33,6 +43,7 @@ export interface AppState {
|
||||||
tuneData: TuneDataState;
|
tuneData: TuneDataState;
|
||||||
config: ConfigState;
|
config: ConfigState;
|
||||||
logs: LogsState,
|
logs: LogsState,
|
||||||
|
toothLogs: ToothLogsState,
|
||||||
ui: UIState;
|
ui: UIState;
|
||||||
status: StatusState;
|
status: StatusState;
|
||||||
navigation: NavigationState;
|
navigation: NavigationState;
|
||||||
|
|
|
@ -4,20 +4,18 @@ import store from '../store';
|
||||||
import stdDialogs from '../data/standardDialogs';
|
import stdDialogs from '../data/standardDialogs';
|
||||||
import help from '../data/help';
|
import help from '../data/help';
|
||||||
import { divider } from '../data/constants';
|
import { divider } from '../data/constants';
|
||||||
import {
|
|
||||||
fetchWithProgress,
|
|
||||||
OnProgress,
|
|
||||||
} from './http';
|
|
||||||
import TuneParser from './tune/TuneParser';
|
import TuneParser from './tune/TuneParser';
|
||||||
import useServerStorage, { CDN_URL } from '../hooks/useServerStorage';
|
import useServerStorage from '../hooks/useServerStorage';
|
||||||
import { TunesRecordFull } from '../types/dbData';
|
import { TunesRecordFull } from '../types/dbData';
|
||||||
import { iniLoadingError } from '../pages/auth/notifications';
|
import { iniLoadingError } from '../pages/auth/notifications';
|
||||||
|
|
||||||
// TODO: refactor this!!
|
// TODO: refactor this!!
|
||||||
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
export const loadTune = async (tuneData: TunesRecordFull | null) => {
|
export const loadTune = async (tuneData: TunesRecordFull | null) => {
|
||||||
if (tuneData === null) {
|
if (tuneData === null) {
|
||||||
store.dispatch({ type: 'config/load', payload: null });
|
store.dispatch({ type: 'config/load', payload: null });
|
||||||
store.dispatch({ type: 'tune/load', payload: null });
|
store.dispatch({ type: 'tune/load', payload: null });
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,24 +60,3 @@ export const loadTune = async (tuneData: TunesRecordFull | null) => {
|
||||||
iniLoadingError((error as Error));
|
iniLoadingError((error as Error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loadLogs = (onProgress?: OnProgress, signal?: AbortSignal) =>
|
|
||||||
fetchWithProgress(
|
|
||||||
`${CDN_URL}/public/temp/long.mlg.gz`,
|
|
||||||
onProgress,
|
|
||||||
signal,
|
|
||||||
).then((response) => response);
|
|
||||||
|
|
||||||
export const loadCompositeLogs = (onProgress?: OnProgress, signal?: AbortSignal) =>
|
|
||||||
fetchWithProgress(
|
|
||||||
`${CDN_URL}/public/temp/composite_1.csv.gz`,
|
|
||||||
onProgress,
|
|
||||||
signal,
|
|
||||||
).then((response) => response);
|
|
||||||
|
|
||||||
export const loadToothLogs = (onProgress?: OnProgress, signal?: AbortSignal) =>
|
|
||||||
fetchWithProgress(
|
|
||||||
`${CDN_URL}/public/temp/tooth_3.csv.gz`,
|
|
||||||
onProgress,
|
|
||||||
signal,
|
|
||||||
).then((response) => response);
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
|
export const isAbortedRequest = (error: Error): boolean => error.message === 'The user aborted a request.';
|
|
@ -49,11 +49,9 @@ class TriggerLogsParser implements ParserInterface {
|
||||||
this.parseCompositeLogs(this.raw);
|
this.parseCompositeLogs(this.raw);
|
||||||
this.parseToothLogs(this.raw);
|
this.parseToothLogs(this.raw);
|
||||||
|
|
||||||
if (this.resultComposite.length > 0) {
|
if (this.resultComposite.length > this.resultTooth.length) {
|
||||||
this.isCompositeLogs = true;
|
this.isCompositeLogs = true;
|
||||||
}
|
} else {
|
||||||
|
|
||||||
if (this.resultTooth.length > 0) {
|
|
||||||
this.isToothLogs = true;
|
this.isToothLogs = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* eslint-disable no-bitwise */
|
/* eslint-disable no-bitwise */
|
||||||
|
|
||||||
import { Parser } from 'mlg-converter';
|
import { Parser } from 'mlg-converter';
|
||||||
|
import Pako from 'pako';
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-globals
|
// eslint-disable-next-line no-restricted-globals
|
||||||
const ctx: Worker = self as any;
|
const ctx: Worker = self as any;
|
||||||
|
@ -8,7 +9,7 @@ const ctx: Worker = self as any;
|
||||||
ctx.addEventListener('message', ({ data }: { data: ArrayBuffer }) => {
|
ctx.addEventListener('message', ({ data }: { data: ArrayBuffer }) => {
|
||||||
try {
|
try {
|
||||||
const t0 = performance.now();
|
const t0 = performance.now();
|
||||||
const result = new Parser(data).parse((progress) => {
|
const result = new Parser(Pako.inflate(new Uint8Array(data)).buffer).parse((progress) => {
|
||||||
ctx.postMessage({
|
ctx.postMessage({
|
||||||
type: 'progress',
|
type: 'progress',
|
||||||
progress,
|
progress,
|
||||||
|
|
Loading…
Reference in New Issue