feat: SettingsContext + Temporary fix for failing to fetch manifest

This commit is contained in:
LynithDev 2024-06-16 21:54:27 +02:00
parent 9cd0cebd06
commit 19ade5dc81
17 changed files with 175 additions and 138 deletions

View file

@ -7,6 +7,7 @@ use crate::proxy::ClusterPayloadType;
pub use crate::store::{Cluster, ClusterPath, JavaOptions, PackageData, State};
use crate::utils::io::{self, canonicalize};
use crate::{cluster, package};
use anyhow::anyhow;
use interpulse::api::modded::LoaderVersion;
use std::path::PathBuf;
@ -226,6 +227,10 @@ pub(crate) async fn get_loader_version(
_ => return Err(CreateClusterError::MissingManifest(loader.to_string()).into()),
};
let loader_meta = loader_meta
.to_owned()
.ok_or(anyhow!("missing loader meta"))?;
let loaders = &loader_meta
.game_versions
.iter()

View file

@ -19,6 +19,7 @@ use std::future::Future;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use anyhow::anyhow;
use tokio::process::Command;
use tokio::sync::RwLock;
@ -311,8 +312,9 @@ pub async fn get_optimal_java_version(path: &ClusterPath) -> crate::Result<Optio
let state = State::get().await?;
if let Some(cluster) = get(path, None).await? {
let metadata = state.metadata.read().await;
let version = metadata
.minecraft
let minecraft_metadata = metadata.minecraft.to_owned().ok_or(anyhow!("misisng minecraft metadata"))?;
let version = minecraft_metadata
.versions
.iter()
.find(|it| it.id == cluster.meta.mc_version)

View file

@ -1,6 +1,7 @@
//! Metadata management interface
use crate::State;
use anyhow::anyhow;
pub use interpulse::api::minecraft::VersionManifest;
pub use interpulse::api::modded::Manifest;
@ -8,7 +9,7 @@ pub use interpulse::api::modded::Manifest;
#[tracing::instrument]
pub async fn get_minecraft_versions() -> crate::Result<VersionManifest> {
let state = State::get().await?;
let meta = state.metadata.read().await.minecraft.clone();
let meta = state.metadata.read().await.minecraft.clone().ok_or(anyhow!("missing minecraft metadata"))?;
Ok(meta)
}
@ -17,7 +18,7 @@ pub async fn get_minecraft_versions() -> crate::Result<VersionManifest> {
#[tracing::instrument]
pub async fn get_fabric_versions() -> crate::Result<Manifest> {
let state = State::get().await?;
let meta = state.metadata.read().await.fabric.clone();
let meta = state.metadata.read().await.fabric.clone().ok_or(anyhow!("missing fabric metadata"))?;
Ok(meta)
}
@ -26,7 +27,7 @@ pub async fn get_fabric_versions() -> crate::Result<Manifest> {
#[tracing::instrument]
pub async fn get_quilt_versions() -> crate::Result<Manifest> {
let state = State::get().await?;
let meta = state.metadata.read().await.quilt.clone();
let meta = state.metadata.read().await.quilt.clone().ok_or(anyhow!("missing quilt metadata"))?;
Ok(meta)
}
@ -35,7 +36,7 @@ pub async fn get_quilt_versions() -> crate::Result<Manifest> {
#[tracing::instrument]
pub async fn get_forge_versions() -> crate::Result<Manifest> {
let state = State::get().await?;
let meta = state.metadata.read().await.forge.clone();
let meta = state.metadata.read().await.forge.clone().ok_or(anyhow!("missing forge metadata"))?;
Ok(meta)
}
@ -44,7 +45,7 @@ pub async fn get_forge_versions() -> crate::Result<Manifest> {
#[tracing::instrument]
pub async fn get_neoforge_versions() -> crate::Result<Manifest> {
let state = State::get().await?;
let meta = state.metadata.read().await.neoforge.clone();
let meta = state.metadata.read().await.neoforge.clone().ok_or(anyhow!("missing neoforce metadata"))?;
Ok(meta)
}
@ -53,7 +54,7 @@ pub async fn get_neoforge_versions() -> crate::Result<Manifest> {
#[tracing::instrument]
pub async fn get_legacy_fabric_versions() -> crate::Result<Manifest> {
let state = State::get().await?;
let meta = state.metadata.read().await.legacy_fabric.clone();
let meta = state.metadata.read().await.legacy_fabric.clone().ok_or(anyhow!("missing legacyfabric metadata"))?;
Ok(meta)
}

View file

@ -11,6 +11,7 @@ use crate::store::{
};
use crate::utils::io::{self, IOError};
use anyhow::anyhow;
use chrono::Utc;
use interpulse as ip;
use interpulse::api::minecraft::{RuleAction, VersionInfo};
@ -71,8 +72,9 @@ pub async fn install_minecraft(
let instance_path = &io::canonicalize(cluster.get_full_path().await?)?;
let metadata = state.metadata.read().await;
let version_idx = metadata
.minecraft
let version_manifest = metadata.minecraft.to_owned().ok_or(anyhow!("missing metadata"))?;
let version_idx = version_manifest
.versions
.iter()
.position(|g| g.id == cluster.meta.mc_version)
@ -81,11 +83,10 @@ pub async fn install_minecraft(
cluster.meta.mc_version
))?;
let version = &metadata.minecraft.versions[version_idx];
let version = &version_manifest.versions[version_idx];
let updated = version_idx
<= metadata
.minecraft
.versions
<= version_manifest
.versions
.iter()
.position(|g| g.id == "22w16a")
.unwrap_or(0); // LWJGL patching
@ -273,8 +274,9 @@ pub async fn launch_minecraft(
let instance_path = cluster.get_full_path().await?;
let instance_path = &io::canonicalize(instance_path)?;
let version_index = metadata
.minecraft
let version_manifest = metadata.minecraft.to_owned().ok_or(anyhow!("missing minecraft metadata"))?;
let version_index = version_manifest
.versions
.iter()
.position(|it| it.id == cluster.meta.mc_version)
@ -282,10 +284,9 @@ pub async fn launch_minecraft(
"invalid game version {}",
cluster.meta.mc_version
))?;
let version = &metadata.minecraft.versions[version_index];
let version = &version_manifest.versions[version_index];
let updated = version_index
<= metadata
.minecraft
<= version_manifest
.versions
.iter()
.position(|x| x.id == "22w16a")

View file

@ -8,6 +8,7 @@ use crate::utils::java;
use crate::utils::java::JavaVersion;
/// A HashMap of all located and installed available Java versions
#[cfg_attr(feature = "tauri", derive(specta::Type))]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct JavaVersions(HashMap<String, JavaVersion>);

View file

@ -24,17 +24,17 @@ const METADATA_URL: &str = "localhost:5543";
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Metadata {
/// The [`MinecraftManifest`] associated with core Minecraft versions.
pub minecraft: MinecraftManifest,
pub minecraft: Option<MinecraftManifest>,
/// The [`ModdedManifest`] associated with core Fabric versions.
pub fabric: ModdedManifest,
pub fabric: Option<ModdedManifest>,
/// The [`ModdedManifest`] associated with core Quilt versions.
pub quilt: ModdedManifest,
pub quilt: Option<ModdedManifest>,
/// The [`ModdedManifest`] associated with core NeoForge versions.
pub neoforge: ModdedManifest,
pub neoforge: Option<ModdedManifest>,
/// The [`ModdedManifest`] associated with core Forge versions.
pub forge: ModdedManifest,
pub forge: Option<ModdedManifest>,
/// The [`ModdedManifest`] associated with core Legacy Fabric versions.
pub legacy_fabric: ModdedManifest,
pub legacy_fabric: Option<ModdedManifest>,
}
impl Metadata {
@ -45,41 +45,49 @@ impl Metadata {
/// Fetch all available metadata types and get a new [`Metadata`] structure.
pub async fn fetch() -> crate::Result<Self> {
let (minecraft, fabric, quilt, neoforge, forge, legacy_fabric) = tokio::try_join! {
async {
let url = Self::get_manifest("minecraft");
fetch_version_manifest(Some(&url)).await
},
async {
let url = Self::get_manifest("fabric");
fetch_modded_manifest(&url).await
},
async {
let url = Self::get_manifest("quilt");
fetch_modded_manifest(&url).await
},
async {
let url = Self::get_manifest("neo");
fetch_modded_manifest(&url).await
},
async {
let url = Self::get_manifest("forge");
fetch_modded_manifest(&url).await
},
async {
let url = Self::get_manifest("legacy-fabric");
fetch_modded_manifest(&url).await
},
}?;
// let (minecraft, fabric, quilt, neoforge, forge, legacy_fabric) = tokio::try_join! {
// async {
// let url = Self::get_manifest("minecraft");
// fetch_version_manifest(Some(&url)).await
// },
// async {
// let url = Self::get_manifest("fabric");
// fetch_modded_manifest(&url).await
// },
// async {
// let url = Self::get_manifest("quilt");
// fetch_modded_manifest(&url).await
// },
// async {
// let url = Self::get_manifest("neo");
// fetch_modded_manifest(&url).await
// },
// async {
// let url = Self::get_manifest("forge");
// fetch_modded_manifest(&url).await
// },
// async {
// let url = Self::get_manifest("legacy-fabric");
// fetch_modded_manifest(&url).await
// },
// }?;
Ok(Self {
minecraft,
fabric,
quilt,
neoforge,
forge,
legacy_fabric,
})
// Ok(Self {
// minecraft,
// fabric,
// quilt,
// neoforge,
// forge,
// legacy_fabric,
// })
Ok(Self {
minecraft: None,
fabric: None,
quilt: None,
neoforge: None,
forge: None,
legacy_fabric: None
})
}
/// Initialize the core Metadata manager.

View file

@ -10,6 +10,7 @@ use std::path::{Path, PathBuf};
const CURRENT_FORMAT_VERSION: u32 = 1;
/// A global settings state for the launcher.
#[cfg_attr(feature = "tauri", derive(specta::Type))]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Settings {
/// A OneLauncher [`Theme`] managed by the core GUI.
@ -123,6 +124,7 @@ impl Settings {
}
/// A OneLauncher theme managed by the GUI.
#[cfg_attr(feature = "tauri", derive(specta::Type))]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Theme {

View file

@ -12,6 +12,8 @@ fn main() {
"get_clusters",
"get_cluster",
"get_manifest",
"get_settings",
"change_settings"
]),
),
)

View file

@ -8,4 +8,6 @@ permissions = [
"allow-get-cluster",
"allow-get-manifest",
"allow-login-msa",
"allow-get-settings",
"allow-change-settings"
]

View file

@ -1,9 +1,10 @@
use std::{collections::HashMap, path::PathBuf, str::FromStr};
use chrono::DateTime;
use onelauncher::{cluster, data::{ClusterMeta, Loader, PackageData}, store::{Cluster, ClusterPath}, State};
use onelauncher::{cluster, data::{ClusterMeta, Loader, PackageData, Settings}, store::{Cluster, ClusterPath}, State};
use serde::{Deserialize, Serialize};
use specta::Type;
use tokio::sync::RwLock;
use uuid::Uuid;
#[macro_export]
@ -17,7 +18,9 @@ macro_rules! collect_commands {
is_dev,
create_cluster,
get_cluster,
get_clusters
get_clusters,
get_settings,
change_settings
])
}
};
@ -117,3 +120,16 @@ pub async fn get_clusters() -> Result<HashMap<ClusterPath, Cluster>, String> {
// cluster::list(None).await.map_err(|op| op.into())
}
#[specta::specta]
#[tauri::command]
pub async fn get_settings() -> Result<Settings, String> {
Ok(State::get().await?.settings.read().await.clone())
}
#[specta::specta]
#[tauri::command]
pub async fn change_settings(settings: Settings) -> Result<(), String> {
State::get().await?.settings.write().await.clone_from(&settings);
Ok(())
}

View file

@ -70,7 +70,9 @@ pub async fn run_app<F: FnOnce(&mut tauri::App) + Send + 'static>(
.build(tauri::tauri_build_context!())
.expect("failed to build tauri application");
initialize_state(app.app_handle().clone()).await;
if let Err(err) = initialize_state(app.app_handle().clone()).await {
tracing::error!("{err}");
};
app.run(|_app_handle, _event| {})
}

View file

@ -5,6 +5,7 @@ import Navbar from './components/Navbar';
import ErrorBoundary from './components/ErrorBoundary';
import AnimatedRoutes from './components/AnimatedRoutes';
import NotificationOverlay from './components/overlay/notifications/NotificationOverlay';
import { SettingsProvider } from './hooks/useSettings';
import { bridge } from '~index';
function App(props: ParentProps) {
@ -15,26 +16,28 @@ function App(props: ParentProps) {
}
return (
<main class="flex flex-col bg-primary w-full min-h-screen overflow-hidden h-screen max-h-screen text-fg-primary">
<WindowFrame />
<div class="flex flex-col px-8">
<Navbar />
</div>
<div class="relative h-full w-full overflow-hidden">
<div class="absolute top-0 left-0 flex flex-col h-full w-full overflow-x-hidden">
<ErrorBoundary>
<OverlayScrollbarsComponent class="os-hide-horizontal-scrollbar absolute top-0 left-0 flex flex-col h-full w-full overflow-x-hidden overflow-y-auto px-8 pb-8">
<AnimatedRoutes>
{props.children}
</AnimatedRoutes>
</OverlayScrollbarsComponent>
</ErrorBoundary>
<SettingsProvider>
<main class="flex flex-col bg-primary w-full min-h-screen overflow-hidden h-screen max-h-screen text-fg-primary">
<WindowFrame />
<div class="flex flex-col px-8">
<Navbar />
</div>
</div>
<NotificationOverlay />
</main>
<div class="relative h-full w-full overflow-hidden">
<div class="absolute top-0 left-0 flex flex-col h-full w-full overflow-x-hidden">
<ErrorBoundary>
<OverlayScrollbarsComponent class="os-hide-horizontal-scrollbar absolute top-0 left-0 flex flex-col h-full w-full overflow-x-hidden overflow-y-auto px-8 pb-8">
<AnimatedRoutes>
{props.children}
</AnimatedRoutes>
</OverlayScrollbarsComponent>
</ErrorBoundary>
</div>
</div>
<NotificationOverlay />
</main>
</SettingsProvider>
);
}

View file

@ -4,7 +4,6 @@ import type { JSX } from 'solid-js';
import { createSignal, onMount } from 'solid-js';
import Modal from './overlay/Modal';
import Button from './base/Button';
import appSettings from '~ui/state/appSettings';
interface TitlebarButtonProps {
icon: (any: any) => JSX.Element;
@ -32,10 +31,10 @@ function WindowFrame() {
onMount(() => {
Window.getCurrent().onCloseRequested((event) => {
if (appSettings.settings.closeDialog) {
event.preventDefault();
setModalVisible(true);
}
// if (appSettings.settings.closeDialog) {
// event.preventDefault();
// setModalVisible(true);
// }
});
});

View file

@ -0,0 +1,34 @@
import { type Context, type ParentProps, type Resource, Show, createContext, useContext } from 'solid-js';
import useCommand from './useCommand';
import type { Settings } from '~bindings';
import { bridge } from '~index';
const SettingsContext = createContext<Settings>() as Context<Settings>;
export function getSettings(): Resource<Settings> | undefined {
const [resource] = useCommand(bridge.commands.getSettings);
return resource;
}
export function SettingsProvider(props: ParentProps) {
const settings = getSettings();
return (
<Show when={settings !== undefined && settings() !== undefined}>
<SettingsContext.Provider value={settings!()!}>
{props.children}
</SettingsContext.Provider>
</Show>
);
}
export function useSettingsContext() {
const context = useContext(SettingsContext);
if (!context)
throw new Error('useSettingsContext should be called inside its SettingsProvider');
return context;
}
export default useSettingsContext;

View file

@ -3,12 +3,9 @@ import SettingsRow from '../../../components/SettingsRow';
import ScrollableContainer from '~ui/components/ScrollableContainer';
import Button from '~ui/components/base/Button';
import Toggle from '~ui/components/base/Toggle';
import appSettings from '~ui/state/appSettings';
import Sidebar from '~ui/components/Sidebar';
function SettingsAppearance() {
const { settings, setSettings } = appSettings;
return (
<Sidebar.Page>
<h1>Appearance</h1>
@ -30,12 +27,7 @@ function SettingsAppearance() {
description="Toggle all animations in the launcher."
icon={<Speedometer04Icon />}
>
<Toggle
defaultChecked={settings.reducedMotion}
onChecked={(checked) => {
setSettings('reducedMotion', checked);
}}
/>
<Toggle />
</SettingsRow>
</ScrollableContainer>
</Sidebar.Page>

View file

@ -1,11 +1,20 @@
import { XIcon } from '@untitled-theme/icons-solid';
import { createEffect } from 'solid-js';
import SettingsRow from '../../../components/SettingsRow';
import ScrollableContainer from '~ui/components/ScrollableContainer';
import Toggle from '~ui/components/base/Toggle';
import appSettings from '~ui/state/appSettings';
import Sidebar from '~ui/components/Sidebar';
import { bridge } from '~index';
import useCommand from '~ui/hooks/useCommand';
import useSettingsContext from '~ui/hooks/useSettings';
function SettingsGeneral() {
const settings = useSettingsContext();
createEffect(() => {
console.log(settings);
});
return (
<Sidebar.Page>
<h1>General</h1>
@ -15,12 +24,7 @@ function SettingsGeneral() {
description="Show a confirmation dialog when closing the launcher."
icon={<XIcon />}
>
<Toggle
defaultChecked={appSettings.settings.closeDialog}
onChecked={(checked) => {
appSettings.setSettings('closeDialog', checked);
}}
/>
<Toggle />
</SettingsRow>
</ScrollableContainer>
</Sidebar.Page>

View file

@ -1,37 +0,0 @@
import { createEffect, createRoot, onMount } from 'solid-js';
import { createStore } from 'solid-js/store';
export interface AppSettingsStore {
reducedMotion: boolean;
closeDialog: boolean;
};
function globalStore() {
// TODO: Much better management of this, currently temporary
const [settings, setSettings] = createStore<AppSettingsStore>({
reducedMotion: false,
closeDialog: true,
});
function set<K extends keyof AppSettingsStore>(key: K, value: AppSettingsStore[K]) {
window.localStorage.setItem(key, value.toString());
}
onMount(() => {
setSettings('reducedMotion', window.localStorage.getItem('reducedMotion') === 'true');
setSettings('closeDialog', window.localStorage.getItem('closeDialog') === 'true');
});
createEffect(() => {
// Reduced Motion
set('reducedMotion', settings.reducedMotion);
document.body.classList.toggle('reduce-motion', settings.reducedMotion);
// Close Dialog
set('closeDialog', settings.closeDialog);
});
return { settings, setSettings };
}
export default createRoot(globalStore);