diff options
Diffstat (limited to 'src/client/apis')
| -rw-r--r-- | src/client/apis/youtube/YoutubeBox.scss | 87 | ||||
| -rw-r--r-- | src/client/apis/youtube/YoutubeBox.tsx | 260 |
2 files changed, 347 insertions, 0 deletions
diff --git a/src/client/apis/youtube/YoutubeBox.scss b/src/client/apis/youtube/YoutubeBox.scss new file mode 100644 index 000000000..5b539b463 --- /dev/null +++ b/src/client/apis/youtube/YoutubeBox.scss @@ -0,0 +1,87 @@ +ul { + list-style-type: none; + padding-inline-start: 10px; +} + + +li { + margin: 4px; + display: inline-flex; +} + +li:hover { + cursor: pointer; + opacity: 0.8; +} + +.search_wrapper { + width: 100%; + display: inline-flex; + height: 175px; + + .textual_info { + font-family: Arial, Helvetica, sans-serif; + + .videoTitle { + margin-left: 4px; + // display: inline-block; + color: #0D0D0D; + -webkit-line-clamp: 2; + display: block; + max-height: 4.8rem; + overflow: hidden; + font-size: 1.8rem; + font-weight: 400; + line-height: 2.4rem; + -webkit-box-orient: vertical; + text-overflow: ellipsis; + white-space: normal; + display: -webkit-box; + } + + .channelName { + color:#606060; + margin-left: 4px; + font-size: 1.3rem; + font-weight: 400; + line-height: 1.8rem; + text-transform: none; + margin-top: 0px; + display: inline-block; + } + + .video_description { + margin-left: 4px; + // font-size: 12px; + color: #606060; + padding-top: 8px; + margin-bottom: 8px; + display: block; + line-height: 1.8rem; + max-height: 4.2rem; + overflow: hidden; + font-size: 1.3rem; + font-weight: 400; + text-transform: none; + } + + .publish_time { + //display: inline-block; + margin-left: 8px; + padding: 0; + border: 0; + background: transparent; + color: #606060; + max-width: 100%; + line-height: 1.8rem; + max-height: 3.6rem; + overflow: hidden; + font-size: 1.3rem; + font-weight: 400; + text-transform: none; + } + + + + } +}
\ No newline at end of file diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx new file mode 100644 index 000000000..7ac8d06f6 --- /dev/null +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -0,0 +1,260 @@ +import "../../views/nodes/WebBox.scss"; +import React = require("react"); +import { FieldViewProps, FieldView } from "../../views/nodes/FieldView"; +import { HtmlField } from "../../../new_fields/HtmlField"; +import { WebField } from "../../../new_fields/URLField"; +import { observer } from "mobx-react"; +import { computed, reaction, IReactionDisposer, observable, action, runInAction } from 'mobx'; +import { DocumentDecorations } from "../../views/DocumentDecorations"; +import { InkingControl } from "../../views/InkingControl"; +import { Utils } from "../../../Utils"; +import { DocServer } from "../../DocServer"; +import { NumCast, Cast, StrCast } from "../../../new_fields/Types"; +import "./YoutubeBox.scss"; +import { Docs } from "../../documents/Documents"; +import { Doc } from "../../../new_fields/Doc"; +import { listSpec } from "../../../new_fields/Schema"; +import { List } from "../../../new_fields/List"; + + +@observer +export class YoutubeBox extends React.Component<FieldViewProps> { + + @observable YoutubeSearchElement: HTMLInputElement | undefined; + @observable searchResultsFound: boolean = false; + @observable searchResults: any[] = []; + @observable videoClicked: boolean = false; + @observable selectedVideoUrl: string = ""; + @observable lisOfBackUp: JSX.Element[] = []; + + + public static LayoutString() { return FieldView.LayoutString(YoutubeBox); } + + async componentWillMount() { + //DocServer.getYoutubeChannels(); + let castedBackUpDocs = Cast(this.props.Document.cachedSearch, listSpec(Doc)); + if (!castedBackUpDocs) { + this.props.Document.cachedSearch = castedBackUpDocs = new List<Doc>(); + } + if (castedBackUpDocs.length !== 0) { + + this.searchResultsFound = true; + + for (let videoBackUp of castedBackUpDocs) { + let curBackUp = await videoBackUp; + let videoId = StrCast(curBackUp.videoId); + let videoTitle = StrCast(curBackUp.videoTitle); + let thumbnailUrl = StrCast(curBackUp.thumbnailUrl); + runInAction(() => this.lisOfBackUp.push(( + <li + onClick={() => this.embedVideoOnClick(videoId, videoTitle)} + key={Utils.GenerateGuid()} + > + <img src={thumbnailUrl} /> + <span className="videoTitle">{videoTitle}</span> + </li>) + )); + } + + + } + + + } + + _ignore = 0; + onPreWheel = (e: React.WheelEvent) => { + this._ignore = e.timeStamp; + } + onPrePointer = (e: React.PointerEvent) => { + this._ignore = e.timeStamp; + } + onPostPointer = (e: React.PointerEvent) => { + if (this._ignore !== e.timeStamp) { + e.stopPropagation(); + } + } + onPostWheel = (e: React.WheelEvent) => { + if (this._ignore !== e.timeStamp) { + e.stopPropagation(); + } + } + + onEnterKeyDown = (e: React.KeyboardEvent) => { + if (e.keyCode === 13) { + let submittedTitle = this.YoutubeSearchElement!.value; + this.YoutubeSearchElement!.value = ""; + this.YoutubeSearchElement!.blur(); + DocServer.getYoutubeVideos(submittedTitle, this.processesVideoResults); + + } + } + + @action + processesVideoResults = (videos: any[]) => { + this.searchResults = videos; + if (this.searchResults.length > 0) { + this.searchResultsFound = true; + this.backUpSearchResults(videos); + if (this.videoClicked) { + this.videoClicked = false; + } + } + } + + backUpSearchResults = (videos: any[]) => { + let newCachedList = new List<Doc>(); + this.props.Document.cachedSearch = newCachedList; + videos.forEach((video) => { + let videoBackUp = new Doc(); + videoBackUp.videoId = video.id.videoId; + videoBackUp.videoTitle = this.filterYoutubeTitleResult(video.snippet.title); + videoBackUp.thumbnailUrl = video.snippet.thumbnails.medium.url; + newCachedList.push(videoBackUp); + }); + } + + filterYoutubeTitleResult = (resultTitle: string) => { + let processedTitle: string = resultTitle.ReplaceAll("&", "&"); + processedTitle = processedTitle.ReplaceAll("'", "'"); + processedTitle = processedTitle.ReplaceAll(""", "\""); + return processedTitle; + } + + roundPublishTime = (publishTime: string) => { + let date = new Date(publishTime); + let curDate = new Date(); + let videoYearDif = curDate.getFullYear() - date.getFullYear(); + let videoMonthDif = curDate.getMonth() - date.getMonth(); + let videoDayDif = curDate.getDay() - date.getDay(); + console.log("video day dif: ", videoDayDif, " first day: ", curDate.getDay(), " second day: ", date.getDay()); + let videoHoursDif = curDate.getHours() - date.getHours(); + let videoMinutesDif = curDate.getMinutes() - date.getMinutes(); + let videoSecondsDif = curDate.getSeconds() - date.getSeconds(); + if (videoYearDif !== 0) { + return videoYearDif + " years ago"; + } else if (videoMonthDif !== 0) { + return videoMonthDif + " months ago"; + } else if (videoDayDif !== 0) { + return videoDayDif + " days ago"; + } else if (videoHoursDif !== 0) { + return videoHoursDif + " hours ago"; + } else if (videoMinutesDif) { + return videoMinutesDif + " minutes ago"; + } else if (videoSecondsDif) { + return videoSecondsDif + " seconds ago"; + } + + console.log("Date : ", date); + } + + roundPublishTime2 = (publishTime: string) => { + let date = new Date(publishTime).getTime(); + let curDate = new Date().getTime(); + let timeDif = curDate - date; + let totalSeconds = timeDif / 1000; + let totalMin = totalSeconds / 60; + let totalHours = totalMin / 60; + let totalDays = totalHours / 24; + let totalMonths = totalDays / 30.417; + let totalYears = totalMonths / 12; + + + let truncYears = Math.trunc(totalYears); + let truncMonths = Math.trunc(totalMonths); + let truncDays = Math.trunc(totalDays); + let truncHours = Math.trunc(totalHours); + let truncMin = Math.trunc(totalMin); + let truncSec = Math.trunc(totalSeconds); + + let pluralCase = ""; + + if (truncYears !== 0) { + truncYears > 1 ? pluralCase = "s" : pluralCase = ""; + return truncYears + " year" + pluralCase + " ago"; + } else if (truncMonths !== 0) { + truncMonths > 1 ? pluralCase = "s" : pluralCase = ""; + return truncMonths + " month" + pluralCase + " ago"; + } else if (truncDays !== 0) { + truncDays > 1 ? pluralCase = "s" : pluralCase = ""; + return truncDays + " day" + pluralCase + " ago"; + } else if (truncHours !== 0) { + truncHours > 1 ? pluralCase = "s" : pluralCase = ""; + return truncHours + " hour" + pluralCase + " ago"; + } else if (truncMin !== 0) { + truncMin > 1 ? pluralCase = "s" : pluralCase = ""; + return truncMin + " minute" + pluralCase + " ago"; + } else if (truncSec !== 0) { + truncSec > 1 ? pluralCase = "s" : pluralCase = ""; + return truncSec + " second" + pluralCase + " ago"; + } + } + + renderSearchResultsOrVideo = () => { + if (this.searchResultsFound) { + if (this.searchResults.length !== 0) { + return <ul> + {this.searchResults.map((video) => { + let filteredTitle = this.filterYoutubeTitleResult(video.snippet.title); + let channelTitle = video.snippet.channelTitle; + let videoDescription = video.snippet.description; + let pusblishDate = this.roundPublishTime2(video.snippet.publishedAt); + // let duration = video.contentDetails.duration; + //let viewCount = video.statistics.viewCount; + //this.roundPublishTime(pusblishDate); + //this.roundPublishTime2(video.snippet.publishedAt); + return <li onClick={() => this.embedVideoOnClick(video.id.videoId, filteredTitle)} key={Utils.GenerateGuid()}> + <div className="search_wrapper"> + <img src={video.snippet.thumbnails.medium.url} /> + <div className="textual_info"> + <span className="videoTitle">{filteredTitle}</span> + <span className="channelName">{channelTitle}</span> + <span className="publish_time">{pusblishDate}</span> + {/* <h6 className="viewCount">{viewCount}</h6> */} + <p className="video_description">{videoDescription}</p> + </div> + </div> + </li>; + })} + </ul>; + } else if (this.lisOfBackUp.length !== 0) { + return <ul>{this.lisOfBackUp}</ul>; + } + } else { + return (null); + } + } + + @action + embedVideoOnClick = (videoId: string, filteredTitle: string) => { + let embeddedUrl = "https://www.youtube.com/embed/" + videoId; + console.log("EmbeddedUrl: ", embeddedUrl); + this.selectedVideoUrl = embeddedUrl; + let addFunction = this.props.addDocument!; + let newVideoX = NumCast(this.props.Document.x); + let newVideoY = NumCast(this.props.Document.y) + NumCast(this.props.Document.height); + + addFunction(Docs.Create.VideoDocument(embeddedUrl, { title: filteredTitle, width: 400, height: 315, x: newVideoX, y: newVideoY })); + this.videoClicked = true; + } + + render() { + let field = this.props.Document[this.props.fieldKey]; + let content = + <div style={{ width: "100%", height: "100%", position: "absolute" }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}> + <input type="text" placeholder="Search for a video" onKeyDown={this.onEnterKeyDown} style={{ height: 40, width: "100%", border: "1px solid black", padding: 5, textAlign: "center" }} ref={(e) => this.YoutubeSearchElement = e!} /> + {this.renderSearchResultsOrVideo()} + </div>; + + let frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; + + let classname = "webBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); + return ( + <> + <div className={classname} > + {content} + </div> + {!frozen ? (null) : <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} />} + </>); + } +}
\ No newline at end of file |
