From 2b6f2a14d7fd3da9631f5b67de0f43e8a4c3e397 Mon Sep 17 00:00:00 2001 From: Jamie Pine Date: Fri, 26 Dec 2025 12:05:19 -0800 Subject: [PATCH] Enhance InstancesTab component for improved device instance display - Refactored the `InstancesTab` component to group file instances by device, providing a clearer overview of file availability across devices. - Introduced a new `InstanceRow` component for better organization and readability of individual file instances. - Added device querying to retrieve device names and icons, enhancing the user interface with relevant device information. - Updated the layout to improve visual clarity and user experience when viewing alternate file instances. --- .../src/inspectors/FileInspector.tsx | 251 ++++++++++++------ 1 file changed, 165 insertions(+), 86 deletions(-) diff --git a/packages/interface/src/inspectors/FileInspector.tsx b/packages/interface/src/inspectors/FileInspector.tsx index 247c82417..30e5834ce 100644 --- a/packages/interface/src/inspectors/FileInspector.tsx +++ b/packages/interface/src/inspectors/FileInspector.tsx @@ -36,7 +36,11 @@ import { import { TagSelectorButton } from "../components/Tags"; import clsx from "clsx"; import type { File } from "@sd/ts-client"; -import { useNormalizedQuery, useLibraryMutation } from "../context"; +import { + useNormalizedQuery, + useLibraryMutation, + getDeviceIcon, +} from "../context"; import { formatBytes } from "../components/Explorer/utils"; import { File as FileComponent } from "../components/Explorer/File"; import { useContextMenu } from "../hooks/useContextMenu"; @@ -948,6 +952,7 @@ function SidecarItem({ } function InstancesTab({ file }: { file: File }) { + // Query for alternate instances with full File data const instancesQuery = useNormalizedQuery< { entry_uuid: string }, @@ -960,33 +965,45 @@ function InstancesTab({ file }: { file: File }) { const instances = instancesQuery.data?.instances || []; - const getPathDisplay = (sdPath: typeof file.sd_path) => { - if ("Physical" in sdPath) { - return sdPath.Physical.path; - } else if ("Cloud" in sdPath) { - return sdPath.Cloud.path; - } else { - return "Content"; - } + // Query devices to get proper names and icons + const devicesQuery = useNormalizedQuery({ + wireMethod: "query:devices.list", + input: { + include_offline: true, + include_details: false, + show_paired: true, + }, + resourceType: "device", + }); + + const devices = devicesQuery.data || []; + + // Group instances by device_slug + const instancesByDevice = instances.reduce( + (acc, instance) => { + let deviceSlug = "unknown"; + if ("Physical" in instance.sd_path) { + deviceSlug = instance.sd_path.Physical.device_slug; + } else if ("Cloud" in instance.sd_path) { + deviceSlug = "cloud"; + } + + if (!acc[deviceSlug]) { + acc[deviceSlug] = []; + } + acc[deviceSlug].push(instance); + return acc; + }, + {} as Record, + ); + + const getDeviceName = (deviceSlug: string) => { + const device = devices.find((d) => d.slug === deviceSlug); + return device?.name || deviceSlug; }; - const getDeviceDisplay = (sdPath: typeof file.sd_path) => { - if ("Physical" in sdPath) { - return sdPath.Physical.device_slug || "Local Device"; - } else if ("Cloud" in sdPath) { - return "Cloud Storage"; - } else { - return "Content Addressed"; - } - }; - - const formatDate = (dateStr: string) => { - const date = new Date(dateStr); - return date.toLocaleDateString("en-US", { - month: "short", - day: "numeric", - year: "numeric", - }); + const getDeviceInfo = (deviceSlug: string) => { + return devices.find((d) => d.slug === deviceSlug); }; if (instancesQuery.isLoading) { @@ -1009,7 +1026,7 @@ function InstancesTab({ file }: { file: File }) { } return ( -
+

All copies of this file across your devices and locations

@@ -1019,78 +1036,140 @@ function InstancesTab({ file }: { file: File }) { No alternate instances found
) : ( -
- {instances.map((instance, i) => ( -
-
- {/* Thumbnail */} -
- -
+
+ {Object.entries(instancesByDevice).map( + ([deviceSlug, deviceInstances]) => { + const deviceInfo = getDeviceInfo(deviceSlug); + const deviceName = getDeviceName(deviceSlug); - {/* Info */} -
-
-
-
- {instance.name} - {instance.extension && - `.${instance.extension}`} -
-
- {formatBytes(instance.size)} -
-
-
+ {/* Device Header */} +
+ -
- -
- - - {getDeviceDisplay( - instance.sd_path, - )} + + {deviceName} +
+
+ {deviceInstances.length} +
-
- {getPathDisplay(instance.sd_path)} -
- -
- Modified{" "} - {formatDate(instance.modified_at)} + {/* List of instances */} +
+ {deviceInstances.map( + (instance, i) => ( + + ), + )}
-
-
- ))} + ); + }, + )}
)}
); } +function InstanceRow({ instance }: { instance: File }) { + const getPathDisplay = (sdPath: typeof instance.sd_path) => { + if ("Physical" in sdPath) { + return sdPath.Physical.path; + } else if ("Cloud" in sdPath) { + return sdPath.Cloud.path; + } else { + return "Content"; + } + }; + + const formatDate = (dateStr: string) => { + const date = new Date(dateStr); + return date.toLocaleDateString("en-US", { + month: "short", + day: "numeric", + }); + }; + + return ( +
+ {/* Thumbnail */} +
+ +
+ + {/* File info */} +
+ + {instance.name} + {instance.extension && `.${instance.extension}`} + +
+ + {/* Metadata */} +
+ {/* Tags */} + {instance.tags && instance.tags.length > 0 && ( +
t.canonical_name) + .join(", ")} + > + {instance.tags.slice(0, 3).map((tag) => ( +
+ ))} + {instance.tags.length > 3 && ( + + +{instance.tags.length - 3} + + )} +
+ )} + + {/* Modified date */} + + {formatDate(instance.modified_at)} + + + {/* Size */} + + {formatBytes(instance.size)} + + + {/* Local indicator */} +
+
+
+ ); +} + function ChatTab() { const [message, setMessage] = useState("");