diff --git a/apps/desktop/src-tauri/build.rs b/apps/desktop/src-tauri/build.rs index 7b318bd1..02f8e137 100644 --- a/apps/desktop/src-tauri/build.rs +++ b/apps/desktop/src-tauri/build.rs @@ -12,6 +12,7 @@ fn main() { "remove_user", "launch_cluster", "create_cluster", + "remove_cluster", "get_clusters", "get_cluster", "get_manifest", diff --git a/apps/desktop/src-tauri/permissions/onelauncher/default.toml b/apps/desktop/src-tauri/permissions/onelauncher/default.toml index 29dee761..28fe4c82 100644 --- a/apps/desktop/src-tauri/permissions/onelauncher/default.toml +++ b/apps/desktop/src-tauri/permissions/onelauncher/default.toml @@ -8,6 +8,7 @@ permissions = [ "allow-remove-user", "allow-launch-cluster", "allow-create-cluster", + "allow-remove-cluster", "allow-get-clusters", "allow-get-cluster", "allow-get-manifest", diff --git a/apps/desktop/src-tauri/src/api/commands.rs b/apps/desktop/src-tauri/src/api/commands.rs index 25318b25..995d710a 100644 --- a/apps/desktop/src-tauri/src/api/commands.rs +++ b/apps/desktop/src-tauri/src/api/commands.rs @@ -25,6 +25,7 @@ macro_rules! collect_commands { get_user, remove_user, create_cluster, + remove_cluster, get_cluster, get_clusters, get_settings, @@ -71,6 +72,13 @@ pub async fn create_cluster(props: CreateCluster) -> Result { } } +#[specta::specta] +#[tauri::command] +pub async fn remove_cluster(uuid: Uuid) -> Result<(), String> { + let path = cluster::get_by_uuid(uuid, None).await?.ok_or("Cluster does not exist")?.cluster_path(); + Ok(cluster::remove(&path).await?) +} + // #[specta::specta] // #[tauri::command] // pub fn update_cluster(cluster: Cluster) -> Result<(), String> { diff --git a/apps/desktop/src/ui/components/WindowFrame.tsx b/apps/desktop/src/ui/components/WindowFrame.tsx index 1d682563..25eb0c63 100644 --- a/apps/desktop/src/ui/components/WindowFrame.tsx +++ b/apps/desktop/src/ui/components/WindowFrame.tsx @@ -56,6 +56,7 @@ function WindowFrame() { title="Close OneLauncher?" visible={isModalVisible} setVisible={setModalVisible} + zIndex={999999} buttons={[ , , diff --git a/apps/desktop/src/ui/components/base/Button.module.scss b/apps/desktop/src/ui/components/base/Button.module.scss index 8cabc4f5..b282cde7 100644 --- a/apps/desktop/src/ui/components/base/Button.module.scss +++ b/apps/desktop/src/ui/components/base/Button.module.scss @@ -12,7 +12,7 @@ @apply bg-component-bg hover:bg-component-bg-hover active:bg-component-bg-pressed disabled:bg-component-bg-disabled; } - &__danger { + &__danger, &__iconDanger { @apply bg-danger hover:bg-danger-hover active:bg-danger-pressed disabled:bg-danger-disabled; } @@ -20,8 +20,8 @@ @apply hover:bg-gray-05 [&[aria-checked="true"]]:bg-gray-10 active:bg-gray-10; } - &__icon, &__iconSecondary { - @apply p-1.5 hover:bg-gray-05 [&[aria-checked="true"]]:bg-gray-10 active:bg-gray-10 aspect-square box-border w-8 h-8; + &__icon, &__iconSecondary, &__iconDanger { + @apply p-1.5 aspect-square box-border w-8 h-8; &.large { @apply w-9 h-9; @@ -33,6 +33,10 @@ & > * { @apply w-full h-full stroke-fg-primary; } + } + + &__icon, &__iconSecondary { + @apply hover:bg-gray-05 [&[aria-checked="true"]]:bg-gray-10 active:bg-gray-10; &:hover > * { @apply stroke-fg-primary-hover; diff --git a/apps/desktop/src/ui/components/base/Button.tsx b/apps/desktop/src/ui/components/base/Button.tsx index 73c24606..abeb5c4c 100644 --- a/apps/desktop/src/ui/components/base/Button.tsx +++ b/apps/desktop/src/ui/components/base/Button.tsx @@ -5,7 +5,7 @@ import styles from './Button.module.scss'; type ButtonProps = JSX.ButtonHTMLAttributes & ParentProps & { iconLeft?: JSX.Element; iconRight?: JSX.Element; - buttonStyle?: 'primary' | 'secondary' | 'danger' | 'icon' | 'iconSecondary' | 'ghost'; + buttonStyle?: 'primary' | 'secondary' | 'danger' | 'icon' | 'iconSecondary' | 'iconDanger' | 'ghost'; large?: boolean; }; diff --git a/apps/desktop/src/ui/components/overlay/FullscreenOverlay.tsx b/apps/desktop/src/ui/components/overlay/FullscreenOverlay.tsx index 8e0a1fbe..97e7b44c 100644 --- a/apps/desktop/src/ui/components/overlay/FullscreenOverlay.tsx +++ b/apps/desktop/src/ui/components/overlay/FullscreenOverlay.tsx @@ -25,10 +25,8 @@ function FullscreenOverlay(props: FullscreenOverlayProps) { style={{ 'z-index': props.zIndex || 1000 }} class={`fixed top-0 left-0 w-screen h-screen bg-black/60 backdrop-blur-sm backdrop-grayscale transition-opacity ${props.visible() ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'}`} > - {/* New element to prevent from having to call stopPropagation in child elements */} - {/*
onBackdropClick()} /> */} -
+
{props.children}
diff --git a/apps/desktop/src/ui/components/overlay/Modal.tsx b/apps/desktop/src/ui/components/overlay/Modal.tsx index 48af4169..79067356 100644 --- a/apps/desktop/src/ui/components/overlay/Modal.tsx +++ b/apps/desktop/src/ui/components/overlay/Modal.tsx @@ -27,11 +27,11 @@ function Modal(props: ModalProps) { mount={props.mount} zIndex={props.zIndex || 1000} > -
-
- {props.children} -
+ {/*
*/} +
+ {props.children}
+ {/*
*/} ); } @@ -56,7 +56,7 @@ Modal.Simple = function (props: ModalSimpleProps) {
{props.children}
-
+
{button => ( @@ -72,7 +72,7 @@ function ModalButton(button: { props: ModalButtonProps }) { <> {typeof button.props === 'string' ? ( - ) diff --git a/apps/desktop/src/ui/components/overlay/cluster/ClusterCreationModal.tsx b/apps/desktop/src/ui/components/overlay/cluster/ClusterCreationModal.tsx index 6c41fcb8..7efbd4e7 100644 --- a/apps/desktop/src/ui/components/overlay/cluster/ClusterCreationModal.tsx +++ b/apps/desktop/src/ui/components/overlay/cluster/ClusterCreationModal.tsx @@ -19,6 +19,7 @@ interface ClusterModalContextFunc { partialCluster: Accessor; setPartialCluster: Setter; updatePartialCluster: PartialClusterUpdateFunc; + start: () => Promise; // Returns true if its finished (created) successfully finish: () => void; visible: Accessor; setVisible: Setter; @@ -37,6 +38,8 @@ export function ClusterModalController(props: ParentProps) { const [step, setStep] = createSignal(ClusterModalStages.STAGE_1_PROVIDER); const [visible, setVisible] = createSignal(false); const [partialCluster, setPartialCluster] = createSignal({}); + const [inProgress, setInProgress] = createSignal(false); + const [error, setError] = createSignal(undefined); const updatePartialCluster: PartialClusterUpdateFunc = (key, value) => { setPartialCluster((prev) => { @@ -47,24 +50,54 @@ export function ClusterModalController(props: ParentProps) { }); }; - const finish = () => { + const start = () => { + setPartialCluster({}); + setStep(ClusterModalStages.STAGE_1_PROVIDER); + setError(undefined); + setInProgress(true); + setVisible(true); + + return new Promise((resolve, reject) => { + createEffect(() => { + const result = error(); + if (inProgress() === true) + return; + + if (result === undefined || result.length === 0) + resolve(); + else + reject(result); + }); + }); + }; + + const finish = async () => { const untracked = untrack(partialCluster); if (untracked.name === undefined || untracked.mc_version === undefined || untracked.mod_loader === undefined) throw new Error('Cluster is missing required fields'); - bridge.commands.createCluster({ - name: untracked.name!, - mod_loader: untracked.mod_loader!, - mc_version: untracked.mc_version!, - // TODO: Implement the rest of the fields - icon: null, - icon_url: null, - loader_version: null, - package_data: null, - skip: null, - skip_watch: null, - }); + setVisible(false); + + try { + await bridge.commands.createCluster({ + name: untracked.name!, + mod_loader: untracked.mod_loader!, + mc_version: untracked.mc_version!, + // TODO: Implement the rest of the fields + icon: null, + icon_url: null, + loader_version: null, + package_data: null, + skip: null, + skip_watch: null, + }); + } + catch (err) { + console.log(err); + } + + setInProgress(false); }; const stepper: ClusterModalContextFunc = { @@ -73,6 +106,7 @@ export function ClusterModalController(props: ParentProps) { partialCluster, setPartialCluster, updatePartialCluster, + start, finish, visible, setVisible, diff --git a/apps/desktop/src/ui/pages/Home.tsx b/apps/desktop/src/ui/pages/Home.tsx index c293340c..cff5bcd1 100644 --- a/apps/desktop/src/ui/pages/Home.tsx +++ b/apps/desktop/src/ui/pages/Home.tsx @@ -26,7 +26,14 @@ function HomePage() { const controller = useClusterModalController(); async function newCluster() { - controller.setVisible(true); + try { + await controller.start(); + } + catch (err) { + console.error(err); + } + + refetch(); } return ( diff --git a/apps/desktop/src/ui/pages/cluster/cluster/ClusterLogs.tsx b/apps/desktop/src/ui/pages/cluster/ClusterLogs.tsx similarity index 100% rename from apps/desktop/src/ui/pages/cluster/cluster/ClusterLogs.tsx rename to apps/desktop/src/ui/pages/cluster/ClusterLogs.tsx diff --git a/apps/desktop/src/ui/pages/cluster/cluster/ClusterMods.tsx b/apps/desktop/src/ui/pages/cluster/ClusterMods.tsx similarity index 100% rename from apps/desktop/src/ui/pages/cluster/cluster/ClusterMods.tsx rename to apps/desktop/src/ui/pages/cluster/ClusterMods.tsx diff --git a/apps/desktop/src/ui/pages/cluster/ClusterOverview.tsx b/apps/desktop/src/ui/pages/cluster/ClusterOverview.tsx new file mode 100644 index 00000000..aa5e6cfa --- /dev/null +++ b/apps/desktop/src/ui/pages/cluster/ClusterOverview.tsx @@ -0,0 +1,153 @@ +import { PlayIcon, Share07Icon, Trash01Icon } from '@untitled-theme/icons-solid'; +import { type Accessor, type Setter, createEffect, createSignal, on } from 'solid-js'; +import { useNavigate } from '@solidjs/router'; +import ClusterCover from '../../components/game/ClusterCover'; +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/primitives'; +import useClusterContext from '~ui/hooks/useCluster'; +import Modal from '~ui/components/overlay/Modal'; +import { bridge } from '~imports'; + +function ClusterOverview() { + const cluster = useClusterContext(); + const [deleteVisible, setDeleteVisible] = createSignal(false); + const navigate = useNavigate(); + + async function deleteCluster() { + await bridge.commands.removeCluster(cluster.uuid); + navigate('/'); + } + + return ( + +

Overview

+ + setDeleteVisible(true)} /> + + + +
+ ); +} + +interface DeleteModalProps { + visible: Accessor; + setVisible: Setter; + deleteCluster: () => void; +}; +function DeleteModal(props: DeleteModalProps) { + const [timeLeft, setTimeLeft] = createSignal(3); + + createEffect(on(() => props.visible(), (visible) => { + if (visible !== true) + return; + + setTimeLeft(3); + const interval = setInterval(() => { + setTimeLeft((prev) => { + const next = prev - 1; + + if (next <= 0) + clearInterval(interval); + + return next; + }); + }, 1000); + })); + + return ( + props.setVisible(false)} + />, +
+
+ ); +} + +export default ClusterOverview; diff --git a/apps/desktop/src/ui/pages/cluster/ClusterRoot.tsx b/apps/desktop/src/ui/pages/cluster/ClusterRoot.tsx index ecb1b01c..c957af6c 100644 --- a/apps/desktop/src/ui/pages/cluster/ClusterRoot.tsx +++ b/apps/desktop/src/ui/pages/cluster/ClusterRoot.tsx @@ -4,11 +4,11 @@ import { EyeIcon, File06Icon, Globe04Icon, Image03Icon, Settings04Icon } from '@ import Sidebar from '../../components/Sidebar'; import AnimatedRoutes from '../../components/AnimatedRoutes'; import ErrorBoundary from '../../components/ErrorBoundary'; -import ClusterOverview from './cluster/ClusterOverview'; -import ClusterLogs from './cluster/ClusterLogs'; -import ClusterMods from './cluster/ClusterMods'; -import ClusterScreenshots from './cluster/ClusterScreenshots'; -import ClusterSettings from './cluster/ClusterSettings'; +import ClusterOverview from './ClusterOverview'; +import ClusterLogs from './ClusterLogs'; +import ClusterMods from './ClusterMods'; +import ClusterScreenshots from './ClusterScreenshots'; +import ClusterSettings from './ClusterSettings'; import useClusterContext, { ClusterProvider } from '~ui/hooks/useCluster'; function ClusterRoutes() { diff --git a/apps/desktop/src/ui/pages/cluster/cluster/ClusterScreenshots.tsx b/apps/desktop/src/ui/pages/cluster/ClusterScreenshots.tsx similarity index 100% rename from apps/desktop/src/ui/pages/cluster/cluster/ClusterScreenshots.tsx rename to apps/desktop/src/ui/pages/cluster/ClusterScreenshots.tsx diff --git a/apps/desktop/src/ui/pages/cluster/cluster/ClusterSettings.tsx b/apps/desktop/src/ui/pages/cluster/ClusterSettings.tsx similarity index 100% rename from apps/desktop/src/ui/pages/cluster/cluster/ClusterSettings.tsx rename to apps/desktop/src/ui/pages/cluster/ClusterSettings.tsx diff --git a/apps/desktop/src/ui/pages/cluster/cluster/ClusterOverview.tsx b/apps/desktop/src/ui/pages/cluster/cluster/ClusterOverview.tsx deleted file mode 100644 index 16d285ab..00000000 --- a/apps/desktop/src/ui/pages/cluster/cluster/ClusterOverview.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { PlayIcon, Share07Icon } from '@untitled-theme/icons-solid'; -import ClusterCover from '../../../components/game/ClusterCover'; -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/primitives'; -import useClusterContext from '~ui/hooks/useCluster'; - -function ClusterOverview() { - const cluster = useClusterContext(); - - return ( - -

Overview

- - - -
- ); -} - -function Banner(props: Cluster) { - return ( -
-
- -
- -
-
-

{props.meta.name}

- - - {upperFirst(props.meta.loader || 'unknown')} - {props.meta.loader_version && {props.meta.loader_version.id}} - {props.meta.mc_version} - -
- - Played for - {' '} - {((props.meta.overall_played || 0n)).toString()} - {' '} - hours - {/* TODO: Ask Pauline if this is measured in seconds or milliseconds */} - -
- -
-
-
- ); -} - -export default ClusterOverview; diff --git a/crates/core/src/api/cluster/create.rs b/crates/core/src/api/cluster/create.rs index 3dc92788..2a53d4c2 100644 --- a/crates/core/src/api/cluster/create.rs +++ b/crates/core/src/api/cluster/create.rs @@ -107,9 +107,14 @@ pub async fn create_cluster( .await?; } - // if !skip.unwrap_or(false) { - // crate::game::install_minecraft(&cluster, None, false).await?; - // } + if !skip.unwrap_or(false) { + let clone = cluster.clone(); + tokio::task::spawn(async move { + if let Err(err) = crate::game::install_minecraft(&clone, None, false).await { + tracing::error!("failed to install Minecraft while creating cluster {}: {}", cluster.uuid, err); + } + }); + } State::sync().await?;