aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/animationtimeline/Track.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/animationtimeline/Track.tsx')
-rw-r--r--src/client/views/animationtimeline/Track.tsx261
1 files changed, 137 insertions, 124 deletions
diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx
index 25c2e68e7..2349ba786 100644
--- a/src/client/views/animationtimeline/Track.tsx
+++ b/src/client/views/animationtimeline/Track.tsx
@@ -1,15 +1,15 @@
-import { action, computed, intercept, observable, reaction, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import { Doc, DocListCast, Opt, DocListCastAsync } from "../../../fields/Doc";
-import { Copy } from "../../../fields/FieldSymbols";
-import { List } from "../../../fields/List";
-import { ObjectField } from "../../../fields/ObjectField";
-import { listSpec } from "../../../fields/Schema";
-import { Cast, NumCast } from "../../../fields/Types";
-import { Transform } from "../../util/Transform";
-import { Keyframe, KeyframeFunc, RegionData } from "./Keyframe";
-import "./Track.scss";
+import { action, computed, intercept, observable, reaction, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc, DocListCast, Opt, DocListCastAsync } from '../../../fields/Doc';
+import { Copy } from '../../../fields/FieldSymbols';
+import { List } from '../../../fields/List';
+import { ObjectField } from '../../../fields/ObjectField';
+import { listSpec } from '../../../fields/Schema';
+import { Cast, NumCast } from '../../../fields/Types';
+import { Transform } from '../../util/Transform';
+import { Keyframe, KeyframeFunc, RegionData } from './Keyframe';
+import './Track.scss';
interface IProps {
node: Doc;
@@ -32,27 +32,22 @@ export class Track extends React.Component<IProps> {
@observable private _newKeyframe: boolean = false;
private readonly MAX_TITLE_HEIGHT = 75;
@observable private _trackHeight = 0;
- private primitiveWhitelist = [
- "x",
- "y",
- "_width",
- "_height",
- "opacity",
- "_scrollTop"
- ];
- private objectWhitelist = [
- "data"
- ];
+ private primitiveWhitelist = ['x', 'y', '_width', '_height', 'opacity', '_layout_scrollTop'];
+ private objectWhitelist = ['data'];
- @computed private get regions() { return DocListCast(this.props.node.regions); }
- @computed private get time() { return NumCast(KeyframeFunc.convertPixelTime(this.props.currentBarX, "mili", "time", this.props.tickSpacing, this.props.tickIncrement)); }
+ @computed private get regions() {
+ return DocListCast(this.props.node.regions);
+ }
+ @computed private get time() {
+ return NumCast(KeyframeFunc.convertPixelTime(this.props.currentBarX, 'mili', 'time', this.props.tickSpacing, this.props.tickIncrement));
+ }
async componentDidMount() {
const regions = await DocListCastAsync(this.props.node.regions);
if (!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
+ //these two lines are exactly same from timeline.tsx
const relativeHeight = window.innerHeight / 20;
- runInAction(() => this._trackHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT); //for responsiveness
+ runInAction(() => (this._trackHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT)); //for responsiveness
this._timelineVisibleReaction = this.timelineVisibleReaction();
this._currentBarXReaction = this.currentBarXReaction();
if (DocListCast(this.props.node.regions).length === 0) this.createRegion(this.time);
@@ -71,7 +66,6 @@ export class Track extends React.Component<IProps> {
}
////////////////////////////////
-
getLastRegionTime = () => {
let lastTime: number = 0;
let lastRegion: Opt<Doc>;
@@ -83,11 +77,11 @@ export class Track extends React.Component<IProps> {
}
});
return lastRegion ? lastTime + NumCast(lastRegion.duration) : 0;
- }
+ };
/**
* keyframe save logic. Needs to be changed so it's more efficient
- *
+ *
*/
@action
saveKeyframe = async () => {
@@ -97,16 +91,18 @@ export class Track extends React.Component<IProps> {
if (this._newKeyframe) {
DocListCast(this.saveStateRegion?.keyframes).forEach((kf, index) => {
this.copyDocDataToKeyFrame(kf);
- kf.opacity = (index === 0 || index === 3) ? 0.1 : 1;
+ kf.opacity = index === 0 || index === 3 ? 0.1 : 1;
});
this._newKeyframe = false;
}
if (!kf) return;
- if (kf.type === KeyframeFunc.KeyframeType.default) { // only save for non-fades
+ if (kf.type === KeyframeFunc.KeyframeType.default) {
+ // only save for non-fades
this.copyDocDataToKeyFrame(kf);
const leftkf = KeyframeFunc.calcMinLeft(this.saveStateRegion!, this.time, kf); // lef keyframe, if it exists
- const rightkf = KeyframeFunc.calcMinRight(this.saveStateRegion!, this.time, kf); //right keyframe, if it exists
- if (leftkf?.type === KeyframeFunc.KeyframeType.fade) { //replicating this keyframe to fades
+ const rightkf = KeyframeFunc.calcMinRight(this.saveStateRegion!, this.time, kf); //right keyframe, if it exists
+ if (leftkf?.type === KeyframeFunc.KeyframeType.fade) {
+ //replicating this keyframe to fades
const edge = KeyframeFunc.calcMinLeft(this.saveStateRegion!, this.time, leftkf);
edge && this.copyDocDataToKeyFrame(edge);
leftkf && this.copyDocDataToKeyFrame(leftkf);
@@ -124,8 +120,7 @@ export class Track extends React.Component<IProps> {
keyframes[kfIndex] = kf;
this.saveStateKf = undefined;
this.saveStateRegion = undefined;
- }
-
+ };
/**
* autocreates keyframe
@@ -136,23 +131,27 @@ export class Track extends React.Component<IProps> {
intercept(this.props.node, change => {
return change;
});
- return reaction(() => {
- return [...this.primitiveWhitelist.map(key => this.props.node[key]), ...objects];
- }, (changed, reaction) => {
- //check for region
- const region = this.findRegion(this.time);
- if (region !== undefined) { //if region at scrub time exist
- const r = region as RegionData; //for some region is returning undefined... which is not the case
- if (DocListCast(r.keyframes).find(kf => kf.time === this.time) === undefined) { //basically when there is no additional keyframe at that timespot
- this.makeKeyData(r, this.time, KeyframeFunc.KeyframeType.default);
+ return reaction(
+ () => {
+ return [...this.primitiveWhitelist.map(key => this.props.node[key]), ...objects];
+ },
+ (changed, reaction) => {
+ //check for region
+ const region = this.findRegion(this.time);
+ if (region !== undefined) {
+ //if region at scrub time exist
+ const r = region as RegionData; //for some region is returning undefined... which is not the case
+ if (DocListCast(r.keyframes).find(kf => kf.time === this.time) === undefined) {
+ //basically when there is no additional keyframe at that timespot
+ this.makeKeyData(r, this.time, KeyframeFunc.KeyframeType.default);
+ }
}
- }
- }, { fireImmediately: false });
- }
-
-
+ },
+ { fireImmediately: false }
+ );
+ };
- // @observable private _storedState:(Doc | undefined) = undefined;
+ // @observable private _storedState:(Doc | undefined) = undefined;
// /**
// * reverting back to previous state before editing on AT
// */
@@ -161,54 +160,62 @@ export class Track extends React.Component<IProps> {
// if (this._storedState) this.applyKeys(this._storedState);
// }
-
/**
* Reaction when scrubber bar changes
* made into function so it's easier to dispose later
- */
+ */
@action
currentBarXReaction = () => {
- return reaction(() => this.props.currentBarX, () => {
- const regiondata = this.findRegion(this.time);
- if (regiondata) {
- this.props.node.hidden = false;
- // if (!this._autoKfReaction) {
- // // this._autoKfReaction = this.autoCreateKeyframe();
- // }
- this.timeChange();
- } else {
- this.props.node.hidden = true;
- this.props.node.opacity = 0;
- //if (this._autoKfReaction) this._autoKfReaction();
+ return reaction(
+ () => this.props.currentBarX,
+ () => {
+ const regiondata = this.findRegion(this.time);
+ if (regiondata) {
+ this.props.node.hidden = false;
+ // if (!this._autoKfReaction) {
+ // // this._autoKfReaction = this.autoCreateKeyframe();
+ // }
+ this.timeChange();
+ } else {
+ this.props.node.hidden = true;
+ this.props.node.opacity = 0;
+ //if (this._autoKfReaction) this._autoKfReaction();
+ }
}
- });
- }
+ );
+ };
/**
* when timeline is visible, reaction is ran so states are reverted
*/
@action
timelineVisibleReaction = () => {
- return reaction(() => {
- return this.props.timelineVisible;
- }, isVisible => {
- if (isVisible) {
- this.regions.filter(region => !region.hasData).forEach(region => {
- for (let i = 0; i < 4; i++) {
- this.copyDocDataToKeyFrame(DocListCast(region.keyframes)[i]);
- if (i === 0 || i === 3) { //manually inputing fades
- DocListCast(region.keyframes)[i].opacity = 0.1;
- }
- }
- });
- } else {
- //this.revertState();
+ return reaction(
+ () => {
+ return this.props.timelineVisible;
+ },
+ isVisible => {
+ if (isVisible) {
+ this.regions
+ .filter(region => !region.hasData)
+ .forEach(region => {
+ for (let i = 0; i < 4; i++) {
+ this.copyDocDataToKeyFrame(DocListCast(region.keyframes)[i]);
+ if (i === 0 || i === 3) {
+ //manually inputing fades
+ DocListCast(region.keyframes)[i].opacity = 0.1;
+ }
+ }
+ });
+ } else {
+ //this.revertState();
+ }
}
- });
- }
+ );
+ };
- @observable private saveStateKf: (Doc | undefined) = undefined;
- @observable private saveStateRegion: (Doc | undefined) = undefined;
+ @observable private saveStateKf: Doc | undefined = undefined;
+ @observable private saveStateRegion: Doc | undefined = undefined;
/**w
* when scrubber position changes. Need to edit the logic
@@ -222,9 +229,9 @@ export class Track extends React.Component<IProps> {
}
const regiondata = await this.findRegion(Math.round(this.time)); //finds a region that the scrubber is on
if (regiondata) {
- const leftkf: (Doc | undefined) = await KeyframeFunc.calcMinLeft(regiondata, this.time); // lef keyframe, if it exists
- const rightkf: (Doc | undefined) = await KeyframeFunc.calcMinRight(regiondata, this.time); //right keyframe, if it exists
- const currentkf: (Doc | undefined) = await this.calcCurrent(regiondata); //if the scrubber is on top of the keyframe
+ const leftkf: Doc | undefined = await KeyframeFunc.calcMinLeft(regiondata, this.time); // lef keyframe, if it exists
+ const rightkf: Doc | undefined = await KeyframeFunc.calcMinRight(regiondata, this.time); //right keyframe, if it exists
+ const currentkf: Doc | undefined = await this.calcCurrent(regiondata); //if the scrubber is on top of the keyframe
if (currentkf) {
await this.applyKeys(currentkf);
this.saveStateKf = currentkf;
@@ -233,10 +240,10 @@ export class Track extends React.Component<IProps> {
await this.interpolate(leftkf, rightkf);
}
}
- }
+ };
/**
- * applying changes (when saving the keyframe)
+ * applying changes (when saving the keyframe)
* need to change the logic here
*/
@action
@@ -249,73 +256,73 @@ export class Track extends React.Component<IProps> {
this.props.node[key] = stored instanceof ObjectField ? stored[Copy]() : stored;
}
});
- }
-
+ };
/**
* calculating current keyframe, if the scrubber is right on the keyframe
*/
@action
calcCurrent = (region: Doc) => {
- let currentkf: (Doc | undefined) = undefined;
+ let currentkf: Doc | undefined = undefined;
const keyframes = DocListCast(region.keyframes!);
- keyframes.forEach((kf) => {
+ keyframes.forEach(kf => {
if (NumCast(kf.time) === Math.round(this.time)) currentkf = kf;
});
return currentkf;
- }
-
+ };
/**
- * basic linear interpolation function
+ * basic linear interpolation function
*/
@action
interpolate = async (left: Doc, right: Doc) => {
this.primitiveWhitelist.forEach(key => {
- if (left[key] && right[key] && typeof (left[key]) === "number" && typeof (right[key]) === "number") { //if it is number, interpolate
+ if (left[key] && right[key] && typeof left[key] === 'number' && typeof right[key] === 'number') {
+ //if it is number, interpolate
const dif = NumCast(right[key]) - NumCast(left[key]);
const deltaLeft = this.time - NumCast(left.time);
const ratio = deltaLeft / (NumCast(right.time) - NumCast(left.time));
- this.props.node[key] = NumCast(left[key]) + (dif * ratio);
- } else { // case data
+ this.props.node[key] = NumCast(left[key]) + dif * ratio;
+ } else {
+ // case data
const stored = left[key];
this.props.node[key] = stored instanceof ObjectField ? stored[Copy]() : stored;
}
});
- }
+ };
/**
* 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?)
*/
findRegion = (time: number) => {
- return this.regions?.find(rd => (time >= NumCast(rd.position) && time <= (NumCast(rd.position) + NumCast(rd.duration))));
- }
-
+ return this.regions?.find(rd => time >= NumCast(rd.position) && time <= NumCast(rd.position) + NumCast(rd.duration));
+ };
/**
- * double click on track. Signalling keyframe creation.
+ * double click on track. Signalling keyframe creation.
*/
@action
onInnerDoubleClick = (e: React.MouseEvent) => {
const inner = this._inner.current!;
const offsetX = Math.round((e.clientX - inner.getBoundingClientRect().left) * this.props.transform.Scale);
- this.createRegion(KeyframeFunc.convertPixelTime(offsetX, "mili", "time", this.props.tickSpacing, this.props.tickIncrement));
- }
-
+ this.createRegion(KeyframeFunc.convertPixelTime(offsetX, 'mili', 'time', this.props.tickSpacing, this.props.tickIncrement));
+ };
/**
- * creates a region (KEYFRAME.TSX stuff).
+ * creates a region (KEYFRAME.TSX stuff).
*/
@action
createRegion = (time: number) => {
- if (this.findRegion(time) === undefined) { //check if there is a region where double clicking (prevents phantom regions)
+ if (this.findRegion(time) === undefined) {
+ //check if there is a region where double clicking (prevents phantom regions)
const regiondata = KeyframeFunc.defaultKeyframe(); //create keyframe data
regiondata.position = time; //set position
const rightRegion = KeyframeFunc.findAdjacentRegion(KeyframeFunc.Direction.right, regiondata, this.regions);
- if (rightRegion && rightRegion.position - regiondata.position <= 4000) { //edge case when there is less than default 4000 duration space between this and right region
+ if (rightRegion && rightRegion.position - regiondata.position <= 4000) {
+ //edge case when there is less than default 4000 duration space between this and right region
regiondata.duration = rightRegion.position - regiondata.position;
}
if (this.regions.length === 0 || !rightRegion || (rightRegion && rightRegion.position - regiondata.position >= NumCast(regiondata.fadeIn) + NumCast(regiondata.fadeOut))) {
@@ -325,27 +332,30 @@ export class Track extends React.Component<IProps> {
return regiondata;
}
}
- }
+ };
@action
- makeKeyData = (regiondata: RegionData, time: number, type: KeyframeFunc.KeyframeType = KeyframeFunc.KeyframeType.default) => { //Kfpos is mouse offsetX, representing time
+ makeKeyData = (regiondata: RegionData, time: number, type: KeyframeFunc.KeyframeType = KeyframeFunc.KeyframeType.default) => {
+ //Kfpos is mouse offsetX, representing time
const trackKeyFrames = DocListCast(regiondata.keyframes);
const existingkf = trackKeyFrames.find(TK => TK.time === time);
if (existingkf) return existingkf;
- //else creates a new doc.
+ //else creates a new doc.
const newKeyFrame: Doc = new Doc();
newKeyFrame.time = time;
newKeyFrame.type = type;
this.copyDocDataToKeyFrame(newKeyFrame);
//assuming there are already keyframes (for keeping keyframes in order, sorted by time)
if (trackKeyFrames.length === 0) regiondata.keyframes!.push(newKeyFrame);
- trackKeyFrames.map(kf => NumCast(kf.time)).forEach((kfTime, index) => {
- if ((kfTime < time && index === trackKeyFrames.length - 1) || (kfTime < time && time < NumCast(trackKeyFrames[index + 1].time))) {
- regiondata.keyframes!.splice(index + 1, 0, newKeyFrame);
- }
- });
+ trackKeyFrames
+ .map(kf => NumCast(kf.time))
+ .forEach((kfTime, index) => {
+ if ((kfTime < time && index === trackKeyFrames.length - 1) || (kfTime < time && time < NumCast(trackKeyFrames[index + 1].time))) {
+ regiondata.keyframes!.splice(index + 1, 0, newKeyFrame);
+ }
+ });
return newKeyFrame;
- }
+ };
@action
copyDocDataToKeyFrame = (doc: Doc) => {
@@ -353,7 +363,7 @@ export class Track extends React.Component<IProps> {
const originalVal = this.props.node[key];
doc[key] = originalVal instanceof ObjectField ? originalVal[Copy]() : originalVal;
});
- }
+ };
/**
* UI sstuff here. Not really much to change
@@ -362,10 +372,13 @@ export class Track extends React.Component<IProps> {
return (
<div className="track-container">
<div className="track">
- <div className="inner" ref={this._inner} style={{ height: `${this._trackHeight}px` }}
+ <div
+ className="inner"
+ ref={this._inner}
+ style={{ height: `${this._trackHeight}px` }}
onDoubleClick={this.onInnerDoubleClick}
onPointerOver={() => Doc.BrushDoc(this.props.node)}
- onPointerOut={() => Doc.UnBrushDoc(this.props.node)} >
+ onPointerOut={() => Doc.UnBrushDoc(this.props.node)}>
{this.regions?.map((region, i) => {
return <Keyframe key={`${i}`} {...this.props} RegionData={region} makeKeyData={this.makeKeyData} />;
})}
@@ -374,4 +387,4 @@ export class Track extends React.Component<IProps> {
</div>
);
}
-} \ No newline at end of file
+}