aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlonkaars <loek@pipeframe.xyz>2021-06-16 20:13:32 +0200
committerlonkaars <loek@pipeframe.xyz>2021-06-16 20:13:32 +0200
commit67d3c02a89cb170fc1819ae49ef7ba08f1d8305c (patch)
tree3c4ce9b92de2a832a312b34f46a1993fe23f2f71
parentbafd27bfec6e6e0cdda14ce577a26b1937697ca3 (diff)
draggable slides
-rw-r--r--pages/editor.tsx61
-rw-r--r--timeline.ts1
2 files changed, 57 insertions, 5 deletions
diff --git a/pages/editor.tsx b/pages/editor.tsx
index 1f79699..0da9a0d 100644
--- a/pages/editor.tsx
+++ b/pages/editor.tsx
@@ -26,6 +26,7 @@ import PlaySkipIconAni from '../components/play-skip';
import Selection from '../components/selection';
var keyframeInAnimations: { [key: string]: { x: number; y: number; }; } = {};
+var slideAPIs: { [key: string]: any; }[] = [];
var player = new TimedVideoPlayer();
var useWorkingTimeline = create((set, get) => ({
@@ -108,6 +109,8 @@ function TimelineKeyframe(props: {
config: { mass: 0.5, tension: 500, friction: 20 },
}));
+ slideAPIs[props.slide.id] = api;
+
useEffect(() => {
setFirstRender(false);
var beginAnimation = keyframeInAnimations[props.slide.id];
@@ -318,15 +321,14 @@ function TimelineEditor(props: {
window.addEventListener('resize', onresize);
}, []);
+ // timeline scrubber
var scrubberDragRef = useRef(null);
-
var [scrubberPos, scrubberSpring] = useSpring(
() => ({
frame: 0,
config: { mass: 0.5, tension: 500, friction: 20 },
}),
);
-
useDrag(({ xy: [x, _y] }) => {
var frame = Math.max(0, Math.round(getFrameAtOffset(x - 240, timelineZoom)) - 1);
setFrame(frame);
@@ -337,6 +339,7 @@ function TimelineEditor(props: {
}
}, { domTarget: scrubberDragRef, eventOptions: { passive: false } });
+ // slide placement ghost
var [ghost, ghostApi] = useSpring(() => ({
x: 0,
y: 0,
@@ -351,6 +354,7 @@ function TimelineEditor(props: {
});
}, []);
+ // selection
var [selectionActive, setSelectionActive] = useState(false);
var [selectionPlaced, setSelectionPlaced] = useState(false);
var [selectionHidden, setSelectionHidden] = useState(true);
@@ -368,7 +372,45 @@ function TimelineEditor(props: {
widthOffset: 0,
config: { mass: 0.5, tension: 500, friction: 20 },
}));
+ var selectionAreaRef = useRef(null);
var selectionRef = useRef(null);
+ var [selection, setSelection] = useState([]);
+ useDrag(({ movement: [x, _y], last }) => {
+ if (!selectionPlaced) return;
+ if (selection.length < 1) return;
+ var frameOffset = Math.round(x / zoomToPx(timelineZoom));
+ selection.forEach((slide: anySlide) => {
+ var api = slideAPIs[slide.id];
+ switch (slide.type as slideTypes | 'loopBegin') {
+ case 'loopBegin': {
+ if (!api) break;
+ var loop = workingTimeline.find((s: anySlide) => s.id == slide.id) as loopSlide;
+ var begin = loop.beginFrame + frameOffset;
+ api.start({ begin });
+
+ if (last) {
+ loop.beginFrame = begin;
+ refreshWorkingTimline();
+ }
+
+ break;
+ }
+ default: {
+ if (!api) break;
+ var frame = slide.frame + frameOffset;
+ api.start({ frame });
+
+ if (last) {
+ workingTimeline.find((s: anySlide) => s.id == slide.id).frame = frame;
+ refreshWorkingTimline();
+ }
+ }
+ }
+ if (last) return;
+ var selectionFrame = selection[0].frame;
+ selectionPosAPI.start({ startingFrame: selectionFrame + frameOffset });
+ });
+ }, { domTarget: selectionRef, eventOptions: { passive: false } });
useDrag(({ xy: [x, y], initial: [bx, by], first, last, movement: [ox, oy] }) => {
if (props.selectedTool != 'cursor') return;
var minDistance = 5; // minimal drag distance in pixels to register selection
@@ -383,6 +425,8 @@ function TimelineEditor(props: {
startOffset: 0,
widthOffset: 0,
});
+ selection = [];
+ setSelection(selection);
var timelineInner = document.querySelector('.timeline .timelineInner');
var timelineRects = timelineInner.getBoundingClientRect();
@@ -416,9 +460,11 @@ function TimelineEditor(props: {
if (slide.type != 'loop') continue;
var beginFrame = (slide as loopSlide).beginFrame;
expandedTimeline.splice(i, 0, new loopBeginSlide(beginFrame));
+ expandedTimeline[i].id = expandedTimeline[i + 1].id;
i++;
}
- var keyframesInSelection = expandedTimeline.filter((slide: anySlide) =>
+
+ var keyframesInSelection = expandedTimeline.filter(slide =>
slide.frame >= Math.floor(startingFrame) && slide.frame <= Math.ceil(endingFrame)
);
@@ -427,6 +473,9 @@ function TimelineEditor(props: {
return;
}
+ selection = keyframesInSelection;
+ setSelection(selection);
+
var left = keyframesInSelection[0];
var right = keyframesInSelection[keyframesInSelection.length - 1];
@@ -446,7 +495,7 @@ function TimelineEditor(props: {
setSelectionPlaced(true);
}
}
- }, { domTarget: selectionRef, eventOptions: { passive: false } });
+ }, { domTarget: selectionAreaRef, eventOptions: { passive: false } });
return <>
<canvas
@@ -492,15 +541,17 @@ function TimelineEditor(props: {
className='keyframes'
style={{ '--total-frames': props.player?.timeline?.framecount.toString() } as CSSProperties}
>
- <div className='selectionarea posabs v0' ref={selectionRef} />
+ <div className='selectionarea posabs v0' ref={selectionAreaRef} />
{workingTimeline.map((slide: anySlide) => <TimelineKeyframe slide={slide} />)}
<div
id='selection'
className={'posabs dispinbl ' + (selectionPlaced ? 'placed ' : '')}
+ ref={selectionRef}
style={{
left: `calc(var(--zoom) * ${selectionPos.startingFrame.toJSON()
+ selectionPos.center.toJSON()} * 1px - 6px + ${selectionPos.startOffset.toJSON()} * 1px)`,
top: selectionPos.y1.toJSON() - 6,
+ pointerEvents: selectionPlaced ? 'all' : 'none',
}}
children={<Selection
className={'' + (selectionActive ? 'active ' : '') + (selectionHidden ? 'hidden ' : '')}
diff --git a/timeline.ts b/timeline.ts
index ade936e..1611eb8 100644
--- a/timeline.ts
+++ b/timeline.ts
@@ -1,6 +1,7 @@
import { v4 as uuid } from 'uuid';
export type slideTypes = 'default' | 'delay' | 'speedChange' | 'loop';
+export var slideTypes = ['default', 'delay', 'speedChange', 'loop'];
export type anySlide = slide | delaySlide | speedChangeSlide | loopSlide;
export class slide {