diff options
-rw-r--r-- | src/client/views/nodes/Keyframe.scss | 2 | ||||
-rw-r--r-- | src/client/views/nodes/Keyframe.tsx | 88 | ||||
-rw-r--r-- | src/client/views/nodes/Timeline.scss | 3 | ||||
-rw-r--r-- | src/client/views/nodes/Timeline.tsx | 9 | ||||
-rw-r--r-- | src/client/views/nodes/Track.tsx | 311 |
5 files changed, 173 insertions, 240 deletions
diff --git a/src/client/views/nodes/Keyframe.scss b/src/client/views/nodes/Keyframe.scss index b3a950afa..2410dceaf 100644 --- a/src/client/views/nodes/Keyframe.scss +++ b/src/client/views/nodes/Keyframe.scss @@ -63,6 +63,7 @@ position: absolute; background-color:black; cursor: col-resize; + pointer-events:none; } .keyframe{ height:100%; @@ -77,6 +78,7 @@ background-color:white; border:3px solid green; z-index: 1000; + pointer-events: none; position:absolute; } diff --git a/src/client/views/nodes/Keyframe.tsx b/src/client/views/nodes/Keyframe.tsx index 2ed63a66e..352e5e0d7 100644 --- a/src/client/views/nodes/Keyframe.tsx +++ b/src/client/views/nodes/Keyframe.tsx @@ -4,9 +4,9 @@ import "./Keyframe.scss"; import "./../globalCssVariables.scss"; import { observer } from "mobx-react"; import { observable, reaction, action, IReactionDisposer, observe, IObservableArray, computed, toJS } from "mobx"; -import { Doc } from "../../../new_fields/Doc"; +import { Doc, DocListCast } from "../../../new_fields/Doc"; import { auto } from "async"; -import { Cast, FieldValue, StrCast } from "../../../new_fields/Types"; +import { Cast, FieldValue, StrCast, NumCast } from "../../../new_fields/Types"; import { StandardLonghandProperties } from "csstype"; import { runInThisContext } from "vm"; import { DateField } from "../../../new_fields/DateField"; @@ -15,41 +15,66 @@ import { DocumentView } from "./DocumentView"; import { anchorPoints, Flyout } from "../TemplateMenu"; import { LinkMenu } from "./LinkMenu"; import { faCircle } from "@fortawesome/free-solid-svg-icons"; -import { node } from "prop-types"; +import { node, number } from "prop-types"; +import { List } from "../../../new_fields/List"; +import { createSchema, defaultSpec, makeInterface, listSpec } from "../../../new_fields/Schema"; +import { CollectionBaseView } from "../collections/CollectionBaseView"; +import { notDeepEqual } from "assert"; interface IProp { node: Doc; - currentBarX: number; + keyframedata: Doc; } +const KeyDataSchema = createSchema({}); +const TimeAndKeyDataSchema = createSchema({ + time: defaultSpec("number", 0), + key: Doc +}); + +type KeyData = makeInterface<[typeof KeyDataSchema]>; +type TimeAndKey = makeInterface<[typeof TimeAndKeyDataSchema]>; +const KeyData = makeInterface(KeyDataSchema); +const TimeAndKey = makeInterface(TimeAndKeyDataSchema); + + +const KeyframeDataSchema = createSchema({ + position: defaultSpec("number", 0), + duration: defaultSpec("number", 0), + kfs: listSpec(Doc) +}); + +type KeyframeData = makeInterface<[typeof KeyframeDataSchema]>; +export const KeyframeData = makeInterface(KeyframeDataSchema); + + @observer export class Keyframe extends React.Component<IProp> { @observable private _display:string = "none"; @observable private _duration:number = 200; @observable private _bar = React.createRef<HTMLDivElement>(); - @observable private _data:Doc = new Doc(); - @observable private _position:number = 0; @observable private _keyframes:number[] = []; + private _reactionDisposers: IReactionDisposer[] = []; + private _selectionManagerChanged?: IReactionDisposer; + + @action componentDidMount() { - this._position = this.props.node.position as number; - reaction (() => this.props.currentBarX, () => { - console.log("reaction triggered!"); - if (this.props.currentBarX !== this._position){ - this.props.node.hidden = true; - } else { - this.props.node.hidden = false; - } - }); - } + // need edge case here when keyframe data already exists when loading.....................; + // let keyframes = Cast(this.props.node.keyframes, listSpec(Doc)) as List<Doc>; + // if keyframes.indexOf() + + let keyframes = Cast(this.props.node.keyframes, listSpec(Doc)) as List<Doc>; + keyframes.indexOf(this.props.keyframedata); + } componentWillUnmount() { @@ -69,6 +94,31 @@ export class Keyframe extends React.Component<IProp> { // //this._display = "none"; // } + @computed + get keyframes() { + return Cast(this.props.node.keyframes, listSpec(Doc)) as List<Doc>; + } + @action + makeKeyData = (kfpos: number) => { //Kfpos is mouse offsetX, representing time + let hasData = false; + let index = this.keyframes.indexOf(this.props.keyframedata); + let kfd = KeyframeData(this.keyframes[index] as Doc); + kfd.kfs!.forEach(TK => { //TK is TimeAndKey + TK = TK as Doc; + if (TK.time === kfpos){ + hasData = true; + } + }); + if (!hasData){ + let TK:Doc = new Doc(); + TK.time = kfpos; + TK.key = this.props.node; + kfd.kfs!.push(TK); + (this.keyframes[index] as Doc) = TK; + } + + } + @action onBarPointerDown = (e: React.PointerEvent) => { e.preventDefault(); @@ -149,14 +199,16 @@ export class Keyframe extends React.Component<IProp> { createKeyframe = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); - let mouse = e.nativeEvent; + let mouse = e.nativeEvent; + let position = NumCast(this.props.keyframedata.position); this._keyframes.push(mouse.offsetX); + this.makeKeyData(position); } render() { return ( <div> - <div className="bar" ref={this._bar} style={{ transform: `translate(${this._position}px)`, width:`${this._duration}px`}} onPointerDown={this.onBarPointerDown} onDoubleClick={this.createKeyframe}> + <div className="bar" ref={this._bar} style={{ transform: `translate(${this.props.keyframedata.position}px)`, width:`${this._duration}px`}} onPointerDown={this.onBarPointerDown} onDoubleClick={this.createKeyframe}> <div className="leftResize" onPointerDown={this.onResizeLeft} ></div> <div className="rightResize" onPointerDown={this.onResizeRight}></div> {/* <div className="menubox" style={{display: this._display}}></div> */} diff --git a/src/client/views/nodes/Timeline.scss b/src/client/views/nodes/Timeline.scss index b74be1fee..d89fce7fc 100644 --- a/src/client/views/nodes/Timeline.scss +++ b/src/client/views/nodes/Timeline.scss @@ -31,7 +31,6 @@ width: calc(100% - 140px); overflow: hidden; padding:0px; - // box-shadow: 0px 10px 20px grey inset; .scrubberbox{ position:absolute; @@ -73,7 +72,7 @@ overflow:hidden; background-color:white; position:absolute; - + box-shadow: -10px 0px 10px 10px grey; } } diff --git a/src/client/views/nodes/Timeline.tsx b/src/client/views/nodes/Timeline.tsx index a06ca43eb..fc3b8f454 100644 --- a/src/client/views/nodes/Timeline.tsx +++ b/src/client/views/nodes/Timeline.tsx @@ -136,7 +136,6 @@ export class Timeline extends CollectionSubView(Document){ let scrubberbox = this._scrubberbox.current!; let offset = scrubberbox.scrollLeft + e.clientX - scrubberbox.getBoundingClientRect().left; this._currentBarX = offset; - ; } @action @@ -221,13 +220,17 @@ export class Timeline extends CollectionSubView(Document){ </div> <div className="info-container" ref ={this._infoContainer}> <div className="scrubberbox" ref ={this._scrubberbox} onClick={this.onScrubberClick}> - {this._ticks.map(element => {return <div className="tick" style={{transform:`translate(${element / 20}px)`, position:"absolute", pointerEvent:"none"}}> <p>{this.toTime(element)}</p></div>})} + {this._ticks.map(element => { + return <div className="tick" style={{transform:`translate(${element / 20}px)`, position:"absolute", pointerEvent:"none"}}> <p>{this.toTime(element)}</p></div>; + })} </div> <div className="scrubber" onPointerDown = {this.onScrubberDown} style={{transform:`translate(${this._currentBarX}px)`}}> <div className="scrubberhead"></div> </div> <div className="trackbox" ref={this._trackbox} onPointerDown={this.onPanDown}> - {this._nodes.map(doc => {return <Track node={(doc as any).value() as Doc} currentBarX = {this._currentBarX}/>;})} + {this._nodes.map(doc => { + return <Track node={(doc as any).value() as Doc} currentBarX = {this._currentBarX}/>; + })} </div> </div> <div className="title-container" ref={this._titleContainer}> diff --git a/src/client/views/nodes/Track.tsx b/src/client/views/nodes/Track.tsx index 855ccedd5..a774fe2d3 100644 --- a/src/client/views/nodes/Track.tsx +++ b/src/client/views/nodes/Track.tsx @@ -22,8 +22,9 @@ import { CompileScript } from "../../util/Scripting"; import { FieldView } from "./FieldView"; import { promises } from "fs"; import { Tapable } from "tapable"; -import { Keyframe } from "./Keyframe"; +import { Keyframe, KeyframeData } from "./Keyframe"; import { timingSafeEqual } from "crypto"; +import { node } from "prop-types"; type Data = List<Doc>; type Keyframes = List<List<Doc>>; @@ -43,26 +44,20 @@ type TimeAndPosition = makeInterface<[typeof TimeAndPositionSchema]>; const TimeAndPosition = makeInterface(TimeAndPositionSchema); -interface props{ +interface IProp{ node: Doc; currentBarX: number; } @observer -export class Track extends React.Component<props> { +export class Track extends React.Component<IProp> { @observable private _inner = React.createRef<HTMLDivElement>(); - @observable private _timeInput = React.createRef<HTMLInputElement>(); - @observable private _playButton = React.createRef<HTMLButtonElement>(); - @observable private _isRecording: Boolean = false; private _reactionDisposers: IReactionDisposer[] = []; private _selectionManagerChanged?: IReactionDisposer; @observable private _currentBarX: number = 0; @observable private _keys = ["x", "y", "width", "height", "panX", "panY", "scale"]; - @observable private _bars: { x: number, doc: Doc }[] = []; - @observable private _barMoved: boolean = false; - @observable private _length:number = 0; // @computed private get _keyframes() { // return Cast(this.props.Document.keyframes, listSpec(Doc)) as any as List<List<Doc>>; @@ -73,160 +68,45 @@ export class Track extends React.Component<props> { // return Cast(this.props.Document[this.props.fieldKey], listSpec(Doc))!; // } - /** - * when the record button is pressed - * @param e MouseEvent - */ - // @action - // onRecord = (e: React.MouseEvent) => { - - // let children = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc)); - // if (!children) { - // return; - // } - // let childrenList = ((children[Self] as any).__fields); - // const addReaction = (node: Doc) => { - // node = (node as any).value(); - // return reaction(() => { - // return this._keys.map(key => FieldValue(node[key])); - // }, async data => { - // if (!this._barMoved) { - // if (this._data.indexOf(node) !== -1 && this._keyframes.length < this._data.length) { - // let timeandpos = this.setTimeAndPos(node); - // let info: List<Doc> = new List<Doc>(new Array<Doc>(1000)); //kinda weird - // info[this._currentBarX] = timeandpos; - // this._keyframes.push(info); - // this._bars = []; - // this._bars.push({ x: this._currentBarX, doc: node }); - // } else { - // let index = this._data.indexOf(node); - // if (this._keyframes[index][this._currentBarX] !== undefined) { //when node is in data, but doesn't have data for this specific time. - // let timeandpos = this.setTimeAndPos(node); - // this._keyframes[index][this._currentBarX] = timeandpos; - // this._bars.push({ x: this._currentBarX, doc: node }); - // } else { //when node is in data, and has data for this specific time - // let timeandpos = this.setTimeAndPos(node); - // this._keyframes[index][this._currentBarX] = timeandpos; - // } - // } - // } - // }); - // }; - // observe(childrenList as IObservableArray<Doc>, change => { - // if (change.type === "update") { - // this._reactionDisposers[change.index](); - // this._reactionDisposers[change.index] = addReaction(change.newValue); - // } else { - // let removed = this._reactionDisposers.splice(change.index, change.removedCount, ...change.added.map(addReaction)); - // removed.forEach(disp => disp()); - // } - // }, true); - - // } - - /** - * sets the time and pos schema doc, given a node - * @param doc (node) - */ - @action - setTimeAndPos = (node: Doc) => { - let pos: Position = Position(node); - let timeandpos = new Doc(); - const newPos = new Doc(); - this._keys.forEach(key => newPos[key] = pos[key]); - timeandpos.position = newPos; - timeandpos.time = this._currentBarX; - return timeandpos; - } - /** - * given time, finds the closest left and right keyframes, and if found, interpolates to that position. - */ @action timeChange = async (time: number) => { - const docs = this._data; - docs.forEach(async (oneDoc, i) => { - let OD: Doc = await oneDoc; - let leftKf!: TimeAndPosition; - let rightKf!: TimeAndPosition; - let singleFrame: Doc | undefined = undefined; - if (i >= this._keyframes.length) { - return; - } - let oneKf = this._keyframes[i]; - oneKf.forEach((singleKf) => { - singleKf = singleKf as Doc; - if (singleKf !== undefined) { - let leftMin = Infinity; - let rightMin = Infinity; - if (singleKf.time !== time) { //choose closest time neighbors - leftMin = this.calcMinLeft(oneKf, time); - if (leftMin !== Infinity) { - let kf = this._keyframes[i][leftMin] as Doc; - leftKf = TimeAndPosition(kf); - } - rightMin = this.calcMinRight(oneKf, time); - if (rightMin !== Infinity) { - let kf = this._keyframes[i][rightMin] as Doc; - rightKf = TimeAndPosition(kf); - } - } else { - singleFrame = singleKf; - if (true || oneKf[i] !== undefined) { - this._keys.map(key => { - let temp = OD[key]; - FieldValue(OD[key]); - }); - } - } - } - }); - if (!singleFrame) { - if (leftKf && rightKf) { - this.interpolate(OD, leftKf, rightKf, this._currentBarX); - } else if (leftKf) { - this._keys.map(async key => { - let pos = (await leftKf.position)!; - if (pos === undefined) { ///something is probably wrong here - return; - } - OD[key] = pos[key]; - }); - } else if (rightKf) { - this._keys.map(async key => { - let pos = (await rightKf.position)!; - if (pos === undefined) { //something is probably wrong here - return; - } - OD[key] = pos[key]; - }); - } - } - }); + let leftkf: (Doc | undefined) = this.calcMinLeft(time); + let rightkf: (Doc | undefined) = this.calcMinRight(time); + if (this.props.node.keyframedata!.kfs!.length < 2){ + return; + } + if (leftkf && rightkf){ + this.interpolate(leftkf, rightkf, time); + } else if(leftkf){ + + } else if (rightkf){ + + } } + /** * calculates the closest left keyframe, if there is one * @param kfList: keyframe list * @param time */ @action - calcMinLeft = (kfList: List<Doc>, time: number): number => { //returns the time of the closet keyframe to the left - let counter: number = Infinity; - let leftMin: number = Infinity; - kfList.forEach((kf) => { + calcMinLeft = (time: number): (Doc|undefined) => { //returns the time of the closet keyframe to the left + let leftKf:Doc = new Doc(); + leftKf.time = Infinity; + this._data.forEach((kf) => { kf = kf as Doc; - if (kf !== undefined && NumCast(kf.time) < time) { - let diff: number = Math.abs(NumCast(kf.time) - time); - if (diff < counter) { - counter = diff; - leftMin = NumCast(kf.time); - } + if (NumCast(kf.time) < time && NumCast(leftKf.time) > NumCast(kf.time)) { + leftKf = kf; } }); - return leftMin; + if (NumCast(leftKf.time) === Infinity){ + return undefined; + } + return leftKf; } /** @@ -235,24 +115,24 @@ export class Track extends React.Component<props> { * @param time: time */ @action - calcMinRight = (kfList: List<Doc>, time: number): number => { //returns the time of the closest keyframe to the right - let counter: number = Infinity; - let rightMin: number = Infinity; - kfList.forEach((kf) => { + calcMinRight = (time: number): (Doc|undefined) => { //returns the time of the closest keyframe to the right + let rightKf:Doc = new Doc(); + rightKf.time = Infinity; + this._data.forEach((kf) => { kf = kf as Doc; - if (kf !== undefined && NumCast(kf.time) > time) { - let diff: number = Math.abs(NumCast(kf.time!) - time); - if (diff < counter) { - counter = diff; - rightMin = NumCast(kf.time); - } + if (NumCast(kf.time) > time && NumCast(rightKf.time) > NumCast(kf.time)) { + rightKf = kf; } }); - return rightMin; + if (NumCast(rightKf.time) === Infinity){ + return undefined; + } + 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 @@ -260,73 +140,65 @@ export class Track extends React.Component<props> { * @param time time that you want to interpolate */ @action - interpolate = async (doc: Doc, kf1: TimeAndPosition, kf2: TimeAndPosition, time: number) => { - const keyFrame1 = (await kf1.position)!; - const keyFrame2 = (await kf2.position)!; + interpolate = async (kf1: Doc, kf2: Doc, time: number) => { + const keyFrame1 = (await kf1)!; + const keyFrame2 = (await kf2)!; - if (keyFrame1 === undefined || keyFrame2 === undefined) { - return; - } + const dif_time = NumCast(kf2.time) - NumCast(kf1.time); + const ratio = (time - NumCast(kf1.time)) / dif_time; //linear - const dif_time = kf2.time - kf1.time; - const ratio = (time - kf1.time) / dif_time; //linear this._keys.forEach(key => { const diff = NumCast(keyFrame2[key]) - NumCast(keyFrame1[key]); const adjusted = diff * ratio; - doc[key] = NumCast(keyFrame1[key]) + adjusted; + this.props.node[key] = NumCast(keyFrame1[key]) + adjusted; }); } - - - /** - * called when you input a certain time on the input bar and press enter. The green bar will move to that location. - * @param e keyboard event - */ - @action - onTimeEntered = (e: React.KeyboardEvent) => { - if (this._timeInput.current) { - if (e.keyCode === 13) { - let input = parseInt(this._timeInput.current.value) || 0; - this._currentBarX = input; - this.timeChange(input); - } - } - } - - - @action componentDidMount() { - - - // if (!this._keyframes) { - // this.props.Document.keyframes = new List<List<Doc>>(); - // } - - // let keys = Doc.allKeys(this.props.node); - // return reaction(() => keys.map(key => FieldValue(this.props.node[key])), data => { - // console.log(data); - // }); - - + this.props.node.hidden = true; + this.props.node.keyframes = new List<Doc>(); + let keyframes = Cast(this.props.node.keyframes, listSpec(Doc)) as List<Doc>; reaction (() => this.props.currentBarX, () => { - console.log("react"); - this._data.forEach((datum) => { - if (this.props.currentBarX >= (datum.begin as number) && this.props.currentBarX <= (datum.end as number)){ - this.props.node.hidden = false; - } else { - this.props.node.hidden = true; + keyframes.forEach((datum) => { + datum = KeyframeData(datum as Doc); + if (keyframes.length !== 0){ + let kf:(Doc | undefined) = this.findKeyframe(this.props.currentBarX); + if (kf !== undefined){ + this.props.node.hidden = false; + console.log(toJS(kf.kfs!)); + } } }); - // if (this.props.currentBarX !== this._position){ - // this.props.node.hidden = true; - // } else { - // this.props.node.hidden = false; - // } + }); + + 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 (keyframes.length !== 0){ + let kf:(Doc | undefined) = this.findKeyframe(this.props.currentBarX); + console.log(kf + "from reaction wheh moving"); + } }); } + + @action + findKeyframe(time:number): (Doc | undefined){ + let foundKeyframe = undefined; + (Cast(this.props.node.keyframes, listSpec(Doc)) as List<Doc>).map(kf => { + kf = kf as Doc; + if (time >= NumCast(kf.position) && time <= (NumCast(kf.position) + NumCast(kf.duration))){ + foundKeyframe = kf; + } + }); + return foundKeyframe; + } /** * removes reaction when the component is removed from the timeline */ @@ -336,18 +208,23 @@ export class Track extends React.Component<props> { } @observable private _keyframes: JSX.Element[] = []; - @observable private _data: Doc[] = []; + + @computed + get keyframes() { + return Cast(this.props.node.keyframes, listSpec(Doc)) as List<Doc>; + } + @action onInnerDoubleClick = (e: React.MouseEvent) => { let inner = this._inner.current!; let left = inner.getBoundingClientRect().left; let offsetX = Math.round(e.clientX - left); - this.props.node.position = offsetX; - let datum = new Doc(); - datum.begin = offsetX; - datum.end = offsetX + 200; - this._data.push(datum); - this._keyframes.push(<Keyframe node={this.props.node} currentBarX={this.props.currentBarX}/>); + let keyframedata:Doc = new Doc(); + keyframedata.duration = 200; + keyframedata.position = offsetX; + keyframedata.kfs = new List<Doc>(); + this.keyframes.push(keyframedata); + this._keyframes.push(<Keyframe node={this.props.node} keyframedata={keyframedata}/>); } render() { |