mirror of
https://github.com/Xevion/byte-me.git
synced 2025-12-06 01:14:33 -06:00
refactor: reorganize frontend code
This commit is contained in:
36
src/App.tsx
36
src/App.tsx
@@ -1,38 +1,12 @@
|
||||
type Frame = {
|
||||
id: string;
|
||||
data: { x: string | number; y: number }[];
|
||||
};
|
||||
|
||||
import { getCurrentWebview } from "@tauri-apps/api/webview";
|
||||
import { useEffect, useState } from "react";
|
||||
import Graph from "./components/graph.js";
|
||||
import DropOverlay from "./components/drop-overlay.js";
|
||||
import { useDragDropPaths } from "./hooks/useDragDropPaths.js";
|
||||
import Graph from "./features/graph/graph.js";
|
||||
import DropOverlay from "./features/drop/drop-overlay.js";
|
||||
import type { Frame } from "./types/graph.js";
|
||||
|
||||
function App() {
|
||||
const data: Frame[] = [];
|
||||
|
||||
const [paths, setPaths] = useState<string[]>([]);
|
||||
useEffect(() => {
|
||||
const unlistenPromise = getCurrentWebview().onDragDropEvent(
|
||||
async ({ payload }) => {
|
||||
if (payload.type === "enter") {
|
||||
setPaths(payload.paths);
|
||||
console.log("User hovering", payload);
|
||||
} else if (payload.type === "leave" || payload.type === "drop") {
|
||||
setPaths([]);
|
||||
console.log("User left", payload);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// you need to call unlisten if your handler goes out of scope e.g. the component is unmounted
|
||||
return () => {
|
||||
unlistenPromise.then((unlisten) => {
|
||||
unlisten();
|
||||
console.log("Unlistened");
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
const paths = useDragDropPaths();
|
||||
|
||||
const graph = <Graph data={data} />;
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// Import generated TypeScript types from ts-rs
|
||||
export type { StreamResult } from "./bindings/StreamResult";
|
||||
export type { StreamDetail } from "./bindings/StreamDetail";
|
||||
export type { StreamResultError } from "./bindings/StreamResultError";
|
||||
export type { MediaType } from "./bindings/MediaType";
|
||||
import type { StreamResult } from "./bindings/StreamResult";
|
||||
import type { StreamDetail } from "./bindings/StreamDetail";
|
||||
import type { StreamResultError } from "./bindings/StreamResultError";
|
||||
import type { MediaType } from "./bindings/MediaType";
|
||||
export type { StreamResult, StreamDetail, StreamResultError, MediaType };
|
||||
|
||||
// Tauri invoke wrapper
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
import { ReactNode, useEffect, useRef, useState } from "react";
|
||||
import { match, P } from "ts-pattern";
|
||||
import {
|
||||
CheckCircle,
|
||||
File as FileIcon,
|
||||
FileText,
|
||||
Film,
|
||||
Image,
|
||||
Loader2,
|
||||
Music,
|
||||
XCircle,
|
||||
} from "lucide-react";
|
||||
import { commands } from "../../bindings";
|
||||
import type { MediaType, StreamDetail } from "../../bindings";
|
||||
|
||||
type DropOverlayProps = {
|
||||
paths: string[];
|
||||
@@ -14,31 +26,19 @@ type State =
|
||||
name: string;
|
||||
key: string;
|
||||
media_type: MediaType;
|
||||
duration?: number;
|
||||
duration?: number | null;
|
||||
size: number;
|
||||
streams: any[];
|
||||
streams: StreamDetail[];
|
||||
}[];
|
||||
}
|
||||
| { status: "error"; reason: string; filename?: string; error_type?: string };
|
||||
|
||||
import {
|
||||
CheckCircle,
|
||||
File as FileIcon,
|
||||
FileText,
|
||||
Film,
|
||||
Image,
|
||||
Loader2,
|
||||
Music,
|
||||
XCircle,
|
||||
} from "lucide-react";
|
||||
import { commands, MediaType } from "../bindings";
|
||||
|
||||
type FileItemProps = {
|
||||
filename: string;
|
||||
media_type: MediaType;
|
||||
duration?: number;
|
||||
duration?: number | null;
|
||||
size: number;
|
||||
streams: any[];
|
||||
streams: StreamDetail[];
|
||||
error?: string;
|
||||
error_type?: string;
|
||||
};
|
||||
@@ -57,7 +57,9 @@ const formatDuration = (seconds: number): string => {
|
||||
const secs = Math.floor(seconds % 60);
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours}:${minutes.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
|
||||
return `${hours}:${minutes.toString().padStart(2, "0")}:${secs
|
||||
.toString()
|
||||
.padStart(2, "0")}`;
|
||||
}
|
||||
return `${minutes}:${secs.toString().padStart(2, "0")}`;
|
||||
};
|
||||
@@ -107,7 +109,10 @@ const getFileIcon = (
|
||||
}
|
||||
};
|
||||
|
||||
const getStreamInfo = (streams: any[], mediaType: MediaType): string => {
|
||||
const getStreamInfo = (
|
||||
streams: StreamDetail[],
|
||||
mediaType: MediaType,
|
||||
): string => {
|
||||
// For non-media files, return file type description
|
||||
if (!["Audio", "Video", "Image"].includes(mediaType)) {
|
||||
switch (mediaType) {
|
||||
@@ -125,17 +130,17 @@ const getStreamInfo = (streams: any[], mediaType: MediaType): string => {
|
||||
}
|
||||
|
||||
// For media files, analyze streams
|
||||
const videoStreams = streams.filter((s) => "Video" in s);
|
||||
const audioStreams = streams.filter((s) => "Audio" in s);
|
||||
const subtitleStreams = streams.filter((s) => "Subtitle" in s);
|
||||
const videoStreams = streams.filter((s: any) => "Video" in s);
|
||||
const audioStreams = streams.filter((s: any) => "Audio" in s);
|
||||
const subtitleStreams = streams.filter((s: any) => "Subtitle" in s);
|
||||
|
||||
const parts = [];
|
||||
const parts = [] as string[];
|
||||
if (videoStreams.length > 0) {
|
||||
const video = videoStreams[0];
|
||||
const video: any = videoStreams[0] as any;
|
||||
if ("Video" in video) {
|
||||
const width = video.Video.width;
|
||||
const height = video.Video.height;
|
||||
const codec = video.Video.codec;
|
||||
const width = (video as any).Video.width;
|
||||
const height = (video as any).Video.height;
|
||||
const codec = (video as any).Video.codec;
|
||||
if (width && height) {
|
||||
parts.push(`${width}x${height} ${codec}`);
|
||||
} else {
|
||||
@@ -144,9 +149,9 @@ const getStreamInfo = (streams: any[], mediaType: MediaType): string => {
|
||||
}
|
||||
}
|
||||
if (audioStreams.length > 0) {
|
||||
const audio = audioStreams[0];
|
||||
const audio: any = audioStreams[0] as any;
|
||||
if ("Audio" in audio) {
|
||||
parts.push(`${audio.Audio.codec} audio`);
|
||||
parts.push(`${(audio as any).Audio.codec} audio`);
|
||||
}
|
||||
}
|
||||
if (subtitleStreams.length > 0) {
|
||||
@@ -219,7 +224,9 @@ const FileItem = ({
|
||||
} else {
|
||||
const streamInfo = getStreamInfo(streams, media_type);
|
||||
const durationStr = duration ? formatDuration(duration) : null;
|
||||
const details = [streamInfo, durationStr, fileSize].filter(Boolean);
|
||||
const details = [streamInfo, durationStr, fileSize].filter(
|
||||
Boolean,
|
||||
) as string[];
|
||||
subtitle = details.join(" • ");
|
||||
status = "success";
|
||||
}
|
||||
@@ -253,7 +260,7 @@ const DropOverlay = ({ paths }: DropOverlayProps) => {
|
||||
key: item.path,
|
||||
media_type: item.media_type,
|
||||
duration: item.duration,
|
||||
size: item.size,
|
||||
size: Number(item.size),
|
||||
streams: item.streams,
|
||||
})),
|
||||
}))
|
||||
@@ -343,7 +350,7 @@ const DropOverlay = ({ paths }: DropOverlayProps) => {
|
||||
</div>
|
||||
);
|
||||
})
|
||||
.with({ status: "error" }, ({ reason, error_type }) => {
|
||||
.with({ status: "error" }, ({ reason }) => {
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div className="flex items-center gap-2 text-red-400">
|
||||
@@ -1,10 +1,6 @@
|
||||
import { ResponsiveLine } from "@nivo/line";
|
||||
import { formatBytes } from "../lib/format.js";
|
||||
|
||||
type Frame = {
|
||||
id: string;
|
||||
data: { x: string | number; y: number }[];
|
||||
};
|
||||
import { formatBytes } from "../../lib/format.js";
|
||||
import type { Frame } from "../../types/graph.js";
|
||||
|
||||
type GraphProps = {
|
||||
data: Frame[];
|
||||
24
src/hooks/useDragDropPaths.ts
Normal file
24
src/hooks/useDragDropPaths.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { getCurrentWebview } from "@tauri-apps/api/webview";
|
||||
|
||||
export function useDragDropPaths(): string[] {
|
||||
const [paths, setPaths] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const unlistenPromise = getCurrentWebview().onDragDropEvent(
|
||||
async ({ payload }) => {
|
||||
if (payload.type === "enter") {
|
||||
setPaths(payload.paths);
|
||||
} else if (payload.type === "leave" || payload.type === "drop") {
|
||||
setPaths([]);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
unlistenPromise.then((unlisten) => unlisten());
|
||||
};
|
||||
}, []);
|
||||
|
||||
return paths;
|
||||
}
|
||||
4
src/types/graph.ts
Normal file
4
src/types/graph.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export type Frame = {
|
||||
id: string;
|
||||
data: { x: string | number; y: number }[];
|
||||
};
|
||||
Reference in New Issue
Block a user