summaryrefslogtreecommitdiff
path: root/apps/web/src/stores/media-store.ts
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2025-06-22 19:28:03 +0200
committerMaze Winther <mazewinther@gmail.com>2025-06-22 19:28:03 +0200
commit6ee16f9df8aeabfef0ca533f9f5b13daab3a3758 (patch)
tree75a9db32a8c4e4a60c54e35c914444b00d1a76d9 /apps/web/src/stores/media-store.ts
parente22aa6620c5cdf97c71947e96e894313ac3e895e (diff)
downloadopencut-6ee16f9df8aeabfef0ca533f9f5b13daab3a3758.tar.gz
feat: so much stuff
Diffstat (limited to 'apps/web/src/stores/media-store.ts')
-rw-r--r--apps/web/src/stores/media-store.ts135
1 files changed, 135 insertions, 0 deletions
diff --git a/apps/web/src/stores/media-store.ts b/apps/web/src/stores/media-store.ts
index 25d11af8..bdb4d52a 100644
--- a/apps/web/src/stores/media-store.ts
+++ b/apps/web/src/stores/media-store.ts
@@ -4,6 +4,11 @@ export interface MediaItem {
id: string;
name: string;
type: "image" | "video" | "audio";
+ file: File;
+ url: string; // Object URL for preview
+ thumbnailUrl?: string; // For video thumbnails
+ duration?: number; // For video/audio duration
+ aspectRatio: number; // width / height
}
interface MediaStore {
@@ -12,8 +17,113 @@ interface MediaStore {
// Actions
addMediaItem: (item: Omit<MediaItem, "id">) => void;
removeMediaItem: (id: string) => void;
+ clearAllMedia: () => void;
}
+// Helper function to determine file type
+export const getFileType = (file: File): "image" | "video" | "audio" | null => {
+ const { type } = file;
+
+ if (type.startsWith("image/")) {
+ return "image";
+ }
+ if (type.startsWith("video/")) {
+ return "video";
+ }
+ if (type.startsWith("audio/")) {
+ return "audio";
+ }
+
+ return null;
+};
+
+// Helper function to get image aspect ratio
+export const getImageAspectRatio = (file: File): Promise<number> => {
+ return new Promise((resolve, reject) => {
+ const img = new Image();
+
+ img.addEventListener("load", () => {
+ const aspectRatio = img.naturalWidth / img.naturalHeight;
+ resolve(aspectRatio);
+ img.remove();
+ });
+
+ img.addEventListener("error", () => {
+ reject(new Error("Could not load image"));
+ img.remove();
+ });
+
+ img.src = URL.createObjectURL(file);
+ });
+};
+
+// Helper function to generate video thumbnail and get aspect ratio
+export const generateVideoThumbnail = (
+ file: File
+): Promise<{ thumbnailUrl: string; aspectRatio: number }> => {
+ return new Promise((resolve, reject) => {
+ const video = document.createElement("video");
+ const canvas = document.createElement("canvas");
+ const ctx = canvas.getContext("2d");
+
+ if (!ctx) {
+ reject(new Error("Could not get canvas context"));
+ return;
+ }
+
+ video.addEventListener("loadedmetadata", () => {
+ canvas.width = video.videoWidth;
+ canvas.height = video.videoHeight;
+
+ // Seek to 1 second or 10% of duration, whichever is smaller
+ video.currentTime = Math.min(1, video.duration * 0.1);
+ });
+
+ video.addEventListener("seeked", () => {
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
+ const thumbnailUrl = canvas.toDataURL("image/jpeg", 0.8);
+ const aspectRatio = video.videoWidth / video.videoHeight;
+
+ resolve({ thumbnailUrl, aspectRatio });
+
+ // Cleanup
+ video.remove();
+ canvas.remove();
+ });
+
+ video.addEventListener("error", () => {
+ reject(new Error("Could not load video"));
+ video.remove();
+ canvas.remove();
+ });
+
+ video.src = URL.createObjectURL(file);
+ video.load();
+ });
+};
+
+// Helper function to get media duration
+export const getMediaDuration = (file: File): Promise<number> => {
+ return new Promise((resolve, reject) => {
+ const element = document.createElement(
+ file.type.startsWith("video/") ? "video" : "audio"
+ ) as HTMLVideoElement | HTMLAudioElement;
+
+ element.addEventListener("loadedmetadata", () => {
+ resolve(element.duration);
+ element.remove();
+ });
+
+ element.addEventListener("error", () => {
+ reject(new Error("Could not load media"));
+ element.remove();
+ });
+
+ element.src = URL.createObjectURL(file);
+ element.load();
+ });
+};
+
export const useMediaStore = create<MediaStore>((set, get) => ({
mediaItems: [],
@@ -28,8 +138,33 @@ export const useMediaStore = create<MediaStore>((set, get) => ({
},
removeMediaItem: (id) => {
+ const state = get();
+ const item = state.mediaItems.find((item) => item.id === id);
+
+ // Cleanup object URLs to prevent memory leaks
+ if (item) {
+ URL.revokeObjectURL(item.url);
+ if (item.thumbnailUrl) {
+ URL.revokeObjectURL(item.thumbnailUrl);
+ }
+ }
+
set((state) => ({
mediaItems: state.mediaItems.filter((item) => item.id !== id),
}));
},
+
+ clearAllMedia: () => {
+ const state = get();
+
+ // Cleanup all object URLs
+ state.mediaItems.forEach((item) => {
+ URL.revokeObjectURL(item.url);
+ if (item.thumbnailUrl) {
+ URL.revokeObjectURL(item.thumbnailUrl);
+ }
+ });
+
+ set({ mediaItems: [] });
+ },
}));