From 444061c0a7d5972016cd5342b77686a543eea693 Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Thu, 8 Aug 2024 21:23:23 +0200 Subject: [PATCH] feat: Cluster Worlds Page --- apps/desktop/src/api/commands/cluster.rs | 17 +++ apps/desktop/src/api/commands/mod.rs | 1 + .../src/assets/images/default_world.png | Bin 0 -> 6857 bytes .../src/ui/components/game/WorldIcon.tsx | 30 +++++ .../src/ui/pages/cluster/ClusterLogs.tsx | 1 - .../src/ui/pages/cluster/ClusterRoot.tsx | 2 + .../src/ui/pages/cluster/ClusterWorlds.tsx | 106 ++++++++++++++++++ .../core/src/api/cluster/content/logger.rs | 6 + packages/core/src/api/cluster/content/mod.rs | 1 + .../src/api/cluster/content/screenshots.rs | 8 -- .../core/src/api/cluster/content/worlds.rs | 23 ++++ 11 files changed, 186 insertions(+), 9 deletions(-) create mode 100644 apps/frontend/src/assets/images/default_world.png create mode 100644 apps/frontend/src/ui/components/game/WorldIcon.tsx create mode 100644 apps/frontend/src/ui/pages/cluster/ClusterWorlds.tsx create mode 100644 packages/core/src/api/cluster/content/worlds.rs diff --git a/apps/desktop/src/api/commands/cluster.rs b/apps/desktop/src/api/commands/cluster.rs index f11a1f02..1fe55516 100644 --- a/apps/desktop/src/api/commands/cluster.rs +++ b/apps/desktop/src/api/commands/cluster.rs @@ -81,6 +81,10 @@ pub async fn edit_game_settings(uuid: Uuid, new_cluster: Cluster) -> Result<(), old.memory = new_cluster.memory; // Process + old.init_hooks = new_cluster.init_hooks.clone(); + + // Java + old.java = new_cluster.java.clone(); async move { Ok(()) } }) @@ -195,3 +199,16 @@ pub async fn get_screenshots(uuid: Uuid) -> Result, String> { Ok(screenshots) } + +#[specta::specta] +#[tauri::command] +pub async fn get_worlds(uuid: Uuid) -> Result, String> { + let cluster = onelauncher::cluster::get_by_uuid(uuid, None) + .await? + .ok_or("cluster not found")?; + + let screenshots = cluster::content::worlds::get_worlds(&cluster.cluster_path()) + .await?; + + Ok(screenshots) +} diff --git a/apps/desktop/src/api/commands/mod.rs b/apps/desktop/src/api/commands/mod.rs index 2cb9d778..692b6e82 100644 --- a/apps/desktop/src/api/commands/mod.rs +++ b/apps/desktop/src/api/commands/mod.rs @@ -40,6 +40,7 @@ macro_rules! collect_commands { get_cluster_log, upload_log, get_screenshots, + get_worlds, // Processor get_running_clusters, get_processes_by_path, diff --git a/apps/frontend/src/assets/images/default_world.png b/apps/frontend/src/assets/images/default_world.png new file mode 100644 index 0000000000000000000000000000000000000000..0de32741294a8bff1bc23d894a5f730938115f40 GIT binary patch literal 6857 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 & { + world_name: string; + cluster_name: string; +}; + +function WorldIcon(props: WorldIconProps) { + const [split, rest] = splitProps(props, ['cluster_name', 'world_name', 'class']); + const { settings } = useSettingsContext(); + const path = () => convertFileSrc(join(settings().config_dir || '', 'clusters', props.cluster_name, 'saves', props.world_name, 'icon.png')); + + return ( + { + e.currentTarget.src = DefaultWorld; + }} + alt={`${props.world_name} icon`} + /> + ); +} + +export default WorldIcon; diff --git a/apps/frontend/src/ui/pages/cluster/ClusterLogs.tsx b/apps/frontend/src/ui/pages/cluster/ClusterLogs.tsx index 21d2ab43..0db0ccb1 100644 --- a/apps/frontend/src/ui/pages/cluster/ClusterLogs.tsx +++ b/apps/frontend/src/ui/pages/cluster/ClusterLogs.tsx @@ -101,7 +101,6 @@ function ClusterLogs() { onClick={openFolder} children="Open Folder" iconLeft={} - disabled={missingLogs()} /> diff --git a/apps/frontend/src/ui/pages/cluster/ClusterRoot.tsx b/apps/frontend/src/ui/pages/cluster/ClusterRoot.tsx index ac75a494..9c15374b 100644 --- a/apps/frontend/src/ui/pages/cluster/ClusterRoot.tsx +++ b/apps/frontend/src/ui/pages/cluster/ClusterRoot.tsx @@ -11,6 +11,7 @@ import ClusterMods from './ClusterMods'; import ClusterScreenshots from './ClusterScreenshots'; import ClusterSettings from './ClusterSettings'; import ClusterGame from './ClusterGame'; +import ClusterWorlds from './ClusterWorlds'; import useClusterContext, { ClusterProvider } from '~ui/hooks/useCluster'; import { supportsMods } from '~utils'; import { tryResult } from '~ui/hooks/useCommand'; @@ -24,6 +25,7 @@ function ClusterRoutes() { + diff --git a/apps/frontend/src/ui/pages/cluster/ClusterWorlds.tsx b/apps/frontend/src/ui/pages/cluster/ClusterWorlds.tsx new file mode 100644 index 00000000..e3094024 --- /dev/null +++ b/apps/frontend/src/ui/pages/cluster/ClusterWorlds.tsx @@ -0,0 +1,106 @@ +import { open } from '@tauri-apps/plugin-shell'; +import { LinkExternal01Icon, Trash01Icon } from '@untitled-theme/icons-solid'; +import { join } from 'pathe'; +import { For, Show } from 'solid-js'; +import { bridge } from '~imports'; +import Button from '~ui/components/base/Button'; +import WorldIcon from '~ui/components/game/WorldIcon'; +import Modal, { createModal } from '~ui/components/overlay/Modal'; +import ScrollableContainer from '~ui/components/ScrollableContainer'; +import Sidebar from '~ui/components/Sidebar'; +import useClusterContext from '~ui/hooks/useCluster'; +import useCommand from '~ui/hooks/useCommand'; +import useSettingsContext from '~ui/hooks/useSettings'; + +function ClusterWorlds() { + const { settings } = useSettingsContext(); + const [cluster] = useClusterContext(); + const [list] = useCommand(bridge.commands.getWorlds, cluster()!.uuid!); + + function openFolder() { + open(join(settings().config_dir || '', 'clusters', cluster()?.path || '', 'saves')); + } + + return ( + +

Worlds

+ + No worlds found.} + when={list() !== undefined && list()!.length > 0} + > +
+ + {world_name => ( + + )} + +
+
+
+ +
+
+
+ ); +} + +export default ClusterWorlds; + +function WorldEntry(props: { name: string; cluster_path: string }) { + const { settings } = useSettingsContext(); + + const dir = () => join(settings().config_dir || '', 'clusters', props.cluster_path, 'saves', props.name); + + const deleteModal = createModal(self => ( + { + // bridge.commands.deleteWorld(props.name); + self.hide(); + }} + /> + )); + + function onClick() { + // TODO: World region viewer or whatever?? + open(dir()); + } + + function deleteWorld(e: Event) { + e.preventDefault(); + e.stopImmediatePropagation(); + + deleteModal.show(); + } + + return ( +
+
+ +
+

{props.name}

+

Todo

+
+
+ +
+
+
+ ); +} diff --git a/packages/core/src/api/cluster/content/logger.rs b/packages/core/src/api/cluster/content/logger.rs index 09104bbb..b66037ac 100644 --- a/packages/core/src/api/cluster/content/logger.rs +++ b/packages/core/src/api/cluster/content/logger.rs @@ -99,6 +99,12 @@ pub async fn get_logs( cluster_path: &ClusterPath, clear: Option, ) -> crate::Result> { + let dir = Directories::cluster_logs_dir(cluster_path).await?; + if !dir.exists() { + io::create_dir(dir).await?; + return Ok(vec![]); + } + let mut logs = Vec::new(); get_logs_by_type(cluster_path, LogType::Info, clear, &mut logs).await?; get_logs_by_type(cluster_path, LogType::Crash, clear, &mut logs).await?; diff --git a/packages/core/src/api/cluster/content/mod.rs b/packages/core/src/api/cluster/content/mod.rs index bac5849e..837c304f 100644 --- a/packages/core/src/api/cluster/content/mod.rs +++ b/packages/core/src/api/cluster/content/mod.rs @@ -1,2 +1,3 @@ pub mod logger; pub mod screenshots; +pub mod worlds; \ No newline at end of file diff --git a/packages/core/src/api/cluster/content/screenshots.rs b/packages/core/src/api/cluster/content/screenshots.rs index 9a0efaee..ef1bf5b1 100644 --- a/packages/core/src/api/cluster/content/screenshots.rs +++ b/packages/core/src/api/cluster/content/screenshots.rs @@ -1,5 +1,3 @@ -use std::path::PathBuf; - use crate::utils::io; use crate::Result; @@ -23,9 +21,3 @@ pub async fn get_screenshots(cluster: &ClusterPath) -> Result> { Ok(list) } - -#[tracing::instrument] -pub async fn get_screenshot_path(cluster: &ClusterPath, file_name: String) -> Result { - let dir = cluster.full_path().await?.join("screenshots"); - Ok(dir.join(file_name)) -} diff --git a/packages/core/src/api/cluster/content/worlds.rs b/packages/core/src/api/cluster/content/worlds.rs new file mode 100644 index 00000000..fc8b384e --- /dev/null +++ b/packages/core/src/api/cluster/content/worlds.rs @@ -0,0 +1,23 @@ +use crate::utils::io; +use crate::Result; + +use crate::store::ClusterPath; + +/// Gets a list of screenshot file names from the [`ClusterPath`] +#[tracing::instrument] +pub async fn get_worlds(cluster: &ClusterPath) -> Result> { + let dir = cluster.full_path().await?.join("saves"); + + if !dir.exists() { + io::create_dir(dir).await?; + return Ok(vec![]); + } + + let mut list = vec![]; + let mut files = io::read_dir(dir).await?; + while let Some(file) = files.next_entry().await? { + list.push(file.file_name().to_string_lossy().to_string()); + } + + Ok(list) +}