diff --git a/cspell.jsonc b/cspell.jsonc index d5c622c..5345052 100644 --- a/cspell.jsonc +++ b/cspell.jsonc @@ -21,6 +21,7 @@ "tauri", "thiserror", "Unhide", + "unlisten", "unoptimized", "walkdir", "Warhammer", diff --git a/frontend/src/components/form.tsx b/frontend/src/components/form.tsx index 326e20c..ff39e89 100644 --- a/frontend/src/components/form.tsx +++ b/frontend/src/components/form.tsx @@ -10,6 +10,7 @@ import Checkbox from "@mui/material/Checkbox"; import ClearAllIcon from "@mui/icons-material/ClearAll"; import ConvertButton from "./buttons/convert_btn"; import Grid from "@mui/material/Unstable_Grid2"; +import SlideshowIcon from "@mui/icons-material/Slideshow"; import VisibilityOffIcon from "@mui/icons-material/VisibilityOff"; import toast from "react-hot-toast"; import type { SubmitHandler } from "react-hook-form"; @@ -23,6 +24,8 @@ import { } from "@/components/lists/select_log_level"; import { SelectPathButton } from "@/components/buttons/path_selector"; import { UnhideDarBtn } from "@/components/buttons/unhide_dar_btn"; +import { listen } from "@tauri-apps/api/event"; +import LinearWithValueLabel from "./progress_bar"; type FormProps = { src: string; @@ -35,6 +38,8 @@ type FormProps = { logLevel: LogLevel; runParallel: boolean; hideDar: boolean; + showProgress: boolean; + progress: number; }; export function ConvertForm() { @@ -49,10 +54,12 @@ export function ConvertForm() { modAuthor: localStorage.getItem("modAuthor") ?? "", mappingPath: localStorage.getItem("mappingPath") ?? "", mapping1personPath: localStorage.getItem("mapping1personPath") ?? "", - runParallel: localStorage.getItem("runParallel") === "true", - hideDar: localStorage.getItem("hideDar") === "true", loading: false as boolean, logLevel: selectLogLevel(localStorage.getItem("logLevel") ?? "error"), + runParallel: localStorage.getItem("runParallel") === "true", + hideDar: localStorage.getItem("hideDar") === "true", + showProgress: localStorage.getItem("showProgress") === "true", + progress: 0, } satisfies FormProps, }); @@ -85,10 +92,25 @@ export function ConvertForm() { mapping1personPath, runParallel, hideDar, + showProgress, }) => { setLoading(true); + let unlisten: (() => void) | null = null; try { + setValue("progress", 0); + let maxNum = 0; + let prog = 0; + + unlisten = await listen<{ index: number }>("show-progress", (event) => { + if (maxNum === 0) { + maxNum = event.payload.index; + } else { + prog = event.payload.index; + } + setValue("progress", (prog * 100) / maxNum); + }); + const completeInfo = await convertDar2oar({ src, dist, @@ -98,11 +120,16 @@ export function ConvertForm() { mapping1personPath, runParallel, hideDar, + showProgress, }); toast.success(completeInfo); + setValue("progress", 100); } catch (err) { toast.error(`${err}`); } finally { + if (unlisten) { + unlisten(); + } setLoading(false); } }; @@ -349,33 +376,37 @@ export function ConvertForm() { ( - Use multi-threading. -
- In most cases, it slows down by tens of ms, but may be - effective when there is more weight on CPU processing with - fewer files to copy and more logic parsing of - "_condition.txt" -

+ <> +

Display detail progress

+

+ However, conversion may be delayed by 5~10 seconds or + more. +

+ } > { - localStorage.setItem("runParallel", `${!value}`); - setValue("runParallel", !value); + setValue("showProgress", !value); + localStorage.setItem("showProgress", `${!value}`); }} checked={value} - aria-label="Run Parallel" + aria-label="Show Progress" /> } - label="Run Parallel" + label={ + + + ProgressBar + + } />
)} @@ -403,6 +434,7 @@ export function ConvertForm() { { localStorage.setItem("hideDar", `${!value}`); + setValue("hideDar", !value); }} checked={value} aria-label="Hide DAR" @@ -431,14 +463,51 @@ export function ConvertForm() {
- - ( - - )} - /> + + + ( + + Use multi-threading. +
+ In most cases, it slows down by tens of ms, but may be + effective when there is more weight on CPU processing with + fewer files to copy and more logic parsing of + "_condition.txt" +

+ } + > + { + localStorage.setItem("runParallel", `${!value}`); + setValue("runParallel", !value); + }} + checked={value} + aria-label="Run Parallel" + /> + } + label="Run Parallel" + /> +
+ )} + /> +
+ + + ( + + )} + /> +
)} /> + + ( + + )} + />
); diff --git a/frontend/src/components/progress_bar.tsx b/frontend/src/components/progress_bar.tsx new file mode 100644 index 0000000..8276924 --- /dev/null +++ b/frontend/src/components/progress_bar.tsx @@ -0,0 +1,35 @@ +import * as React from "react"; +import LinearProgress, { + LinearProgressProps, +} from "@mui/material/LinearProgress"; +import Typography from "@mui/material/Typography"; +import Box from "@mui/material/Box"; + +function LinearProgressWithLabel( + props: LinearProgressProps & { value: number }, +) { + return ( + + + + + + {`${Math.round( + props.value, + )}%`} + + + ); +} + +type Props = { + progress: number; +}; + +export default function LinearWithValueLabel({ progress }: Readonly) { + return ( + + + + ); +} diff --git a/frontend/src/tauri_cmd/index.ts b/frontend/src/tauri_cmd/index.ts index 9db7dcd..d978db1 100644 --- a/frontend/src/tauri_cmd/index.ts +++ b/frontend/src/tauri_cmd/index.ts @@ -2,6 +2,7 @@ import { invoke } from "@tauri-apps/api"; import { open } from "@tauri-apps/api/dialog"; import { appLogDir } from "@tauri-apps/api/path"; import { open as openShell } from "@tauri-apps/api/shell"; +import { selectLogLevel } from "@/components/lists/select_log_level"; export type LogLevel = "trace" | "debug" | "info" | "warn" | "error"; @@ -14,6 +15,7 @@ type ConverterArgs = { mapping1personPath?: string; runParallel?: boolean; hideDar?: boolean; + showProgress?: boolean; }; /** @@ -21,28 +23,29 @@ type ConverterArgs = { * # Throw Error */ export async function convertDar2oar(props: ConverterArgs): Promise { - const darDir = props.src === "" ? undefined : props.src; - const oarDir = props.dist === "" ? undefined : props.dist; - const modName = props.modName === "" ? undefined : props.modName; - const modAuthor = props.modAuthor === "" ? undefined : props.modAuthor; - const mapping1personPath = - props.mapping1personPath === "" ? undefined : props.mapping1personPath; - const mappingPath = props.mappingPath === "" ? undefined : props.mappingPath; - const runParallel = props.runParallel ?? false; - const hideDar = props.hideDar ?? false; - - return invoke("convert_dar2oar", { + const args = { options: { - darDir, - oarDir, - modName, - modAuthor, - mappingPath, - mapping1personPath, - runParallel, - hideDar, + darDir: props.src === "" ? undefined : props.src, + oarDir: props.dist === "" ? undefined : props.dist, + modName: props.modName === "" ? undefined : props.modName, + modAuthor: props.modAuthor === "" ? undefined : props.modAuthor, + mappingPath: props.mappingPath === "" ? undefined : props.mappingPath, + mapping1personPath: + props.mapping1personPath === "" ? undefined : props.mapping1personPath, + runParallel: props.runParallel ?? false, + hideDar: props.hideDar ?? false, }, - }); + }; + + let logLevel = selectLogLevel(localStorage.getItem("logLevel") ?? ""); + changeLogLevel(logLevel); + + const showProgress = props.showProgress ?? false; + if (showProgress) { + return invoke("convert_dar2oar_with_progress", args); + } else { + return invoke("convert_dar2oar", args); + } } export async function changeLogLevel(logLevel?: LogLevel): Promise { diff --git a/frontend/src/utils/styles.ts b/frontend/src/utils/styles.ts index 4ced4eb..df4b53e 100644 --- a/frontend/src/utils/styles.ts +++ b/frontend/src/utils/styles.ts @@ -52,6 +52,10 @@ label.Mui-focused { .MuiLoadingButton-root:hover { background-color: var(--hover-convert-btn-color); +} + +.MuiLinearProgress-bar { + background-color: var(--theme-color); }`; } diff --git a/src-tauri/src/cmd.rs b/src-tauri/src/cmd.rs index 109fea9..aeeb43a 100644 --- a/src-tauri/src/cmd.rs +++ b/src-tauri/src/cmd.rs @@ -3,6 +3,7 @@ use dar2oar_core::{ convert_dar_to_oar, fs::{async_closure::AsyncClosure, parallel, remove_oar, unhide_dar, ConvertOptions}, }; +use tauri::Window; /// early return with Err() and write log error. macro_rules! bail { @@ -31,6 +32,39 @@ pub(crate) async fn convert_dar2oar(options: GuiConverterOptions<'_>) -> Result< } } +#[tauri::command] +pub(crate) async fn convert_dar2oar_with_progress( + window: Window, + options: GuiConverterOptions<'_>, +) -> Result { + tracing::debug!("options: {:?}", &options); + let run_parallel = options.run_parallel.unwrap_or_default(); + + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] + struct Payload { + index: usize, + } + let sender = move |index: usize| { + if let Err(err) = window.emit("show-progress", Payload { index }) { + tracing::error!("{}", err); + }; + async move {} + }; + + let config = ConvertOptions::async_from(options).await; + let res = match run_parallel { + true => parallel::convert_dar_to_oar(config, sender).await, + false => convert_dar_to_oar(config, sender).await, + }; + match res { + Ok(complete_msg) => { + tracing::info!("{}", complete_msg); + Ok(complete_msg.to_string()) + } + Err(err) => bail!(err), + } +} + #[tauri::command] pub(crate) async fn change_log_level(log_level: Option<&str>) -> Result<(), String> { tracing::debug!("Selected log level: {:?}", log_level); diff --git a/src-tauri/src/convert_option.rs b/src-tauri/src/convert_option.rs index 0d4efde..99b4ff7 100644 --- a/src-tauri/src/convert_option.rs +++ b/src-tauri/src/convert_option.rs @@ -13,6 +13,7 @@ pub(crate) struct GuiConverterOptions<'a> { pub(crate) mapping_1person_path: Option<&'a str>, pub(crate) run_parallel: Option, pub(crate) hide_dar: Option, + pub(crate) show_progress: Option, } #[async_trait::async_trait] @@ -32,6 +33,7 @@ impl<'a> AsyncFrom> for ConvertOptions<'a, &'a str> { mapping_1person_path, run_parallel: _, hide_dar, + show_progress: _, } = options; let oar_dir = oar_dir.and_then(|dist| match dist.is_empty() { diff --git a/src-tauri/src/runner.rs b/src-tauri/src/runner.rs index 51083e0..c84a015 100644 --- a/src-tauri/src/runner.rs +++ b/src-tauri/src/runner.rs @@ -12,6 +12,7 @@ pub fn run_tauri() -> anyhow::Result<()> { .invoke_handler(tauri::generate_handler![ crate::cmd::change_log_level, crate::cmd::convert_dar2oar, + crate::cmd::convert_dar2oar_with_progress, crate::cmd::remove_oar_dir, crate::cmd::restore_dar_dir, ]) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index e40617a..1c94dfd 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -73,7 +73,7 @@ "windows": [ { "fullscreen": false, - "height": 880, + "height": 900, "resizable": true, "theme": "Dark", "title": "DAR to OAR Converter",