feat: add conditional rendering to properties panel with text change functionality
This commit is contained in:
@ -4,13 +4,18 @@ import { useProjectStore } from "@/stores/project-store";
|
|||||||
import { useAspectRatio } from "@/hooks/use-aspect-ratio";
|
import { useAspectRatio } from "@/hooks/use-aspect-ratio";
|
||||||
import { Label } from "../ui/label";
|
import { Label } from "../ui/label";
|
||||||
import { ScrollArea } from "../ui/scroll-area";
|
import { ScrollArea } from "../ui/scroll-area";
|
||||||
|
import { useTimelineStore } from "@/stores/timeline-store";
|
||||||
|
import { Input } from "../ui/input";
|
||||||
|
import { MediaElement, TextElement } from "@/types/timeline";
|
||||||
|
import { useMediaStore } from "@/stores/media-store";
|
||||||
|
|
||||||
export function PropertiesPanel() {
|
export function PropertiesPanel() {
|
||||||
const { activeProject } = useProjectStore();
|
const { activeProject } = useProjectStore();
|
||||||
const { getDisplayName, canvasSize } = useAspectRatio();
|
const { getDisplayName, canvasSize } = useAspectRatio();
|
||||||
|
const { selectedElements, tracks, updateTextElement } = useTimelineStore();
|
||||||
|
const { mediaItems } = useMediaStore();
|
||||||
|
|
||||||
return (
|
const emptyView = (
|
||||||
<ScrollArea className="h-full bg-panel rounded-sm">
|
|
||||||
<div className="space-y-4 p-5">
|
<div className="space-y-4 p-5">
|
||||||
{/* Media Properties */}
|
{/* Media Properties */}
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
@ -23,6 +28,50 @@ export function PropertiesPanel() {
|
|||||||
<PropertyItem label="Frame rate:" value="30.00fps" />
|
<PropertyItem label="Frame rate:" value="30.00fps" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const TextProperties = (element: TextElement, trackId: string) => (
|
||||||
|
<div className="space-y-4 p-5">
|
||||||
|
<Input
|
||||||
|
placeholder="Name"
|
||||||
|
defaultValue={element.content}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateTextElement(trackId, element.id, { content: e.target.value })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const MediaProperties = (element: MediaElement) => {
|
||||||
|
const mediaItem = mediaItems.find((item) => item.id === element.mediaId);
|
||||||
|
|
||||||
|
if (mediaItem?.type === "audio") {
|
||||||
|
return <div className="space-y-4 p-5">Audio properties</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// video or image
|
||||||
|
return <div className="space-y-4 p-5">Video/Image properties</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ElementProperties = (
|
||||||
|
<>
|
||||||
|
{selectedElements.map(({ trackId, elementId }) => {
|
||||||
|
const track = tracks.find((t) => t.id === trackId);
|
||||||
|
const element = track?.elements.find((e) => e.id === elementId);
|
||||||
|
|
||||||
|
if (element?.type === "text") {
|
||||||
|
return <div key={elementId}>{TextProperties(element, trackId)}</div>;
|
||||||
|
}
|
||||||
|
if (element?.type === "media") {
|
||||||
|
return <div key={elementId}>{MediaProperties(element)}</div>;
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollArea className="h-full bg-panel rounded-sm">
|
||||||
|
{selectedElements.length > 0 ? ElementProperties : emptyView}
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
TimelineElement,
|
TimelineElement,
|
||||||
CreateTimelineElement,
|
CreateTimelineElement,
|
||||||
TimelineTrack,
|
TimelineTrack,
|
||||||
|
TextElement,
|
||||||
sortTracksByOrder,
|
sortTracksByOrder,
|
||||||
ensureMainTrack,
|
ensureMainTrack,
|
||||||
validateElementTrackCompatibility,
|
validateElementTrackCompatibility,
|
||||||
@ -136,6 +137,28 @@ interface TimelineStore {
|
|||||||
loadProjectTimeline: (projectId: string) => Promise<void>;
|
loadProjectTimeline: (projectId: string) => Promise<void>;
|
||||||
saveProjectTimeline: (projectId: string) => Promise<void>;
|
saveProjectTimeline: (projectId: string) => Promise<void>;
|
||||||
clearTimeline: () => void;
|
clearTimeline: () => void;
|
||||||
|
updateTextElement: (
|
||||||
|
trackId: string,
|
||||||
|
elementId: string,
|
||||||
|
updates: Partial<
|
||||||
|
Pick<
|
||||||
|
TextElement,
|
||||||
|
| "content"
|
||||||
|
| "fontSize"
|
||||||
|
| "fontFamily"
|
||||||
|
| "color"
|
||||||
|
| "backgroundColor"
|
||||||
|
| "textAlign"
|
||||||
|
| "fontWeight"
|
||||||
|
| "fontStyle"
|
||||||
|
| "textDecoration"
|
||||||
|
| "x"
|
||||||
|
| "y"
|
||||||
|
| "rotation"
|
||||||
|
| "opacity"
|
||||||
|
>
|
||||||
|
>
|
||||||
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useTimelineStore = create<TimelineStore>((set, get) => {
|
export const useTimelineStore = create<TimelineStore>((set, get) => {
|
||||||
@ -494,6 +517,24 @@ export const useTimelineStore = create<TimelineStore>((set, get) => {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateTextElement: (trackId, elementId, updates) => {
|
||||||
|
get().pushHistory();
|
||||||
|
updateTracksAndSave(
|
||||||
|
get()._tracks.map((track) =>
|
||||||
|
track.id === trackId
|
||||||
|
? {
|
||||||
|
...track,
|
||||||
|
elements: track.elements.map((element) =>
|
||||||
|
element.id === elementId && element.type === "text"
|
||||||
|
? { ...element, ...updates }
|
||||||
|
: element
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: track
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
splitElement: (trackId, elementId, splitTime) => {
|
splitElement: (trackId, elementId, splitTime) => {
|
||||||
const { _tracks } = get();
|
const { _tracks } = get();
|
||||||
const track = _tracks.find((t) => t.id === trackId);
|
const track = _tracks.find((t) => t.id === trackId);
|
||||||
|
Reference in New Issue
Block a user