Skip to content

Commit 2b6f2a1

Browse files
committed
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.
1 parent 996df04 commit 2b6f2a1

1 file changed

Lines changed: 165 additions & 86 deletions

File tree

packages/interface/src/inspectors/FileInspector.tsx

Lines changed: 165 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,11 @@ import {
3636
import { TagSelectorButton } from "../components/Tags";
3737
import clsx from "clsx";
3838
import type { File } from "@sd/ts-client";
39-
import { useNormalizedQuery, useLibraryMutation } from "../context";
39+
import {
40+
useNormalizedQuery,
41+
useLibraryMutation,
42+
getDeviceIcon,
43+
} from "../context";
4044
import { formatBytes } from "../components/Explorer/utils";
4145
import { File as FileComponent } from "../components/Explorer/File";
4246
import { useContextMenu } from "../hooks/useContextMenu";
@@ -948,6 +952,7 @@ function SidecarItem({
948952
}
949953

950954
function InstancesTab({ file }: { file: File }) {
955+
951956
// Query for alternate instances with full File data
952957
const instancesQuery = useNormalizedQuery<
953958
{ entry_uuid: string },
@@ -960,33 +965,45 @@ function InstancesTab({ file }: { file: File }) {
960965

961966
const instances = instancesQuery.data?.instances || [];
962967

963-
const getPathDisplay = (sdPath: typeof file.sd_path) => {
964-
if ("Physical" in sdPath) {
965-
return sdPath.Physical.path;
966-
} else if ("Cloud" in sdPath) {
967-
return sdPath.Cloud.path;
968-
} else {
969-
return "Content";
970-
}
971-
};
968+
// Query devices to get proper names and icons
969+
const devicesQuery = useNormalizedQuery<any, any[]>({
970+
wireMethod: "query:devices.list",
971+
input: {
972+
include_offline: true,
973+
include_details: false,
974+
show_paired: true,
975+
},
976+
resourceType: "device",
977+
});
972978

973-
const getDeviceDisplay = (sdPath: typeof file.sd_path) => {
974-
if ("Physical" in sdPath) {
975-
return sdPath.Physical.device_slug || "Local Device";
976-
} else if ("Cloud" in sdPath) {
977-
return "Cloud Storage";
978-
} else {
979-
return "Content Addressed";
980-
}
979+
const devices = devicesQuery.data || [];
980+
981+
// Group instances by device_slug
982+
const instancesByDevice = instances.reduce(
983+
(acc, instance) => {
984+
let deviceSlug = "unknown";
985+
if ("Physical" in instance.sd_path) {
986+
deviceSlug = instance.sd_path.Physical.device_slug;
987+
} else if ("Cloud" in instance.sd_path) {
988+
deviceSlug = "cloud";
989+
}
990+
991+
if (!acc[deviceSlug]) {
992+
acc[deviceSlug] = [];
993+
}
994+
acc[deviceSlug].push(instance);
995+
return acc;
996+
},
997+
{} as Record<string, File[]>,
998+
);
999+
1000+
const getDeviceName = (deviceSlug: string) => {
1001+
const device = devices.find((d) => d.slug === deviceSlug);
1002+
return device?.name || deviceSlug;
9811003
};
9821004

983-
const formatDate = (dateStr: string) => {
984-
const date = new Date(dateStr);
985-
return date.toLocaleDateString("en-US", {
986-
month: "short",
987-
day: "numeric",
988-
year: "numeric",
989-
});
1005+
const getDeviceInfo = (deviceSlug: string) => {
1006+
return devices.find((d) => d.slug === deviceSlug);
9901007
};
9911008

9921009
if (instancesQuery.isLoading) {
@@ -1009,7 +1026,7 @@ function InstancesTab({ file }: { file: File }) {
10091026
}
10101027

10111028
return (
1012-
<div className="no-scrollbar mask-fade-out flex flex-col space-y-4 overflow-x-hidden overflow-y-scroll pb-10 px-2 pt-2">
1029+
<div className="no-scrollbar mask-fade-out flex flex-col space-y-5 overflow-x-hidden overflow-y-scroll pb-10 px-2 pt-2">
10131030
<p className="text-xs text-sidebar-inkDull">
10141031
All copies of this file across your devices and locations
10151032
</p>
@@ -1019,78 +1036,140 @@ function InstancesTab({ file }: { file: File }) {
10191036
No alternate instances found
10201037
</div>
10211038
) : (
1022-
<div className="space-y-2.5">
1023-
{instances.map((instance, i) => (
1024-
<div
1025-
key={i}
1026-
className="p-2.5 bg-app-box/40 rounded-lg border border-app-line/50 hover:bg-app-box/60 transition-colors"
1027-
>
1028-
<div className="flex items-start gap-3">
1029-
{/* Thumbnail */}
1030-
<div className="shrink-0">
1031-
<FileComponent.Thumb
1032-
file={instance}
1033-
size={64}
1034-
iconScale={0.5}
1035-
className="rounded overflow-hidden"
1036-
/>
1037-
</div>
1038-
1039-
{/* Info */}
1040-
<div className="flex-1 min-w-0 space-y-1.5">
1041-
<div className="flex items-start justify-between gap-2">
1042-
<div className="flex-1 min-w-0">
1043-
<div className="text-xs font-medium text-sidebar-ink truncate">
1044-
{instance.name}
1045-
{instance.extension &&
1046-
`.${instance.extension}`}
1047-
</div>
1048-
<div className="text-[11px] text-sidebar-inkDull mt-0.5">
1049-
{formatBytes(instance.size)}
1050-
</div>
1051-
</div>
1052-
<div
1053-
className={clsx(
1054-
"size-2 rounded-full shrink-0 mt-1",
1055-
instance.is_local
1056-
? "bg-accent"
1057-
: "bg-sidebar-inkDull/40",
1058-
)}
1059-
title={
1060-
instance.is_local
1061-
? "Available locally"
1062-
: "Remote"
1063-
}
1039+
<div className="space-y-4">
1040+
{Object.entries(instancesByDevice).map(
1041+
([deviceSlug, deviceInstances]) => {
1042+
const deviceInfo = getDeviceInfo(deviceSlug);
1043+
const deviceName = getDeviceName(deviceSlug);
1044+
1045+
return (
1046+
<div key={deviceSlug} className="space-y-1">
1047+
{/* Device Header */}
1048+
<div className="flex items-center gap-2 px-2">
1049+
<img
1050+
src={getDeviceIcon(deviceInfo)}
1051+
className="size-4 shrink-0"
1052+
alt=""
10641053
/>
1065-
</div>
1066-
1067-
<div className="flex items-center gap-1.5 text-[11px] text-sidebar-inkDull">
1068-
<MapPin size={12} weight="bold" />
1069-
<span className="truncate">
1070-
{getDeviceDisplay(
1071-
instance.sd_path,
1072-
)}
1054+
<span className="text-xs font-semibold text-sidebar-ink">
1055+
{deviceName}
10731056
</span>
1057+
<div className="flex-1" />
1058+
<div className="flex items-center justify-center size-5 rounded-full bg-app-box border border-app-line text-[10px] font-semibold text-sidebar-inkDull">
1059+
{deviceInstances.length}
1060+
</div>
10741061
</div>
10751062

1076-
<div className="text-[10px] text-sidebar-inkDull/70 font-mono truncate">
1077-
{getPathDisplay(instance.sd_path)}
1078-
</div>
1079-
1080-
<div className="text-[10px] text-sidebar-inkDull/70">
1081-
Modified{" "}
1082-
{formatDate(instance.modified_at)}
1063+
{/* List of instances */}
1064+
<div className="space-y-0.5">
1065+
{deviceInstances.map(
1066+
(instance, i) => (
1067+
<InstanceRow
1068+
key={i}
1069+
instance={instance}
1070+
/>
1071+
),
1072+
)}
10831073
</div>
10841074
</div>
1085-
</div>
1086-
</div>
1087-
))}
1075+
);
1076+
},
1077+
)}
10881078
</div>
10891079
)}
10901080
</div>
10911081
);
10921082
}
10931083

1084+
function InstanceRow({ instance }: { instance: File }) {
1085+
const getPathDisplay = (sdPath: typeof instance.sd_path) => {
1086+
if ("Physical" in sdPath) {
1087+
return sdPath.Physical.path;
1088+
} else if ("Cloud" in sdPath) {
1089+
return sdPath.Cloud.path;
1090+
} else {
1091+
return "Content";
1092+
}
1093+
};
1094+
1095+
const formatDate = (dateStr: string) => {
1096+
const date = new Date(dateStr);
1097+
return date.toLocaleDateString("en-US", {
1098+
month: "short",
1099+
day: "numeric",
1100+
});
1101+
};
1102+
1103+
return (
1104+
<div
1105+
className="flex items-center gap-2 px-2 py-1.5 rounded-md hover:bg-app-box/40 transition-colors cursor-default"
1106+
title={getPathDisplay(instance.sd_path)}
1107+
>
1108+
{/* Thumbnail */}
1109+
<div className="flex-shrink-0 [&_*]:!rounded-[3px]">
1110+
<FileComponent.Thumb file={instance} size={20} />
1111+
</div>
1112+
1113+
{/* File info */}
1114+
<div className="flex-1 min-w-0 flex items-center gap-2">
1115+
<span className="text-xs text-sidebar-ink truncate">
1116+
{instance.name}
1117+
{instance.extension && `.${instance.extension}`}
1118+
</span>
1119+
</div>
1120+
1121+
{/* Metadata */}
1122+
<div className="flex items-center gap-2 shrink-0">
1123+
{/* Tags */}
1124+
{instance.tags && instance.tags.length > 0 && (
1125+
<div
1126+
className="flex items-center gap-0.5"
1127+
title={instance.tags
1128+
.map((t) => t.canonical_name)
1129+
.join(", ")}
1130+
>
1131+
{instance.tags.slice(0, 3).map((tag) => (
1132+
<div
1133+
key={tag.id}
1134+
className="size-1.5 rounded-full"
1135+
style={{
1136+
backgroundColor: tag.color || "#3B82F6",
1137+
}}
1138+
/>
1139+
))}
1140+
{instance.tags.length > 3 && (
1141+
<span className="text-[9px] text-ink-faint font-medium">
1142+
+{instance.tags.length - 3}
1143+
</span>
1144+
)}
1145+
</div>
1146+
)}
1147+
1148+
{/* Modified date */}
1149+
<span className="text-[10px] text-sidebar-inkDull">
1150+
{formatDate(instance.modified_at)}
1151+
</span>
1152+
1153+
{/* Size */}
1154+
<span className="text-[10px] text-sidebar-inkDull min-w-[50px] text-right">
1155+
{formatBytes(instance.size)}
1156+
</span>
1157+
1158+
{/* Local indicator */}
1159+
<div
1160+
className={clsx(
1161+
"size-1.5 rounded-full",
1162+
instance.is_local
1163+
? "bg-accent"
1164+
: "bg-sidebar-inkDull/40",
1165+
)}
1166+
title={instance.is_local ? "Available locally" : "Remote"}
1167+
/>
1168+
</div>
1169+
</div>
1170+
);
1171+
}
1172+
10941173
function ChatTab() {
10951174
const [message, setMessage] = useState("");
10961175

0 commit comments

Comments
 (0)