diff options
-rw-r--r-- | src/client/views/animationtimeline/Keyframe.tsx | 65 | ||||
-rw-r--r-- | src/client/views/animationtimeline/Timeline.tsx | 127 | ||||
-rw-r--r-- | src/client/views/animationtimeline/Track.tsx | 62 |
3 files changed, 218 insertions, 36 deletions
diff --git a/src/client/views/animationtimeline/Keyframe.tsx b/src/client/views/animationtimeline/Keyframe.tsx index c3e018112..c3c1f5f8c 100644 --- a/src/client/views/animationtimeline/Keyframe.tsx +++ b/src/client/views/animationtimeline/Keyframe.tsx @@ -15,6 +15,11 @@ import { Docs } from "../../documents/Documents"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { undoBatch, UndoManager } from "../../util/UndoManager"; + + +/** + * Useful static functions that you can use. Mostly for logic, but you can also add UI logic here also + */ export namespace KeyframeFunc { export enum KeyframeType { fade = "fade", @@ -134,6 +139,30 @@ interface IProps { checkCallBack: (visible: boolean) => void; } + +/** + * + * This class handles the green region stuff + * Key facts: + * + * Structure looks like this + * + * region as a whole + * <------------------------------REGION-------------------------------> + * + * region broken down + * + * <|---------|############ MAIN CONTENT #################|-----------|> .....followed by void......... + * (start) (Fade 2) + * (fade 1) (finish) + * + * + * As you can see, this is different from After Effect and Premiere Pro, but this is how TAG worked. + * If you want to checkout TAG, it's in the lockers, and the password is the usual lab door password. It's the blue laptop. + * If you want to know the exact location of the computer, message me. + * + * @author Andrew Kim + */ @observer export class Keyframe extends React.Component<IProps> { @@ -363,6 +392,10 @@ export class Keyframe extends React.Component<IProps> { this.props.node.backgroundColor = "#000000"; } + + /** + * custom keyframe context menu items (when clicking on the keyframe circle) + */ @action makeKeyframeMenu = (kf: Doc, e: MouseEvent) => { TimelineMenu.Instance.addItem("button", "Show Data", () => { @@ -396,6 +429,9 @@ export class Keyframe extends React.Component<IProps> { TimelineMenu.Instance.openMenu(e.clientX, e.clientY); } + /** + * context menu for region (anywhere on the green region). + */ @action makeRegionMenu = (kf: Doc, e: MouseEvent) => { TimelineMenu.Instance.addItem("button", "Add Ease", () => { @@ -481,6 +517,10 @@ export class Keyframe extends React.Component<IProps> { TimelineMenu.Instance.openMenu(e.clientX, e.clientY); } + + /** + * checking if input is correct (might have to use external module) + */ @action checkInput = (val: any) => { return typeof (val === "number"); @@ -495,6 +535,9 @@ export class Keyframe extends React.Component<IProps> { }); } + /** + * hovering effect when hovered (hidden div darkens) + */ @action onContainerOver = (e: React.PointerEvent, ref: React.RefObject<HTMLDivElement>) => { e.preventDefault(); @@ -504,6 +547,9 @@ export class Keyframe extends React.Component<IProps> { Doc.BrushDoc(this.props.node); } + /** + * hovering effect when hovered out (hidden div becomes invisible) + */ @action onContainerOut = (e: React.PointerEvent, ref: React.RefObject<HTMLDivElement>) => { e.preventDefault(); @@ -520,6 +566,9 @@ export class Keyframe extends React.Component<IProps> { private _type: string = ""; + /** + * Need to fix this. skip + */ @action onContainerDown = (kf: Doc, type: string) => { let listenerCreated = false; @@ -544,6 +593,9 @@ export class Keyframe extends React.Component<IProps> { }); } + /** + * for custom draw interpolation. Need to be refactored + */ @action onReactionListen = () => { if (this._reac && this._plotList && this._interpolationKeyframe) { @@ -589,6 +641,13 @@ export class Keyframe extends React.Component<IProps> { } } + ///////////////////////UI STUFF ///////////////////////// + + + /** + * drawing keyframe. Handles both keyframe with a circle (one that you create by double clicking) and one without circle (fades) + * this probably needs biggest change, since everyone expected all keyframes to have a circle (and draggable) + */ @action drawKeyframes = () => { let keyframeDivs: JSX.Element[] = []; @@ -616,6 +675,9 @@ export class Keyframe extends React.Component<IProps> { return keyframeDivs; } + /** + * drawing the hidden divs that partition different intervals within a region. + */ @action drawKeyframeDividers = () => { let keyframeDividers: JSX.Element[] = []; @@ -645,6 +707,9 @@ export class Keyframe extends React.Component<IProps> { return keyframeDividers; } + /** + * rendering that green region + */ render() { return ( <div> diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx index c13a039a5..9cf600b5f 100644 --- a/src/client/views/animationtimeline/Timeline.tsx +++ b/src/client/views/animationtimeline/Timeline.tsx @@ -15,9 +15,32 @@ import { FieldViewProps } from "../nodes/FieldView"; import { KeyframeFunc } from "./Keyframe"; import { Utils } from "../../../Utils"; + +/** + * Timeline class controls most of timeline functions besides individual keyframe and track mechanism. Main functions are + * zooming, panning, currentBarX (scrubber movement). Most of the UI stuff is also handled here. You shouldn't really make + * any logical changes here. Most work is needed on UI. + * + * The hierarchy works this way: + * + * Timeline.tsx --> Track.tsx --> Keyframe.tsx + | | + | TimelineMenu.tsx (timeline's custom contextmenu) + | + | + TimelineOverview.tsx (youtube like dragging thing is play mode, complex dragging thing in editing mode) + + + Most style changes are in SCSS file. + If you have any questions, email me or text me. + @author Andrew Kim + */ + + @observer export class Timeline extends React.Component<FieldViewProps> { - + + //readonly constants private readonly DEFAULT_TICK_SPACING: number = 50; private readonly MAX_TITLE_HEIGHT = 75; @@ -70,38 +93,35 @@ export class Timeline extends React.Component<FieldViewProps> { return Cast(this.props.Document[this.props.fieldKey], listSpec(Doc)) as List<Doc>; } - /** - * - */ + /////////lifecycle functions//////////// componentWillMount() { - let relativeHeight = window.innerHeight / 14; - this._titleHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT; - this.MIN_CONTAINER_HEIGHT = this._titleHeight + 130; - this.DEFAULT_CONTAINER_HEIGHT = this._titleHeight * 2 + 130; + let relativeHeight = window.innerHeight / 14; //sets height to arbitrary size, relative to innerHeight + this._titleHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT; //check if relHeight is less than Maxheight. Else, just set relheight to max + this.MIN_CONTAINER_HEIGHT = this._titleHeight + 130; //offset + this.DEFAULT_CONTAINER_HEIGHT = this._titleHeight * 2 + 130; //twice the titleheight + offset } componentDidMount() { runInAction(() => { - if (!this.props.Document.AnimationLength) { - this.props.Document.AnimationLength = this._time; + if (!this.props.Document.AnimationLength) { //if animation length did not exist + this.props.Document.AnimationLength = this._time; //set it to default time } else { - this._time = NumCast(this.props.Document.AnimationLength); - console.log(this._time); + this._time = NumCast(this.props.Document.AnimationLength); //else, set time to animationlength stored from before } - this._totalLength = this._tickSpacing * (this._time / this._tickIncrement); - this._visibleLength = this._infoContainer.current!.getBoundingClientRect().width; - this._visibleStart = this._infoContainer.current!.scrollLeft; - this.props.Document.isATOn = !this.props.Document.isATOn; + this._totalLength = this._tickSpacing * (this._time / this._tickIncrement); //the entire length of the timeline div (actual div part itself) + this._visibleLength = this._infoContainer.current!.getBoundingClientRect().width; //the visible length of the timeline (the length that you current see) + this._visibleStart = this._infoContainer.current!.scrollLeft; //where the div starts + this.props.Document.isATOn = !this.props.Document.isATOn; //turns the boolean on, saying AT (animation timeline) is on this.toggleHandle(); }); } componentWillUnmount() { runInAction(() => { - console.log(this._time); - this.props.Document.AnimationLength = this._time; + this.props.Document.AnimationLength = this._time; //save animation length }); } + ///////////////////////////////////////////////// /** * React Functional Component @@ -116,7 +136,9 @@ export class Timeline extends React.Component<FieldViewProps> { return ticks; } - + /** + * changes the scrubber to actual pixel position + */ @action changeCurrentBarX = (pixel: number) => { pixel <= 0 ? this._currentBarX = 0 : pixel >= this._totalLength ? this._currentBarX = this._totalLength : this._currentBarX = pixel; @@ -130,6 +152,9 @@ export class Timeline extends React.Component<FieldViewProps> { this.play(); } + /** + * when playbutton is clicked + */ @action play = () => { if (this._isPlaying) { @@ -177,7 +202,9 @@ export class Timeline extends React.Component<FieldViewProps> { } } - + /** + * scrubber down + */ @action onScrubberDown = (e: React.PointerEvent) => { e.preventDefault(); @@ -188,16 +215,22 @@ export class Timeline extends React.Component<FieldViewProps> { }); } + /** + * when there is any scrubber movement + */ @action onScrubberMove = (e: PointerEvent) => { e.preventDefault(); e.stopPropagation(); let scrubberbox = this._infoContainer.current!; let left = scrubberbox.getBoundingClientRect().left; - let offsetX = Math.round(e.clientX - left) * this.props.ScreenToLocalTransform().Scale; - this.changeCurrentBarX(offsetX + this._visibleStart); + let offsetX = Math.round(e.clientX - left) * this.props.ScreenToLocalTransform().Scale; + this.changeCurrentBarX(offsetX + this._visibleStart); //changes scrubber to clicked scrubber position } + /** + * when panning the timeline (in editing mode) + */ @action onPanDown = (e: React.PointerEvent) => { e.preventDefault(); @@ -223,6 +256,9 @@ export class Timeline extends React.Component<FieldViewProps> { } } + /** + * when moving the timeline (in editing mode) + */ @action onPanMove = (e: PointerEvent) => { e.preventDefault(); @@ -243,6 +279,8 @@ export class Timeline extends React.Component<FieldViewProps> { } } + + @action movePanX = (pixel: number) => { let infoContainer = this._infoContainer.current!; @@ -250,6 +288,9 @@ export class Timeline extends React.Component<FieldViewProps> { this._visibleStart = infoContainer.scrollLeft; } + /** + * resizing timeline (in editing mode) (the hamburger drag icon) + */ @action onResizeDown = (e: React.PointerEvent) => { e.preventDefault(); @@ -274,6 +315,9 @@ export class Timeline extends React.Component<FieldViewProps> { } } + /** + * for displaying time to standard min:sec + */ @action toReadTime = (time: number): string => { const inSeconds = time / 1000; @@ -286,6 +330,11 @@ export class Timeline extends React.Component<FieldViewProps> { return `${min}:${sec}`; } + + /** + * context menu function. + * opens the timeline or closes the timeline. + */ timelineContextMenu = (e: MouseEvent): void => { ContextMenu.Instance.addItem({ description: (this._timelineVisible ? "Close" : "Open") + " Animation Timeline", event: action(() => { @@ -295,6 +344,10 @@ export class Timeline extends React.Component<FieldViewProps> { } + /** + * timeline zoom function + * use mouse middle button to zoom in/out the timeline + */ @action onWheelZoom = (e: React.WheelEvent) => { e.preventDefault(); @@ -311,6 +364,9 @@ export class Timeline extends React.Component<FieldViewProps> { this.changeCurrentBarX(currCurrent); } + /** + * zooming mechanism (increment and spacing changes) + */ @action zoom = (dir: boolean) => { let spacingChange = this._tickSpacing; @@ -340,6 +396,9 @@ export class Timeline extends React.Component<FieldViewProps> { } } + /** + * tool box includes the toggle buttons at the top of the timeline (both editing mode and play mode) + */ private timelineToolBox = (scale: number) => { let size = 40 * scale; //50 is default return ( @@ -359,6 +418,9 @@ export class Timeline extends React.Component<FieldViewProps> { ); } + /** + * manual time input (kinda broken right now) + */ @action private onTimeInput = (e: React.KeyboardEvent) => { if (e.keyCode === 13) { @@ -369,6 +431,10 @@ export class Timeline extends React.Component<FieldViewProps> { } } + + /** + * when the user decides to click the toggle button (either user wants to enter editing mode or play mode) + */ @action private toggleChecked = (e: React.PointerEvent) => { e.preventDefault(); @@ -376,6 +442,9 @@ export class Timeline extends React.Component<FieldViewProps> { this.toggleHandle(); } + /** + * turns on the toggle button (the purple slide button that changes from editing mode and play mode + */ private toggleHandle = () => { let roundToggle = this._roundToggleRef.current!; let roundToggleContainer = this._roundToggleContainerRef.current!; @@ -410,7 +479,9 @@ export class Timeline extends React.Component<FieldViewProps> { } - + /** + * check mark thing that needs to be fixed. Do not edit this, because it most likely change. + */ @action private checkCallBack = (visible: boolean) => { this._checkVisible = visible; @@ -421,12 +492,10 @@ export class Timeline extends React.Component<FieldViewProps> { } - - - - - - + /** + * if you have any question here, just shoot me an email or text. + * basically the only thing you need to edit besides render methods in track (individual track lines) and keyframe (green region) + */ render() { return ( <div> diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx index d0bcdd655..d9b8026c5 100644 --- a/src/client/views/animationtimeline/Track.tsx +++ b/src/client/views/animationtimeline/Track.tsx @@ -62,9 +62,11 @@ export class Track extends React.Component<IProps> { @computed private get regions() { return Cast(this.props.node.regions, listSpec(Doc)) as List<Doc>; } + ////////// life cycle functions/////////////// componentWillMount() { runInAction(() => { - if (!this.props.node.regions) this.props.node.regions = new List<Doc>(); + if (!this.props.node.regions) this.props.node.regions = new List<Doc>(); //if there is no region, then create new doc to store stuff + //these two lines are exactly same from timeline.tsx let relativeHeight = window.innerHeight / 14; this._trackHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT; //for responsiveness }); @@ -80,6 +82,9 @@ export class Track extends React.Component<IProps> { }); } + /** + * mainly for disposing reactions + */ componentWillUnmount() { runInAction(() => { //disposing reactions @@ -87,7 +92,11 @@ export class Track extends React.Component<IProps> { if (this._timelineVisibleReaction) this._timelineVisibleReaction(); }); } + //////////////////////////////// + /** + * keyframe save logic. Needs to be changed so it's more efficient + */ @action saveKeyframe = async (ref: Doc, regiondata: Doc) => { let keyframes: List<Doc> = (Cast(regiondata.keyframes, listSpec(Doc)) as List<Doc>); @@ -119,6 +128,9 @@ export class Track extends React.Component<IProps> { this._isOnKeyframe = false; } + /** + * reverting back to previous state before editing on AT + */ @action revertState = () => { let copyDoc = Doc.MakeCopy(this.props.node, true); @@ -128,6 +140,10 @@ export class Track extends React.Component<IProps> { this._storedState = newState; } + /** + * Reaction when scrubber bar changes + * made into function so it's easier to dispose later + */ @action currentBarXReaction = () => { return reaction(() => this.props.currentBarX, async () => { @@ -141,6 +157,10 @@ export class Track extends React.Component<IProps> { } }); } + + /** + * when timeline is visible, reaction is ran so states are reverted + */ @action timelineVisibleReaction = () => { return reaction(() => { @@ -163,13 +183,9 @@ export class Track extends React.Component<IProps> { }); } - @action - /** - * Simple loop through all the regions to update the keyframes. Only applies to timelineVisibleReaction + /**w + * when scrubber position changes. Need to edit the logic */ - updateAllRegions = () => { - - } @action timeChange = async (time: number) => { if (this._isOnKeyframe && this._onKeyframe && this._onRegionData) { @@ -191,6 +207,10 @@ export class Track extends React.Component<IProps> { } } + /** + * applying changes (when saving the keyframe) + * need to change the logic here + */ @action private applyKeys = async (kf: Doc) => { let kfNode = await Cast(kf.key, Doc) as Doc; @@ -212,6 +232,9 @@ export class Track extends React.Component<IProps> { + /** + * changing the filter here + */ @action private filterKeys = (keys: string[]): string[] => { return keys.reduce((acc: string[], key: string) => { @@ -220,6 +243,10 @@ export class Track extends React.Component<IProps> { }, []); } + + /** + * calculating current keyframe, if the scrubber is right on the keyframe + */ @action calcCurrent = async (region: Doc) => { let currentkf: (Doc | undefined) = undefined; @@ -231,6 +258,10 @@ export class Track extends React.Component<IProps> { } + /** + * interpolation. definetely needs to be changed. (currently involves custom linear splicing interpolations). + * Too complex right now. Also need to apply quadratic spline later on (for smoothness, instead applying "gains") + */ @action interpolate = async (left: Doc, right: Doc, regiondata: Doc) => { let leftNode = left.key as Doc; @@ -290,6 +321,10 @@ export class Track extends React.Component<IProps> { }); } + /** + * finds region that corresponds to specific time (is there a region at this time?) + * linear O(n) (maybe possible to optimize this with other Data structures?) + */ @action findRegion = async (time: number) => { let foundRegion: (Doc | undefined) = undefined; @@ -303,6 +338,10 @@ export class Track extends React.Component<IProps> { return foundRegion; } + + /** + * double click on track. Signalling keyframe creation. Problem with phantom regions + */ @action onInnerDoubleClick = (e: React.MouseEvent) => { let inner = this._inner.current!; @@ -311,6 +350,10 @@ export class Track extends React.Component<IProps> { this.forceUpdate(); } + + /** + * creates a region (KEYFRAME.TSX stuff). + */ createRegion = (position: number) => { let regiondata = KeyframeFunc.defaultKeyframe(); regiondata.position = position; @@ -325,6 +368,11 @@ export class Track extends React.Component<IProps> { } } + + + /** + * UI sstuff here. Not really much to change + */ render() { return ( <div className="track-container"> |