Skip to content

Commit 9dc6bd4

Browse files
committed
refactor: wordcloud 로직 수정 및 로그인 화면으로 가는 로직 수정
2 parents 77f64dd + e4ffa25 commit 9dc6bd4

9 files changed

Lines changed: 86 additions & 227 deletions

File tree

apps/www/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"react": "19.0.0-rc-66855b96-20241106",
1616
"react-dom": "19.0.0-rc-66855b96-20241106",
1717
"three": "^0.175.0",
18+
"wordcloud": "^1.2.3",
1819
"zustand": "^5.0.1"
1920
},
2021
"devDependencies": {
@@ -23,6 +24,7 @@
2324
"@types/node": "^20",
2425
"@types/react": "^18",
2526
"@types/react-dom": "^18",
26-
"@types/three": "^0.175.0"
27+
"@types/three": "^0.175.0",
28+
"@types/wordcloud": "^1.2.2"
2729
}
2830
}

apps/www/src/app/(with-header)/page.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import GetStart from "@/components/landing/get-start";
1+
import Link from "next/link";
2+
23
import SampleGraph from "@/components/landing/sample-graph";
34

45
export default function LandingPage() {
@@ -16,7 +17,26 @@ export default function LandingPage() {
1617
</p>
1718
</div>
1819
</div>
19-
<GetStart />
20+
<div className="hidden flex-1 space-y-7 md:block">
21+
<div className="space-y-2 text-end">
22+
<Link href="/bookmarks" className="text-2xl">
23+
시작페이지
24+
</Link>
25+
<p className="text-sm text-gray7">
26+
북마크 정보와 관리된 노드들을 볼 수 있는 페이지 입니다
27+
</p>
28+
</div>
29+
<div className="space-y-2 text-end">
30+
<Link
31+
className="text-2xl"
32+
href="https://chromewebstore.google.com/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=ko"
33+
target="_blank"
34+
>
35+
크롬 익스텐션 다운로드
36+
</Link>
37+
<p className="text-sm text-gray7">크롬 익스텐션을 다운 받을 수 있는 곳 입니다</p>
38+
</div>
39+
</div>
2040
</section>
2141
<SampleGraph />
2242
</main>

apps/www/src/app/(with-header)/trending/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { getTrendKeywords } from "@/service/keyword";
44
export default async function TrendPage() {
55
const { result } = await getTrendKeywords();
66

7-
const keywords: Keyword[] = result.mostUsedKeywordList
7+
const keywords: Keyword[] = result?.mostUsedKeywordList
88
.sort((a, b) => b.usedCnt - a.usedCnt)
99
.map((item, idx) => ({
1010
word: item.keywordName,
@@ -16,7 +16,7 @@ export default async function TrendPage() {
1616
<main className="max-w-with-header flex min-h-body flex-1 flex-col gap-2.5 px-10">
1717
<h1 className="text-3xl font-semibold text-white">트렌드 키워드</h1>
1818
<h2 className="mb-10 text-gray7">많이 검색되는 키워드를 살펴보세요.</h2>
19-
<WordCloud keywords={keywords} />
19+
<WordCloud keywords={keywords || []} />
2020
</main>
2121
);
2222
}

apps/www/src/components/landing/get-start/index.tsx

Lines changed: 0 additions & 41 deletions
This file was deleted.

apps/www/src/components/layout/header/index.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,8 @@ import Image from "next/image";
22
import Link from "next/link";
33

44
import logo from "@/assets/icons/logo.svg";
5-
import { getCookie } from "@/utils/cookies";
6-
7-
import LoginButton from "./login-button";
85

96
const Header = async () => {
10-
const token = await getCookie("accessToken");
11-
127
return (
138
<header className="flex justify-between px-10 py-6 text-white">
149
<Link href="/" className="flex items-center gap-2">
@@ -18,7 +13,6 @@ const Header = async () => {
1813
<div className="flex items-center gap-6">
1914
<Link href="/trending">Trending</Link>
2015
<Link href="/history">History</Link>
21-
<LoginButton token={token?.value || ""} />
2216
</div>
2317
</header>
2418
);

apps/www/src/components/layout/header/login-button.tsx

Lines changed: 0 additions & 30 deletions
This file was deleted.

apps/www/src/components/trending/word-cloud.tsx

Lines changed: 42 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -1,168 +1,65 @@
11
"use client";
22

3-
import { useEffect, useMemo, useRef, useState } from "react";
3+
import { useEffect, useRef, useState } from "react";
44
import Link from "next/link";
55

6+
import WordCloud2, { ListEntry } from "wordcloud";
7+
68
export interface Keyword {
79
word: string;
810
rank: number;
911
usedCnt: number;
1012
}
1113

12-
const minFont = 16;
13-
const maxFont = 48;
14-
const colors = ["#fff", "#A5B4FC", "#818CF8", "#6366F1", "#64748B"];
15-
16-
function getFontSize(rank: number) {
17-
return Math.max(maxFont - (rank - 1) * 6, minFont);
18-
}
19-
function getColor(rank: number) {
20-
return colors[rank - 1] || colors[colors.length - 1];
21-
}
14+
const colors = ["#6366F1", "#818CF8", "#A5B4FC", "#fff"];
2215

23-
interface PlacedKeyword {
24-
word: string;
25-
rank: number;
26-
usedCnt: number;
27-
left: number;
28-
top: number;
29-
fontSize: number;
30-
color: string;
31-
width: number;
32-
height: number;
33-
}
16+
export default function WordCloud({ keywords }: { keywords: Keyword[] }) {
17+
const canvasRef = useRef<HTMLCanvasElement>(null);
18+
const [width, setWidth] = useState(Math.min(Math.max(360, window.innerWidth - 620), 700));
3419

35-
function measureText(
36-
text: string,
37-
fontSize: number,
38-
fontWeight = 700
39-
): { width: number; height: number } {
40-
if (typeof window === "undefined") return { width: 100, height: fontSize };
41-
const canvas = document.createElement("canvas");
42-
const ctx = canvas.getContext("2d");
43-
if (!ctx) return { width: 100, height: fontSize };
44-
ctx.font = `${fontWeight} ${fontSize}px Pretendard, sans-serif`;
45-
const metrics = ctx.measureText(text);
46-
return { width: metrics.width, height: fontSize };
47-
}
20+
const height = 420;
4821

49-
function isOverlap(a: PlacedKeyword, b: PlacedKeyword) {
50-
return !(
51-
a.left + a.width < b.left ||
52-
a.left > b.left + b.width ||
53-
a.top + a.height < b.top ||
54-
a.top > b.top + b.height
55-
);
56-
}
57-
58-
function placeKeywords(keywords: Keyword[], width: number, height: number): PlacedKeyword[] {
59-
const placed: PlacedKeyword[] = [];
60-
const maxTries = 100;
61-
for (const k of keywords) {
62-
const fontSize = getFontSize(k.rank);
63-
const color = getColor(k.rank);
64-
const { width: textWidth, height: textHeight } = measureText(k.word, fontSize);
65-
let left = 0,
66-
top = 0,
67-
tries = 0;
68-
let overlap = false;
69-
do {
70-
const maxLeft = Math.max(width - textWidth, 0);
71-
const maxTop = Math.max(height - textHeight, 0);
72-
left = Math.random() * maxLeft;
73-
top = Math.random() * maxTop;
74-
overlap = placed.some((p) =>
75-
isOverlap(
76-
{
77-
...p,
78-
left,
79-
top,
80-
width: textWidth,
81-
height: textHeight,
82-
word: k.word,
83-
rank: k.rank,
84-
fontSize,
85-
color,
86-
usedCnt: k.usedCnt,
87-
} as PlacedKeyword,
88-
p
89-
)
90-
);
91-
tries++;
92-
} while (overlap && tries < maxTries);
93-
placed.push({
94-
word: k.word,
95-
rank: k.rank,
96-
usedCnt: k.usedCnt,
97-
left,
98-
top,
99-
fontSize,
100-
color,
101-
width: textWidth,
102-
height: textHeight,
22+
useEffect(() => {
23+
const cloudKeywords = keywords.map((k) => [k.word, k.usedCnt]) as ListEntry[];
24+
WordCloud2(canvasRef.current!, {
25+
list: cloudKeywords,
26+
shape: "circle",
27+
minRotation: 0,
28+
maxRotation: 0,
29+
shrinkToFit: true,
30+
minSize: 14,
31+
backgroundColor: "#00000000",
32+
weightFactor: (size) => size * 10,
33+
color: (word) => {
34+
const idx = cloudKeywords.findIndex((k) => k[0] === word);
35+
const colorIdx = idx > 3 ? 3 : idx;
36+
return colors[colorIdx];
37+
},
38+
click: (item) => {
39+
window.open(`https://www.google.com/search?q=${item[0]}`, "_blank");
40+
},
10341
});
104-
}
105-
return placed;
106-
}
107-
108-
export default function WordCloud({ keywords }: { keywords: Keyword[] }) {
109-
const [isClient, setIsClient] = useState(false);
110-
const [size, setSize] = useState({ width: 900, height: 420 });
111-
const containerRef = useRef<HTMLDivElement>(null);
11242

113-
useEffect(() => {
114-
setIsClient(true);
115-
if (!containerRef.current) return;
116-
const handleResize = () => {
117-
if (containerRef.current) {
118-
setSize({
119-
width: containerRef.current.offsetWidth,
120-
height: containerRef.current.offsetHeight,
121-
});
122-
}
43+
const onResize = () => {
44+
setWidth(Math.min(Math.max(360, window.innerWidth - 620), 700));
12345
};
124-
handleResize();
125-
if (typeof window !== "undefined" && "ResizeObserver" in window) {
126-
const observer = new window.ResizeObserver(handleResize);
127-
observer.observe(containerRef.current);
128-
return () => observer.disconnect();
129-
}
130-
}, []);
131-
132-
const placedKeywords = useMemo(() => {
133-
if (!isClient) return [];
134-
return placeKeywords(keywords, size.width, size.height);
135-
}, [isClient, keywords, size]);
136-
137-
if (!isClient) return null;
46+
window.addEventListener("resize", onResize);
47+
return () => {
48+
WordCloud2.stop();
49+
window.removeEventListener("resize", onResize);
50+
};
51+
}, [keywords, width]);
13852

13953
return (
140-
<div
141-
ref={containerRef}
142-
className="relative mx-auto flex h-full w-full flex-row-reverse rounded-lg bg-gray7/30 px-20 py-16"
143-
>
144-
{placedKeywords.map((k) => (
145-
<Link
146-
key={k.word}
147-
target="_blank"
148-
href={`https://www.google.com/search?q=${k.word}`}
149-
className="absolute flex cursor-pointer select-none items-center gap-1 whitespace-nowrap font-bold transition-transform duration-150"
150-
style={{
151-
left: k.left,
152-
top: k.top,
153-
fontSize: k.fontSize,
154-
color: k.color,
155-
textShadow: "0 2px 8px rgba(0,0,0,0.15)",
156-
}}
157-
>
158-
{k.word}
159-
</Link>
160-
))}
161-
<ul className="flex flex-col flex-wrap text-white">
162-
{placedKeywords.slice(0, 10).map((k) => (
54+
<div className="semi-md:flex-row semi-md:items-start semi-md:px-20 mx-auto flex h-full w-full flex-col items-center justify-between gap-5 rounded-lg bg-gray7/30 px-5 py-16">
55+
<div style={{ width, height }}>
56+
<canvas ref={canvasRef} width={width} height={height} />
57+
</div>
58+
<ul className="flex w-full max-w-80 flex-col flex-wrap text-white md:ml-8">
59+
{keywords.slice(0, 10).map((k) => (
16360
<li
16461
key={`trend-keyword-${k.word}`}
165-
className="flex w-80 items-center justify-between gap-5 border-b border-b-gray7 px-4 py-2.5"
62+
className="flex items-center justify-between gap-5 border-b border-b-gray7 px-4 py-2.5"
16663
>
16764
<Link
16865
target="_blank"

apps/www/tailwind.config.mts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ export default {
3232
...baseConfig.theme?.extend?.maxWidth,
3333
"with-header": "90rem",
3434
},
35+
screens: {
36+
"semi-md": "49.375rem",
37+
},
3538
},
3639
},
3740
} satisfies Config;

0 commit comments

Comments
 (0)