abstracted navigation logic
This commit is contained in:
@@ -15,6 +15,32 @@ import Profile from './components/Profile';
|
||||
import { useArticles } from './providers/Article';
|
||||
import { useAuth } from './providers/Author';
|
||||
import { View, useViewRouter } from "./types/views";
|
||||
import { ArticleModel } from "./types/models";
|
||||
import { ArticleViewProps, ArticleEditorProps } from "./types/props";
|
||||
|
||||
function HomeView({ currentUser, open_login, open_profile, open_create, articles, openArticle }: any) {
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ display: "flex", justifyContent: "flex-end", mb: 2, gap: 1 }}>
|
||||
{!currentUser ? (
|
||||
<Button variant="outlined" onClick={open_login}>Login</Button>
|
||||
) : (
|
||||
<>
|
||||
<Button variant="outlined" onClick={open_profile}>
|
||||
{currentUser.username}
|
||||
</Button>
|
||||
<Button variant="contained" onClick={open_create}>
|
||||
New Article
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<MainContent articles={articles} onSelectArticle={openArticle} />
|
||||
<Latest articles={articles} onSelectArticle={openArticle} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Blog(props: { disableCustomTheme?: boolean }) {
|
||||
const { articles, loading, error } = useArticles();
|
||||
@@ -27,72 +53,86 @@ export default function Blog(props: { disableCustomTheme?: boolean }) {
|
||||
|
||||
const {
|
||||
goBack,
|
||||
openLogin,
|
||||
openRegister,
|
||||
openProfile,
|
||||
openCreate,
|
||||
openEditor,
|
||||
navigateToChildren,
|
||||
openArticle,
|
||||
} = useViewRouter(setUI);
|
||||
|
||||
type RouterContext = {
|
||||
ui: any;
|
||||
articles: ArticleModel[];
|
||||
currentUser: any;
|
||||
openArticle: (index: number) => void;
|
||||
};
|
||||
|
||||
type ViewComponentEntry<P> = {
|
||||
component: React.ComponentType<P>;
|
||||
extraProps?: (ctx: RouterContext) => Partial<P>;
|
||||
};
|
||||
|
||||
const VIEW_COMPONENTS: Record<View, ViewComponentEntry<any>> = {
|
||||
home: {
|
||||
component: HomeView,
|
||||
},
|
||||
|
||||
login: {
|
||||
component: Login,
|
||||
},
|
||||
|
||||
register: {
|
||||
component: Register,
|
||||
},
|
||||
|
||||
profile: {
|
||||
component: Profile,
|
||||
},
|
||||
|
||||
article: {
|
||||
component: ArticleView,
|
||||
extraProps: ({ ui, articles }) => ({
|
||||
article: articles[ui.selectedArticle!],
|
||||
}) satisfies Partial<ArticleViewProps>,
|
||||
},
|
||||
|
||||
editor: {
|
||||
component: ArticleEditor,
|
||||
extraProps: ({ ui, articles }) => ({
|
||||
article: ui.selectedArticle !== null ? articles[ui.selectedArticle] : null,
|
||||
}) satisfies Partial<ArticleEditorProps>,
|
||||
},
|
||||
|
||||
create: {
|
||||
component: ArticleEditor,
|
||||
extraProps: () => ({
|
||||
article: null,
|
||||
}) satisfies Partial<ArticleEditorProps>,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
const renderView = () => {
|
||||
switch (ui.view) {
|
||||
case "login":
|
||||
return <Login onBack={() => goBack(ui.view)} onRegister={openRegister} />;
|
||||
const entry = VIEW_COMPONENTS[ui.view];
|
||||
const ViewComponent = entry.component;
|
||||
|
||||
case "register":
|
||||
return <Register onBack={() => goBack(ui.view)} />;
|
||||
const childNav = navigateToChildren(ui.view);
|
||||
|
||||
case "profile":
|
||||
return <Profile onBack={() => goBack(ui.view)} />;
|
||||
const ctx: RouterContext = {
|
||||
ui,
|
||||
articles,
|
||||
currentUser,
|
||||
|
||||
case "article":
|
||||
return (
|
||||
<ArticleView
|
||||
article={articles[ui.selectedArticle!]}
|
||||
onBack={() => goBack(ui.view)}
|
||||
onEdit={openEditor}
|
||||
/>
|
||||
);
|
||||
openArticle,
|
||||
};
|
||||
|
||||
case "editor":
|
||||
return (
|
||||
<ArticleEditor
|
||||
article={ui.selectedArticle !== null ? articles[ui.selectedArticle] : null}
|
||||
onBack={() => goBack(ui.view)}
|
||||
/>
|
||||
);
|
||||
const extraProps = entry.extraProps ? entry.extraProps(ctx) : {};
|
||||
|
||||
case "create":
|
||||
return (
|
||||
<ArticleEditor
|
||||
onBack={() => goBack(ui.view)}
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ display: "flex", justifyContent: "flex-end", mb: 2, gap: 1 }}>
|
||||
{!currentUser ? (
|
||||
<Button variant="outlined" onClick={openLogin}>Login</Button>
|
||||
) : (
|
||||
<>
|
||||
<Button variant="outlined" onClick={openProfile}>
|
||||
{currentUser.username}
|
||||
</Button>
|
||||
<Button variant="contained" onClick={openCreate}>
|
||||
New Article
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<MainContent articles={articles} onSelectArticle={openArticle} />
|
||||
<Latest articles={articles} onSelectArticle={openArticle} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ViewComponent
|
||||
{...ctx}
|
||||
{...childNav}
|
||||
onBack={() => goBack(ui.view)}
|
||||
{...extraProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { styled } from '@mui/material/styles';
|
||||
import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded';
|
||||
import EditRoundedIcon from '@mui/icons-material/EditRounded';
|
||||
import { ArticleMeta } from "../ArticleMeta";
|
||||
import { ArticleProps } from '../../types/props';
|
||||
import { ArticleViewProps } from '../../types/props';
|
||||
import {useAuth} from "../../providers/Author";
|
||||
|
||||
const ArticleContainer = styled(Box)(({ theme }) => ({
|
||||
@@ -28,10 +28,12 @@ const CoverImage = styled('img')({
|
||||
export default function ArticleView({
|
||||
article,
|
||||
onBack,
|
||||
onEdit,
|
||||
}: ArticleProps) {
|
||||
open_editor,
|
||||
}: ArticleViewProps) {
|
||||
|
||||
const { currentUser } = useAuth();
|
||||
const onEdit = open_editor;
|
||||
|
||||
return (
|
||||
<ArticleContainer>
|
||||
<Box
|
||||
|
||||
@@ -6,12 +6,13 @@ export interface LatestProps {
|
||||
onLoadMore?: (offset: number, limit: number) => Promise<void>; // optional async callback
|
||||
}
|
||||
|
||||
export interface ArticleProps {
|
||||
export interface ArticleViewProps {
|
||||
article: ArticleModel;
|
||||
onBack: () => void;
|
||||
onEdit: () => void;
|
||||
open_editor?: () => void; // optional because home → article must still work
|
||||
}
|
||||
|
||||
|
||||
export interface ArticleEditorProps {
|
||||
article?: ArticleModel | null;
|
||||
onBack: () => void;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// types.ts
|
||||
export type View =
|
||||
| "home"
|
||||
| "login"
|
||||
@@ -40,17 +39,16 @@ export const VIEW_TREE: Record<View, ViewNode> = {
|
||||
},
|
||||
};
|
||||
|
||||
export function useViewRouter(setUI: React.Dispatch<any>) {
|
||||
export function useViewRouter(setUI: any) {
|
||||
const navigate = (view: View) => {
|
||||
setUI((prev: any) => ({ ...prev, view }));
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
};
|
||||
|
||||
const goBack = (currentView: View) => {
|
||||
const parent = VIEW_TREE[currentView].parent;
|
||||
if (parent) {
|
||||
setUI((prev: any) => ({ ...prev, view: parent }));
|
||||
}
|
||||
// auto back logic from parent
|
||||
const goBack = (view: View) => {
|
||||
const parent = VIEW_TREE[view].parent;
|
||||
if (parent) navigate(parent);
|
||||
};
|
||||
|
||||
const openArticle = (i: number) => {
|
||||
@@ -58,14 +56,17 @@ export function useViewRouter(setUI: React.Dispatch<any>) {
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
};
|
||||
|
||||
return {
|
||||
navigate,
|
||||
goBack,
|
||||
openLogin: () => navigate("login"),
|
||||
openRegister: () => navigate("register"),
|
||||
openProfile: () => navigate("profile"),
|
||||
openCreate: () => navigate("create"),
|
||||
openEditor: () => navigate("editor"),
|
||||
openArticle,
|
||||
// auto child navigators from children[]
|
||||
const navigateToChildren = (view: View) => {
|
||||
const node = VIEW_TREE[view];
|
||||
const funcs: Record<string, () => void> = {};
|
||||
|
||||
node.children?.forEach((child) => {
|
||||
funcs[`open_${child}`] = () => navigate(child);
|
||||
});
|
||||
|
||||
return funcs;
|
||||
};
|
||||
|
||||
return { navigate, goBack, openArticle, navigateToChildren };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user