Skip to content
Merged
10 changes: 9 additions & 1 deletion apps/web/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,15 @@ const nextConfig: NextConfig = {
},
{
protocol: "https",
hostname: "res.cloudinary.com",
hostname: "api.iconify.design",
},
{
protocol: "https",
hostname: "api.simplesvg.com",
},
{
protocol: "https",
hostname: "api.unisvg.com",
},
],
},
Expand Down
Binary file added apps/web/public/landing-page-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 24 additions & 0 deletions apps/web/src/app/animation/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"use client";

import { InputWithBack } from "@/components/ui/input-with-back";
import { useState } from "react";

export default function AnimationPage() {
const [isExpanded, setIsExpanded] = useState(false);

return (
<div className="h-dvh flex">
<div className="bg-panel p-6 w-[20rem]">
<InputWithBack isExpanded={isExpanded} setIsExpanded={setIsExpanded} />
</div>
<div className="p-6 flex items-end justify-end">
<p
onClick={() => setIsExpanded(!isExpanded)}
className="cursor-pointer hover:opacity-75 transition-opacity"
>
{isExpanded ? "Collapse" : "Expand"}
</p>
</div>
</div>
);
}
37 changes: 4 additions & 33 deletions apps/web/src/components/editor-header.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
"use client";

import { Button } from "./ui/button";
import {
ChevronDown,
ArrowLeft,
Download,
SquarePen,
Trash,
Sun,
} from "lucide-react";
import { useTimelineStore } from "@/stores/timeline-store";
import { ChevronDown, ArrowLeft, SquarePen, Trash, Sun } from "lucide-react";
import { HeaderBase } from "./header-base";
import { formatTimeCode } from "@/lib/time";
import { useProjectStore } from "@/stores/project-store";
import { KeyboardShortcutsHelp } from "./keyboard-shortcuts-help";
import { useState } from "react";
Expand All @@ -28,13 +19,10 @@ import { DeleteProjectDialog } from "./delete-project-dialog";
import { useRouter } from "next/navigation";
import { FaDiscord } from "react-icons/fa6";
import { useTheme } from "next-themes";
import { usePlaybackStore } from "@/stores/playback-store";
import { TransitionUpIcon } from "./icons";
import { PanelPresetSelector } from "./panel-preset-selector";

export function EditorHeader() {
const { getTotalDuration } = useTimelineStore();
const { currentTime } = usePlaybackStore();
const { activeProject, renameProject, deleteProject } = useProjectStore();
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false);
Expand Down Expand Up @@ -124,22 +112,6 @@ export function EditorHeader() {
</div>
);

const centerContent = (
<div className="flex items-center gap-2 text-xs">
<span className="text-foreground tabular-nums">
{formatTimeCode(currentTime, "HH:MM:SS:FF", activeProject?.fps || 30)}
</span>
<span className="text-foreground/50">/</span>
<span className="text-foreground/50 tabular-nums">
{formatTimeCode(
getTotalDuration(),
"HH:MM:SS:FF",
activeProject?.fps || 30
)}
</span>
</div>
);

const rightContent = (
<nav className="flex items-center gap-2">
<PanelPresetSelector />
Expand All @@ -160,7 +132,6 @@ export function EditorHeader() {
return (
<HeaderBase
leftContent={leftContent}
centerContent={centerContent}
rightContent={rightContent}
className="bg-background h-[3.2rem] px-3 items-center mt-0.5"
/>
Expand All @@ -177,14 +148,14 @@ function ExportButton() {

return (
<button
className="flex items-center gap-1.5 bg-[#38BDF8] text-white rounded-md px-[0.1rem] py-[0.1rem] cursor-pointer hover:brightness-95 transition-all duration-200"
className="flex items-center gap-1.5 bg-[#38BDF8] text-white rounded-md px-[0.12rem] py-[0.12rem] cursor-pointer hover:brightness-95 transition-all duration-200"
onClick={handleExport}
>
<div className="flex items-center gap-1.5 bg-linear-270 from-[#2567EC] to-[#37B6F7] rounded-[0.8rem] px-4 py-1 relative shadow-[0_1px_3px_0px_rgba(0,0,0,0.45)]">
<div className="flex items-center gap-1.5 bg-linear-270 from-[#2567EC] to-[#37B6F7] rounded-[0.8rem] px-4 py-1 relative shadow-[0_1px_3px_0px_rgba(0,0,0,0.65)]">
<TransitionUpIcon className="z-50" />
<span className="text-[0.875rem] z-50">Export</span>
<div className="absolute w-full h-full left-0 top-0 bg-linear-to-t from-white/0 to-white/50 z-10 rounded-[0.8rem] flex items-center justify-center">
<div className="absolute w-[calc(100%-4px)] h-[calc(100%-4px)] top-[0.12rem] bg-linear-270 from-[#2567EC] to-[#37B6F7] z-50 rounded-lg"></div>
<div className="absolute w-[calc(100%-2px)] h-[calc(100%-2px)] top-[0.08rem] bg-linear-270 from-[#2567EC] to-[#37B6F7] z-50 rounded-[0.8rem]"></div>
</div>
</div>
</button>
Expand Down
7 changes: 2 additions & 5 deletions apps/web/src/components/editor/media-panel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { MediaView } from "./views/media";
import { useMediaPanelStore, Tab } from "./store";
import { TextView } from "./views/text";
import { SoundsView } from "./views/sounds";
import { StickersView } from "./views/stickers";
import { Separator } from "@/components/ui/separator";
import { SettingsView } from "./views/settings";
import { Captions } from "./views/captions";
Expand All @@ -16,11 +17,7 @@ export function MediaPanel() {
media: <MediaView />,
sounds: <SoundsView />,
text: <TextView />,
stickers: (
<div className="p-4 text-muted-foreground">
Stickers view coming soon...
</div>
),
stickers: <StickersView />,
effects: (
<div className="p-4 text-muted-foreground">
Effects view coming soon...
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/components/editor/media-panel/views/media.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ export function MediaView() {

useEffect(() => {
let filtered = mediaItems.filter((item) => {
if (item.ephemeral) return false;
if (mediaFilter && mediaFilter !== "all" && item.type !== mediaFilter) {
return false;
}
Expand Down
32 changes: 14 additions & 18 deletions apps/web/src/components/editor/media-panel/views/sounds.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { Input } from "@/components/ui/input";
import { useState, useMemo, useRef, useEffect } from "react";
import { useState, useMemo, useEffect } from "react";
import { Separator } from "@/components/ui/separator";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
Expand Down Expand Up @@ -32,6 +32,7 @@ import {
DialogTrigger,
} from "@/components/ui/dialog";
import { cn } from "@/lib/utils";
import { useInfiniteScroll } from "@/hooks/use-infinite-scroll";

export function SoundsView() {
return (
Expand Down Expand Up @@ -96,35 +97,30 @@ function SoundEffectsView() {
null
);

// Scroll position persistence
const scrollAreaRef = useRef<HTMLDivElement>(null);
const { scrollAreaRef, handleScroll } = useInfiniteScroll({
onLoadMore: loadMore,
hasMore: hasNextPage,
isLoading: isLoadingMore || isSearching,
});

// Load saved sounds and restore scroll position when component mounts
useEffect(() => {
loadSavedSounds();

if (scrollAreaRef.current && scrollPosition > 0) {
const timeoutId = setTimeout(() => {
scrollAreaRef.current?.scrollTo({ top: scrollPosition });
}, 100); // Small delay to ensure content is rendered
}, 100);

return () => clearTimeout(timeoutId);
}
}, []); // Only run on mount
}, []);

// Track scroll position changes and handle infinite scroll
const handleScroll = (event: React.UIEvent<HTMLDivElement>) => {
const { scrollTop, scrollHeight, clientHeight } = event.currentTarget;
const handleScrollWithPosition = (event: React.UIEvent<HTMLDivElement>) => {
const { scrollTop } = event.currentTarget;
setScrollPosition(scrollTop);

// Trigger loadMore when scrolled to within 200px of bottom
const nearBottom = scrollTop + clientHeight >= scrollHeight - 200;
if (nearBottom && hasNextPage && !isLoadingMore && !isSearching) {
loadMore();
}
handleScroll(event);
};

// Use your existing design, just swap the data source

const displayedSounds = useMemo(() => {
const sounds = searchQuery ? searchResults : topSoundEffects;
return sounds;
Expand Down Expand Up @@ -199,7 +195,7 @@ function SoundEffectsView() {
<ScrollArea
className="flex-1 h-full"
ref={scrollAreaRef}
onScrollCapture={handleScroll}
onScrollCapture={handleScrollWithPosition}
>
<div className="flex flex-col gap-4">
{isLoading && !searchQuery && (
Expand Down
Loading
Loading