diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index aa7ff7f..262aefd 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1626,6 +1626,17 @@ dependencies = [ "objc_exception", ] +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + [[package]] name = "objc_exception" version = "0.1.2" @@ -2088,6 +2099,30 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +[[package]] +name = "rfd" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0149778bd99b6959285b0933288206090c50e2327f47a9c463bfdbf45c8823ea" +dependencies = [ + "block", + "dispatch", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "lazy_static", + "log", + "objc", + "objc-foundation", + "objc_id", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.37.0", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -2536,6 +2571,7 @@ dependencies = [ "rand 0.8.5", "raw-window-handle", "regex", + "rfd", "semver", "serde", "serde_json", @@ -3081,6 +3117,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.84" @@ -3110,6 +3158,16 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webkit2gtk" version = "0.18.2" @@ -3226,6 +3284,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647" +dependencies = [ + "windows_aarch64_msvc 0.37.0", + "windows_i686_gnu 0.37.0", + "windows_i686_msvc 0.37.0", + "windows_x86_64_gnu 0.37.0", + "windows_x86_64_msvc 0.37.0", +] + [[package]] name = "windows" version = "0.39.0" @@ -3326,6 +3397,12 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +[[package]] +name = "windows_aarch64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a" + [[package]] name = "windows_aarch64_msvc" version = "0.39.0" @@ -3338,6 +3415,12 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +[[package]] +name = "windows_i686_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1" + [[package]] name = "windows_i686_gnu" version = "0.39.0" @@ -3350,6 +3433,12 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +[[package]] +name = "windows_i686_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c" + [[package]] name = "windows_i686_msvc" version = "0.39.0" @@ -3362,6 +3451,12 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +[[package]] +name = "windows_x86_64_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d" + [[package]] name = "windows_x86_64_gnu" version = "0.39.0" @@ -3380,6 +3475,12 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +[[package]] +name = "windows_x86_64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d" + [[package]] name = "windows_x86_64_msvc" version = "0.39.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index a49a9e2..cbdd53a 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -14,7 +14,7 @@ tauri-build = { version = "1.2", features = [] } [dependencies] once_cell = "1.17.0" -tauri = { version = "1.2", features = ["shell-open"] } +tauri = { version = "1.2", features = ["dialog-open", "shell-open"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" chrono = { version = "0.4.23", features = ["serde"] } diff --git a/src-tauri/src/commands/files.rs b/src-tauri/src/commands/files.rs new file mode 100644 index 0000000..8a40a59 --- /dev/null +++ b/src-tauri/src/commands/files.rs @@ -0,0 +1,36 @@ +use anyhow::anyhow; +use serde::Serialize; +use walkdir::WalkDir; + +#[derive(Debug, Clone, Serialize)] +pub struct FileItem { + pub filename: String, + pub path: String, +} + +#[tauri::command] +pub fn scan_directory(target: String) -> Result, String> { + WalkDir::new(target) + .into_iter() + .filter_map(|f| f.ok()) + .filter(|f| f.path().is_file()) + .map(|f| { + Ok(FileItem { + filename: f + .path() + .file_name() + .ok_or(anyhow!("不能获取到文件名。"))? + .to_owned() + .into_string() + .unwrap(), + path: f + .path() + .clone() + .to_str() + .ok_or(anyhow!("不能获取到文件路径。"))? + .to_string(), + }) + }) + .collect::, anyhow::Error>>() + .map_err(|e| e.to_string()) +} diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index b70eeb8..e554353 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -1,5 +1,11 @@ use tauri::{App, AppHandle, Runtime, Window}; +mod files; + +pub mod prelude { + pub use super::files::*; +} + /// 用于持有应用实例,可存放不同的应用实例。 pub enum AppHold<'a, R: Runtime> { Instance(&'a App), diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 72c8e61..0769172 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -8,7 +8,7 @@ use tauri::Manager; fn main() { tauri::Builder::default() - .invoke_handler(tauri::generate_handler![]) + .invoke_handler(tauri::generate_handler![commands::prelude::scan_directory]) .setup(|app| { let main_window = app.get_window("main").unwrap(); #[cfg(debug_assertions)] diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 4304da7..0d94b9a 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -16,6 +16,14 @@ "shell": { "all": false, "open": true + }, + "dialog": { + "all": false, + "ask": false, + "confirm": false, + "message": false, + "open": true, + "save": false } }, "bundle": { diff --git a/src/MainLayout.tsx b/src/MainLayout.tsx index 934d7a6..603dbff 100644 --- a/src/MainLayout.tsx +++ b/src/MainLayout.tsx @@ -1,15 +1,17 @@ -import { Box, Group } from "@mantine/core"; -import { FC } from "react"; -import { Outlet } from "react-router-dom"; -import { NavMenu } from "./NavMenu"; +import { Group, Stack } from '@mantine/core'; +import { FC } from 'react'; +import { ComicView } from './components/ComicView'; +import { PicToolbar } from './components/PicToolbar'; +import { NavMenu } from './NavMenu'; export const MainLayout: FC = () => { return ( - - - + + + + ); }; diff --git a/src/NavMenu.tsx b/src/NavMenu.tsx index 7a771d9..13f22f6 100644 --- a/src/NavMenu.tsx +++ b/src/NavMenu.tsx @@ -1,32 +1,38 @@ -import { Stack, useMantineTheme } from "@mantine/core"; -import { ifElse, path, propEq } from "ramda"; -import { FC, useMemo } from "react"; +import { Stack, useMantineTheme } from '@mantine/core'; +import { ifElse, path, propEq } from 'ramda'; +import { FC, useMemo } from 'react'; +import { FileList } from './components/FileList'; +import { FileToolbar } from './components/FileTools'; const bgSelectFn = ifElse( - propEq("colorScheme", "dark"), - path(["colors", "cbg", 2]), - path(["colors", "cbg", 7]) + propEq('colorScheme', 'dark'), + path(['colors', 'cbg', 2]), + path(['colors', 'cbg', 7]) ); export const NavMenu: FC = () => { const theme = useMantineTheme(); - const normalColor = useMemo(() => path(["violet", 7])(theme.colors), [theme.colors]); - const activatedColor = useMemo(() => path(["violet", 3])(theme.colors), [theme.colors]); - const disabledColor = useMemo(() => path(["gray", 7])(theme.colors), [theme.colors]); + const normalColor = useMemo(() => path(['violet', 7])(theme.colors), [theme.colors]); + const activatedColor = useMemo(() => path(['violet', 3])(theme.colors), [theme.colors]); + const disabledColor = useMemo(() => path(['gray', 7])(theme.colors), [theme.colors]); const navMenuBg = useMemo(() => bgSelectFn(theme), [theme, theme]); return ( ({ + sx={theme => ({ flexGrow: 1, backgroundColor: navMenuBg, + overflow: 'hidden' })} + px={4} + py={4} align="center" - px={16} - py={16} - > + > + + + ); }; diff --git a/src/components/ComicView.tsx b/src/components/ComicView.tsx new file mode 100644 index 0000000..c1e9efb --- /dev/null +++ b/src/components/ComicView.tsx @@ -0,0 +1,6 @@ +import { Box } from '@mantine/core'; +import { FC } from 'react'; + +export const ComicView: FC = () => { + return ; +}; diff --git a/src/components/FileList.tsx b/src/components/FileList.tsx new file mode 100644 index 0000000..4561e89 --- /dev/null +++ b/src/components/FileList.tsx @@ -0,0 +1,23 @@ +import { Box, Center, Text } from '@mantine/core'; +import { isEmpty, map, pipe, sort } from 'ramda'; +import { FC } from 'react'; +import { useFileListStore } from '../states/files'; + +export const FileList: FC = () => { + const files = useFileListStore.use.files(); + console.log('[debug]files from store: ', files); + + return ( + + {pipe( + sort((fa, fb) => fa.sort - fb.sort), + map(item =>
{item.filename}
) + )(files)} + {isEmpty(files) && ( +
+ 请先打开一个文件夹。 +
+ )} +
+ ); +}; diff --git a/src/components/FileTools.tsx b/src/components/FileTools.tsx new file mode 100644 index 0000000..c91222d --- /dev/null +++ b/src/components/FileTools.tsx @@ -0,0 +1,42 @@ +import { Button, Group, Tooltip } from '@mantine/core'; +import { notifications } from '@mantine/notifications'; +import { IconFolder } from '@tabler/icons-react'; +import { invoke } from '@tauri-apps/api'; +import { open } from '@tauri-apps/api/dialog'; +import { FC, useCallback } from 'react'; +import { useFileListStore } from '../states/files'; + +export const FileToolbar: FC = () => { + const storeFiles = useFileListStore.use.updateFiles(); + const handleOpenAction = useCallback(async () => { + try { + const directory = await open({ + title: '打开要浏览的漫画所在的文件夹', + directory: true, + multiple: false + }); + const files = await invoke('scan_directory', { target: directory }); + console.log('[debug]file list: ', files); + storeFiles(files); + } catch (e) { + console.error('[error]打开文件夹', e); + notifications.show({ title: '未能成功打开指定文件夹,请重试。', color: 'red' }); + } + }, [storeFiles]); + + return ( + + + + + + ); +}; diff --git a/src/components/PicToolbar.tsx b/src/components/PicToolbar.tsx new file mode 100644 index 0000000..aa3a8ee --- /dev/null +++ b/src/components/PicToolbar.tsx @@ -0,0 +1,61 @@ +import { ActionIcon, Group, NumberInput, rem, SegmentedControl, Tooltip } from '@mantine/core'; +import { + IconArrowAutofitWidth, + IconLock, + IconPercentage, + IconZoomIn, + IconZoomOut +} from '@tabler/icons-react'; +import { FC } from 'react'; +import { useZoomState } from '../states/zoom'; + +export const PicToolbar: FC = () => { + const { lock, autoFit, currentZoom, viewMode } = useZoomState(); + + return ( + + + + + + + + + + + + + + + + + } + /> + + + + + + + + + + ); +}; diff --git a/src/models.ts b/src/models.ts new file mode 100644 index 0000000..66b2fb0 --- /dev/null +++ b/src/models.ts @@ -0,0 +1,5 @@ +export type FileItem = { + sort: number; + filename: string; + path: string; +}; diff --git a/src/states/files.ts b/src/states/files.ts new file mode 100644 index 0000000..23c830d --- /dev/null +++ b/src/states/files.ts @@ -0,0 +1,28 @@ +import { addIndex, map, mergeRight } from 'ramda'; +import { FileItem } from '../models'; +import { SyncObjectCallback } from '../types'; +import { createStoreHook } from '../utils/store_creator'; + +interface FileListState { + files: FileItem[]; +} + +type FileListActions = { + updateFiles: SyncObjectCallback[]>; +}; + +const initialState: FileListState = { + files: [] +}; + +export const useFileListStore = createStoreHook(set => ({ + ...initialState, + updateFiles(files) { + set(df => { + df.files = addIndex, FileItem>(map)( + (item, index) => mergeRight({ sort: index * 10 }, item), + files + ); + }); + } +})); diff --git a/src/states/zoom.ts b/src/states/zoom.ts new file mode 100644 index 0000000..aec10d2 --- /dev/null +++ b/src/states/zoom.ts @@ -0,0 +1,19 @@ +import { createStoreHook } from '../utils/store_creator'; + +interface ZoomState { + lock: boolean; + autoFit: boolean; + currentZoom: number; + viewMode: 'single' | 'double' | 'continuation'; +} + +const initialState: ZoomState = { + lock: true, + autoFit: false, + currentZoom: 100, + viewMode: 'continuation' +}; + +export const useZoomState = createStoreHook(set => ({ + ...initialState +}));