From 4b231bbaf21939144ed8639d35f022834a406e59 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 17 Oct 2024 22:02:19 -0400
Subject: changed layout_isFlashcard to layout_flashcardType with value of
 'flashcard' to make it easy to search for flashcards. made carousel views
 able to focus() on a Doc.  added ability for carousel and card views to have
 anchors so they can be pinned and linked.  fixed pinning collections to save
 filters

---
 src/client/views/collections/CollectionTimeView.tsx | 2 --
 1 file changed, 2 deletions(-)

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

diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 8a24db330..5738e7ce3 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -147,7 +147,6 @@ export class CollectionTimeView extends CollectionSubView() {
         return (
             <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this._props.isContentActive() ? undefined : 'none' }} onClick={this.contentsDown}>
                 <CollectionFreeFormView
-                    // eslint-disable-next-line react/jsx-props-no-spreading
                     {...this._props}
                     engineProps={{ pivotField: this.pivotField, childFilters: this.childDocFilters, childFiltersByRanges: this.childDocRangeFilters }}
                     fitContentsToBox={returnTrue}
@@ -257,7 +256,6 @@ ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBou
             const pivotView = DocumentView.getDocumentView(pivotDoc);
             if (pivotDoc && pivotView?.ComponentView instanceof CollectionTimeView && filterVals.length === 1) {
                 if (pivotView?.ComponentView.childDocs.length && pivotView.ComponentView.childDocs[0][filterVals[0]]) {
-                    // eslint-disable-next-line prefer-destructuring
                     pivotDoc._pivotField = filterVals[0];
                 }
             }
-- 
cgit v1.2.3-70-g09d2


From 6d5b80adf8c0d1f93dce30401e1f90d11b838de3 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 18 Oct 2024 10:00:01 -0400
Subject: fixed following anchors to collection card/carousel to set the
 current/selected Doc

---
 src/client/documents/Documents.ts                  |  2 +-
 src/client/views/PinFuncs.ts                       |  6 +--
 src/client/views/ViewBoxInterface.ts               |  2 +-
 .../views/collections/CollectionCardDeckView.tsx   | 55 +++++++++-------------
 .../views/collections/CollectionCarousel3DView.tsx | 10 +---
 .../views/collections/CollectionCarouselView.tsx   | 10 +---
 .../views/collections/CollectionTimeView.tsx       |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/trails/PresBox.tsx          | 36 +++++++-------
 src/fields/ScriptField.ts                          |  6 +--
 10 files changed, 53 insertions(+), 78 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index be3b9c3bb..98e8bc932 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -373,7 +373,7 @@ export class DocumentOptions {
     config_panY?: NUMt = new NumInfo('panY saved as a view spec', false);
     config_zoom?: NUMt = new NumInfo('zoom saved as a view spec', false);
     config_carousel_index?: NUMt = new NumInfo('saved carousel index', false);
-    config_curDoc?: DOCt = new DocInfo('current doc in a collection view, e.g., cardView');
+    config_card_curDoc?: DOCt = new DocInfo('current doc in a collection view, e.g., cardView');
     config_viewScale?: NUMt = new NumInfo('viewScale saved as a view Spec', false);
     presentation_transition?: NUMt = new NumInfo('the time taken for the transition TO a document', false);
     presentation_duration?: NUMt = new NumInfo('the duration of the slide in presentation view', false);
diff --git a/src/client/views/PinFuncs.ts b/src/client/views/PinFuncs.ts
index 17964c656..bed473eec 100644
--- a/src/client/views/PinFuncs.ts
+++ b/src/client/views/PinFuncs.ts
@@ -16,7 +16,7 @@ export interface pinDataTypes {
     scrollable?: boolean;
     dataviz?: number[];
     pannable?: boolean;
-    type_collection?: boolean;
+    collectionType?: boolean;
     inkable?: boolean;
     filters?: boolean;
     pivot?: boolean;
@@ -65,7 +65,7 @@ export function PinDocView(pinDocIn: Doc, pinProps: PinProps, targetDoc: Doc) {
             pinProps.pinData.scrollable ||
             pinProps.pinData.temporal ||
             pinProps.pinData.pannable ||
-            pinProps.pinData.type_collection ||
+            pinProps.pinData.collectionType ||
             pinProps.pinData.clippable ||
             pinProps.pinData.datarange ||
             pinProps.pinData.dataview ||
@@ -118,7 +118,7 @@ export function PinDocView(pinDocIn: Doc, pinProps: PinProps, targetDoc: Doc) {
                     })
                 )
             );
-        if (pinProps.pinData.type_collection) pinDoc.config_type_collection = targetDoc._type_collection;
+        if (pinProps.pinData.collectionType) pinDoc.config_type_collection = targetDoc._type_collection;
         if (pinProps.pinData.filters) pinDoc.config_docFilters = ObjectField.MakeCopy(targetDoc.childFilters as ObjectField) ?? new List<string>();
         if (pinProps.pinData.pivot) pinDoc.config_pivotField = targetDoc._pivotField;
         if (pinProps.pinData.pannable) {
diff --git a/src/client/views/ViewBoxInterface.ts b/src/client/views/ViewBoxInterface.ts
index f66f6062e..b7980d74e 100644
--- a/src/client/views/ViewBoxInterface.ts
+++ b/src/client/views/ViewBoxInterface.ts
@@ -24,7 +24,7 @@ export abstract class ViewBoxInterface<P> extends ObservableReactComponent<React
     promoteCollection?: () => void; // moves contents of collection to parent
     updateIcon?: (usePanelDimensions?: boolean) => Promise<void>; // updates the icon representation of the document
     getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box)
-    restoreView?: (viewSpec: Doc) => boolean;
+    restoreView?: (viewSpec: Doc) => boolean; // DEPRECATED: do not use, it will go away.  see PresBox.restoreTargetDocView
     scrollPreview?: (docView: DocumentView, doc: Doc, focusSpeed: number, options: FocusViewOptions) => Opt<number>; // returns the duration of the focus
     brushView?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number, holdTime: number) => void; // highlight a region of a view (used by freeforms)
     getView?: (doc: Doc, options: FocusViewOptions) => Promise<Opt<DocumentView>>; // returns a nested DocumentView for the specified doc or undefined
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 729a1585a..beb45f326 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -51,14 +51,13 @@ export class CollectionCardView extends CollectionSubView() {
     private _textToDoc = new Map<string, Doc>();
     private _oldWheel: HTMLElement | null = null;
     private _dropped = false; // set when a card doc has just moved and the drop method has been called - prevents the pointerUp method from hiding doc decorations (which needs to be done when clicking on a card to animate it to front/center)
-    private _clickScript = () => ScriptField.MakeScript('scriptContext._curDoc=this', { scriptContext: 'any' })!;
+    private _setCurDocScript = () => ScriptField.MakeScript('scriptContext.layoutDoc._card_curDoc=this', { scriptContext: 'any' })!;
 
     @observable _forceChildXf = 0;
     @observable _hoveredNodeIndex = -1;
     @observable _docRefs = new ObservableMap<Doc, DocumentView>();
     @observable _maxRowCount = 10;
     @observable _docDraggedIndex: number = -1;
-    @observable _curDoc: Doc | undefined = undefined;
 
     constructor(props: SubCollectionViewProps) {
         super(props);
@@ -165,10 +164,10 @@ export class CollectionCardView extends CollectionSubView() {
     /**
      * When in quiz mode, randomly selects a document
      */
-    quizMode = action(() => {
+    quizMode = () => {
         const randomIndex = Math.floor(Math.random() * this.childDocs.length);
-        this._curDoc = this.childDocs[randomIndex];
-    });
+        this.layoutDoc._card_curDoc = this.childDocs[randomIndex];
+    };
 
     setHoveredNodeIndex = action((index: number) => {
         if (!SnappingManager.IsDragging) this._hoveredNodeIndex = index;
@@ -319,7 +318,7 @@ export class CollectionCardView extends CollectionSubView() {
         (doc: Doc) => () =>
             this._props.isContentActive?.() === false
                 ? false
-                : this._props.isDocumentActive?.() && this._curDoc === doc
+                : this._props.isDocumentActive?.() && this.curDoc() === doc
                   ? true
                   : this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false
                     ? false
@@ -347,7 +346,7 @@ export class CollectionCardView extends CollectionSubView() {
             scriptContext={this}
             focus={this.focus}
             onDoubleClickScript={this.onChildDoubleClick}
-            onClickScript={this._curDoc === doc ? undefined : this._clickScript}
+            onClickScript={this.curDoc() === doc ? undefined : this._setCurDocScript}
             dontCenter="y" // Don't center it vertically, because the grid it's in is already doing that and we don't want to do it twice.
             dragAction={(this.Document.childDragAction ?? this._props.childDragAction) as dropActionType}
             showTags={BoolCast(this.layoutDoc.showChildTags)}
@@ -572,8 +571,8 @@ export class CollectionCardView extends CollectionSubView() {
      * @param doc doc that will be animated away from center focus
      */
     releaseCurDoc = action(() => {
-        const selDoc = this._curDoc;
-        this._curDoc = undefined;
+        const selDoc = this.curDoc();
+        this.layoutDoc._card_curDoc = undefined;
         const cardDocView = DocumentView.getDocumentView(selDoc, this.DocumentView?.());
         if (cardDocView && selDoc) {
             DocumentView.DeselectView(cardDocView);
@@ -590,7 +589,7 @@ export class CollectionCardView extends CollectionSubView() {
      * turns off the _dropped flag at the end of a drag/drop, or releases the focused Doc if a different Doc is clicked
      */
     cardPointerUp = action((doc: Doc) => {
-        if (this._curDoc === doc || this._dropped) {
+        if (this.curDoc() === doc || this._dropped) {
             this._dropped = false;
         } else {
             this.releaseCurDoc(); // NOTE: the onClick script for the card will select the new card (ie, 'doc')
@@ -601,28 +600,20 @@ export class CollectionCardView extends CollectionSubView() {
         const docs = DocListCast(this.Document[this.fieldKey]);
         if (anchor.type === DocumentType.CONFIG || docs.includes(anchor)) {
             const foundDoc = DocCast(
-                anchor.config_curDoc,
+                anchor.config_card_curDoc,
                 docs.find(doc => doc === DocCast(anchor.annotationOn, anchor))
             );
             options.didMove = foundDoc !== this.curDoc() ? true : false;
-            options.didMove && (this._curDoc = foundDoc);
+            options.didMove && (this.layoutDoc._card_curDoc = foundDoc);
         }
         return undefined;
     });
     getAnchor = (addAsAnnotation: boolean) => {
-        const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document, config_curDoc: this.curDoc() });
-        PinDocView(anchor, { pinData: { type_collection: true, filters: true } }, this.Document);
+        const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document, config_card_curDoc: this.curDoc() });
+        PinDocView(anchor, { pinData: { collectionType: true, filters: true } }, this.Document);
         addAsAnnotation && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_annotations', anchor); // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered
         return anchor;
     };
-    // pinned / linked anchor doc includes selected rows, graph titles, and graph colors
-    restoreView = (viewData: Doc) => {
-        if (viewData.config_curDoc !== undefined && this.curDoc() !== viewData.config_curDoc) {
-            this._curDoc = DocCast(viewData.config_curDoc);
-            return true;
-        }
-        return false;
-    };
     addDocTab = this.addLinkedDocTab;
 
     /**
@@ -634,9 +625,9 @@ export class CollectionCardView extends CollectionSubView() {
         return this.sortedDocs.map((doc, index) => {
             const cardsInRow = this.cardsInRowThatIncludesCardIndex(index);
 
-            const childScreenToLocal = this.childScreenToLocal(doc, index, doc === this._curDoc);
+            const childScreenToLocal = this.childScreenToLocal(doc, index, doc === this.curDoc());
 
-            const translateToCenterIfActive = () => (doc === this._curDoc ? (cardsInRow / 2 - (index % this._maxRowCount)) * 100 - 50 : 0);
+            const translateToCenterIfActive = () => (doc === this.curDoc() ? (cardsInRow / 2 - (index % this._maxRowCount)) * 100 - 50 : 0);
 
             const aspect = NumCast(doc.height) / NumCast(doc.width, 1);
             const vscale = Math.max(1,Math.min((this._props.PanelHeight() * 0.95 * this.fitContentScale * this.nativeScaling) / (aspect * this.childPanelWidth()), 
@@ -645,15 +636,15 @@ export class CollectionCardView extends CollectionSubView() {
             return (
                 <div
                     key={doc[Id]}
-                    className={`card-item${doc === this._curDoc ? '-active' : this.isAnyChildContentActive() ? '-inactive' : ''}`}
+                    className={`card-item${doc === this.curDoc() ? '-active' : this.isAnyChildContentActive() ? '-inactive' : ''}`}
                     onPointerUp={() => this.cardPointerUp(doc)}
                     style={{
                         width: this.childPanelWidth(),
                         height: 'max-content',
-                        transform: `translateY(${this.adjustCardYtoFitArch(doc === this._curDoc, index)}px)
+                        transform: `translateY(${this.adjustCardYtoFitArch(doc === this.curDoc(), index)}px)
                                     translateX(calc(${translateToCenterIfActive()}% + ${this.horizontalAdjustmentForPartialRows(index, cardsInRow)}px))
-                                    rotate(${doc !== this._curDoc ? this.rotate(index) : 0}rad)
-                                    scale(${doc === this._curDoc ? `${Math.min(hscale, vscale) * 100}%` : this._hoveredNodeIndex === index ? 1.1 : 1})`,
+                                    rotate(${doc !== this.curDoc()? this.rotate(index) : 0}rad)
+                                    scale(${doc === this.curDoc()? `${Math.min(hscale, vscale) * 100}%` : this._hoveredNodeIndex === index ? 1.1 : 1})`,
                     }} // prettier-ignore
                     onPointerEnter={() => this.setHoveredNodeIndex(index)}
                     onPointerLeave={() => this.setHoveredNodeIndex(-1)}>
@@ -671,10 +662,10 @@ export class CollectionCardView extends CollectionSubView() {
         isContentActive: emptyFunction,
         ScreenToLocalTransform: this.contentScreenToLocalXf,
     });
-    answered = action(() => {
-        this._curDoc = this.curDoc ? this.filteredChildDocs()[(this.filteredChildDocs().findIndex(d => d === this.curDoc()) + 1) % (this.filteredChildDocs().length || 1)] : undefined;
-    });
-    curDoc = () => this._curDoc;
+    answered = () => {
+        this.layoutDoc._card_curDoc = this.curDoc() ? this.filteredChildDocs()[(this.filteredChildDocs().findIndex(d => d === this.curDoc()) + 1) % (this.filteredChildDocs().length || 1)] : undefined;
+    };
+    curDoc = () => DocCast(this.layoutDoc._card_curDoc);
 
     render() {
         const fitContentScale = this.childCards.length === 0 ? 1 : this.fitContentScale;
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 081e2fe5a..61a74dd7a 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -103,18 +103,10 @@ export class CollectionCarousel3DView extends CollectionSubView() {
     };
     getAnchor = (addAsAnnotation: boolean) => {
         const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document, config_carousel_index: this.layoutDoc._carousel_index as number });
-        PinDocView(anchor, { pinData: { type_collection: true, filters: true } }, this.Document);
+        PinDocView(anchor, { pinData: { collectionType: true, filters: true } }, this.Document);
         addAsAnnotation && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_annotations', anchor); // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered
         return anchor;
     };
-    // pinned / linked anchor doc includes selected rows, graph titles, and graph colors
-    restoreView = (viewData: Doc) => {
-        if (viewData.config_carousel_index !== undefined && this.layoutDoc._carousel_index !== viewData.config_carousel_index) {
-            this.layoutDoc._carousel_index = viewData.config_carousel_index;
-            return true;
-        }
-        return false;
-    };
     addDocTab = this.addLinkedDocTab;
     @computed get content() {
         const currentIndex = NumCast(this.layoutDoc._carousel_index);
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index bfb58733d..f714e2a00 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -86,18 +86,10 @@ export class CollectionCarouselView extends CollectionSubView() {
 
     getAnchor = (addAsAnnotation: boolean) => {
         const anchor = Docs.Create.ConfigDocument({ annotationOn: this.Document, config_carousel_index: this.carouselIndex });
-        PinDocView(anchor, { pinData: { type_collection: true, filters: true } }, this.Document);
+        PinDocView(anchor, { pinData: { collectionType: true, filters: true } }, this.Document);
         addAsAnnotation && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_annotations', anchor); // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered
         return anchor;
     };
-    // pinned / linked anchor doc includes selected rows, graph titles, and graph colors
-    restoreView = (viewData: Doc) => {
-        if (viewData.config_carousel_index !== undefined && this.layoutDoc._carousel_index !== viewData.config_carousel_index) {
-            this.layoutDoc._carousel_index = viewData.config_carousel_index;
-            return true;
-        }
-        return false;
-    };
     addDocTab = this.addLinkedDocTab;
 
     captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<FieldViewProps>, property: string) => {
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index 5738e7ce3..d75c633ac 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -52,7 +52,7 @@ export class CollectionTimeView extends CollectionSubView() {
             title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as unknown as string, // title can take a functiono or a string
             annotationOn: this.Document,
         });
-        PinDocView(anchor, { pinData: { type_collection: true, pivot: true, filters: true } }, this.Document);
+        PinDocView(anchor, { pinData: { collectionType: true, pivot: true, filters: true } }, this.Document);
 
         if (addAsAnnotation) {
             // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 0241fc2dd..ff711314b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1743,7 +1743,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         });
         PinDocView(
             anchor,
-            { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ? { ...pinProps.pinData, poslayoutview: pinProps.pinData.dataview } : {}), pannable: !this.Document.isGroup, type_collection: true, filters: true } },
+            { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ? { ...pinProps.pinData, poslayoutview: pinProps.pinData.dataview } : {}), pannable: !this.Document.isGroup, collectionType: true, filters: true } },
             this.Document
         );
 
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 212dfec08..5d3672aee 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -14,7 +14,7 @@ import { StopEvent, lightOrDark, returnFalse, returnOne, setupMoveUpEvents } fro
 import { emptyFunction, stringHash } from '../../../../Utils';
 import { Doc, DocListCast, Field, FieldResult, FieldType, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
 import { Animation, DocData, TransitionTimer } from '../../../../fields/DocSymbols';
-import { Copy } from '../../../../fields/FieldSymbols';
+import { Copy, Id } from '../../../../fields/FieldSymbols';
 import { InkField } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
 import { ObjectField } from '../../../../fields/ObjectField';
@@ -382,7 +382,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             if (typeof res === 'string') {
                 const resObj = JSON.parse(res);
                 console.log('Parsed GPT Result ', resObj);
-                // eslint-disable-next-line no-restricted-syntax
                 for (const key in resObj) {
                     if (resObj[key]) {
                         console.log('typeof property', typeof resObj[key]);
@@ -563,11 +562,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const datarange = [DocumentType.FUNCPLOT].includes(targetType);
         const dataview = [DocumentType.INK, DocumentType.COL, DocumentType.IMG, DocumentType.RTF].includes(targetType) && target?.activeFrame === undefined;
         const poslayoutview = [DocumentType.COL].includes(targetType) && target?.activeFrame === undefined;
-        const typeCollection = targetType === DocumentType.COL;
+        const collectionType = targetType === DocumentType.COL;
         const filters = true;
         const pivot = true;
         const dataannos = false;
-        return { scrollable, pannable, inkable, type_collection: typeCollection, pivot, map, filters, temporal, clippable, dataview, datarange, poslayoutview, dataannos };
+        return { scrollable, pannable, inkable, collectionType, pivot, map, filters, temporal, clippable, dataview, datarange, poslayoutview, dataannos };
     }
 
     @action
@@ -575,7 +574,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         /* empty */
     };
     @action
-    // eslint-disable-next-line default-param-last
     static restoreTargetDocView(bestTargetView: Opt<DocumentView>, activeItem: Doc, transTime: number, pinDocLayout: boolean = BoolCast(activeItem.config_pinLayout), pinDataTypes?: dataTypes, targetDoc?: Doc) {
         const bestTarget = bestTargetView?.Document ?? (targetDoc?.layout_unrendered ? DocCast(targetDoc?.annotationOn) : targetDoc);
         if (!bestTarget) return undefined;
@@ -701,7 +699,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 changed = true;
             }
         }
-        if ((pinDataTypes?.type_collection && activeItem.config_type_collection !== undefined) || (!pinDataTypes && activeItem.config_type_collection !== undefined)) {
+        if ((pinDataTypes?.collectionType && activeItem.config_card_curDoc !== undefined) || (!pinDataTypes && activeItem.config_card_curDoc !== undefined)) {
+            if (bestTarget._card_curDoc !== activeItem.config_card_curDoc) {
+                bestTarget._card_curDoc = activeItem.config_card_curDoc;
+                changed = true;
+            }
+        }
+        if ((pinDataTypes?.collectionType && activeItem.config_carousel_index !== undefined) || (!pinDataTypes && activeItem.config_carousel_index !== undefined)) {
+            if (bestTarget._carousel_index !== activeItem.config_carousel_index) {
+                bestTarget._carousel_index = activeItem.config_carousel_index;
+                changed = true;
+            }
+        }
+        if ((pinDataTypes?.collectionType && activeItem.config_type_collection !== undefined) || (!pinDataTypes && activeItem.config_type_collection !== undefined)) {
             if (bestTarget._type_collection !== activeItem.config_type_collection) {
                 bestTarget._type_collection = activeItem.config_type_collection;
                 changed = true;
@@ -1135,7 +1145,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     return false;
                 }
             } else if (doc.type !== DocumentType.PRES) {
-                // eslint-disable-next-line operator-assignment
                 if (!doc.presentation_targetDoc) doc.title = doc.title + ' - Slide';
                 doc.presentation_targetDoc = doc.createdFrom ?? doc; // dropped document will be a new embedding of an embedded document somewhere else.
                 doc.presentation_movement = PresMovement.Zoom;
@@ -1167,8 +1176,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const tagDoc = Cast(curDoc.presentation_targetDoc, Doc, null);
             if (curDoc && curDoc === this.activeItem)
                 return (
-                    // eslint-disable-next-line react/no-array-index-key
-                    <div key={index} className="selectedList-items">
+                    <div key={doc[Id]} className="selectedList-items">
                         <b>
                             {index + 1}. {StrCast(curDoc.title)})
                         </b>
@@ -1176,15 +1184,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 );
             if (tagDoc)
                 return (
-                    // eslint-disable-next-line react/no-array-index-key
-                    <div key={index} className="selectedList-items">
+                    <div key={doc[Id]} className="selectedList-items">
                         {index + 1}. {StrCast(curDoc.title)}
                     </div>
                 );
             if (curDoc)
                 return (
-                    // eslint-disable-next-line react/no-array-index-key
-                    <div key={index} className="selectedList-items">
+                    <div key={doc[Id]} className="selectedList-items">
                         {index + 1}. {StrCast(curDoc.title)}
                     </div>
                 );
@@ -1369,7 +1375,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             const tagDoc = PresBox.targetRenderedDoc(doc);
             const srcContext = Cast(tagDoc.embedContainer, Doc, null);
             const labelCreator = (top: number, left: number, edge: number, fontSize: number) => (
-                // eslint-disable-next-line react/no-array-index-key
                 <div className="pathOrder" key={tagDoc.id + 'pres' + index} style={{ top, left, width: edge, height: edge, fontSize }} onClick={() => this.selectElement(doc)}>
                     <div className="pathOrder-frame">{index + 1}</div>
                 </div>
@@ -1620,7 +1625,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this.updateEffect(this.activeItem.presentation_effect as PresEffect, false, true);
         this.updateEffect(this.activeItem.presBulletEffect as PresEffect, true, true);
         this.updateEffectDirection(this.activeItem.presentation_effectDirection as PresEffectDirection, true);
-        // eslint-disable-next-line camelcase
         const { presentation_transition: pt, presentation_duration: pd, presentation_hideBefore: ph, presentation_hideAfter: pa } = this.activeItem;
         array.forEach(curDoc => {
             curDoc.presentation_transition = pt;
@@ -2018,7 +2022,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 <div className="presBox-effects">
                                     {this.generatedAnimations.map((elem, i) => (
                                         <div
-                                            // eslint-disable-next-line react/no-array-index-key
                                             key={i}
                                             className="presBox-effect-container"
                                             onClick={() => {
@@ -3046,7 +3049,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     <div className="Slide">
                         {mode !== CollectionViewType.Invalid ? (
                             <CollectionView
-                                // eslint-disable-next-line react/jsx-props-no-spreading
                                 {...this._props}
                                 PanelWidth={this._props.PanelWidth}
                                 PanelHeight={this.panelHeight}
diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts
index 582c09f29..0252961d3 100644
--- a/src/fields/ScriptField.ts
+++ b/src/fields/ScriptField.ts
@@ -85,6 +85,7 @@ async function deserializeScript(scriptIn: ScriptField) {
 }
 
 @scriptingGlobal
+// eslint-disable-next-line no-use-before-define
 @Deserializable('script', (obj: unknown) => deserializeScript(obj as ScriptField))
 export class ScriptField extends ObjectField {
     @serializable
@@ -137,7 +138,6 @@ export class ScriptField extends ObjectField {
     [ToString]() {
         return this.script.originalScript;
     }
-    // eslint-disable-next-line default-param-last
     public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Doc | string | number | boolean }, transformer?: Transformer) {
         return CompileScript(script, {
             params: {
@@ -156,13 +156,11 @@ export class ScriptField extends ObjectField {
         });
     }
 
-    // eslint-disable-next-line default-param-last
     public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {
         const compiled = ScriptField.CompileScript(script, params, true, capturedVariables);
         return compiled.compiled ? new ScriptField(compiled) : undefined;
     }
 
-    // eslint-disable-next-line default-param-last
     public static MakeScript(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) {
         const compiled = ScriptField.CompileScript(script, params, false, capturedVariables);
         return compiled.compiled ? new ScriptField(compiled) : undefined;
@@ -186,6 +184,7 @@ export class ScriptField extends ObjectField {
 }
 
 @scriptingGlobal
+// eslint-disable-next-line no-use-before-define
 @Deserializable('computed', (obj: unknown) => deserializeScript(obj as ComputedField))
 export class ComputedField extends ScriptField {
     static undefined = '__undefined';
@@ -229,7 +228,6 @@ export class ComputedField extends ScriptField {
     [ToValue](doc: Doc)   { return ComputedField.useComputed ? { value: this.value(doc) } : undefined; } // prettier-ignore
     [Copy](): ObjectField { return new ComputedField(this.script, this.setterscript, this.rawscript); } // prettier-ignore
 
-    // eslint-disable-next-line default-param-last
     public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }, setterscript?: string) {
         const compiled = ScriptField.CompileScript(script, params, true, { value: '', ...capturedVariables });
         const compiledsetter = setterscript ? ScriptField.CompileScript(setterscript, { ...params, value: 'any' }, false, capturedVariables) : undefined;
-- 
cgit v1.2.3-70-g09d2


From 28792ad2da9f5dfab96f98f34a11136bd1453e0f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 6 Mar 2025 10:49:20 -0500
Subject: fixed resizing height to be > 10 pixels.  split pivotView out of
 timeView.

---
 src/client/documents/DocumentTypes.ts              |   1 +
 src/client/util/CurrentUserUtils.ts                |   4 +-
 src/client/views/DocumentDecorations.tsx           |   3 +-
 .../views/collections/CollectionCarouselView.tsx   |   2 +-
 .../views/collections/CollectionPivotView.tsx      | 147 +++++++++++++++++++
 .../views/collections/CollectionTimeView.tsx       | 158 ++-------------------
 src/client/views/collections/CollectionView.tsx    |   7 +-
 .../CollectionFreeFormLayoutEngines.tsx            |   6 +-
 8 files changed, 174 insertions(+), 154 deletions(-)
 create mode 100644 src/client/views/collections/CollectionPivotView.tsx

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

diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 8aa844c0b..845aa280c 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -61,6 +61,7 @@ export enum CollectionViewType {
     Multirow = 'multirow',
     NoteTaking = 'notetaking',
     Pile = 'pileup',
+    Pivot = 'pivot',
     Schema = 'schema',
     Stacking = 'stacking',
     StackedTimeline = 'stacked timeline',
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 60acd5b21..8736225d0 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -826,7 +826,7 @@ pie title Minerals in my tap water
         return [
             { btnList: new List<string>([CollectionViewType.Freeform, CollectionViewType.Card, CollectionViewType.Carousel,CollectionViewType.Carousel3D, 
                     CollectionViewType.Masonry, CollectionViewType.Multicolumn, CollectionViewType.Multirow, CollectionViewType.Linear, 
-                    CollectionViewType.Map, CollectionViewType.NoteTaking, CollectionViewType.Schema, CollectionViewType.Stacking, 
+                    CollectionViewType.Map, CollectionViewType.NoteTaking, CollectionViewType.Pivot, CollectionViewType.Schema, CollectionViewType.Stacking, 
                     CollectionViewType.Calendar, CollectionViewType.Grid, CollectionViewType.Tree, CollectionViewType.Time, ]),
               title: "Perspective",                    toolTip: "View",                 btnType: ButtonType.DropdownList,                                                                                                                             ignoreClick: true, width: 100, scripts: { script:  '{ return setView(value, shiftKey, _readOnly_); }'}},
             { title: "Pin",     icon: "map-pin",       toolTip: "Pin View to Trail",    btnType: ButtonType.ClickButton,  expertMode: false,                                                                                                                             width: 30,  scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}},
@@ -911,7 +911,7 @@ pie title Minerals in my tap water
                             CurrentUserUtils.createToolButton(opts), scripts, funcs);
         
         const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet
-            { opts: { title: "Replicate",icon:"camera",toolTip: "Copy dashboard layout",btnType: ButtonType.ClickButton, expertMode: true},                                                                                             scripts: { onClick:  `snapshotDashboard()`}},
+            { opts: { title: "Replicate",icon:"camera",toolTip:"Copy dashboard layout",btnType: ButtonType.ClickButton, expertMode: true},                                                                                             scripts: { onClick:  `snapshotDashboard()`}},
             { opts: { title: "Recordings",            toolTip: "Workspace Recordings", btnType: ButtonType.DropdownList,expertMode: false, ignoreClick: true, width: 100}, funcs: {hidden: `false`, btnList:`getWorkspaceRecordings()`},scripts: { script:  `{ return replayWorkspace(value, _readOnly_); }`, onDragScript: `{ return startRecordingDrag(value); }`}},
             { opts: { title: "Stop Rec",icon: "stop", toolTip: "Stop recording",       btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `!isWorkspaceRecording()`},                                                 scripts: { onClick: `stopWorkspaceRecording()`}},
             { opts: { title: "Play",    icon: "play", toolTip: "Play recording",       btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${mediaState.Paused}"`},                       scripts: { onClick: `resumeWorkspaceReplaying(getCurrentRecording())`}},
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index d9b6bdf1a..54e050f9f 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -36,7 +36,6 @@ import { ImageBox } from './nodes/ImageBox';
 import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { TagsView } from './TagsView';
-import { ImageField } from '../../fields/URLField';
 
 interface DocumentDecorationsProps {
     PanelWidth: number;
@@ -567,7 +566,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
             }
 
             doc._width = Math.max(NumCast(doc._width_min, 25), NumCast(doc._width) * scale.x);
-            doc._height = Math.max(NumCast(doc._height_min), NumCast(doc._height) * scale.y);
+            doc._height = Math.max(NumCast(doc._height_min, 10), NumCast(doc._height) * scale.y);
             const { deltaX, deltaY } = this.realignRefPt(doc, refCent, initWidth || 1, initHeight || 1);
             doc.x = NumCast(doc.x) + deltaX;
             doc.y = NumCast(doc.y) + deltaY;
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 87c6e3e5c..a7d217076 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -249,9 +249,9 @@ export class CollectionCarouselView extends CollectionSubView() {
                         position: 'relative',
                     }}>
                     {this.content}
-                    {this.flashCardUI(this.curDoc, this.docViewProps, this.answered)}
                     {this.navButtons}
                 </div>
+                {this.flashCardUI(this.curDoc, this.docViewProps, this.answered)}
             </div>
         );
     }
diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx
new file mode 100644
index 000000000..2600c0f57
--- /dev/null
+++ b/src/client/views/collections/CollectionPivotView.tsx
@@ -0,0 +1,147 @@
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { returnTrue } from '../../../ClientUtils';
+import { Doc, Opt, StrListCast } from '../../../fields/Doc';
+import { List } from '../../../fields/List';
+import { ObjectField } from '../../../fields/ObjectField';
+import { ComputedField, ScriptField } from '../../../fields/ScriptField';
+import { NumCast, StrCast } from '../../../fields/Types';
+import { Docs } from '../../documents/Documents';
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
+import { FieldsDropdown } from '../FieldsDropdown';
+import { PinDocView } from '../PinFuncs';
+import { DocumentView } from '../nodes/DocumentView';
+import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
+import './CollectionTimeView.scss';
+import { ViewDefBounds, computePivotLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
+import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+
+@observer
+export class CollectionPivotView extends CollectionSubView() {
+    _changing = false;
+    @observable _collapsed: boolean = false;
+    @observable _childClickedScript: Opt<ScriptField> = undefined;
+    @observable _viewDefDivClick: Opt<ScriptField> = undefined;
+    @observable _focusPivotField: Opt<string> = undefined;
+
+    constructor(props: SubCollectionViewProps) {
+        super(props);
+        makeObservable(this);
+    }
+
+    componentDidMount() {
+        this._props.setContentViewBox?.(this);
+        runInAction(() => {
+            this._childClickedScript = ScriptField.MakeScript('openInLightbox(this)', { this: Doc.name });
+            this._viewDefDivClick = ScriptField.MakeScript('pivotColumnClick(this,payload)', { payload: 'any' });
+        });
+    }
+
+    get pivotField() {
+        return this._focusPivotField || StrCast(this.layoutDoc._pivotField);
+    }
+
+    getAnchor = (addAsAnnotation: boolean) => {
+        const anchor = Docs.Create.ConfigDocument({
+            title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as unknown as string, // title can take a functiono or a string
+            annotationOn: this.Document,
+        });
+        PinDocView(anchor, { pinData: { collectionType: true, pivot: true, filters: true } }, this.Document);
+        addAsAnnotation && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_annotations', anchor); // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered
+        return anchor;
+    };
+
+    @action
+    scrollPreview = (docView: DocumentView, anchor: Doc /* , focusSpeed: number, options: FocusViewOptions */) => {
+        // if in preview, then override document's fields with view spec
+        this._focusFilters = StrListCast(anchor.config_docFilters);
+        this._focusRangeFilters = StrListCast(anchor.config_docRangeFilters);
+        this._focusPivotField = StrCast(anchor.config_pivotField);
+        return undefined;
+    };
+
+    toggleVisibility = action(() => {
+        this._collapsed = !this._collapsed;
+    });
+
+    goTo = (prevFilterIndex: number) => {
+        this.layoutDoc._pivotField = this.layoutDoc['_prevPivotFields' + prevFilterIndex];
+        this.layoutDoc._childFilters = ObjectField.MakeCopy(this.layoutDoc['_prevDocFilter' + prevFilterIndex] as ObjectField);
+        this.layoutDoc._childFiltersByRanges = ObjectField.MakeCopy(this.layoutDoc['_prevDocRangeFilters' + prevFilterIndex] as ObjectField);
+        this.layoutDoc._prevFilterIndex = prevFilterIndex;
+    };
+
+    @action
+    contentsDown = () => {
+        const prevFilterIndex = NumCast(this.layoutDoc._prevFilterIndex);
+        if (prevFilterIndex > 0) {
+            this.goTo(prevFilterIndex - 1);
+        } else {
+            this.layoutDoc._childFilters = new List([]);
+        }
+    };
+    layoutEngine = () => computePivotLayout.name;
+    @computed get contents() {
+        return (
+            <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this._props.isContentActive() ? undefined : 'none' }} onClick={this.contentsDown}>
+                <CollectionFreeFormView
+                    {...this._props}
+                    engineProps={{ pivotField: this.pivotField, childFilters: this.childDocFilters, childFiltersByRanges: this.childDocRangeFilters }}
+                    fitContentsToBox={returnTrue}
+                    childClickScript={this._childClickedScript}
+                    viewDefDivClick={this._viewDefDivClick}
+                    layoutEngine={this.layoutEngine}
+                />
+            </div>
+        );
+    }
+
+    render() {
+        return (
+            <div className="collectionTimeView-pivot" style={{ width: this._props.PanelWidth(), height: '100%' }}>
+                {this.contents}
+                <div style={{ right: 0, top: 0, position: 'absolute' }}>
+                    <FieldsDropdown
+                        Document={this.Document}
+                        selectFunc={fieldKey => {
+                            this.layoutDoc._pivotField = fieldKey;
+                        }}
+                        placeholder={StrCast(this.layoutDoc._pivotField)}
+                    />
+                </div>
+            </div>
+        );
+    }
+}
+
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) {
+    const pivotField = StrCast(pivotDoc._pivotField, 'author');
+    let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex);
+    const originalFilter = StrListCast(ObjectField.MakeCopy(pivotDoc._childFilters as ObjectField));
+    pivotDoc['_prevDocFilter' + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._childFilters as ObjectField);
+    pivotDoc['_prevDocRangeFilters' + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._childFiltersByRanges as ObjectField);
+    pivotDoc['_prevPivotFields' + prevFilterIndex] = pivotField;
+    pivotDoc._prevFilterIndex = ++prevFilterIndex;
+    pivotDoc._childFilters = new List();
+    setTimeout(
+        action(() => {
+            const filterVals = bounds.payload as string[];
+            filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, pivotField, filterVal, 'check'));
+            const pivotView = DocumentView.getDocumentView(pivotDoc);
+            if (pivotDoc && pivotView?.ComponentView instanceof CollectionPivotView && filterVals.length === 1) {
+                if (pivotView?.ComponentView.childDocs.length && pivotView.ComponentView.childDocs[0][filterVals[0]]) {
+                    pivotDoc._pivotField = filterVals[0];
+                }
+            }
+            const newFilters = StrListCast(pivotDoc._childFilters);
+            if (newFilters.length && originalFilter.length && newFilters.lastElement() === originalFilter.lastElement()) {
+                pivotDoc._prevFilterIndex = --prevFilterIndex;
+                pivotDoc['_prevDocFilter' + prevFilterIndex] = undefined;
+                pivotDoc['_prevDocRangeFilters' + prevFilterIndex] = undefined;
+                pivotDoc['_prevPivotFields' + prevFilterIndex] = undefined;
+            }
+        })
+    );
+});
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index d75c633ac..98bd06221 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -1,33 +1,22 @@
-import { action, computed, makeObservable, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../ClientUtils';
 import { emptyFunction } from '../../../Utils';
 import { Doc, Opt, StrListCast } from '../../../fields/Doc';
-import { List } from '../../../fields/List';
-import { ObjectField } from '../../../fields/ObjectField';
-import { listSpec } from '../../../fields/Schema';
-import { ComputedField, ScriptField } from '../../../fields/ScriptField';
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { NumCast, StrCast } from '../../../fields/Types';
 import { Docs } from '../../documents/Documents';
-import { ScriptingGlobals } from '../../util/ScriptingGlobals';
-import { ContextMenu } from '../ContextMenu';
-import { ContextMenuProps } from '../ContextMenuItem';
 import { FieldsDropdown } from '../FieldsDropdown';
 import { PinDocView } from '../PinFuncs';
 import { DocumentView } from '../nodes/DocumentView';
 import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
 import './CollectionTimeView.scss';
-import { ViewDefBounds, computePivotLayout, computeTimelineLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
+import { computeTimelineLayout } from './collectionFreeForm/CollectionFreeFormLayoutEngines';
 import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
 
 @observer
 export class CollectionTimeView extends CollectionSubView() {
-    _changing = false;
-    @observable _layoutEngine = computePivotLayout.name;
     @observable _collapsed: boolean = false;
-    @observable _childClickedScript: Opt<ScriptField> = undefined;
-    @observable _viewDefDivClick: Opt<ScriptField> = undefined;
     @observable _focusPivotField: Opt<string> = undefined;
 
     constructor(props: SubCollectionViewProps) {
@@ -37,10 +26,6 @@ export class CollectionTimeView extends CollectionSubView() {
 
     componentDidMount() {
         this._props.setContentViewBox?.(this);
-        runInAction(() => {
-            this._childClickedScript = ScriptField.MakeScript('openInLightbox(this)', { this: Doc.name });
-            this._viewDefDivClick = ScriptField.MakeScript('pivotColumnClick(this,payload)', { payload: 'any' });
-        });
     }
 
     get pivotField() {
@@ -49,19 +34,10 @@ export class CollectionTimeView extends CollectionSubView() {
 
     getAnchor = (addAsAnnotation: boolean) => {
         const anchor = Docs.Create.ConfigDocument({
-            title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as unknown as string, // title can take a functiono or a string
             annotationOn: this.Document,
         });
         PinDocView(anchor, { pinData: { collectionType: true, pivot: true, filters: true } }, this.Document);
-
-        if (addAsAnnotation) {
-            // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered
-            if (Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), null) !== undefined) {
-                Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), []).push(anchor);
-            } else {
-                this.dataDoc[this._props.fieldKey + '_annotations'] = new List<Doc>([anchor]);
-            }
-        }
+        addAsAnnotation && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_annotations', anchor); // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered
         return anchor;
     };
 
@@ -74,7 +50,6 @@ export class CollectionTimeView extends CollectionSubView() {
         return undefined;
     };
 
-    layoutEngine = () => this._layoutEngine;
     toggleVisibility = action(() => {
         this._collapsed = !this._collapsed;
     });
@@ -126,105 +101,24 @@ export class CollectionTimeView extends CollectionSubView() {
         );
     };
 
-    goTo = (prevFilterIndex: number) => {
-        this.layoutDoc._pivotField = this.layoutDoc['_prevPivotFields' + prevFilterIndex];
-        this.layoutDoc._childFilters = ObjectField.MakeCopy(this.layoutDoc['_prevDocFilter' + prevFilterIndex] as ObjectField);
-        this.layoutDoc._childFiltersByRanges = ObjectField.MakeCopy(this.layoutDoc['_prevDocRangeFilters' + prevFilterIndex] as ObjectField);
-        this.layoutDoc._prevFilterIndex = prevFilterIndex;
-    };
-
-    @action
-    contentsDown = () => {
-        const prevFilterIndex = NumCast(this.layoutDoc._prevFilterIndex);
-        if (prevFilterIndex > 0) {
-            this.goTo(prevFilterIndex - 1);
-        } else {
-            this.layoutDoc._childFilters = new List([]);
-        }
-    };
-
+    layoutEngine = () => computeTimelineLayout.name;
     @computed get contents() {
         return (
-            <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this._props.isContentActive() ? undefined : 'none' }} onClick={this.contentsDown}>
+            <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this._props.isContentActive() ? undefined : 'none' }}>
                 <CollectionFreeFormView
                     {...this._props}
                     engineProps={{ pivotField: this.pivotField, childFilters: this.childDocFilters, childFiltersByRanges: this.childDocRangeFilters }}
                     fitContentsToBox={returnTrue}
-                    childClickScript={this._childClickedScript}
-                    viewDefDivClick={this.layoutEngine() === computeTimelineLayout.name ? undefined : this._viewDefDivClick}
                     layoutEngine={this.layoutEngine}
                 />
             </div>
         );
     }
 
-    public static SyncTimelineToPresentation(doc: Doc) {
-        const fieldKey = Doc.LayoutFieldKey(doc);
-        doc[fieldKey + '-timelineCur'] = ComputedField.MakeFunction("(activePresentationItem()[this._pivotField || 'year'] || 0)");
-    }
-    specificMenu = () => {
-        const layoutItems: ContextMenuProps[] = [];
-        const doc = this.layoutDoc;
-
-        layoutItems.push({
-            description: 'Force Timeline',
-            event: () => {
-                doc._forceRenderEngine = computeTimelineLayout.name;
-            },
-            icon: 'compress-arrows-alt',
-        });
-        layoutItems.push({
-            description: 'Force Pivot',
-            event: () => {
-                doc._forceRenderEngine = computePivotLayout.name;
-            },
-            icon: 'compress-arrows-alt',
-        });
-        layoutItems.push({
-            description: 'Auto Time/Pivot layout',
-            event: () => {
-                doc._forceRenderEngine = undefined;
-            },
-            icon: 'compress-arrows-alt',
-        });
-        layoutItems.push({ description: 'Sync with presentation', event: () => CollectionTimeView.SyncTimelineToPresentation(doc), icon: 'compress-arrows-alt' });
-
-        ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' });
-    };
-
     render() {
-        let nonNumbers = 0;
-        this.childDocs.forEach(doc => {
-            const num = NumCast(doc[this.pivotField], Number(StrCast(doc[this.pivotField])));
-            if (isNaN(num)) {
-                nonNumbers++;
-            }
-        });
-        const forceLayout = StrCast(this.layoutDoc._forceRenderEngine);
-        const doTimeline = forceLayout ? forceLayout === computeTimelineLayout.name : nonNumbers / this.childDocs.length < 0.1 && this._props.PanelWidth() / this._props.PanelHeight() > 6;
-        if (doTimeline !== (this._layoutEngine === computeTimelineLayout.name)) {
-            if (!this._changing) {
-                this._changing = true;
-                setTimeout(
-                    action(() => {
-                        this._layoutEngine = doTimeline ? computeTimelineLayout.name : computePivotLayout.name;
-                        this._changing = false;
-                    }),
-                    0
-                );
-            }
-        }
-
         return (
-            <div className={'collectionTimeView' + (doTimeline ? '' : '-pivot')} onContextMenu={this.specificMenu} style={{ width: this._props.PanelWidth(), height: '100%' }}>
+            <div className="collectionTimeView" style={{ width: this._props.PanelWidth(), height: '100%' }}>
                 {this.contents}
-                {!this._props.isSelected() || !doTimeline ? null : (
-                    <>
-                        <div className="collectionTimeView-thumb-min collectionTimeView-thumb" key="min" onPointerDown={this.onMinDown} />
-                        <div className="collectionTimeView-thumb-max collectionTimeView-thumb" key="mid" onPointerDown={this.onMaxDown} />
-                        <div className="collectionTimeView-thumb-mid collectionTimeView-thumb" key="max" onPointerDown={this.onMidDown} />
-                    </>
-                )}
                 <div style={{ right: 0, top: 0, position: 'absolute' }}>
                     <FieldsDropdown
                         Document={this.Document}
@@ -234,38 +128,14 @@ export class CollectionTimeView extends CollectionSubView() {
                         placeholder={StrCast(this.layoutDoc._pivotField)}
                     />
                 </div>
+                {!this._props.isSelected() ? null : (
+                    <>
+                        <div className="collectionTimeView-thumb-min collectionTimeView-thumb" key="min" onPointerDown={this.onMinDown} />
+                        <div className="collectionTimeView-thumb-max collectionTimeView-thumb" key="mid" onPointerDown={this.onMaxDown} />
+                        <div className="collectionTimeView-thumb-mid collectionTimeView-thumb" key="max" onPointerDown={this.onMidDown} />
+                    </>
+                )}
             </div>
         );
     }
 }
-
-// eslint-disable-next-line prefer-arrow-callback
-ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBounds) {
-    const pivotField = StrCast(pivotDoc._pivotField, 'author');
-    let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex);
-    const originalFilter = StrListCast(ObjectField.MakeCopy(pivotDoc._childFilters as ObjectField));
-    pivotDoc['_prevDocFilter' + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._childFilters as ObjectField);
-    pivotDoc['_prevDocRangeFilters' + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._childFiltersByRanges as ObjectField);
-    pivotDoc['_prevPivotFields' + prevFilterIndex] = pivotField;
-    pivotDoc._prevFilterIndex = ++prevFilterIndex;
-    pivotDoc._childFilters = new List();
-    setTimeout(
-        action(() => {
-            const filterVals = bounds.payload as string[];
-            filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, pivotField, filterVal, 'check'));
-            const pivotView = DocumentView.getDocumentView(pivotDoc);
-            if (pivotDoc && pivotView?.ComponentView instanceof CollectionTimeView && filterVals.length === 1) {
-                if (pivotView?.ComponentView.childDocs.length && pivotView.ComponentView.childDocs[0][filterVals[0]]) {
-                    pivotDoc._pivotField = filterVals[0];
-                }
-            }
-            const newFilters = StrListCast(pivotDoc._childFilters);
-            if (newFilters.length && originalFilter.length && newFilters.lastElement() === originalFilter.lastElement()) {
-                pivotDoc._prevFilterIndex = --prevFilterIndex;
-                pivotDoc['_prevDocFilter' + prevFilterIndex] = undefined;
-                pivotDoc['_prevDocRangeFilters' + prevFilterIndex] = undefined;
-                pivotDoc['_prevPivotFields' + prevFilterIndex] = undefined;
-            }
-        })
-    );
-});
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index a4900e9d7..7ba8989ce 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -15,12 +15,14 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent } from '../DocComponent';
 import { FieldView } from '../nodes/FieldView';
 import { OpenWhere } from '../nodes/OpenWhere';
+import { CalendarBox } from '../nodes/calendarBox/CalendarBox';
 import { CollectionCardView } from './CollectionCardDeckView';
 import { CollectionCarousel3DView } from './CollectionCarousel3DView';
 import { CollectionCarouselView } from './CollectionCarouselView';
 import { CollectionDockingView } from './CollectionDockingView';
 import { CollectionNoteTakingView } from './CollectionNoteTakingView';
 import { CollectionPileView } from './CollectionPileView';
+import { CollectionPivotView } from './CollectionPivotView';
 import { CollectionStackingView } from './CollectionStackingView';
 import { CollectionViewProps, SubCollectionViewProps } from './CollectionSubView';
 import { CollectionTimeView } from './CollectionTimeView';
@@ -32,7 +34,6 @@ import { CollectionLinearView } from './collectionLinear';
 import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView';
 import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
 import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView';
-import { CalendarBox } from '../nodes/calendarBox/CalendarBox';
 
 @observer
 export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewProps>() {
@@ -105,6 +106,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
             case CollectionViewType.NoteTaking:   return <CollectionNoteTakingView  key="collview" {...props} />;
             case CollectionViewType.Masonry:      return <CollectionStackingView    key="collview" {...props} />;
             case CollectionViewType.Time:         return <CollectionTimeView        key="collview" {...props} />;
+            case CollectionViewType.Pivot:        return <CollectionPivotView       key="collview" {...props} />;
             case CollectionViewType.Grid:         return <CollectionGridView        key="collview" {...props} />;
             case CollectionViewType.Card:         return <CollectionCardView        key="collview" {...props} />;
         }
@@ -126,7 +128,8 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
                 { description: 'Carousel',    event: () => func(CollectionViewType.Carousel), icon: 'columns' },
                 { description: '3D Carousel', event: () => func(CollectionViewType.Carousel3D), icon: 'columns' },
                 { description: 'Calendar',    event: () => func(CollectionViewType.Calendar), icon: 'columns' },
-                { description: 'Pivot/Time',  event: () => func(CollectionViewType.Time), icon: 'columns' },
+                { description: 'Pivot',       event: () => func(CollectionViewType.Pivot), icon: 'columns' },
+                { description: 'Time',        event: () => func(CollectionViewType.Time), icon: 'columns' },
                 { description: 'Map',         event: () => func(CollectionViewType.Map), icon: 'globe-americas' },
                 { description: 'Grid',        event: () => func(CollectionViewType.Grid), icon: 'th-list' },
             ];
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index bebdbd731..241a56a88 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -4,7 +4,7 @@ import { Id, ToString } from '../../../../fields/FieldSymbols';
 import { ObjectField } from '../../../../fields/ObjectField';
 import { RefField } from '../../../../fields/RefField';
 import { listSpec } from '../../../../fields/Schema';
-import { Cast, NumCast, StrCast } from '../../../../fields/Types';
+import { Cast, DateCast, NumCast, StrCast } from '../../../../fields/Types';
 import { aggregateBounds } from '../../../../Utils';
 
 export interface ViewDefBounds {
@@ -107,7 +107,7 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc
 }
 
 function toNumber(val: FieldResult<FieldType>) {
-    return val === undefined ? undefined : NumCast(val, Number(StrCast(val)));
+    return val === undefined ? undefined : DateCast(val) ? DateCast(val).date.getMilliseconds() : NumCast(val, Number(StrCast(val)));
 }
 
 export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] /* , engineProps: any */) {
@@ -295,7 +295,7 @@ export function computeTimelineLayout(poolData: Map<string, PoolData>, pivotDoc:
     let minTime = minTimeReq === undefined ? Number.MAX_VALUE : minTimeReq;
     let maxTime = maxTimeReq === undefined ? -Number.MAX_VALUE : maxTimeReq;
     childPairs.forEach(pair => {
-        const num = NumCast(pair.layout[timelineFieldKey], Number(StrCast(pair.layout[timelineFieldKey])));
+        const num = toNumber(pair.layout[timelineFieldKey]) ?? 0;
         if (!isNaN(num) && (!minTimeReq || num >= minTimeReq) && (!maxTimeReq || num <= maxTimeReq)) {
             !pivotDateGroups.get(num) && pivotDateGroups.set(num, []);
             pivotDateGroups.get(num)!.push(pair.layout);
-- 
cgit v1.2.3-70-g09d2

