diff options
| author | monikahedman <monika_hedman@brown.edu> | 2019-08-21 10:23:23 -0400 |
|---|---|---|
| committer | monikahedman <monika_hedman@brown.edu> | 2019-08-21 10:23:23 -0400 |
| commit | 4a6231e1631cda4f3f09ce0202538108609a8d47 (patch) | |
| tree | 41a355f31456e9181ec30ba85b746080add71282 /src/client/views/nodes | |
| parent | 2423533d1044ed14b5b356709234bbaa27fd2561 (diff) | |
| parent | 9984f2f19001c07e63738947172e12da25e4498e (diff) | |
pulled from master
Diffstat (limited to 'src/client/views/nodes')
| -rw-r--r-- | src/client/views/nodes/DocumentContentsView.tsx | 5 | ||||
| -rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 71 | ||||
| -rw-r--r-- | src/client/views/nodes/FieldView.tsx | 5 | ||||
| -rw-r--r-- | src/client/views/nodes/FormattedTextBox.tsx | 157 | ||||
| -rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 7 | ||||
| -rw-r--r-- | src/client/views/nodes/KeyValuePair.tsx | 1 | ||||
| -rw-r--r-- | src/client/views/nodes/LinkMenu.tsx | 3 | ||||
| -rw-r--r-- | src/client/views/nodes/LinkMenuItem.tsx | 2 | ||||
| -rw-r--r-- | src/client/views/nodes/PDFBox.tsx | 2 | ||||
| -rw-r--r-- | src/client/views/nodes/PresBox.tsx | 701 |
10 files changed, 906 insertions, 48 deletions
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index c73e960d8..d77662355 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -13,6 +13,7 @@ import { FormattedTextBox } from "./FormattedTextBox"; import { ImageBox } from "./ImageBox"; import { DragBox } from "./DragBox"; import { ButtonBox } from "./ButtonBox"; +import { PresBox } from "./PresBox"; import { IconBox } from "./IconBox"; import { KeyValueBox } from "./KeyValueBox"; import { PDFBox } from "./PDFBox"; @@ -67,7 +68,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { } get dataDoc() { - if (this.props.DataDoc === undefined && this.props.Document.layout instanceof Doc) { + if (this.props.DataDoc === undefined && (this.props.Document.layout instanceof Doc || this.props.Document instanceof Promise)) { // if there is no dataDoc (ie, we're not rendering a template layout), but this document // has a template layout document, then we will render the template layout but use // this document as the data document for the layout. @@ -107,7 +108,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & { if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null); return <ObserverJsxParser blacklistedAttrs={[]} - components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, DragBox, ButtonBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, YoutubeBox }} + components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, DragBox, ButtonBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox }} bindings={this.CreateBindings()} jsx={this.finalLayout} showWarnings={true} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 900cd266e..9f1d98bb5 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -33,7 +33,6 @@ import { DocComponent } from "../DocComponent"; import { EditableView } from '../EditableView'; import { MainView } from '../MainView'; import { OverlayView } from '../OverlayView'; -import { PresentationView } from "../presentationview/PresentationView"; import { ScriptBox } from '../ScriptBox'; import { ScriptingRepl } from '../ScriptingRepl'; import { Template } from "./../Templates"; @@ -41,7 +40,7 @@ import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import { FormattedTextBox } from './FormattedTextBox'; import React = require("react"); -import { IDisposable } from '../../northstar/utils/IDisposable'; +import { PresBox } from './PresBox'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? library.add(fa.faTrash); @@ -98,6 +97,7 @@ export interface DocumentViewProps { whenActiveChanged: (isActive: boolean) => void; bringToFront: (doc: Doc, sendToBack?: boolean) => void; addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void; + pinToPres: (document: Doc) => void; collapseToPoint?: (scrpt: number[], expandedDocs: Doc[] | undefined) => void; zoomToScale: (scale: number) => void; backgroundColor: (doc: Doc) => string | undefined; @@ -139,6 +139,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu private _lastTap: number = 0; private _doubleTap = false; private _hitExpander = false; + private _hitTemplateDrag = false; private _mainCont = React.createRef<HTMLDivElement>(); private _dropDisposer?: DragManager.DragDropDisposer; _animateToIconDisposer?: IReactionDisposer; @@ -228,7 +229,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } get dataDoc() { - if (this.props.DataDoc === undefined && this.props.Document.layout instanceof Doc) { + if (this.props.DataDoc === undefined && (this.props.Document.layout instanceof Doc || this.props.Document instanceof Promise)) { // if there is no dataDoc (ie, we're not rendering a temlplate layout), but this document // has a template layout document, then we will render the template layout but use // this document as the data document for the layout. @@ -236,7 +237,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } return this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined; } - startDragging(x: number, y: number, dropAction: dropActionType, dragSubBullets: boolean) { + startDragging(x: number, y: number, dropAction: dropActionType, dragSubBullets: boolean, applyAsTemplate?: boolean) { if (this._mainCont.current) { let allConnected = [this.props.Document, ...(dragSubBullets ? DocListCast(this.props.Document.subBulletDocs) : [])]; let alldataConnected = [this.dataDoc, ...(dragSubBullets ? DocListCast(this.props.Document.subBulletDocs) : [])]; @@ -247,6 +248,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu dragData.xOffset = xoff; dragData.yOffset = yoff; dragData.moveDocument = this.props.moveDocument; + dragData.applyAsTemplate = applyAsTemplate; DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, { handlers: { dragComplete: action(emptyFunction) @@ -377,15 +379,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } } } + + onPointerDown = (e: React.PointerEvent): void => { if (e.nativeEvent.cancelBubble) return; this._downX = e.clientX; this._downY = e.clientY; this._hitExpander = DocListCast(this.props.Document.subBulletDocs).length > 0; - // if (e.shiftKey && e.buttons === 1 && CollectionDockingView.Instance) { - // CollectionDockingView.Instance.StartOtherDrag(e, [Doc.MakeAlias(this.props.Document)], [this.dataDoc]); - // e.stopPropagation(); - // } else { + this._hitTemplateDrag = false; + for (let element = (e.target as any); element && !this._hitTemplateDrag; element = element.parentElement) { + if (element.className && element.className.toString() === "collectionViewBaseChrome-collapse") { + this._hitTemplateDrag = true; + } + } if (this.active) e.stopPropagation(); // events stop at the lowest document that is active. document.removeEventListener("pointermove", this.onPointerMove); document.addEventListener("pointermove", this.onPointerMove); @@ -399,7 +405,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu if (!e.altKey && !this.topMost && e.buttons === 1 && !BoolCast(this.props.Document.lockedPosition)) { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); - this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitExpander); + this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitExpander, this._hitTemplateDrag); } } e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers @@ -457,6 +463,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Link from ${StrCast(annotationDoc.title)}`); } + if (de.data instanceof DragManager.DocumentDragData && de.data.applyAsTemplate) { + Doc.ApplyTemplateTo(de.data.draggedDocuments[0], this.props.Document, this.props.DataDoc); + e.stopPropagation(); + } if (de.data instanceof DragManager.LinkDragData) { let sourceDoc = de.data.linkSourceDocument; let destDoc = this.props.Document; @@ -573,21 +583,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu cm.addItem({ description: "Open...", subitems: subitems, icon: "external-link-alt" }); let existingMake = ContextMenu.Instance.findByDescription("Make..."); let makes: ContextMenuProps[] = existingMake && "subitems" in existingMake ? existingMake.subitems : []; - makes.push({ description: this.props.Document.isBackground ? "Remove Background" : "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" }); - makes.push({ description: "Edit OnClick script", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onClick") }); + makes.push({ description: this.props.Document.isBackground ? "Remove Background" : "Into Background", event: this.makeBackground, icon: BoolCast(this.props.Document.lockedPosition) ? "unlock" : "lock" }); + makes.push({ description: this.props.Document.isButton ? "Remove Button" : "Into Button", event: this.makeBtnClicked, icon: "concierge-bell" }); + makes.push({ description: "OnClick script", icon: "edit", event: () => ScriptBox.EditClickScript(this.props.Document, "onClick") }); makes.push({ - description: "Make Portal", event: () => { + description: "Into Portal", event: () => { let portal = Docs.Create.FreeformDocument([], { width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: this.props.Document.title + ".portal" }); - //Doc.GetProto(this.props.Document).subBulletDocs = new List<Doc>([portal]); - //summary.proto!.maximizeLocation = "inTab"; // or "inPlace", or "onRight" - //Doc.GetProto(this.props.Document).templates = new List<string>([Templates.Bullet.Layout]); - //let coll = Docs.Create.StackingDocument([this.props.Document, portal], { x: NumCast(this.props.Document.x), y: NumCast(this.props.Document.y), width: this.props.Document[WidthSym]() + 10, height: this.props.Document[HeightSym](), title: this.props.Document.title + ".cont" }); - //this.props.addDocument && this.props.addDocument(coll); - //this.props.removeDocument && this.props.removeDocument(this.props.Document); DocUtils.MakeLink(this.props.Document, portal, undefined, this.props.Document.title + ".portal"); this.makeBtnClicked(); - }, icon: "window-restore" }); !existingMake && cm.addItem({ description: "Make...", subitems: makes, icon: "hand-point-right" }); @@ -614,7 +617,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu let analyzers: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : []; analyzers.push({ description: "Transcribe Speech", event: this.listen, icon: "microphone" }); !existingAnalyze && cm.addItem({ description: "Analyzers...", subitems: analyzers, icon: "hand-point-right" }); - cm.addItem({ description: "Pin to Presentation", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "map-pin" }); + cm.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.Document), icon: "map-pin" }); //I think this should work... and it does! A miracle! cm.addItem({ description: "Add Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) }); cm.addItem({ description: "Download document", icon: "download", event: () => { @@ -661,7 +664,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu try { let stuff = await rp.get(Utils.prepend(RouteStore.getUsers)); const users: User[] = JSON.parse(stuff); - usersMenu = users.filter(({ email }) => email !== CurrentUserUtils.email).map(({ email, userDocumentId }) => ({ + usersMenu = users.filter(({ email }) => email !== Doc.CurrentUserEmail).map(({ email, userDocumentId }) => ({ description: email, event: async () => { const userDocument = await Cast(DocServer.GetRefField(userDocumentId), Doc); if (!userDocument) { @@ -684,6 +687,30 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } runInAction(() => { cm.addItem({ description: "Share...", subitems: usersMenu, icon: "share" }); + if (!ClientUtils.RELEASE) { + let setWriteMode = (mode: DocServer.WriteMode) => { + DocServer.AclsMode = mode; + const mode1 = mode; + const mode2 = mode === DocServer.WriteMode.Default ? mode : DocServer.WriteMode.Playground; + DocServer.setFieldWriteMode("x", mode1); + DocServer.setFieldWriteMode("y", mode1); + DocServer.setFieldWriteMode("width", mode1); + DocServer.setFieldWriteMode("height", mode1); + + DocServer.setFieldWriteMode("panX", mode2); + DocServer.setFieldWriteMode("panY", mode2); + DocServer.setFieldWriteMode("scale", mode2); + DocServer.setFieldWriteMode("viewType", mode2); + }; + let aclsMenu: ContextMenuProps[] = []; + aclsMenu.push({ description: "Default (write/read all)", event: () => setWriteMode(DocServer.WriteMode.Default), icon: DocServer.AclsMode === DocServer.WriteMode.Default ? "check" : "exclamation" }); + aclsMenu.push({ description: "Playground (write own/no read)", event: () => setWriteMode(DocServer.WriteMode.Playground), icon: DocServer.AclsMode === DocServer.WriteMode.Playground ? "check" : "exclamation" }); + aclsMenu.push({ description: "Live Playground (write own/read others)", event: () => setWriteMode(DocServer.WriteMode.LivePlayground), icon: DocServer.AclsMode === DocServer.WriteMode.LivePlayground ? "check" : "exclamation" }); + aclsMenu.push({ description: "Live Readonly (no write/read others)", event: () => setWriteMode(DocServer.WriteMode.LiveReadonly), icon: DocServer.AclsMode === DocServer.WriteMode.LiveReadonly ? "check" : "exclamation" }); + cm.addItem({ description: "Collaboration ACLs...", subitems: aclsMenu, icon: "share" }); + cm.addItem({ description: "Undo Debug Test", event: () => UndoManager.TraceOpenBatches(), icon: "exclamation" }); + } + if (!this.topMost) { // DocumentViews should stop propagation of this event e.stopPropagation(); diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 3287949e2..f0f1b3b73 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -39,6 +39,7 @@ export interface FieldViewProps { selectOnLoad: boolean; addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void; + pinToPres: (document: Doc) => void; removeDocument?: (document: Doc) => boolean; moveDocument?: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; ScreenToLocalTransform: () => Transform; @@ -57,6 +58,7 @@ export interface FieldViewProps { export class FieldView extends React.Component<FieldViewProps> { public static LayoutString(fieldType: { name: string }, fieldStr: string = "data", fieldExt: string = "") { return `<${fieldType.name} {...props} fieldKey={"${fieldStr}"} fieldExt={"${fieldExt}"} />`; + //"<ImageBox {...props} />" } @computed @@ -78,6 +80,9 @@ export class FieldView extends React.Component<FieldViewProps> { else if (field instanceof ImageField) { return <ImageBox {...this.props} leaveNativeSize={true} />; } + // else if (field instaceof PresBox) { + // return <PresBox {...this.props} />; + // } else if (field instanceof IconField) { return <IconBox {...this.props} />; } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index c2015a147..9ae092bad 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,6 +1,6 @@ import { library } from '@fortawesome/fontawesome-svg-core'; -import { faEdit, faSmile, faTextHeight } from '@fortawesome/free-solid-svg-icons'; -import { action, computed, IReactionDisposer, Lambda, observable, reaction } from "mobx"; +import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons'; +import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { baseKeymap } from "prosemirror-commands"; import { history } from "prosemirror-history"; @@ -12,9 +12,9 @@ import { DateField } from '../../../new_fields/DateField'; import { Doc, DocListCast, Opt, WidthSym } from "../../../new_fields/Doc"; import { Copy, Id } from '../../../new_fields/FieldSymbols'; import { List } from '../../../new_fields/List'; -import { RichTextField } from "../../../new_fields/RichTextField"; +import { RichTextField, ToPlainText, FromPlainText } from "../../../new_fields/RichTextField"; +import { BoolCast, Cast, NumCast, StrCast, DateCast } from "../../../new_fields/Types"; import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { BoolCast, Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types"; import { Utils } from '../../../Utils'; import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from '../../documents/Documents'; @@ -29,17 +29,21 @@ import { TooltipTextMenu } from "../../util/TooltipTextMenu"; import { undoBatch, UndoManager } from "../../util/UndoManager"; import { DocComponent } from "../DocComponent"; import { InkingControl } from "../InkingControl"; -import { MainOverlayTextBox } from '../MainOverlayTextBox'; import { FieldView, FieldViewProps } from "./FieldView"; import "./FormattedTextBox.scss"; import React = require("react"); +import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils'; +import { DocumentDecorations } from '../DocumentDecorations'; +import { MainOverlayTextBox } from '../MainOverlayTextBox'; library.add(faEdit); -library.add(faSmile, faTextHeight); +library.add(faSmile, faTextHeight, faUpload); // FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document // +export const Blank = `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`; + export interface FormattedTextBoxProps { isOverlay?: boolean; hideOnLeave?: boolean; @@ -53,9 +57,13 @@ const richTextSchema = createSchema({ documentText: "string" }); +export const GoogleRef = "googleDocId"; + type RichTextDocument = makeInterface<[typeof richTextSchema]>; const RichTextDocument = makeInterface(richTextSchema); +type PullHandler = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => void; + @observer export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxProps), RichTextDocument>(RichTextDocument) { public static LayoutString(fieldStr: string = "data") { @@ -73,8 +81,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe private _textReactionDisposer: Opt<IReactionDisposer>; private _heightReactionDisposer: Opt<IReactionDisposer>; private _proxyReactionDisposer: Opt<IReactionDisposer>; + private pullReactionDisposer: Opt<IReactionDisposer>; + private pushReactionDisposer: Opt<IReactionDisposer>; private dropDisposer?: DragManager.DragDropDisposer; public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } + private isGoogleDocsUpdate = false; @observable _entered = false; @observable public static InputBoxOverlay?: FormattedTextBox = undefined; @@ -289,19 +300,55 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }, { fireImmediately: true }); } + this.pullFromGoogleDoc(this.checkState); + runInAction(() => DocumentDecorations.Instance.isAnimatingFetch = true); + this._reactionDisposer = reaction( () => { const field = this.dataDoc ? Cast(this.dataDoc[this.props.fieldKey], RichTextField) : undefined; - return field ? field.Data : `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`; + return field ? field.Data : Blank; }, - field2 => this._editorView && !this._applyingChange && - this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field2))) + incomingValue => { + if (this._editorView && !this._applyingChange) { + let updatedState = JSON.parse(incomingValue); + this._editorView.updateState(EditorState.fromJSON(config, updatedState)); + // manually sets cursor selection at the end of the text on focus + if (this.isGoogleDocsUpdate) { + this.isGoogleDocsUpdate = false; + let end = this._editorView.state.doc.content.size - 1; + updatedState.selection = { type: "text", anchor: end, head: end }; + this._editorView.updateState(EditorState.fromJSON(config, updatedState)); + } + this.tryUpdateHeight(); + } + } + ); + + this.pullReactionDisposer = reaction( + () => this.props.Document[Pulls], + () => { + if (!DocumentDecorations.hasPulledHack) { + DocumentDecorations.hasPulledHack = true; + let unchanged = this.dataDoc.unchanged; + this.pullFromGoogleDoc(unchanged ? this.checkState : this.updateState); + } + } ); - this.props.isOverlay && (this._heightReactionDisposer = reaction( + this.pushReactionDisposer = reaction( + () => this.props.Document[Pushes], + () => { + if (!DocumentDecorations.hasPushedHack) { + DocumentDecorations.hasPushedHack = true; + this.pushToGoogleDoc(); + } + } + ); + + this._heightReactionDisposer = reaction( () => this.props.Document[WidthSym](), () => this.tryUpdateHeight() - )); + ); this._textReactionDisposer = reaction( () => this.extensionDoc, @@ -332,6 +379,83 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }, { fireImmediately: true }); } + pushToGoogleDoc = async () => { + this.pullFromGoogleDoc(async (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => { + let modes = GoogleApiClientUtils.Docs.WriteMode; + let mode = modes.Replace; + let reference: Opt<GoogleApiClientUtils.Docs.Reference> = Cast(this.dataDoc[GoogleRef], "string"); + if (!reference) { + mode = modes.Insert; + reference = { + title: StrCast(this.dataDoc.title), + handler: id => this.dataDoc[GoogleRef] = id + }; + } + let redo = async () => { + let data = Cast(this.dataDoc.data, RichTextField); + if (this._editorView && reference && data) { + let content = data[ToPlainText](); + let response = await GoogleApiClientUtils.Docs.write({ reference, content, mode }); + let pushSuccess = response !== undefined && !("errors" in response); + dataDoc.unchanged = pushSuccess; + DocumentDecorations.Instance.startPushOutcome(pushSuccess); + } + }; + let undo = () => { + let content = exportState.body; + if (reference && content) { + GoogleApiClientUtils.Docs.write({ reference, content, mode }); + } + }; + UndoManager.AddEvent({ undo, redo }); + redo(); + }); + } + + pullFromGoogleDoc = async (handler: PullHandler) => { + let dataDoc = this.dataDoc; + let documentId = StrCast(dataDoc[GoogleRef]); + let exportState: GoogleApiClientUtils.Docs.ReadResult = {}; + if (documentId) { + exportState = await GoogleApiClientUtils.Docs.read({ documentId }); + } + UndoManager.RunInBatch(() => handler(exportState, dataDoc), Pulls); + } + + updateState = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => { + let pullSuccess = false; + if (exportState !== undefined && exportState.body !== undefined && exportState.title !== undefined) { + let data = Cast(dataDoc.data, RichTextField); + if (data) { + pullSuccess = true; + this.isGoogleDocsUpdate = true; + dataDoc.data = new RichTextField(data[FromPlainText](exportState.body)); + dataDoc.title = exportState.title; + dataDoc.unchanged = true; + } + } else { + delete dataDoc[GoogleRef]; + } + DocumentDecorations.Instance.startPullOutcome(pullSuccess); + this.tryUpdateHeight(); + } + + checkState = (exportState: GoogleApiClientUtils.Docs.ReadResult, dataDoc: Doc) => { + if (exportState !== undefined && exportState.body !== undefined && exportState.title !== undefined) { + let data = Cast(dataDoc.data, RichTextField); + if (data) { + let storedPlainText = data[ToPlainText]() + "\n"; + let receivedPlainText = exportState.body; + let storedTitle = dataDoc.title; + let receivedTitle = exportState.title; + let unchanged = storedPlainText === receivedPlainText && storedTitle === receivedTitle; + dataDoc.unchanged = unchanged; + DocumentDecorations.Instance.setPullState(unchanged); + } + } + } + + clipboardTextSerializer = (slice: Slice): string => { let text = "", separated = true; const from = 0, to = slice.content.size; @@ -450,8 +574,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (this.props.selectOnLoad) { if (!this.props.isOverlay) this.props.select(false); else this._editorView!.focus(); - this.tryUpdateHeight(); } + this.tryUpdateHeight(); } componentWillUnmount() { @@ -459,6 +583,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._reactionDisposer && this._reactionDisposer(); this._proxyReactionDisposer && this._proxyReactionDisposer(); this._textReactionDisposer && this._textReactionDisposer(); + this.pushReactionDisposer && this.pushReactionDisposer(); + this.pullReactionDisposer && this.pullReactionDisposer(); this._heightReactionDisposer && this._heightReactionDisposer(); this._searchReactionDisposer && this._searchReactionDisposer(); } @@ -612,9 +738,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @action tryUpdateHeight() { - if (this.props.Document.autoHeight && this.props.isOverlay) { + if (this.props.Document.autoHeight && this._ref.current!.scrollHeight !== 0) { + console.log("DT = " + this.props.Document.title + " " + this._ref.current!.clientHeight + " " + this._ref.current!.scrollHeight + " " + this._ref.current!.textContent); let xf = this._ref.current!.getBoundingClientRect(); - let scrBounds = this.props.ScreenToLocalTransform().transformBounds(0, 0, xf.width, xf.height); + let scrBounds = this.props.ScreenToLocalTransform().transformBounds(0, 0, xf.width, this._ref.current!.textContent === "" ? 35 : this._ref.current!.scrollHeight); let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0); let dh = NumCast(this.props.Document.height, 0); let sh = scrBounds.height; @@ -641,6 +768,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe // }); // ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems, icon: "text-height" }); } + + render() { let self = this; let style = this.props.isOverlay ? "scroll" : "hidden"; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 708e00576..6fc94a140 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -89,10 +89,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD drop = (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.DocumentDragData) { de.data.droppedDocuments.forEach(action((drop: Doc) => { - if (de.mods === "CtrlKey") { - Doc.ApplyTemplateTo(drop, this.props.Document, this.props.DataDoc); - e.stopPropagation(); - } else if (de.mods === "AltKey" && /*this.dataDoc !== this.props.Document &&*/ drop.data instanceof ImageField) { + 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); e.stopPropagation(); } else if (de.mods === "MetaKey") { @@ -321,7 +318,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD let rotation = NumCast(this.dataDoc.rotation) % 180; let realsize = rotation === 90 || rotation === 270 ? { height: size.width, width: size.height } : size; let aspect = realsize.height / realsize.width; - if (Math.abs(NumCast(layoutdoc.height) - realsize.height) > 1 || Math.abs(NumCast(layoutdoc.width) - realsize.width) > 1) { + if (layoutdoc.nativeHeight !== 0 && layoutdoc.nativeWidth !== 0 && (Math.abs(NumCast(layoutdoc.height) - realsize.height) > 1 || Math.abs(NumCast(layoutdoc.width) - realsize.width) > 1)) { setTimeout(action(() => { layoutdoc.height = layoutdoc[WidthSym]() * aspect; layoutdoc.nativeHeight = realsize.height; diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 534a42efc..8001b24a7 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -68,6 +68,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { PanelWidth: returnZero, PanelHeight: returnZero, addDocTab: returnZero, + pinToPres: returnZero, ContentScaling: returnOne }; let contents = <FieldView {...props} />; diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx index fe7d88457..16e318f7a 100644 --- a/src/client/views/nodes/LinkMenu.tsx +++ b/src/client/views/nodes/LinkMenu.tsx @@ -12,9 +12,6 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; library.add(faTrash); -import { Cast, FieldValue, StrCast } from "../../../new_fields/Types"; -import { Id } from "../../../new_fields/FieldSymbols"; -import { DocumentType } from "../../documents/Documents"; interface Props { docView: DocumentView; diff --git a/src/client/views/nodes/LinkMenuItem.tsx b/src/client/views/nodes/LinkMenuItem.tsx index 1e7ff07c8..9349dbbac 100644 --- a/src/client/views/nodes/LinkMenuItem.tsx +++ b/src/client/views/nodes/LinkMenuItem.tsx @@ -94,7 +94,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> { let dockingFunc = (document: Doc) => { this.props.addDocTab(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, async document => dockingFunc(document), undefined, targetContext!); + DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, async document => dockingFunc(document), undefined, targetContext); } else if (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext) { DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, false, document => dockingFunc(sourceContext!)); diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 6450cb826..18f82ff47 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -198,7 +198,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen <PDFViewer pdf={this._pdf} url={pdfUrl.url.pathname} active={this.props.active} scrollTo={this.scrollTo} loaded={this.loaded} panY={NumCast(this.props.Document.panY)} Document={this.props.Document} DataDoc={this.props.DataDoc} addDocTab={this.props.addDocTab} setPanY={this.setPanY} - addDocument={this.props.addDocument} + pinToPres={this.props.pinToPres} addDocument={this.props.addDocument} fieldKey={this.props.fieldKey} fieldExtensionDoc={this.fieldExtensionDoc} /> {this.settingsPanel()} </div>); diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx new file mode 100644 index 000000000..112d39c32 --- /dev/null +++ b/src/client/views/nodes/PresBox.tsx @@ -0,0 +1,701 @@ +import React = require("react"); +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faArrowLeft, faArrowRight, faEdit, faMinus, faPlay, faPlus, faStop, faTimes } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action, computed, observable, runInAction } from "mobx"; +import { observer } from "mobx-react"; +import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc"; +import { Id } from "../../../new_fields/FieldSymbols"; +import { List } from "../../../new_fields/List"; +import { listSpec } from "../../../new_fields/Schema"; +import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; +import { Utils } from "../../../Utils"; +import { DocumentManager } from "../../util/DocumentManager"; +import { undoBatch } from "../../util/UndoManager"; +import PresentationElement, { buttonIndex } from "../presentationview/PresentationElement"; +import PresentationViewList from "../presentationview/PresentationList"; +import "../presentationview/PresentationView.scss"; +import { FieldView, FieldViewProps } from './FieldView'; +import { ContextMenu } from "../ContextMenu"; + +library.add(faArrowLeft); +library.add(faArrowRight); +library.add(faPlay); +library.add(faStop); +library.add(faPlus); +library.add(faTimes); +library.add(faMinus); +library.add(faEdit); + + +export interface PresViewProps { + Documents: List<Doc>; +} + +const expandedWidth = 450; + +@observer +export class PresBox extends React.Component<FieldViewProps> { //FieldViewProps? + + + public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(PresBox, fieldKey); } + + public static Instance: PresBox; + + //Keeping track of the doc for the current presentation -- bcz: keeping a list of current presentations shouldn't be needed. Let users create them, store them, as they see fit. + @computed get curPresentation() { return this.props.Document; } + + //Mapping from presentation ids to a list of doc that represent a group + @observable groupMappings: Map<String, Doc[]> = new Map(); + //mapping from docs to their rendered component + @observable presElementsMappings: Map<Doc, PresentationElement> = new Map(); + //variable that holds all the docs in the presentation + @observable childrenDocs: Doc[] = []; + //variable to hold if presentation is started + @observable presStatus: boolean = false; + //back-up so that presentation stays the way it's when refreshed + @observable presGroupBackUp: Doc = new Doc(); + @observable presButtonBackUp: Doc = new Doc(); + //Mapping of guids to presentations. + @observable presentationsMapping: Map<String, Doc> = new Map(); + //Mapping of presentations to guid, so that select option values can be given. + @observable presentationsKeyMapping: Map<Doc, String> = new Map(); + //Variable to keep track of guid of the current presentation + @observable currentSelectedPresValue: string | undefined; + //A flag to keep track if title input is open, which is used in rendering. + @observable PresTitleInputOpen: boolean = false; + //Variable that holds reference to title input, so that new presentations get titles assigned. + @observable titleInputElement: HTMLInputElement | undefined; + @observable PresTitleChangeOpen: boolean = false; + + @observable opacity = 1; + @observable persistOpacity = true; + @observable labelOpacity = 0; + @observable presMode = false; + + @observable public static CurrentPresentation: PresBox; + + //initilize class variables + constructor(props: FieldViewProps) { + super(props); + runInAction(() => PresBox.CurrentPresentation = this); + } + + @action + toggle = (forcedValue: boolean | undefined) => { + if (forcedValue !== undefined) { + this.curPresentation.width = forcedValue ? expandedWidth : 0; + } else { + this.curPresentation.width = this.curPresentation.width === expandedWidth ? 0 : expandedWidth; + } + } + + //Second lifecycle function that gets called when component mounts. It makes sure toS + //get the back-up information from previous session for the current presentation. + async componentDidMount() { + this.setPresentationBackUps(); + } + + + /** + * The function that retrieves the backUps for the current Presentation if present, + * otherwise initializes. + */ + setPresentationBackUps = async () => { + //getting both backUp documents + + let castedGroupBackUp = Cast(this.curPresentation.presGroupBackUp, Doc); + let castedButtonBackUp = Cast(this.curPresentation.presButtonBackUp, Doc); + //if instantiated before + if (castedGroupBackUp instanceof Promise) { + castedGroupBackUp.then(doc => { + let toAssign = doc ? doc : new Doc(); + this.curPresentation.presGroupBackUp = toAssign; + runInAction(() => this.presGroupBackUp = toAssign); + if (doc) { + if (toAssign[Id] === doc[Id]) { + this.retrieveGroupMappings(); + } + } + }); + + //if never instantiated a store doc yet + } else if (castedGroupBackUp instanceof Doc) { + let castedDoc: Doc = await castedGroupBackUp; + runInAction(() => this.presGroupBackUp = castedDoc); + this.retrieveGroupMappings(); + } else { + runInAction(() => { + let toAssign = new Doc(); + this.presGroupBackUp = toAssign; + this.curPresentation.presGroupBackUp = toAssign; + + }); + + } + //if instantiated before + if (castedButtonBackUp instanceof Promise) { + castedButtonBackUp.then(doc => { + let toAssign = doc ? doc : new Doc(); + this.curPresentation.presButtonBackUp = toAssign; + runInAction(() => this.presButtonBackUp = toAssign); + }); + + //if never instantiated a store doc yet + } else if (castedButtonBackUp instanceof Doc) { + let castedDoc: Doc = await castedButtonBackUp; + runInAction(() => this.presButtonBackUp = castedDoc); + + } else { + runInAction(() => { + let toAssign = new Doc(); + this.presButtonBackUp = toAssign; + this.curPresentation.presButtonBackUp = toAssign; + }); + + } + + + //storing the presentation status,ie. whether it was stopped or playing + let presStatusBackUp = BoolCast(this.curPresentation.presStatus); + runInAction(() => this.presStatus = presStatusBackUp); + } + + /** + * This is the function that is called to retrieve the groups that have been stored and + * push them to the groupMappings. + */ + retrieveGroupMappings = async () => { + let castedGroupDocs = await DocListCastAsync(this.presGroupBackUp.groupDocs); + if (castedGroupDocs !== undefined) { + castedGroupDocs.forEach(async (groupDoc: Doc, index: number) => { + let castedGrouping = await DocListCastAsync(groupDoc.grouping); + let castedKey = StrCast(groupDoc.presentIdStore, null); + if (castedGrouping) { + castedGrouping.forEach((doc: Doc) => { + doc.presentId = castedKey; + }); + } + if (castedGrouping !== undefined && castedKey !== undefined) { + this.groupMappings.set(castedKey, castedGrouping); + } + }); + } + } + + //observable means render is re-called every time variable is changed + @observable + collapsed: boolean = false; + next = async () => { + const current = NumCast(this.curPresentation.selectedDoc); + //asking to get document at current index + let docAtCurrentNext = await this.getDocAtIndex(current + 1); + if (docAtCurrentNext === undefined) { + return; + } + //asking for it's presentation id + let curNextPresId = StrCast(docAtCurrentNext.presentId); + let nextSelected = current + 1; + + //if curDoc is in a group, selection slides until last one, if not it's next one + if (this.groupMappings.has(curNextPresId)) { + let currentsArray = this.groupMappings.get(StrCast(docAtCurrentNext.presentId))!; + nextSelected = current + currentsArray.length - currentsArray.indexOf(docAtCurrentNext); + + //end of grup so go beyond + if (nextSelected === current) nextSelected = current + 1; + } + + this.gotoDocument(nextSelected, current); + + } + back = async () => { + const current = NumCast(this.curPresentation.selectedDoc); + //requesting for the doc at current index + let docAtCurrent = await this.getDocAtIndex(current); + if (docAtCurrent === undefined) { + return; + } + + //asking for its presentation id. + let curPresId = StrCast(docAtCurrent.presentId); + let prevSelected = current - 1; + let zoomOut: boolean = false; + + //checking if this presentation id is mapped to a group, if so chosing the first element in group + if (this.groupMappings.has(curPresId)) { + let currentsArray = this.groupMappings.get(StrCast(docAtCurrent.presentId))!; + prevSelected = current - currentsArray.length + (currentsArray.length - currentsArray.indexOf(docAtCurrent)) - 1; + //end of grup so go beyond + if (prevSelected === current) prevSelected = current - 1; + + //checking if any of the group members had used zooming in + currentsArray.forEach((doc: Doc) => { + //let presElem: PresentationElement | undefined = this.presElementsMappings.get(doc); + if (this.presElementsMappings.get(doc)!.selected[buttonIndex.Show]) { + zoomOut = true; + return; + } + }); + + } + + // if a group set that flag to zero or a single element + //If so making sure to zoom out, which goes back to state before zooming action + if (current > 0) { + if (zoomOut || this.presElementsMappings.get(docAtCurrent)!.selected[buttonIndex.Show]) { + let prevScale = NumCast(this.childrenDocs[prevSelected].viewScale, null); + let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[current]); + if (prevScale !== undefined) { + if (prevScale !== curScale) { + DocumentManager.Instance.zoomIntoScale(docAtCurrent, prevScale); + } + } + } + } + this.gotoDocument(prevSelected, current); + + } + + /** + * This is the method that checks for the actions that need to be performed + * after the document has been presented, which involves 3 button options: + * Hide Until Presented, Hide After Presented, Fade After Presented + */ + showAfterPresented = (index: number) => { + this.presElementsMappings.forEach((presElem: PresentationElement, key: Doc) => { + let selectedButtons: boolean[] = presElem.selected; + //the order of cases is aligned based on priority + if (selectedButtons[buttonIndex.HideTillPressed]) { + if (this.childrenDocs.indexOf(key) <= index) { + key.opacity = 1; + } + } + if (selectedButtons[buttonIndex.HideAfter]) { + if (this.childrenDocs.indexOf(key) < index) { + key.opacity = 0; + } + } + if (selectedButtons[buttonIndex.FadeAfter]) { + if (this.childrenDocs.indexOf(key) < index) { + key.opacity = 0.5; + } + } + }); + } + + /** + * This is the method that checks for the actions that need to be performed + * before the document has been presented, which involves 3 button options: + * Hide Until Presented, Hide After Presented, Fade After Presented + */ + hideIfNotPresented = (index: number) => { + this.presElementsMappings.forEach((presElem: PresentationElement, key: Doc) => { + let selectedButtons: boolean[] = presElem.selected; + + //the order of cases is aligned based on priority + + if (selectedButtons[buttonIndex.HideAfter]) { + if (this.childrenDocs.indexOf(key) >= index) { + key.opacity = 1; + } + } + if (selectedButtons[buttonIndex.FadeAfter]) { + if (this.childrenDocs.indexOf(key) >= index) { + key.opacity = 1; + } + } + if (selectedButtons[buttonIndex.HideTillPressed]) { + if (this.childrenDocs.indexOf(key) > index) { + key.opacity = 0; + } + } + }); + } + + /** + * This method makes sure that cursor navigates to the element that + * has the option open and last in the group. If not in the group, and it has + * te option open, navigates to that element. + */ + navigateToElement = async (curDoc: Doc, fromDoc: number) => { + let docToJump: Doc = curDoc; + let curDocPresId = StrCast(curDoc.presentId, null); + let willZoom: boolean = false; + + //checking if in group + if (curDocPresId !== undefined) { + if (this.groupMappings.has(curDocPresId)) { + let currentDocGroup = this.groupMappings.get(curDocPresId)!; + currentDocGroup.forEach((doc: Doc, index: number) => { + let selectedButtons: boolean[] = this.presElementsMappings.get(doc)!.selected; + if (selectedButtons[buttonIndex.Navigate]) { + docToJump = doc; + willZoom = false; + } + if (selectedButtons[buttonIndex.Show]) { + docToJump = doc; + willZoom = true; + } + }); + } + + } + //docToJump stayed same meaning, it was not in the group or was the last element in the group + if (docToJump === curDoc) { + //checking if curDoc has navigation open + let curDocButtons = this.presElementsMappings.get(curDoc)!.selected; + if (curDocButtons[buttonIndex.Navigate]) { + DocumentManager.Instance.jumpToDocument(curDoc, false); + } 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); + let newScale = DocumentManager.Instance.getScaleOfDocView(curDoc); + curDoc.viewScale = newScale; + + //saving the scale user was on before zooming in + if (curScale !== 1) { + this.childrenDocs[fromDoc].viewScale = curScale; + } + + } + return; + } + 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(docToJump, willZoom); + let newScale = DocumentManager.Instance.getScaleOfDocView(curDoc); + curDoc.viewScale = newScale; + //saving the scale that user was on + if (curScale !== 1) { + this.childrenDocs[fromDoc].viewScale = curScale; + } + + } + + /** + * Async function that supposedly return the doc that is located at given index. + */ + getDocAtIndex = async (index: number) => { + const list = FieldValue(Cast(this.curPresentation.data, listSpec(Doc))); + if (!list) { + return undefined; + } + if (index < 0 || index >= list.length) { + return undefined; + } + + this.curPresentation.selectedDoc = index; + //awaiting async call to finish to get Doc instance + const doc = await list[index]; + return doc; + } + + /** + * The function that removes a doc from a presentation. It also makes sure to + * do necessary updates to backUps and mappings stored locally. + */ + @action + public RemoveDoc = async (index: number) => { + const value = FieldValue(Cast(this.curPresentation.data, listSpec(Doc))); + if (value) { + let removedDoc = await value.splice(index, 1)[0]; + + //removing the Presentation Element stored for it + this.presElementsMappings.delete(removedDoc); + + let removedDocPresentId = StrCast(removedDoc.presentId); + + //Removing it from local mapping of the groups + if (this.groupMappings.has(removedDocPresentId)) { + let removedDocsGroup = this.groupMappings.get(removedDocPresentId); + if (removedDocsGroup) { + removedDocsGroup.splice(removedDocsGroup.indexOf(removedDoc), 1); + if (removedDocsGroup.length === 0) { + this.groupMappings.delete(removedDocPresentId); + } + } + } + + //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) { + for (let doc of castedList) { + let curDoc = await doc; + let curDocId = StrCast(curDoc.docId); + if (curDocId === removedDoc[Id]) { + castedList.splice(castedList.indexOf(curDoc), 1); + break; + + } + } + } + + //removing it from the backup of groups + let castedGroupDocs = await DocListCastAsync(this.presGroupBackUp.groupDocs); + if (castedGroupDocs) { + castedGroupDocs.forEach(async (groupDoc: Doc, index: number) => { + let castedKey = StrCast(groupDoc.presentIdStore, null); + if (castedKey === removedDocPresentId) { + let castedGrouping = await DocListCastAsync(groupDoc.grouping); + if (castedGrouping) { + castedGrouping.splice(castedGrouping.indexOf(removedDoc), 1); + if (castedGrouping.length === 0) { + castedGroupDocs!.splice(castedGroupDocs!.indexOf(groupDoc), 1); + } + } + } + + }); + + } + + + } + } + + 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; + } + return false; + } + + //The function that is called when a document is clicked or reached through next or back. + //it'll also execute the necessary actions if presentation is playing. + @action + public gotoDocument = async (index: number, fromDoc: number) => { + const list = FieldValue(Cast(this.curPresentation.data, listSpec(Doc))); + if (!list) { + return; + } + if (index < 0 || index >= list.length) { + return; + } + this.curPresentation.selectedDoc = index; + + if (!this.presStatus) { + this.presStatus = true; + this.startPresentation(index); + } + + const doc = await list[index]; + if (this.presStatus) { + this.navigateToElement(doc, fromDoc); + this.hideIfNotPresented(index); + this.showAfterPresented(index); + } + + } + + //Function that is called to resetGroupIds, so that documents get new groupIds at + //first load, when presentation is changed. + resetGroupIds = async () => { + let castedGroupDocs = await DocListCastAsync(this.presGroupBackUp.groupDocs); + if (castedGroupDocs !== undefined) { + castedGroupDocs.forEach(async (groupDoc: Doc, index: number) => { + let castedGrouping = await DocListCastAsync(groupDoc.grouping); + if (castedGrouping) { + castedGrouping.forEach((doc: Doc) => { + doc.presentId = Utils.GenerateGuid(); + }); + } + }); + } + runInAction(() => this.groupMappings = new Map()); + } + + //Function that sets the store of the children docs. + @action + setChildrenDocs = (docList: Doc[]) => { + this.childrenDocs = docList; + } + + //The function that is called to render the play or pause button depending on + //if presentation is running or not. + renderPlayPauseButton = () => { + if (this.presStatus) { + return <button title="Reset Presentation" className="presentation-button" onClick={this.startOrResetPres}><FontAwesomeIcon icon="stop" /></button>; + } else { + return <button title="Start Presentation From Start" className="presentation-button" onClick={this.startOrResetPres}><FontAwesomeIcon icon="play" /></button>; + } + } + + //The function that starts or resets presentaton functionally, depending on status flag. + @action + startOrResetPres = () => { + if (this.presStatus) { + this.resetPresentation(); + } else { + this.presStatus = true; + this.startPresentation(0); + const current = NumCast(this.curPresentation.selectedDoc); + this.gotoDocument(0, current); + } + this.curPresentation.presStatus = this.presStatus; + } + + //The function that resets the presentation by removing every action done by it. It also + //stops the presentaton. + @action + resetPresentation = () => { + this.childrenDocs.forEach((doc: Doc) => { + doc.opacity = 1; + doc.viewScale = 1; + }); + this.curPresentation.selectedDoc = 0; + this.presStatus = false; + this.curPresentation.presStatus = this.presStatus; + if (this.childrenDocs.length === 0) { + return; + } + DocumentManager.Instance.zoomIntoScale(this.childrenDocs[0], 1); + } + + + //The function that starts the presentation, also checking if actions should be applied + //directly at start. + startPresentation = (startIndex: number) => { + let selectedButtons: boolean[]; + this.presElementsMappings.forEach((component: PresentationElement, doc: Doc) => { + selectedButtons = component.selected; + if (selectedButtons[buttonIndex.HideTillPressed]) { + if (this.childrenDocs.indexOf(doc) > startIndex) { + doc.opacity = 0; + } + + } + if (selectedButtons[buttonIndex.HideAfter]) { + if (this.childrenDocs.indexOf(doc) < startIndex) { + doc.opacity = 0; + } + } + if (selectedButtons[buttonIndex.FadeAfter]) { + if (this.childrenDocs.indexOf(doc) < startIndex) { + doc.opacity = 0.5; + } + } + + }); + + } + + + /** + * The function that is called to render either select for presentations, or title inputting. + */ + renderSelectOrPresSelection = () => { + if (this.PresTitleInputOpen || this.PresTitleChangeOpen) { + return <input ref={(e) => this.titleInputElement = e!} type="text" className="presentationView-title" placeholder="Enter Name!" onKeyDown={this.submitPresentationTitle} />; + } else { + return (null); + } + } + + /** + * The function that is called on enter press of title input. It gives the + * new presentation the title user entered. If nothing is entered, gives a default title. + */ + @action + submitPresentationTitle = (e: React.KeyboardEvent) => { + if (e.keyCode === 13) { + let presTitle = this.titleInputElement!.value; + this.titleInputElement!.value = ""; + if (this.PresTitleChangeOpen) { + this.PresTitleChangeOpen = false; + this.changePresentationTitle(presTitle); + } + } + } + /** + * The function that is called to change title of presentation to what user entered. + */ + @undoBatch + changePresentationTitle = (newTitle: string) => { + if (newTitle === "") { + return; + } + this.curPresentation.title = newTitle; + } + + addPressElem = (keyDoc: Doc, elem: PresentationElement) => { + this.presElementsMappings.set(keyDoc, elem); + } + + minimize = undoBatch(action(() => { + this.presMode = true; + this.props.addDocTab && this.props.addDocTab(this.props.Document, this.props.DataDoc, "close"); + })); + + specificContextMenu = (e: React.MouseEvent): void => { + ContextMenu.Instance.addItem({ description: "Make Current Presentation", event: action(() => Doc.UserDoc().curPresentation = this.props.Document), icon: "asterisk" }); + } + + render() { + + let width = "100%"; //NumCast(this.curPresentation.width) + return ( + <div className="presentationView-cont" onPointerEnter={action(() => !this.persistOpacity && (this.opacity = 1))} onContextMenu={this.specificContextMenu} + onPointerLeave={action(() => !this.persistOpacity && (this.opacity = 0.4))} + style={{ width: width, opacity: this.opacity, }}> + <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> + <button title="Minimize" className="presentation-button" onClick={this.minimize}><FontAwesomeIcon icon={"eye"} /></button> + </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()} + /> + </div> + ); + } + + +}
\ No newline at end of file |
