Skip to content

Commit a561e4f

Browse files
authored
Merge pull request #87 from ut-code/improve-loading
ページ読み込みの改善
2 parents c80d9e2 + b5182ad commit a561e4f

File tree

10 files changed

+297
-108
lines changed

10 files changed

+297
-108
lines changed

app/[docs_id]/chatHistory.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
"use client";
22

3-
import { ChatWithMessages } from "@/lib/chatHistory";
3+
import { ChatWithMessages, getChat } from "@/lib/chatHistory";
44
import {
55
createContext,
66
ReactNode,
77
useContext,
88
useEffect,
99
useState,
1010
} from "react";
11+
import useSWR from "swr";
1112

1213
export interface IChatHistoryContext {
1314
chatHistories: ChatWithMessages[];
@@ -27,19 +28,38 @@ export function useChatHistoryContext() {
2728

2829
export function ChatHistoryProvider({
2930
children,
31+
docs_id,
3032
initialChatHistories,
3133
}: {
3234
children: ReactNode;
35+
docs_id: string;
3336
initialChatHistories: ChatWithMessages[];
3437
}) {
3538
const [chatHistories, setChatHistories] =
3639
useState<ChatWithMessages[]>(initialChatHistories);
40+
// 最初はSSRで取得したinitialChatHistoriesを使用(キャッシュされているので古い可能性がある)
3741
useEffect(() => {
3842
setChatHistories(initialChatHistories);
3943
}, [initialChatHistories]);
44+
// その後、クライアント側で最新のchatHistoriesを改めて取得して更新する
45+
const { data: fetchedChatHistories } = useSWR<ChatWithMessages[]>(
46+
docs_id,
47+
getChat,
48+
{
49+
// リクエストは古くても構わないので1回でいい
50+
revalidateIfStale: false,
51+
revalidateOnFocus: false,
52+
revalidateOnReconnect: false,
53+
}
54+
);
55+
useEffect(() => {
56+
if (fetchedChatHistories) {
57+
setChatHistories(fetchedChatHistories);
58+
}
59+
}, [fetchedChatHistories]);
4060

61+
// チャットを更新した際にはクライアントサイドでchatHistoryに反映する
4162
const addChat = (chat: ChatWithMessages) => {
42-
// サーバー側で追加された新しいchatをクライアント側にも反映する
4363
setChatHistories([...chatHistories, chat]);
4464
};
4565

app/[docs_id]/dynamicMdContext.tsx

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

app/[docs_id]/loading.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export default function Loading() {
2+
return (
3+
<div className="p-4 mx-auto w-full max-w-200">
4+
<div className="skeleton h-8 w-3/4 my-4">{/* heading1 */}</div>
5+
<div className="skeleton h-20 w-full my-2">{/* <p> */}</div>
6+
<div className="skeleton h-7 w-2/4 mt-4 mb-3">{/* heading2 */}</div>
7+
<div className="skeleton h-40 w-full my-2">{/* <p> */}</div>
8+
<div className="skeleton h-7 w-2/4 mt-4 mb-3">{/* heading2 */}</div>
9+
<div className="skeleton h-40 w-full my-2">{/* <p> */}</div>
10+
</div>
11+
);
12+
}

app/[docs_id]/markdown.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import Markdown, { Components } from "react-markdown";
22
import remarkGfm from "remark-gfm";
33
import SyntaxHighlighter from "react-syntax-highlighter";
4-
import { type AceLang, EditorComponent, getAceLang } from "../terminal/editor";
4+
import { EditorComponent, getAceLang } from "../terminal/editor";
55
import { ExecFile } from "../terminal/exec";
66
import { useChangeTheme } from "./themeToggle";
77
import {
88
tomorrow,
99
atomOneDark,
1010
} from "react-syntax-highlighter/dist/esm/styles/hljs";
1111
import { ReactNode } from "react";
12-
import { getRuntimeLang, RuntimeLang } from "@/terminal/runtime";
12+
import { getRuntimeLang } from "@/terminal/runtime";
1313
import { ReplTerminal } from "@/terminal/repl";
1414

1515
export function StyledMarkdown({ content }: { content: string }) {

app/[docs_id]/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { MarkdownSection, splitMarkdown } from "./splitMarkdown";
66
import pyodideLock from "pyodide/pyodide-lock.json";
77
import { PageContent } from "./pageContent";
88
import { ChatHistoryProvider } from "./chatHistory";
9-
import { getChat } from "@/lib/chatHistory";
9+
import { getChatFromCache } from "@/lib/chatHistory";
1010

1111
export default async function Page({
1212
params,
@@ -50,10 +50,10 @@ export default async function Page({
5050
splitMarkdown(text)
5151
);
5252

53-
const initialChatHistories = getChat(docs_id);
53+
const initialChatHistories = getChatFromCache(docs_id);
5454

5555
return (
56-
<ChatHistoryProvider initialChatHistories={await initialChatHistories}>
56+
<ChatHistoryProvider initialChatHistories={await initialChatHistories} docs_id={docs_id}>
5757
<PageContent
5858
documentContent={await mdContent}
5959
splitMdContent={await splitMdContent}

app/[docs_id]/pageContent.tsx

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { MarkdownSection } from "./splitMarkdown";
55
import { ChatForm } from "./chatForm";
66
import { Heading, StyledMarkdown } from "./markdown";
77
import { useChatHistoryContext } from "./chatHistory";
8-
import { useSidebarMdContext } from "./dynamicMdContext";
8+
import { useSidebarMdContext } from "../sidebar";
99
import clsx from "clsx";
1010

1111
// MarkdownSectionに追加で、ユーザーが今そのセクションを読んでいるかどうか、などの動的な情報を持たせる
@@ -21,7 +21,7 @@ interface PageContentProps {
2121
}
2222
export function PageContent(props: PageContentProps) {
2323
const { setSidebarMdContent } = useSidebarMdContext();
24-
24+
2525
// SSR用のローカルstate
2626
const [dynamicMdContent, setDynamicMdContent] = useState<
2727
DynamicMarkdownSection[]
@@ -41,12 +41,7 @@ export function PageContent(props: PageContentProps) {
4141
sectionId: `${props.docs_id}-${i}`,
4242
}));
4343
setDynamicMdContent(newContent);
44-
setSidebarMdContent(newContent);
45-
46-
// クリーンアップ:コンポーネントがアンマウントされたらcontextをクリア
47-
return () => {
48-
setSidebarMdContent([]);
49-
};
44+
setSidebarMdContent(props.docs_id, newContent);
5045
}, [props.splitMdContent, props.docs_id, setSidebarMdContent]);
5146

5247
const sectionRefs = useRef<Array<HTMLDivElement | null>>([]);
@@ -57,28 +52,31 @@ export function PageContent(props: PageContentProps) {
5752

5853
useEffect(() => {
5954
const handleScroll = () => {
60-
const updateContent = (prevDynamicMdContent: DynamicMarkdownSection[]) => {
55+
const updateContent = (
56+
prevDynamicMdContent: DynamicMarkdownSection[]
57+
) => {
6158
const dynMdContent = prevDynamicMdContent.slice(); // Reactの変更検知のために新しい配列を作成
6259
for (let i = 0; i < sectionRefs.current.length; i++) {
6360
if (sectionRefs.current.at(i) && dynMdContent.at(i)) {
6461
const rect = sectionRefs.current.at(i)!.getBoundingClientRect();
6562
dynMdContent.at(i)!.inView =
66-
rect.top < window.innerHeight && rect.bottom >= 0;
63+
rect.top < window.innerHeight * 0.9 &&
64+
rect.bottom >= window.innerHeight * 0.1;
6765
}
6866
}
6967
return dynMdContent;
7068
};
71-
69+
7270
// ローカルstateとcontextの両方を更新
7371
setDynamicMdContent(updateContent);
74-
setSidebarMdContent(updateContent);
72+
setSidebarMdContent(props.docs_id, updateContent);
7573
};
7674
window.addEventListener("scroll", handleScroll);
7775
handleScroll();
7876
return () => {
7977
window.removeEventListener("scroll", handleScroll);
8078
};
81-
}, [setSidebarMdContent]);
79+
}, [setSidebarMdContent, props.docs_id]);
8280

8381
const [isFormVisible, setIsFormVisible] = useState(false);
8482

@@ -107,8 +105,9 @@ export function PageContent(props: PageContentProps) {
107105
</div>
108106
<div key={`${index}-chat`}>
109107
{/* 右側に表示するチャット履歴欄 */}
110-
{chatHistories.filter((c) => c.sectionId === section.sectionId).map(
111-
({chatId, messages}) => (
108+
{chatHistories
109+
.filter((c) => c.sectionId === section.sectionId)
110+
.map(({ chatId, messages }) => (
112111
<div
113112
key={chatId}
114113
className="max-w-xs mb-2 p-2 text-sm border border-base-content/10 rounded-sm shadow-sm bg-base-100"
@@ -136,8 +135,7 @@ export function PageContent(props: PageContentProps) {
136135
))}
137136
</div>
138137
</div>
139-
)
140-
)}
138+
))}
141139
</div>
142140
</>
143141
))}

app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Sidebar } from "./sidebar";
77
import { ReactNode } from "react";
88
import { EmbedContextProvider } from "./terminal/embedContext";
99
import { AutoAnonymousLogin } from "./accountMenu";
10-
import { SidebarMdProvider } from "./[docs_id]/dynamicMdContext";
10+
import { SidebarMdProvider } from "./sidebar";
1111
import { RuntimeProvider } from "./terminal/runtime";
1212

1313
export const metadata: Metadata = {

app/lib/cache.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const cacheData: Map<string, ArrayBuffer> = new Map();
2+
/**
3+
* nodejsにcache apiがないので、web標準のcache APIに相当するものの自前実装
4+
*/
5+
export const inMemoryCache = {
6+
async put(key: string, response: Response): Promise<void> {
7+
const arrayBuffer = await response.arrayBuffer();
8+
cacheData.set(key, arrayBuffer);
9+
},
10+
async match(key: string): Promise<Response | undefined> {
11+
const arrayBuffer = cacheData.get(key);
12+
if (arrayBuffer) {
13+
return new Response(arrayBuffer);
14+
}
15+
return undefined;
16+
},
17+
async delete(key: string): Promise<boolean> {
18+
return cacheData.delete(key);
19+
},
20+
} as const;

0 commit comments

Comments
 (0)