diff --git a/apps/frontend/src/ui/pages/Home.tsx b/apps/frontend/src/ui/pages/Home.tsx index ffdca9e2..384c6123 100644 --- a/apps/frontend/src/ui/pages/Home.tsx +++ b/apps/frontend/src/ui/pages/Home.tsx @@ -1,5 +1,6 @@ import { CheckIcon, + ChevronRightIcon, DotsVerticalIcon, EyeIcon, PlayIcon, @@ -7,7 +8,7 @@ import { SearchMdIcon, } from '@untitled-theme/icons-solid'; import type { Accessor, Setter } from 'solid-js'; -import { For, Show, createMemo, createSignal } from 'solid-js'; +import { For, Show, createEffect, createMemo, createSignal } from 'solid-js'; import { useNavigate } from '@solidjs/router'; import { DragDropProvider, @@ -24,11 +25,12 @@ import ContextMenu from '../components/overlay/ContextMenu'; import Button from '../components/base/Button'; import Tag from '../components/base/Tag'; import TextField from '../components/base/TextField'; +import ClusterRoot from './cluster/ClusterRoot'; import ClusterCover from '~ui/components/game/ClusterCover'; import type { Cluster } from '~bindings'; import useCommand from '~ui/hooks/useCommand'; import { bridge } from '~imports'; -import { upperFirst } from '~utils/primitives'; +import { secondsToWords, upperFirst } from '~utils/primitives'; import { useClusterModalController } from '~ui/components/overlay/cluster/ClusterCreationModal'; type GroupedClusters = Record; @@ -50,71 +52,71 @@ function HomePage() { refetch(); } - function _getContainerByCluster(id: string) { - const list = clusters(); + // function getContainerByCluster(id: string) { + // const list = clusters(); - if (list === undefined) - return undefined; + // if (list === undefined) + // return undefined; - if (list[id] !== undefined) - return id; + // if (list[id] !== undefined) + // return id; - for (const [key, value] of Object.entries(list)) - if (value.find(cluster => cluster.uuid === id)) - return key; + // for (const [key, value] of Object.entries(list)) + // if (value.find(cluster => cluster.uuid === id)) + // return key; - return undefined; - } + // return undefined; + // } - function move(_fromId: string, _toId: string) { - // const from = getContainerByCluster(fromId); - // const to = getContainerByCluster(toId); + // function move(fromId: string, toId: string) { + // const from = getContainerByCluster(fromId); + // const to = getContainerByCluster(toId); - // console.log(from, to); + // console.log(from, to); - // if (from === undefined || to === undefined) - // return; + // 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 (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; + // if (fromIndex === toIndex) + // return; - // const newList = [...fromList]; - // const [removed] = newList.splice(fromIndex, 1); - // newList.splice(toIndex, 0, removed); + // 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); + // 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 newListFrom = [...fromList]; + // const newListTo = [...toList]; - // const [removed] = newListFrom.splice(fromIndex, 1); - // newListTo.splice(toIndex, 0, removed); + // const [removed] = newListFrom.splice(fromIndex, 1); + // newListTo.splice(toIndex, 0, removed); - // mutate({ ...list, [from]: newListFrom, [to]: newListTo }); - // } - } + // 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); - }; + // const onDragEnd: DragEventHandler = (event) => { + // if (event.draggable && event.droppable) + // move(event.draggable.id as string, event.droppable.id as string); + // }; return (
- +
@@ -133,21 +135,21 @@ function HomePage() { 0} children={( - - + // + // - - {([group, clusters]) => ( - - )} - + + {([group, clusters]) => ( + + )} + - - {draggable => ( - draggable?.node.cloneNode(true) - )} - - + // + // {draggable => ( + // draggable?.node.cloneNode(true) + // )} + // + // )} fallback={(
@@ -180,7 +182,11 @@ function OneConfigLogo() { ); } -function Banner() { +interface BannerProps { + clusters: GroupedClusters | undefined; +}; + +function Banner(props: BannerProps) { // TODO: Banner information /** * If there are any clusters, display the most recent cluster name + some statistics as the "description". @@ -190,29 +196,89 @@ function Banner() { * * If there are no clusters, display a generic background with the button action creating a new cluster. */ + const [cluster, setCluster] = createSignal(); + 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 (
-

Building worlds

-

DEV BUILD - NOT FINAL

+

{cluster()?.meta.name || 'Create a cluster'}

+ +

+ You've played + {' '} + + {cluster()!.meta.mc_version} + {' '} + {upperFirst(cluster()!.meta.loader) || 'Unknown'} + + {' '} + for + {' '} + {secondsToWords((cluster()!.meta.overall_played || 0n))} + . +

+
-
- } /> - }>Verified + {/* TODO: These tags */} + {/* } /> + }>Verified */}
@@ -230,10 +296,6 @@ interface ClusterCardContextMenuProps { function ClusterCardContextMenu(props: ClusterCardContextMenuProps) { const navigate = useNavigate(); - function viewCluster() { - navigate(`/clusters/?id=${props.id}`); - } - return ( } text="View" - onClick={() => viewCluster()} + onClick={() => ClusterRoot.open(navigate, props.id)} /> @@ -261,7 +323,7 @@ function ClusterCard(props: Cluster) { const navigate = useNavigate(); const [pos, setPos] = createSignal({ x: 0, y: 0 }); const [contextMenuVisible, setContextMenuVisible] = createSignal(false); - const sortable = createSortable(props.uuid, props); + // const sortable = createSortable(props.uuid, props); let ref!: HTMLDivElement; function openClusterPage(_e: MouseEvent) { @@ -284,7 +346,10 @@ function ClusterCard(props: Cluster) { onClick={e => openClusterPage(e)} onMouseDown={e => onMouseDown(e)} onContextMenu={e => openContextMenu(e)} - ref={mergeRefs(ref, sortable)} + 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" >
@@ -331,16 +396,19 @@ interface ClusterGroupProps { } function ClusterGroup(props: ClusterGroupProps) { - const droppable = createDroppable(props.title); - const ids = createMemo(() => props.clusters.map(cluster => cluster.uuid)); + // const droppable = createDroppable(props.title); + // const ids = createMemo(() => props.clusters.map(cluster => cluster.uuid)); return ( -
+

{props.title}

- - {item => } - + {/* */} + {item => } + {/* */}
); diff --git a/apps/frontend/src/ui/pages/cluster/ClusterGame.tsx b/apps/frontend/src/ui/pages/cluster/ClusterGame.tsx new file mode 100644 index 00000000..fe54ad11 --- /dev/null +++ b/apps/frontend/src/ui/pages/cluster/ClusterGame.tsx @@ -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 ( + +

Game Running

+ +

TODO

+
+
+ ); +} + +export default ClusterGame; diff --git a/apps/frontend/src/ui/pages/cluster/ClusterRoot.tsx b/apps/frontend/src/ui/pages/cluster/ClusterRoot.tsx index 366d0b1c..a01824cc 100644 --- a/apps/frontend/src/ui/pages/cluster/ClusterRoot.tsx +++ b/apps/frontend/src/ui/pages/cluster/ClusterRoot.tsx @@ -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 { EyeIcon, File06Icon, Globe04Icon, Image03Icon, PackagePlusIcon, Settings04Icon } from '@untitled-theme/icons-solid'; import Sidebar from '../../components/Sidebar'; @@ -9,6 +9,7 @@ import ClusterLogs from './ClusterLogs'; import ClusterMods from './ClusterMods'; import ClusterScreenshots from './ClusterScreenshots'; import ClusterSettings from './ClusterSettings'; +import ClusterGame from './ClusterGame'; import useClusterContext, { ClusterProvider } from '~ui/hooks/useCluster'; function ClusterRoutes() { @@ -19,6 +20,7 @@ function ClusterRoutes() { + ); } @@ -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() { const [cluster] = useClusterContext(); @@ -60,6 +71,7 @@ function ClusterSidebar() { [, 'Worlds', '/worlds'], [, 'Logs', '/logs'], [, 'Game Settings', '/settings'], + // TODO: Add game page CONDITIONALLY ], }} /> diff --git a/apps/frontend/src/utils/primitives.ts b/apps/frontend/src/utils/primitives.ts index ed80e050..26bb416f 100644 --- a/apps/frontend/src/utils/primitives.ts +++ b/apps/frontend/src/utils/primitives.ts @@ -6,13 +6,13 @@ export function upperFirst(object: any): string { export function abbreviateNumber(n: number) { if (n < 1e3) return `${n}`; - if (n >= 1e3 && n < 1e6) + else if (n >= 1e3 && n < 1e6) return `${+(n / 1e3).toFixed(1)}K`; - if (n >= 1e6 && n < 1e9) + else if (n >= 1e6 && n < 1e9) return `${+(n / 1e6).toFixed(1)}M`; - if (n >= 1e9 && n < 1e12) + else if (n >= 1e9 && n < 1e12) return `${+(n / 1e9).toFixed(1)}B`; - if (n >= 1e12) + else if (n >= 1e12) return `${+(n / 1e12).toFixed(1)}T`; 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 { upperFirst, abbreviateNumber, diff --git a/packages/core/src/store/clusters.rs b/packages/core/src/store/clusters.rs index 45275f29..7c333969 100644 --- a/packages/core/src/store/clusters.rs +++ b/packages/core/src/store/clusters.rs @@ -257,7 +257,7 @@ pub struct ClusterMeta { /// (Defaults to None if the cluster has never been played) #[serde(skip_serializing_if = "Option::is_none")] pub played_at: Option>, - /// The overall time played stored as a [`u64`]. + /// The overall time played in seconds stored as a [`u64`]. #[serde(default)] pub overall_played: u64, /// The recent time played stored as a [`u64`].