From 0f034260b18dc06f1a1267af8707481eff8ac21a Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Tue, 25 Jun 2019 16:58:02 -0400 Subject: Youtube Api Example Js setup --- src/client/apis/youtube/youtubeApiSample.d.ts | 3 + src/client/apis/youtube/youtubeApiSample.js | 128 +++++++++++++++++++++ .../views/presentationview/PresentationElement.tsx | 3 - 3 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 src/client/apis/youtube/youtubeApiSample.d.ts create mode 100644 src/client/apis/youtube/youtubeApiSample.js (limited to 'src') diff --git a/src/client/apis/youtube/youtubeApiSample.d.ts b/src/client/apis/youtube/youtubeApiSample.d.ts new file mode 100644 index 000000000..87a669e36 --- /dev/null +++ b/src/client/apis/youtube/youtubeApiSample.d.ts @@ -0,0 +1,3 @@ + +declare const YoutubeApi: any; +export = YoutubeApi; \ No newline at end of file diff --git a/src/client/apis/youtube/youtubeApiSample.js b/src/client/apis/youtube/youtubeApiSample.js new file mode 100644 index 000000000..07c3add36 --- /dev/null +++ b/src/client/apis/youtube/youtubeApiSample.js @@ -0,0 +1,128 @@ +let fs = require('fs'); +let readline = require('readline'); +let { google } = require('googleapis'); +let OAuth2 = google.auth.OAuth2; + +// If modifying these scopes, delete your previously saved credentials +// at ~/.credentials/youtube-nodejs-quickstart.json +let SCOPES = ['https://www.googleapis.com/auth/youtube.readonly']; +let TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH || + process.env.USERPROFILE) + '/.credentials/'; +let TOKEN_PATH = TOKEN_DIR + 'youtube-nodejs-quickstart.json'; + +function readFsFile() { + // Load client secrets from a local file. + fs.readFile('client_secret.json', function processClientSecrets(err, content) { + if (err) { + console.log('Error loading client secret file: ' + err); + return; + } + // Authorize a client with the loaded credentials, then call the YouTube API. + authorize(JSON.parse(content), getChannel); + }); +} + +/** + * Create an OAuth2 client with the given credentials, and then execute the + * given callback function. + * + * @param {Object} credentials The authorization client credentials. + * @param {function} callback The callback to call with the authorized client. + */ +function authorize(credentials, callback) { + let clientSecret = credentials.installed.client_secret; + let clientId = credentials.installed.client_id; + let redirectUrl = credentials.installed.redirect_uris[0]; + let oauth2Client = new OAuth2(clientId, clientSecret, redirectUrl); + + // Check if we have previously stored a token. + fs.readFile(TOKEN_PATH, function (err, token) { + if (err) { + getNewToken(oauth2Client, callback); + } else { + oauth2Client.credentials = JSON.parse(token); + callback(oauth2Client); + } + }); +} + +/** + * Get and store new token after prompting for user authorization, and then + * execute the given callback with the authorized OAuth2 client. + * + * @param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for. + * @param {getEventsCallback} callback The callback to call with the authorized + * client. + */ +function getNewToken(oauth2Client, callback) { + var authUrl = oauth2Client.generateAuthUrl({ + access_type: 'offline', + scope: SCOPES + }); + console.log('Authorize this app by visiting this url: ', authUrl); + var rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + rl.question('Enter the code from that page here: ', function (code) { + rl.close(); + oauth2Client.getToken(code, function (err, token) { + if (err) { + console.log('Error while trying to retrieve access token', err); + return; + } + oauth2Client.credentials = token; + storeToken(token); + callback(oauth2Client); + }); + }); +} + +/** + * Store token to disk be used in later program executions. + * + * @param {Object} token The token to store to disk. + */ +function storeToken(token) { + try { + fs.mkdirSync(TOKEN_DIR); + } catch (err) { + if (err.code != 'EEXIST') { + throw err; + } + } + fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => { + if (err) throw err; + console.log('Token stored to ' + TOKEN_PATH); + }); + console.log('Token stored to ' + TOKEN_PATH); +} + +/** + * Lists the names and IDs of up to 10 files. + * + * @param {google.auth.OAuth2} auth An authorized OAuth2 client. + */ +function getChannel(auth) { + var service = google.youtube('v3'); + service.channels.list({ + auth: auth, + part: 'snippet,contentDetails,statistics', + forUsername: 'GoogleDevelopers' + }, function (err, response) { + if (err) { + console.log('The API returned an error: ' + err); + return; + } + var channels = response.data.items; + if (channels.length == 0) { + console.log('No channel found.'); + } else { + console.log('This channel\'s ID is %s. Its title is \'%s\', and ' + + 'it has %s views.', + channels[0].id, + channels[0].snippet.title, + channels[0].statistics.viewCount); + } + }); +} \ No newline at end of file diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx index 07cdcd43a..5818519de 100644 --- a/src/client/views/presentationview/PresentationElement.tsx +++ b/src/client/views/presentationview/PresentationElement.tsx @@ -13,9 +13,6 @@ import { faFile as fileRegular } from '@fortawesome/free-regular-svg-icons'; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; - - - library.add(faArrowUp); library.add(fileSolid); library.add(fileRegular); -- cgit v1.2.3-70-g09d2 From b285803c4e8c37302f6e02624a6127667d628305 Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Tue, 25 Jun 2019 19:49:54 -0400 Subject: Youtube Api Exploration --- package.json | 2 + src/client/DocServer.ts | 6 +++ src/client/apis/youtube/YoutubeBox.tsx | 72 +++++++++++++++++++++++++ src/client/apis/youtube/youtubeApiSample.js | 22 +++----- src/client/documents/Documents.ts | 20 ++++++- src/client/views/MainView.tsx | 6 ++- src/client/views/nodes/DocumentContentsView.tsx | 3 +- src/new_fields/URLField.ts | 3 +- src/server/Message.ts | 1 + src/server/index.ts | 20 ++++++- 10 files changed, 135 insertions(+), 20 deletions(-) create mode 100644 src/client/apis/youtube/YoutubeBox.tsx (limited to 'src') diff --git a/package.json b/package.json index 2371d530e..2b1c8f262 100644 --- a/package.json +++ b/package.json @@ -102,6 +102,7 @@ "bluebird": "^3.5.3", "body-parser": "^1.18.3", "bootstrap": "^4.3.1", + "child_process": "^1.0.2", "class-transformer": "^0.2.0", "connect-flash": "^0.1.1", "connect-mongo": "^2.0.3", @@ -171,6 +172,7 @@ "react-simple-dropdown": "^3.2.3", "react-split-pane": "^0.1.85", "react-table": "^6.9.2", + "readline": "^1.3.0", "request": "^2.88.0", "request-image-size": "^2.1.0", "request-promise": "^4.2.4", diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index cbcf751ee..c9cbce78e 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -47,6 +47,12 @@ export namespace DocServer { } } + export async function getYoutubeApiKey() { + let apiKey = await Utils.EmitCallback(_socket, MessageStore.YoutubeApiKey, undefined); + return apiKey; + } + + export async function GetRefFields(ids: string[]): Promise<{ [id: string]: Opt }> { const requestedIds: string[] = []; const waitingIds: string[] = []; diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx new file mode 100644 index 000000000..ee190750f --- /dev/null +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -0,0 +1,72 @@ +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 } from 'mobx'; +import { DocumentDecorations } from "../../views/DocumentDecorations"; +import { InkingControl } from "../../views/InkingControl"; +import * as YoutubeApi from "./youtubeApiSample"; +import { Utils } from "../../../Utils"; +import { DocServer } from "../../DocServer"; + + +@observer +export class YoutubeBox extends React.Component { + + private youtubeApiKey: string = ""; + + public static LayoutString() { return FieldView.LayoutString(YoutubeBox); } + + async componentWillMount() { + let apiKey = await DocServer.getYoutubeApiKey(); + this.youtubeApiKey = apiKey; + YoutubeApi.authorizedGetChannel(this.youtubeApiKey); + } + + _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(); + } + } + render() { + let field = this.props.Document[this.props.fieldKey]; + let view; + YoutubeApi.readFsFile(); + if (field instanceof HtmlField) { + view = ; + } else if (field instanceof WebField) { + view = ; } else { return (null); } } + @action + embedVideoOnClick = (videoId: string) => { + let embeddedUrl = "https://www.youtube.com/embed/" + videoId; + this.selectedVideoUrl = embeddedUrl; + this.searchResultsFound = false; + this.videoClicked = true; + } + render() { let field = this.props.Document[this.props.fieldKey]; let content =
- this.YoutubeSearchElement = e!} /> - {this.renderSearchResults()} + this.YoutubeSearchElement = e!} /> + {this.renderSearchResultsOrVideo()}
; let frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; -- cgit v1.2.3-70-g09d2 From f70b95879e87a6bb61aaae5de29747d9474623a7 Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Thu, 27 Jun 2019 17:14:15 -0400 Subject: css for youtubeBox --- src/client/apis/youtube/YoutubeBox.scss | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'src') diff --git a/src/client/apis/youtube/YoutubeBox.scss b/src/client/apis/youtube/YoutubeBox.scss index 962f814a2..e6ccfea90 100644 --- a/src/client/apis/youtube/YoutubeBox.scss +++ b/src/client/apis/youtube/YoutubeBox.scss @@ -1,3 +1,13 @@ +ul { + list-style-type: none; +} + + li { margin: 4px; +} + +li:hover { + cursor: pointer; + opacity: 0.8; } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 9c868077db569a606ec465557d9d693bcd3abd34 Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Tue, 16 Jul 2019 19:31:40 -0400 Subject: Merge Fixed --- package.json | 2 +- src/client/views/InkingControl.tsx | 4 ++-- src/client/views/MainView.tsx | 4 ++-- src/client/views/nodes/FormattedTextBox.tsx | 1 - 4 files changed, 5 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/package.json b/package.json index 376d3db77..28c975500 100644 --- a/package.json +++ b/package.json @@ -205,4 +205,4 @@ "xoauth2": "^1.2.0", "youtube": "^0.1.0" } -} \ No newline at end of file +} diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index c7f7bdb66..1910e409b 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -1,5 +1,5 @@ import { observable, action, computed, runInAction } from "mobx"; -import { ColorResult } from 'react-color'; +import { ColorState } from 'react-color'; import React = require("react"); import { observer } from "mobx-react"; import "./InkingControl.scss"; @@ -41,7 +41,7 @@ export class InkingControl extends React.Component { } @undoBatch - switchColor = action((color: ColorResult): void => { + switchColor = action((color: ColorState): void => { this._selectedColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff"); if (InkingControl.Instance.selectedTool === InkTool.None) { if (MainOverlayTextBox.Instance.SetColor(color.hex)) return; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 5a2e0c6c3..18e98a5cc 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,5 +1,5 @@ import { IconName, library } from '@fortawesome/fontawesome-svg-core'; -import { faArrowDown, faCloudUploadAlt, faArrowUp, faClone, faCheck, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faPortrait, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faThumbtack, faTree, faUndoAlt, faCat } from '@fortawesome/free-solid-svg-icons'; +import { faArrowDown, faCloudUploadAlt, faArrowUp, faClone, faCheck, faPlay, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faPortrait, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faThumbtack, faTree, faUndoAlt, faCat } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, runInAction, reaction, trace } from 'mobx'; import { observer } from 'mobx-react'; @@ -381,7 +381,7 @@ export class MainView extends React.Component { let addImageNode = action(() => Docs.Create.ImageDocument(imgurl, { width: 200, title: "an image of a cat" })); let addImportCollectionNode = action(() => Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })); let youtubeurl = "https://www.youtube.com/embed/TqcApsGRzWw"; - let addYoutubeSearcher = action(() => Docs.YoutubeDocument(youtubeurl, { width: 200, height: 200, title: "youtube node" })); + let addYoutubeSearcher = action(() => Docs.Create.YoutubeDocument(youtubeurl, { width: 200, height: 200, title: "youtube node" })); let btns: [React.RefObject, IconName, string, () => Doc][] = [ [React.createRef(), "object-group", "Add Collection", addColNode], diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 45e7171d2..3e15f2ca9 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -308,7 +308,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe href = parent.childNodes[0].href ? parent.childNodes[0].href : parent.href; } if (href) { - `` if (href.indexOf(DocServer.prepend("/doc/")) === 0) { this._linkClicked = href.replace(DocServer.prepend("/doc/"), "").split("?")[0]; if (this._linkClicked) { -- cgit v1.2.3-70-g09d2 From 49edd4e6071d0ea84cd0a652d69acb826866c99b Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Wed, 17 Jul 2019 17:50:45 -0400 Subject: New VideoBox spawns when a youtube video search result is clicked --- src/client/apis/youtube/YoutubeBox.tsx | 31 ++++++++++++++++++++++++------- src/server/youtubeApi/youtubeApiSample.js | 2 +- 2 files changed, 25 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index 3e7e9e06d..e7913da9e 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -11,6 +11,7 @@ import { Utils } from "../../../Utils"; import { DocServer } from "../../DocServer"; import { NumCast } from "../../../new_fields/Types"; import "./YoutubeBox.scss"; +import { Docs } from "../../documents/Documents"; @observer @@ -25,7 +26,7 @@ export class YoutubeBox extends React.Component { public static LayoutString() { return FieldView.LayoutString(YoutubeBox); } componentWillMount() { - DocServer.getYoutubeChannels(); + //DocServer.getYoutubeChannels(); } _ignore = 0; @@ -59,34 +60,50 @@ export class YoutubeBox extends React.Component { @action processesVideoResults = (videos: any[]) => { this.searchResults = videos; + console.log("Results: ", this.searchResults); if (this.searchResults.length > 0) { this.searchResultsFound = true; - this.searchResults.forEach((video) => console.log("Image Url", video.snippet)); if (this.videoClicked) { this.videoClicked = false; } } } + filterYoutubeTitleResult = (resultTitle: string) => { + let processedTitle: string = resultTitle.ReplaceAll("&", "&"); + processedTitle = processedTitle.ReplaceAll("'", "'"); + processedTitle = processedTitle.ReplaceAll(""", "\""); + return processedTitle; + } + renderSearchResultsOrVideo = () => { if (this.searchResultsFound) { return
    {this.searchResults.map((video) => { - return
  • this.embedVideoOnClick(video.id.videoId)} key={video.id.videoId}> {video.snippet.title}
  • ; + let filteredTitle = this.filterYoutubeTitleResult(video.snippet.title); + return
  • this.embedVideoOnClick(video.id.videoId, filteredTitle)} key={video.id.videoId}> {filteredTitle}
  • ; })}
; - } else if (this.videoClicked) { - return ; + // } else if (this.videoClicked) { + // return ; + // } } else { return (null); } } @action - embedVideoOnClick = (videoId: string) => { + embedVideoOnClick = (videoId: string, filteredTitle: string) => { let embeddedUrl = "https://www.youtube.com/embed/" + videoId; this.selectedVideoUrl = embeddedUrl; - this.searchResultsFound = false; + let addFunction = this.props.addDocument!; + let newVideoX = NumCast(this.props.Document.x) + NumCast(this.props.Document.width); + 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.props.addDocument(Docs.Create.VideoDocument(embeddedUrl, { title: embeddedUrl, width: 400, height: 315 })); + //this.searchResultsFound = false; this.videoClicked = true; } diff --git a/src/server/youtubeApi/youtubeApiSample.js b/src/server/youtubeApi/youtubeApiSample.js index e95f99015..f875812d5 100644 --- a/src/server/youtubeApi/youtubeApiSample.js +++ b/src/server/youtubeApi/youtubeApiSample.js @@ -153,7 +153,7 @@ function getSampleVideos(auth, args) { return; } let videos = response.data.items; - console.log('Videos found: ' + videos[0].id.videoId, " ", videos[0].snippet.title); + console.log('Videos found: ' + videos[0].id.videoId, " ", unescape(videos[0].snippet.title)); args.callBack(videos); }); } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 1f1f857847cd9ffa0fdd5001c0dd72f06ba903c0 Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Wed, 17 Jul 2019 20:26:00 -0400 Subject: Titles alligned --- src/client/apis/youtube/YoutubeBox.scss | 6 ++++++ src/client/apis/youtube/YoutubeBox.tsx | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/apis/youtube/YoutubeBox.scss b/src/client/apis/youtube/YoutubeBox.scss index e6ccfea90..23f264b95 100644 --- a/src/client/apis/youtube/YoutubeBox.scss +++ b/src/client/apis/youtube/YoutubeBox.scss @@ -5,9 +5,15 @@ ul { li { margin: 4px; + display: inline-flex; } li:hover { cursor: pointer; opacity: 0.8; +} + +.videoTitle { + margin-left: 4px; + font-family: Arial, Helvetica, sans-serif; } \ No newline at end of file diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index e7913da9e..d94f3785c 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -81,7 +81,7 @@ export class YoutubeBox extends React.Component { return
    {this.searchResults.map((video) => { let filteredTitle = this.filterYoutubeTitleResult(video.snippet.title); - return
  • this.embedVideoOnClick(video.id.videoId, filteredTitle)} key={video.id.videoId}> {filteredTitle}
  • ; + return
  • this.embedVideoOnClick(video.id.videoId, filteredTitle)} key={video.id.videoId}> {filteredTitle}
  • ; })}
; // } else if (this.videoClicked) { -- cgit v1.2.3-70-g09d2 From 808443664d66dc5009aaace48420659cf98c6c47 Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Thu, 18 Jul 2019 13:31:17 -0400 Subject: Youtube Search Results Backed Up, Key error is present --- src/client/apis/youtube/YoutubeBox.tsx | 112 +++++++++++++++++++++++++++++---- 1 file changed, 100 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index d94f3785c..fa2d3fb53 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -4,14 +4,17 @@ 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 } from 'mobx'; +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 } from "../../../new_fields/Types"; +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 @@ -22,11 +25,43 @@ export class YoutubeBox extends React.Component { @observable searchResults: any[] = []; @observable videoClicked: boolean = false; @observable selectedVideoUrl: string = ""; + // @observable cachedResults: List | undefined; + @observable lisOfBackUp: JSX.Element[] = []; + public static LayoutString() { return FieldView.LayoutString(YoutubeBox); } - componentWillMount() { + async componentWillMount() { //DocServer.getYoutubeChannels(); + let castedBackUpDocs = Cast(this.props.Document.cachedSearch, listSpec(Doc)); + if (!castedBackUpDocs) { + this.props.Document.cachedSearch = castedBackUpDocs = new List(); + } + if (castedBackUpDocs.length !== 0) { + //let awaitedRes = await castedBackUpDocs; + + 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(( +
  • this.embedVideoOnClick(videoId, videoTitle)} + key={videoId} + > + + {videoTitle} +
  • ) + )); + } + + + } + + } _ignore = 0; @@ -63,12 +98,24 @@ export class YoutubeBox extends React.Component { console.log("Results: ", this.searchResults); if (this.searchResults.length > 0) { this.searchResultsFound = true; + this.backUpSearchResults(videos); if (this.videoClicked) { this.videoClicked = false; } } } + backUpSearchResults = (videos: any[]) => { + let castedBackUpDocs = Cast(this.props.Document.cachedSearch, listSpec(Doc)); + 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; + castedBackUpDocs!.push(videoBackUp); + }); + } + filterYoutubeTitleResult = (resultTitle: string) => { let processedTitle: string = resultTitle.ReplaceAll("&", "&"); processedTitle = processedTitle.ReplaceAll("'", "'"); @@ -76,17 +123,56 @@ export class YoutubeBox extends React.Component { return processedTitle; } + // mapSearchResults = () => { + // if (this.searchResults.length !== 0) { + // console.log("Entered here"); + // return
      { + // this.searchResults.map((video) => { + // let filteredTitle = this.filterYoutubeTitleResult(video.snippet.title); + // return
    • this.embedVideoOnClick(video.id.videoId, filteredTitle)} key={video.id.videoId}> {filteredTitle}
    • ; + // })} + //
    ; + // } else if (this.cachedResults!.length !== 0) { + // return
      { + // this.cachedResults!.map(async (videoBackUp) => { + // let curBackUp = await videoBackUp; + // let videoId = StrCast(curBackUp.videoId); + // let videoTitle = StrCast(curBackUp.videoTitle); + // let thumbnailUrl = StrCast(curBackUp.thumbnailUrl); + // return
    • this.embedVideoOnClick(videoTitle, videoTitle)} key={videoId}> {videoTitle}
    • ; + // })} + //
    ; + // } + // } + renderSearchResultsOrVideo = () => { if (this.searchResultsFound) { - return
      - {this.searchResults.map((video) => { - let filteredTitle = this.filterYoutubeTitleResult(video.snippet.title); - return
    • this.embedVideoOnClick(video.id.videoId, filteredTitle)} key={video.id.videoId}> {filteredTitle}
    • ; - })} -
    ; - // } else if (this.videoClicked) { - // return ; - // } + if (this.searchResults.length !== 0) { + return
      + {this.searchResults.map((video) => { + let filteredTitle = this.filterYoutubeTitleResult(video.snippet.title); + return
    • this.embedVideoOnClick(video.id.videoId, filteredTitle)} key={video.id.videoId}> {filteredTitle}
    • ; + })} +
    ; + } else if (this.lisOfBackUp.length !== 0) { + // let lis: JSX.Element[] = []; + // for (let videoBackUp of this.cachedResults!) { + // let curBackUp = await videoBackUp; + // let videoId = StrCast(curBackUp.videoId); + // let videoTitle = StrCast(curBackUp.videoTitle); + // let thumbnailUrl = StrCast(curBackUp.thumbnailUrl); + // lis.push(( + //
  • this.embedVideoOnClick(videoTitle, videoTitle)} + // key={videoId} + // > + // + // {videoTitle} + //
  • ) + // ); + // } + return
      {this.lisOfBackUp}
    ; + } } else { return (null); } @@ -95,6 +181,7 @@ export class YoutubeBox extends React.Component { @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) + NumCast(this.props.Document.width); @@ -109,6 +196,7 @@ export class YoutubeBox extends React.Component { render() { let field = this.props.Document[this.props.fieldKey]; + //let results = this.renderSearchResultsOrVideo(); let content =
    this.YoutubeSearchElement = e!} /> -- cgit v1.2.3-70-g09d2 From 421311806a686dd0de2bbdd7d8aa7dbb5c9fe9d5 Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Thu, 18 Jul 2019 14:14:43 -0400 Subject: Overriding allowed, keys problem still exist --- src/client/apis/youtube/YoutubeBox.tsx | 54 ++++------------------------------ 1 file changed, 5 insertions(+), 49 deletions(-) (limited to 'src') diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index fa2d3fb53..33d989b6a 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -25,7 +25,6 @@ export class YoutubeBox extends React.Component { @observable searchResults: any[] = []; @observable videoClicked: boolean = false; @observable selectedVideoUrl: string = ""; - // @observable cachedResults: List | undefined; @observable lisOfBackUp: JSX.Element[] = []; @@ -38,7 +37,6 @@ export class YoutubeBox extends React.Component { this.props.Document.cachedSearch = castedBackUpDocs = new List(); } if (castedBackUpDocs.length !== 0) { - //let awaitedRes = await castedBackUpDocs; this.searchResultsFound = true; @@ -50,7 +48,7 @@ export class YoutubeBox extends React.Component { runInAction(() => this.lisOfBackUp.push((
  • this.embedVideoOnClick(videoId, videoTitle)} - key={videoId} + key={Utils.GenerateGuid()} > {videoTitle} @@ -95,7 +93,6 @@ export class YoutubeBox extends React.Component { @action processesVideoResults = (videos: any[]) => { this.searchResults = videos; - console.log("Results: ", this.searchResults); if (this.searchResults.length > 0) { this.searchResultsFound = true; this.backUpSearchResults(videos); @@ -106,13 +103,14 @@ export class YoutubeBox extends React.Component { } backUpSearchResults = (videos: any[]) => { - let castedBackUpDocs = Cast(this.props.Document.cachedSearch, listSpec(Doc)); + let newCachedList = new List(); + 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; - castedBackUpDocs!.push(videoBackUp); + newCachedList.push(videoBackUp); }); } @@ -123,54 +121,16 @@ export class YoutubeBox extends React.Component { return processedTitle; } - // mapSearchResults = () => { - // if (this.searchResults.length !== 0) { - // console.log("Entered here"); - // return
      { - // this.searchResults.map((video) => { - // let filteredTitle = this.filterYoutubeTitleResult(video.snippet.title); - // return
    • this.embedVideoOnClick(video.id.videoId, filteredTitle)} key={video.id.videoId}> {filteredTitle}
    • ; - // })} - //
    ; - // } else if (this.cachedResults!.length !== 0) { - // return
      { - // this.cachedResults!.map(async (videoBackUp) => { - // let curBackUp = await videoBackUp; - // let videoId = StrCast(curBackUp.videoId); - // let videoTitle = StrCast(curBackUp.videoTitle); - // let thumbnailUrl = StrCast(curBackUp.thumbnailUrl); - // return
    • this.embedVideoOnClick(videoTitle, videoTitle)} key={videoId}> {videoTitle}
    • ; - // })} - //
    ; - // } - // } - renderSearchResultsOrVideo = () => { if (this.searchResultsFound) { if (this.searchResults.length !== 0) { return
      {this.searchResults.map((video) => { let filteredTitle = this.filterYoutubeTitleResult(video.snippet.title); - return
    • this.embedVideoOnClick(video.id.videoId, filteredTitle)} key={video.id.videoId}> {filteredTitle}
    • ; + return
    • this.embedVideoOnClick(video.id.videoId, filteredTitle)} key={Utils.GenerateGuid()}> {filteredTitle}
    • ; })}
    ; } else if (this.lisOfBackUp.length !== 0) { - // let lis: JSX.Element[] = []; - // for (let videoBackUp of this.cachedResults!) { - // let curBackUp = await videoBackUp; - // let videoId = StrCast(curBackUp.videoId); - // let videoTitle = StrCast(curBackUp.videoTitle); - // let thumbnailUrl = StrCast(curBackUp.thumbnailUrl); - // lis.push(( - //
  • this.embedVideoOnClick(videoTitle, videoTitle)} - // key={videoId} - // > - // - // {videoTitle} - //
  • ) - // ); - // } return
      {this.lisOfBackUp}
    ; } } else { @@ -188,15 +148,11 @@ export class YoutubeBox extends React.Component { 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.props.addDocument(Docs.Create.VideoDocument(embeddedUrl, { title: embeddedUrl, width: 400, height: 315 })); - //this.searchResultsFound = false; this.videoClicked = true; } render() { let field = this.props.Document[this.props.fieldKey]; - //let results = this.renderSearchResultsOrVideo(); let content =
    this.YoutubeSearchElement = e!} /> -- cgit v1.2.3-70-g09d2 From 82a9c1f854fad05db0878717aa82572ffc1290c1 Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Thu, 18 Jul 2019 18:08:07 -0400 Subject: Update on coordinates --- src/client/apis/youtube/YoutubeBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index 33d989b6a..da3c4b851 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -144,7 +144,7 @@ export class YoutubeBox extends React.Component { console.log("EmbeddedUrl: ", embeddedUrl); this.selectedVideoUrl = embeddedUrl; let addFunction = this.props.addDocument!; - let newVideoX = NumCast(this.props.Document.x) + NumCast(this.props.Document.width); + 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 })); -- cgit v1.2.3-70-g09d2 From 55bc9d93a8f39dfc6781677677dd3e4c55642c15 Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Thu, 18 Jul 2019 18:13:10 -0400 Subject: Small css stuff --- src/client/views/MainView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 18e98a5cc..5a1b1991e 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -381,7 +381,7 @@ export class MainView extends React.Component { let addImageNode = action(() => Docs.Create.ImageDocument(imgurl, { width: 200, title: "an image of a cat" })); let addImportCollectionNode = action(() => Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })); let youtubeurl = "https://www.youtube.com/embed/TqcApsGRzWw"; - let addYoutubeSearcher = action(() => Docs.Create.YoutubeDocument(youtubeurl, { width: 200, height: 200, title: "youtube node" })); + let addYoutubeSearcher = action(() => Docs.Create.YoutubeDocument(youtubeurl, { width: 600, height: 600, title: "youtube search" })); let btns: [React.RefObject, IconName, string, () => Doc][] = [ [React.createRef(), "object-group", "Add Collection", addColNode], -- cgit v1.2.3-70-g09d2 From 157060f7e6029c76765aa20d8fdbe325401a3880 Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Fri, 19 Jul 2019 18:29:03 -0400 Subject: Youtube Search UI imitated mostly --- src/client/apis/youtube/YoutubeBox.scss | 74 +++++++++++++++++++++++++-- src/client/apis/youtube/YoutubeBox.tsx | 89 ++++++++++++++++++++++++++++++++- 2 files changed, 159 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/apis/youtube/YoutubeBox.scss b/src/client/apis/youtube/YoutubeBox.scss index 23f264b95..5b539b463 100644 --- a/src/client/apis/youtube/YoutubeBox.scss +++ b/src/client/apis/youtube/YoutubeBox.scss @@ -1,5 +1,6 @@ ul { list-style-type: none; + padding-inline-start: 10px; } @@ -13,7 +14,74 @@ li:hover { opacity: 0.8; } -.videoTitle { - margin-left: 4px; - font-family: Arial, Helvetica, sans-serif; +.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 index da3c4b851..7ac8d06f6 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -121,13 +121,100 @@ export class YoutubeBox extends React.Component { 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
      {this.searchResults.map((video) => { let filteredTitle = this.filterYoutubeTitleResult(video.snippet.title); - return
    • this.embedVideoOnClick(video.id.videoId, filteredTitle)} key={Utils.GenerateGuid()}> {filteredTitle}
    • ; + 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
    • this.embedVideoOnClick(video.id.videoId, filteredTitle)} key={Utils.GenerateGuid()}> +
      + +
      + {filteredTitle} + {channelTitle} + {pusblishDate} + {/*
      {viewCount}
      */} +

      {videoDescription}

      +
      +
      +
    • ; })}
    ; } else if (this.lisOfBackUp.length !== 0) { -- cgit v1.2.3-70-g09d2 From 4446a3a52c4cf4b03c201ab2d6a9179647686e40 Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Mon, 22 Jul 2019 18:47:14 -0400 Subject: Pulled Duration and ViewCount details, Need to csss duration --- src/client/DocServer.ts | 4 ++ src/client/apis/youtube/YoutubeBox.scss | 37 ++++++++++++++++++ src/client/apis/youtube/YoutubeBox.tsx | 64 ++++++++++++++++++++++++++++--- src/server/Message.ts | 3 +- src/server/index.ts | 2 + src/server/youtubeApi/youtubeApiSample.js | 21 ++++++++++ 6 files changed, 125 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index bc5819061..8a9abb514 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -168,6 +168,10 @@ export namespace DocServer { Utils.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.SearchVideo, userInput: videoTitle }, callBack); } + export function getYoutubeVideoDetails(videoIds: string, callBack: (videoDetails: any[]) => void) { + Utils.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.VideoDetails, videoIds: videoIds }, callBack); + } + /** * Given a list of Doc GUIDs, this utility function will asynchronously attempt to each id's associated diff --git a/src/client/apis/youtube/YoutubeBox.scss b/src/client/apis/youtube/YoutubeBox.scss index 5b539b463..00979f945 100644 --- a/src/client/apis/youtube/YoutubeBox.scss +++ b/src/client/apis/youtube/YoutubeBox.scss @@ -19,6 +19,27 @@ li:hover { display: inline-flex; height: 175px; + .video_duration { + margin: 0; + padding: 0; + border: 0; + background: transparent; + display: inline-block; + position: absolute; + bottom: 0; + right: 0; + margin: 4px; + color: #FFFFFF; + background-color: rgba(0, 0, 0, 0.80); + padding: 2px 4px; + border-radius: 2px; + letter-spacing: .5px; + font-size: 1.2rem; + font-weight: 500; + line-height: 1.2rem; + + } + .textual_info { font-family: Arial, Helvetica, sans-serif; @@ -80,6 +101,22 @@ li:hover { font-weight: 400; text-transform: none; } + + .viewCount { + + 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; + } diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index 7ac8d06f6..824a0251d 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -26,12 +26,15 @@ export class YoutubeBox extends React.Component { @observable videoClicked: boolean = false; @observable selectedVideoUrl: string = ""; @observable lisOfBackUp: JSX.Element[] = []; + @observable videoIds: string | undefined; + @observable videoDetails: any[] = []; public static LayoutString() { return FieldView.LayoutString(YoutubeBox); } async componentWillMount() { //DocServer.getYoutubeChannels(); + //DocServer.getYoutubeVideoDetails("Ks-_Mh1QhMc, 1NmvhSmN2uM", (results: any[]) => console.log("Details results: ", results)); let castedBackUpDocs = Cast(this.props.Document.cachedSearch, listSpec(Doc)); if (!castedBackUpDocs) { this.props.Document.cachedSearch = castedBackUpDocs = new List(); @@ -95,6 +98,15 @@ export class YoutubeBox extends React.Component { this.searchResults = videos; if (this.searchResults.length > 0) { this.searchResultsFound = true; + this.videoIds = ""; + videos.forEach((video) => { + if (this.videoIds === "") { + this.videoIds = video.id.videoId; + } else { + this.videoIds = this.videoIds! + ", " + video.id.videoId; + } + }); + DocServer.getYoutubeVideoDetails(this.videoIds, this.processVideoDetails); this.backUpSearchResults(videos); if (this.videoClicked) { this.videoClicked = false; @@ -102,6 +114,12 @@ export class YoutubeBox extends React.Component { } } + @action + processVideoDetails = (videoDetails: any[]) => { + this.videoDetails = videoDetails; + console.log("Detail Res: ", this.videoDetails); + } + backUpSearchResults = (videos: any[]) => { let newCachedList = new List(); this.props.Document.cachedSearch = newCachedList; @@ -190,28 +208,64 @@ export class YoutubeBox extends React.Component { } } + convertIsoTimeToDuration = (isoDur: string) => { + + let convertedTime = isoDur.replace(/D|H|M/g, ":").replace(/P|T|S/g, "").split(":"); + + if (1 === convertedTime.length) { + 2 !== convertedTime[0].length && (convertedTime[0] = "0" + convertedTime[0]), convertedTime[0] = "0:" + convertedTime[0]; + } else { + for (var r = 1, l = convertedTime.length - 1; l >= r; r++) { + 2 !== convertedTime[r].length && (convertedTime[r] = "0" + convertedTime[r]); + } + } + + return convertedTime.join(":"); + } + + abbreviateViewCount = (viewCount: number) => { + if (viewCount < 1000) { + return viewCount.toString(); + } else if (viewCount >= 1000 && viewCount < 1000000) { + return (Math.trunc(viewCount / 1000)) + "K"; + } else if (viewCount >= 1000000 && viewCount < 1000000000) { + return (Math.trunc(viewCount / 1000000)) + "M"; + } else if (viewCount >= 1000000000) { + return (Math.trunc(viewCount / 1000000000)) + "B"; + } + } + renderSearchResultsOrVideo = () => { if (this.searchResultsFound) { if (this.searchResults.length !== 0) { return
      - {this.searchResults.map((video) => { + {this.searchResults.map((video, index) => { 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; + let duration; + let viewCount; + if (this.videoDetails.length !== 0) { + duration = this.convertIsoTimeToDuration(this.videoDetails[index].contentDetails.duration); + viewCount = this.abbreviateViewCount(this.videoDetails[index].statistics.viewCount); + } //this.roundPublishTime(pusblishDate); //this.roundPublishTime2(video.snippet.publishedAt); + return
    • this.embedVideoOnClick(video.id.videoId, filteredTitle)} key={Utils.GenerateGuid()}>
      - +
      + + {duration} +
      {filteredTitle} {channelTitle} + {viewCount} {pusblishDate} - {/*
      {viewCount}
      */}

      {videoDescription}

      +
    • ; diff --git a/src/server/Message.ts b/src/server/Message.ts index 1e29aef0b..aaee143e8 100644 --- a/src/server/Message.ts +++ b/src/server/Message.ts @@ -25,12 +25,13 @@ export interface Transferable { } export enum YoutubeQueryTypes { - Channels, SearchVideo + Channels, SearchVideo, VideoDetails } export interface YoutubeQueryInput { readonly type: YoutubeQueryTypes; readonly userInput?: string; + readonly videoIds?: string; } export interface Reference { diff --git a/src/server/index.ts b/src/server/index.ts index 60e34de8c..dfbc1a468 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -537,6 +537,8 @@ function HandleYoutubeQuery([query, callback]: [YoutubeQueryInput, (result?: any break; case YoutubeQueryType.SearchVideo: YoutubeApi.authorizedGetVideos(youtubeApiKey, query.userInput, callback); + case YoutubeQueryType.VideoDetails: + YoutubeApi.authorizedGetVideoDetails(youtubeApiKey, query.videoIds, callback); } } diff --git a/src/server/youtubeApi/youtubeApiSample.js b/src/server/youtubeApi/youtubeApiSample.js index f875812d5..cf41a33e7 100644 --- a/src/server/youtubeApi/youtubeApiSample.js +++ b/src/server/youtubeApi/youtubeApiSample.js @@ -33,6 +33,10 @@ module.exports.authorizedGetVideos = (apiKey, userInput, callBack) => { authorize(JSON.parse(apiKey), getSampleVideos, { userInput: userInput, callBack: callBack }); } +module.exports.authorizedGetVideoDetails = (apiKey, videoIds, callBack) => { + authorize(JSON.parse(apiKey), getVideoDetails, { videoIds: videoIds, callBack: callBack }); +} + /** * Create an OAuth2 client with the given credentials, and then execute the @@ -156,4 +160,21 @@ function getSampleVideos(auth, args) { console.log('Videos found: ' + videos[0].id.videoId, " ", unescape(videos[0].snippet.title)); args.callBack(videos); }); +} + +function getVideoDetails(auth, args) { + let service = google.youtube('v3'); + service.videos.list({ + auth: auth, + part: 'contentDetails, statistics', + id: args.videoIds + }, function (err, response) { + if (err) { + console.log('The API returned an error: ' + err); + return; + } + let videoDetails = response.data.items; + console.log('Video Details founds: ', videoDetails); + args.callBack(videoDetails); + }); } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From e1b750da8faf8f00707de1b65efbd210c19fa723 Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Tue, 23 Jul 2019 14:29:08 -0400 Subject: Store and css --- src/client/apis/youtube/YoutubeBox.scss | 10 +++++----- src/client/apis/youtube/YoutubeBox.tsx | 23 +++++++++++++++++++++-- 2 files changed, 26 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/client/apis/youtube/YoutubeBox.scss b/src/client/apis/youtube/YoutubeBox.scss index 00979f945..1fc91a9ae 100644 --- a/src/client/apis/youtube/YoutubeBox.scss +++ b/src/client/apis/youtube/YoutubeBox.scss @@ -20,14 +20,14 @@ li:hover { height: 175px; .video_duration { - margin: 0; - padding: 0; + // margin: 0; + // padding: 0; border: 0; background: transparent; display: inline-block; - position: absolute; - bottom: 0; - right: 0; + position: relative; + bottom: 25px; + left: 85%; margin: 4px; color: #FFFFFF; background-color: rgba(0, 0, 0, 0.80); diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index 824a0251d..373eee5c4 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -12,7 +12,7 @@ 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 { Doc, DocListCastAsync } from "../../../new_fields/Doc"; import { listSpec } from "../../../new_fields/Schema"; import { List } from "../../../new_fields/List"; @@ -36,12 +36,29 @@ export class YoutubeBox extends React.Component { //DocServer.getYoutubeChannels(); //DocServer.getYoutubeVideoDetails("Ks-_Mh1QhMc, 1NmvhSmN2uM", (results: any[]) => console.log("Details results: ", results)); let castedBackUpDocs = Cast(this.props.Document.cachedSearch, listSpec(Doc)); + let castedSearchBackUp = Cast(this.props.Document.cachedSearchResults, Doc); + let awaitedBackUp = await castedSearchBackUp; + + console.log("Backup results: ", awaitedBackUp); + console.log("Original Backup results: ", castedBackUpDocs); + + let json = Cast(awaitedBackUp!.json, Doc); + let jsonList = await DocListCastAsync(json); + console.log("Fucked up list: ", jsonList); + for (let video of jsonList!) { + let videoId = await Cast(video.id, Doc); + let id = StrCast(videoId!.videoId); + console.log("ID: ", id); + } + + + if (!castedBackUpDocs) { this.props.Document.cachedSearch = castedBackUpDocs = new List(); } if (castedBackUpDocs.length !== 0) { - this.searchResultsFound = true; + runInAction(() => this.searchResultsFound = true); for (let videoBackUp of castedBackUpDocs) { let curBackUp = await videoBackUp; @@ -121,6 +138,8 @@ export class YoutubeBox extends React.Component { } backUpSearchResults = (videos: any[]) => { + console.log("Res: ", videos); + this.props.Document.cachedSearchResults = Docs.Get.DocumentHierarchyFromJson(videos, "videosBackUp"); let newCachedList = new List(); this.props.Document.cachedSearch = newCachedList; videos.forEach((video) => { -- cgit v1.2.3-70-g09d2 From 0591b8f1e60d1285fd9aac3e61160824948a166b Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Tue, 23 Jul 2019 17:33:31 -0400 Subject: Everything related to search stored --- src/client/apis/youtube/YoutubeBox.tsx | 123 +++++++++++++++++++----------- src/client/documents/Documents.ts | 2 +- src/server/youtubeApi/youtubeApiSample.js | 4 - 3 files changed, 81 insertions(+), 48 deletions(-) (limited to 'src') diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index 373eee5c4..e630c11ae 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -16,6 +16,16 @@ import { Doc, DocListCastAsync } from "../../../new_fields/Doc"; import { listSpec } from "../../../new_fields/Schema"; import { List } from "../../../new_fields/List"; +interface VideoTemplate { + thumbnailUrl: string; + videoTitle: string; + videoId: string; + duration: string; + channelTitle: string; + viewCount: string; + publishDate: string; + videoDescription: string; +} @observer export class YoutubeBox extends React.Component { @@ -28,58 +38,71 @@ export class YoutubeBox extends React.Component { @observable lisOfBackUp: JSX.Element[] = []; @observable videoIds: string | undefined; @observable videoDetails: any[] = []; + @observable curVideoTemplates: VideoTemplate[] = []; public static LayoutString() { return FieldView.LayoutString(YoutubeBox); } async componentWillMount() { //DocServer.getYoutubeChannels(); - //DocServer.getYoutubeVideoDetails("Ks-_Mh1QhMc, 1NmvhSmN2uM", (results: any[]) => console.log("Details results: ", results)); - let castedBackUpDocs = Cast(this.props.Document.cachedSearch, listSpec(Doc)); let castedSearchBackUp = Cast(this.props.Document.cachedSearchResults, Doc); let awaitedBackUp = await castedSearchBackUp; + let castedDetailBackUp = Cast(this.props.Document.cachedDetails, Doc); + let awaitedDetails = await castedDetailBackUp; - console.log("Backup results: ", awaitedBackUp); - console.log("Original Backup results: ", castedBackUpDocs); - - let json = Cast(awaitedBackUp!.json, Doc); - let jsonList = await DocListCastAsync(json); - console.log("Fucked up list: ", jsonList); - for (let video of jsonList!) { - let videoId = await Cast(video.id, Doc); - let id = StrCast(videoId!.videoId); - console.log("ID: ", id); - } + let jsonList = await DocListCastAsync(awaitedBackUp!.json); + let jsonDetailList = await DocListCastAsync(awaitedDetails!.json); - if (!castedBackUpDocs) { - this.props.Document.cachedSearch = castedBackUpDocs = new List(); - } - if (castedBackUpDocs.length !== 0) { - + if (jsonList!.length !== 0) { runInAction(() => this.searchResultsFound = true); + let index = 0; + for (let video of jsonList!) { + + let videoId = await Cast(video.id, Doc); + let id = StrCast(videoId!.videoId); + let snippet = await Cast(video.snippet, Doc); + let videoTitle = this.filterYoutubeTitleResult(StrCast(snippet!.title)); + let thumbnail = await Cast(snippet!.thumbnails, Doc); + let thumbnailMedium = await Cast(thumbnail!.medium, Doc); + let thumbnailUrl = StrCast(thumbnailMedium!.url); + let videoDescription = StrCast(snippet!.description); + let pusblishDate = (this.roundPublishTime2(StrCast(snippet!.publishedAt)))!; + let channelTitle = StrCast(snippet!.channelTitle); + let duration: string; + let viewCount: string; + if (jsonDetailList!.length !== 0) { + let contentDetails = await Cast(jsonDetailList![index].contentDetails, Doc); + let statistics = await Cast(jsonDetailList![index].statistics, Doc); + duration = this.convertIsoTimeToDuration(StrCast(contentDetails!.duration)); + viewCount = this.abbreviateViewCount(NumCast(statistics!.viewCount))!; + } + index = index + 1; + let newTemplate: VideoTemplate = { videoId: id, videoTitle: videoTitle, thumbnailUrl: thumbnailUrl, publishDate: pusblishDate, channelTitle: channelTitle, videoDescription: videoDescription, duration: duration!, viewCount: viewCount! }; + runInAction(() => this.curVideoTemplates.push(newTemplate)); + + // runInAction(() => this.lisOfBackUp.push(( + //
    • this.embedVideoOnClick(id, videoTitle)} key={Utils.GenerateGuid() + id}> + //
      + //
      + // + // {duration} + //
      + //
      + // {videoTitle} + // {channelTitle} + // {viewCount} + // {pusblishDate} + //

      {videoDescription}

      + + //
      + //
      + //
    • ) + // )); - 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(( -
    • this.embedVideoOnClick(videoId, videoTitle)} - key={Utils.GenerateGuid()} - > - - {videoTitle} -
    • ) - )); } - - } - - } _ignore = 0; @@ -134,11 +157,10 @@ export class YoutubeBox extends React.Component { @action processVideoDetails = (videoDetails: any[]) => { this.videoDetails = videoDetails; - console.log("Detail Res: ", this.videoDetails); + this.props.Document.cachedDetails = Docs.Get.DocumentHierarchyFromJson(videoDetails, "detailBackUp"); } backUpSearchResults = (videos: any[]) => { - console.log("Res: ", videos); this.props.Document.cachedSearchResults = Docs.Get.DocumentHierarchyFromJson(videos, "videosBackUp"); let newCachedList = new List(); this.props.Document.cachedSearch = newCachedList; @@ -164,7 +186,6 @@ export class YoutubeBox extends React.Component { 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(); @@ -182,7 +203,6 @@ export class YoutubeBox extends React.Component { return videoSecondsDif + " seconds ago"; } - console.log("Date : ", date); } roundPublishTime2 = (publishTime: string) => { @@ -290,8 +310,26 @@ export class YoutubeBox extends React.Component { ; })}
    ; - } else if (this.lisOfBackUp.length !== 0) { - return
      {this.lisOfBackUp}
    ; + } else if (this.curVideoTemplates.length !== 0) { + return
      + {this.curVideoTemplates.map((video: VideoTemplate) => { + return
    • this.embedVideoOnClick(video.videoId, video.videoTitle)} key={Utils.GenerateGuid()}> +
      +
      + + {video.duration} +
      +
      + {video.videoTitle} + {video.channelTitle} + {video.viewCount} + {video.publishDate} +

      {video.videoDescription}

      +
      +
      +
    • ; + })} +
    ; } } else { return (null); @@ -301,7 +339,6 @@ export class YoutubeBox extends React.Component { @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); diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 191be9b7d..333e9859b 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -498,7 +498,7 @@ export namespace Docs { const convertObject = (object: any, title?: string): Doc => { let target = new Doc(), result: Opt; Object.keys(object).map(key => (result = toField(object[key], key)) && (target[key] = result)); - title && (target.title = title); + title && !target.title && (target.title = title); return target; }; diff --git a/src/server/youtubeApi/youtubeApiSample.js b/src/server/youtubeApi/youtubeApiSample.js index cf41a33e7..4fede08aa 100644 --- a/src/server/youtubeApi/youtubeApiSample.js +++ b/src/server/youtubeApi/youtubeApiSample.js @@ -23,8 +23,6 @@ module.exports.readApiKey = (callback) => { module.exports.authorizedGetChannel = (apiKey) => { //this didnt get called - console.log("I get called ", apiKey); - console.log(TOKEN_PATH); // Authorize a client with the loaded credentials, then call the YouTube API. authorize(JSON.parse(apiKey), getChannel); } @@ -157,7 +155,6 @@ function getSampleVideos(auth, args) { return; } let videos = response.data.items; - console.log('Videos found: ' + videos[0].id.videoId, " ", unescape(videos[0].snippet.title)); args.callBack(videos); }); } @@ -174,7 +171,6 @@ function getVideoDetails(auth, args) { return; } let videoDetails = response.data.items; - console.log('Video Details founds: ', videoDetails); args.callBack(videoDetails); }); } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 778286579008b57a76fbf82235348b613f5c1a5b Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Tue, 23 Jul 2019 18:56:23 -0400 Subject: Fixed document --- src/client/apis/youtube/YoutubeBox.tsx | 24 ++---------------------- src/server/youtubeApi/youtubeApiSample.js | 2 +- 2 files changed, 3 insertions(+), 23 deletions(-) (limited to 'src') diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index e630c11ae..d2f5112c2 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -81,26 +81,6 @@ export class YoutubeBox extends React.Component { index = index + 1; let newTemplate: VideoTemplate = { videoId: id, videoTitle: videoTitle, thumbnailUrl: thumbnailUrl, publishDate: pusblishDate, channelTitle: channelTitle, videoDescription: videoDescription, duration: duration!, viewCount: viewCount! }; runInAction(() => this.curVideoTemplates.push(newTemplate)); - - // runInAction(() => this.lisOfBackUp.push(( - //
  • this.embedVideoOnClick(id, videoTitle)} key={Utils.GenerateGuid() + id}> - //
    - //
    - // - // {duration} - //
    - //
    - // {videoTitle} - // {channelTitle} - // {viewCount} - // {pusblishDate} - //

    {videoDescription}

    - - //
    - //
    - //
  • ) - // )); - } } } @@ -146,6 +126,7 @@ export class YoutubeBox extends React.Component { this.videoIds = this.videoIds! + ", " + video.id.videoId; } }); + console.log("Video Ids: ", this.videoIds); DocServer.getYoutubeVideoDetails(this.videoIds, this.processVideoDetails); this.backUpSearchResults(videos); if (this.videoClicked) { @@ -289,8 +270,7 @@ export class YoutubeBox extends React.Component { duration = this.convertIsoTimeToDuration(this.videoDetails[index].contentDetails.duration); viewCount = this.abbreviateViewCount(this.videoDetails[index].statistics.viewCount); } - //this.roundPublishTime(pusblishDate); - //this.roundPublishTime2(video.snippet.publishedAt); + return
  • this.embedVideoOnClick(video.id.videoId, filteredTitle)} key={Utils.GenerateGuid()}>
    diff --git a/src/server/youtubeApi/youtubeApiSample.js b/src/server/youtubeApi/youtubeApiSample.js index 4fede08aa..f81f0dfb5 100644 --- a/src/server/youtubeApi/youtubeApiSample.js +++ b/src/server/youtubeApi/youtubeApiSample.js @@ -167,7 +167,7 @@ function getVideoDetails(auth, args) { id: args.videoIds }, function (err, response) { if (err) { - console.log('The API returned an error: ' + err); + console.log('The API returned an error from details: ' + err); return; } let videoDetails = response.data.items; -- cgit v1.2.3-70-g09d2 From c86f580191ddf70a6ac2994819a4f33731d79011 Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Tue, 23 Jul 2019 19:07:59 -0400 Subject: Documentation --- src/client/apis/youtube/YoutubeBox.tsx | 87 +++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 37 deletions(-) (limited to 'src') diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index d2f5112c2..414abcc15 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -27,6 +27,9 @@ interface VideoTemplate { videoDescription: string; } +/** + * This class models the youtube search document that can be dropped on to canvas. + */ @observer export class YoutubeBox extends React.Component { @@ -43,6 +46,10 @@ export class YoutubeBox extends React.Component { public static LayoutString() { return FieldView.LayoutString(YoutubeBox); } + /** + * When component mounts, last search's results are laoded in based on the back up stored + * in the document of the props. + */ async componentWillMount() { //DocServer.getYoutubeChannels(); let castedSearchBackUp = Cast(this.props.Document.cachedSearchResults, Doc); @@ -58,6 +65,7 @@ export class YoutubeBox extends React.Component { if (jsonList!.length !== 0) { runInAction(() => this.searchResultsFound = true); let index = 0; + //getting the necessary information from backUps and building templates that will be used to map in render for (let video of jsonList!) { let videoId = await Cast(video.id, Doc); @@ -68,7 +76,7 @@ export class YoutubeBox extends React.Component { let thumbnailMedium = await Cast(thumbnail!.medium, Doc); let thumbnailUrl = StrCast(thumbnailMedium!.url); let videoDescription = StrCast(snippet!.description); - let pusblishDate = (this.roundPublishTime2(StrCast(snippet!.publishedAt)))!; + let pusblishDate = (this.roundPublishTime(StrCast(snippet!.publishedAt)))!; let channelTitle = StrCast(snippet!.channelTitle); let duration: string; let viewCount: string; @@ -103,6 +111,9 @@ export class YoutubeBox extends React.Component { } } + /** + * Function that submits the title entered by user on enter press. + */ onEnterKeyDown = (e: React.KeyboardEvent) => { if (e.keyCode === 13) { let submittedTitle = this.YoutubeSearchElement!.value; @@ -113,6 +124,11 @@ export class YoutubeBox extends React.Component { } } + /** + * The callback that is passed in to server, which functions as a way to + * get videos that is returned by search. It also makes a call to server + * to get details for the videos found. + */ @action processesVideoResults = (videos: any[]) => { this.searchResults = videos; @@ -126,7 +142,7 @@ export class YoutubeBox extends React.Component { this.videoIds = this.videoIds! + ", " + video.id.videoId; } }); - console.log("Video Ids: ", this.videoIds); + //Asking for details that include duration and viewCount from server for videoIds DocServer.getYoutubeVideoDetails(this.videoIds, this.processVideoDetails); this.backUpSearchResults(videos); if (this.videoClicked) { @@ -135,25 +151,26 @@ export class YoutubeBox extends React.Component { } } + /** + * The callback that is given to server to process and receive returned details about the videos. + */ @action processVideoDetails = (videoDetails: any[]) => { this.videoDetails = videoDetails; this.props.Document.cachedDetails = Docs.Get.DocumentHierarchyFromJson(videoDetails, "detailBackUp"); } + /** + * The function that stores the search results in the props document. + */ backUpSearchResults = (videos: any[]) => { this.props.Document.cachedSearchResults = Docs.Get.DocumentHierarchyFromJson(videos, "videosBackUp"); - let newCachedList = new List(); - 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); - }); } + /** + * The function that filters out escaped characters returned by the api + * in the title of the videos. + */ filterYoutubeTitleResult = (resultTitle: string) => { let processedTitle: string = resultTitle.ReplaceAll("&", "&"); processedTitle = processedTitle.ReplaceAll("'", "'"); @@ -161,32 +178,13 @@ export class YoutubeBox extends React.Component { 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(); - 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"; - } - } - roundPublishTime2 = (publishTime: string) => { + /** + * The function that converts ISO date, which is passed in, to normal date and finds the + * difference between today's date and that date, in terms of "ago" to imitate youtube. + */ + roundPublishTime = (publishTime: string) => { let date = new Date(publishTime).getTime(); let curDate = new Date().getTime(); let timeDif = curDate - date; @@ -228,6 +226,9 @@ export class YoutubeBox extends React.Component { } } + /** + * The function that converts the passed in ISO time to normal duration time. + */ convertIsoTimeToDuration = (isoDur: string) => { let convertedTime = isoDur.replace(/D|H|M/g, ":").replace(/P|T|S/g, "").split(":"); @@ -243,6 +244,10 @@ export class YoutubeBox extends React.Component { return convertedTime.join(":"); } + /** + * The function that rounds the viewCount to the nearest + * thousand, million or billion, given a viewCount number. + */ abbreviateViewCount = (viewCount: number) => { if (viewCount < 1000) { return viewCount.toString(); @@ -255,6 +260,11 @@ export class YoutubeBox extends React.Component { } } + /** + * The function that is called to decide on what'll be rendered by the component. + * It renders search Results if found. If user didn't do a new search, it renders from the videoTemplates + * generated by the backUps. If none present, renders nothing. + */ renderSearchResultsOrVideo = () => { if (this.searchResultsFound) { if (this.searchResults.length !== 0) { @@ -263,7 +273,7 @@ export class YoutubeBox extends React.Component { 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 pusblishDate = this.roundPublishTime(video.snippet.publishedAt); let duration; let viewCount; if (this.videoDetails.length !== 0) { @@ -316,6 +326,10 @@ export class YoutubeBox extends React.Component { } } + /** + * Given a videoId and title, creates a new youtube embedded url, and uses that + * to create a new video document. + */ @action embedVideoOnClick = (videoId: string, filteredTitle: string) => { let embeddedUrl = "https://www.youtube.com/embed/" + videoId; @@ -329,7 +343,6 @@ export class YoutubeBox extends React.Component { } render() { - let field = this.props.Document[this.props.fieldKey]; let content =
    this.YoutubeSearchElement = e!} /> -- cgit v1.2.3-70-g09d2 From 6cdebe507b777f60a0e30a1d7a75300304fbce09 Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Tue, 23 Jul 2019 19:17:03 -0400 Subject: Refactor and flag --- src/client/apis/youtube/YoutubeBox.tsx | 63 ++++++++++++++++--------------- src/server/youtubeApi/youtubeApiSample.js | 4 +- 2 files changed, 35 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index 414abcc15..8d6334c6e 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -58,37 +58,40 @@ export class YoutubeBox extends React.Component { let awaitedDetails = await castedDetailBackUp; - - let jsonList = await DocListCastAsync(awaitedBackUp!.json); - let jsonDetailList = await DocListCastAsync(awaitedDetails!.json); - - if (jsonList!.length !== 0) { - runInAction(() => this.searchResultsFound = true); - let index = 0; - //getting the necessary information from backUps and building templates that will be used to map in render - for (let video of jsonList!) { - - let videoId = await Cast(video.id, Doc); - let id = StrCast(videoId!.videoId); - let snippet = await Cast(video.snippet, Doc); - let videoTitle = this.filterYoutubeTitleResult(StrCast(snippet!.title)); - let thumbnail = await Cast(snippet!.thumbnails, Doc); - let thumbnailMedium = await Cast(thumbnail!.medium, Doc); - let thumbnailUrl = StrCast(thumbnailMedium!.url); - let videoDescription = StrCast(snippet!.description); - let pusblishDate = (this.roundPublishTime(StrCast(snippet!.publishedAt)))!; - let channelTitle = StrCast(snippet!.channelTitle); - let duration: string; - let viewCount: string; - if (jsonDetailList!.length !== 0) { - let contentDetails = await Cast(jsonDetailList![index].contentDetails, Doc); - let statistics = await Cast(jsonDetailList![index].statistics, Doc); - duration = this.convertIsoTimeToDuration(StrCast(contentDetails!.duration)); - viewCount = this.abbreviateViewCount(NumCast(statistics!.viewCount))!; + if (awaitedBackUp) { + + + let jsonList = await DocListCastAsync(awaitedBackUp!.json); + let jsonDetailList = await DocListCastAsync(awaitedDetails!.json); + + if (jsonList!.length !== 0) { + runInAction(() => this.searchResultsFound = true); + let index = 0; + //getting the necessary information from backUps and building templates that will be used to map in render + for (let video of jsonList!) { + + let videoId = await Cast(video.id, Doc); + let id = StrCast(videoId!.videoId); + let snippet = await Cast(video.snippet, Doc); + let videoTitle = this.filterYoutubeTitleResult(StrCast(snippet!.title)); + let thumbnail = await Cast(snippet!.thumbnails, Doc); + let thumbnailMedium = await Cast(thumbnail!.medium, Doc); + let thumbnailUrl = StrCast(thumbnailMedium!.url); + let videoDescription = StrCast(snippet!.description); + let pusblishDate = (this.roundPublishTime(StrCast(snippet!.publishedAt)))!; + let channelTitle = StrCast(snippet!.channelTitle); + let duration: string; + let viewCount: string; + if (jsonDetailList!.length !== 0) { + let contentDetails = await Cast(jsonDetailList![index].contentDetails, Doc); + let statistics = await Cast(jsonDetailList![index].statistics, Doc); + duration = this.convertIsoTimeToDuration(StrCast(contentDetails!.duration)); + viewCount = this.abbreviateViewCount(NumCast(statistics!.viewCount))!; + } + index = index + 1; + let newTemplate: VideoTemplate = { videoId: id, videoTitle: videoTitle, thumbnailUrl: thumbnailUrl, publishDate: pusblishDate, channelTitle: channelTitle, videoDescription: videoDescription, duration: duration!, viewCount: viewCount! }; + runInAction(() => this.curVideoTemplates.push(newTemplate)); } - index = index + 1; - let newTemplate: VideoTemplate = { videoId: id, videoTitle: videoTitle, thumbnailUrl: thumbnailUrl, publishDate: pusblishDate, channelTitle: channelTitle, videoDescription: videoDescription, duration: duration!, viewCount: viewCount! }; - runInAction(() => this.curVideoTemplates.push(newTemplate)); } } } diff --git a/src/server/youtubeApi/youtubeApiSample.js b/src/server/youtubeApi/youtubeApiSample.js index f81f0dfb5..9853241b6 100644 --- a/src/server/youtubeApi/youtubeApiSample.js +++ b/src/server/youtubeApi/youtubeApiSample.js @@ -28,7 +28,7 @@ module.exports.authorizedGetChannel = (apiKey) => { } module.exports.authorizedGetVideos = (apiKey, userInput, callBack) => { - authorize(JSON.parse(apiKey), getSampleVideos, { userInput: userInput, callBack: callBack }); + authorize(JSON.parse(apiKey), getVideos, { userInput: userInput, callBack: callBack }); } module.exports.authorizedGetVideoDetails = (apiKey, videoIds, callBack) => { @@ -141,7 +141,7 @@ function getChannel(auth) { }); } -function getSampleVideos(auth, args) { +function getVideos(auth, args) { let service = google.youtube('v3'); service.search.list({ auth: auth, -- cgit v1.2.3-70-g09d2 From f5b17bf655ab93d3214ebd1eb7697dd21265d3b5 Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Tue, 23 Jul 2019 19:27:25 -0400 Subject: Casting problem fixed --- src/client/apis/youtube/YoutubeBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index 8d6334c6e..019d191bc 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -86,7 +86,7 @@ export class YoutubeBox extends React.Component { let contentDetails = await Cast(jsonDetailList![index].contentDetails, Doc); let statistics = await Cast(jsonDetailList![index].statistics, Doc); duration = this.convertIsoTimeToDuration(StrCast(contentDetails!.duration)); - viewCount = this.abbreviateViewCount(NumCast(statistics!.viewCount))!; + viewCount = this.abbreviateViewCount(parseInt(StrCast(statistics!.viewCount)))!; } index = index + 1; let newTemplate: VideoTemplate = { videoId: id, videoTitle: videoTitle, thumbnailUrl: thumbnailUrl, publishDate: pusblishDate, channelTitle: channelTitle, videoDescription: videoDescription, duration: duration!, viewCount: viewCount! }; -- cgit v1.2.3-70-g09d2 From 2edcfb8a755b7fec7f937f135c794cbadbe0c94e Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Tue, 23 Jul 2019 19:40:47 -0400 Subject: Error on api extra call fixed. Don't know why would it get called twice tho --- src/server/youtubeApi/youtubeApiSample.js | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/server/youtubeApi/youtubeApiSample.js b/src/server/youtubeApi/youtubeApiSample.js index 9853241b6..ec532c78e 100644 --- a/src/server/youtubeApi/youtubeApiSample.js +++ b/src/server/youtubeApi/youtubeApiSample.js @@ -160,6 +160,9 @@ function getVideos(auth, args) { } function getVideoDetails(auth, args) { + if (args.videoIds === undefined) { + return; + } let service = google.youtube('v3'); service.videos.list({ auth: auth, -- cgit v1.2.3-70-g09d2 From 7844debd9862b15d2b36bf9ce1efb3274616a313 Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Wed, 24 Jul 2019 18:53:21 -0400 Subject: Context Menu and Some Preview --- .../views/collections/CollectionTreeView.tsx | 2 +- .../views/presentationview/PresentationElement.tsx | 58 ++++++++++++++++++++-- .../views/presentationview/PresentationView.scss | 4 ++ .../views/presentationview/PresentationView.tsx | 2 +- 4 files changed, 60 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 006de0c70..c0347ad7d 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -64,7 +64,7 @@ library.add(faArrowsAltH); /** * Component that takes in a document prop and a boolean whether it's collapsed or not. */ -class TreeView extends React.Component { +export class TreeView extends React.Component { private _header?: React.RefObject = React.createRef(); private _treedropDisposer?: DragManager.DragDropDisposer; private _dref = React.createRef(); diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx index 329630875..f354a14c2 100644 --- a/src/client/views/presentationview/PresentationElement.tsx +++ b/src/client/views/presentationview/PresentationElement.tsx @@ -1,11 +1,11 @@ import { observer } from "mobx-react"; import React = require("react"); import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; -import { NumCast, BoolCast, StrCast, Cast } from "../../../new_fields/Types"; +import { NumCast, BoolCast, StrCast, Cast, FieldValue } from "../../../new_fields/Types"; import { Id } from "../../../new_fields/FieldSymbols"; import { observable, action, computed, runInAction } from "mobx"; import "./PresentationView.scss"; -import { Utils } from "../../../Utils"; +import { Utils, emptyFunction, returnFalse } from "../../../Utils"; import { library } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faFile as fileSolid, faFileDownload, faLocationArrow, faArrowUp, faSearch } from '@fortawesome/free-solid-svg-icons'; @@ -16,6 +16,11 @@ import { DragManager, SetupDrag, dropActionType } from "../../util/DragManager"; import { SelectionManager } from "../../util/SelectionManager"; import { indexOf } from "typescript-collections/dist/lib/arrays"; import { map } from "bluebird"; +import { ContextMenu } from "../ContextMenu"; +import { DocumentContentsView } from "../nodes/DocumentContentsView"; +import { Transform } from "../../util/Transform"; +import { FieldView } from "../nodes/FieldView"; +import { DocumentView } from "../nodes/DocumentView"; library.add(faArrowUp); library.add(fileSolid); @@ -768,8 +773,51 @@ export default class PresentationElement extends React.Component) => { + e.preventDefault(); + e.stopPropagation(); + ContextMenu.Instance.addItem({ description: this.embedInline ? "Collapse Inline" : "Expand Inline", event: () => this.embedInline = !this.embedInline, icon: "expand" }); + ContextMenu.Instance.displayMenu(e.clientX, e.clientY); + } + renderEmbeddedInline = () => { + if (!this.embedInline) { + return (null); + } + + // return
      + // {TreeView.GetChildElements([this.props.document], "", new Doc(), undefined, "", (doc: Doc, relativeTo?: Doc, before?: boolean) => false, this.props.removeDocByRef, this.move, + // StrCast(this.props.document.dropAction) as dropActionType, (doc: Doc, dataDoc: Doc | undefined, where: string) => { }, Transform.Identity, () => ({ translateX: 0, translateY: 0 }), () => false, () => 400, 7)} + //
    ; + return ( + 1} + PanelWidth={() => 400} + PanelHeight={() => 400} + focus={(doc: Doc, willZoom: boolean) => { }} + selectOnLoad={false} + parentActive={returnFalse} + whenActiveChanged={(isActive: boolean) => { }} + bringToFront={(doc: Doc) => { }} + addDocTab={(doc: Doc, dataDoc: Doc | undefined, where: string) => { }} + zoomToScale={(scale: number) => { }} + getScale={() => 3.1415} + /> + ); + } render() { let p = this.props; @@ -786,7 +834,7 @@ export default class PresentationElement extends React.Component p.document, this.move, dropAction, this.props.mainDocument[Id], true); return ( -
    { p.gotoDocument(p.index, NumCast(this.props.mainDocument.selectedDoc)); e.stopPropagation(); }}> @@ -811,7 +860,8 @@ export default class PresentationElement extends React.Component - +
    + {this.renderEmbeddedInline()}
    ); } diff --git a/src/client/views/presentationview/PresentationView.scss b/src/client/views/presentationview/PresentationView.scss index 2bb0ec8c8..56c8beba5 100644 --- a/src/client/views/presentationview/PresentationView.scss +++ b/src/client/views/presentationview/PresentationView.scss @@ -19,6 +19,10 @@ -ms-user-select: none; user-select: none; transition: all .1s; + + .jsx-parser { + height: auto; + } } .presentationView-item-above { diff --git a/src/client/views/presentationview/PresentationView.tsx b/src/client/views/presentationview/PresentationView.tsx index b318f0321..95458b4ca 100644 --- a/src/client/views/presentationview/PresentationView.tsx +++ b/src/client/views/presentationview/PresentationView.tsx @@ -16,6 +16,7 @@ import { faArrowRight, faArrowLeft, faPlay, faStop, faPlus, faTimes, faMinus, fa import { Docs } from "../../documents/Documents"; import { undoBatch, UndoManager } from "../../util/UndoManager"; import PresentationViewList from "./PresentationList"; +import { ContextMenu } from "../ContextMenu"; library.add(faArrowLeft); library.add(faArrowRight); @@ -805,7 +806,6 @@ export class PresentationView extends React.Component { this.presElementsMappings.set(keyDoc, elem); } - render() { let width = NumCast(this.curPresentation.width); -- cgit v1.2.3-70-g09d2 From 1bb4cc79000b6d7c3a3fc137926e754877bf1dce Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Wed, 24 Jul 2019 20:50:13 -0400 Subject: some style --- src/client/views/presentationview/PresentationView.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/presentationview/PresentationView.scss b/src/client/views/presentationview/PresentationView.scss index 56c8beba5..392ec1fb7 100644 --- a/src/client/views/presentationview/PresentationView.scss +++ b/src/client/views/presentationview/PresentationView.scss @@ -21,7 +21,8 @@ transition: all .1s; .jsx-parser { - height: auto; + height: 300px; + width: 400px; } } -- cgit v1.2.3-70-g09d2 From 3d5884541db27dca38dac25b773a0eae7451a57f Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Wed, 24 Jul 2019 21:15:42 -0400 Subject: Ask Bob About sizing and scaling --- .../views/presentationview/PresentationElement.tsx | 44 ++++++++++++++++------ .../views/presentationview/PresentationView.scss | 1 + 2 files changed, 33 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx index f354a14c2..2091974ee 100644 --- a/src/client/views/presentationview/PresentationElement.tsx +++ b/src/client/views/presentationview/PresentationElement.tsx @@ -5,7 +5,7 @@ import { NumCast, BoolCast, StrCast, Cast, FieldValue } from "../../../new_field import { Id } from "../../../new_fields/FieldSymbols"; import { observable, action, computed, runInAction } from "mobx"; import "./PresentationView.scss"; -import { Utils, emptyFunction, returnFalse } from "../../../Utils"; +import { Utils, emptyFunction, returnFalse, returnOne } from "../../../Utils"; import { library } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faFile as fileSolid, faFileDownload, faLocationArrow, faArrowUp, faSearch } from '@fortawesome/free-solid-svg-icons'; @@ -799,22 +799,42 @@ export default class PresentationElement extends React.Component; return ( + // 1} + // PanelWidth={() => 400} + // PanelHeight={() => 400} + // focus={(doc: Doc, willZoom: boolean) => { }} + // selectOnLoad={false} + // parentActive={returnFalse} + // whenActiveChanged={(isActive: boolean) => { }} + // bringToFront={(doc: Doc) => { }} + // addDocTab={(doc: Doc, dataDoc: Doc | undefined, where: string) => { }} + // zoomToScale={(scale: number) => { }} + // getScale={() => 3.1415} + // /> 1} - PanelWidth={() => 400} - PanelHeight={() => 400} - focus={(doc: Doc, willZoom: boolean) => { }} + addDocTab={returnFalse} + renderDepth={1} + PanelWidth={returnXDimension} + PanelHeight={returnYDimension} + focus={emptyFunction} selectOnLoad={false} parentActive={returnFalse} - whenActiveChanged={(isActive: boolean) => { }} - bringToFront={(doc: Doc) => { }} - addDocTab={(doc: Doc, dataDoc: Doc | undefined, where: string) => { }} - zoomToScale={(scale: number) => { }} - getScale={() => 3.1415} + whenActiveChanged={returnFalse} + bringToFront={emptyFunction} + zoomToScale={emptyFunction} + getScale={returnOne} + ContainingCollectionView={undefined} + ContentScaling={scale} /> ); } diff --git a/src/client/views/presentationview/PresentationView.scss b/src/client/views/presentationview/PresentationView.scss index 392ec1fb7..b8b4f23c4 100644 --- a/src/client/views/presentationview/PresentationView.scss +++ b/src/client/views/presentationview/PresentationView.scss @@ -23,6 +23,7 @@ .jsx-parser { height: 300px; width: 400px; + margin-top: 20px; } } -- cgit v1.2.3-70-g09d2 From f55dd40423b85efcdfbae1dd037743de9f6f15c4 Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Thu, 25 Jul 2019 12:51:59 -0400 Subject: Some Preview Shown. Css left --- src/client/views/presentationview/PresentationElement.tsx | 15 ++++++++------- src/client/views/presentationview/PresentationView.scss | 4 +++- 2 files changed, 11 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx index 2091974ee..859ab9862 100644 --- a/src/client/views/presentationview/PresentationElement.tsx +++ b/src/client/views/presentationview/PresentationElement.tsx @@ -21,6 +21,7 @@ import { DocumentContentsView } from "../nodes/DocumentContentsView"; import { Transform } from "../../util/Transform"; import { FieldView } from "../nodes/FieldView"; import { DocumentView } from "../nodes/DocumentView"; +import { DocumentType } from "../../documents/Documents"; library.add(faArrowUp); library.add(fileSolid); @@ -656,7 +657,7 @@ export default class PresentationElement extends React.Component { - this.props.document.libraryBrush = true; + // this.props.document.libraryBrush = true; if (e.buttons === 1 && SelectionManager.GetIsDragging()) { let selected = NumCast(this.props.mainDocument.selectedDoc, 0); @@ -673,7 +674,7 @@ export default class PresentationElement extends React.Component { - this.props.document.libraryBrush = false; + // this.props.document.libraryBrush = false; //to get currently selected presentation doc let selected = NumCast(this.props.mainDocument.selectedDoc, 0); @@ -797,7 +798,7 @@ export default class PresentationElement extends React.Component false, this.props.removeDocByRef, this.move, // StrCast(this.props.document.dropAction) as dropActionType, (doc: Doc, dataDoc: Doc | undefined, where: string) => { }, Transform.Identity, () => ({ translateX: 0, translateY: 0 }), () => false, () => 400, 7)} // ; - + let scale = () => 175 / NumCast(this.props.document.nativeWidth, 175); return ( // { }} // getScale={() => 3.1415} // /> + 175} + PanelHeight={() => 175} focus={emptyFunction} selectOnLoad={false} parentActive={returnFalse} @@ -861,8 +863,7 @@ export default class PresentationElement extends React.Component { p.gotoDocument(p.index, NumCast(this.props.mainDocument.selectedDoc)); e.stopPropagation(); }}> diff --git a/src/client/views/presentationview/PresentationView.scss b/src/client/views/presentationview/PresentationView.scss index b8b4f23c4..c2d064941 100644 --- a/src/client/views/presentationview/PresentationView.scss +++ b/src/client/views/presentationview/PresentationView.scss @@ -19,12 +19,14 @@ -ms-user-select: none; user-select: none; transition: all .1s; + max-height: 250px; .jsx-parser { height: 300px; - width: 400px; + //width: 400px; margin-top: 20px; } + } .presentationView-item-above { -- cgit v1.2.3-70-g09d2 From 24a73583847e6ab00c8b1a00859129665e6ac212 Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Thu, 25 Jul 2019 19:03:04 -0400 Subject: Css Acting weird, fixes needed --- .../views/presentationview/PresentationElement.tsx | 66 ++++++++++++++-------- .../views/presentationview/PresentationView.scss | 14 ++++- 2 files changed, 55 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx index 859ab9862..ff68f47ba 100644 --- a/src/client/views/presentationview/PresentationElement.tsx +++ b/src/client/views/presentationview/PresentationElement.tsx @@ -798,6 +798,8 @@ export default class PresentationElement extends React.Component false, this.props.removeDocByRef, this.move, // StrCast(this.props.document.dropAction) as dropActionType, (doc: Doc, dataDoc: Doc | undefined, where: string) => { }, Transform.Identity, () => ({ translateX: 0, translateY: 0 }), () => false, () => 400, 7)} // ; + let propDocWidth = NumCast(this.props.document.nativeWidth); + let propDocHeight = NumCast(this.props.document.nativeHeight); let scale = () => 175 / NumCast(this.props.document.nativeWidth, 175); return ( // { }} // getScale={() => 3.1415} // /> - - 175} - PanelHeight={() => 175} - focus={emptyFunction} - selectOnLoad={false} - parentActive={returnFalse} - whenActiveChanged={returnFalse} - bringToFront={emptyFunction} - zoomToScale={emptyFunction} - getScale={returnOne} - ContainingCollectionView={undefined} - ContentScaling={scale} - /> +
    + 20} + PanelHeight={() => 20} + focus={emptyFunction} + selectOnLoad={false} + parentActive={returnFalse} + whenActiveChanged={returnFalse} + bringToFront={emptyFunction} + zoomToScale={emptyFunction} + getScale={returnOne} + ContainingCollectionView={undefined} + ContentScaling={scale} + /> +
    +
    ); } @@ -863,7 +885,7 @@ export default class PresentationElement extends React.Component { p.gotoDocument(p.index, NumCast(this.props.mainDocument.selectedDoc)); e.stopPropagation(); }}> diff --git a/src/client/views/presentationview/PresentationView.scss b/src/client/views/presentationview/PresentationView.scss index c2d064941..93cf0b365 100644 --- a/src/client/views/presentationview/PresentationView.scss +++ b/src/client/views/presentationview/PresentationView.scss @@ -19,12 +19,20 @@ -ms-user-select: none; user-select: none; transition: all .1s; - max-height: 250px; + //max-height: 250px; .jsx-parser { - height: 300px; + // height: 300px; //width: 400px; - margin-top: 20px; + // margin-top: 20px; + // overflow-y: scroll; + } + + .documentView-node { + // height: auto !important; + // width: aut !important; + position: absolute; + z-index: 1; } } -- cgit v1.2.3-70-g09d2 From 3152e69dfafe1c393bed38f3aad1e55881e62a33 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Fri, 26 Jul 2019 02:39:43 -0400 Subject: initial commit --- deploy/assets/Sunflower.mp3 | Bin 0 -> 7682122 bytes package.json | 1 + src/client/cognitive_services/CognitiveServices.ts | 27 +++++++++- src/client/views/MainView.tsx | 3 ++ src/server/RouteStore.ts | 1 + src/server/index.ts | 59 ++++++++++++++++++++- 6 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 deploy/assets/Sunflower.mp3 (limited to 'src') diff --git a/deploy/assets/Sunflower.mp3 b/deploy/assets/Sunflower.mp3 new file mode 100644 index 000000000..ab04baac4 Binary files /dev/null and b/deploy/assets/Sunflower.mp3 differ diff --git a/package.json b/package.json index 4a15cbb2f..12f0cd302 100644 --- a/package.json +++ b/package.json @@ -139,6 +139,7 @@ "jsonwebtoken": "^8.5.0", "jsx-to-string": "^1.4.0", "lodash": "^4.17.11", + "microsoft-cognitiveservices-speech-sdk": "^1.6.0", "mobile-detect": "^1.4.3", "mobx": "^5.9.0", "mobx-react": "^5.3.5", diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index d69378d0e..40bbe55a1 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -9,6 +9,10 @@ import { Utils } from "../../Utils"; import { CompileScript } from "../util/Scripting"; import { ComputedField } from "../../new_fields/ScriptField"; import { InkData } from "../../new_fields/InkField"; +import "microsoft-cognitiveservices-speech-sdk"; +import "fs"; +import { AudioInputStream } from "microsoft-cognitiveservices-speech-sdk"; +import { createReadStream, ReadStream } from "fs"; type APIManager = { converter: BodyConverter, requester: RequestExecutor, analyzer: AnalysisApplier }; type RequestExecutor = (apiKey: string, body: string, service: Service) => Promise; @@ -22,7 +26,8 @@ export type Rectangle = { top: number, left: number, width: number, height: numb export enum Service { ComputerVision = "vision", Face = "face", - Handwriting = "handwriting" + Handwriting = "handwriting", + Transcription = "transcription" } export enum Confidence { @@ -232,4 +237,24 @@ export namespace CognitiveServices { } + export namespace Transcription { + + export const Manager: APIManager = { + + converter: (data: string) => data, + + requester: async (apiKey: string, body: string, service: Service) => { + let analysis = await fetch(`${RouteStore.audioData}/${body}`).then(async response => JSON.parse(await response.json())); + console.log(analysis); + return ""; + }, + + analyzer: async (doc: Doc, keys: string[], filename: string) => { + let results = await executeQuery(Service.Transcription, Manager, filename); + } + + }; + + } + } \ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 61a013963..ca75ab2c4 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -39,6 +39,7 @@ import { PreviewCursor } from './PreviewCursor'; import { FilterBox } from './search/FilterBox'; import { CollectionTreeView } from './collections/CollectionTreeView'; import { ClientUtils } from '../util/ClientUtils'; +import { CognitiveServices } from '../cognitive_services/CognitiveServices'; @observer export class MainView extends React.Component { @@ -67,6 +68,8 @@ export class MainView extends React.Component { componentWillMount() { var tag = document.createElement('script'); + CognitiveServices.Transcription.Manager.analyzer(new Doc, ["hello", "world"], "Sunflower.mp3"); + tag.src = "https://www.youtube.com/iframe_api"; var firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag); diff --git a/src/server/RouteStore.ts b/src/server/RouteStore.ts index e30015e39..53f176c81 100644 --- a/src/server/RouteStore.ts +++ b/src/server/RouteStore.ts @@ -13,6 +13,7 @@ export enum RouteStore { upload = "/upload", dataUriToImage = "/uploadURI", images = "/images", + audioData = "/audioData", // USER AND WORKSPACES getCurrUser = "/getCurrentUser", diff --git a/src/server/index.ts b/src/server/index.ts index 40c0e7981..0a02b667e 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -40,6 +40,8 @@ import { Search } from './Search'; import { debug } from 'util'; import _ = require('lodash'); import { Response } from 'express-serve-static-core'; +import { AudioInputStream, AudioConfig, SpeechConfig, SpeechRecognizer, SpeechRecognitionResult } from 'microsoft-cognitiveservices-speech-sdk'; +import { Opt } from '../new_fields/Doc'; const MongoStore = require('connect-mongo')(session); const mongoose = require('mongoose'); const probe = require("probe-image-size"); @@ -297,7 +299,8 @@ addSecureRoute( const ServicesApiKeyMap = new Map([ ["face", process.env.FACE], ["vision", process.env.VISION], - ["handwriting", process.env.HANDWRITING] + ["handwriting", process.env.HANDWRITING], + ["transcription", process.env.TRANSCRIPTION] ]); addSecureRoute(Method.GET, (user, res, req) => { @@ -305,6 +308,60 @@ addSecureRoute(Method.GET, (user, res, req) => { res.send(ServicesApiKeyMap.get(service)); }, undefined, `${RouteStore.cognitiveServices}/:requestedservice`); +addSecureRoute( + Method.GET, + (user, res, req) => { + let asset = req.params.asset; + let pushStream = AudioInputStream.createPushStream(); + let readStream = fs.createReadStream(path.join(__dirname, '../../deploy/assets/' + asset)); + + let apiKey = process.env.TRANSCRIPTION; + if (!apiKey) { + res.send(undefined); + return; + } + + console.log("API KEY FOUND: ", apiKey); + + readStream.on('data', arrayBuffer => { + pushStream.write(arrayBuffer.buffer); + console.log(arrayBuffer.buffer); + }); + readStream.on('end', () => pushStream.close()); + readStream.on('error', (error) => { + console.log("ERROR! ", error); + res.end(error); + }); + + let audioConfig = AudioConfig.fromStreamInput(pushStream); + let speechConfig = SpeechConfig.fromSubscription(apiKey, "eastus"); + + console.log("Here are the configs!"); + console.log(audioConfig); + console.log(speechConfig); + + speechConfig.speechRecognitionLanguage = "en-US"; + + let recognizer: Opt = new SpeechRecognizer(speechConfig, audioConfig); + recognizer.recognizeOnceAsync( + (result: SpeechRecognitionResult) => { + console.log("RESULT! ", result); + res.send(result); + recognizer && recognizer.close(); + recognizer = undefined; + }, + (error: string) => { + console.log("RESULT ERROR: ", error); + res.send(error); + recognizer && recognizer.close(); + recognizer = undefined; + }, + ); + }, + undefined, + `${RouteStore.audioData}/:asset` +); + class NodeCanvasFactory { create = (width: number, height: number) => { var canvas = createCanvas(width, height); -- cgit v1.2.3-70-g09d2 From 4e73b2b844846ee39e743f69df6225fb4cbf4f2c Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Fri, 26 Jul 2019 17:24:27 -0400 Subject: Preview shown with right css --- src/client/views/presentationview/PresentationElement.tsx | 14 ++++++++++---- src/client/views/presentationview/PresentationView.tsx | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx index ff68f47ba..3297acee9 100644 --- a/src/client/views/presentationview/PresentationElement.tsx +++ b/src/client/views/presentationview/PresentationElement.tsx @@ -800,7 +800,12 @@ export default class PresentationElement extends React.Component; let propDocWidth = NumCast(this.props.document.nativeWidth); let propDocHeight = NumCast(this.props.document.nativeHeight); - let scale = () => 175 / NumCast(this.props.document.nativeWidth, 175); + let previewWidth = NumCast(this.props.document.schemaPreviewWidth); + let scale = () => { + let newScale = 175 / NumCast(this.props.document.nativeWidth, 175); + console.log("New Scale: ", newScale); + return newScale; + }; return ( // { }} // getScale={() => 3.1415} // /> +
    20} - PanelHeight={() => 20} + PanelWidth={() => 350} + PanelHeight={() => 100} focus={emptyFunction} selectOnLoad={false} parentActive={returnFalse} diff --git a/src/client/views/presentationview/PresentationView.tsx b/src/client/views/presentationview/PresentationView.tsx index 95458b4ca..718af1bf2 100644 --- a/src/client/views/presentationview/PresentationView.tsx +++ b/src/client/views/presentationview/PresentationView.tsx @@ -811,7 +811,7 @@ export class PresentationView extends React.Component { let width = NumCast(this.curPresentation.width); return ( -
    +
    {this.renderSelectOrPresSelection()} -- cgit v1.2.3-70-g09d2 From c6da9a2800037a951ea5070b4a77663401c4d00e Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Fri, 26 Jul 2019 18:52:30 -0400 Subject: resizable pres view --- .../views/presentationview/PresentationElement.tsx | 22 ----- .../views/presentationview/PresentationView.scss | 2 +- .../views/presentationview/PresentationView.tsx | 109 +++++++++++++++------ 3 files changed, 80 insertions(+), 53 deletions(-) (limited to 'src') diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx index 3297acee9..fdddf0e41 100644 --- a/src/client/views/presentationview/PresentationElement.tsx +++ b/src/client/views/presentationview/PresentationElement.tsx @@ -800,36 +800,14 @@ export default class PresentationElement extends React.Component; let propDocWidth = NumCast(this.props.document.nativeWidth); let propDocHeight = NumCast(this.props.document.nativeHeight); - let previewWidth = NumCast(this.props.document.schemaPreviewWidth); let scale = () => { let newScale = 175 / NumCast(this.props.document.nativeWidth, 175); console.log("New Scale: ", newScale); return newScale; }; return ( - // 1} - // PanelWidth={() => 400} - // PanelHeight={() => 400} - // focus={(doc: Doc, willZoom: boolean) => { }} - // selectOnLoad={false} - // parentActive={returnFalse} - // whenActiveChanged={(isActive: boolean) => { }} - // bringToFront={(doc: Doc) => { }} - // addDocTab={(doc: Doc, dataDoc: Doc | undefined, where: string) => { }} - // zoomToScale={(scale: number) => { }} - // getScale={() => 3.1415} - // /> -
    { //Variable that holds reference to title input, so that new presentations get titles assigned. @observable titleInputElement: HTMLInputElement | undefined; @observable PresTitleChangeOpen: boolean = false; + @observable PresViewWidth: number | undefined; //initilize class variables constructor(props: PresViewProps) { @@ -81,6 +82,7 @@ export class PresentationView extends React.Component { //The first lifecycle function that gets called to set up the current presentation. async componentWillMount() { + this.props.Documents.forEach(async (doc, index: number) => { //For each presentation received from mainContainer, a mapping is created. @@ -806,43 +808,90 @@ export class PresentationView extends React.Component { this.presElementsMappings.set(keyDoc, elem); } + //_downsize = 0; + onPointerDown = (e: React.PointerEvent) => { + //this._downsize = e.clientX; + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointermove", this.onPointerMove); + document.addEventListener("pointerup", this.onPointerUp); + e.stopPropagation(); + e.preventDefault(); + } + @action + onPointerMove = (e: PointerEvent) => { + + this.curPresentation.width = Math.max(window.innerWidth - e.clientX, 255); + } + @action + onPointerUp = (e: PointerEvent) => { + //this.isPointerDown = false; + // if (Math.abs(e.clientX - this._downsize) < 4) { + // if (this.flyoutWidth < 5) this.flyoutWidth = 250; + // else this.flyoutWidth = 0; + // } + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + } + + togglePresView = (e) => { + e.stopPropagation(); + e.preventDefault(); + let width = NumCast(this.curPresentation.width); + if (width === 0) { + this.curPresentation.width = 255; + } else { + this.curPresentation.width = 0; + } + } + render() { let width = NumCast(this.curPresentation.width); + this.PresViewWidth = width; + return ( -
    -
    - {this.renderSelectOrPresSelection()} - - - - +
    +
    +
    + {this.renderSelectOrPresSelection()} + + + + +
    +
    + + {this.renderPlayPauseButton()} + +
    + + this.presElementsMappings.clear()} + />
    -
    - - {this.renderPlayPauseButton()} - +
    +
    - this.presElementsMappings.clear()} - />
    ); } -- cgit v1.2.3-70-g09d2 From 811cb72cdb04b316f19d02c609ad515d6ec0a1b1 Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Fri, 26 Jul 2019 18:54:37 -0400 Subject: CleanUp --- src/client/views/presentationview/PresentationView.scss | 7 +------ src/client/views/presentationview/PresentationView.tsx | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/client/views/presentationview/PresentationView.scss b/src/client/views/presentationview/PresentationView.scss index 5769a1a0a..ee413f379 100644 --- a/src/client/views/presentationview/PresentationView.scss +++ b/src/client/views/presentationview/PresentationView.scss @@ -21,12 +21,7 @@ transition: all .1s; //max-height: 250px; - .jsx-parser { - // height: 300px; - //width: 400px; - // margin-top: 20px; - // overflow-y: scroll; - } + .documentView-node { // height: auto !important; diff --git a/src/client/views/presentationview/PresentationView.tsx b/src/client/views/presentationview/PresentationView.tsx index 273fa5e5f..de6c8ff82 100644 --- a/src/client/views/presentationview/PresentationView.tsx +++ b/src/client/views/presentationview/PresentationView.tsx @@ -834,7 +834,7 @@ export class PresentationView extends React.Component { document.removeEventListener("pointerup", this.onPointerUp); } - togglePresView = (e) => { + togglePresView = (e: React.MouseEvent) => { e.stopPropagation(); e.preventDefault(); let width = NumCast(this.curPresentation.width); -- cgit v1.2.3-70-g09d2 From 1acc70319c038cd8566035fbc5fec5c0b441b083 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 28 Jul 2019 17:11:06 -0400 Subject: undoable prop for context menu commands --- src/client/views/ContextMenuItem.tsx | 9 ++++++++- src/client/views/collections/CollectionStackingView.tsx | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index badb9cf19..4ff0fe388 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -4,12 +4,14 @@ import { observer } from "mobx-react"; import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; import { faAngleRight } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { UndoManager } from "../util/UndoManager"; library.add(faAngleRight); export interface OriginalMenuProps { description: string; event: () => void; + undoable?: boolean; icon: IconProp; //maybe should be optional (icon?) closeMenu?: () => void; } @@ -37,7 +39,12 @@ export class ContextMenuItem extends React.Component) => { if ("event" in this.props) { + let batch: UndoManager.Batch | undefined; + if (this.props.undoable !== false) { + batch = UndoManager.StartBatch(`Context menu event: ${this.props.description}`); + } this.props.event(); + batch && batch.end(); this.props.closeMenu && this.props.closeMenu(); } } @@ -94,7 +101,7 @@ export class ContextMenuItem extends React.Component {this.props.description} - +
    {submenu}
    diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index dab03f052..e166c145d 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -119,7 +119,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { children(docs: Doc[]) { this._docXfs.length = 0; return docs.map((d, i) => { - let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, d) + let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, d); let width = () => d.nativeWidth && !BoolCast(d.ignoreAspect) ? Math.min(pair.layout[WidthSym](), this.columnWidth) : this.columnWidth; let height = () => this.getDocHeight(pair.layout); let dref = React.createRef(); -- cgit v1.2.3-70-g09d2 From 27cafb6eebd1c6229c3377187a5c0043db25ba0a Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Sun, 28 Jul 2019 22:58:32 -0400 Subject: templating fixes, context menu undo streamlining --- src/.DS_Store | Bin 6148 -> 6148 bytes src/client/documents/Documents.ts | 5 ++--- src/client/views/ContextMenuItem.tsx | 4 ++-- src/client/views/collections/CollectionTreeView.tsx | 10 +++++----- src/client/views/collections/CollectionView.tsx | 14 +++++++------- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- src/client/views/nodes/KeyValueBox.tsx | 17 ++++++++++++----- src/scraping/buxton/scraper.py | 4 ++-- 8 files changed, 31 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/.DS_Store b/src/.DS_Store index 071dafa1e..c544bc837 100644 Binary files a/src/.DS_Store and b/src/.DS_Store differ diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 3859f2255..30a114e33 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -163,7 +163,6 @@ export namespace Docs { [DocumentType.LINKDOC, { data: new List(), layout: { view: EmptyBox }, - options: {} }], [DocumentType.BUTTON, { layout: { view: ButtonBox }, @@ -414,8 +413,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Tree }); } - export function StackingDocument(documents: Array, options: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: CollectionViewType.Stacking }); + export function StackingDocument(documents: Array, options: DocumentOptions, viewType: CollectionViewType = CollectionViewType.Stacking) { + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaColumns: new List(["title"]), ...options, viewType: viewType }); } export function ButtonDocument(options?: DocumentOptions) { diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index 4ff0fe388..a1787e78f 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -37,13 +37,13 @@ export class ContextMenuItem extends React.Component) => { + handleEvent = async (e: React.MouseEvent) => { if ("event" in this.props) { let batch: UndoManager.Batch | undefined; if (this.props.undoable !== false) { batch = UndoManager.StartBatch(`Context menu event: ${this.props.description}`); } - this.props.event(); + await this.props.event(); batch && batch.end(); this.props.closeMenu && this.props.closeMenu(); } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 1c7fe4bee..3eab109bc 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -237,10 +237,10 @@ class TreeView extends React.Component { if (DocumentManager.Instance.getDocumentViews(this.dataDoc).length) { ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.dataDoc).map(view => view.props.focus(this.props.document, true)), icon: "camera" }); } - ContextMenu.Instance.addItem({ description: "Delete Item", event: undoBatch(() => this.props.deleteDoc(this.props.document)), icon: "trash-alt" }); + ContextMenu.Instance.addItem({ description: "Delete Item", event: () => this.props.deleteDoc(this.props.document), icon: "trash-alt" }); } else { - ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.dataDoc)), icon: "caret-square-right" }); - ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.props.deleteDoc(this.props.document)), icon: "trash-alt" }); + ContextMenu.Instance.addItem({ description: "Open as Workspace", event: () => MainView.Instance.openWorkspace(this.dataDoc), icon: "caret-square-right" }); + ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.props.deleteDoc(this.props.document), icon: "trash-alt" }); } ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { let kvp = Docs.Create.KVPDocument(this.props.document, { width: 300, height: 300 }); this.props.addDocTab(kvp, this.props.dataDoc ? this.props.dataDoc : kvp, "onRight"); }, icon: "layer-group" }); ContextMenu.Instance.displayMenu(e.pageX > 156 ? e.pageX - 156 : 0, e.pageY - 15); @@ -520,8 +520,8 @@ export class CollectionTreeView extends CollectionSubView(Document) { onContextMenu = (e: React.MouseEvent): void => { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout if (!e.isPropagationStopped() && this.props.Document.workspaceLibrary) { // excludeFromLibrary means this is the user document - ContextMenu.Instance.addItem({ description: "Create Workspace", event: undoBatch(() => MainView.Instance.createNewWorkspace()), icon: "plus" }); - ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.remove(this.props.Document)), icon: "minus" }); + ContextMenu.Instance.addItem({ description: "Create Workspace", event: () => MainView.Instance.createNewWorkspace(), icon: "plus" }); + ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.remove(this.props.Document), icon: "minus" }); e.stopPropagation(); e.preventDefault(); ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index b4f29755c..e3f5d1eb8 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -53,14 +53,14 @@ export class CollectionView extends React.Component { onContextMenu = (e: React.MouseEvent): void => { if (!this.isAnnotationOverlay && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 let subItems: ContextMenuProps[] = []; - subItems.push({ description: "Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Freeform), icon: "signature" }); + subItems.push({ description: "Freeform", event: () => this.props.Document.viewType = CollectionViewType.Freeform, icon: "signature" }); if (CollectionBaseView.InSafeMode()) { - ContextMenu.Instance.addItem({ description: "Test Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Invalid), icon: "project-diagram" }); + ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => this.props.Document.viewType = CollectionViewType.Invalid, icon: "project-diagram" }); } - subItems.push({ description: "Schema", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Schema), icon: "th-list" }); - subItems.push({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree), icon: "tree" }); - subItems.push({ description: "Stacking", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Stacking), icon: "ellipsis-v" }); - subItems.push({ description: "Masonry", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Masonry), icon: "columns" }); + subItems.push({ description: "Schema", event: () => this.props.Document.viewType = CollectionViewType.Schema, icon: "th-list" }); + subItems.push({ description: "Treeview", event: () => this.props.Document.viewType = CollectionViewType.Tree, icon: "tree" }); + subItems.push({ description: "Stacking", event: () => this.props.Document.viewType = CollectionViewType.Stacking, icon: "ellipsis-v" }); + subItems.push({ description: "Masonry", event: () => this.props.Document.viewType = CollectionViewType.Masonry, icon: "columns" }); switch (this.props.Document.viewType) { case CollectionViewType.Freeform: { subItems.push({ description: "Custom", icon: "fingerprint", event: CollectionFreeFormView.AddCustomLayout(this.props.Document, this.props.fieldKey) }); @@ -68,7 +68,7 @@ export class CollectionView extends React.Component { } } ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems, icon: "eye" }); - ContextMenu.Instance.addItem({ description: "Apply Template", event: undoBatch(() => this.props.addDocTab && this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight")), icon: "project-diagram" }); + ContextMenu.Instance.addItem({ description: "Apply Template", event: () => this.props.addDocTab && this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" }); } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index d07fc7f80..a6152e1b7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -496,7 +496,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let layoutItems: ContextMenuProps[] = []; layoutItems.push({ description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`, - event: undoBatch(async () => this.props.Document.fitToBox = !this.fitToBox), + event: async () => this.props.Document.fitToBox = !this.fitToBox, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" }); layoutItems.push({ diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 77824b4ff..b0fcc4202 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -20,6 +20,7 @@ import { RichTextField } from "../../../new_fields/RichTextField"; import { ImageField } from "../../../new_fields/URLField"; import { SelectionManager } from "../../util/SelectionManager"; import { listSpec } from "../../../new_fields/Schema"; +import { CollectionViewType } from "../collections/CollectionBaseView"; export type KVPScript = { script: CompiledScript; @@ -195,6 +196,9 @@ export class KeyValueBox extends React.Component { } let fieldTemplate = await this.inferType(sourceDoc[metaKey], metaKey); + if (!fieldTemplate) { + return; + } let previousViewType = fieldTemplate.viewType; Doc.MakeTemplate(fieldTemplate, metaKey, Doc.GetProto(parentStackingDoc)); previousViewType && (fieldTemplate.viewType = previousViewType); @@ -211,14 +215,17 @@ export class KeyValueBox extends React.Component { return Docs.Create.StackingDocument([], options); } let first = await Cast(data[0], Doc); - if (!first) { + if (!first || !first.data) { return Docs.Create.StackingDocument([], options); } - switch (first.type) { - case "image": - return Docs.Create.StackingDocument([], options); - case "text": + switch (first.data.constructor) { + case RichTextField: return Docs.Create.TreeDocument([], options); + case ImageField: + return Docs.Create.StackingDocument([], options, CollectionViewType.Masonry); + default: + console.log(`Template for ${first.data.constructor} not supported!`); + return undefined; } } else if (data instanceof ImageField) { return Docs.Create.ImageDocument("https://image.flaticon.com/icons/png/512/23/23765.png", options); diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py index 182b22a1a..1ff0e3b31 100644 --- a/src/scraping/buxton/scraper.py +++ b/src/scraping/buxton/scraper.py @@ -139,7 +139,7 @@ def write_text_doc(content): data_doc = { "_id": data_doc_guid, "fields": { - "proto": protofy("commonImportProto"), + "proto": protofy("textProto"), "data": { "Data": '{"doc":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"' + content + '"}]}]},"selection":{"type":"text","anchor":1,"head":1}' + '}', "__type": "RichTextField" @@ -235,7 +235,7 @@ def parse_document(file_name: str): count += 1 view_guids.append(write_image(pure_name, image)) copyfile(dir_path + "/" + image, dir_path + - "/" + image.replace(".", "_o.", 1)) + "/" + image.replace(".", "_o.", 1)) os.rename(dir_path + "/" + image, dir_path + "/" + image.replace(".", "_m.", 1)) print(f"extracted {count} images...") -- cgit v1.2.3-70-g09d2 From 1c5c84c91742ff7822194519f536338bf96e6453 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Mon, 29 Jul 2019 13:58:36 -0400 Subject: fixed things to chrome and stacking view styling --- .../views/collections/CollectionSchemaView.scss | 27 +++++++++++++--------- .../views/collections/CollectionSchemaView.tsx | 1 - .../views/collections/CollectionStackingView.scss | 1 + .../views/collections/CollectionStackingView.tsx | 2 +- .../CollectionStackingViewFieldColumn.tsx | 2 +- src/client/views/collections/CollectionView.tsx | 21 ++++++++++++----- .../views/collections/CollectionViewChromes.scss | 5 ++-- 7 files changed, 37 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss index e0de76247..2697b482f 100644 --- a/src/client/views/collections/CollectionSchemaView.scss +++ b/src/client/views/collections/CollectionSchemaView.scss @@ -6,14 +6,14 @@ border-style: solid; border-radius: $border-radius; box-sizing: border-box; - // position: absolute; + position: absolute; + top: 0; width: 100%; height: calc(100% - 50px); // overflow: hidden; // overflow-x: scroll; // border: none; overflow: hidden; - transition: top 0.5s; // .collectionSchemaView-cellContents { // height: $MAX_ROW_HEIGHT; @@ -78,7 +78,7 @@ overflow-x: auto; height: 100%; display: -webkit-inline-box; - direction: ltr; + direction: ltr; } .rt-thead { @@ -122,7 +122,7 @@ font-size: 13px; text-align: center; background-color: $light-color-secondary; - + &:last-child { overflow: visible; } @@ -147,7 +147,7 @@ // &:nth-child(even) { // background-color: $light-color; // } - + // &:nth-child(odd) { // background-color: $light-color-secondary; // } @@ -175,7 +175,7 @@ padding: 0; font-size: 13px; text-align: center; - + // white-space: normal; .imageBox-cont { @@ -207,18 +207,19 @@ background: $light-color; } -.collectionSchema-col{ +.collectionSchema-col { height: 100%; .collectionSchema-col-wrapper { &.col-before { border-left: 2px solid red; } + &.col-after { border-right: 2px solid red; } } -} +} .collectionSchemaView-header { @@ -285,7 +286,7 @@ button.add-column { background-color: $light-color; border: 1px solid $light-color-secondary; padding: 2px 3px; - + &:not(:last-child) { border-top: 0; } @@ -313,7 +314,7 @@ button.add-column { background-color: white; &.row-focused { - background-color: rgb(255, 246, 246);//$light-color-secondary; + background-color: rgb(255, 246, 246); //$light-color-secondary; } &.row-wrapped { @@ -358,9 +359,11 @@ button.add-column { &.row-above { border-top: 1px solid red; } + &.row-below { border-bottom: 1px solid red; } + &.row-inside { border: 1px solid red; } @@ -461,7 +464,9 @@ button.add-column { .rt-table { overflow-x: hidden; // todo; this shouldnt be like this :(( overflow-y: visible; - } // TODO fix + } + + // TODO fix .row-dragger { background-color: rgb(252, 252, 252); diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 8436b22a4..8fe66a949 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -72,7 +72,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @observable private _node: HTMLDivElement | null = null; @observable private _focusedTable: Doc = this.props.Document; - @computed get chromeCollapsed() { return this.props.chromeCollapsed; } @computed get previewWidth() { return () => NumCast(this.props.Document.schemaPreviewWidth); } @computed get previewHeight() { return () => this.props.PanelHeight() - 2 * this.borderWidth; } @computed get tableWidth() { return this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH - this.previewWidth(); } diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss index 9dbe4ccb8..004b57eff 100644 --- a/src/client/views/collections/CollectionStackingView.scss +++ b/src/client/views/collections/CollectionStackingView.scss @@ -5,6 +5,7 @@ width: 100%; position: absolute; display: flex; + top: 0; overflow-y: auto; flex-wrap: wrap; transition: top .5s; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index f647da8f0..287c8a461 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -278,7 +278,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { }; // let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx); return ( -
    e.stopPropagation()} > {/* {sectionFilter as boolean ? [ ["width > height", this.filteredChildren.filter(f => f[WidthSym]() >= 1 + f[HeightSym]())], diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 387e189e7..c45b0c60a 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -266,7 +266,7 @@ export class CollectionStackingViewFieldColumn extends React.Component { @observable private _collapsed = false; + private _reactionDisposer: IReactionDisposer | undefined; + public static LayoutString(fieldStr: string = "data", fieldExt: string = "") { return FieldView.LayoutString(CollectionView, fieldStr, fieldExt); } componentDidMount = () => { - // chrome status is one of disabled, collapsed, or visible. this determines initial state from document - let chromeStatus = this.props.Document.chromeStatus; - if (chromeStatus && (chromeStatus === "disabled" || chromeStatus === "collapsed")) { - runInAction(() => this._collapsed = true); - } + this._reactionDisposer = reaction(() => StrCast(this.props.Document.chromeStatus), + () => { + // chrome status is one of disabled, collapsed, or visible. this determines initial state from document + let chromeStatus = this.props.Document.chromeStatus; + if (chromeStatus && (chromeStatus === "disabled" || chromeStatus === "collapsed")) { + runInAction(() => this._collapsed = true); + } + }); + } + + componentWillUnmount = () => { + this._reactionDisposer && this._reactionDisposer(); } private SubViewHelper = (type: CollectionViewType, renderProps: CollectionRenderProps) => { diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss index 6525f3b07..731333ff5 100644 --- a/src/client/views/collections/CollectionViewChromes.scss +++ b/src/client/views/collections/CollectionViewChromes.scss @@ -3,16 +3,17 @@ .collectionViewChrome-cont { position: relative; + opacity: 0.9; z-index: 9001; transition: top .5s; - background: lightslategray; + background: lightgrey; padding: 10px; .collectionViewChrome { display: grid; grid-template-columns: 1fr auto; padding-bottom: 10px; - border-bottom: .5px solid lightgrey; + border-bottom: .5px solid rgb(180, 180, 180); .collectionViewBaseChrome { display: flex; -- cgit v1.2.3-70-g09d2 From b6591b8403e83d836d3d74ce43739c5fc4a7d671 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Mon, 29 Jul 2019 14:08:31 -0400 Subject: Made extension docs be prefetched --- src/new_fields/Doc.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index da4f459e2..59314783b 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -12,6 +12,7 @@ import { scriptingGlobal } from "../client/util/Scripting"; import { List } from "./List"; import { DocumentType } from "../client/documents/Documents"; import { ComputedField } from "./ScriptField"; +import { PrefetchProxy } from "./Proxy"; export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { @@ -348,7 +349,7 @@ export namespace Doc { while (proto && !Doc.IsPrototype(proto)) { proto = proto.proto; } - (proto ? proto : doc)[fieldKey + "_ext"] = docExtensionForField; + (proto ? proto : doc)[fieldKey + "_ext"] = new PrefetchProxy(docExtensionForField); }, 0); } else if (doc instanceof Doc) { // backward compatibility -- add fields for docs that don't have them already docExtensionForField.extendsDoc === undefined && setTimeout(() => docExtensionForField.extendsDoc = doc, 0); -- cgit v1.2.3-70-g09d2 From bb607ecb02a7ecb8fe782d60e10ebf5bdedd790b Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Mon, 29 Jul 2019 14:08:46 -0400 Subject: Fixed bad headers crashing cors proxy on server --- src/server/index.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/server/index.ts b/src/server/index.ts index 40c0e7981..adf218be6 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -437,8 +437,22 @@ app.post(RouteStore.forgot, postForgot); app.get(RouteStore.reset, getReset); app.post(RouteStore.reset, postReset); -app.use(RouteStore.corsProxy, (req, res) => - req.pipe(request(decodeURIComponent(req.url.substring(1)))).pipe(res)); +const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/; +app.use(RouteStore.corsProxy, (req, res) => { + req.pipe(request(decodeURIComponent(req.url.substring(1)))).on("response", res => { + const headers = Object.keys(res.headers); + headers.forEach(headerName => { + const header = res.headers[headerName]; + if (Array.isArray(header)) { + res.headers[headerName] = header.filter(h => !headerCharRegex.test(h)); + } else if (header) { + if (headerCharRegex.test(header as any)) { + delete res.headers[headerName]; + } + } + }); + }).pipe(res); +}); app.get(RouteStore.delete, (req, res) => { if (release) { -- cgit v1.2.3-70-g09d2 From 5fff8155b3102e76feb718c4f67c6e85050aab6e Mon Sep 17 00:00:00 2001 From: yipstanley Date: Mon, 29 Jul 2019 14:50:43 -0400 Subject: stacking view background color to inherit --- src/client/views/collections/CollectionStackingViewFieldColumn.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index c45b0c60a..30177ebd7 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -34,7 +34,7 @@ interface CSVFieldColumnProps { @observer export class CollectionStackingViewFieldColumn extends React.Component { - @observable private _background = "white"; + @observable private _background = "inherit"; private _dropRef: HTMLDivElement | null = null; private dropDisposer?: DragManager.DragDropDisposer; @@ -158,7 +158,7 @@ export class CollectionStackingViewFieldColumn extends React.Component { - this._background = "white"; + this._background = "inherit"; } @action -- cgit v1.2.3-70-g09d2 From 93df48fe22f4152878e3a74e0366668fbbab895e Mon Sep 17 00:00:00 2001 From: yipstanley Date: Mon, 29 Jul 2019 14:51:50 -0400 Subject: fixed document decorations when chrome expanded on stacking view --- src/client/views/collections/CollectionStackingViewFieldColumn.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 30177ebd7..aa86d9a54 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -111,7 +111,7 @@ export class CollectionStackingViewFieldColumn extends React.Component Date: Mon, 29 Jul 2019 14:56:05 -0400 Subject: + ADD / ADD GROUP does not show if chromeStatus is disabled --- src/client/views/collections/CollectionStackingView.tsx | 2 +- .../views/collections/CollectionStackingViewFieldColumn.tsx | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 287c8a461..85aa322a2 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -287,7 +287,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { {this.props.Document.sectionFilter ? Array.from(this.Sections.entries()).sort(this.sortFunc). map(section => this.section(section[0], section[1])) : this.section(undefined, this.filteredChildren)} - {this.props.Document.sectionFilter ? + {(this.props.Document.sectionFilter && this.props.CollectionView.props.Document.chromeStatus !== 'disabled') ?
    diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index c45b0c60a..6b94efb1c 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -279,10 +279,11 @@ export class CollectionStackingViewFieldColumn extends React.Component -
    - -
    + {(this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'disabled') ? +
    + +
    : null}
    ); } -- cgit v1.2.3-70-g09d2 From c19fc89ebbaec30ff284988b7234815d822bbe0a Mon Sep 17 00:00:00 2001 From: kimdahey Date: Mon, 29 Jul 2019 15:40:30 -0400 Subject: fixed column width not adjusting with omitted + ADD NEW / NEW GROUP --- src/client/views/collections/CollectionStackingView.tsx | 4 ++-- src/client/views/collections/CollectionStackingViewFieldColumn.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 85aa322a2..5e4d8bda5 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -287,9 +287,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { {this.props.Document.sectionFilter ? Array.from(this.Sections.entries()).sort(this.sortFunc). map(section => this.section(section[0], section[1])) : this.section(undefined, this.filteredChildren)} - {(this.props.Document.sectionFilter && this.props.CollectionView.props.Document.chromeStatus !== 'disabled') ? + {this.props.CollectionView.props.Document.chromeStatus !== 'disabled' ?
    + style={{ width: (this.columnWidth / (headings.length + (this.props.CollectionView.props.Document.chromeStatus !== 'disabled' ? 1 : 0))) - 10, marginTop: 10 }}>
    : null}
    diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 6b94efb1c..9e39d5383 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -261,7 +261,7 @@ export class CollectionStackingViewFieldColumn extends React.Component : (null); for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth}px `; return ( -
    {headingView}
    {(this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'disabled') ?
    + style={{ width: style.columnWidth / (uniqueHeadings.length + (this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'disabled' ? 1 : 0)) }}>
    : null}
    -- cgit v1.2.3-70-g09d2 From 6079237a6a18bf63d18a9f471baadc9b48d9f105 Mon Sep 17 00:00:00 2001 From: kimdahey Date: Mon, 29 Jul 2019 15:46:27 -0400 Subject: fixed column width not adjusting with omitted + ADD NEW / NEW GROUP --- src/client/views/collections/CollectionStackingViewFieldColumn.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 9e39d5383..7727a23c7 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -239,7 +239,7 @@ export class CollectionStackingViewFieldColumn extends React.Component + style={{ width: (style.columnWidth) / (uniqueHeadings.length + (this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'disabled' ? 1 : 0)) }}> {/* the default bucket (no key value) has a tooltip that describes what it is. Further, it does not have a color and cannot be deleted. */}
    Date: Mon, 29 Jul 2019 15:59:47 -0400 Subject: re-added in sectionFilter check --- src/client/views/collections/CollectionStackingView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 5e4d8bda5..6e1968774 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -287,7 +287,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { {this.props.Document.sectionFilter ? Array.from(this.Sections.entries()).sort(this.sortFunc). map(section => this.section(section[0], section[1])) : this.section(undefined, this.filteredChildren)} - {this.props.CollectionView.props.Document.chromeStatus !== 'disabled' ? + {(this.props.Document.sectionFilter && this.props.CollectionView.props.Document.chromeStatus !== 'disabled') ?
    -- cgit v1.2.3-70-g09d2 From a560acaf539c058a2918a12e5de5061cca4b19e2 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Mon, 29 Jul 2019 16:10:13 -0400 Subject: Bunch of fixes --- src/client/views/MainOverlayTextBox.tsx | 6 ++++-- src/client/views/collections/CollectionSchemaCells.tsx | 3 ++- src/client/views/nodes/DocumentView.tsx | 16 ++++++++++++---- src/client/views/nodes/FieldView.tsx | 2 ++ src/client/views/nodes/FormattedTextBox.tsx | 4 +++- src/client/views/nodes/ImageBox.tsx | 6 +++++- src/client/views/nodes/KeyValuePair.tsx | 3 ++- src/client/views/nodes/PDFBox.tsx | 14 -------------- 8 files changed, 30 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index 126efd11c..8e2d7be85 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -4,7 +4,7 @@ import "normalize.css"; import * as React from 'react'; import { Doc } from '../../new_fields/Doc'; import { BoolCast } from '../../new_fields/Types'; -import { emptyFunction, returnTrue, returnZero, Utils } from '../../Utils'; +import { emptyFunction, returnTrue, returnZero, Utils, returnOne } from '../../Utils'; import { DragManager } from '../util/DragManager'; import { Transform } from '../util/Transform'; import { CollectionDockingView } from './collections/CollectionDockingView'; @@ -29,6 +29,7 @@ export class MainOverlayTextBox extends React.Component private _outerdiv: HTMLElement | null = null; private _textBox: FormattedTextBox | undefined; private _tooltip?: HTMLElement; + ChromeHeight?: () => number; @observable public TextDoc?: Doc; @observable public TextDataDoc?: Doc; @@ -49,6 +50,7 @@ export class MainOverlayTextBox extends React.Component (box?: FormattedTextBox) => { this._textBox = box; if (box) { + this.ChromeHeight = box.props.ChromeHeight; this.TextDoc = box.props.Document; this.TextDataDoc = box.props.DataDoc; let xf = () => { @@ -140,7 +142,7 @@ export class MainOverlayTextBox extends React.Component Document={FormattedTextBox.InputBoxOverlay.props.Document} DataDoc={FormattedTextBox.InputBoxOverlay.props.DataDoc} isSelected={returnTrue} select={emptyFunction} renderDepth={0} selectOnLoad={true} - ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} + ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} ContentScaling={returnOne} ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} outer_div={(tooltip: HTMLElement) => { this._tooltip = tooltip; this.updateTooltip(); }} />
    diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index 194765880..fdf0896ac 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -115,7 +115,7 @@ export class CollectionSchemaCell extends React.Component { } } - private dropRef = (ele: HTMLElement) => { + private dropRef = (ele: HTMLElement | null) => { this._dropDisposer && this._dropDisposer(); if (ele) { this._dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); @@ -154,6 +154,7 @@ export class CollectionSchemaCell extends React.Component { PanelHeight: returnZero, PanelWidth: returnZero, addDocTab: this.props.addDocTab, + ContentScaling: returnOne }; let field = props.Document[props.fieldKey]; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index f101222ae..9f0945c6b 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -99,6 +99,7 @@ export interface DocumentViewProps { zoomToScale: (scale: number) => void; getScale: () => number; animateBetweenIcon?: (iconPos: number[], startTime: number, maximizing: boolean) => void; + ChromeHeight?: () => number; } const schema = createSchema({ @@ -644,6 +645,7 @@ export class DocumentView extends DocComponent(Docu @computed get nativeHeight() { return this.Document.nativeHeight || 0; } @computed get contents() { return ((Docu DataDoc={this.dataDoc} />); } + chromeHeight = () => { + let showOverlays = this.props.showOverlays ? this.props.showOverlays(this.layoutDoc) : undefined; + let showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.layoutDoc.showTitle); + return showTitle ? 25 : 0; + } + get layoutDoc() { // if this document's layout field contains a document (ie, a rendering template), then we will use that // to determine the render JSX string, otherwise the layout field should directly contain a JSX layout string. @@ -666,8 +674,8 @@ export class DocumentView extends DocComponent(Docu var nativeWidth = this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? `${this.nativeWidth}px` : "100%"; var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%"; let showOverlays = this.props.showOverlays ? this.props.showOverlays(this.layoutDoc) : undefined; - let showTitle = showOverlays && showOverlays.title !== "undefined" ? showOverlays.title : StrCast(this.layoutDoc.showTitle); - let showCaption = showOverlays && showOverlays.caption !== "undefined" ? showOverlays.caption : StrCast(this.layoutDoc.showCaption); + let showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : StrCast(this.layoutDoc.showTitle); + let showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : StrCast(this.layoutDoc.showCaption); let templates = Cast(this.layoutDoc.templates, listSpec("string")); if (!showOverlays && templates instanceof List) { templates.map(str => { @@ -716,11 +724,11 @@ export class DocumentView extends DocComponent(Docu transformOrigin: "top left", transform: `scale(${1 / this.props.ContentScaling()})` }}> StrCast(this.layoutDoc[showTitle!])} + GetValue={() => StrCast((this.layoutDoc.isTemplate || !this.dataDoc ? this.layoutDoc : this.dataDoc)[showTitle!])} SetValue={(value: string) => (Doc.GetProto(this.layoutDoc)[showTitle!] = value) ? true : true} />
    diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index ffaee8042..da54ecc3a 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -48,6 +48,8 @@ export interface FieldViewProps { PanelHeight: () => number; setVideoBox?: (player: VideoBox) => void; setPdfBox?: (player: PDFBox) => void; + ContentScaling: () => number; + ChromeHeight?: () => number; } @observer diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index f019868aa..0f51ca2ce 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -35,6 +35,7 @@ import "./FormattedTextBox.scss"; import React = require("react"); import { DateField } from '../../../new_fields/DateField'; import { Utils } from '../../../Utils'; +import { MainOverlayTextBox } from '../MainOverlayTextBox'; library.add(faEdit); library.add(faSmile, faTextHeight); @@ -557,7 +558,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let nh = NumCast(this.dataDoc.nativeHeight, 0); let dh = NumCast(this.props.Document.height, 0); let sh = scrBounds.height; - this.props.Document.height = nh ? dh / nh * sh : sh; + const ChromeHeight = MainOverlayTextBox.Instance.ChromeHeight; + this.props.Document.height = (nh ? dh / nh * sh : sh) + (ChromeHeight ? ChromeHeight() : 0); this.dataDoc.nativeHeight = nh ? sh : undefined; } } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 29a76b0c8..82af4b4ca 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -96,7 +96,11 @@ export class ImageBox extends DocComponent(ImageD this.props.Document.width = drop.width; this.props.Document.height = drop.height; Doc.GetProto(this.props.Document).type = DocumentType.TEMPLATE; - this.props.Document.layout = temp; + if (this.props.DataDoc && this.props.DataDoc.layout === this.props.Document) { + this.props.DataDoc.layout = temp; + } else { + this.props.Document.layout = temp; + } e.stopPropagation(); } else if (de.mods === "AltKey" && /*this.dataDoc !== this.props.Document &&*/ drop.data instanceof ImageField) { Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new ImageField(drop.data.url); diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 064f3edcc..4abf87b9f 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -1,7 +1,7 @@ import { action, observable } from 'mobx'; import { observer } from "mobx-react"; import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app -import { emptyFunction, returnFalse, returnZero, returnTrue } from '../../../Utils'; +import { emptyFunction, returnFalse, returnZero, returnTrue, returnOne } from '../../../Utils'; import { CompileScript, CompiledScript, ScriptOptions } from "../../util/Scripting"; import { Transform } from '../../util/Transform'; import { EditableView } from "../EditableView"; @@ -70,6 +70,7 @@ export class KeyValuePair extends React.Component { PanelWidth: returnZero, PanelHeight: returnZero, addDocTab: returnZero, + ContentScaling: returnOne }; let contents = ; // let fieldKey = Object.keys(props.Document).indexOf(props.fieldKey) !== -1 ? props.fieldKey : "(" + props.fieldKey + ")"; diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 4973340df..fa072aecf 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -69,24 +69,10 @@ export class PDFBox extends DocComponent(PdfDocumen componentDidMount() { if (this.props.setPdfBox) this.props.setPdfBox(this); - - document.removeEventListener("copy", this.copy); - document.addEventListener("copy", this.copy); } componentWillUnmount() { this._reactionDisposer && this._reactionDisposer(); - document.removeEventListener("copy", this.copy); - } - - private copy = (e: ClipboardEvent) => { - if (this.props.active()) { - if (e.clipboardData) { - e.clipboardData.setData("text/plain", text); - e.clipboardData.setData("dash/pdfOrigin", this.props.Document[Id]); - e.preventDefault(); - } - } } public GetPage() { -- cgit v1.2.3-70-g09d2 From d87020047c1953b212257236a25036df3903dd47 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Mon, 29 Jul 2019 16:20:20 -0400 Subject: chrome default to collapsed --- src/client/documents/Documents.ts | 11 ++++++----- src/client/views/collections/CollectionView.tsx | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 18709499a..d1b3071ed 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -82,6 +82,7 @@ export interface DocumentOptions { backgroundColor?: string; dropAction?: dropActionType; backgroundLayout?: string; + chromeStatus?: string; curPage?: number; documentText?: string; borderRounding?: string; @@ -403,23 +404,23 @@ export namespace Docs { } export function FreeformDocument(documents: Array, options: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaColumns: new List([new SchemaHeaderField("title")]), ...options, viewType: CollectionViewType.Freeform }); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title")]), ...options, viewType: CollectionViewType.Freeform }); } export function SchemaDocument(schemaColumns: SchemaHeaderField[], documents: Array, options: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaColumns: new List(schemaColumns), ...options, viewType: CollectionViewType.Schema }); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List(schemaColumns), ...options, viewType: CollectionViewType.Schema }); } export function TreeDocument(documents: Array, options: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaColumns: new List([new SchemaHeaderField("title")]), ...options, viewType: CollectionViewType.Tree }); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title")]), ...options, viewType: CollectionViewType.Tree }); } export function StackingDocument(documents: Array, options: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaColumns: new List([new SchemaHeaderField("title")]), ...options, viewType: CollectionViewType.Stacking }); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title")]), ...options, viewType: CollectionViewType.Stacking }); } export function MasonryDocument(documents: Array, options: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaColumns: new List([new SchemaHeaderField("title")]), ...options, viewType: CollectionViewType.Masonry }); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title")]), ...options, viewType: CollectionViewType.Masonry }); } export function ButtonDocument(options?: DocumentOptions) { diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 55e014c66..b7ac8768f 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -35,7 +35,7 @@ library.add(faImage, faEye); @observer export class CollectionView extends React.Component { - @observable private _collapsed = false; + @observable private _collapsed = true; private _reactionDisposer: IReactionDisposer | undefined; -- cgit v1.2.3-70-g09d2 From 06dfb537826e8b1642783a9c4ab2514fd7da2fe3 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Mon, 29 Jul 2019 16:53:52 -0400 Subject: More fixes --- src/client/views/collections/CollectionDockingView.tsx | 2 +- src/client/views/collections/CollectionStackingView.scss | 1 + src/client/views/collections/CollectionStackingViewFieldColumn.tsx | 4 ++-- src/client/views/nodes/ImageBox.tsx | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 1859ebee7..d7b5262b1 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -561,7 +561,7 @@ export class DockedFrameRenderer extends React.Component { } return Transform.Identity(); } - get previewPanelCenteringOffset() { return this.nativeWidth && !BoolCast(this._document!.ignoreAspect) ? (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2 : 0; } + get previewPanelCenteringOffset() { return this.nativeWidth && !BoolCast(this._document!.ignoreAspect) ? (this._panelWidth - this.nativeWidth()) / 2 : 0; } addDocTab = (doc: Doc, dataDoc: Doc | undefined, location: string) => { if (doc.dockingConfig) { diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss index 004b57eff..2bb36c825 100644 --- a/src/client/views/collections/CollectionStackingView.scss +++ b/src/client/views/collections/CollectionStackingView.scss @@ -74,6 +74,7 @@ transform-origin: top left; grid-column-end: span 1; height: 100%; + margin: auto; } .collectionStackingView-sectionHeader { diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 71c4d9f0c..b5dbc1a22 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -239,7 +239,7 @@ export class CollectionStackingViewFieldColumn extends React.Component + style={{ width: (style.columnWidth) / ((uniqueHeadings.length + (this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'disabled' ? 1 : 0)) || 1) }}> {/* the default bucket (no key value) has a tooltip that describes what it is. Further, it does not have a color and cannot be deleted. */}
    : (null); for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth}px `; return ( -
    {headingView}
    (ImageD let aspect = size.height / size.width; let rotation = NumCast(this.dataDoc.rotation) % 180; if (rotation === 90 || rotation === 270) aspect = 1 / aspect; - if (Math.abs(layoutdoc[HeightSym]() / layoutdoc[WidthSym]() - aspect) > 0.01) { + if (Math.abs(NumCast(layoutdoc.height) - size.height) > 1 || Math.abs(NumCast(layoutdoc.width) - size.width) > 1) { setTimeout(action(() => { layoutdoc.height = layoutdoc[WidthSym]() * aspect; layoutdoc.nativeHeight = size.height; -- cgit v1.2.3-70-g09d2 From ad00d760b364c3d3478be5c0f12b3eb6b6afaf5b Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Mon, 29 Jul 2019 16:58:34 -0400 Subject: fixed escape to close full screen --- src/client/views/MainView.tsx | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index f5a6715e5..91c8fe57c 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -92,6 +92,8 @@ export class MainView extends React.Component { componentWillUnMount() { window.removeEventListener("keydown", KeyManager.Instance.handle); + window.removeEventListener("pointerdown", this.globalPointerDown); + window.removeEventListener("pointerup", this.globalPointerUp); } constructor(props: Readonly<{}>) { @@ -138,18 +140,23 @@ export class MainView extends React.Component { this.initAuthenticationRouters(); } + globalPointerDown = action((e: PointerEvent) => { + this.isPointerDown = true; + const targets = document.elementsFromPoint(e.x, e.y); + if (targets && targets.length && targets[0].className.toString().indexOf("contextMenu") === -1) { + ContextMenu.Instance.closeMenu(); + } + }); + + globalPointerUp = () => this.isPointerDown = false; + initEventListeners = () => { // window.addEventListener("pointermove", (e) => this.reportLocation(e)) window.addEventListener("drop", (e) => e.preventDefault(), false); // drop event handler window.addEventListener("dragover", (e) => e.preventDefault(), false); // drag event handler // click interactions for the context menu - document.addEventListener("pointerdown", action((e: PointerEvent) => { - this.isPointerDown = true; - const targets = document.elementsFromPoint(e.x, e.y); - if (targets && targets.length && targets[0].className.toString().indexOf("contextMenu") === -1) { - ContextMenu.Instance.closeMenu(); - } - }), true); + document.addEventListener("pointerdown", this.globalPointerDown); + document.addEventListener("pointerup", this.globalPointerUp); } initAuthenticationRouters = async () => { @@ -292,7 +299,6 @@ export class MainView extends React.Component { } @action onPointerUp = (e: PointerEvent) => { - this.isPointerDown = false; if (Math.abs(e.clientX - this._downsize) < 4) { if (this.flyoutWidth < 5) this.flyoutWidth = 250; else this.flyoutWidth = 0; -- cgit v1.2.3-70-g09d2 From 3d5e8b4cfff2bc0385d455587934538d5b82bee9 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Mon, 29 Jul 2019 17:41:56 -0400 Subject: Added some undos --- src/client/views/MetadataEntryMenu.tsx | 2 ++ src/client/views/nodes/KeyValueBox.tsx | 2 ++ src/client/views/nodes/KeyValuePair.tsx | 5 +++-- 3 files changed, 7 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx index 652e0e91a..36c240dd8 100644 --- a/src/client/views/MetadataEntryMenu.tsx +++ b/src/client/views/MetadataEntryMenu.tsx @@ -5,6 +5,7 @@ import { observable, action, runInAction, trace } from 'mobx'; import { KeyValueBox } from './nodes/KeyValueBox'; import { Doc, Field } from '../../new_fields/Doc'; import * as Autosuggest from 'react-autosuggest'; +import { undoBatch } from '../util/UndoManager'; export type DocLike = Doc | Doc[] | Promise | Promise; export interface MetadataEntryProps { @@ -74,6 +75,7 @@ export class MetadataEntryMenu extends React.Component{ this.userModified = e.target.value.trim() !== ""; } + @undoBatch @action onValueKeyDown = async (e: React.KeyboardEvent) => { if (e.key === "Enter") { diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index e8075d076..f10079169 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -21,6 +21,7 @@ import { ImageField } from "../../../new_fields/URLField"; import { SelectionManager } from "../../util/SelectionManager"; import { listSpec } from "../../../new_fields/Schema"; import { CollectionViewType } from "../collections/CollectionBaseView"; +import { undoBatch } from "../../util/UndoManager"; export type KVPScript = { script: CompiledScript; @@ -90,6 +91,7 @@ export class KeyValueBox extends React.Component { return false; } + @undoBatch public static SetField(doc: Doc, key: string, value: string) { const script = this.CompileKVPScript(value); if (!script) return false; diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 4abf87b9f..3775f0f47 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -16,6 +16,7 @@ import { DragManager, SetupDrag } from '../../util/DragManager'; import { ContextMenu } from '../ContextMenu'; import { Docs } from '../../documents/Documents'; import { CollectionDockingView } from '../collections/CollectionDockingView'; +import { undoBatch } from '../../util/UndoManager'; // Represents one row in a key value plane @@ -92,12 +93,12 @@ export class KeyValuePair extends React.Component { this.isPointerOver = true)} onPointerLeave={action(() => this.isPointerOver = false)}>
    - Date: Mon, 29 Jul 2019 19:17:29 -0400 Subject: Made AddTab be able to accept no stack --- src/client/views/collections/CollectionDockingView.tsx | 15 ++++++++++++++- src/new_fields/SchemaHeaderField.ts | 1 - 2 files changed, 14 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index d7b5262b1..ab4d1aa62 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -211,7 +211,20 @@ export class CollectionDockingView extends React.Component Date: Mon, 29 Jul 2019 19:19:49 -0400 Subject: PresMode, CLoseable Pres, and some bug fixes --- src/client/views/MainView.tsx | 2 +- .../views/presentationview/PresentationElement.tsx | 24 ++++- .../views/presentationview/PresentationList.tsx | 2 +- .../presentationview/PresentationModeMenu.scss | 30 ++++++ .../presentationview/PresentationModeMenu.tsx | 92 ++++++++++++++++ .../views/presentationview/PresentationView.scss | 2 +- .../views/presentationview/PresentationView.tsx | 116 +++++++++++++++++---- 7 files changed, 238 insertions(+), 30 deletions(-) create mode 100644 src/client/views/presentationview/PresentationModeMenu.scss create mode 100644 src/client/views/presentationview/PresentationModeMenu.tsx (limited to 'src') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 53be0278e..0dbb241e4 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -267,7 +267,7 @@ export class MainView extends React.Component { zoomToScale={emptyFunction} getScale={returnOne} />} - {castRes ? : null} + {castRes ? : null}
    } ; diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx index fdddf0e41..05a70f79a 100644 --- a/src/client/views/presentationview/PresentationElement.tsx +++ b/src/client/views/presentationview/PresentationElement.tsx @@ -8,7 +8,7 @@ import "./PresentationView.scss"; import { Utils, emptyFunction, returnFalse, returnOne } from "../../../Utils"; import { library } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faFile as fileSolid, faFileDownload, faLocationArrow, faArrowUp, faSearch } from '@fortawesome/free-solid-svg-icons'; +import { faFile as fileSolid, faFileDownload, faLocationArrow, faArrowUp, faSearch, faArrowRight } from '@fortawesome/free-solid-svg-icons'; import { faFile as fileRegular } from '@fortawesome/free-regular-svg-icons'; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; @@ -28,6 +28,7 @@ library.add(fileSolid); library.add(faLocationArrow); library.add(fileRegular as any); library.add(faSearch); +library.add(faArrowRight); interface PresentationElementProps { mainDocument: Doc; @@ -54,6 +55,7 @@ export enum buttonIndex { FadeAfter = 3, HideAfter = 4, Group = 5, + OpenRight = 6 } @@ -76,7 +78,7 @@ export default class PresentationElement extends React.Component { + e.stopPropagation(); + if (this.selectedButtons[buttonIndex.OpenRight]) { + this.selectedButtons[buttonIndex.OpenRight] = false; + // action maybe + } else { + this.selectedButtons[buttonIndex.OpenRight] = true; + } + this.autoSaveButtonChange(buttonIndex.OpenRight); + } + /** * Creating a drop target for drag and drop when called. */ @@ -637,7 +651,7 @@ export default class PresentationElement extends React.Component { let castedList = Cast(this.props.presButtonBackUp.selectedButtonDocs, listSpec(Doc)); - let foundSelectedButtons: boolean[] = new Array(6); + let foundSelectedButtons: boolean[] = new Array(7); //if this is the first time this doc mounts, push a doc for it to store for (let doc of castedList!) { @@ -887,6 +901,8 @@ export default class PresentationElement extends React.Component + +
    {this.renderEmbeddedInline()}
    diff --git a/src/client/views/presentationview/PresentationList.tsx b/src/client/views/presentationview/PresentationList.tsx index 2d63d41b5..2a31137af 100644 --- a/src/client/views/presentationview/PresentationList.tsx +++ b/src/client/views/presentationview/PresentationList.tsx @@ -95,7 +95,7 @@ export default class PresentationViewList extends React.Component this.props.PresElementsMappings.set(doc, e); } }} - key={doc[Id]} + key={doc[Id] + index} mainDocument={this.props.mainDocument} document={doc} index={index} diff --git a/src/client/views/presentationview/PresentationModeMenu.scss b/src/client/views/presentationview/PresentationModeMenu.scss new file mode 100644 index 000000000..336f43d20 --- /dev/null +++ b/src/client/views/presentationview/PresentationModeMenu.scss @@ -0,0 +1,30 @@ +.presMenu-cont { + position: fixed; + z-index: 10000; + height: 35px; + background: #323232; + box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); + border-radius: 0px 6px 6px 6px; + overflow: hidden; + display: flex; + + .presMenu-button { + background-color: transparent; + width: 35px; + height: 35px; + } + + .presMenu-button:hover { + background-color: #121212; + } + + .presMenu-dragger { + height: 100%; + transition: width .2s; + background-image: url("https://logodix.com/logo/1020374.png"); + background-size: 90% 100%; + background-repeat: no-repeat; + background-position: left center; + } + +} \ No newline at end of file diff --git a/src/client/views/presentationview/PresentationModeMenu.tsx b/src/client/views/presentationview/PresentationModeMenu.tsx new file mode 100644 index 000000000..b3edeb1e2 --- /dev/null +++ b/src/client/views/presentationview/PresentationModeMenu.tsx @@ -0,0 +1,92 @@ +import React = require("react"); +import { observable, action, runInAction } from "mobx"; +import "./PresentationModeMenu.scss"; +import { observer } from "mobx-react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + + +export interface PresModeMenuProps { + next: () => void; + back: () => void; + presStatus: boolean; + startOrResetPres: () => void; + closePresMode: () => void; +} + +@observer +export default class PresModeMenu extends React.Component { + + @observable private _top: number = 20; + @observable private _right: number = 0; + @observable private _opacity: number = 1; + @observable private _transition: string = "opacity 0.5s"; + @observable private _transitionDelay: string = ""; + //@observable private Pinned: boolean = false; + + + private _mainCont: React.RefObject = React.createRef(); + + @action + pointerEntered = (e: React.PointerEvent) => { + this._transition = "opacity 0.1s"; + this._transitionDelay = ""; + this._opacity = 1; + } + + @action + dragging = (e: PointerEvent) => { + this._right -= e.movementX; + this._top += e.movementY; + + e.stopPropagation(); + e.preventDefault(); + } + + dragEnd = (e: PointerEvent) => { + document.removeEventListener("pointermove", this.dragging); + document.removeEventListener("pointerup", this.dragEnd); + e.stopPropagation(); + e.preventDefault(); + } + + dragStart = (e: React.PointerEvent) => { + document.removeEventListener("pointermove", this.dragging); + document.addEventListener("pointermove", this.dragging); + document.removeEventListener("pointerup", this.dragEnd); + document.addEventListener("pointerup", this.dragEnd); + let clientRect = this._mainCont.current!.getBoundingClientRect(); + + // runInAction(() => this._left = (clientRect.width - e.nativeEvent.offsetX) + clientRect.left); + // runInAction(() => this._top = e.nativeEvent.offsetY); + + e.stopPropagation(); + e.preventDefault(); + } + + renderPlayPauseButton = () => { + if (this.props.presStatus) { + return ; + } else { + return ; + } + } + + render() { + return ( +
    + + {this.renderPlayPauseButton()} + + +
    +
    + ); + } + + + + +} \ No newline at end of file diff --git a/src/client/views/presentationview/PresentationView.scss b/src/client/views/presentationview/PresentationView.scss index ee413f379..97cbd4a24 100644 --- a/src/client/views/presentationview/PresentationView.scss +++ b/src/client/views/presentationview/PresentationView.scss @@ -66,7 +66,7 @@ padding-bottom: 3px; font-size: 25px; display: inline-block; - width: calc(100% - 160px); + width: calc(100% - 200px); } .presentation-icon { diff --git a/src/client/views/presentationview/PresentationView.tsx b/src/client/views/presentationview/PresentationView.tsx index de6c8ff82..70d86624c 100644 --- a/src/client/views/presentationview/PresentationView.tsx +++ b/src/client/views/presentationview/PresentationView.tsx @@ -4,7 +4,7 @@ import { observable, action, runInAction, reaction, autorun } from "mobx"; import "./PresentationView.scss"; import { DocumentManager } from "../../util/DocumentManager"; import { Utils } from "../../../Utils"; -import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; +import { Doc, DocListCast, DocListCastAsync, WidthSym } from "../../../new_fields/Doc"; import { listSpec } from "../../../new_fields/Schema"; import { Cast, NumCast, FieldValue, PromiseValue, StrCast, BoolCast } from "../../../new_fields/Types"; import { Id } from "../../../new_fields/FieldSymbols"; @@ -12,11 +12,12 @@ import { List } from "../../../new_fields/List"; import PresentationElement, { buttonIndex } from "./PresentationElement"; import { library } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faArrowRight, faArrowLeft, faPlay, faStop, faPlus, faTimes, faMinus, faEdit } from '@fortawesome/free-solid-svg-icons'; +import { faArrowRight, faArrowLeft, faPlay, faStop, faPlus, faTimes, faMinus, faEdit, faEye } from '@fortawesome/free-solid-svg-icons'; import { Docs } from "../../documents/Documents"; import { undoBatch, UndoManager } from "../../util/UndoManager"; import PresentationViewList from "./PresentationList"; import { ContextMenu } from "../ContextMenu"; +import PresModeMenu from "./PresentationModeMenu"; library.add(faArrowLeft); library.add(faArrowRight); @@ -26,10 +27,12 @@ library.add(faPlus); library.add(faTimes); library.add(faMinus); library.add(faEdit); +library.add(faEye); export interface PresViewProps { Documents: List; + addDocTab: (doc: Doc) => void; } const expandedWidth = 400; @@ -63,7 +66,8 @@ export class PresentationView extends React.Component { //Variable that holds reference to title input, so that new presentations get titles assigned. @observable titleInputElement: HTMLInputElement | undefined; @observable PresTitleChangeOpen: boolean = false; - @observable PresViewWidth: number | undefined; + @observable presMode: boolean = false; + //initilize class variables constructor(props: PresViewProps) { @@ -360,11 +364,21 @@ export class PresentationView extends React.Component { //checking if curDoc has navigation open let curDocButtons = this.presElementsMappings.get(curDoc)!.selected; if (curDocButtons[buttonIndex.Navigate]) { - DocumentManager.Instance.jumpToDocument(curDoc, false); + // if (curDocButtons[buttonIndex.OpenRight]) { + // DocumentManager.Instance.jumpToDocument(curDoc, false); + // } else { + // DocumentManager.Instance.jumpToDocument(curDoc, false, undefined, this.props.addDocTab); + // } + this.jumpToTabOrRight(curDocButtons, curDoc); } else if (curDocButtons[buttonIndex.Show]) { let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[fromDoc]); - //awaiting jump so that new scale can be found, since jumping is async - await DocumentManager.Instance.jumpToDocument(curDoc, true); + if (curDocButtons[buttonIndex.OpenRight]) { + //awaiting jump so that new scale can be found, since jumping is async + await DocumentManager.Instance.jumpToDocument(curDoc, true); + } else { + await DocumentManager.Instance.jumpToDocument(curDoc, false, undefined, this.props.addDocTab); + } + let newScale = DocumentManager.Instance.getScaleOfDocView(curDoc); curDoc.viewScale = newScale; @@ -377,9 +391,15 @@ export class PresentationView extends React.Component { return; } let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[fromDoc]); + let curDocButtons = this.presElementsMappings.get(docToJump)!.selected; - //awaiting jump so that new scale can be found, since jumping is async - await DocumentManager.Instance.jumpToDocument(docToJump, willZoom); + + if (curDocButtons[buttonIndex.OpenRight]) { + //awaiting jump so that new scale can be found, since jumping is async + await DocumentManager.Instance.jumpToDocument(docToJump, willZoom); + } else { + await DocumentManager.Instance.jumpToDocument(docToJump, willZoom, undefined, this.props.addDocTab); + } let newScale = DocumentManager.Instance.getScaleOfDocView(curDoc); curDoc.viewScale = newScale; //saving the scale that user was on @@ -389,6 +409,15 @@ export class PresentationView extends React.Component { } + jumpToTabOrRight = (curDocButtons: boolean[], curDoc: Doc) => { + if (curDocButtons[buttonIndex.OpenRight]) { + DocumentManager.Instance.jumpToDocument(curDoc, false); + } else { + console.log("Open in tab!!"); + DocumentManager.Instance.jumpToDocument(curDoc, false, undefined, this.props.addDocTab); + } + } + /** * Async function that supposedly return the doc that is located at given index. */ @@ -578,18 +607,35 @@ export class PresentationView extends React.Component { //The function that starts or resets presentaton functionally, depending on status flag. @action - startOrResetPres = () => { + startOrResetPres = async () => { if (this.presStatus) { this.resetPresentation(); } else { this.presStatus = true; - this.startPresentation(0); + let startIndex = await this.findStartDocument(); + this.startPresentation(startIndex); const current = NumCast(this.curPresentation.selectedDoc); - this.gotoDocument(0, current); + this.gotoDocument(startIndex, current); } this.curPresentation.presStatus = this.presStatus; } + findStartDocument = async () => { + let docAtZero = await this.getDocAtIndex(0); + if (docAtZero === undefined) { + return 0; + } + let docAtZeroPresId = StrCast(docAtZero.presentId); + + if (this.groupMappings.has(docAtZeroPresId)) { + let group = this.groupMappings.get(docAtZeroPresId)!; + let lastDoc = group[group.length - 1]; + return this.childrenDocs.indexOf(lastDoc); + } else { + return 0; + } + } + //The function that resets the presentation by removing every action done by it. It also //stops the presentaton. @action @@ -808,9 +854,9 @@ export class PresentationView extends React.Component { this.presElementsMappings.set(keyDoc, elem); } - //_downsize = 0; + _downsize = 0; onPointerDown = (e: React.PointerEvent) => { - //this._downsize = e.clientX; + this._downsize = e.clientX; document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointermove", this.onPointerMove); @@ -821,15 +867,16 @@ export class PresentationView extends React.Component { @action onPointerMove = (e: PointerEvent) => { - this.curPresentation.width = Math.max(window.innerWidth - e.clientX, 255); + this.curPresentation.width = Math.max(window.innerWidth - e.clientX, 300); } @action onPointerUp = (e: PointerEvent) => { - //this.isPointerDown = false; - // if (Math.abs(e.clientX - this._downsize) < 4) { - // if (this.flyoutWidth < 5) this.flyoutWidth = 250; - // else this.flyoutWidth = 0; - // } + if (Math.abs(e.clientX - this._downsize) < 4) { + let presWidth = NumCast(this.curPresentation.width); + if (presWidth - 300 !== 0) { + this.curPresentation.width = 0; + } + } document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); } @@ -839,17 +886,38 @@ export class PresentationView extends React.Component { e.preventDefault(); let width = NumCast(this.curPresentation.width); if (width === 0) { - this.curPresentation.width = 255; - } else { + this.curPresentation.width = 300; + } + } + @action + openPresMode = () => { + if (!this.presMode) { this.curPresentation.width = 0; + this.presMode = true; + } + } + + @action + closePresMode = () => { + if (this.presMode) { + this.presMode = false; + this.curPresentation.width = 300; + } + + } + + renderPresMode = () => { + if (this.presMode) { + return ; + } else { + return (null); } + } render() { let width = NumCast(this.curPresentation.width); - this.PresViewWidth = width; - return (
    @@ -857,6 +925,7 @@ export class PresentationView extends React.Component {
    {this.renderSelectOrPresSelection()} +
    + {this.renderPresMode()}
    ); } -- cgit v1.2.3-70-g09d2 From ae3e8e1497eb172d80b12cfc980a10486adabd8b Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 29 Jul 2019 19:22:02 -0400 Subject: fixed templates with stacking and other nested views. --- src/client/views/collections/CollectionSchemaView.tsx | 1 + src/client/views/collections/CollectionStackingView.tsx | 2 ++ src/client/views/collections/CollectionTreeView.tsx | 1 + 3 files changed, 4 insertions(+) (limited to 'src') diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 996118c00..08ab22725 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -233,6 +233,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { } render() { + Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey); // if (SelectionManager.SelectedDocuments().length > 0) console.log(StrCast(SelectionManager.SelectedDocuments()[0].Document.title)); // if (DocumentManager.Instance.getDocumentView(this.props.Document)) console.log(StrCast(this.props.Document.title), SelectionManager.IsSelected(DocumentManager.Instance.getDocumentView(this.props.Document)!)) return ( diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 6e1968774..bcf3a85d7 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -276,6 +276,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { SetValue: this.addGroup, contents: "+ ADD A GROUP" }; + Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey); + // let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx); return (
    Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before); let moveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc); -- cgit v1.2.3-70-g09d2 From 378849b4423017d8af675ceb9992e186a5407f8f Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 29 Jul 2019 19:41:48 -0400 Subject: made summaries go back to having no chrome. --- src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 1c767e012..4d659392c 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -293,14 +293,14 @@ export class MarqueeView extends React.Component d.page = -1; return d; }); + newCollection.chromeStatus = "disabled"; let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); newCollection.proto!.summaryDoc = summary; selected = [newCollection]; newCollection.x = bounds.left + bounds.width; summary.proto!.subBulletDocs = new List(selected); - //summary.proto!.maximizeLocation = "inTab"; // or "inPlace", or "onRight" summary.templates = new List([Templates.Bullet.Layout]); - let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, title: "-summary-" }); + let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" }); container.viewType = CollectionViewType.Stacking; this.props.addLiveTextDocument(container); // }); @@ -312,6 +312,7 @@ export class MarqueeView extends React.Component d.page = -1; return d; }); + newCollection.chromeStatus = "disabled"; let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" }); newCollection.proto!.summaryDoc = summary; selected = [newCollection]; -- cgit v1.2.3-70-g09d2 From 7b2bbf518779db3d5ef389f6a9f0d41aa2862999 Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Mon, 29 Jul 2019 19:54:00 -0400 Subject: trying to get tab --- src/client/views/presentationview/PresentationView.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/presentationview/PresentationView.tsx b/src/client/views/presentationview/PresentationView.tsx index cd38fc2ec..c583ae4ce 100644 --- a/src/client/views/presentationview/PresentationView.tsx +++ b/src/client/views/presentationview/PresentationView.tsx @@ -18,6 +18,7 @@ import { undoBatch, UndoManager } from "../../util/UndoManager"; import PresentationViewList from "./PresentationList"; import { ContextMenu } from "../ContextMenu"; import PresModeMenu from "./PresentationModeMenu"; +import { CollectionDockingView } from "../collections/CollectionDockingView"; library.add(faArrowLeft); library.add(faArrowRight); @@ -418,7 +419,8 @@ export class PresentationView extends React.Component { DocumentManager.Instance.jumpToDocument(curDoc, false); } else { console.log("Open in tab!!"); - DocumentManager.Instance.jumpToDocument(curDoc, false, undefined, this.props.addDocTab); + // DocumentManager.Instance.jumpToDocument(curDoc, false, undefined, this.props.addDocTab); + CollectionDockingView.Instance.AddTab(undefined, curDoc, undefined); } } -- cgit v1.2.3-70-g09d2 From c8074da503a5f4d252417c652709f7a393d7d158 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Mon, 29 Jul 2019 20:07:27 -0400 Subject: Partially fixed add tab --- src/client/views/collections/CollectionDockingView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index ab4d1aa62..3a402b6b2 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -210,14 +210,14 @@ export class CollectionDockingView extends React.Component Date: Mon, 29 Jul 2019 20:22:13 -0400 Subject: Important fix for backUp receive --- src/client/views/presentationview/PresentationList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/presentationview/PresentationList.tsx b/src/client/views/presentationview/PresentationList.tsx index 2a31137af..2d63d41b5 100644 --- a/src/client/views/presentationview/PresentationList.tsx +++ b/src/client/views/presentationview/PresentationList.tsx @@ -95,7 +95,7 @@ export default class PresentationViewList extends React.Component this.props.PresElementsMappings.set(doc, e); } }} - key={doc[Id] + index} + key={doc[Id]} mainDocument={this.props.mainDocument} document={doc} index={index} -- cgit v1.2.3-70-g09d2 From c4a8b656eff910fb6c2cd1693308f0b948547cb0 Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Mon, 29 Jul 2019 20:43:18 -0400 Subject: Opening as a tab if not present as default instead of open right --- src/client/views/MainView.tsx | 2 +- src/client/views/presentationview/PresentationView.tsx | 14 +++----------- 2 files changed, 4 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 0f9f2687b..91c8fe57c 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -277,7 +277,7 @@ export class MainView extends React.Component { zoomToScale={emptyFunction} getScale={returnOne} />} - {castRes ? : null} + {castRes ? : null}
    } ; diff --git a/src/client/views/presentationview/PresentationView.tsx b/src/client/views/presentationview/PresentationView.tsx index c583ae4ce..ea85a8c6a 100644 --- a/src/client/views/presentationview/PresentationView.tsx +++ b/src/client/views/presentationview/PresentationView.tsx @@ -33,7 +33,6 @@ library.add(faEye); export interface PresViewProps { Documents: List; - addDocTab: (doc: Doc) => void; } const expandedWidth = 400; @@ -369,11 +368,6 @@ export class PresentationView extends React.Component { //checking if curDoc has navigation open let curDocButtons = this.presElementsMappings.get(curDoc)!.selected; if (curDocButtons[buttonIndex.Navigate]) { - // if (curDocButtons[buttonIndex.OpenRight]) { - // DocumentManager.Instance.jumpToDocument(curDoc, false); - // } else { - // DocumentManager.Instance.jumpToDocument(curDoc, false, undefined, this.props.addDocTab); - // } this.jumpToTabOrRight(curDocButtons, curDoc); } else if (curDocButtons[buttonIndex.Show]) { let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[fromDoc]); @@ -381,7 +375,7 @@ export class PresentationView extends React.Component { //awaiting jump so that new scale can be found, since jumping is async await DocumentManager.Instance.jumpToDocument(curDoc, true); } else { - await DocumentManager.Instance.jumpToDocument(curDoc, false, undefined, this.props.addDocTab); + await DocumentManager.Instance.jumpToDocument(curDoc, false, undefined, doc => CollectionDockingView.Instance.AddTab(undefined, doc, undefined)); } let newScale = DocumentManager.Instance.getScaleOfDocView(curDoc); @@ -403,7 +397,7 @@ export class PresentationView extends React.Component { //awaiting jump so that new scale can be found, since jumping is async await DocumentManager.Instance.jumpToDocument(docToJump, willZoom); } else { - await DocumentManager.Instance.jumpToDocument(docToJump, willZoom, undefined, this.props.addDocTab); + await DocumentManager.Instance.jumpToDocument(docToJump, willZoom, undefined, doc => CollectionDockingView.Instance.AddTab(undefined, doc, undefined)); } let newScale = DocumentManager.Instance.getScaleOfDocView(curDoc); curDoc.viewScale = newScale; @@ -418,9 +412,7 @@ export class PresentationView extends React.Component { if (curDocButtons[buttonIndex.OpenRight]) { DocumentManager.Instance.jumpToDocument(curDoc, false); } else { - console.log("Open in tab!!"); - // DocumentManager.Instance.jumpToDocument(curDoc, false, undefined, this.props.addDocTab); - CollectionDockingView.Instance.AddTab(undefined, curDoc, undefined); + DocumentManager.Instance.jumpToDocument(curDoc, false, undefined, doc => CollectionDockingView.Instance.AddTab(undefined, doc, undefined)); } } -- cgit v1.2.3-70-g09d2 From 6954d47c252de5eadb88725963773fe0b9b0bffe Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Mon, 29 Jul 2019 20:48:02 -0400 Subject: Changed how AddTab works to work better --- .../views/collections/CollectionDockingView.tsx | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 3a402b6b2..588102f01 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -210,20 +210,22 @@ export class CollectionDockingView extends React.Component Date: Mon, 29 Jul 2019 20:50:04 -0400 Subject: not elegant, but should compute bounds for computed layouts --- .../collectionFreeForm/CollectionFreeFormView.tsx | 36 +++++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 0780320d6..d2c95532e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -65,8 +65,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return (this.props as any).ContentScaling && this.fitToBox && !this.isAnnotationOverlay ? (this.props as any).ContentScaling() : 1; } + ComputeContentBounds(boundsList: { x: number, y: number, width: number, height: number }[]) { + let bounds = boundsList.reduce((bounds, b) => { + var [sptX, sptY] = [b.x, b.y]; + let [bptX, bptY] = [sptX + b.width, sptY + b.height]; + return { + x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), + r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) + }; + }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE }); + return bounds; + } + @computed get contentBounds() { - let bounds = this.fitToBox && !this.isAnnotationOverlay ? Doc.ComputeContentBounds(DocListCast(this.props.Document.data)) : undefined; + let bounds = this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds).map(e => e.bounds!)) : undefined; return { panX: bounds ? (bounds.x + bounds.r) / 2 : this.Document.panX || 0, panY: bounds ? (bounds.y + bounds.b) / 2 : this.Document.panY || 0, @@ -446,13 +458,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } @computed.struct - get views() { + get elements() { let curPage = FieldValue(this.Document.curPage, -1); const initScript = this.Document.arrangeInit; const script = this.Document.arrangeScript; let state: any = undefined; const docs = this.childDocs; - let elements: JSX.Element[] = []; + let elements: { ele: JSX.Element, bounds?: { x: number, y: number, width: number, height: number } }[] = []; if (initScript) { const initResult = initScript.script.run({ docs, collection: this.Document }); if (initResult.success) { @@ -460,23 +472,26 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const { state: scriptState, views } = result; state = scriptState; if (Array.isArray(views)) { - elements = views.reduce((prev, ele) => { + elements = views.reduce((prev, ele) => { const jsx = this.viewDefToJSX(ele); - jsx && prev.push(jsx); + jsx && prev.push({ ele: jsx }); return prev; }, elements); } } } - let docviews = docs.reduce((prev, doc) => { - if (!(doc instanceof Doc)) return prev; + let docviews = docs.filter(doc => doc instanceof Doc).reduce((prev, doc) => { var page = NumCast(doc.page, -1); + let bounds: { x?: number, y?: number, width?: number, height?: number }; if ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1) { let minim = BoolCast(doc.isMinimized); if (minim === undefined || !minim) { const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) : {}; state = pos.state === undefined ? state : pos.state; - prev.push(); + prev.push({ + ele: , + bounds: (pos.x !== undefined && pos.y !== undefined && pos.width !== undefined && pos.height !== undefined) ? { x: pos.x, y: pos.y, width: pos.width, height: pos.height } : undefined + }); } } return prev; @@ -487,6 +502,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return docviews; } + @computed.struct + get views() { + return this.elements.map(ele => ele.ele); + } + @action onCursorMove = (e: React.PointerEvent) => { super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY)); -- cgit v1.2.3-70-g09d2 From e042f916375fbe4f23288ece0dcec5b61ef2fbed Mon Sep 17 00:00:00 2001 From: yipstanley Date: Mon, 29 Jul 2019 21:20:51 -0400 Subject: annotations now open the target context --- src/client/util/DragManager.ts | 1 + .../collectionFreeForm/CollectionFreeFormView.tsx | 2 ++ src/client/views/nodes/DocumentView.tsx | 4 +++- src/client/views/pdf/Annotation.tsx | 12 +++++++++--- src/client/views/pdf/Page.tsx | 2 +- 5 files changed, 16 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 9221ef274..abcc3a4e1 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -216,6 +216,7 @@ export namespace DragManager { this.annotationDocument = annotationDoc; this.xOffset = this.yOffset = 0; } + targetContext: Doc | undefined; dragDocument: Doc; annotationDocument: Doc; dropDocument: Doc; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 0780320d6..179a65739 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -151,6 +151,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let dropY = NumCast(de.data.dropDocument.y); dragDoc.x = x + NumCast(dragDoc.x) - dropX; dragDoc.y = y + NumCast(dragDoc.y) - dropY; + de.data.targetContext = this.props.Document; + dragDoc.targetContext = this.props.Document; this.bringToFront(dragDoc); } } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index f101222ae..b37485e62 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -439,7 +439,9 @@ export class DocumentView extends DocComponent(Docu e.stopPropagation(); let annotationDoc = de.data.annotationDocument; annotationDoc.linkedToDoc = true; + de.data.targetContext = this.props.ContainingCollectionView!.props.Document; let targetDoc = this.props.Document; + targetDoc.targetContext = de.data.targetContext; let annotations = await DocListCastAsync(annotationDoc.annotations); if (annotations) { annotations.forEach(anno => { @@ -448,7 +450,7 @@ export class DocumentView extends DocComponent(Docu } let pdfDoc = await Cast(annotationDoc.pdfDoc, Doc); if (pdfDoc) { - DocUtils.MakeLink(annotationDoc, targetDoc, undefined, `Annotation from ${StrCast(pdfDoc.title)}`, "", StrCast(pdfDoc.title)); + DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Annotation from ${StrCast(pdfDoc.title)}`, "", StrCast(pdfDoc.title)); } } if (de.data instanceof DragManager.LinkDragData) { diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index ed7081b1d..e8d1da94b 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -9,6 +9,8 @@ import { List } from "../../../new_fields/List"; import PDFMenu from "./PDFMenu"; import { DocumentManager } from "../../util/DocumentManager"; import { PresentationView } from "../presentationview/PresentationView"; +import { LinkManager } from "../../util/LinkManager"; +import { CollectionDockingView } from "../collections/CollectionDockingView"; interface IAnnotationProps { anno: Doc; @@ -110,11 +112,15 @@ class RegionAnnotation extends React.Component { } @action - onPointerDown = (e: React.PointerEvent) => { + onPointerDown = async (e: React.PointerEvent) => { if (e.button === 0) { - let targetDoc = Cast(this.props.document.target, Doc, null); + let targetDoc = await Cast(this.props.document.target, Doc); if (targetDoc) { - DocumentManager.Instance.jumpToDocument(targetDoc, false); + let context = await Cast(targetDoc.targetContext, Doc); + if (context) { + DocumentManager.Instance.jumpToDocument(targetDoc, false, undefined, + ((doc) => this.props.parent.props.parent.props.addDocTab(context, context.proto, e.ctrlKey ? "onRight" : "inTab"))); + } } } if (e.button === 2) { diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index c205617b4..c5b2a1dda 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -175,7 +175,7 @@ export default class Page extends React.Component { } let pdfDoc = await Cast(annotationDoc.pdfDoc, Doc); if (pdfDoc) { - DocUtils.MakeLink(annotationDoc, targetDoc, undefined, `Annotation from ${StrCast(pdfDoc.title)}`, "", StrCast(pdfDoc.title)); + DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(pdfDoc.title)}`, "", StrCast(pdfDoc.title)); } } } -- cgit v1.2.3-70-g09d2 From 1723fed27c615ced10b5bf9648f86f923cf61a42 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 29 Jul 2019 21:23:26 -0400 Subject: added button titles. made summary's autoHeight --- src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 2 ++ src/client/views/nodes/ButtonBox.tsx | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 4d659392c..67bed284f 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -302,6 +302,7 @@ export class MarqueeView extends React.Component summary.templates = new List([Templates.Bullet.Layout]); let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" }); container.viewType = CollectionViewType.Stacking; + container.autoHeight = true; this.props.addLiveTextDocument(container); // }); } else if (e.key === "S") { @@ -320,6 +321,7 @@ export class MarqueeView extends React.Component //this.props.addDocument(newCollection, false); summary.proto!.summarizedDocs = new List(selected); summary.proto!.maximizeLocation = "inTab"; // or "inPlace", or "onRight" + summary.autoHeight = true; this.props.addLiveTextDocument(summary); } diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index d2c23fdab..e2c559c9a 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -70,7 +70,7 @@ export class ButtonBox extends DocComponent(Butt render() { return (
    - +
    ); } -- cgit v1.2.3-70-g09d2 From 8f8e942a648f36e480af18457a86c413e2cbdf0e Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 29 Jul 2019 21:43:53 -0400 Subject: fixed pasted pdf links to contain pdf context. --- src/client/views/nodes/FormattedTextBox.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 0f51ca2ce..fc0cc98aa 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -329,7 +329,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe fieldExtDoc.annotations = new List(targetAnnotations); } - let link = DocUtils.MakeLink(this.props.Document, region); + let link = DocUtils.MakeLink(this.props.Document, region, doc); if (link) { cbe.clipboardData!.setData("dash/linkDoc", link[Id]); linkId = link[Id]; @@ -443,6 +443,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } if (targetContext) { DocumentManager.Instance.jumpToDocument(targetContext, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); + } else if (jumpToDoc) { + DocumentManager.Instance.jumpToDocument(jumpToDoc, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); + } } }); -- cgit v1.2.3-70-g09d2 From f6583e4b504a4a60f4aefc68c7a60692a92869dd Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Mon, 29 Jul 2019 22:33:35 -0400 Subject: Included extra ui elements in bounds calculation --- .../collectionFreeForm/CollectionFreeFormView.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2b2f0bcd5..8dac785e1 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -440,7 +440,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return result.result === undefined ? {} : result.result; } - private viewDefToJSX(viewDef: any): JSX.Element | undefined { + private viewDefToJSX(viewDef: any): { ele: JSX.Element, bounds?: { x: number, y: number, width: number, height: number } } | undefined { if (viewDef.type === "text") { const text = Cast(viewDef.text, "string"); const x = Cast(viewDef.x, "number"); @@ -448,14 +448,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const width = Cast(viewDef.width, "number"); const height = Cast(viewDef.height, "number"); const fontSize = Cast(viewDef.fontSize, "number"); - if ([text, x, y].some(val => val === undefined)) { + if ([text, x, y, width, height].some(val => val === undefined)) { return undefined; } - return
    {text}
    ; + return { + ele:
    {text}
    , bounds: { x: x!, y: y!, width: width!, height: height! } + }; } } @@ -474,9 +476,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const { state: scriptState, views } = result; state = scriptState; if (Array.isArray(views)) { - elements = views.reduce((prev, ele) => { + elements = views.reduce((prev, ele) => { const jsx = this.viewDefToJSX(ele); - jsx && prev.push({ ele: jsx }); + jsx && prev.push(jsx); return prev; }, elements); } -- cgit v1.2.3-70-g09d2 From ba2c6a9f3cd6d68bcf28ff3dc880b4f8b4635747 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Mon, 29 Jul 2019 23:48:35 -0400 Subject: styling changes to stacking --- src/client/views/collections/CollectionStackingView.scss | 5 +++-- .../views/collections/CollectionStackingViewFieldColumn.tsx | 8 ++++++-- src/client/views/pdf/Annotation.tsx | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss index 2bb36c825..0cb01dc9d 100644 --- a/src/client/views/collections/CollectionStackingView.scss +++ b/src/client/views/collections/CollectionStackingView.scss @@ -135,9 +135,9 @@ .collectionStackingView-addDocumentButton, .collectionStackingView-addGroupButton { - display: inline-block; - margin: 0 5px; + display: flex; overflow: hidden; + margin: auto; width: 90%; color: lightgrey; overflow: ellipses; @@ -146,6 +146,7 @@ .editableView-container-editing { color: grey; padding: 10px; + width: 100%; } .editableView-input:hover, diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index b5dbc1a22..d8bed7e88 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -239,7 +239,11 @@ export class CollectionStackingViewFieldColumn extends React.Component + style={{ + width: (style.columnWidth) / + ((uniqueHeadings.length + + (this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'disabled' ? 1 : 0)) || 1) + }}> {/* the default bucket (no key value) has a tooltip that describes what it is. Further, it does not have a color and cannot be deleted. */}
    { let context = await Cast(targetDoc.targetContext, Doc); if (context) { DocumentManager.Instance.jumpToDocument(targetDoc, false, undefined, - ((doc) => this.props.parent.props.parent.props.addDocTab(context, context.proto, e.ctrlKey ? "onRight" : "inTab"))); + ((doc) => this.props.parent.props.parent.props.addDocTab(context!, context!.proto, e.ctrlKey ? "onRight" : "inTab"))); } } } -- cgit v1.2.3-70-g09d2 From 2468ab85d642b954e47a560e2d0739dba9674f88 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Tue, 30 Jul 2019 00:02:21 -0400 Subject: put back contains/not contains colors --- src/client/views/collections/KeyRestrictionRow.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/collections/KeyRestrictionRow.tsx b/src/client/views/collections/KeyRestrictionRow.tsx index 9c3c9c07c..9baa250a6 100644 --- a/src/client/views/collections/KeyRestrictionRow.tsx +++ b/src/client/views/collections/KeyRestrictionRow.tsx @@ -29,6 +29,7 @@ export default class KeyRestrictionRow extends React.Component runInAction(() => this._key = e.target.value)} placeholder="KEY" /> -- cgit v1.2.3-70-g09d2 From 14455e7be675ed3cb04fa645df79845b2aecd0d4 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Tue, 30 Jul 2019 00:09:01 -0400 Subject: Started implementing downloading "exported" version of documents --- src/server/database.ts | 23 +++++++++++++++++ src/server/index.ts | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) (limited to 'src') diff --git a/src/server/database.ts b/src/server/database.ts index 7f5331998..acb6ce751 100644 --- a/src/server/database.ts +++ b/src/server/database.ts @@ -126,6 +126,29 @@ export class Database { } } + public async visit(ids: string[], fn: (result: any) => string[], collectionName = "newDocuments") { + if (this.db) { + const visited = new Set(); + while (ids.length) { + const count = Math.min(ids.length, 1000); + const index = ids.length - count; + const fetchIds = ids.splice(index, count).filter(id => !visited.has(id)); + if (!fetchIds.length) { + continue; + } + const docs = await new Promise<{ [key: string]: any }[]>(res => Database.Instance.getDocuments(fetchIds, res, "newDocuments")); + for (const doc of docs) { + const id = doc.id; + visited.add(id); + ids.push(...fn(doc)); + } + } + + } else { + this.onConnect.push(() => this.visit(ids, fn, collectionName)); + } + } + public query(query: { [key: string]: any }, projection?: { [key: string]: 0 | 1 }, collectionName = "newDocuments"): Promise { if (this.db) { let cursor = this.db.collection(collectionName).find(query); diff --git a/src/server/index.ts b/src/server/index.ts index adf218be6..230c574cf 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -177,6 +177,75 @@ function msToTime(duration: number) { return hoursS + ":" + minutesS + ":" + secondsS + "." + milliseconds; } +app.get("/serializeDoc/:docId", async (req, res) => { + const files: { [name: string]: string[] } = {}; + const docs: { [id: string]: any } = {}; + const fn = (doc: any): string[] => { + const ids: string[] = []; + for (const key in doc) { + if (!doc.hasOwnProperty(key)) { + continue; + } + const field = doc[key]; + if (field === undefined || field === null) { + continue; + } + + if (field.__type === "proxy" || field.__type === "prefetch_proxy") { + ids.push(field.fieldId); + } else if (field.__type === "list") { + ids.push(...fn(field.fields)); + } else if (typeof field === "string") { + const re = /"(?:dataD|d)ocumentId"\s*:\s*"([\w\-]*)"/g; + let match: string[] | null; + while ((match = re.exec(field)) !== null) { + ids.push(match[1]); + } + } else if (field.__type === "RichTextField") { + const re = /"href"\s*:\s*"(.*?)"/g; + let match: string[] | null; + while ((match = re.exec(field.Data)) !== null) { + const urlString = match[1]; + const split = new URL(urlString).pathname.split("doc/"); + if (split.length > 1) { + ids.push(split[split.length - 1]); + } + } + const re2 = /"src"\s*:\s*"(.*?)"/g; + while ((match = re2.exec(field.Data)) !== null) { + const urlString = match[1]; + const pathname = new URL(urlString).pathname; + const ext = path.extname(pathname); + const fileName = path.basename(pathname, ext); + let exts = files[fileName]; + if (!exts) { + files[fileName] = exts = []; + } + exts.push(ext); + } + } else if (["audio", "image", "video", "pdf", "web"].includes(field.__type)) { + const url = new URL(field.url); + const pathname = url.pathname; + const ext = path.extname(pathname); + const fileName = path.basename(pathname, ext); + let exts = files[fileName]; + if (!exts) { + files[fileName] = exts = []; + } + exts.push(ext); + } + } + + docs[doc.id] = doc; + return ids; + }; + Database.Instance.visit([req.params.docId], fn); +}); + +app.get("/downloadId/:docId", (req, res) => { + res.download(`/serializeDoc/${req.params.docId}`, `DocumentExport.zip`); +}); + app.get("/whosOnline", (req, res) => { let users: any = { active: {}, inactive: {} }; const now = Date.now(); -- cgit v1.2.3-70-g09d2 From 5248a770123e312e1684d7147ecb7118dd6ef1e7 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 30 Jul 2019 01:24:37 -0400 Subject: tag generation and faces on extensionDoc --- src/client/cognitive_services/CognitiveServices.ts | 40 ++++-------------- src/client/views/nodes/FaceRectangles.tsx | 2 +- src/client/views/nodes/ImageBox.tsx | 49 +++++++++++++++++++--- 3 files changed, 54 insertions(+), 37 deletions(-) (limited to 'src') diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index bbc438a9b..076907f09 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -42,7 +42,7 @@ export enum Confidence { */ export namespace CognitiveServices { - const executeQuery = async (service: Service, manager: APIManager, data: D): Promise> => { + const ExecuteQuery = async (service: Service, manager: APIManager, data: D): Promise> => { return fetch(Utils.prepend(`${RouteStore.cognitiveServices}/${service}`)).then(async response => { let apiKey = await response.text(); if (!apiKey) { @@ -103,15 +103,15 @@ export namespace CognitiveServices { return request.post(options); }, - analyzer: async (target: Doc, keys: string[], service: Service, converter: Converter) => { + analyzer: async (target: Doc, keys: string[], url: string, service: Service, converter: Converter) => { let batch = UndoManager.StartBatch("Image Analysis"); - let imageData = Cast(target.data, ImageField); + let storageKey = keys[0]; - if (!imageData || await Cast(target[storageKey], Doc)) { + if (!url || await Cast(target[storageKey], Doc)) { return; } let toStore: any; - let results = await executeQuery(service, Manager, imageData.url.href); + let results = await ExecuteQuery(service, Manager, url); if (!results) { toStore = "Cognitive Services could not process the given image URL."; } else { @@ -122,6 +122,7 @@ export namespace CognitiveServices { } } target[storageKey] = toStore; + batch.end(); } @@ -129,31 +130,6 @@ export namespace CognitiveServices { export type Face = { faceAttributes: any, faceId: string, faceRectangle: Rectangle }; - export const generateMetadata = async (target: Doc, threshold: Confidence = Confidence.Excellent) => { - let converter = (results: any) => { - let tagDoc = new Doc; - results.tags.map((tag: Tag) => { - let sanitized = tag.name.replace(" ", "_"); - let script = `return (${tag.confidence} >= this.confidence) ? ${tag.confidence} : "${ComputedField.undefined}"`; - let computed = CompileScript(script, { params: { this: "Doc" } }); - computed.compiled && (tagDoc[sanitized] = new ComputedField(computed)); - }); - tagDoc.title = "Generated Tags"; - tagDoc.confidence = threshold; - return tagDoc; - }; - Manager.analyzer(target, ["generatedTags"], Service.ComputerVision, converter); - }; - - export const extractFaces = async (target: Doc) => { - let converter = (results: any) => { - let faceDocs = new List(); - results.map((face: Face) => faceDocs.push(Docs.Get.DocumentHierarchyFromJson(face, `Face: ${face.faceId}`)!)); - return faceDocs; - }; - Manager.analyzer(target, ["faces"], Service.Face, converter); - }; - } export namespace Inking { @@ -209,7 +185,8 @@ export namespace CognitiveServices { analyzer: async (target: Doc, keys: string[], inkData: InkData) => { let batch = UndoManager.StartBatch("Ink Analysis"); - let results = await executeQuery(Service.Handwriting, Manager, inkData); + + let results = await ExecuteQuery(Service.Handwriting, Manager, inkData); if (results) { results.recognitionUnits && (results = results.recognitionUnits); target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Ink Analysis"); @@ -217,6 +194,7 @@ export namespace CognitiveServices { let individualWords = recognizedText.filter((text: string) => text && text.split(" ").length === 1); target[keys[1]] = individualWords.join(" "); } + batch.end(); } diff --git a/src/client/views/nodes/FaceRectangles.tsx b/src/client/views/nodes/FaceRectangles.tsx index 3570531b2..acf1aced3 100644 --- a/src/client/views/nodes/FaceRectangles.tsx +++ b/src/client/views/nodes/FaceRectangles.tsx @@ -20,7 +20,7 @@ export interface RectangleTemplate { export default class FaceRectangles extends React.Component { render() { - let faces = DocListCast(Doc.GetProto(this.props.document).faces); + let faces = DocListCast(this.props.document.faces); let templates: RectangleTemplate[] = faces.map(faceDoc => { let rectangle = Cast(faceDoc.faceRectangle, Doc) as Doc; let style = { diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 29a76b0c8..5554475ec 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -25,9 +25,12 @@ import { Docs, DocumentType } from '../../documents/Documents'; import { DocServer } from '../../DocServer'; import { Font } from '@react-pdf/renderer'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { CognitiveServices } from '../../cognitive_services/CognitiveServices'; +import { CognitiveServices, Service, Tag, Confidence } from '../../cognitive_services/CognitiveServices'; import FaceRectangles from './FaceRectangles'; import { faEye } from '@fortawesome/free-regular-svg-icons'; +import { ComputedField } from '../../../new_fields/ScriptField'; +import { CompileScript } from '../../util/Scripting'; +import { thisExpression } from 'babel-types'; var requestImageSize = require('../../util/request-image-size'); var path = require('path'); const { Howl } = require('howler'); @@ -226,20 +229,56 @@ export class ImageBox extends DocComponent(ImageD funcs.push({ description: "Rotate", event: this.rotate, icon: "expand-arrows-alt" }); let modes: ContextMenuProps[] = []; - let dataDoc = Doc.GetProto(this.props.Document); - modes.push({ description: "Generate Tags", event: () => CognitiveServices.Image.generateMetadata(dataDoc), icon: "tag" }); - modes.push({ description: "Find Faces", event: () => CognitiveServices.Image.extractFaces(dataDoc), icon: "camera" }); + modes.push({ description: "Generate Tags", event: this.generateMetadata, icon: "tag" }); + modes.push({ description: "Find Faces", event: this.extractFaces, icon: "camera" }); ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: funcs, icon: "asterisk" }); ContextMenu.Instance.addItem({ description: "Analyze...", subitems: modes, icon: "eye" }); } } + extractFaces = () => { + let converter = (results: any) => { + let faceDocs = new List(); + results.map((face: CognitiveServices.Image.Face) => faceDocs.push(Docs.Get.DocumentHierarchyFromJson(face, `Face: ${face.faceId}`)!)); + return faceDocs; + }; + CognitiveServices.Image.Manager.analyzer(this.extensionDoc, ["faces"], this.url, Service.Face, converter); + } + + generateMetadata = (threshold: Confidence = Confidence.Excellent) => { + let converter = (results: any) => { + let tagDoc = new Doc; + let tagsList = new List(); + results.tags.map((tag: Tag) => { + tagsList.push(tag.name); + let sanitized = tag.name.replace(" ", "_"); + let script = `return (${tag.confidence} >= this.confidence) ? ${tag.confidence} : "${ComputedField.undefined}"`; + let computed = CompileScript(script, { params: { this: "Doc" } }); + computed.compiled && (tagDoc[sanitized] = new ComputedField(computed)); + }); + this.extensionDoc.generatedTags = tagsList; + tagDoc.title = "Generated Tags Doc"; + tagDoc.confidence = threshold; + return tagDoc; + }; + CognitiveServices.Image.Manager.analyzer(this.extensionDoc, ["generatedTagsDoc"], this.url, Service.ComputerVision, converter); + } + @action onDotDown(index: number) { this.Document.curPage = index; } + @computed get fieldExtensionDoc() { + return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); + } + + @computed private get url() { + let data = Cast(Doc.GetProto(this.props.Document).data, ImageField); + return data ? data.url.href : undefined; + } + dots(paths: string[]) { let nativeWidth = FieldValue(this.Document.nativeWidth, 1); let dist = Math.min(nativeWidth / paths.length, 40); @@ -394,7 +433,7 @@ export class ImageBox extends DocComponent(ImageD style={{ color: [DocListCast(this.extensionDoc.audioAnnotations).length ? "blue" : "gray", "green", "red"][this._audioState] }} icon={faFileAudio} size="sm" />
    {/* {this.lightbox(paths)} */} - +
    ); } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 4f0ea3428e1e98d42b356c00f276fe74a6c4a672 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 30 Jul 2019 09:37:38 -0400 Subject: removed TreeView export --- src/client/views/collections/CollectionTreeView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 6cc065342..b1e6eada0 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -66,7 +66,7 @@ library.add(faPlus, faMinus); /** * Component that takes in a document prop and a boolean whether it's collapsed or not. */ -export class TreeView extends React.Component { +class TreeView extends React.Component { private _header?: React.RefObject = React.createRef(); private _treedropDisposer?: DragManager.DragDropDisposer; private _dref = React.createRef(); -- cgit v1.2.3-70-g09d2 From 5b455e2aaf119c7db1fe9ef22d71a3accf55a8e2 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 30 Jul 2019 10:27:42 -0400 Subject: tweaks. --- src/client/apis/youtube/YoutubeBox.tsx | 22 +++++++++------------- src/client/views/collections/CollectionView.tsx | 2 +- .../views/collections/CollectionViewChromes.tsx | 1 + 3 files changed, 11 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index 019d191bc..7f9a3ad70 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -1,20 +1,16 @@ -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 { action, observable, runInAction } from 'mobx'; 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 { Doc, DocListCastAsync } from "../../../new_fields/Doc"; +import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; 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, DocListCastAsync } from "../../../new_fields/Doc"; -import { listSpec } from "../../../new_fields/Schema"; -import { List } from "../../../new_fields/List"; +import { DocumentDecorations } from "../../views/DocumentDecorations"; +import { InkingControl } from "../../views/InkingControl"; +import { FieldView, FieldViewProps } from "../../views/nodes/FieldView"; +import "../../views/nodes/WebBox.scss"; +import "./YoutubeBox.scss"; +import React = require("react"); interface VideoTemplate { thumbnailUrl: string; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index b7ac8768f..212cc5477 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -85,7 +85,7 @@ export class CollectionView extends React.Component { } else { return [ - (), + (), this.SubViewHelper(type, renderProps) ]; } diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 2bffe3cc0..38aafd3cc 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -208,6 +208,7 @@ export class CollectionViewBaseChrome extends React.Component { }} onPointerDown={this.openViewSpecs} />
    Date: Tue, 30 Jul 2019 11:00:57 -0400 Subject: clean up --- src/client/cognitive_services/CognitiveServices.ts | 8 +++----- src/client/views/MainView.tsx | 1 - src/server/index.ts | 4 +--- 3 files changed, 4 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index 720892b61..b9c718cfa 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -5,8 +5,6 @@ import { Docs } from "../documents/Documents"; import { RouteStore } from "../../server/RouteStore"; import { Utils } from "../../Utils"; import { InkData } from "../../new_fields/InkField"; -import "microsoft-cognitiveservices-speech-sdk"; -import "fs"; import { UndoManager } from "../util/UndoManager"; type APIManager = { converter: BodyConverter, requester: RequestExecutor, analyzer: AnalysisApplier }; @@ -27,7 +25,7 @@ export type Rectangle = { top: number, left: number, width: number, height: numb export enum Service { ComputerVision = "vision", Face = "face", - Handwriting = "handwriting", + Handwriting = "handwriting" } export enum Confidence { @@ -221,7 +219,7 @@ export namespace CognitiveServices { export namespace Transcription { - export const analyzer = (doc: Doc, keys: string[]) => { + export const analyzer = (target: Doc, keys: string[]) => { let { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow; let recognizer = new webkitSpeechRecognition(); recognizer.interimResults = true; @@ -229,7 +227,7 @@ export namespace CognitiveServices { recognizer.onresult = (e: any) => { let result = e.results[0][0]; - doc[keys[0]] = result.transcript; + target[keys[0]] = result.transcript; }; recognizer.start(); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index ccf8f571e..91c8fe57c 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -38,7 +38,6 @@ import { PreviewCursor } from './PreviewCursor'; import { FilterBox } from './search/FilterBox'; import { CollectionTreeView } from './collections/CollectionTreeView'; import { ClientUtils } from '../util/ClientUtils'; -import { CognitiveServices } from '../cognitive_services/CognitiveServices'; import { SchemaHeaderField, RandomPastel } from '../../new_fields/SchemaHeaderField'; @observer diff --git a/src/server/index.ts b/src/server/index.ts index f4bbd4423..adf218be6 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -40,8 +40,6 @@ import { Search } from './Search'; import { debug } from 'util'; import _ = require('lodash'); import { Response } from 'express-serve-static-core'; -import { AudioInputStream, AudioConfig, SpeechConfig, SpeechRecognizer, SpeechRecognitionResult } from 'microsoft-cognitiveservices-speech-sdk'; -import { Opt } from '../new_fields/Doc'; const MongoStore = require('connect-mongo')(session); const mongoose = require('mongoose'); const probe = require("probe-image-size"); @@ -299,7 +297,7 @@ addSecureRoute( const ServicesApiKeyMap = new Map([ ["face", process.env.FACE], ["vision", process.env.VISION], - ["handwriting", process.env.HANDWRITING], + ["handwriting", process.env.HANDWRITING] ]); addSecureRoute(Method.GET, (user, res, req) => { -- cgit v1.2.3-70-g09d2 From 3468440a4297a105cf4138892c3d163ca9a0c83c Mon Sep 17 00:00:00 2001 From: Mohammad Amoush Date: Tue, 30 Jul 2019 11:30:48 -0400 Subject: CleanUp and Documentation --- .../views/presentationview/PresentationElement.tsx | 37 ++++++----- .../views/presentationview/PresentationList.tsx | 2 - .../presentationview/PresentationModeMenu.tsx | 34 ++++++---- .../views/presentationview/PresentationView.tsx | 76 ++++++++++++++-------- 4 files changed, 92 insertions(+), 57 deletions(-) (limited to 'src') diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx index ccc3a72a9..11f3eb846 100644 --- a/src/client/views/presentationview/PresentationElement.tsx +++ b/src/client/views/presentationview/PresentationElement.tsx @@ -12,12 +12,8 @@ import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { Utils, returnFalse, emptyFunction, returnOne } from "../../../Utils"; import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager"; import { SelectionManager } from "../../util/SelectionManager"; -import { indexOf } from "typescript-collections/dist/lib/arrays"; -import { map } from "bluebird"; import { ContextMenu } from "../ContextMenu"; -import { DocumentContentsView } from "../nodes/DocumentContentsView"; import { Transform } from "../../util/Transform"; -import { FieldView } from "../nodes/FieldView"; import { DocumentView } from "../nodes/DocumentView"; import { DocumentType } from "../../documents/Documents"; import React = require("react"); @@ -73,9 +69,6 @@ export default class PresentationElement extends React.Component { //get the list that stores docs that keep track of buttons @@ -404,6 +400,10 @@ export default class PresentationElement extends React.Component { e.stopPropagation(); @@ -671,7 +671,6 @@ export default class PresentationElement extends React.Component { - // this.props.document.libraryBrush = true; if (e.buttons === 1 && SelectionManager.GetIsDragging()) { let selected = NumCast(this.props.mainDocument.selectedDoc, 0); @@ -688,7 +687,6 @@ export default class PresentationElement extends React.Component { - // this.props.document.libraryBrush = false; //to get currently selected presentation doc let selected = NumCast(this.props.mainDocument.selectedDoc, 0); @@ -787,15 +785,23 @@ export default class PresentationElement extends React.Component) => { e.preventDefault(); e.stopPropagation(); @@ -803,20 +809,19 @@ export default class PresentationElement extends React.Component { if (!this.embedInline) { return (null); } - // return
      - // {TreeView.GetChildElements([this.props.document], "", new Doc(), undefined, "", (doc: Doc, relativeTo?: Doc, before?: boolean) => false, this.props.removeDocByRef, this.move, - // StrCast(this.props.document.dropAction) as dropActionType, (doc: Doc, dataDoc: Doc | undefined, where: string) => { }, Transform.Identity, () => ({ translateX: 0, translateY: 0 }), () => false, () => 400, 7)} - //
    ; let propDocWidth = NumCast(this.props.document.nativeWidth); let propDocHeight = NumCast(this.props.document.nativeHeight); let scale = () => { let newScale = 175 / NumCast(this.props.document.nativeWidth, 175); - console.log("New Scale: ", newScale); return newScale; }; return ( @@ -836,7 +841,7 @@ export default class PresentationElement extends React.Component 350} - PanelHeight={() => 100} + PanelHeight={() => 90} focus={emptyFunction} selectOnLoad={false} parentActive={returnFalse} diff --git a/src/client/views/presentationview/PresentationList.tsx b/src/client/views/presentationview/PresentationList.tsx index 2d63d41b5..e853c4070 100644 --- a/src/client/views/presentationview/PresentationList.tsx +++ b/src/client/views/presentationview/PresentationList.tsx @@ -7,8 +7,6 @@ import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; import { NumCast, StrCast } from "../../../new_fields/Types"; import { Id } from "../../../new_fields/FieldSymbols"; import PresentationElement, { buttonIndex } from "./PresentationElement"; -import { DragManager } from "../../util/DragManager"; -import { CollectionDockingView } from "../collections/CollectionDockingView"; import "../../../new_fields/Doc"; diff --git a/src/client/views/presentationview/PresentationModeMenu.tsx b/src/client/views/presentationview/PresentationModeMenu.tsx index b3edeb1e2..4de8da587 100644 --- a/src/client/views/presentationview/PresentationModeMenu.tsx +++ b/src/client/views/presentationview/PresentationModeMenu.tsx @@ -13,6 +13,10 @@ export interface PresModeMenuProps { closePresMode: () => void; } +/** + * This class is responsible for modeling of the Presentation Mode Menu. The menu allows + * user to navigate through presentation elements, and start/stop the presentation. + */ @observer export default class PresModeMenu extends React.Component { @@ -21,18 +25,14 @@ export default class PresModeMenu extends React.Component { @observable private _opacity: number = 1; @observable private _transition: string = "opacity 0.5s"; @observable private _transitionDelay: string = ""; - //@observable private Pinned: boolean = false; private _mainCont: React.RefObject = React.createRef(); - @action - pointerEntered = (e: React.PointerEvent) => { - this._transition = "opacity 0.1s"; - this._transitionDelay = ""; - this._opacity = 1; - } - + /** + * The function that changes the coordinates of the menu, depending on the + * movement of the mouse when it's being dragged. + */ @action dragging = (e: PointerEvent) => { this._right -= e.movementX; @@ -42,6 +42,10 @@ export default class PresModeMenu extends React.Component { e.preventDefault(); } + /** + * The function that removes the event listeners that are responsible for + * dragging of the menu. + */ dragEnd = (e: PointerEvent) => { document.removeEventListener("pointermove", this.dragging); document.removeEventListener("pointerup", this.dragEnd); @@ -49,20 +53,24 @@ export default class PresModeMenu extends React.Component { e.preventDefault(); } + /** + * The function that starts the dragging of the presentation mode menu. When + * the lines on further right are clicked on. + */ dragStart = (e: React.PointerEvent) => { document.removeEventListener("pointermove", this.dragging); document.addEventListener("pointermove", this.dragging); document.removeEventListener("pointerup", this.dragEnd); document.addEventListener("pointerup", this.dragEnd); - let clientRect = this._mainCont.current!.getBoundingClientRect(); - - // runInAction(() => this._left = (clientRect.width - e.nativeEvent.offsetX) + clientRect.left); - // runInAction(() => this._top = e.nativeEvent.offsetY); e.stopPropagation(); e.preventDefault(); } + /** + * The function that is responsible for rendering the play or pause button, depending on the + * status of the presentation. + */ renderPlayPauseButton = () => { if (this.props.presStatus) { return ; @@ -73,7 +81,7 @@ export default class PresModeMenu extends React.Component { render() { return ( -
    {this.renderPlayPauseButton()} diff --git a/src/client/views/presentationview/PresentationView.tsx b/src/client/views/presentationview/PresentationView.tsx index ea85a8c6a..4fe9d3a1b 100644 --- a/src/client/views/presentationview/PresentationView.tsx +++ b/src/client/views/presentationview/PresentationView.tsx @@ -16,7 +16,6 @@ import { faArrowRight, faArrowLeft, faPlay, faStop, faPlus, faTimes, faMinus, fa import { Docs } from "../../documents/Documents"; import { undoBatch, UndoManager } from "../../util/UndoManager"; import PresentationViewList from "./PresentationList"; -import { ContextMenu } from "../ContextMenu"; import PresModeMenu from "./PresentationModeMenu"; import { CollectionDockingView } from "../collections/CollectionDockingView"; @@ -36,6 +35,7 @@ export interface PresViewProps { } const expandedWidth = 400; +const presMinWidth = 300; @observer export class PresentationView extends React.Component { @@ -408,6 +408,10 @@ export class PresentationView extends React.Component { } + /** + * This function checks if right option is clicked on a presentation element, if not it does open it as a tab + * with help of CollectionDockingView. + */ jumpToTabOrRight = (curDocButtons: boolean[], curDoc: Doc) => { if (curDocButtons[buttonIndex.OpenRight]) { DocumentManager.Instance.jumpToDocument(curDoc, false); @@ -460,22 +464,6 @@ export class PresentationView extends React.Component { } } - //removing it from the backUp of selected Buttons - // let castedList = Cast(this.presButtonBackUp.selectedButtonDocs, listSpec(Doc)); - // if (castedList) { - // castedList.forEach(async (doc, indexOfDoc) => { - // let curDoc = await doc; - // let curDocId = StrCast(curDoc.docId); - // if (curDocId === removedDoc[Id]) { - // if (castedList) { - // castedList.splice(indexOfDoc, 1); - // return; - // } - // } - // }); - - // } - //removing it from the backUp of selected Buttons let castedList = Cast(this.presButtonBackUp.selectedButtonDocs, listSpec(Doc)); if (castedList) { @@ -513,13 +501,16 @@ export class PresentationView extends React.Component { } } + /** + * An alternative remove method that removes a doc from presentation by its actual + * reference. + */ public removeDocByRef = (doc: Doc) => { let indexOfDoc = this.childrenDocs.indexOf(doc); const value = FieldValue(Cast(this.curPresentation.data, listSpec(Doc))); if (value) { value.splice(indexOfDoc, 1)[0]; } - //this.RemoveDoc(indexOfDoc, true); if (indexOfDoc !== - 1) { return true; } @@ -618,6 +609,11 @@ export class PresentationView extends React.Component { this.curPresentation.presStatus = this.presStatus; } + /** + * This method is called to find the start document of presentation. So + * that when user presses on play, the correct presentation element will be + * selected. + */ findStartDocument = async () => { let docAtZero = await this.getDocAtIndex(0); if (docAtZero === undefined) { @@ -848,10 +844,11 @@ export class PresentationView extends React.Component { this.curPresentation.title = newTitle; } - addPressElem = (keyDoc: Doc, elem: PresentationElement) => { - this.presElementsMappings.set(keyDoc, elem); - } - + /** + * On pointer down element that is catched on resizer of te + * presentation view. Sets up the event listeners to change the size with + * mouse move. + */ _downsize = 0; onPointerDown = (e: React.PointerEvent) => { this._downsize = e.clientX; @@ -862,16 +859,26 @@ export class PresentationView extends React.Component { e.stopPropagation(); e.preventDefault(); } + /** + * Changes the size of the presentation view, with mouse move. + * Minimum size is set to 300, so that every button is visible. + */ @action onPointerMove = (e: PointerEvent) => { - this.curPresentation.width = Math.max(window.innerWidth - e.clientX, 300); + this.curPresentation.width = Math.max(window.innerWidth - e.clientX, presMinWidth); } + + /** + * The method that is called on pointer up event. It checks if the button is just + * clicked so that presentation view will be closed. The way it's done is to check + * for minimal pixel change like 4, and accept it as it's just a click on top of the dragger. + */ @action onPointerUp = (e: PointerEvent) => { if (Math.abs(e.clientX - this._downsize) < 4) { let presWidth = NumCast(this.curPresentation.width); - if (presWidth - 300 !== 0) { + if (presWidth - presMinWidth !== 0) { this.curPresentation.width = 0; } } @@ -879,14 +886,23 @@ export class PresentationView extends React.Component { document.removeEventListener("pointerup", this.onPointerUp); } + /** + * This function gets triggered on click of the dragger. It opens up the + * presentation view, if it was closed beforehand. + */ togglePresView = (e: React.MouseEvent) => { e.stopPropagation(); e.preventDefault(); let width = NumCast(this.curPresentation.width); if (width === 0) { - this.curPresentation.width = 300; + this.curPresentation.width = presMinWidth; } } + /** + * This function is a setter that opens up the + * presentation mode, by setting it's render flag + * to true. It also closes the presentation view. + */ @action openPresMode = () => { if (!this.presMode) { @@ -895,15 +911,23 @@ export class PresentationView extends React.Component { } } + /** + * This function closes the presentation mode by setting its + * render flag to false. It also opens up the presentation view. + * By setting it to it's minimum size. + */ @action closePresMode = () => { if (this.presMode) { this.presMode = false; - this.curPresentation.width = 300; + this.curPresentation.width = presMinWidth; } } + /** + * Function that is called to render the presentation mode, depending on its flag. + */ renderPresMode = () => { if (this.presMode) { return ; -- cgit v1.2.3-70-g09d2 From b8ace05da169802bc18d138b83576dbab98053e9 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 30 Jul 2019 12:50:18 -0400 Subject: added youtube as button snapshots. changed video default to 10. --- src/client/views/nodes/VideoBox.tsx | 49 +++++++++++++++++++++---------- src/server/youtubeApi/youtubeApiSample.js | 2 +- 2 files changed, 35 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 34cb47b20..d2657227a 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -8,7 +8,6 @@ import { Cast, FieldValue, NumCast } from "../../../new_fields/Types"; import { VideoField } from "../../../new_fields/URLField"; import { RouteStore } from "../../../server/RouteStore"; import { Utils } from "../../../Utils"; -import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from "../../documents/Documents"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; @@ -21,6 +20,9 @@ import { pageSchema } from "./ImageBox"; import "./VideoBox.scss"; import { library } from "@fortawesome/fontawesome-svg-core"; import { faVideo } from "@fortawesome/free-solid-svg-icons"; +import { CompileScript } from "../../util/Scripting"; +import { Doc } from "../../../new_fields/Doc"; +import { ScriptField } from "../../../new_fields/ScriptField"; type VideoDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>; const VideoDocument = makeInterface(positionSchema, pageSchema); @@ -165,21 +167,38 @@ export class VideoBox extends DocComponent(VideoD this._videoRef && ctx.drawImage(this._videoRef, 0, 0, canvas.width, canvas.height); } - //convert to desired file format - var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png' - // if you want to preview the captured image, - let filename = encodeURIComponent("snapshot" + this.props.Document.title + "_" + this.props.Document.curPage).replace(/\./g, ""); - VideoBox.convertDataUri(dataUrl, filename).then(returnedFilename => { - if (returnedFilename) { - let url = Utils.prepend(returnedFilename); - let imageSummary = Docs.Create.ImageDocument(url, { - x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y), - width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-" - }); - this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(imageSummary, false); - DocUtils.MakeLink(imageSummary, this.props.Document); + if (!this._videoRef) { // can't find a way to take snapshots of videos + let b = Docs.Create.ButtonDocument({ + x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y), width: 150, height: 50, title: NumCast(this.props.Document.curPage).toString() + }); + const script = CompileScript(`this.props.Document.curPage = ${NumCast(this.props.Document.curPage)}`, { + params: { this: Doc.name }, + typecheck: false, + editable: true, + }); + if (!script.compiled) { + console.log(script.errors.map(error => error.messageText).join("\n")); + return; } - }); + b.onClick = new ScriptField(script); + this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(b, false); + } else { + //convert to desired file format + var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png' + // if you want to preview the captured image, + let filename = encodeURIComponent("snapshot" + this.props.Document.title + "_" + this.props.Document.curPage).replace(/\./g, ""); + VideoBox.convertDataUri(dataUrl, filename).then(returnedFilename => { + if (returnedFilename) { + let url = Utils.prepend(returnedFilename); + let imageSummary = Docs.Create.ImageDocument(url, { + x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y), + width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-" + }); + this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(imageSummary, false); + DocUtils.MakeLink(imageSummary, this.props.Document); + } + }); + } }, icon: "expand-arrows-alt" }); diff --git a/src/server/youtubeApi/youtubeApiSample.js b/src/server/youtubeApi/youtubeApiSample.js index ec532c78e..50b3c7b38 100644 --- a/src/server/youtubeApi/youtubeApiSample.js +++ b/src/server/youtubeApi/youtubeApiSample.js @@ -148,7 +148,7 @@ function getVideos(auth, args) { part: 'id, snippet', type: 'video', q: args.userInput, - maxResults: 3 + maxResults: 10 }, function (err, response) { if (err) { console.log('The API returned an error: ' + err); -- cgit v1.2.3-70-g09d2 From 1aac1e8820c62a5f06d7e7630394e0bd58b19a94 Mon Sep 17 00:00:00 2001 From: Sam Wilkins Date: Tue, 30 Jul 2019 12:56:25 -0400 Subject: refactored and implemented dictation manager and shift keyboard shortcut for voice commands --- package.json | 2 +- src/client/cognitive_services/CognitiveServices.ts | 24 ------------- src/client/util/DictationManager.ts | 39 ++++++++++++++++++++++ src/client/views/ContextMenu.tsx | 8 +++-- src/client/views/GlobalKeyHandler.ts | 29 ++++++++++++++-- src/client/views/nodes/DocumentView.tsx | 8 ++++- 6 files changed, 79 insertions(+), 31 deletions(-) create mode 100644 src/client/util/DictationManager.ts (limited to 'src') diff --git a/package.json b/package.json index 3f100a3ef..37052fde3 100644 --- a/package.json +++ b/package.json @@ -203,4 +203,4 @@ "xoauth2": "^1.2.0", "youtube": "^0.1.0" } -} \ No newline at end of file +} diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts index b9c718cfa..c118d91d3 100644 --- a/src/client/cognitive_services/CognitiveServices.ts +++ b/src/client/cognitive_services/CognitiveServices.ts @@ -13,12 +13,6 @@ type AnalysisApplier = (target: Doc, relevantKeys: string[], ...args: any) => an type BodyConverter = (data: D) => string; type Converter = (results: any) => Field; -namespace CORE { - export interface IWindow extends Window { - webkitSpeechRecognition: any; - } -} - export type Tag = { name: string, confidence: number }; export type Rectangle = { top: number, left: number, width: number, height: number }; @@ -217,22 +211,4 @@ export namespace CognitiveServices { } - export namespace Transcription { - - export const analyzer = (target: Doc, keys: string[]) => { - let { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow; - let recognizer = new webkitSpeechRecognition(); - recognizer.interimResults = true; - recognizer.continuous = true; - - recognizer.onresult = (e: any) => { - let result = e.results[0][0]; - target[keys[0]] = result.transcript; - }; - - recognizer.start(); - }; - - } - } \ No newline at end of file diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts new file mode 100644 index 000000000..b58bdb6c7 --- /dev/null +++ b/src/client/util/DictationManager.ts @@ -0,0 +1,39 @@ +namespace CORE { + export interface IWindow extends Window { + webkitSpeechRecognition: any; + } +} + +const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow; + +export default class DictationManager { + public static Instance = new DictationManager(); + private isListening = false; + private recognizer: any; + + constructor() { + this.recognizer = new webkitSpeechRecognition(); + this.recognizer.interimResults = false; + this.recognizer.continuous = true; + } + + finish = (handler: any, data: any) => { + handler(data); + this.isListening = false; + this.recognizer.stop(); + } + + listen = () => { + if (this.isListening) { + return undefined; + } + this.isListening = true; + this.recognizer.start(); + return new Promise((resolve, reject) => { + this.recognizer.onresult = (e: any) => this.finish(resolve, e.results[0][0].transcript); + this.recognizer.onerror = (e: any) => this.finish(reject, e); + }); + + } + +} \ No newline at end of file diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index a608e448a..98025ac31 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -38,8 +38,12 @@ export class ContextMenu extends React.Component { this._items = []; } - findByDescription = (target: string) => { - return this._items.find(menuItem => menuItem.description === target); + findByDescription = (target: string, toLowerCase = false) => { + return this._items.find(menuItem => { + let reference = menuItem.description; + toLowerCase && (reference = reference.toLowerCase()); + reference === target; + }); } @action diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index e31b44514..373584b4e 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -5,9 +5,13 @@ import { MainView } from "./MainView"; import { DragManager } from "../util/DragManager"; import { action } from "mobx"; import { Doc } from "../../new_fields/Doc"; +import { CognitiveServices } from "../cognitive_services/CognitiveServices"; +import DictationManager from "../util/DictationManager"; +import { ContextMenu } from "./ContextMenu"; +import { ContextMenuProps } from "./ContextMenuItem"; const modifiers = ["control", "meta", "shift", "alt"]; -type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo; +type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise; type KeyControlInfo = { preventDefault: boolean, stopPropagation: boolean @@ -25,9 +29,10 @@ export default class KeyManager { this.router.set(isMac ? "0001" : "0100", this.ctrl); this.router.set(isMac ? "0100" : "0010", this.alt); this.router.set(isMac ? "1001" : "1100", this.ctrl_shift); + this.router.set("1000", this.shift); } - public handle = (e: KeyboardEvent) => { + public handle = async (e: KeyboardEvent) => { let keyname = e.key.toLowerCase(); this.handleGreedy(keyname); @@ -43,7 +48,7 @@ export default class KeyManager { return; } - let control = handleConstrained(keyname, e); + let control = await handleConstrained(keyname, e); control.stopPropagation && e.stopPropagation(); control.preventDefault && e.preventDefault(); @@ -95,6 +100,24 @@ export default class KeyManager { }; }); + private shift = async (keyname: string) => { + let stopPropagation = true; + let preventDefault = true; + + switch (keyname) { + case " ": + let transcript = await DictationManager.Instance.listen(); + console.log(`I heard${transcript ? `: ${transcript.toLowerCase()}` : " nothing: I thought I was still listening from an earlier session."}`); + let command: ContextMenuProps | undefined; + transcript && (command = ContextMenu.Instance.findByDescription(transcript, true)) && "event" in command && command.event(); + } + + return { + stopPropagation: stopPropagation, + preventDefault: preventDefault + }; + } + private alt = action((keyname: string) => { let stopPropagation = true; let preventDefault = true; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index fd53cd451..dc56c1c8f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -42,6 +42,7 @@ import { EditableView } from '../EditableView'; import { faHandPointer, faHandPointRight } from '@fortawesome/free-regular-svg-icons'; import { DocumentDecorations } from '../DocumentDecorations'; import { CognitiveServices } from '../../cognitive_services/CognitiveServices'; +import DictationManager from '../../util/DictationManager'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? library.add(fa.faTrash); @@ -536,6 +537,11 @@ export class DocumentView extends DocComponent(Docu this.props.Document.lockedPosition = BoolCast(this.props.Document.lockedPosition) ? undefined : true; } + listen = async () => { + let transcript = await DictationManager.Instance.listen(); + transcript && (Doc.GetProto(this.props.Document).transcript = transcript); + } + @action onContextMenu = async (e: React.MouseEvent): Promise => { e.persist(); @@ -559,7 +565,7 @@ export class DocumentView extends DocComponent(Docu cm.addItem({ description: BoolCast(this.props.Document.ignoreAspect, false) || !this.props.Document.nativeWidth || !this.props.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" }); cm.addItem({ description: "Pin to Presentation", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" }); cm.addItem({ description: BoolCast(this.props.Document.lockedPosition) ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" }); - cm.addItem({ description: "Transcribe Speech", event: () => CognitiveServices.Transcription.analyzer(Doc.GetProto(this.props.Document), ["transcript"]), icon: "microphone" }); + cm.addItem({ description: "Transcribe Speech", event: this.listen, icon: "microphone" }); let makes: ContextMenuProps[] = []; makes.push({ description: "Make Background", event: this.makeBackground, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" }); makes.push({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeBtnClicked, icon: "concierge-bell" }); -- cgit v1.2.3-70-g09d2 From 937af0cd2b2c01e7971c1edf120567ee15a4d5b9 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 30 Jul 2019 13:43:02 -0400 Subject: fixed snapshots for youtube videos. --- src/client/views/MainView.tsx | 3 +- .../views/collections/CollectionVideoView.scss | 9 ++ .../views/collections/CollectionVideoView.tsx | 24 +++-- src/client/views/nodes/VideoBox.tsx | 101 +++++++++++---------- 4 files changed, 80 insertions(+), 57 deletions(-) (limited to 'src') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index ababbe949..88a636784 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,5 +1,5 @@ import { IconName, library } from '@fortawesome/fontawesome-svg-core'; -import { faArrowDown, faCloudUploadAlt, faArrowUp, faClone, faCheck, faPlay, faCaretUp, faLongArrowAltRight, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faPortrait, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faThumbtack, faTree, faUndoAlt, faCat, faBolt } from '@fortawesome/free-solid-svg-icons'; +import { faArrowDown, faCloudUploadAlt, faArrowUp, faClone, faCheck, faPlay, faPause, faCaretUp, faLongArrowAltRight, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faPortrait, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faThumbtack, faTree, faUndoAlt, faCat, faBolt } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, runInAction, reaction, trace } from 'mobx'; import { observer } from 'mobx-react'; @@ -126,6 +126,7 @@ export class MainView extends React.Component { library.add(faMusic); library.add(faTree); library.add(faPlay); + library.add(faPause); library.add(faClone); library.add(faCut); library.add(faCommentAlt); diff --git a/src/client/views/collections/CollectionVideoView.scss b/src/client/views/collections/CollectionVideoView.scss index 9d2c23d3e..509851ebb 100644 --- a/src/client/views/collections/CollectionVideoView.scss +++ b/src/client/views/collections/CollectionVideoView.scss @@ -6,6 +6,7 @@ top: 0; left:0; z-index: -1; + display:inline-table; } .collectionVideoView-time{ color : white; @@ -15,6 +16,14 @@ background-color: rgba(50, 50, 50, 0.2); transform-origin: left top; } +.collectionVideoView-snapshot{ + color : white; + top :25px; + right : 25px; + position: absolute; + background-color: rgba(50, 50, 50, 0.2); + transform-origin: left top; +} .collectionVideoView-play { width: 25px; height: 20px; diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx index a264cc402..5185d9d0e 100644 --- a/src/client/views/collections/CollectionVideoView.tsx +++ b/src/client/views/collections/CollectionVideoView.tsx @@ -9,6 +9,7 @@ import "./CollectionVideoView.scss"; import React = require("react"); import { InkingControl } from "../InkingControl"; import { InkTool } from "../../../new_fields/InkField"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @observer @@ -21,18 +22,20 @@ export class CollectionVideoView extends React.Component { private get uIButtons() { let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale); let curTime = NumCast(this.props.Document.curPage); - return ([
    + return ([
    {"" + Math.round(curTime)} {" " + Math.round((curTime - Math.trunc(curTime)) * 100)}
    , +
    + +
    , VideoBox._showControls ? (null) : [ -
    - {this._videoBox && this._videoBox.Playing ? "\"" : ">"} +
    +
    , -
    +
    F -
    - +
    ]]); } @@ -56,6 +59,15 @@ export class CollectionVideoView extends React.Component { } } + @action + onSnapshot = (e: React.PointerEvent) => { + if (this._videoBox) { + this._videoBox.Snapshot(); + e.stopPropagation(); + e.preventDefault(); + } + } + _isclick = 0; @action onResetDown = (e: React.PointerEvent) => { diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index d2657227a..1f8636826 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -89,6 +89,56 @@ export class VideoBox extends DocComponent(VideoD this._youtubePlayer && this.props.addDocTab(this.props.Document, this.props.DataDoc, "inTab"); } + @action public Snapshot() { + let width = NumCast(this.props.Document.width); + let height = NumCast(this.props.Document.height); + var canvas = document.createElement('canvas'); + canvas.width = 640; + canvas.height = 640 * NumCast(this.props.Document.nativeHeight) / NumCast(this.props.Document.nativeWidth); + var ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions + if (ctx) { + ctx.rect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = "blue"; + ctx.fill(); + this._videoRef && ctx.drawImage(this._videoRef, 0, 0, canvas.width, canvas.height); + } + + if (!this._videoRef) { // can't find a way to take snapshots of videos + let b = Docs.Create.ButtonDocument({ + x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y), + width: 150, height: 50, title: NumCast(this.props.Document.curPage).toString() + }); + const script = CompileScript(`(self as any).curPage = ${NumCast(this.props.Document.curPage)}`, { + params: { this: Doc.name }, + capturedVariables: { self: this.props.Document }, + typecheck: false, + editable: true, + }); + if (script.compiled) { + b.onClick = new ScriptField(script); + this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(b, false); + } else { + console.log(script.errors.map(error => error.messageText).join("\n")); + } + } else { + //convert to desired file format + var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png' + // if you want to preview the captured image, + let filename = encodeURIComponent("snapshot" + this.props.Document.title + "_" + this.props.Document.curPage).replace(/\./g, ""); + VideoBox.convertDataUri(dataUrl, filename).then(returnedFilename => { + if (returnedFilename) { + let url = Utils.prepend(returnedFilename); + let imageSummary = Docs.Create.ImageDocument(url, { + x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y), + width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-" + }); + this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(imageSummary, false); + DocUtils.MakeLink(imageSummary, this.props.Document); + } + }); + } + } + @action updateTimecode = () => { this.player && (this.props.Document.curPage = this.player.currentTime); @@ -152,56 +202,7 @@ export class VideoBox extends DocComponent(VideoD let subitems: ContextMenuProps[] = []; subitems.push({ description: "Copy path", event: () => { Utils.CopyText(url); }, icon: "expand-arrows-alt" }); subitems.push({ description: "Toggle Show Controls", event: action(() => VideoBox._showControls = !VideoBox._showControls), icon: "expand-arrows-alt" }); - let width = NumCast(this.props.Document.width); - let height = NumCast(this.props.Document.height); - subitems.push({ - description: "Take Snapshot", event: async () => { - var canvas = document.createElement('canvas'); - canvas.width = 640; - canvas.height = 640 * NumCast(this.props.Document.nativeHeight) / NumCast(this.props.Document.nativeWidth); - var ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions - if (ctx) { - ctx.rect(0, 0, canvas.width, canvas.height); - ctx.fillStyle = "blue"; - ctx.fill(); - this._videoRef && ctx.drawImage(this._videoRef, 0, 0, canvas.width, canvas.height); - } - - if (!this._videoRef) { // can't find a way to take snapshots of videos - let b = Docs.Create.ButtonDocument({ - x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y), width: 150, height: 50, title: NumCast(this.props.Document.curPage).toString() - }); - const script = CompileScript(`this.props.Document.curPage = ${NumCast(this.props.Document.curPage)}`, { - params: { this: Doc.name }, - typecheck: false, - editable: true, - }); - if (!script.compiled) { - console.log(script.errors.map(error => error.messageText).join("\n")); - return; - } - b.onClick = new ScriptField(script); - this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(b, false); - } else { - //convert to desired file format - var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png' - // if you want to preview the captured image, - let filename = encodeURIComponent("snapshot" + this.props.Document.title + "_" + this.props.Document.curPage).replace(/\./g, ""); - VideoBox.convertDataUri(dataUrl, filename).then(returnedFilename => { - if (returnedFilename) { - let url = Utils.prepend(returnedFilename); - let imageSummary = Docs.Create.ImageDocument(url, { - x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y), - width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-" - }); - this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(imageSummary, false); - DocUtils.MakeLink(imageSummary, this.props.Document); - } - }); - } - }, - icon: "expand-arrows-alt" - }); + subitems.push({ description: "Take Snapshot", event: () => this.Snapshot(), icon: "expand-arrows-alt" }); ContextMenu.Instance.addItem({ description: "Video Funcs...", subitems: subitems, icon: "video" }); } } -- cgit v1.2.3-70-g09d2 From 26086ee95a9a16486d637aa43c96638b6154379f Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Tue, 30 Jul 2019 16:02:42 -0400 Subject: Added document export and import --- package.json | 4 + .../collectionFreeForm/CollectionFreeFormView.tsx | 39 +++++- src/client/views/nodes/DocumentView.tsx | 10 ++ src/scraping/buxton/scraper.py | 2 + src/server/database.ts | 37 +++++- src/server/index.ts | 142 +++++++++++++++++---- 6 files changed, 202 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/package.json b/package.json index 37052fde3..b29355738 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,9 @@ "@hig/theme-context": "^2.1.3", "@hig/theme-data": "^2.3.3", "@trendmicro/react-dropdown": "^1.3.0", + "@types/adm-zip": "^0.4.32", "@types/animejs": "^2.0.2", + "@types/archiver": "^3.0.0", "@types/async": "^2.4.1", "@types/bcrypt-nodejs": "0.0.30", "@types/bluebird": "^3.5.25", @@ -105,6 +107,8 @@ "@types/uuid": "^3.4.4", "@types/webpack": "^4.4.25", "@types/youtube": "0.0.38", + "adm-zip": "^0.4.13", + "archiver": "^3.0.3", "async": "^2.6.2", "babel-runtime": "^6.26.0", "bcrypt-nodejs": "0.0.3", diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 8dac785e1..cbab14976 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -5,7 +5,7 @@ import { Id } from "../../../../new_fields/FieldSymbols"; import { InkField, StrokeData } from "../../../../new_fields/InkField"; import { createSchema, makeInterface } from "../../../../new_fields/Schema"; import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types"; -import { emptyFunction, returnOne } from "../../../../Utils"; +import { emptyFunction, returnOne, Utils } from "../../../../Utils"; import { DocumentManager } from "../../../util/DocumentManager"; import { DragManager } from "../../../util/DragManager"; import { HistoryUtil } from "../../../util/History"; @@ -34,12 +34,14 @@ import { CompileScript } from "../../../util/Scripting"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { library } from "@fortawesome/fontawesome-svg-core"; import { faEye } from "@fortawesome/free-regular-svg-icons"; -import { faTable, faPaintBrush, faAsterisk, faExpandArrowsAlt, faCompressArrowsAlt, faCompass } from "@fortawesome/free-solid-svg-icons"; +import { faTable, faPaintBrush, faAsterisk, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload } from "@fortawesome/free-solid-svg-icons"; import { undo } from "prosemirror-history"; import { number } from "prop-types"; import { ContextMenu } from "../../ContextMenu"; +import { RouteStore } from "../../../../server/RouteStore"; +import { DocServer } from "../../../DocServer"; -library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass); +library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload); export const panZoomSchema = createSchema({ panX: "number", @@ -516,7 +518,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY)); } - onContextMenu = () => { + onContextMenu = (e: React.MouseEvent) => { let layoutItems: ContextMenuProps[] = []; layoutItems.push({ description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`, @@ -561,6 +563,35 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { CognitiveServices.Inking.Manager.analyzer(this.fieldExtensionDoc, relevantKeys, data.inkData); }, icon: "paint-brush" }); + ContextMenu.Instance.addItem({ + description: "Import document", icon: "upload", event: () => { + const input = document.createElement("input"); + input.type = "file"; + input.accept = ".zip"; + input.onchange = async _e => { + const files = input.files; + if (!files) return; + const file = files[0]; + let formData = new FormData(); + formData.append('file', file); + formData.append('remap', "true"); + const upload = Utils.prepend("/uploadDoc"); + const response = await fetch(upload, { method: "POST", body: formData }); + const json = await response.json(); + if (json === "error") { + return; + } + const doc = await DocServer.GetRefField(json); + if (!doc || !(doc instanceof Doc)) { + return; + } + const [x, y] = this.props.ScreenToLocalTransform().transformPoint(e.pageX, e.pageY); + doc.x = x, doc.y = y; + this.addDocument(doc, false); + }; + input.click(); + } + }); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 4b5cf3a43..58e2443c2 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -45,6 +45,7 @@ const JsxParser = require('react-jsx-parser').default; //TODO Why does this need library.add(fa.faTrash); library.add(fa.faShare); +library.add(fa.faDownload); library.add(fa.faExpandArrowsAlt); library.add(fa.faCompressArrowsAlt); library.add(fa.faLayerGroup); @@ -597,6 +598,15 @@ export class DocumentView extends DocComponent(Docu copies.push({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" }); cm.addItem({ description: "Copy...", subitems: copies, icon: "copy" }); } + cm.addItem({ + description: "Download document", icon: "download", event: () => { + const a = document.createElement("a"); + const url = Utils.prepend(`/downloadId/${this.props.Document[Id]}`); + a.href = url; + a.download = `DocExport-${this.props.Document[Id]}.zip`; + a.click(); + } + }); cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" }); type User = { email: string, userDocumentId: string }; let usersMenu: ContextMenuProps[] = []; diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py index 1ff0e3b31..8ff7cb223 100644 --- a/src/scraping/buxton/scraper.py +++ b/src/scraping/buxton/scraper.py @@ -236,6 +236,8 @@ def parse_document(file_name: str): view_guids.append(write_image(pure_name, image)) copyfile(dir_path + "/" + image, dir_path + "/" + image.replace(".", "_o.", 1)) + copyfile(dir_path + "/" + image, dir_path + + "/" + image) os.rename(dir_path + "/" + image, dir_path + "/" + image.replace(".", "_m.", 1)) print(f"extracted {count} images...") diff --git a/src/server/database.ts b/src/server/database.ts index acb6ce751..a7254fb0c 100644 --- a/src/server/database.ts +++ b/src/server/database.ts @@ -17,7 +17,7 @@ export class Database { }); } - public update(id: string, value: any, callback: () => void, upsert = true, collectionName = Database.DocumentsCollection) { + public update(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = Database.DocumentsCollection) { if (this.db) { let collection = this.db.collection(collectionName); const prom = this.currentWrites[id]; @@ -30,7 +30,7 @@ export class Database { delete this.currentWrites[id]; } resolve(); - callback(); + callback(err, res); }); }); }; @@ -41,6 +41,30 @@ export class Database { } } + public replace(id: string, value: any, callback: (err: mongodb.MongoError, res: mongodb.UpdateWriteOpResult) => void, upsert = true, collectionName = Database.DocumentsCollection) { + if (this.db) { + let collection = this.db.collection(collectionName); + const prom = this.currentWrites[id]; + let newProm: Promise; + const run = (): Promise => { + return new Promise(resolve => { + collection.replaceOne({ _id: id }, value, { upsert } + , (err, res) => { + if (this.currentWrites[id] === newProm) { + delete this.currentWrites[id]; + } + resolve(); + callback(err, res); + }); + }); + }; + newProm = prom ? prom.then(run) : run(); + this.currentWrites[id] = newProm; + } else { + this.onConnect.push(() => this.replace(id, value, callback, upsert, collectionName)); + } + } + public delete(query: any, collectionName?: string): Promise; public delete(id: string, collectionName?: string): Promise; public delete(id: any, collectionName = Database.DocumentsCollection) { @@ -126,7 +150,7 @@ export class Database { } } - public async visit(ids: string[], fn: (result: any) => string[], collectionName = "newDocuments") { + public async visit(ids: string[], fn: (result: any) => string[], collectionName = "newDocuments"): Promise { if (this.db) { const visited = new Set(); while (ids.length) { @@ -145,7 +169,12 @@ export class Database { } } else { - this.onConnect.push(() => this.visit(ids, fn, collectionName)); + return new Promise(res => { + this.onConnect.push(() => { + this.visit(ids, fn, collectionName); + res(); + }); + }); } } diff --git a/src/server/index.ts b/src/server/index.ts index 230c574cf..2fa5132d0 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -27,6 +27,7 @@ import { Client } from './Client'; import { Database } from './database'; import { MessageStore, Transferable, Types, Diff, Message } from "./Message"; import { RouteStore } from './RouteStore'; +import v4 = require('uuid/v4'); const app = express(); const config = require('../../webpack.config'); import { createCanvas, loadImage, Canvas } from "canvas"; @@ -39,7 +40,10 @@ import c = require("crypto"); import { Search } from './Search'; import { debug } from 'util'; import _ = require('lodash'); +import * as Archiver from 'archiver'; +import * as AdmZip from 'adm-zip'; import { Response } from 'express-serve-static-core'; +import { DocComponent } from '../client/views/DocComponent'; const MongoStore = require('connect-mongo')(session); const mongoose = require('mongoose'); const probe = require("probe-image-size"); @@ -177,16 +181,21 @@ function msToTime(duration: number) { return hoursS + ":" + minutesS + ":" + secondsS + "." + milliseconds; } -app.get("/serializeDoc/:docId", async (req, res) => { - const files: { [name: string]: string[] } = {}; +async function getDocs(id: string) { + const files = new Set(); const docs: { [id: string]: any } = {}; const fn = (doc: any): string[] => { + const id = doc.id; + if (typeof id === "string" && id.endsWith("Proto")) { + //Skip protos + return []; + } const ids: string[] = []; - for (const key in doc) { - if (!doc.hasOwnProperty(key)) { + for (const key in doc.fields) { + if (!doc.fields.hasOwnProperty(key)) { continue; } - const field = doc[key]; + const field = doc.fields[key]; if (field === undefined || field === null) { continue; } @@ -194,7 +203,7 @@ app.get("/serializeDoc/:docId", async (req, res) => { if (field.__type === "proxy" || field.__type === "prefetch_proxy") { ids.push(field.fieldId); } else if (field.__type === "list") { - ids.push(...fn(field.fields)); + ids.push(...fn(field)); } else if (typeof field === "string") { const re = /"(?:dataD|d)ocumentId"\s*:\s*"([\w\-]*)"/g; let match: string[] | null; @@ -215,35 +224,120 @@ app.get("/serializeDoc/:docId", async (req, res) => { while ((match = re2.exec(field.Data)) !== null) { const urlString = match[1]; const pathname = new URL(urlString).pathname; - const ext = path.extname(pathname); - const fileName = path.basename(pathname, ext); - let exts = files[fileName]; - if (!exts) { - files[fileName] = exts = []; - } - exts.push(ext); + files.add(pathname); } } else if (["audio", "image", "video", "pdf", "web"].includes(field.__type)) { const url = new URL(field.url); const pathname = url.pathname; - const ext = path.extname(pathname); - const fileName = path.basename(pathname, ext); - let exts = files[fileName]; - if (!exts) { - files[fileName] = exts = []; - } - exts.push(ext); + files.add(pathname); } } - docs[doc.id] = doc; + if (doc.id) { + docs[doc.id] = doc; + } return ids; }; - Database.Instance.visit([req.params.docId], fn); + await Database.Instance.visit([id], fn); + return { id, docs, files }; +} +app.get("/serializeDoc/:docId", async (req, res) => { + const { docs, files } = await getDocs(req.params.docId); + res.send({ docs, files: Array.from(files) }); }); -app.get("/downloadId/:docId", (req, res) => { - res.download(`/serializeDoc/${req.params.docId}`, `DocumentExport.zip`); +app.get("/downloadId/:docId", async (req, res) => { + res.set('Content-disposition', `attachment;`); + res.set('Content-Type', "application/zip"); + const { id, docs, files } = await getDocs(req.params.docId); + const docString = JSON.stringify({ id, docs }); + const zip = Archiver('zip'); + zip.pipe(res); + zip.append(docString, { name: "doc.json" }); + files.forEach(val => { + zip.file(__dirname + RouteStore.public + val, { name: val.substring(1) }); + }); + zip.finalize(); +}); + +app.post("/uploadDoc", (req, res) => { + let form = new formidable.IncomingForm(); + form.keepExtensions = true; + // let path = req.body.path; + const ids: { [id: string]: string } = {}; + let remap = true; + const getId = (id: string): string => { + if (!remap) return id; + if (id.endsWith("Proto")) return id; + if (id in ids) { + return ids[id]; + } else { + return ids[id] = v4(); + } + }; + const mapFn = (doc: any) => { + if (doc.id) { + doc.id = getId(doc.id); + } + for (const key in doc.fields) { + if (!doc.fields.hasOwnProperty(key)) { + continue; + } + const field = doc.fields[key]; + if (field === undefined || field === null) { + continue; + } + + if (field.__type === "proxy" || field.__type === "prefetch_proxy") { + field.fieldId = getId(field.fieldId); + } else if (field.__type === "list") { + mapFn(field); + } else if (typeof field === "string") { + const re = /("(?:dataD|d)ocumentId"\s*:\s*")([\w\-]*)"/g; + doc.fields[key] = (field as any).replace(re, (match: any, p1: string, p2: string) => { + return `${p1}${getId(p2)}"`; + }); + } else if (field.__type === "RichTextField") { + const re = /("href"\s*:\s*")(.*?)"/g; + field.Data = field.Data.replace(re, (match: any, p1: string, p2: string) => { + return `${p1}${getId(p2)}"`; + }); + } + } + }; + form.parse(req, async (err, fields, files) => { + remap = fields.remap !== "false"; + let id: string = ""; + try { + for (const name in files) { + const path = files[name].path; + const zip = new AdmZip(path); + zip.getEntries().forEach(entry => { + if (!entry.name.startsWith("files/")) return; + zip.extractEntryTo(entry.name, __dirname + RouteStore.public, true, false); + }); + const json = zip.getEntry("doc.json"); + let docs: any; + try { + let data = JSON.parse(json.getData().toString("utf8")); + docs = data.docs; + id = data.id; + docs = Object.keys(docs).map(key => docs[key]); + docs.forEach(mapFn); + await Promise.all(docs.map((doc: any) => new Promise(res => Database.Instance.replace(doc.id, doc, (err, r) => { + err && console.log(err); + res(); + }, true, "newDocuments")))); + } catch (e) { console.log(e); } + fs.unlink(path, () => { }); + } + if (id) { + res.send(JSON.stringify(getId(id))); + } else { + res.send(JSON.stringify("error")); + } + } catch (e) { console.log(e); } + }); }); app.get("/whosOnline", (req, res) => { -- cgit v1.2.3-70-g09d2 From 8ca17d379ce7d3cc751408553b6819223d31a3e0 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Tue, 30 Jul 2019 16:22:54 -0400 Subject: Fixed import thing --- src/scraping/buxton/scraper.py | 2 -- src/server/index.ts | 8 ++++++++ 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py index 8ff7cb223..f0f45d8f9 100644 --- a/src/scraping/buxton/scraper.py +++ b/src/scraping/buxton/scraper.py @@ -237,8 +237,6 @@ def parse_document(file_name: str): copyfile(dir_path + "/" + image, dir_path + "/" + image.replace(".", "_o.", 1)) copyfile(dir_path + "/" + image, dir_path + - "/" + image) - os.rename(dir_path + "/" + image, dir_path + "/" + image.replace(".", "_m.", 1)) print(f"extracted {count} images...") diff --git a/src/server/index.ts b/src/server/index.ts index 281fc1eb2..1912cf5c1 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -205,6 +205,10 @@ async function getDocs(id: string) { if (field.__type === "proxy" || field.__type === "prefetch_proxy") { ids.push(field.fieldId); + } else if (field.__type === "script" || field.__type === "computed") { + if (field.captures) { + ids.push(field.captures.fieldId); + } } else if (field.__type === "list") { ids.push(...fn(field)); } else if (typeof field === "string") { @@ -293,6 +297,10 @@ app.post("/uploadDoc", (req, res) => { if (field.__type === "proxy" || field.__type === "prefetch_proxy") { field.fieldId = getId(field.fieldId); + } else if (field.__type === "script" || field.__type === "computed") { + if (field.captures) { + field.captures.fieldId = getId(field.captures.fieldId); + } } else if (field.__type === "list") { mapFn(field); } else if (typeof field === "string") { -- cgit v1.2.3-70-g09d2