|
@@ -9,18 +9,21 @@ interface ITimelineProps {
|
|
|
selected: IAnimationKey | null;
|
|
|
currentFrame: number;
|
|
|
onCurrentFrameChange: (frame: number) => void;
|
|
|
+ dragKeyframe: (frame: number, index: number) => void;
|
|
|
}
|
|
|
|
|
|
|
|
|
-export class Timeline extends React.Component<ITimelineProps, { selected: IAnimationKey }>{
|
|
|
+export class Timeline extends React.Component<ITimelineProps, { selected: IAnimationKey, activeKeyframe: number | null }>{
|
|
|
readonly _frames: object[] = Array(300).fill({});
|
|
|
private _scrollable: React.RefObject<HTMLDivElement>;
|
|
|
+ private _direction: number;
|
|
|
constructor(props: ITimelineProps) {
|
|
|
super(props);
|
|
|
- if (this.props.selected !== null){
|
|
|
- this.state = { selected: this.props.selected };
|
|
|
+ if (this.props.selected !== null) {
|
|
|
+ this.state = { selected: this.props.selected, activeKeyframe: null };
|
|
|
}
|
|
|
this._scrollable = React.createRef();
|
|
|
+ this._direction = 0;
|
|
|
}
|
|
|
|
|
|
handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
|
|
@@ -44,42 +47,103 @@ export class Timeline extends React.Component<ITimelineProps, { selected: IAnima
|
|
|
|
|
|
nextKeyframe(event: React.MouseEvent<HTMLDivElement>) {
|
|
|
event.preventDefault();
|
|
|
- if (this.props.keyframes !== null){
|
|
|
- let first = this.props.keyframes.find(kf => kf.frame > this.props.currentFrame);
|
|
|
- if (first) {
|
|
|
- this.props.onCurrentFrameChange(first.frame);
|
|
|
- this.setState({ selected: first });
|
|
|
- (this._scrollable.current as HTMLDivElement).scrollLeft = first.frame * 5;
|
|
|
+ if (this.props.keyframes !== null) {
|
|
|
+ let first = this.props.keyframes.find(kf => kf.frame > this.props.currentFrame);
|
|
|
+ if (first) {
|
|
|
+ this.props.onCurrentFrameChange(first.frame);
|
|
|
+ this.setState({ selected: first });
|
|
|
+ (this._scrollable.current as HTMLDivElement).scrollLeft = first.frame * 5;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
previousKeyframe(event: React.MouseEvent<HTMLDivElement>) {
|
|
|
event.preventDefault();
|
|
|
- if (this.props.keyframes !== null){
|
|
|
- let first = this.props.keyframes.find(kf => kf.frame < this.props.currentFrame);
|
|
|
- if (first) {
|
|
|
- this.props.onCurrentFrameChange(first.frame);
|
|
|
- this.setState({ selected: first });
|
|
|
- (this._scrollable.current as HTMLDivElement).scrollLeft = -(first.frame * 5);
|
|
|
+ if (this.props.keyframes !== null) {
|
|
|
+ let first = this.props.keyframes.find(kf => kf.frame < this.props.currentFrame);
|
|
|
+ if (first) {
|
|
|
+ this.props.onCurrentFrameChange(first.frame);
|
|
|
+ this.setState({ selected: first });
|
|
|
+ (this._scrollable.current as HTMLDivElement).scrollLeft = -(first.frame * 5);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ dragStart(e: React.TouchEvent<SVGSVGElement>): void;
|
|
|
+ dragStart(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
|
|
|
+ dragStart(e: any): void {
|
|
|
+ e.preventDefault();
|
|
|
+ this.setState({ activeKeyframe: parseInt(e.target.id.replace('kf_', '')) });
|
|
|
+ this._direction = e.clientX;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ drag(e: React.TouchEvent<SVGSVGElement>): void;
|
|
|
+ drag(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
|
|
|
+ drag(e: any): void {
|
|
|
+ e.preventDefault();
|
|
|
+ if (this.props.keyframes) {
|
|
|
+ if (this.state.activeKeyframe === parseInt(e.target.id.replace('kf_', ''))) {
|
|
|
+ let updatedKeyframe = this.props.keyframes[this.state.activeKeyframe];
|
|
|
+ if (this._direction > e.clientX) {
|
|
|
+ console.log(`Dragging left ${this.state.activeKeyframe}`);
|
|
|
+ let used = this.isFrameBeingUsed(updatedKeyframe.frame - 1, -1);
|
|
|
+ if (used){
|
|
|
+ updatedKeyframe.frame = used
|
|
|
+ }
|
|
|
+
|
|
|
+ } else {
|
|
|
+ console.log(`Dragging Right ${this.state.activeKeyframe}`)
|
|
|
+ let used = this.isFrameBeingUsed(updatedKeyframe.frame + 1, 1);
|
|
|
+ if (used){
|
|
|
+ updatedKeyframe.frame = used
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.props.dragKeyframe(updatedKeyframe.frame, this.state.activeKeyframe);
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ isFrameBeingUsed(frame: number, direction: number){
|
|
|
+ let used = this.props.keyframes?.find(kf => kf.frame === frame);
|
|
|
+ if (used){
|
|
|
+ this.isFrameBeingUsed(used.frame + direction, direction);
|
|
|
+ return false;
|
|
|
+ } else {
|
|
|
+ return frame;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ dragEnd(e: React.TouchEvent<SVGSVGElement>): void;
|
|
|
+ dragEnd(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
|
|
|
+ dragEnd(e: any): void {
|
|
|
+ e.preventDefault();
|
|
|
+ this._direction = 0;
|
|
|
+ this.setState({ activeKeyframe: null })
|
|
|
}
|
|
|
|
|
|
render() {
|
|
|
return (
|
|
|
<>
|
|
|
<div className="timeline">
|
|
|
- <div ref={this._scrollable} className="display-line">
|
|
|
- <svg viewBox="0 0 2010 100" style={{ width: 2000 }}>
|
|
|
+ <div ref={this._scrollable} className="display-line" >
|
|
|
+ <svg viewBox="0 0 2010 100" style={{ width: 2000 }} onMouseMove={(e) => this.drag(e)}
|
|
|
+ onTouchMove={(e) => this.drag(e)}
|
|
|
+ onTouchStart={(e) => this.dragStart(e)}
|
|
|
+ onTouchEnd={(e) => this.dragEnd(e)}
|
|
|
+ onMouseDown={(e) => this.dragStart(e)}
|
|
|
+ onMouseUp={(e) => this.dragEnd(e)}
|
|
|
+ onMouseLeave={(e) => this.dragEnd(e)}>
|
|
|
|
|
|
<line x1={this.props.currentFrame * 10} y1="10" x2={this.props.currentFrame * 10} y2="20" style={{ stroke: '#12506b', strokeWidth: 6 }} />
|
|
|
|
|
|
{
|
|
|
this.props.keyframes && this.props.keyframes.map((kf, i) => {
|
|
|
|
|
|
- return <svg key={`kf_${i}`}>
|
|
|
- <line x1={kf.frame * 10} y1="10" x2={kf.frame * 10} y2="20" style={{ stroke: 'red', strokeWidth: 6 }} />
|
|
|
+ return <svg key={`kf_${i}`} style={{ cursor: 'pointer' }} tabIndex={i + 40} >
|
|
|
+ <line id={`kf_${i.toString()}`} x1={kf.frame * 10} y1="10" x2={kf.frame * 10} y2="20" style={{ stroke: 'red', strokeWidth: 6 }} />
|
|
|
</svg>
|
|
|
|
|
|
})
|