From 2af86b5fda840a83e4e775d1de8df8f59f133bfe Mon Sep 17 00:00:00 2001 From: LynithDev <61880709+LynithDev@users.noreply.github.com> Date: Tue, 26 Mar 2024 20:01:46 +0100 Subject: [PATCH] fix: Minecraft manifest deserializing --- core/src/game/minecraft.rs | 119 +++++++++++++++++++-------------- src/bridge/game.ts | 17 +++++ src/ui/pages/Home.tsx | 132 ++++++------------------------------- 3 files changed, 107 insertions(+), 161 deletions(-) diff --git a/core/src/game/minecraft.rs b/core/src/game/minecraft.rs index d453ee2d..b54a9243 100644 --- a/core/src/game/minecraft.rs +++ b/core/src/game/minecraft.rs @@ -1,4 +1,7 @@ -use serde::{Serialize, Deserialize}; +use core::fmt; +use std::{collections::HashMap, marker::PhantomData}; + +use serde::{de::{self, SeqAccess, Visitor}, Deserialize, Deserializer, Serialize}; use serde_json::Value; #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -6,32 +9,30 @@ use serde_json::Value; pub struct MinecraftManifest { pub asset_index: AssetIndex, pub downloads: Downloads, - #[serde(rename = "id")] - pub version: String, pub java_version: JavaVersion, pub libraries: Vec, pub logging: Logging, pub main_class: String, - pub arguments: Arguments, pub release_time: String, + + #[serde(rename = "id")] + pub version: String, + #[serde(rename = "type")] pub release_type: ReleaseType, + + #[serde(skip_serializing_if = "Option::is_none")] + pub minecraft_arguments: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub arguments: Option, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -#[serde(untagged)] -pub enum Arguments { - // TODO: https://serde.rs/string-or-struct.html - MinecraftArguments(String), - Arguments { - game: Vec, - jvm: Vec, - } -} - -impl Default for Arguments { - fn default() -> Self { Arguments::MinecraftArguments(String::new()) } +pub struct ModernArguments { + game: Vec, + jvm: Vec, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -39,18 +40,18 @@ impl Default for Arguments { #[serde(untagged)] pub enum ModernArgumentsItem { Simple(String), - Rule { - rules: Vec, - value: ArgumentRuleValue, - }, + Rule(ArgumentRule), } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -#[serde(untagged)] -pub enum ArgumentRuleValue { - String(String), - List(Vec), +pub struct ArgumentRule { + #[serde(skip_serializing_if = "Vec::is_empty")] + rules: Vec, + + #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(deserialize_with = "string_or_seq")] + value: Vec, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -96,17 +97,17 @@ pub struct JavaVersion { #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Library { - pub downloads: Downloads2, + pub downloads: LibraryDownload, pub name: String, - #[serde(default)] + #[serde(skip_serializing_if = "Vec::is_empty", default)] pub rules: Vec, #[serde(skip_serializing_if = "Option::is_none")] - pub natives: Option, + pub natives: Option>, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct Downloads2 { +pub struct LibraryDownload { #[serde(skip_serializing_if = "Option::is_none")] pub artifact: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -126,23 +127,15 @@ pub struct Artifact { pub struct Classifiers { #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "natives-osx")] - pub natives_osx: Option, + pub natives_osx: Option, #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "natives-linux")] - pub natives_linux: Option, + pub natives_linux: Option, #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "natives-windows")] - pub natives_windows: Option, -} - -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Native { - pub path: String, - pub sha1: String, - pub url: String, + pub natives_windows: Option, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -154,33 +147,28 @@ pub struct Rule { pub os: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub features: Option, + pub features: Option>, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -#[serde(untagged)] pub enum RuleAction { + #[serde(rename = "allow")] Allow, + #[serde(rename = "disallow")] Disallow, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Os { + #[serde(skip_serializing_if = "Option::is_none")] pub name: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub arch: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub version: Option, } -#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Natives { - pub windows: Option, - pub linux: Option, - pub osx: Option, -} - #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Logging { @@ -203,3 +191,34 @@ pub struct File { pub sha1: String, pub url: String, } + +fn string_or_seq<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + struct StringOrVec(PhantomData>); + + impl<'de> Visitor<'de> for StringOrVec { + type Value = Vec; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("string or list of strings") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + Ok(vec![value.into()]) + } + + fn visit_seq(self, seq: S) -> Result + where + S: SeqAccess<'de>, + { + Deserialize::deserialize(de::value::SeqAccessDeserializer::new(seq)) + } + } + + deserializer.deserialize_any(StringOrVec(PhantomData)) +} \ No newline at end of file diff --git a/src/bridge/game.ts b/src/bridge/game.ts index 8b01a817..f3dbcbda 100644 --- a/src/bridge/game.ts +++ b/src/bridge/game.ts @@ -21,6 +21,23 @@ export async function getInstancesWithManifests(): Promise> { + const instances = await getInstancesWithManifests(); + const map = new Map(); + + instances.forEach((instance, index) => { + const groupName = instance.instance.group || 'Unnamed'; + const value = { ...instance, index }; + + if (map.has(groupName)) + map.get(groupName)!.push(value); + else + map.set(groupName, [value]); + }); + + return map; +} + export async function getInstances(): Promise { return await invoke('plugin:launcher-core|get_instances'); } diff --git a/src/ui/pages/Home.tsx b/src/ui/pages/Home.tsx index 3e2d1976..e4052ee1 100644 --- a/src/ui/pages/Home.tsx +++ b/src/ui/pages/Home.tsx @@ -5,13 +5,10 @@ import { EyeIcon, PlayIcon, PlusIcon, - RefreshCw01Icon, SearchMdIcon, } from '@untitled-theme/icons-solid'; import type { Accessor, Setter } from 'solid-js'; -import { For, Suspense, createSignal, lazy } from 'solid-js'; -import { DragDropProvider, DragDropSensors, SortableProvider, closestCenter, createSortable } from '@thisbeyond/solid-dnd'; -import type { DragEventHandler, Id } from '@thisbeyond/solid-dnd'; +import { For, Show, createResource, createSignal } from 'solid-js'; import { useNavigate } from '@solidjs/router'; import image from '../../assets/images/header.png'; import Button from '../components/base/Button'; @@ -19,7 +16,8 @@ import Tag from '../components/base/Tag'; import TextField from '../components/base/TextField'; import defaultCover from '../../assets/images/default_instance_cover.jpg'; import ContextMenu from '../components/overlay/ContextMenu'; -import { createInstance, getInstancesWithManifests, refreshClientManager } from '../../bridge/game'; +import { createInstance, getGroupedInstances, getInstancesWithManifests } from '../../bridge/game'; +import Modal from '../components/overlay/Modal'; // TODO: Replace this into it's own component // possibly with the core ui lirbary with autogenerated and optimized svgs pls @@ -99,12 +97,6 @@ function InstanceCardContextMenu(props: InstanceCardContextMenuProps) { ); } -interface InstanceCardProps { - id: number; - instance: Core.Instance; - manifest: Core.Manifest; -} - declare module 'solid-js' { // eslint-disable-next-line ts/no-namespace namespace JSX { @@ -114,39 +106,19 @@ declare module 'solid-js' { } } -function InstanceCard(props: InstanceCardProps) { +function InstanceCard(props: Core.InstanceWithManifest) { const navigate = useNavigate(); const [pos, setPos] = createSignal({ x: 0, y: 0 }); - const [dragged, setDragged] = createSignal(false); const [contextMenuVisible, setContextMenuVisible] = createSignal(false); - const id = () => props.id as Id; let ref!: HTMLDivElement; - const sortable = createSortable(id()); - - function didDrag() { - const value = dragged(); - if (value) { - setDragged(false); - return true; - } - - return false; - } - function openInstancePage(_e: MouseEvent) { - if (didDrag()) - return; - navigate(`/instances/?id=${props.instance.id}`); } function openContextMenu(e: MouseEvent) { e.preventDefault(); e.stopPropagation(); - if (didDrag()) - return; - setContextMenuVisible(!contextMenuVisible()); } @@ -154,21 +126,11 @@ function InstanceCard(props: InstanceCardProps) { setPos({ x: e.clientX, y: e.clientY }); } - function onMouseUp(e: MouseEvent) { - const xMoved = Math.abs(e.clientX - pos().x) > 3; - const yMoved = Math.abs(e.clientY - pos().y) > 3; - - if (xMoved || yMoved) - setDragged(true); - } - return ( <>
openInstancePage(e)} onMouseDown={e => onMouseDown(e)} - onMouseUp={e => onMouseUp(e)} onContextMenu={e => openContextMenu(e)} ref={ref} 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-0.05" @@ -216,81 +178,31 @@ interface InstanceGroupProps { } function InstanceGroup(props: InstanceGroupProps) { - // eslint-disable-next-line solid/reactivity - const [instances, setInstances] = createSignal(props.instances.map((instance, index) => ({ ...instance, id: index }))); - const ids = () => instances().map(instance => instance.id); - - const onDragStart: DragEventHandler = ({ draggable }) => { - draggable.node.setAttribute('data-dragging', 'true'); - draggable.node.style.zIndex = '1000'; - }; - - const onDragEnd: DragEventHandler = ({ draggable, droppable }) => { - draggable.node.setAttribute('data-dragging', 'false'); - draggable.node.style.zIndex = ''; - if (draggable && droppable) { - const currentItems = ids(); - const fromIndex = currentItems.indexOf(draggable.id as number); - const toIndex = currentItems.indexOf(droppable.id as number); - - if (fromIndex !== toIndex) { - const updatedItems = instances().slice(); - updatedItems.splice(toIndex, 0, ...updatedItems.splice(fromIndex, 1)); - setInstances(updatedItems); - } - } - }; - return (

{props.title}

- - -
- - {item => } - -
-
+
+ {item => } +
); } -const Instances = lazy(async () => { - const instances = await getInstancesWithManifests(); - const map = new Map(); - - instances.forEach((instance) => { - const groupName = instance.instance.group || 'Unnamed'; - - if (map.has(groupName)) - map.get(groupName)!.push(instance); - else - map.set(groupName, [instance]); - }); - - return { - default: () => Array.from(map.entries()).map(([title, instances]) => ( - - )), - }; -}); - function HomePage() { - function newInstance() { - createInstance({ + const [instances, { refetch }] = createResource(getGroupedInstances); + + async function newInstance() { + await createInstance({ name: 'New Instance', - version: '1.19.2', + version: '1.8.9', client: { type: 'Vanilla', }, cover: null, group: null, }); + + await refetch(); } return ( @@ -314,18 +226,16 @@ function HomePage() { iconLeft={} children="From URL" /> - -
- - - + + + {([title, instances]) => ( + + )} + + ); }