diff options
author | lonkaars <loek@pipeframe.xyz> | 2021-05-23 12:32:44 +0200 |
---|---|---|
committer | lonkaars <loek@pipeframe.xyz> | 2021-05-23 12:32:44 +0200 |
commit | 9a0cb1148039c30b7a001bfca548e4223137ecbb (patch) | |
tree | 5423f94be9b3609329a0782acfaf2e76196ebbb7 | |
parent | b5f6d0eb73d84e65067e1c2b79ade78511916a6e (diff) |
keyframes draggable :tada:g
-rw-r--r-- | pages/editor.tsx | 66 | ||||
-rw-r--r-- | styles/editor.css | 10 | ||||
-rw-r--r-- | timeline.schema.json | 18 | ||||
-rw-r--r-- | timeline.ts | 1 |
4 files changed, 58 insertions, 37 deletions
diff --git a/pages/editor.tsx b/pages/editor.tsx index e1e164a..e3607e6 100644 --- a/pages/editor.tsx +++ b/pages/editor.tsx @@ -2,7 +2,7 @@ import { CSSProperties, ReactNode, useEffect, useRef, useState } from 'react'; import { animated, useSpring, useSprings } from 'react-spring'; import { useDrag } from 'react-use-gesture'; import create from 'zustand'; -import { loopSlide } from '../timeline'; +import { delaySlide, loopSlide, slide, speedChangeSlide } from '../timeline'; import { TimedVideoPlayer } from './present'; import AppBar from '@material-ui/core/AppBar'; @@ -49,41 +49,43 @@ var useFrame = create(set => ({ setFrame: (newFrame: number) => set(() => ({ currentFrame: newFrame })), })); -function TimelineEditor(props: { - player: TimedVideoPlayer; +function TimelineKeyframe(props: { + slide: slide | delaySlide | loopSlide | speedChangeSlide; }) { - var timelineZoom = getTimelineZoom((st: any) => st.zoom); - - var keyframes: Array<{ - html: ReactNode; - id: string; - }> = []; - var [keyframeSprings, _keyframeSpringsAPI] = useSprings( - props.player?.timeline?.slides.length || 0, - i => ({ - frame: props.player.timeline.slides[i].frame, + var ref = useRef(null); + var [spring, api] = useSpring( + () => ({ + frame: props.slide.frame, config: { mass: 0.5, tension: 500, friction: 20 }, }), ); - props.player?.timeline?.slides.forEach((slide, index) => { - var id = 'frame' + index; - var html = <animated.div - className='frame posabs' - style={{ '--frame': keyframeSprings[index].frame } as CSSProperties} - id={id} - > - <div className={'keyframeWrapper posabs abscenterh keyframe-index-' + index}> - {slide.type == 'loop' - ? <Loop length={slide.frame - (slide as loopSlide).beginFrame} /> - : <SlideKeyframe type={slide.type} />} - </div> - </animated.div>; - keyframes[index] = { - html, - id, - }; - }); + var timelineZoom = getTimelineZoom((st: any) => st.zoom); + + useDrag(({ xy: [x, _y] }) => { + var frame = Math.max(0, Math.round(getFrameAtOffset(x - 240, timelineZoom)) - 1); + console.log(frame); + api.start({ frame }); + }, { domTarget: ref, eventOptions: { passive: false } }); + + return <animated.div + className='frame posabs' + style={{ '--frame': spring.frame } as CSSProperties} + id={'slide-' + props.slide.id} + ref={ref} + > + <div className='keyframeWrapper posabs abscenterh'> + {props.slide.type == 'loop' + ? <Loop length={props.slide.frame - (props.slide as loopSlide).beginFrame} /> + : <SlideKeyframe type={props.slide.type} />} + </div> + </animated.div>; +} + +function TimelineEditor(props: { + player: TimedVideoPlayer; +}) { + var timelineZoom = getTimelineZoom((st: any) => st.zoom); var timelineLabels = useTimelineLabels((st: any) => st.labels); var setTimelineLabels = useTimelineLabels((st: any) => st.setLabels); @@ -233,7 +235,7 @@ function TimelineEditor(props: { <div className='keyframes' style={{ '--total-frames': props.player?.timeline?.framecount.toString() } as CSSProperties} - children={keyframes.map(kf => kf.html)} + children={props.player?.timeline?.slides.map(slide => <TimelineKeyframe slide={slide} />)} /> </div> </>; diff --git a/styles/editor.css b/styles/editor.css index 5325004..3c30d68 100644 --- a/styles/editor.css +++ b/styles/editor.css @@ -218,7 +218,8 @@ overflow: visible; filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3)); z-index: 1; - left: calc((var(--zoom) * (var(--frame) + 0.5)) * 1px - 1px) + left: calc((var(--zoom) * (var(--frame) + 0.5)) * 1px - 1px); + pointer-events: none; } .timeline .frameOverlay { @@ -228,9 +229,10 @@ opacity: .0; } - -.timeline .scrubberJumpArea { cursor: grab; } -.timeline .scrubberJumpArea:active { cursor: grabbing; } +.timeline .scrubberJumpArea, +.timeline .keyframes .frame .keyframeWrapper { cursor: grab; } +.timeline .scrubberJumpArea:active, +.timeline .keyframes .frame .keyframeWrapper:active { cursor: grabbing; } .timeline .scrubber .head { fill: var(--blue); } .timeline .scrubber .needle { background-color: var(--blue); } diff --git a/timeline.schema.json b/timeline.schema.json index c92e722..2d8c6ff 100644 --- a/timeline.schema.json +++ b/timeline.schema.json @@ -18,6 +18,9 @@ "frame": { "type": "number" }, + "id": { + "type": "string" + }, "type": { "$ref": "#/definitions/keyframeTypes" } @@ -26,6 +29,7 @@ "clickThroughBehaviour", "delay", "frame", + "id", "type" ], "type": "object" @@ -55,6 +59,9 @@ "frame": { "type": "number" }, + "id": { + "type": "string" + }, "playbackType": { "enum": [ "PingPong", @@ -70,6 +77,7 @@ "beginFrame", "clickThroughBehaviour", "frame", + "id", "playbackType", "type" ], @@ -101,6 +109,9 @@ "frame": { "type": "number" }, + "id": { + "type": "string" + }, "type": { "$ref": "#/definitions/keyframeTypes" } @@ -108,7 +119,8 @@ "required": [ "frame", "clickThroughBehaviour", - "type" + "type", + "id" ], "type": "object" }, @@ -125,6 +137,9 @@ "frame": { "type": "number" }, + "id": { + "type": "string" + }, "newFramerate": { "type": "number" }, @@ -135,6 +150,7 @@ "required": [ "clickThroughBehaviour", "frame", + "id", "newFramerate", "type" ], diff --git a/timeline.ts b/timeline.ts index 3a51ed3..764bc7a 100644 --- a/timeline.ts +++ b/timeline.ts @@ -4,6 +4,7 @@ export interface slide { frame: number; clickThroughBehaviour: 'ImmediatelySkip' | 'PlayOut'; type: keyframeTypes; + id: string; } export interface delaySlide extends slide { |