11"use client" ;
22import React , { useEffect , useRef , useState } from 'react'
33import { Button } from './button'
4+ import { ButtonGroup } from "@/components/ui/button-group"
45import { useImageExportStore , usePlotStore } from '@/utils/GlobalStates'
56import { useShallow } from 'zustand/shallow'
67import { Slider } from './slider'
78import './css/KeyFrames.css'
8- import { TbDiamondsFilled } from "react-icons/tb" ;
99import { Input } from './input' ;
1010import { IoCloseCircleSharp } from "react-icons/io5" ;
11- import { CiWarning } from "react-icons/ci" ;
11+ import { FaPlusCircle } from "react-icons/fa" ;
12+ import { MdDeleteForever } from "react-icons/md" ;
13+ import { MdPreview } from "react-icons/md" ;
14+ import { TbKeyframeFilled } from "react-icons/tb" ;
15+ import { TbKeyframesFilled } from "react-icons/tb" ;
16+ import { Card , CardContent } from "@/components/ui/card" ;
17+ import { toast } from "sonner"
18+ import {
19+ Tooltip ,
20+ TooltipContent ,
21+ TooltipTrigger ,
22+ } from "@/components/ui/tooltip" ;
1223
1324function pick < T extends object , K extends keyof T > ( obj : T , keys : K [ ] ) : Pick < T , K > {
1425 return keys . reduce ( ( acc , key ) => {
@@ -63,6 +74,7 @@ const KeyFrames = () => {
6374 const timeRatio = timeRate / frameRate
6475 const keyFrameList = keyFrames ? Array . from ( keyFrames . keys ( ) ) . sort ( ( a , b ) => a - b ) : null ;
6576 const originalAnimProg = useRef < number | null > ( null )
77+ const [ MdLg , setMdLg ] = useState < "md" | "lg" > ( "md" ) ;
6678
6779 useEffect ( ( ) => { // Clear KeyFrames if it is empty.
6880 if ( keyFrameList && keyFrameList . length == 0 ) {
@@ -79,79 +91,151 @@ const KeyFrames = () => {
7991 }
8092 } , [ ] )
8193
94+ useEffect ( ( ) => {
95+ const handleResize = ( ) => {
96+ setMdLg ( window . innerWidth < 768 ? "md" : "lg" ) ;
97+ } ;
98+ handleResize ( ) ;
99+ window . addEventListener ( "resize" , handleResize ) ;
100+ return ( ) => window . removeEventListener ( "resize" , handleResize ) ;
101+ } , [ ] ) ;
102+
103+ { /* Information */ }
104+ useEffect ( ( ) => {
105+ if ( orbit || useTime ) {
106+ toast . warning ( "Warning!" , {
107+ description : "Camera Motion overwritten by orbit!" ,
108+ action : {
109+ label : "close" ,
110+ onClick : ( ) => console . log ( "close" ) ,
111+ } ,
112+ } ) ;
113+ }
114+ } , [ orbit , useTime ] ) ;
115+
116+ useEffect ( ( ) => {
117+ if ( ( orbit || useTime ) && ! keyFrames ) {
118+ toast . warning ( "Warning!" , {
119+ description : "Keyframe required to preview orbit!" ,
120+ action : {
121+ label : "close" ,
122+ onClick : ( ) => console . log ( "close" ) ,
123+ } ,
124+ } ) ;
125+ }
126+ } , [ orbit , useTime , keyFrames ] ) ;
127+
82128 return (
83- < div className = 'keyframes-container' >
84- < IoCloseCircleSharp
129+ < Card className = 'keyframes-container' >
130+ < Tooltip delayDuration = { 500 } >
131+ < TooltipTrigger asChild >
132+ < IoCloseCircleSharp
85133 style = { {
86134 position :'absolute' ,
87135 top :'10px' ,
88136 left :'10px' ,
89137 cursor :'pointer'
90138 } }
91139 size = { 20 }
92- color = 'var(--play-background)'
93140 onClick = { ( ) => useImageExportStore . getState ( ) . setKeyFrameEditor ( false ) }
94141 />
95- < div className = 'flex justify-between items-center' >
96- { /* Information */ }
97- < div className = { `ml-4 bg-[var(--warning-area)] min-w-[20%] px-4 py-2 rounded-md` }
98- style = { {
99- visibility :( orbit || useTime ) ? "visible" : "hidden"
100- } }
101- >
102- < div style = { { display : orbit ? "flex" : "none" , alignItems :"center" , width :"100%" , justifyContent :"space-between" } } >
103- < CiWarning /> < b > Camera Motion overwriten by orbit</ b > < CiWarning />
104- </ div >
105- < div style = { { display : ( orbit && ! keyFrames ) ? "flex" : "none" , alignItems :"center" , width :"100%" , justifyContent :"space-between" } } >
106- < CiWarning /> < b > Keyframe required to preview orbit</ b > < CiWarning />
107- </ div >
108-
109- </ div >
110-
142+ </ TooltipTrigger >
143+ < TooltipContent side = "top" align = "start" >
144+ Close Keyframe Editor
145+ </ TooltipContent >
146+ </ Tooltip >
147+ < CardContent className = 'flex flex-col gap-1 w-full h-full px-1 py-1' >
148+ < div className = 'flex flex-wrap justify-center gap-1 ml-6 mr-0 md:ml-8 md:mr-4' >
111149 { /* Buttons */ }
112- < div className = 'flex justify-center items-center' >
113- < Button
114- className = 'cursor-pointer'
115- onClick = { ( ) => { SetKeyFrame ( currentFrame ) } }
116- > Add Keyframe
117- </ Button >
118- < Button
119- disabled = { ! keyFrameList }
120- className = 'cursor-pointer'
121- onClick = { ( ) => { useImageExportStore . setState ( { keyFrames : undefined } ) } }
122- > Clear Keyframes
123- </ Button >
150+ < ButtonGroup >
151+ < Tooltip delayDuration = { 500 } >
152+ < TooltipTrigger asChild >
153+ < Button
154+ className = 'cursor-pointer'
155+ size = "sm"
156+ variant = "outline"
157+ onClick = { ( ) => { SetKeyFrame ( currentFrame ) } }
158+ >
159+ < FaPlusCircle /> { MdLg === "lg" ? 'Keyframe' : < TbKeyframeFilled /> }
160+ </ Button >
161+ </ TooltipTrigger >
162+ < TooltipContent side = "top" align = "start" >
163+ Add new Keyframe
164+ </ TooltipContent >
165+ </ Tooltip >
166+ < Tooltip delayDuration = { 500 } >
167+ < TooltipTrigger asChild >
168+ < Button
169+ disabled = { ! keyFrameList }
170+ className = 'cursor-pointer'
171+ size = "sm"
172+ variant = "outline"
173+ onClick = { ( ) => { useImageExportStore . setState ( { keyFrames : undefined } ) } }
174+ >
175+ < MdDeleteForever className = 'size-6' /> { MdLg === "lg" ? 'Keyframes' : < TbKeyframesFilled /> }
176+ </ Button >
177+ </ TooltipTrigger >
178+ < TooltipContent side = "top" align = "start" >
179+ Clear all Keyframes
180+ </ TooltipContent >
181+ </ Tooltip >
182+
183+ < Tooltip delayDuration = { 500 } >
184+ < TooltipTrigger asChild >
124185 < Button
125186 disabled = { ! keyFrameList }
126187 className = 'cursor-pointer'
188+ size = "sm"
189+ variant = "outline"
127190 onClick = { ( ) => { useImageExportStore . getState ( ) . PreviewKeyFrames ( ) } }
128- > Preview Full Animation
191+ >
192+ < MdPreview className = 'size-6' /> { MdLg === "lg" ? 'Preview' : '' }
129193 </ Button >
130- </ div >
131-
194+ </ TooltipTrigger >
195+ < TooltipContent side = "top" align = "start" >
196+ Preview full animation
197+ </ TooltipContent >
198+ </ Tooltip >
199+ </ ButtonGroup >
132200 { /* Frame Information */ }
133- < div className = 'flex justify-center' >
134- < div className = 'flex justify-end items-center mr-2' >
135- < label htmlFor = "frames" > < b > Frames:</ b > </ label >
136- < Input className = 'w-[80px] ml-2' id = "frames" type = 'number' step = { 1 } value = { frames } onChange = { e => setFrames ( Math . max ( parseInt ( e . target . value ) , 2 ) ) } />
137- </ div >
138- < div className = 'flex justify-end items-center' >
139- < b > Frame:</ b >
140- < Input value = { currentFrame } type = 'number'
141- className = 'w-[80px] ml-2'
142- min = { 1 }
143- step = { 1 }
144- onChange = { e => parseInt ( e . target . value ) ? setCurrentFrame ( Math . max ( parseInt ( e . target . value ) , 1 ) ) : 1 }
145- />
146- </ div >
147- </ div >
201+ < ButtonGroup >
202+ < Tooltip delayDuration = { 500 } >
203+ < TooltipTrigger asChild >
204+ < Button size = "sm" variant = "outline" >
205+ < TbKeyframesFilled /> { MdLg === "lg" ? 'Frames' : '' }
206+ </ Button >
207+ </ TooltipTrigger >
208+ < TooltipContent side = "top" align = "start" >
209+ Frames
210+ </ TooltipContent >
211+ </ Tooltip >
212+ < Input className = 'w-[80px] h-[32px]' id = "frames" type = 'number' step = { 1 } value = { frames } onChange = { e => setFrames ( Math . max ( parseInt ( e . target . value ) , 2 ) ) } />
213+ </ ButtonGroup >
214+ < ButtonGroup >
215+ < Tooltip delayDuration = { 500 } >
216+ < TooltipTrigger asChild >
217+ < Button size = "sm" variant = "outline" >
218+ < TbKeyframeFilled /> { MdLg === "lg" ? 'Frame' : '' }
219+ </ Button >
220+ </ TooltipTrigger >
221+ < TooltipContent side = "top" align = "start" >
222+ Frame
223+ </ TooltipContent >
224+ </ Tooltip >
225+ < Input value = { currentFrame } type = 'number'
226+ className = 'w-[80px] h-[32px]'
227+ min = { 1 }
228+ step = { 1 }
229+ onChange = { e => parseInt ( e . target . value ) ? setCurrentFrame ( Math . max ( parseInt ( e . target . value ) , 1 ) ) : 1 }
230+ />
231+ </ ButtonGroup >
148232 </ div >
149- < div className = "relative w-full my-2 px-2 bg-[var(--background)] drop-shadow-[0_0_4px_var(--notice-shadow)] rounded-lg" >
233+ < div className = "relative w-full my-2 px-2 drop-shadow-[0_0_4px_var(--notice-shadow)] rounded-lg" >
150234 { keyFrameList ?. map ( ( frame ) => {
151235 const thumbRadius = 8 + 8 ; //Thumbradius plus padding
152236 const percent = ( ( frame - 1 ) / ( frames - 1 ) ) * 100 ;
153237 return (
154- < TbDiamondsFilled
238+ < TbKeyframeFilled
155239 key = { frame }
156240 style = { {
157241 position : "absolute" ,
@@ -162,7 +246,7 @@ const KeyFrames = () => {
162246 cursor :"pointer" ,
163247 visibility :percent <= 100 ? "visible" : "hidden"
164248 } }
165- color = 'red '
249+ color = 'orangered '
166250 size = { 18 }
167251 onClick = { ( ) => setCurrentFrame ( frame ) }
168252 onDoubleClick = { ( ) => {
@@ -187,7 +271,8 @@ const KeyFrames = () => {
187271 />
188272 </ div >
189273
190- </ div >
274+ </ CardContent >
275+ </ Card >
191276 )
192277}
193278
0 commit comments