Merge branch 'main' of github.com:Polyfrost/OneLauncher
This commit is contained in:
commit
d4781db671
10 changed files with 296 additions and 53 deletions
|
|
@ -1,5 +1,4 @@
|
||||||
use std::path::PathBuf;
|
use std::{collections::HashMap, path::PathBuf, str::FromStr};
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use onelauncher::cluster::content::logger;
|
use onelauncher::cluster::content::logger;
|
||||||
use onelauncher::cluster::{self};
|
use onelauncher::cluster::{self};
|
||||||
|
|
@ -25,6 +24,12 @@ pub async fn get_clusters() -> Result<Vec<Cluster>, String> {
|
||||||
Ok(onelauncher::cluster::list(None).await?)
|
Ok(onelauncher::cluster::list(None).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[specta::specta]
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn get_clusters_grouped() -> Result<HashMap<String, Vec<Cluster>>, String> {
|
||||||
|
Ok(onelauncher::cluster::list_grouped(None).await?)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Type)]
|
#[derive(Serialize, Deserialize, Type)]
|
||||||
pub struct CreateCluster {
|
pub struct CreateCluster {
|
||||||
name: String,
|
name: String,
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ macro_rules! collect_commands {
|
||||||
remove_cluster,
|
remove_cluster,
|
||||||
get_cluster,
|
get_cluster,
|
||||||
get_clusters,
|
get_clusters,
|
||||||
|
get_clusters_grouped,
|
||||||
run_cluster,
|
run_cluster,
|
||||||
get_cluster_logs,
|
get_cluster_logs,
|
||||||
get_cluster_log,
|
get_cluster_log,
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import type { Result } from '~bindings';
|
||||||
* @param cmd The command to call
|
* @param cmd The command to call
|
||||||
* @throws
|
* @throws
|
||||||
*/
|
*/
|
||||||
export default function useCommand<R, E, Args extends unknown[]>(
|
export default function useCommand<R, E = string, Args extends unknown[] = []>(
|
||||||
cmd: (...args: Args) => Promise<Result<R, E>>,
|
cmd: (...args: Args) => Promise<Result<R, E>>,
|
||||||
...args: Args
|
...args: Args
|
||||||
): ResourceReturn<R> {
|
): ResourceReturn<R> {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import {
|
import {
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
|
ChevronRightIcon,
|
||||||
DotsVerticalIcon,
|
DotsVerticalIcon,
|
||||||
EyeIcon,
|
EyeIcon,
|
||||||
PlayIcon,
|
PlayIcon,
|
||||||
|
|
@ -7,24 +8,39 @@ import {
|
||||||
SearchMdIcon,
|
SearchMdIcon,
|
||||||
} from '@untitled-theme/icons-solid';
|
} from '@untitled-theme/icons-solid';
|
||||||
import type { Accessor, Setter } from 'solid-js';
|
import type { Accessor, Setter } from 'solid-js';
|
||||||
import { For, Match, Switch, createSignal } from 'solid-js';
|
import { For, Show, createEffect, createMemo, createSignal } from 'solid-js';
|
||||||
import { useNavigate } from '@solidjs/router';
|
import { useNavigate } from '@solidjs/router';
|
||||||
|
import {
|
||||||
|
DragDropProvider,
|
||||||
|
DragDropSensors,
|
||||||
|
type DragEventHandler,
|
||||||
|
DragOverlay,
|
||||||
|
SortableProvider,
|
||||||
|
createDroppable,
|
||||||
|
createSortable,
|
||||||
|
} from '@thisbeyond/solid-dnd';
|
||||||
|
import { mergeRefs } from '@solid-primitives/refs';
|
||||||
import image from '../../assets/images/header.png';
|
import image from '../../assets/images/header.png';
|
||||||
import ContextMenu from '../components/overlay/ContextMenu';
|
import ContextMenu from '../components/overlay/ContextMenu';
|
||||||
import Button from '../components/base/Button';
|
import Button from '../components/base/Button';
|
||||||
import Tag from '../components/base/Tag';
|
import Tag from '../components/base/Tag';
|
||||||
import TextField from '../components/base/TextField';
|
import TextField from '../components/base/TextField';
|
||||||
|
import ClusterRoot from './cluster/ClusterRoot';
|
||||||
import ClusterCover from '~ui/components/game/ClusterCover';
|
import ClusterCover from '~ui/components/game/ClusterCover';
|
||||||
import type { Cluster } from '~bindings';
|
import type { Cluster } from '~bindings';
|
||||||
import useCommand from '~ui/hooks/useCommand';
|
import useCommand from '~ui/hooks/useCommand';
|
||||||
import { bridge } from '~imports';
|
import { bridge } from '~imports';
|
||||||
import { upperFirst } from '~utils/primitives';
|
import { secondsToWords, upperFirst } from '~utils/primitives';
|
||||||
import { useClusterModalController } from '~ui/components/overlay/cluster/ClusterCreationModal';
|
import { useClusterModalController } from '~ui/components/overlay/cluster/ClusterCreationModal';
|
||||||
|
|
||||||
|
type GroupedClusters = Record<string, Cluster[]>;
|
||||||
|
|
||||||
function HomePage() {
|
function HomePage() {
|
||||||
const [clusters, { refetch }] = useCommand(bridge.commands.getClusters);
|
const [clusters, { refetch }] = useCommand<GroupedClusters>(bridge.commands.getClustersGrouped);
|
||||||
const controller = useClusterModalController();
|
const controller = useClusterModalController();
|
||||||
|
|
||||||
|
const containerIds = (list: GroupedClusters | undefined) => Object.keys(list || []);
|
||||||
|
|
||||||
async function newCluster() {
|
async function newCluster() {
|
||||||
try {
|
try {
|
||||||
await controller.start();
|
await controller.start();
|
||||||
|
|
@ -36,9 +52,71 @@ function HomePage() {
|
||||||
refetch();
|
refetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// function getContainerByCluster(id: string) {
|
||||||
|
// const list = clusters();
|
||||||
|
|
||||||
|
// if (list === undefined)
|
||||||
|
// return undefined;
|
||||||
|
|
||||||
|
// if (list[id] !== undefined)
|
||||||
|
// return id;
|
||||||
|
|
||||||
|
// for (const [key, value] of Object.entries(list))
|
||||||
|
// if (value.find(cluster => cluster.uuid === id))
|
||||||
|
// return key;
|
||||||
|
|
||||||
|
// return undefined;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function move(fromId: string, toId: string) {
|
||||||
|
// const from = getContainerByCluster(fromId);
|
||||||
|
// const to = getContainerByCluster(toId);
|
||||||
|
|
||||||
|
// console.log(from, to);
|
||||||
|
|
||||||
|
// if (from === undefined || to === undefined)
|
||||||
|
// return;
|
||||||
|
|
||||||
|
// if (from === to) {
|
||||||
|
// const list = clusters();
|
||||||
|
// const fromList = list[from];
|
||||||
|
// const fromIndex = fromList.findIndex(cluster => cluster.uuid === fromId);
|
||||||
|
// const toIndex = fromList.findIndex(cluster => cluster.uuid === toId);
|
||||||
|
|
||||||
|
// if (fromIndex === toIndex)
|
||||||
|
// return;
|
||||||
|
|
||||||
|
// const newList = [...fromList];
|
||||||
|
// const [removed] = newList.splice(fromIndex, 1);
|
||||||
|
// newList.splice(toIndex, 0, removed);
|
||||||
|
|
||||||
|
// mutate({ ...list, [from]: newList });
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// const list = clusters();
|
||||||
|
// const fromList = list[from];
|
||||||
|
// const toList = list[to];
|
||||||
|
// const fromIndex = fromList.findIndex(cluster => cluster.uuid === fromId);
|
||||||
|
// const toIndex = toList.findIndex(cluster => cluster.uuid === toId);
|
||||||
|
|
||||||
|
// const newListFrom = [...fromList];
|
||||||
|
// const newListTo = [...toList];
|
||||||
|
|
||||||
|
// const [removed] = newListFrom.splice(fromIndex, 1);
|
||||||
|
// newListTo.splice(toIndex, 0, removed);
|
||||||
|
|
||||||
|
// mutate({ ...list, [from]: newListFrom, [to]: newListTo });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const onDragEnd: DragEventHandler = (event) => {
|
||||||
|
// if (event.draggable && event.droppable)
|
||||||
|
// move(event.draggable.id as string, event.droppable.id as string);
|
||||||
|
// };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="flex flex-col h-full gap-y-4 text-fg-primary">
|
<div class="flex flex-col h-full gap-y-4 text-fg-primary">
|
||||||
<Banner />
|
<Banner clusters={clusters()} />
|
||||||
|
|
||||||
<div class="flex flex-row justify-between items-center">
|
<div class="flex flex-row justify-between items-center">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -54,17 +132,32 @@ function HomePage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Switch>
|
<Show
|
||||||
<Match when={clusters()?.length === 0}>
|
when={containerIds(clusters()).length > 0}
|
||||||
|
children={(
|
||||||
|
// <DragDropProvider onDragEnd={onDragEnd}>
|
||||||
|
// <DragDropSensors />
|
||||||
|
|
||||||
|
<For each={Object.entries(clusters() ?? {})}>
|
||||||
|
{([group, clusters]) => (
|
||||||
|
<ClusterGroup title={group} clusters={clusters} />
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
|
||||||
|
// <DragOverlay>
|
||||||
|
// {draggable => (
|
||||||
|
// draggable?.node.cloneNode(true)
|
||||||
|
// )}
|
||||||
|
// </DragOverlay>
|
||||||
|
// </DragDropProvider>
|
||||||
|
)}
|
||||||
|
fallback={(
|
||||||
<div class="flex flex-col flex-1 gap-y-4 max-h-64 justify-center items-center">
|
<div class="flex flex-col flex-1 gap-y-4 max-h-64 justify-center items-center">
|
||||||
<span class="text-lg font-bold uppercase text-fg-secondary">No clusters were found.</span>
|
<span class="text-lg font-bold uppercase text-fg-secondary">No clusters were found.</span>
|
||||||
<span class="text-xl font-bold">Create one now with the New Cluster button.</span>
|
<span class="text-xl font-bold">Create one now with the New Cluster button.</span>
|
||||||
</div>
|
</div>
|
||||||
</Match>
|
)}
|
||||||
<Match when={clusters() !== undefined}>
|
/>
|
||||||
<ClusterGroup title="Clusters" clusters={clusters()!} />
|
|
||||||
</Match>
|
|
||||||
</Switch>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -89,7 +182,11 @@ function OneConfigLogo() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Banner() {
|
interface BannerProps {
|
||||||
|
clusters: GroupedClusters | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
function Banner(props: BannerProps) {
|
||||||
// TODO: Banner information
|
// TODO: Banner information
|
||||||
/**
|
/**
|
||||||
* If there are any clusters, display the most recent cluster name + some statistics as the "description".
|
* If there are any clusters, display the most recent cluster name + some statistics as the "description".
|
||||||
|
|
@ -99,29 +196,89 @@ function Banner() {
|
||||||
*
|
*
|
||||||
* If there are no clusters, display a generic background with the button action creating a new cluster.
|
* If there are no clusters, display a generic background with the button action creating a new cluster.
|
||||||
*/
|
*/
|
||||||
|
const [cluster, setCluster] = createSignal<Cluster>();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
// eslint-disable-next-line no-undef-init -- This is fine. I love eslint rule clash though
|
||||||
|
let mostRecentCluster: Cluster | undefined = undefined;
|
||||||
|
|
||||||
|
if (props.clusters === undefined)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (const [_, clusters] of Object.entries(props.clusters))
|
||||||
|
for (const cluster of clusters) {
|
||||||
|
if (mostRecentCluster === undefined) {
|
||||||
|
mostRecentCluster = cluster;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof mostRecentCluster.meta.played_at !== 'string' && typeof cluster.meta.played_at === 'string') {
|
||||||
|
mostRecentCluster = cluster;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof mostRecentCluster.meta.played_at === 'string' && typeof cluster.meta.played_at === 'string') {
|
||||||
|
const playedAt = new Date(mostRecentCluster.meta.played_at);
|
||||||
|
const clusterPlayedAt = new Date(cluster.meta.played_at);
|
||||||
|
|
||||||
|
if (clusterPlayedAt > playedAt)
|
||||||
|
mostRecentCluster = cluster;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setCluster(mostRecentCluster);
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="relative w-full h-52">
|
<div class="relative w-full h-52">
|
||||||
<img src={image} class="absolute rounded-xl w-full h-52 object-cover" />
|
<img src={image} class="absolute rounded-xl w-full h-52 object-cover" />
|
||||||
<div class="relative z-10 h-full px-8 py-6 text-fg-primary flex flex-col justify-between items-start">
|
<div class="relative z-10 h-full px-8 py-6 text-fg-primary flex flex-col justify-between items-start">
|
||||||
<div class="flex flex-col gap-y-2">
|
<div class="flex flex-col gap-y-2">
|
||||||
<h1>Building worlds</h1>
|
<h1>{cluster()?.meta.name || 'Create a cluster'}</h1>
|
||||||
<p>DEV BUILD - NOT FINAL</p>
|
<Show when={cluster() !== undefined}>
|
||||||
|
<p>
|
||||||
|
You've played
|
||||||
|
{' '}
|
||||||
|
<strong>
|
||||||
|
{cluster()!.meta.mc_version}
|
||||||
|
{' '}
|
||||||
|
{upperFirst(cluster()!.meta.loader) || 'Unknown'}
|
||||||
|
</strong>
|
||||||
|
{' '}
|
||||||
|
for
|
||||||
|
{' '}
|
||||||
|
<strong>{secondsToWords((cluster()!.meta.overall_played || 0n))}</strong>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex w-full flex-row justify-between items-end">
|
<div class="flex w-full flex-row justify-between items-end">
|
||||||
<div class="flex flex-row items-center gap-x-4">
|
<div class="flex flex-row items-center gap-x-4">
|
||||||
<Button
|
<Show
|
||||||
buttonStyle="primary"
|
when={cluster() !== undefined}
|
||||||
iconLeft={<PlayIcon />}
|
children={(
|
||||||
children="Launch 1.20.4"
|
<>
|
||||||
/>
|
<Button
|
||||||
<Button
|
buttonStyle="primary"
|
||||||
buttonStyle="icon"
|
iconLeft={<PlayIcon />}
|
||||||
children={<DotsVerticalIcon />}
|
children={`Launch ${cluster()!.meta.mc_version}`}
|
||||||
|
onClick={() => ClusterRoot.launch(navigate, cluster()!.uuid)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
buttonStyle="iconSecondary"
|
||||||
|
class="bg-op-10!"
|
||||||
|
children={<ChevronRightIcon />}
|
||||||
|
onClick={() => ClusterRoot.open(navigate, cluster()!.uuid)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-row gap-x-2">
|
<div class="flex flex-row gap-x-2">
|
||||||
<Tag iconLeft={<OneConfigLogo />} />
|
{/* TODO: These tags */}
|
||||||
<Tag iconLeft={<CheckIcon />}>Verified</Tag>
|
{/* <Tag iconLeft={<OneConfigLogo />} />
|
||||||
|
<Tag iconLeft={<CheckIcon />}>Verified</Tag> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -139,10 +296,6 @@ interface ClusterCardContextMenuProps {
|
||||||
function ClusterCardContextMenu(props: ClusterCardContextMenuProps) {
|
function ClusterCardContextMenu(props: ClusterCardContextMenuProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
function viewCluster() {
|
|
||||||
navigate(`/clusters/?id=${props.id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
pos={props.pos}
|
pos={props.pos}
|
||||||
|
|
@ -152,7 +305,7 @@ function ClusterCardContextMenu(props: ClusterCardContextMenuProps) {
|
||||||
<ContextMenu.Row
|
<ContextMenu.Row
|
||||||
icon={<EyeIcon />}
|
icon={<EyeIcon />}
|
||||||
text="View"
|
text="View"
|
||||||
onClick={() => viewCluster()}
|
onClick={() => ClusterRoot.open(navigate, props.id)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ContextMenu.Seperator />
|
<ContextMenu.Seperator />
|
||||||
|
|
@ -170,6 +323,7 @@ function ClusterCard(props: Cluster) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [pos, setPos] = createSignal({ x: 0, y: 0 });
|
const [pos, setPos] = createSignal({ x: 0, y: 0 });
|
||||||
const [contextMenuVisible, setContextMenuVisible] = createSignal(false);
|
const [contextMenuVisible, setContextMenuVisible] = createSignal(false);
|
||||||
|
// const sortable = createSortable(props.uuid, props);
|
||||||
let ref!: HTMLDivElement;
|
let ref!: HTMLDivElement;
|
||||||
|
|
||||||
function openClusterPage(_e: MouseEvent) {
|
function openClusterPage(_e: MouseEvent) {
|
||||||
|
|
@ -192,7 +346,10 @@ function ClusterCard(props: Cluster) {
|
||||||
onClick={e => openClusterPage(e)}
|
onClick={e => openClusterPage(e)}
|
||||||
onMouseDown={e => onMouseDown(e)}
|
onMouseDown={e => onMouseDown(e)}
|
||||||
onContextMenu={e => openContextMenu(e)}
|
onContextMenu={e => openContextMenu(e)}
|
||||||
ref={ref}
|
ref={mergeRefs(
|
||||||
|
ref,
|
||||||
|
// sortable
|
||||||
|
)}
|
||||||
class="relative h-[152px] group flex flex-col rounded-xl bg-component-bg hover:bg-component-bg-hover active:bg-component-bg-pressed border border-gray-05"
|
class="relative h-[152px] group flex flex-col rounded-xl bg-component-bg hover:bg-component-bg-hover active:bg-component-bg-pressed border border-gray-05"
|
||||||
>
|
>
|
||||||
<div class="flex-1 relative overflow-hidden rounded-t-xl">
|
<div class="flex-1 relative overflow-hidden rounded-t-xl">
|
||||||
|
|
@ -239,11 +396,19 @@ interface ClusterGroupProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function ClusterGroup(props: ClusterGroupProps) {
|
function ClusterGroup(props: ClusterGroupProps) {
|
||||||
|
// const droppable = createDroppable(props.title);
|
||||||
|
// const ids = createMemo(() => props.clusters.map(cluster => cluster.uuid));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="flex flex-col gap-y-4">
|
<div
|
||||||
|
// ref={droppable}
|
||||||
|
class="flex flex-col gap-y-4"
|
||||||
|
>
|
||||||
<h4>{props.title}</h4>
|
<h4>{props.title}</h4>
|
||||||
<div class="grid grid-cols-4 2xl:grid-cols-6 gap-4">
|
<div class="grid grid-cols-4 2xl:grid-cols-6 gap-4 min-h-38">
|
||||||
|
{/* <SortableProvider ids={ids()}> */}
|
||||||
<For each={props.clusters}>{item => <ClusterCard {...item} />}</For>
|
<For each={props.clusters}>{item => <ClusterCard {...item} />}</For>
|
||||||
|
{/* </SortableProvider> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
20
apps/frontend/src/ui/pages/cluster/ClusterGame.tsx
Normal file
20
apps/frontend/src/ui/pages/cluster/ClusterGame.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import ScrollableContainer from '~ui/components/ScrollableContainer';
|
||||||
|
import Sidebar from '~ui/components/Sidebar';
|
||||||
|
import useClusterContext from '~ui/hooks/useCluster';
|
||||||
|
|
||||||
|
function ClusterGame() {
|
||||||
|
const cluster = useClusterContext();
|
||||||
|
|
||||||
|
// TODO: Save cluster settings
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sidebar.Page>
|
||||||
|
<h1>Game Running</h1>
|
||||||
|
<ScrollableContainer>
|
||||||
|
<p>TODO</p>
|
||||||
|
</ScrollableContainer>
|
||||||
|
</Sidebar.Page>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ClusterGame;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Route, useSearchParams } from '@solidjs/router';
|
import { type Navigator, Route, useSearchParams } from '@solidjs/router';
|
||||||
import type { ParentProps } from 'solid-js';
|
import type { ParentProps } from 'solid-js';
|
||||||
import { EyeIcon, File06Icon, Globe04Icon, Image03Icon, PackagePlusIcon, Settings04Icon } from '@untitled-theme/icons-solid';
|
import { EyeIcon, File06Icon, Globe04Icon, Image03Icon, PackagePlusIcon, Settings04Icon } from '@untitled-theme/icons-solid';
|
||||||
import Sidebar from '../../components/Sidebar';
|
import Sidebar from '../../components/Sidebar';
|
||||||
|
|
@ -9,6 +9,7 @@ import ClusterLogs from './ClusterLogs';
|
||||||
import ClusterMods from './ClusterMods';
|
import ClusterMods from './ClusterMods';
|
||||||
import ClusterScreenshots from './ClusterScreenshots';
|
import ClusterScreenshots from './ClusterScreenshots';
|
||||||
import ClusterSettings from './ClusterSettings';
|
import ClusterSettings from './ClusterSettings';
|
||||||
|
import ClusterGame from './ClusterGame';
|
||||||
import useClusterContext, { ClusterProvider } from '~ui/hooks/useCluster';
|
import useClusterContext, { ClusterProvider } from '~ui/hooks/useCluster';
|
||||||
|
|
||||||
function ClusterRoutes() {
|
function ClusterRoutes() {
|
||||||
|
|
@ -19,6 +20,7 @@ function ClusterRoutes() {
|
||||||
<Route path="/mods" component={ClusterMods} />
|
<Route path="/mods" component={ClusterMods} />
|
||||||
<Route path="/screenshots" component={ClusterScreenshots} />
|
<Route path="/screenshots" component={ClusterScreenshots} />
|
||||||
<Route path="/settings" component={ClusterSettings} />
|
<Route path="/settings" component={ClusterSettings} />
|
||||||
|
<Route path="/game" component={ClusterGame} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -45,6 +47,15 @@ function ClusterRoot(props: ParentProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ClusterRoot.open = function (navigate: Navigator, uuid: string) {
|
||||||
|
navigate(`/clusters/?id=${uuid}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
ClusterRoot.launch = function (navigate: Navigator, uuid: string) {
|
||||||
|
// TODO: Launch game page
|
||||||
|
navigate(`/clusters/game?id=${uuid}&launch=true`);
|
||||||
|
};
|
||||||
|
|
||||||
function ClusterSidebar() {
|
function ClusterSidebar() {
|
||||||
const [cluster] = useClusterContext();
|
const [cluster] = useClusterContext();
|
||||||
|
|
||||||
|
|
@ -60,6 +71,7 @@ function ClusterSidebar() {
|
||||||
[<Globe04Icon />, 'Worlds', '/worlds'],
|
[<Globe04Icon />, 'Worlds', '/worlds'],
|
||||||
[<File06Icon />, 'Logs', '/logs'],
|
[<File06Icon />, 'Logs', '/logs'],
|
||||||
[<Settings04Icon />, 'Game Settings', '/settings'],
|
[<Settings04Icon />, 'Game Settings', '/settings'],
|
||||||
|
// TODO: Add game page CONDITIONALLY
|
||||||
],
|
],
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,13 @@ export function upperFirst(object: any): string {
|
||||||
export function abbreviateNumber(n: number) {
|
export function abbreviateNumber(n: number) {
|
||||||
if (n < 1e3)
|
if (n < 1e3)
|
||||||
return `${n}`;
|
return `${n}`;
|
||||||
if (n >= 1e3 && n < 1e6)
|
else if (n >= 1e3 && n < 1e6)
|
||||||
return `${+(n / 1e3).toFixed(1)}K`;
|
return `${+(n / 1e3).toFixed(1)}K`;
|
||||||
if (n >= 1e6 && n < 1e9)
|
else if (n >= 1e6 && n < 1e9)
|
||||||
return `${+(n / 1e6).toFixed(1)}M`;
|
return `${+(n / 1e6).toFixed(1)}M`;
|
||||||
if (n >= 1e9 && n < 1e12)
|
else if (n >= 1e9 && n < 1e12)
|
||||||
return `${+(n / 1e9).toFixed(1)}B`;
|
return `${+(n / 1e9).toFixed(1)}B`;
|
||||||
if (n >= 1e12)
|
else if (n >= 1e12)
|
||||||
return `${+(n / 1e12).toFixed(1)}T`;
|
return `${+(n / 1e12).toFixed(1)}T`;
|
||||||
return `${n}`;
|
return `${n}`;
|
||||||
};
|
};
|
||||||
|
|
@ -23,6 +23,27 @@ export function getEnumMembers(obj: any): string[] {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function secondsToWords(seconds: number | bigint): string {
|
||||||
|
const n = Number(seconds);
|
||||||
|
|
||||||
|
if (n < 60)
|
||||||
|
return `${n} seconds`;
|
||||||
|
else if (n < (60 * 60))
|
||||||
|
return `${Math.floor(n / 60)} minutes`;
|
||||||
|
else if (n < (60 * 60 * 24))
|
||||||
|
return `${Math.floor(n / (60 * 60))} hours`;
|
||||||
|
else if (n < (60 * 60 * 24 * 7))
|
||||||
|
return `${Math.floor(n / (60 * 60 * 24))} days`;
|
||||||
|
else if (n < (60 * 60 * 24 * 30))
|
||||||
|
return `${Math.floor(n / (60 * 60 * 24 * 7))} weeks`;
|
||||||
|
else if (n < (60 * 60 * 24 * 365))
|
||||||
|
return `${Math.floor(n / (60 * 60 * 24 * 30))} months`;
|
||||||
|
else if (n < (60 * 60 * 24 * 365 * 10))
|
||||||
|
return `${Math.floor(n / (60 * 60 * 24 * 365))} years`;
|
||||||
|
|
||||||
|
return `${n} seconds`; // Hmmmmmm
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
upperFirst,
|
upperFirst,
|
||||||
abbreviateNumber,
|
abbreviateNumber,
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,25 @@ pub async fn list(clear: Option<bool>) -> crate::Result<Vec<Cluster>> {
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// get a Map of [`Cluster`] group names containing lists of [`Cluster`]s
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn list_grouped(clear: Option<bool>) -> crate::Result<HashMap<String, Vec<Cluster>>> {
|
||||||
|
// TODO: This can 100% be made better
|
||||||
|
let list = list(clear).await?;
|
||||||
|
let mut map = HashMap::<String, Vec<Cluster>>::new();
|
||||||
|
|
||||||
|
for cluster in list {
|
||||||
|
let group = cluster.meta.group.clone().unwrap_or(String::from("Ungrouped"));
|
||||||
|
if let Some(items) = map.get_mut(&group) {
|
||||||
|
items.push(cluster);
|
||||||
|
} else {
|
||||||
|
map.insert(group, vec![cluster]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(map)
|
||||||
|
}
|
||||||
|
|
||||||
/// run a Minecraft [`Cluster`] using the default credentials.
|
/// run a Minecraft [`Cluster`] using the default credentials.
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn run(path: &ClusterPath) -> crate::Result<Arc<RwLock<ProcessorChild>>> {
|
pub async fn run(path: &ClusterPath) -> crate::Result<Arc<RwLock<ProcessorChild>>> {
|
||||||
|
|
|
||||||
|
|
@ -257,7 +257,7 @@ pub struct ClusterMeta {
|
||||||
/// (Defaults to None if the cluster has never been played)
|
/// (Defaults to None if the cluster has never been played)
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub played_at: Option<DateTime<Utc>>,
|
pub played_at: Option<DateTime<Utc>>,
|
||||||
/// The overall time played stored as a [`u64`].
|
/// The overall time played in seconds stored as a [`u64`].
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub overall_played: u64,
|
pub overall_played: u64,
|
||||||
/// The recent time played stored as a [`u64`].
|
/// The recent time played stored as a [`u64`].
|
||||||
|
|
|
||||||
24
pnpm-lock.yaml
generated
24
pnpm-lock.yaml
generated
|
|
@ -95,13 +95,13 @@ importers:
|
||||||
version: 2.0.0-beta.16
|
version: 2.0.0-beta.16
|
||||||
'@tauri-apps/plugin-clipboard-manager':
|
'@tauri-apps/plugin-clipboard-manager':
|
||||||
specifier: github:tauri-apps/tauri-plugin-clipboard-manager#v2
|
specifier: github:tauri-apps/tauri-plugin-clipboard-manager#v2
|
||||||
version: https://codeload.github.com/tauri-apps/tauri-plugin-clipboard-manager/tar.gz/d503abbce40595d0af6b36c10439b85c640b4c53
|
version: https://codeload.github.com/tauri-apps/tauri-plugin-clipboard-manager/tar.gz/20e84cfbdebcd86bb1854846feb0aaff4481fbd0
|
||||||
'@tauri-apps/plugin-dialog':
|
'@tauri-apps/plugin-dialog':
|
||||||
specifier: 2.0.0-beta.8
|
specifier: 2.0.0-beta.8
|
||||||
version: 2.0.0-beta.8
|
version: 2.0.0-beta.8
|
||||||
'@tauri-apps/plugin-shell':
|
'@tauri-apps/plugin-shell':
|
||||||
specifier: github:tauri-apps/tauri-plugin-shell#v2
|
specifier: github:tauri-apps/tauri-plugin-shell#v2
|
||||||
version: https://codeload.github.com/tauri-apps/tauri-plugin-shell/tar.gz/adb938ddf434547ae35f74dc1a080908da03ea96
|
version: https://codeload.github.com/tauri-apps/tauri-plugin-shell/tar.gz/5cc36dadeaad0520111c6f5e32ecdbbae7cb1311
|
||||||
'@thisbeyond/solid-dnd':
|
'@thisbeyond/solid-dnd':
|
||||||
specifier: ^0.7.5
|
specifier: ^0.7.5
|
||||||
version: 0.7.5(solid-js@1.8.19)
|
version: 0.7.5(solid-js@1.8.19)
|
||||||
|
|
@ -1268,16 +1268,16 @@ packages:
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@tauri-apps/plugin-clipboard-manager@https://codeload.github.com/tauri-apps/tauri-plugin-clipboard-manager/tar.gz/d503abbce40595d0af6b36c10439b85c640b4c53':
|
'@tauri-apps/plugin-clipboard-manager@https://codeload.github.com/tauri-apps/tauri-plugin-clipboard-manager/tar.gz/20e84cfbdebcd86bb1854846feb0aaff4481fbd0':
|
||||||
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-clipboard-manager/tar.gz/d503abbce40595d0af6b36c10439b85c640b4c53}
|
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-clipboard-manager/tar.gz/20e84cfbdebcd86bb1854846feb0aaff4481fbd0}
|
||||||
version: 2.1.0-beta.5
|
version: 2.1.0-beta.6
|
||||||
|
|
||||||
'@tauri-apps/plugin-dialog@2.0.0-beta.8':
|
'@tauri-apps/plugin-dialog@2.0.0-beta.8':
|
||||||
resolution: {integrity: sha512-6iVhEBceWYLUgX6ygBc4/yJ1o/D4YwgoAGt7ebFUTjEyIhp79PeFtE8OTjdyJbtVOc6bAcDeAn4+pbWtSw9/4w==}
|
resolution: {integrity: sha512-6iVhEBceWYLUgX6ygBc4/yJ1o/D4YwgoAGt7ebFUTjEyIhp79PeFtE8OTjdyJbtVOc6bAcDeAn4+pbWtSw9/4w==}
|
||||||
|
|
||||||
'@tauri-apps/plugin-shell@https://codeload.github.com/tauri-apps/tauri-plugin-shell/tar.gz/adb938ddf434547ae35f74dc1a080908da03ea96':
|
'@tauri-apps/plugin-shell@https://codeload.github.com/tauri-apps/tauri-plugin-shell/tar.gz/5cc36dadeaad0520111c6f5e32ecdbbae7cb1311':
|
||||||
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-shell/tar.gz/adb938ddf434547ae35f74dc1a080908da03ea96}
|
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-shell/tar.gz/5cc36dadeaad0520111c6f5e32ecdbbae7cb1311}
|
||||||
version: 2.0.0-beta.8
|
version: 2.0.0-beta.9
|
||||||
|
|
||||||
'@testing-library/dom@10.4.0':
|
'@testing-library/dom@10.4.0':
|
||||||
resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==}
|
resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==}
|
||||||
|
|
@ -4565,17 +4565,17 @@ snapshots:
|
||||||
'@tauri-apps/cli-win32-ia32-msvc': 2.0.0-beta.23
|
'@tauri-apps/cli-win32-ia32-msvc': 2.0.0-beta.23
|
||||||
'@tauri-apps/cli-win32-x64-msvc': 2.0.0-beta.23
|
'@tauri-apps/cli-win32-x64-msvc': 2.0.0-beta.23
|
||||||
|
|
||||||
'@tauri-apps/plugin-clipboard-manager@https://codeload.github.com/tauri-apps/tauri-plugin-clipboard-manager/tar.gz/d503abbce40595d0af6b36c10439b85c640b4c53':
|
'@tauri-apps/plugin-clipboard-manager@https://codeload.github.com/tauri-apps/tauri-plugin-clipboard-manager/tar.gz/20e84cfbdebcd86bb1854846feb0aaff4481fbd0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tauri-apps/api': 2.0.0-beta.15
|
'@tauri-apps/api': 2.0.0-beta.16
|
||||||
|
|
||||||
'@tauri-apps/plugin-dialog@2.0.0-beta.8':
|
'@tauri-apps/plugin-dialog@2.0.0-beta.8':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tauri-apps/api': 2.0.0-beta.16
|
'@tauri-apps/api': 2.0.0-beta.16
|
||||||
|
|
||||||
'@tauri-apps/plugin-shell@https://codeload.github.com/tauri-apps/tauri-plugin-shell/tar.gz/adb938ddf434547ae35f74dc1a080908da03ea96':
|
'@tauri-apps/plugin-shell@https://codeload.github.com/tauri-apps/tauri-plugin-shell/tar.gz/5cc36dadeaad0520111c6f5e32ecdbbae7cb1311':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tauri-apps/api': 2.0.0-beta.15
|
'@tauri-apps/api': 2.0.0-beta.16
|
||||||
|
|
||||||
'@testing-library/dom@10.4.0':
|
'@testing-library/dom@10.4.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue