feat: Cluster Worlds Page

This commit is contained in:
LynithDev 2024-08-08 21:23:23 +02:00
parent dfb8edd069
commit 444061c0a7
11 changed files with 186 additions and 9 deletions

View file

@ -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<Vec<String>, String> {
Ok(screenshots)
}
#[specta::specta]
#[tauri::command]
pub async fn get_worlds(uuid: Uuid) -> Result<Vec<String>, 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)
}

View file

@ -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,

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View file

@ -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<HTMLImageElement> & {
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 (
<img
{...rest}
class={`w-16 h-16 rounded-md ${split.class || ''}`}
src={path()}
onError={(e) => {
e.currentTarget.src = DefaultWorld;
}}
alt={`${props.world_name} icon`}
/>
);
}
export default WorldIcon;

View file

@ -101,7 +101,6 @@ function ClusterLogs() {
onClick={openFolder}
children="Open Folder"
iconLeft={<LinkExternal01Icon />}
disabled={missingLogs()}
/>
</div>
</div>

View file

@ -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() {
<Route path="/logs" component={ClusterLogs} />
<Route path="/mods" component={ClusterMods} />
<Route path="/screenshots" component={ClusterScreenshots} />
<Route path="/worlds" component={ClusterWorlds} />
<Route path="/settings" component={ClusterSettings} />
<Route path="/game" component={ClusterGame} />
</>

View file

@ -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 (
<Sidebar.Page>
<h1>Worlds</h1>
<ScrollableContainer>
<Show
fallback={<div class="text-gray-400">No worlds found.</div>}
when={list() !== undefined && list()!.length > 0}
>
<div class="flex flex-col gap-2">
<For each={list()!}>
{world_name => (
<WorldEntry name={world_name} cluster_path={cluster()?.path || ''} />
)}
</For>
</div>
</Show>
</ScrollableContainer>
<div class="flex flex-row justify-end items-end mt-2">
<Button
buttonStyle="primary"
iconLeft={<LinkExternal01Icon />}
children="Open Folder"
onClick={openFolder}
/>
</div>
</Sidebar.Page>
);
}
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 => (
<Modal.Delete
{...self}
title={`Delete '${props.name}'`}
onDelete={() => {
// 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 (
<div
class="bg-component-bg hover:bg-component-bg-hover active:bg-component-bg-pressed p-3 gap-3 rounded-xl flex flex-row justify-between items-center"
onClick={onClick}
>
<div class="flex flex-row items-center gap-x-3">
<WorldIcon cluster_name={props.cluster_path} world_name={props.name} class="w-16 h-16 aspect-ratio-square" />
<div class="flex flex-col gap-y-2">
<h3>{props.name}</h3>
<p>Todo</p>
</div>
</div>
<div class="flex flex-row justify-end items-center gap-x-3">
<Button
buttonStyle="iconDanger"
children={<Trash01Icon />}
onClick={deleteWorld}
/>
</div>
</div>
);
}

View file

@ -99,6 +99,12 @@ pub async fn get_logs(
cluster_path: &ClusterPath,
clear: Option<bool>,
) -> crate::Result<Vec<LogManager>> {
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?;

View file

@ -1,2 +1,3 @@
pub mod logger;
pub mod screenshots;
pub mod worlds;

View file

@ -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<Vec<String>> {
Ok(list)
}
#[tracing::instrument]
pub async fn get_screenshot_path(cluster: &ClusterPath, file_name: String) -> Result<PathBuf> {
let dir = cluster.full_path().await?.join("screenshots");
Ok(dir.join(file_name))
}

View file

@ -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<Vec<String>> {
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)
}