diff options
author | lonkaars <loek@pipeframe.xyz> | 2021-06-26 22:41:52 +0200 |
---|---|---|
committer | lonkaars <loek@pipeframe.xyz> | 2021-06-26 22:41:52 +0200 |
commit | 7773863c11c58e6c9ac45a0eff245cb204a69924 (patch) | |
tree | 46289ed2c3b23e0cae5574b48fb10dedbe6b851f | |
parent | 93926d8313a33e892a39191430268af250715cbe (diff) |
global select() function + auto-select keyframe on click
-rw-r--r-- | pages/editor.tsx | 180 |
1 files changed, 101 insertions, 79 deletions
diff --git a/pages/editor.tsx b/pages/editor.tsx index bbda787..872e9f7 100644 --- a/pages/editor.tsx +++ b/pages/editor.tsx @@ -1,6 +1,6 @@ import { createState, Downgraded, none, State, useHookstate } from '@hookstate/core'; import { CSSProperties, ReactNode, Ref, useEffect, useRef, useState } from 'react'; -import { animated, useSpring } from 'react-spring'; +import { animated, SpringRef, SpringValues, useSpring } from 'react-spring'; import { useDrag } from 'react-use-gesture'; import { useMousetrap } from 'use-mousetrap'; import { v4 as uuid } from 'uuid'; @@ -153,6 +153,22 @@ var global = createState<globalState>({ }, }); +interface project { + timeline: timeline; +} + +var project = createState<project>({ + timeline: { + name: '', + slides: [], + settings: { + controlType: 'FullScreen', + }, + framerate: 0, + framecount: 0, + }, +}); + var settings = { 'default': { node: <DefaultSettings />, @@ -169,21 +185,60 @@ function setSetting(name: keyof typeof settings) { global.settings.set(setting); } -interface project { - timeline: timeline; +interface selectionPos { + x1: number; + y1: number; + x2: number; + y2: number; + center: number; + startingFrame: number; + frameWidth: number; + startOffset: number; + widthOffset: number; +} +var selectionPos: SpringValues<selectionPos>, + selectionPosAPI: SpringRef<selectionPos>; +function select(slides: anySlide[]) { + if (slides.length == 0) { + global.selection.set({ + slides: [], + active: false, + placed: false, + hidden: true, + type: { + left: null, + right: null, + }, + }); + setSetting('default'); + } else { + var left = slides[0]; + var right = slides[slides.length - 1]; + + var [startOffset, widthOffset] = calculateSelectionOffsets(left.type, right.type); + + selectionPosAPI[global.selection.hidden.value ? 'set' : 'start']({ + y1: 50, + y2: 62, + startingFrame: left.frame, + frameWidth: right.frame - left.frame, + center: 0.5, + startOffset, + widthOffset, + }); + global.selection.set({ + slides, + active: false, + placed: true, + hidden: false, + type: { + left: left.type, + right: right.type, + }, + }); + setSetting('slide'); + } } - -var project = createState<project>({ - timeline: { - name: '', - slides: [], - settings: { - controlType: 'FullScreen', - }, - framerate: 0, - framecount: 0, - }, -}); var zoomToPx = (zoom: number) => (12 - 0.5) * zoom ** (1 / 0.4) + 0.5; @@ -248,31 +303,39 @@ function TimelineKeyframe(props: { // drag keyframe var [startOffset, setStartOffset] = useState(0); var [endOffset, setEndOffset] = useState(0); - useDrag(({ xy: [x, _y], first }) => { + useDrag(({ xy: [x, _y], first, intentional }) => { var frame = Math.max(0, Math.round(getFrameAtOffset(x - 240, timelineZoom.value)) - 1); if (props.slide.type == 'loop') { - if (first) { - var startFrame = spring.begin.toJSON(); - var endFrame = spring.frame.toJSON(); - var grabFrameOffset = frame; + if (intentional) { + if (first) { + var startFrame = spring.begin.toJSON(); + var endFrame = spring.frame.toJSON(); + var grabFrameOffset = frame; - setStartOffset(startFrame - grabFrameOffset); - startOffset = startFrame - grabFrameOffset; + setStartOffset(startFrame - grabFrameOffset); + startOffset = startFrame - grabFrameOffset; - setEndOffset(endFrame - grabFrameOffset); - endOffset = endFrame - grabFrameOffset; - } - api.start({ begin: frame + startOffset, frame: frame + endOffset }); + setEndOffset(endFrame - grabFrameOffset); + endOffset = endFrame - grabFrameOffset; + } + api.start({ begin: frame + startOffset, frame: frame + endOffset }); - modifySlide({ frame: frame + endOffset }); - modifySlide({ beginFrame: frame + startOffset }); + modifySlide({ frame: frame + endOffset }); + modifySlide({ beginFrame: frame + startOffset }); + } } else { - api.start({ frame }); + if (intentional) { + api.start({ frame }); + modifySlide({ frame }); + } - modifySlide({ frame }); + /** + * Edit <TimelineSelection/> to use global.selection for slide crimping + */ + select([props.slide]); } - }, { domTarget: dragRef, eventOptions: { passive: false } }); + }, { domTarget: dragRef, eventOptions: { passive: false }, threshold: 10, triggerAllEvents: true }); if (props.slide.type == 'loop') { // loop start @@ -335,13 +398,13 @@ function TimelineLabels() { return <div className='labels' children={labels.attach(Downgraded).get()} />; } -function TimelineSelection(props: { selectionAreaRef: Ref<ReactNode>; }) { +function TimelineSelection(props: { selectionDragArea: Ref<ReactNode>; }) { var workingTimeline = useHookstate(global).timeline.workingTimeline; var tool = useHookstate(global).timeline.tool; var selection = useHookstate(global).selection; - var [selectionPos, selectionPosAPI] = useSpring(() => ({ + [selectionPos, selectionPosAPI] = useSpring(() => ({ x1: 0, y1: 0, x2: 0, @@ -459,51 +522,10 @@ function TimelineSelection(props: { selectionAreaRef: Ref<ReactNode>; }) { slide.frame >= Math.floor(startingFrame) && slide.frame <= Math.ceil(endingFrame) ); - if (keyframesInSelection.length < 1) { - setSetting('default'); - global.selection.hidden.set(true); - return; - } - - global.selection.slides.set(keyframesInSelection); - - var left = keyframesInSelection[0]; - var right = keyframesInSelection[keyframesInSelection.length - 1]; - - var [startOffset, widthOffset] = calculateSelectionOffsets(left.type, right.type); - - selectionPosAPI.start({ - y1: 50, - y2: 62, - startingFrame: left.frame, - frameWidth: right.frame - left.frame, - center: 0.5, - startOffset, - widthOffset, - }); - - setTimeout(() => { - selectionPosAPI.start({ - y1: 50, - y2: 62, - startingFrame: left.frame, - frameWidth: right.frame - left.frame, - center: 0.5, - startOffset, - widthOffset, - }); - }, 100); - setSetting('slide'); - global.selection.merge({ - type: { - left: left.type, - right: right.type, - }, - placed: true, - }); + select(keyframesInSelection); } } - }, { domTarget: props.selectionAreaRef, eventOptions: { passive: false } }); + }, { domTarget: props.selectionDragArea, eventOptions: { passive: false } }); useMousetrap(['del', 'backspace'], () => { if (!global.selection.placed) return; @@ -578,7 +600,7 @@ function TimelineEditor() { var proj = useHookstate(project).timeline; var timelineRef = useRef(null); - var selectionAreaRef = useRef(null); + var selectionDragArea = useRef(null); useEffect(() => { timelineRef.current.addEventListener('wheel', (e: WheelEvent) => { if (!e.ctrlKey && !e.altKey) return; @@ -772,9 +794,9 @@ function TimelineEditor() { className='keyframes' style={{ '--total-frames': proj.framecount.get() } as CSSProperties} > - <div className='selectionarea posabs v0' ref={selectionAreaRef} /> + <div className='selectionarea posabs v0' ref={selectionDragArea} /> {workingTimeline.value.map(slide => <TimelineKeyframe slide={slide} key={slide.id} />)} - <TimelineSelection selectionAreaRef={selectionAreaRef} /> + <TimelineSelection selectionDragArea={selectionDragArea} /> </div> </div> <div className={'ghostArea posabs a0' + (tool.value != 'cursor' ? ' active' : '')}> |