diff --git a/src/App.tsx b/src/App.tsx index 5aa5e5d..b539c1d 100644 --- a/src/App.tsx +++ b/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(); // 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 }) = - {/* - - */} + + + + + diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx index 15890d5..dd9008a 100644 --- a/src/components/SideBar.tsx +++ b/src/components/SideBar.tsx @@ -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]); diff --git a/src/components/TopBar.tsx b/src/components/TopBar.tsx index 78bc6e4..eff4ddd 100644 --- a/src/components/TopBar.tsx +++ b/src/components/TopBar.tsx @@ -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 }) => { history.push(e.target.value)} > + + + + {sm && 'Info'} + + diff --git a/src/hooks/useDb.ts b/src/hooks/useDb.ts index a16d963..b0820f4 100644 --- a/src/hooks/useDb.ts +++ b/src/hooks/useDb.ts @@ -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); diff --git a/src/pages/Info.tsx b/src/pages/Info.tsx new file mode 100644 index 0000000..c3c9bf3 --- /dev/null +++ b/src/pages/Info.tsx @@ -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 ( +
+ +
+ ); + } + + return ( +
+ Details +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ README +
+ {tuneData.details?.readme && + {`${tuneData.details?.readme}`} + } +
+
+ ); +}; + +export default connect(mapStateToProps)(Info); diff --git a/src/pages/Tune.tsx b/src/pages/Tune.tsx index ec4997f..f75cb08 100644 --- a/src/pages/Tune.tsx +++ b/src/pages/Tune.tsx @@ -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, }); diff --git a/src/pages/Upload.tsx b/src/pages/Upload.tsx index f94dc3e..bb87405 100644 --- a/src/pages/Upload.tsx +++ b/src/pages/Upload.tsx @@ -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 = () => { : } diff --git a/src/store.ts b/src/store.ts index e97a648..cdbc021 100644 --- a/src/store.ts +++ b/src/store.ts @@ -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('tune/update'); const loadTune = createAction('tune/load'); +const loadTuneData = createAction('tuneData/load'); const loadConfig = createAction('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; }) diff --git a/src/types/dbData.ts b/src/types/dbData.ts index 94c3aef..d639cba 100644 --- a/src/types/dbData.ts +++ b/src/types/dbData.ts @@ -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; diff --git a/src/types/state.ts b/src/types/state.ts index b96f8eb..7634da1 100644 --- a/src/types/state.ts +++ b/src/types/state.ts @@ -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;