Upload validation (#364)
This commit is contained in:
parent
0c307b2e4d
commit
bd56b26a5d
|
@ -12,6 +12,7 @@
|
||||||
"@reduxjs/toolkit": "^1.7.1",
|
"@reduxjs/toolkit": "^1.7.1",
|
||||||
"@sentry/react": "^6.16.1",
|
"@sentry/react": "^6.16.1",
|
||||||
"@sentry/tracing": "^6.16.1",
|
"@sentry/tracing": "^6.16.1",
|
||||||
|
"@speedy-tuner/ini": "^0.2.2",
|
||||||
"@speedy-tuner/types": "^0.2.1",
|
"@speedy-tuner/types": "^0.2.1",
|
||||||
"antd": "^4.18.2",
|
"antd": "^4.18.2",
|
||||||
"easymde": "^2.15.0",
|
"easymde": "^2.15.0",
|
||||||
|
@ -3907,6 +3908,33 @@
|
||||||
"eslint-plugin-prettier": "^4.0.0"
|
"eslint-plugin-prettier": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@speedy-tuner/ini": {
|
||||||
|
"version": "0.2.2",
|
||||||
|
"resolved": "https://npm.pkg.github.com/download/@speedy-tuner/ini/0.2.2/38755d6ecbf8f478233f2a40251989192659620e0195f62eb6f4a79b920b9a0e",
|
||||||
|
"integrity": "sha512-sktFLjNF7oZa9haK+a71Ebc6csJ5uC/Huba879WiANydWRF7iIPdFyL6sfp8gOfoOj+I/jhg1MB+1z972hKdWw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@speedy-tuner/types": "^0.2.1",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
|
"parsimmon": "^1.18.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@speedy-tuner/ini/node_modules/argparse": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
||||||
|
},
|
||||||
|
"node_modules/@speedy-tuner/ini/node_modules/js-yaml": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||||
|
"dependencies": {
|
||||||
|
"argparse": "^2.0.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"js-yaml": "bin/js-yaml.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@speedy-tuner/types": {
|
"node_modules/@speedy-tuner/types": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://npm.pkg.github.com/download/@speedy-tuner/types/0.2.1/378faaf77fc78a8b33a9f09b9a742b272cc01a9349fe6fe61081e202d953210b",
|
"resolved": "https://npm.pkg.github.com/download/@speedy-tuner/types/0.2.1/378faaf77fc78a8b33a9f09b9a742b272cc01a9349fe6fe61081e202d953210b",
|
||||||
|
@ -15733,6 +15761,11 @@
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/parsimmon": {
|
||||||
|
"version": "1.18.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/parsimmon/-/parsimmon-1.18.1.tgz",
|
||||||
|
"integrity": "sha512-u7p959wLfGAhJpSDJVYXoyMCXWYwHia78HhRBWqk7AIbxdmlrfdp5wX0l3xv/iTSH5HvhN9K7o26hwwpgS5Nmw=="
|
||||||
|
},
|
||||||
"node_modules/pascal-case": {
|
"node_modules/pascal-case": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
|
||||||
|
@ -27125,6 +27158,31 @@
|
||||||
"eslint-plugin-prettier": "^4.0.0"
|
"eslint-plugin-prettier": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@speedy-tuner/ini": {
|
||||||
|
"version": "0.2.2",
|
||||||
|
"resolved": "https://npm.pkg.github.com/download/@speedy-tuner/ini/0.2.2/38755d6ecbf8f478233f2a40251989192659620e0195f62eb6f4a79b920b9a0e",
|
||||||
|
"integrity": "sha512-sktFLjNF7oZa9haK+a71Ebc6csJ5uC/Huba879WiANydWRF7iIPdFyL6sfp8gOfoOj+I/jhg1MB+1z972hKdWw==",
|
||||||
|
"requires": {
|
||||||
|
"@speedy-tuner/types": "^0.2.1",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
|
"parsimmon": "^1.18.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"argparse": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
||||||
|
},
|
||||||
|
"js-yaml": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||||
|
"requires": {
|
||||||
|
"argparse": "^2.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@speedy-tuner/types": {
|
"@speedy-tuner/types": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://npm.pkg.github.com/download/@speedy-tuner/types/0.2.1/378faaf77fc78a8b33a9f09b9a742b272cc01a9349fe6fe61081e202d953210b",
|
"resolved": "https://npm.pkg.github.com/download/@speedy-tuner/types/0.2.1/378faaf77fc78a8b33a9f09b9a742b272cc01a9349fe6fe61081e202d953210b",
|
||||||
|
@ -36304,6 +36362,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
|
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
|
||||||
},
|
},
|
||||||
|
"parsimmon": {
|
||||||
|
"version": "1.18.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/parsimmon/-/parsimmon-1.18.1.tgz",
|
||||||
|
"integrity": "sha512-u7p959wLfGAhJpSDJVYXoyMCXWYwHia78HhRBWqk7AIbxdmlrfdp5wX0l3xv/iTSH5HvhN9K7o26hwwpgS5Nmw=="
|
||||||
|
},
|
||||||
"pascal-case": {
|
"pascal-case": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
"@reduxjs/toolkit": "^1.7.1",
|
"@reduxjs/toolkit": "^1.7.1",
|
||||||
"@sentry/react": "^6.16.1",
|
"@sentry/react": "^6.16.1",
|
||||||
"@sentry/tracing": "^6.16.1",
|
"@sentry/tracing": "^6.16.1",
|
||||||
|
"@speedy-tuner/ini": "^0.2.2",
|
||||||
"@speedy-tuner/types": "^0.2.1",
|
"@speedy-tuner/types": "^0.2.1",
|
||||||
"antd": "^4.18.2",
|
"antd": "^4.18.2",
|
||||||
"easymde": "^2.15.0",
|
"easymde": "^2.15.0",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import Storage from '../utils/storage';
|
import Storage from '../utils/Storage';
|
||||||
|
|
||||||
const useStorage = () => {
|
const useStorage = () => {
|
||||||
const storage = useMemo(() => new Storage(), []);
|
const storage = useMemo(() => new Storage(), []);
|
||||||
|
|
|
@ -108,9 +108,12 @@ const Diagnose = ({ ui, config, loadedLogs }: { ui: UIState, config: Config, loa
|
||||||
setFileSize(formatBytes(compositeRaw.byteLength));
|
setFileSize(formatBytes(compositeRaw.byteLength));
|
||||||
setStep(1);
|
setStep(1);
|
||||||
|
|
||||||
const parser = new TriggerLogsParser();
|
const resultComposite = (new TriggerLogsParser(pako.inflate(new Uint8Array(compositeRaw))))
|
||||||
const resultComposite = parser.parse(pako.inflate(new Uint8Array(compositeRaw))).getCompositeLogs();
|
.parse()
|
||||||
const resultTooth = parser.parse(pako.inflate(new Uint8Array(toothRaw))).getToothLogs();
|
.getCompositeLogs();
|
||||||
|
const resultTooth = (new TriggerLogsParser(pako.inflate(new Uint8Array(toothRaw))))
|
||||||
|
.parse()
|
||||||
|
.getToothLogs();
|
||||||
|
|
||||||
setLogs(resultComposite);
|
setLogs(resultComposite);
|
||||||
setToothLogs(resultTooth);
|
setToothLogs(resultTooth);
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
Divider,
|
Divider,
|
||||||
Input,
|
Input,
|
||||||
notification,
|
notification,
|
||||||
|
Row,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
Space,
|
Space,
|
||||||
Switch,
|
Switch,
|
||||||
|
@ -25,6 +26,7 @@ import {
|
||||||
ShareAltOutlined,
|
ShareAltOutlined,
|
||||||
FileTextOutlined,
|
FileTextOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
|
import { INI } from '@speedy-tuner/ini';
|
||||||
import { UploadRequestOption } from 'rc-upload/lib/interface';
|
import { UploadRequestOption } from 'rc-upload/lib/interface';
|
||||||
import { UploadFile } from 'antd/lib/upload/interface';
|
import { UploadFile } from 'antd/lib/upload/interface';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
@ -55,6 +57,7 @@ import TuneParser from '../utils/tune/TuneParser';
|
||||||
|
|
||||||
import 'easymde/dist/easymde.min.css';
|
import 'easymde/dist/easymde.min.css';
|
||||||
import TriggerLogsParser from '../utils/logs/TriggerLogsParser';
|
import TriggerLogsParser from '../utils/logs/TriggerLogsParser';
|
||||||
|
import LogParser from '../utils/logs/LogParser';
|
||||||
|
|
||||||
enum MaxFiles {
|
enum MaxFiles {
|
||||||
TUNE_FILES = 1,
|
TUNE_FILES = 1,
|
||||||
|
@ -122,7 +125,7 @@ const UploadPage = () => {
|
||||||
const [isPublic, setIsPublic] = useState(true);
|
const [isPublic, setIsPublic] = useState(true);
|
||||||
const [isListed, setIsListed] = useState(true);
|
const [isListed, setIsListed] = useState(true);
|
||||||
const [description, setDescription] = useState('# My Tune \ndescription');
|
const [description, setDescription] = useState('# My Tune \ndescription');
|
||||||
const [tuneFile, setTuneFile] = useState<UploadedFile | null>(null);
|
const [tuneFile, setTuneFile] = useState<UploadedFile | null | false>(null);
|
||||||
const [logFiles, setLogFiles] = useState<UploadedFile>({});
|
const [logFiles, setLogFiles] = useState<UploadedFile>({});
|
||||||
const [toothLogFiles, setToothLogFiles] = useState<UploadedFile>({});
|
const [toothLogFiles, setToothLogFiles] = useState<UploadedFile>({});
|
||||||
const [customIniFile, setCustomIniFile] = useState<UploadedFile | null>(null);
|
const [customIniFile, setCustomIniFile] = useState<UploadedFile | null>(null);
|
||||||
|
@ -166,12 +169,10 @@ const UploadPage = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeFile = (path: string) => {
|
const removeFile = async (path: string) => {
|
||||||
try {
|
try {
|
||||||
return deleteObject(storageRef(storage, path));
|
return await deleteObject(storageRef(storage, path));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
|
||||||
genericError(error as Error);
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -278,17 +279,24 @@ const UploadPage = () => {
|
||||||
const { path } = (options.data as unknown as UploadFileData);
|
const { path } = (options.data as unknown as UploadFileData);
|
||||||
const tune: UploadedFile = {};
|
const tune: UploadedFile = {};
|
||||||
tune[(options.file as UploadFile).uid] = path;
|
tune[(options.file as UploadFile).uid] = path;
|
||||||
setTuneFile(tune);
|
|
||||||
upload(path, options, () => {
|
upload(path, options, () => {
|
||||||
updateDbData(newTuneId!, { tuneFile: path });
|
updateDbData(newTuneId!, { tuneFile: path });
|
||||||
}, async (file) => {
|
}, async (file) => {
|
||||||
const { result, message } = await validateSize(file);
|
const { result, message } = await validateSize(file);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
setTuneFile(false);
|
||||||
return { result, message };
|
return { result, message };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const valid = (new TuneParser()).parse(await file.arrayBuffer()).isValid();
|
||||||
|
if (!valid) {
|
||||||
|
setTuneFile(false);
|
||||||
|
} else {
|
||||||
|
setTuneFile(tune);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
result: (new TuneParser()).parse(await file.arrayBuffer()).isValid(),
|
result: valid,
|
||||||
message: 'Tune file is not valid!',
|
message: 'Tune file is not valid!',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -297,14 +305,42 @@ const UploadPage = () => {
|
||||||
const uploadLogs = async (options: UploadRequestOption) => {
|
const uploadLogs = async (options: UploadRequestOption) => {
|
||||||
const { path } = (options.data as unknown as UploadFileData);
|
const { path } = (options.data as unknown as UploadFileData);
|
||||||
const tune: UploadedFile = {};
|
const tune: UploadedFile = {};
|
||||||
tune[(options.file as UploadFile).uid] = path;
|
const uuid = (options.file as UploadFile).uid;
|
||||||
|
tune[uuid] = path;
|
||||||
const newValues = { ...logFiles, ...tune };
|
const newValues = { ...logFiles, ...tune };
|
||||||
setLogFiles(newValues);
|
|
||||||
upload(path, options, () => {
|
upload(path, options, () => {
|
||||||
updateDbData(newTuneId!, { logFiles: Object.values(newValues) });
|
updateDbData(newTuneId!, { logFiles: Object.values(newValues) });
|
||||||
}, async (file) => {
|
}, async (file) => {
|
||||||
const { result, message } = await validateSize(file);
|
const { result, message } = await validateSize(file);
|
||||||
return { result, message };
|
if (!result) {
|
||||||
|
return { result, message };
|
||||||
|
}
|
||||||
|
|
||||||
|
let valid = true;
|
||||||
|
const extension = file.name.split('.').pop();
|
||||||
|
const parser = new LogParser(await file.arrayBuffer());
|
||||||
|
|
||||||
|
switch (extension) {
|
||||||
|
case 'mlg':
|
||||||
|
valid = parser.isMLG();
|
||||||
|
break;
|
||||||
|
case 'msl':
|
||||||
|
case 'csv':
|
||||||
|
valid = parser.isMSL();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
valid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valid) {
|
||||||
|
setLogFiles(newValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: valid,
|
||||||
|
message: 'Log file is empty or not valid!',
|
||||||
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -313,7 +349,6 @@ const UploadPage = () => {
|
||||||
const tune: UploadedFile = {};
|
const tune: UploadedFile = {};
|
||||||
tune[(options.file as UploadFile).uid] = path;
|
tune[(options.file as UploadFile).uid] = path;
|
||||||
const newValues = { ...toothLogFiles, ...tune };
|
const newValues = { ...toothLogFiles, ...tune };
|
||||||
setToothLogFiles(newValues);
|
|
||||||
upload(path, options, () => {
|
upload(path, options, () => {
|
||||||
updateDbData(newTuneId!, { toothLogFiles: Object.values(newValues) });
|
updateDbData(newTuneId!, { toothLogFiles: Object.values(newValues) });
|
||||||
}, async (file) => {
|
}, async (file) => {
|
||||||
|
@ -322,10 +357,15 @@ const UploadPage = () => {
|
||||||
return { result, message };
|
return { result, message };
|
||||||
}
|
}
|
||||||
|
|
||||||
const parser = (new TriggerLogsParser()).parse(await file.arrayBuffer());
|
const parser = new TriggerLogsParser(await file.arrayBuffer());
|
||||||
|
const valid = parser.isComposite() || parser.isTooth();
|
||||||
|
|
||||||
|
if (valid) {
|
||||||
|
setToothLogFiles(newValues);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
result: parser.isComposite() || parser.isTooth(),
|
result: valid,
|
||||||
message: 'Tooth logs file is empty or not valid!',
|
message: 'Tooth logs file is empty or not valid!',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -335,21 +375,42 @@ const UploadPage = () => {
|
||||||
const { path } = (options.data as unknown as UploadFileData);
|
const { path } = (options.data as unknown as UploadFileData);
|
||||||
const tune: UploadedFile = {};
|
const tune: UploadedFile = {};
|
||||||
tune[(options.file as UploadFile).uid] = path;
|
tune[(options.file as UploadFile).uid] = path;
|
||||||
setCustomIniFile(tune);
|
|
||||||
upload(path, options, () => {
|
upload(path, options, () => {
|
||||||
updateDbData(newTuneId!, { customIniFile: path });
|
updateDbData(newTuneId!, { customIniFile: path });
|
||||||
}, () => Promise.resolve({ result: true, message: '' }));
|
}, async (file) => {
|
||||||
|
const { result, message } = await validateSize(file);
|
||||||
|
if (!result) {
|
||||||
|
return { result, message };
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: change to common interface, add some validation method
|
||||||
|
const parser = new INI((new TextDecoder()).decode(await file.arrayBuffer()));
|
||||||
|
const valid = parser.parse().megaTune.signature.length > 0;
|
||||||
|
|
||||||
|
if (valid) {
|
||||||
|
setCustomIniFile(tune);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: valid,
|
||||||
|
message: 'INI file is empty or not valid!',
|
||||||
|
};
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeTuneFile = async (file: UploadFile) => {
|
const removeTuneFile = async (file: UploadFile) => {
|
||||||
|
if (tuneFile) {
|
||||||
|
removeFile(tuneFile[file.uid]);
|
||||||
|
}
|
||||||
setTuneFile(null);
|
setTuneFile(null);
|
||||||
removeFile(tuneFile![file.uid]);
|
|
||||||
updateDbData(newTuneId!, { tuneFile: null });
|
updateDbData(newTuneId!, { tuneFile: null });
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeLogFile = async (file: UploadFile) => {
|
const removeLogFile = async (file: UploadFile) => {
|
||||||
const { uid } = file;
|
const { uid } = file;
|
||||||
removeFile(logFiles[file.uid]);
|
if (logFiles[file.uid]) {
|
||||||
|
removeFile(logFiles[file.uid]);
|
||||||
|
}
|
||||||
const newValues = { ...logFiles };
|
const newValues = { ...logFiles };
|
||||||
delete newValues[uid];
|
delete newValues[uid];
|
||||||
setLogFiles(newValues);
|
setLogFiles(newValues);
|
||||||
|
@ -358,7 +419,9 @@ const UploadPage = () => {
|
||||||
|
|
||||||
const removeToothLogFile = async (file: UploadFile) => {
|
const removeToothLogFile = async (file: UploadFile) => {
|
||||||
const { uid } = file;
|
const { uid } = file;
|
||||||
removeFile(toothLogFiles[file.uid]);
|
if (toothLogFiles[file.uid]) {
|
||||||
|
removeFile(toothLogFiles[file.uid]);
|
||||||
|
}
|
||||||
const newValues = { ...toothLogFiles };
|
const newValues = { ...toothLogFiles };
|
||||||
delete newValues[uid];
|
delete newValues[uid];
|
||||||
setToothLogFiles(newValues);
|
setToothLogFiles(newValues);
|
||||||
|
@ -366,7 +429,9 @@ const UploadPage = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeCustomIniFile = async (file: UploadFile) => {
|
const removeCustomIniFile = async (file: UploadFile) => {
|
||||||
removeFile(customIniFile![file.uid]);
|
if (customIniFile) {
|
||||||
|
removeFile(customIniFile![file.uid]);
|
||||||
|
}
|
||||||
setCustomIniFile(null);
|
setCustomIniFile(null);
|
||||||
updateDbData(newTuneId!, { customIniFile: null });
|
updateDbData(newTuneId!, { customIniFile: null });
|
||||||
};
|
};
|
||||||
|
@ -415,29 +480,33 @@ const UploadPage = () => {
|
||||||
const shareSection = (
|
const shareSection = (
|
||||||
<>
|
<>
|
||||||
<Divider>Publish & Share</Divider>
|
<Divider>Publish & Share</Divider>
|
||||||
<Input
|
<Row>
|
||||||
style={{ width: `calc(100% - ${hasNavigatorShare ? 160 : 128}px)` }}
|
<Input
|
||||||
value={shareUrl!}
|
style={{ width: `calc(100% - ${hasNavigatorShare ? 65 : 35}px)` }}
|
||||||
/>
|
value={shareUrl!}
|
||||||
<Tooltip title={copied ? 'Copied!' : 'Copy URL'}>
|
/>
|
||||||
<Button icon={<CopyOutlined />} onClick={copyToClipboard} />
|
<Tooltip title={copied ? 'Copied!' : 'Copy URL'}>
|
||||||
</Tooltip>
|
<Button icon={<CopyOutlined />} onClick={copyToClipboard} />
|
||||||
{hasNavigatorShare && (
|
|
||||||
<Tooltip title="Share">
|
|
||||||
<Button
|
|
||||||
icon={<ShareAltOutlined />}
|
|
||||||
onClick={() => navigator.share({ url: shareUrl! })}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
{hasNavigatorShare && (
|
||||||
<Button
|
<Tooltip title="Share">
|
||||||
type="primary"
|
<Button
|
||||||
style={{ float: 'right' }}
|
icon={<ShareAltOutlined />}
|
||||||
disabled={isPublished || isLoading}
|
onClick={() => navigator.share({ url: shareUrl! })}
|
||||||
onClick={publish}
|
/>
|
||||||
>
|
</Tooltip>
|
||||||
{isPublished && !isLoading ? 'Published' : 'Publish'}
|
)}
|
||||||
</Button>
|
</Row>
|
||||||
|
<Row style={{ marginTop: 10 }}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
block
|
||||||
|
disabled={isPublished || isLoading}
|
||||||
|
onClick={publish}
|
||||||
|
>
|
||||||
|
{isPublished && !isLoading ? 'Published' : 'Publish'}
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -562,7 +631,7 @@ const UploadPage = () => {
|
||||||
disabled={isPublished}
|
disabled={isPublished}
|
||||||
accept=".msq"
|
accept=".msq"
|
||||||
>
|
>
|
||||||
{!tuneFile && uploadButton}
|
{tuneFile === null && uploadButton}
|
||||||
</Upload>
|
</Upload>
|
||||||
{tuneFile && optionalSection}
|
{tuneFile && optionalSection}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
export interface ParserInterface {
|
||||||
|
parse(): this;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ParserConstructor {
|
||||||
|
new(buffer: ArrayBuffer): ParserInterface;
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import BrowserStorage from './storage/browserStorage';
|
import BrowserStorage from './storage/BrowserStorage';
|
||||||
import { StorageInterface } from './storageInterface';
|
import { StorageInterface } from './StorageInterface';
|
||||||
|
|
||||||
class Storage {
|
class Storage {
|
||||||
private storage: StorageInterface;
|
private storage: StorageInterface;
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { ParserInterface } from '../ParserInterface';
|
||||||
|
|
||||||
|
class LogParser implements ParserInterface {
|
||||||
|
private MLG_FORMAT_LENGTH = 6;
|
||||||
|
|
||||||
|
private isMLGLogs: boolean = false;
|
||||||
|
|
||||||
|
private isMSLLogs: boolean = false;
|
||||||
|
|
||||||
|
private buffer: ArrayBuffer = new ArrayBuffer(0);
|
||||||
|
|
||||||
|
private raw: string = '';
|
||||||
|
|
||||||
|
constructor(buffer: ArrayBuffer) {
|
||||||
|
this.buffer = buffer;
|
||||||
|
this.raw = (new TextDecoder()).decode(buffer);
|
||||||
|
|
||||||
|
this.checkMLG();
|
||||||
|
this.checkMSL();
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(): this {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
isMLG(): boolean {
|
||||||
|
return this.isMLGLogs;
|
||||||
|
}
|
||||||
|
|
||||||
|
isMSL(): boolean {
|
||||||
|
return this.isMSLLogs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkMLG() {
|
||||||
|
const fileFormat = new TextDecoder('utf8')
|
||||||
|
.decode(this.buffer.slice(0, this.MLG_FORMAT_LENGTH))
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
|
.replace(/\x00/gu, '');
|
||||||
|
|
||||||
|
if (fileFormat === 'MLVLG') {
|
||||||
|
this.isMLGLogs = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkMSL() {
|
||||||
|
const lines = this.raw.split('\n');
|
||||||
|
for (let index = 0; index < lines.length; index++) {
|
||||||
|
if (lines[index].startsWith('Time')) {
|
||||||
|
this.isMSLLogs = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LogParser;
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { ParserInterface } from '../ParserInterface';
|
||||||
import { isNumber } from '../tune/expression';
|
import { isNumber } from '../tune/expression';
|
||||||
|
|
||||||
export enum EntryType {
|
export enum EntryType {
|
||||||
|
@ -23,7 +24,7 @@ export interface ToothLogEntry {
|
||||||
time: number;
|
time: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
class TriggerLogsParser {
|
class TriggerLogsParser implements ParserInterface {
|
||||||
private COMMENT_PREFIX = '#';
|
private COMMENT_PREFIX = '#';
|
||||||
|
|
||||||
private MARKER_PREFIX = 'MARK';
|
private MARKER_PREFIX = 'MARK';
|
||||||
|
@ -36,10 +37,17 @@ class TriggerLogsParser {
|
||||||
|
|
||||||
private resultTooth: ToothLogEntry[] = [];
|
private resultTooth: ToothLogEntry[] = [];
|
||||||
|
|
||||||
parse(buffer: ArrayBuffer): TriggerLogsParser {
|
private alreadyParsed: boolean = false;
|
||||||
const raw = (new TextDecoder()).decode(buffer);
|
|
||||||
this.parseCompositeLogs(raw);
|
private raw: string = '';
|
||||||
this.parseToothLogs(raw);
|
|
||||||
|
constructor(buffer: ArrayBuffer) {
|
||||||
|
this.raw = (new TextDecoder()).decode(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(): this {
|
||||||
|
this.parseCompositeLogs(this.raw);
|
||||||
|
this.parseToothLogs(this.raw);
|
||||||
|
|
||||||
if (this.resultComposite.length > 0) {
|
if (this.resultComposite.length > 0) {
|
||||||
this.isCompositeLogs = true;
|
this.isCompositeLogs = true;
|
||||||
|
@ -49,6 +57,8 @@ class TriggerLogsParser {
|
||||||
this.isToothLogs = true;
|
this.isToothLogs = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.alreadyParsed = true;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,10 +71,18 @@ class TriggerLogsParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
isTooth(): boolean {
|
isTooth(): boolean {
|
||||||
|
if (!this.alreadyParsed) {
|
||||||
|
this.parse();
|
||||||
|
}
|
||||||
|
|
||||||
return this.isToothLogs;
|
return this.isToothLogs;
|
||||||
}
|
}
|
||||||
|
|
||||||
isComposite(): boolean {
|
isComposite(): boolean {
|
||||||
|
if (!this.alreadyParsed) {
|
||||||
|
this.parse();
|
||||||
|
}
|
||||||
|
|
||||||
return this.isCompositeLogs;
|
return this.isCompositeLogs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { StorageInterface } from '../storageInterface';
|
import { StorageInterface } from '../StorageInterface';
|
||||||
|
|
||||||
class BrowserStorage implements StorageInterface {
|
class BrowserStorage implements StorageInterface {
|
||||||
private storage: Storage;
|
private storage: Storage;
|
Loading…
Reference in New Issue