aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlonkaars <loek@pipeframe.xyz>2021-05-23 12:32:44 +0200
committerlonkaars <loek@pipeframe.xyz>2021-05-23 12:32:44 +0200
commit9a0cb1148039c30b7a001bfca548e4223137ecbb (patch)
tree5423f94be9b3609329a0782acfaf2e76196ebbb7
parentb5f6d0eb73d84e65067e1c2b79ade78511916a6e (diff)
keyframes draggable :tada:g
-rw-r--r--pages/editor.tsx66
-rw-r--r--styles/editor.css10
-rw-r--r--timeline.schema.json18
-rw-r--r--timeline.ts1
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 {