diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index e06bcfb..a85cf3e 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -1,5 +1,5 @@ name: "Nextjs build test" -on: [push, pull_request] +on: [push] jobs: build_test: runs-on: ubuntu-latest diff --git a/app/(app)/app/_components/edit/main.tsx b/app/(app)/app/_components/edit/main.tsx index eee12e3..35fef77 100644 --- a/app/(app)/app/_components/edit/main.tsx +++ b/app/(app)/app/_components/edit/main.tsx @@ -3,110 +3,28 @@ import { customSession } from "@/@types/customSession"; import { fileType } from "@/@types/fileType"; import { useSession } from "next-auth/react"; -import React, { useEffect, useState } from "react"; +import React from "react"; import EditMenu from "./EditMenu"; -import { - getFileContent, - getFileInfo, - updateFileInfo, - uploadFile, -} from "@/googledrive"; import { useHotkeys } from "react-hotkeys-hook"; import { useDocumentTitle } from "@uidotdev/usehooks"; import EditHeader from "./editHeader"; +import { useFile } from "../../../../../googledrive/useFile"; export default function FileMenu({ fileID }: { fileID: string }) { - const [title, setTitle] = useState(""); //拡張子付き - const [fileContent, setFileContent] = useState(); - const [loading, setLoading] = useState(true); - const [saving, setSaving] = useState(false); // 保存中はtrue - const [titleSaving, setTitleSaving] = useState(false); // タイトル保存中はtrue - const [fileContentSaving, setFileContentSaving] = useState(false); // ファイルコンテンツ保存中はtrue - const [serverFileContent, setServerFileContent] = useState< - fileType | undefined - >(undefined); - const [serverTitle, setServerTitle] = useState(undefined); //拡張子付き - const [shouldSaveTitle, setShouldSaveTitle] = useState(false); // タイトルを保存する必要があるときはtrue - const [shouldSaveFileContent, setShouldSaveFileContent] = useState(false); // ファイルコンテンツを保存する必要があるときはtrue const { data: session }: { data: customSession | null } = useSession() as unknown as { data: customSession }; const token = session?.accessToken; - useEffect(() => { - (async () => { - if (!token) return; - try { - const title = (await getFileInfo(token, fileID)).name; - if (!title) throw new Error("file is not found"); - const fileContent = - JSON.parse(await getFileContent(token, fileID)) || - ({ mode: null, content: [] } as fileType); - console.log("fileFound"); - setTitle(title); - setServerTitle(title); - setFileContent(fileContent); - setServerFileContent(fileContent); - } catch (e: any) { - // 空ファイルでは "SyntaxError: Unexpected end of JSON input" を吐くが問題なし - if (e.message == "Unexpected end of JSON input") { - setFileContent({ mode: null, content: [] } as fileType); - return; - } - console.error(e); - } finally { - setLoading(false); - } - })(); - }, [token, fileID]); - /** - * タイトルを更新します - * @returns - */ - async function saveFileInfo() { - if (!token) return; - if (title === "") return; - if (titleSaving) { - setShouldSaveTitle(true); - return; - } - setSaving(true); - setTitleSaving(true); - await updateFileInfo(token, fileID, { - name: title, - }); - if (shouldSaveTitle) saveFileContent(); - setSaving(false); - setTitleSaving(false); - setShouldSaveTitle(false); - } - /** - * ファイル本体を更新します - * @returns - */ - async function saveFileContent() { - if (!token) return; - if (fileContent?.content?.length === 0) return; - if (fileContentSaving) { - setShouldSaveFileContent(true); - return; - } - setSaving(true); - setFileContentSaving(true); - await uploadFile(token, fileID, JSON.stringify(fileContent)); - if (shouldSaveFileContent) saveFileContent(); - setSaving(false); - setFileContentSaving(false); - setShouldSaveFileContent(false); - } - useEffect(() => { - if (serverFileContent === fileContent) return; - saveFileContent(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fileID, fileContent, token]); - useEffect(() => { - if (serverTitle === title) return; - saveFileInfo(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fileID, title, token]); + const { + title, + setTitle, + fileContent, + setFileContent, + loading, + saving, + saveFileContent, + saveFileInfo, + } = useFile(token, fileID); + useHotkeys( "ctrl+s", () => { diff --git a/app/(app)/app/layout.tsx b/app/(app)/app/layout.tsx index e40aea6..082d1d8 100644 --- a/app/(app)/app/layout.tsx +++ b/app/(app)/app/layout.tsx @@ -5,13 +5,13 @@ import { Metadata } from "next"; export default function AppLayout({ children }: { children: ReactNode }) { return ( -
+ <>
{children}
-
+ ); } export const metadata: Metadata = { diff --git a/app/(app)/flashCard/HeaderRight.tsx b/app/(app)/flashCard/HeaderRight.tsx index 4bf9216..aae42f5 100644 --- a/app/(app)/flashCard/HeaderRight.tsx +++ b/app/(app)/flashCard/HeaderRight.tsx @@ -1,43 +1,43 @@ "use client"; -import { ReactNode, useState } from "react"; -import { CiSettings } from "react-icons/ci"; +import { useState } from "react"; import { CgMaximizeAlt } from "react-icons/cg"; -import { MdOutlineZoomInMap } from "react-icons/md"; +import { MdLogout, MdOutlineZoomInMap } from "react-icons/md"; -export default function HeaderRight() { +export default function HeaderRight({ + mode, + setMode, +}: { + mode: "home" | "cards" | "result"; + setMode: (mode: "home" | "cards" | "result") => void; +}) { const [isMaximized, setIsMaximized] = useState(false); document.addEventListener("fullscreenchange", () => { setIsMaximized((prev) => !prev); }); + return ( ); } diff --git a/app/(app)/flashCard/_card/card.tsx b/app/(app)/flashCard/_card/card.tsx index 90fa2e7..b72db0d 100644 --- a/app/(app)/flashCard/_card/card.tsx +++ b/app/(app)/flashCard/_card/card.tsx @@ -91,10 +91,7 @@ export default function FlashCard({ ); return ( -
+
{currentQuestion && ( )} -
); } diff --git a/app/(app)/flashCard/main.tsx b/app/(app)/flashCard/main.tsx index 4ab0893..97c9afd 100644 --- a/app/(app)/flashCard/main.tsx +++ b/app/(app)/flashCard/main.tsx @@ -103,52 +103,52 @@ export default function Card({ fileId }: { fileId: string }) { .join(".")} | フラッシュカード | VocabPhrase | chakkun1121` ); return ( -
-
+ <> +

VocabPhrase

{title.split(".").slice(0, -1).join(".")}

- +
- {loading ? ( -
- loading... -
- ) : ( - fileContent && ( - <> - {mode === "home" && ( - - )} - {mode === "cards" && ( - - )} - {mode === "result" && ( - - )} - - ) - )} -
+
+ {loading ? ( +
loading...
+ ) : ( + fileContent && ( + <> + {mode === "home" && ( + + )} + {mode === "cards" && ( + + )} + {mode === "result" && ( + + )} + + ) + )} +
+ ); } diff --git a/app/layout.tsx b/app/layout.tsx index 0e9e0e8..a30cabb 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -70,7 +70,7 @@ export default function RootLayout({ - {children} + {children} diff --git a/googledrive/useFile.tsx b/googledrive/useFile.tsx new file mode 100644 index 0000000..0ec3d7e --- /dev/null +++ b/googledrive/useFile.tsx @@ -0,0 +1,110 @@ +"use client"; +import { fileType } from "@/@types/fileType"; +import { useEffect, useState } from "react"; +import { + getFileContent, + getFileInfo, + updateFileInfo, + uploadFile, +} from "@/googledrive"; + +export function useFile(token: string, fileId: string) { + const [title, setTitle] = useState(""); //拡張子付き + const [fileContent, setFileContent] = useState(); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); // 保存中はtrue + const [titleSaving, setTitleSaving] = useState(false); // タイトル保存中はtrue + const [fileContentSaving, setFileContentSaving] = useState(false); // ファイルコンテンツ保存中はtrue + const [serverFileContent, setServerFileContent] = useState< + fileType | undefined + >(undefined); + const [serverTitle, setServerTitle] = useState(undefined); //拡張子付き + const [shouldSaveTitle, setShouldSaveTitle] = useState(false); // タイトルを保存する必要があるときはtrue + const [shouldSaveFileContent, setShouldSaveFileContent] = useState(false); // ファイルコンテンツを保存する必要があるときはtrue + useEffect(() => { + (async () => { + if (!token) return; + try { + const title = (await getFileInfo(token, fileId)).name; + if (!title) throw new Error("file is not found"); + const fileContent = + JSON.parse(await getFileContent(token, fileId)) || + ({ mode: null, content: [] } as fileType); + console.log("fileFound"); + setTitle(title); + setServerTitle(title); + setFileContent(fileContent); + setServerFileContent(fileContent); + } catch (e: any) { + // 空ファイルでは "SyntaxError: Unexpected end of JSON input" を吐くが問題なし + if (e.message == "Unexpected end of JSON input") { + setFileContent({ mode: null, content: [] } as fileType); + return; + } + console.error(e); + } finally { + setLoading(false); + } + })(); + }, [token, fileId]); + /** + * タイトルを更新します + * @returns + */ + async function saveFileInfo() { + if (!token) return; + if (title === "") return; + if (titleSaving) { + setShouldSaveTitle(true); + return; + } + setSaving(true); + setTitleSaving(true); + await updateFileInfo(token, fileId, { + name: title, + }); + if (shouldSaveTitle) saveFileContent(); + setSaving(false); + setTitleSaving(false); + setShouldSaveTitle(false); + } + /** + * ファイル本体を更新します + * @returns + */ + async function saveFileContent() { + if (!token) return; + if (fileContent?.content?.length === 0) return; + if (fileContentSaving) { + setShouldSaveFileContent(true); + return; + } + setSaving(true); + setFileContentSaving(true); + await uploadFile(token, fileId, JSON.stringify(fileContent)); + if (shouldSaveFileContent) saveFileContent(); + setSaving(false); + setFileContentSaving(false); + setShouldSaveFileContent(false); + } + useEffect(() => { + if (serverFileContent === fileContent) return; + saveFileContent(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [fileId, fileContent, token]); + useEffect(() => { + if (serverTitle === title) return; + saveFileInfo(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [fileId, title, token]); + return { + title, + setTitle, + fileContent, + setFileContent, + loading, + saving, + saveFileContent, + saveFileInfo, + }; +}