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 00000000..0de32741 Binary files /dev/null and b/apps/frontend/src/assets/images/default_world.png differ diff --git a/apps/frontend/src/ui/components/game/WorldIcon.tsx b/apps/frontend/src/ui/components/game/WorldIcon.tsx new file mode 100644 index 00000000..ad26cebc --- /dev/null +++ b/apps/frontend/src/ui/components/game/WorldIcon.tsx @@ -0,0 +1,30 @@ +import { convertFileSrc } from '@tauri-apps/api/core'; +import { join } from 'pathe'; +import { type JSX, splitProps } from 'solid-js'; +import useSettingsContext from '~ui/hooks/useSettings'; +import DefaultWorld from '~assets/images/default_world.png?url'; + +export type WorldIconProps = JSX.HTMLAttributes & { + 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) +}