};
```
The `ExplorerContext` maintains a history stack and current position. When you click back or forward, it restores the previous entry and updates the URL accordingly. This works seamlessly across both file system paths and virtual device listings.
## Virtual Listings
Virtual listings display non-file entities (devices, locations, volumes) as if they were files in a directory. This allows the Explorer to reuse all existing view components (grid, list, column) without special cases.
### Mapping to Files
The `virtualFiles.ts` module converts backend entities into `File` objects with a special `_virtual` metadata field:
```typescript
export function mapLocationToFile(location: Location, iconUrl?: string): File {
return {
id: `virtual:location:${location.id}`,
kind: "Directory",
name: location.name,
sd_path: location.sd_path,
// ... standard File fields
_virtual: {
type: "location",
data: location,
iconUrl,
}
};
}
```
The `_virtual` field marks this as a display-only entity. Virtual files cannot be copied, moved, or deleted. The `isVirtualFile()` helper checks for this field before allowing file operations.
### View Detection
The `useVirtualListing` hook detects virtual view parameters in the URL and fetches the appropriate data:
```typescript
// Example: Device view
if (view === "device" && id) {
// Fetch locations and volumes for this device
const locations = locationsData?.locations.filter(
loc => loc.sd_path.Physical?.device_slug === device.slug
);
const volumes = volumesData?.volumes.filter(
vol => vol.device_id === id
);
// Map to virtual files
return locations.map(mapLocationToFile)
.concat(volumes.map(mapVolumeToFile));
}
```
This returns an array of virtual files that the Explorer views consume like regular directory listings.
### Column View Integration
Column view presents a unique challenge for virtual listings. When you select a virtual location, the next column should show that location's real contents. The system handles this by conditionally rendering columns:
```typescript
if (isVirtualView && virtualFiles) {
const selectedDirectory =
selectedFiles.length === 1 && selectedFiles[0].kind === "Directory"
? selectedFiles[0]
: null;
return (
{/* Virtual column */}
{/* Real content column when directory selected */}
{selectedDirectory && (
)}
);
}
```
The first column displays virtual files. When you select one, the second column queries the backend for real directory contents using the virtual file's `sd_path`.
Virtual files must never be passed to file operations like copy, move, or delete. Always check `isVirtualFile()` before backend mutations.
## Safety Guards
The system includes multiple layers of protection against accidental operations on virtual files:
**Context Menu**: Copy and delete menu items are hidden when virtual files are selected. The `getTargetFiles()` function filters out virtual files before operations.
**Drag and Drop**: The `useDraggableFile` hook disables dragging for virtual files by setting `disabled: true` on the draggable configuration.
**Type Checking**: Functions that access `_virtual` metadata use optional chaining to handle undefined files gracefully:
```typescript
const virtualMetadata = file?._virtual;
const isVolume = virtualMetadata?.type === "volume";
```
## Path Bar Navigation
The path bar displays breadcrumbs for the current path. For physical paths, the device icon is clickable and navigates to that device's virtual view:
```typescript
const handleDeviceClick = () => {
if (device) {
navigateToView("device", device.id);
}
};
```
This creates a seamless transition from browsing a file system path to viewing all locations and volumes on that device.
For virtual views, a separate `VirtualPathBar` component renders appropriate breadcrumbs like "Devices → My Device".
## Enum Handling
The backend returns Rust enum variants that can be objects like `{ Other: "value" }`. These cannot be rendered directly in React. Always convert enums to strings before displaying:
```typescript
const volumeTypeStr = typeof volume.volume_type === "string"
? volume.volume_type
: (volume.volume_type as any)?.Other || JSON.stringify(volume.volume_type);
```
This pattern extracts the `Other` variant value when present, falls back to JSON stringification, or uses the string directly if it's already a string.
Apply this pattern consistently to `file_system`, `disk_type`, `volume_type`, `form_factor`, and any other backend enum fields.
## Component Communication
The `ExplorerContext` provides methods for navigation that other components use:
```typescript
const { navigateToPath, navigateToView, goBack, goForward } = useExplorer();
// Navigate to a file system path
navigateToPath({ Physical: { device_slug: "home", path: "/Documents" }});
// Navigate to a virtual view
navigateToView("device", deviceId);
```
These methods update both the internal history stack and the URL. Components that render navigation UI (sidebar, path bar) use `useLocation()` to read the current URL and derive their active state.
The sidebar calculates `isActive` by comparing its item's path or view parameters to the current URL parameters. This ensures the active state always matches what's visible in the Explorer.
## Inspector Integration
The Inspector sidebar detects virtual files and renders appropriate content:
```typescript
if (isVirtualFile(file) && file._virtual?.type === "location") {
const locationData = file._virtual.data;
return ;
}
```
This allows clicking a virtual location in the device view to show the `LocationInspector` rather than the generic `FileInspector`.
## View Modes
All three view modes (grid, list, column) consume the same file array. When `useVirtualListing` returns virtual files, the views render them using the same components as real files. Icon overrides in `Thumb.tsx` check for `_virtual.iconUrl` and display custom icons for locations and volumes.
The grid view conditionally renders a volume capacity bar when displaying virtual volumes:
```typescript
const isVolume = isVirtualFile(file) && file._virtual?.type === "volume";
if (isVolume) {
return ;
}
```
This adds visual information without requiring volume-specific components or branching logic in the core Explorer.