diff options
-rw-r--r-- | components/icons.tsx | 2 | ||||
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | pages/editor.tsx | 50 | ||||
-rw-r--r-- | styles/editor.css | 4 | ||||
-rw-r--r-- | styles/keyframes.css | 10 | ||||
-rw-r--r-- | yarn.lock | 5 |
6 files changed, 56 insertions, 16 deletions
diff --git a/components/icons.tsx b/components/icons.tsx index ea73b26..cf60f54 100644 --- a/components/icons.tsx +++ b/components/icons.tsx @@ -121,7 +121,7 @@ export function SlideKeyframe(props: { loopEnd?: boolean; }) { return <div className={'keyframe dispinbl posrel' + (props.ghost ? ' ghost' : '')}> - {props.ghost && <SlideKeyframeOutline type={props.type} loopEnd={props.loopEnd} />} + <SlideKeyframeOutline type={props.type} loopEnd={props.loopEnd} /> <SlideKeyframeBackground type={props.type} loopEnd={props.loopEnd} /> </div>; } diff --git a/package.json b/package.json index a8438b3..9ece09e 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@material-ui/lab": "^4.0.0-alpha.58", "@mdi/js": "^5.9.55", "@mdi/react": "^1.5.0", + "@types/uuid": "^8.3.0", "ajv": "^8.3.0", "mousetrap": "^1.6.5", "next": "^10.2.0", diff --git a/pages/editor.tsx b/pages/editor.tsx index 2058485..c2406a5 100644 --- a/pages/editor.tsx +++ b/pages/editor.tsx @@ -1,6 +1,7 @@ import { CSSProperties, ReactNode, useEffect, useRef, useState } from 'react'; import { animated, useSpring } from 'react-spring'; import { useDrag } from 'react-use-gesture'; +import { v4 as uuid } from 'uuid'; import create from 'zustand'; import { anySlide, loopSlide, slide, slideTypes } from '../timeline'; import { TimedVideoPlayer } from './present'; @@ -25,9 +26,19 @@ import { PressureIcon, SlideKeyframe } from '../components/icons'; import PlaySkipIconAni from '../components/play-skip'; var player = new TimedVideoPlayer(); -var useWorkingTimeline = create(set => ({ +var useWorkingTimeline = create((set, get) => ({ timeline: [], setTimeline: (newTimeline: anySlide[]) => set(() => ({ timeline: newTimeline })), + refreshLiveTimeline: () => { + player.timeline.slides = Array(...((get() as any).timeline)); + player.timeline.slides.sort((a: anySlide, b: anySlide) => a.frame - b.frame); + player.timeline.slides[-1] = { // TODO: dry + id: '00000000-0000-0000-0000-000000000000', + frame: 0, + type: 'default', + clickThroughBehaviour: 'ImmediatelySkip', + }; + }, })); var getTimelineZoom = create(set => ({ @@ -59,6 +70,7 @@ function TimelineKeyframe(props: { }) { var workingTimeline = useWorkingTimeline((st: any) => st.timeline); var setWorkingTimeline = useWorkingTimeline((st: any) => st.setTimeline); + var updateTimeline = useWorkingTimeline((st: any) => st.refreshLiveTimeline); function modifySlide(newProps: Partial<anySlide>) { var slide = workingTimeline.find((s: anySlide) => s.id == props.slide.id); @@ -130,14 +142,7 @@ function TimelineKeyframe(props: { var mouseUpListener = useRef(null); useDrag(({ last }) => { if (!last) return; - player.timeline.slides = Array(...workingTimeline); - player.timeline.slides.sort((a: anySlide, b: anySlide) => a.frame - b.frame); - player.timeline.slides[-1] = { // TODO: dry - id: '00000000-0000-0000-0000-000000000000', - frame: 0, - type: 'default', - clickThroughBehaviour: 'ImmediatelySkip', - }; + updateTimeline(); }, { domTarget: mouseUpListener, eventOptions: { passive: false } }); return <animated.div @@ -177,8 +182,8 @@ function TimelineEditor(props: { var setTimelineLabels = useTimelineLabels((st: any) => st.setLabels); var workingTimeline = useWorkingTimeline((st: any) => st.timeline); + var setWorkingTimeline = useWorkingTimeline((st: any) => st.setTimeline); - // var frame = useFrame((st: any) => st.currentFrame); var setFrame = useFrame((st: any) => st.setFrame); useEffect(() => { @@ -305,18 +310,33 @@ function TimelineEditor(props: { useEffect(() => { document.querySelector('.timeline').addEventListener('mousemove', (e: MouseEvent) => { var rect = document.querySelector('.timeline').getBoundingClientRect(); - var offset = 16; - var x = e.clientX - rect.left - offset; - var y = e.clientY - rect.top - offset; + var x = e.clientX - rect.left; + var y = e.clientY - rect.top; ghostApi.start({ x, y }); }); }, []); return <> - <canvas className='timeScale posabs a0' id='timeScaleCanvas' /> + <canvas + className='timeScale posabs a0' + id='timeScaleCanvas' + onClick={event => { + // place new keyframe + var x = event.clientX - 240; + var frame = Math.round(getFrameAtOffset(x, timelineZoom)); + var id = uuid(); + workingTimeline.push({ + frame, + id, + type: props.selectedTool as slideTypes, + clickThroughBehaviour: 'ImmediatelySkip', + }); + setWorkingTimeline(workingTimeline); + }} + /> <div className='labels' children={timelineLabels} /> <div className='scrubberJumpArea posabs h0 t0' ref={scrubberDragRef} /> - <div className='timelineInner posabs a0'> + <div className={'timelineInner posabs a0' + (props.selectedTool != 'cursor' ? ' blur' : '')}> <animated.div className='scrubber posabs v0' style={{ '--frame': scrubberPos.frame } as CSSProperties} diff --git a/styles/editor.css b/styles/editor.css index 7eda309..a333a56 100644 --- a/styles/editor.css +++ b/styles/editor.css @@ -217,6 +217,10 @@ white-space: nowrap; } +.appGrid .timeline .timelineInner.blur { + pointer-events: none; +} + .timeline .scrubber { width: 2px; overflow: visible; diff --git a/styles/keyframes.css b/styles/keyframes.css index 8a4f06f..4d9f472 100644 --- a/styles/keyframes.css +++ b/styles/keyframes.css @@ -44,5 +44,15 @@ fill: currentColor; } +.keyframe .background, +.keyframe .outline { + opacity: 1; + transition-property: opacity; + transition-duration: 200ms; +} + .keyframe.ghost .background { opacity: .2; } .keyframe.ghost .outline { opacity: .7; } + +#ghost { transform: translate(-16px, -16px); } + @@ -323,6 +323,11 @@ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275" integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA== +"@types/uuid@^8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f" + integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ== + ajv@^8.3.0: version "8.5.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.5.0.tgz#695528274bcb5afc865446aa275484049a18ae4b" |