From bdf5f4b44e5ee44eeba3d905805fdd1feeed24a1 Mon Sep 17 00:00:00 2001 From: Aiden Date: Tue, 19 May 2026 16:23:51 +1000 Subject: [PATCH] Aiden added closer matching look --- README.md | 2 + src/components/DetailsPanel.tsx | 130 ++++++++++++++++- src/services/details.ts | 46 ++++++ src/services/remoteControl.ts | 4 + src/services/thumbnailCache.ts | 106 ++++++++++++++ src/styles.css | 243 +++++++++++++++++++++++++++++++- 6 files changed, 522 insertions(+), 9 deletions(-) create mode 100644 src/services/thumbnailCache.ts diff --git a/README.md b/README.md index 6ffe791..2c71f5f 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ The property request uses `READ_ACCESS` and omits `propertyName`, which asks Unr Details payloads are cached in browser `localStorage` for five minutes per object path. When you reselect an object, cached properties render immediately and the app refreshes them from Unreal in the background. +Asset-looking property values also request `/remote/object/thumbnail` and cache thumbnail data in `localStorage`, so mesh/material fields can render closer to Unreal's Details panel. + ## Run Start Unreal Editor, enable the Remote Control API, and make sure it is listening on `127.0.0.1:30010`. diff --git a/src/components/DetailsPanel.tsx b/src/components/DetailsPanel.tsx index d72b530..4ca076d 100644 --- a/src/components/DetailsPanel.tsx +++ b/src/components/DetailsPanel.tsx @@ -1,8 +1,17 @@ import React from "react"; -import { ChevronDown, Search, Settings, SlidersHorizontal, X } from "lucide-react"; +import { Box, ChevronDown, Grid2X2, RotateCcw, Search, Settings, SlidersHorizontal, Star, X } from "lucide-react"; import type { DetailProperty, ObjectDetails, TreeNode } from "../types"; import { basename } from "../lib/remoteValues"; -import { formatDetailValue } from "../services/details"; +import { + detailAssetPath, + formatDetailValue, + isBooleanProperty, + isVectorLikeProperty, + normalizeThumbnailPayload, + vectorParts, +} from "../services/details"; +import { readRemoteObjectThumbnail } from "../services/remoteControl"; +import { readCachedThumbnail, writeCachedThumbnail } from "../services/thumbnailCache"; import { NodeIcon } from "./NodeIcon"; const emptyNode: TreeNode = { @@ -15,6 +24,80 @@ const emptyNode: TreeNode = { children: [], }; +const categoryTabs = ["General", "Actor", "LOD", "Misc", "Physics", "Rendering", "Streaming", "All"]; + +function DetailValue({ property }: { property: DetailProperty }) { + const assetPath = detailAssetPath(property); + const [thumbnail, setThumbnail] = React.useState(() => (assetPath ? readCachedThumbnail(assetPath) : "")); + + React.useEffect(() => { + let isCancelled = false; + + if (!assetPath) { + setThumbnail(""); + return; + } + + const cached = readCachedThumbnail(assetPath); + if (cached) { + setThumbnail(cached); + return; + } + + readRemoteObjectThumbnail(assetPath) + .then((payload) => { + const dataUrl = normalizeThumbnailPayload(payload); + if (!isCancelled && dataUrl) { + setThumbnail(dataUrl); + writeCachedThumbnail(assetPath, dataUrl); + } + }) + .catch(() => { + if (!isCancelled) { + setThumbnail(""); + } + }); + + return () => { + isCancelled = true; + }; + }, [assetPath]); + + if (assetPath) { + return ( +
+
{thumbnail ? : }
+
+ {formatDetailValue(property.value)} + +
+
+ ); + } + + if (isVectorLikeProperty(property)) { + return ( +
+ {vectorParts(property.value).map((part, index) => ( + + {part.value} + + ))} +
+ ); + } + + if (isBooleanProperty(property)) { + return ; + } + + return ( +
+ {formatDetailValue(property.value)} +
+ ); +} + export function DetailsPanel({ node, details, @@ -27,10 +110,13 @@ export function DetailsPanel({ error: string | null; }) { const [query, setQuery] = React.useState(""); + const [activeCategory, setActiveCategory] = React.useState("All"); const groupedProperties = React.useMemo(() => { const lowerQuery = query.toLowerCase(); const visibleProperties = (details?.properties ?? []).filter((property) => { + const matchesCategory = activeCategory === "All" || property.category.toLowerCase().includes(activeCategory.toLowerCase()); + if (!matchesCategory) return false; if (!lowerQuery) return true; return [property.displayName, property.name, property.type, property.category, formatDetailValue(property.value)].some((field) => field.toLowerCase().includes(lowerQuery), @@ -43,7 +129,7 @@ export function DetailsPanel({ groups[category].push(property); return groups; }, {}); - }, [details, query]); + }, [activeCategory, details, query]); return (