Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/client/components/Popup/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ const Popup: React.FC<PopupProps> = ({
}

div.classList.remove("invisible");
div.classList.add("shadow-lg");
div.classList.add("border");
div.classList.add("border-gray-200");

setTimeout(() => {
subscriptionRef.current = createEventListener(
Expand All @@ -75,6 +78,9 @@ const Popup: React.FC<PopupProps> = ({
});
} else {
div.classList.add("invisible");
div.classList.remove("shadow-lg");
div.classList.remove("border");
div.classList.remove("border-gray-200");
}

return () => {
Expand Down
63 changes: 59 additions & 4 deletions src/client/page/index/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useHistory } from "react-router-dom";
import { useRecoilState } from "recoil";
import ElectronWebtoonAppBar from "../../components/appbar";
import Popup from "@components/Popup";
import { useMessage } from "@components/useMessage";
import styles from "./index.css";
import { useRecoilValueMemo } from "../../utils";

Expand Down Expand Up @@ -53,11 +54,28 @@ function getCardGridStyle(width: number, height: number) {

function IndexPage() {
const [contextComicId, setContextComicId] = useState<string | null>(null);
const [archivePath, setArchivePath] = useState<string>("");
const [searchKey, setSearchKey] = useState("");
const comicList = useRecoilValueMemo(selector.comicList);
const refreshComicList = useRecoilRefresher_UNSTABLE(selector.comicList);
const history = useHistory();
const [_, setNextOpenComicInfo] = useRecoilState(selector.nextOpenComicInfo);
const { pushMessage } = useMessage();

useEffect(() => {
let mounted = true;
async function loadArchivePath() {
const ipc = await getIPC();
const value = await ipc.get("archivePath");
if (mounted && value) {
setArchivePath(value as string);
}
}
loadArchivePath();
return () => {
mounted = false;
};
}, []);

const onContextMenu = useCallback((e) => {
e.preventDefault();
Expand All @@ -77,7 +95,24 @@ function IndexPage() {
}

setContextComicId(null);
}, [contextComicId]);
}, [contextComicId, refreshComicList]);

const onArchiveComic = useCallback(async () => {
const ipc = await getIPC();
if (contextComicId) {
try {
await ipc.archiveComic(contextComicId);
refreshComicList();
} catch (error) {
console.error("Archive error:", error);
const errorMsg =
(error as any)?.message || "归档失败,请检查路径和权限";
pushMessage(errorMsg, 2000);
}
}

setContextComicId(null);
}, [contextComicId, refreshComicList, pushMessage]);

const onClickItem = useCallback((e) => {
if (e.currentTarget.dataset.cover) {
Expand Down Expand Up @@ -124,15 +159,35 @@ function IndexPage() {
visibleChange={() => setContextComicId(null)}
visible={contextComicId === comic.id}
tooltip={
<div className="flex bg-white p-5 cursor-pointer text-rose-400 transition-all whitespace-nowrap">
<div className="flex flex-col bg-white divide-y divide-gray-100 cursor-pointer shadow-md rounded-md overflow-hidden min-w-[120px]">
<div
onClick={onDeleteComic}
className="p-2 hover:bg-gray-100/10"
className="px-4 py-2.5 text-sm text-red-600 hover:bg-red-50 transition-colors font-medium"
>
删除本漫画
</div>
<div
className="p-2 ml-2 text-black hover:bg-gray-100/10"
onClick={
archivePath
? onArchiveComic
: () =>
pushMessage("请先在设置页面配置归档路径", 2000)
}
className={`px-4 py-2.5 text-sm font-medium transition-colors ${
archivePath
? "text-orange-600 hover:bg-orange-50 cursor-pointer"
: "text-gray-400 cursor-not-allowed opacity-60"
}`}
title={
archivePath
? "归档此漫画"
: "请先在设置页面配置归档路径"
}
>
归档
</div>
<div
className="px-4 py-2.5 text-sm text-gray-600 hover:bg-gray-50 transition-colors font-medium"
onClick={() => setContextComicId(null)}
>
取消
Expand Down
100 changes: 88 additions & 12 deletions src/client/page/settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,27 @@ import { useMessage } from "@components/useMessage";

// 设置页面
export default function Settings() {
const [path, setPath] = useState<string>("");
const [decompressPath, setDecompressPath] = useState<string>("");
const [archivePath, setArchivePath] = useState<string>("");
const [saving, setSaving] = useState<boolean>(false);
const [savingArchive, setSavingArchive] = useState<boolean>(false);
const [resetting, setResetting] = useState<boolean>(false);
const [resettingArchive, setResettingArchive] = useState<boolean>(false);
const { pushMessage } = useMessage();

useEffect(() => {
let mounted = true;
async function load() {
const ipc = await getIPC();
const value = await ipc.get("decompressPath");
if (mounted && value) {
setPath(value as string);
const decompressValue = await ipc.get("decompressPath");
const archiveValue = await ipc.get("archivePath");
if (mounted) {
if (decompressValue) {
setDecompressPath(decompressValue as string);
}
if (archiveValue) {
setArchivePath(archiveValue as string);
}
}
}
load();
Expand All @@ -29,22 +38,43 @@ export default function Settings() {
const ipc = await getIPC();
const result = await ipc.takeDirectory();
if (!result.canceled && result.filePaths && result.filePaths[0]) {
setPath(result.filePaths[0]);
setDecompressPath(result.filePaths[0]);
}
}, []);

const onChooseArchive = useCallback(async () => {
const ipc = await getIPC();
const result = await ipc.takeDirectory();
if (!result.canceled && result.filePaths && result.filePaths[0]) {
setArchivePath(result.filePaths[0]);
}
}, []);

const onSave = useCallback(async () => {
setSaving(true);
try {
const ipc = await getIPC();
await ipc.set("decompressPath", path);
await ipc.set("decompressPath", decompressPath);
pushMessage("保存成功", 1000);
} catch {
pushMessage("保存失败", 1000);
} finally {
setSaving(false);
}
}, [path, pushMessage]);
}, [decompressPath, pushMessage]);

const onSaveArchive = useCallback(async () => {
setSavingArchive(true);
try {
const ipc = await getIPC();
await ipc.set("archivePath", archivePath);
pushMessage("保存成功", 1000);
} catch {
pushMessage("保存失败", 1000);
} finally {
setSavingArchive(false);
}
}, [archivePath, pushMessage]);

const onReset = useCallback(async () => {
setResetting(true);
Expand All @@ -53,13 +83,25 @@ export default function Settings() {
await ipc.reset("decompressPath");
const value = await ipc.get("decompressPath");
if (value) {
setPath(value as string);
setDecompressPath(value as string);
}
} finally {
setResetting(false);
}
}, []);

const onResetArchive = useCallback(async () => {
setResettingArchive(true);
try {
const ipc = await getIPC();
await ipc.reset("archivePath");
const value = await ipc.get("archivePath");
setArchivePath(value as string);
} finally {
setResettingArchive(false);
}
}, []);

return (
<div className="pt-[70px]">
<ElectronWebtoonAppBar hasSearch={false}></ElectronWebtoonAppBar>
Expand All @@ -71,8 +113,8 @@ export default function Settings() {
<div className="flex items-center">
<input
className="flex-1 border rounded p-2 mr-2"
value={path}
onChange={(e) => setPath(e.target.value)}
value={decompressPath}
onChange={(e) => setDecompressPath(e.target.value)}
placeholder="请选择或输入解压缩目录"
/>
<button
Expand All @@ -96,10 +138,44 @@ export default function Settings() {
保存
</button>
</div>
<div className="text-sm text-gray-500 mt-2">
该路径用于解压缩临时文件,建议选择一个有足够空间的文件夹。设置后,新添加的压缩包将解压缩到该位置。
</div>
</div>

<div className="text-sm text-gray-500">
该路径用于解压缩临时文件,建议选择一个有足够空间的文件夹。设置后,新添加的压缩包将解压缩到该位置。
<div className="mb-4">
<label className="block mb-2">归档路径</label>
<div className="flex items-center">
<input
className="flex-1 border rounded p-2 mr-2"
value={archivePath}
onChange={(e) => setArchivePath(e.target.value)}
placeholder="请选择或输入归档目录"
/>
<button
className="bg-gray-200 px-3 py-2 rounded mr-2"
onClick={onChooseArchive}
>
选择...
</button>
<button
className="bg-gray-200 px-3 py-2 rounded mr-2"
onClick={onResetArchive}
disabled={resettingArchive}
>
{resettingArchive ? "重置中..." : "重置"}
</button>
<button
className="bg-sky-500 text-white px-3 py-2 rounded"
onClick={onSaveArchive}
disabled={savingArchive}
>
保存
</button>
</div>
<div className="text-sm text-gray-500 mt-2">
当右键归档漫画时,该漫画源文件会移到此位置,并从当前漫画库删除。
</div>
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "electron-webtoon",
"productName": "electron-webtoon",
"version": "2.4.5",
"version": "2.4.6",
"description": "ElectronWebtoon 漫画阅读器",
"main": "./main.prod.js",
"author": {
Expand Down
Loading