From af072afa65ee05ef1ef2a10a999fb58b9c7f3bdb Mon Sep 17 00:00:00 2001 From: anishghanwat Date: Tue, 15 Jul 2025 02:26:51 +0530 Subject: [PATCH 01/39] Improve login error feedback for better user experience --- apps/web/src/hooks/auth/useLogin.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/web/src/hooks/auth/useLogin.ts b/apps/web/src/hooks/auth/useLogin.ts index 12eddcbf6..995326443 100644 --- a/apps/web/src/hooks/auth/useLogin.ts +++ b/apps/web/src/hooks/auth/useLogin.ts @@ -20,7 +20,7 @@ export function useLogin() { }); if (error) { - setError(error.message || "An unexpected error occurred."); + setError(getFriendlyError(error)); setIsEmailLoading(false); return; } @@ -58,3 +58,16 @@ export function useLogin() { handleGoogleLogin, }; } + +function getFriendlyError(error: any) { + if (!error) return "An unexpected error occurred. Please try again."; + const msg = error.message?.toLowerCase() || ""; + if (msg.includes("invalid") || msg.includes("credentials")) { + return "Invalid email or password. Please try again."; + } + if (msg.includes("not found")) { + return "No account found with this email."; + } + // Add more mappings as needed + return "An unexpected error occurred. Please try again."; +} From ab00b1900d30ee8882018ed84606c03d7639991c Mon Sep 17 00:00:00 2001 From: Nizav Date: Mon, 14 Jul 2025 22:46:28 +0700 Subject: [PATCH 02/39] refactor: move the add button below the placeholder text --- .../editor/media-panel/views/media.tsx | 49 +++++++++---------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/apps/web/src/components/editor/media-panel/views/media.tsx b/apps/web/src/components/editor/media-panel/views/media.tsx index 48e549617..8c5718b1c 100644 --- a/apps/web/src/components/editor/media-panel/views/media.tsx +++ b/apps/web/src/components/editor/media-panel/views/media.tsx @@ -207,9 +207,8 @@ export function MediaView() {
- {/* Button to add/upload media */} + {/* Search and filter controls (removed old Add button) */}
- {/* Search and filter controls */} @@ -258,7 +258,10 @@ export function MediaView() { ) : ( <> - + Add From 5e51809a8c29154a8b87af173ed83ed4a0fb170b Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Mon, 14 Jul 2025 20:34:16 +0200 Subject: [PATCH 04/39] feat: roadmap page and move all open-graph images to /open-graph folder --- apps/web/package.json | 1 + .../default.jpg} | Bin apps/web/public/open-graph/roadmap.jpg | Bin 0 -> 57900 bytes apps/web/src/app/metadata.ts | 4 +- apps/web/src/app/roadmap/page.tsx | 247 ++++++++++++++++++ apps/web/src/components/footer.tsx | 8 + bun.lock | 161 ++++++++++++ 7 files changed, 419 insertions(+), 2 deletions(-) rename apps/web/public/{opengraph-image.jpg => open-graph/default.jpg} (100%) create mode 100644 apps/web/public/open-graph/roadmap.jpg create mode 100644 apps/web/src/app/roadmap/page.tsx diff --git a/apps/web/package.json b/apps/web/package.json index 4e5018202..9e1393b74 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -45,6 +45,7 @@ "react-dom": "^18.2.0", "react-hook-form": "^7.54.0", "react-icons": "^5.4.0", + "react-markdown": "^10.1.0", "react-phone-number-input": "^3.4.11", "react-resizable-panels": "^2.1.7", "recharts": "^2.14.1", diff --git a/apps/web/public/opengraph-image.jpg b/apps/web/public/open-graph/default.jpg similarity index 100% rename from apps/web/public/opengraph-image.jpg rename to apps/web/public/open-graph/default.jpg diff --git a/apps/web/public/open-graph/roadmap.jpg b/apps/web/public/open-graph/roadmap.jpg new file mode 100644 index 0000000000000000000000000000000000000000..63374c4f615aec53e8151ce4cb2649e41f6c1060 GIT binary patch literal 57900 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 literal 0 HcmV?d00001 diff --git a/apps/web/src/app/metadata.ts b/apps/web/src/app/metadata.ts index 6db268113..47f0020b4 100644 --- a/apps/web/src/app/metadata.ts +++ b/apps/web/src/app/metadata.ts @@ -3,8 +3,8 @@ import { Metadata } from "next"; const title = "OpenCut"; const description = "A simple but powerful video editor that gets the job done. In your browser."; -const openGraphImageUrl = "https://opencut.app/opengraph-image.jpg"; -const twitterImageUrl = "/opengraph-image.jpg"; +const openGraphImageUrl = "https://opencut.app/open-graph/default.jpg"; +const twitterImageUrl = "/open-graph/default.jpg"; export const baseMetaData: Metadata = { metadataBase: new URL("https://opencut.app"), diff --git a/apps/web/src/app/roadmap/page.tsx b/apps/web/src/app/roadmap/page.tsx new file mode 100644 index 000000000..6b10db471 --- /dev/null +++ b/apps/web/src/app/roadmap/page.tsx @@ -0,0 +1,247 @@ +import { Metadata } from "next"; +import { Badge } from "@/components/ui/badge"; +import { GithubIcon } from "@/components/icons"; +import Link from "next/link"; +import { Footer } from "@/components/footer"; +import { Header } from "@/components/header"; +import ReactMarkdown from "react-markdown"; +import { cn } from "@/lib/utils"; + +const roadmapItems: { + title: string; + description: string; + status: { + text: string; + type: "complete" | "pending" | "default" | "info"; + }; +}[] = [ + { + title: "Start", + description: + "This is where it all started. Repository created, initial project structure, and the vision for a free, open-source video editor. [Check out the first tweet](https://x.com/mazeincoding/status/1936706642512388188) to see where it started.", + status: { + text: "Completed", + type: "complete", + }, + }, + { + title: "Core UI", + description: + "Built the foundation - main layout, header, sidebar, timeline container, and basic component structure. Not all functionality yet, but the UI framework that everything else builds on.", + status: { + text: "Completed", + type: "complete", + }, + }, + { + title: "Basic Functionality", + description: + "The heart of any video editor. Timeline zoom in/out, making clips longer/shorter, dragging elements around, selection, playhead scrubbing. **This part has to be fucking perfect** because it's what users interact with 99% of the time.", + status: { + text: "In Progress", + type: "pending", + }, + }, + { + title: "Export/Preview Logic", + description: + "The foundation that enables everything else. Real-time preview, video rendering, export functionality. Once this works, we can add effects, filters, transitions - basically everything that makes a video editor powerful.", + status: { + text: "In Progress", + type: "pending", + }, + }, + { + title: "Text", + description: + "After media, text is the next most important thing. Font selection with custom font imports, text stroke, colors. All the text essential text properties.", + status: { + text: "Not Started", + type: "default", + }, + }, + { + title: "Effects", + description: + "Adding visual effects to both text and media. Blur, brightness, contrast, saturation, filters, and all the creative tools that make videos pop. This is where the magic happens.", + status: { + text: "Not Started", + type: "default", + }, + }, + { + title: "Transitions", + description: + "Smooth transitions between clips. Fade in/out, slide, zoom, dissolve, and custom transition effects.", + status: { + text: "Not Started", + type: "default", + }, + }, + { + title: "Refine from Here", + description: + "Once we nail the above, we have a **solid foundation** to build anything. Advanced features, performance optimizations, mobile support, desktop app.", + status: { + text: "Future", + type: "info", + }, + }, +]; + +export const metadata: Metadata = { + title: "Roadmap - OpenCut", + description: + "See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.", + openGraph: { + title: "OpenCut Roadmap - What's Coming Next", + description: + "See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.", + type: "website", + images: [ + { + url: "/open-graph/roadmap.jpg", + width: 1200, + height: 630, + alt: "OpenCut Roadmap", + }, + ], + }, + twitter: { + card: "summary_large_image", + title: "OpenCut Roadmap - What's Coming Next", + description: + "See what's coming next for OpenCut - the free, open-source video editor that respects your privacy.", + images: ["/open-graph/roadmap.jpg"], + }, +}; + +export default function RoadmapPage() { + return ( +
+
+
+
+
+
+
+
+
+
+ + + + Open Source + + +

+ Roadmap +

+

+ What's coming next for OpenCut (last updated: January 15, 2025) +

+
+
+ {roadmapItems.map((item, index) => ( +
+
+ + {index + 1}. + +
+
+

{item.title}

+ + {item.status.text} + +
+
+ ( + + {children} + + ), + strong: ({ children }) => ( + + {children} + + ), + }} + > + {item.description} + +
+
+
+
+ ))} +
+ +
+
+

Want to Help?

+

+ OpenCut is open source and built by the community. Every + contribution, no matter how small, helps us build the best + free video editor possible. +

+
+ + + + Start Contributing + + + + + Report Issues + + +
+
+
+
+
+
+
+
+ ); +} diff --git a/apps/web/src/components/footer.tsx b/apps/web/src/components/footer.tsx index 10f0e1190..43a9db0e1 100644 --- a/apps/web/src/components/footer.tsx +++ b/apps/web/src/components/footer.tsx @@ -66,6 +66,14 @@ export function Footer() {

Resources

    +
  • + + Roadmap + +
  • =0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], + "estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="], + "eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + "fast-equals": ["fast-equals@5.2.2", "", {}, "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw=="], "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], @@ -614,26 +655,44 @@ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + "hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="], + + "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], + + "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="], + + "inline-style-parser": ["inline-style-parser@0.2.4", "", {}, "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q=="], + "input-format": ["input-format@0.3.14", "", { "dependencies": { "prop-types": "^15.8.1" }, "peerDependencies": { "react": ">=18.1.0", "react-dom": ">=18.1.0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-gHMrgrbCgmT4uK5Um5eVDUohuV9lcs95ZUUN9Px2Y0VIfjTzT2wF8Q3Z4fwLFm7c5Z2OXCm53FHoovj6SlOKdg=="], "input-otp": ["input-otp@1.4.2", "", { "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA=="], "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], + "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], + + "is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="], + "is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="], "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], + "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], @@ -654,14 +713,74 @@ "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], + "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], + "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "lucide-react": ["lucide-react@0.468.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" } }, "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA=="], + "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + + "mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="], + + "mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.2.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q=="], + + "mdast-util-mdxjs-esm": ["mdast-util-mdxjs-esm@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg=="], + + "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="], + + "mdast-util-to-hast": ["mdast-util-to-hast@13.2.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA=="], + + "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="], + + "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="], + + "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="], + + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], + + "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="], + + "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="], + + "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="], + + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], + + "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], @@ -696,6 +815,8 @@ "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], @@ -754,6 +875,8 @@ "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], + "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], + "pvtsutils": ["pvtsutils@1.3.6", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg=="], "pvutils": ["pvutils@1.1.3", "", {}, "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ=="], @@ -776,6 +899,8 @@ "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], + "react-markdown": ["react-markdown@10.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ=="], + "react-phone-number-input": ["react-phone-number-input@3.4.12", "", { "dependencies": { "classnames": "^2.5.1", "country-flag-icons": "^1.5.17", "input-format": "^0.3.10", "libphonenumber-js": "^1.11.20", "prop-types": "^15.8.1" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-Raob77KdtLGm49iC6nuOX9qy6Mg16idkgC7Y1mHmvG2WBYoauHpzxYNlfmFskQKeiztrJIwPhPzBhjFwjenNCA=="], "react-redux": ["react-redux@9.2.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="], @@ -802,6 +927,10 @@ "redux": ["redux@5.0.1", "", {}, "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="], + "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], + + "remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="], + "resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="], "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], @@ -836,6 +965,8 @@ "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], "streamsearch": ["streamsearch@1.1.0", "", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="], @@ -844,10 +975,16 @@ "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], + "strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "style-to-js": ["style-to-js@1.1.17", "", { "dependencies": { "style-to-object": "1.0.9" } }, "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA=="], + + "style-to-object": ["style-to-object@1.0.9", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw=="], + "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], "sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="], @@ -868,6 +1005,10 @@ "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], + + "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], + "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], @@ -894,6 +1035,18 @@ "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], + + "unist-util-is": ["unist-util-is@6.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw=="], + + "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], + + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], + + "unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], + + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw=="], + "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], @@ -904,6 +1057,10 @@ "vaul": ["vaul@1.1.2", "", { "dependencies": { "@radix-ui/react-dialog": "^1.1.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA=="], + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], + + "vfile-message": ["vfile-message@4.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw=="], + "victory-vendor": ["victory-vendor@36.9.2", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ=="], "wavesurfer.js": ["wavesurfer.js@7.9.8", "", {}, "sha512-Mxz6qRwkSmuWVxLzp0XQ6EzSv1FTvQgMEUJTirLN1Ox76sn0YeyQlI99WuE+B0IuxShPHXIhvEuoBSJdaQs7tA=="], @@ -922,6 +1079,8 @@ "zustand": ["zustand@5.0.5", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-mILtRfKW9xM47hqxGIxCv12gXusoY/xTSHBYApXozR0HmQv299whhBeeAcRy+KrPPybzosvJBCOmVjq6x12fCg=="], + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], "@types/bun/bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="], @@ -934,6 +1093,8 @@ "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], + "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], From a63f35fc37b79b7080282a8ed3d460d8a9bb85eb Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Mon, 14 Jul 2025 23:53:33 +0200 Subject: [PATCH 05/39] docs: vercel sponsor to readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 30fc43a60..dcc6ca750 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,10 @@ We welcome contributions! Please see our [Contributing Guide](.github/CONTRIBUTI Thanks to [Vercel](https://vercel.com?utm_source=github-opencut&utm_campaign=oss) for their support of open-source software. + + Vercel OSS Program + + [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FOpenCut-app%2FOpenCut&project-name=opencut&repository-name=opencut) ## License From dd76e3d0452208fc515b1278f20bd6956217d04f Mon Sep 17 00:00:00 2001 From: enkeii64 Date: Tue, 15 Jul 2025 19:24:34 +1000 Subject: [PATCH 06/39] feat: Add Discord link to footer --- apps/web/src/components/footer.tsx | 272 +++++++++++++++-------------- 1 file changed, 140 insertions(+), 132 deletions(-) diff --git a/apps/web/src/components/footer.tsx b/apps/web/src/components/footer.tsx index 43a9db0e1..81505d4a9 100644 --- a/apps/web/src/components/footer.tsx +++ b/apps/web/src/components/footer.tsx @@ -1,132 +1,140 @@ -"use client"; - -import { motion } from "motion/react"; -import Link from "next/link"; -import { useEffect, useState } from "react"; -import { RiGithubLine, RiTwitterXLine } from "react-icons/ri"; -import { getStars } from "@/lib/fetch-github-stars"; -import Image from "next/image"; - -export function Footer() { - const [star, setStar] = useState(); - - useEffect(() => { - const fetchStars = async () => { - try { - const data = await getStars(); - setStar(data); - } catch (err) { - console.error("Failed to fetch GitHub stars", err); - } - }; - - fetchStars(); - }, []); - - return ( - -
    -
    - {/* Brand Section */} -
    -
    - OpenCut - OpenCut -
    -

    - The open source video editor that gets the job done. Simple, - powerful, and works on any platform. -

    -
    - - - - - - -
    -
    - -
    -
    -

    Resources

    -
      -
    • - - Roadmap - -
    • -
    • - - Privacy policy - -
    • -
    • - - Terms of use - -
    • -
    -
    - - {/* Company Links */} -
    -

    Company

    -
      -
    • - - Contributors - -
    • -
    • - - About - -
    • -
    -
    -
    -
    - - {/* Bottom Section */} -
    -
    - © 2025 OpenCut, All Rights Reserved -
    -
    -
    -
    - ); -} +"use client"; + +import { motion } from "motion/react"; +import Link from "next/link"; +import { useEffect, useState } from "react"; +import { RiDiscordFill, RiGithubLine, RiTwitterXLine } from "react-icons/ri"; +import { getStars } from "@/lib/fetch-github-stars"; +import Image from "next/image"; + +export function Footer() { + const [star, setStar] = useState(); + + useEffect(() => { + const fetchStars = async () => { + try { + const data = await getStars(); + setStar(data); + } catch (err) { + console.error("Failed to fetch GitHub stars", err); + } + }; + + fetchStars(); + }, []); + + return ( + +
    +
    + {/* Brand Section */} +
    +
    + OpenCut + OpenCut +
    +

    + The open source video editor that gets the job done. Simple, + powerful, and works on any platform. +

    +
    + + + + + + + + + +
    +
    + +
    +
    +

    Resources

    +
      +
    • + + Roadmap + +
    • +
    • + + Privacy policy + +
    • +
    • + + Terms of use + +
    • +
    +
    + + {/* Company Links */} +
    +

    Company

    +
      +
    • + + Contributors + +
    • +
    • + + About + +
    • +
    +
    +
    +
    + + {/* Bottom Section */} +
    +
    + © 2025 OpenCut, All Rights Reserved +
    +
    +
    +
    + ); +} From 104697d80f1feb56950c32d1356ac43b859cea40 Mon Sep 17 00:00:00 2001 From: tranminhquang Date: Tue, 15 Jul 2025 13:29:16 +0700 Subject: [PATCH 07/39] feat: integrate @t3-oss/env for environment variable management across the application --- apps/web/drizzle.config.ts | 11 +++----- apps/web/package.json | 2 ++ apps/web/src/components/development-debug.tsx | 3 ++- apps/web/src/components/header.tsx | 3 ++- apps/web/src/env.ts | 27 +++++++++++++++++++ apps/web/src/lib/rate-limit.ts | 5 ++-- apps/web/src/middleware.ts | 3 ++- packages/auth/package.json | 9 ++++--- packages/auth/src/client.ts | 7 +++-- packages/auth/src/index.ts | 5 +++- packages/auth/src/keys.ts | 20 ++++++++++++++ packages/auth/src/server.ts | 11 +++++--- packages/db/drizzle.config.ts | 15 +++++------ packages/db/package.json | 7 +++-- packages/db/src/index.ts | 8 +++--- packages/db/src/keys.ts | 19 +++++++++++++ 16 files changed, 118 insertions(+), 37 deletions(-) create mode 100644 apps/web/src/env.ts create mode 100644 packages/auth/src/keys.ts create mode 100644 packages/db/src/keys.ts diff --git a/apps/web/drizzle.config.ts b/apps/web/drizzle.config.ts index 615eea570..87547bd04 100644 --- a/apps/web/drizzle.config.ts +++ b/apps/web/drizzle.config.ts @@ -1,23 +1,20 @@ import type { Config } from "drizzle-kit"; import * as dotenv from "dotenv"; +import { env } from "@/env"; // Load the right env file based on environment -if (process.env.NODE_ENV === "production") { +if (env.NODE_ENV === "production") { dotenv.config({ path: ".env.production" }); } else { dotenv.config({ path: ".env.local" }); } -if (!process.env.DATABASE_URL) { - throw new Error("DATABASE_URL is not set"); -} - export default { schema: "../../packages/db/src/schema.ts", dialect: "postgresql", dbCredentials: { - url: process.env.DATABASE_URL, + url: env.DATABASE_URL, }, out: "./migrations", - strict: process.env.NODE_ENV === "production", + strict: env.NODE_ENV === "production", } satisfies Config; diff --git a/apps/web/package.json b/apps/web/package.json index 9e1393b74..b7bbc4999 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -21,6 +21,8 @@ "@hookform/resolvers": "^3.9.1", "@opencut/auth": "workspace:*", "@opencut/db": "workspace:*", + "@t3-oss/env-core": "^0.13.8", + "@t3-oss/env-nextjs": "^0.13.8", "@upstash/ratelimit": "^2.0.5", "@upstash/redis": "^1.35.0", "@vercel/analytics": "^1.4.1", diff --git a/apps/web/src/components/development-debug.tsx b/apps/web/src/components/development-debug.tsx index 3b891e38c..62e606ff0 100644 --- a/apps/web/src/components/development-debug.tsx +++ b/apps/web/src/components/development-debug.tsx @@ -7,9 +7,10 @@ import { usePlaybackStore } from "@/stores/playback-store"; import { Button } from "@/components/ui/button"; import { useState } from "react"; import type { TimelineElement } from "@/types/timeline"; +import { env } from "@/env"; // Only show in development -const SHOW_DEBUG_INFO = process.env.NODE_ENV === "development"; +const SHOW_DEBUG_INFO = env.NODE_ENV === "development"; interface ActiveElement { element: TimelineElement; diff --git a/apps/web/src/components/header.tsx b/apps/web/src/components/header.tsx index bfe314d61..455e0140c 100644 --- a/apps/web/src/components/header.tsx +++ b/apps/web/src/components/header.tsx @@ -8,6 +8,7 @@ import { useSession } from "@opencut/auth/client"; import { getStars } from "@/lib/fetch-github-stars"; import { useEffect, useState } from "react"; import Image from "next/image"; +import { env } from "@/env"; export function Header() { const { data: session } = useSession(); @@ -40,7 +41,7 @@ export function Header() { Contributors - {process.env.NODE_ENV === "development" ? ( + {env.NODE_ENV === "development" ? ( + + Toggle snapping (S) + + + + + + + Toggle grid snapping + + + + + + + Toggle element snapping + + + + + + + Toggle playhead snapping +
@@ -1033,6 +1158,17 @@ export function Timeline() { ))} )} + + {/* Snap Indicator */} +
From 11bcaea798e5119cbf8ba64873812a90722cb1f3 Mon Sep 17 00:00:00 2001 From: Anwarul Islam Date: Tue, 15 Jul 2025 04:27:19 +0600 Subject: [PATCH 15/39] feat: integrate snapping functionality into timeline track component --- .../src/components/editor/timeline-track.tsx | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/apps/web/src/components/editor/timeline-track.tsx b/apps/web/src/components/editor/timeline-track.tsx index 033b79a63..0b29c7781 100644 --- a/apps/web/src/components/editor/timeline-track.tsx +++ b/apps/web/src/components/editor/timeline-track.tsx @@ -22,6 +22,7 @@ import { TIMELINE_CONSTANTS, } from "@/constants/timeline-constants"; import { useProjectStore } from "@/stores/project-store"; +import { useTimelineSnapping } from "@/hooks/use-timeline-snapping"; export function TimelineTrackContent({ track, @@ -45,8 +46,25 @@ export function TimelineTrackContent({ endDrag: endDragAction, clearSelectedElements, insertTrackAt, + snappingEnabled, + gridSnappingEnabled, + elementSnappingEnabled, + playheadSnappingEnabled, + snapThreshold, + gridInterval, } = useTimelineStore(); + const { currentTime } = usePlaybackStore(); + + // Initialize snapping hook + const { snapElementPosition } = useTimelineSnapping({ + snapThreshold, + gridInterval, + enableGridSnapping: snappingEnabled && gridSnappingEnabled, + enableElementSnapping: snappingEnabled && elementSnappingEnabled, + enablePlayheadSnapping: snappingEnabled && playheadSnappingEnabled, + }); + const timelineRef = useRef(null); const [isDropping, setIsDropping] = useState(false); const [dropPosition, setDropPosition] = useState(null); @@ -85,12 +103,26 @@ export function TimelineTrackContent({ mouseX / (TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel) ); const adjustedTime = Math.max(0, mouseTime - dragState.clickOffsetTime); - // Use frame snapping if project has FPS, otherwise use decimal snapping - const projectStore = useProjectStore.getState(); - const projectFps = projectStore.activeProject?.fps || 30; - const snappedTime = snapTimeToFrame(adjustedTime, projectFps); - updateDragTime(snappedTime); + // Apply snapping if enabled + let finalTime = adjustedTime; + if (snappingEnabled) { + const snapResult = snapElementPosition( + adjustedTime, + tracks, + currentTime, + zoomLevel, + dragState.elementId || undefined + ); + finalTime = snapResult.snappedTime; + } else { + // Use frame snapping if project has FPS, otherwise use decimal snapping + const projectStore = useProjectStore.getState(); + const projectFps = projectStore.activeProject?.fps || 30; + finalTime = snapTimeToFrame(adjustedTime, projectFps); + } + + updateDragTime(finalTime); }; const handleMouseUp = (e: MouseEvent) => { From f96f562a6b90c2bbaeb19b4e5abfd61efb04188e Mon Sep 17 00:00:00 2001 From: Anwarul Islam Date: Tue, 15 Jul 2025 04:32:36 +0600 Subject: [PATCH 16/39] refactor: remove keyboard shortcuts for better integration later --- apps/web/src/components/editor/timeline.tsx | 28 --------------------- 1 file changed, 28 deletions(-) diff --git a/apps/web/src/components/editor/timeline.tsx b/apps/web/src/components/editor/timeline.tsx index cf139aa23..2f0e3d91e 100644 --- a/apps/web/src/components/editor/timeline.tsx +++ b/apps/web/src/components/editor/timeline.tsx @@ -341,34 +341,6 @@ export function Timeline() { return () => window.removeEventListener("keydown", handleKeyDown); }, [redo]); - // Keyboard shortcuts for snapping - useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - // Don't trigger when typing in input fields or textareas - if ( - e.target instanceof HTMLInputElement || - e.target instanceof HTMLTextAreaElement - ) { - return; - } - - // Only trigger when timeline is focused or mouse is over timeline - if ( - !isInTimeline && - !timelineRef.current?.contains(document.activeElement) - ) { - return; - } - - if (e.key === "s" || e.key === "S") { - e.preventDefault(); - toggleSnapping(); - } - }; - window.addEventListener("keydown", handleKeyDown); - return () => window.removeEventListener("keydown", handleKeyDown); - }, [toggleSnapping, isInTimeline]); - // Old marquee system removed - using new SelectionBox component instead const handleDragEnter = (e: React.DragEvent) => { From 14432a2ae01cbdf0bbbdff386e0e6302efcae94a Mon Sep 17 00:00:00 2001 From: Anwarul Islam Date: Tue, 15 Jul 2025 06:59:46 +0600 Subject: [PATCH 17/39] feat: enforce non-negative snap threshold and minimum grid interval in timeline store --- apps/web/src/stores/timeline-store.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/src/stores/timeline-store.ts b/apps/web/src/stores/timeline-store.ts index b45f55077..2734903a0 100644 --- a/apps/web/src/stores/timeline-store.ts +++ b/apps/web/src/stores/timeline-store.ts @@ -996,11 +996,11 @@ export const useTimelineStore = create((set, get) => { }, setSnapThreshold: (threshold) => { - set({ snapThreshold: threshold }); + set({ snapThreshold: Math.max(0, threshold) }); // Ensure non-negative threshold }, setGridInterval: (interval) => { - set({ gridInterval: interval }); + set({ gridInterval: Math.max(0.1, interval) }); // Ensure minimum interval }, }; }); From 03a51f8a3d58ed95b599f6e3559fc7b10ac4b371 Mon Sep 17 00:00:00 2001 From: Anwarul Islam Date: Tue, 15 Jul 2025 07:03:36 +0600 Subject: [PATCH 18/39] feat: enhance snapping functionality by adding snap point handling in Timeline component --- apps/web/src/components/editor/timeline.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/editor/timeline.tsx b/apps/web/src/components/editor/timeline.tsx index 2f0e3d91e..882af66e0 100644 --- a/apps/web/src/components/editor/timeline.tsx +++ b/apps/web/src/components/editor/timeline.tsx @@ -55,7 +55,7 @@ import { import { SelectionBox } from "./selection-box"; import { useSelectionBox } from "@/hooks/use-selection-box"; import { SnapIndicator } from "./snap-indicator"; -import { useTimelineSnapping } from "@/hooks/use-timeline-snapping"; +import { useTimelineSnapping, SnapPoint } from "@/hooks/use-timeline-snapping"; import type { DragData, TimelineTrack } from "@/types/timeline"; import { getTrackHeight, @@ -172,9 +172,16 @@ export function Timeline() { }); // Calculate snap indicator state - const [currentSnapPoint, setCurrentSnapPoint] = useState(null); + const [currentSnapPoint, setCurrentSnapPoint] = useState( + null + ); const showSnapIndicator = - dragState.isDragging && snappingEnabled && currentSnapPoint; + dragState.isDragging && snappingEnabled && currentSnapPoint !== null; + + // Callback to handle snap point changes from TimelineTrackContent + const handleSnapPointChange = useCallback((snapPoint: SnapPoint | null) => { + setCurrentSnapPoint(snapPoint); + }, []); // Timeline content click to seek handler const handleTimelineContentClick = useCallback( @@ -1113,6 +1120,7 @@ export function Timeline() { From ee581e40c1053b9da9e0bcddd742dda1976e3a6b Mon Sep 17 00:00:00 2001 From: Anwarul Islam Date: Tue, 15 Jul 2025 07:06:29 +0600 Subject: [PATCH 19/39] feat: add snap point notification to TimelineTrackContent for improved snapping feedback --- .../src/components/editor/timeline-track.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/editor/timeline-track.tsx b/apps/web/src/components/editor/timeline-track.tsx index 0b29c7781..aeb6f0407 100644 --- a/apps/web/src/components/editor/timeline-track.tsx +++ b/apps/web/src/components/editor/timeline-track.tsx @@ -22,14 +22,16 @@ import { TIMELINE_CONSTANTS, } from "@/constants/timeline-constants"; import { useProjectStore } from "@/stores/project-store"; -import { useTimelineSnapping } from "@/hooks/use-timeline-snapping"; +import { useTimelineSnapping, SnapPoint } from "@/hooks/use-timeline-snapping"; export function TimelineTrackContent({ track, zoomLevel, + onSnapPointChange, }: { track: TimelineTrack; zoomLevel: number; + onSnapPointChange?: (snapPoint: SnapPoint | null) => void; }) { const { mediaItems } = useMediaStore(); const { @@ -106,6 +108,7 @@ export function TimelineTrackContent({ // Apply snapping if enabled let finalTime = adjustedTime; + let snapPoint = null; if (snappingEnabled) { const snapResult = snapElementPosition( adjustedTime, @@ -115,11 +118,18 @@ export function TimelineTrackContent({ dragState.elementId || undefined ); finalTime = snapResult.snappedTime; + snapPoint = snapResult.snapPoint; + + // Notify parent component about snap point change + onSnapPointChange?.(snapPoint); } else { // Use frame snapping if project has FPS, otherwise use decimal snapping const projectStore = useProjectStore.getState(); const projectFps = projectStore.activeProject?.fps || 30; finalTime = snapTimeToFrame(adjustedTime, projectFps); + + // Clear snap point when not snapping + onSnapPointChange?.(null); } updateDragTime(finalTime); @@ -140,6 +150,8 @@ export function TimelineTrackContent({ dragState.currentTime ); endDragAction(); + // Clear snap point when drag ends + onSnapPointChange?.(null); } return; } @@ -236,6 +248,8 @@ export function TimelineTrackContent({ if (isTrackThatStartedDrag) { endDragAction(); + // Clear snap point when drag ends + onSnapPointChange?.(null); } }; @@ -261,6 +275,7 @@ export function TimelineTrackContent({ endDragAction, selectedElements, selectElement, + onSnapPointChange, ]); const handleElementMouseDown = ( From c36dbbf41b522ac1dfc4aea0a9386448669388f0 Mon Sep 17 00:00:00 2001 From: Anwarul Islam Date: Tue, 15 Jul 2025 07:17:45 +0600 Subject: [PATCH 20/39] fix: ensure correct type assertion for snappedTime in useTimelineSnapping --- apps/web/src/hooks/use-timeline-snapping.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/web/src/hooks/use-timeline-snapping.ts b/apps/web/src/hooks/use-timeline-snapping.ts index 68b9a7c09..c7e1f15d9 100644 --- a/apps/web/src/hooks/use-timeline-snapping.ts +++ b/apps/web/src/hooks/use-timeline-snapping.ts @@ -126,7 +126,9 @@ export function useTimelineSnapping({ }); return { - snappedTime: closestSnapPoint ? closestSnapPoint.time : targetTime, + snappedTime: closestSnapPoint + ? (closestSnapPoint as SnapPoint).time + : targetTime, snapPoint: closestSnapPoint, snapDistance: closestDistance, }; From f1d9b118802c1712e6b6c92caf097da4d8c90b27 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 15 Jul 2025 14:13:02 +0200 Subject: [PATCH 21/39] fix: tooltip styling --- apps/web/src/components/ui/tooltip.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/ui/tooltip.tsx b/apps/web/src/components/ui/tooltip.tsx index a2fb0a012..0da023e68 100644 --- a/apps/web/src/components/ui/tooltip.tsx +++ b/apps/web/src/components/ui/tooltip.tsx @@ -20,7 +20,7 @@ const TooltipContent = React.forwardRef< ref={ref} sideOffset={sideOffset} className={cn( - "z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", + "z-50 overflow-hidden rounded-md bg-foreground/10 px-3 py-1.5 text-xs text-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className )} {...props} From c4992e8e8bd2a7e6641f61510519f3f09bef613e Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 15 Jul 2025 14:16:54 +0200 Subject: [PATCH 22/39] cleanup --- .../src/components/editor/snap-indicator.tsx | 65 +-- .../components/editor/timeline-playhead.tsx | 14 +- .../src/components/editor/timeline-track.tsx | 13 +- apps/web/src/components/editor/timeline.tsx | 412 ++++++++---------- apps/web/src/hooks/use-timeline-snapping.ts | 32 +- apps/web/src/stores/timeline-store.ts | 39 -- 6 files changed, 214 insertions(+), 361 deletions(-) diff --git a/apps/web/src/components/editor/snap-indicator.tsx b/apps/web/src/components/editor/snap-indicator.tsx index 398e06c61..83a1f6788 100644 --- a/apps/web/src/components/editor/snap-indicator.tsx +++ b/apps/web/src/components/editor/snap-indicator.tsx @@ -2,74 +2,53 @@ import { SnapPoint } from "@/hooks/use-timeline-snapping"; import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants"; +import type { TimelineTrack } from "@/types/timeline"; interface SnapIndicatorProps { snapPoint: SnapPoint | null; zoomLevel: number; - timelineHeight: number; isVisible: boolean; + tracks: TimelineTrack[]; + timelineRef: React.RefObject; + trackLabelsRef?: React.RefObject; } export function SnapIndicator({ snapPoint, zoomLevel, - timelineHeight, isVisible, + tracks, + timelineRef, + trackLabelsRef, }: SnapIndicatorProps) { if (!isVisible || !snapPoint) { return null; } - const leftPosition = - snapPoint.time * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel; + const timelineContainerHeight = timelineRef.current?.offsetHeight || 400; + const totalHeight = timelineContainerHeight - 8; // 8px padding from edges - const getIndicatorColor = () => { - switch (snapPoint.type) { - case "grid": - return "bg-blue-400"; - case "element-start": - case "element-end": - return "bg-green-400"; - case "playhead": - return "bg-red-400"; - default: - return "bg-gray-400"; - } - }; + // Get dynamic track labels width, fallback to 0 if no tracks or no ref + const trackLabelsWidth = + tracks.length > 0 && trackLabelsRef?.current + ? trackLabelsRef.current.offsetWidth + : 0; - const getIndicatorLabel = () => { - switch (snapPoint.type) { - case "grid": - return "Grid"; - case "element-start": - return "Start"; - case "element-end": - return "End"; - case "playhead": - return "Playhead"; - default: - return ""; - } - }; + const leftPosition = + trackLabelsWidth + + snapPoint.time * TIMELINE_CONSTANTS.PIXELS_PER_SECOND * zoomLevel; return (
- {/* Snap line */} -
- - {/* Snap label */} -
- {getIndicatorLabel()} ({snapPoint.time.toFixed(1)}s) -
+
); } diff --git a/apps/web/src/components/editor/timeline-playhead.tsx b/apps/web/src/components/editor/timeline-playhead.tsx index ce4ed1ff7..78aed3a8a 100644 --- a/apps/web/src/components/editor/timeline-playhead.tsx +++ b/apps/web/src/components/editor/timeline-playhead.tsx @@ -20,6 +20,7 @@ interface TimelinePlayheadProps { trackLabelsRef?: React.RefObject; timelineRef: React.RefObject; playheadRef?: React.RefObject; + isSnappingToPlayhead?: boolean; } export function TimelinePlayhead({ @@ -34,6 +35,7 @@ export function TimelinePlayhead({ trackLabelsRef, timelineRef, playheadRef: externalPlayheadRef, + isSnappingToPlayhead = false, }: TimelinePlayheadProps) { const internalPlayheadRef = useRef(null); const playheadRef = externalPlayheadRef || internalPlayheadRef; @@ -73,11 +75,15 @@ export function TimelinePlayhead({ }} onMouseDown={handlePlayheadMouseDown} > - {/* The red line spanning full height */} -
+ {/* The playhead line spanning full height */} +
- {/* Red dot indicator at the top (in ruler area) */} -
+ {/* Playhead dot indicator at the top (in ruler area) */} +
); } diff --git a/apps/web/src/components/editor/timeline-track.tsx b/apps/web/src/components/editor/timeline-track.tsx index aeb6f0407..2be143ebf 100644 --- a/apps/web/src/components/editor/timeline-track.tsx +++ b/apps/web/src/components/editor/timeline-track.tsx @@ -49,22 +49,15 @@ export function TimelineTrackContent({ clearSelectedElements, insertTrackAt, snappingEnabled, - gridSnappingEnabled, - elementSnappingEnabled, - playheadSnappingEnabled, - snapThreshold, - gridInterval, } = useTimelineStore(); const { currentTime } = usePlaybackStore(); // Initialize snapping hook const { snapElementPosition } = useTimelineSnapping({ - snapThreshold, - gridInterval, - enableGridSnapping: snappingEnabled && gridSnappingEnabled, - enableElementSnapping: snappingEnabled && elementSnappingEnabled, - enablePlayheadSnapping: snappingEnabled && playheadSnappingEnabled, + snapThreshold: 10, + enableElementSnapping: snappingEnabled, + enablePlayheadSnapping: snappingEnabled, }); const timelineRef = useRef(null); diff --git a/apps/web/src/components/editor/timeline.tsx b/apps/web/src/components/editor/timeline.tsx index 882af66e0..e83b64b93 100644 --- a/apps/web/src/components/editor/timeline.tsx +++ b/apps/web/src/components/editor/timeline.tsx @@ -16,9 +16,7 @@ import { Music, TypeIcon, Magnet, - Grid3X3, - Move, - Target, + Lock, } from "lucide-react"; import { Tooltip, @@ -40,13 +38,6 @@ import { useTimelineZoom } from "@/hooks/use-timeline-zoom"; import { processMediaFiles } from "@/lib/media-processing"; import { toast } from "sonner"; import { useState, useRef, useEffect, useCallback } from "react"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "../ui/select"; import { TimelineTrackContent } from "./timeline-track"; import { TimelinePlayhead, @@ -55,7 +46,7 @@ import { import { SelectionBox } from "./selection-box"; import { useSelectionBox } from "@/hooks/use-selection-box"; import { SnapIndicator } from "./snap-indicator"; -import { useTimelineSnapping, SnapPoint } from "@/hooks/use-timeline-snapping"; +import { SnapPoint } from "@/hooks/use-timeline-snapping"; import type { DragData, TimelineTrack } from "@/types/timeline"; import { getTrackHeight, @@ -85,15 +76,7 @@ export function Timeline() { undo, redo, snappingEnabled, - gridSnappingEnabled, - elementSnappingEnabled, - playheadSnappingEnabled, - snapThreshold, - gridInterval, toggleSnapping, - toggleGridSnapping, - toggleElementSnapping, - togglePlayheadSnapping, dragState, } = useTimelineStore(); const { mediaItems, addMediaItem } = useMediaStore(); @@ -135,7 +118,7 @@ export function Timeline() { const lastVerticalSync = useRef(0); // Timeline playhead ruler handlers - const { handleRulerMouseDown, isDraggingRuler } = useTimelinePlayheadRuler({ + const { handleRulerMouseDown } = useTimelinePlayheadRuler({ currentTime, duration, zoomLevel, @@ -162,15 +145,6 @@ export function Timeline() { }, }); - // Initialize snapping functionality - const { snapElementPosition } = useTimelineSnapping({ - snapThreshold, - gridInterval, - enableGridSnapping: snappingEnabled && gridSnappingEnabled, - enableElementSnapping: snappingEnabled && elementSnappingEnabled, - enablePlayheadSnapping: snappingEnabled && playheadSnappingEnabled, - }); - // Calculate snap indicator state const [currentSnapPoint, setCurrentSnapPoint] = useState( null @@ -724,202 +698,168 @@ export function Timeline() { onMouseLeave={() => setIsInTimeline(false)} > {/* Toolbar */} -
- - {/* Play/Pause Button */} - - - - - - {isPlaying ? "Pause (Space)" : "Play (Space)"} - - -
- {/* Time Display */} -
- {currentTime.toFixed(1)}s / {duration.toFixed(1)}s -
- {/* Test Clip Button - for debugging */} - {tracks.length === 0 && ( - <> -
- - - - - Add a test clip to try playback - - - )} -
- - - - - Split element (Ctrl+S) - - - - - - Split and keep left (Ctrl+Q) - - - - - - Split and keep right (Ctrl+W) - - - - - - Separate audio (Ctrl+D) - - - - - - Duplicate element (Ctrl+D) - - - - - - Freeze frame (F) - - - - - - Delete element (Delete) - - -
- - {/* Snapping Controls */} - - - - - Toggle snapping (S) - - - - - - - Toggle grid snapping - - - - - - - Toggle element snapping - - - - - - - Toggle playhead snapping - - +
+
+ + {/* Play/Pause Button */} + + + + + + {isPlaying ? "Pause (Space)" : "Play (Space)"} + + +
+ {/* Time Display */} +
+ {currentTime.toFixed(1)}s / {duration.toFixed(1)}s +
+ {/* Test Clip Button - for debugging */} + {tracks.length === 0 && ( + <> +
+ + + + + + Add a test clip to try playback + + + + )} +
+ + + + + Split element (Ctrl+S) + + + + + + Split and keep left (Ctrl+Q) + + + + + + Split and keep right (Ctrl+W) + + + + + + Separate audio (Ctrl+D) + + + + + + Duplicate element (Ctrl+D) + + + + + + Freeze frame (F) + + + + + + Delete element (Delete) + + +
+
+ + + + + + Auto snapping + + +
{/* Timeline Container */} @@ -939,6 +879,17 @@ export function Timeline() { trackLabelsRef={trackLabelsRef} timelineRef={timelineRef} playheadRef={playheadRef} + isSnappingToPlayhead={ + showSnapIndicator && currentSnapPoint?.type === "playhead" + } + /> + {/* Timeline Header with Ruler */}
@@ -1138,17 +1089,6 @@ export function Timeline() { ))} )} - - {/* Snap Indicator */} -
diff --git a/apps/web/src/hooks/use-timeline-snapping.ts b/apps/web/src/hooks/use-timeline-snapping.ts index c7e1f15d9..f7f963925 100644 --- a/apps/web/src/hooks/use-timeline-snapping.ts +++ b/apps/web/src/hooks/use-timeline-snapping.ts @@ -1,10 +1,10 @@ import { useCallback } from "react"; -import { TimelineElement, TimelineTrack } from "@/types/timeline"; +import { TimelineTrack } from "@/types/timeline"; import { TIMELINE_CONSTANTS } from "@/constants/timeline-constants"; export interface SnapPoint { time: number; - type: "grid" | "element-start" | "element-end" | "playhead"; + type: "element-start" | "element-end" | "playhead"; elementId?: string; trackId?: string; } @@ -17,16 +17,12 @@ export interface SnapResult { export interface UseTimelineSnappingOptions { snapThreshold?: number; // Distance in pixels to trigger snapping - gridInterval?: number; // Grid interval in seconds - enableGridSnapping?: boolean; enableElementSnapping?: boolean; enablePlayheadSnapping?: boolean; } export function useTimelineSnapping({ snapThreshold = 10, - gridInterval = 1, - enableGridSnapping = true, enableElementSnapping = true, enablePlayheadSnapping = true, }: UseTimelineSnappingOptions = {}) { @@ -40,23 +36,6 @@ export function useTimelineSnapping({ ): SnapPoint[] => { const snapPoints: SnapPoint[] = []; - // Add grid snap points - if (enableGridSnapping) { - const gridStart = Math.floor(currentTime / gridInterval) * gridInterval; - const gridEnd = Math.ceil(currentTime / gridInterval) * gridInterval; - - // Add nearby grid points - for (let i = -2; i <= 2; i++) { - const gridTime = gridStart + i * gridInterval; - if (gridTime >= 0) { - snapPoints.push({ - time: gridTime, - type: "grid", - }); - } - } - } - // Add element snap points if (enableElementSnapping) { tracks.forEach((track) => { @@ -97,12 +76,7 @@ export function useTimelineSnapping({ return snapPoints; }, - [ - enableGridSnapping, - enableElementSnapping, - enablePlayheadSnapping, - gridInterval, - ] + [enableElementSnapping, enablePlayheadSnapping] ); const snapToNearestPoint = useCallback( diff --git a/apps/web/src/stores/timeline-store.ts b/apps/web/src/stores/timeline-store.ts index 2734903a0..f6b9bfd8f 100644 --- a/apps/web/src/stores/timeline-store.ts +++ b/apps/web/src/stores/timeline-store.ts @@ -44,19 +44,9 @@ interface TimelineStore { // Snapping settings snappingEnabled: boolean; - gridSnappingEnabled: boolean; - elementSnappingEnabled: boolean; - playheadSnappingEnabled: boolean; - snapThreshold: number; - gridInterval: number; // Snapping actions toggleSnapping: () => void; - toggleGridSnapping: () => void; - toggleElementSnapping: () => void; - togglePlayheadSnapping: () => void; - setSnapThreshold: (threshold: number) => void; - setGridInterval: (interval: number) => void; // Multi-selection selectedElements: { trackId: string; elementId: string }[]; @@ -221,11 +211,6 @@ export const useTimelineStore = create((set, get) => { // Snapping settings defaults snappingEnabled: true, - gridSnappingEnabled: true, - elementSnappingEnabled: true, - playheadSnappingEnabled: true, - snapThreshold: 10, // pixels - gridInterval: 1, // seconds getSortedTracks: () => { const { _tracks } = get(); @@ -978,29 +963,5 @@ export const useTimelineStore = create((set, get) => { toggleSnapping: () => { set((state) => ({ snappingEnabled: !state.snappingEnabled })); }, - - toggleGridSnapping: () => { - set((state) => ({ gridSnappingEnabled: !state.gridSnappingEnabled })); - }, - - toggleElementSnapping: () => { - set((state) => ({ - elementSnappingEnabled: !state.elementSnappingEnabled, - })); - }, - - togglePlayheadSnapping: () => { - set((state) => ({ - playheadSnappingEnabled: !state.playheadSnappingEnabled, - })); - }, - - setSnapThreshold: (threshold) => { - set({ snapThreshold: Math.max(0, threshold) }); // Ensure non-negative threshold - }, - - setGridInterval: (interval) => { - set({ gridInterval: Math.max(0.1, interval) }); // Ensure minimum interval - }, }; }); From 788da4d39baa4924548411f96b4a1b0b9e412bd1 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 15 Jul 2025 14:58:08 +0200 Subject: [PATCH 23/39] refactor: remove development debug --- apps/web/src/app/layout.tsx | 2 - apps/web/src/components/development-debug.tsx | 109 ------------------ 2 files changed, 111 deletions(-) delete mode 100644 apps/web/src/components/development-debug.tsx diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 71716e74b..9ac54262f 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -4,7 +4,6 @@ import Script from "next/script"; import "./globals.css"; import { Toaster } from "../components/ui/sonner"; import { TooltipProvider } from "../components/ui/tooltip"; -import { DevelopmentDebug } from "../components/development-debug"; import { StorageProvider } from "../components/storage-provider"; import { baseMetaData } from "./metadata"; import { defaultFont } from "../lib/font-config"; @@ -24,7 +23,6 @@ export default function RootLayout({ {children} -