Skip to content

Commit

Permalink
breadcrumb 실제 데이터 반영 (+ 로딩표시 범위 조정, copy로직 개선) (#175)
Browse files Browse the repository at this point in the history
* feat: breadcrumb UI be와 연동

* fix: prompt 창 닫힐 때 uncaught error 처리

* feat: copy 로직에 polyfill 적용

* refactor: copy 함수가 promise를 반환하도록 수정
  • Loading branch information
hoqn authored Dec 2, 2024
1 parent a85aca7 commit 66b4afe
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 62 deletions.
19 changes: 10 additions & 9 deletions packages/frontend/src/api/space.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { API_V2_URL } from "./constants";
import { BreadcrumbItem } from "shared/types";

import { API_V1_URL, API_V2_URL } from "./constants";
import http from "./http";

type CreateSpaceRequestBody = {
Expand All @@ -19,12 +21,11 @@ export async function createSpace(body: CreateSpaceRequestBody) {
return response.data;
}

// export async function getSpace(spaceId: string) {
// // FIXME
// throw new Error("Not implemented");
// }
type GetBreadcrumbResponseBody = BreadcrumbItem[];

// export async function createSubspace() {
// // FIXME
// throw new Error("Not implemented");
// }
export async function getBreadcrumbOfSpace(spaceUrlPath: string) {
const response = await http.get<GetBreadcrumbResponseBody>(
`${API_V1_URL}/space/breadcrumb/${spaceUrlPath}`,
);
return response.data;
}
39 changes: 19 additions & 20 deletions packages/frontend/src/components/SpaceBreadcrumb.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Link } from "react-router-dom";

import { BreadcrumbItem as BreadcrumbItemData } from "shared/types";

import {
Breadcrumb,
BreadcrumbEllipsis,
Expand All @@ -16,27 +18,27 @@ import {
DropdownMenuTrigger,
} from "./ui/dropdown-menu";

type SpacePath = {
name: string;
urlPath: string;
};
function splitSpacePaths(
spacePaths: BreadcrumbItemData[],
itemCountToDisplay: number,
) {
const itemCount = spacePaths.length;

function splitSpacePaths(spacePaths: SpacePath[], itemCountToDisplay: number) {
// 처음 스페이스는 무조건 보여준다.
const firstSpacePath = spacePaths[0];
const firstSpacePath = itemCount > 1 ? spacePaths[0] : null;

// 중간 스페이스들은 ...으로 표시하고, 클릭 시 드롭다운 메뉴로 보여준다.
const hiddenSpacePaths = spacePaths.slice(1, -2);
const hiddenSpacePaths = spacePaths.slice(1, -itemCountToDisplay + 1);

// 마지막 (n-1)개 스페이스는 무조건 보여준다.
const lastItemCount = Math.min(spacePaths.length, itemCountToDisplay - 1);
const shownSpacePaths = spacePaths.slice(-lastItemCount);
const shownSpacePathCount = Math.min(itemCount, itemCountToDisplay) - 1;
const shownSpacePaths = spacePaths.slice(-shownSpacePathCount);

return [firstSpacePath, hiddenSpacePaths, shownSpacePaths] as const;
}

type HiddenItemsProps = {
spacePaths: SpacePath[];
spacePaths: BreadcrumbItemData[];
};

function HiddenItems({ spacePaths }: HiddenItemsProps) {
Expand All @@ -48,9 +50,9 @@ function HiddenItems({ spacePaths }: HiddenItemsProps) {
<BreadcrumbEllipsis />
</DropdownMenuTrigger>
<DropdownMenuContent>
{spacePaths.map(({ name, urlPath }) => (
<DropdownMenuItem key={urlPath} asChild>
<Link to={`/space/${urlPath}`}>{name}</Link>
{spacePaths.map(({ name, url }) => (
<DropdownMenuItem key={url} asChild>
<Link to={`/space/${url}`}>{name}</Link>
</DropdownMenuItem>
))}
</DropdownMenuContent>
Expand All @@ -62,7 +64,7 @@ function HiddenItems({ spacePaths }: HiddenItemsProps) {
}

type SpaceBreadcrumbItemProps = {
spacePath: SpacePath;
spacePath: BreadcrumbItemData;
isPage?: boolean;
};

Expand All @@ -81,10 +83,7 @@ function SpaceBreadcrumbItem({ spacePath, isPage }: SpaceBreadcrumbItemProps) {
<>
<BreadcrumbItem>
<BreadcrumbLink asChild>
<Link
className="truncate max-w-20"
to={`/space/${spacePath.urlPath}`}
>
<Link className="truncate max-w-20" to={`/space/${spacePath.url}`}>
{spacePath.name}
</Link>
</BreadcrumbLink>
Expand All @@ -95,7 +94,7 @@ function SpaceBreadcrumbItem({ spacePath, isPage }: SpaceBreadcrumbItemProps) {
}

type SpaceBreadcrumbProps = {
spacePaths: SpacePath[];
spacePaths: BreadcrumbItemData[];
itemCountToDisplay?: number;
};

Expand All @@ -118,7 +117,7 @@ export default function SpaceBreadcrumb({
)}
{shownSpacePaths.map((spacePath, index) => (
<SpaceBreadcrumbItem
key={spacePath.urlPath}
key={spacePath.url}
spacePath={spacePath}
isPage={index === shownSpacePaths.length - 1}
/>
Expand Down
8 changes: 4 additions & 4 deletions packages/frontend/src/components/SpaceShareAlertContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useRef, useState } from "react";

import { CheckIcon, ClipboardCopyIcon } from "lucide-react";

import { cn } from "@/lib/utils";
import { cn, copyToClipboard } from "@/lib/utils";

import { Button } from "./ui/button";

Expand All @@ -11,8 +11,8 @@ export default function SpaceShareAlertContent() {
const timeoutRef = useRef<number | null>(null);

const handleClickCopy = () => {
async function copyToClipboard() {
await navigator.clipboard.writeText(window.location.href);
async function copy() {
await copyToClipboard(window.location.href);
setHasCopied(true);

if (timeoutRef.current) {
Expand All @@ -24,7 +24,7 @@ export default function SpaceShareAlertContent() {
}, 2000);
}

copyToClipboard();
copy();
};

return (
Expand Down
42 changes: 23 additions & 19 deletions packages/frontend/src/components/space/SpacePageHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,44 @@
import { useEffect, useState } from "react";

import { BreadcrumbItem } from "shared/types";

import { getBreadcrumbOfSpace } from "@/api/space";
import { prompt } from "@/lib/prompt-dialog";

import SpaceBreadcrumb from "../SpaceBreadcrumb";
import SpaceShareAlertContent from "../SpaceShareAlertContent";
import SpaceUsersIndicator from "../SpaceUsersIndicator";
import { Button } from "../ui/button";

export default function SpacePageHeader() {
type SpacePageHeaderProps = {
spaceId: string;
};

export default function SpacePageHeader({ spaceId }: SpacePageHeaderProps) {
const [spacePaths, setSpacePaths] = useState<BreadcrumbItem[] | null>(null);

useEffect(() => {
async function fetchSpacePaths() {
const data = await getBreadcrumbOfSpace(spaceId);
setSpacePaths(data);
}

fetchSpacePaths();
}, [spaceId]);

return (
<header className="fixed z-20 top-0 inset-x-0 h-16 bg-background/50 backdrop-blur-lg">
<div className="container mx-auto px-6 h-full flex flex-row items-center justify-between">
<div className="flex-1">
<SpaceBreadcrumb
spacePaths={[
{ name: "하나", urlPath: "1" },
{ name: "셋", urlPath: "3" },
{ name: "넷", urlPath: "4" },
{ name: "다섯", urlPath: "5" },
{ name: "여섯", urlPath: "6" },
{ name: "일곱", urlPath: "7" },
{ name: "여덟", urlPath: "8" },
{ name: "아홉", urlPath: "9" },
{
name: "엄청 긴 제목을 가진 스페이스다아아아아아아아아아아아아아",
urlPath: "2",
},
{ name: "열", urlPath: "10" },
]}
/>
{spacePaths && <SpaceBreadcrumb spacePaths={spacePaths} />}
</div>
<div className="flex-grow-0 flex flex-row justify-center items-center">
<SpaceUsersIndicator />
<Button
className="ml-2"
variant="outline"
onClick={() => {
Promise.resolve(prompt("공유", <SpaceShareAlertContent />));
prompt("공유", <SpaceShareAlertContent />).catch(() => {});
}}
>
공유
Expand Down
37 changes: 37 additions & 0 deletions packages/frontend/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,40 @@ export function throttle<T extends (...args: unknown[]) => unknown>(
}, delay);
};
}

export async function copyToClipboard(text: string) {
if (navigator.clipboard) {
return navigator.clipboard.writeText(text);
}

// polyfill
const span = document.createElement("span");
span.textContent = text;

span.style.whiteSpace = "pre";
span.style.webkitUserSelect = "auto";
span.style.userSelect = "all";

document.body.appendChild(span);

const selection = window.getSelection();
const range = window.document.createRange();

selection?.removeAllRanges();
range.selectNode(span);
selection?.addRange(range);

let isSuccessful = false;

try {
isSuccessful = document.execCommand("copy");
} finally {
document.body.removeChild(span);
}

if (!isSuccessful) {
return Promise.reject(new Error("복사에 실패했습니다"));
}

return Promise.resolve();
}
18 changes: 8 additions & 10 deletions packages/frontend/src/pages/Space.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,17 @@ export default function SpacePage() {
);
}

if (status === "connecting") {
return (
<div className="flex justify-center items-center h-full">
<CircleDashedIcon className="animate-spin w-32 h-32 text-primary" />
</div>
);
}

return (
<YjsStoreProvider value={{ yDoc, yProvider, setYDoc, setYProvider }}>
<div className="w-full h-full" ref={containerRef}>
<SpaceView spaceId={spaceId} autofitTo={containerRef} />
<SpacePageHeader />
{status === "connecting" ? (
<div className="flex items-center justify-center w-full h-full">
<CircleDashedIcon className="animate-spin w-32 h-32 text-primary" />
</div>
) : (
<SpaceView spaceId={spaceId} autofitTo={containerRef} />
)}
<SpacePageHeader spaceId={spaceId} />
</div>
</YjsStoreProvider>
);
Expand Down

0 comments on commit 66b4afe

Please sign in to comment.