diff options
Diffstat (limited to 'src/client/views/MainView.tsx')
-rw-r--r-- | src/client/views/MainView.tsx | 667 |
1 files changed, 298 insertions, 369 deletions
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index a244e22e7..90b3d4c1b 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,124 +1,63 @@ -import { IconName, library } from '@fortawesome/fontawesome-svg-core'; -import { faLink, faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faUndoAlt, faTv } from '@fortawesome/free-solid-svg-icons'; +import { library } from '@fortawesome/fontawesome-svg-core'; +import { + faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, + faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter, faMicrophone +} from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import "normalize.css"; import * as React from 'react'; -import { SketchPicker } from 'react-color'; import Measure from 'react-measure'; -import { Doc, DocListCast, Opt, HeightSym } from '../../new_fields/Doc'; -import { List } from '../../new_fields/List'; +import { Doc, DocListCast, Field, FieldResult, Opt } from '../../new_fields/Doc'; import { Id } from '../../new_fields/FieldSymbols'; -import { InkTool } from '../../new_fields/InkField'; +import { List } from '../../new_fields/List'; import { listSpec } from '../../new_fields/Schema'; -import { BoolCast, Cast, FieldValue, StrCast, NumCast } from '../../new_fields/Types'; +import { Cast, FieldValue, StrCast } from '../../new_fields/Types'; import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; import { RouteStore } from '../../server/RouteStore'; -import { emptyFunction, returnOne, returnTrue, Utils, returnEmptyString } from '../../Utils'; +import { emptyFunction, returnEmptyString, returnFalse, returnOne, returnTrue, Utils } from '../../Utils'; +import GoogleAuthenticationManager from '../apis/GoogleAuthenticationManager'; import { DocServer } from '../DocServer'; -import { Docs } from '../documents/Documents'; -import { ClientUtils } from '../util/ClientUtils'; -import { DictationManager } from '../util/DictationManager'; -import { SetupDrag } from '../util/DragManager'; +import { Docs, DocumentOptions } from '../documents/Documents'; import { HistoryUtil } from '../util/History'; +import SharingManager from '../util/SharingManager'; import { Transform } from '../util/Transform'; -import { UndoManager, undoBatch } from '../util/UndoManager'; -import { CollectionBaseView } from './collections/CollectionBaseView'; +import { CollectionLinearView } from './CollectionLinearView'; +import { CollectionViewType, CollectionView } from './collections/CollectionView'; import { CollectionDockingView } from './collections/CollectionDockingView'; -import { CollectionTreeView } from './collections/CollectionTreeView'; import { ContextMenu } from './ContextMenu'; +import { DictationOverlay } from './DictationOverlay'; import { DocumentDecorations } from './DocumentDecorations'; import KeyManager from './GlobalKeyHandler'; -import { InkingControl } from './InkingControl'; -import "./Main.scss"; -import { MainOverlayTextBox } from './MainOverlayTextBox'; +import "./MainView.scss"; +import { MainViewNotifs } from './MainViewNotifs'; import { DocumentView } from './nodes/DocumentView'; import { OverlayView } from './OverlayView'; import PDFMenu from './pdf/PDFMenu'; import { PreviewCursor } from './PreviewCursor'; -import { FilterBox } from './search/FilterBox'; +import { Scripting } from '../util/Scripting'; +import { AudioBox } from './nodes/AudioBox'; +import {Timeline} from './animationtimeline/Timeline'; import { TimelineMenu } from './animationtimeline/TimelineMenu'; -import PresModeMenu from './presentationview/PresentationModeMenu'; -import { PresBox } from './nodes/PresBox'; -import { LinkFollowBox } from './linking/LinkFollowBox'; -import { DocumentManager } from '../util/DocumentManager'; - @observer export class MainView extends React.Component { public static Instance: MainView; - @observable addMenuToggle = React.createRef<HTMLInputElement>(); - @observable public pwidth: number = 0; - @observable public pheight: number = 0; + private _buttonBarHeight = 75; + private _flyoutSizeOnDown = 0; + private _urlState: HistoryUtil.DocUrl; + private _docBtnRef = React.createRef<HTMLDivElement>(); - @observable private dictationState = DictationManager.placeholder; - @observable private dictationSuccessState: boolean | undefined = undefined; - @observable private dictationDisplayState = false; - @observable private dictationListeningState: DictationManager.Controls.ListeningUIStatus = false; - - public overlayTimeout: NodeJS.Timeout | undefined; - - public initiateDictationFade = () => { - let duration = DictationManager.Commands.dictationFadeDuration; - this.overlayTimeout = setTimeout(() => { - this.dictationOverlayVisible = false; - this.dictationSuccess = undefined; - setTimeout(() => this.dictatedPhrase = DictationManager.placeholder, 500); - }, duration); - } + @observable private _panelWidth: number = 0; + @observable private _panelHeight: number = 0; + @observable private _flyoutTranslate: boolean = true; + @observable public flyoutWidth: number = 250; - public cancelDictationFade = () => { - if (this.overlayTimeout) { - clearTimeout(this.overlayTimeout); - this.overlayTimeout = undefined; - } - } + @computed private get userDoc() { return CurrentUserUtils.UserDocument; } + @computed private get mainContainer() { return this.userDoc ? FieldValue(Cast(this.userDoc.activeWorkspace, Doc)) : CurrentUserUtils.GuestWorkspace; } + @computed public get mainFreeform(): Opt<Doc> { return (docs => (docs && docs.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); } - @computed private get mainContainer(): Opt<Doc> { - return FieldValue(Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc)); - } - @computed get mainFreeform(): Opt<Doc> { - let docs = DocListCast(this.mainContainer!.data); - return (docs && docs.length > 1) ? docs[1] : undefined; - } public isPointerDown = false; - private set mainContainer(doc: Opt<Doc>) { - if (doc) { - CurrentUserUtils.UserDocument.activeWorkspace = doc; - } - } - - @computed public get dictatedPhrase() { - return this.dictationState; - } - - public set dictatedPhrase(value: string) { - runInAction(() => this.dictationState = value); - } - - @computed public get dictationSuccess() { - return this.dictationSuccessState; - } - - public set dictationSuccess(value: boolean | undefined) { - runInAction(() => this.dictationSuccessState = value); - } - - @computed public get dictationOverlayVisible() { - return this.dictationDisplayState; - } - - public set dictationOverlayVisible(value: boolean) { - runInAction(() => this.dictationDisplayState = value); - } - - @computed public get isListening() { - return this.dictationListeningState; - } - - public set isListening(value: DictationManager.Controls.ListeningUIStatus) { - runInAction(() => this.dictationListeningState = value); - } componentWillMount() { var tag = document.createElement('script'); @@ -128,27 +67,10 @@ export class MainView extends React.Component { firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag); window.removeEventListener("keydown", KeyManager.Instance.handle); window.addEventListener("keydown", KeyManager.Instance.handle); - - reaction(() => { - let workspaces = CurrentUserUtils.UserDocument.workspaces; - let recent = CurrentUserUtils.UserDocument.recentlyClosed; - if (!(recent instanceof Doc)) return 0; - if (!(workspaces instanceof Doc)) return 0; - let workspacesDoc = workspaces; - let recentDoc = recent; - let libraryHeight = this.getPHeight() - workspacesDoc[HeightSym]() - recentDoc[HeightSym]() - 20 + CurrentUserUtils.UserDocument[HeightSym]() * 0.00001; - return libraryHeight; - }, (libraryHeight: number) => { - if (libraryHeight && Math.abs(CurrentUserUtils.UserDocument[HeightSym]() - libraryHeight) > 5) { - CurrentUserUtils.UserDocument.height = libraryHeight; - } - (Cast(CurrentUserUtils.UserDocument.recentlyClosed, Doc) as Doc).allowClear = true; - }, { fireImmediately: true }); } componentWillUnMount() { window.removeEventListener("keydown", KeyManager.Instance.handle); - //close presentation window.removeEventListener("pointerdown", this.globalPointerDown); window.removeEventListener("pointerup", this.globalPointerUp); } @@ -156,7 +78,7 @@ export class MainView extends React.Component { constructor(props: Readonly<{}>) { super(props); MainView.Instance = this; - + this._urlState = HistoryUtil.parseUrl(window.location) || {} as any; // causes errors to be generated when modifying an observable outside of an action configure({ enforceActions: "observed" }); if (window.location.pathname !== RouteStore.home) { @@ -165,6 +87,11 @@ export class MainView extends React.Component { let type = pathname[0]; if (type === "doc") { CurrentUserUtils.MainDocId = pathname[1]; + if (!this.userDoc) { + runInAction(() => this.flyoutWidth = 0); + DocServer.GetRefField(CurrentUserUtils.MainDocId).then(action((field: Opt<Field>) => + field instanceof Doc && (CurrentUserUtils.GuestTarget = field))); + } } } } @@ -179,7 +106,13 @@ export class MainView extends React.Component { library.add(faGlobeAsia); library.add(faUndoAlt); library.add(faRedoAlt); + library.add(faMousePointer); + library.add(faPen); + library.add(faHighlighter); + library.add(faEraser); + library.add(faFileAudio); library.add(faPenNib); + library.add(faMicrophone); library.add(faFilm); library.add(faMusic); library.add(faTree); @@ -196,12 +129,15 @@ export class MainView extends React.Component { library.add(faArrowUp); library.add(faCloudUploadAlt); library.add(faBolt); + library.add(faChevronRight); + library.add(faEllipsisV); this.initEventListeners(); this.initAuthenticationRouters(); } globalPointerDown = action((e: PointerEvent) => { this.isPointerDown = true; + AudioBox.Enabled = true; const targets = document.elementsFromPoint(e.x, e.y); if (targets && targets.length && targets[0].className.toString().indexOf("contextMenu") === -1) { ContextMenu.Instance.closeMenu(); @@ -214,7 +150,6 @@ export class MainView extends React.Component { 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 @@ -224,74 +159,91 @@ export class MainView extends React.Component { initAuthenticationRouters = async () => { // Load the user's active workspace, or create a new one if initial session after signup - if (!CurrentUserUtils.MainDocId) { - const doc = await Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc); + let received = CurrentUserUtils.MainDocId; + if (received && !this.userDoc) { + reaction( + () => CurrentUserUtils.GuestTarget, + target => target && this.createNewWorkspace(), + { fireImmediately: true } + ); + } else { + if (received && this._urlState.sharing) { + reaction(() => CollectionDockingView.Instance && CollectionDockingView.Instance.initialized, + initialized => initialized && received && DocServer.GetRefField(received).then(field => { + if (field instanceof Doc && field.viewType !== CollectionViewType.Docking) { + CollectionDockingView.AddRightSplit(field, undefined); + } + }), + ); + } + let doc = this.userDoc && await Cast(this.userDoc.activeWorkspace, Doc); if (doc) { this.openWorkspace(doc); } else { this.createNewWorkspace(); } - } else { - DocServer.GetRefField(CurrentUserUtils.MainDocId).then(field => { - field instanceof Doc ? this.openWorkspace(field) : - this.createNewWorkspace(CurrentUserUtils.MainDocId); - }); } } - @action createNewWorkspace = async (id?: string) => { - let workspaces = Cast(CurrentUserUtils.UserDocument.workspaces, Doc); - if (!(workspaces instanceof Doc)) return; - const list = Cast((CurrentUserUtils.UserDocument.workspaces as Doc).data, listSpec(Doc)); - if (list) { - let freeformDoc = Docs.Create.FreeformDocument([], { x: 0, y: 400, width: this.pwidth * .7, height: this.pheight, title: `WS collection ${list.length + 1}` }); - var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(freeformDoc, freeformDoc, 600)] }] }; - let mainDoc = Docs.Create.DockDocument([CurrentUserUtils.UserDocument, freeformDoc], JSON.stringify(dockingLayout), { title: `Workspace ${list.length + 1}` }, id); - if (!CurrentUserUtils.UserDocument.linkManagerDoc) { - let linkManagerDoc = new Doc(); - linkManagerDoc.allLinks = new List<Doc>([]); - CurrentUserUtils.UserDocument.linkManagerDoc = linkManagerDoc; - } - list.push(mainDoc); - // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container) - setTimeout(() => { - this.openWorkspace(mainDoc); - // let pendingDocument = Docs.StackingDocument([], { title: "New Mobile Uploads" }); - // mainDoc.optionalRightCollection = pendingDocument; - }, 0); + let freeformOptions: DocumentOptions = { + x: 0, + y: 400, + width: this._panelWidth * .7, + height: this._panelHeight, + title: "My Blank Collection", + backgroundColor: "white" + }; + let workspaces: FieldResult<Doc>; + let freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); + var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(freeformDoc, freeformDoc, 600)] }] }; + let mainDoc = Docs.Create.DockDocument([freeformDoc], JSON.stringify(dockingLayout), {}, id); + if (this.userDoc && ((workspaces = Cast(this.userDoc.workspaces, Doc)) instanceof Doc)) { + Doc.AddDocToList(workspaces, "data", mainDoc); + mainDoc.title = `Workspace ${DocListCast(workspaces.data).length}`; } + // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container) + setTimeout(() => this.openWorkspace(mainDoc), 0); } @action openWorkspace = async (doc: Doc, fromHistory = false) => { CurrentUserUtils.MainDocId = doc[Id]; - this.mainContainer = doc; - const state = HistoryUtil.parseUrl(window.location) || {} as any; - fromHistory || HistoryUtil.pushState({ type: "doc", docId: doc[Id], readonly: state.readonly, nro: state.nro }); - if (state.readonly === true || state.readonly === null) { + + if (doc) { // this has the side-effect of setting the main container since we're assigning the active/guest workspace + !("presentationView" in doc) && (doc.presentationView = new List<Doc>([Docs.Create.TreeDocument([], { title: "Presentation" })])); + this.userDoc ? (this.userDoc.activeWorkspace = doc) : (CurrentUserUtils.GuestWorkspace = doc); + } + let state = this._urlState; + if (state.sharing === true && !this.userDoc) { DocServer.Control.makeReadOnly(); - } else if (state.safe) { - if (!state.nro) { + } else { + fromHistory || HistoryUtil.pushState({ + type: "doc", + docId: doc[Id], + readonly: state.readonly, + nro: state.nro, + sharing: false, + }); + if (state.readonly === true || state.readonly === null) { DocServer.Control.makeReadOnly(); + } else if (state.safe) { + if (!state.nro) { + DocServer.Control.makeReadOnly(); + } + CollectionView.SetSafeMode(true); + } else if (state.nro || state.nro === null || state.readonly === false) { + } else if (doc.readOnly) { + DocServer.Control.makeReadOnly(); + } else { + DocServer.Control.makeEditable(); } - CollectionBaseView.SetSafeMode(true); - } else if (state.nro || state.nro === null || state.readonly === false) { - } else if (BoolCast(doc.readOnly)) { - DocServer.Control.makeReadOnly(); - } else { - DocServer.Control.makeEditable(); } - const col = await Cast(CurrentUserUtils.UserDocument.optionalRightCollection, Doc); // if there is a pending doc, and it has new data, show it (syip: we use a timeout to prevent collection docking view from being uninitialized) setTimeout(async () => { - if (col) { - const l = Cast(col.data, listSpec(Doc)); - if (l) { - runInAction(() => CollectionTreeView.NotifsCol = col); - } - } + const col = this.userDoc && await Cast(this.userDoc.optionalRightCollection, Doc); + col && Cast(col.data, listSpec(Doc)) && runInAction(() => MainViewNotifs.NotifsCol = col); }, 100); return true; } @@ -304,25 +256,22 @@ export class MainView extends React.Component { @action onResize = (r: any) => { - this.pwidth = r.offset.width; - this.pheight = r.offset.height; - } - getPWidth = () => { - return this.pwidth; - } - getPHeight = () => { - return this.pheight; + this._panelWidth = r.offset.width; + this._panelHeight = r.offset.height; } + getPWidth = () => this._panelWidth; + getPHeight = () => this._panelHeight; + getContentsHeight = () => this._panelHeight - this._buttonBarHeight; - @observable flyoutWidth: number = 250; @computed get dockingContent() { - let flyoutWidth = this.flyoutWidth; - let mainCont = this.mainContainer; + const mainContainer = this.mainContainer; + let flyoutWidth = this.flyoutWidth; // bcz: need to be here because Measure messes with observables. + let flyoutTranslate = this._flyoutTranslate; return <Measure offset onResize={this.onResize}> {({ measureRef }) => - <div ref={measureRef} id="mainContent-div" style={{ width: `calc(100% - ${flyoutWidth}px`, transform: `translate(${flyoutWidth}px, 0px)` }} onDrop={this.onDrop}> - {!mainCont ? (null) : - <DocumentView Document={mainCont} + <div ref={measureRef} id="mainContent-div" style={{ width: `calc(100% - ${flyoutTranslate ? flyoutWidth : 0}px`, transform: `translate(${flyoutTranslate ? flyoutWidth : 0}px, 0px)` }} onDrop={this.onDrop}> + {!mainContainer ? (null) : + <DocumentView Document={mainContainer} DataDoc={undefined} addDocument={undefined} addDocTab={this.addDocTabFunc} @@ -350,9 +299,8 @@ export class MainView extends React.Component { </Measure>; } - _downsize = 0; onPointerDown = (e: React.PointerEvent) => { - this._downsize = e.clientX; + this._flyoutSizeOnDown = e.clientX; document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointermove", this.onPointerMove); @@ -360,15 +308,31 @@ export class MainView extends React.Component { e.stopPropagation(); e.preventDefault(); } + + @action + pointerOverDragger = () => { + if (this.flyoutWidth === 0) { + this.flyoutWidth = 250; + this._flyoutTranslate = false; + } + } + + @action + pointerLeaveDragger = () => { + if (!this._flyoutTranslate) { + this.flyoutWidth = 0; + this._flyoutTranslate = true; + } + } + @action onPointerMove = (e: PointerEvent) => { this.flyoutWidth = Math.max(e.clientX, 0); } @action onPointerUp = (e: PointerEvent) => { - 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._flyoutSizeOnDown) < 4) { + this.flyoutWidth = this.flyoutWidth < 5 ? 250 : 0; } document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); @@ -385,207 +349,172 @@ export class MainView extends React.Component { return CollectionDockingView.AddRightSplit(doc, undefined); } } - @computed - get flyout() { - let sidebar = CurrentUserUtils.UserDocument.sidebar; - if (!(sidebar instanceof Doc)) return (null); - let sidebarDoc = sidebar; - return <DocumentView - Document={sidebarDoc} - DataDoc={undefined} - addDocument={undefined} - addDocTab={this.addDocTabFunc} - pinToPres={emptyFunction} - removeDocument={undefined} - ruleProvider={undefined} - onClick={undefined} - ScreenToLocalTransform={Transform.Identity} - ContentScaling={returnOne} - PanelWidth={this.flyoutWidthFunc} - PanelHeight={this.getPHeight} - renderDepth={0} - focus={emptyFunction} - backgroundColor={returnEmptyString} - parentActive={returnTrue} - whenActiveChanged={emptyFunction} - bringToFront={emptyFunction} - ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} - zoomToScale={emptyFunction} - getScale={returnOne}> - </DocumentView>; - } - @computed - get mainContent() { - let sidebar = CurrentUserUtils.UserDocument.sidebar; - if (!(sidebar instanceof Doc)) return (null); - return <div className="mainContent" style={{ width: "100%", height: "100%", position: "absolute" }}> - <div className="mainView-libraryHandle" - style={{ cursor: "ew-resize", left: `${this.flyoutWidth - 10}px`, backgroundColor: `${StrCast(sidebar.backgroundColor, "lightGray")}` }} - onPointerDown={this.onPointerDown}> - <span title="library View Dragger" style={{ width: "100%", height: "100%", position: "absolute" }} /> - </div> - <div className="mainView-libraryFlyout" style={{ width: `${this.flyoutWidth}px`, zIndex: 1 }}> - {this.flyout} - </div> - {this.dockingContent} - </div>; - } - - selected = (tool: InkTool) => { - if (!InkingControl.Instance || InkingControl.Instance.selectedTool === InkTool.None) return { display: "none" }; - if (InkingControl.Instance.selectedTool === tool) { - return { color: "#61aaa3", fontSize: "50%" }; - } - return { fontSize: "50%" }; - } - - onColorClick = (e: React.MouseEvent) => { - let target = (e.nativeEvent as any).path[0]; - let parent = (e.nativeEvent as any).path[1]; - if (target.localName === "input" || parent.localName === "span") { - e.stopPropagation(); - } - } + mainContainerXf = () => new Transform(0, -this._buttonBarHeight, 1); - toggleLinkFollowBox = (shouldClose: boolean) => { - if (LinkFollowBox.Instance) { - let dvs = DocumentManager.Instance.getDocumentViews(LinkFollowBox.Instance.props.Document); - // if it already exisits, close it - LinkFollowBox.Instance.props.Document.isMinimized = (dvs.length > 0 && shouldClose); + @computed get flyout() { + let sidebarContent = this.userDoc && this.userDoc.sidebarContainer; + if (!(sidebarContent instanceof Doc)) { + return (null); } - } - - @observable private _colorPickerDisplay = false; - /* for the expandable add nodes menu. Not included with the miscbuttons because once it expands it expands the whole div with it, making canvas interactions limited. */ - nodesMenu() { - let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"; - - let addColNode = action(() => Docs.Create.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" })); - let addPresNode = action(() => Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List<Doc>(), { width: 200, height: 500, title: "a presentation trail" })); - let addWebNode = action(() => Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })); - let addDragboxNode = action(() => Docs.Create.DragboxDocument({ width: 40, height: 40, title: "drag collection" })); - let addImageNode = action(() => Docs.Create.ImageDocument(imgurl, { width: 200, title: "an image of a cat" })); - let addButtonDocument = action(() => Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })); - 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: 600, height: 600, title: "youtube search" })); - - let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Doc][] = [ - [React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode], - [React.createRef<HTMLDivElement>(), "tv", "Add Presentation Trail", addPresNode], - [React.createRef<HTMLDivElement>(), "globe-asia", "Add Website", addWebNode], - [React.createRef<HTMLDivElement>(), "bolt", "Add Button", addButtonDocument], - [React.createRef<HTMLDivElement>(), "file", "Add Document Dragger", addDragboxNode], - [React.createRef<HTMLDivElement>(), "cloud-upload-alt", "Import Directory", addImportCollectionNode], //remove at some point in favor of addImportCollectionNode - //[React.createRef<HTMLDivElement>(), "play", "Add Youtube Searcher", addYoutubeSearcher], - ]; - if (!ClientUtils.RELEASE) btns.unshift([React.createRef<HTMLDivElement>(), "cat", "Add Cat Image", addImageNode]); - - return < div id="add-nodes-menu" style={{ left: this.flyoutWidth + 20, bottom: 20 }} > - - <input type="checkbox" id="add-menu-toggle" ref={this.addMenuToggle} /> - <label htmlFor="add-menu-toggle" style={{ marginTop: 2 }} title="Close Menu"><p>+</p></label> - - <div id="add-options-content"> - <ul id="add-options-list"> - <li key="search"><button className="add-button round-button" title="Search" onClick={this.toggleSearch}><FontAwesomeIcon icon="search" size="sm" /></button></li> - <li key="undo"><button className="add-button round-button" title="Undo" style={{ opacity: UndoManager.CanUndo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button></li> - <li key="redo"><button className="add-button round-button" title="Redo" style={{ opacity: UndoManager.CanRedo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button></li> - {btns.map(btn => - <li key={btn[1]} ><div ref={btn[0]}> - <button className="round-button add-button" title={btn[2]} onPointerDown={SetupDrag(btn[0], btn[3])}> - <FontAwesomeIcon icon={btn[1]} size="sm" /> - </button> - </div></li>)} - <li key="linkFollow"><button className="add-button round-button" title="Open Link Follower" onClick={() => this.toggleLinkFollowBox(true)}><FontAwesomeIcon icon="link" size="sm" /></button></li> - <li key="color"><button className="add-button round-button" title="Select Color" style={{ zIndex: 1000 }} onClick={() => this.toggleColorPicker()}><div className="toolbar-color-button" style={{ backgroundColor: InkingControl.Instance.selectedColor }} > - <div className="toolbar-color-picker" onClick={this.onColorClick} style={this._colorPickerDisplay ? { color: "black", display: "block" } : { color: "black", display: "none" }}> - <SketchPicker color={InkingControl.Instance.selectedColor} onChange={InkingControl.Instance.switchColor} /> - </div> - </div></button></li> - <li key="ink" style={{ paddingRight: "6px" }}><button className="toolbar-button round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /> </button></li> - <li key="pen"><button onClick={() => InkingControl.Instance.switchTool(InkTool.Pen)} title="Pen" style={this.selected(InkTool.Pen)}><FontAwesomeIcon icon="pen" size="lg" /></button></li> - <li key="marker"><button onClick={() => InkingControl.Instance.switchTool(InkTool.Highlighter)} title="Highlighter" style={this.selected(InkTool.Highlighter)}><FontAwesomeIcon icon="highlighter" size="lg" /></button></li> - <li key="eraser"><button onClick={() => InkingControl.Instance.switchTool(InkTool.Eraser)} title="Eraser" style={this.selected(InkTool.Eraser)}><FontAwesomeIcon icon="eraser" size="lg" /></button></li> - <li key="inkControls"><InkingControl /></li> - <li key="logout"><button onClick={() => window.location.assign(Utils.prepend(RouteStore.logout))}>Log Out</button></li> - </ul> + let sidebarButtonsDoc = Cast(CurrentUserUtils.UserDocument.sidebarButtons, Doc) as Doc; + sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30; + return <div className="mainView-flyoutContainer" > + <div className="mainView-tabButtons" style={{ height: `${this._buttonBarHeight}px` }}> + <DocumentView + Document={sidebarButtonsDoc} + DataDoc={undefined} + addDocument={undefined} + addDocTab={this.addDocTabFunc} + pinToPres={emptyFunction} + removeDocument={undefined} + ruleProvider={undefined} + onClick={undefined} + ScreenToLocalTransform={Transform.Identity} + ContentScaling={returnOne} + PanelWidth={this.flyoutWidthFunc} + PanelHeight={this.getPHeight} + renderDepth={0} + focus={emptyFunction} + backgroundColor={returnEmptyString} + parentActive={returnTrue} + whenActiveChanged={emptyFunction} + bringToFront={emptyFunction} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + zoomToScale={emptyFunction} + getScale={returnOne}> + </DocumentView> </div> - </div >; - } - - - - @action - toggleColorPicker = (close = false) => { - this._colorPickerDisplay = close ? false : !this._colorPickerDisplay; - } - - /* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */ - @computed - get miscButtons() { - return [ - this.isSearchVisible ? <div className="main-searchDiv" key="search" style={{ top: '34px', right: '1px', position: 'absolute' }} > <FilterBox /> </div> : null, - ]; - - } - - @observable isSearchVisible = false; - @action.bound - toggleSearch = () => { - this.isSearchVisible = !this.isSearchVisible; + <div style={{ position: "relative", height: `calc(100% - ${this._buttonBarHeight}px)`, width: "100%", overflow: "auto" }}> + <DocumentView + Document={sidebarContent} + DataDoc={undefined} + addDocument={undefined} + addDocTab={this.addDocTabFunc} + pinToPres={emptyFunction} + removeDocument={returnFalse} + ruleProvider={undefined} + onClick={undefined} + ScreenToLocalTransform={this.mainContainerXf} + ContentScaling={returnOne} + PanelWidth={this.flyoutWidthFunc} + PanelHeight={this.getContentsHeight} + renderDepth={0} + focus={emptyFunction} + backgroundColor={returnEmptyString} + parentActive={returnTrue} + whenActiveChanged={emptyFunction} + bringToFront={emptyFunction} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + zoomToScale={emptyFunction} + getScale={returnOne}> + </DocumentView> + <button className="mainView-logout" key="logout" onClick={() => window.location.assign(Utils.prepend(RouteStore.logout))}> + {CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"} + </button> + </div></div>; + } + + @computed get mainContent() { + const sidebar = this.userDoc && this.userDoc.sidebarContainer; + return !this.userDoc || !(sidebar instanceof Doc) ? (null) : ( + <div className="mainView-mainContent" > + <div className="mainView-flyoutContainer" onPointerLeave={this.pointerLeaveDragger}> + <div className="mainView-libraryHandle" + style={{ cursor: "ew-resize", left: `${(this.flyoutWidth * (this._flyoutTranslate ? 1 : 0)) - 10}px`, backgroundColor: `${StrCast(sidebar.backgroundColor, "lightGray")}` }} + onPointerDown={this.onPointerDown} onPointerOver={this.pointerOverDragger}> + <span title="library View Dragger" style={{ + width: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "5vw", + height: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "30vh", + position: "absolute", + top: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "" : "-10vh" + }} /> + </div> + <div className="mainView-libraryFlyout" style={{ + width: `${this.flyoutWidth}px`, + zIndex: 1, + transformOrigin: this._flyoutTranslate ? "" : "left center", + transition: this._flyoutTranslate ? "" : "width .5s", + transform: `scale(${this._flyoutTranslate ? 1 : 0.8})`, + boxShadow: this._flyoutTranslate ? "" : "rgb(156, 147, 150) 0.2vw 0.2vw 0.8vw" + }}> + {this.flyout} + {this.expandButton} + </div> + </div> + {this.dockingContent} + </div>); } - private get dictationOverlay() { - let display = this.dictationOverlayVisible; - let success = this.dictationSuccess; - let result = this.isListening && !this.isListening.interim ? DictationManager.placeholder : `"${this.dictatedPhrase}"`; - return ( - <div> - <div - className={"dictation-prompt"} - style={{ - opacity: display ? 1 : 0, - background: success === undefined ? "gainsboro" : success ? "lawngreen" : "red", - borderColor: this.isListening ? "red" : "black", - }} - >{result}</div> - <div - className={"dictation-prompt-overlay"} - style={{ - opacity: display ? 0.4 : 0, - backgroundColor: this.isListening ? "red" : "darkslategrey" - }} - /> - </div> - ); - } + public static expandFlyout = action(() => { + MainView.Instance._flyoutTranslate = true; + MainView.Instance.flyoutWidth = 250; + }); - @computed get miniPresentation() { - let next = () => PresBox.CurrentPresentation.next(); - let back = () => PresBox.CurrentPresentation.back(); - let startOrResetPres = () => PresBox.CurrentPresentation.startOrResetPres(); - let closePresMode = action(() => { PresBox.CurrentPresentation.presMode = false; this.addDocTabFunc(PresBox.CurrentPresentation.props.Document, undefined, "onRight"); }); - return !PresBox.CurrentPresentation || !PresBox.CurrentPresentation.presMode ? (null) : <PresModeMenu next={next} back={back} presStatus={PresBox.CurrentPresentation.presStatus} startOrResetPres={startOrResetPres} closePresMode={closePresMode} > </PresModeMenu>; + @computed get expandButton() { + return !this._flyoutTranslate ? (<div className="mainView-expandFlyoutButton" title="Re-attach sidebar" onPointerDown={MainView.expandFlyout}><FontAwesomeIcon icon="chevron-right" color="grey" size="lg" /></div>) : (null); + } + + addButtonDoc = (doc: Doc) => Doc.AddDocToList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data", doc); + remButtonDoc = (doc: Doc) => Doc.RemoveDocFromList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data", doc); + moveButtonDoc = (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => this.remButtonDoc(doc) && addDocument(doc); + + buttonBarXf = () => { + if (!this._docBtnRef.current) return Transform.Identity(); + let { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current); + return new Transform(-translateX, -translateY, 1 / scale); + } + @computed get docButtons() { + if (CurrentUserUtils.UserDocument.expandingButtons instanceof Doc) { + return <div className="mainView-docButtons" ref={this._docBtnRef} + style={{ left: (this._flyoutTranslate ? this.flyoutWidth : 0) + 20, height: !CurrentUserUtils.UserDocument.expandingButtons.isExpanded ? "42px" : undefined }} > + <MainViewNotifs /> + <CollectionLinearView + Document={CurrentUserUtils.UserDocument.expandingButtons} + DataDoc={undefined} + fieldKey={"data"} + annotationsKey={""} + select={emptyFunction} + chromeCollapsed={true} + active={returnFalse} + isSelected={returnFalse} + moveDocument={this.moveButtonDoc} + CollectionView={undefined} + addDocument={this.addButtonDoc} + addDocTab={this.addDocTabFunc} + pinToPres={emptyFunction} + removeDocument={this.remButtonDoc} + ruleProvider={undefined} + onClick={undefined} + ScreenToLocalTransform={this.buttonBarXf} + ContentScaling={returnOne} + PanelWidth={this.flyoutWidthFunc} + PanelHeight={this.getContentsHeight} + renderDepth={0} + focus={emptyFunction} + whenActiveChanged={emptyFunction} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} /> + </div>; + } + return (null); } render() { - return ( - <div id="main-div"> - {this.dictationOverlay} - <DocumentDecorations /> - {this.mainContent} - {this.miniPresentation} - <PreviewCursor /> - <ContextMenu /> - {this.nodesMenu()} - {this.miscButtons} - <PDFMenu /> - <TimelineMenu/> - <MainOverlayTextBox firstinstance={true} /> - <OverlayView /> - </div > - ); + return (<div id="mainView-container"> + <DictationOverlay /> + <SharingManager /> + <GoogleAuthenticationManager /> + <DocumentDecorations /> + {this.mainContent} + <PreviewCursor /> + <ContextMenu /> + {this.docButtons} + <PDFMenu /> + <OverlayView /> + <TimelineMenu/> + </div >); } } +Scripting.addGlobal(function freezeSidebar() { MainView.expandFlyout(); });
\ No newline at end of file |