From 1e46be88a32dbf2196b58708834dfe138ff5aad2 Mon Sep 17 00:00:00 2001 From: andrewdkim Date: Mon, 8 Jul 2019 17:01:33 -0400 Subject: scrubber change, keyframe mod, flyout features --- src/client/views/nodes/Keyframe.tsx | 147 ++++++++++++++++++----------------- src/client/views/nodes/Timeline.scss | 9 +-- src/client/views/nodes/Timeline.tsx | 140 +++++++++++++++++++++++++-------- src/client/views/nodes/Track.tsx | 91 +++++++++------------- 4 files changed, 226 insertions(+), 161 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/Keyframe.tsx b/src/client/views/nodes/Keyframe.tsx index 10ae2f427..4bccc6040 100644 --- a/src/client/views/nodes/Keyframe.tsx +++ b/src/client/views/nodes/Keyframe.tsx @@ -10,43 +10,68 @@ import { List } from "../../../new_fields/List"; import { createSchema, defaultSpec, makeInterface, listSpec } from "../../../new_fields/Schema"; import { any } from "bluebird"; import { FlyoutProps } from "./Timeline"; - -enum Direction { - left = "left", - right = "right" +import { number } from "prop-types"; + +export namespace KeyframeFunc{ + export enum Direction{ + left = "left", + right = "right" + } + export const findAdjacentRegion = (dir: KeyframeFunc.Direction, currentRegion:Doc, regions:List): (RegionData | undefined) => { + let leftMost: (RegionData | undefined) = undefined; + let rightMost: (RegionData | undefined) = undefined; + regions.forEach(region => { + let neighbor = RegionData(region as Doc); + if (currentRegion.position! > neighbor.position) { + if (!leftMost || neighbor.position > leftMost.position) { + leftMost = neighbor; + } + } else if (currentRegion.position! < neighbor.position) { + if (!rightMost || neighbor.position < rightMost.position) { + rightMost = neighbor; + } + } + }); + if (dir === Direction.left) { + return leftMost; + } else if (dir === Direction.right) { + return rightMost; + } + }; } -interface IProps { - node: Doc; - RegionData: Doc; - setFlyout:(props:FlyoutProps) => any; -} const RegionDataSchema = createSchema({ position: defaultSpec("number", 0), duration: defaultSpec("number", 0), - keyframes: listSpec(Doc) + keyframes: listSpec(Doc), + fadeIn: defaultSpec("number", 0), + fadeOut: defaultSpec("number", 0) }); type RegionData = makeInterface<[typeof RegionDataSchema]>; -export const RegionData = makeInterface(RegionDataSchema); +const RegionData = makeInterface(RegionDataSchema); +interface IProps { + node: Doc; + RegionData: Doc; + changeCurrentBarX: (x: number) => void; + setFlyout:(props:FlyoutProps) => any; +} @observer export class Keyframe extends React.Component { @observable private _bar = React.createRef(); - @observable private fadein: number = 0; - @observable private fadeout: number = 0; @action - componentDidMount() { - // need edge case here when keyframe data already exists when loading.....................; + componentWillMount() { } componentWillUnmount() { } + @computed private get regiondata() { let index = this.regions.indexOf(this.props.RegionData); @@ -78,29 +103,21 @@ export class Keyframe extends React.Component { @action onBarPointerDown = (e: React.PointerEvent) => { - let mouse = e.nativeEvent; - e.preventDefault(); e.stopPropagation(); - if (mouse.which === 1){ - document.addEventListener("pointermove", this.onBarPointerMove); - document.addEventListener("pointerup", (e: PointerEvent) => { - document.removeEventListener("pointermove", this.onBarPointerMove); - }); - }// else if(mouse.which === 3) { - // e.preventDefault(); - // e.stopPropagation(); - // let bar = this._bar.current!; - // this.props.setFlyout({x:e.clientX, y: e.clientY,display:"block", time: this.regiondata.position, duration:this.regiondata.duration}); - // } + document.addEventListener("pointermove", this.onBarPointerMove); + document.addEventListener("pointerup", (e: PointerEvent) => { + document.removeEventListener("pointermove", this.onBarPointerMove); + }); } + @action onBarPointerMove = (e: PointerEvent) => { e.preventDefault(); e.stopPropagation(); - let left = this.findAdjacentRegion(Direction.left); - let right = this.findAdjacentRegion(Direction.right); + let left = KeyframeFunc.findAdjacentRegion(KeyframeFunc.Direction.left, this.regiondata, this.regions)!; + let right = KeyframeFunc.findAdjacentRegion(KeyframeFunc.Direction.right, this.regiondata, this.regions!); let bar = this._bar.current!; let barX = bar.getBoundingClientRect().left; let offset = e.clientX - barX; @@ -116,29 +133,7 @@ export class Keyframe extends React.Component { } } - @action - findAdjacentRegion = (dir: Direction): (RegionData | undefined) => { - let leftMost: (RegionData | undefined) = undefined; - let rightMost: (RegionData | undefined) = undefined; - this.regions.forEach(region => { - let neighbor = RegionData(region as Doc); - if (this.regiondata.position > neighbor.position) { - if (!leftMost || neighbor.position > leftMost.position) { - leftMost = neighbor; - } - } else if (this.regiondata.position < neighbor.position) { - if (!rightMost || neighbor.position < rightMost.position) { - rightMost = neighbor; - } - } - }); - if (dir === Direction.left) { - return leftMost; - } else if (dir === Direction.right) { - return rightMost; - } - } - + @action onResizeLeft = (e: React.PointerEvent) => { e.preventDefault(); @@ -166,8 +161,13 @@ export class Keyframe extends React.Component { let bar = this._bar.current!; let barX = bar.getBoundingClientRect().left; let offset = e.clientX - barX; - this.regiondata.duration -= offset; - this.regiondata.position += offset; + if (this.regiondata.duration - offset < this.regiondata.fadeIn + this.regiondata.fadeOut){ + this.regiondata.position -= (this.regiondata.fadeIn + this.regiondata.fadeOut - this.regiondata.duration); + this.regiondata.duration = this.regiondata.fadeIn + this.regiondata.fadeOut; + } else { + this.regiondata.duration -= offset; + this.regiondata.position += offset; + } } @@ -178,10 +178,14 @@ export class Keyframe extends React.Component { let bar = this._bar.current!; let barX = bar.getBoundingClientRect().right; let offset = e.clientX - barX; - this.regiondata.duration += offset; + if (this.regiondata.duration + offset < this.regiondata.fadeIn + this.regiondata.fadeOut){ + this.regiondata.duration = this.regiondata.fadeIn + this.regiondata.fadeOut; + } else { + this.regiondata.duration += offset; + } } - createDivider = (type?: Direction): JSX.Element => { + createDivider = (type?: KeyframeFunc.Direction): JSX.Element => { if (type === "left") { return
; } else if (type === "right") { @@ -197,14 +201,14 @@ export class Keyframe extends React.Component { let bar = this._bar.current!; let offset = e.clientX - bar.getBoundingClientRect().left; let position = NumCast(this.regiondata.position); - this.makeKeyData(position + offset); + this.makeKeyData(Math.round(position + offset)); } @action - moveKeyframe = (e: React.MouseEvent) => { + moveKeyframe = (e: React.MouseEvent, kf:Doc) => { e.preventDefault(); e.stopPropagation(); - + this.props.changeCurrentBarX(NumCast(kf.time!)); } @@ -215,26 +219,25 @@ export class Keyframe extends React.Component { onPointerDown={this.onBarPointerDown} onDoubleClick={this.createKeyframe} onContextMenu={action((e:React.MouseEvent)=>{ - let mouse = e.nativeEvent; - if (mouse.which === 3){ - this.props.setFlyout({x:e.clientX, y: e.clientY, display:"block"}); - } else { - this.props.setFlyout({display:"block"}); - } + let offsetLeft = this._bar.current!.getBoundingClientRect().left - this._bar.current!.parentElement!.getBoundingClientRect().left; + let offsetTop = this._bar.current!.getBoundingClientRect().top; //+ this._bar.current!.parentElement!.getBoundingClientRect().top; + console.log(offsetLeft); + console.log(offsetTop); + this.props.setFlyout({x:offsetLeft, y: offsetTop, display:"block", regiondata:this.regiondata, regions:this.regions}); })}>
-
{this.createDivider(Direction.left)}
-
{this.createDivider(Direction.right)}
+
{this.createDivider(KeyframeFunc.Direction.left)}
+
{this.createDivider(KeyframeFunc.Direction.right)}
{this.regiondata.keyframes!.map(kf => { kf = kf as Doc; - return
+ return
{this.createDivider()} -
+
{this.moveKeyframe(e, kf as Doc);}}>
; })} - {this.createDivider(Direction.left)} - {this.createDivider(Direction.right)} + {this.createDivider(KeyframeFunc.Direction.left)} + {this.createDivider(KeyframeFunc.Direction.right)}
); diff --git a/src/client/views/nodes/Timeline.scss b/src/client/views/nodes/Timeline.scss index a416cbb1c..0a510ba6e 100644 --- a/src/client/views/nodes/Timeline.scss +++ b/src/client/views/nodes/Timeline.scss @@ -8,7 +8,8 @@ } .flyout-container{ background-color: transparent; - position:absolute; + position:absolute; + z-index:9999; height: 150px; width: 150px; @@ -36,8 +37,6 @@ left:0px; color:white } - - } .timeline-container{ @@ -46,8 +45,8 @@ position:absolute; background-color: $light-color-secondary; box-shadow: 0px 10px 20px; - transition: transform 1000ms ease-in-out; - + //transition: transform 1000ms ease-in-out; + .toolbox{ position:absolute; width: 100%; diff --git a/src/client/views/nodes/Timeline.tsx b/src/client/views/nodes/Timeline.tsx index fd235fad3..1c51dbcad 100644 --- a/src/client/views/nodes/Timeline.tsx +++ b/src/client/views/nodes/Timeline.tsx @@ -1,12 +1,11 @@ import * as React from "react"; -import * as ReactDOM from "react-dom"; import "./Timeline.scss"; import { CollectionSubView } from "../collections/CollectionSubView"; import { Document, listSpec, createSchema, makeInterface, defaultSpec } from "../../../new_fields/Schema"; import { observer } from "mobx-react"; import { Track } from "./Track"; import { observable, reaction, action, IReactionDisposer, observe, IObservableArray, computed, toJS, Reaction, IObservableObject } from "mobx"; -import { Cast } from "../../../new_fields/Types"; +import { Cast, NumCast } from "../../../new_fields/Types"; import { SelectionManager } from "../../util/SelectionManager"; import { List } from "../../../new_fields/List"; import { Self } from "../../../new_fields/FieldSymbols"; @@ -16,29 +15,39 @@ import { faCircle, faPlayCircle, faBackward, faForward, faGripLines } from "@for import { DocumentContentsView } from "./DocumentContentsView"; import { ContextMenuProps } from "../ContextMenuItem"; import { ContextMenu } from "../ContextMenu"; +import { string } from "prop-types"; export interface FlyoutProps { x?: number; y?: number; display?: string; - time?: number; - duration?: number; + regiondata?:Doc; + regions?:List; } @observer export class Timeline extends CollectionSubView(Document) { private readonly DEFAULT_CONTAINER_HEIGHT: number = 300; + private readonly DEFAULT_TICK_SPACING:number = 50; private readonly MIN_CONTAINER_HEIGHT: number = 205; private readonly MAX_CONTAINER_HEIGHT: number = 800; - - @observable private _tickSpacing = 50; + + @observable private _isMinimized = false; + @observable private _tickSpacing = this.DEFAULT_TICK_SPACING; @observable private _scrubberbox = React.createRef(); + @observable private _scrubber = React.createRef(); @observable private _trackbox = React.createRef(); @observable private _titleContainer = React.createRef(); @observable private _timelineContainer = React.createRef(); + @observable private _timeInput = React.createRef(); + @observable private _durationInput = React.createRef(); + @observable private _fadeInInput = React.createRef(); + @observable private _fadeOutInput = React.createRef(); + + @observable private _currentBarX: number = 0; @observable private _windSpeed: number = 1; @observable private _isPlaying: boolean = false; @@ -50,7 +59,7 @@ export class Timeline extends CollectionSubView(Document) { @observable private _infoContainer = React.createRef(); @observable private _ticks: number[] = []; - @observable private flyoutInfo: FlyoutProps = { x: 0, y: 0, display: "none" }; + @observable private flyoutInfo: FlyoutProps = { x: 0, y: 0, display: "none", regiondata: new Doc(), regions: new List()}; private block = false; @@ -80,6 +89,10 @@ export class Timeline extends CollectionSubView(Document) { document.addEventListener("pointerdown", this.closeFlyout); } + @action + changeCurrentBarX = (x: number) => { + this._currentBarX = x; + } @action onFlyoutDown = (e: React.PointerEvent) => { this.flyoutInfo.display = "block"; @@ -169,19 +182,7 @@ export class Timeline extends CollectionSubView(Document) { this._currentBarX = offset; } - @action - toTime = (time: number): string => { - const inSeconds = time / 1000; - let min: (string | number) = Math.floor(inSeconds / 60); - let sec: (string | number) = inSeconds % 60; - - if (Math.floor(sec / 10) === 0) { - sec = "0" + sec; - } - return `${min}:${sec}`; - - } - + @action onPanDown = (e: React.PointerEvent) => { @@ -205,8 +206,6 @@ export class Timeline extends CollectionSubView(Document) { titleContainer.scrollTop = titleContainer.scrollTop - e.movementY; } - - @action onResizeDown = (e: React.PointerEvent) => { e.preventDefault(); @@ -231,7 +230,24 @@ export class Timeline extends CollectionSubView(Document) { } } - @observable private _isMinimized = false; + @action + onTimelineDown = (e:React.PointerEvent) => { + e.preventDefault(); + e.stopPropagation(); + document.addEventListener("pointermove", this.onTimelineMove); + document.addEventListener("pointerup", () => {document.removeEventListener("pointermove", this.onTimelineMove);}); + } + + @action + onTimelineMove = (e:PointerEvent) => { + e.preventDefault(); + e.stopPropagation(); + let timelineContainer = this._timelineContainer.current!; + timelineContainer.style.transform = `translate(${timelineContainer.getBoundingClientRect().left + 1}px, ${timelineContainer.getBoundingClientRect().top + 1}px)`; + console.log("mouse move!"); + timelineContainer.style.width = "500px"; + } + @action minimize = (e: React.MouseEvent) => { e.preventDefault(); @@ -246,6 +262,17 @@ export class Timeline extends CollectionSubView(Document) { } } + @action + toTime = (time: number): string => { + const inSeconds = time / 1000; + let min: (string | number) = Math.floor(inSeconds / 60); + let sec: (string | number) = inSeconds % 60; + + if (Math.floor(sec / 10) === 0) { + sec = "0" + sec; + } + return `${min}:${sec}`; + } @action getFlyout = (props: FlyoutProps) => { @@ -265,15 +292,63 @@ export class Timeline extends CollectionSubView(Document) { }), icon: "pinterest" }); ContextMenu.Instance.addItem({ description: "Timeline Funcs...", subitems: subitems }); + + } + + @action + changeTime = (e: React.KeyboardEvent) => { + let time = this._timeInput.current!; + if (e.keyCode === 13){ + if (!Number.isNaN(Number(time.value))){ + this.flyoutInfo.regiondata!.position = Number(time.value) / 1000 * this._tickSpacing; + time.placeholder = time.value +"ms"; + time.value = ""; + } + } + } + + + @action + changeDuration = (e:React.KeyboardEvent) => { + let duration = this._durationInput.current!; + if (e.keyCode === 13){ + if (!Number.isNaN(Number(duration.value))){ + this.flyoutInfo.regiondata!.duration = Number(duration.value) / 1000 * this._tickSpacing; + duration.placeholder = duration.value +"ms"; + duration.value = ""; + } + } + } + + @action + changeFadeIn = (e:React.KeyboardEvent) => { + let fadeIn = this._fadeInInput.current!; + if (e.keyCode === 13){ + if (!Number.isNaN(Number(fadeIn.value))){ + this.flyoutInfo.regiondata!.fadeIn = Number(fadeIn.value); + fadeIn.placeholder = fadeIn.value +"ms"; + fadeIn.value = ""; + } + } } + @action + changeFadeOut = (e:React.KeyboardEvent) => { + let fadeOut = this._fadeOutInput.current!; + if (e.keyCode === 13){ + if (!Number.isNaN(Number(fadeOut.value))){ + this.flyoutInfo.regiondata!.fadeOut = Number(fadeOut.value); + fadeOut.placeholder = fadeOut.value +"ms"; + fadeOut.value = ""; + } + } + } render() { return (
-
-
+

Time:

@@ -282,12 +357,15 @@ export class Timeline extends CollectionSubView(Document) {

Fade-out

- - - - + + + + +
+
+
@@ -304,12 +382,12 @@ export class Timeline extends CollectionSubView(Document) { return

{this.toTime(element)}

; })}
-
+
{this._nodes.map(doc => { - return ; + return ; })}
diff --git a/src/client/views/nodes/Track.tsx b/src/client/views/nodes/Track.tsx index cf613a827..f3b92f20c 100644 --- a/src/client/views/nodes/Track.tsx +++ b/src/client/views/nodes/Track.tsx @@ -6,29 +6,32 @@ import { Doc, DocListCastAsync, DocListCast } from "../../../new_fields/Doc"; import { Document, listSpec, createSchema, makeInterface, defaultSpec } from "../../../new_fields/Schema"; import { FieldValue, Cast, NumCast, BoolCast } from "../../../new_fields/Types"; import { List } from "../../../new_fields/List"; -import { Keyframe, RegionData } from "./Keyframe"; +import { Keyframe, KeyframeFunc } from "./Keyframe"; import { FlyoutProps } from "./Timeline"; interface IProps { node: Doc; currentBarX: number; + changeCurrentBarX: (x:number) => void; setFlyout: (props:FlyoutProps) => any; } @observer export class Track extends React.Component { - @observable private _inner = React.createRef(); + @observable private _inner = React.createRef(); + @observable private _keys = ["x", "y", "width", "height", "panX", "panY", "scale"]; private _reactionDisposers: IReactionDisposer[] = []; private _selectionManagerChanged?: IReactionDisposer; - @observable private _keys = ["x", "y", "width", "height", "panX", "panY", "scale"]; - + @computed + private get regions() { + return Cast(this.props.node.regions, listSpec(Doc)) as List; + } componentWillMount() { this.props.node.regions = new List(); - console.log((Cast(this.props.node.regions, listSpec(Doc)) as List).length); } @action @@ -45,18 +48,21 @@ export class Track extends React.Component { } }); - // reaction(() => { - // let keys = Doc.allKeys(this.props.node); - // let x = keys.indexOf("keyframes"); - // let afterX = keys.slice(x + 1); - // let beforeX = keys.slice(0, x); - // keys = beforeX.concat(afterX); - // return keys.map(key => FieldValue(this.props.node[key])); - // }, data => { - // if (this.keyframes.length !== 0){ - // let kf:(Doc | undefined) = this.findKeyframe(this.props.currentBarX); - // } - // }); + reaction(() => { + let keys = Doc.allKeys(this.props.node); + return keys.map(key => FieldValue(this.props.node[key])); + }, data => { + let regiondata = this.findRegion(this.props.currentBarX); + if (regiondata){ + (Cast(regiondata.keyframes!, listSpec(Doc)) as List).forEach((kf) => { + kf = kf as Doc; + if(NumCast(kf.time!) === this.props.currentBarX){ + console.log("hoorayy!!!"); + } + }); + } + + }); } /** * removes reaction when the component is removed from the timeline @@ -72,21 +78,16 @@ export class Track extends React.Component { let region = this.findRegion(time); let leftkf: (Doc | undefined) = this.calcMinLeft(region!); let rightkf: (Doc | undefined) = this.calcMinRight(region!); - - if (leftkf && rightkf) { this.interpolate(leftkf, rightkf); } else if (leftkf) { - console.log("left exists"); + console.log("left exists"); console.log(leftkf.time); this._keys.forEach(k => { let data = leftkf!.key as Doc; this.props.node[k] = data[k]; }); } else if (rightkf) { - - console.log("right exists"); - console.log(rightkf.time); this._keys.forEach(k => { let data = rightkf!.key as Doc; this.props.node[k] = data[k]; @@ -95,11 +96,6 @@ export class Track extends React.Component { } - /** - * calculates the closest left keyframe, if there is one - * @param kfList: keyframe list - * @param time - */ @action calcMinLeft = (region: Doc): (Doc | undefined) => { //returns the time of the closet keyframe to the left let leftKf:(Doc| undefined) = undefined; @@ -114,11 +110,7 @@ export class Track extends React.Component { return leftKf; } - /** - * calculates the closest right keyframe, if there is one - * @param kfList: keyframe lsit - * @param time: time - */ + @action calcMinRight = (region: Doc): (Doc | undefined) => { //returns the time of the closest keyframe to the right let rightKf: (Doc|undefined) = undefined; @@ -133,15 +125,6 @@ export class Track extends React.Component { return rightKf; } - - - /** - * Linearly interpolates a document from time1 to time2 - * @param Doc that needs to be modified - * @param kf1 timeandposition of the first yellow bar - * @param kf2 timeandposition of the second yellow bar - * @param time time that you want to interpolate - */ @action interpolate = async (kf1: Doc, kf2: Doc) => { let node1 = kf1.key as Doc; @@ -158,13 +141,11 @@ export class Track extends React.Component { }); } - - @action findRegion(time: number): (Doc | undefined) { let foundRegion = undefined; this.regions.map(region => { - region = RegionData(region as Doc); + region = region as Doc; if (time >= NumCast(region.position) && time <= (NumCast(region.position) + NumCast(region.duration))) { foundRegion = region; } @@ -172,25 +153,29 @@ export class Track extends React.Component { return foundRegion; } - @action onInnerDoubleClick = (e: React.MouseEvent) => { let inner = this._inner.current!; let left = inner.getBoundingClientRect().left; let offsetX = Math.round(e.clientX - left); - let regiondata: Doc = new Doc(); //creating regiond ata + let regiondata: Doc = new Doc(); //creating regiondata regiondata.duration = 200; regiondata.position = offsetX; + regiondata.fadeIn = 20; + regiondata.fadeOut = 20; regiondata.keyframes = new List(); + let leftRegion = KeyframeFunc.findAdjacentRegion(KeyframeFunc.Direction.left, regiondata, this.regions); + let rightRegion = KeyframeFunc.findAdjacentRegion(KeyframeFunc.Direction.right, regiondata, this.regions); + if ((rightRegion && leftRegion && rightRegion.position - (leftRegion.position + leftRegion.duration) < regiondata.fadeIn + regiondata.fadeOut) || (rightRegion && rightRegion.position - regiondata.position < regiondata.fadeIn + regiondata.fadeOut)){ + return; + } else if (rightRegion && rightRegion.position - regiondata.position >= regiondata.fadeIn + regiondata.fadeOut){ + regiondata.duration = rightRegion.position - regiondata.position; + } this.regions.push(regiondata); } - @computed - private get regions() { - console.log((Cast(this.props.node.regions, listSpec(Doc)) as List).length + "from get"); - return Cast(this.props.node.regions, listSpec(Doc)) as List; - } + render() { return ( @@ -198,7 +183,7 @@ export class Track extends React.Component {
{this.regions.map((region) => { - return ; + return ; })}
-- cgit v1.2.3-70-g09d2