Refactor upload details, Update dependencies (#376)
This commit is contained in:
parent
0229830b1b
commit
83963dd8c7
|
@ -30,4 +30,4 @@ There are many ways in which you can participate in the project and every bit of
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
Please see [Developer guide](https://github.com/speedy-tuner/speedy-tuner-cloud/blob/master/DEVELOPMENT.md)
|
Before you begin please see [Development guide](https://github.com/speedy-tuner/speedy-tuner-cloud/blob/master/DEVELOPMENT.md).
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
|
@ -35,18 +35,18 @@
|
||||||
"@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/ini": "^0.2.2",
|
||||||
"@speedy-tuner/types": "^0.2.1",
|
"@speedy-tuner/types": "^0.3.0",
|
||||||
"antd": "^4.18.2",
|
"antd": "^4.18.3",
|
||||||
"firebase": "^9.6.1",
|
"firebase": "^9.6.3",
|
||||||
"mlg-converter": "^0.5.1",
|
"mlg-converter": "^0.5.1",
|
||||||
"nanoid": "^3.1.30",
|
"nanoid": "^3.1.32",
|
||||||
"pako": "^2.0.4",
|
"pako": "^2.0.4",
|
||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-markdown": "^7.1.2",
|
"react-markdown": "^7.1.2",
|
||||||
"react-perfect-scrollbar": "^1.5.8",
|
"react-perfect-scrollbar": "^1.5.8",
|
||||||
"react-redux": "^7.2.6",
|
"react-redux": "^7.2.6",
|
||||||
"react-router-dom": "^5.2.1",
|
"react-router-dom": "^5.3.0",
|
||||||
"react-scripts": "^4.0.3",
|
"react-scripts": "^4.0.3",
|
||||||
"uplot": "^1.6.18",
|
"uplot": "^1.6.18",
|
||||||
"uplot-react": "^1.1.1"
|
"uplot-react": "^1.1.1"
|
||||||
|
@ -59,7 +59,7 @@
|
||||||
"@types/pako": "^1.0.3",
|
"@types/pako": "^1.0.3",
|
||||||
"@types/react": "^17.0.38",
|
"@types/react": "^17.0.38",
|
||||||
"@types/react-dom": "^17.0.11",
|
"@types/react-dom": "^17.0.11",
|
||||||
"@types/react-redux": "^7.1.21",
|
"@types/react-redux": "^7.1.22",
|
||||||
"@types/react-router-dom": "^5.3.2",
|
"@types/react-router-dom": "^5.3.2",
|
||||||
"eslint-plugin-modules-newline": "^0.0.6",
|
"eslint-plugin-modules-newline": "^0.0.6",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/icons/icon.ico" />
|
<link rel="icon" href="%PUBLIC_URL%/icons/icon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#222629" />
|
<meta name="theme-color" content="#222629" />
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/icons/icon.png" />
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/icons/icon.png" />
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {
|
||||||
AppState,
|
AppState,
|
||||||
ConfigState,
|
ConfigState,
|
||||||
StatusState,
|
StatusState,
|
||||||
} from '@speedy-tuner/types';
|
} from '../types/state';
|
||||||
|
|
||||||
const { Footer } = Layout;
|
const { Footer } = Layout;
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@ import {
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||||
import {
|
import {
|
||||||
AppState,
|
|
||||||
Dialogs as DialogsType,
|
Dialogs as DialogsType,
|
||||||
Dialog as DialogType,
|
Dialog as DialogType,
|
||||||
Config as ConfigType,
|
Config as ConfigType,
|
||||||
|
@ -27,8 +26,11 @@ import {
|
||||||
ScalarConstant as ScalarConstantType,
|
ScalarConstant as ScalarConstantType,
|
||||||
ConstantTypes,
|
ConstantTypes,
|
||||||
Tune as TuneType,
|
Tune as TuneType,
|
||||||
UIState,
|
|
||||||
} from '@speedy-tuner/types';
|
} from '@speedy-tuner/types';
|
||||||
|
import {
|
||||||
|
AppState,
|
||||||
|
UIState,
|
||||||
|
} from '../../types/state';
|
||||||
import SmartSelect from './Dialog/SmartSelect';
|
import SmartSelect from './Dialog/SmartSelect';
|
||||||
import SmartNumber from './Dialog/SmartNumber';
|
import SmartNumber from './Dialog/SmartNumber';
|
||||||
import TextField from './Dialog/TextField';
|
import TextField from './Dialog/TextField';
|
||||||
|
|
|
@ -8,12 +8,14 @@ import {
|
||||||
} from '../firebase';
|
} from '../firebase';
|
||||||
import { TuneDbData } from '../types/dbData';
|
import { TuneDbData } from '../types/dbData';
|
||||||
|
|
||||||
|
const TUNES_PATH = 'publicTunes';
|
||||||
|
|
||||||
const genericError = (error: Error) => notification.error({ message: 'Database Error', description: error.message });
|
const genericError = (error: Error) => notification.error({ message: 'Database Error', description: error.message });
|
||||||
|
|
||||||
const useDb = () => {
|
const useDb = () => {
|
||||||
const getData = async (tuneId: string, collection: string) => {
|
const getData = async (tuneId: string) => {
|
||||||
try {
|
try {
|
||||||
const tune = (await getDoc(fireStoreDoc(db, collection, tuneId))).data() as TuneDbData;
|
const tune = (await getDoc(fireStoreDoc(db, TUNES_PATH, tuneId))).data() as TuneDbData;
|
||||||
|
|
||||||
return Promise.resolve(tune);
|
return Promise.resolve(tune);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -25,9 +27,9 @@ const useDb = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateData = async (tuneId: string, collection: string, data: TuneDbData) => {
|
const updateData = async (tuneId: string, data: TuneDbData) => {
|
||||||
try {
|
try {
|
||||||
await setDoc(fireStoreDoc(db, collection, tuneId), data, { merge: true });
|
await setDoc(fireStoreDoc(db, TUNES_PATH, tuneId), data, { merge: true });
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -40,8 +42,8 @@ const useDb = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getTune: (tuneId: string): Promise<TuneDbData> => getData(tuneId, 'tunes'),
|
getTune: (tuneId: string): Promise<TuneDbData> => getData(tuneId),
|
||||||
updateData: (tuneId: string, data: TuneDbData): Promise<void> => updateData(tuneId, 'tunes', data),
|
updateData: (tuneId: string, data: TuneDbData): Promise<void> => updateData(tuneId, data),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
import { notification } from 'antd';
|
import { notification } from 'antd';
|
||||||
import * as Sentry from '@sentry/browser';
|
import * as Sentry from '@sentry/browser';
|
||||||
|
import { UploadTask } from 'firebase/storage';
|
||||||
import {
|
import {
|
||||||
storage,
|
storage,
|
||||||
storageRef,
|
storageRef,
|
||||||
getBytes,
|
getBytes,
|
||||||
|
deleteObject,
|
||||||
|
uploadBytesResumable,
|
||||||
} from '../firebase';
|
} from '../firebase';
|
||||||
|
|
||||||
|
const TUNES_PATH = 'public/users';
|
||||||
|
|
||||||
const genericError = (error: Error) => notification.error({ message: 'Database Error', description: error.message });
|
const genericError = (error: Error) => notification.error({ message: 'Database Error', description: error.message });
|
||||||
|
|
||||||
const useServerStorage = () => {
|
const useServerStorage = () => {
|
||||||
|
@ -23,8 +28,32 @@ const useServerStorage = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const removeFile = async (path: string) => {
|
||||||
|
try {
|
||||||
|
await deleteObject(storageRef(storage, `${TUNES_PATH}/${path}`));
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
} catch (error) {
|
||||||
|
Sentry.captureException(error);
|
||||||
|
console.error(error);
|
||||||
|
genericError(error as Error);
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const uploadFile = (path: string, file: File, data: Uint8Array) =>
|
||||||
|
uploadBytesResumable(storageRef(storage, `${TUNES_PATH}/${path}`), data, {
|
||||||
|
customMetadata: {
|
||||||
|
name: file.name,
|
||||||
|
size: `${file.size}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getFile: (path: string): Promise<ArrayBuffer> => getFile(path),
|
getFile: (path: string): Promise<ArrayBuffer> => getFile(path),
|
||||||
|
removeFile: (path: string): Promise<void> => removeFile(path),
|
||||||
|
uploadFile: (path: string, file: File, data: Uint8Array): UploadTask => uploadFile(path, file, data),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -25,11 +25,13 @@ 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 {
|
import {
|
||||||
AppState,
|
|
||||||
UIState,
|
|
||||||
Config,
|
Config,
|
||||||
Logs,
|
Logs,
|
||||||
} from '@speedy-tuner/types';
|
} from '@speedy-tuner/types';
|
||||||
|
import {
|
||||||
|
AppState,
|
||||||
|
UIState,
|
||||||
|
} from '../types/state';
|
||||||
import {
|
import {
|
||||||
loadCompositeLogs,
|
loadCompositeLogs,
|
||||||
loadToothLogs,
|
loadToothLogs,
|
||||||
|
|
|
@ -29,8 +29,6 @@ import PerfectScrollbar from 'react-perfect-scrollbar';
|
||||||
// eslint-disable-next-line import/no-unresolved
|
// eslint-disable-next-line import/no-unresolved
|
||||||
import MlgParserWorker from 'worker-loader!../workers/mlgParser.worker';
|
import MlgParserWorker from 'worker-loader!../workers/mlgParser.worker';
|
||||||
import {
|
import {
|
||||||
AppState,
|
|
||||||
UIState,
|
|
||||||
Config,
|
Config,
|
||||||
OutputChannel,
|
OutputChannel,
|
||||||
Logs,
|
Logs,
|
||||||
|
@ -49,6 +47,10 @@ import {
|
||||||
isExpression,
|
isExpression,
|
||||||
stripExpression,
|
stripExpression,
|
||||||
} from '../utils/tune/expression';
|
} from '../utils/tune/expression';
|
||||||
|
import {
|
||||||
|
AppState,
|
||||||
|
UIState,
|
||||||
|
} from '../types/state';
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
const { TabPane } = Tabs;
|
||||||
const { Content } = Layout;
|
const { Content } = Layout;
|
||||||
|
|
|
@ -19,6 +19,7 @@ import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Typography,
|
Typography,
|
||||||
Upload,
|
Upload,
|
||||||
|
Form,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import {
|
import {
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
|
@ -46,18 +47,15 @@ import {
|
||||||
} from './auth/notifications';
|
} from './auth/notifications';
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
import { useAuth } from '../contexts/AuthContext';
|
||||||
import { Routes } from '../routes';
|
import { Routes } from '../routes';
|
||||||
import {
|
|
||||||
storage,
|
|
||||||
storageRef,
|
|
||||||
uploadBytesResumable,
|
|
||||||
deleteObject,
|
|
||||||
} from '../firebase';
|
|
||||||
import useBrowserStorage from '../hooks/useBrowserStorage';
|
import useBrowserStorage from '../hooks/useBrowserStorage';
|
||||||
import TuneParser from '../utils/tune/TuneParser';
|
import TuneParser from '../utils/tune/TuneParser';
|
||||||
import TriggerLogsParser from '../utils/logs/TriggerLogsParser';
|
import TriggerLogsParser from '../utils/logs/TriggerLogsParser';
|
||||||
import LogParser from '../utils/logs/LogParser';
|
import LogParser from '../utils/logs/LogParser';
|
||||||
import { TuneDbData } from '../types/dbData';
|
import { TuneDbData } from '../types/dbData';
|
||||||
import useDb from '../hooks/useDb';
|
import useDb from '../hooks/useDb';
|
||||||
|
import useServerStorage from '../hooks/useServerStorage';
|
||||||
|
|
||||||
|
const { Item } = Form;
|
||||||
|
|
||||||
enum MaxFiles {
|
enum MaxFiles {
|
||||||
TUNE_FILES = 1,
|
TUNE_FILES = 1,
|
||||||
|
@ -92,7 +90,7 @@ const containerStyle = {
|
||||||
const newTuneIdKey = 'newTuneId';
|
const newTuneIdKey = 'newTuneId';
|
||||||
const maxFileSizeMB = 10;
|
const maxFileSizeMB = 10;
|
||||||
const descriptionEditorHeight = 260;
|
const descriptionEditorHeight = 260;
|
||||||
const rowProps = { gutter: 10, style: { marginBottom: 10 } };
|
const rowProps = { gutter: 10 };
|
||||||
|
|
||||||
const tuneIcon = () => <ToolOutlined />;
|
const tuneIcon = () => <ToolOutlined />;
|
||||||
const logIcon = () => <FundOutlined />;
|
const logIcon = () => <FundOutlined />;
|
||||||
|
@ -100,18 +98,14 @@ const toothLogIcon = () => <SettingOutlined />;
|
||||||
const iniIcon = () => <FileTextOutlined />;
|
const iniIcon = () => <FileTextOutlined />;
|
||||||
|
|
||||||
const nanoidCustom = customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 10);
|
const nanoidCustom = customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 10);
|
||||||
const baseUploadPath = 'public/users';
|
|
||||||
|
|
||||||
const UploadPage = () => {
|
const UploadPage = () => {
|
||||||
const [newTuneId, setNewTuneId] = useState<string>();
|
const [newTuneId, setNewTuneId] = useState<string>();
|
||||||
const [isUserAuthorized, setIsUserAuthorized] = useState(false);
|
const [isUserAuthorized, setIsUserAuthorized] = useState(false);
|
||||||
const [shareUrl, setShareUrl] = useState<string>();
|
const [shareUrl, setShareUrl] = useState<string>();
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
const [showDetails, setShowDetails] = useState(false);
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isPublished, setIsPublished] = useState(false);
|
const [isPublished, setIsPublished] = useState(false);
|
||||||
const [isPublic, setIsPublic] = useState(true);
|
|
||||||
const [isListed, setIsListed] = useState(true);
|
|
||||||
const [tuneFile, setTuneFile] = useState<UploadedFile | null | false>(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>({});
|
||||||
|
@ -121,21 +115,9 @@ const UploadPage = () => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { storageSet, storageGet, storageDelete } = useBrowserStorage();
|
const { storageSet, storageGet, storageDelete } = useBrowserStorage();
|
||||||
const { updateData, getTune } = useDb();
|
const { updateData, getTune } = useDb();
|
||||||
|
const { removeFile, uploadFile } = useServerStorage();
|
||||||
// details
|
const requiredRules = [{ required: true, message: 'This field is required!' }];
|
||||||
const [readme, setReadme] = useState('# My Tune\n\ndescription');
|
const [readme, setReadme] = useState('# My Tune\n\ndescription');
|
||||||
const [make, setMake] = useState<string>();
|
|
||||||
const [model, setModel] = useState<string>();
|
|
||||||
const [displacement, setDisplacement] = useState<string>();
|
|
||||||
const [year, setYear] = useState<number>();
|
|
||||||
const [hp, setHp] = useState<number>();
|
|
||||||
const [stockHp, setStockHp] = useState<number>();
|
|
||||||
const [engineCode, setEngineCode] = useState<string>();
|
|
||||||
const [cylinders, setCylinders] = useState<number>();
|
|
||||||
const [aspiration, setAspiration] = useState<string>();
|
|
||||||
const [fuel, setFuel] = useState<string>();
|
|
||||||
const [injectors, setInjectors] = useState<string>();
|
|
||||||
const [coils, setCoils] = useState<string>();
|
|
||||||
|
|
||||||
const noop = () => { };
|
const noop = () => { };
|
||||||
|
|
||||||
|
@ -149,39 +131,31 @@ const UploadPage = () => {
|
||||||
|
|
||||||
const genericError = (error: Error) => notification.error({ message: 'Error', description: error.message });
|
const genericError = (error: Error) => notification.error({ message: 'Error', description: error.message });
|
||||||
|
|
||||||
const removeFile = async (path: string) => {
|
const publish = async (values: any) => {
|
||||||
try {
|
|
||||||
return await deleteObject(storageRef(storage, path));
|
|
||||||
} catch (error) {
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const publish = async () => {
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
await updateData(newTuneId!, {
|
await updateData(newTuneId!, {
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
isPublished: true,
|
isPublished: true,
|
||||||
isPublic,
|
isPublic: values.isPublic,
|
||||||
isListed,
|
isListed: values.isListed,
|
||||||
details: {
|
details: {
|
||||||
readme: readme || null,
|
readme: readme || null,
|
||||||
make: make || null,
|
make: values.make || null,
|
||||||
model: model || null,
|
model: values.model || null,
|
||||||
displacement: displacement || null,
|
displacement: values.displacement || null,
|
||||||
year: year || null,
|
year: values.year || null,
|
||||||
hp: hp || null,
|
hp: values.hp || null,
|
||||||
stockHp: stockHp || null,
|
stockHp: values.stockHp || null,
|
||||||
engineCode: engineCode || null,
|
engineCode: values.engineCode || null,
|
||||||
cylinders: cylinders || null,
|
cylindersCount: values.cylindersCount || null,
|
||||||
aspiration: aspiration || null,
|
aspiration: values.aspiration || null,
|
||||||
fuel: fuel || null,
|
fuel: values.fuel || null,
|
||||||
injectors: injectors || null,
|
injectorsSize: values.injectorsSize || null,
|
||||||
coils: coils || null,
|
coils: values.coils || null,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
setIsPublished(true);
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
setIsPublished(true);
|
||||||
storageDelete(newTuneIdKey);
|
storageDelete(newTuneIdKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -205,12 +179,7 @@ const UploadPage = () => {
|
||||||
try {
|
try {
|
||||||
const buffer = await (file as File).arrayBuffer();
|
const buffer = await (file as File).arrayBuffer();
|
||||||
const compressed = pako.deflate(new Uint8Array(buffer));
|
const compressed = pako.deflate(new Uint8Array(buffer));
|
||||||
const uploadTask = uploadBytesResumable(storageRef(storage, path), compressed, {
|
const uploadTask = uploadFile(path, file as File, compressed);
|
||||||
customMetadata: {
|
|
||||||
name: (file as File).name,
|
|
||||||
size: `${(file as File).size}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
uploadTask.on(
|
uploadTask.on(
|
||||||
'state_changed',
|
'state_changed',
|
||||||
|
@ -232,35 +201,35 @@ const UploadPage = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const tuneFileData = () => ({
|
const tuneFileData = () => ({
|
||||||
path: `${baseUploadPath}/${currentUser!.uid}/tunes/${newTuneId}/${nanoid()}.msq.gz`,
|
path: `${currentUser!.uid}/tunes/${newTuneId}/${nanoid()}.msq.gz`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const logFileData = (file: UploadFile) => {
|
const logFileData = (file: UploadFile) => {
|
||||||
const { name } = file;
|
const { name } = file;
|
||||||
const extension = name.split('.').pop();
|
const extension = name.split('.').pop();
|
||||||
return {
|
return {
|
||||||
path: `${baseUploadPath}/${currentUser!.uid}/tunes/${newTuneId}/logs/${nanoid()}.${extension}.gz`,
|
path: `${currentUser!.uid}/tunes/${newTuneId}/logs/${nanoid()}.${extension}.gz`,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const toothLogFilesData = () => ({
|
const toothLogFilesData = () => ({
|
||||||
path: `${baseUploadPath}/${currentUser!.uid}/tunes/${newTuneId}/tooth-logs/${nanoid()}.csv.gz`,
|
path: `${currentUser!.uid}/tunes/${newTuneId}/tooth-logs/${nanoid()}.csv.gz`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const customIniFileData = () => ({
|
const customIniFileData = () => ({
|
||||||
path: `${baseUploadPath}/${currentUser!.uid}/tunes/${newTuneId}/${nanoid()}.ini.gz`,
|
path: `${currentUser!.uid}/tunes/${newTuneId}/${nanoid()}.ini.gz`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const uploadTune = async (options: UploadRequestOption) => {
|
const uploadTune = async (options: UploadRequestOption) => {
|
||||||
const found = await getTune(newTuneId!);
|
const found = await getTune(newTuneId!);
|
||||||
if (found) {
|
if (!found) {
|
||||||
const tuneData: TuneDbData = {
|
const tuneData: TuneDbData = {
|
||||||
userUid: currentUser!.uid,
|
userUid: currentUser!.uid,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
isPublished: false,
|
isPublished: false,
|
||||||
isPublic,
|
isPublic: true,
|
||||||
isListed,
|
isListed: true,
|
||||||
tuneFile: null,
|
tuneFile: null,
|
||||||
logFiles: [],
|
logFiles: [],
|
||||||
toothLogFiles: [],
|
toothLogFiles: [],
|
||||||
|
@ -495,22 +464,24 @@ const UploadPage = () => {
|
||||||
)}
|
)}
|
||||||
</Row>}
|
</Row>}
|
||||||
<Row style={{ marginTop: 10 }}>
|
<Row style={{ marginTop: 10 }}>
|
||||||
{!isPublished ? <Button
|
<Item style={{ width: '100%' }}>
|
||||||
type="primary"
|
{!isPublished ? <Button
|
||||||
block
|
type="primary"
|
||||||
disabled={isLoading}
|
block
|
||||||
onClick={publish}
|
loading={isLoading}
|
||||||
>
|
htmlType="submit"
|
||||||
Publish
|
>
|
||||||
</Button> : <Button
|
Publish
|
||||||
type="primary"
|
</Button> : <Button
|
||||||
block
|
type="primary"
|
||||||
onClick={() => {
|
block
|
||||||
window.location.href = shareUrl as string;
|
onClick={() => {
|
||||||
}}
|
window.location.href = shareUrl as string;
|
||||||
>
|
}}
|
||||||
Open
|
>
|
||||||
</Button>}
|
Open
|
||||||
|
</Button>}
|
||||||
|
</Item>
|
||||||
</Row>
|
</Row>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -518,24 +489,85 @@ const UploadPage = () => {
|
||||||
const detailsSection = (
|
const detailsSection = (
|
||||||
<>
|
<>
|
||||||
<Divider>
|
<Divider>
|
||||||
<Space>
|
<Space>Details</Space>
|
||||||
Upload Custom INI
|
|
||||||
<Typography.Text type="secondary">(.ini)</Typography.Text>
|
|
||||||
</Space>
|
|
||||||
</Divider>
|
</Divider>
|
||||||
<Upload
|
<Row {...rowProps}>
|
||||||
listType="picture-card"
|
<Col span={12}>
|
||||||
customRequest={uploadCustomIni}
|
<Item name="make" rules={requiredRules}>
|
||||||
data={customIniFileData}
|
<Input addonBefore="Make"/>
|
||||||
onRemove={removeCustomIniFile}
|
</Item>
|
||||||
iconRender={iniIcon}
|
</Col>
|
||||||
disabled={isPublished}
|
<Col span={12}>
|
||||||
onPreview={noop}
|
<Item name="model" rules={requiredRules}>
|
||||||
accept=".ini"
|
<Input addonBefore="Model"/>
|
||||||
>
|
</Item>
|
||||||
{!customIniFile && uploadButton}
|
</Col>
|
||||||
</Upload>
|
</Row>
|
||||||
<Divider>
|
<Row {...rowProps}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Item name="year" rules={requiredRules}>
|
||||||
|
<InputNumber addonBefore="Year" style={{ width: '100%' }} min={1886} max={2222} />
|
||||||
|
</Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Item name="displacement" rules={requiredRules}>
|
||||||
|
<InputNumber addonBefore="Displacement" addonAfter="l" min={0} max={100} />
|
||||||
|
</Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row {...rowProps}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Item name="hp">
|
||||||
|
<InputNumber addonBefore="HP" style={{ width: '100%' }} min={0} />
|
||||||
|
</Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Item name="stockHp">
|
||||||
|
<InputNumber addonBefore="Stock HP" style={{ width: '100%' }} min={0} />
|
||||||
|
</Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row {...rowProps}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Item name="engineCode">
|
||||||
|
<Input addonBefore="Engine code"/>
|
||||||
|
</Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Item name="cylindersCount">
|
||||||
|
<InputNumber addonBefore="No of cylinders" style={{ width: '100%' }} min={0} />
|
||||||
|
</Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row {...rowProps}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Item name="aspiration">
|
||||||
|
<Select placeholder="Aspiration" style={{ width: '100%' }}>
|
||||||
|
<Select.Option value="na">Naturally aspirated</Select.Option>
|
||||||
|
<Select.Option value="turbocharger">Turbocharged</Select.Option>
|
||||||
|
<Select.Option value="supercharger">Supercharged</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Item name="fuel">
|
||||||
|
<Input addonBefore="Fuel" />
|
||||||
|
</Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row {...rowProps}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Item name="injectorsSize">
|
||||||
|
<InputNumber addonBefore="Injectors size" addonAfter="cc" min={0} />
|
||||||
|
</Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Item name="coils">
|
||||||
|
<Input addonBefore="Coils" />
|
||||||
|
</Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Divider style={{ marginTop: 40 }}>
|
||||||
<Space>
|
<Space>
|
||||||
README
|
README
|
||||||
<Typography.Text type="secondary">(markdown)</Typography.Text>
|
<Typography.Text type="secondary">(markdown)</Typography.Text>
|
||||||
|
@ -543,13 +575,15 @@ const UploadPage = () => {
|
||||||
</Divider>
|
</Divider>
|
||||||
<Tabs defaultActiveKey="source" className="upload-readme">
|
<Tabs defaultActiveKey="source" className="upload-readme">
|
||||||
<Tabs.TabPane tab="Edit" key="source" style={{ height: descriptionEditorHeight }}>
|
<Tabs.TabPane tab="Edit" key="source" style={{ height: descriptionEditorHeight }}>
|
||||||
<Input.TextArea
|
<Item name="readme">
|
||||||
rows={10}
|
<Input.TextArea
|
||||||
showCount
|
rows={10}
|
||||||
value={readme}
|
showCount
|
||||||
onChange={(e) => setReadme(e.target.value)}
|
value={readme}
|
||||||
maxLength={3_000}
|
onChange={(e) => setReadme(e.target.value)}
|
||||||
/>
|
maxLength={3_000}
|
||||||
|
/>
|
||||||
|
</Item>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane tab="Preview" key="preview" style={{ height: descriptionEditorHeight }}>
|
<Tabs.TabPane tab="Preview" key="preview" style={{ height: descriptionEditorHeight }}>
|
||||||
<div className="markdown-preview" style={{ height: '100%' }}>
|
<div className="markdown-preview" style={{ height: '100%' }}>
|
||||||
|
@ -559,72 +593,15 @@ const UploadPage = () => {
|
||||||
</div>
|
</div>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<Divider>
|
|
||||||
<Space>Details</Space>
|
|
||||||
</Divider>
|
|
||||||
<Row {...rowProps}>
|
|
||||||
<Col span={12}>
|
|
||||||
<Input addonBefore="Make" value={make} onChange={(e) => setMake(e.target.value)} />
|
|
||||||
</Col>
|
|
||||||
<Col span={12}>
|
|
||||||
<Input addonBefore="Model" value={model} onChange={(e) => setModel(e.target.value)} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row {...rowProps}>
|
|
||||||
<Col span={12}>
|
|
||||||
<InputNumber addonBefore="Year" value={year} onChange={setYear} style={{ width: '100%' }} min={1886} />
|
|
||||||
</Col>
|
|
||||||
<Col span={12}>
|
|
||||||
<Input addonBefore="Displacement" addonAfter="l" value={displacement} onChange={(e) => setDisplacement(e.target.value)} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row {...rowProps}>
|
|
||||||
<Col span={12}>
|
|
||||||
<InputNumber addonBefore="HP" value={hp} onChange={setHp} style={{ width: '100%' }} min={0} />
|
|
||||||
</Col>
|
|
||||||
<Col span={12}>
|
|
||||||
<InputNumber addonBefore="Stock HP" value={stockHp} onChange={setStockHp} style={{ width: '100%' }} min={0} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row {...rowProps}>
|
|
||||||
<Col span={12}>
|
|
||||||
<Input addonBefore="Engine code" value={engineCode} onChange={(e) => setEngineCode(e.target.value)} />
|
|
||||||
</Col>
|
|
||||||
<Col span={12}>
|
|
||||||
<InputNumber addonBefore="No of cylinders" value={cylinders} onChange={setCylinders} style={{ width: '100%' }} min={0} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row {...rowProps}>
|
|
||||||
<Col span={12}>
|
|
||||||
<Select placeholder="Aspiration" value={aspiration} onChange={setAspiration} style={{ width: '100%' }}>
|
|
||||||
<Select.Option value="NA">Naturally aspirated</Select.Option>
|
|
||||||
<Select.Option value="turbo">Turbo</Select.Option>
|
|
||||||
<Select.Option value="compressor">Compressor</Select.Option>
|
|
||||||
</Select>
|
|
||||||
</Col>
|
|
||||||
<Col span={12}>
|
|
||||||
<Input addonBefore="Fuel type" value={fuel} onChange={(e) => setFuel(e.target.value)} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row {...rowProps}>
|
|
||||||
<Col span={12}>
|
|
||||||
<Input addonBefore="Injectors" value={injectors} onChange={(e) => setInjectors(e.target.value)} />
|
|
||||||
</Col>
|
|
||||||
<Col span={12}>
|
|
||||||
<Input addonBefore="Coils" value={coils} onChange={(e) => setCoils(e.target.value)} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Divider>
|
<Divider>
|
||||||
Visibility
|
Visibility
|
||||||
</Divider>
|
</Divider>
|
||||||
<Space direction="vertical" size="large">
|
<Item name="isPublic" label="Public:" valuePropName="checked">
|
||||||
<Space>
|
<Switch disabled />
|
||||||
Public:<Switch disabled checked={isPublic} onChange={setIsPublic} />
|
</Item>
|
||||||
</Space>
|
<Item name="isListed" label="Listed:" valuePropName="checked">
|
||||||
<Space>
|
<Switch />
|
||||||
Listed:<Switch checked={isListed} onChange={setIsListed} />
|
</Item>
|
||||||
</Space>
|
|
||||||
</Space>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -669,11 +646,25 @@ const UploadPage = () => {
|
||||||
>
|
>
|
||||||
{Object.keys(toothLogFiles).length < MaxFiles.TOOTH_LOG_FILES && uploadButton}
|
{Object.keys(toothLogFiles).length < MaxFiles.TOOTH_LOG_FILES && uploadButton}
|
||||||
</Upload>
|
</Upload>
|
||||||
<Space style={{ marginTop: 30 }}>
|
<Divider>
|
||||||
Show details:
|
<Space>
|
||||||
<Switch checked={showDetails} onChange={setShowDetails} />
|
Upload Custom INI
|
||||||
</Space>
|
<Typography.Text type="secondary">(.ini)</Typography.Text>
|
||||||
{showDetails && detailsSection}
|
</Space>
|
||||||
|
</Divider>
|
||||||
|
<Upload
|
||||||
|
listType="picture-card"
|
||||||
|
customRequest={uploadCustomIni}
|
||||||
|
data={customIniFileData}
|
||||||
|
onRemove={removeCustomIniFile}
|
||||||
|
iconRender={iniIcon}
|
||||||
|
disabled={isPublished}
|
||||||
|
onPreview={noop}
|
||||||
|
accept=".ini"
|
||||||
|
>
|
||||||
|
{!customIniFile && uploadButton}
|
||||||
|
</Upload>
|
||||||
|
{detailsSection}
|
||||||
{shareUrl && tuneFile && shareSection}
|
{shareUrl && tuneFile && shareSection}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -696,25 +687,34 @@ const UploadPage = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={containerStyle}>
|
<div style={containerStyle}>
|
||||||
<Divider>
|
<Form
|
||||||
<Space>
|
onFinish={publish}
|
||||||
Upload Tune
|
initialValues={{
|
||||||
<Typography.Text type="secondary">(.msq)</Typography.Text>
|
readme: '# My Tune\n\ndescription',
|
||||||
</Space>
|
isPublic: true,
|
||||||
</Divider>
|
isListed: true,
|
||||||
<Upload
|
}}
|
||||||
listType="picture-card"
|
|
||||||
customRequest={uploadTune}
|
|
||||||
data={tuneFileData}
|
|
||||||
onRemove={removeTuneFile}
|
|
||||||
iconRender={tuneIcon}
|
|
||||||
disabled={isPublished}
|
|
||||||
onPreview={noop}
|
|
||||||
accept=".msq"
|
|
||||||
>
|
>
|
||||||
{tuneFile === null && uploadButton}
|
<Divider>
|
||||||
</Upload>
|
<Space>
|
||||||
{tuneFile && optionalSection}
|
Upload Tune
|
||||||
|
<Typography.Text type="secondary">(.msq)</Typography.Text>
|
||||||
|
</Space>
|
||||||
|
</Divider>
|
||||||
|
<Upload
|
||||||
|
listType="picture-card"
|
||||||
|
customRequest={uploadTune}
|
||||||
|
data={tuneFileData}
|
||||||
|
onRemove={removeTuneFile}
|
||||||
|
iconRender={tuneIcon}
|
||||||
|
disabled={isPublished}
|
||||||
|
onPreview={noop}
|
||||||
|
accept=".msq"
|
||||||
|
>
|
||||||
|
{tuneFile === null && uploadButton}
|
||||||
|
</Upload>
|
||||||
|
{tuneFile && optionalSection}
|
||||||
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,15 +2,15 @@ export interface TuneDataDetails {
|
||||||
readme?: string | null;
|
readme?: string | null;
|
||||||
make?: string | null;
|
make?: string | null;
|
||||||
model?: string | null;
|
model?: string | null;
|
||||||
displacement?: string | null;
|
displacement?: number | null;
|
||||||
year?: number | null;
|
year?: number | null;
|
||||||
hp?: number | null;
|
hp?: number | null;
|
||||||
stockHp?: number | null;
|
stockHp?: number | null;
|
||||||
engineCode?: string | null;
|
engineCode?: string | null;
|
||||||
cylinders?: number | null;
|
cylindersCount?: number | null;
|
||||||
aspiration?: string | null;
|
aspiration?: string | null;
|
||||||
fuel?: string | null;
|
fuel?: string | null;
|
||||||
injectors?: string | null;
|
injectorsSize?: number | null;
|
||||||
coils?: string | null;
|
coils?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue