import { IconName, library } from '@fortawesome/fontawesome-svg-core'; import { faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup, faPenNib, faThumbtack, faRedoAlt, faTable, faTree, faUndoAlt, faBell, faCommentAlt } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; import { CirclePicker, SliderPicker, BlockPicker, TwitterPicker, SketchPicker } from 'react-color'; import "normalize.css"; import * as React from 'react'; import Measure from 'react-measure'; import * as request from 'request'; import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; import { RouteStore } from '../../server/RouteStore'; import { emptyFunction, returnTrue, Utils, returnOne, returnZero } from '../../Utils'; import { Docs, DocTypes } from '../documents/Documents'; import { SetupDrag, DragManager } from '../util/DragManager'; import { Transform } from '../util/Transform'; import { UndoManager } from '../util/UndoManager'; import { PresentationView } from './PresentationView'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { ContextMenu } from './ContextMenu'; import { DocumentDecorations } from './DocumentDecorations'; import { InkingControl } from './InkingControl'; import "./Main.scss"; import { MainOverlayTextBox } from './MainOverlayTextBox'; import { DocumentView } from './nodes/DocumentView'; import { PreviewCursor } from './PreviewCursor'; import { FilterBox } from './search/FilterBox'; import { SelectionManager } from '../util/SelectionManager'; import { FieldResult, Field, Doc, Opt, DocListCast } from '../../new_fields/Doc'; import { Cast, FieldValue, StrCast, PromiseValue } from '../../new_fields/Types'; import { DocServer } from '../DocServer'; import { listSpec } from '../../new_fields/Schema'; import { Id } from '../../new_fields/FieldSymbols'; import { HistoryUtil } from '../util/History'; import { CollectionBaseView } from './collections/CollectionBaseView'; import PDFMenu from './pdf/PDFMenu'; import { InkTool } from '../../new_fields/InkField'; import * as _ from "lodash"; @observer export class MainView extends React.Component { public static Instance: MainView; @observable private _workspacesShown: boolean = false; @observable public pwidth: number = 0; @observable public pheight: number = 0; @computed private get mainContainer(): Opt { return FieldValue(Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc)); } @computed private get mainFreeform(): Opt { let docs = DocListCast(this.mainContainer!.data); return (docs && docs.length > 1) ? docs[1] : undefined; } private globalDisplayFlags = observable({ jumpToVisible: false }); private set mainContainer(doc: Opt) { if (doc) { if (!("presentationView" in doc)) { doc.presentationView = Docs.TreeDocument([], { title: "Presentation" }); } CurrentUserUtils.UserDocument.activeWorkspace = doc; } } componentWillMount() { document.removeEventListener("keydown", this.globalKeyHandler); document.addEventListener("keydown", this.globalKeyHandler); } componentWillUnMount() { document.removeEventListener("keydown", this.globalKeyHandler); } constructor(props: Readonly<{}>) { super(props); MainView.Instance = this; // causes errors to be generated when modifying an observable outside of an action configure({ enforceActions: "observed" }); if (window.location.search.includes("readonly")) { DocServer.makeReadOnly(); } if (window.location.search.includes("safe")) { if (!window.location.search.includes("nro")) { DocServer.makeReadOnly(); } CollectionBaseView.SetSafeMode(true); } if (window.location.pathname !== RouteStore.home) { let pathname = window.location.pathname.substr(1).split("/"); if (pathname.length > 1) { let type = pathname[0]; if (type === "doc") { CurrentUserUtils.MainDocId = pathname[1]; } } } library.add(faFont); library.add(faImage); library.add(faFilePdf); library.add(faObjectGroup); library.add(faTable); library.add(faGlobeAsia); library.add(faUndoAlt); library.add(faRedoAlt); library.add(faPenNib); library.add(faFilm); library.add(faMusic); library.add(faTree); library.add(faCommentAlt); library.add(faThumbtack); this.initEventListeners(); this.initAuthenticationRouters(); } 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 window.addEventListener("keydown", (e) => { if (e.key === "Escape") { DragManager.AbortDrag(); SelectionManager.DeselectAll(); } else if (e.key === "z" && e.ctrlKey) { e.preventDefault(); UndoManager.Undo(); } else if ((e.key === "y" && e.ctrlKey) || (e.key === "z" && e.ctrlKey && e.shiftKey)) { e.preventDefault(); UndoManager.Redo(); } }, false); // drag event handler // click interactions for the context menu document.addEventListener("pointerdown", action(function (e: PointerEvent) { const targets = document.elementsFromPoint(e.x, e.y); if (targets && targets.length && targets[0].className.toString().indexOf("contextMenu") === -1) { ContextMenu.Instance.closeMenu(); } }), true); } 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); 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) => { const list = Cast(CurrentUserUtils.UserDocument.data, listSpec(Doc)); if (list) { let freeformDoc = Docs.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(CurrentUserUtils.UserDocument, 150), CollectionDockingView.makeDocumentConfig(freeformDoc, 600)] }] }; let mainDoc = Docs.DockDocument([CurrentUserUtils.UserDocument, freeformDoc], JSON.stringify(dockingLayout), { title: `Workspace ${list.length + 1}` }, id); 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); } } @observable _notifsCol: Opt; @action openWorkspace = async (doc: Doc, fromHistory = false) => { CurrentUserUtils.MainDocId = doc[Id]; this.mainContainer = doc; fromHistory || HistoryUtil.pushState({ type: "doc", docId: doc[Id], initializers: {} }); 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(() => this._notifsCol = col); } } }, 100); } openNotifsCol = () => { if (this._notifsCol && CollectionDockingView.Instance) { CollectionDockingView.Instance.AddRightSplit(this._notifsCol); } } onDrop = (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); console.log("Drop"); } @action onResize = (r: any) => { this.pwidth = r.offset.width; this.pheight = r.offset.height; } getPWidth = () => { return this.pwidth; } getPHeight = () => { return this.pheight; } @computed get mainContent() { let mainCont = this.mainContainer; let content = !mainCont ? (null) : ; const pres = mainCont ? FieldValue(Cast(mainCont.presentationView, Doc)) : undefined; return {({ measureRef }) =>
{content} {pres ? : null}
}
; } 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(); } } @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.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" })); let addTreeNode = action(() => CurrentUserUtils.UserDocument); let addImageNode = action(() => Docs.ImageDocument(imgurl, { width: 200, title: "an image of a cat" })); let btns: [React.RefObject, IconName, string, () => Doc][] = [ [React.createRef(), "image", "Add Image", addImageNode], [React.createRef(), "object-group", "Add Collection", addColNode], [React.createRef(), "tree", "Add Tree", addTreeNode], ]; return < div id="add-nodes-menu" >
  • {btns.map(btn =>
  • )}
; } @action toggleColorPicker = () => { this._colorPickerDisplay = !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() { const length = this._notifsCol ? DocListCast(this._notifsCol.data).length : 0; const notifsRef = React.createRef(); const dragNotifs = action(() => this._notifsCol!); let logoutRef = React.createRef(); return [
0 ? { "display": "initial" } : { "display": "none" }}> {length}
, this.isSearchVisible ?
: null,
]; } @observable isSearchVisible = false; @action toggleSearch = () => { this.isSearchVisible = !this.isSearchVisible; } @action globalKeyHandler = (e: KeyboardEvent) => { if (e.key === "Control" || !e.ctrlKey) return; if(e.key === "v") return; if(e.key === "c") return; e.preventDefault(); e.stopPropagation(); switch (e.key) { case "ArrowRight": if (this.mainFreeform) { CollectionDockingView.Instance.AddRightSplit(this.mainFreeform!); } break; case "ArrowLeft": if (this.mainFreeform) { CollectionDockingView.Instance.CloseRightSplit(this.mainFreeform!); } break; case "o": this.globalDisplayFlags.jumpToVisible = true; break; case "escape": _.mapValues(this.globalDisplayFlags, () => false); break; case "f": this.isSearchVisible = !this.isSearchVisible; } } render() { return (
{this.mainContent} {this.nodesMenu()} {this.miscButtons}
); } }