Fix tune loading and routing (#378)
This commit is contained in:
parent
affdae49e8
commit
c53cd0dcc3
25
src/App.tsx
25
src/App.tsx
|
@ -18,7 +18,6 @@ import {
|
|||
useEffect,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
import useBrowserStorage from './hooks/useBrowserStorage';
|
||||
import TopBar from './components/TopBar';
|
||||
import StatusBar from './components/StatusBar';
|
||||
import { Routes } from './routes';
|
||||
|
@ -35,6 +34,7 @@ import {
|
|||
} from './types/state';
|
||||
import useDb from './hooks/useDb';
|
||||
import useServerStorage from './hooks/useServerStorage';
|
||||
import Info from './pages/Info';
|
||||
|
||||
// TODO: fix this
|
||||
// lazy loading this component causes a weird Curve canvas scaling
|
||||
|
@ -59,7 +59,6 @@ const App = ({ ui, navigation }: { ui: UIState, navigation: NavigationState }) =
|
|||
const margin = ui.sidebarCollapsed ? 80 : 250;
|
||||
const { getTune } = useDb();
|
||||
const { getFile } = useServerStorage();
|
||||
const { storageSet } = useBrowserStorage();
|
||||
|
||||
// const [lastDialogPath, setLastDialogPath] = useState<string|null>();
|
||||
// const lastDialogPath = storageGetSync('lastDialog');
|
||||
|
@ -69,21 +68,21 @@ const App = ({ ui, navigation }: { ui: UIState, navigation: NavigationState }) =
|
|||
path: Routes.TUNE_ROOT,
|
||||
}), [pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
const tuneId = (matchedTunePath?.params as any)?.tuneId;
|
||||
if (tuneId) {
|
||||
|
||||
useEffect(() => {
|
||||
if (tuneId) {
|
||||
getTune(tuneId).then(async (tuneData) => {
|
||||
const [tuneRaw, iniRaw] = await Promise.all([
|
||||
await getFile(tuneData.tuneFile!),
|
||||
await getFile(tuneData.customIniFile!),
|
||||
getFile(tuneData.tuneFile!),
|
||||
getFile(tuneData.customIniFile!),
|
||||
]);
|
||||
|
||||
store.dispatch({ type: 'tuneData/load', payload: tuneData });
|
||||
|
||||
loadTune(tuneRaw, iniRaw);
|
||||
});
|
||||
|
||||
|
||||
storageSet('lastTuneId', tuneId);
|
||||
store.dispatch({ type: 'navigation/tuneId', payload: tuneId });
|
||||
}
|
||||
|
||||
|
@ -96,7 +95,7 @@ const App = ({ ui, navigation }: { ui: UIState, navigation: NavigationState }) =
|
|||
// };
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}, [tuneId]);
|
||||
|
||||
const ContentFor = useCallback((props: { children: ReactNode, marginLeft?: number }) => {
|
||||
const { children, marginLeft } = props;
|
||||
|
@ -127,9 +126,6 @@ const App = ({ ui, navigation }: { ui: UIState, navigation: NavigationState }) =
|
|||
<TopBar tuneId={navigation.tuneId} />
|
||||
<Switch>
|
||||
<Route path={Routes.ROOT} exact>
|
||||
{/* <Route path={Routes.ROOT} exact>
|
||||
<Redirect to={lastDialogPath || Routes.TUNE_ROOT} />
|
||||
</Route> */}
|
||||
<ContentFor>
|
||||
<Result
|
||||
status="info"
|
||||
|
@ -138,6 +134,11 @@ const App = ({ ui, navigation }: { ui: UIState, navigation: NavigationState }) =
|
|||
/>
|
||||
</ContentFor>
|
||||
</Route>
|
||||
<Route path={Routes.TUNE_ROOT} exact>
|
||||
<ContentFor>
|
||||
<Info />
|
||||
</ContentFor>
|
||||
</Route>
|
||||
<Route path={Routes.TUNE_TUNE}>
|
||||
<ContentFor marginLeft={margin}>
|
||||
<Tune />
|
||||
|
|
|
@ -86,7 +86,7 @@ const SideBar = ({
|
|||
const { isConfigReady } = useConfig(config);
|
||||
const checkCondition = useCallback((condition: string) => evaluateExpression(condition, tune.constants, config), [tune.constants, config]);
|
||||
const buildUrl = useCallback((main: string, sub: string) => generatePath(Routes.TUNE_DIALOG, {
|
||||
tuneId: navigation.tuneId || 'not-ready',
|
||||
tuneId: navigation.tuneId!,
|
||||
category: main,
|
||||
dialog: sub,
|
||||
}), [navigation.tuneId]);
|
||||
|
|
|
@ -39,6 +39,7 @@ import {
|
|||
FundOutlined,
|
||||
UserAddOutlined,
|
||||
LogoutOutlined,
|
||||
InfoCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import {
|
||||
useCallback,
|
||||
|
@ -69,6 +70,9 @@ const TopBar = ({ tuneId }: { tuneId: string | null }) => {
|
|||
const { currentUser, logout } = useAuth();
|
||||
const history = useHistory();
|
||||
const buildTuneUrl = (route: string) => tuneId ? generatePath(route, { tuneId }) : null;
|
||||
const matchedTuneRootPath = useMemo(() => matchPath(pathname, {
|
||||
path: Routes.TUNE_ROOT,
|
||||
}), [pathname]);
|
||||
const matchedTabPath = useMemo(() => matchPath(pathname, {
|
||||
path: Routes.TUNE_TAB,
|
||||
}), [pathname]);
|
||||
|
@ -106,11 +110,17 @@ const TopBar = ({ tuneId }: { tuneId: string | null }) => {
|
|||
<Col span={10} md={10} sm={16} style={{ textAlign: 'center' }}>
|
||||
<Radio.Group
|
||||
key={pathname}
|
||||
defaultValue={matchedTabPath?.url}
|
||||
defaultValue={matchedTabPath?.url || matchedTuneRootPath?.url}
|
||||
optionType="button"
|
||||
buttonStyle="solid"
|
||||
onChange={(e) => history.push(e.target.value)}
|
||||
>
|
||||
<Radio.Button value={buildTuneUrl(Routes.TUNE_ROOT)}>
|
||||
<Space>
|
||||
<InfoCircleOutlined />
|
||||
{sm && 'Info'}
|
||||
</Space>
|
||||
</Radio.Button>
|
||||
<Radio.Button value={buildTuneUrl(Routes.TUNE_TUNE)}>
|
||||
<Space>
|
||||
<ToolOutlined />
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { notification } from 'antd';
|
||||
import * as Sentry from '@sentry/browser';
|
||||
import { Timestamp } from 'firebase/firestore';
|
||||
import {
|
||||
fireStoreDoc,
|
||||
getDoc,
|
||||
|
@ -16,8 +17,13 @@ const useDb = () => {
|
|||
const getData = async (tuneId: string) => {
|
||||
try {
|
||||
const tune = (await getDoc(fireStoreDoc(db, TUNES_PATH, tuneId))).data() as TuneDbData;
|
||||
const processed = {
|
||||
...tune,
|
||||
createdAt: (tune?.createdAt as Timestamp)?.toDate().toISOString(),
|
||||
updatedAt: (tune?.updatedAt as Timestamp)?.toDate().toISOString(),
|
||||
};
|
||||
|
||||
return Promise.resolve(tune);
|
||||
return Promise.resolve(processed);
|
||||
} catch (error) {
|
||||
Sentry.captureException(error);
|
||||
console.error(error);
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
import { connect } from 'react-redux';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import {
|
||||
Col,
|
||||
Divider,
|
||||
Form,
|
||||
Input,
|
||||
Row,
|
||||
Select,
|
||||
Skeleton,
|
||||
} from 'antd';
|
||||
import {
|
||||
AppState,
|
||||
TuneDataState,
|
||||
} from '../types/state';
|
||||
|
||||
const { Item } = Form;
|
||||
const containerStyle = {
|
||||
padding: 20,
|
||||
maxWidth: 600,
|
||||
margin: '0 auto',
|
||||
};
|
||||
const rowProps = { gutter: 10 };
|
||||
|
||||
const mapStateToProps = (state: AppState) => ({
|
||||
tuneData: state.tuneData,
|
||||
});
|
||||
|
||||
const Info = ({ tuneData }: { tuneData: TuneDataState }) => {
|
||||
if (!tuneData.details) {
|
||||
return (
|
||||
<div style={containerStyle}>
|
||||
<Skeleton active />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={containerStyle}>
|
||||
<Divider>Details</Divider>
|
||||
<Form>
|
||||
<Row {...rowProps}>
|
||||
<Col span={12}>
|
||||
<Item>
|
||||
<Input value={tuneData.details.make!} addonBefore="Make" />
|
||||
</Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Item>
|
||||
<Input value={tuneData.details.model!} addonBefore="Model" />
|
||||
</Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row {...rowProps}>
|
||||
<Col span={12}>
|
||||
<Item>
|
||||
<Input value={tuneData.details.year!} addonBefore="Year" style={{ width: '100%' }} />
|
||||
</Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Item>
|
||||
<Input value={tuneData.details.displacement!} addonBefore="Displacement" addonAfter="l" />
|
||||
</Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row {...rowProps}>
|
||||
<Col span={12}>
|
||||
<Item>
|
||||
<Input value={tuneData.details.hp!} addonBefore="HP" style={{ width: '100%' }} />
|
||||
</Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Item>
|
||||
<Input value={tuneData.details.stockHp!} addonBefore="Stock HP" style={{ width: '100%' }} />
|
||||
</Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row {...rowProps}>
|
||||
<Col span={12}>
|
||||
<Item>
|
||||
<Input value={tuneData.details.engineCode!} addonBefore="Engine code" />
|
||||
</Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Item>
|
||||
<Input value={tuneData.details.cylindersCount!} addonBefore="No of cylinders" style={{ width: '100%' }} />
|
||||
</Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row {...rowProps}>
|
||||
<Col span={12}>
|
||||
<Item>
|
||||
<Select placeholder="Aspiration" style={{ width: '100%' }} value={tuneData.details.aspiration}>
|
||||
<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>
|
||||
<Input value={tuneData.details.fuel!} addonBefore="Fuel" />
|
||||
</Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row {...rowProps}>
|
||||
<Col span={12}>
|
||||
<Item>
|
||||
<Input value={tuneData.details.injectorsSize!} addonBefore="Injectors size" addonAfter="cc" />
|
||||
</Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Item>
|
||||
<Input value={tuneData.details.coils!} addonBefore="Coils" />
|
||||
</Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
<Divider>README</Divider>
|
||||
<div className="markdown-preview" style={{ height: '100%' }}>
|
||||
{tuneData.details?.readme && <ReactMarkdown>
|
||||
{`${tuneData.details?.readme}`}
|
||||
</ReactMarkdown>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(Info);
|
|
@ -42,7 +42,7 @@ const Tune = ({ navigation, config }: { navigation: NavigationState, config: Con
|
|||
const firstCategory = Object.keys(config.menus)[0];
|
||||
const firstDialog = Object.keys(config.menus[firstCategory].subMenus)[0];
|
||||
return generatePath(Routes.TUNE_DIALOG, {
|
||||
tuneId: navigation.tuneId || 'not-ready',
|
||||
tuneId: navigation.tuneId!,
|
||||
category: firstCategory,
|
||||
dialog: firstDialog,
|
||||
});
|
||||
|
|
|
@ -34,7 +34,10 @@ import * as Sentry from '@sentry/browser';
|
|||
import { INI } from '@speedy-tuner/ini';
|
||||
import { UploadRequestOption } from 'rc-upload/lib/interface';
|
||||
import { UploadFile } from 'antd/lib/upload/interface';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import {
|
||||
generatePath,
|
||||
useHistory,
|
||||
} from 'react-router-dom';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import pako from 'pako';
|
||||
import {
|
||||
|
@ -47,7 +50,6 @@ import {
|
|||
} from './auth/notifications';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { Routes } from '../routes';
|
||||
import useBrowserStorage from '../hooks/useBrowserStorage';
|
||||
import TuneParser from '../utils/tune/TuneParser';
|
||||
import TriggerLogsParser from '../utils/logs/TriggerLogsParser';
|
||||
import LogParser from '../utils/logs/LogParser';
|
||||
|
@ -87,7 +89,6 @@ const containerStyle = {
|
|||
margin: '0 auto',
|
||||
};
|
||||
|
||||
const newTuneIdKey = 'newTuneId';
|
||||
const maxFileSizeMB = 10;
|
||||
const descriptionEditorHeight = 260;
|
||||
const rowProps = { gutter: 10 };
|
||||
|
@ -113,7 +114,6 @@ const UploadPage = () => {
|
|||
const hasNavigatorShare = navigator.share !== undefined;
|
||||
const { currentUser, refreshToken } = useAuth();
|
||||
const history = useHistory();
|
||||
const { storageSet, storageGet, storageDelete } = useBrowserStorage();
|
||||
const { removeFile, uploadFile, basePathForFile } = useServerStorage();
|
||||
const { updateData, getTune } = useDb();
|
||||
const requiredRules = [{ required: true, message: 'This field is required!' }];
|
||||
|
@ -121,6 +121,10 @@ const UploadPage = () => {
|
|||
|
||||
const noop = () => { };
|
||||
|
||||
const goToNewTune = () => history.push(generatePath(Routes.TUNE_ROOT, {
|
||||
tuneId: newTuneId!,
|
||||
}));
|
||||
|
||||
const copyToClipboard = async () => {
|
||||
if (navigator.clipboard) {
|
||||
await navigator.clipboard.writeText(shareUrl!);
|
||||
|
@ -156,7 +160,6 @@ const UploadPage = () => {
|
|||
});
|
||||
setIsLoading(false);
|
||||
setIsPublished(true);
|
||||
storageDelete(newTuneIdKey);
|
||||
};
|
||||
|
||||
const validateSize = (file: File) => Promise.resolve({
|
||||
|
@ -425,18 +428,12 @@ const UploadPage = () => {
|
|||
setIsUserAuthorized(true);
|
||||
} catch (error) {
|
||||
Sentry.captureException(error);
|
||||
storageDelete(newTuneIdKey);
|
||||
console.error(error);
|
||||
genericError(error as Error);
|
||||
}
|
||||
|
||||
let newTuneIdTemp = await storageGet(newTuneIdKey);
|
||||
if (!newTuneIdTemp) {
|
||||
newTuneIdTemp = nanoidCustom();
|
||||
await storageSet(newTuneIdKey, newTuneIdTemp);
|
||||
}
|
||||
setNewTuneId(newTuneIdTemp);
|
||||
}, [currentUser, history, refreshToken, storageDelete, storageGet, storageSet]);
|
||||
setNewTuneId(nanoidCustom());
|
||||
}, [currentUser, history, refreshToken]);
|
||||
|
||||
useEffect(() => {
|
||||
prepareData();
|
||||
|
@ -480,9 +477,7 @@ const UploadPage = () => {
|
|||
</Button> : <Button
|
||||
type="primary"
|
||||
block
|
||||
onClick={() => {
|
||||
window.location.href = shareUrl as string;
|
||||
}}
|
||||
onClick={goToNewTune}
|
||||
>
|
||||
Open
|
||||
</Button>}
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
AppState,
|
||||
ConfigState,
|
||||
LogsState,
|
||||
TuneDataState,
|
||||
TuneState,
|
||||
UpdateTunePayload,
|
||||
} from './types/state';
|
||||
|
@ -16,6 +17,7 @@ import {
|
|||
// tune and config
|
||||
const updateTune = createAction<UpdateTunePayload>('tune/update');
|
||||
const loadTune = createAction<TuneState>('tune/load');
|
||||
const loadTuneData = createAction<TuneDataState>('tuneData/load');
|
||||
const loadConfig = createAction<ConfigState>('config/load');
|
||||
|
||||
// navigation
|
||||
|
@ -35,6 +37,7 @@ const initialState: AppState = {
|
|||
tune: {
|
||||
constants: {},
|
||||
},
|
||||
tuneData: {},
|
||||
logs: [],
|
||||
config: {} as any,
|
||||
ui: {
|
||||
|
@ -56,6 +59,9 @@ const rootReducer = createReducer(initialState, (builder) => {
|
|||
.addCase(loadTune, (state: AppState, action) => {
|
||||
state.tune = action.payload;
|
||||
})
|
||||
.addCase(loadTuneData, (state: AppState, action) => {
|
||||
state.tuneData = action.payload;
|
||||
})
|
||||
.addCase(loadLogs, (state: AppState, action) => {
|
||||
state.logs = action.payload;
|
||||
})
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { Timestamp } from 'firebase/firestore';
|
||||
|
||||
export interface TuneDataDetails {
|
||||
readme?: string | null;
|
||||
make?: string | null;
|
||||
|
@ -16,8 +18,8 @@ export interface TuneDataDetails {
|
|||
|
||||
export interface TuneDbData {
|
||||
userUid?: string;
|
||||
createdAt?: Date;
|
||||
updatedAt?: Date;
|
||||
createdAt?: Date | Timestamp | string;
|
||||
updatedAt?: Date | Timestamp | string;
|
||||
isPublished?: boolean;
|
||||
isListed?: boolean;
|
||||
isPublic?: boolean;
|
||||
|
|
|
@ -3,11 +3,14 @@ import {
|
|||
Logs,
|
||||
Tune,
|
||||
} from '@speedy-tuner/types';
|
||||
import { TuneDbData } from './dbData';
|
||||
|
||||
export interface ConfigState extends Config {}
|
||||
|
||||
export interface TuneState extends Tune {}
|
||||
|
||||
export interface TuneDataState extends TuneDbData {}
|
||||
|
||||
export interface LogsState extends Logs {}
|
||||
|
||||
export interface UIState {
|
||||
|
@ -24,6 +27,7 @@ export interface NavigationState {
|
|||
|
||||
export interface AppState {
|
||||
tune: TuneState;
|
||||
tuneData: TuneDataState;
|
||||
config: ConfigState;
|
||||
logs: LogsState,
|
||||
ui: UIState;
|
||||
|
|
Loading…
Reference in New Issue