diff options
| author | bobzel <zzzman@gmail.com> | 2023-06-23 21:44:01 -0400 |
|---|---|---|
| committer | bobzel <zzzman@gmail.com> | 2023-06-23 21:44:01 -0400 |
| commit | 85c017527f209c9d007d67ac70958843ab45e729 (patch) | |
| tree | e2649860002e0c60e98d84439a67235002ddd9a4 /src/client/views/animationtimeline | |
| parent | e9d5dbeef2bf1dab9dfb863d970b70b3074e3d0a (diff) | |
| parent | 1429ab79eac9aa316082f52c14c576f6b3a97111 (diff) | |
Merge branch 'master' into heartbeat
Diffstat (limited to 'src/client/views/animationtimeline')
| -rw-r--r-- | src/client/views/animationtimeline/Keyframe.tsx | 418 | ||||
| -rw-r--r-- | src/client/views/animationtimeline/Timeline.tsx | 14 | ||||
| -rw-r--r-- | src/client/views/animationtimeline/Track.tsx | 261 |
3 files changed, 376 insertions, 317 deletions
diff --git a/src/client/views/animationtimeline/Keyframe.tsx b/src/client/views/animationtimeline/Keyframe.tsx index 92d3e2bed..3465a5283 100644 --- a/src/client/views/animationtimeline/Keyframe.tsx +++ b/src/client/views/animationtimeline/Keyframe.tsx @@ -1,38 +1,37 @@ -import { action, computed, observable, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; -import { Doc, DocListCast, Opt } from "../../../fields/Doc"; -import { List } from "../../../fields/List"; -import { createSchema, defaultSpec, listSpec, makeInterface } from "../../../fields/Schema"; -import { Cast, NumCast } from "../../../fields/Types"; -import { Docs } from "../../documents/Documents"; -import { Transform } from "../../util/Transform"; -import { CollectionDockingView } from "../collections/CollectionDockingView"; -import "../global/globalCssVariables.scss"; -import "./Keyframe.scss"; -import "./Timeline.scss"; -import { TimelineMenu } from "./TimelineMenu"; - +import { action, computed, observable, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Doc, DocListCast, Opt } from '../../../fields/Doc'; +import { List } from '../../../fields/List'; +import { createSchema, defaultSpec, listSpec, makeInterface } from '../../../fields/Schema'; +import { Cast, NumCast } from '../../../fields/Types'; +import { Docs } from '../../documents/Documents'; +import { Transform } from '../../util/Transform'; +import { CollectionDockingView } from '../collections/CollectionDockingView'; +import '../global/globalCssVariables.scss'; +import { OpenWhereMod } from '../nodes/DocumentView'; +import './Keyframe.scss'; +import './Timeline.scss'; +import { TimelineMenu } from './TimelineMenu'; /** - * Useful static functions that you can use. Mostly for logic, but you can also add UI logic here also + * 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 { - end = "end", - fade = "fade", - default = "default", + end = 'end', + fade = 'fade', + default = 'default', } export enum Direction { - left = "left", - right = "right" + left = 'left', + right = 'right', } - export const findAdjacentRegion = (dir: KeyframeFunc.Direction, currentRegion: Doc, regions: Doc[]): (RegionData | undefined) => { - let leftMost: (RegionData | undefined) = undefined; - let rightMost: (RegionData | undefined) = undefined; + export const findAdjacentRegion = (dir: KeyframeFunc.Direction, currentRegion: Doc, regions: Doc[]): RegionData | undefined => { + let leftMost: RegionData | undefined = undefined; + let rightMost: RegionData | undefined = undefined; regions.forEach(region => { const neighbor = RegionData(region); if (currentRegion.position! > neighbor.position) { @@ -52,11 +51,12 @@ export namespace KeyframeFunc { } }; - export const calcMinLeft = (region: Doc, currentBarX: number, ref?: Doc) => { //returns the time of the closet keyframe to the left + export const calcMinLeft = (region: Doc, currentBarX: number, ref?: Doc) => { + //returns the time of the closet keyframe to the left let leftKf: Opt<Doc>; let time: number = 0; const keyframes = DocListCast(region.keyframes!); - keyframes.map((kf) => { + keyframes.map(kf => { let compTime = currentBarX; if (ref) compTime = NumCast(ref.time); if (NumCast(kf.time) < compTime && NumCast(kf.time) >= time) { @@ -67,11 +67,11 @@ export namespace KeyframeFunc { return leftKf; }; - - export const calcMinRight = (region: Doc, currentBarX: number, ref?: Doc) => { //returns the time of the closest keyframe to the right + export const calcMinRight = (region: Doc, currentBarX: number, ref?: Doc) => { + //returns the time of the closest keyframe to the right let rightKf: Opt<Doc>; let time: number = Infinity; - DocListCast(region.keyframes!).forEach((kf) => { + DocListCast(region.keyframes!).forEach(kf => { let compTime = currentBarX; if (ref) compTime = NumCast(ref.time); if (NumCast(kf.time) > compTime && NumCast(kf.time) <= NumCast(time)) { @@ -93,27 +93,31 @@ export namespace KeyframeFunc { return regiondata; }; - - export const convertPixelTime = (pos: number, unit: "mili" | "sec" | "min" | "hr", dir: "pixel" | "time", tickSpacing: number, tickIncrement: number) => { - const time = dir === "pixel" ? (pos * tickSpacing) / tickIncrement : (pos / tickSpacing) * tickIncrement; + export const convertPixelTime = (pos: number, unit: 'mili' | 'sec' | 'min' | 'hr', dir: 'pixel' | 'time', tickSpacing: number, tickIncrement: number) => { + const time = dir === 'pixel' ? (pos * tickSpacing) / tickIncrement : (pos / tickSpacing) * tickIncrement; switch (unit) { - case "mili": return time; - case "sec": return dir === "pixel" ? time / 1000 : time * 1000; - case "min": return dir === "pixel" ? time / 60000 : time * 60000; - case "hr": return dir === "pixel" ? time / 3600000 : time * 3600000; - default: return time; + case 'mili': + return time; + case 'sec': + return dir === 'pixel' ? time / 1000 : time * 1000; + case 'min': + return dir === 'pixel' ? time / 60000 : time * 60000; + case 'hr': + return dir === 'pixel' ? time / 3600000 : time * 3600000; + default: + return time; } }; } export const RegionDataSchema = createSchema({ - position: defaultSpec("number", 0), - duration: defaultSpec("number", 0), + position: defaultSpec('number', 0), + duration: defaultSpec('number', 0), keyframes: listSpec(Doc), - fadeIn: defaultSpec("number", 0), - fadeOut: defaultSpec("number", 0), + fadeIn: defaultSpec('number', 0), + fadeOut: defaultSpec('number', 0), functions: listSpec(Doc), - hasData: defaultSpec("boolean", false) + hasData: defaultSpec('boolean', false), }); export type RegionData = makeInterface<[typeof RegionDataSchema]>; export const RegionData = makeInterface(RegionDataSchema); @@ -130,50 +134,63 @@ interface IProps { makeKeyData: (region: RegionData, pos: number, kftype: KeyframeFunc.KeyframeType) => Doc; } - /** - * + * * This class handles the green region stuff * Key facts: - * + * * Structure looks like this - * + * * region as a whole * <------------------------------REGION-------------------------------> - * - * region broken down - * + * + * 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 + * + * + * 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> { - @observable private _bar = React.createRef<HTMLDivElement>(); @observable private _mouseToggled = false; @observable private _doubleClickEnabled = false; - @computed private get regiondata() { return RegionData(this.props.RegionData); } - @computed private get regions() { return DocListCast(this.props.node.regions); } - @computed private get keyframes() { return DocListCast(this.regiondata.keyframes); } - @computed private get pixelPosition() { return KeyframeFunc.convertPixelTime(this.regiondata.position, "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement); } - @computed private get pixelDuration() { return KeyframeFunc.convertPixelTime(this.regiondata.duration, "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement); } - @computed private get pixelFadeIn() { return KeyframeFunc.convertPixelTime(this.regiondata.fadeIn, "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement); } - @computed private get pixelFadeOut() { return KeyframeFunc.convertPixelTime(this.regiondata.fadeOut, "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement); } + @computed private get regiondata() { + return RegionData(this.props.RegionData); + } + @computed private get regions() { + return DocListCast(this.props.node.regions); + } + @computed private get keyframes() { + return DocListCast(this.regiondata.keyframes); + } + @computed private get pixelPosition() { + return KeyframeFunc.convertPixelTime(this.regiondata.position, 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement); + } + @computed private get pixelDuration() { + return KeyframeFunc.convertPixelTime(this.regiondata.duration, 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement); + } + @computed private get pixelFadeIn() { + return KeyframeFunc.convertPixelTime(this.regiondata.fadeIn, 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement); + } + @computed private get pixelFadeOut() { + return KeyframeFunc.convertPixelTime(this.regiondata.fadeOut, 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement); + } constructor(props: any) { super(props); } componentDidMount() { - setTimeout(() => { //giving it a temporary 1sec delay... + setTimeout(() => { + //giving it a temporary 1sec delay... if (!this.regiondata.keyframes) this.regiondata.keyframes = new List<Doc>(); const start = this.props.makeKeyData(this.regiondata, this.regiondata.position, KeyframeFunc.KeyframeType.end); const fadeIn = this.props.makeKeyData(this.regiondata, this.regiondata.position + this.regiondata.fadeIn, KeyframeFunc.KeyframeType.fade); @@ -202,12 +219,12 @@ export class Keyframe extends React.Component<IProps> { this._doubleClickEnabled = false; }, 200); this._doubleClickEnabled = true; - document.addEventListener("pointermove", this.onBarPointerMove); - document.addEventListener("pointerup", (e: PointerEvent) => { - document.removeEventListener("pointermove", this.onBarPointerMove); + document.addEventListener('pointermove', this.onBarPointerMove); + document.addEventListener('pointerup', (e: PointerEvent) => { + document.removeEventListener('pointermove', this.onBarPointerMove); }); } - } + }; @action onBarPointerMove = (e: PointerEvent) => { @@ -219,46 +236,46 @@ export class Keyframe extends React.Component<IProps> { const left = KeyframeFunc.findAdjacentRegion(KeyframeFunc.Direction.left, this.regiondata, this.regions)!; const right = KeyframeFunc.findAdjacentRegion(KeyframeFunc.Direction.right, this.regiondata, this.regions)!; const prevX = this.regiondata.position; - const futureX = this.regiondata.position + KeyframeFunc.convertPixelTime(e.movementX, "mili", "time", this.props.tickSpacing, this.props.tickIncrement); + const futureX = this.regiondata.position + KeyframeFunc.convertPixelTime(e.movementX, 'mili', 'time', this.props.tickSpacing, this.props.tickIncrement); if (futureX <= 0) { this.regiondata.position = 0; - } else if ((left && left.position + left.duration >= futureX)) { + } else if (left && left.position + left.duration >= futureX) { this.regiondata.position = left.position + left.duration; - } else if ((right && right.position <= futureX + this.regiondata.duration)) { + } else if (right && right.position <= futureX + this.regiondata.duration) { this.regiondata.position = right.position - this.regiondata.duration; } else { this.regiondata.position = futureX; } const movement = this.regiondata.position - prevX; - this.keyframes.forEach(kf => kf.time = NumCast(kf.time) + movement); - } + this.keyframes.forEach(kf => (kf.time = NumCast(kf.time) + movement)); + }; @action onResizeLeft = (e: React.PointerEvent) => { e.preventDefault(); e.stopPropagation(); - document.addEventListener("pointermove", this.onDragResizeLeft); - document.addEventListener("pointerup", () => { - document.removeEventListener("pointermove", this.onDragResizeLeft); + document.addEventListener('pointermove', this.onDragResizeLeft); + document.addEventListener('pointerup', () => { + document.removeEventListener('pointermove', this.onDragResizeLeft); }); - } + }; @action onResizeRight = (e: React.PointerEvent) => { e.preventDefault(); e.stopPropagation(); - document.addEventListener("pointermove", this.onDragResizeRight); - document.addEventListener("pointerup", () => { - document.removeEventListener("pointermove", this.onDragResizeRight); + document.addEventListener('pointermove', this.onDragResizeRight); + document.addEventListener('pointerup', () => { + document.removeEventListener('pointermove', this.onDragResizeRight); }); - } + }; @action onDragResizeLeft = (e: PointerEvent) => { e.preventDefault(); e.stopPropagation(); const bar = this._bar.current!; - const offset = KeyframeFunc.convertPixelTime(Math.round((e.clientX - bar.getBoundingClientRect().left) * this.props.transform.Scale), "mili", "time", this.props.tickSpacing, this.props.tickIncrement); + const offset = KeyframeFunc.convertPixelTime(Math.round((e.clientX - bar.getBoundingClientRect().left) * this.props.transform.Scale), 'mili', 'time', this.props.tickSpacing, this.props.tickIncrement); const leftRegion = KeyframeFunc.findAdjacentRegion(KeyframeFunc.Direction.left, this.regiondata, this.regions); if (leftRegion && this.regiondata.position + offset <= leftRegion.position + leftRegion.duration) { this.regiondata.position = leftRegion.position + leftRegion.duration; @@ -275,90 +292,91 @@ export class Keyframe extends React.Component<IProps> { } this.keyframes[0].time = this.regiondata.position; this.keyframes[1].time = this.regiondata.position + this.regiondata.fadeIn; - } - + }; @action onDragResizeRight = (e: PointerEvent) => { e.preventDefault(); e.stopPropagation(); const bar = this._bar.current!; - const offset = KeyframeFunc.convertPixelTime(Math.round((e.clientX - bar.getBoundingClientRect().right) * this.props.transform.Scale), "mili", "time", this.props.tickSpacing, this.props.tickIncrement); + const offset = KeyframeFunc.convertPixelTime(Math.round((e.clientX - bar.getBoundingClientRect().right) * this.props.transform.Scale), 'mili', 'time', this.props.tickSpacing, this.props.tickIncrement); const rightRegion = KeyframeFunc.findAdjacentRegion(KeyframeFunc.Direction.right, this.regiondata, this.regions); const fadeOutKeyframeTime = NumCast(this.keyframes[this.keyframes.length - 3].time); - if (this.regiondata.position + this.regiondata.duration - this.regiondata.fadeOut + offset <= fadeOutKeyframeTime) { //case 1: when third to last keyframe is in the way + if (this.regiondata.position + this.regiondata.duration - this.regiondata.fadeOut + offset <= fadeOutKeyframeTime) { + //case 1: when third to last keyframe is in the way this.regiondata.duration = fadeOutKeyframeTime - this.regiondata.position + this.regiondata.fadeOut; - } else if (rightRegion && (this.regiondata.position + this.regiondata.duration + offset >= rightRegion.position)) { + } else if (rightRegion && this.regiondata.position + this.regiondata.duration + offset >= rightRegion.position) { this.regiondata.duration = rightRegion.position - this.regiondata.position; } else { this.regiondata.duration += offset; } this.keyframes[this.keyframes.length - 2].time = this.regiondata.position + this.regiondata.duration - this.regiondata.fadeOut; this.keyframes[this.keyframes.length - 1].time = this.regiondata.position + this.regiondata.duration; - } - + }; @action createKeyframe = async (clientX: number) => { this._mouseToggled = true; const bar = this._bar.current!; - const offset = KeyframeFunc.convertPixelTime(Math.round((clientX - bar.getBoundingClientRect().left) * this.props.transform.Scale), "mili", "time", this.props.tickSpacing, this.props.tickIncrement); - if (offset > this.regiondata.fadeIn && offset < this.regiondata.duration - this.regiondata.fadeOut) { //make sure keyframe is not created inbetween fades and ends + const offset = KeyframeFunc.convertPixelTime(Math.round((clientX - bar.getBoundingClientRect().left) * this.props.transform.Scale), 'mili', 'time', this.props.tickSpacing, this.props.tickIncrement); + if (offset > this.regiondata.fadeIn && offset < this.regiondata.duration - this.regiondata.fadeOut) { + //make sure keyframe is not created inbetween fades and ends const position = this.regiondata.position; this.props.makeKeyData(this.regiondata, Math.round(position + offset), KeyframeFunc.KeyframeType.default); this.regiondata.hasData = true; - this.props.changeCurrentBarX(KeyframeFunc.convertPixelTime(Math.round(position + offset), "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement)); //first move the keyframe to the correct location and make a copy so the correct file gets coppied - + this.props.changeCurrentBarX(KeyframeFunc.convertPixelTime(Math.round(position + offset), 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement)); //first move the keyframe to the correct location and make a copy so the correct file gets coppied } - } - + }; @action moveKeyframe = async (e: React.MouseEvent, kf: Doc) => { e.preventDefault(); e.stopPropagation(); - this.props.changeCurrentBarX(KeyframeFunc.convertPixelTime(NumCast(kf.time!), "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement)); - } + this.props.changeCurrentBarX(KeyframeFunc.convertPixelTime(NumCast(kf.time!), 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement)); + }; /** * custom keyframe context menu items (when clicking on the keyframe circle) */ @action makeKeyframeMenu = (kf: Doc, e: MouseEvent) => { - TimelineMenu.Instance.addItem("button", "Toggle Fade Only", () => { + TimelineMenu.Instance.addItem('button', 'Toggle Fade Only', () => { kf.type = kf.type === KeyframeFunc.KeyframeType.fade ? KeyframeFunc.KeyframeType.default : KeyframeFunc.KeyframeType.fade; }), - TimelineMenu.Instance.addItem("button", "Show Data", action(() => { - const kvp = Docs.Create.KVPDocument(kf, { _width: 300, _height: 300 }); - CollectionDockingView.AddSplit(kvp, "right"); - })), - TimelineMenu.Instance.addItem("button", "Delete", action(() => { - (this.regiondata.keyframes as List<Doc>).splice(this.keyframes.indexOf(kf), 1); - this.forceUpdate(); - })), - TimelineMenu.Instance.addItem("input", "Move", action((val) => { - let cannotMove: boolean = false; - const kfIndex: number = this.keyframes.indexOf(kf); - if (val < 0 || (val < NumCast(this.keyframes[kfIndex - 1].time) || val > NumCast(this.keyframes[kfIndex + 1].time))) { - cannotMove = true; - } - if (!cannotMove) { - this.keyframes[kfIndex].time = parseInt(val, 10); - this.keyframes[1].time = this.regiondata.position + this.regiondata.fadeIn; - } - })); - TimelineMenu.Instance.addMenu("Keyframe"); + TimelineMenu.Instance.addItem( + 'button', + 'Delete', + action(() => { + (this.regiondata.keyframes as List<Doc>).splice(this.keyframes.indexOf(kf), 1); + this.forceUpdate(); + }) + ), + TimelineMenu.Instance.addItem( + 'input', + 'Move', + action(val => { + let cannotMove: boolean = false; + const kfIndex: number = this.keyframes.indexOf(kf); + if (val < 0 || val < NumCast(this.keyframes[kfIndex - 1].time) || val > NumCast(this.keyframes[kfIndex + 1].time)) { + cannotMove = true; + } + if (!cannotMove) { + this.keyframes[kfIndex].time = parseInt(val, 10); + this.keyframes[1].time = this.regiondata.position + this.regiondata.fadeIn; + } + }) + ); + TimelineMenu.Instance.addMenu('Keyframe'); TimelineMenu.Instance.openMenu(e.clientX, e.clientY); - } + }; /** - * context menu for region (anywhere on the green region). + * context menu for region (anywhere on the green region). */ @action makeRegionMenu = (kf: Doc, e: MouseEvent) => { - TimelineMenu.Instance.addItem("button", "Remove Region", () => - Cast(this.props.node.regions, listSpec(Doc))?.splice(this.regions.indexOf(this.props.RegionData), 1)), - TimelineMenu.Instance.addItem("input", `fadeIn: ${this.regiondata.fadeIn}ms`, (val) => { + TimelineMenu.Instance.addItem('button', 'Remove Region', () => Cast(this.props.node.regions, listSpec(Doc))?.splice(this.regions.indexOf(this.props.RegionData), 1)), + TimelineMenu.Instance.addItem('input', `fadeIn: ${this.regiondata.fadeIn}ms`, val => { runInAction(() => { let cannotMove: boolean = false; if (val < 0 || val > NumCast(this.keyframes[2].time) - this.regiondata.position) { @@ -370,7 +388,7 @@ export class Keyframe extends React.Component<IProps> { } }); }), - TimelineMenu.Instance.addItem("input", `fadeOut: ${this.regiondata.fadeOut}ms`, (val) => { + TimelineMenu.Instance.addItem('input', `fadeOut: ${this.regiondata.fadeOut}ms`, val => { runInAction(() => { let cannotMove: boolean = false; if (val < 0 || val > this.regiondata.position + this.regiondata.duration - NumCast(this.keyframes[this.keyframes.length - 3].time)) { @@ -382,34 +400,38 @@ export class Keyframe extends React.Component<IProps> { } }); }), - TimelineMenu.Instance.addItem("input", `position: ${this.regiondata.position}ms`, (val) => { + TimelineMenu.Instance.addItem('input', `position: ${this.regiondata.position}ms`, val => { runInAction(() => { const prevPosition = this.regiondata.position; let cannotMove: boolean = false; - this.regions.map(region => ({ pos: NumCast(region.position), dur: NumCast(region.duration) })).forEach(({ pos, dur }) => { - if (pos !== this.regiondata.position) { - if ((val < 0) || (val > pos && val < pos + dur || (this.regiondata.duration + val > pos && this.regiondata.duration + val < pos + dur))) { - cannotMove = true; + this.regions + .map(region => ({ pos: NumCast(region.position), dur: NumCast(region.duration) })) + .forEach(({ pos, dur }) => { + if (pos !== this.regiondata.position) { + if (val < 0 || (val > pos && val < pos + dur) || (this.regiondata.duration + val > pos && this.regiondata.duration + val < pos + dur)) { + cannotMove = true; + } } - } - }); + }); if (!cannotMove) { this.regiondata.position = parseInt(val, 10); this.updateKeyframes(this.regiondata.position - prevPosition); } }); }), - TimelineMenu.Instance.addItem("input", `duration: ${this.regiondata.duration}ms`, (val) => { + TimelineMenu.Instance.addItem('input', `duration: ${this.regiondata.duration}ms`, val => { runInAction(() => { let cannotMove: boolean = false; - this.regions.map(region => ({ pos: NumCast(region.position), dur: NumCast(region.duration) })).forEach(({ pos, dur }) => { - if (pos !== this.regiondata.position) { - val += this.regiondata.position; - if ((val < 0) || (val > pos && val < pos + dur)) { - cannotMove = true; + this.regions + .map(region => ({ pos: NumCast(region.position), dur: NumCast(region.duration) })) + .forEach(({ pos, dur }) => { + if (pos !== this.regiondata.position) { + val += this.regiondata.position; + if (val < 0 || (val > pos && val < pos + dur)) { + cannotMove = true; + } } - } - }); + }); if (!cannotMove) { this.regiondata.duration = parseInt(val, 10); this.keyframes[this.keyframes.length - 1].time = this.regiondata.position + this.regiondata.duration; @@ -417,9 +439,9 @@ export class Keyframe extends React.Component<IProps> { } }); }), - TimelineMenu.Instance.addMenu("Region"); + TimelineMenu.Instance.addMenu('Region'); TimelineMenu.Instance.openMenu(e.clientX, e.clientY); - } + }; @action updateKeyframes = (incr: number, filter: number[] = []) => { @@ -428,7 +450,7 @@ export class Keyframe extends React.Component<IProps> { kf.time = NumCast(kf.time) + incr; } }); - } + }; /** * hovering effect when hovered (hidden div darkens) @@ -438,9 +460,9 @@ export class Keyframe extends React.Component<IProps> { e.preventDefault(); e.stopPropagation(); const div = ref.current!; - div.style.opacity = "1"; + div.style.opacity = '1'; Doc.BrushDoc(this.props.node); - } + }; /** * hovering effect when hovered out (hidden div becomes invisible) @@ -450,14 +472,12 @@ export class Keyframe extends React.Component<IProps> { e.preventDefault(); e.stopPropagation(); const div = ref.current!; - div.style.opacity = "0"; + div.style.opacity = '0'; Doc.UnBrushDoc(this.props.node); - } - + }; ///////////////////////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) @@ -465,32 +485,43 @@ export class Keyframe extends React.Component<IProps> { drawKeyframes = () => { const keyframeDivs: JSX.Element[] = []; return DocListCast(this.regiondata.keyframes).map(kf => { - if (kf.type as KeyframeFunc.KeyframeType !== KeyframeFunc.KeyframeType.end) { - return <> - <div className="keyframe" style={{ left: `${KeyframeFunc.convertPixelTime(NumCast(kf.time), "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement) - this.pixelPosition}px` }}> - <div className="divider"></div> - <div className="keyframeCircle keyframe-indicator" - onPointerDown={(e) => { e.preventDefault(); e.stopPropagation(); this.moveKeyframe(e, kf); }} - onContextMenu={(e: React.MouseEvent) => { - e.preventDefault(); - e.stopPropagation(); - this.makeKeyframeMenu(kf, e.nativeEvent); - }} - onDoubleClick={(e) => { e.preventDefault(); e.stopPropagation(); }}> + if ((kf.type as KeyframeFunc.KeyframeType) !== KeyframeFunc.KeyframeType.end) { + return ( + <> + <div className="keyframe" style={{ left: `${KeyframeFunc.convertPixelTime(NumCast(kf.time), 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement) - this.pixelPosition}px` }}> + <div className="divider"></div> + <div + className="keyframeCircle keyframe-indicator" + onPointerDown={e => { + e.preventDefault(); + e.stopPropagation(); + this.moveKeyframe(e, kf); + }} + onContextMenu={(e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + this.makeKeyframeMenu(kf, e.nativeEvent); + }} + onDoubleClick={e => { + e.preventDefault(); + e.stopPropagation(); + }}></div> </div> - </div> - <div className="keyframe-information" /> - </>; + <div className="keyframe-information" /> + </> + ); } else { - return <div className="keyframe" style={{ left: `${KeyframeFunc.convertPixelTime(NumCast(kf.time), "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement) - this.pixelPosition}px` }}> - <div className="divider" /> - </div>; + return ( + <div className="keyframe" style={{ left: `${KeyframeFunc.convertPixelTime(NumCast(kf.time), 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement) - this.pixelPosition}px` }}> + <div className="divider" /> + </div> + ); } }); - } + }; /** - * drawing the hidden divs that partition different intervals within a region. + * drawing the hidden divs that partition different intervals within a region. */ @action drawKeyframeDividers = () => { @@ -500,26 +531,36 @@ export class Keyframe extends React.Component<IProps> { if (index !== this.keyframes.length - 1) { const right = this.keyframes[index + 1]; const bodyRef = React.createRef<HTMLDivElement>(); - const kfPos = KeyframeFunc.convertPixelTime(NumCast(kf.time), "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement); - const rightPos = KeyframeFunc.convertPixelTime(NumCast(right.time), "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement); + const kfPos = KeyframeFunc.convertPixelTime(NumCast(kf.time), 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement); + const rightPos = KeyframeFunc.convertPixelTime(NumCast(right.time), 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement); keyframeDividers.push( - <div ref={bodyRef} className="body-container" style={{ left: `${kfPos - this.pixelPosition}px`, width: `${rightPos - kfPos}px` }} - onPointerOver={(e) => { e.preventDefault(); e.stopPropagation(); this.onContainerOver(e, bodyRef); }} - onPointerOut={(e) => { e.preventDefault(); e.stopPropagation(); this.onContainerOut(e, bodyRef); }} - onContextMenu={(e) => { + <div + ref={bodyRef} + className="body-container" + style={{ left: `${kfPos - this.pixelPosition}px`, width: `${rightPos - kfPos}px` }} + onPointerOver={e => { + e.preventDefault(); + e.stopPropagation(); + this.onContainerOver(e, bodyRef); + }} + onPointerOut={e => { + e.preventDefault(); + e.stopPropagation(); + this.onContainerOut(e, bodyRef); + }} + onContextMenu={e => { e.preventDefault(); e.stopPropagation(); if (index !== 0 || index !== this.keyframes.length - 2) { this._mouseToggled = true; } this.makeRegionMenu(kf, e.nativeEvent); - }}> - </div> + }}></div> ); } }); return keyframeDividers; - } + }; /** * rendering that green region @@ -527,13 +568,18 @@ export class Keyframe extends React.Component<IProps> { //154, 206, 223 render() { return ( - <div className="bar" ref={this._bar} style={{ - transform: `translate(${this.pixelPosition}px)`, - width: `${this.pixelDuration}px`, - background: `linear-gradient(90deg, rgba(154, 206, 223, 0) 0%, rgba(154, 206, 223, 1) ${this.pixelFadeIn / this.pixelDuration * 100}%, rgba(154, 206, 223, 1) ${(this.pixelDuration - this.pixelFadeOut) / this.pixelDuration * 100}%, rgba(154, 206, 223, 0) 100% )` - }} + <div + className="bar" + ref={this._bar} + style={{ + transform: `translate(${this.pixelPosition}px)`, + width: `${this.pixelDuration}px`, + background: `linear-gradient(90deg, rgba(154, 206, 223, 0) 0%, rgba(154, 206, 223, 1) ${(this.pixelFadeIn / this.pixelDuration) * 100}%, rgba(154, 206, 223, 1) ${ + ((this.pixelDuration - this.pixelFadeOut) / this.pixelDuration) * 100 + }%, rgba(154, 206, 223, 0) 100% )`, + }} onPointerDown={this.onBarPointerDown}> - <div className="leftResize keyframe-indicator" onPointerDown={this.onResizeLeft} ></div> + <div className="leftResize keyframe-indicator" onPointerDown={this.onResizeLeft}></div> {/* <div className="keyframe-information"></div> */} <div className="rightResize keyframe-indicator" onPointerDown={this.onResizeRight}></div> {/* <div className="keyframe-information"></div> */} @@ -542,4 +588,4 @@ export class Keyframe extends React.Component<IProps> { </div> ); } -}
\ No newline at end of file +} diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx index 7a393a4f7..adc97bbb4 100644 --- a/src/client/views/animationtimeline/Timeline.tsx +++ b/src/client/views/animationtimeline/Timeline.tsx @@ -76,7 +76,7 @@ export class Timeline extends React.Component<FieldViewProps> { private get children(): Doc[] { const annotatedDoc = [DocumentType.IMG, DocumentType.VID, DocumentType.PDF, DocumentType.MAP].includes(StrCast(this.props.Document.type) as any); if (annotatedDoc) { - return DocListCast(this.props.Document[Doc.LayoutFieldKey(this.props.Document) + '-annotations']); + return DocListCast(this.props.Document[Doc.LayoutFieldKey(this.props.Document) + '_annotations']); } return DocListCast(this.props.Document[this.props.fieldKey]); } @@ -290,15 +290,15 @@ export class Timeline extends React.Component<FieldViewProps> { }; resetView(doc: Doc) { - doc._panX = doc._customOriginX ?? 0; - doc._panY = doc._customOriginY ?? 0; - doc._viewScale = doc._customOriginScale ?? 1; + doc._freeform_panX = doc._customOriginX ?? 0; + doc._freeform_panY = doc._customOriginY ?? 0; + doc._freeform_scale = doc._customOriginScale ?? 1; } setView(doc: Doc) { - doc._customOriginX = doc._panX; - doc._customOriginY = doc._panY; - doc._customOriginScale = doc._viewScale; + doc._customOriginX = doc._freeform_panX; + doc._customOriginY = doc._freeform_panY; + doc._customOriginScale = doc._freeform_scale; } /** * zooming mechanism (increment and spacing changes) 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 +} |
