chore: Do some work on reimplementing the Rust side into the frontend side

This commit is contained in:
LynithDev 2024-06-13 19:25:37 +02:00
parent e2495c78d4
commit 576c8f0d22
8 changed files with 129 additions and 46 deletions

View file

@ -42,6 +42,7 @@ pub enum ClusterStage {
}
/// Relative Path wrapper to be used as an identifer for a cluster path.
#[cfg_attr(feature = "tauri", derive(specta::Type))]
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Hash)]
#[serde(transparent)]
pub struct ClusterPath(pub PathBuf);

View file

@ -1,6 +1,7 @@
use std::path::PathBuf;
use std::{collections::HashMap, path::PathBuf, str::FromStr};
use onelauncher::{cluster, data::{Loader, PackageData}};
use chrono::DateTime;
use onelauncher::{cluster, data::{ClusterMeta, Loader, PackageData}, store::{Cluster, ClusterPath}, State};
use serde::{Deserialize, Serialize};
use specta::Type;
use uuid::Uuid;
@ -11,9 +12,12 @@ macro_rules! collect_commands {
{
use $crate::api::commands::*;
tauri_specta::ts::builder()
.config(specta::ts::ExportConfig::default().bigint(specta::ts::BigIntExportBehavior::BigInt))
.commands(tauri_specta::collect_commands![
is_dev,
create_cluster
create_cluster,
get_cluster,
get_clusters
])
}
};
@ -59,3 +63,57 @@ pub async fn create_cluster(props: CreateCluster) -> Result<Uuid, String> {
Err("Cluster does not exist".to_string())
}
}
fn placeholder_cluster() -> Cluster {
let path = ClusterPath("test".into());
Cluster {
uuid: Uuid::from_str("56d1cbcf-1961-4477-b263-80e3b1c7a9d1").unwrap(),
stage: onelauncher::store::ClusterStage::Installed,
path: path.0,
meta: ClusterMeta {
created_at: DateTime::from_timestamp_millis(1718297861712).unwrap(),
modified_at: DateTime::from_timestamp_millis(1718297861712).unwrap(),
group: vec![],
icon: None,
icon_url: None,
loader: Loader::Vanilla,
loader_version: None,
mc_version: "1.8.9".into(),
name: "Test Cluster".into(),
overall_played: 58195,
recently_played: 0,
package_data: None,
played_at: None
},
memory: None,
java: None,
resolution: None,
force_fullscreen: None,
init_hooks: None,
packages: HashMap::new(),
update: None
}
}
#[specta::specta]
#[tauri::command]
pub async fn get_cluster(uuid: Uuid) -> Result<Cluster, String> {
Ok(placeholder_cluster())
// match cluster::get_by_uuid(uuid, None).await? {
// Some(cluster) => Ok(cluster),
// None => Err("Cluster does not exist".into())
// }
}
#[specta::specta]
#[tauri::command]
pub async fn get_clusters() -> Result<HashMap<ClusterPath, Cluster>, String> {
let mut map = HashMap::<ClusterPath, Cluster>::new();
let cluster = placeholder_cluster();
map.insert(cluster.cluster_path(), cluster);
Ok(map)
// cluster::list(None).await.map_err(|op| op.into())
}

View file

@ -1,14 +1,15 @@
import { type JSX, splitProps } from 'solid-js';
import defaultCover from '~assets/images/default_instance_cover.jpg';
import type { Cluster } from '~bindings';
type ClusterCoverProps = JSX.HTMLAttributes<HTMLImageElement> & {
cluster: Core.Cluster;
cluster: Cluster;
};
function ClusterCover(props: ClusterCoverProps) {
const [split, rest] = splitProps(props, ['cluster']);
return (
<img {...rest} src={split.cluster.cover || defaultCover} />
<img {...rest} src={split.cluster.meta.icon_url || defaultCover} />
);
}

View file

@ -3,28 +3,30 @@ import fabric from '~assets/logos/fabric.png';
import forge from '~assets/logos/forge.png';
import quilt from '~assets/logos/quilt.png';
import vanilla from '~assets/logos/vanilla.png';
import type { Loader } from '~bindings';
export function getLoaderIcon(loader: Core.Loader) {
export function getLoaderIcon(loader: Loader): string {
switch (loader) {
case 'Vanilla':
return vanilla;
case 'Forge':
case 'forge':
return forge;
case 'Fabric':
case 'fabric':
return fabric;
case 'Quilt':
case 'quilt':
return quilt;
case 'vanilla':
default:
return vanilla;
}
}
type LoaderIconProp = JSX.HTMLAttributes<HTMLImageElement> & {
loader: Core.Loader;
loader: Loader | undefined;
};
function LoaderIcon(props: LoaderIconProp) {
const [split, rest] = splitProps(props, ['loader']);
const [{ loader = 'vanilla' }, rest] = splitProps(props, ['loader']);
return (
<img {...rest} src={getLoaderIcon(split.loader)} alt={split.loader} />
<img {...rest} src={getLoaderIcon(loader)} alt={loader} />
);
}

View file

@ -1,15 +1,13 @@
import type { Resource } from 'solid-js';
import { createResource } from 'solid-js';
import useCommand from './useCommand';
import type { Cluster } from '~bindings';
import { bridge } from '~index';
function getCluster(uuid: string): Core.ClusterWithManifest {
throw new Error('get cluster');
}
function useCluster(uuid: string | undefined | null): Resource<Core.ClusterWithManifest> | null {
function useCluster(uuid: string | undefined | null): Resource<Cluster> | null {
if (typeof uuid !== 'string' || uuid.length === 0)
return null;
const [resource] = createResource(uuid, getCluster);
const [resource] = useCommand(bridge.commands.getCluster, uuid);
return resource;
}

View file

@ -0,0 +1,21 @@
import { type Resource, type ResourceReturn, createResource } from 'solid-js';
import type { Result } from '~bindings';
/**
* A helper function which handles a Rust Result properly
* @param cmd The command to call
* @throws
*/
export default function useCommand<R, E, Args extends unknown[]>(
cmd: (...args: Args) => Promise<Result<R, E>>,
...args: Args
): ResourceReturn<R> {
return createResource(async () => {
const value = await cmd(...args);
if (value.status === 'ok')
return value.data;
throw value.error;
});
}

View file

@ -7,7 +7,7 @@ import {
SearchMdIcon,
} from '@untitled-theme/icons-solid';
import type { Accessor, Setter } from 'solid-js';
import { For, createSignal } from 'solid-js';
import { For, Show, createSignal } from 'solid-js';
import { useNavigate } from '@solidjs/router';
import image from '../../assets/images/header.png';
import ContextMenu from '../components/overlay/ContextMenu';
@ -15,6 +15,9 @@ import Button from '../components/base/Button';
import Tag from '../components/base/Tag';
import TextField from '../components/base/TextField';
import ClusterCover from '~ui/components/game/ClusterCover';
import type { Cluster } from '~bindings';
import useCommand from '~ui/hooks/useCommand';
import { bridge } from '~index';
// TODO: Replace this into it's own component
// possibly with the core ui lirbary with autogenerated and optimized svgs pls
@ -97,14 +100,14 @@ function ClusterCardContextMenu(props: ClusterCardContextMenuProps) {
);
}
function ClusterCard(props: Core.ClusterWithManifest) {
function ClusterCard(props: Cluster) {
const navigate = useNavigate();
const [pos, setPos] = createSignal({ x: 0, y: 0 });
const [contextMenuVisible, setContextMenuVisible] = createSignal(false);
let ref!: HTMLDivElement;
function openClusterPage(_e: MouseEvent) {
navigate(`/clusters/?id=${props.cluster.id}`);
navigate(`/clusters/?id=${props.uuid}`);
}
function openContextMenu(e: MouseEvent) {
@ -132,18 +135,18 @@ function ClusterCard(props: Core.ClusterWithManifest) {
style={{ '-webkit-transform': 'translateZ(0)' }}
>
<ClusterCover
cluster={props.cluster}
cluster={props}
class="object-cover h-full w-full"
/>
</div>
</div>
<div class="z-10 p-3 flex flex-row items-center justify-between">
<div class="flex flex-col gap-1.5">
<p class="h-4 font-semibold">{props.cluster.name}</p>
<p class="h-4 font-semibold">{props.meta.name}</p>
<p class="h-4 text-xs">
{props.cluster.client.type}
{props.meta.loader}
{' '}
{props.manifest.manifest.id}
{props.meta.mc_version}
{/* {' '}
{props.instance.mods && `• ${props.mods} mods`} */}
</p>
@ -158,7 +161,7 @@ function ClusterCard(props: Core.ClusterWithManifest) {
contextMenuVisible={contextMenuVisible}
setContextMenuVisible={setContextMenuVisible}
pos={pos}
id={props.cluster.id}
id={props.uuid}
/>
</>
);
@ -166,7 +169,7 @@ function ClusterCard(props: Core.ClusterWithManifest) {
interface ClusterGroupProps {
title: string;
clusters: Core.ClusterWithManifest[];
clusters: Cluster[];
}
function ClusterGroup(props: ClusterGroupProps) {
@ -181,7 +184,7 @@ function ClusterGroup(props: ClusterGroupProps) {
}
function HomePage() {
// const [clusters, { refetch }] = createResource(getClustersGrouped);
const [clusters, { refetch }] = useCommand(bridge.commands.getClusters);
async function newInstance() {
// bridge.commands.createCluster({
@ -225,13 +228,9 @@ function HomePage() {
</div>
</div>
{/* <Show when={clusters() !== undefined}>
<For each={Array.from(clusters()!.entries())}>
{([title, clusters]) => (
<ClusterGroup title={title} clusters={clusters} />
)}
</For>
</Show> */}
<Show when={clusters() !== undefined}>
<ClusterGroup title="Placeholder" clusters={Object.values(clusters()!)} />
</Show>
</div>
);
}

View file

@ -7,6 +7,8 @@ import LoaderIcon from '../../../components/game/LoaderIcon';
import Button from '../../../components/base/Button';
import ScrollableContainer from '~ui/components/ScrollableContainer';
import Sidebar from '~ui/components/Sidebar';
import type { Cluster } from '~bindings';
import { upperFirst } from '~utils/string';
function ClusterOverview() {
const [params] = useSearchParams();
@ -26,29 +28,30 @@ function ClusterOverview() {
);
}
// eslint-disable-next-line solid/no-destructure
function Banner({ cluster, manifest }: Core.ClusterWithManifest) {
function Banner(props: Cluster) {
return (
<div class="flex flex-row bg-component-bg rounded-xl p-2.5 gap-x-2.5">
<div class="w-48 rounded-lg overflow-hidden border border-gray-10">
<ClusterCover cluster={cluster} />
<ClusterCover cluster={props} />
</div>
<div class="flex flex-col flex-1 text-fg-primary">
<div class="flex-1">
<h2 class="text-2xl">{cluster.name}</h2>
<h2 class="text-2xl">{props.meta.name}</h2>
<span class="flex flex-row items-center gap-x-1">
<LoaderIcon loader={cluster.client.type} class="w-5" />
<span>{cluster.client.type}</span>
<span>{manifest.manifest.id}</span>
<LoaderIcon loader={props.meta.loader} class="w-5" />
<span>{upperFirst(props.meta.loader || 'unknown')}</span>
{props.meta.loader_version && <span>{props.meta.loader_version.id}</span>}
<span>{props.meta.mc_version}</span>
</span>
</div>
<span class="text-xs text-fg-secondary">
Played for
{' '}
<b>3.9</b>
<b>{((props.meta.overall_played || 0n)).toString()}</b>
{' '}
hours
{/* TODO: Ask Pauline if this is measured in seconds or milliseconds */}
</span>
</div>