Commit 40585607 authored by Johnny's avatar Johnny

refactor: streamline MemoView component and related hooks, removing unused...

refactor: streamline MemoView component and related hooks, removing unused code and integrating user data
parent 64ae1383
import { memo, useMemo, useRef, useState } from "react"; import { memo, useMemo, useRef, useState } from "react";
import { useUser } from "@/hooks/useUserQueries";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import type { Memo } from "@/types/proto/api/v1/memo_service_pb";
import MemoEditor from "../MemoEditor"; import MemoEditor from "../MemoEditor";
import PreviewImageDialog from "../PreviewImageDialog"; import PreviewImageDialog from "../PreviewImageDialog";
import { MemoBody, MemoHeader } from "./components"; import { MemoBody, MemoHeader } from "./components";
import { MEMO_CARD_BASE_CLASSES } from "./constants"; import { MEMO_CARD_BASE_CLASSES } from "./constants";
import { import { useImagePreview, useKeyboardShortcuts, useMemoActions, useMemoHandlers, useMemoViewDerivedState, useNsfwContent } from "./hooks";
useImagePreview,
useKeyboardShortcuts,
useMemoActions,
useMemoCreator,
useMemoEditor,
useMemoHandlers,
useMemoViewDerivedState,
useNsfwContent,
} from "./hooks";
import { MemoViewContext } from "./MemoViewContext"; import { MemoViewContext } from "./MemoViewContext";
import type { MemoViewProps } from "./types";
interface Props {
memo: Memo;
compact?: boolean;
showCreator?: boolean;
showVisibility?: boolean;
showPinned?: boolean;
showNsfwContent?: boolean;
className?: string;
parentPage?: string;
}
/** /**
* MemoView component displays a memo card with all its content, metadata, and interactive elements. * MemoView component displays a memo card with all its content, metadata, and interactive elements.
...@@ -49,17 +30,22 @@ interface Props { ...@@ -49,17 +30,22 @@ interface Props {
* /> * />
* ``` * ```
*/ */
const MemoView: React.FC<Props> = (props: Props) => { const MemoView: React.FC<MemoViewProps> = (props: MemoViewProps) => {
const { memo: memoData, className } = props; const { memo: memoData, className } = props;
const cardRef = useRef<HTMLDivElement>(null); const cardRef = useRef<HTMLDivElement>(null);
const [reactionSelectorOpen, setReactionSelectorOpen] = useState(false); const [reactionSelectorOpen, setReactionSelectorOpen] = useState(false);
const [showEditor, setShowEditor] = useState(false);
const creator = useMemoCreator(memoData.creator); const creator = useUser(memoData.creator).data;
const { isArchived, readonly, parentPage } = useMemoViewDerivedState(memoData, props.parentPage); const { isArchived, readonly, parentPage } = useMemoViewDerivedState(memoData, props.parentPage);
const { showNSFWContent, toggleNsfwVisibility } = useNsfwContent(memoData, props.showNsfwContent); const { nsfw, showNSFWContent, toggleNsfwVisibility } = useNsfwContent(memoData, props.showNsfwContent);
const { previewState, openPreview, setPreviewOpen } = useImagePreview(); const { previewState, openPreview, setPreviewOpen } = useImagePreview();
const { showEditor, openEditor, handleEditorConfirm, handleEditorCancel } = useMemoEditor(); const { archiveMemo, unpinMemo } = useMemoActions(memoData, isArchived);
const { archiveMemo, unpinMemo } = useMemoActions(memoData);
const handleEditorConfirm = () => setShowEditor(false);
const handleEditorCancel = () => setShowEditor(false);
const openEditor = () => setShowEditor(true);
const { handleGotoMemoDetailPage, handleMemoContentClick, handleMemoContentDoubleClick } = useMemoHandlers({ const { handleGotoMemoDetailPage, handleMemoContentClick, handleMemoContentDoubleClick } = useMemoHandlers({
memoName: memoData.name, memoName: memoData.name,
parentPage, parentPage,
...@@ -76,15 +62,15 @@ const MemoView: React.FC<Props> = (props: Props) => { ...@@ -76,15 +62,15 @@ const MemoView: React.FC<Props> = (props: Props) => {
onArchive: archiveMemo, onArchive: archiveMemo,
}); });
// Minimal essential context - only non-derivable data
const contextValue = useMemo( const contextValue = useMemo(
() => ({ () => ({
memo: memoData, memo: memoData,
creator, creator,
parentPage, parentPage,
showNSFWContent, showNSFWContent,
nsfw,
}), }),
[memoData, creator, parentPage, showNSFWContent], [memoData, creator, parentPage, showNSFWContent, nsfw],
); );
if (showEditor) { if (showEditor) {
......
...@@ -9,12 +9,12 @@ import type { User } from "@/types/proto/api/v1/user_service_pb"; ...@@ -9,12 +9,12 @@ import type { User } from "@/types/proto/api/v1/user_service_pb";
import { isSuperUser } from "@/utils/user"; import { isSuperUser } from "@/utils/user";
import { RELATIVE_TIME_THRESHOLD_MS } from "./constants"; import { RELATIVE_TIME_THRESHOLD_MS } from "./constants";
// Minimal essential context - only data that cannot be easily derived
export interface MemoViewContextValue { export interface MemoViewContextValue {
memo: Memo; memo: Memo;
creator: User | undefined; creator: User | undefined;
parentPage: string; parentPage: string;
showNSFWContent: boolean; showNSFWContent: boolean;
nsfw: boolean;
} }
export const MemoViewContext = createContext<MemoViewContextValue | null>(null); export const MemoViewContext = createContext<MemoViewContextValue | null>(null);
...@@ -27,7 +27,6 @@ export const useMemoViewContext = (): MemoViewContextValue => { ...@@ -27,7 +27,6 @@ export const useMemoViewContext = (): MemoViewContextValue => {
return context; return context;
}; };
// Utility hooks to derive common values from context
export const useMemoViewDerived = () => { export const useMemoViewDerived = () => {
const { memo } = useMemoViewContext(); const { memo } = useMemoViewContext();
const location = useLocation(); const location = useLocation();
...@@ -45,14 +44,11 @@ export const useMemoViewDerived = () => { ...@@ -45,14 +44,11 @@ export const useMemoViewDerived = () => {
const relativeTimeFormat: "datetime" | "auto" = const relativeTimeFormat: "datetime" | "auto" =
displayTime && Date.now() - displayTime.getTime() > RELATIVE_TIME_THRESHOLD_MS ? "datetime" : "auto"; displayTime && Date.now() - displayTime.getTime() > RELATIVE_TIME_THRESHOLD_MS ? "datetime" : "auto";
const nsfw = memo.tags.some((tag) => tag.toLowerCase() === "nsfw");
return { return {
isArchived, isArchived,
readonly, readonly,
isInMemoDetailPage, isInMemoDetailPage,
commentAmount, commentAmount,
relativeTimeFormat, relativeTimeFormat,
nsfw,
}; };
}; };
...@@ -4,21 +4,13 @@ import { useTranslate } from "@/utils/i18n"; ...@@ -4,21 +4,13 @@ import { useTranslate } from "@/utils/i18n";
import MemoContent from "../../MemoContent"; import MemoContent from "../../MemoContent";
import { MemoReactionListView } from "../../MemoReactionListView"; import { MemoReactionListView } from "../../MemoReactionListView";
import { AttachmentList, LocationDisplay, RelationList } from "../../memo-metadata"; import { AttachmentList, LocationDisplay, RelationList } from "../../memo-metadata";
import { useMemoViewContext, useMemoViewDerived } from "../MemoViewContext"; import { useMemoViewContext } from "../MemoViewContext";
import type { MemoBodyProps } from "../types";
interface Props { const MemoBody: React.FC<MemoBodyProps> = ({ compact, onContentClick, onContentDoubleClick, onToggleNsfwVisibility }) => {
compact?: boolean;
onContentClick: (e: React.MouseEvent) => void;
onContentDoubleClick: (e: React.MouseEvent) => void;
onToggleNsfwVisibility: () => void;
}
const MemoBody: React.FC<Props> = ({ compact, onContentClick, onContentDoubleClick, onToggleNsfwVisibility }) => {
const t = useTranslate(); const t = useTranslate();
// Get essential context and derive other values const { memo, parentPage, showNSFWContent, nsfw } = useMemoViewContext();
const { memo, parentPage, showNSFWContent } = useMemoViewContext();
const { nsfw } = useMemoViewDerived();
const referencedMemos = memo.relations.filter((relation) => relation.type === MemoRelation_Type.REFERENCE); const referencedMemos = memo.relations.filter((relation) => relation.type === MemoRelation_Type.REFERENCE);
......
...@@ -13,20 +13,9 @@ import { ReactionSelector } from "../../MemoReactionListView"; ...@@ -13,20 +13,9 @@ import { ReactionSelector } from "../../MemoReactionListView";
import UserAvatar from "../../UserAvatar"; import UserAvatar from "../../UserAvatar";
import VisibilityIcon from "../../VisibilityIcon"; import VisibilityIcon from "../../VisibilityIcon";
import { useMemoViewContext, useMemoViewDerived } from "../MemoViewContext"; import { useMemoViewContext, useMemoViewDerived } from "../MemoViewContext";
import type { MemoHeaderProps } from "../types";
interface Props { const MemoHeader: React.FC<MemoHeaderProps> = ({
showCreator?: boolean;
showVisibility?: boolean;
showPinned?: boolean;
onEdit: () => void;
onGotoDetail: () => void;
onUnpin: () => void;
onToggleNsfwVisibility?: () => void;
reactionSelectorOpen: boolean;
onReactionSelectorOpenChange: (open: boolean) => void;
}
const MemoHeader: React.FC<Props> = ({
showCreator, showCreator,
showVisibility, showVisibility,
showPinned, showPinned,
...@@ -39,9 +28,8 @@ const MemoHeader: React.FC<Props> = ({ ...@@ -39,9 +28,8 @@ const MemoHeader: React.FC<Props> = ({
}) => { }) => {
const t = useTranslate(); const t = useTranslate();
// Get essential context and derive other values const { memo, creator, parentPage, showNSFWContent, nsfw } = useMemoViewContext();
const { memo, creator, parentPage, showNSFWContent } = useMemoViewContext(); const { isArchived, readonly, isInMemoDetailPage, commentAmount, relativeTimeFormat } = useMemoViewDerived();
const { isArchived, readonly, isInMemoDetailPage, commentAmount, relativeTimeFormat, nsfw } = useMemoViewDerived();
const displayTime = isArchived ? ( const displayTime = isArchived ? (
(memo.displayTime ? timestampDate(memo.displayTime) : undefined)?.toLocaleString(i18n.language) (memo.displayTime ? timestampDate(memo.displayTime) : undefined)?.toLocaleString(i18n.language)
......
export { useImagePreview } from "./useImagePreview"; export { useImagePreview } from "./useImagePreview";
export { useKeyboardShortcuts } from "./useKeyboardShortcuts"; export { useKeyboardShortcuts } from "./useKeyboardShortcuts";
export { useMemoActions } from "./useMemoActions"; export { useMemoActions } from "./useMemoActions";
export { useMemoCreator } from "./useMemoCreator";
export { useMemoEditor } from "./useMemoEditor";
export { useMemoHandlers } from "./useMemoHandlers"; export { useMemoHandlers } from "./useMemoHandlers";
export { useMemoViewDerivedState } from "./useMemoViewDerivedState"; export { useMemoViewDerivedState } from "./useMemoViewDerivedState";
export { useNsfwContent } from "./useNsfwContent"; export { useNsfwContent } from "./useNsfwContent";
...@@ -5,10 +5,9 @@ import { State } from "@/types/proto/api/v1/common_pb"; ...@@ -5,10 +5,9 @@ import { State } from "@/types/proto/api/v1/common_pb";
import type { Memo } from "@/types/proto/api/v1/memo_service_pb"; import type { Memo } from "@/types/proto/api/v1/memo_service_pb";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
export const useMemoActions = (memo: Memo) => { export const useMemoActions = (memo: Memo, isArchived: boolean) => {
const t = useTranslate(); const t = useTranslate();
const { mutateAsync: updateMemo } = useUpdateMemo(); const { mutateAsync: updateMemo } = useUpdateMemo();
const isArchived = memo.state === State.ARCHIVED;
const archiveMemo = async () => { const archiveMemo = async () => {
if (isArchived) return; if (isArchived) return;
......
import { useUser } from "@/hooks/useUserQueries";
export const useMemoCreator = (creatorName: string) => {
const { data: creator } = useUser(creatorName);
return creator;
};
import { useState } from "react";
export const useMemoEditor = () => {
const [showEditor, setShowEditor] = useState(false);
return {
showEditor,
openEditor: () => setShowEditor(true),
handleEditorConfirm: () => {
setShowEditor(false);
},
handleEditorCancel: () => setShowEditor(false),
};
};
import { timestampDate } from "@bufbuild/protobuf/wkt";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { State } from "@/types/proto/api/v1/common_pb"; import { State } from "@/types/proto/api/v1/common_pb";
import type { Memo } from "@/types/proto/api/v1/memo_service_pb"; import type { Memo } from "@/types/proto/api/v1/memo_service_pb";
import { MemoRelation_Type } from "@/types/proto/api/v1/memo_service_pb";
import { isSuperUser } from "@/utils/user"; import { isSuperUser } from "@/utils/user";
import { RELATIVE_TIME_THRESHOLD_MS } from "../constants";
export const useMemoViewDerivedState = (memo: Memo, parentPageProp?: string) => { export const useMemoViewDerivedState = (memo: Memo, parentPageProp?: string) => {
const location = useLocation(); const location = useLocation();
const user = useCurrentUser(); const user = useCurrentUser();
const commentAmount = memo.relations.filter(
(relation) => relation.type === MemoRelation_Type.COMMENT && relation.relatedMemo?.name === memo.name,
).length;
const displayTime = memo.displayTime ? timestampDate(memo.displayTime) : undefined;
const relativeTimeFormat: "datetime" | "auto" =
displayTime && Date.now() - displayTime.getTime() > RELATIVE_TIME_THRESHOLD_MS ? "datetime" : "auto";
const isArchived = memo.state === State.ARCHIVED; const isArchived = memo.state === State.ARCHIVED;
const readonly = memo.creator !== user?.name && !isSuperUser(user); const readonly = memo.creator !== user?.name && !isSuperUser(user);
const isInMemoDetailPage = location.pathname.startsWith(`/${memo.name}`);
const parentPage = parentPageProp || location.pathname; const parentPage = parentPageProp || location.pathname;
return { commentAmount, relativeTimeFormat, isArchived, readonly, isInMemoDetailPage, parentPage }; return { isArchived, readonly, parentPage };
}; };
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment