import * as React from "react"; import "./Timeline.scss"; import { CollectionSubView } from "../collections/CollectionSubView"; import { Document, listSpec, createSchema, makeInterface, defaultSpec } from "../../../new_fields/Schema"; import { observer } from "mobx-react"; import { Track } from "./Track"; import { observable, reaction, action, IReactionDisposer, observe, IObservableArray, computed, toJS, Reaction, IObservableObject, trace, autorun, runInAction } from "mobx"; import { Cast, NumCast, FieldValue } from "../../../new_fields/Types"; import { SelectionManager } from "../../util/SelectionManager"; import { List } from "../../../new_fields/List"; import { Self } from "../../../new_fields/FieldSymbols"; import { Doc, DocListCast } from "../../../new_fields/Doc"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCircle, faPlayCircle, faBackward, faForward, faGripLines } from "@fortawesome/free-solid-svg-icons"; import { DocumentContentsView } from "./DocumentContentsView"; import { ContextMenuProps } from "../ContextMenuItem"; import { ContextMenu } from "../ContextMenu"; import { string } from "prop-types"; import { checkIfStateModificationsAreAllowed } from "mobx/lib/internal"; import { SelectorContextMenu } from "../collections/ParentDocumentSelector"; import { DocumentManager } from "../../util/DocumentManager"; import { CollectionVideoView } from "../collections/CollectionVideoView"; export interface FlyoutProps { x?: number; y?: number; display?: string; regiondata?: Doc; regions?: List; } @observer export class Timeline extends CollectionSubView(Document) { private readonly DEFAULT_CONTAINER_HEIGHT: number = 300; private readonly DEFAULT_TICK_SPACING: number = 50; private readonly MIN_CONTAINER_HEIGHT: number = 205; private readonly MAX_CONTAINER_HEIGHT: number = 800; @observable private _isMinimized = false; @observable private _tickSpacing = this.DEFAULT_TICK_SPACING; @observable private _scrubberbox = React.createRef(); @observable private _scrubber = React.createRef(); @observable private _trackbox = React.createRef(); @observable private _titleContainer = React.createRef(); @observable private _timelineContainer = React.createRef(); @observable private _timeInput = React.createRef(); @observable private _durationInput = React.createRef(); @observable private _fadeInInput = React.createRef(); @observable private _fadeOutInput = React.createRef(); @observable private _timelineWrapper = React.createRef(); @observable private _currentBarX: number = 0; @observable private _windSpeed: number = 1; @observable private _isPlaying: boolean = false; @observable private _boxLength: number = 0; @observable private _containerHeight: number = this.DEFAULT_CONTAINER_HEIGHT; @observable private _time = 100000; //DEFAULT @observable private _infoContainer = React.createRef(); @observable private _ticks: number[] = []; @observable private flyoutInfo: FlyoutProps = { x: 0, y: 0, display: "none", regiondata: new Doc(), regions: new List() }; private block = false; @computed private get children(){ return Cast(this.props.Document[this.props.fieldKey], listSpec(Doc)) as List; } componentWillMount() { console.log(this._ticks.length); runInAction(() => { //check if this is a video frame for (let i = 0; i < this._time;) { this._ticks.push(i); i += 1000; } }); } componentDidMount() { this.initialize(); console.log(DocumentManager.Instance.getDocumentView(this.props.Document)); console.log(toJS(this.props.Document.data)); } componentDidUpdate() { runInAction(() => this._time = 100001); } componentWillUnmount() { document.removeEventListener("pointerdown", this.closeFlyout); } initialize = action(() => { let scrubber = this._scrubberbox.current!; this._boxLength = scrubber.getBoundingClientRect().width; reaction(() => this._time, time => { let infoContainer = this._infoContainer.current!; let trackbox = this._trackbox.current!; this._boxLength = infoContainer.scrollWidth; trackbox.style.width = `${this._boxLength}`; }); document.addEventListener("pointerdown", this.closeFlyout); }); @action changeCurrentBarX = (x: number) => { this._currentBarX = x; } @action onFlyoutDown = (e: React.PointerEvent) => { this.flyoutInfo.display = "block"; this.block = true; } @action closeFlyout = (e: PointerEvent) => { if (this.block) { this.block = false; return; } this.flyoutInfo.display = "none"; } //for playing @action onPlay = async (e: React.MouseEvent) => { if (this._isPlaying) { this._isPlaying = false; } else { this._isPlaying = true; this.changeCurrentX(); } } @action changeCurrentX = () => { if (this._currentBarX === this._boxLength && this._isPlaying) { this._currentBarX = 0; } if (this._currentBarX <= this._boxLength && this._isPlaying) { this._currentBarX = this._currentBarX + this._windSpeed; setTimeout(this.changeCurrentX, 15); } } @action windForward = (e: React.MouseEvent) => { if (this._windSpeed < 64) { //max speed is 32 this._windSpeed = this._windSpeed * 2; } } @action windBackward = (e: React.MouseEvent) => { if (this._windSpeed > 1 / 16) { // min speed is 1/8 this._windSpeed = this._windSpeed / 2; } } //for scrubber action @action onScrubberDown = (e: React.PointerEvent) => { e.preventDefault(); e.stopPropagation(); document.addEventListener("pointermove", this.onScrubberMove); document.addEventListener("pointerup", () => { document.removeEventListener("pointermove", this.onScrubberMove); }); } @action onScrubberMove = (e: PointerEvent) => { e.preventDefault(); e.stopPropagation(); let scrubberbox = this._scrubberbox.current!; let left = scrubberbox.getBoundingClientRect().left; let offsetX = Math.round(e.clientX - left); this._currentBarX = offsetX; } @action onScrubberClick = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); let scrubberbox = this._scrubberbox.current!; let offset = scrubberbox.scrollLeft + e.clientX - scrubberbox.getBoundingClientRect().left; this._currentBarX = offset; } @action onPanDown = (e: React.PointerEvent) => { e.preventDefault(); e.stopPropagation(); document.addEventListener("pointermove", this.onPanMove); document.addEventListener("pointerup", () => { document.removeEventListener("pointermove", this.onPanMove); }); } @action onPanMove = (e: PointerEvent) => { e.preventDefault(); e.stopPropagation(); let infoContainer = this._infoContainer.current!; let trackbox = this._trackbox.current!; let titleContainer = this._titleContainer.current!; infoContainer.scrollLeft = infoContainer.scrollLeft - e.movementX; trackbox.scrollTop = trackbox.scrollTop - e.movementY; titleContainer.scrollTop = titleContainer.scrollTop - e.movementY; } @action onResizeDown = (e: React.PointerEvent) => { e.preventDefault(); e.stopPropagation(); document.addEventListener("pointermove", this.onResizeMove); document.addEventListener("pointerup", () => { document.removeEventListener("pointermove", this.onResizeMove); }); } @action onResizeMove = (e: PointerEvent) => { e.preventDefault(); e.stopPropagation(); let offset = e.clientY - this._containerHeight; if (this._containerHeight + offset <= this.MIN_CONTAINER_HEIGHT) { this._containerHeight = this.MIN_CONTAINER_HEIGHT; } else if (this._containerHeight + offset >= this.MAX_CONTAINER_HEIGHT) { this._containerHeight = this.MAX_CONTAINER_HEIGHT; } else { this._containerHeight += offset; } } @action onTimelineDown = (e: React.PointerEvent) => { e.preventDefault(); //e.stopPropagation(); if (e.nativeEvent.which === 1){ document.addEventListener("pointermove", this.onTimelineMove); document.addEventListener("pointerup", () => { document.removeEventListener("pointermove", this.onTimelineMove);}); } } @action onTimelineMove = (e: PointerEvent) => { e.preventDefault(); e.stopPropagation(); let timelineContainer = this._timelineWrapper.current!; let left = parseFloat(timelineContainer.style.left!); let top = parseFloat(timelineContainer.style.top!); timelineContainer.style.left = `${left + e.movementX}px`; timelineContainer.style.top = `${top + e.movementY}px`; this.setPlacementHighlight(0, 0, 1000, 1000); // do something with setting the placement highlighting } @action minimize = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); let timelineContainer = this._timelineContainer.current!; if (this._isMinimized) { this._isMinimized = false; timelineContainer.style.transform = `translate(0px, 0px)`; } else { this._isMinimized = true; timelineContainer.style.transform = `translate(0px, ${- this._containerHeight - 30}px)`; } } @action toTime = (time: number): string => { const inSeconds = time / 1000; let min: (string | number) = Math.floor(inSeconds / 60); let sec: (string | number) = inSeconds % 60; if (Math.floor(sec / 10) === 0) { sec = "0" + sec; } return `${min}:${sec}`; } @action getFlyout = (props: FlyoutProps) => { for (const [k, v] of Object.entries(props)) { (this.flyoutInfo as any)[k] = v; } } timelineContextMenu = (e: React.MouseEvent): void => { let subitems: ContextMenuProps[] = []; let timelineContainer = this._timelineWrapper.current!; subitems.push({ description: "Pin to Top", event: action(() => { timelineContainer.style.transition = "top 1000ms ease-in, left 1000ms ease-in"; //????? timelineContainer.style.left = "0px"; timelineContainer.style.top = "0px"; timelineContainer.style.transition = "none"; }), icon: "pinterest" }); subitems.push({ description: "Pin to Bottom", event: action(() => { console.log(timelineContainer.getBoundingClientRect().bottom); timelineContainer.style.transform = `translate(0px, ${e.pageY - this._containerHeight}px)`; }), icon: "pinterest" }); ContextMenu.Instance.addItem({ description: "Timeline Funcs...", subitems: subitems }); } @action changeTime = (e: React.KeyboardEvent) => { let time = this._timeInput.current!; if (e.keyCode === 13) { if (!Number.isNaN(Number(time.value))) { this.flyoutInfo.regiondata!.position = Number(time.value) / 1000 * this._tickSpacing; time.placeholder = time.value + "ms"; time.value = ""; } } } @action changeDuration = (e: React.KeyboardEvent) => { let duration = this._durationInput.current!; if (e.keyCode === 13) { if (!Number.isNaN(Number(duration.value))) { this.flyoutInfo.regiondata!.duration = Number(duration.value) / 1000 * this._tickSpacing; duration.placeholder = duration.value + "ms"; duration.value = ""; } } } @action changeFadeIn = (e: React.KeyboardEvent) => { let fadeIn = this._fadeInInput.current!; if (e.keyCode === 13) { if (!Number.isNaN(Number(fadeIn.value))) { this.flyoutInfo.regiondata!.fadeIn = Number(fadeIn.value); fadeIn.placeholder = fadeIn.value + "ms"; fadeIn.value = ""; } } } @action changeFadeOut = (e: React.KeyboardEvent) => { let fadeOut = this._fadeOutInput.current!; if (e.keyCode === 13) { if (!Number.isNaN(Number(fadeOut.value))) { this.flyoutInfo.regiondata!.fadeOut = Number(fadeOut.value); fadeOut.placeholder = fadeOut.value + "ms"; fadeOut.value = ""; } } } private setPlacementHighlight = (x = 0, y = 0, height:(string| number) = 0, width:(string | number) = 0):JSX.Element => { return
; } render() { return (
{this.setPlacementHighlight(0,0,300,400)}

Time:

Duration:

Fade-in

Fade-out

{/*

Timeline Overview

*/}
{this._ticks.map(element => { return

{this.toTime(element)}

; })}
{DocListCast(this.children).map(doc => { return ; })}
{DocListCast(this.children).map(doc => { return

{doc.title}

; })}
); } }