aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--components/icons.tsx5
-rw-r--r--pages/editor.tsx57
-rw-r--r--pages/present.tsx6
-rw-r--r--timeline.schema.json57
-rw-r--r--timeline.ts7
5 files changed, 87 insertions, 45 deletions
diff --git a/components/icons.tsx b/components/icons.tsx
index 50bf8f0..f8d6668 100644
--- a/components/icons.tsx
+++ b/components/icons.tsx
@@ -1,5 +1,4 @@
-import { Ref } from 'react';
-import { keyframeTypes } from '../timeline';
+import { slideTypes } from '../timeline';
export function PressureIcon() {
return <svg width='48' height='48' viewBox='0 0 48 48' fill='none' xmlns='http://www.w3.org/2000/svg'>
@@ -117,7 +116,7 @@ export function PressureIcon() {
}
export function SlideKeyframe(props: {
- type: keyframeTypes;
+ type: slideTypes;
loopEnd?: boolean;
}) {
return <svg
diff --git a/pages/editor.tsx b/pages/editor.tsx
index e001dfd..7d8834c 100644
--- a/pages/editor.tsx
+++ b/pages/editor.tsx
@@ -1,8 +1,8 @@
import { CSSProperties, ReactNode, useEffect, useRef, useState } from 'react';
-import { animated, useSpring, useSprings } from 'react-spring';
+import { animated, useSpring } from 'react-spring';
import { useDrag } from 'react-use-gesture';
import create from 'zustand';
-import { delaySlide, loopSlide, slide, speedChangeSlide } from '../timeline';
+import { anySlide, loopSlide, slide } from '../timeline';
import { TimedVideoPlayer } from './present';
import AppBar from '@material-ui/core/AppBar';
@@ -15,15 +15,19 @@ import ZoomInRoundedIcon from '@material-ui/icons/ZoomInRounded';
import ZoomOutRoundedIcon from '@material-ui/icons/ZoomOutRounded';
import Icon from '@mdi/react';
-import { PressureIcon, SlideKeyframe } from '../components/icons';
-import Loop from '../components/loop';
-
import FullscreenRoundedIcon from '@material-ui/icons/FullscreenRounded';
import NavigateBeforeRoundedIcon from '@material-ui/icons/NavigateBeforeRounded';
import NavigateNextRoundedIcon from '@material-ui/icons/NavigateNextRounded';
import PauseRoundedIcon from '@material-ui/icons/PauseRounded';
import SkipPreviousRoundedIcon from '@material-ui/icons/SkipPreviousRounded';
import { mdiCursorDefault } from '@mdi/js';
+import { PressureIcon, SlideKeyframe } from '../components/icons';
+
+var player = new TimedVideoPlayer();
+var useWorkingTimeline = create(set => ({
+ timeline: [],
+ setTimeline: (newTimeline: anySlide[]) => set(() => ({ timeline: newTimeline })),
+}));
var getTimelineZoom = create(set => ({
zoom: 0.687077725615,
@@ -50,8 +54,17 @@ var useFrame = create(set => ({
}));
function TimelineKeyframe(props: {
- slide: slide | delaySlide | loopSlide | speedChangeSlide;
+ slide: slide;
}) {
+ var workingTimeline = useWorkingTimeline((st: any) => st.timeline);
+ var setWorkingTimeline = useWorkingTimeline((st: any) => st.setTimeline);
+
+ function modifySlide(newProps: Partial<anySlide>) {
+ var slide = workingTimeline.find((s: anySlide) => s.id == props.slide.id);
+ slide = Object.assign(slide, newProps);
+ setWorkingTimeline(workingTimeline);
+ }
+
var dragRef = useRef(null);
var loopStartRef = useRef(null);
var loopEndRef = useRef(null);
@@ -83,8 +96,13 @@ function TimelineKeyframe(props: {
endOffset = endFrame - grabFrameOffset;
}
api.start({ begin: frame + startOffset, frame: frame + endOffset });
+
+ modifySlide({ frame: frame + endOffset });
+ modifySlide({ beginFrame: frame + startOffset });
} else {
api.start({ frame });
+
+ modifySlide({ frame });
}
}, { domTarget: dragRef, eventOptions: { passive: false } });
@@ -92,20 +110,35 @@ function TimelineKeyframe(props: {
// loop start
useDrag(({ xy: [x, _y] }) => {
var frame = Math.max(0, Math.round(getFrameAtOffset(x - 240, timelineZoom)) - 1);
+
api.start({ begin: frame });
+
+ modifySlide({ beginFrame: frame });
}, { domTarget: loopStartRef, eventOptions: { passive: false } });
// loop end
useDrag(({ xy: [x, _y] }) => {
var frame = Math.max(0, Math.round(getFrameAtOffset(x - 240, timelineZoom)) - 1);
+
api.start({ frame });
+
+ modifySlide({ frame });
}, { domTarget: loopEndRef, eventOptions: { passive: false } });
}
+ 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);
+ }, { domTarget: mouseUpListener, eventOptions: { passive: false } });
+
return <animated.div
className='frame posabs'
style={{ '--frame': spring.frame } as CSSProperties}
id={'slide-' + props.slide.id}
+ ref={mouseUpListener}
>
<div className='keyframeWrapper posabs abscenterh'>
{props.slide.type == 'loop'
@@ -128,14 +161,14 @@ function TimelineKeyframe(props: {
</animated.div>;
}
-function TimelineEditor(props: {
- player: TimedVideoPlayer;
-}) {
+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);
+ var workingTimeline = useWorkingTimeline((st: any) => st.timeline);
+
// var frame = useFrame((st: any) => st.currentFrame);
var setFrame = useFrame((st: any) => st.setFrame);
@@ -281,7 +314,7 @@ function TimelineEditor(props: {
<div
className='keyframes'
style={{ '--total-frames': props.player?.timeline?.framecount.toString() } as CSSProperties}
- children={props.player?.timeline?.slides.map(slide => <TimelineKeyframe slide={slide} />)}
+ children={workingTimeline.map((slide: anySlide) => <TimelineKeyframe slide={slide} />)}
/>
</div>
</>;
@@ -290,7 +323,8 @@ function TimelineEditor(props: {
export default function Index() {
var [dummy, setDummy] = useState(false);
var rerender = () => setDummy(!dummy);
- var [player, _setPlayer] = useState(new TimedVideoPlayer());
+
+ var setWorkingTimeline = useWorkingTimeline((st: any) => st.setTimeline);
var timelineZoom = getTimelineZoom((st: any) => st.zoom);
var setTimelineZoom = getTimelineZoom((st: any) => st.setZoom);
@@ -375,6 +409,7 @@ export default function Index() {
var reader = new FileReader();
reader.addEventListener('load', ev => {
player.loadSlides(ev.target.result as string);
+ setWorkingTimeline(player.timeline.slides);
rerender();
});
reader.readAsText(file);
diff --git a/pages/present.tsx b/pages/present.tsx
index f5955fc..9769577 100644
--- a/pages/present.tsx
+++ b/pages/present.tsx
@@ -58,6 +58,9 @@ export class TimedVideoPlayer {
jumpToFrame(frame: number) {
this.player.currentTime = this.frameToTimestamp(frame);
this.frame = frame;
+
+ var event = new CustomEvent('TimedVideoPlayerOnFrame', { detail: this.frame });
+ this.dispatchEvent(event);
}
jumpToSlide(slide: slide) {
@@ -128,7 +131,6 @@ export class TimedVideoPlayer {
setInterval(() => {
if (this.player.paused) return;
- var lastFrame = this.frame;
this.frame = this.timestampToFrame(this.player.currentTime);
var event = new CustomEvent('TimedVideoPlayerOnFrame', { detail: this.frame });
@@ -169,6 +171,7 @@ export class TimedVideoPlayer {
this.framerate = this.timeline.framerate;
this.timeline.slides[-1] = {
+ id: '00000000-0000-0000-0000-000000000000',
frame: 0,
type: 'default',
clickThroughBehaviour: 'ImmediatelySkip',
@@ -202,6 +205,7 @@ export class TimedVideoPlayer {
if (!this.registeredEventListeners) return;
this.slide = Math.max(this.slide - 1, -1);
+
var event = new CustomEvent('TimedVideoPlayerSlide', { detail: this.slide });
this.dispatchEvent(event);
diff --git a/timeline.schema.json b/timeline.schema.json
index 2d8c6ff..75c5265 100644
--- a/timeline.schema.json
+++ b/timeline.schema.json
@@ -2,6 +2,22 @@
"$ref": "#/definitions/timeline",
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
+ "anySlide": {
+ "anyOf": [
+ {
+ "$ref": "#/definitions/slide"
+ },
+ {
+ "$ref": "#/definitions/delaySlide"
+ },
+ {
+ "$ref": "#/definitions/speedChangeSlide"
+ },
+ {
+ "$ref": "#/definitions/loopSlide"
+ }
+ ]
+ },
"delaySlide": {
"additionalProperties": false,
"properties": {
@@ -22,7 +38,7 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/keyframeTypes"
+ "$ref": "#/definitions/slideTypes"
}
},
"required": [
@@ -34,15 +50,6 @@
],
"type": "object"
},
- "keyframeTypes": {
- "enum": [
- "default",
- "delay",
- "speedChange",
- "loop"
- ],
- "type": "string"
- },
"loopSlide": {
"additionalProperties": false,
"properties": {
@@ -70,7 +77,7 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/keyframeTypes"
+ "$ref": "#/definitions/slideTypes"
}
},
"required": [
@@ -113,7 +120,7 @@
"type": "string"
},
"type": {
- "$ref": "#/definitions/keyframeTypes"
+ "$ref": "#/definitions/slideTypes"
}
},
"required": [
@@ -124,6 +131,15 @@
],
"type": "object"
},
+ "slideTypes": {
+ "enum": [
+ "default",
+ "delay",
+ "speedChange",
+ "loop"
+ ],
+ "type": "string"
+ },
"speedChangeSlide": {
"additionalProperties": false,
"properties": {
@@ -144,7 +160,7 @@
"type": "number"
},
"type": {
- "$ref": "#/definitions/keyframeTypes"
+ "$ref": "#/definitions/slideTypes"
}
},
"required": [
@@ -173,20 +189,7 @@
},
"slides": {
"items": {
- "anyOf": [
- {
- "$ref": "#/definitions/slide"
- },
- {
- "$ref": "#/definitions/delaySlide"
- },
- {
- "$ref": "#/definitions/speedChangeSlide"
- },
- {
- "$ref": "#/definitions/loopSlide"
- }
- ]
+ "$ref": "#/definitions/anySlide"
},
"type": "array"
}
diff --git a/timeline.ts b/timeline.ts
index 764bc7a..5d0f51c 100644
--- a/timeline.ts
+++ b/timeline.ts
@@ -1,9 +1,10 @@
-export type keyframeTypes = 'default' | 'delay' | 'speedChange' | 'loop';
+export type slideTypes = 'default' | 'delay' | 'speedChange' | 'loop';
+export type anySlide = slide | delaySlide | speedChangeSlide | loopSlide;
export interface slide {
frame: number;
clickThroughBehaviour: 'ImmediatelySkip' | 'PlayOut';
- type: keyframeTypes;
+ type: slideTypes;
id: string;
}
@@ -25,7 +26,7 @@ export interface presentationSettings {
}
export default interface timeline {
- slides: Array<slide | delaySlide | speedChangeSlide | loopSlide>;
+ slides: Array<anySlide>;
framecount: number;
framerate: number;
name: string;