aboutsummaryrefslogtreecommitdiff
path: root/pages/editor.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'pages/editor.tsx')
-rw-r--r--pages/editor.tsx254
1 files changed, 128 insertions, 126 deletions
diff --git a/pages/editor.tsx b/pages/editor.tsx
index 0695d5c..f0ce7dd 100644
--- a/pages/editor.tsx
+++ b/pages/editor.tsx
@@ -1,4 +1,4 @@
-import { createState, Downgraded, useState as useHookState } from '@hookstate/core';
+import { createState, Downgraded, State, useState as useHookState } from '@hookstate/core';
import mousetrap from 'mousetrap';
import { CSSProperties, ReactNode, useEffect, useRef, useState } from 'react';
import { animated, useSpring } from 'react-spring';
@@ -236,19 +236,40 @@ function TimelineKeyframe(props: {
</animated.div>;
}
+function TimelineLabels() {
+ var labels = useHookState(project.timeline.labels);
+ return <div className='labels' children={labels.attach(Downgraded).get()} />;
+}
+
function TimelineEditor() {
var timelineZoom = useHookState(project.timeline.zoom);
- var timelineLabels = useHookState(project.timeline.labels);
var workingTimeline = useHookState(project.timeline.workingTimeline);
+ var tool = useHookState(project.timeline.tool);
- var refreshWorkingTimline = useHookState(project.update.refreshLiveTimeline).value;
- var setFrame = useHookState(project.timeline.frame).set;
+ var mouseX = 0;
- var tool = useHookState(project.timeline.tool);
+ var timelineRef = useRef(null);
+ useEffect(() => {
+ timelineRef.current.addEventListener('wheel', (e: WheelEvent) => {
+ if (!e.ctrlKey && !e.altKey) return;
+ e.preventDefault();
+
+ var newZoom = Math.min(1, Math.max(0, project.timeline.zoom.value + (-e.deltaY / 1000)));
+ zoomAroundPoint(newZoom, mouseX);
+ });
+ }, []);
+
+ useEffect(() => {
+ var canvas = document.querySelector('.timeline .timeScale');
+ window.addEventListener('mousemove', e => {
+ var rect = canvas.getBoundingClientRect();
+ mouseX = e.clientX - rect.x;
+ });
+ }, []);
useEffect(() => {
player.addEventListener('TimedVideoPlayerOnFrame', (event: CustomEvent) => {
- setFrame(event.detail);
+ project.timeline.frame.set(event.detail);
scrubberSpring.start({ frame: event.detail });
});
}, []);
@@ -281,9 +302,7 @@ function TimelineEditor() {
var offset = document.querySelector('.timeline .timelineInner').scrollLeft;
- var frameWidth = Number(
- getComputedStyle(document.querySelector('.timeline')).getPropertyValue('--zoom').trim(),
- );
+ var frameWidth = zoomToPx(project.timeline.zoom.value);
var d = true;
var a = 0;
@@ -352,12 +371,14 @@ function TimelineEditor() {
}),
);
useDrag(({ xy: [x, _y] }) => {
- var frame = Math.max(0, Math.round(getFrameAtOffset(x - 240, timelineZoom.value)) - 1);
- setFrame(frame);
+ console.log('scrubber drag');
+ var frame = Math.max(0, Math.round(getFrameAtOffset(x - 240, project.timeline.zoom.value)) - 1);
scrubberSpring.start({ frame });
if (player.player) {
- player.player.currentTime = player.frameToTimestamp(frame + 1);
+ var time = player.frameToTimestamp(frame + 1);
+ if (isFinite(time)) player.player.currentTime = time;
}
+ project.timeline.frame.set(frame);
}, { domTarget: scrubberDragRef, eventOptions: { passive: false } });
// slide placement ghost
@@ -395,12 +416,12 @@ function TimelineEditor() {
}));
var selectionAreaRef = useRef(null);
var selectionRef = useRef(null);
- var [selection, setSelection] = useState([]);
+ var [selection, setSelection] = useState<anySlide[]>([]);
useDrag(({ movement: [x, _y], last }) => {
if (!selectionPlaced) return;
if (selection.length < 1) return;
- var frameOffset = Math.round(x / zoomToPx(timelineZoom.value));
- selection.forEach((slide: anySlide) => {
+ var frameOffset = Math.round(x / zoomToPx(project.timeline.zoom.value));
+ selection.forEach(slide => {
var api = slideAPIs[slide.id];
switch (slide.type as slideTypes | 'loopBegin') {
case 'loopBegin': {
@@ -410,8 +431,9 @@ function TimelineEditor() {
api.start({ begin });
if (last) {
- loop.beginFrame = begin;
- refreshWorkingTimline();
+ (project.timeline.workingTimeline.find(s => s.value.id == slide.id) as State<loopSlide>)
+ .beginFrame.set(begin);
+ project.update.refreshLiveTimeline.value();
}
break;
@@ -422,9 +444,8 @@ function TimelineEditor() {
api.start({ frame });
if (last) {
- workingTimeline.value.find(s => s.id == slide.id).frame = frame;
- project.timeline.workingTimeline.set(workingTimeline.value);
- refreshWorkingTimline();
+ workingTimeline.find(s => s.value.id == slide.id).frame.set(frame);
+ project.update.refreshLiveTimeline.value();
}
}
}
@@ -465,7 +486,7 @@ function TimelineEditor() {
var x2 = x1 + Math.abs(sx);
var y2 = y1 + Math.abs(sy);
- var zoom = zoomToPx(timelineZoom.value);
+ var zoom = zoomToPx(project.timeline.zoom.value);
var frameWidth = Math.abs(sx) / zoom;
var startingFrame = x1 / zoom;
@@ -524,14 +545,15 @@ function TimelineEditor() {
mousetrap.bind(delkeys, () => {
if (!selectionPlaced) return;
- selection.forEach((slide: anySlide) => {
+ selection.forEach(slide => {
if (!slideTypes.includes(slide.type)) return;
- var index = workingTimeline.value.findIndex(s => s?.id == slide.id);
+ var index = workingTimeline.findIndex(s => s.value?.id == slide.id);
if (index == -1) return;
- delete workingTimeline.value[index];
- // !!! AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+ var timeline = new Array(...workingTimeline.value);
+ timeline.splice(index, 1);
+ workingTimeline.set(timeline);
});
- refreshWorkingTimline();
+ project.update.refreshLiveTimeline.value();
setSelectionPlaced(false);
setSelectionHidden(true);
@@ -543,7 +565,11 @@ function TimelineEditor() {
};
}, [selectionPlaced, workingTimeline]);
- return <>
+ return <div
+ className='timeline posrel'
+ style={{ '--zoom': zoomToPx(timelineZoom.value) } as CSSProperties}
+ ref={timelineRef}
+ >
<canvas
className='timeScale posabs a0'
id='timeScaleCanvas'
@@ -551,7 +577,7 @@ function TimelineEditor() {
// place new keyframe
var offset = -4; // keyframe offset
var x = event.clientX - 240 + offset;
- var frame = getFrameAtOffset(x, timelineZoom.value) - 0.5;
+ var frame = getFrameAtOffset(x, project.timeline.zoom.value) - 0.5;
var slide = new toolToSlide[tool.value](Math.round(frame));
workingTimeline.value.push(slide);
workingTimeline.set(workingTimeline.value);
@@ -559,10 +585,10 @@ function TimelineEditor() {
x: frame,
y: event.clientY - window.innerHeight + 210,
};
- refreshWorkingTimline();
+ project.update.refreshLiveTimeline.value();
}}
/>
- <div className='labels' children={timelineLabels.attach(Downgraded).get()} />
+ <TimelineLabels />
<div className='scrubberJumpArea posabs h0 t0' ref={scrubberDragRef} />
<div className={'timelineInner posabs a0' + (tool.value != 'cursor' ? ' blur' : '')}>
<animated.div
@@ -622,16 +648,11 @@ function TimelineEditor() {
children={<SlideKeyframe type={tool.value as slideTypes} ghost />}
/>
</div>
- </>;
+ </div>;
}
// https://material.io/design/navigation/navigation-transitions.html#peer-transitions
function DefaultSettings() {
- var setPlaying = useHookState(project.timeline.playing).set;
-
- var setWorkingTimeline = useHookState(project.timeline.workingTimeline).set;
- var refreshLiveTimeline = useHookState(project.update.refreshLiveTimeline).value;
-
var [nextSlideKeybinds, setNextSlideKeybinds] = useState(['Space', 'n', 'Enter']);
var [previousSlideKeybinds, setPreviousSlideKeybinds] = useState(['Backspace', 'p']);
var [showMenuKeybinds, setShowMenuKeybinds] = useState(['Escape', 'm']);
@@ -721,8 +742,8 @@ function DefaultSettings() {
reader.addEventListener('load', ev => {
player.loadVideo(ev.target.result as string);
- player.player.addEventListener('play', () => setPlaying(true));
- player.player.addEventListener('pause', () => setPlaying(false));
+ player.player.addEventListener('play', () => project.timeline.playing.set(true));
+ player.player.addEventListener('pause', () => project.timeline.playing.set(false));
});
reader.readAsDataURL(file);
}}
@@ -745,8 +766,8 @@ function DefaultSettings() {
var reader = new FileReader();
reader.addEventListener('load', ev => {
player.loadSlides(ev.target.result as string);
- setWorkingTimeline(player.timeline.slides);
- refreshLiveTimeline();
+ project.timeline.workingTimeline.set(player.timeline.slides);
+ project.update.refreshLiveTimeline.value();
});
reader.readAsText(file);
}}
@@ -780,45 +801,76 @@ function DefaultSettings() {
</>;
}
-export default function Index() {
- var timelineZoom = useHookState(project.timeline.zoom);
- var frame = useHookState(project.timeline.frame);
- var tool = useHookState(project.timeline.tool);
- var playing = useHookState(project.timeline.playing);
-
- var mouseX = 0;
-
- useEffect(() => {
- var videoEL = document.getElementById('player') as HTMLVideoElement;
- player.registerPlayer(videoEL);
- }, []);
+function zoomAroundPoint(newZoom: number, pivot: number) {
+ var timeline = document.querySelector('.timeline .timelineInner');
+ var frame = getFrameAtOffset(pivot, project.timeline.zoom.value);
+ var newOffset = (frame * zoomToPx(newZoom)) - pivot;
- function zoomAroundPoint(newZoom: number, pivot: number) {
- var timeline = document.querySelector('.timeline .timelineInner');
- var frame = getFrameAtOffset(pivot, timelineZoom.value);
- var newOffset = (frame * zoomToPx(newZoom)) - pivot;
+ timeline.scrollLeft = newOffset;
+ project.timeline.zoom.set(newZoom);
+}
- timeline.scrollLeft = newOffset;
- timelineZoom.set(newZoom);
- /* timelineZoom = newZoom; */
- }
+function Tools() {
+ var frame = useHookState(project.timeline.frame);
+ var tool = useHookState(project.timeline.tool);
+ var timelineZoom = useHookState(project.timeline.zoom);
- useEffect(() => {
- document.querySelector('.timeline').addEventListener('wheel', (e: WheelEvent) => {
- if (!e.ctrlKey && !e.altKey) return;
- e.preventDefault();
+ return <div className='tools'>
+ <div className='time posrel'>
+ <span className='framerate numbers posabs l0 t0'>@{player.framerate}fps</span>
+ <h2 className='timecode numbers posabs r0 t0'>
+ {player.frameToTimestampString(frame.value, false)}
+ </h2>
+ </div>
+ <ToggleButtonGroup
+ color='primary'
+ aria-label='outlined primary button group'
+ value={tool.get()}
+ exclusive
+ onChange={(_event: any, newTool: string | null) => {
+ if (newTool === null) return;
+ tool.set(newTool);
+ }}
+ >
+ <ToggleButton value='cursor' children={<Icon path={mdiCursorDefault} size={1} />} />
+ <ToggleButton value='default' children={<SlideKeyframe type='default' />} />
+ <ToggleButton value='delay' children={<SlideKeyframe type='delay' />} />
+ <ToggleButton value='speedChange' children={<SlideKeyframe type='speedChange' />} />
+ <ToggleButton value='loop'>
+ <div className='loopStartEnd'>
+ <span className='posabs start' children={<SlideKeyframe type='loop' />} />
+ <span className='posabs end' children={<SlideKeyframe type='loop' loopEnd />} />
+ </div>
+ </ToggleButton>
+ </ToggleButtonGroup>
+ <div className='zoom'>
+ <ZoomOutRoundedIcon />
+ <div className='spacing'>
+ <Slider
+ value={timelineZoom.value}
+ onChange={(_event: any, newValue: number | number[]) => {
+ var center = document.querySelector('.timeline .timelineInner').clientWidth / 2;
+ zoomAroundPoint(newValue as number, center);
+ }}
+ min={0}
+ step={0.00000001}
+ max={1}
+ aria-labelledby='continuous-slider'
+ />
+ </div>
+ <ZoomInRoundedIcon />
+ </div>
+ </div>;
+}
- var newZoom = Math.min(1, Math.max(0, timelineZoom.value + (-e.deltaY / 1000)));
- zoomAroundPoint(newZoom, mouseX);
- }, { passive: false });
- }, []);
+export default function Index() {
+ // var playing = useHookState(project.timeline.playing);
+ //
+ var playing = { get: () => false };
+ var playerRef = useRef(null);
useEffect(() => {
- var canvas = document.querySelector('.timeline .timeScale');
- window.addEventListener('mousemove', e => {
- var rect = canvas.getBoundingClientRect();
- mouseX = e.clientX - rect.x;
- });
+ player.registerPlayer(playerRef.current);
}, []);
useEffect(() => {
@@ -843,7 +895,7 @@ export default function Index() {
<div className='viewer'>
<div className='player posrel'>
<div className='outer posabs abscenter'>
- <video id='player' className='fullwidth' />
+ <video id='player' ref={playerRef} className='fullwidth' />
</div>
</div>
<div className='controls'>
@@ -886,58 +938,8 @@ export default function Index() {
</div>
</div>
</div>
- <div className='tools'>
- <div className='time posrel'>
- <span className='framerate numbers posabs l0 t0'>@{player.framerate}fps</span>
- <h2 className='timecode numbers posabs r0 t0'>
- {player.frameToTimestampString(frame.value, false)}
- </h2>
- </div>
- <ToggleButtonGroup
- color='primary'
- aria-label='outlined primary button group'
- value={tool.get()}
- exclusive
- onChange={(_event: any, newTool: string | null) => {
- if (newTool === null) return;
- tool.set(newTool);
- }}
- >
- <ToggleButton value='cursor' children={<Icon path={mdiCursorDefault} size={1} />} />
- <ToggleButton value='default' children={<SlideKeyframe type='default' />} />
- <ToggleButton value='delay' children={<SlideKeyframe type='delay' />} />
- <ToggleButton value='speedChange' children={<SlideKeyframe type='speedChange' />} />
- <ToggleButton value='loop'>
- <div className='loopStartEnd'>
- <span className='posabs start' children={<SlideKeyframe type='loop' />} />
- <span className='posabs end' children={<SlideKeyframe type='loop' loopEnd />} />
- </div>
- </ToggleButton>
- </ToggleButtonGroup>
- <div className='zoom'>
- <ZoomOutRoundedIcon />
- <div className='spacing'>
- <Slider
- value={timelineZoom.value}
- onChange={(_event: any, newValue: number | number[]) => {
- var center = document.querySelector('.timeline .timelineInner').clientWidth / 2;
- zoomAroundPoint(newValue as number, center);
- }}
- min={0}
- step={0.00000001}
- max={1}
- aria-labelledby='continuous-slider'
- />
- </div>
- <ZoomInRoundedIcon />
- </div>
- </div>
- <div
- className='timeline posrel'
- style={{ '--zoom': zoomToPx(timelineZoom.value) } as CSSProperties}
- >
- <TimelineEditor />
- </div>
+ <Tools />
+ <TimelineEditor />
</div>
</>;
}