Fix tune loading and routing (#378)

This commit is contained in:
Piotr Rogowski 2022-01-16 18:32:55 +01:00 committed by GitHub
parent affdae49e8
commit c53cd0dcc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 188 additions and 35 deletions

View File

@ -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) {
const tuneId = (matchedTunePath?.params as any)?.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 />

View File

@ -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]);

View File

@ -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 />

View File

@ -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);

129
src/pages/Info.tsx Normal file
View File

@ -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);

View File

@ -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,
});

View File

@ -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>}

View File

@ -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;
})

View File

@ -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;

View File

@ -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;