From abca906d804e876a01d4c07c226caefeeaca9920 Mon Sep 17 00:00:00 2001 From: lonkaars Date: Sat, 15 May 2021 10:54:55 +0200 Subject: timeline display in editor --- package.json | 1 + pages/editor.tsx | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- pages/present.tsx | 8 ++++- styles/editor.css | 46 ++++++++++++++++++++++++++ styles/globals.css | 15 +++++++++ yarn.lock | 5 +++ 6 files changed, 168 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index bb41537..91bdf2f 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "next": "^10.2.0", "react": "^17.0.2", "react-dom": "^17.0.2", + "timecode-boss": "^4.2.3", "ts-json-schema-generator": "^0.92.0" }, "devDependencies": { diff --git a/pages/editor.tsx b/pages/editor.tsx index c550eac..aeb4f87 100644 --- a/pages/editor.tsx +++ b/pages/editor.tsx @@ -1,8 +1,13 @@ +import { useEffect, useState } from 'react'; +import { loopSlide } from '../timeline'; +import { TimedVideoPlayer } from './present'; + import AppBar from '@material-ui/core/AppBar'; +import Button from '@material-ui/core/Button'; import Fab from '@material-ui/core/Fab'; import Toolbar from '@material-ui/core/Toolbar'; -import { PressureIcon } from '../components/icons'; +import { PressureIcon, SlideKeyframe } from '../components/icons'; import FullscreenRoundedIcon from '@material-ui/icons/FullscreenRounded'; import NavigateBeforeRoundedIcon from '@material-ui/icons/NavigateBeforeRounded'; @@ -10,7 +15,44 @@ import NavigateNextRoundedIcon from '@material-ui/icons/NavigateNextRounded'; import PauseRoundedIcon from '@material-ui/icons/PauseRounded'; import SkipPreviousRoundedIcon from '@material-ui/icons/SkipPreviousRounded'; +function TimelineEditor(props: { + player: TimedVideoPlayer; +}) { + var frames = [...new Array(props.player.timeline?.framecount || 0)].map((el, i) => +
+ + {props.player?.frameToTimestampString(i + 1)} + +
+ {(() => { + var slide = props.player?.timeline?.slides.find(slide => slide.frame == i + 1); + if (slide) { + return ; + } + var loop = props.player?.timeline?.slides.find(slide => + slide.type == 'loop' && (slide as loopSlide).beginFrame == i + 1 + ); + if (loop) { + return ; + } + })()} +
+
+ ); + + return
{frames}
; +} + export default function Index() { + var [dummy, setDummy] = useState(false); + var rerender = () => setDummy(!dummy); + var [player, setPlayer] = useState(new TimedVideoPlayer()); + + useEffect(() => { + var videoEL = document.getElementById('player') as HTMLVideoElement; + player.registerPlayer(videoEL); + }, []); + return <>
@@ -19,10 +61,55 @@ export default function Index() {

pressure

-
+
+ { + var file = event.target.files[0]; + if (!file) return; + var reader = new FileReader(); + reader.addEventListener('load', ev => { + player.loadVideo(ev.target.result as string); + }); + reader.readAsDataURL(file); + }} + /> + { + var file = event.target.files[0]; + if (!file) return; + var reader = new FileReader(); + reader.addEventListener('load', ev => { + player.loadSlides(ev.target.result as string); + rerender(); + }); + reader.readAsText(file); + }} + /> +
+
@@ -38,7 +125,11 @@ export default function Index() {
-
+
+ +
; } diff --git a/pages/present.tsx b/pages/present.tsx index ad3cec2..19ef111 100644 --- a/pages/present.tsx +++ b/pages/present.tsx @@ -1,6 +1,7 @@ import Button from '@material-ui/core/Button'; import Ajv from 'ajv'; import { useEffect, useState } from 'react'; +import Timecode from 'timecode-boss'; import timeline, { delaySlide, loopSlide, slide, speedChangeSlide } from '../timeline'; import * as timelineSchema from '../timeline.schema.json'; @@ -12,7 +13,7 @@ import SettingsRoundedIcon from '@material-ui/icons/SettingsRounded'; import CodeRoundedIcon from '@material-ui/icons/CodeRounded'; import MovieRoundedIcon from '@material-ui/icons/MovieRounded'; -class TimedVideoPlayer { +export class TimedVideoPlayer { slide: number; timeline: timeline; precision: number; @@ -30,6 +31,11 @@ class TimedVideoPlayer { this.registeredEventListeners = false; } + frameToTimestampString(frame: number) { + var timecodeString = new Timecode(frame, this.framerate).toString(); + return timecodeString.replace(/^(00:)+/, '') + 'f'; + } + timestampToFrame(timestamp: number): number { return Math.ceil((timestamp * 1e3) / (1e3 / this.framerate)); } diff --git a/styles/editor.css b/styles/editor.css index a25108d..8848b6a 100644 --- a/styles/editor.css +++ b/styles/editor.css @@ -48,6 +48,7 @@ .appGrid .viewer .player { margin: 16px; margin-bottom: 0px; + line-height: 0; } .appGrid .viewer .player .outer { @@ -92,3 +93,48 @@ box-shadow: none; margin: 4px; } + +.appGrid .timeline .frames { + height: 100%; +} + +.appGrid .timeline .frames .frame { + background-color: transparent; + margin-top: 28px; + height: 100%; + width: 4px; + display: inline-block; + border-radius: 2px 2px 0 0; + position: relative; + overflow: visible; +} + +.appGrid .timeline .frames .frame .timecode { + display: none; +} + +.appGrid .timeline .frames .frame .keyframeWrapper { + line-height: 0; + top: 16px; + z-index: 999; + +} + +.appGrid .timeline .frames .frame:nth-child(30n) { + background-color: var(--c300); +} + +.appGrid .timeline .frames .frame:nth-child(30n) .timecode { + color: var(--c700); + font-weight: 500; + line-height: 1; + display: inline-block; + top: -16px; +} + +.appGrid .timeline { + overflow-y: hidden; + overflow-x: scroll; + white-space: nowrap; +} + diff --git a/styles/globals.css b/styles/globals.css index 9ed6119..c45aa2d 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -23,3 +23,18 @@ h3 { font-size: 1rem; } font-family: "Inter", sans-serif; } +::-webkit-scrollbar-track, +::-webkit-scrollbar-track-piece, +::-webkit-scrollbar { + background-color: var(--c100); +} + +::-webkit-scrollbar-thumb { + background-color: var(--c400); +} + +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + diff --git a/yarn.lock b/yarn.lock index adc2d5a..df0ae64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2055,6 +2055,11 @@ symbol-observable@1.2.0: resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== +timecode-boss@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/timecode-boss/-/timecode-boss-4.2.3.tgz#c569ef8b815c0434ad551f7681577cd46949abf9" + integrity sha512-DnZWYwn/QCAW3ElK0TOaAy3v2QEr6I+VMdc1aqo/9/H0uiebMp5xIeaU44IDpwQUE7W6W4k2+2aDM6VwQaXjOQ== + timers-browserify@2.0.12, timers-browserify@^2.0.4: version "2.0.12" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" -- cgit v1.2.3