Commit d2b42121 authored by Johnny's avatar Johnny

chore: optimize RelationList and unify RelationCard usage

parent 77e9376e
...@@ -2,12 +2,10 @@ import { create } from "@bufbuild/protobuf"; ...@@ -2,12 +2,10 @@ import { create } from "@bufbuild/protobuf";
import { LinkIcon, XIcon } from "lucide-react"; import { LinkIcon, XIcon } from "lucide-react";
import type { FC } from "react"; import type { FC } from "react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Link } from "react-router-dom"; import RelationCard from "@/components/MemoView/components/metadata/RelationCard";
import { memoServiceClient } from "@/connect"; import { memoServiceClient } from "@/connect";
import { extractMemoIdFromName } from "@/helpers/resource-names"; import type { MemoRelation } from "@/types/proto/api/v1/memo_service_pb";
import { cn } from "@/lib/utils"; import { MemoRelation_Memo, MemoRelation_MemoSchema } from "@/types/proto/api/v1/memo_service_pb";
import type { Memo, MemoRelation } from "@/types/proto/api/v1/memo_service_pb";
import { MemoRelation_MemoSchema } from "@/types/proto/api/v1/memo_service_pb";
interface RelationListProps { interface RelationListProps {
relations: MemoRelation[]; relations: MemoRelation[];
...@@ -20,65 +18,44 @@ const RelationItemCard: FC<{ ...@@ -20,65 +18,44 @@ const RelationItemCard: FC<{
onRemove?: () => void; onRemove?: () => void;
parentPage?: string; parentPage?: string;
}> = ({ memo, onRemove, parentPage }) => { }> = ({ memo, onRemove, parentPage }) => {
const memoId = extractMemoIdFromName(memo!.name);
if (onRemove) {
return (
<div
className={cn(
"relative flex items-center gap-1.5 px-1.5 py-1 rounded border border-transparent hover:border-border hover:bg-accent/20 transition-all",
)}
>
<LinkIcon className="w-3.5 h-3.5 shrink-0 text-muted-foreground" />
<span className="text-xs font-medium truncate flex-1" title={memo!.snippet}>
{memo!.snippet}
</span>
<div className="flex-shrink-0 flex items-center gap-0.5">
<button
type="button"
onClick={onRemove}
className="p-0.5 rounded hover:bg-destructive/10 active:bg-destructive/10 transition-colors touch-manipulation"
title="Remove"
aria-label="Remove relation"
>
<XIcon className="w-3 h-3 text-muted-foreground hover:text-destructive" />
</button>
</div>
</div>
);
}
return ( return (
<Link <div className="group relative flex items-center justify-between w-full rounded hover:bg-accent/20 transition-colors">
className={cn( <RelationCard memo={memo!} parentPage={parentPage} className="flex-1 hover:bg-transparent" />
"relative flex items-center gap-1.5 px-1.5 py-1 rounded border border-transparent hover:border-border hover:bg-accent/20 transition-all",
{onRemove && (
<button
type="button"
onClick={onRemove}
className="p-1 mr-0.5 rounded opacity-0 group-hover:opacity-100 hover:bg-destructive/10 active:bg-destructive/10 transition-all touch-manipulation"
title="Remove"
aria-label="Remove relation"
>
<XIcon className="w-3 h-3 text-muted-foreground hover:text-destructive" />
</button>
)} )}
to={`/${memo!.name}`} </div>
viewTransition
state={{ from: parentPage }}
>
<span className="text-[10px] font-mono px-1 py-0.5 rounded bg-muted/50 text-muted-foreground shrink-0">{memoId.slice(0, 6)}</span>
<span className="text-xs truncate flex-1" title={memo!.snippet}>
{memo!.snippet}
</span>
</Link>
); );
}; };
const RelationList: FC<RelationListProps> = ({ relations, onRelationsChange, parentPage }) => { const RelationList: FC<RelationListProps> = ({ relations, onRelationsChange, parentPage }) => {
const [referencingMemos, setReferencingMemos] = useState<Memo[]>([]); const [fetchedMemos, setFetchedMemos] = useState<Record<string, MemoRelation_Memo>>({});
useEffect(() => { useEffect(() => {
(async () => { (async () => {
if (relations.length > 0) { const missingSnippetRelations = relations.filter((relation) => !relation.relatedMemo?.snippet && relation.relatedMemo?.name);
const requests = relations.map(async (relation) => { if (missingSnippetRelations.length > 0) {
return await memoServiceClient.getMemo({ name: relation.relatedMemo!.name }); const requests = missingSnippetRelations.map(async (relation) => {
const memo = await memoServiceClient.getMemo({ name: relation.relatedMemo!.name });
return create(MemoRelation_MemoSchema, { name: memo.name, snippet: memo.snippet });
}); });
const list = await Promise.all(requests); const list = await Promise.all(requests);
setReferencingMemos(list); setFetchedMemos((prev) => {
} else { const next = { ...prev };
setReferencingMemos([]); for (const memo of list) {
next[memo.name] = memo;
}
return next;
});
} }
})(); })();
}, [relations]); }, [relations]);
...@@ -89,7 +66,7 @@ const RelationList: FC<RelationListProps> = ({ relations, onRelationsChange, par ...@@ -89,7 +66,7 @@ const RelationList: FC<RelationListProps> = ({ relations, onRelationsChange, par
} }
}; };
if (referencingMemos.length === 0) { if (relations.length === 0) {
return null; return null;
} }
...@@ -97,18 +74,15 @@ const RelationList: FC<RelationListProps> = ({ relations, onRelationsChange, par ...@@ -97,18 +74,15 @@ const RelationList: FC<RelationListProps> = ({ relations, onRelationsChange, par
<div className="w-full rounded-lg border border-border bg-muted/20 overflow-hidden"> <div className="w-full rounded-lg border border-border bg-muted/20 overflow-hidden">
<div className="flex items-center gap-1.5 px-2 py-1.5 border-b border-border bg-muted/30"> <div className="flex items-center gap-1.5 px-2 py-1.5 border-b border-border bg-muted/30">
<LinkIcon className="w-3.5 h-3.5 text-muted-foreground" /> <LinkIcon className="w-3.5 h-3.5 text-muted-foreground" />
<span className="text-xs font-medium text-muted-foreground">Relations ({referencingMemos.length})</span> <span className="text-xs font-medium text-muted-foreground">Relations ({relations.length})</span>
</div> </div>
<div className="p-1 sm:p-1.5 flex flex-col gap-0.5"> <div className="p-1 sm:p-1.5 flex flex-col gap-0.5">
{referencingMemos.map((memo) => ( {relations.map((relation) => {
<RelationItemCard const relatedMemo = relation.relatedMemo!;
key={memo.name} const memo = relatedMemo.snippet ? relatedMemo : fetchedMemos[relatedMemo.name] || relatedMemo;
memo={create(MemoRelation_MemoSchema, { name: memo.name, snippet: memo.snippet })} return <RelationItemCard key={memo.name} memo={memo} onRemove={() => handleDeleteRelation(memo.name)} parentPage={parentPage} />;
onRemove={() => handleDeleteRelation(memo.name)} })}
parentPage={parentPage}
/>
))}
</div> </div>
</div> </div>
); );
......
...@@ -30,9 +30,9 @@ const SectionHeader = ({ icon: Icon, title, count, tabs }: SectionHeaderProps) = ...@@ -30,9 +30,9 @@ const SectionHeader = ({ icon: Icon, title, count, tabs }: SectionHeaderProps) =
tab.active ? "text-foreground" : "text-muted-foreground hover:text-foreground", tab.active ? "text-foreground" : "text-muted-foreground hover:text-foreground",
)} )}
> >
{tab.label}({tab.count}) {tab.label} ({tab.count})
</button> </button>
{idx < tabs.length - 1 && <span className="text-muted-foreground/50">/</span>} {idx < tabs.length - 1 && <span className="text-muted-foreground/40 font-mono text-xs">/</span>}
</div> </div>
))} ))}
</div> </div>
......
...@@ -21,7 +21,7 @@ const StatisticsView = (props: Props) => { ...@@ -21,7 +21,7 @@ const StatisticsView = (props: Props) => {
}, [activityStats]); }, [activityStats]);
return ( return (
<div className="group w-full mt-2 flex flex-col gap-1 text-muted-foreground animate-fade-in"> <div className="group w-full mt-2 flex flex-col text-muted-foreground animate-fade-in">
<MonthNavigator visibleMonth={visibleMonthString} onMonthChange={setVisibleMonthString} activityStats={activityStats} /> <MonthNavigator visibleMonth={visibleMonthString} onMonthChange={setVisibleMonthString} activityStats={activityStats} />
<div className="w-full animate-scale-in"> <div className="w-full animate-scale-in">
......
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