refactor: move to a typed-tracks system and add support for text

This commit is contained in:
Maze Winther
2025-07-06 20:45:29 +02:00
parent 0e32c732dd
commit 40c7fbb4f8
20 changed files with 1799 additions and 1469 deletions

View File

@ -1,16 +1,25 @@
import { create } from "zustand";
import { storageService } from "@/lib/storage/storage-service";
export type MediaType = "image" | "video" | "audio";
export interface MediaItem {
id: string;
name: string;
type: "image" | "video" | "audio";
type: MediaType;
file: File;
url: string; // Object URL for preview
url?: string; // Object URL for preview
thumbnailUrl?: string; // For video thumbnails
duration?: number; // For video/audio duration
width?: number; // For video/image width
height?: number; // For video/image height
// Text-specific properties
content?: string; // Text content
fontSize?: number; // Font size
fontFamily?: string; // Font family
color?: string; // Text color
backgroundColor?: string; // Background color
textAlign?: "left" | "center" | "right"; // Text alignment
}
interface MediaStore {
@ -25,7 +34,7 @@ interface MediaStore {
}
// Helper function to determine file type
export const getFileType = (file: File): "image" | "video" | "audio" | null => {
export const getFileType = (file: File): MediaType | null => {
const { type } = file;
if (type.startsWith("image/")) {
@ -46,7 +55,7 @@ export const getImageDimensions = (
file: File
): Promise<{ width: number; height: number }> => {
return new Promise((resolve, reject) => {
const img = new Image();
const img = new window.Image();
img.addEventListener("load", () => {
const width = img.naturalWidth;
@ -69,8 +78,8 @@ export const generateVideoThumbnail = (
file: File
): Promise<{ thumbnailUrl: string; width: number; height: number }> => {
return new Promise((resolve, reject) => {
const video = document.createElement("video");
const canvas = document.createElement("canvas");
const video = document.createElement("video") as HTMLVideoElement;
const canvas = document.createElement("canvas") as HTMLCanvasElement;
const ctx = canvas.getContext("2d");
if (!ctx) {
@ -115,7 +124,7 @@ 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;
) as HTMLVideoElement;
element.addEventListener("loadedmetadata", () => {
resolve(element.duration);
@ -162,17 +171,17 @@ export const useMediaStore = create<MediaStore>((set, get) => ({
console.error("Failed to save media item:", error);
// Remove from local state if save failed
set((state) => ({
mediaItems: state.mediaItems.filter((item) => item.id !== newItem.id),
mediaItems: state.mediaItems.filter((media) => media.id !== newItem.id),
}));
}
},
removeMediaItem: async (id) => {
removeMediaItem: async (id: string) => {
const state = get();
const item = state.mediaItems.find((item) => item.id === id);
const item = state.mediaItems.find((media) => media.id === id);
// Cleanup object URLs to prevent memory leaks
if (item) {
if (item && item.url) {
URL.revokeObjectURL(item.url);
if (item.thumbnailUrl) {
URL.revokeObjectURL(item.thumbnailUrl);
@ -181,7 +190,7 @@ export const useMediaStore = create<MediaStore>((set, get) => ({
// Remove from local state immediately
set((state) => ({
mediaItems: state.mediaItems.filter((item) => item.id !== id),
mediaItems: state.mediaItems.filter((media) => media.id !== id),
}));
// Remove from persistent storage
@ -189,7 +198,6 @@ export const useMediaStore = create<MediaStore>((set, get) => ({
await storageService.deleteMediaItem(id);
} catch (error) {
console.error("Failed to delete media item:", error);
// Could re-add to local state here if needed
}
},
@ -211,7 +219,9 @@ export const useMediaStore = create<MediaStore>((set, get) => ({
// Cleanup all object URLs
state.mediaItems.forEach((item) => {
URL.revokeObjectURL(item.url);
if (item.url) {
URL.revokeObjectURL(item.url);
}
if (item.thumbnailUrl) {
URL.revokeObjectURL(item.thumbnailUrl);
}