From 9636494fa599862607e8658ef6e6a83a5f527dd8 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 5 Feb 2020 21:23:37 -0500
Subject: fixed pivot view rendering with text.  fixed dragging items with
 click handlers.

---
 .../CollectionFreeFormLayoutEngines.tsx            | 29 ++++++++++++++++------
 1 file changed, 22 insertions(+), 7 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx')

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index f35662dab..cee8f159e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -57,6 +57,21 @@ function toLabel(target: FieldResult<Field>) {
         return target[ToString]();
     }
     return String(target);
+}/**
+ * Uses canvas.measureText to compute and return the width of the given text of given font in pixels.
+ * 
+ * @param {String} text The text to be rendered.
+ * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana").
+ * 
+ * @see https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
+ */
+function getTextWidth(text: string, font: string): number {
+    // re-use canvas object for better performance
+    var canvas = (getTextWidth as any).canvas || ((getTextWidth as any).canvas = document.createElement("canvas"));
+    var context = canvas.getContext("2d");
+    context.font = font;
+    var metrics = context.measureText(text);
+    return metrics.width;
 }
 
 export function computePivotLayout(
@@ -69,7 +84,6 @@ export function computePivotLayout(
 ) {
     const fieldKey = "data";
     const pivotColumnGroups = new Map<FieldResult<Field>, Doc[]>();
-    const fontSize = NumCast(pivotDoc[fieldKey + "-timelineFontSize"], panelDim[1] > 58 ? 20 : Math.max(7, panelDim[1] / 3));
 
     const pivotFieldKey = toLabel(pivotDoc.pivotField);
     for (const doc of childDocs) {
@@ -91,10 +105,14 @@ export function computePivotLayout(
             }
         }
     }
+    const fontSize = NumCast(pivotDoc[fieldKey + "-timelineFontSize"], panelDim[1] > 58 ? 20 : Math.max(7, panelDim[1] / 3));
+    const desc = `${fontSize}px ${getComputedStyle(document.body).fontFamily}`;
+    const textlen = Array.from(pivotColumnGroups.keys()).map(c => getTextWidth(toLabel(c), desc)).reduce((p, c) => Math.max(p, c), 0 as number);
+    const max_text = Math.min(Math.ceil(textlen / 120) * 28, panelDim[1]/2);
     let maxInColumn = Array.from(pivotColumnGroups.values()).reduce((p, s) => Math.max(p, s.length), 1);
 
     const colWidth = panelDim[0] / pivotColumnGroups.size;
-    const colHeight = panelDim[1];
+    const colHeight = panelDim[1] - max_text;
     let numCols = 0;
     let bestArea = 0;
     let pivotAxisWidth = 0;
@@ -103,9 +121,8 @@ export function computePivotLayout(
         const hd = colHeight / numInCol;
         const wd = colWidth / i;
         const dim = Math.min(hd, wd);
-        const area = dim * dim * i * numInCol;
-        if (area > bestArea) {
-            bestArea = area;
+        if (dim > bestArea) {
+            bestArea = dim;
             numCols = i;
             pivotAxisWidth = dim;
         }
@@ -117,13 +134,11 @@ export function computePivotLayout(
     const expander = 1.05;
     const gap = .15;
     let x = 0;
-    let max_text = 60;
     Array.from(pivotColumnGroups.keys()).sort().forEach(key => {
         const val = pivotColumnGroups.get(key)!;
         let y = 0;
         let xCount = 0;
         const text = toLabel(key);
-        max_text = Math.max(max_text, Math.min(500, text.length));
         groupNames.push({
             type: "text",
             text,
-- 
cgit v1.2.3-70-g09d2


From 829872a08cd70675990b96d7388e886d57283c27 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 5 Feb 2020 23:29:46 -0500
Subject: added additional view modes for presenations

---
 .../views/collections/CollectionCarouselView.tsx   |  3 -
 .../CollectionFreeFormLayoutEngines.tsx            |  4 +-
 src/client/views/nodes/DocumentView.tsx            | 22 ++----
 src/client/views/nodes/PresBox.scss                | 17 +++-
 src/client/views/nodes/PresBox.tsx                 | 90 +++++++++++++++-------
 .../views/presentationview/PresElementBox.tsx      |  2 +-
 6 files changed, 82 insertions(+), 56 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx')

diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index e30d08592..00edf71dd 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -13,9 +13,6 @@ import { faCaretLeft, faCaretRight } from '@fortawesome/free-solid-svg-icons';
 import { Doc } from '../../../new_fields/Doc';
 import { FormattedTextBox } from '../nodes/FormattedTextBox';
 
-
-
-
 type CarouselDocument = makeInterface<[typeof documentSchema,]>;
 const CarouselDocument = makeInterface(documentSchema);
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index cee8f159e..84acbe0f0 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -108,7 +108,7 @@ export function computePivotLayout(
     const fontSize = NumCast(pivotDoc[fieldKey + "-timelineFontSize"], panelDim[1] > 58 ? 20 : Math.max(7, panelDim[1] / 3));
     const desc = `${fontSize}px ${getComputedStyle(document.body).fontFamily}`;
     const textlen = Array.from(pivotColumnGroups.keys()).map(c => getTextWidth(toLabel(c), desc)).reduce((p, c) => Math.max(p, c), 0 as number);
-    const max_text = Math.min(Math.ceil(textlen / 120) * 28, panelDim[1]/2);
+    const max_text = Math.min(Math.ceil(textlen / 120) * 28, panelDim[1] / 2);
     let maxInColumn = Array.from(pivotColumnGroups.values()).reduce((p, s) => Math.max(p, s.length), 1);
 
     const colWidth = panelDim[0] / pivotColumnGroups.size;
@@ -171,7 +171,7 @@ export function computePivotLayout(
         x += pivotAxisWidth * (numCols * expander + gap);
     });
 
-    const maxColHeight = pivotAxisWidth * Math.ceil(maxInColumn / numCols) + pivotAxisWidth;
+    const maxColHeight = pivotAxisWidth * expander * Math.ceil(maxInColumn / numCols);
     const dividers = Array.from(pivotColumnGroups.values()).map((pg, i) =>
         ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap), y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight } as any));
     groupNames.push(...dividers);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 189c06288..06e9209d3 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -467,10 +467,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     this._hitTemplateDrag = true;
                 }
             }
-            if ((this.active || this.Document.onDragStart || this.onClickHandler) && 
-                !e.ctrlKey && 
-                (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) && 
-                !this.Document.lockedPosition && 
+            if ((this.active || this.Document.onDragStart || this.onClickHandler) &&
+                !e.ctrlKey &&
+                (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) &&
+                !this.Document.lockedPosition &&
                 !this.Document.inOverlay) {
                 e.stopPropagation(); // events stop at the lowest document that is active.  if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag);
             }
@@ -616,18 +616,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         }
     }
 
-    @action
-    onDrop = (e: React.DragEvent) => {
-        const text = e.dataTransfer.getData("text/plain");
-        if (!e.isDefaultPrevented() && text && text.startsWith("<div")) {
-            const oldLayout = this.Document.layout || "";
-            const layout = text.replace("{layout}", oldLayout);
-            this.Document.layout = layout;
-            e.stopPropagation();
-            e.preventDefault();
-        }
-    }
-
     @undoBatch
     @action
     freezeNativeDimensions = (): void => {
@@ -981,7 +969,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         let highlighting = fullDegree && this.layoutDoc.type !== DocumentType.FONTICON && this.layoutDoc._viewType !== CollectionViewType.Linear;
         highlighting = highlighting && this.props.focus !== emptyFunction;  // bcz: hack to turn off highlighting onsidebar panel documents.  need to flag a document as not highlightable in a more direct way
         return <div className={`documentView-node${this.topMost ? "-topmost" : ""}`} ref={this._mainCont} onKeyDown={this.onKeyDown}
-            onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
+            onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
             onPointerEnter={e => Doc.BrushDoc(this.props.Document)} onPointerLeave={e => Doc.UnBrushDoc(this.props.Document)}
             style={{
                 transition: this.Document.isAnimating ? ".5s linear" : StrCast(this.Document.transition),
diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/PresBox.scss
index 3e1967a18..7618aa7e3 100644
--- a/src/client/views/nodes/PresBox.scss
+++ b/src/client/views/nodes/PresBox.scss
@@ -14,10 +14,6 @@
     transition: 0.7s opacity ease;
     pointer-events: all;
 
-    .presBox-listCont {
-        position: relative;
-    }
-
     .presBox-buttons {
         padding: 10px;
         width: 100%;
@@ -28,4 +24,17 @@
             border-radius: 5px;
         }
     }
+    .presBox-backward, .presBox-forward {
+        width: 25px;
+        border-radius: 5px;
+        top:50%;
+        position: absolute;
+        display: inline-block;
+    }
+    .presBox-backward {
+        left:5;
+    }
+    .presBox-forward {
+        right:5;
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 2b195855c..44405c413 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -2,7 +2,7 @@ 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, IReactionDisposer, reaction } from "mobx";
+import { action, computed, IReactionDisposer, reaction, observable } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
 import { listSpec } from "../../../new_fields/Schema";
@@ -16,6 +16,10 @@ import { CollectionView, CollectionViewType } from "../collections/CollectionVie
 import { ContextMenu } from "../ContextMenu";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./PresBox.scss";
+import { CollectionCarouselView } from "../collections/CollectionCarouselView";
+import { returnFalse } from "../../../Utils";
+import { ContextMenuProps } from "../ContextMenuItem";
+import { CollectionTimeView } from "../collections/CollectionTimeView";
 
 library.add(faArrowLeft);
 library.add(faArrowRight);
@@ -30,18 +34,26 @@ library.add(faEdit);
 export class PresBox extends React.Component<FieldViewProps> {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); }
     _childReaction: IReactionDisposer | undefined;
+    _slideshowReaction: IReactionDisposer | undefined;
     componentDidMount() {
         const userDoc = CurrentUserUtils.UserDocument;
-        let presTemp = Cast(userDoc.presentationTemplate, Doc);
-        if (presTemp instanceof Promise) {
-            presTemp.then(presTemp => this.props.Document.childLayout = presTemp);
-        }
-        else if (presTemp === undefined) {
-            presTemp = userDoc.presentationTemplate = Docs.Create.PresElementBoxDocument({ backgroundColor: "transparent", _xMargin: 5, isTemplateDoc: true, isTemplateForField: "data" });
-        }
-        else {
-            this.props.Document.childLayout = presTemp;
-        }
+        this._slideshowReaction = reaction(() => this.props.Document._slideshow,
+            (slideshow) => {
+                if (!slideshow) {
+                    let presTemp = Cast(userDoc.presentationTemplate, Doc);
+                    if (presTemp instanceof Promise) {
+                        presTemp.then(presTemp => this.props.Document.childLayout = presTemp);
+                    }
+                    else if (presTemp === undefined) {
+                        presTemp = userDoc.presentationTemplate = Docs.Create.PresElementBoxDocument({ backgroundColor: "transparent", _xMargin: 5, isTemplateDoc: true, isTemplateForField: "data" });
+                    }
+                    else {
+                        this.props.Document.childLayout = presTemp;
+                    }
+                } else {
+                    this.props.Document.childLayout = undefined;
+                }
+            }, { fireImmediately: true });
         this._childReaction = reaction(() => this.childDocs.slice(),
             (children) => children.forEach((child, i) => child.presentationIndex = i), { fireImmediately: true });
     }
@@ -52,7 +64,7 @@ export class PresBox extends React.Component<FieldViewProps> {
     @computed get childDocs() { return DocListCast(this.props.Document[this.props.fieldKey]); }
 
     next = async () => {
-        const current = NumCast(this.props.Document.selectedDoc);
+        const current = NumCast(this.props.Document._itemIndex);
         //asking to get document at current index
         const docAtCurrentNext = await this.getDocAtIndex(current + 1);
         if (docAtCurrentNext !== undefined) {
@@ -69,7 +81,7 @@ export class PresBox extends React.Component<FieldViewProps> {
         }
     }
     back = async () => {
-        const current = NumCast(this.props.Document.selectedDoc);
+        const current = NumCast(this.props.Document._itemIndex);
         //requesting for the doc at current index
         const docAtCurrent = await this.getDocAtIndex(current);
         if (docAtCurrent !== undefined) {
@@ -222,7 +234,7 @@ export class PresBox extends React.Component<FieldViewProps> {
     getDocAtIndex = async (index: number) => {
         const list = FieldValue(Cast(this.props.Document[this.props.fieldKey], listSpec(Doc)));
         if (list && index >= 0 && index < list.length) {
-            this.props.Document.selectedDoc = index;
+            this.props.Document._itemIndex = index;
             //awaiting async call to finish to get Doc instance
             return list[index];
         }
@@ -250,7 +262,7 @@ export class PresBox extends React.Component<FieldViewProps> {
         Doc.UnBrushAllDocs();
         const list = FieldValue(Cast(this.props.Document[this.props.fieldKey], listSpec(Doc)));
         if (list && index >= 0 && index < list.length) {
-            this.props.Document.selectedDoc = index;
+            this.props.Document._itemIndex = index;
 
             if (!this.props.Document.presStatus) {
                 this.props.Document.presStatus = true;
@@ -274,7 +286,7 @@ export class PresBox extends React.Component<FieldViewProps> {
         } else {
             this.props.Document.presStatus = true;
             this.startPresentation(0);
-            this.gotoDocument(0, NumCast(this.props.Document.selectedDoc));
+            this.gotoDocument(0, NumCast(this.props.Document._itemIndex));
         }
     }
 
@@ -293,7 +305,7 @@ export class PresBox extends React.Component<FieldViewProps> {
             doc.opacity = 1;
             doc.viewScale = 1;
         });
-        this.props.Document.selectedDoc = 0;
+        this.props.Document._itemIndex = 0;
         this.props.Document.presStatus = false;
         if (this.childDocs.length !== 0) {
             DocumentManager.Instance.zoomIntoScale(this.childDocs[0], 1);
@@ -330,7 +342,12 @@ export class PresBox extends React.Component<FieldViewProps> {
     }));
 
     specificContextMenu = (e: React.MouseEvent): void => {
-        ContextMenu.Instance.addItem({ description: "Make Current Presentation", event: action(() => Doc.UserDoc().curPresentation = this.props.Document), icon: "asterisk" });
+        const funcs: ContextMenuProps[] = [];
+        funcs.push({ description: "Make Current Presentation", event: action(() => Doc.UserDoc().curPresentation = this.props.Document), icon: "asterisk" });
+        funcs.push({ description: "Make slideshow", event: action(() => this.props.Document._slideshow = "slideshow"), icon: "asterisk" });
+        funcs.push({ description: "Make timeline", event: action(() => this.props.Document._slideshow = "timeline"), icon: "asterisk" });
+        funcs.push({ description: "Make list", event: action(() => this.props.Document._slideshow = "list"), icon: "asterisk" });
+        ContextMenu.Instance.addItem({ description: "Presentatoin Funcs...", subitems: funcs, icon: "asterisk" });
     }
 
     /**
@@ -353,19 +370,37 @@ export class PresBox extends React.Component<FieldViewProps> {
         });
     }
 
-
     selectElement = (doc: Doc) => {
         const index = DocListCast(this.props.Document[this.props.fieldKey]).indexOf(doc);
-        index !== -1 && this.gotoDocument(index, NumCast(this.props.Document.selectedDoc));
+        index !== -1 && this.gotoDocument(index, NumCast(this.props.Document._itemIndex));
     }
 
     getTransform = () => {
         return this.props.ScreenToLocalTransform().translate(0, -50);// listBox padding-left and pres-box-cont minHeight
     }
+    panelHeight = () => {
+        return this.props.PanelHeight() - 20;
+    }
     render() {
         this.initializeScaleViews(this.childDocs, NumCast(this.props.Document._viewType));
-        return (
+        return (this.props.Document._slideshow ?
             <div className="presBox-cont" onContextMenu={this.specificContextMenu}>
+                {this.props.Document.inOverlay ? (null) :
+                    <div className="presBox-listCont" >
+                        {this.props.Document._slideshow === "slideshow" ?
+                            <CollectionCarouselView {...this.props} PanelHeight={this.panelHeight} chromeCollapsed={true} annotationsKey={""} CollectionView={undefined}
+                                moveDocument={returnFalse}
+                                addDocument={this.addDocument} removeDocument={returnFalse} focus={this.selectElement} ScreenToLocalTransform={this.getTransform} />
+                            :
+                            <CollectionTimeView {...this.props} PanelHeight={this.panelHeight} chromeCollapsed={true} annotationsKey={""} CollectionView={undefined}
+                                moveDocument={returnFalse}
+                                addDocument={this.addDocument} removeDocument={returnFalse} focus={this.selectElement} ScreenToLocalTransform={this.getTransform} />
+                        }
+                    </div>}
+                <button className="presBox-backward" title="Back" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></button>
+                <button className="presBox-forward" title="Next" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></button>
+            </div>
+            : <div className="presBox-cont" onContextMenu={this.specificContextMenu}>
                 <div className="presBox-buttons">
                     <button className="presBox-button" title="Back" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></button>
                     <button className="presBox-button" title={"Reset Presentation" + this.props.Document.presStatus ? "" : " From Start"} onClick={this.startOrResetPres}>
@@ -373,13 +408,10 @@ export class PresBox extends React.Component<FieldViewProps> {
                     </button>
                     <button className="presBox-button" title="Next" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></button>
                     <button className="presBox-button" title={this.props.Document.inOverlay ? "Expand" : "Minimize"} onClick={this.toggleMinimize}><FontAwesomeIcon icon={"eye"} /></button>
-                </div>
-                {this.props.Document.inOverlay ? (null) :
-                    <div className="presBox-listCont" >
-                        <CollectionView {...this.props} addDocument={this.addDocument} focus={this.selectElement} ScreenToLocalTransform={this.getTransform} />
-                    </div>
-                }
-            </div>
-        );
+                    {this.props.Document.inOverlay ? (null) :
+                        <div className="presBox-listCont" >
+                            <CollectionView {...this.props} PanelHeight={this.panelHeight} addDocument={this.addDocument} focus={this.selectElement} ScreenToLocalTransform={this.getTransform} />
+                        </div>}
+                </div></div>);
     }
 }
\ No newline at end of file
diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx
index ea3acf75c..52773d466 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -54,7 +54,7 @@ export class PresElementBox extends DocExtendableComponent<FieldViewProps, PresD
     @computed get presentationDoc() { return Cast(this.originalLayout?.presBox, Doc) as Doc; }
     @computed get originalLayout() { return this.props.Document.expandedTemplate as Doc; }
     @computed get targetDoc() { return this.originalLayout?.presentationTargetDoc as Doc; }
-    @computed get currentIndex() { return NumCast(this.presentationDoc?.selectedDoc); }
+    @computed get currentIndex() { return NumCast(this.presentationDoc?._itemIndex); }
 
     componentDidMount() {
         this._heightDisposer = reaction(() => [this.originalLayout.embedOpen, this.originalLayout.collapsedHeight],
-- 
cgit v1.2.3-70-g09d2

