mirror of
https://github.com/Xevion/byte-me.git
synced 2026-01-31 04:23:47 -06:00
refactor: reorganize frontend code
This commit is contained in:
+5
-31
@@ -1,38 +1,12 @@
|
|||||||
type Frame = {
|
import { useDragDropPaths } from "./hooks/useDragDropPaths.js";
|
||||||
id: string;
|
import Graph from "./features/graph/graph.js";
|
||||||
data: { x: string | number; y: number }[];
|
import DropOverlay from "./features/drop/drop-overlay.js";
|
||||||
};
|
import type { Frame } from "./types/graph.js";
|
||||||
|
|
||||||
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";
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const data: Frame[] = [];
|
const data: Frame[] = [];
|
||||||
|
|
||||||
const [paths, setPaths] = useState<string[]>([]);
|
const paths = useDragDropPaths();
|
||||||
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 graph = <Graph data={data} />;
|
const graph = <Graph data={data} />;
|
||||||
|
|
||||||
|
|||||||
+5
-4
@@ -1,8 +1,9 @@
|
|||||||
// Import generated TypeScript types from ts-rs
|
// Import generated TypeScript types from ts-rs
|
||||||
export type { StreamResult } from "./bindings/StreamResult";
|
import type { StreamResult } from "./bindings/StreamResult";
|
||||||
export type { StreamDetail } from "./bindings/StreamDetail";
|
import type { StreamDetail } from "./bindings/StreamDetail";
|
||||||
export type { StreamResultError } from "./bindings/StreamResultError";
|
import type { StreamResultError } from "./bindings/StreamResultError";
|
||||||
export type { MediaType } from "./bindings/MediaType";
|
import type { MediaType } from "./bindings/MediaType";
|
||||||
|
export type { StreamResult, StreamDetail, StreamResultError, MediaType };
|
||||||
|
|
||||||
// Tauri invoke wrapper
|
// Tauri invoke wrapper
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
import { ReactNode, useEffect, useRef, useState } from "react";
|
import { ReactNode, useEffect, useRef, useState } from "react";
|
||||||
import { match, P } from "ts-pattern";
|
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 = {
|
type DropOverlayProps = {
|
||||||
paths: string[];
|
paths: string[];
|
||||||
@@ -14,31 +26,19 @@ type State =
|
|||||||
name: string;
|
name: string;
|
||||||
key: string;
|
key: string;
|
||||||
media_type: MediaType;
|
media_type: MediaType;
|
||||||
duration?: number;
|
duration?: number | null;
|
||||||
size: number;
|
size: number;
|
||||||
streams: any[];
|
streams: StreamDetail[];
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
| { status: "error"; reason: string; filename?: string; error_type?: string };
|
| { 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 = {
|
type FileItemProps = {
|
||||||
filename: string;
|
filename: string;
|
||||||
media_type: MediaType;
|
media_type: MediaType;
|
||||||
duration?: number;
|
duration?: number | null;
|
||||||
size: number;
|
size: number;
|
||||||
streams: any[];
|
streams: StreamDetail[];
|
||||||
error?: string;
|
error?: string;
|
||||||
error_type?: string;
|
error_type?: string;
|
||||||
};
|
};
|
||||||
@@ -57,7 +57,9 @@ const formatDuration = (seconds: number): string => {
|
|||||||
const secs = Math.floor(seconds % 60);
|
const secs = Math.floor(seconds % 60);
|
||||||
|
|
||||||
if (hours > 0) {
|
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")}`;
|
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
|
// For non-media files, return file type description
|
||||||
if (!["Audio", "Video", "Image"].includes(mediaType)) {
|
if (!["Audio", "Video", "Image"].includes(mediaType)) {
|
||||||
switch (mediaType) {
|
switch (mediaType) {
|
||||||
@@ -125,17 +130,17 @@ const getStreamInfo = (streams: any[], mediaType: MediaType): string => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// For media files, analyze streams
|
// For media files, analyze streams
|
||||||
const videoStreams = streams.filter((s) => "Video" in s);
|
const videoStreams = streams.filter((s: any) => "Video" in s);
|
||||||
const audioStreams = streams.filter((s) => "Audio" in s);
|
const audioStreams = streams.filter((s: any) => "Audio" in s);
|
||||||
const subtitleStreams = streams.filter((s) => "Subtitle" in s);
|
const subtitleStreams = streams.filter((s: any) => "Subtitle" in s);
|
||||||
|
|
||||||
const parts = [];
|
const parts = [] as string[];
|
||||||
if (videoStreams.length > 0) {
|
if (videoStreams.length > 0) {
|
||||||
const video = videoStreams[0];
|
const video: any = videoStreams[0] as any;
|
||||||
if ("Video" in video) {
|
if ("Video" in video) {
|
||||||
const width = video.Video.width;
|
const width = (video as any).Video.width;
|
||||||
const height = video.Video.height;
|
const height = (video as any).Video.height;
|
||||||
const codec = video.Video.codec;
|
const codec = (video as any).Video.codec;
|
||||||
if (width && height) {
|
if (width && height) {
|
||||||
parts.push(`${width}x${height} ${codec}`);
|
parts.push(`${width}x${height} ${codec}`);
|
||||||
} else {
|
} else {
|
||||||
@@ -144,9 +149,9 @@ const getStreamInfo = (streams: any[], mediaType: MediaType): string => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (audioStreams.length > 0) {
|
if (audioStreams.length > 0) {
|
||||||
const audio = audioStreams[0];
|
const audio: any = audioStreams[0] as any;
|
||||||
if ("Audio" in audio) {
|
if ("Audio" in audio) {
|
||||||
parts.push(`${audio.Audio.codec} audio`);
|
parts.push(`${(audio as any).Audio.codec} audio`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (subtitleStreams.length > 0) {
|
if (subtitleStreams.length > 0) {
|
||||||
@@ -219,7 +224,9 @@ const FileItem = ({
|
|||||||
} else {
|
} else {
|
||||||
const streamInfo = getStreamInfo(streams, media_type);
|
const streamInfo = getStreamInfo(streams, media_type);
|
||||||
const durationStr = duration ? formatDuration(duration) : null;
|
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(" • ");
|
subtitle = details.join(" • ");
|
||||||
status = "success";
|
status = "success";
|
||||||
}
|
}
|
||||||
@@ -253,7 +260,7 @@ const DropOverlay = ({ paths }: DropOverlayProps) => {
|
|||||||
key: item.path,
|
key: item.path,
|
||||||
media_type: item.media_type,
|
media_type: item.media_type,
|
||||||
duration: item.duration,
|
duration: item.duration,
|
||||||
size: item.size,
|
size: Number(item.size),
|
||||||
streams: item.streams,
|
streams: item.streams,
|
||||||
})),
|
})),
|
||||||
}))
|
}))
|
||||||
@@ -343,7 +350,7 @@ const DropOverlay = ({ paths }: DropOverlayProps) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.with({ status: "error" }, ({ reason, error_type }) => {
|
.with({ status: "error" }, ({ reason }) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center gap-4">
|
<div className="flex flex-col items-center gap-4">
|
||||||
<div className="flex items-center gap-2 text-red-400">
|
<div className="flex items-center gap-2 text-red-400">
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
import { ResponsiveLine } from "@nivo/line";
|
import { ResponsiveLine } from "@nivo/line";
|
||||||
import { formatBytes } from "../lib/format.js";
|
import { formatBytes } from "../../lib/format.js";
|
||||||
|
import type { Frame } from "../../types/graph.js";
|
||||||
type Frame = {
|
|
||||||
id: string;
|
|
||||||
data: { x: string | number; y: number }[];
|
|
||||||
};
|
|
||||||
|
|
||||||
type GraphProps = {
|
type GraphProps = {
|
||||||
data: Frame[];
|
data: Frame[];
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export type Frame = {
|
||||||
|
id: string;
|
||||||
|
data: { x: string | number; y: number }[];
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user