From 918b0567a071e4fcb61c2afed676c850f8616e89 Mon Sep 17 00:00:00 2001 From: andrewdkim Date: Wed, 10 Jul 2019 17:13:17 -0400 Subject: Interpolation and keyframe modifications --- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- src/client/views/nodes/Keyframe.tsx | 87 ++++++----- src/client/views/nodes/Timeline.tsx | 170 +++++++++++---------- src/client/views/nodes/Track.tsx | 74 +++++---- 4 files changed, 185 insertions(+), 148 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 1a99ac383..5ee16970b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -365,8 +365,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { + - ); } diff --git a/src/client/views/nodes/Keyframe.tsx b/src/client/views/nodes/Keyframe.tsx index 2f75a4b03..4de311057 100644 --- a/src/client/views/nodes/Keyframe.tsx +++ b/src/client/views/nodes/Keyframe.tsx @@ -14,6 +14,11 @@ import { number } from "prop-types"; import { CollectionSchemaView, CollectionSchemaPreview } from "../collections/CollectionSchemaView"; export namespace KeyframeFunc{ + export enum KeyframeType{ + fade = "fade", + default = "default", + new = "new" + } export enum Direction{ left = "left", right = "right" @@ -80,32 +85,22 @@ export class Keyframe extends React.Component { @action componentDidMount() { - let fadeIn = this.makeKeyData(this.regiondata.position + this.regiondata.fadeIn)!; - let fadeOut = this.makeKeyData(this.regiondata.position + this.regiondata.duration - this.regiondata.fadeOut)!; - let fadeInIndex = this.regiondata.keyframes!.indexOf(fadeIn); - let fadeOutIndex = this.regiondata.keyframes!.indexOf(fadeOut); - + let fadeIn = this.makeKeyData(this.regiondata.position + this.regiondata.fadeIn, KeyframeFunc.KeyframeType.fade)!; + let fadeOut = this.makeKeyData(this.regiondata.position + this.regiondata.duration - this.regiondata.fadeOut, KeyframeFunc.KeyframeType.fade)!; + let start = this.makeKeyData(this.regiondata.position, KeyframeFunc.KeyframeType.fade)!; + let finish = this.makeKeyData(this.regiondata.position + this.regiondata.duration, KeyframeFunc.KeyframeType.fade)!; (fadeIn.key! as Doc).opacity = 1; (fadeOut.key! as Doc).opacity = 1; - - this.regiondata.keyframes![fadeInIndex] =fadeIn; - this.regiondata.keyframes![fadeOutIndex] =fadeOut; - - - let start = this.makeKeyData(this.regiondata.position)!; - let finish = this.makeKeyData(this.regiondata.position + this.regiondata.duration)!; - - - let startIndex = this.regiondata.keyframes!.indexOf(start); - let finishIndex = this.regiondata.keyframes!.indexOf(finish); - (start.key! as Doc).opacity = 0.1; (finish.key! as Doc).opacity = 0.1; - + let fadeInIndex = this.regiondata.keyframes!.indexOf(fadeIn); + let fadeOutIndex = this.regiondata.keyframes!.indexOf(fadeOut); + let startIndex = this.regiondata.keyframes!.indexOf(start); + let finishIndex = this.regiondata.keyframes!.indexOf(finish); + this.regiondata.keyframes![fadeInIndex] =fadeIn; + this.regiondata.keyframes![fadeOutIndex] =fadeOut; this.regiondata.keyframes![startIndex] = start; this.regiondata.keyframes![finishIndex] = finish; - - } componentWillUnmount() { @@ -126,7 +121,7 @@ export class Keyframe extends React.Component { @action - makeKeyData = (kfpos: number) => { //Kfpos is mouse offsetX, representing time + makeKeyData = (kfpos: number, type:KeyframeFunc.KeyframeType = KeyframeFunc.KeyframeType.new) => { //Kfpos is mouse offsetX, representing time let hasData = false; this.regiondata.keyframes!.forEach(TK => { //TK is TimeAndKey TK = TK as Doc; @@ -136,8 +131,14 @@ export class Keyframe extends React.Component { }); if (!hasData) { let TK: Doc = new Doc(); - TK.time = kfpos; - TK.key = Doc.MakeCopy(this.props.node); + TK.time = kfpos; + if (type === KeyframeFunc.KeyframeType.fade){ + TK.key = new Doc(); + } else { + TK.key = Doc.MakeCopy(this.props.node, true); + console.log(toJS(TK.key)); + } + TK.type = type; this.regiondata.keyframes!.push(TK); return TK; } @@ -175,7 +176,6 @@ export class Keyframe extends React.Component { } } - @action onResizeLeft = (e: React.PointerEvent) => { e.preventDefault(); @@ -242,9 +242,9 @@ export class Keyframe extends React.Component { e.stopPropagation(); let bar = this._bar.current!; let offset = e.clientX - bar.getBoundingClientRect().left; - let position = NumCast(this.regiondata.position); - this.makeKeyData(Math.round(position + offset)); - this.props.changeCurrentBarX(NumCast(Math.round(position + offset))); + let position = NumCast(this.regiondata.position); + this.makeKeyData(Math.round(position + offset)); + this.props.changeCurrentBarX(NumCast(Math.round(position + offset))); //first move the keyframe to the correct location and make a copy so the correct file gets coppied } @action @@ -255,7 +255,25 @@ export class Keyframe extends React.Component { } - + @action + private createKeyframeJSX = (kf:Doc, type = KeyframeFunc.KeyframeType.default) => { + if (type === KeyframeFunc.KeyframeType.default){ + return ( +
+ {this.createDivider()} +
{this.moveKeyframe(e, kf as Doc);} } onContextMenu={(e:React.MouseEvent)=>{ + e.preventDefault(); + e.stopPropagation(); + console.log(toJS(kf.key)); + }}>
+
); + } + return ( +
+ {this.createDivider()} +
+ ); + } render() { return ( @@ -274,19 +292,8 @@ export class Keyframe extends React.Component { })}>
- {/*
{this.createDivider(KeyframeFunc.Direction.left)}
-
{this.createDivider(KeyframeFunc.Direction.right)}
*/} - {this.regiondata.keyframes!.map(kf => { - kf = kf as Doc; - return
- {this.createDivider()} -
{this.moveKeyframe(e, kf as Doc);} } onContextMenu={(e:React.MouseEvent)=>{ - e.preventDefault(); - e.stopPropagation(); - - }}>
-
; + return this.createKeyframeJSX(kf as Doc, (kf! as Doc).type as KeyframeFunc.KeyframeType); })} {this.createDivider(KeyframeFunc.Direction.left)} {this.createDivider(KeyframeFunc.Direction.right)} diff --git a/src/client/views/nodes/Timeline.tsx b/src/client/views/nodes/Timeline.tsx index 1c51dbcad..d25aa7953 100644 --- a/src/client/views/nodes/Timeline.tsx +++ b/src/client/views/nodes/Timeline.tsx @@ -4,7 +4,7 @@ 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 { observable, reaction, action, IReactionDisposer, observe, IObservableArray, computed, toJS, Reaction, IObservableObject, trace, autorun, runInAction } from "mobx"; import { Cast, NumCast } from "../../../new_fields/Types"; import { SelectionManager } from "../../util/SelectionManager"; import { List } from "../../../new_fields/List"; @@ -22,30 +22,30 @@ export interface FlyoutProps { x?: number; y?: number; display?: string; - regiondata?:Doc; - regions?:List; + 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 DEFAULT_TICK_SPACING: number = 50; private readonly MIN_CONTAINER_HEIGHT: number = 205; private readonly MAX_CONTAINER_HEIGHT: number = 800; - + @observable private _isMinimized = false; @observable private _tickSpacing = this.DEFAULT_TICK_SPACING; @observable private _scrubberbox = React.createRef(); - @observable private _scrubber = 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 _timeInput = React.createRef(); + @observable private _durationInput = React.createRef(); + @observable private _fadeInInput = React.createRef(); + @observable private _fadeOutInput = React.createRef(); @observable private _currentBarX: number = 0; @@ -59,12 +59,25 @@ 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", regiondata: new Doc(), regions: new List()}; + @observable private flyoutInfo: FlyoutProps = { x: 0, y: 0, display: "none", regiondata: new Doc(), regions: new List() }; private block = false; - @action componentDidMount() { + this.initialize(); + } + + componentWillMount() { + runInAction(() => { + //check if this is a video frame + for (let i = 0; i < this._time;) { + this._ticks.push(i); + i += 1000; + } + }); + } + + initialize = action(() => { let scrubber = this._scrubberbox.current!; this._boxLength = scrubber.getBoundingClientRect().width; let children = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc)); @@ -81,17 +94,12 @@ export class Timeline extends CollectionSubView(Document) { trackbox.style.width = `${this._boxLength}`; }); - //check if this is a video frame - for (let i = 0; i < this._time;) { - this._ticks.push(i); - i += 1000; - } document.addEventListener("pointerdown", this.closeFlyout); - } + }); - @action + @action changeCurrentBarX = (x: number) => { - this._currentBarX = x; + this._currentBarX = x; } @action onFlyoutDown = (e: React.PointerEvent) => { @@ -108,10 +116,10 @@ export class Timeline extends CollectionSubView(Document) { this.flyoutInfo.display = "none"; } - @action componentDidUpdate() { - this._time = 100001; + runInAction(() => this._time = 100001); } + componentWillUnmount() { document.removeEventListener("pointerdown", this.closeFlyout); } @@ -182,7 +190,7 @@ export class Timeline extends CollectionSubView(Document) { this._currentBarX = offset; } - + @action onPanDown = (e: React.PointerEvent) => { @@ -230,22 +238,22 @@ export class Timeline extends CollectionSubView(Document) { } } - @action - onTimelineDown = (e:React.PointerEvent) => { - e.preventDefault(); - e.stopPropagation(); - document.addEventListener("pointermove", this.onTimelineMove); - document.addEventListener("pointerup", () => {document.removeEventListener("pointermove", this.onTimelineMove);}); + @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 + 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 @@ -292,54 +300,54 @@ 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"; + 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"; + @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))){ + @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.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 = ""; + @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 = ""; } } } @@ -348,23 +356,23 @@ export class Timeline extends CollectionSubView(Document) { return (
-
- -
-

Time:

-

Duration:

-

Fade-in

-

Fade-out

-
-
- - - - - -
- +
+ +
+

Time:

+

Duration:

+

Fade-in

+

Fade-out

+
+ + + + + +
+ +
@@ -387,7 +395,7 @@ export class Timeline extends CollectionSubView(Document) {
{this._nodes.map(doc => { - return ; + return ; })}
diff --git a/src/client/views/nodes/Track.tsx b/src/client/views/nodes/Track.tsx index e6d5189af..5f7d7619c 100644 --- a/src/client/views/nodes/Track.tsx +++ b/src/client/views/nodes/Track.tsx @@ -25,6 +25,7 @@ interface IProps { export class Track extends React.Component { @observable private _inner = React.createRef(); @observable private _keys = ["x", "y", "width", "height", "panX", "panY", "scale", "opacity"]; + @observable private _onInterpolate:boolean = false; private _reactionDisposers: IReactionDisposer[] = []; private _selectionManagerChanged?: IReactionDisposer; @@ -43,35 +44,43 @@ export class Track extends React.Component { componentDidMount() { this.props.node.hidden = true; this.props.node.opacity = 1; - reaction(() => this.props.currentBarX, () => { - let region: (Doc | undefined) = this.findRegion(this.props.currentBarX); - if (region !== undefined) { - this.props.node.hidden = false; - this.timeChange(this.props.currentBarX); + + this._reactionDisposers.push(reaction(() => this.props.currentBarX, () => { + let regiondata: (Doc | undefined) = this.findRegion(this.props.currentBarX); + if (regiondata) { + this.timeChange(this.props.currentBarX); //first interpolates over to that position; + (Cast(regiondata.keyframes!, listSpec(Doc)) as List).forEach((kf) => { + kf = kf as Doc; + if(NumCast(kf.time!) === this.props.currentBarX && kf.type !== KeyframeFunc.KeyframeType.fade){ + kf.key = Doc.MakeCopy(this.props.node, true); + if (kf.type === KeyframeFunc.KeyframeType.new){ + kf.type = KeyframeFunc.KeyframeType.default; + } + } + }); + this.props.node.hidden = false; } else { this.props.node.hidden = true; } - }); - - reaction(() => { + })); + this._reactionDisposers.push(reaction(() => { if (!this._onInterpolate){ let keys = Doc.allKeys(this.props.node); return keys.map(key => FieldValue(this.props.node[key])); } }, data => { - let regiondata = this.findRegion(this.props.currentBarX); + 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){ - kf.key = Doc.MakeCopy(this.props.node); - console.log("key updated"); - } + if(NumCast(kf.time!) === this.props.currentBarX && kf.type !== KeyframeFunc.KeyframeType.fade){ + kf.key = Doc.MakeCopy(this.props.node, true); + } }); } - - }); + })); } + /** * removes reaction when the component is removed from the timeline */ @@ -81,24 +90,22 @@ export class Track extends React.Component { } - @observable private _onInterpolate:boolean = false; @action timeChange = async (time: number) => { let region = this.findRegion(time); let leftkf: (Doc | undefined) = this.calcMinLeft(region!); let rightkf: (Doc | undefined) = this.calcMinRight(region!); let currentkf: (Doc | undefined) = this.calcCurrent(region!); - if (currentkf){ + if (currentkf && (currentkf.type !== KeyframeFunc.KeyframeType.new)){ this._onInterpolate = true; - console.log(this.filterKeys(Doc.allKeys(currentkf.key as Doc))); this.filterKeys(Doc.allKeys(currentkf.key as Doc)).forEach(k => { - console.log(k); this.props.node[k] = (currentkf!.key as Doc)[k]; }); this._onInterpolate = false; } else if (leftkf && rightkf) { this.interpolate(leftkf, rightkf); - } else if (leftkf) { + } else if (leftkf) { + console.log(Doc.GetProto(leftkf!.key as Doc)); this.filterKeys(Doc.allKeys(leftkf.key as Doc)).forEach(k => { this.props.node[k] = (leftkf!.key as Doc)[k]; }); @@ -164,17 +171,32 @@ export class Track extends React.Component { @action interpolate = async (kf1: Doc, kf2: Doc) => { - console.log("interpolation"); let node1 = kf1.key as Doc; let node2 = kf2.key as Doc; - + let mainNode = new Doc(); const dif_time = NumCast(kf2.time) - NumCast(kf1.time); const ratio = (this.props.currentBarX - NumCast(kf1.time)) / dif_time; //linear - this._keys.forEach(key => { - const diff = NumCast(node2[key]) - NumCast(node1[key]); - const adjusted = diff * ratio; - this.props.node[key] = NumCast(node1[key]) + adjusted; + let keys = []; + if (this.filterKeys(Doc.allKeys(node1)).length === Math.max(this.filterKeys(Doc.allKeys(node1)).length, this.filterKeys(Doc.allKeys(node2)).length )){ + keys = this.filterKeys(Doc.allKeys(node1)); + mainNode = node1; + } else { + keys = this.filterKeys(Doc.allKeys(node2)); + mainNode = node2; + } + + + keys.forEach(key => { + if (node1[key] && node2[key] && typeof(node1[key]) === "number" && typeof(node2[key]) === "number"){ + const diff = NumCast(node2[key]) - NumCast(node1[key]); + const adjusted = diff * ratio; + this.props.node[key] = NumCast(node1[key]) + adjusted; + } else if (key === "title") { + Doc.SetOnPrototype(this.props.node, "title", mainNode[key] as string); + } else if (key === "documentText"){ + Doc.SetOnPrototype(this.props.node, "documentText", mainNode[key] as string); + } }); } -- cgit v1.2.3-70-g09d2