From 8500dd770bab08b07b01e4c402c25920c8bd8345 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Wed, 21 Jan 2026 12:52:20 +0100 Subject: [PATCH] uhh --- .cursor/rules/codebase-index.mdc | 370 +++++++++++------- apps/web/src/app/api/sounds/search/route.ts | 16 +- apps/web/src/app/base-page.tsx | 2 +- apps/web/src/app/blog/[slug]/page.tsx | 2 +- apps/web/src/app/blog/page.tsx | 2 +- apps/web/src/app/projects/page.tsx | 21 +- apps/web/src/app/roadmap/page.tsx | 2 +- apps/web/src/app/rss.xml/route.ts | 7 +- apps/web/src/app/sitemap.ts | 2 +- .../components/editor/assets-panel/tabbar.tsx | 2 +- .../editor/assets-panel/views/captions.tsx | 4 +- .../editor/assets-panel/views/media.tsx | 2 +- .../editor/assets-panel/views/settings.tsx | 4 +- .../editor/assets-panel/views/sounds.tsx | 11 +- .../editor/assets-panel/views/stickers.tsx | 2 +- .../src/components/editor/export-button.tsx | 4 +- .../src/components/editor/panel-base-view.tsx | 2 +- .../editor/properties-panel/property-item.tsx | 2 +- .../properties-panel/text-properties.tsx | 17 +- .../web/src/components/editor/scenes-view.tsx | 16 +- .../editor/timeline/timeline-element.tsx | 2 +- .../editor/timeline/timeline-toolbar.tsx | 4 +- .../editor/timeline/timeline-track.tsx | 2 +- apps/web/src/components/footer.tsx | 2 +- apps/web/src/components/header.tsx | 2 +- apps/web/src/components/language-select.tsx | 2 +- apps/web/src/components/theme-toggle.tsx | 2 +- apps/web/src/components/ui/accordion.tsx | 2 +- apps/web/src/components/ui/alert-dialog.tsx | 2 +- apps/web/src/components/ui/alert.tsx | 2 +- apps/web/src/components/ui/avatar.tsx | 2 +- apps/web/src/components/ui/badge.tsx | 4 +- apps/web/src/components/ui/breadcrumb.tsx | 2 +- apps/web/src/components/ui/button.tsx | 4 +- apps/web/src/components/ui/calendar.tsx | 2 +- apps/web/src/components/ui/card.tsx | 2 +- apps/web/src/components/ui/carousel.tsx | 2 +- apps/web/src/components/ui/chart.tsx | 42 +- apps/web/src/components/ui/checkbox.tsx | 2 +- apps/web/src/components/ui/color-picker.tsx | 7 +- apps/web/src/components/ui/command.tsx | 2 +- apps/web/src/components/ui/context-menu.tsx | 2 +- apps/web/src/components/ui/dialog.tsx | 2 +- apps/web/src/components/ui/draggable-item.tsx | 2 +- apps/web/src/components/ui/drawer.tsx | 2 +- apps/web/src/components/ui/dropdown-menu.tsx | 2 +- .../src/components/ui/editable-timecode.tsx | 4 +- apps/web/src/components/ui/form.tsx | 2 +- apps/web/src/components/ui/hover-card.tsx | 2 +- apps/web/src/components/ui/input-otp.tsx | 2 +- apps/web/src/components/ui/input.tsx | 2 +- apps/web/src/components/ui/label.tsx | 4 +- apps/web/src/components/ui/menubar.tsx | 2 +- .../web/src/components/ui/navigation-menu.tsx | 2 +- apps/web/src/components/ui/pagination.tsx | 2 +- apps/web/src/components/ui/phone-input.tsx | 2 +- apps/web/src/components/ui/popover.tsx | 2 +- apps/web/src/components/ui/progress.tsx | 2 +- apps/web/src/components/ui/prose.tsx | 2 +- apps/web/src/components/ui/radio-group.tsx | 2 +- .../components/ui/react-markdown-wrapper.tsx | 2 +- apps/web/src/components/ui/resizable.tsx | 2 +- apps/web/src/components/ui/scroll-area.tsx | 2 +- apps/web/src/components/ui/select.tsx | 6 +- apps/web/src/components/ui/separator.tsx | 2 +- apps/web/src/components/ui/sheet.tsx | 4 +- apps/web/src/components/ui/skeleton.tsx | 2 +- apps/web/src/components/ui/slider.tsx | 2 +- apps/web/src/components/ui/split-button.tsx | 2 +- apps/web/src/components/ui/switch.tsx | 2 +- apps/web/src/components/ui/table.tsx | 2 +- apps/web/src/components/ui/tabs.tsx | 2 +- apps/web/src/components/ui/textarea.tsx | 2 +- apps/web/src/components/ui/toast.tsx | 4 +- apps/web/src/components/ui/toggle-group.tsx | 6 +- apps/web/src/components/ui/toggle.tsx | 4 +- apps/web/src/components/ui/tooltip.tsx | 6 +- apps/web/src/core/managers/media-manager.ts | 2 +- apps/web/src/core/managers/project-manager.ts | 4 +- .../web/src/core/managers/renderer-manager.ts | 4 +- apps/web/src/core/managers/scenes-manager.ts | 2 +- .../element/use-element-interaction.ts | 4 +- .../timeline/element/use-element-resize.ts | 2 +- .../hooks/timeline/use-timeline-drag-drop.ts | 2 +- .../timeline/use-timeline-interactions.ts | 2 +- .../hooks/timeline/use-timeline-playhead.ts | 2 +- .../src/hooks/use-keyboard-shortcuts-help.ts | 2 +- .../src/lib/{blog-query.ts => blog/query.ts} | 0 .../src/lib/commands/media/add-media-asset.ts | 2 +- .../src/lib/commands/scene/create-scene.ts | 2 +- .../src/lib/commands/scene/delete-scene.ts | 2 +- .../src/lib/commands/scene/remove-bookmark.ts | 2 +- .../src/lib/commands/scene/rename-scene.ts | 2 +- .../src/lib/commands/scene/toggle-bookmark.ts | 2 +- .../lib/commands/timeline/clipboard/paste.ts | 2 +- .../timeline/element/duplicate-elements.ts | 2 +- .../timeline/element/insert-element.ts | 2 +- .../timeline/element/split-elements.ts | 2 +- .../lib/commands/timeline/track/add-track.ts | 2 +- apps/web/src/lib/editor-utils.ts | 11 - apps/web/src/lib/export.ts | 15 - apps/web/src/lib/keyboard-utils.ts | 9 - .../lib/{audio-utils.ts => media/audio.ts} | 2 +- apps/web/src/lib/{ => media}/media-utils.ts | 0 apps/web/src/lib/media/mediabunny.ts | 2 +- apps/web/src/lib/media/processing.ts | 2 +- .../web/src/lib/{scene-utils.ts => scenes.ts} | 2 +- apps/web/src/lib/{time-utils.ts => time.ts} | 0 apps/web/src/lib/timeline/bookmarks.ts | 2 +- apps/web/src/lib/timeline/track-utils.ts | 2 +- .../caption.ts} | 0 apps/web/src/lib/utils.ts | 86 ---- .../services/storage/migrations/v0-to-v1.ts | 2 +- apps/web/src/stores/keybindings-store.ts | 3 +- apps/web/src/utils/browser.ts | 17 + apps/web/src/utils/date.ts | 7 + apps/web/src/utils/export.ts | 22 ++ apps/web/src/utils/geometry.ts | 13 + apps/web/src/utils/id.ts | 28 ++ apps/web/src/utils/math.ts | 11 + apps/web/src/utils/platform.ts | 11 + apps/web/src/utils/string-utils.ts | 7 + apps/web/src/utils/ui.ts | 6 + 123 files changed, 544 insertions(+), 441 deletions(-) rename apps/web/src/lib/{blog-query.ts => blog/query.ts} (100%) delete mode 100644 apps/web/src/lib/editor-utils.ts delete mode 100644 apps/web/src/lib/export.ts delete mode 100644 apps/web/src/lib/keyboard-utils.ts rename apps/web/src/lib/{audio-utils.ts => media/audio.ts} (99%) rename apps/web/src/lib/{ => media}/media-utils.ts (100%) rename apps/web/src/lib/{scene-utils.ts => scenes.ts} (97%) rename apps/web/src/lib/{time-utils.ts => time.ts} (100%) rename apps/web/src/lib/{caption-utils.ts => transcription/caption.ts} (100%) delete mode 100644 apps/web/src/lib/utils.ts create mode 100644 apps/web/src/utils/browser.ts create mode 100644 apps/web/src/utils/date.ts create mode 100644 apps/web/src/utils/export.ts create mode 100644 apps/web/src/utils/geometry.ts create mode 100644 apps/web/src/utils/id.ts create mode 100644 apps/web/src/utils/math.ts create mode 100644 apps/web/src/utils/platform.ts create mode 100644 apps/web/src/utils/string-utils.ts create mode 100644 apps/web/src/utils/ui.ts diff --git a/.cursor/rules/codebase-index.mdc b/.cursor/rules/codebase-index.mdc index 77a5fb9b..a058c5ef 100644 --- a/.cursor/rules/codebase-index.mdc +++ b/.cursor/rules/codebase-index.mdc @@ -319,106 +319,26 @@ use-element-selection.ts ## apps/web/src/lib -audio-utils.ts - export type CollectedAudioElement = Omit< - AudioElement, - "type" | "mediaId" | "volume" | "id" | "name" | "sourceType" | "sourceU... - export function createAudioContext(): AudioContext - export interface DecodedAudio { - samples: Float32Array - sampleRate: number - } - export function decodeAudioToFloat32({ - audioBlob, +browser-utils.ts + export function isTypableDOMElement({ + element, }: { - audioBlob: Blob; - }): Promise - export function collectAudioElements({ - tracks, - mediaAssets, - audioContext, - }: { - tracks: TimelineTrack[]; - mediaAssets: MediaAsset[]; - audioContext: AudioContext; - }): Promise - export function collectAudioMixSources({ - tracks, - mediaAssets, - }: { - tracks: TimelineTrack[]; - mediaAssets: MediaAsset[]; - }): Promise - export function createTimelineAudioBuffer({ - tracks, - mediaAssets, - duration, - sampleRate = 44100, - }: { - tracks: TimelineTrack[]; - mediaAssets: MediaAsset[]; - duration: number; - sampleRate?: number; - }): Promise - -blog-query.ts - export function getPosts() - export function getTags() - export function getSinglePost({ slug }: { slug: string }) - export function getCategories() - export function getAuthors() - export function processHtmlContent({ html }: { html: string }): Promise - -caption-utils.ts - export function buildCaptionChunks({ - segments, - wordsPerChunk = DEFAULT_WORDS_PER_CAPTION, - minDuration = MIN_CAPTION_DURATION_SECONDS, - }: { - segments: TranscriptionSegment[]; - wordsPerChunk?: number; - minDuration?: number; - }): CaptionChunk[] - -drag-data.ts - export function setDragData({ - dataTransfer, - dragData, - }: { - dataTransfer: DataTransfer; - dragData: TimelineDragData; - }): void - export function getDragData({ - dataTransfer, - }: { - dataTransfer: DataTransfer; - }): TimelineDragData | null - export function hasDragData({ - dataTransfer, - }: { - dataTransfer: DataTransfer; + element: HTMLElement; }): boolean - export function clearDragData(): void -editor-utils.ts - export function dimensionToAspectRatio({ - width, - height, - }: { - width: number; - height: number; - }): string +date-utils.ts + export function formatDate({ date }: { date: Date }): string export.ts export function getExportMimeType({ format, }: { - format: "mp4" | "webm"; + format: ExportFormat; }): string export function getExportFileExtension({ format, }: { - format: "mp4" | "webm"; + format: ExportFormat; }): string iconify-api.ts @@ -493,22 +413,21 @@ iconify-api.ts collections: Record ): string[] -keyboard-utils.ts +math-utils.ts + export function clamp({ + value, + min, + max, + }: { + value: number; + min: number; + max: number; + }): number + +platform-utils.ts export function getPlatformSpecialKey(): string export function getPlatformAlternateKey(): string - -media-utils.ts - export const SUPPORTS_AUDIO: readonly MediaType[] - export function mediaSupportsAudio({ - media, - }: { - media: MediaAsset | null | undefined; - }): boolean - export const getMediaTypeFromFile = ({ file }: { file: File }): MediaType | null => ... - -rate-limit.ts - export const baseRateLimit - export function checkRateLimit({ request }: { request: Request }) + export function isAppleDevice(): boolean scene-utils.ts export function getMainScene({ scenes }: { scenes: TScene[] }): TScene | null @@ -550,6 +469,10 @@ scene-utils.ts updates: Partial; }): TScene[] +string-utils.ts + export function capitalizeFirstLetter({ string }: { string: string }) + export function uppercase({ string }: { string: string }) + time-utils.ts export function roundToFrame({ time, @@ -612,28 +535,6 @@ time-utils.ts fps: number; }): number -utils.ts - export function cn(...inputs: ClassValue[]): string - export function formatDate({ date }: { date: Date }): string - export function capitalizeFirstLetter({ string }: { string: string }) - export function uppercase({ string }: { string: string }) - export function generateUUID(): string - export function isTypableDOMElement({ - element, - }: { - element: HTMLElement; - }): boolean - export function isAppleDevice(): boolean - export function clamp({ - value, - min, - max, - }: { - value: number; - min: number; - max: number; - }): number - ## apps/web/src/lib/actions definitions.ts @@ -698,6 +599,16 @@ server.ts export const auth export type Auth = typeof auth +## apps/web/src/lib/blog + +query.ts + export function getPosts() + export function getTags() + export function getSinglePost({ slug }: { slug: string }) + export function getCategories() + export function getAuthors() + export function processHtmlContent({ html }: { html: string }): Promise + ## apps/web/src/lib/db index.ts @@ -739,6 +650,15 @@ parser.ts ## apps/web/src/lib/media +media-utils.ts + export const SUPPORTS_AUDIO: readonly MediaType[] + export function mediaSupportsAudio({ + media, + }: { + media: MediaAsset | null | undefined; + }): boolean + export const getMediaTypeFromFile = ({ file }: { file: File }): MediaType | null => ... + mediabunny.ts export const initFFmpeg = (): Promise => ... export function getVideoInfo({ @@ -1006,6 +926,161 @@ zoom-utils.ts containerWidth: number | null | undefined; }): number +## apps/web/src/lib/utils + +audio.ts + export type CollectedAudioElement = Omit< + AudioElement, + "type" | "mediaId" | "volume" | "id" | "name" | "sourceType" | "sourceU... + export function createAudioContext(): AudioContext + export interface DecodedAudio { + samples: Float32Array + sampleRate: number + } + export function decodeAudioToFloat32({ + audioBlob, + }: { + audioBlob: Blob; + }): Promise + export function collectAudioElements({ + tracks, + mediaAssets, + audioContext, + }: { + tracks: TimelineTrack[]; + mediaAssets: MediaAsset[]; + audioContext: AudioContext; + }): Promise + export function collectAudioMixSources({ + tracks, + mediaAssets, + }: { + tracks: TimelineTrack[]; + mediaAssets: MediaAsset[]; + }): Promise + export function createTimelineAudioBuffer({ + tracks, + mediaAssets, + duration, + sampleRate = 44100, + }: { + tracks: TimelineTrack[]; + mediaAssets: MediaAsset[]; + duration: number; + sampleRate?: number; + }): Promise + +blog-query.ts + export function getPosts() + export function getTags() + export function getSinglePost({ slug }: { slug: string }) + export function getCategories() + export function getAuthors() + export function processHtmlContent({ html }: { html: string }): Promise + +browser.ts + export function isTypableDOMElement({ + element, + }: { + element: HTMLElement; + }): boolean + +date.ts + export function formatDate({ date }: { date: Date }): string + +editor.ts + export function dimensionToAspectRatio({ + width, + height, + }: { + width: number; + height: number; + }): string + +id.ts + export function generateUUID(): string + +math.ts + export function clamp({ + value, + min, + max, + }: { + value: number; + min: number; + max: number; + }): number + +platform.ts + export function getPlatformSpecialKey(): string + export function getPlatformAlternateKey(): string + export function isAppleDevice(): boolean + +time.ts + export function roundToFrame({ + time, + fps, + }: { + time: number; + fps: number; + }): number + export function formatTimeCode({ + timeInSeconds, + format = "HH:MM:SS:CS", + fps, + }: { + timeInSeconds: number; + format?: TTimeCode; + fps?: number; + }): string + export function parseTimeCode({ + timeCode, + format = "HH:MM:SS:CS", + fps, + }: { + timeCode: string; + format?: TTimeCode; + fps: number; + }): number | null + export function guessTimeCodeFormat({ + timeCode, + }: { + timeCode: string; + }): TTimeCode | null + export function timeToFrame({ + time, + fps, + }: { + time: number; + fps: number; + }): number + export function frameToTime({ + frame, + fps, + }: { + frame: number; + fps: number; + }): number + export function snapTimeToFrame({ + time, + fps, + }: { + time: number; + fps: number; + }): number + export function getSnappedSeekTime({ + rawTime, + duration, + fps, + }: { + rawTime: number; + duration: number; + fps: number; + }): number + +ui.ts + export function cn(...inputs: ClassValue[]): string + ## apps/web/src/services/media video-cache.ts @@ -1584,20 +1659,19 @@ transcription.ts segments: TranscriptionSegment[] language: string } - export type TranscriptionStatus = | "idle" - | "loading-model" - | "ready" - | "transcribing" - | "complete" - | "er... + export type TranscriptionStatus = | "idle" + | "loading-model" + | "transcribing" + | "complete" + | "error" export interface TranscriptionProgress { status: TranscriptionStatus progress: number message?: string } - export type TranscriptionModelId = | "whisper-tiny" - | "whisper-small" - | "whisper-medium" + export type TranscriptionModelId = | "whisper-tiny" + | "whisper-small" + | "whisper-medium" | "whisper-large-v3-turbo" export interface TranscriptionModel { id: TranscriptionModelId @@ -1611,6 +1685,36 @@ transcription.ts duration: number } +## apps/web/src/utils + +geometry.ts + export function dimensionToAspectRatio({ + width, + height, + }: { + width: number; + height: number; + }): string + +math.ts + export function clamp({ + value, + min, + max, + }: { + value: number; + min: number; + max: number; + }): number + +platform.ts + export function getPlatformSpecialKey(): string + export function getPlatformAlternateKey(): string + export function isAppleDevice(): boolean + +ui.ts + export function cn(...inputs: ClassValue[]): string + ## packages/ui/src/icons index.tsx diff --git a/apps/web/src/app/api/sounds/search/route.ts b/apps/web/src/app/api/sounds/search/route.ts index 16eeb75f..a477c9db 100644 --- a/apps/web/src/app/api/sounds/search/route.ts +++ b/apps/web/src/app/api/sounds/search/route.ts @@ -93,14 +93,14 @@ function buildSortParameter({ query, sort }: { query?: string; sort: string }) { return sort === "score" ? "score" : `${sort}_desc`; } -function applyEffectsFilters({ - params, - min_rating, - commercial_only -}: { - params: URLSearchParams; - min_rating: number; - commercial_only: boolean; +function applyEffectsFilters({ + params, + min_rating, + commercial_only +}: { + params: URLSearchParams; + min_rating: number; + commercial_only: boolean; }) { params.append("filter", "duration:[* TO 30.0]"); params.append("filter", `avg_rating:[${min_rating} TO *]`); diff --git a/apps/web/src/app/base-page.tsx b/apps/web/src/app/base-page.tsx index 61c827c7..48303e8e 100644 --- a/apps/web/src/app/base-page.tsx +++ b/apps/web/src/app/base-page.tsx @@ -1,6 +1,6 @@ import { Header } from "@/components/header"; import { Footer } from "@/components/footer"; -import { cn } from "@/lib/utils"; +import { cn } from "@/utils/ui"; interface BasePageProps { children: React.ReactNode; diff --git a/apps/web/src/app/blog/[slug]/page.tsx b/apps/web/src/app/blog/[slug]/page.tsx index 7814edff..7325d859 100644 --- a/apps/web/src/app/blog/[slug]/page.tsx +++ b/apps/web/src/app/blog/[slug]/page.tsx @@ -1,7 +1,7 @@ import { BasePage } from "@/app/base-page"; import Prose from "@/components/ui/prose"; import { Separator } from "@/components/ui/separator"; -import { getPosts, getSinglePost, processHtmlContent } from "@/lib/blog-query"; +import { getPosts, getSinglePost, processHtmlContent } from "@/lib/blog/query"; import { Post, Author } from "@/types/blog"; import { Metadata } from "next"; import Image from "next/image"; diff --git a/apps/web/src/app/blog/page.tsx b/apps/web/src/app/blog/page.tsx index ed4f0164..6abfc46e 100644 --- a/apps/web/src/app/blog/page.tsx +++ b/apps/web/src/app/blog/page.tsx @@ -2,7 +2,7 @@ import { Metadata } from "next"; import { BasePage } from "@/app/base-page"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import Link from "next/link"; -import { getPosts } from "@/lib/blog-query"; +import { getPosts } from "@/lib/blog/query"; import { Post, Author } from "@/types/blog"; import { Separator } from "@/components/ui/separator"; diff --git a/apps/web/src/app/projects/page.tsx b/apps/web/src/app/projects/page.tsx index 929f076f..7bba1023 100644 --- a/apps/web/src/app/projects/page.tsx +++ b/apps/web/src/app/projects/page.tsx @@ -40,7 +40,7 @@ import { MigrationDialog } from "@/components/editor/migration-dialog"; import type { TProjectMetadata } from "@/types/project"; import { toast } from "sonner"; import { useEditor } from "@/hooks/use-editor"; -import { formatDate } from "@/lib/utils"; +import { formatDate } from "@/utils/date"; export default function ProjectsPage() { const [isSelectionMode, setIsSelectionMode] = useState(false); @@ -419,14 +419,12 @@ function ProjectCard({ const cardContent = (
{isSelectionMode && (
@@ -477,11 +475,10 @@ function ProjectCard({ aria-label="project options" variant="text" size="sm" - className={`ml-2 size-6 shrink-0 p-0 transition-all ${ - isDropdownOpen - ? "opacity-100" - : "opacity-0 group-hover:opacity-100" - }`} + className={`ml-2 size-6 shrink-0 p-0 transition-all ${isDropdownOpen + ? "opacity-100" + : "opacity-0 group-hover:opacity-100" + }`} onClick={(event) => event.preventDefault()} >