Switch to ts-client types from root package

This commit is contained in:
Jamie Pine
2025-11-20 21:43:48 -08:00
parent 8c14803eae
commit 553fadd2d4
73 changed files with 1353 additions and 1450 deletions

View File

@@ -19,3 +19,4 @@

View File

@@ -21,19 +21,13 @@ use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Type)]
pub enum EntryKind {
/// Regular file
File {
/// File extension (without dot)
extension: Option<String>,
},
File,
/// Directory
Directory,
/// Symbolic link
Symlink {
/// Target path
target: String,
},
Symlink,
}
/// Represents a file within the Spacedrive VDFS.
@@ -49,9 +43,15 @@ pub struct File {
/// The universal path to the file in Spacedrive's VDFS
pub sd_path: SdPath,
/// The file kind (file, directory, symlink)
pub kind: EntryKind,
/// The name of the file, including the extension
pub name: String,
/// The file extension (without dot)
pub extension: Option<String>,
/// The size of the file in bytes
pub size: u64,
@@ -78,10 +78,8 @@ pub struct File {
pub accessed_at: Option<DateTime<Utc>>,
/// Additional computed fields
pub content_kind: ContentKind,
pub extension: Option<String>,
pub kind: EntryKind,
pub is_local: bool,
pub content_kind: ContentKind, // This is redundant with ContentIdentity, it lives inside
pub is_local: bool, // this is also redundant with SdPath
/// Video duration (for grid display optimization)
pub duration_seconds: Option<f64>,
@@ -226,22 +224,13 @@ impl File {
// Convert entity kind to domain EntryKind
let kind = match model.kind {
0 => EntryKind::File {
extension: model.extension.clone(),
},
0 => EntryKind::File,
1 => EntryKind::Directory,
2 => EntryKind::Symlink {
target: String::new(),
},
_ => EntryKind::File {
extension: model.extension.clone(),
},
2 => EntryKind::Symlink,
_ => EntryKind::File,
};
let extension = match &kind {
EntryKind::File { extension } => extension.clone(),
_ => None,
};
let extension = model.extension.clone();
// Generate UUID from id if uuid is None
let id = model.uuid.unwrap_or_else(|| {

View File

@@ -69,3 +69,4 @@ enum VideoMediaData {
}

View File

@@ -48,3 +48,4 @@ enum Entries {

View File

@@ -9,3 +9,4 @@ pub use input::LibraryOpenInput;
pub use output::LibraryOpenOutput;

View File

@@ -123,3 +123,4 @@ mod tests {
}

View File

@@ -15,3 +15,4 @@ pub struct VolumeTrackInput {

View File

@@ -12,3 +12,4 @@ pub struct VolumeUntrackInput {

View File

@@ -6,3 +6,4 @@ analysis.md

View File

@@ -40,3 +40,4 @@ required-features = ["cli"]

View File

@@ -51,3 +51,4 @@ fn main() -> Result<()> {

View File

@@ -18,3 +18,4 @@ pub fn export_json(templates: &[Template], groups: &[LogGroup]) -> Result<String
}

View File

@@ -240,3 +240,4 @@ mod tests {

View File

@@ -105,3 +105,4 @@ mod tests {

View File

@@ -22,7 +22,7 @@ import { QuickPreviewModal } from "./components/QuickPreview";
import { createExplorerRouter } from "./router";
import { useNormalizedCache } from "./context";
import { usePlatform } from "./platform";
import type { LocationInfo } from "@sd/ts-client/generated/types";
import type { LocationInfo } from "@sd/ts-client";
interface AppProps {
client: SpacedriveClient;

View File

@@ -1,7 +1,7 @@
import { ArrowSquareOut } from "@phosphor-icons/react";
import { useEffect, useState, useMemo } from "react";
import { useParams } from "react-router-dom";
import type { File, LocationInfo } from "@sd/ts-client/generated/types";
import type { File, LocationInfo } from "@sd/ts-client";
import { useLibraryQuery, useNormalizedCache } from "./context";
import { usePlatform } from "./platform";
import { useSelection } from "./components/Explorer/SelectionContext";

View File

@@ -1,5 +1,5 @@
import clsx from "clsx";
import type { File as FileType } from "@sd/ts-client/generated/types";
import type { File as FileType } from "@sd/ts-client";
import { Thumb } from "./Thumb";
import { Title } from "./Title";
import { Metadata } from "./Metadata";

View File

@@ -1,5 +1,5 @@
import clsx from "clsx";
import type { File } from "@sd/ts-client/generated/types";
import type { File } from "@sd/ts-client";
import { formatBytes, formatRelativeTime } from "../utils";
interface MetadataProps {

View File

@@ -1,16 +1,16 @@
import { useState, memo, useEffect } from "react";
import clsx from "clsx";
import { getIcon } from "@sd/assets/util";
import type { File } from "@sd/ts-client/generated/types";
import type { File } from "@sd/ts-client";
import { ThumbstripScrubber } from "./ThumbstripScrubber";
interface ThumbProps {
file: File;
size?: number;
className?: string;
frameClassName?: string; // Custom frame styling (border, radius, bg)
iconScale?: number; // Scale factor for fallback icon (0-1, default 1)
squareMode?: boolean; // Whether thumbnail is cropped to square (media view) or maintains aspect ratio
file: File;
size?: number;
className?: string;
frameClassName?: string; // Custom frame styling (border, radius, bg)
iconScale?: number; // Scale factor for fallback icon (0-1, default 1)
squareMode?: boolean; // Whether thumbnail is cropped to square (media view) or maintains aspect ratio
}
// Global cache for thumbnail loaded states (survives component unmount/remount)
@@ -18,197 +18,197 @@ const thumbLoadedCache = new Map<string, boolean>();
const thumbErrorCache = new Map<string, boolean>();
export const Thumb = memo(function Thumb({
file,
size = 100,
className,
frameClassName,
iconScale = 1,
squareMode = false,
file,
size = 100,
className,
frameClassName,
iconScale = 1,
squareMode = false,
}: ThumbProps) {
const cacheKey = `${file.id}-${size}`;
const cacheKey = `${file.id}-${size}`;
const [thumbLoaded, setThumbLoaded] = useState(
() => thumbLoadedCache.get(cacheKey) || false,
);
const [thumbError, setThumbError] = useState(
() => thumbErrorCache.get(cacheKey) || false,
);
const [thumbLoaded, setThumbLoaded] = useState(
() => thumbLoadedCache.get(cacheKey) || false,
);
const [thumbError, setThumbError] = useState(
() => thumbErrorCache.get(cacheKey) || false,
);
// Update cache when state changes
useEffect(() => {
if (thumbLoaded) thumbLoadedCache.set(cacheKey, true);
}, [thumbLoaded, cacheKey]);
// Update cache when state changes
useEffect(() => {
if (thumbLoaded) thumbLoadedCache.set(cacheKey, true);
}, [thumbLoaded, cacheKey]);
useEffect(() => {
if (thumbError) thumbErrorCache.set(cacheKey, true);
}, [thumbError, cacheKey]);
useEffect(() => {
if (thumbError) thumbErrorCache.set(cacheKey, true);
}, [thumbError, cacheKey]);
const iconSize = size * iconScale;
const iconSize = size * iconScale;
// Check if this is a video with thumbstrip sidecar
const isVideo = file.content_identity?.kind === "video";
const hasThumbstrip = file.sidecars?.some((s) => s.kind === "thumbstrip");
// Check if this is a video with thumbstrip sidecar
const isVideo = file.content_identity?.kind === "video";
const hasThumbstrip = file.sidecars?.some((s) => s.kind === "thumbstrip");
// Get appropriate thumbnail URL from sidecars based on size
const getThumbnailUrl = (targetSize: number) => {
const serverUrl = (window as any).__SPACEDRIVE_SERVER_URL__;
const libraryId = (window as any).__SPACEDRIVE_LIBRARY_ID__;
// Get appropriate thumbnail URL from sidecars based on size
const getThumbnailUrl = (targetSize: number) => {
const serverUrl = (window as any).__SPACEDRIVE_SERVER_URL__;
const libraryId = (window as any).__SPACEDRIVE_LIBRARY_ID__;
if (!serverUrl || !libraryId) {
return null;
}
if (!serverUrl || !libraryId) {
return null;
}
// Need content_identity to build sidecar URL
if (!file.content_identity?.uuid) {
return null;
}
// Need content_identity to build sidecar URL
if (!file.content_identity?.uuid) {
return null;
}
// Find thumbnail sidecar closest to requested size
const thumbnails = file.sidecars.filter((s) => s.kind === "thumb");
// Find thumbnail sidecar closest to requested size
const thumbnails = file.sidecars.filter((s) => s.kind === "thumb");
if (thumbnails.length === 0) {
return null;
}
if (thumbnails.length === 0) {
return null;
}
// Prefer 1x (lower resolution) variants for better performance
// Only use higher resolution for very large sizes (>400px)
const preferredSize = targetSize <= 400 ? targetSize * 0.6 : targetSize;
// Prefer 1x (lower resolution) variants for better performance
// Only use higher resolution for very large sizes (>400px)
const preferredSize = targetSize <= 400 ? targetSize * 0.6 : targetSize;
const thumbnail = thumbnails.sort((a, b) => {
// Parse variant (e.g., "grid@1x", "detail@1x") to get size and scale
const aSize = parseInt(
a.variant.split("x")[0]?.replace(/\D/g, "") || "0",
);
const bSize = parseInt(
b.variant.split("x")[0]?.replace(/\D/g, "") || "0",
);
const thumbnail = thumbnails.sort((a, b) => {
// Parse variant (e.g., "grid@1x", "detail@1x") to get size and scale
const aSize = parseInt(
a.variant.split("x")[0]?.replace(/\D/g, "") || "0",
);
const bSize = parseInt(
b.variant.split("x")[0]?.replace(/\D/g, "") || "0",
);
// Extract scale factor (1x, 2x, 3x) from variants like "grid@1x" or "detail@2x"
const aScaleMatch = a.variant.match(/@(\d+)x/);
const bScaleMatch = b.variant.match(/@(\d+)x/);
const aScale = aScaleMatch ? parseInt(aScaleMatch[1]) : 1;
const bScale = bScaleMatch ? parseInt(bScaleMatch[1]) : 1;
// Extract scale factor (1x, 2x, 3x) from variants like "grid@1x" or "detail@2x"
const aScaleMatch = a.variant.match(/@(\d+)x/);
const bScaleMatch = b.variant.match(/@(\d+)x/);
const aScale = aScaleMatch ? parseInt(aScaleMatch[1]) : 1;
const bScale = bScaleMatch ? parseInt(bScaleMatch[1]) : 1;
// Strongly prefer 1x variants (add penalty for higher scales)
const aPenalty = (aScale - 1) * 100;
const bPenalty = (bScale - 1) * 100;
// Strongly prefer 1x variants (add penalty for higher scales)
const aPenalty = (aScale - 1) * 100;
const bPenalty = (bScale - 1) * 100;
// Find closest match to preferred size, with scale penalty
return (
Math.abs(aSize - preferredSize) +
aPenalty -
(Math.abs(bSize - preferredSize) + bPenalty)
);
})[0];
// Find closest match to preferred size, with scale penalty
return (
Math.abs(aSize - preferredSize) +
aPenalty -
(Math.abs(bSize - preferredSize) + bPenalty)
);
})[0];
const contentUuid = file.content_identity.uuid;
const url = `${serverUrl}/sidecar/${libraryId}/${contentUuid}/${thumbnail.kind}/${thumbnail.variant}.${thumbnail.format}`;
const contentUuid = file.content_identity.uuid;
const url = `${serverUrl}/sidecar/${libraryId}/${contentUuid}/${thumbnail.kind}/${thumbnail.variant}.${thumbnail.format}`;
return url;
};
return url;
};
const thumbnailSrc = getThumbnailUrl(size);
const thumbnailSrc = getThumbnailUrl(size);
// Get Spacedrive asset icon (dark theme)
const kindCapitalized = file.content_identity?.kind
? file.content_identity.kind.charAt(0).toUpperCase() +
file.content_identity.kind.slice(1)
: "Document";
// This is jank and has to be done in several places. Ideally a util function.
const fileKind =
file?.content_identity?.kind && file.content_identity.kind !== "unknown"
? file.content_identity.kind
: file.kind === "File"
? file.extension || "File"
: file.kind;
// this too
const kindCapitalized = fileKind.charAt(0).toUpperCase() + fileKind.slice(1);
const icon = getIcon(
kindCapitalized,
true, // Dark theme
file.extension,
file.kind === "Directory",
);
const icon = getIcon(
kindCapitalized,
true, // Dark theme
file.extension,
file.kind === "Directory",
);
return (
<div
className={clsx(
"relative flex shrink-0 grow-0 items-center justify-center",
className,
)}
style={{
width: size,
height: size,
minWidth: size,
minHeight: size,
maxWidth: size,
maxHeight: size,
}}
>
{/* Always show icon first (instant), then thumbnail loads over it */}
<img
src={icon}
alt=""
className={clsx(
"object-contain transition-opacity",
thumbLoaded && "opacity-0",
)}
style={{
width: iconSize,
height: iconSize,
maxWidth: "100%",
maxHeight: "100%",
}}
/>
return (
<div
className={clsx(
"relative flex shrink-0 grow-0 items-center justify-center",
className,
)}
style={{
width: size,
height: size,
minWidth: size,
minHeight: size,
maxWidth: size,
maxHeight: size,
}}
>
{/* Always show icon first (instant), then thumbnail loads over it */}
<img
src={icon}
alt=""
className={clsx(
"object-contain transition-opacity",
thumbLoaded && "opacity-0",
)}
style={{
width: iconSize,
height: iconSize,
maxWidth: "100%",
maxHeight: "100%",
}}
/>
{/* Load thumbnail if available */}
{thumbnailSrc && !thumbError && (
<img
src={thumbnailSrc}
alt={file.name}
className={clsx(
"absolute inset-0 m-auto max-h-full max-w-full object-contain transition-opacity",
// Default frame styling (can be overridden)
frameClassName ||
"rounded-lg border border-app-line/50 bg-app-box/30",
!thumbLoaded && "opacity-0",
)}
onLoad={() => setThumbLoaded(true)}
onError={() => setThumbError(true)}
/>
)}
{/* Load thumbnail if available */}
{thumbnailSrc && !thumbError && (
<img
src={thumbnailSrc}
alt={file.name}
className={clsx(
"absolute inset-0 m-auto max-h-full max-w-full object-contain transition-opacity",
// Default frame styling (can be overridden)
frameClassName ||
"rounded-lg border border-app-line/50 bg-app-box/30",
!thumbLoaded && "opacity-0",
)}
onLoad={() => setThumbLoaded(true)}
onError={() => setThumbError(true)}
/>
)}
{/* Thumbstrip scrubber overlay (for videos with thumbstrips) */}
{isVideo && hasThumbstrip && thumbLoaded && (
<ThumbstripScrubber
file={file}
size={size}
squareMode={squareMode}
/>
)}
</div>
);
{/* Thumbstrip scrubber overlay (for videos with thumbstrips) */}
{isVideo && hasThumbstrip && thumbLoaded && (
<ThumbstripScrubber file={file} size={size} squareMode={squareMode} />
)}
</div>
);
});
export function Icon({
file,
size = 24,
className,
file,
size = 24,
className,
}: {
file: File;
size?: number;
className?: string;
file: File;
size?: number;
className?: string;
}) {
const kindCapitalized = file.content_identity?.kind
? file.content_identity.kind.charAt(0).toUpperCase() +
file.content_identity.kind.slice(1)
: "Document";
const kindCapitalized = file.content_identity?.kind
? file.content_identity.kind.charAt(0).toUpperCase() +
file.content_identity.kind.slice(1)
: "Document";
const icon = getIcon(
kindCapitalized,
true, // Dark theme
file.extension,
file.kind === "Directory",
);
const icon = getIcon(
kindCapitalized,
true, // Dark theme
file.kind.type === "File" ? file.kind.data?.extension : undefined,
file.kind.type === "Directory",
);
return (
<img
src={icon}
alt=""
className={className}
style={{ width: size, height: size }}
/>
);
return (
<img
src={icon}
alt=""
className={className}
style={{ width: size, height: size }}
/>
);
}

View File

@@ -1,6 +1,6 @@
import { useState, useRef, useEffect, memo } from "react";
import clsx from "clsx";
import type { File } from "@sd/ts-client/generated/types";
import type { File } from "@sd/ts-client";
interface ThumbstripScrubberProps {
file: File;

View File

@@ -1,6 +1,6 @@
import { useState, useEffect, useRef } from "react";
import clsx from "clsx";
import type { File } from "@sd/ts-client/generated/types";
import type { File } from "@sd/ts-client";
interface TitleProps {
file: File;

View File

@@ -1,6 +1,6 @@
import { createContext, useContext, useState, useCallback, useMemo, useEffect, type ReactNode } from "react";
import { usePlatform } from "../../platform";
import type { File } from "@sd/ts-client/generated/types";
import type { File } from "@sd/ts-client";
interface SelectionContextValue {
selectedFiles: File[];

View File

@@ -12,7 +12,7 @@ import {
import { motion, AnimatePresence } from "framer-motion";
import clsx from "clsx";
import { TopBarButton } from "@sd/ui";
import type { DirectorySortBy, MediaSortBy } from "@sd/ts-client/generated/types";
import type { DirectorySortBy, MediaSortBy } from "@sd/ts-client";
interface SortMenuProps {
sortBy: DirectorySortBy | MediaSortBy;

View File

@@ -15,7 +15,7 @@ import * as Tabs from "@sd/ui/Tabs";
import type {
IndexMode,
LocationAddInput,
} from "@sd/ts-client/generated/types";
} from "@sd/ts-client";
import { useLibraryMutation, useLibraryQuery } from "../../../context";
import { usePlatform } from "../../../platform";
import { NewLocation } from "@sd/assets/icons";

View File

@@ -1,5 +1,5 @@
import clsx from "clsx";
import type { SdPath } from "@sd/ts-client/generated/types";
import type { SdPath } from "@sd/ts-client";
interface BreadcrumbProps {
path: SdPath;

View File

@@ -1,7 +1,7 @@
import { useNavigate, useParams } from "react-router-dom";
import { useRef, useEffect } from "react";
import { Plus } from "@phosphor-icons/react";
import type { LocationInfo } from "@sd/ts-client/generated/types";
import type { LocationInfo } from "@sd/ts-client";
import { useNormalizedCache } from "../../../context";
import { Section } from "./Section";
import { SidebarItem } from "./SidebarItem";

View File

@@ -2,7 +2,7 @@ import { useState, useEffect } from "react";
import { motion } from "framer-motion";
import clsx from "clsx";
import { CaretRight } from "@phosphor-icons/react";
import type { SdPath, LibraryDeviceInfo } from "@sd/ts-client/generated/types";
import type { SdPath, LibraryDeviceInfo } from "@sd/ts-client";
import { getDeviceIconBySlug } from "@sd/ts-client";
import { sdPathToUri } from "../utils";
import LaptopIcon from "@sd/assets/icons/Laptop.png";

View File

@@ -16,7 +16,7 @@ import type {
LibraryDeviceInfo,
DirectorySortBy,
MediaSortBy,
} from "@sd/ts-client/generated/types";
} from "@sd/ts-client";
interface ViewSettings {
gridSize: number; // 80-400px

View File

@@ -2,7 +2,7 @@ import { useEffect } from "react";
import { useExplorer } from "../context";
import { useSelection } from "../SelectionContext";
import { useNormalizedCache } from "../../../context";
import type { DirectorySortBy } from "@sd/ts-client/generated/types";
import type { DirectorySortBy } from "@sd/ts-client";
export function useExplorerKeyboard() {
const { currentPath, sortBy, setCurrentPath, viewMode, viewSettings, sidebarVisible, inspectorVisible, openQuickPreview } = useExplorer();
@@ -85,7 +85,7 @@ export function useExplorerKeyboard() {
// Enter: Navigate into directory (for column view)
if (e.key === "Enter" && selectedFiles.length === 1) {
const selected = selectedFiles[0];
if (selected.kind === "Directory") {
if (selected.kind.type === "Directory") {
e.preventDefault();
setCurrentPath(selected.sd_path);
}

View File

@@ -2,7 +2,7 @@ import LaptopIcon from "@sd/assets/icons/Laptop.png";
import MobileIcon from "@sd/assets/icons/Mobile.png";
import ServerIcon from "@sd/assets/icons/Server.png";
import PCIcon from "@sd/assets/icons/PC.png";
import type { SdPath } from "@sd/ts-client/generated/types";
import type { SdPath } from "@sd/ts-client";
export function formatBytes(bytes: number): string {
if (bytes === 0) return "0 B";

View File

@@ -1,7 +1,7 @@
import { useRef, useMemo } from "react";
import { useVirtualizer } from "@tanstack/react-virtual";
import clsx from "clsx";
import type { File, SdPath } from "@sd/ts-client/generated/types";
import type { File, SdPath } from "@sd/ts-client";
import { useNormalizedCache } from "../../../../context";
import { ColumnItem } from "./ColumnItem";
import { useExplorer } from "../../context";
@@ -67,7 +67,7 @@ export function Column({ path, isActive, onNavigate }: ColumnProps) {
icon: FolderOpen,
label: "Open",
onClick: (file: File) => {
if (file.kind === "Directory") {
if (file.kind.type === "Directory") {
onNavigate(file.sd_path);
}
},

View File

@@ -1,5 +1,5 @@
import clsx from "clsx";
import type { File } from "@sd/ts-client/generated/types";
import type { File } from "@sd/ts-client";
import { File as FileComponent } from "../../File";
interface ColumnItemProps {
@@ -40,7 +40,7 @@ export function ColumnItem({
>
<FileComponent.Thumb file={file} size={20} />
<span className="text-sm truncate flex-1">{file.name}</span>
{file.kind === "Directory" && (
{file.kind.type === "Directory" && (
<svg
className="size-3 text-ink-dull"
fill="none"

View File

@@ -1,5 +1,5 @@
import { useState, useEffect, useMemo } from "react";
import type { SdPath } from "@sd/ts-client/generated/types";
import type { SdPath } from "@sd/ts-client";
import { useExplorer } from "../../context";
import { useSelection } from "../../SelectionContext";
import { Column } from "./Column";
@@ -19,7 +19,7 @@ export function ColumnView() {
// Add a column when a directory is selected
useEffect(() => {
if (selectedFiles.length === 1 && selectedFiles[0].kind === "Directory") {
if (selectedFiles.length === 1 && selectedFiles[0].kind.type === "Directory") {
const selectedDir = selectedFiles[0];
const selectedPath = selectedDir.sd_path;

View File

@@ -19,7 +19,7 @@ import {
Crop,
FileVideo,
} from "@phosphor-icons/react";
import type { File } from "@sd/ts-client/generated/types";
import type { File } from "@sd/ts-client";
import { File as FileComponent } from "../../File";
import { useExplorer } from "../../context";
import { useSelection } from "../../SelectionContext";
@@ -70,7 +70,7 @@ export const FileCard = memo(function FileCard({ file, fileIndex, allFiles, sele
icon: FolderOpen,
label: "Open",
onClick: () => {
if (file.kind === "Directory") {
if (file.kind.type === "Directory") {
setCurrentPath(file.sd_path);
} else {
console.log("Open file:", file.name);
@@ -78,7 +78,7 @@ export const FileCard = memo(function FileCard({ file, fileIndex, allFiles, sele
}
},
keybind: "⌘O",
condition: () => file.kind === "Directory" || file.kind === "File",
condition: () => file.kind.type === "Directory" || file.kind.type === "File",
},
{
icon: MagnifyingGlass,
@@ -308,7 +308,7 @@ export const FileCard = memo(function FileCard({ file, fileIndex, allFiles, sele
type: "submenu",
icon: FileText,
label: "Document Processing",
condition: () => ["pdf", "doc", "docx"].includes(file.extension || ""),
condition: () => ["pdf", "doc", "docx"].includes(file.kind.type === "File" ? file.kind.data?.extension || "" : ""),
submenu: [
{
icon: TextAa,
@@ -425,7 +425,7 @@ export const FileCard = memo(function FileCard({ file, fileIndex, allFiles, sele
};
const handleDoubleClick = () => {
if (file.kind === "Directory") {
if (file.kind.type === "Directory") {
setCurrentPath(file.sd_path);
}
};

View File

@@ -2,7 +2,7 @@ import { useExplorer } from "../../context";
import { useSelection } from "../../SelectionContext";
import { useNormalizedCache } from "../../../../context";
import { FileCard } from "./FileCard";
import type { DirectorySortBy } from "@sd/ts-client/generated/types";
import type { DirectorySortBy } from "@sd/ts-client";
export function GridView() {
const { currentPath, sortBy, viewSettings } = useExplorer();

View File

@@ -1,5 +1,5 @@
import clsx from "clsx";
import type { File } from "@sd/ts-client/generated/types";
import type { File } from "@sd/ts-client";
import { File as FileComponent } from "../../File";
import { useExplorer } from "../../context";
import { useSelection } from "../../SelectionContext";
@@ -24,7 +24,7 @@ export function FileRow({ file, fileIndex, allFiles }: FileRowProps) {
};
const handleDoubleClick = () => {
if (file.kind === "Directory") {
if (file.kind.type === "Directory") {
setCurrentPath(file.sd_path);
}
};
@@ -60,7 +60,7 @@ export function FileRow({ file, fileIndex, allFiles }: FileRowProps) {
{formatRelativeTime(file.modified_at)}
</div>
<div className="w-24 text-sm text-ink-dull">
{file.extension || "Folder"}
{file.kind.type === "File" ? file.kind.data?.extension || "—" : "Folder"}
</div>
</div>
);

View File

@@ -1,7 +1,7 @@
import { useExplorer } from "../../context";
import { useNormalizedCache } from "../../../../context";
import { FileRow } from "./FileRow";
import type { DirectorySortBy } from "@sd/ts-client/generated/types";
import type { DirectorySortBy } from "@sd/ts-client";
export function ListView() {
const { currentPath, sortBy } = useExplorer();

View File

@@ -17,7 +17,7 @@ import {
Crop,
FileVideo,
} from "@phosphor-icons/react";
import type { File } from "@sd/ts-client/generated/types";
import type { File } from "@sd/ts-client";
import { File as FileComponent } from "../../File";
import { useExplorer } from "../../context";
import { useSelection } from "../../SelectionContext";

View File

@@ -1,4 +1,4 @@
import type { File } from "@sd/ts-client/generated/types";
import type { File } from "@sd/ts-client";
export function formatDate(
date: Date | { from: Date; to: Date },

View File

@@ -1,5 +1,5 @@
import clsx from "clsx";
import type { File } from "@sd/ts-client/generated/types";
import type { File } from "@sd/ts-client";
import { formatBytes } from "../../utils";
interface SizeCircleProps {
@@ -11,7 +11,7 @@ interface SizeCircleProps {
// Get file extension or type
function getFileType(file: File): string {
if (file.kind === "Directory") return "Folder";
if (file.kind.type === "Directory") return "Folder";
const name = file.name;
const lastDot = name.lastIndexOf(".");
@@ -22,7 +22,7 @@ function getFileType(file: File): string {
// Get color based on file type
function getFileColor(file: File): string {
if (file.kind === "Directory") return "bg-blue-500";
if (file.kind.type === "Directory") return "bg-blue-500";
const ext = file.name.split(".").pop()?.toLowerCase() || "";

View File

@@ -1,6 +1,6 @@
import { useEffect, useRef, useMemo, useState } from "react";
import * as d3 from "d3";
import type { File, DirectorySortBy } from "@sd/ts-client/generated/types";
import type { File, DirectorySortBy } from "@sd/ts-client";
import { useExplorer } from "../../context";
import { useSelection } from "../../SelectionContext";
import { useNormalizedCache } from "../../../../context";
@@ -29,7 +29,7 @@ function getTailwindColor(className: string): string {
}
function getFileColorClass(file: File): string {
if (file.kind === "Directory") return "bg-accent";
if (file.kind.type === "Directory") return "bg-accent";
const ext = file.name.split(".").pop()?.toLowerCase() || "";
@@ -71,7 +71,7 @@ function getFileColor(file: File): string {
}
function getFileType(file: File): string {
if (file.kind === "Directory") return "Folder";
if (file.kind.type === "Directory") return "Folder";
const name = file.name;
const lastDot = name.lastIndexOf(".");
@@ -348,7 +348,7 @@ export function SizeView() {
}
// Navigate if directory
if (d.data.file.kind === "Directory") {
if (d.data.file.kind.type === "Directory") {
setCurrentPathRef.current(d.data.file.sd_path);
}
})

View File

@@ -1,4 +1,4 @@
import type { JobStatus } from "@sd/ts-client/generated/types";
import type { JobStatus } from "@sd/ts-client";
import { JOB_STATUS_COLORS, PROGRESS_BAR_HEIGHT } from "../types";
interface JobProgressBarProps {

View File

@@ -1,4 +1,4 @@
import type { JobStatus } from "@sd/ts-client/generated/types";
import type { JobStatus } from "@sd/ts-client";
import {
MagnifyingGlass,
Image,

View File

@@ -1,4 +1,4 @@
import type { JobStatus, JobListItem as GeneratedJobListItem, JsonValue, SdPath } from "@sd/ts-client/generated/types";
import type { JobStatus, JobListItem as GeneratedJobListItem, JsonValue, SdPath } from "@sd/ts-client";
// Extend the generated type with runtime fields from JobProgress events
export type JobListItem = GeneratedJobListItem & {

View File

@@ -8,7 +8,7 @@ import {
SkipForward,
} from "@phosphor-icons/react";
import { motion, AnimatePresence } from "framer-motion";
import type { File } from "@sd/ts-client/generated/types";
import type { File } from "@sd/ts-client";
import { File as FileComponent } from "../Explorer/File";
import { formatBytes } from "../Explorer/utils";

View File

@@ -1,4 +1,4 @@
import type { File, ContentKind } from "@sd/ts-client/generated/types";
import type { File, ContentKind } from "@sd/ts-client";
import { File as FileComponent } from "../Explorer/File";
import { formatBytes } from "../Explorer/utils";
import { usePlatform } from "../../platform";
@@ -295,7 +295,7 @@ function DefaultRenderer({ file }: ContentRendererProps) {
export function ContentRenderer({ file }: ContentRendererProps) {
// Handle directories first
if (file.kind === "Directory") {
if (file.kind.type === "Directory") {
return (
<div className="flex flex-col items-center justify-center h-full text-ink-dull">
<img src={Folder} alt="Folder Icon" className="w-16 h-16 mb-4" />

View File

@@ -1,6 +1,6 @@
import { useLibraryQuery } from "../../context";
import { usePlatform } from "../../platform";
import type { File } from "@sd/ts-client/generated/types";
import type { File } from "@sd/ts-client";
import { useEffect, useState } from "react";
import { formatBytes } from "../Explorer/utils";
import { X } from "@phosphor-icons/react";

View File

@@ -1,7 +1,7 @@
import { motion, AnimatePresence } from 'framer-motion';
import { X, ArrowLeft, ArrowRight } from '@phosphor-icons/react';
import { useEffect } from 'react';
import type { File } from '@sd/ts-client/generated/types';
import type { File } from '@sd/ts-client';
import { useLibraryQuery } from '../../context';
import { Inspector } from '../../Inspector';
import { ContentRenderer } from './ContentRenderer';

View File

@@ -1,5 +1,5 @@
import { useEffect, useState, useRef } from 'react';
import type { File } from '@sd/ts-client/generated/types';
import type { File } from '@sd/ts-client';
interface SubtitleCue {
index: number;

View File

@@ -1,5 +1,5 @@
import { memo } from 'react';
import type { File } from '@sd/ts-client/generated/types';
import type { File } from '@sd/ts-client';
interface TimelineScrubberProps {
file: File;

View File

@@ -1,7 +1,7 @@
import { useState, useRef, useEffect } from 'react';
import { Play, Pause, SpeakerHigh, SpeakerSlash, ArrowsOut, ClosedCaptioning, MagnifyingGlassPlus, MagnifyingGlassMinus, ArrowCounterClockwise, Gear } from '@phosphor-icons/react';
import { motion, AnimatePresence } from 'framer-motion';
import type { File } from '@sd/ts-client/generated/types';
import type { File } from '@sd/ts-client';
import { Subtitles, type SubtitleSettings } from './Subtitles';
import { SubtitleSettingsMenu } from './SubtitleSettingsMenu';
import { useZoomPan } from './useZoomPan';

View File

@@ -2,7 +2,7 @@ import { useState } from 'react';
import { Input, Label, dialogManager, useDialog, Dialog } from '@sd/ui';
import { useLibraryMutation } from '@sd/ts-client';
import { useForm } from 'react-hook-form';
import type { GroupType } from '@sd/ts-client/generated/types';
import type { GroupType } from '@sd/ts-client';
interface FormData {
groupName: string;

View File

@@ -1,6 +1,6 @@
import { CaretRight, Desktop } from '@phosphor-icons/react';
import clsx from 'clsx';
import type { SpaceItem as SpaceItemType } from '@sd/ts-client/generated/types';
import type { SpaceItem as SpaceItemType } from '@sd/ts-client';
import { SpaceItem } from './SpaceItem';
interface DeviceGroupProps {

View File

@@ -4,7 +4,7 @@ import type {
SpaceGroup as SpaceGroupType,
SpaceItem as SpaceItemType,
GroupType,
} from '@sd/ts-client/generated/types';
} from '@sd/ts-client';
import { useSidebarStore } from '@sd/ts-client/stores/sidebar';
import { SpaceItem } from './SpaceItem';
import { DeviceGroup } from './DeviceGroup';

View File

@@ -12,7 +12,7 @@ import { Location } from "@sd/assets/icons";
import type {
SpaceItem as SpaceItemType,
ItemType,
} from "@sd/ts-client/generated/types";
} from "@sd/ts-client";
interface SpaceItemProps {
item: SpaceItemType;

View File

@@ -1,7 +1,7 @@
import clsx from 'clsx';
import { CaretDown, Plus, GearSix } from '@phosphor-icons/react';
import { DropdownMenu } from '@sd/ui';
import type { Space } from '@sd/ts-client/generated/types';
import type { Space } from '@sd/ts-client';
import { useCreateSpaceDialog } from './CreateSpaceModal';
interface SpaceSwitcherProps {

View File

@@ -2,7 +2,7 @@ import { CaretRight } from "@phosphor-icons/react";
import clsx from "clsx";
import { useNormalizedCache } from "@sd/ts-client";
import { SpaceItem } from "./SpaceItem";
import type { VolumeItem } from "@sd/ts-client/generated/types";
import type { VolumeItem } from "@sd/ts-client";
interface VolumesGroupProps {
isCollapsed: boolean;

View File

@@ -8,7 +8,7 @@ import {
Share,
SignIn
} from '@phosphor-icons/react';
import type { LibrarySyncAction, PairedDeviceInfo, RemoteLibraryInfo } from '@sd/ts-client/generated/types';
import type { LibrarySyncAction, PairedDeviceInfo, RemoteLibraryInfo } from '@sd/ts-client';
import { Button, Dialog, dialogManager, useDialog } from '@sd/ui';
import { useCoreQuery, useCoreMutation, useSpacedriveClient } from '../context';

View File

@@ -28,4 +28,4 @@ export type {
LocationInfo,
LocationsListOutput,
LibraryInfo,
} from "@sd/ts-client/generated/types";
} from "@sd/ts-client";

View File

@@ -58,3 +58,4 @@ export function useJobDispatch() {

View File

File diff suppressed because it is too large Load Diff

View File

@@ -27,7 +27,7 @@ import {
TabContent,
} from "../components/Inspector";
import clsx from "clsx";
import type { LocationInfo } from "@sd/ts-client/generated/types";
import type { LocationInfo } from "@sd/ts-client";
import { useLibraryMutation } from "../context";
import LocationIcon from "@sd/assets/icons/Location.png";

View File

@@ -14,7 +14,7 @@ import type {
VolumeItem,
LibraryDeviceInfo,
ListLibraryDevicesInput,
} from "@sd/ts-client/generated/types";
} from "@sd/ts-client";
function formatBytes(bytes: number): string {
if (bytes === 0) return "0 B";

View File

@@ -11,7 +11,7 @@ import { DevicesPanel } from "./DevicesPanel";
import { ContentBreakdown } from "./ContentBreakdown";
import { OverviewTopBar } from "./OverviewTopBar";
import { useNormalizedCache } from "../../context";
import type { LibraryInfoOutput } from "@sd/ts-client/generated/types";
import type { LibraryInfoOutput } from "@sd/ts-client";
export function Overview() {
// Fetch library info with statistics using normalizedCache

View File

@@ -4,6 +4,16 @@
"description": "Type-safe TypeScript client for Spacedrive daemon",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./generated/types": {
"types": "./dist/generated/types.d.ts",
"default": "./dist/generated/types.js"
}
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",

View File

@@ -1,3 +1,4 @@
// @ts-nocheck
import type { LibraryDeviceInfo } from "./generated/types";
import Laptop from "@sd/assets/icons/Laptop.png";
import Mobile from "@sd/assets/icons/Mobile.png";

View File

@@ -88,6 +88,5 @@ export const DEFAULT_EVENT_SUBSCRIPTION: EventVariant[] = [
* Noisy events that are excluded from the default subscription
*/
export const NOISY_EVENTS: EventVariant[] = [
"LogMessage",
"IndexingProgress",
];

View File

@@ -3,6 +3,8 @@
// This file has been generated by Specta. DO NOT EDIT.
export type Empty = Record<string, never>;
export type ActionContextInfo = { action_type: string; initiated_at: string; initiated_by: string | null; action_input: JsonValue; context: JsonValue };
export type AddGroupInput = { space_id: string; name: string; group_type: GroupType };
@@ -390,11 +392,7 @@ export type EntryKind =
/**
* Regular file
*/
{ File: {
/**
* File extension (without dot)
*/
extension: string | null } } |
"File" |
/**
* Directory
*/
@@ -402,11 +400,7 @@ extension: string | null } } |
/**
* Symbolic link
*/
{ Symlink: {
/**
* Target path
*/
target: string } };
"Symlink";
/**
* Error event for tracking recent errors
@@ -511,10 +505,18 @@ id: string;
* The universal path to the file in Spacedrive's VDFS
*/
sd_path: SdPath;
/**
* The file kind (file, directory, symlink)
*/
kind: EntryKind;
/**
* The name of the file, including the extension
*/
name: string;
/**
* The file extension (without dot)
*/
extension: string | null;
/**
* The size of the file in bytes
*/
@@ -546,7 +548,7 @@ created_at: string; modified_at: string; accessed_at: string | null;
/**
* Additional computed fields
*/
content_kind: ContentKind; extension: string | null; kind: EntryKind; is_local: boolean;
content_kind: ContentKind; is_local: boolean;
/**
* Video duration (for grid display optimization)
*/
@@ -3177,185 +3179,185 @@ success: boolean };
// ===== API Type Unions =====
export type CoreAction =
{ type: 'network.start'; input: NetworkStartInput; output: NetworkStartOutput }
| { type: 'network.device.revoke'; input: DeviceRevokeInput; output: DeviceRevokeOutput }
| { type: 'network.pair.cancel'; input: PairCancelInput; output: PairCancelOutput }
| { type: 'network.stop'; input: NetworkStopInput; output: NetworkStopOutput }
| { type: 'network.sync_setup'; input: LibrarySyncSetupInput; output: LibrarySyncSetupOutput }
{ type: 'network.stop'; input: NetworkStopInput; output: NetworkStopOutput }
| { type: 'network.start'; input: NetworkStartInput; output: NetworkStartOutput }
| { type: 'network.pair.join'; input: PairJoinInput; output: PairJoinOutput }
| { type: 'network.pair.generate'; input: PairGenerateInput; output: PairGenerateOutput }
| { type: 'libraries.open'; input: LibraryOpenInput; output: LibraryOpenOutput }
| { type: 'network.spacedrop.send'; input: SpacedropSendInput; output: SpacedropSendOutput }
| { type: 'libraries.delete'; input: LibraryDeleteInput; output: LibraryDeleteOutput }
| { type: 'libraries.create'; input: LibraryCreateInput; output: LibraryCreateOutput }
| { type: 'models.whisper.delete'; input: DeleteWhisperModelInput; output: DeleteWhisperModelOutput }
| { type: 'models.whisper.download'; input: DownloadWhisperModelInput; output: DownloadWhisperModelOutput }
| { type: 'network.sync_setup'; input: LibrarySyncSetupInput; output: LibrarySyncSetupOutput }
| { type: 'network.device.revoke'; input: DeviceRevokeInput; output: DeviceRevokeOutput }
| { type: 'libraries.delete'; input: LibraryDeleteInput; output: LibraryDeleteOutput }
| { type: 'network.pair.cancel'; input: PairCancelInput; output: PairCancelOutput }
| { type: 'network.spacedrop.send'; input: SpacedropSendInput; output: SpacedropSendOutput }
;
export type LibraryAction =
{ type: 'volumes.track'; input: VolumeTrackInput; output: VolumeTrackOutput }
| { type: 'locations.rescan'; input: LocationRescanInput; output: LocationRescanOutput }
| { type: 'volumes.untrack'; input: VolumeUntrackInput; output: VolumeUntrackOutput }
| { type: 'volumes.remove_cloud'; input: VolumeRemoveCloudInput; output: VolumeRemoveCloudOutput }
| { type: 'media.ocr.extract'; input: ExtractTextInput; output: ExtractTextOutput }
| { type: 'spaces.delete_item'; input: DeleteItemInput; output: DeleteItemOutput }
| { type: 'libraries.rename'; input: LibraryRenameInput; output: LibraryRenameOutput }
| { type: 'indexing.start'; input: IndexInput; output: JobReceipt }
| { type: 'volumes.speed_test'; input: VolumeSpeedTestInput; output: VolumeSpeedTestOutput }
| { type: 'spaces.delete'; input: SpaceDeleteInput; output: SpaceDeleteOutput }
| { type: 'media.proxy.generate'; input: GenerateProxyInput; output: GenerateProxyOutput }
| { type: 'volumes.add_cloud'; input: VolumeAddCloudInput; output: VolumeAddCloudOutput }
| { type: 'indexing.verify'; input: IndexVerifyInput; output: IndexVerifyOutput }
| { type: 'jobs.resume'; input: JobResumeInput; output: JobResumeOutput }
| { type: 'files.copy'; input: FileCopyInput; output: JobReceipt }
| { type: 'spaces.delete_group'; input: DeleteGroupInput; output: DeleteGroupOutput }
| { type: 'spaces.update_group'; input: UpdateGroupInput; output: UpdateGroupOutput }
| { type: 'media.speech.transcribe'; input: TranscribeAudioInput; output: TranscribeAudioOutput }
| { type: 'locations.remove'; input: LocationRemoveInput; output: LocationRemoveOutput }
| { type: 'spaces.reorder_items'; input: ReorderItemsInput; output: ReorderOutput }
| { type: 'spaces.reorder_groups'; input: ReorderGroupsInput; output: ReorderOutput }
| { type: 'tags.apply'; input: ApplyTagsInput; output: ApplyTagsOutput }
| { type: 'volumes.refresh'; input: VolumeRefreshInput; output: VolumeRefreshOutput }
{ type: 'jobs.resume'; input: JobResumeInput; output: JobResumeOutput }
| { type: 'media.thumbnail.regenerate'; input: RegenerateThumbnailInput; output: RegenerateThumbnailOutput }
| { type: 'media.thumbnail'; input: ThumbnailInput; output: JobReceipt }
| { type: 'media.thumbstrip.generate'; input: GenerateThumbstripInput; output: GenerateThumbstripOutput }
| { type: 'spaces.add_item'; input: AddItemInput; output: AddItemOutput }
| { type: 'jobs.pause'; input: JobPauseInput; output: JobPauseOutput }
| { type: 'locations.update'; input: LocationUpdateInput; output: LocationUpdateOutput }
| { type: 'locations.add'; input: LocationAddInput; output: LocationAddOutput }
| { type: 'jobs.cancel'; input: JobCancelInput; output: JobCancelOutput }
| { type: 'spaces.add_group'; input: AddGroupInput; output: AddGroupOutput }
| { type: 'spaces.update'; input: SpaceUpdateInput; output: SpaceUpdateOutput }
| { type: 'files.delete'; input: FileDeleteInput; output: JobReceipt }
| { type: 'tags.create'; input: CreateTagInput; output: CreateTagOutput }
| { type: 'libraries.rename'; input: LibraryRenameInput; output: LibraryRenameOutput }
| { type: 'spaces.delete_group'; input: DeleteGroupInput; output: DeleteGroupOutput }
| { type: 'tags.apply'; input: ApplyTagsInput; output: ApplyTagsOutput }
| { type: 'locations.rescan'; input: LocationRescanInput; output: LocationRescanOutput }
| { type: 'volumes.add_cloud'; input: VolumeAddCloudInput; output: VolumeAddCloudOutput }
| { type: 'libraries.export'; input: LibraryExportInput; output: LibraryExportOutput }
| { type: 'volumes.speed_test'; input: VolumeSpeedTestInput; output: VolumeSpeedTestOutput }
| { type: 'spaces.create'; input: SpaceCreateInput; output: SpaceCreateOutput }
| { type: 'spaces.delete'; input: SpaceDeleteInput; output: SpaceDeleteOutput }
| { type: 'locations.add'; input: LocationAddInput; output: LocationAddOutput }
| { type: 'indexing.start'; input: IndexInput; output: JobReceipt }
| { type: 'spaces.add_group'; input: AddGroupInput; output: AddGroupOutput }
| { type: 'tags.create'; input: CreateTagInput; output: CreateTagOutput }
| { type: 'spaces.delete_item'; input: DeleteItemInput; output: DeleteItemOutput }
| { type: 'jobs.cancel'; input: JobCancelInput; output: JobCancelOutput }
| { type: 'volumes.track'; input: VolumeTrackInput; output: VolumeTrackOutput }
| { type: 'spaces.add_item'; input: AddItemInput; output: AddItemOutput }
| { type: 'spaces.update'; input: SpaceUpdateInput; output: SpaceUpdateOutput }
| { type: 'jobs.pause'; input: JobPauseInput; output: JobPauseOutput }
| { type: 'media.proxy.generate'; input: GenerateProxyInput; output: GenerateProxyOutput }
| { type: 'volumes.remove_cloud'; input: VolumeRemoveCloudInput; output: VolumeRemoveCloudOutput }
| { type: 'media.ocr.extract'; input: ExtractTextInput; output: ExtractTextOutput }
| { type: 'volumes.refresh'; input: VolumeRefreshInput; output: VolumeRefreshOutput }
| { type: 'volumes.untrack'; input: VolumeUntrackInput; output: VolumeUntrackOutput }
| { type: 'locations.update'; input: LocationUpdateInput; output: LocationUpdateOutput }
| { type: 'locations.triggerJob'; input: LocationTriggerJobInput; output: LocationTriggerJobOutput }
| { type: 'files.copy'; input: FileCopyInput; output: JobReceipt }
| { type: 'media.speech.transcribe'; input: TranscribeAudioInput; output: TranscribeAudioOutput }
| { type: 'indexing.verify'; input: IndexVerifyInput; output: IndexVerifyOutput }
| { type: 'locations.remove'; input: LocationRemoveInput; output: LocationRemoveOutput }
| { type: 'files.delete'; input: FileDeleteInput; output: JobReceipt }
| { type: 'media.thumbstrip.generate'; input: GenerateThumbstripInput; output: GenerateThumbstripOutput }
| { type: 'spaces.update_group'; input: UpdateGroupInput; output: UpdateGroupOutput }
| { type: 'spaces.reorder_items'; input: ReorderItemsInput; output: ReorderOutput }
| { type: 'spaces.reorder_groups'; input: ReorderGroupsInput; output: ReorderOutput }
;
export type CoreQuery =
{ type: 'libraries.list'; input: ListLibrariesInput; output: [LibraryInfo] }
| { type: 'core.status'; input: Empty; output: CoreStatus }
| { type: 'network.status'; input: NetworkStatusQueryInput; output: NetworkStatus }
{ type: 'network.devices.list'; input: ListPairedDevicesInput; output: ListPairedDevicesOutput }
| { type: 'models.whisper.list'; input: ListWhisperModelsInput; output: ListWhisperModelsOutput }
| { type: 'network.pair.status'; input: PairStatusQueryInput; output: PairStatusOutput }
| { type: 'network.sync_setup.discover'; input: DiscoverRemoteLibrariesInput; output: DiscoverRemoteLibrariesOutput }
| { type: 'core.events.list'; input: ListEventsInput; output: ListEventsOutput }
| { type: 'network.pair.status'; input: PairStatusQueryInput; output: PairStatusOutput }
| { type: 'network.devices.list'; input: ListPairedDevicesInput; output: ListPairedDevicesOutput }
| { type: 'libraries.list'; input: ListLibrariesInput; output: [LibraryInfo] }
| { type: 'core.status'; input: Empty; output: CoreStatus }
| { type: 'network.status'; input: NetworkStatusQueryInput; output: NetworkStatus }
;
export type LibraryQuery =
{ type: 'files.by_id'; input: FileByIdQuery; output: File }
| { type: 'spaces.get_layout'; input: SpaceLayoutQueryInput; output: SpaceLayout }
{ type: 'files.media_listing'; input: MediaListingInput; output: MediaListingOutput }
| { type: 'tags.search'; input: SearchTagsInput; output: SearchTagsOutput }
| { type: 'locations.list'; input: LocationsListQueryInput; output: LocationsListOutput }
| { type: 'test.ping'; input: PingInput; output: PingOutput }
| { type: 'spaces.list'; input: SpacesListQueryInput; output: SpacesListOutput }
| { type: 'sync.metrics'; input: GetSyncMetricsInput; output: GetSyncMetricsOutput }
| { type: 'files.media_listing'; input: MediaListingInput; output: MediaListingOutput }
| { type: 'jobs.info'; input: JobInfoQueryInput; output: JobInfoOutput }
| { type: 'libraries.info'; input: LibraryInfoQueryInput; output: LibraryInfoOutput }
| { type: 'files.unique_to_location'; input: UniqueToLocationInput; output: UniqueToLocationOutput }
| { type: 'files.by_path'; input: FileByPathQuery; output: File }
| { type: 'locations.suggested'; input: SuggestedLocationsQueryInput; output: SuggestedLocationsOutput }
| { type: 'search.files'; input: FileSearchInput; output: FileSearchOutput }
| { type: 'jobs.list'; input: JobListInput; output: JobListOutput }
| { type: 'devices.list'; input: ListLibraryDevicesInput; output: [LibraryDeviceInfo] }
| { type: 'volumes.list'; input: VolumeListQueryInput; output: VolumeListOutput }
| { type: 'spaces.get'; input: SpaceGetQueryInput; output: SpaceGetOutput }
| { type: 'spaces.list'; input: SpacesListQueryInput; output: SpacesListOutput }
| { type: 'libraries.info'; input: LibraryInfoQueryInput; output: LibraryInfoOutput }
| { type: 'search.files'; input: FileSearchInput; output: FileSearchOutput }
| { type: 'sync.metrics'; input: GetSyncMetricsInput; output: GetSyncMetricsOutput }
| { type: 'jobs.info'; input: JobInfoQueryInput; output: JobInfoOutput }
| { type: 'files.unique_to_location'; input: UniqueToLocationInput; output: UniqueToLocationOutput }
| { type: 'jobs.list'; input: JobListInput; output: JobListOutput }
| { type: 'volumes.list'; input: VolumeListQueryInput; output: VolumeListOutput }
| { type: 'locations.list'; input: LocationsListQueryInput; output: LocationsListOutput }
| { type: 'locations.suggested'; input: SuggestedLocationsQueryInput; output: SuggestedLocationsOutput }
| { type: 'files.by_path'; input: FileByPathQuery; output: File }
| { type: 'files.directory_listing'; input: DirectoryListingInput; output: DirectoryListingOutput }
| { type: 'devices.list'; input: ListLibraryDevicesInput; output: [LibraryDeviceInfo] }
| { type: 'files.by_id'; input: FileByIdQuery; output: File }
| { type: 'test.ping'; input: PingInput; output: PingOutput }
| { type: 'spaces.get_layout'; input: SpaceLayoutQueryInput; output: SpaceLayout }
;
// ===== Wire Method Mappings =====
export const WIRE_METHODS = {
coreActions: {
'network.start': 'action:network.start.input',
'network.device.revoke': 'action:network.device.revoke.input',
'network.pair.cancel': 'action:network.pair.cancel.input',
'network.stop': 'action:network.stop.input',
'network.sync_setup': 'action:network.sync_setup.input',
'network.start': 'action:network.start.input',
'network.pair.join': 'action:network.pair.join.input',
'network.pair.generate': 'action:network.pair.generate.input',
'libraries.open': 'action:libraries.open.input',
'network.spacedrop.send': 'action:network.spacedrop.send.input',
'libraries.delete': 'action:libraries.delete.input',
'libraries.create': 'action:libraries.create.input',
'models.whisper.delete': 'action:models.whisper.delete.input',
'models.whisper.download': 'action:models.whisper.download.input',
'network.sync_setup': 'action:network.sync_setup.input',
'network.device.revoke': 'action:network.device.revoke.input',
'libraries.delete': 'action:libraries.delete.input',
'network.pair.cancel': 'action:network.pair.cancel.input',
'network.spacedrop.send': 'action:network.spacedrop.send.input',
},
libraryActions: {
'volumes.track': 'action:volumes.track.input',
'locations.rescan': 'action:locations.rescan.input',
'volumes.untrack': 'action:volumes.untrack.input',
'volumes.remove_cloud': 'action:volumes.remove_cloud.input',
'media.ocr.extract': 'action:media.ocr.extract.input',
'spaces.delete_item': 'action:spaces.delete_item.input',
'libraries.rename': 'action:libraries.rename.input',
'indexing.start': 'action:indexing.start.input',
'volumes.speed_test': 'action:volumes.speed_test.input',
'spaces.delete': 'action:spaces.delete.input',
'media.proxy.generate': 'action:media.proxy.generate.input',
'volumes.add_cloud': 'action:volumes.add_cloud.input',
'indexing.verify': 'action:indexing.verify.input',
'jobs.resume': 'action:jobs.resume.input',
'files.copy': 'action:files.copy.input',
'spaces.delete_group': 'action:spaces.delete_group.input',
'spaces.update_group': 'action:spaces.update_group.input',
'media.speech.transcribe': 'action:media.speech.transcribe.input',
'locations.remove': 'action:locations.remove.input',
'spaces.reorder_items': 'action:spaces.reorder_items.input',
'spaces.reorder_groups': 'action:spaces.reorder_groups.input',
'tags.apply': 'action:tags.apply.input',
'volumes.refresh': 'action:volumes.refresh.input',
'media.thumbnail.regenerate': 'action:media.thumbnail.regenerate.input',
'media.thumbnail': 'action:media.thumbnail.input',
'media.thumbstrip.generate': 'action:media.thumbstrip.generate.input',
'spaces.add_item': 'action:spaces.add_item.input',
'jobs.pause': 'action:jobs.pause.input',
'locations.update': 'action:locations.update.input',
'locations.add': 'action:locations.add.input',
'jobs.cancel': 'action:jobs.cancel.input',
'spaces.add_group': 'action:spaces.add_group.input',
'spaces.update': 'action:spaces.update.input',
'files.delete': 'action:files.delete.input',
'tags.create': 'action:tags.create.input',
'libraries.rename': 'action:libraries.rename.input',
'spaces.delete_group': 'action:spaces.delete_group.input',
'tags.apply': 'action:tags.apply.input',
'locations.rescan': 'action:locations.rescan.input',
'volumes.add_cloud': 'action:volumes.add_cloud.input',
'libraries.export': 'action:libraries.export.input',
'volumes.speed_test': 'action:volumes.speed_test.input',
'spaces.create': 'action:spaces.create.input',
'spaces.delete': 'action:spaces.delete.input',
'locations.add': 'action:locations.add.input',
'indexing.start': 'action:indexing.start.input',
'spaces.add_group': 'action:spaces.add_group.input',
'tags.create': 'action:tags.create.input',
'spaces.delete_item': 'action:spaces.delete_item.input',
'jobs.cancel': 'action:jobs.cancel.input',
'volumes.track': 'action:volumes.track.input',
'spaces.add_item': 'action:spaces.add_item.input',
'spaces.update': 'action:spaces.update.input',
'jobs.pause': 'action:jobs.pause.input',
'media.proxy.generate': 'action:media.proxy.generate.input',
'volumes.remove_cloud': 'action:volumes.remove_cloud.input',
'media.ocr.extract': 'action:media.ocr.extract.input',
'volumes.refresh': 'action:volumes.refresh.input',
'volumes.untrack': 'action:volumes.untrack.input',
'locations.update': 'action:locations.update.input',
'locations.triggerJob': 'action:locations.triggerJob.input',
'files.copy': 'action:files.copy.input',
'media.speech.transcribe': 'action:media.speech.transcribe.input',
'indexing.verify': 'action:indexing.verify.input',
'locations.remove': 'action:locations.remove.input',
'files.delete': 'action:files.delete.input',
'media.thumbstrip.generate': 'action:media.thumbstrip.generate.input',
'spaces.update_group': 'action:spaces.update_group.input',
'spaces.reorder_items': 'action:spaces.reorder_items.input',
'spaces.reorder_groups': 'action:spaces.reorder_groups.input',
},
coreQueries: {
'network.devices.list': 'query:network.devices.list',
'models.whisper.list': 'query:models.whisper.list',
'network.pair.status': 'query:network.pair.status',
'network.sync_setup.discover': 'query:network.sync_setup.discover',
'core.events.list': 'query:core.events.list',
'libraries.list': 'query:libraries.list',
'core.status': 'query:core.status',
'network.status': 'query:network.status',
'models.whisper.list': 'query:models.whisper.list',
'network.sync_setup.discover': 'query:network.sync_setup.discover',
'core.events.list': 'query:core.events.list',
'network.pair.status': 'query:network.pair.status',
'network.devices.list': 'query:network.devices.list',
},
libraryQueries: {
'files.by_id': 'query:files.by_id',
'spaces.get_layout': 'query:spaces.get_layout',
'tags.search': 'query:tags.search',
'locations.list': 'query:locations.list',
'test.ping': 'query:test.ping',
'spaces.list': 'query:spaces.list',
'sync.metrics': 'query:sync.metrics',
'files.media_listing': 'query:files.media_listing',
'jobs.info': 'query:jobs.info',
'libraries.info': 'query:libraries.info',
'files.unique_to_location': 'query:files.unique_to_location',
'files.by_path': 'query:files.by_path',
'locations.suggested': 'query:locations.suggested',
'search.files': 'query:search.files',
'jobs.list': 'query:jobs.list',
'devices.list': 'query:devices.list',
'volumes.list': 'query:volumes.list',
'tags.search': 'query:tags.search',
'spaces.get': 'query:spaces.get',
'spaces.list': 'query:spaces.list',
'libraries.info': 'query:libraries.info',
'search.files': 'query:search.files',
'sync.metrics': 'query:sync.metrics',
'jobs.info': 'query:jobs.info',
'files.unique_to_location': 'query:files.unique_to_location',
'jobs.list': 'query:jobs.list',
'volumes.list': 'query:volumes.list',
'locations.list': 'query:locations.list',
'locations.suggested': 'query:locations.suggested',
'files.by_path': 'query:files.by_path',
'files.directory_listing': 'query:files.directory_listing',
'devices.list': 'query:devices.list',
'files.by_id': 'query:files.by_id',
'test.ping': 'query:test.ping',
'spaces.get_layout': 'query:spaces.get_layout',
},
} as const;

View File

@@ -1,3 +1,4 @@
// @ts-nocheck
import { useMutation, type UseMutationOptions, type UseMutationResult } from "@tanstack/react-query";
import { useSpacedriveClient } from "./useClient";
import type { CoreAction, LibraryAction } from "../generated/types";

View File

@@ -205,6 +205,11 @@ export function handleResourceEvent(
queryKey: any[],
queryClient: QueryClient,
) {
// Skip string events (like "CoreStarted", "CoreShutdown")
if (typeof event === "string") {
return;
}
// Refresh event - invalidate all queries
if ("Refresh" in event) {
queryClient.invalidateQueries();

View File

@@ -1,3 +1,4 @@
// @ts-nocheck
import { useQuery, type UseQueryOptions, type UseQueryResult } from "@tanstack/react-query";
import { useSpacedriveClient } from "./useClient";
import type { CoreQuery, LibraryQuery } from "../generated/types";

View File

@@ -20,6 +20,7 @@ interface EventFilter {
resource_type?: string;
path_scope?: any;
include_descendants?: boolean;
event_types?: string[];
}
interface SubscriptionEntry {

View File

@@ -19,5 +19,5 @@
"types": ["jest", "node"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.test.tsx", "**/__tests__/**"]
}