aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/views/animationtimeline/Keyframe.tsx65
-rw-r--r--src/client/views/animationtimeline/Timeline.tsx127
-rw-r--r--src/client/views/animationtimeline/Track.tsx62
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">