diff options
author | fawn <fangrui_tong@brown.edu> | 2019-07-30 16:52:12 -0400 |
---|---|---|
committer | fawn <fangrui_tong@brown.edu> | 2019-07-30 16:52:12 -0400 |
commit | f7c0948910182f5f6cb2c10c216994e2bc7b91b0 (patch) | |
tree | 6d443543c7475f4104bf7b8a3be788bb3ce2a3ec /src/client/views/presentationview/PresentationView.tsx | |
parent | 78999b8b35267db9236bbb69e7e90e8691c59ba9 (diff) | |
parent | 8ca17d379ce7d3cc751408553b6819223d31a3e0 (diff) |
merged
Diffstat (limited to 'src/client/views/presentationview/PresentationView.tsx')
-rw-r--r-- | src/client/views/presentationview/PresentationView.tsx | 280 |
1 files changed, 209 insertions, 71 deletions
diff --git a/src/client/views/presentationview/PresentationView.tsx b/src/client/views/presentationview/PresentationView.tsx index e25725275..4fe9d3a1b 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,10 +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 PresModeMenu from "./PresentationModeMenu"; +import { CollectionDockingView } from "../collections/CollectionDockingView"; library.add(faArrowLeft); library.add(faArrowRight); @@ -25,6 +27,7 @@ library.add(faPlus); library.add(faTimes); library.add(faMinus); library.add(faEdit); +library.add(faEye); export interface PresViewProps { @@ -32,6 +35,7 @@ export interface PresViewProps { } const expandedWidth = 400; +const presMinWidth = 300; @observer export class PresentationView extends React.Component<PresViewProps> { @@ -62,6 +66,8 @@ export class PresentationView extends React.Component<PresViewProps> { //Variable that holds reference to title input, so that new presentations get titles assigned. @observable titleInputElement: HTMLInputElement | undefined; @observable PresTitleChangeOpen: boolean = false; + @observable presMode: boolean = false; + @observable opacity = 1; @observable persistOpacity = true; @@ -84,6 +90,7 @@ export class PresentationView extends React.Component<PresViewProps> { //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. @@ -361,11 +368,16 @@ export class PresentationView extends React.Component<PresViewProps> { //checking if curDoc has navigation open let curDocButtons = this.presElementsMappings.get(curDoc)!.selected; if (curDocButtons[buttonIndex.Navigate]) { - DocumentManager.Instance.jumpToDocument(curDoc, false); + 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, doc => CollectionDockingView.Instance.AddTab(undefined, doc, undefined)); + } + let newScale = DocumentManager.Instance.getScaleOfDocView(curDoc); curDoc.viewScale = newScale; @@ -378,9 +390,15 @@ export class PresentationView extends React.Component<PresViewProps> { 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, doc => CollectionDockingView.Instance.AddTab(undefined, doc, undefined)); + } let newScale = DocumentManager.Instance.getScaleOfDocView(curDoc); curDoc.viewScale = newScale; //saving the scale that user was on @@ -391,6 +409,18 @@ export class PresentationView extends React.Component<PresViewProps> { } /** + * 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); + } else { + DocumentManager.Instance.jumpToDocument(curDoc, false, undefined, doc => CollectionDockingView.Instance.AddTab(undefined, doc, undefined)); + } + } + + /** * Async function that supposedly return the doc that is located at given index. */ getDocAtIndex = async (index: number) => { @@ -434,22 +464,6 @@ export class PresentationView extends React.Component<PresViewProps> { } } - //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) { @@ -487,13 +501,16 @@ export class PresentationView extends React.Component<PresViewProps> { } } + /** + * 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; } @@ -579,18 +596,40 @@ export class PresentationView extends React.Component<PresViewProps> { //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; } + /** + * 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) { + 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 @@ -805,60 +844,159 @@ export class PresentationView extends React.Component<PresViewProps> { 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; + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointermove", this.onPointerMove); + document.addEventListener("pointerup", this.onPointerUp); + 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, 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 - presMinWidth !== 0) { + this.curPresentation.width = 0; + } + } + document.removeEventListener("pointermove", this.onPointerMove); + 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 = 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) { + this.curPresentation.width = 0; + this.presMode = true; + } + } + + /** + * 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 = presMinWidth; + } + + } + + /** + * Function that is called to render the presentation mode, depending on its flag. + */ + renderPresMode = () => { + if (this.presMode) { + return <PresModeMenu next={this.next} back={this.back} startOrResetPres={this.startOrResetPres} presStatus={this.presStatus} closePresMode={this.closePresMode} />; + } else { + return (null); + } + + } render() { let width = NumCast(this.curPresentation.width); return ( - <div className="presentationView-cont" onPointerEnter={action(() => !this.persistOpacity && (this.opacity = 1))} onPointerLeave={action(() => !this.persistOpacity && (this.opacity = 0.4))} style={{ width: width, overflow: "hidden", opacity: this.opacity, transition: "0.7s opacity ease" }}> - <div className="presentationView-heading"> - {this.renderSelectOrPresSelection()} - <button title="Close Presentation" className='presentation-icon' onClick={this.closePresentation}><FontAwesomeIcon icon={"times"} /></button> - <button title="Add Presentation" className="presentation-icon" style={{ marginRight: 10 }} onClick={() => { - runInAction(() => { if (this.PresTitleChangeOpen) { this.PresTitleChangeOpen = false; } }); - runInAction(() => this.PresTitleInputOpen ? this.PresTitleInputOpen = false : this.PresTitleInputOpen = true); - }}><FontAwesomeIcon icon={"plus"} /></button> - <button title="Remove Presentation" className='presentation-icon' style={{ marginRight: 10 }} onClick={this.removePresentation}><FontAwesomeIcon icon={"minus"} /></button> - <button title="Change Presentation Title" className="presentation-icon" style={{ marginRight: 10 }} onClick={() => { - runInAction(() => { if (this.PresTitleInputOpen) { this.PresTitleInputOpen = false; } }); - runInAction(() => this.PresTitleChangeOpen ? this.PresTitleChangeOpen = false : this.PresTitleChangeOpen = true); - }}><FontAwesomeIcon icon={"edit"} /></button> + <div> + <div className="presentationView-cont" onPointerEnter={action(() => !this.persistOpacity && (this.opacity = 1))} onPointerLeave={action(() => !this.persistOpacity && (this.opacity = 0.4))} style={{ width: width, overflowY: "scroll", overflowX: "hidden", opacity: this.opacity, transition: "0.7s opacity ease" }}> + <div className="presentationView-heading"> + {this.renderSelectOrPresSelection()} + <button title="Close Presentation" className='presentation-icon' onClick={this.closePresentation}><FontAwesomeIcon icon={"times"} /></button> + <button title="Open Presentation Mode" className="presentation-icon" style={{ marginRight: 10 }} onClick={this.openPresMode}><FontAwesomeIcon icon={"eye"} /></button> + <button title="Add Presentation" className="presentation-icon" style={{ marginRight: 10 }} onClick={() => { + runInAction(() => { if (this.PresTitleChangeOpen) { this.PresTitleChangeOpen = false; } }); + runInAction(() => this.PresTitleInputOpen ? this.PresTitleInputOpen = false : this.PresTitleInputOpen = true); + }}><FontAwesomeIcon icon={"plus"} /></button> + <button title="Remove Presentation" className='presentation-icon' style={{ marginRight: 10 }} onClick={this.removePresentation}><FontAwesomeIcon icon={"minus"} /></button> + <button title="Change Presentation Title" className="presentation-icon" style={{ marginRight: 10 }} onClick={() => { + runInAction(() => { if (this.PresTitleInputOpen) { this.PresTitleInputOpen = false; } }); + runInAction(() => this.PresTitleChangeOpen ? this.PresTitleChangeOpen = false : this.PresTitleChangeOpen = true); + }}><FontAwesomeIcon icon={"edit"} /></button> + </div> + <div className="presentation-buttons"> + <button title="Back" className="presentation-button" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></button> + {this.renderPlayPauseButton()} + <button title="Next" className="presentation-button" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></button> + </div> + + <PresentationViewList + mainDocument={this.curPresentation} + deleteDocument={this.RemoveDoc} + gotoDocument={this.gotoDocument} + groupMappings={this.groupMappings} + PresElementsMappings={this.presElementsMappings} + setChildrenDocs={this.setChildrenDocs} + presStatus={this.presStatus} + presButtonBackUp={this.presButtonBackUp} + presGroupBackUp={this.presGroupBackUp} + removeDocByRef={this.removeDocByRef} + clearElemMap={() => this.presElementsMappings.clear()} + /> + <input + type="checkbox" + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { + this.persistOpacity = e.target.checked; + this.opacity = this.persistOpacity ? 1 : 0.4; + })} + checked={this.persistOpacity} + style={{ position: "absolute", bottom: 5, left: 5 }} + onPointerEnter={action(() => this.labelOpacity = 1)} + onPointerLeave={action(() => this.labelOpacity = 0)} + /> + <p style={{ position: "absolute", bottom: 1, left: 22, opacity: this.labelOpacity, transition: "0.7s opacity ease" }}>opacity {this.persistOpacity ? "persistent" : "on focus"}</p> </div> - <div className="presentation-buttons"> - <button title="Back" className="presentation-button" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></button> - {this.renderPlayPauseButton()} - <button title="Next" className="presentation-button" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></button> + <div className="mainView-libraryHandle" + style={{ cursor: "ew-resize", right: `${width - 10}px`, backgroundColor: "white", opacity: this.opacity, transition: "0.7s opacity ease" }} + onPointerDown={this.onPointerDown} onClick={this.togglePresView}> + <span title="library View Dragger" style={{ width: "100%", height: "100%", position: "absolute" }} /> </div> - <input - type="checkbox" - onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { - this.persistOpacity = e.target.checked; - this.opacity = this.persistOpacity ? 1 : 0.4; - })} - checked={this.persistOpacity} - style={{ position: "absolute", bottom: 5, left: 5 }} - onPointerEnter={action(() => this.labelOpacity = 1)} - onPointerLeave={action(() => this.labelOpacity = 0)} - /> - <p style={{ position: "absolute", bottom: 1, left: 22, opacity: this.labelOpacity, transition: "0.7s opacity ease" }}>opacity {this.persistOpacity ? "persistent" : "on focus"}</p> - <PresentationViewList - mainDocument={this.curPresentation} - deleteDocument={this.RemoveDoc} - gotoDocument={this.gotoDocument} - groupMappings={this.groupMappings} - PresElementsMappings={this.presElementsMappings} - setChildrenDocs={this.setChildrenDocs} - presStatus={this.presStatus} - presButtonBackUp={this.presButtonBackUp} - presGroupBackUp={this.presGroupBackUp} - removeDocByRef={this.removeDocByRef} - clearElemMap={() => this.presElementsMappings.clear()} - /> + {this.renderPresMode()} + </div> ); } |