Skip to content

Commit

Permalink
Merge pull request #35 from chakkun1121/feature/make-right-click-menu
Browse files Browse the repository at this point in the history
close #32
  • Loading branch information
chakkun1121 authored Jan 30, 2024
2 parents 4f25442 + d082e09 commit 034e531
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 42 deletions.
7 changes: 6 additions & 1 deletion @types/settings.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
export type serverSettingsType = {};
export type serverSettingsType = {
customSearch: {
name: string;
url: string; //検索キーワードが%sに入る
}[];
};
export type localSettings = {};
174 changes: 174 additions & 0 deletions app/(app)/_components/rightClick/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
"use client";

import { usePathname, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
type menuInfo = {
[key: string]: menuInfoOptions[];
};
type menuInfoOptions = {
name: {
ja: string;
en: string;
};
onclick?: (text: string) => void;
options?: menuInfoOptions[];
when?: {
onSelected?: boolean;
path?: string[];
};
};
export default function RightClick() {
const [isShow, setIsShow] = useState(false);
const [x, setX] = useState(0);
const [y, setY] = useState(0);
const pathName = usePathname();
const searchParams = useSearchParams();
const menu: menuInfoOptions[] = [
{
name: {
ja: "コピー",
en: "Copy",
},
onclick: (text: string) => {
navigator.clipboard.writeText(text);
},
when: { onSelected: true },
},
{
name: {
ja: "読み上げ",
en: "speech",
},
onclick: (text: string) => {
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = "en-US";
speechSynthesis.speak(utterance);
},
when: { onSelected: true },
},
{
name: {
ja: "で検索",
en: "Search with",
},
options: [
{
name: {
ja: "Google",
en: "Google",
},
onclick: (text: string) => {
window.open(`https://www.google.com/search?q=${text}`, "_blank");
},
},
{
name: {
ja: "Google翻訳",
en: "Google Translate",
},
onclick: (text: string) => {
window.open(
`https://translate.google.com/?sl=auto&tl=auto&text=${text}&op=translate`,
"_blank"
);
},
},
// {
// name: {
// ja: "+追加",
// en: "+add",
// },
// onclick: () => {
// window.open("/settings#dictionaries", "_blank");
// },
// },
],
when: { onSelected: true },
},
{
name: {
ja: "印刷",
en: "print",
},
onclick: () => {
window.open(`/print?fileId=${searchParams.get("fileId")}`, "_blank");
},
},
{
name: {
ja: "ヘルプ",
en: "Help",
},
onclick: () => {
window.open("/help", "_blank");
},
},
// {
// name: {
// ja: "設定",
// en: "settings",
// },
// onclick: () => {
// window.open("/settings", "_blank");
// },
// },
];

useEffect(() => {
window.addEventListener("contextmenu", (e) => {
e.preventDefault();
setX(e.clientX);
setY(e.clientY);
setIsShow(true);
});
window.addEventListener("click", () => setIsShow(false));
});
const onSelected = !!window.getSelection()?.toString();
const currentMenu = menu.filter((e) => {
if (e.when?.onSelected && !onSelected) return false;
if (e.when?.path && !e.when.path.includes(pathName)) return false;
return true;
});
return (
<>
{isShow && (
<>
<div
className="absolute top-0 left-0 w-64 bg-gray-100 rounded shadow-lg z-50 select-none"
style={{
top: y,
left: x,
}}
>
{currentMenu.map((e) => (
<MenuButton menuInfo={e} key={e.name.en} x={x} />
))}
</div>
</>
)}
</>
);
}
function MenuButton({ menuInfo, x }: { menuInfo: menuInfoOptions; x: number }) {
return menuInfo.options ? (
<>
<p className="w-full h-12 px-4 text-left hover:bg-gray-200 text-button flex items-center group after:content-['>'] justify-between after:text-gray-500">
{menuInfo.name["ja"]}
<div className="absolute w-64 bg-gray-100 rounded shadow-lg z-50 hidden group-hover:block left-64">
{menuInfo.options.map((e) => (
<MenuButton menuInfo={e} key={e.name.en} x={x + 256} />
))}
</div>
</p>
</>
) : (
<button
className="w-full h-12 px-4 text-left hover:bg-gray-200"
onClick={() => {
menuInfo.onclick?.(window.getSelection()?.toString() || "");
}}
>
{menuInfo.name["ja"]}
</button>
);
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
"use client";

import { customSession } from "@/@types/customSession";
import { localSettings, serverSettingsType } from "@/@types/settings";
import { serverSettingsType } from "@/@types/settings";
import { listFiles, getFileContent, uploadFile } from "@/googledrive";
import { useSession } from "next-auth/react";
import { useEffect, useState } from "react";
import { atom, useRecoilState } from "recoil";
import { recoilPersist } from "recoil-persist";

export default function Settings() {
const [serverSettings, setServerSettings] = useState<serverSettingsType>({});
const [localSettings, setLocalSettings] = useRecoilState(localSettingsState);
export function useServerSettings(token: string) {
const [settingsFileID, setSettingsFileID] = useState<string>();
const { data: session }: { data: customSession | null } =
useSession() as unknown as { data: customSession };
const token = session?.accessToken;

const [serverSettings, setServerSettings] = useState<
serverSettingsType | undefined
>();
const [isLoading, setIsLoading] = useState(true);
const [isSaving, setIsSaving] = useState(false);
useEffect(() => {
(async () => {
if (!token) return;
setIsLoading(true);
const settingsFile = await listFiles(
token,
"name='settings.json'",
Expand All @@ -32,11 +27,13 @@ export default function Settings() {
JSON.parse(await getFileContent(token, settingsFile.id)) || {}
);
}
setIsLoading(false);
})();
}, [token]);
useEffect(() => {
(async () => {
if (!token) return;
setIsSaving(true);
if (settingsFileID) {
uploadFile(token, settingsFileID, JSON.stringify(serverSettings));
} else {
Expand All @@ -56,18 +53,8 @@ export default function Settings() {
.then((r) => r.id)
);
}
setIsSaving(false);
})();
}, [serverSettings, settingsFileID, token]);
return <></>;
return { serverSettings, setServerSettings, isLoading, isSaving };
}

// recoil
const { persistAtom } = recoilPersist({
key: "settings",
storage: typeof window === "undefined" ? undefined : localStorage,
});
export const localSettingsState = atom<localSettings>({
key: "settings",
default: {},
effects_UNSTABLE: [persistAtom],
});
7 changes: 6 additions & 1 deletion app/(app)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ import { Metadata } from "next";
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
import { ReactNode } from "react";
import RightClick from "./_components/rightClick/main";

export default async function AppLayout({ children }: { children: ReactNode }) {
const session = await getServerSession();
if (!session) redirect("/login?redirectTo=/app");
return children as JSX.Element;
return (
<>
{children} <RightClick />
</>
);
}
export const metadata: Metadata = {
robots: "noindex",
Expand Down
28 changes: 28 additions & 0 deletions app/(app)/settings/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use client";

import { customSession } from "@/@types/customSession";
import { localSettings } from "@/@types/settings";
import { useSession } from "next-auth/react";
import { atom, useRecoilState } from "recoil";
import { recoilPersist } from "recoil-persist";
import { useServerSettings } from "../_library/settings/useServerSettings";

export default function Settings() {
const { data: session }: { data: customSession | null } =
useSession() as unknown as { data: customSession };
const token = session?.accessToken;
const { serverSettings, setServerSettings, isLoading, isSaving } =
useServerSettings(token);
if (isLoading) return <p className="text-center">loading...</p>;
return <div></div>;
}
// recoil
const { persistAtom } = recoilPersist({
key: "settings",
storage: typeof window === "undefined" ? undefined : localStorage,
});
export const localSettingsState = atom<localSettings>({
key: "settings",
default: {},
effects_UNSTABLE: [persistAtom],
});
11 changes: 11 additions & 0 deletions app/(app)/settings/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Metadata } from "next";
import Settings from "./main";

export default function Page() {
return <Settings />;
}
export const metadata: Metadata = {
title: "設定",
description: "VocabPhraseの設定ページです。",
robots: "noindex",
};
15 changes: 0 additions & 15 deletions app/(home)/settings/page.tsx

This file was deleted.

0 comments on commit 034e531

Please sign in to comment.