diff options
author | andrewdkim <adkim414@gmail.com> | 2019-07-02 16:49:56 -0400 |
---|---|---|
committer | andrewdkim <adkim414@gmail.com> | 2019-07-02 16:49:56 -0400 |
commit | 11326788a7212eca86e06ee8f2c9a4aa65ab6070 (patch) | |
tree | 4da9e408a7989c5ab9f4bf410f3c9c7deb2afe5f | |
parent | e7e7f21510103394239f96bb3e398e5509a7533e (diff) |
context menu + repositioning + interpolation and data
-rw-r--r-- | src/client/views/nodes/Keyframe.scss | 1 | ||||
-rw-r--r-- | src/client/views/nodes/Keyframe.tsx | 103 | ||||
-rw-r--r-- | src/client/views/nodes/Timeline.scss | 29 | ||||
-rw-r--r-- | src/client/views/nodes/Timeline.tsx | 161 | ||||
-rw-r--r-- | src/client/views/nodes/Track.tsx | 24 |
5 files changed, 180 insertions, 138 deletions
diff --git a/src/client/views/nodes/Keyframe.scss b/src/client/views/nodes/Keyframe.scss index 44850cd64..19a61bde1 100644 --- a/src/client/views/nodes/Keyframe.scss +++ b/src/client/views/nodes/Keyframe.scss @@ -78,7 +78,6 @@ 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 263df0fd5..10ae2f427 100644 --- a/src/client/views/nodes/Keyframe.tsx +++ b/src/client/views/nodes/Keyframe.tsx @@ -10,28 +10,18 @@ import { List } from "../../../new_fields/List"; import { createSchema, defaultSpec, makeInterface, listSpec } from "../../../new_fields/Schema"; import { any } from "bluebird"; import { FlyoutProps } from "./Timeline"; -import { exportDefaultSpecifier } from "babel-types"; enum Direction { left = "left", right = "right" } -interface IProp { +interface IProps { node: Doc; RegionData: Doc; setFlyout:(props:FlyoutProps) => any; } - -const KeyframeDataSchema = createSchema({ - time: defaultSpec("number", 0), - key: Doc -}); -type KeyframeData = makeInterface<[typeof KeyframeDataSchema]>; -const KeyframeData = makeInterface(KeyframeDataSchema); - - const RegionDataSchema = createSchema({ position: defaultSpec("number", 0), duration: defaultSpec("number", 0), @@ -42,18 +32,14 @@ export const RegionData = makeInterface(RegionDataSchema); @observer -export class Keyframe extends React.Component<IProp> { +export class Keyframe extends React.Component<IProps> { - @observable private _display: string = "none"; - @observable private _bar = React.createRef<HTMLDivElement>(); - @observable private _keyframes: number[] = []; - @observable private position: number = 0; + @observable private _bar = React.createRef<HTMLDivElement>(); @observable private fadein: number = 0; @observable private fadeout: number = 0; @action componentDidMount() { - // need edge case here when keyframe data already exists when loading.....................; } @@ -88,7 +74,6 @@ export class Keyframe extends React.Component<IProp> { TK.key = Doc.MakeCopy(this.props.node); this.regiondata.keyframes!.push(TK); } - } @action @@ -102,20 +87,12 @@ export class Keyframe extends React.Component<IProp> { 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:this.regiondata.position + 130, y: bar.getBoundingClientRect().bottom,display:"block", time: this.regiondata.position, duration:this.regiondata.duration}); - let removeFlyout = (e:PointerEvent) => { - if (e.which === 1){ - console.log("wut"); - this.props.setFlyout({display:"none"}); - document.removeEventListener("pointerdown", removeFlyout); - } - }; - document.addEventListener("pointerdown", removeFlyout); - } + }// 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}); + // } } @action @@ -169,7 +146,6 @@ export class Keyframe extends React.Component<IProp> { document.addEventListener("pointermove", this.onDragResizeLeft); document.addEventListener("pointerup", () => { document.removeEventListener("pointermove", this.onDragResizeLeft); - }); } @@ -180,7 +156,6 @@ export class Keyframe extends React.Component<IProp> { document.addEventListener("pointermove", this.onDragResizeRight); document.addEventListener("pointerup", () => { document.removeEventListener("pointermove", this.onDragResizeRight); - }); } @@ -193,10 +168,6 @@ export class Keyframe extends React.Component<IProp> { let offset = e.clientX - barX; this.regiondata.duration -= offset; this.regiondata.position += offset; - this.regiondata.keyframes!.forEach(kf => { - kf = kf as Doc; - kf.time = NumCast(kf.time) + offset; - }); } @@ -207,11 +178,10 @@ export class Keyframe extends React.Component<IProp> { let bar = this._bar.current!; let barX = bar.getBoundingClientRect().right; let offset = e.clientX - barX; - console.log(offset); this.regiondata.duration += offset; } - createDivider = (type?: string): JSX.Element => { + createDivider = (type?: Direction): JSX.Element => { if (type === "left") { return <div className="divider" style={{ right: "0px" }}></div>; } else if (type === "right") { @@ -224,33 +194,48 @@ export class Keyframe extends React.Component<IProp> { createKeyframe = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); - let mouse = e.nativeEvent; + let bar = this._bar.current!; + let offset = e.clientX - bar.getBoundingClientRect().left; let position = NumCast(this.regiondata.position); - this._keyframes.push(mouse.offsetX); - this.makeKeyData(position + mouse.offsetX); + this.makeKeyData(position + offset); } + @action + moveKeyframe = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + + } render() { return ( <div> - <div className="bar" ref={this._bar} style={{ transform: `translate(${this.regiondata.position}px)`, width: `${this.regiondata.duration}px` }} - onPointerDown={this.onBarPointerDown} - onDoubleClick={this.createKeyframe}> - <div className="leftResize" onPointerDown={this.onResizeLeft} ></div> - <div className="rightResize" onPointerDown={this.onResizeRight}></div> - <div className="fadeLeft" style={{ width: `${20}px` }}>{this.createDivider("left")}</div> - <div className="fadeRight" style={{ width: `${20}px` }}>{this.createDivider("right")}</div> - {this._keyframes.map(kf => { - return <div className="keyframe" style={{ left: `${kf}px` }}> - {this.createDivider()} - <div className="keyframeCircle"></div> - </div>; - })} - {this.createDivider("left")} - {this.createDivider("right")} - </div> + <div className="bar" ref={this._bar} style={{ transform: `translate(${this.regiondata.position}px)`, width: `${this.regiondata.duration}px` }} + 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"}); + } + })}> + <div className="leftResize" onPointerDown={this.onResizeLeft} ></div> + <div className="rightResize" onPointerDown={this.onResizeRight}></div> + <div className="fadeLeft" style={{ width: `${20}px` }}>{this.createDivider(Direction.left)}</div> + <div className="fadeRight" style={{ width: `${20}px` }}>{this.createDivider(Direction.right)}</div> + {this.regiondata.keyframes!.map(kf => { + kf = kf as Doc; + return <div className="keyframe" style={{ left: `${NumCast(kf.time) - this.regiondata.position}px` }}> + {this.createDivider()} + <div className="keyframeCircle" onPointerDown={this.moveKeyframe}></div> + </div>; + })} + {this.createDivider(Direction.left)} + {this.createDivider(Direction.right)} + </div> </div> ); } diff --git a/src/client/views/nodes/Timeline.scss b/src/client/views/nodes/Timeline.scss index 44b6cf03e..a416cbb1c 100644 --- a/src/client/views/nodes/Timeline.scss +++ b/src/client/views/nodes/Timeline.scss @@ -1,5 +1,11 @@ @import "./../globalCssVariables.scss"; +.minimize{ + position:relative; + z-index: 1000; + height: 30px; + width: 100px; +} .flyout-container{ background-color: transparent; position:absolute; @@ -15,17 +21,22 @@ width: 100%; height: 100%; } - div{ - color: white; - position:absolute; - top:30px; - left:0px; + .input-container{ + position: absolute; + right:0px; + top: 30px; + width: 70px; + input{ + width: 100%; + } } - input{ + .text-container{ position:absolute; - width: 40px; - right:0px; + top:30px; + left:0px; + color:white } + } @@ -35,6 +46,8 @@ position:absolute; background-color: $light-color-secondary; box-shadow: 0px 10px 20px; + 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 8050830fa..fd235fad3 100644 --- a/src/client/views/nodes/Timeline.tsx +++ b/src/client/views/nodes/Timeline.tsx @@ -12,16 +12,18 @@ import { List } from "../../../new_fields/List"; import { Self } from "../../../new_fields/FieldSymbols"; import { Doc, DocListCast } from "../../../new_fields/Doc"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faCircle, faPlayCircle, faBackward, faForward, faGripLines} from "@fortawesome/free-solid-svg-icons"; +import { faCircle, faPlayCircle, faBackward, faForward, faGripLines } from "@fortawesome/free-solid-svg-icons"; import { DocumentContentsView } from "./DocumentContentsView"; +import { ContextMenuProps } from "../ContextMenuItem"; +import { ContextMenu } from "../ContextMenu"; -export interface FlyoutProps{ - x?: number; - y?: number; - display?:string; - time?:number; - duration?:number; +export interface FlyoutProps { + x?: number; + y?: number; + display?: string; + time?: number; + duration?: number; } @@ -31,11 +33,12 @@ export class Timeline extends CollectionSubView(Document) { private readonly MIN_CONTAINER_HEIGHT: number = 205; private readonly MAX_CONTAINER_HEIGHT: number = 800; - @observable private _tickSpacing = 50; + @observable private _tickSpacing = 50; @observable private _scrubberbox = React.createRef<HTMLDivElement>(); @observable private _trackbox = React.createRef<HTMLDivElement>(); @observable private _titleContainer = React.createRef<HTMLDivElement>(); + @observable private _timelineContainer = React.createRef<HTMLDivElement>(); @observable private _currentBarX: number = 0; @observable private _windSpeed: number = 1; @observable private _isPlaying: boolean = false; @@ -47,8 +50,9 @@ export class Timeline extends CollectionSubView(Document) { @observable private _infoContainer = React.createRef<HTMLDivElement>(); @observable private _ticks: number[] = []; - @observable private flyoutInfo:FlyoutProps = {x:0, y:0,display:"none"}; + @observable private flyoutInfo: FlyoutProps = { x: 0, y: 0, display: "none" }; + private block = false; @action componentDidMount() { @@ -73,6 +77,22 @@ export class Timeline extends CollectionSubView(Document) { this._ticks.push(i); i += 1000; } + document.addEventListener("pointerdown", this.closeFlyout); + } + + @action + onFlyoutDown = (e: React.PointerEvent) => { + this.flyoutInfo.display = "block"; + this.block = true; + } + + @action + closeFlyout = (e: PointerEvent) => { + if (this.block) { + this.block = false; + return; + } + this.flyoutInfo.display = "none"; } @action @@ -80,7 +100,7 @@ export class Timeline extends CollectionSubView(Document) { this._time = 100001; } componentWillUnmount() { - + document.removeEventListener("pointerdown", this.closeFlyout); } //for playing @@ -211,75 +231,100 @@ export class Timeline extends CollectionSubView(Document) { } } + @observable private _isMinimized = false; @action minimize = (e: React.MouseEvent) => { - this._containerHeight = 0; + e.preventDefault(); + e.stopPropagation(); + let timelineContainer = this._timelineContainer.current!; + if (this._isMinimized) { + this._isMinimized = false; + timelineContainer.style.transform = `translate(0px, 0px)`; + } else { + this._isMinimized = true; + timelineContainer.style.transform = `translate(0px, ${- this._containerHeight - 30}px)`; + } } @action - getFlyout = (props: FlyoutProps) => { - for(const[k, v] of Object.entries(props)){ - (this.flyoutInfo as any)[k] = v; + getFlyout = (props: FlyoutProps) => { + for (const [k, v] of Object.entries(props)) { + (this.flyoutInfo as any)[k] = v; } - - } + } - @action - onFlyoutDown = (e: React.PointerEvent) => { - console.log("clicked!"); - this.flyoutInfo.display = "block"; + timelineContextMenu = (e: React.MouseEvent): void => { + let subitems: ContextMenuProps[] = []; + let timelineContainer = this._timelineContainer.current!; + subitems.push({ description: "Pin to Top", event: action(() => { timelineContainer.style.transform = "translate(0px, 0px)"; }), icon: "pinterest" }); + subitems.push({ + description: "Pin to Bottom", event: action(() => { + timelineContainer.style.transform = `translate(0px, ${e.pageY - this._containerHeight}px)`; + }), icon: "pinterest" + }); + ContextMenu.Instance.addItem({ description: "Timeline Funcs...", subitems: subitems }); } render() { return ( - <div className="timeline-container" style={{ height: `${this._containerHeight}px` }}> - <div className="flyout-container" style={{transform: `translate(${this.flyoutInfo.x}px, ${this.flyoutInfo.y}px)`, display:this.flyoutInfo.display}} onPointerDown={this.onFlyoutDown}> - <FontAwesomeIcon className="flyout" icon="comment-alt" color="grey"/> - <div> - <p>Time:</p><input type="text" placeholder={`${Math.round(this.flyoutInfo.time! / this._tickSpacing * 1000)}ms`}/> - <p>Duration:</p><input type="text" placeholder={`${Math.round(this.flyoutInfo.duration! / this._tickSpacing * 1000)}ms`}/> - <p>Fade-in</p> - <p>Fade-out</p> + <div> + <button className="minimize" onClick={this.minimize}>Minimize</button> + <div className="timeline-container" style={{ height: `${this._containerHeight}px` }} ref={this._timelineContainer} onContextMenu={this.timelineContextMenu}> + <div className="flyout-container" style={{ transform: `translate(${this.flyoutInfo.x}px, ${this.flyoutInfo.y}px)`, display: this.flyoutInfo.display }} onPointerDown={this.onFlyoutDown}> + <FontAwesomeIcon className="flyout" icon="comment-alt" color="grey" /> + <div className="text-container"> + <p>Time:</p> + <p>Duration:</p> + <p>Fade-in</p> + <p>Fade-out</p> + </div> + <div className="input-container"> + <input type="text" placeholder={`${Math.round(this.flyoutInfo.time! / this._tickSpacing * 1000)}ms`} /> + <input type="text" placeholder={`${Math.round(this.flyoutInfo.duration! / this._tickSpacing * 1000)}ms`} /> + <input type="text" placeholder={`${Math.round(this.flyoutInfo.time! / this._tickSpacing * 1000)}ms`} /> + <input type="text" placeholder={`${Math.round(this.flyoutInfo.duration! / this._tickSpacing * 1000)}ms`} /> + </div> </div> - </div> - <div className="toolbox"> - <div onClick={this.windBackward}> <FontAwesomeIcon icon={faBackward} size="2x" /> </div> - <div onClick={this.onPlay}> <FontAwesomeIcon icon={faPlayCircle} size="2x" /> </div> - <div onClick={this.windForward}> <FontAwesomeIcon icon={faForward} size="2x" /> </div> - {/* <div> - <p>Timeline Overview</p> - <div className="overview"></div> - </div> */} - </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/1000 * this._tickSpacing}px)`, position: "absolute", pointerEvents: "none" }}> <p>{this.toTime(element)}</p></div>; - })} + <div className="toolbox"> + <div onClick={this.windBackward}> <FontAwesomeIcon icon={faBackward} size="2x" /> </div> + <div onClick={this.onPlay}> <FontAwesomeIcon icon={faPlayCircle} size="2x" /> </div> + <div onClick={this.windForward}> <FontAwesomeIcon icon={faForward} size="2x" /> </div> + {/* <div> + <p>Timeline Overview</p> + <div className="overview"></div> + </div> */} </div> - <div className="scrubber" onPointerDown={this.onScrubberDown} style={{ transform: `translate(${this._currentBarX}px)` }}> - <div className="scrubberhead"></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 / 1000 * this._tickSpacing}px)`, position: "absolute", pointerEvents: "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} setFlyout={this.getFlyout} />; + })} + </div> </div> - <div className="trackbox" ref={this._trackbox} onPointerDown={this.onPanDown}> + <div className="title-container" ref={this._titleContainer}> {this._nodes.map(doc => { - return <Track node={(doc as any).value() as Doc} currentBarX={this._currentBarX} setFlyout={this.getFlyout} />; + return <div className="datapane"> + <p>{((doc as any).value() as Doc).title}</p> + </div>; })} </div> + <div onPointerDown={this.onResizeDown}> + <FontAwesomeIcon className="resize" icon={faGripLines} /> + </div> </div> - <div className="title-container" ref={this._titleContainer}> - {this._nodes.map(doc => { - return <div className="datapane"> - <p>{((doc as any).value() as Doc).title}</p> - </div>; - })} - </div> - <div onPointerDown={this.onResizeDown}> - <FontAwesomeIcon className="resize" icon={faGripLines} /> - </div> + </div> ); } diff --git a/src/client/views/nodes/Track.tsx b/src/client/views/nodes/Track.tsx index 94c0b5563..cf613a827 100644 --- a/src/client/views/nodes/Track.tsx +++ b/src/client/views/nodes/Track.tsx @@ -73,16 +73,20 @@ export class Track extends React.Component<IProps> { 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(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]; @@ -98,17 +102,15 @@ export class Track extends React.Component<IProps> { */ @action calcMinLeft = (region: Doc): (Doc | undefined) => { //returns the time of the closet keyframe to the left - let leftKf: Doc = new Doc(); - leftKf.time = Infinity; + let leftKf:(Doc| undefined) = undefined; + let time:number = 0; (region.keyframes! as List<Doc>).forEach((kf) => { kf = kf as Doc; - if (NumCast(kf.time) < this.props.currentBarX && NumCast(leftKf.time) > NumCast(kf.time)) { + if (NumCast(kf.time) < this.props.currentBarX && NumCast(kf.time) >= NumCast(time)) { leftKf = kf; + time = NumCast(kf.time); } }); - if (NumCast(leftKf.time) === Infinity) { - return undefined; - } return leftKf; } @@ -119,17 +121,15 @@ export class Track extends React.Component<IProps> { */ @action calcMinRight = (region: Doc): (Doc | undefined) => { //returns the time of the closest keyframe to the right - let rightKf: Doc = new Doc(); - rightKf.time = Infinity; + let rightKf: (Doc|undefined) = undefined; + let time:number = Infinity; (region.keyframes! as List<Doc>).forEach((kf) => { kf = kf as Doc; - if (NumCast(kf.time) > this.props.currentBarX && NumCast(rightKf.time) > NumCast(kf.time)) { + if (NumCast(kf.time) > this.props.currentBarX && NumCast(kf.time) <= NumCast(time)) { rightKf = kf; + time = NumCast(kf.time); } }); - if (NumCast(rightKf.time) === Infinity) { - return undefined; - } return rightKf; } |