summaryrefslogtreecommitdiff
path: root/apps/web/src/components/editor/panels/properties/index.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/components/editor/panels/properties/index.tsx')
-rw-r--r--apps/web/src/components/editor/panels/properties/index.tsx141
1 files changed, 75 insertions, 66 deletions
diff --git a/apps/web/src/components/editor/panels/properties/index.tsx b/apps/web/src/components/editor/panels/properties/index.tsx
index cb45996d..3f3ff1f3 100644
--- a/apps/web/src/components/editor/panels/properties/index.tsx
+++ b/apps/web/src/components/editor/panels/properties/index.tsx
@@ -1,86 +1,95 @@
"use client";
import { ScrollArea } from "@/components/ui/scroll-area";
-import { AudioProperties } from "./audio-properties";
-import { VideoProperties } from "./video-properties";
-import { TextProperties } from "./text-properties";
-import { EffectProperties } from "./effect-properties";
-import { ClipEffectsProperties } from "./clip-effects-properties";
-import { EmptyView } from "./empty-view";
+import { Button } from "@/components/ui/button";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
import { useEditor } from "@/hooks/use-editor";
import { useElementSelection } from "@/hooks/timeline/element/use-element-selection";
-import { usePropertiesStore } from "@/stores/properties-store";
-import { isVisualElement } from "@/lib/timeline";
-import type { TimelineElement, TimelineTrack } from "@/types/timeline";
-
-function ElementProperties({
- track,
- element,
-}: {
- track: TimelineTrack;
- element: TimelineElement;
-}) {
- if (element.type === "text") {
- return <TextProperties element={element} trackId={track.id} />;
- }
- if (element.type === "audio") {
- return <AudioProperties />;
- }
- if (
- element.type === "video" ||
- element.type === "image" ||
- element.type === "sticker"
- ) {
- return <VideoProperties element={element} trackId={track.id} />;
- }
- if (element.type === "effect") {
- return <EffectProperties element={element} trackId={track.id} />;
- }
- return null;
-}
+import { usePropertiesStore } from "./stores/properties-store";
+import { getPropertiesConfig } from "./registry";
+import { cn } from "@/utils/ui";
+import { EmptyView } from "./empty-view";
export function PropertiesPanel() {
const editor = useEditor();
+ useEditor((e) => e.timeline.getTracks());
+ useEditor((e) => e.media.getAssets());
const { selectedElements } = useElementSelection();
- const clipEffectsTarget = usePropertiesStore(
- (state) => state.clipEffectsTarget,
- );
+ const { activeTabPerType, setActiveTab } = usePropertiesStore();
- const clipEffectsTrack = clipEffectsTarget
- ? editor.timeline.getTrackById({ trackId: clipEffectsTarget.trackId })
- : null;
- const clipEffectsElement = clipEffectsTrack?.elements.find(
- (element) => element.id === clipEffectsTarget?.elementId,
- );
- const isShowingClipEffects =
- clipEffectsTrack &&
- clipEffectsElement &&
- isVisualElement(clipEffectsElement);
+ if (selectedElements.length === 0) {
+ return (
+ <div className="panel bg-background flex h-full flex-col items-center justify-center overflow-hidden rounded-sm border">
+ <EmptyView />
+ </div>
+ );
+ }
+
+ if (selectedElements.length > 1) {
+ return (
+ <div className="panel bg-background flex h-full flex-col items-center justify-center overflow-hidden rounded-sm border">
+ <p className="text-muted-foreground text-sm">
+ {selectedElements.length} elements selected.0
+ </p>
+ </div>
+ );
+ }
+
+ const mediaAssets = editor.media.getAssets();
const elementsWithTracks = editor.timeline.getElementsWithTracks({
elements: selectedElements,
});
+ const elementWithTrack = elementsWithTracks[0];
+
+ if (!elementWithTrack) return null;
+
+ const { element, track } = elementWithTrack;
+ const config = getPropertiesConfig({ element, mediaAssets });
+ const visibleTabs = config.tabs;
+
+ const storedTabId = activeTabPerType[element.type];
+ const isStoredTabVisible = visibleTabs.some((t) => t.id === storedTabId);
+ const activeTabId = isStoredTabVisible ? storedTabId : config.defaultTab;
+ const activeTab =
+ visibleTabs.find((t) => t.id === activeTabId) ?? visibleTabs[0];
+
+ if (!activeTab) return null;
return (
- <div className="panel bg-background h-full rounded-sm border overflow-hidden">
- {isShowingClipEffects ? (
- <ClipEffectsProperties
- element={clipEffectsElement}
- trackId={clipEffectsTrack.id}
- />
- ) : selectedElements.length > 0 ? (
- <ScrollArea className="h-full scrollbar-hidden">
- {elementsWithTracks.map(({ track, element }) => (
- <ElementProperties
- key={element.id}
- track={track}
- element={element}
- />
+ <div className="panel bg-background flex h-full overflow-hidden rounded-sm border">
+ <TooltipProvider delayDuration={0}>
+ <div className="flex shrink-0 flex-col gap-0.5 border-r p-1 scrollbar-hidden overflow-y-auto">
+ {visibleTabs.map((tab) => (
+ <Tooltip key={tab.id}>
+ <TooltipTrigger asChild>
+ <Button
+ variant={tab.id === activeTab.id ? "secondary" : "ghost"}
+ size="icon"
+ onClick={() => setActiveTab(element.type, tab.id)}
+ aria-label={tab.label}
+ className={cn(
+ "shrink-0",
+ "h-8 w-8",
+ tab.id !== activeTab.id && "text-muted-foreground",
+ )}
+ >
+ {tab.icon}
+ </Button>
+ </TooltipTrigger>
+ <TooltipContent side="right">{tab.label}</TooltipContent>
+ </Tooltip>
))}
- </ScrollArea>
- ) : (
- <EmptyView />
- )}
+ </div>
+ </TooltipProvider>
+ <ScrollArea className="flex-1 scrollbar-hidden">
+ {activeTab.content({ trackId: track.id })}
+ </ScrollArea>
</div>
);
}