From e0641c3175bc7cf53dea924524e51f1eefa6a8b1 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Mon, 18 Mar 2024 13:39:34 -0400
Subject: the lack of pushing is astounding actually

---
 .../views/collections/CollectionCardDeckView.tsx   | 164 +++++++++++++++++++++
 1 file changed, 164 insertions(+)
 create mode 100644 src/client/views/collections/CollectionCardDeckView.tsx

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

diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
new file mode 100644
index 000000000..feb9e61cc
--- /dev/null
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -0,0 +1,164 @@
+import { action, computed, IReactionDisposer, makeObservable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc, DocListCast } from '../../../fields/Doc';
+import { ScriptField } from '../../../fields/ScriptField';
+import { NumCast, StrCast } from '../../../fields/Types';
+import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
+import { DocUtils } from '../../documents/Documents';
+import { SelectionManager } from '../../util/SelectionManager';
+import { undoBatch, UndoManager } from '../../util/UndoManager';
+import { OpenWhere } from '../nodes/DocumentView';
+import { computePassLayout, computeStarburstLayout, computeCardDeckLayout } from './collectionFreeForm';
+import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import './CollectionPileView.scss';
+import { CollectionSubView } from './CollectionSubView';
+import { dropActionType } from '../../util/DragManager';
+
+@observer
+export class CollectionCardView extends CollectionSubView() {
+    _originalChrome: any = '';
+    _disposers: { [name: string]: IReactionDisposer } = {};
+
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+    }
+
+    componentDidMount() {
+        if (this.layoutEngine() !== computePassLayout.name && this.layoutEngine() !== computeCardDeckLayout.name) {
+            this.Document._freeform_cardEngine = computePassLayout.name;
+        }
+        this._originalChrome = this.layoutDoc._chromeHidden;
+        this.layoutDoc._chromeHidden = true;
+    }
+    componentWillUnmount() {
+        this.layoutDoc._chromeHidden = this._originalChrome; 
+        Object.values(this._disposers).forEach(disposer => disposer?.());
+    }
+
+    layoutEngine = () => StrCast(this.Document._freeform_cardEngine);
+
+    @undoBatch
+    addCardDoc = (doc: Doc | Doc[]) => {
+        (doc instanceof Doc ? [doc] : doc).map(d => DocUtils.iconify(d));
+        return this._props.addDocument?.(doc) || false;
+    };
+
+    @undoBatch
+    removeCardDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
+        (doc instanceof Doc ? [doc] : doc).forEach(d => Doc.deiconifyView(d));
+        const ret = this._props.moveDocument?.(doc, targetCollection, addDoc) || false;
+        if (ret && !DocListCast(this.dataDoc[this.fieldKey ?? 'data']).length) this.DocumentView?.()._props.removeDocument?.(this.Document);
+        return ret;
+    };
+
+    @computed get toggleIcon() {
+        return ScriptField.MakeScript('documentView.iconify()', { documentView: 'any' });
+    }
+    @computed get contentEvents() {
+        const isCard = this.layoutEngine() === computeCardDeckLayout.name;
+        return this._props.isContentActive() && isCard ? undefined : 'none';
+    }
+
+    // returns the contents of the cardSpread in a CollectionFreeFormView
+    @computed get contents() {
+        return (
+            <div className="collectionPileView-innards" style={{ pointerEvents: this.contentEvents }}>
+                <CollectionFreeFormView
+                    {...this._props} //
+                    layoutEngine={this.layoutEngine}
+                    addDocument={this.addCardDoc}
+                    moveDocument={this.removeCardDoc}
+                    // pile children never have their contents active, but will be document active whenever the entire pile is.
+                    childContentsActive={returnFalse}
+                    childDocumentsActive={this._props.isDocumentActive}
+                    childDragAction={dropActionType.move}
+                    childClickScript={this.toggleIcon}
+                />
+            </div>
+        );
+    }
+
+    // // toggles the pileup between starburst to compact
+    // toggleStarburst = action(() => {
+    //     this.layoutDoc._freeform_scale = undefined;
+    //     if (this.layoutEngine() === computeStarburstLayout.name) {
+    //         if (NumCast(this.layoutDoc._width) !== NumCast(this.Document._starburstDiameter, 500)) {
+    //             this.Document._starburstDiameter = NumCast(this.layoutDoc._width);
+    //         }
+    //         const defaultSize = 110;
+    //         this.Document.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width) / 2 - NumCast(this.layoutDoc._freeform_pileWidth, defaultSize) / 2;
+    //         this.Document.y = NumCast(this.Document.y) + NumCast(this.layoutDoc._height) / 2 - NumCast(this.layoutDoc._freeform_pileHeight, defaultSize) / 2;
+    //         this.layoutDoc._width = NumCast(this.layoutDoc._freeform_pileWidth, defaultSize);
+    //         this.layoutDoc._height = NumCast(this.layoutDoc._freeform_pileHeight, defaultSize);
+    //         DocUtils.pileup(this.childDocs, undefined, undefined, NumCast(this.layoutDoc._width) / 2, false);
+    //         this.layoutDoc._freeform_panX = 0;
+    //         this.layoutDoc._freeform_panY = -10;
+    //         this.Document._freeform_pileEngine = computePassLayout.name;
+    //     } else {
+    //         const defaultSize = NumCast(this.Document._starburstDiameter, 400);
+    //         this.Document.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width) / 2 - defaultSize / 2;
+    //         this.Document.y = NumCast(this.Document.y) + NumCast(this.layoutDoc._height) / 2 - defaultSize / 2;
+    //         this.layoutDoc._freeform_pileWidth = NumCast(this.layoutDoc._width);
+    //         this.layoutDoc._freeform_pileHeight = NumCast(this.layoutDoc._height);
+    //         this.layoutDoc._freeform_panX = this.layoutDoc._freeform_panY = 0;
+    //         this.layoutDoc._width = this.layoutDoc._height = defaultSize;
+    //         this.layoutDoc.background;
+    //         this.Document._freeform_pileEngine = computeStarburstLayout.name;
+    //     }
+    // });
+
+    // for dragging documents out of the pileup view
+    _undoBatch: UndoManager.Batch | undefined;
+    pointerDown = (e: React.PointerEvent) => {
+        let dist = 0;
+        setupMoveUpEvents(
+            this,
+            e,
+            (e: PointerEvent, down: number[], delta: number[]) => {
+                if (this.layoutEngine() === 'pass' && this.childDocs.length && e.shiftKey) {
+                    dist += Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]);
+                    if (dist > 100) {
+                        if (!this._undoBatch) {
+                            this._undoBatch = UndoManager.StartBatch('layout pile');
+                        }
+                        const doc = this.childDocs[0];
+                        doc.x = e.clientX;
+                        doc.y = e.clientY;
+                        this._props.addDocTab(doc, OpenWhere.inParentFromScreen) && (this._props.removeDocument?.(doc) || false);
+                        dist = 0;
+                    }
+                }
+                return false;
+            },
+            () => {
+                this._undoBatch?.end();
+                this._undoBatch = undefined;
+            },
+            emptyFunction,
+            e.shiftKey && this.layoutEngine() === computePassLayout.name,
+            this.layoutEngine() === computePassLayout.name && e.shiftKey
+        ); // this sets _doubleTap
+    };
+
+    // onClick for toggling the pileup view
+    // @undoBatch
+    // onClick = (e: React.MouseEvent) => {
+    //     if (e.button === 0) {
+    //         SelectionManager.DeselectAll();
+    //         this.toggleStarburst();
+    //         e.stopPropagation();
+    //     }
+    // };
+
+    render() {
+        return (
+            <div className={`collectionPileView`} 
+            // onClick={this.onClick} 
+            onPointerDown={this.pointerDown} style={{ width: this._props.PanelWidth(), height: '100%' }}>
+                {this.contents}
+            </div>
+        );
+    }
+}
-- 
cgit v1.2.3-70-g09d2


From d3383640a8007a15e0deb47715e203508c4fd64a Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Tue, 19 Mar 2024 16:21:34 -0400
Subject: working on implementing card logic

---
 src/client/documents/Documents.ts                  |  16 +-
 src/client/util/CurrentUserUtils.ts                |   2 +-
 .../views/collections/CollectionCardDeckView.scss  |   0
 .../views/collections/CollectionCardDeckView.tsx   | 281 +++++++++++----------
 src/client/views/collections/CollectionMenu.tsx    |   4 +
 .../CollectionFreeFormLayoutEngines.tsx            |  61 ++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   6 +-
 7 files changed, 198 insertions(+), 172 deletions(-)
 create mode 100644 src/client/views/collections/CollectionCardDeckView.scss

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index d12c61fd7..c453fd793 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1219,14 +1219,7 @@ export namespace Docs {
             );
         }
 
-        export function CardDeckDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            return InstanceFromProto(
-                Prototypes.get(DocumentType.COL),
-                new List(documents),
-                { backgroundColor: 'transparent', dropAction: dropActionType.move, _forceActive: true, _freeform_noZoom: true, _freeform_noAutoPan: true, ...options, _type_collection: CollectionViewType.Card },
-                id
-            );
-        }
+        
 
         export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Linear }, id);
@@ -1244,6 +1237,10 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Carousel3D });
         }
 
+        export function CardDeckDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Card});
+        }
+
         export function SchemaDocument(schemaHeaders: SchemaHeaderField[], documents: Array<Doc>, options: DocumentOptions) {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaHeaders: new List(schemaHeaders), ...options, _type_collection: CollectionViewType.Schema });
         }
@@ -1887,6 +1884,7 @@ export namespace DocUtils {
     }
 
     export function spreadCards(docList: Doc[], x: number = 0, y: number = 0, spreadAngle: number = 30, radius: number = 100, create: boolean = true) {
+        console.log('spread cards');
         const totalCards = docList.length;
         const halfSpreadAngle = spreadAngle * 0.5;
         const angleStep = spreadAngle / (totalCards - 1);
@@ -1897,7 +1895,7 @@ export namespace DocUtils {
                 const angle = (-halfSpreadAngle + angleStep * i) * (Math.PI / 180); // Convert degrees to radians
                 d.x = x + Math.cos(angle) * radius;
                 d.y = y + Math.sin(angle) * radius;
-                d.rotation = angle; // Assuming 'd.rotation' sets the rotation, adjust accordingly
+                d.rotation = angle; 
                 d._timecodeToShow = undefined;
             });
         });
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 11f6c82ec..15c3ddad6 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -796,7 +796,7 @@ pie title Minerals in my tap water
                     CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Multicolumn,
                     CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel,
                     CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map,
-                    CollectionViewType.Grid, CollectionViewType.NoteTaking]),
+                    CollectionViewType.Grid, CollectionViewType.NoteTaking, CollectionViewType.Card]),
               title: "Perspective",                    toolTip: "View",                 btnType: ButtonType.DropdownList,                                                                                                                                    ignoreClick: true, width: 100, scripts: { script:  'setView(value, _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()"}},
             { title: "Header",  icon: "heading",       toolTip: "Doc Titlebar Color",   btnType: ButtonType.ColorButton,         expertMode: false,                                                                                                          ignoreClick: true,             scripts: { script:  'return setHeaderColor(value, _readOnly_)'} },
diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index feb9e61cc..e35f7b421 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -1,164 +1,185 @@
-import { action, computed, IReactionDisposer, makeObservable } from 'mobx';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { Utils, emptyFunction, returnFalse, returnZero } from '../../../Utils';
 import { Doc, DocListCast } from '../../../fields/Doc';
-import { ScriptField } from '../../../fields/ScriptField';
-import { NumCast, StrCast } from '../../../fields/Types';
-import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils';
-import { DocUtils } from '../../documents/Documents';
+import { Id } from '../../../fields/FieldSymbols';
+import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { DragManager } from '../../util/DragManager';
 import { SelectionManager } from '../../util/SelectionManager';
-import { undoBatch, UndoManager } from '../../util/UndoManager';
-import { OpenWhere } from '../nodes/DocumentView';
-import { computePassLayout, computeStarburstLayout, computeCardDeckLayout } from './collectionFreeForm';
-import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
-import './CollectionPileView.scss';
+import { StyleProp } from '../StyleProvider';
+import { DocumentView } from '../nodes/DocumentView';
+import { FocusViewOptions } from '../nodes/FieldView';
+import './CollectionCarousel3DView.scss';
 import { CollectionSubView } from './CollectionSubView';
-import { dropActionType } from '../../util/DragManager';
-
+const { default: { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
 @observer
 export class CollectionCardView extends CollectionSubView() {
-    _originalChrome: any = '';
-    _disposers: { [name: string]: IReactionDisposer } = {};
-
+    @computed get scrollSpeed() {
+        return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; //default scroll speed
+    }
     constructor(props: any) {
         super(props);
         makeObservable(this);
     }
 
-    componentDidMount() {
-        if (this.layoutEngine() !== computePassLayout.name && this.layoutEngine() !== computeCardDeckLayout.name) {
-            this.Document._freeform_cardEngine = computePassLayout.name;
-        }
-        this._originalChrome = this.layoutDoc._chromeHidden;
-        this.layoutDoc._chromeHidden = true;
-    }
+    private _dropDisposer?: DragManager.DragDropDisposer;
+
     componentWillUnmount() {
-        this.layoutDoc._chromeHidden = this._originalChrome; 
-        Object.values(this._disposers).forEach(disposer => disposer?.());
+        this._dropDisposer?.();
     }
 
-    layoutEngine = () => StrCast(this.Document._freeform_cardEngine);
-
-    @undoBatch
-    addCardDoc = (doc: Doc | Doc[]) => {
-        (doc instanceof Doc ? [doc] : doc).map(d => DocUtils.iconify(d));
-        return this._props.addDocument?.(doc) || false;
+    protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
+        this._dropDisposer?.();
+        if (ele) {
+            this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
+        }
     };
 
-    @undoBatch
-    removeCardDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => {
-        (doc instanceof Doc ? [doc] : doc).forEach(d => Doc.deiconifyView(d));
-        const ret = this._props.moveDocument?.(doc, targetCollection, addDoc) || false;
-        if (ret && !DocListCast(this.dataDoc[this.fieldKey ?? 'data']).length) this.DocumentView?.()._props.removeDocument?.(this.Document);
-        return ret;
+    centerScale = Number(CAROUSEL3D_CENTER_SCALE);
+    panelWidth = () => this._props.PanelWidth() / 3;
+    panelHeight = () => this._props.PanelHeight() * Number(CAROUSEL3D_SIDE_SCALE);
+    onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
+    isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
+    isChildContentActive = () => (this.isContentActive() ? true : false);
+    childScreenToLocal = () =>
+        this._props // document's left is the panel shifted by the doc's index * panelWidth/#docs.  But it scales by centerScale around its center, so it's left moves left by the distance of the left from the center (panelwidth/2) * the scale delta (centerScale-1)
+            .ScreenToLocalTransform() // the top behaves the same way ecept it's shifted by the 'top' amount specified for the panel in css and then by the scale factor.
+            .translate(-this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2, -((Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2)
+            .scale(1 / this.centerScale);
+
+    focus = (anchor: Doc, options: FocusViewOptions) => {
+        const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
+        if (anchor.type !== DocumentType.CONFIG && !docs.includes(anchor)) return;
+        options.didMove = true;
+        const target = DocCast(anchor.annotationOn) ?? anchor;
+        const index = docs.indexOf(target);
+        index !== -1 && (this.layoutDoc._carousel_index = index); //if index is not -1, then assign index to this.layoutDoc._carousel_index
+        return undefined;
     };
 
-    @computed get toggleIcon() {
-        return ScriptField.MakeScript('documentView.iconify()', { documentView: 'any' });
-    }
-    @computed get contentEvents() {
-        const isCard = this.layoutEngine() === computeCardDeckLayout.name;
-        return this._props.isContentActive() && isCard ? undefined : 'none';
+    
+    @computed get content() {
+        const currentIndex = NumCast(this.layoutDoc._carousel_index);
+        const displayDoc = (childPair: { layout: Doc; data: Doc }) => {
+            return (
+                <DocumentView
+                    {...this._props}
+                    Document={childPair.layout}
+                    TemplateDataDocument={childPair.data}
+                    //suppressSetHeight={true}
+                    NativeWidth={returnZero}
+                    NativeHeight={returnZero}
+                    layout_fitWidth={undefined}
+                    onDoubleClickScript={this.onChildDoubleClick}
+                    renderDepth={this._props.renderDepth + 1}
+                    LayoutTemplate={this._props.childLayoutTemplate}
+                    LayoutTemplateString={this._props.childLayoutString}
+                    focus={this.focus}
+                    ScreenToLocalTransform={this.childScreenToLocal} //makes sure the box wrapper thing is in the right spot 
+                    isContentActive={this.isChildContentActive}
+                    isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
+                    PanelWidth={this.panelWidth}
+                    PanelHeight={this.panelHeight}
+                />
+            );
+        };
+
+        return this.childLayoutPairs.map((childPair, index) => {
+            return (
+                <div key={childPair.layout[Id]} className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`} style={{ width: this.panelWidth() }}>
+                    {displayDoc(childPair)}
+                </div>
+            );
+        });
     }
 
-    // returns the contents of the cardSpread in a CollectionFreeFormView
-    @computed get contents() {
+    changeSlide = (direction: number) => {
+        SelectionManager.DeselectAll();
+        this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + direction + this.childLayoutPairs.length) % this.childLayoutPairs.length;
+    };
+
+    onArrowClick = (direction: number) => {
+        this.changeSlide(direction);
+        !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = direction === 1 ? 'fwd' : 'back'); // while autoscroll is on, keep the other autoscroll button hidden
+        !this.layoutDoc.autoScrollOn && this.fadeScrollButton(); // keep pause button visible while autoscroll is on
+    };
+
+    interval?: number;
+    startAutoScroll = (direction: number) => {
+        this.interval = window.setInterval(() => {
+            this.changeSlide(direction);
+        }, this.scrollSpeed);
+    };
+
+    stopAutoScroll = () => {
+        window.clearInterval(this.interval);
+        this.interval = undefined;
+        this.fadeScrollButton();
+    };
+
+    toggleAutoScroll = (direction: number) => {
+        this.layoutDoc.autoScrollOn = this.layoutDoc.autoScrollOn ? false : true;
+        this.layoutDoc.autoScrollOn ? this.startAutoScroll(direction) : this.stopAutoScroll();
+    };
+
+    fadeScrollButton = () => {
+        window.setTimeout(() => {
+            !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = 'none'); //fade away after 1.5s if it's not clicked.
+        }, 1500);
+    };
+
+    @computed get buttons() {
         return (
-            <div className="collectionPileView-innards" style={{ pointerEvents: this.contentEvents }}>
-                <CollectionFreeFormView
-                    {...this._props} //
-                    layoutEngine={this.layoutEngine}
-                    addDocument={this.addCardDoc}
-                    moveDocument={this.removeCardDoc}
-                    // pile children never have their contents active, but will be document active whenever the entire pile is.
-                    childContentsActive={returnFalse}
-                    childDocumentsActive={this._props.isDocumentActive}
-                    childDragAction={dropActionType.move}
-                    childClickScript={this.toggleIcon}
-                />
+            <div className="arrow-buttons">
+                <div title="click to go back" key="back" className="carousel3DView-back" onClick={e => this.onArrowClick(-1)} />
+                <div title="click to advance" key="fwd" className="carousel3DView-fwd" onClick={e => this.onArrowClick(1)} />
+                {/* {this.autoScrollButton} */}
             </div>
         );
     }
 
-    // // toggles the pileup between starburst to compact
-    // toggleStarburst = action(() => {
-    //     this.layoutDoc._freeform_scale = undefined;
-    //     if (this.layoutEngine() === computeStarburstLayout.name) {
-    //         if (NumCast(this.layoutDoc._width) !== NumCast(this.Document._starburstDiameter, 500)) {
-    //             this.Document._starburstDiameter = NumCast(this.layoutDoc._width);
-    //         }
-    //         const defaultSize = 110;
-    //         this.Document.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width) / 2 - NumCast(this.layoutDoc._freeform_pileWidth, defaultSize) / 2;
-    //         this.Document.y = NumCast(this.Document.y) + NumCast(this.layoutDoc._height) / 2 - NumCast(this.layoutDoc._freeform_pileHeight, defaultSize) / 2;
-    //         this.layoutDoc._width = NumCast(this.layoutDoc._freeform_pileWidth, defaultSize);
-    //         this.layoutDoc._height = NumCast(this.layoutDoc._freeform_pileHeight, defaultSize);
-    //         DocUtils.pileup(this.childDocs, undefined, undefined, NumCast(this.layoutDoc._width) / 2, false);
-    //         this.layoutDoc._freeform_panX = 0;
-    //         this.layoutDoc._freeform_panY = -10;
-    //         this.Document._freeform_pileEngine = computePassLayout.name;
-    //     } else {
-    //         const defaultSize = NumCast(this.Document._starburstDiameter, 400);
-    //         this.Document.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width) / 2 - defaultSize / 2;
-    //         this.Document.y = NumCast(this.Document.y) + NumCast(this.layoutDoc._height) / 2 - defaultSize / 2;
-    //         this.layoutDoc._freeform_pileWidth = NumCast(this.layoutDoc._width);
-    //         this.layoutDoc._freeform_pileHeight = NumCast(this.layoutDoc._height);
-    //         this.layoutDoc._freeform_panX = this.layoutDoc._freeform_panY = 0;
-    //         this.layoutDoc._width = this.layoutDoc._height = defaultSize;
-    //         this.layoutDoc.background;
-    //         this.Document._freeform_pileEngine = computeStarburstLayout.name;
-    //     }
-    // });
-
-    // for dragging documents out of the pileup view
-    _undoBatch: UndoManager.Batch | undefined;
-    pointerDown = (e: React.PointerEvent) => {
-        let dist = 0;
-        setupMoveUpEvents(
-            this,
-            e,
-            (e: PointerEvent, down: number[], delta: number[]) => {
-                if (this.layoutEngine() === 'pass' && this.childDocs.length && e.shiftKey) {
-                    dist += Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]);
-                    if (dist > 100) {
-                        if (!this._undoBatch) {
-                            this._undoBatch = UndoManager.StartBatch('layout pile');
-                        }
-                        const doc = this.childDocs[0];
-                        doc.x = e.clientX;
-                        doc.y = e.clientY;
-                        this._props.addDocTab(doc, OpenWhere.inParentFromScreen) && (this._props.removeDocument?.(doc) || false);
-                        dist = 0;
-                    }
-                }
-                return false;
-            },
-            () => {
-                this._undoBatch?.end();
-                this._undoBatch = undefined;
-            },
-            emptyFunction,
-            e.shiftKey && this.layoutEngine() === computePassLayout.name,
-            this.layoutEngine() === computePassLayout.name && e.shiftKey
-        ); // this sets _doubleTap
-    };
+    @computed get autoScrollButton() {
+        const whichButton = this.layoutDoc.showScrollButton;
+        return (
+            <>
+                <div className={`carousel3DView-back-scroll${whichButton === 'back' ? '' : '-hidden'}`} style={{ background: `${StrCast(this.Document.backgroundColor)}` }} onClick={() => this.toggleAutoScroll(-1)}>
+                    {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={'pause'} size={'1x'} /> : <FontAwesomeIcon icon={'angle-double-left'} size={'1x'} />}
+                </div>
+                <div className={`carousel3DView-fwd-scroll${whichButton === 'fwd' ? '' : '-hidden'}`} style={{ background: `${StrCast(this.Document.backgroundColor)}` }} onClick={() => this.toggleAutoScroll(1)}>
+                    {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={'pause'} size={'1x'} /> : <FontAwesomeIcon icon={'angle-double-right'} size={'1x'} />}
+                </div>
+            </>
+        );
+    }
 
-    // onClick for toggling the pileup view
-    // @undoBatch
-    // onClick = (e: React.MouseEvent) => {
-    //     if (e.button === 0) {
-    //         SelectionManager.DeselectAll();
-    //         this.toggleStarburst();
-    //         e.stopPropagation();
-    //     }
-    // };
+    @computed get dots() {
+        return this.childLayoutPairs.map((_child, index) => <div key={Utils.GenerateGuid()} className={`dot${index === NumCast(this.layoutDoc._carousel_index) ? '-active' : ''}`} onClick={() => (this.layoutDoc._carousel_index = index)} />);
+    }
+    @computed get translateX() {
+        const index = NumCast(this.layoutDoc._carousel_index);
+        return this.panelWidth() * (1 - index);
+    }
 
     render() {
         return (
-            <div className={`collectionPileView`} 
-            // onClick={this.onClick} 
-            onPointerDown={this.pointerDown} style={{ width: this._props.PanelWidth(), height: '100%' }}>
-                {this.contents}
+            <div
+                className="collectionCarousel3DView-outer"
+                ref={this.createDashEventsTarget}
+                style={{
+                    background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor),
+                    color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color),
+                }}>
+                <div className="carousel-wrapper" style={{ transform: `translateX(${this.translateX}px)` }}>
+                    {this.content}
+                </div>
+                {this.buttons}
+                <div className="dot-bar">{this.dots}</div>
             </div>
         );
     }
+
+
 }
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 8729ef549..9d135eb3b 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -319,6 +319,10 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
                 return this._freeform_commands;
             case CollectionViewType.Carousel3D:
                 return this._freeform_commands;
+            case CollectionViewType.Card:
+                return this._freeform_commands;
+            
+            
         }
     }
     private _commandRef = React.createRef<HTMLInputElement>();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index e972f44f1..b3ae382e3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -126,39 +126,40 @@ export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc
     return normalizeResults(burstDiam, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]);
 }
 
-export function computeCardDeckLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
-    const docMap = new Map<string, PoolData>();
-    const spreadWidth = Math.min(panelDim[0], childPairs.length * 50); // Total width of the spread
-    const startX = -(spreadWidth / 2); // Starting X position
-    const fanAngle = 5; // Angle in degrees for fanning out cards
-    const baseZIndex = 1000; // Base Z-index to ensure cards are stacked in order
+// export function computeCardDeckLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
+//     console.log('hi');
+//     const docMap = new Map<string, PoolData>();
+//     const spreadWidth = Math.min(panelDim[0], childPairs.length * 50); // Total width of the spread
+//     const startX = -(spreadWidth / 2); // Starting X position
+//     const fanAngle = 5; // Angle in degrees for fanning out cards
+//     const baseZIndex = 1000; // Base Z-index to ensure cards are stacked in order
 
-    childPairs.forEach(({ layout, data }, i) => {
-        const aspect = NumCast(layout._height) / NumCast(layout._width);
-        const docSize = Math.min(400, NumCast(layout._width)) * NumCast(pivotDoc._starburstDocScale, 1);
-        const posX = startX + (spreadWidth / childPairs.length) * i;
-        const posY = 0; // Adjust if you want to change the vertical alignment
-        const rotation = (i - (childPairs.length / 2)) * fanAngle; // Calculate rotation for fanning effect
+//     childPairs.forEach(({ layout, data }, i) => {
+//         const aspect = NumCast(layout._height) / NumCast(layout._width);
+//         const docSize = Math.min(400, NumCast(layout._width)) * NumCast(pivotDoc._starburstDocScale, 1);
+//         const posX = startX + (spreadWidth / childPairs.length) * i;
+//         const posY = 0; // Adjust if you want to change the vertical alignment
+//         const rotation = (i - (childPairs.length / 2)) * fanAngle; // Calculate rotation for fanning effect
 
-        docMap.set(layout[Id], {
-            x: posX,
-            y: posY,
-            width: docSize,
-            height: docSize * aspect,
-            zIndex: baseZIndex + i, // Increment Z-index for each card to stack them correctly
-            rotation: rotation, // Optional: Add this if you want to rotate elements for a fanned effect
-            pair: { layout, data },
-            replica: '',
-            color: 'white',
-            backgroundColor: 'white',
-            transition: 'all 0.3s',
-        });
-    });
+//         docMap.set(layout[Id], {
+//             x: posX,
+//             y: posY,
+//             width: docSize,
+//             height: docSize * aspect,
+//             zIndex: baseZIndex + i,
+//             rotation: rotation,
+//             pair: { layout, data },
+//             replica: '',
+//             color: 'white',
+//             backgroundColor: 'white',
+//             transition: 'all 0.3s',
+//         });
+//     });
 
-    // This is a placeholder for the divider object and may need to be adjusted based on actual usage
-    const divider = { type: 'div', color: 'transparent', x: -panelDim[0] / 2, y: -panelDim[1] / 2, width: 15, height: 15, payload: undefined };
-    return normalizeResults(panelDim, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]);
-}
+//     // This is a placeholder for the divider object and may need to be adjusted based on actual usage
+//     const divider = { type: 'div', color: 'transparent', x: -panelDim[0] / 2, y: -panelDim[1] / 2, width: 15, height: 15, payload: undefined };
+//     return normalizeResults(panelDim, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]);
+// }
 
 export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
     const docMap = new Map<string, PoolData>();
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index f9fe306fa..10b849dab 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -49,7 +49,9 @@ import { CollectionSubView } from '../CollectionSubView';
 import { TreeViewType } from '../CollectionTreeView';
 import { CollectionFreeFormBackgroundGrid } from './CollectionFreeFormBackgroundGrid';
 import { CollectionFreeFormInfoUI } from './CollectionFreeFormInfoUI';
-import { computePassLayout, computePivotLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult, computeCardDeckLayout } from './CollectionFreeFormLayoutEngines';
+import { computePassLayout, computePivotLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult,
+    //  computeCardDeckLayout 
+    } from './CollectionFreeFormLayoutEngines';
 import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannableContents';
 import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors';
 import './CollectionFreeFormView.scss';
@@ -1383,7 +1385,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             case computeTimelineLayout.name:  return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
             case computePivotLayout.name:     return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) };
             case computeStarburstLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeStarburstLayout) };
-            case computeCardDeckLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeCardDeckLayout) };
+            // case computeCardDeckLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeCardDeckLayout) };
 
         }
         return { newPool, computedElementData: this.doFreeformLayout(newPool) };
-- 
cgit v1.2.3-70-g09d2


From 828c53aee1cbb7db51354e2e0c3752c2cab34d22 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Wed, 20 Mar 2024 03:27:55 -0400
Subject: me vs pointer events who will win

---
 src/client/views/DocComponent.tsx                  |   2 +-
 .../views/collections/CollectionCardDeckView.tsx   | 168 ++++++++++++---------
 2 files changed, 94 insertions(+), 76 deletions(-)

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

diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 18e7ab12a..a7a9763ec 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -35,7 +35,7 @@ export interface ViewBoxInterface {
     addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections)
     removeDocument?: (doc: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean, dontAddToRemoved?: boolean) => boolean; // add a document (used only by collections)
     select?: (ctrlKey: boolean, shiftKey: boolean) => void;
-    focus?: (textAnchor: Doc, options: FocusViewOptions) => Opt<number>;
+    focus?: (textAnchor: Doc, options: FocusViewOptions) => Opt<number>; 
     viewTransition?: () => Opt<string>; // duration of a view transition animation
     isAnyChildContentActive?: () => boolean; // is any child content of the document active
     onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index e35f7b421..778280215 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -1,6 +1,6 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { computed, makeObservable } from 'mobx';
-import { observer } from 'mobx-react';
+import { computed, makeObservable, observable, action } from 'mobx';
+import { observer, } from 'mobx-react';
 import * as React from 'react';
 import { Utils, emptyFunction, returnFalse, returnZero } from '../../../Utils';
 import { Doc, DocListCast } from '../../../fields/Doc';
@@ -14,12 +14,31 @@ import { DocumentView } from '../nodes/DocumentView';
 import { FocusViewOptions } from '../nodes/FieldView';
 import './CollectionCarousel3DView.scss';
 import { CollectionSubView } from './CollectionSubView';
+import { ScriptField } from '../../../fields/ScriptField';
+
 const { default: { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
 @observer
 export class CollectionCardView extends CollectionSubView() {
-    @computed get scrollSpeed() {
-        return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; //default scroll speed
-    }
+    // @computed get scrollSpeed() {
+    //     return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; //default scroll speed
+    // }
+
+    selectedNodeIndex = observable.box(-1); // -1 indicates no selection
+
+    @action
+    setSelectedNodeIndex = (index: number) => {
+        console.log('hi');
+        SelectionManager.DeselectAll();
+        this.selectedNodeIndex.set(index);
+    };
+
+    @computed
+    get rotationDegree() {
+    return this.isChildContentActive() ? 30 : 0; // Rotate by 30 degrees if selected, otherwise no rotation
+}
+
+
+
     constructor(props: any) {
         super(props);
         makeObservable(this);
@@ -39,17 +58,23 @@ export class CollectionCardView extends CollectionSubView() {
     };
 
     centerScale = Number(CAROUSEL3D_CENTER_SCALE);
-    panelWidth = () => this._props.PanelWidth() / 3;
-    panelHeight = () => this._props.PanelHeight() * Number(CAROUSEL3D_SIDE_SCALE);
+    panelWidth = () => (this._props.PanelWidth()) / this.childLayoutPairs.length;
+    panelHeight = () => this.panelWidth() * 1.5;
     onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
+    onChildClick = () => ScriptCast(this.rotate(3, 3));
     isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
     isChildContentActive = () => (this.isContentActive() ? true : false);
+    
+    //thid needs to be fixed
     childScreenToLocal = () =>
         this._props // document's left is the panel shifted by the doc's index * panelWidth/#docs.  But it scales by centerScale around its center, so it's left moves left by the distance of the left from the center (panelwidth/2) * the scale delta (centerScale-1)
             .ScreenToLocalTransform() // the top behaves the same way ecept it's shifted by the 'top' amount specified for the panel in css and then by the scale factor.
-            .translate(-this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2, -((Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2)
-            .scale(1 / this.centerScale);
+            // .translate(-this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2,-((Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2)
+            .rotate(this.rotate(this.childLayoutPairs.length, Number(this.layoutDoc._carousel_index)))
+            // .scale(1 / this.centerScale);
 
+
+    //literally doesnot do anythin
     focus = (anchor: Doc, options: FocusViewOptions) => {
         const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
         if (anchor.type !== DocumentType.CONFIG && !docs.includes(anchor)) return;
@@ -60,15 +85,41 @@ export class CollectionCardView extends CollectionSubView() {
         return undefined;
     };
 
+    rotate = (amCards: number, index: number) => {
+
+        const possRotate = -30 + (index * (30/((amCards - (amCards%2))/2)));
+
+        if (amCards%2 == 0 && possRotate==0){
+                console.log('whaddup')
+                return possRotate + Math.abs(-30 + ((index-1) * (30/((amCards - 1)/2))))
+        }
+    
+       return possRotate;
+    }
+
+    // translateY = (amCards: number, index: number) => {
+    //     // Assuming you want a default value when index > amCards/2
+    //     // Adjust the logic as necessary for your use case
+    //     if (index <= amCards / 2) {
+    //         return -((50 / ((amCards - (amCards % 2)) / 2)) * index);
+    //     } else {
+    //         // Return a default or calculated value for indices greater than amCards/2
+    //         // This is just an example; adjust the logic as needed
+    //         return -((50 / ((amCards - (amCards % 2)) / 2)) * (amCards - index - 1));
+    //     }
+    // };
+
     
     @computed get content() {
-        const currentIndex = NumCast(this.layoutDoc._carousel_index);
+        // const currentIndex = NumCast(this.layoutDoc._carousel_index);
+        const amCards = this.childLayoutPairs.length;
         const displayDoc = (childPair: { layout: Doc; data: Doc }) => {
             return (
                 <DocumentView
                     {...this._props}
                     Document={childPair.layout}
                     TemplateDataDocument={childPair.data}
+                    // onClickScript={this.toggleIcon}
                     //suppressSetHeight={true}
                     NativeWidth={returnZero}
                     NativeHeight={returnZero}
@@ -88,76 +139,41 @@ export class CollectionCardView extends CollectionSubView() {
         };
 
         return this.childLayoutPairs.map((childPair, index) => {
+
+            const isSelected = this.selectedNodeIndex.get() === index;
+            const rotationDegree = isSelected ? 30 : 0; // Rotate selected node by 5 degrees, for example
+
+
             return (
-                <div key={childPair.layout[Id]} className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`} style={{ width: this.panelWidth() }}>
+                <div key={childPair.layout[Id]} 
+                // className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`} 
+                style={{ width: this.panelWidth(), height: this.panelHeight(), 
+                    transform: `rotate(${this.rotate(amCards, index) + rotationDegree}deg)`,
+                
+        
+                // translateY(${this.translateY(amCards, index)}px)`,
+                }}
+                onClick={() => this.setSelectedNodeIndex(index)}
+
+ 
+                
+                >
+                    {/* {this.lol(childPair.data, index)} */}
+
+                
                     {displayDoc(childPair)}
-                </div>
+                </div> 
             );
         });
     }
 
-    changeSlide = (direction: number) => {
-        SelectionManager.DeselectAll();
-        this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + direction + this.childLayoutPairs.length) % this.childLayoutPairs.length;
-    };
-
-    onArrowClick = (direction: number) => {
-        this.changeSlide(direction);
-        !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = direction === 1 ? 'fwd' : 'back'); // while autoscroll is on, keep the other autoscroll button hidden
-        !this.layoutDoc.autoScrollOn && this.fadeScrollButton(); // keep pause button visible while autoscroll is on
-    };
-
-    interval?: number;
-    startAutoScroll = (direction: number) => {
-        this.interval = window.setInterval(() => {
-            this.changeSlide(direction);
-        }, this.scrollSpeed);
-    };
+    // lol = (d : Doc, index: number) => {
+    //     if (SelectionManager.IsSelected(d)){
+    //         this.setSelectedNodeIndex(index);
 
-    stopAutoScroll = () => {
-        window.clearInterval(this.interval);
-        this.interval = undefined;
-        this.fadeScrollButton();
-    };
-
-    toggleAutoScroll = (direction: number) => {
-        this.layoutDoc.autoScrollOn = this.layoutDoc.autoScrollOn ? false : true;
-        this.layoutDoc.autoScrollOn ? this.startAutoScroll(direction) : this.stopAutoScroll();
-    };
+    //     }
+    // }
 
-    fadeScrollButton = () => {
-        window.setTimeout(() => {
-            !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = 'none'); //fade away after 1.5s if it's not clicked.
-        }, 1500);
-    };
-
-    @computed get buttons() {
-        return (
-            <div className="arrow-buttons">
-                <div title="click to go back" key="back" className="carousel3DView-back" onClick={e => this.onArrowClick(-1)} />
-                <div title="click to advance" key="fwd" className="carousel3DView-fwd" onClick={e => this.onArrowClick(1)} />
-                {/* {this.autoScrollButton} */}
-            </div>
-        );
-    }
-
-    @computed get autoScrollButton() {
-        const whichButton = this.layoutDoc.showScrollButton;
-        return (
-            <>
-                <div className={`carousel3DView-back-scroll${whichButton === 'back' ? '' : '-hidden'}`} style={{ background: `${StrCast(this.Document.backgroundColor)}` }} onClick={() => this.toggleAutoScroll(-1)}>
-                    {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={'pause'} size={'1x'} /> : <FontAwesomeIcon icon={'angle-double-left'} size={'1x'} />}
-                </div>
-                <div className={`carousel3DView-fwd-scroll${whichButton === 'fwd' ? '' : '-hidden'}`} style={{ background: `${StrCast(this.Document.backgroundColor)}` }} onClick={() => this.toggleAutoScroll(1)}>
-                    {this.layoutDoc.autoScrollOn ? <FontAwesomeIcon icon={'pause'} size={'1x'} /> : <FontAwesomeIcon icon={'angle-double-right'} size={'1x'} />}
-                </div>
-            </>
-        );
-    }
-
-    @computed get dots() {
-        return this.childLayoutPairs.map((_child, index) => <div key={Utils.GenerateGuid()} className={`dot${index === NumCast(this.layoutDoc._carousel_index) ? '-active' : ''}`} onClick={() => (this.layoutDoc._carousel_index = index)} />);
-    }
     @computed get translateX() {
         const index = NumCast(this.layoutDoc._carousel_index);
         return this.panelWidth() * (1 - index);
@@ -172,11 +188,13 @@ export class CollectionCardView extends CollectionSubView() {
                     background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor),
                     color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color),
                 }}>
-                <div className="carousel-wrapper" style={{ transform: `translateX(${this.translateX}px)` }}>
+                <div className="carousel-wrapper" 
+                // style={{ transform: `translateX(${this.translateX}px)` }}
+                >
                     {this.content}
                 </div>
-                {this.buttons}
-                <div className="dot-bar">{this.dots}</div>
+                {/* {this.buttons}
+                <div className="dot-bar">{this.dots}</div> */}
             </div>
         );
     }
-- 
cgit v1.2.3-70-g09d2


From 568d196c15c44f89388985c267d4ffe6c2e28a7b Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Wed, 20 Mar 2024 14:14:02 -0400
Subject: figuring out how to find the doc wrapper thing

---
 .../views/collections/CollectionCardDeckView.scss  |  19 ++++
 .../views/collections/CollectionCardDeckView.tsx   | 114 ++++++++++++++-------
 2 files changed, 96 insertions(+), 37 deletions(-)

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

diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss
index e69de29bb..b6d9ca202 100644
--- a/src/client/views/collections/CollectionCardDeckView.scss
+++ b/src/client/views/collections/CollectionCardDeckView.scss
@@ -0,0 +1,19 @@
+@import '../global/globalCssVariables.module.scss';
+
+.collectionCardView-outer {
+    height: 100%;
+    position: relative;
+    background-color: white;
+    overflow: hidden;
+}
+
+.card-wrapper {
+    display: flex;
+    position: absolute;
+    top: $CAROUSEL3D_TOP * 1%;
+    height: ($CAROUSEL3D_SIDE_SCALE * 100) * 1%;
+    align-items: center;
+    transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955);
+}
+
+
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 778280215..3b3c4a764 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -12,36 +12,69 @@ import { SelectionManager } from '../../util/SelectionManager';
 import { StyleProp } from '../StyleProvider';
 import { DocumentView } from '../nodes/DocumentView';
 import { FocusViewOptions } from '../nodes/FieldView';
-import './CollectionCarousel3DView.scss';
+import './CollectionCardDeckView.scss';
 import { CollectionSubView } from './CollectionSubView';
 import { ScriptField } from '../../../fields/ScriptField';
+import { lengthToDegrees } from '@turf/turf';
 
-const { default: { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } } = require('../global/globalCssVariables.module.scss'); // prettier-ignore
 @observer
 export class CollectionCardView extends CollectionSubView() {
-    // @computed get scrollSpeed() {
-    //     return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; //default scroll speed
-    // }
+   
+    @observable selectedNodeIndex = -1;
+
+    @observable hoveredNodeIndex = -1;
+
+    @action
+    setHoveredNodeIndex = (index : number) => {
+        this.hoveredNodeIndex = index;
+    }
 
-    selectedNodeIndex = observable.box(-1); // -1 indicates no selection
+    translateHover = (index : number) => {
+        if (this.hoveredNodeIndex == index){
+            return -50;
+        }
+        return 0;
+    }
+
+    
 
     @action
     setSelectedNodeIndex = (index: number) => {
-        console.log('hi');
-        SelectionManager.DeselectAll();
-        this.selectedNodeIndex.set(index);
+
+        const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
+        console.log("goodnight")
+
+        if (SelectionManager.IsSelected(docs[index])){
+            console.log("good mornings")
+            this.setSelectedNodeIndex(index);
+        }
     };
 
-    @computed
-    get rotationDegree() {
-    return this.isChildContentActive() ? 30 : 0; // Rotate by 30 degrees if selected, otherwise no rotation
-}
+
+    isSelected = (index : number) => {
+        const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
+
+        return SelectionManager.IsSelected(docs[index])
+
+    }
+
+    // @computed
+    // rotationDegree =  (index : number) =>  {
+    //     const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
+
+    //         if (SelectionManager.IsSelected(docs[index])){
+    //             return 30;
+    //         }
+
+    //     return 0;
+    // }
 
 
 
     constructor(props: any) {
         super(props);
         makeObservable(this);
+        // this.rotationDegree(7);
     }
 
     private _dropDisposer?: DragManager.DragDropDisposer;
@@ -57,11 +90,10 @@ export class CollectionCardView extends CollectionSubView() {
         }
     };
 
-    centerScale = Number(CAROUSEL3D_CENTER_SCALE);
     panelWidth = () => (this._props.PanelWidth()) / this.childLayoutPairs.length;
     panelHeight = () => this.panelWidth() * 1.5;
     onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
-    onChildClick = () => ScriptCast(this.rotate(3, 3));
+    // onChildClick = () => ScriptCast(this.rotate(3, 3));
     isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
     isChildContentActive = () => (this.isContentActive() ? true : false);
     
@@ -75,15 +107,15 @@ export class CollectionCardView extends CollectionSubView() {
 
 
     //literally doesnot do anythin
-    focus = (anchor: Doc, options: FocusViewOptions) => {
-        const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
-        if (anchor.type !== DocumentType.CONFIG && !docs.includes(anchor)) return;
-        options.didMove = true;
-        const target = DocCast(anchor.annotationOn) ?? anchor;
-        const index = docs.indexOf(target);
-        index !== -1 && (this.layoutDoc._carousel_index = index); //if index is not -1, then assign index to this.layoutDoc._carousel_index
-        return undefined;
-    };
+    // focus = (anchor: Doc, options: FocusViewOptions) => {
+    //     const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
+    //     if (anchor.type !== DocumentType.CONFIG && !docs.includes(anchor)) return;
+    //     options.didMove = true;
+    //     const target = DocCast(anchor.annotationOn) ?? anchor;
+    //     const index = docs.indexOf(target);
+    //     index !== -1 && (this.layoutDoc._carousel_index = index); //if index is not -1, then assign index to this.layoutDoc._carousel_index
+    //     return undefined;
+    // };
 
     rotate = (amCards: number, index: number) => {
 
@@ -128,7 +160,7 @@ export class CollectionCardView extends CollectionSubView() {
                     renderDepth={this._props.renderDepth + 1}
                     LayoutTemplate={this._props.childLayoutTemplate}
                     LayoutTemplateString={this._props.childLayoutString}
-                    focus={this.focus}
+                    // focus={this.focus}
                     ScreenToLocalTransform={this.childScreenToLocal} //makes sure the box wrapper thing is in the right spot 
                     isContentActive={this.isChildContentActive}
                     isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
@@ -140,20 +172,29 @@ export class CollectionCardView extends CollectionSubView() {
 
         return this.childLayoutPairs.map((childPair, index) => {
 
-            const isSelected = this.selectedNodeIndex.get() === index;
-            const rotationDegree = isSelected ? 30 : 0; // Rotate selected node by 5 degrees, for example
-
+            const isSelected = this.isSelected(index);
+            console.log(index + "is select?: " + isSelected);
+            const isHovered = this.hoveredNodeIndex === index;
 
             return (
                 <div key={childPair.layout[Id]} 
                 // className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`} 
                 style={{ width: this.panelWidth(), height: this.panelHeight(), 
-                    transform: `rotate(${this.rotate(amCards, index) + rotationDegree}deg)`,
-                
-        
-                // translateY(${this.translateY(amCards, index)}px)`,
+                    transform: `rotate(${!isSelected ? this.rotate(amCards, index) : 0}deg) 
+                    scale(${isSelected ? 1.25 : 1})
+                    translateY(${isHovered ? this.translateHover(index) : isSelected ? 50 : 0}px)
+                    `,
+
+
+
+
                 }}
-                onClick={() => this.setSelectedNodeIndex(index)}
+
+                
+                // onClick={() => this.setSelectedNodeIndex(index)}
+
+                onMouseEnter={() => this.setHoveredNodeIndex(index)}
+                onMouseLeave={() => this.setHoveredNodeIndex(-1)}
 
  
                 
@@ -182,19 +223,18 @@ export class CollectionCardView extends CollectionSubView() {
     render() {
         return (
             <div
-                className="collectionCarousel3DView-outer"
+                className="collectionCardView-outer"
                 ref={this.createDashEventsTarget}
                 style={{
                     background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor),
                     color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color),
                 }}>
-                <div className="carousel-wrapper" 
+                <div 
+                className="card-wrapper" 
                 // style={{ transform: `translateX(${this.translateX}px)` }}
                 >
                     {this.content}
                 </div>
-                {/* {this.buttons}
-                <div className="dot-bar">{this.dots}</div> */}
             </div>
         );
     }
-- 
cgit v1.2.3-70-g09d2


From 1bc9290023c854f805a5fea15ffe34383be3d242 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 20 Mar 2024 16:09:22 -0400
Subject: fix for document decorations

---
 .../views/collections/CollectionCardDeckView.tsx   | 127 +++++++++------------
 1 file changed, 55 insertions(+), 72 deletions(-)

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

diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 3b3c4a764..460f74a61 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -1,62 +1,52 @@
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { computed, makeObservable, observable, action } from 'mobx';
-import { observer, } from 'mobx-react';
+import { ObservableMap, action, computed, makeObservable, observable } from 'mobx';
+import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Utils, emptyFunction, returnFalse, returnZero } from '../../../Utils';
+import { Utils, returnZero } from '../../../Utils';
 import { Doc, DocListCast } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
-import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
-import { DocumentType } from '../../documents/DocumentTypes';
+import { NumCast, ScriptCast } from '../../../fields/Types';
 import { DragManager } from '../../util/DragManager';
 import { SelectionManager } from '../../util/SelectionManager';
 import { StyleProp } from '../StyleProvider';
 import { DocumentView } from '../nodes/DocumentView';
-import { FocusViewOptions } from '../nodes/FieldView';
 import './CollectionCardDeckView.scss';
 import { CollectionSubView } from './CollectionSubView';
-import { ScriptField } from '../../../fields/ScriptField';
-import { lengthToDegrees } from '@turf/turf';
+import { Transform } from '../../util/Transform';
 
 @observer
 export class CollectionCardView extends CollectionSubView() {
-   
     @observable selectedNodeIndex = -1;
 
     @observable hoveredNodeIndex = -1;
 
     @action
-    setHoveredNodeIndex = (index : number) => {
+    setHoveredNodeIndex = (index: number) => {
         this.hoveredNodeIndex = index;
-    }
+    };
 
-    translateHover = (index : number) => {
-        if (this.hoveredNodeIndex == index){
+    translateHover = (index: number) => {
+        if (this.hoveredNodeIndex == index) {
             return -50;
         }
         return 0;
-    }
-
-    
+    };
 
     @action
     setSelectedNodeIndex = (index: number) => {
-
         const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
-        console.log("goodnight")
+        console.log('goodnight');
 
-        if (SelectionManager.IsSelected(docs[index])){
-            console.log("good mornings")
+        if (SelectionManager.IsSelected(docs[index])) {
+            console.log('good mornings');
             this.setSelectedNodeIndex(index);
         }
     };
 
-
-    isSelected = (index : number) => {
+    isSelected = (index: number) => {
         const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
 
-        return SelectionManager.IsSelected(docs[index])
-
-    }
+        return SelectionManager.IsSelected(docs[index]);
+    };
 
     // @computed
     // rotationDegree =  (index : number) =>  {
@@ -69,8 +59,6 @@ export class CollectionCardView extends CollectionSubView() {
     //     return 0;
     // }
 
-
-
     constructor(props: any) {
         super(props);
         makeObservable(this);
@@ -90,21 +78,20 @@ export class CollectionCardView extends CollectionSubView() {
         }
     };
 
-    panelWidth = () => (this._props.PanelWidth()) / this.childLayoutPairs.length;
+    panelWidth = () => this._props.PanelWidth() / this.childLayoutPairs.length;
     panelHeight = () => this.panelWidth() * 1.5;
     onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
     // onChildClick = () => ScriptCast(this.rotate(3, 3));
     isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
     isChildContentActive = () => (this.isContentActive() ? true : false);
-    
+
     //thid needs to be fixed
     childScreenToLocal = () =>
         this._props // document's left is the panel shifted by the doc's index * panelWidth/#docs.  But it scales by centerScale around its center, so it's left moves left by the distance of the left from the center (panelwidth/2) * the scale delta (centerScale-1)
             .ScreenToLocalTransform() // the top behaves the same way ecept it's shifted by the 'top' amount specified for the panel in css and then by the scale factor.
             // .translate(-this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2,-((Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2)
-            .rotate(this.rotate(this.childLayoutPairs.length, Number(this.layoutDoc._carousel_index)))
-            // .scale(1 / this.centerScale);
-
+            .rotate(this.rotate(this.childLayoutPairs.length, Number(this.layoutDoc._carousel_index)));
+    // .scale(1 / this.centerScale);
 
     //literally doesnot do anythin
     // focus = (anchor: Doc, options: FocusViewOptions) => {
@@ -118,16 +105,15 @@ export class CollectionCardView extends CollectionSubView() {
     // };
 
     rotate = (amCards: number, index: number) => {
+        const possRotate = -30 + index * (30 / ((amCards - (amCards % 2)) / 2));
 
-        const possRotate = -30 + (index * (30/((amCards - (amCards%2))/2)));
-
-        if (amCards%2 == 0 && possRotate==0){
-                console.log('whaddup')
-                return possRotate + Math.abs(-30 + ((index-1) * (30/((amCards - 1)/2))))
+        if (amCards % 2 == 0 && possRotate == 0) {
+            console.log('whaddup');
+            return possRotate + Math.abs(-30 + (index - 1) * (30 / ((amCards - 1) / 2)));
         }
-    
-       return possRotate;
-    }
+
+        return possRotate;
+    };
 
     // translateY = (amCards: number, index: number) => {
     //     // Assuming you want a default value when index > amCards/2
@@ -140,15 +126,16 @@ export class CollectionCardView extends CollectionSubView() {
     //         return -((50 / ((amCards - (amCards % 2)) / 2)) * (amCards - index - 1));
     //     }
     // };
+    @observable docRefs = new ObservableMap<Doc, DocumentView>();
 
-    
     @computed get content() {
         // const currentIndex = NumCast(this.layoutDoc._carousel_index);
         const amCards = this.childLayoutPairs.length;
-        const displayDoc = (childPair: { layout: Doc; data: Doc }) => {
+        const displayDoc = (childPair: { layout: Doc; data: Doc }, screenToLocalTransform: () => Transform) => {
             return (
                 <DocumentView
                     {...this._props}
+                    ref={action((r: DocumentView) => r?.ContentDiv && this.docRefs.set(childPair.layout, r))}
                     Document={childPair.layout}
                     TemplateDataDocument={childPair.data}
                     // onClickScript={this.toggleIcon}
@@ -161,7 +148,7 @@ export class CollectionCardView extends CollectionSubView() {
                     LayoutTemplate={this._props.childLayoutTemplate}
                     LayoutTemplateString={this._props.childLayoutString}
                     // focus={this.focus}
-                    ScreenToLocalTransform={this.childScreenToLocal} //makes sure the box wrapper thing is in the right spot 
+                    ScreenToLocalTransform={screenToLocalTransform} //makes sure the box wrapper thing is in the right spot
                     isContentActive={this.isChildContentActive}
                     isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
                     PanelWidth={this.panelWidth}
@@ -171,39 +158,37 @@ export class CollectionCardView extends CollectionSubView() {
         };
 
         return this.childLayoutPairs.map((childPair, index) => {
-
             const isSelected = this.isSelected(index);
-            console.log(index + "is select?: " + isSelected);
+            console.log(index + 'is select?: ' + isSelected);
             const isHovered = this.hoveredNodeIndex === index;
 
+            const childScreenToLocal = () => {
+                const dref = this.docRefs.get(childPair.layout);
+                const { translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv);
+                // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform.  so we have to subtract that off
+                return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(1 / (isSelected ? 1.25 : 1)).rotate(!isSelected ? -this.rotate(amCards, index) : 0);
+            };
+
             return (
-                <div key={childPair.layout[Id]} 
-                // className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`} 
-                style={{ width: this.panelWidth(), height: this.panelHeight(), 
-                    transform: `rotate(${!isSelected ? this.rotate(amCards, index) : 0}deg) 
+                <div
+                    key={childPair.layout[Id]}
+                    // className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`}
+                    style={{
+                        width: this.panelWidth(),
+                        height: this.panelHeight(),
+                        transform: `rotate(${!isSelected ? this.rotate(amCards, index) : 0}deg) 
                     scale(${isSelected ? 1.25 : 1})
                     translateY(${isHovered ? this.translateHover(index) : isSelected ? 50 : 0}px)
                     `,
+                    }}
+                    // onClick={() => this.setSelectedNodeIndex(index)}
 
-
-
-
-                }}
-
-                
-                // onClick={() => this.setSelectedNodeIndex(index)}
-
-                onMouseEnter={() => this.setHoveredNodeIndex(index)}
-                onMouseLeave={() => this.setHoveredNodeIndex(-1)}
-
- 
-                
-                >
+                    onMouseEnter={() => this.setHoveredNodeIndex(index)}
+                    onMouseLeave={() => this.setHoveredNodeIndex(-1)}>
                     {/* {this.lol(childPair.data, index)} */}
 
-                
-                    {displayDoc(childPair)}
-                </div> 
+                    {displayDoc(childPair, childScreenToLocal)}
+                </div>
             );
         });
     }
@@ -229,15 +214,13 @@ export class CollectionCardView extends CollectionSubView() {
                     background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor),
                     color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color),
                 }}>
-                <div 
-                className="card-wrapper" 
-                // style={{ transform: `translateX(${this.translateX}px)` }}
+                <div
+                    className="card-wrapper"
+                    // style={{ transform: `translateX(${this.translateX}px)` }}
                 >
                     {this.content}
                 </div>
             </div>
         );
     }
-
-
 }
-- 
cgit v1.2.3-70-g09d2


From e15b5873b429e9a01cf92a471748d8ee123461ef Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Thu, 21 Mar 2024 03:29:26 -0400
Subject: .

---
 .../views/collections/CollectionCardDeckView.scss  |  10 ++
 .../views/collections/CollectionCardDeckView.tsx   | 110 ++++++++++++---------
 2 files changed, 71 insertions(+), 49 deletions(-)

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

diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss
index b6d9ca202..95a9b658c 100644
--- a/src/client/views/collections/CollectionCardDeckView.scss
+++ b/src/client/views/collections/CollectionCardDeckView.scss
@@ -16,4 +16,14 @@
     transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955);
 }
 
+.card-item-active, .card-item{
+    transition: transform 0.3s ease-in-out;
+}
+
+
+.card-item-active{
+    position: absolute;
+    z-index: 100;
+}
+
 
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 460f74a61..a4d0d1862 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -12,6 +12,7 @@ import { DocumentView } from '../nodes/DocumentView';
 import './CollectionCardDeckView.scss';
 import { CollectionSubView } from './CollectionSubView';
 import { Transform } from '../../util/Transform';
+import { indexes } from 'd3';
 
 @observer
 export class CollectionCardView extends CollectionSubView() {
@@ -21,11 +22,13 @@ export class CollectionCardView extends CollectionSubView() {
 
     @action
     setHoveredNodeIndex = (index: number) => {
+        if (!this.isSelected(index)){
         this.hoveredNodeIndex = index;
+        }
     };
 
-    translateHover = (index: number) => {
-        if (this.hoveredNodeIndex == index) {
+    translateHover = (index: number): number => {
+        if (this.hoveredNodeIndex == index && !this.isSelected(index)) {
             return -50;
         }
         return 0;
@@ -42,22 +45,17 @@ export class CollectionCardView extends CollectionSubView() {
         }
     };
 
-    isSelected = (index: number) => {
+    isSelected = (index: number): boolean => {
         const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
 
         return SelectionManager.IsSelected(docs[index]);
     };
 
-    // @computed
-    // rotationDegree =  (index : number) =>  {
-    //     const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
-
-    //         if (SelectionManager.IsSelected(docs[index])){
-    //             return 30;
-    //         }
+    inactiveDocs = () => {
+        const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
 
-    //     return 0;
-    // }
+        return docs.filter(d => !SelectionManager.IsSelected(d));
+    }
 
     constructor(props: any) {
         super(props);
@@ -81,28 +79,9 @@ export class CollectionCardView extends CollectionSubView() {
     panelWidth = () => this._props.PanelWidth() / this.childLayoutPairs.length;
     panelHeight = () => this.panelWidth() * 1.5;
     onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
-    // onChildClick = () => ScriptCast(this.rotate(3, 3));
     isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
     isChildContentActive = () => (this.isContentActive() ? true : false);
 
-    //thid needs to be fixed
-    childScreenToLocal = () =>
-        this._props // document's left is the panel shifted by the doc's index * panelWidth/#docs.  But it scales by centerScale around its center, so it's left moves left by the distance of the left from the center (panelwidth/2) * the scale delta (centerScale-1)
-            .ScreenToLocalTransform() // the top behaves the same way ecept it's shifted by the 'top' amount specified for the panel in css and then by the scale factor.
-            // .translate(-this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2,-((Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2)
-            .rotate(this.rotate(this.childLayoutPairs.length, Number(this.layoutDoc._carousel_index)));
-    // .scale(1 / this.centerScale);
-
-    //literally doesnot do anythin
-    // focus = (anchor: Doc, options: FocusViewOptions) => {
-    //     const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
-    //     if (anchor.type !== DocumentType.CONFIG && !docs.includes(anchor)) return;
-    //     options.didMove = true;
-    //     const target = DocCast(anchor.annotationOn) ?? anchor;
-    //     const index = docs.indexOf(target);
-    //     index !== -1 && (this.layoutDoc._carousel_index = index); //if index is not -1, then assign index to this.layoutDoc._carousel_index
-    //     return undefined;
-    // };
 
     rotate = (amCards: number, index: number) => {
         const possRotate = -30 + index * (30 / ((amCards - (amCards % 2)) / 2));
@@ -126,11 +105,33 @@ export class CollectionCardView extends CollectionSubView() {
     //         return -((50 / ((amCards - (amCards % 2)) / 2)) * (amCards - index - 1));
     //     }
     // };
+
+
+    translateSelected = (index: number): number => {
+        if (this.isSelected(index)){
+            const middleOfPanel = this._props.PanelWidth() / 2;
+            const scaledNodeWidth = this.panelWidth() * 1.25;
+            
+            // Calculate the position of the node's left edge before scaling
+            const nodeLeftEdge = index * this.panelWidth();
+            // Find the center of the node after scaling
+            const scaledNodeCenter = nodeLeftEdge + (scaledNodeWidth / 2);
+            
+            // Calculate the translation needed to align the scaled node's center with the panel's center
+            const translation = middleOfPanel - scaledNodeCenter;
+    
+            return translation;
+        }
+    
+        return 0;
+    };
+
     @observable docRefs = new ObservableMap<Doc, DocumentView>();
 
     @computed get content() {
         // const currentIndex = NumCast(this.layoutDoc._carousel_index);
-        const amCards = this.childLayoutPairs.length;
+        const amCards = this.inactiveDocs().length;
+        // const myInactives = 
         const displayDoc = (childPair: { layout: Doc; data: Doc }, screenToLocalTransform: () => Transform) => {
             return (
                 <DocumentView
@@ -159,32 +160,39 @@ export class CollectionCardView extends CollectionSubView() {
 
         return this.childLayoutPairs.map((childPair, index) => {
             const isSelected = this.isSelected(index);
-            console.log(index + 'is select?: ' + isSelected);
             const isHovered = this.hoveredNodeIndex === index;
+            const inactiveIndex = this.inactiveDocs().indexOf(childPair.layout);
 
             const childScreenToLocal = () => {
                 const dref = this.docRefs.get(childPair.layout);
                 const { translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv);
                 // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform.  so we have to subtract that off
-                return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(1 / (isSelected ? 1.25 : 1)).rotate(!isSelected ? -this.rotate(amCards, index) : 0);
+                return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(1 / (isSelected ? 1.25 : 1)).rotate(!isSelected ? -this.rotate(amCards, inactiveIndex) : 0);
             };
 
             return (
                 <div
                     key={childPair.layout[Id]}
-                    // className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`}
+                    className={`card-item${isSelected ? '-active' : ''}`}
                     style={{
                         width: this.panelWidth(),
                         height: this.panelHeight(),
-                        transform: `rotate(${!isSelected ? this.rotate(amCards, index) : 0}deg) 
-                    scale(${isSelected ? 1.25 : 1})
-                    translateY(${isHovered ? this.translateHover(index) : isSelected ? 50 : 0}px)
-                    `,
+                        transform: 
+                            `
+                            rotate(${!isSelected ? this.rotate(amCards, inactiveIndex) : 0}deg) 
+                            translateX(${isSelected ? ((this._props.PanelWidth() / 2) - 210) : 0}px)
+                            translateY(${isHovered ? this.translateHover(index) : isSelected ? 50 : 0}px)
+                            scale(${isSelected ? 1.25 : 1})` //scale has to be applied last or selected offset gets messed up
+
+                    ,
                     }}
+
+
+
                     // onClick={() => this.setSelectedNodeIndex(index)}
 
                     onMouseEnter={() => this.setHoveredNodeIndex(index)}
-                    onMouseLeave={() => this.setHoveredNodeIndex(-1)}>
+                    >
                     {/* {this.lol(childPair.data, index)} */}
 
                     {displayDoc(childPair, childScreenToLocal)}
@@ -193,16 +201,13 @@ export class CollectionCardView extends CollectionSubView() {
         });
     }
 
-    // lol = (d : Doc, index: number) => {
-    //     if (SelectionManager.IsSelected(d)){
-    //         this.setSelectedNodeIndex(index);
+    @computed get translateWrapperX() {
 
-    //     }
-    // }
+        if (this.inactiveDocs().length != this.childLayoutPairs.length){
+            return this.panelWidth()/2
+        }
 
-    @computed get translateX() {
-        const index = NumCast(this.layoutDoc._carousel_index);
-        return this.panelWidth() * (1 - index);
+        return 0;
     }
 
     render() {
@@ -216,10 +221,17 @@ export class CollectionCardView extends CollectionSubView() {
                 }}>
                 <div
                     className="card-wrapper"
-                    // style={{ transform: `translateX(${this.translateX}px)` }}
+                    style={{ transform: `translateX(${this.translateWrapperX}px)`,
+                    //  height: this.panelHeight() 
+                    }}
+                    onMouseLeave={() => this.setHoveredNodeIndex(-1)}
                 >
                     {this.content}
                 </div>
+
+                {/* {this.focusContent} */}
+
+
             </div>
         );
     }
-- 
cgit v1.2.3-70-g09d2


From bf84209b90427d9bd91e9f1e5c191f613a96a90a Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Thu, 21 Mar 2024 04:54:19 -0400
Subject: figuring out sorting-- may just leave it to god

---
 .../views/collections/CollectionCardDeckView.tsx   | 47 ++++++++++++++++++++--
 1 file changed, 43 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index a4d0d1862..66729d64b 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -2,9 +2,9 @@ import { ObservableMap, action, computed, makeObservable, observable } from 'mob
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Utils, returnZero } from '../../../Utils';
-import { Doc, DocListCast } from '../../../fields/Doc';
+import { Doc, DocListCast, Field } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
-import { NumCast, ScriptCast } from '../../../fields/Types';
+import { NumCast, ScriptCast, StrCast, BoolCast } from '../../../fields/Types';
 import { DragManager } from '../../util/DragManager';
 import { SelectionManager } from '../../util/SelectionManager';
 import { StyleProp } from '../StyleProvider';
@@ -57,6 +57,19 @@ export class CollectionCardView extends CollectionSubView() {
         return docs.filter(d => !SelectionManager.IsSelected(d));
     }
 
+    middleIndex = Math.floor(this.inactiveDocs().length / 2);
+
+
+    // verticalOffset = (index: number) => {
+
+    //     const distanceFromMiddle = Math.abs(index - this.middleIndex);
+    //     // Adjust '4' to control the curvature; larger values create a flatter arc.
+    //     return Math.pow(distanceFromMiddle, 2)* (Math.floor(64/this.inactiveDocs().length)); // Example quadratic function
+    //   };
+
+
+
+
     constructor(props: any) {
         super(props);
         makeObservable(this);
@@ -126,6 +139,25 @@ export class CollectionCardView extends CollectionSubView() {
         return 0;
     };
 
+    @computed get sortedDocs() {
+        const field = StrCast(this.layoutDoc.sortField);
+        const desc = BoolCast(this.layoutDoc.sortDesc);
+        const docs = !field
+            ? this.childDocs
+            : [...this.childDocs].sort((docA, docB) => {
+                  const aStr = Field.toString(docA[field] as Field);
+                  const bStr = Field.toString(docB[field] as Field);
+                  var out = 0;
+                  if (aStr < bStr) out = -1;
+                  if (aStr > bStr) out = 1;
+                  if (desc) out *= -1;
+                  return out;
+              });
+        return { docs };
+    }
+
+
+
     @observable docRefs = new ObservableMap<Doc, DocumentView>();
 
     @computed get content() {
@@ -162,6 +194,8 @@ export class CollectionCardView extends CollectionSubView() {
             const isSelected = this.isSelected(index);
             const isHovered = this.hoveredNodeIndex === index;
             const inactiveIndex = this.inactiveDocs().indexOf(childPair.layout);
+            // const yOffset = this.verticalOffset(index);
+
 
             const childScreenToLocal = () => {
                 const dref = this.docRefs.get(childPair.layout);
@@ -180,15 +214,20 @@ export class CollectionCardView extends CollectionSubView() {
                         transform: 
                             `
                             rotate(${!isSelected ? this.rotate(amCards, inactiveIndex) : 0}deg) 
-                            translateX(${isSelected ? ((this._props.PanelWidth() / 2) - 210) : 0}px)
                             translateY(${isHovered ? this.translateHover(index) : isSelected ? 50 : 0}px)
-                            scale(${isSelected ? 1.25 : 1})` //scale has to be applied last or selected offset gets messed up
+                            translateX(${isSelected ? ((this._props.PanelWidth() / 2) - ((this.panelWidth())/2) - (this.panelWidth()/2)) : 0}px)
+
+                            scale(${isSelected ? 1.25 : 1})
+                            
+                            ` //scale has to be applied last or selected offset gets messed up
 
                     ,
                     }}
 
 
 
+
+
                     // onClick={() => this.setSelectedNodeIndex(index)}
 
                     onMouseEnter={() => this.setHoveredNodeIndex(index)}
-- 
cgit v1.2.3-70-g09d2


From 268c4487aab34c8bef51df87953d83b968864fc4 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 21 Mar 2024 11:10:16 -0400
Subject: transformation fixes for cardDeckView

---
 .../views/collections/CollectionCardDeckView.scss  | 10 +--
 .../views/collections/CollectionCardDeckView.tsx   | 93 +++++++++-------------
 2 files changed, 39 insertions(+), 64 deletions(-)

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

diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss
index 95a9b658c..ebd7f9856 100644
--- a/src/client/views/collections/CollectionCardDeckView.scss
+++ b/src/client/views/collections/CollectionCardDeckView.scss
@@ -10,20 +10,16 @@
 .card-wrapper {
     display: flex;
     position: absolute;
-    top: $CAROUSEL3D_TOP * 1%;
-    height: ($CAROUSEL3D_SIDE_SCALE * 100) * 1%;
     align-items: center;
     transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955);
 }
 
-.card-item-active, .card-item{
+.card-item-active,
+.card-item {
     transition: transform 0.3s ease-in-out;
 }
 
-
-.card-item-active{
+.card-item-active {
     position: absolute;
     z-index: 100;
 }
-
-
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 66729d64b..e923f9a72 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -1,7 +1,7 @@
 import { ObservableMap, action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Utils, returnZero } from '../../../Utils';
+import { Utils, returnFalse, returnTrue, returnZero } from '../../../Utils';
 import { Doc, DocListCast, Field } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { NumCast, ScriptCast, StrCast, BoolCast } from '../../../fields/Types';
@@ -12,7 +12,6 @@ import { DocumentView } from '../nodes/DocumentView';
 import './CollectionCardDeckView.scss';
 import { CollectionSubView } from './CollectionSubView';
 import { Transform } from '../../util/Transform';
-import { indexes } from 'd3';
 
 @observer
 export class CollectionCardView extends CollectionSubView() {
@@ -22,8 +21,8 @@ export class CollectionCardView extends CollectionSubView() {
 
     @action
     setHoveredNodeIndex = (index: number) => {
-        if (!this.isSelected(index)){
-        this.hoveredNodeIndex = index;
+        if (!this.isSelected(index)) {
+            this.hoveredNodeIndex = index;
         }
     };
 
@@ -55,21 +54,10 @@ export class CollectionCardView extends CollectionSubView() {
         const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
 
         return docs.filter(d => !SelectionManager.IsSelected(d));
-    }
+    };
 
     middleIndex = Math.floor(this.inactiveDocs().length / 2);
 
-
-    // verticalOffset = (index: number) => {
-
-    //     const distanceFromMiddle = Math.abs(index - this.middleIndex);
-    //     // Adjust '4' to control the curvature; larger values create a flatter arc.
-    //     return Math.pow(distanceFromMiddle, 2)* (Math.floor(64/this.inactiveDocs().length)); // Example quadratic function
-    //   };
-
-
-
-
     constructor(props: any) {
         super(props);
         makeObservable(this);
@@ -89,13 +77,19 @@ export class CollectionCardView extends CollectionSubView() {
         }
     };
 
-    panelWidth = () => this._props.PanelWidth() / this.childLayoutPairs.length;
-    panelHeight = () => this.panelWidth() * 1.5;
+    childDocumentWidth = 600; // target width of a Doc...
+    /**
+     * how much to scale down the contents of the view so that everything will fit
+     */
+    @computed get fitContentScale() {
+        return (this.childDocumentWidth * this.childLayoutPairs.length) / this._props.PanelWidth();
+    }
+    panelWidth = () => this.childDocumentWidth;
+    panelHeight = (layout: Doc) => () => (2 * (this.panelWidth() * NumCast(layout._height))) / NumCast(layout._width);
     onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
     isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
     isChildContentActive = () => (this.isContentActive() ? true : false);
 
-
     rotate = (amCards: number, index: number) => {
         const possRotate = -30 + index * (30 / ((amCards - (amCards % 2)) / 2));
 
@@ -119,23 +113,22 @@ export class CollectionCardView extends CollectionSubView() {
     //     }
     // };
 
-
     translateSelected = (index: number): number => {
-        if (this.isSelected(index)){
+        if (this.isSelected(index)) {
             const middleOfPanel = this._props.PanelWidth() / 2;
             const scaledNodeWidth = this.panelWidth() * 1.25;
-            
+
             // Calculate the position of the node's left edge before scaling
             const nodeLeftEdge = index * this.panelWidth();
             // Find the center of the node after scaling
-            const scaledNodeCenter = nodeLeftEdge + (scaledNodeWidth / 2);
-            
+            const scaledNodeCenter = nodeLeftEdge + scaledNodeWidth / 2;
+
             // Calculate the translation needed to align the scaled node's center with the panel's center
             const translation = middleOfPanel - scaledNodeCenter;
-    
+
             return translation;
         }
-    
+
         return 0;
     };
 
@@ -156,14 +149,12 @@ export class CollectionCardView extends CollectionSubView() {
         return { docs };
     }
 
-
-
     @observable docRefs = new ObservableMap<Doc, DocumentView>();
 
     @computed get content() {
         // const currentIndex = NumCast(this.layoutDoc._carousel_index);
         const amCards = this.inactiveDocs().length;
-        // const myInactives = 
+        // const myInactives =
         const displayDoc = (childPair: { layout: Doc; data: Doc }, screenToLocalTransform: () => Transform) => {
             return (
                 <DocumentView
@@ -175,7 +166,7 @@ export class CollectionCardView extends CollectionSubView() {
                     //suppressSetHeight={true}
                     NativeWidth={returnZero}
                     NativeHeight={returnZero}
-                    layout_fitWidth={undefined}
+                    layout_fitWidth={returnFalse}
                     onDoubleClickScript={this.onChildDoubleClick}
                     renderDepth={this._props.renderDepth + 1}
                     LayoutTemplate={this._props.childLayoutTemplate}
@@ -185,7 +176,7 @@ export class CollectionCardView extends CollectionSubView() {
                     isContentActive={this.isChildContentActive}
                     isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
                     PanelWidth={this.panelWidth}
-                    PanelHeight={this.panelHeight}
+                    PanelHeight={this.panelHeight(childPair.layout)}
                 />
             );
         };
@@ -196,12 +187,11 @@ export class CollectionCardView extends CollectionSubView() {
             const inactiveIndex = this.inactiveDocs().indexOf(childPair.layout);
             // const yOffset = this.verticalOffset(index);
 
-
             const childScreenToLocal = () => {
                 const dref = this.docRefs.get(childPair.layout);
-                const { translateX, translateY } = Utils.GetScreenTransform(dref?.ContentDiv);
+                const { translateX, translateY, scale } = Utils.GetScreenTransform(dref?.ContentDiv);
                 // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform.  so we have to subtract that off
-                return new Transform(-translateX + (dref?.centeringX || 0), -translateY + (dref?.centeringY || 0), 1).scale(1 / (isSelected ? 1.25 : 1)).rotate(!isSelected ? -this.rotate(amCards, inactiveIndex) : 0);
+                return new Transform(-translateX + (dref?.centeringX || 0) * scale, -translateY + (dref?.centeringY || 0) * scale, 1).scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, inactiveIndex) : 0);
             };
 
             return (
@@ -210,28 +200,19 @@ export class CollectionCardView extends CollectionSubView() {
                     className={`card-item${isSelected ? '-active' : ''}`}
                     style={{
                         width: this.panelWidth(),
-                        height: this.panelHeight(),
-                        transform: 
-                            `
+                        height: this.panelHeight(childPair.layout)(),
+                        transform: `
                             rotate(${!isSelected ? this.rotate(amCards, inactiveIndex) : 0}deg) 
                             translateY(${isHovered ? this.translateHover(index) : isSelected ? 50 : 0}px)
-                            translateX(${isSelected ? ((this._props.PanelWidth() / 2) - ((this.panelWidth())/2) - (this.panelWidth()/2)) : 0}px)
+                            translateX(${isSelected ? (this._props.PanelWidth() / 2) * this.fitContentScale - this.childDocumentWidth / 2 : 0}px)
 
                             scale(${isSelected ? 1.25 : 1})
                             
-                            ` //scale has to be applied last or selected offset gets messed up
-
-                    ,
+                            `, //scale has to be applied last or selected offset gets messed up
                     }}
-
-
-
-
-
                     // onClick={() => this.setSelectedNodeIndex(index)}
 
-                    onMouseEnter={() => this.setHoveredNodeIndex(index)}
-                    >
+                    onMouseEnter={() => this.setHoveredNodeIndex(index)}>
                     {/* {this.lol(childPair.data, index)} */}
 
                     {displayDoc(childPair, childScreenToLocal)}
@@ -241,9 +222,8 @@ export class CollectionCardView extends CollectionSubView() {
     }
 
     @computed get translateWrapperX() {
-
-        if (this.inactiveDocs().length != this.childLayoutPairs.length){
-            return this.panelWidth()/2
+        if (this.inactiveDocs().length != this.childLayoutPairs.length) {
+            return this.panelWidth();
         }
 
         return 0;
@@ -260,17 +240,16 @@ export class CollectionCardView extends CollectionSubView() {
                 }}>
                 <div
                     className="card-wrapper"
-                    style={{ transform: `translateX(${this.translateWrapperX}px)`,
-                    //  height: this.panelHeight() 
+                    style={{
+                        transform: ` scale(${1 / this.fitContentScale})`,
+                        transformOrigin: 'top left',
+                        height: `${100 * this.fitContentScale}%`,
                     }}
-                    onMouseLeave={() => this.setHoveredNodeIndex(-1)}
-                >
+                    onMouseLeave={() => this.setHoveredNodeIndex(-1)}>
                     {this.content}
                 </div>
 
                 {/* {this.focusContent} */}
-
-
             </div>
         );
     }
-- 
cgit v1.2.3-70-g09d2


From 10f6780728ca83d76a058552ba7023c4a5c881b5 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 21 Mar 2024 14:43:08 -0400
Subject: from last

---
 src/client/views/collections/CollectionCardDeckView.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index e923f9a72..b636b878e 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -28,7 +28,7 @@ export class CollectionCardView extends CollectionSubView() {
 
     translateHover = (index: number): number => {
         if (this.hoveredNodeIndex == index && !this.isSelected(index)) {
-            return -50;
+            return -50 * this.fitContentScale;
         }
         return 0;
     };
@@ -203,7 +203,7 @@ export class CollectionCardView extends CollectionSubView() {
                         height: this.panelHeight(childPair.layout)(),
                         transform: `
                             rotate(${!isSelected ? this.rotate(amCards, inactiveIndex) : 0}deg) 
-                            translateY(${isHovered ? this.translateHover(index) : isSelected ? 50 : 0}px)
+                            translateY(${isHovered ? this.translateHover(index) : isSelected ? 50 * this.fitContentScale : 0}px)
                             translateX(${isSelected ? (this._props.PanelWidth() / 2) * this.fitContentScale - this.childDocumentWidth / 2 : 0}px)
 
                             scale(${isSelected ? 1.25 : 1})
-- 
cgit v1.2.3-70-g09d2


From 276250446b4ad0f86a43a6773d2fff83e8bcaa49 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Thu, 4 Apr 2024 04:22:37 -0400
Subject: fixed weird centering, finally got the y positioning working, and am
 (unsuccesfully) working on creating rows

---
 .../views/collections/CollectionCardDeckView.scss  |   9 ++
 .../views/collections/CollectionCardDeckView.tsx   | 174 ++++++++++++++++++---
 2 files changed, 162 insertions(+), 21 deletions(-)

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

diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss
index ebd7f9856..ff9c0a569 100644
--- a/src/client/views/collections/CollectionCardDeckView.scss
+++ b/src/client/views/collections/CollectionCardDeckView.scss
@@ -14,6 +14,15 @@
     transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955);
 }
 
+.card-row{
+    display: flex;
+    position: absolute;
+    align-items: center;
+    transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955);
+
+
+}
+
 .card-item-active,
 .card-item {
     transition: transform 0.3s ease-in-out;
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index b636b878e..98b4e8ec9 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -92,26 +92,55 @@ export class CollectionCardView extends CollectionSubView() {
 
     rotate = (amCards: number, index: number) => {
         const possRotate = -30 + index * (30 / ((amCards - (amCards % 2)) / 2));
+        const stepMag = Math.abs(-30 + ((amCards/2)- 1) * (30 / ((amCards - (amCards % 2)) / 2)));
+
+        // console.log(possRotate + "poss")
 
         if (amCards % 2 == 0 && possRotate == 0) {
-            console.log('whaddup');
-            return possRotate + Math.abs(-30 + (index - 1) * (30 / ((amCards - 1) / 2)));
+            // console.log('whaddup');
+            return possRotate + Math.abs(-30 + (index - 1) * (30 / ((amCards) / 2)));
+        }
+
+        else if (amCards %2 == 0 && index > (amCards+1)/2){
+            // console.log("sup" + stepMag);
+            
+            return possRotate + stepMag;
         }
 
         return possRotate;
     };
 
-    // translateY = (amCards: number, index: number) => {
-    //     // Assuming you want a default value when index > amCards/2
-    //     // Adjust the logic as necessary for your use case
-    //     if (index <= amCards / 2) {
-    //         return -((50 / ((amCards - (amCards % 2)) / 2)) * index);
-    //     } else {
-    //         // Return a default or calculated value for indices greater than amCards/2
-    //         // This is just an example; adjust the logic as needed
-    //         return -((50 / ((amCards - (amCards % 2)) / 2)) * (amCards - index - 1));
-    //     }
-    // };
+    translateY = (amCards: number, index: number) => {
+        const evenOdd =amCards% 2 
+        const apex = (amCards-evenOdd)/2
+        const stepMag = (200 / ((amCards - evenOdd)/2))
+         + Math.abs(((apex-index) *25))
+
+        console.log("steo" + stepMag)
+
+        if (evenOdd == 1 || index < apex -1){
+            console.log('hi' + index)
+            return Math.abs(stepMag * (apex - index))
+        }
+
+        else{
+            if (index == apex || index == apex -1){
+                return 0
+            }
+
+            return Math.abs(stepMag * (apex - index -1))
+
+        }
+
+    };
+
+    translateOverFlowY = (amCards: number, index: number) => {
+        if (amCards>8 && index > amCards/2){
+            return 100
+
+        }
+        return 0
+    }
 
     translateSelected = (index: number): number => {
         if (this.isSelected(index)) {
@@ -151,9 +180,80 @@ export class CollectionCardView extends CollectionSubView() {
 
     @observable docRefs = new ObservableMap<Doc, DocumentView>();
 
-    @computed get content() {
+    // @computed get content() {
+    //     // const currentIndex = NumCast(this.layoutDoc._carousel_index);
+    //     const amCards = this.inactiveDocs().length;
+    //     // const myInactives =
+    //     const displayDoc = (childPair: { layout: Doc; data: Doc }, screenToLocalTransform: () => Transform) => {
+    //         return (
+    //             <DocumentView
+    //                 {...this._props}
+    //                 ref={action((r: DocumentView) => r?.ContentDiv && this.docRefs.set(childPair.layout, r))}
+    //                 Document={childPair.layout}
+    //                 TemplateDataDocument={childPair.data}
+    //                 // onClickScript={this.toggleIcon}
+    //                 //suppressSetHeight={true}
+    //                 NativeWidth={returnZero}
+    //                 NativeHeight={returnZero}
+    //                 layout_fitWidth={returnFalse}
+    //                 onDoubleClickScript={this.onChildDoubleClick}
+    //                 renderDepth={this._props.renderDepth + 1}
+    //                 LayoutTemplate={this._props.childLayoutTemplate}
+    //                 LayoutTemplateString={this._props.childLayoutString}
+    //                 // focus={this.focus}
+    //                 ScreenToLocalTransform={screenToLocalTransform} //makes sure the box wrapper thing is in the right spot
+    //                 isContentActive={this.isChildContentActive}
+    //                 isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
+    //                 PanelWidth={this.panelWidth}
+    //                 PanelHeight={this.panelHeight(childPair.layout)}
+    //             />
+    //         );
+    //     };
+
+    //     return this.childLayoutPairs.map((childPair, index) => {
+    //         const isSelected = this.isSelected(index);
+    //         const isHovered = this.hoveredNodeIndex === index;
+    //         const isOverflow = amCards > 8 && index > amCards/2
+    //         const inactiveIndex = this.inactiveDocs().indexOf(childPair.layout);
+    //         // const yOffset = this.verticalOffset(index);
+
+    //         const childScreenToLocal = () => {
+    //             const dref = this.docRefs.get(childPair.layout);
+    //             const { translateX, translateY, scale } = Utils.GetScreenTransform(dref?.ContentDiv);
+    //             // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform.  so we have to subtract that off
+    //             return new Transform(-translateX + (dref?.centeringX || 0) * scale, -translateY + (dref?.centeringY || 0) * scale, 1).scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, inactiveIndex) : 0);
+    //         };
+
+    //         return (
+    //             <div
+    //                 key={childPair.layout[Id]}
+    //                 className={`card-item${isSelected ? '-active' : ''}`}
+    //                 style={{
+    //                     width: this.panelWidth(),
+    //                     height: this.panelHeight(childPair.layout)(),
+    //                     transform: `
+    //                         rotate(${!isSelected ? this.rotate(amCards, inactiveIndex) : 0}deg) 
+    //                         translateY(${isHovered ? this.translateHover(index) : isSelected ? 50 * this.fitContentScale : this.translateY(amCards, inactiveIndex) + this.translateOverFlowY(amCards, inactiveIndex)}px)
+    //                         translateX(${isSelected ? (this._props.PanelWidth() / 2) * this.fitContentScale - this.childDocumentWidth : 0}px)
+
+    //                         scale(${isSelected ? 1.25 : 1})
+                            
+    //                         `, //scale has to be applied last or selected offset gets messed up
+    //                 }}
+    //                 // onClick={() => this.setSelectedNodeIndex(index)}
+
+    //                 onMouseEnter={() => this.setHoveredNodeIndex(index)}>
+    //                 {/* {this.lol(childPair.data, index)} */}
+
+    //                 {displayDoc(childPair, childScreenToLocal)}
+    //             </div>
+    //         );
+    //     });
+    // }
+
+    @action content(amCards: number, startIndex: number, stopIndex: number) {
         // const currentIndex = NumCast(this.layoutDoc._carousel_index);
-        const amCards = this.inactiveDocs().length;
+        // const amCards = this.inactiveDocs().length;
         // const myInactives =
         const displayDoc = (childPair: { layout: Doc; data: Doc }, screenToLocalTransform: () => Transform) => {
             return (
@@ -181,9 +281,13 @@ export class CollectionCardView extends CollectionSubView() {
             );
         };
 
-        return this.childLayoutPairs.map((childPair, index) => {
+        const inactiveDocs = this.inactiveDocs();
+        return this.childLayoutPairs.filter((childPair, index) => { const inactiveIndex = inactiveDocs.indexOf(childPair.layout); return inactiveIndex >= startIndex && inactiveIndex <= stopIndex;
+            }).map((childPair, index) => {
+
             const isSelected = this.isSelected(index);
             const isHovered = this.hoveredNodeIndex === index;
+            const isOverflow = amCards > 8 && index > amCards/2
             const inactiveIndex = this.inactiveDocs().indexOf(childPair.layout);
             // const yOffset = this.verticalOffset(index);
 
@@ -203,8 +307,10 @@ export class CollectionCardView extends CollectionSubView() {
                         height: this.panelHeight(childPair.layout)(),
                         transform: `
                             rotate(${!isSelected ? this.rotate(amCards, inactiveIndex) : 0}deg) 
-                            translateY(${isHovered ? this.translateHover(index) : isSelected ? 50 * this.fitContentScale : 0}px)
-                            translateX(${isSelected ? (this._props.PanelWidth() / 2) * this.fitContentScale - this.childDocumentWidth / 2 : 0}px)
+                            translateY(${isHovered ? this.translateHover(index) : isSelected ? 50 * this.fitContentScale : this.translateY(amCards, inactiveIndex)
+                                //  + this.translateOverFlowY(amCards, inactiveIndex)
+                                }px)
+                            translateX(${isSelected ? (this._props.PanelWidth() / 2) * this.fitContentScale - this.childDocumentWidth : 0}px)
 
                             scale(${isSelected ? 1.25 : 1})
                             
@@ -221,9 +327,35 @@ export class CollectionCardView extends CollectionSubView() {
         });
     }
 
+    renderRows() {
+        const cards = this.inactiveDocs();
+        const totalCards = cards.length;
+        const rowsNeeded = Math.ceil(totalCards / 8);
+        
+        let rows = [];
+    
+        for (let rowIndex = 0; rowIndex < rowsNeeded; rowIndex++) {
+            let rowStartIndex = rowIndex * 8;
+            let rowEndIndex = rowStartIndex + 8;
+            let rowCards = cards.slice(rowStartIndex, rowEndIndex);
+    
+            rows.push(this.renderRowCards(rowCards, rowIndex));
+        }
+    
+        return rows;
+    }
+
+    renderRowCards(rowCards: Doc[], rowIndex: number) {
+        return (
+            <div className={`card-row row-${rowIndex}`} key={rowIndex} style = {{transform: `translateY(${rowIndex*60})`}}>
+                {this.content(rowCards.length, rowIndex*8, rowIndex*8+8)}
+            </div>
+        );
+    }
+
     @computed get translateWrapperX() {
         if (this.inactiveDocs().length != this.childLayoutPairs.length) {
-            return this.panelWidth();
+            return this.panelWidth()/2;
         }
 
         return 0;
@@ -241,12 +373,12 @@ export class CollectionCardView extends CollectionSubView() {
                 <div
                     className="card-wrapper"
                     style={{
-                        transform: ` scale(${1 / this.fitContentScale})`,
+                        transform: ` scale(${1 / this.fitContentScale}) translateX(${this.translateWrapperX}px)`,
                         transformOrigin: 'top left',
                         height: `${100 * this.fitContentScale}%`,
                     }}
                     onMouseLeave={() => this.setHoveredNodeIndex(-1)}>
-                    {this.content}
+                    {this.renderRows()}
                 </div>
 
                 {/* {this.focusContent} */}
-- 
cgit v1.2.3-70-g09d2


From ba68af85177c9db3475532a13af08fd15f6b6ebe Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Thu, 11 Apr 2024 12:23:52 -0400
Subject: sorting works but nothing else does as is life

---
 .../views/collections/CollectionCardDeckView.tsx   | 277 ++++++++++-----------
 1 file changed, 128 insertions(+), 149 deletions(-)

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

diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 98b4e8ec9..d0a796218 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -12,6 +12,8 @@ import { DocumentView } from '../nodes/DocumentView';
 import './CollectionCardDeckView.scss';
 import { CollectionSubView } from './CollectionSubView';
 import { Transform } from '../../util/Transform';
+// import Card from 'react-bootstrap/Card';
+
 
 @observer
 export class CollectionCardView extends CollectionSubView() {
@@ -53,6 +55,8 @@ export class CollectionCardView extends CollectionSubView() {
     inactiveDocs = () => {
         const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
 
+
+
         return docs.filter(d => !SelectionManager.IsSelected(d));
     };
 
@@ -91,19 +95,18 @@ export class CollectionCardView extends CollectionSubView() {
     isChildContentActive = () => (this.isContentActive() ? true : false);
 
     rotate = (amCards: number, index: number) => {
+        console.log(amCards + "wtf")
         const possRotate = -30 + index * (30 / ((amCards - (amCards % 2)) / 2));
-        const stepMag = Math.abs(-30 + ((amCards/2)- 1) * (30 / ((amCards - (amCards % 2)) / 2)));
+        const stepMag = Math.abs(-30 + (amCards / 2 - 1) * (30 / ((amCards - (amCards % 2)) / 2)));
 
         // console.log(possRotate + "poss")
 
         if (amCards % 2 == 0 && possRotate == 0) {
             // console.log('whaddup');
-            return possRotate + Math.abs(-30 + (index - 1) * (30 / ((amCards) / 2)));
-        }
-
-        else if (amCards %2 == 0 && index > (amCards+1)/2){
+            return possRotate + Math.abs(-30 + (index - 1) * (30 / (amCards / 2)));
+        } else if (amCards % 2 == 0 && index > (amCards + 1) / 2) {
             // console.log("sup" + stepMag);
-            
+
             return possRotate + stepMag;
         }
 
@@ -111,36 +114,30 @@ export class CollectionCardView extends CollectionSubView() {
     };
 
     translateY = (amCards: number, index: number) => {
-        const evenOdd =amCards% 2 
-        const apex = (amCards-evenOdd)/2
-        const stepMag = (200 / ((amCards - evenOdd)/2))
-         + Math.abs(((apex-index) *25))
-
-        console.log("steo" + stepMag)
-
-        if (evenOdd == 1 || index < apex -1){
-            console.log('hi' + index)
-            return Math.abs(stepMag * (apex - index))
-        }
-
-        else{
-            if (index == apex || index == apex -1){
-                return 0
+        const evenOdd = amCards % 2;
+        const apex = (amCards - evenOdd) / 2;
+        const stepMag = 200 / ((amCards - evenOdd) / 2) + Math.abs((apex - index) * 25);
+
+        console.log('steo' + stepMag);
+
+        if (evenOdd == 1 || index < apex - 1) {
+            console.log('hi' + index);
+            return Math.abs(stepMag * (apex - index));
+        } else {
+            if (index == apex || index == apex - 1) {
+                return 0;
             }
 
-            return Math.abs(stepMag * (apex - index -1))
-
+            return Math.abs(stepMag * (apex - index - 1));
         }
-
     };
 
     translateOverFlowY = (amCards: number, index: number) => {
-        if (amCards>8 && index > amCards/2){
-            return 100
-
+        if (amCards > 8 && index > amCards / 2) {
+            return 100;
         }
-        return 0
-    }
+        return 0;
+    };
 
     translateSelected = (index: number): number => {
         if (this.isSelected(index)) {
@@ -161,99 +158,100 @@ export class CollectionCardView extends CollectionSubView() {
         return 0;
     };
 
-    @computed get sortedDocs() {
-        const field = StrCast(this.layoutDoc.sortField);
+    @computed get sortedDocsType() {
+        // This field is set to 'type' to sort documents by their type
+        // const typeField = 'type';
+
+        // Whether to sort in descending order
         const desc = BoolCast(this.layoutDoc.sortDesc);
-        const docs = !field
-            ? this.childDocs
-            : [...this.childDocs].sort((docA, docB) => {
-                  const aStr = Field.toString(docA[field] as Field);
-                  const bStr = Field.toString(docB[field] as Field);
-                  var out = 0;
-                  if (aStr < bStr) out = -1;
-                  if (aStr > bStr) out = 1;
-                  if (desc) out *= -1;
-                  return out;
-              });
+
+        // Copy and sort documents by type
+        const docs = this.childLayoutPairs.sort((docA, docB) => {
+            const typeA = docA.layout.type ?? ''; // If docA.type is undefined, use an empty string
+            const typeB = docB.layout.type ?? ''; // If docB.type is undefined, use an empty string
+
+            // Perform a basic string comparison if types are strings
+            let out = 0;
+            if (typeA < typeB) out = -1;
+            if (typeA > typeB) out = 1;
+            if (desc) out *= -1; // Reverse the sort order if descending is true
+            return out;
+        });
+
         return { docs };
     }
 
     @observable docRefs = new ObservableMap<Doc, DocumentView>();
 
-    // @computed get content() {
-    //     // const currentIndex = NumCast(this.layoutDoc._carousel_index);
-    //     const amCards = this.inactiveDocs().length;
-    //     // const myInactives =
-    //     const displayDoc = (childPair: { layout: Doc; data: Doc }, screenToLocalTransform: () => Transform) => {
-    //         return (
-    //             <DocumentView
-    //                 {...this._props}
-    //                 ref={action((r: DocumentView) => r?.ContentDiv && this.docRefs.set(childPair.layout, r))}
-    //                 Document={childPair.layout}
-    //                 TemplateDataDocument={childPair.data}
-    //                 // onClickScript={this.toggleIcon}
-    //                 //suppressSetHeight={true}
-    //                 NativeWidth={returnZero}
-    //                 NativeHeight={returnZero}
-    //                 layout_fitWidth={returnFalse}
-    //                 onDoubleClickScript={this.onChildDoubleClick}
-    //                 renderDepth={this._props.renderDepth + 1}
-    //                 LayoutTemplate={this._props.childLayoutTemplate}
-    //                 LayoutTemplateString={this._props.childLayoutString}
-    //                 // focus={this.focus}
-    //                 ScreenToLocalTransform={screenToLocalTransform} //makes sure the box wrapper thing is in the right spot
-    //                 isContentActive={this.isChildContentActive}
-    //                 isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
-    //                 PanelWidth={this.panelWidth}
-    //                 PanelHeight={this.panelHeight(childPair.layout)}
-    //             />
-    //         );
-    //     };
-
-    //     return this.childLayoutPairs.map((childPair, index) => {
-    //         const isSelected = this.isSelected(index);
-    //         const isHovered = this.hoveredNodeIndex === index;
-    //         const isOverflow = amCards > 8 && index > amCards/2
-    //         const inactiveIndex = this.inactiveDocs().indexOf(childPair.layout);
-    //         // const yOffset = this.verticalOffset(index);
-
-    //         const childScreenToLocal = () => {
-    //             const dref = this.docRefs.get(childPair.layout);
-    //             const { translateX, translateY, scale } = Utils.GetScreenTransform(dref?.ContentDiv);
-    //             // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform.  so we have to subtract that off
-    //             return new Transform(-translateX + (dref?.centeringX || 0) * scale, -translateY + (dref?.centeringY || 0) * scale, 1).scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, inactiveIndex) : 0);
-    //         };
-
-    //         return (
-    //             <div
-    //                 key={childPair.layout[Id]}
-    //                 className={`card-item${isSelected ? '-active' : ''}`}
-    //                 style={{
-    //                     width: this.panelWidth(),
-    //                     height: this.panelHeight(childPair.layout)(),
-    //                     transform: `
-    //                         rotate(${!isSelected ? this.rotate(amCards, inactiveIndex) : 0}deg) 
-    //                         translateY(${isHovered ? this.translateHover(index) : isSelected ? 50 * this.fitContentScale : this.translateY(amCards, inactiveIndex) + this.translateOverFlowY(amCards, inactiveIndex)}px)
-    //                         translateX(${isSelected ? (this._props.PanelWidth() / 2) * this.fitContentScale - this.childDocumentWidth : 0}px)
-
-    //                         scale(${isSelected ? 1.25 : 1})
-                            
-    //                         `, //scale has to be applied last or selected offset gets messed up
-    //                 }}
-    //                 // onClick={() => this.setSelectedNodeIndex(index)}
+    @computed get contentSorted() {
+        const sortedDocs = this.sortedDocsType.docs; 
+        const amCards = this.inactiveDocs().length; 
+
+        const displayDoc = (childPair: { layout: Doc; data: Doc }, screenToLocalTransform: () => Transform) => {
+            return (
+                <DocumentView
+                    {...this._props}
+                    ref={action((r: DocumentView) => r?.ContentDiv && this.docRefs.set(childPair.layout, r))}
+                    Document={childPair.layout}
+                    TemplateDataDocument={childPair.data}
+                    NativeWidth={returnZero}
+                    NativeHeight={returnZero}
+                    layout_fitWidth={returnFalse}
+                    onDoubleClickScript={this.onChildDoubleClick}
+                    renderDepth={this._props.renderDepth + 1}
+                    LayoutTemplate={this._props.childLayoutTemplate}
+                    LayoutTemplateString={this._props.childLayoutString}
+                    ScreenToLocalTransform={screenToLocalTransform}
+                    isContentActive={this.isChildContentActive}
+                    isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
+                    PanelWidth={this.panelWidth}
+                    PanelHeight={this.panelHeight(childPair.layout)}
+                />
+            );
+        };
+
+        // Map sorted documents to their rendered components
+        return sortedDocs.map((childPair, index) => {
+            // const childPair = { layout: doc, data: doc }; 
+            const isSelected = this.isSelected(index);
+            const isHovered = this.hoveredNodeIndex === index;
+            const inactiveIndex = this.sortedDocsType.docs.indexOf(childPair);
+
+
 
-    //                 onMouseEnter={() => this.setHoveredNodeIndex(index)}>
-    //                 {/* {this.lol(childPair.data, index)} */}
+            const childScreenToLocal = () => {
+                const dref = this.docRefs.get(childPair.layout);
+                const { translateX, translateY, scale } = Utils.GetScreenTransform(dref?.ContentDiv);
+                return new Transform(-translateX + (dref?.centeringX || 0) * scale, -translateY + (dref?.centeringY || 0) * scale, 1).scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, inactiveIndex) : 0);
+            };
 
-    //                 {displayDoc(childPair, childScreenToLocal)}
-    //             </div>
-    //         );
-    //     });
-    // }
+            return (
+                <div
+                    key={childPair.layout[Id]}
+                    className={`card-item${isSelected ? '-active' : ''}`}
+                    style={{
+                        width: this.panelWidth(),
+                        height: this.panelHeight(childPair.layout)(),
+                        transform: `
+                            rotate(${!isSelected ? this.rotate(amCards, inactiveIndex) : 0}deg)
+                            translateY(${isHovered ? this.translateHover(index) : isSelected ? 50 * this.fitContentScale : this.translateY(amCards, inactiveIndex)}px)
+                            translateX(${isSelected ? (this._props.PanelWidth() / 2) * this.fitContentScale - this.childDocumentWidth : 0}px)
+                            scale(${isSelected ? 1.25 : 1})
+                        `,
+                    }}
+                    onMouseEnter={() => this.setHoveredNodeIndex(index)}>
+                    {displayDoc(childPair, childScreenToLocal)}
+                </div>
+            );
+        });
+    }
 
-    @action content(amCards: number, startIndex: number, stopIndex: number) {
+    @computed get content() {
         // const currentIndex = NumCast(this.layoutDoc._carousel_index);
-        // const amCards = this.inactiveDocs().length;
+        const amCards = this.inactiveDocs().length;
+        console.log(amCards + "lol")
+        const sortedDocs = this.sortedDocsType.docs; // Retrieve sorted documents
+
         // const myInactives =
         const displayDoc = (childPair: { layout: Doc; data: Doc }, screenToLocalTransform: () => Transform) => {
             return (
@@ -281,13 +279,11 @@ export class CollectionCardView extends CollectionSubView() {
             );
         };
 
-        const inactiveDocs = this.inactiveDocs();
-        return this.childLayoutPairs.filter((childPair, index) => { const inactiveIndex = inactiveDocs.indexOf(childPair.layout); return inactiveIndex >= startIndex && inactiveIndex <= stopIndex;
-            }).map((childPair, index) => {
-
+        return this.childLayoutPairs.map((childPair, index) => {
             const isSelected = this.isSelected(index);
             const isHovered = this.hoveredNodeIndex === index;
-            const isOverflow = amCards > 8 && index > amCards/2
+            const isOverflow = amCards > 8 && index > amCards / 2;
+            // const  inactiveIndex = this.sortedDocsType.docs.indexOf(childPair);
             const inactiveIndex = this.inactiveDocs().indexOf(childPair.layout);
             // const yOffset = this.verticalOffset(index);
 
@@ -307,11 +303,8 @@ export class CollectionCardView extends CollectionSubView() {
                         height: this.panelHeight(childPair.layout)(),
                         transform: `
                             rotate(${!isSelected ? this.rotate(amCards, inactiveIndex) : 0}deg) 
-                            translateY(${isHovered ? this.translateHover(index) : isSelected ? 50 * this.fitContentScale : this.translateY(amCards, inactiveIndex)
-                                //  + this.translateOverFlowY(amCards, inactiveIndex)
-                                }px)
+                            translateY(${isHovered ? this.translateHover(index) : isSelected ? 50 * this.fitContentScale : this.translateY(amCards, inactiveIndex)}px)
                             translateX(${isSelected ? (this._props.PanelWidth() / 2) * this.fitContentScale - this.childDocumentWidth : 0}px)
-
                             scale(${isSelected ? 1.25 : 1})
                             
                             `, //scale has to be applied last or selected offset gets messed up
@@ -327,35 +320,9 @@ export class CollectionCardView extends CollectionSubView() {
         });
     }
 
-    renderRows() {
-        const cards = this.inactiveDocs();
-        const totalCards = cards.length;
-        const rowsNeeded = Math.ceil(totalCards / 8);
-        
-        let rows = [];
-    
-        for (let rowIndex = 0; rowIndex < rowsNeeded; rowIndex++) {
-            let rowStartIndex = rowIndex * 8;
-            let rowEndIndex = rowStartIndex + 8;
-            let rowCards = cards.slice(rowStartIndex, rowEndIndex);
-    
-            rows.push(this.renderRowCards(rowCards, rowIndex));
-        }
-    
-        return rows;
-    }
-
-    renderRowCards(rowCards: Doc[], rowIndex: number) {
-        return (
-            <div className={`card-row row-${rowIndex}`} key={rowIndex} style = {{transform: `translateY(${rowIndex*60})`}}>
-                {this.content(rowCards.length, rowIndex*8, rowIndex*8+8)}
-            </div>
-        );
-    }
-
     @computed get translateWrapperX() {
         if (this.inactiveDocs().length != this.childLayoutPairs.length) {
-            return this.panelWidth()/2;
+            return this.panelWidth() / 2;
         }
 
         return 0;
@@ -378,9 +345,21 @@ export class CollectionCardView extends CollectionSubView() {
                         height: `${100 * this.fitContentScale}%`,
                     }}
                     onMouseLeave={() => this.setHoveredNodeIndex(-1)}>
-                    {this.renderRows()}
+                    {this.contentSorted}
                 </div>
 
+                {/* <Card  className={`custom-modal-position step-${step}`}>
+                <Card.Header>
+                    {header}
+                    <Button variant="close" className= "tut-close"onClick={store.toggleTutorial} style={{ position: 'absolute', top: 0, right: 0 }} />
+                </Card.Header>
+                <Card.Body>{body}
+                <Button className= 'next-step' variant="primary" onClick={store.nextStep}>Next Step!</Button>
+                </Card.Body>
+
+
+            </Card> */}
+
                 {/* {this.focusContent} */}
             </div>
         );
-- 
cgit v1.2.3-70-g09d2


From 8ca77556c3a9caba66faf37fbf259762c2084c0b Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Tue, 7 May 2024 17:28:21 -0400
Subject: hm

---
 src/client/documents/Documents.ts                  |  6 +++
 src/client/util/CurrentUserUtils.ts                | 10 +++++
 src/client/views/MainView.tsx                      |  2 +
 .../views/collections/CollectionCardDeckView.tsx   | 42 ++++++++++++-----
 src/client/views/global/globalScripts.ts           | 52 +++++++++++++++++++++-
 5 files changed, 100 insertions(+), 12 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index c453fd793..b4aa1c90c 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -495,6 +495,12 @@ export class DocumentOptions {
     hoverBackgroundColor?: string; // background color of a label when hovered
     userBackgroundColor?: STRt = new StrInfo('background color associated with a Dash user (seen in header fields of shared documents)');
     userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)');
+
+    card_sort_time?: BOOLt = new BoolInfo('whether sorting cards in deck view by time');
+    card_sort_type?: BOOLt = new BoolInfo('whether sorting cards in deck view by type');
+    card_sort_color?: BOOLt = new BoolInfo('whether sorting cards in deck view by color');
+
+
 }
 
 export const DocOptions = new DocumentOptions();
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 15c3ddad6..cb10f0658 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -719,6 +719,14 @@ pie title Minerals in my tap water
             { title: "Center",    icon: "align-center",        toolTip: "Center Align Stack",  btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"center",     funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
         ]
     }
+    static cardTools(): Button[] {
+        return [
+            { title: "Time",   icon:"hourglass-half", toolTip:"Sort by most recent document creation",  btnType: ButtonType.ClickButton,  expertMode: false, toolType:"time",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Type",   icon:"eye", toolTip:"Sort by document type",  btnType: ButtonType.ClickButton,  expertMode: false, toolType:"docType",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Color",   icon:"palette", toolTip:"Sort by document color",  btnType: ButtonType.ClickButton,  expertMode: false, toolType:"color",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+
+        ]
+    }
     static viewTools(): Button[] {
         return [
             { title: "Snap",      icon: "th",                  toolTip: "Show Snap Lines",     btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
@@ -727,6 +735,7 @@ pie title Minerals in my tap water
             { title: "Clusters",  icon: "braille",             toolTip: "Show Doc Clusters",   btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters",   funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
             { title: "Cards",     icon: "brain",               toolTip: "Flashcards",          btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
             { title: "Arrange",   icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange",  btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"arrange",    funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+
         ]
     }
     static textTools():Button[] {
@@ -811,6 +820,7 @@ pie title Minerals in my tap water
             { title: "Doc",     icon: "Doc",           toolTip: "Freeform Doc tools",   subMenu: CurrentUserUtils.freeTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode, true)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "View",    icon: "View",          toolTip: "View tools",           subMenu: CurrentUserUtils.viewTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "Stack",   icon: "View",          toolTip: "Stacking tools",       subMenu: CurrentUserUtils.stackTools(),  expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
+            { title: "Card",    icon: "Sort",          toolTip: "Card sort",            subMenu: CurrentUserUtils.cardTools(),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
             { title: "Video",   icon: "Video",         toolTip: "Video functions",      subMenu: CurrentUserUtils.videoTools(),  expertMode: false, toolType:DocumentType.VID,            funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
             { title: "Schema",  icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Schema is selected
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 56d28ee5d..e6ae57911 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -526,6 +526,8 @@ export class MainView extends ObservableReactComponent<{}> {
                 fa.faZ,
                 fa.faArrowsUpToLine,
                 fa.faArrowsDownToLine,
+                fa.faPalette,
+                fa.faHourglassHalf
             ]
         );
     }
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index d0a796218..ab7aac267 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -159,14 +159,18 @@ export class CollectionCardView extends CollectionSubView() {
     };
 
     @computed get sortedDocsType() {
-        // This field is set to 'type' to sort documents by their type
-        // const typeField = 'type';
-
-        // Whether to sort in descending order
+        
         const desc = BoolCast(this.layoutDoc.sortDesc);
 
+        let sorted = []
+
+        for (let i=0; i< this.childLayoutPairs.length; i++){ //copying everything in childlayout pairs to sorted so that i can use the sort function without altering the original list
+            sorted[i] = this.childLayoutPairs[i]
+        }
+
+
         // Copy and sort documents by type
-        const docs = this.childLayoutPairs.sort((docA, docB) => {
+        const docs = sorted.sort((docA, docB) => {
             const typeA = docA.layout.type ?? ''; // If docA.type is undefined, use an empty string
             const typeB = docB.layout.type ?? ''; // If docB.type is undefined, use an empty string
 
@@ -177,10 +181,15 @@ export class CollectionCardView extends CollectionSubView() {
             if (desc) out *= -1; // Reverse the sort order if descending is true
             return out;
         });
+        
 
         return { docs };
     }
 
+    
+
+   
+
     @observable docRefs = new ObservableMap<Doc, DocumentView>();
 
     @computed get contentSorted() {
@@ -213,9 +222,10 @@ export class CollectionCardView extends CollectionSubView() {
         // Map sorted documents to their rendered components
         return sortedDocs.map((childPair, index) => {
             // const childPair = { layout: doc, data: doc }; 
-            const isSelected = this.isSelected(index);
             const isHovered = this.hoveredNodeIndex === index;
-            const inactiveIndex = this.sortedDocsType.docs.indexOf(childPair);
+            const inactiveIndex = this.sortedDocsType.docs.filter(d => !SelectionManager.IsSelected(d.layout)).indexOf(childPair);
+            const isSelected = SelectionManager.IsSelected(childPair.layout);
+
 
 
 
@@ -234,7 +244,7 @@ export class CollectionCardView extends CollectionSubView() {
                         height: this.panelHeight(childPair.layout)(),
                         transform: `
                             rotate(${!isSelected ? this.rotate(amCards, inactiveIndex) : 0}deg)
-                            translateY(${isHovered ? this.translateHover(index) : isSelected ? 50 * this.fitContentScale : this.translateY(amCards, inactiveIndex)}px)
+                            translateY(${isHovered ? this.translateHover(inactiveIndex) : isSelected ? 50 * this.fitContentScale : this.translateY(amCards, inactiveIndex)}px)
                             translateX(${isSelected ? (this._props.PanelWidth() / 2) * this.fitContentScale - this.childDocumentWidth : 0}px)
                             scale(${isSelected ? 1.25 : 1})
                         `,
@@ -250,7 +260,7 @@ export class CollectionCardView extends CollectionSubView() {
         // const currentIndex = NumCast(this.layoutDoc._carousel_index);
         const amCards = this.inactiveDocs().length;
         console.log(amCards + "lol")
-        const sortedDocs = this.sortedDocsType.docs; // Retrieve sorted documents
+        // const sortedDocs = this.sortedDocsType.docs; // Retrieve sorted documents
 
         // const myInactives =
         const displayDoc = (childPair: { layout: Doc; data: Doc }, screenToLocalTransform: () => Transform) => {
@@ -328,6 +338,18 @@ export class CollectionCardView extends CollectionSubView() {
         return 0;
     }
 
+    @computed get renderCardsSort(){
+        if (BoolCast(this._props.Document.card_sort_type) == true){
+            return this.contentSorted
+        }
+
+        else{
+            return this.content
+        }
+    }
+
+    
+
     render() {
         return (
             <div
@@ -345,7 +367,7 @@ export class CollectionCardView extends CollectionSubView() {
                         height: `${100 * this.fitContentScale}%`,
                     }}
                     onMouseLeave={() => this.setHoveredNodeIndex(-1)}>
-                    {this.contentSorted}
+                    {this.renderCardsSort}
                 </div>
 
                 {/* <Card  className={`custom-modal-position step-${step}`}>
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 3fdc9a488..ffb1d751d 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -102,10 +102,10 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
     selected ? selected.CollectionFreeFormDocumentView?.float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
 });
 
-ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce', checkResult?: boolean, persist?: boolean) {
+ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color', checkResult?: boolean, persist?: boolean) {
     const selected = SelectionManager.Docs.lastElement();
     // prettier-ignore
-    const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([
+    const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([
         ['grid', {
             checkResult: (doc:Doc) => BoolCast(doc?._freeform_backgroundGrid, false),
             setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid,
@@ -135,6 +135,19 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid
             checkResult: (doc:Doc) => BoolCast(Doc.UserDoc().defaultToFlashcards, false),
             setDoc: (doc:Doc,dv:DocumentView) => Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards,
         }],
+        ['time', {
+            checkResult: (doc:Doc) => StrCast(doc?.cardSort),
+            setDoc: (doc:Doc,dv:DocumentView) => doc.card_sort_time = !doc.card_sort_time,
+        }],
+        ['docType', {
+            checkResult: (doc:Doc) => StrCast(doc?.cardSort),
+            setDoc: (doc:Doc,dv:DocumentView) => doc.card_sort_type = !doc.card_sort_type,
+        }],
+        ['color', {
+            checkResult: (doc:Doc) => StrCast(doc?.cardSort),
+            setDoc: (doc:Doc,dv:DocumentView) => doc.card_sort_color = !doc.card_sort_color,
+        }],
+
    ]);
 
     if (checkResult) {
@@ -145,6 +158,39 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid
     setTimeout(() => batch.end(), 100);
 });
 
+// ScriptingGlobals.add(function setCardSortAttr(attr: 'time' | 'docType' | 'color', value: any, checkResult?: boolean) {
+//     // const editorView = RichTextMenu.Instance?.TextView?.EditorView;
+//     const selected = SelectionManager.Docs.lastElement();
+//     // prettier-ignore
+//     const map: Map<'time' | 'docType' | 'color', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([
+//         ['time', {
+//             checkResult: (doc:Doc) => StrCast(doc?.cardSort),
+//             setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "time",
+//         }],
+//         ['docType', {
+//             checkResult: (doc:Doc) => StrCast(doc?.cardSort),
+//             setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "type",
+//         }],
+//         ['color', {
+//             checkResult: (doc:Doc) => StrCast(doc?.cardSort),
+//             setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "color",
+//         }],
+//         // ['custom', {
+//         //     checkResult: () => RichTextMenu.Instance.textAlign,
+//         //     setDoc: () => value && editorView?.state ? RichTextMenu.Instance.align(editorView, editorView.dispatch, value):(Doc.UserDoc().textAlign = value),
+//         // }]
+//         // ,
+//     ]);
+
+//     if (checkResult) {
+//         return map.get(attr)?.checkResult(selected);
+//     }
+
+//     console.log('hey')
+//     SelectionManager.Views.map(dv => map.get(attr)?.setDoc(dv.layoutDoc, dv));
+//     console.log('success')
+// });
+
 ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highlight' | 'fontSize' | 'alignment', value: any, checkResult?: boolean) {
     const editorView = RichTextMenu.Instance?.TextView?.EditorView;
     const selected = SelectionManager.Docs.lastElement();
@@ -182,6 +228,8 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh
     map.get(attr)?.setDoc?.();
 });
 
+
+
 type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'elide' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal';
 type attrfuncs = [attrname, { checkResult: () => boolean; toggle: () => any }];
 
-- 
cgit v1.2.3-70-g09d2


From 4429a0565888d9118e37f1e58dec300220831661 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Sat, 11 May 2024 22:30:39 -0400
Subject: transformations are being unbelieveably silly rn

---
 src/client/documents/Documents.ts                  |   7 +-
 src/client/util/CurrentUserUtils.ts                |   8 +-
 .../views/collections/CollectionCardDeckView.scss  |  31 +-
 .../views/collections/CollectionCardDeckView.tsx   | 417 ++++++++++++++-------
 src/client/views/global/globalScripts.ts           |  15 +-
 5 files changed, 316 insertions(+), 162 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 1c57c5d63..77cc5f51e 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -499,9 +499,10 @@ export class DocumentOptions {
     userBackgroundColor?: STRt = new StrInfo('background color associated with a Dash user (seen in header fields of shared documents)');
     userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)');
 
-    card_sort_time?: BOOLt = new BoolInfo('whether sorting cards in deck view by time');
-    card_sort_type?: BOOLt = new BoolInfo('whether sorting cards in deck view by type');
-    card_sort_color?: BOOLt = new BoolInfo('whether sorting cards in deck view by color');
+    cardSort?: STRt = new StrInfo('way cards are sorted in deck view');
+    // card_sort_time?: BOOLt = new BoolInfo('whether sorting cards in deck view by time');
+    // card_sort_type?: BOOLt = new BoolInfo('whether sorting cards in deck view by type');
+    // card_sort_color?: BOOLt = new BoolInfo('whether sorting cards in deck view by color');
 
 
 }
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 88fcf098b..767d6c635 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -713,9 +713,11 @@ pie title Minerals in my tap water
     }
     static cardTools(): Button[] {
         return [
-            { title: "Time",   icon:"hourglass-half", toolTip:"Sort by most recent document creation",  btnType: ButtonType.ClickButton,  expertMode: false, toolType:"time",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Type",   icon:"eye", toolTip:"Sort by document type",  btnType: ButtonType.ClickButton,  expertMode: false, toolType:"docType",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Color",   icon:"palette", toolTip:"Sort by document color",  btnType: ButtonType.ClickButton,  expertMode: false, toolType:"color",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Time",   icon:"hourglass-half", toolTip:"Sort by most recent document creation",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"time",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Type",   icon:"eye", toolTip:"Sort by document type",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"docType",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Color",   icon:"palette", toolTip:"Sort by document color",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"color",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Links",   icon:"link", toolTip:"Sort by its links",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"links",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+
 
         ]
     }
diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss
index ff9c0a569..aafc3b556 100644
--- a/src/client/views/collections/CollectionCardDeckView.scss
+++ b/src/client/views/collections/CollectionCardDeckView.scss
@@ -2,26 +2,43 @@
 
 .collectionCardView-outer {
     height: 100%;
+    width: 100%;
     position: relative;
     background-color: white;
     overflow: hidden;
 }
 
 .card-wrapper {
-    display: flex;
+    display: grid;
+    grid-template-columns: repeat(10, 1fr); /* Creates 10 columns, adjust number as needed */
+    // width: 100%;
+
     position: absolute;
     align-items: center;
+    justify-items: center;
+    justify-content: center;
+
     transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955);
 }
 
-.card-row{
-    display: flex;
-    position: absolute;
-    align-items: center;
-    transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955);
+// .card-wrapper::after {
+//     content: "";
+//     width: 100%; /* Forces wrapping */
+// }
 
+// .card-wrapper > .card-item:nth-child(10n)::after {
+//     content: "";
+//     width: 100%; /* Forces wrapping after every 10th item */
+// }
 
-}
+// .card-row{
+//     display: flex;
+//     position: absolute;
+//     align-items: center;
+//     transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955);
+
+
+// }
 
 .card-item-active,
 .card-item {
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index ab7aac267..bda72b2d6 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
 import { Utils, returnFalse, returnTrue, returnZero } from '../../../Utils';
 import { Doc, DocListCast, Field } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
-import { NumCast, ScriptCast, StrCast, BoolCast } from '../../../fields/Types';
+import { NumCast, ScriptCast, StrCast, BoolCast, DocCast } from '../../../fields/Types';
 import { DragManager } from '../../util/DragManager';
 import { SelectionManager } from '../../util/SelectionManager';
 import { StyleProp } from '../StyleProvider';
@@ -12,7 +12,10 @@ import { DocumentView } from '../nodes/DocumentView';
 import './CollectionCardDeckView.scss';
 import { CollectionSubView } from './CollectionSubView';
 import { Transform } from '../../util/Transform';
+import { LinkManager } from '../../util/LinkManager';
 // import Card from 'react-bootstrap/Card';
+import { DocumentType } from '../../documents/DocumentTypes';
+import { forEach } from 'lodash';
 
 
 @observer
@@ -21,6 +24,10 @@ export class CollectionCardView extends CollectionSubView() {
 
     @observable hoveredNodeIndex = -1;
 
+    @computed get myChildLayoutPairs(){
+        return this.childLayoutPairs.filter(l => l.layout.type != DocumentType.LINK)
+    }
+
     @action
     setHoveredNodeIndex = (index: number) => {
         if (!this.isSelected(index)) {
@@ -38,10 +45,10 @@ export class CollectionCardView extends CollectionSubView() {
     @action
     setSelectedNodeIndex = (index: number) => {
         const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
-        console.log('goodnight');
+        // console.log('goodnight');
 
         if (SelectionManager.IsSelected(docs[index])) {
-            console.log('good mornings');
+            // console.log('good mornings');
             this.setSelectedNodeIndex(index);
         }
     };
@@ -53,12 +60,11 @@ export class CollectionCardView extends CollectionSubView() {
     };
 
     inactiveDocs = () => {
-        const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
-
+        const docs = this.myChildLayoutPairs;
+        return docs.filter(d => !SelectionManager.IsSelected(d.layout));
+    };
 
 
-        return docs.filter(d => !SelectionManager.IsSelected(d));
-    };
 
     middleIndex = Math.floor(this.inactiveDocs().length / 2);
 
@@ -81,12 +87,21 @@ export class CollectionCardView extends CollectionSubView() {
         }
     };
 
+
+
     childDocumentWidth = 600; // target width of a Doc...
     /**
      * how much to scale down the contents of the view so that everything will fit
      */
     @computed get fitContentScale() {
-        return (this.childDocumentWidth * this.childLayoutPairs.length) / this._props.PanelWidth();
+        if (this.myChildLayoutPairs.length< this.maxRowCount){
+            length = this.myChildLayoutPairs.length
+        }
+
+        else{
+            length = this.maxRowCount
+        }
+        return (this.childDocumentWidth * length) / this._props.PanelWidth();
     }
     panelWidth = () => this.childDocumentWidth;
     panelHeight = (layout: Doc) => () => (2 * (this.panelWidth() * NumCast(layout._height))) / NumCast(layout._width);
@@ -95,7 +110,7 @@ export class CollectionCardView extends CollectionSubView() {
     isChildContentActive = () => (this.isContentActive() ? true : false);
 
     rotate = (amCards: number, index: number) => {
-        console.log(amCards + "wtf")
+        // console.log(amCards + "wtf")
         const possRotate = -30 + index * (30 / ((amCards - (amCards % 2)) / 2));
         const stepMag = Math.abs(-30 + (amCards / 2 - 1) * (30 / ((amCards - (amCards % 2)) / 2)));
 
@@ -113,31 +128,39 @@ export class CollectionCardView extends CollectionSubView() {
         return possRotate;
     };
 
-    translateY = (amCards: number, index: number) => {
+    translateY = (amCards: number, index: number, realIndex: number) => {
         const evenOdd = amCards % 2;
         const apex = (amCards - evenOdd) / 2;
         const stepMag = 200 / ((amCards - evenOdd) / 2) + Math.abs((apex - index) * 25);
+        let rowOffset = 0
 
-        console.log('steo' + stepMag);
+        if (realIndex > this.maxRowCount -1){
+            // 11 - 1 = 10
+            rowOffset = 400 * ((realIndex - (realIndex % this.maxRowCount)) / this.maxRowCount)
+        }
+
+        
+
+        // console.log('steo' + stepMag);
 
         if (evenOdd == 1 || index < apex - 1) {
-            console.log('hi' + index);
-            return Math.abs(stepMag * (apex - index));
+            // console.log('hi' + index);
+            return Math.abs(stepMag * (apex - index)) - rowOffset;
         } else {
             if (index == apex || index == apex - 1) {
-                return 0;
+                return 0 - rowOffset;
             }
 
-            return Math.abs(stepMag * (apex - index - 1));
+            return Math.abs(stepMag * (apex - index - 1)) - rowOffset;
         }
     };
 
-    translateOverFlowY = (amCards: number, index: number) => {
-        if (amCards > 8 && index > amCards / 2) {
-            return 100;
-        }
-        return 0;
-    };
+    // translateOverFlowY = (amCards: number, index: number) => {
+    //     if (amCards > 8 && index > amCards / 2) {
+    //         return 100;
+    //     }
+    //     return 0;
+    // };
 
     translateSelected = (index: number): number => {
         if (this.isSelected(index)) {
@@ -161,78 +184,190 @@ export class CollectionCardView extends CollectionSubView() {
     @computed get sortedDocsType() {
         
         const desc = BoolCast(this.layoutDoc.sortDesc);
-
         let sorted = []
+        let docs = []
 
-        for (let i=0; i< this.childLayoutPairs.length; i++){ //copying everything in childlayout pairs to sorted so that i can use the sort function without altering the original list
-            sorted[i] = this.childLayoutPairs[i]
+        for (let i=0; i< this.myChildLayoutPairs.length; i++){ //copying everything in childlayout pairs to sorted so that i can use the sort function without altering the original list
+            sorted[i] = this.myChildLayoutPairs[i]
         }
 
+        switch(this._props.Document.cardSort){
+            case "type":
+                 // Copy and sort documents by type 
+                return this.sort(sorted, "type", desc)
+            case "color":
+                return this.sort(sorted, "color", desc)
+            case "links":
+                console.log("hi")
+
+                   let links = LinkManager.Instance.getAllRelatedLinks(this.myChildLayoutPairs[0].layout)
+
+                   console.log(links)
+                   
+                   // }
 
-        // Copy and sort documents by type
-        const docs = sorted.sort((docA, docB) => {
-            const typeA = docA.layout.type ?? ''; // If docA.type is undefined, use an empty string
-            const typeB = docB.layout.type ?? ''; // If docB.type is undefined, use an empty string
+            default:
+                docs =  this.myChildLayoutPairs
+                return {docs}
+
+
+                
+        }
+    }
+
+    
+    
+
+    hexToHsv = (hex: string): [number, number, number] => {
+        if (!hex) return [0, 0, 0]; // Default to black if hex is not defined
+        const r = parseInt(hex.slice(1, 3), 16) / 255;
+        const g = parseInt(hex.slice(3, 5), 16) / 255;
+        const b = parseInt(hex.slice(5, 7), 16) / 255;
+        const max = Math.max(r, g, b), min = Math.min(r, g, b);
+        const d = max - min;
+        let h: number;
+        const s = (max === 0 ? 0 : d / max);
+        const v = max;
+
+        switch (max) {
+            case min: h = 0; break;
+            case r: h = (g - b) / d + (g < b ? 6 : 0); break;
+            case g: h = (b - r) / d + 2; break;
+            case b: h = (r - g) / d + 4; break;
+            default: h = 0; break;
+        }
+        h /= 6;
+        return [h, s, v];
+    };
+
+    
+
+    sort = (docs: {layout: Doc;  data: Doc; }[], sortType: string, isDesc: boolean) => {
+
+        docs.sort((docA, docB) => {
+            let typeA;
+            let typeB             
+
+            switch(sortType) {
+                case "color":
+                    typeA = this.hexToHsv(StrCast(docA.layout.backgroundColor)) ?? ''; // If docA.type is undefined, use an empty string
+                    typeB = this.hexToHsv(StrCast(docB.layout.backgroundColor)) ?? ''; // If docB.type is undefined, use an empty string
+                    break
+
+                default:
+                    typeA = docA.layout.type ?? ''; // If docA.type is undefined, use an empty string
+                    typeB = docB.layout.type ?? ''; // If docB.type is undefined, use an empty string
+                    break
+            }
+            
 
             // Perform a basic string comparison if types are strings
             let out = 0;
             if (typeA < typeB) out = -1;
             if (typeA > typeB) out = 1;
-            if (desc) out *= -1; // Reverse the sort order if descending is true
+            if (isDesc) out *= -1; // Reverse the sort order if descending is true
             return out;
-        });
-        
+            });
 
-        return { docs };
+        return {docs}
     }
 
     
 
+    // @computed get cards(){
+
+
+    // }
+
+    
+    displayDoc = (childPair: { layout: Doc; data: Doc }, screenToLocalTransform: () => Transform) => {
+        return (
+            <DocumentView
+                {...this._props}
+                ref={action((r: DocumentView) => r?.ContentDiv && this.docRefs.set(childPair.layout, r))}
+                Document={childPair.layout}
+                TemplateDataDocument={childPair.data}
+                // onClickScript={this.toggleIcon}
+                //suppressSetHeight={true}
+                NativeWidth={returnZero}
+                NativeHeight={returnZero}
+                layout_fitWidth={returnFalse}
+                onDoubleClickScript={this.onChildDoubleClick}
+                renderDepth={this._props.renderDepth + 1}
+                LayoutTemplate={this._props.childLayoutTemplate}
+                LayoutTemplateString={this._props.childLayoutString}
+                // focus={this.focus}
+                ScreenToLocalTransform={screenToLocalTransform} //makes sure the box wrapper thing is in the right spot
+                isContentActive={this.isChildContentActive}
+                isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
+                PanelWidth={this.panelWidth}
+                PanelHeight={this.panelHeight(childPair.layout)}
+            />
+        );
+    };
    
 
     @observable docRefs = new ObservableMap<Doc, DocumentView>();
 
+    @observable maxRowCount = 10;
+
+    overflowAmCardsCalc(index: number) {
+        if (this.inactiveDocs().length < this.maxRowCount){
+            return this.inactiveDocs().length
+        }
+        // 13 - 3 = 10 
+        const totalCards = this.inactiveDocs().length
+        // if 9 or less
+        if ( index < ((totalCards - (totalCards % 10)))){
+            return this.maxRowCount
+        }
+        //(3)
+        return totalCards % 10
+
+    }
+
+    overflowIndexCalc(realIndex: number) {
+        if (realIndex < 10){
+            return realIndex
+        }
+
+        return realIndex % 10
+
+    }
+
+    translateOverflowX(realIndex: number, calcRowCards: number){
+        if (realIndex < this.maxRowCount){
+            return 0
+        }
+
+        return (10 - calcRowCards) * (this.panelWidth()/2)
+
+
+
+
+    }
+
     @computed get contentSorted() {
         const sortedDocs = this.sortedDocsType.docs; 
-        const amCards = this.inactiveDocs().length; 
-
-        const displayDoc = (childPair: { layout: Doc; data: Doc }, screenToLocalTransform: () => Transform) => {
-            return (
-                <DocumentView
-                    {...this._props}
-                    ref={action((r: DocumentView) => r?.ContentDiv && this.docRefs.set(childPair.layout, r))}
-                    Document={childPair.layout}
-                    TemplateDataDocument={childPair.data}
-                    NativeWidth={returnZero}
-                    NativeHeight={returnZero}
-                    layout_fitWidth={returnFalse}
-                    onDoubleClickScript={this.onChildDoubleClick}
-                    renderDepth={this._props.renderDepth + 1}
-                    LayoutTemplate={this._props.childLayoutTemplate}
-                    LayoutTemplateString={this._props.childLayoutString}
-                    ScreenToLocalTransform={screenToLocalTransform}
-                    isContentActive={this.isChildContentActive}
-                    isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
-                    PanelWidth={this.panelWidth}
-                    PanelHeight={this.panelHeight(childPair.layout)}
-                />
-            );
-        };
+        // const amCards = this.inactiveDocs().length; 
 
         // Map sorted documents to their rendered components
         return sortedDocs.map((childPair, index) => {
             // const childPair = { layout: doc, data: doc }; 
-            const isHovered = this.hoveredNodeIndex === index;
-            const inactiveIndex = this.sortedDocsType.docs.filter(d => !SelectionManager.IsSelected(d.layout)).indexOf(childPair);
-            const isSelected = SelectionManager.IsSelected(childPair.layout);
 
 
+            const isHovered = this.hoveredNodeIndex === index;
+            const realIndex = this.sortedDocsType.docs.filter(d => !SelectionManager.IsSelected(d.layout)).indexOf(childPair);
+            const calcRowIndex = this.overflowIndexCalc(realIndex)
+
+            const amCards = this.overflowAmCardsCalc(realIndex)
 
+            const isSelected = SelectionManager.IsSelected(childPair.layout);
 
             const childScreenToLocal = () => {
                 const dref = this.docRefs.get(childPair.layout);
                 const { translateX, translateY, scale } = Utils.GetScreenTransform(dref?.ContentDiv);
-                return new Transform(-translateX + (dref?.centeringX || 0) * scale, -translateY + (dref?.centeringY || 0) * scale, 1).scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, inactiveIndex) : 0);
+                return new Transform(-translateX + (dref?.centeringX || 0) * scale, -translateY + (dref?.centeringY || 0) * scale, 1).scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, calcRowIndex) : 0);
             };
 
             return (
@@ -243,111 +378,105 @@ export class CollectionCardView extends CollectionSubView() {
                         width: this.panelWidth(),
                         height: this.panelHeight(childPair.layout)(),
                         transform: `
-                            rotate(${!isSelected ? this.rotate(amCards, inactiveIndex) : 0}deg)
-                            translateY(${isHovered ? this.translateHover(inactiveIndex) : isSelected ? 50 * this.fitContentScale : this.translateY(amCards, inactiveIndex)}px)
-                            translateX(${isSelected ? (this._props.PanelWidth() / 2) * this.fitContentScale - this.childDocumentWidth : 0}px)
+                            rotate(${!isSelected ? this.rotate(amCards, calcRowIndex) : 0}deg)
+                            translateY(${isHovered ? this.translateHover(realIndex) : isSelected ? 50 * this.fitContentScale : this.translateY(amCards, calcRowIndex, realIndex)}px)
+                            translateX(${isSelected ? (this._props.PanelWidth() / 2) * this.fitContentScale - this.childDocumentWidth : this.translateOverflowX(realIndex, amCards)}px)
                             scale(${isSelected ? 1.25 : 1})
                         `,
                     }}
                     onMouseEnter={() => this.setHoveredNodeIndex(index)}>
-                    {displayDoc(childPair, childScreenToLocal)}
+                    {this.displayDoc(childPair, childScreenToLocal)}
+
+                    {/* <circle r="45" cx="50" cy="50" fill="red" /> */}
+
                 </div>
             );
         });
     }
 
-    @computed get content() {
-        // const currentIndex = NumCast(this.layoutDoc._carousel_index);
-        const amCards = this.inactiveDocs().length;
-        console.log(amCards + "lol")
-        // const sortedDocs = this.sortedDocsType.docs; // Retrieve sorted documents
-
-        // const myInactives =
-        const displayDoc = (childPair: { layout: Doc; data: Doc }, screenToLocalTransform: () => Transform) => {
-            return (
-                <DocumentView
-                    {...this._props}
-                    ref={action((r: DocumentView) => r?.ContentDiv && this.docRefs.set(childPair.layout, r))}
-                    Document={childPair.layout}
-                    TemplateDataDocument={childPair.data}
-                    // onClickScript={this.toggleIcon}
-                    //suppressSetHeight={true}
-                    NativeWidth={returnZero}
-                    NativeHeight={returnZero}
-                    layout_fitWidth={returnFalse}
-                    onDoubleClickScript={this.onChildDoubleClick}
-                    renderDepth={this._props.renderDepth + 1}
-                    LayoutTemplate={this._props.childLayoutTemplate}
-                    LayoutTemplateString={this._props.childLayoutString}
-                    // focus={this.focus}
-                    ScreenToLocalTransform={screenToLocalTransform} //makes sure the box wrapper thing is in the right spot
-                    isContentActive={this.isChildContentActive}
-                    isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
-                    PanelWidth={this.panelWidth}
-                    PanelHeight={this.panelHeight(childPair.layout)}
-                />
-            );
-        };
+    
 
-        return this.childLayoutPairs.map((childPair, index) => {
-            const isSelected = this.isSelected(index);
-            const isHovered = this.hoveredNodeIndex === index;
-            const isOverflow = amCards > 8 && index > amCards / 2;
-            // const  inactiveIndex = this.sortedDocsType.docs.indexOf(childPair);
-            const inactiveIndex = this.inactiveDocs().indexOf(childPair.layout);
-            // const yOffset = this.verticalOffset(index);
+    // @computed get content() {
+    //     // const currentIndex = NumCast(this.layoutDoc._carousel_index);
+    //     const amCards = this.inactiveDocs().length;
+    //     console.log(amCards + "lol")
+    //     // const sortedDocs = this.sortedDocsType.docs; // Retrieve sorted documents
 
-            const childScreenToLocal = () => {
-                const dref = this.docRefs.get(childPair.layout);
-                const { translateX, translateY, scale } = Utils.GetScreenTransform(dref?.ContentDiv);
-                // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform.  so we have to subtract that off
-                return new Transform(-translateX + (dref?.centeringX || 0) * scale, -translateY + (dref?.centeringY || 0) * scale, 1).scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, inactiveIndex) : 0);
-            };
+    //     // const myInactives =
+        
 
-            return (
-                <div
-                    key={childPair.layout[Id]}
-                    className={`card-item${isSelected ? '-active' : ''}`}
-                    style={{
-                        width: this.panelWidth(),
-                        height: this.panelHeight(childPair.layout)(),
-                        transform: `
-                            rotate(${!isSelected ? this.rotate(amCards, inactiveIndex) : 0}deg) 
-                            translateY(${isHovered ? this.translateHover(index) : isSelected ? 50 * this.fitContentScale : this.translateY(amCards, inactiveIndex)}px)
-                            translateX(${isSelected ? (this._props.PanelWidth() / 2) * this.fitContentScale - this.childDocumentWidth : 0}px)
-                            scale(${isSelected ? 1.25 : 1})
+    //     return this.myChildLayoutPairs.map((childPair, index) => {
+    //         const isSelected = this.isSelected(index);
+    //         const isHovered = this.hoveredNodeIndex === index;
+    //         const isOverflow = amCards > 8 && index > amCards / 2;
+    //         // const  inactiveIndex = this.sortedDocsType.docs.indexOf(childPair);
+    //         const inactiveIndex = this.inactiveDocs().indexOf(childPair.layout);
+    //         // const yOffset = this.verticalOffset(index);
+
+    //         const childScreenToLocal = () => {
+    //             const dref = this.docRefs.get(childPair.layout);
+    //             const { translateX, translateY, scale } = Utils.GetScreenTransform(dref?.ContentDiv);
+    //             // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform.  so we have to subtract that off
+    //             return new Transform(-translateX + (dref?.centeringX || 0) * scale, -translateY + (dref?.centeringY || 0) * scale, 1).scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, inactiveIndex) : 0);
+    //         };
+
+    //         return (
+    //             <div
+    //                 key={childPair.layout[Id]}
+    //                 className={`card-item${isSelected ? '-active' : ''}`}
+    //                 style={{
+    //                     width: this.panelWidth(),
+    //                     height: this.panelHeight(childPair.layout)(),
+    //                     transform: `
+    //                         rotate(${!isSelected ? this.rotate(amCards, inactiveIndex) : 0}deg) 
+    //                         translateY(${isHovered ? this.translateHover(index) : isSelected ? 50 * this.fitContentScale : this.translateY(amCards, inactiveIndex)}px)
+    //                         translateX(${isSelected ? (this._props.PanelWidth() / 2) * this.fitContentScale - this.childDocumentWidth : 0}px)
+    //                         scale(${isSelected ? 1.25 : 1})
                             
-                            `, //scale has to be applied last or selected offset gets messed up
-                    }}
-                    // onClick={() => this.setSelectedNodeIndex(index)}
+    //                         `, //scale has to be applied last or selected offset gets messed up
+    //                 }}
+    //                 // onClick={() => this.setSelectedNodeIndex(index)}
 
-                    onMouseEnter={() => this.setHoveredNodeIndex(index)}>
-                    {/* {this.lol(childPair.data, index)} */}
+    //                 onMouseEnter={() => this.setHoveredNodeIndex(index)}>
+    //                 {/* {this.lol(childPair.data, index)} */}
 
-                    {displayDoc(childPair, childScreenToLocal)}
-                </div>
-            );
-        });
-    }
+    //                 {this.displayDoc(childPair, childScreenToLocal)}
+    //             </div>
+    //         );
+    //     });
+    // }
 
     @computed get translateWrapperX() {
-        if (this.inactiveDocs().length != this.childLayoutPairs.length) {
-            return this.panelWidth() / 2;
-        }
 
-        return 0;
-    }
+        let translate = 0
 
-    @computed get renderCardsSort(){
-        if (BoolCast(this._props.Document.card_sort_type) == true){
-            return this.contentSorted
+        if (this.inactiveDocs().length != this.myChildLayoutPairs.length) {
+            translate += this.panelWidth() / 2;
         }
 
-        else{
-            return this.content
-        }
+        console.log("in" + this.inactiveDocs().length)
+        console.log("max" + this.maxRowCount)
+
+        // if (this.inactiveDocs().length > this.maxRowCount){
+        //     translate += ((this.inactiveDocs().length % this.maxRowCount) * this.panelWidth()) / 2
+        //     console.log("trans" + (this.inactiveDocs().length % this.maxRowCount) / 2 )
+        // }
+
+
+
+        return translate;
     }
 
+    // @computed get renderCardsSort(){
+    //     if (StrCast(this._props.Document.cardSort) == "type"){
+    //         return this.contentSorted
+    //     }
+
+    //     else{
+    //         return this.content
+    //     }
+    // }
+
     
 
     render() {
@@ -367,7 +496,7 @@ export class CollectionCardView extends CollectionSubView() {
                         height: `${100 * this.fitContentScale}%`,
                     }}
                     onMouseLeave={() => this.setHoveredNodeIndex(-1)}>
-                    {this.renderCardsSort}
+                    {this.contentSorted}
                 </div>
 
                 {/* <Card  className={`custom-modal-position step-${step}`}>
@@ -386,4 +515,4 @@ export class CollectionCardView extends CollectionSubView() {
             </div>
         );
     }
-}
+}
\ No newline at end of file
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 0c6095346..526315802 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -105,10 +105,10 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
     selected ? selected.CollectionFreeFormDocumentView?.float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
 });
 
-ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color', checkResult?: boolean, persist?: boolean) {
+ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links', checkResult?: boolean, persist?: boolean) {
     const selected = SelectionManager.Docs.lastElement();
     // prettier-ignore
-    const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([
+    const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([
         ['grid', {
             checkResult: (doc:Doc) => BoolCast(doc?._freeform_backgroundGrid, false),
             setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid,
@@ -140,15 +140,20 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid
         }],
         ['time', {
             checkResult: (doc:Doc) => StrCast(doc?.cardSort),
-            setDoc: (doc:Doc,dv:DocumentView) => doc.card_sort_time = !doc.card_sort_time,
+            setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "time", 
         }],
         ['docType', {
             checkResult: (doc:Doc) => StrCast(doc?.cardSort),
-            setDoc: (doc:Doc,dv:DocumentView) => doc.card_sort_type = !doc.card_sort_type,
+            setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "type",
         }],
         ['color', {
             checkResult: (doc:Doc) => StrCast(doc?.cardSort),
-            setDoc: (doc:Doc,dv:DocumentView) => doc.card_sort_color = !doc.card_sort_color,
+            setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "color",
+        }],
+
+        ['links', {
+            checkResult: (doc:Doc) => StrCast(doc?.cardSort),
+            setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "links",
         }],
 
    ]);
-- 
cgit v1.2.3-70-g09d2


From 13b4aa868ac664399bfd320902f3ddee57072392 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Mon, 13 May 2024 09:21:29 -0400
Subject: create technically working

---
 src/client/documents/Documents.ts                  |   1 +
 src/client/util/CurrentUserUtils.ts                |  18 +++-
 src/client/util/SelectionManager.ts                |   1 +
 src/client/views/MainView.tsx                      |   3 +-
 .../views/collections/CollectionCardDeckView.scss  |  27 ++++-
 .../views/collections/CollectionCardDeckView.tsx   | 111 ++++++++++++++++++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   2 +-
 src/client/views/global/globalScripts.ts           |  21 +++-
 8 files changed, 163 insertions(+), 21 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 77cc5f51e..df69e215a 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -500,6 +500,7 @@ export class DocumentOptions {
     userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)');
 
     cardSort?: STRt = new StrInfo('way cards are sorted in deck view');
+    customSortCount?: NUMt = new NumInfo('number of custom sorts the user has created');
     // card_sort_time?: BOOLt = new BoolInfo('whether sorting cards in deck view by time');
     // card_sort_type?: BOOLt = new BoolInfo('whether sorting cards in deck view by type');
     // card_sort_color?: BOOLt = new BoolInfo('whether sorting cards in deck view by color');
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 767d6c635..59560dd78 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -9,7 +9,7 @@ import { PrefetchProxy } from "../../fields/Proxy";
 import { RichTextField } from "../../fields/RichTextField";
 import { listSpec } from "../../fields/Schema";
 import { ScriptField } from "../../fields/ScriptField";
-import { Cast, DateCast, DocCast, StrCast } from "../../fields/Types";
+import { Cast, DateCast, DocCast, StrCast, NumCast } from "../../fields/Types";
 import { WebField, nullAudio } from "../../fields/URLField";
 import { SetCachedGroups, SharingPermissions } from "../../fields/util";
 import { GestureUtils } from "../../pen-gestures/GestureUtils";
@@ -34,6 +34,9 @@ import { SnappingManager } from "./SnappingManager";
 import { UndoManager } from "./UndoManager";
 import { LabelBox } from "../views/nodes/LabelBox";
 import { ImageBox } from "../views/nodes/ImageBox";
+import { CollectionFreeFormView } from "../views/collections/collectionFreeForm";
+import { CollectionCardView } from "../views/collections/CollectionCardDeckView";
+import { SelectionManager } from "./SelectionManager";
 
 interface Button {
     // DocumentOptions fields a button can set
@@ -712,12 +715,23 @@ pie title Minerals in my tap water
         ]
     }
     static cardTools(): Button[] {
+       
+
+    //     if (view != undefined){
+    //         for (let i=0; i< NumCast(view.layoutDoc.customSortCount); i++){
+    //             customs.push(   
+    //                 { title: "Custom " + i,   icon:"robot", toolTip:"Custom sort option " + i,  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}
+    //             )
+
+    //         }
+    // }
         return [
             { title: "Time",   icon:"hourglass-half", toolTip:"Sort by most recent document creation",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"time",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Type",   icon:"eye", toolTip:"Sort by document type",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"docType",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Color",   icon:"palette", toolTip:"Sort by document color",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"color",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Links",   icon:"link", toolTip:"Sort by its links",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"links",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-
+            { title: "Create",   icon:"plus", toolTip:"Create new custom groupings!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            // ...customs
 
         ]
     }
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 36b926053..d507e35ad 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -68,6 +68,7 @@ export class SelectionManager {
     public static IsSelected = (doc?: Doc) => Array.from(doc?.[DocViews] ?? []).some(dv => dv?.IsSelected);
     public static get Views() { return this.Instance.SelectedViews; } // prettier-ignore
     public static get SelectedSchemaDoc() { return this.Instance.SelectedSchemaDocument; } // prettier-ignore
+    
     public static get Docs() { return this.Instance.SelectedViews.map(dv => dv.Document).filter(doc => doc?._type_collection !== CollectionViewType.Docking); } // prettier-ignore
 }
 
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index abdb5abef..a5882dcf3 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -526,7 +526,8 @@ export class MainView extends ObservableReactComponent<{}> {
                 fa.faArrowsUpToLine,
                 fa.faArrowsDownToLine,
                 fa.faPalette,
-                fa.faHourglassHalf
+                fa.faHourglassHalf,
+                fa.faRobot
             ]
         );
     }
diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss
index aafc3b556..222a1d226 100644
--- a/src/client/views/collections/CollectionCardDeckView.scss
+++ b/src/client/views/collections/CollectionCardDeckView.scss
@@ -10,7 +10,7 @@
 
 .card-wrapper {
     display: grid;
-    grid-template-columns: repeat(10, 1fr); /* Creates 10 columns, adjust number as needed */
+    grid-template-columns: repeat(10, 1fr);
     // width: 100%;
 
     position: absolute;
@@ -21,6 +21,31 @@
     transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955);
 }
 
+.card-button-container {
+    display: flex;         
+    padding: 3px;
+    // width: 300px;        
+    background-color: rgb(218, 218, 218); /* Background color of the container */
+    border-radius: 50px;    /* Rounds the corners of the container */
+    transform: translateY(-50px); 
+    // box-shadow: 0 4px 8px rgba(0,0,0,0.1); /* Optional: Adds shadow for depth */
+    align-items: center;    /* Centers buttons vertically */
+    justify-content: start;/* Centers buttons horizontally */
+}
+
+button {
+    width: 35px;
+    height: 35px;
+    border-radius: 50%;
+    background-color: $dark-gray;
+    // border-color: $medium-blue;
+    margin: 5px;    // transform: translateY(-50px); 
+}
+
+// button:hover {
+//     transform: translateY(-50px); 
+// }
+
 // .card-wrapper::after {
 //     content: "";
 //     width: 100%; /* Forces wrapping */
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index bda72b2d6..bf26ad4b5 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -37,7 +37,7 @@ export class CollectionCardView extends CollectionSubView() {
 
     translateHover = (index: number): number => {
         if (this.hoveredNodeIndex == index && !this.isSelected(index)) {
-            return -50 * this.fitContentScale;
+            return -50;
         }
         return 0;
     };
@@ -109,6 +109,10 @@ export class CollectionCardView extends CollectionSubView() {
     isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
     isChildContentActive = () => (this.isContentActive() ? true : false);
 
+
+
+    
+
     rotate = (amCards: number, index: number) => {
         // console.log(amCards + "wtf")
         const possRotate = -30 + index * (30 / ((amCards - (amCards % 2)) / 2));
@@ -163,7 +167,7 @@ export class CollectionCardView extends CollectionSubView() {
     // };
 
     translateSelected = (index: number): number => {
-        if (this.isSelected(index)) {
+        // if (this.isSelected(index)) {
             const middleOfPanel = this._props.PanelWidth() / 2;
             const scaledNodeWidth = this.panelWidth() * 1.25;
 
@@ -173,12 +177,12 @@ export class CollectionCardView extends CollectionSubView() {
             const scaledNodeCenter = nodeLeftEdge + scaledNodeWidth / 2;
 
             // Calculate the translation needed to align the scaled node's center with the panel's center
-            const translation = middleOfPanel - scaledNodeCenter;
+            const translation = middleOfPanel - scaledNodeCenter - scaledNodeWidth - (scaledNodeWidth /4);
 
             return translation;
-        }
+        // }
 
-        return 0;
+        // return 0;
     };
 
     @computed get sortedDocsType() {
@@ -197,15 +201,20 @@ export class CollectionCardView extends CollectionSubView() {
                 return this.sort(sorted, "type", desc)
             case "color":
                 return this.sort(sorted, "color", desc)
-            case "links":
-                console.log("hi")
+            // case "links":
+            //     console.log("hi")
 
-                   let links = LinkManager.Instance.getAllRelatedLinks(this.myChildLayoutPairs[0].layout)
+                //    let links = LinkManager.Instance.getAllRelatedLinks(this.myChildLayoutPairs[0].layout)
 
-                   console.log(links)
+                //    console.log(links)
                    
                    // }
 
+            case "custom":
+                console.log("hiiiii")
+                return this.sort(sorted, "custom", desc)
+            
+
             default:
                 docs =  this.myChildLayoutPairs
                 return {docs}
@@ -254,6 +263,14 @@ export class CollectionCardView extends CollectionSubView() {
                     typeB = this.hexToHsv(StrCast(docB.layout.backgroundColor)) ?? ''; // If docB.type is undefined, use an empty string
                     break
 
+                case "custom":
+                    typeA = this.customGroupDictionary.get(docs.indexOf(docA)) ?? ''
+                    // console.log(typeA + "A")
+                    typeB = this.customGroupDictionary.get(docs.indexOf(docB)) ?? ''
+                    // console.log(typeB + 'b')
+                    break
+
+
                 default:
                     typeA = docA.layout.type ?? ''; // If docA.type is undefined, use an empty string
                     typeB = docB.layout.type ?? ''; // If docB.type is undefined, use an empty string
@@ -341,10 +358,24 @@ export class CollectionCardView extends CollectionSubView() {
         }
 
         return (10 - calcRowCards) * (this.panelWidth()/2)
+    }
 
+   
+    calculateTranslateY(isHovered: boolean, isSelected: boolean, realIndex: number, amCards: number, calcRowIndex: number) {
 
+        let trans = 0;
+        if (isHovered) {
+            trans += this.translateHover(realIndex);
+        }
+        
+        trans += this.translateY(amCards, calcRowIndex, realIndex);
+        
 
+        if (isSelected) {
+            trans = 50 * this.fitContentScale;
+        }
 
+        return trans
     }
 
     @computed get contentSorted() {
@@ -357,6 +388,7 @@ export class CollectionCardView extends CollectionSubView() {
 
 
             const isHovered = this.hoveredNodeIndex === index;
+            const childPairIndex = this.myChildLayoutPairs.indexOf(childPair)
             const realIndex = this.sortedDocsType.docs.filter(d => !SelectionManager.IsSelected(d.layout)).indexOf(childPair);
             const calcRowIndex = this.overflowIndexCalc(realIndex)
 
@@ -379,21 +411,72 @@ export class CollectionCardView extends CollectionSubView() {
                         height: this.panelHeight(childPair.layout)(),
                         transform: `
                             rotate(${!isSelected ? this.rotate(amCards, calcRowIndex) : 0}deg)
-                            translateY(${isHovered ? this.translateHover(realIndex) : isSelected ? 50 * this.fitContentScale : this.translateY(amCards, calcRowIndex, realIndex)}px)
-                            translateX(${isSelected ? (this._props.PanelWidth() / 2) * this.fitContentScale - this.childDocumentWidth : this.translateOverflowX(realIndex, amCards)}px)
+                            translateY(${this.calculateTranslateY(isHovered, isSelected, realIndex, amCards, calcRowIndex)}px)
+                            translateX(${isSelected ? this.translateSelected(calcRowIndex) : this.translateOverflowX(realIndex, amCards)}px)
                             scale(${isSelected ? 1.25 : 1})
                         `,
                     }}
-                    onMouseEnter={() => this.setHoveredNodeIndex(index)}>
+                    onMouseEnter={() => this.setHoveredNodeIndex(index)}
+                    
+                    >
                     {this.displayDoc(childPair, childScreenToLocal)}
 
-                    {/* <circle r="45" cx="50" cy="50" fill="red" /> */}
+                    {this._props.Document.cardSort == 'custom' ? this.renderButtons(childPairIndex) : ''}
 
                 </div>
             );
         });
     }
 
+    @observable amGroups = 0
+    @observable customGroupDictionary = new Map<
+       number, number>();
+
+
+    @action toggleButton(childPairIndex: number, buttonID: number) {
+    
+        this.customGroupDictionary.set(childPairIndex, buttonID);
+    }
+
+    renderButtons (childPairIndex: number) {
+        const buttons = []; // Array to hold the button elements
+
+        let amButtons = 4
+
+        if (this.amGroups > 4){
+            amButtons = this.amGroups
+        }
+
+        let activeButtonIndex = this.customGroupDictionary.get(childPairIndex)
+
+        for (let i = 0; i < amButtons; i++) {
+            const isActive = activeButtonIndex == i
+      
+            buttons.push(
+                <button key={i}
+                    style = {{backgroundColor: `${isActive ?  "#4476f7" : "#323232"}`}}
+                    onClick={() => this.toggleButton(childPairIndex, i)} >
+                </button>); 
+        }
+
+        const totalWidth = amButtons * 35 + (amButtons * 2* 5) + 6;
+        return (
+            <div className="card-button-container" style={{ width: `${totalWidth}px` }}>
+                {buttons}
+            </div>
+        );  
+    }
+
+
+    
+
+
+
+
+
+
+
+
     
 
     // @computed get content() {
@@ -450,7 +533,7 @@ export class CollectionCardView extends CollectionSubView() {
 
         let translate = 0
 
-        if (this.inactiveDocs().length != this.myChildLayoutPairs.length) {
+        if (this.inactiveDocs().length != this.myChildLayoutPairs.length && this.inactiveDocs().length < 10) {
             translate += this.panelWidth() / 2;
         }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 2d05b5490..11193f496 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -2010,4 +2010,4 @@ ScriptingGlobals.add(function datavizFromSchema(doc: Doc) {
             SchemaCSVPopUp.Instance.setVisible(true);
         }
     });
-});
+});
\ No newline at end of file
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 526315802..9056917fe 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -105,10 +105,12 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
     selected ? selected.CollectionFreeFormDocumentView?.float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
 });
 
-ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links', checkResult?: boolean, persist?: boolean) {
+
+
+ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom' | 'create', checkResult?: boolean, persist?: boolean, customNumber?: number) {
     const selected = SelectionManager.Docs.lastElement();
     // prettier-ignore
-    const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([
+    const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom' | 'create', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([
         ['grid', {
             checkResult: (doc:Doc) => BoolCast(doc?._freeform_backgroundGrid, false),
             setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid,
@@ -156,6 +158,21 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid
             setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "links",
         }],
 
+        ['custom', {
+            checkResult: (doc:Doc) => StrCast(doc?.cardSort),
+            setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "custom",
+        }],
+
+        //in an ideal world lol
+
+
+        // ['create', {
+        //     checkResult: (doc:Doc) => NumCast(doc?.customSortCount),
+        //     setDoc: (doc:Doc,dv:DocumentView) => doc.customSortCount = NumCast(doc.customSortCount) + 1,
+        // }],
+
+       
+
    ]);
 
     if (checkResult) {
-- 
cgit v1.2.3-70-g09d2


From f0b41a82df3ce7ea9a29e8a779a1967349f5b92a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 13 May 2024 10:49:06 -0400
Subject: fixed documentDecorations for cardDeck view to not lag behind
 document movement

---
 .../views/collections/CollectionCardDeckView.tsx   | 286 +++++++++------------
 1 file changed, 123 insertions(+), 163 deletions(-)

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

diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index bf26ad4b5..80159ae38 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -16,7 +16,7 @@ import { LinkManager } from '../../util/LinkManager';
 // import Card from 'react-bootstrap/Card';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { forEach } from 'lodash';
-
+import { SnappingManager } from '../../util/SnappingManager';
 
 @observer
 export class CollectionCardView extends CollectionSubView() {
@@ -24,8 +24,8 @@ export class CollectionCardView extends CollectionSubView() {
 
     @observable hoveredNodeIndex = -1;
 
-    @computed get myChildLayoutPairs(){
-        return this.childLayoutPairs.filter(l => l.layout.type != DocumentType.LINK)
+    @computed get myChildLayoutPairs() {
+        return this.childLayoutPairs.filter(l => l.layout.type != DocumentType.LINK);
     }
 
     @action
@@ -64,8 +64,6 @@ export class CollectionCardView extends CollectionSubView() {
         return docs.filter(d => !SelectionManager.IsSelected(d.layout));
     };
 
-
-
     middleIndex = Math.floor(this.inactiveDocs().length / 2);
 
     constructor(props: any) {
@@ -87,19 +85,15 @@ export class CollectionCardView extends CollectionSubView() {
         }
     };
 
-
-
     childDocumentWidth = 600; // target width of a Doc...
     /**
      * how much to scale down the contents of the view so that everything will fit
      */
     @computed get fitContentScale() {
-        if (this.myChildLayoutPairs.length< this.maxRowCount){
-            length = this.myChildLayoutPairs.length
-        }
-
-        else{
-            length = this.maxRowCount
+        if (this.myChildLayoutPairs.length < this.maxRowCount) {
+            length = this.myChildLayoutPairs.length;
+        } else {
+            length = this.maxRowCount;
         }
         return (this.childDocumentWidth * length) / this._props.PanelWidth();
     }
@@ -109,10 +103,6 @@ export class CollectionCardView extends CollectionSubView() {
     isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
     isChildContentActive = () => (this.isContentActive() ? true : false);
 
-
-
-    
-
     rotate = (amCards: number, index: number) => {
         // console.log(amCards + "wtf")
         const possRotate = -30 + index * (30 / ((amCards - (amCards % 2)) / 2));
@@ -136,15 +126,13 @@ export class CollectionCardView extends CollectionSubView() {
         const evenOdd = amCards % 2;
         const apex = (amCards - evenOdd) / 2;
         const stepMag = 200 / ((amCards - evenOdd) / 2) + Math.abs((apex - index) * 25);
-        let rowOffset = 0
+        let rowOffset = 0;
 
-        if (realIndex > this.maxRowCount -1){
+        if (realIndex > this.maxRowCount - 1) {
             // 11 - 1 = 10
-            rowOffset = 400 * ((realIndex - (realIndex % this.maxRowCount)) / this.maxRowCount)
+            rowOffset = 400 * ((realIndex - (realIndex % this.maxRowCount)) / this.maxRowCount);
         }
 
-        
-
         // console.log('steo' + stepMag);
 
         if (evenOdd == 1 || index < apex - 1) {
@@ -168,115 +156,114 @@ export class CollectionCardView extends CollectionSubView() {
 
     translateSelected = (index: number): number => {
         // if (this.isSelected(index)) {
-            const middleOfPanel = this._props.PanelWidth() / 2;
-            const scaledNodeWidth = this.panelWidth() * 1.25;
+        const middleOfPanel = this._props.PanelWidth() / 2;
+        const scaledNodeWidth = this.panelWidth() * 1.25;
 
-            // Calculate the position of the node's left edge before scaling
-            const nodeLeftEdge = index * this.panelWidth();
-            // Find the center of the node after scaling
-            const scaledNodeCenter = nodeLeftEdge + scaledNodeWidth / 2;
+        // Calculate the position of the node's left edge before scaling
+        const nodeLeftEdge = index * this.panelWidth();
+        // Find the center of the node after scaling
+        const scaledNodeCenter = nodeLeftEdge + scaledNodeWidth / 2;
 
-            // Calculate the translation needed to align the scaled node's center with the panel's center
-            const translation = middleOfPanel - scaledNodeCenter - scaledNodeWidth - (scaledNodeWidth /4);
+        // Calculate the translation needed to align the scaled node's center with the panel's center
+        const translation = middleOfPanel - scaledNodeCenter - scaledNodeWidth - scaledNodeWidth / 4;
 
-            return translation;
+        return translation;
         // }
 
         // return 0;
     };
 
     @computed get sortedDocsType() {
-        
         const desc = BoolCast(this.layoutDoc.sortDesc);
-        let sorted = []
-        let docs = []
+        let sorted = [];
+        let docs = [];
 
-        for (let i=0; i< this.myChildLayoutPairs.length; i++){ //copying everything in childlayout pairs to sorted so that i can use the sort function without altering the original list
-            sorted[i] = this.myChildLayoutPairs[i]
+        for (let i = 0; i < this.myChildLayoutPairs.length; i++) {
+            //copying everything in childlayout pairs to sorted so that i can use the sort function without altering the original list
+            sorted[i] = this.myChildLayoutPairs[i];
         }
 
-        switch(this._props.Document.cardSort){
-            case "type":
-                 // Copy and sort documents by type 
-                return this.sort(sorted, "type", desc)
-            case "color":
-                return this.sort(sorted, "color", desc)
+        switch (this._props.Document.cardSort) {
+            case 'type':
+                // Copy and sort documents by type
+                return this.sort(sorted, 'type', desc);
+            case 'color':
+                return this.sort(sorted, 'color', desc);
             // case "links":
             //     console.log("hi")
 
-                //    let links = LinkManager.Instance.getAllRelatedLinks(this.myChildLayoutPairs[0].layout)
+            //    let links = LinkManager.Instance.getAllRelatedLinks(this.myChildLayoutPairs[0].layout)
 
-                //    console.log(links)
-                   
-                   // }
+            //    console.log(links)
 
-            case "custom":
-                console.log("hiiiii")
-                return this.sort(sorted, "custom", desc)
-            
+            // }
 
-            default:
-                docs =  this.myChildLayoutPairs
-                return {docs}
+            case 'custom':
+                console.log('hiiiii');
+                return this.sort(sorted, 'custom', desc);
 
-
-                
+            default:
+                docs = this.myChildLayoutPairs;
+                return { docs };
         }
     }
 
-    
-    
-
     hexToHsv = (hex: string): [number, number, number] => {
         if (!hex) return [0, 0, 0]; // Default to black if hex is not defined
         const r = parseInt(hex.slice(1, 3), 16) / 255;
         const g = parseInt(hex.slice(3, 5), 16) / 255;
         const b = parseInt(hex.slice(5, 7), 16) / 255;
-        const max = Math.max(r, g, b), min = Math.min(r, g, b);
+        const max = Math.max(r, g, b),
+            min = Math.min(r, g, b);
         const d = max - min;
         let h: number;
-        const s = (max === 0 ? 0 : d / max);
+        const s = max === 0 ? 0 : d / max;
         const v = max;
 
         switch (max) {
-            case min: h = 0; break;
-            case r: h = (g - b) / d + (g < b ? 6 : 0); break;
-            case g: h = (b - r) / d + 2; break;
-            case b: h = (r - g) / d + 4; break;
-            default: h = 0; break;
+            case min:
+                h = 0;
+                break;
+            case r:
+                h = (g - b) / d + (g < b ? 6 : 0);
+                break;
+            case g:
+                h = (b - r) / d + 2;
+                break;
+            case b:
+                h = (r - g) / d + 4;
+                break;
+            default:
+                h = 0;
+                break;
         }
         h /= 6;
         return [h, s, v];
     };
 
-    
-
-    sort = (docs: {layout: Doc;  data: Doc; }[], sortType: string, isDesc: boolean) => {
-
+    sort = (docs: { layout: Doc; data: Doc }[], sortType: string, isDesc: boolean) => {
         docs.sort((docA, docB) => {
             let typeA;
-            let typeB             
+            let typeB;
 
-            switch(sortType) {
-                case "color":
+            switch (sortType) {
+                case 'color':
                     typeA = this.hexToHsv(StrCast(docA.layout.backgroundColor)) ?? ''; // If docA.type is undefined, use an empty string
                     typeB = this.hexToHsv(StrCast(docB.layout.backgroundColor)) ?? ''; // If docB.type is undefined, use an empty string
-                    break
+                    break;
 
-                case "custom":
-                    typeA = this.customGroupDictionary.get(docs.indexOf(docA)) ?? ''
+                case 'custom':
+                    typeA = this.customGroupDictionary.get(docs.indexOf(docA)) ?? '';
                     // console.log(typeA + "A")
-                    typeB = this.customGroupDictionary.get(docs.indexOf(docB)) ?? ''
+                    typeB = this.customGroupDictionary.get(docs.indexOf(docB)) ?? '';
                     // console.log(typeB + 'b')
-                    break
-
+                    break;
 
                 default:
                     typeA = docA.layout.type ?? ''; // If docA.type is undefined, use an empty string
                     typeB = docB.layout.type ?? ''; // If docB.type is undefined, use an empty string
-                    break
+                    break;
             }
-            
 
             // Perform a basic string comparison if types are strings
             let out = 0;
@@ -284,19 +271,15 @@ export class CollectionCardView extends CollectionSubView() {
             if (typeA > typeB) out = 1;
             if (isDesc) out *= -1; // Reverse the sort order if descending is true
             return out;
-            });
-
-        return {docs}
-    }
+        });
 
-    
+        return { docs };
+    };
 
     // @computed get cards(){
 
-
     // }
 
-    
     displayDoc = (childPair: { layout: Doc; data: Doc }, screenToLocalTransform: () => Transform) => {
         return (
             <DocumentView
@@ -322,81 +305,76 @@ export class CollectionCardView extends CollectionSubView() {
             />
         );
     };
-   
 
     @observable docRefs = new ObservableMap<Doc, DocumentView>();
 
     @observable maxRowCount = 10;
 
     overflowAmCardsCalc(index: number) {
-        if (this.inactiveDocs().length < this.maxRowCount){
-            return this.inactiveDocs().length
+        if (this.inactiveDocs().length < this.maxRowCount) {
+            return this.inactiveDocs().length;
         }
-        // 13 - 3 = 10 
-        const totalCards = this.inactiveDocs().length
+        // 13 - 3 = 10
+        const totalCards = this.inactiveDocs().length;
         // if 9 or less
-        if ( index < ((totalCards - (totalCards % 10)))){
-            return this.maxRowCount
+        if (index < totalCards - (totalCards % 10)) {
+            return this.maxRowCount;
         }
         //(3)
-        return totalCards % 10
-
+        return totalCards % 10;
     }
 
     overflowIndexCalc(realIndex: number) {
-        if (realIndex < 10){
-            return realIndex
+        if (realIndex < 10) {
+            return realIndex;
         }
 
-        return realIndex % 10
-
+        return realIndex % 10;
     }
 
-    translateOverflowX(realIndex: number, calcRowCards: number){
-        if (realIndex < this.maxRowCount){
-            return 0
+    translateOverflowX(realIndex: number, calcRowCards: number) {
+        if (realIndex < this.maxRowCount) {
+            return 0;
         }
 
-        return (10 - calcRowCards) * (this.panelWidth()/2)
+        return (10 - calcRowCards) * (this.panelWidth() / 2);
     }
 
-   
     calculateTranslateY(isHovered: boolean, isSelected: boolean, realIndex: number, amCards: number, calcRowIndex: number) {
-
         let trans = 0;
         if (isHovered) {
             trans += this.translateHover(realIndex);
         }
-        
+
         trans += this.translateY(amCards, calcRowIndex, realIndex);
-        
 
         if (isSelected) {
             trans = 50 * this.fitContentScale;
         }
 
-        return trans
+        return trans;
     }
 
+    @observable _forceChildXf = false;
     @computed get contentSorted() {
-        const sortedDocs = this.sortedDocsType.docs; 
-        // const amCards = this.inactiveDocs().length; 
+        const sortedDocs = this.sortedDocsType.docs;
+        // const amCards = this.inactiveDocs().length;
 
         // Map sorted documents to their rendered components
         return sortedDocs.map((childPair, index) => {
-            // const childPair = { layout: doc, data: doc }; 
-
+            // const childPair = { layout: doc, data: doc };
 
             const isHovered = this.hoveredNodeIndex === index;
-            const childPairIndex = this.myChildLayoutPairs.indexOf(childPair)
+            const childPairIndex = this.myChildLayoutPairs.indexOf(childPair);
             const realIndex = this.sortedDocsType.docs.filter(d => !SelectionManager.IsSelected(d.layout)).indexOf(childPair);
-            const calcRowIndex = this.overflowIndexCalc(realIndex)
+            const calcRowIndex = this.overflowIndexCalc(realIndex);
 
-            const amCards = this.overflowAmCardsCalc(realIndex)
+            const amCards = this.overflowAmCardsCalc(realIndex);
 
             const isSelected = SelectionManager.IsSelected(childPair.layout);
 
             const childScreenToLocal = () => {
+                this._forceChildXf;
                 const dref = this.docRefs.get(childPair.layout);
                 const { translateX, translateY, scale } = Utils.GetScreenTransform(dref?.ContentDiv);
                 return new Transform(-translateX + (dref?.centeringX || 0) * scale, -translateY + (dref?.centeringY || 0) * scale, 1).scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, calcRowIndex) : 0);
@@ -406,6 +384,16 @@ export class CollectionCardView extends CollectionSubView() {
                 <div
                     key={childPair.layout[Id]}
                     className={`card-item${isSelected ? '-active' : ''}`}
+                    onPointerUp={e => {
+                        SnappingManager.SetIsResizing(this.Document);
+                        setTimeout(
+                            action(() => {
+                                SnappingManager.SetIsResizing(undefined);
+                                this._forceChildXf = !this._forceChildXf;
+                            }),
+                            400
+                        );
+                    }}
                     style={{
                         width: this.panelWidth(),
                         height: this.panelHeight(childPair.layout)(),
@@ -416,69 +404,47 @@ export class CollectionCardView extends CollectionSubView() {
                             scale(${isSelected ? 1.25 : 1})
                         `,
                     }}
-                    onMouseEnter={() => this.setHoveredNodeIndex(index)}
-                    
-                    >
+                    onMouseEnter={() => this.setHoveredNodeIndex(index)}>
                     {this.displayDoc(childPair, childScreenToLocal)}
 
                     {this._props.Document.cardSort == 'custom' ? this.renderButtons(childPairIndex) : ''}
-
                 </div>
             );
         });
     }
 
-    @observable amGroups = 0
-    @observable customGroupDictionary = new Map<
-       number, number>();
-
+    @observable amGroups = 0;
+    @observable customGroupDictionary = new Map<number, number>();
 
     @action toggleButton(childPairIndex: number, buttonID: number) {
-    
         this.customGroupDictionary.set(childPairIndex, buttonID);
     }
 
-    renderButtons (childPairIndex: number) {
+    renderButtons(childPairIndex: number) {
         const buttons = []; // Array to hold the button elements
 
-        let amButtons = 4
+        let amButtons = 4;
 
-        if (this.amGroups > 4){
-            amButtons = this.amGroups
+        if (this.amGroups > 4) {
+            amButtons = this.amGroups;
         }
 
-        let activeButtonIndex = this.customGroupDictionary.get(childPairIndex)
+        let activeButtonIndex = this.customGroupDictionary.get(childPairIndex);
 
         for (let i = 0; i < amButtons; i++) {
-            const isActive = activeButtonIndex == i
-      
-            buttons.push(
-                <button key={i}
-                    style = {{backgroundColor: `${isActive ?  "#4476f7" : "#323232"}`}}
-                    onClick={() => this.toggleButton(childPairIndex, i)} >
-                </button>); 
+            const isActive = activeButtonIndex == i;
+
+            buttons.push(<button key={i} style={{ backgroundColor: `${isActive ? '#4476f7' : '#323232'}` }} onClick={() => this.toggleButton(childPairIndex, i)}></button>);
         }
 
-        const totalWidth = amButtons * 35 + (amButtons * 2* 5) + 6;
+        const totalWidth = amButtons * 35 + amButtons * 2 * 5 + 6;
         return (
             <div className="card-button-container" style={{ width: `${totalWidth}px` }}>
                 {buttons}
             </div>
-        );  
+        );
     }
 
-
-    
-
-
-
-
-
-
-
-
-    
-
     // @computed get content() {
     //     // const currentIndex = NumCast(this.layoutDoc._carousel_index);
     //     const amCards = this.inactiveDocs().length;
@@ -486,7 +452,6 @@ export class CollectionCardView extends CollectionSubView() {
     //     // const sortedDocs = this.sortedDocsType.docs; // Retrieve sorted documents
 
     //     // const myInactives =
-        
 
     //     return this.myChildLayoutPairs.map((childPair, index) => {
     //         const isSelected = this.isSelected(index);
@@ -511,11 +476,11 @@ export class CollectionCardView extends CollectionSubView() {
     //                     width: this.panelWidth(),
     //                     height: this.panelHeight(childPair.layout)(),
     //                     transform: `
-    //                         rotate(${!isSelected ? this.rotate(amCards, inactiveIndex) : 0}deg) 
+    //                         rotate(${!isSelected ? this.rotate(amCards, inactiveIndex) : 0}deg)
     //                         translateY(${isHovered ? this.translateHover(index) : isSelected ? 50 * this.fitContentScale : this.translateY(amCards, inactiveIndex)}px)
     //                         translateX(${isSelected ? (this._props.PanelWidth() / 2) * this.fitContentScale - this.childDocumentWidth : 0}px)
     //                         scale(${isSelected ? 1.25 : 1})
-                            
+
     //                         `, //scale has to be applied last or selected offset gets messed up
     //                 }}
     //                 // onClick={() => this.setSelectedNodeIndex(index)}
@@ -530,23 +495,20 @@ export class CollectionCardView extends CollectionSubView() {
     // }
 
     @computed get translateWrapperX() {
-
-        let translate = 0
+        let translate = 0;
 
         if (this.inactiveDocs().length != this.myChildLayoutPairs.length && this.inactiveDocs().length < 10) {
             translate += this.panelWidth() / 2;
         }
 
-        console.log("in" + this.inactiveDocs().length)
-        console.log("max" + this.maxRowCount)
+        console.log('in' + this.inactiveDocs().length);
+        console.log('max' + this.maxRowCount);
 
         // if (this.inactiveDocs().length > this.maxRowCount){
         //     translate += ((this.inactiveDocs().length % this.maxRowCount) * this.panelWidth()) / 2
         //     console.log("trans" + (this.inactiveDocs().length % this.maxRowCount) / 2 )
         // }
 
-
-
         return translate;
     }
 
@@ -560,8 +522,6 @@ export class CollectionCardView extends CollectionSubView() {
     //     }
     // }
 
-    
-
     render() {
         return (
             <div
@@ -598,4 +558,4 @@ export class CollectionCardView extends CollectionSubView() {
             </div>
         );
     }
-}
\ No newline at end of file
+}
-- 
cgit v1.2.3-70-g09d2


From d699cac7cab64e868aeee8d88b16e7a76e92e483 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Mon, 13 May 2024 16:42:25 -0400
Subject: god is good

---
 src/client/views/collections/CollectionCardDeckView.scss |  1 +
 src/client/views/collections/CollectionCardDeckView.tsx  | 16 +++++++++++-----
 2 files changed, 12 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss
index 222a1d226..09d6f70ea 100644
--- a/src/client/views/collections/CollectionCardDeckView.scss
+++ b/src/client/views/collections/CollectionCardDeckView.scss
@@ -67,6 +67,7 @@ button {
 
 .card-item-active,
 .card-item {
+    position: relative;
     transition: transform 0.3s ease-in-out;
 }
 
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 80159ae38..996c5711b 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -332,7 +332,13 @@ export class CollectionCardView extends CollectionSubView() {
         return realIndex % 10;
     }
 
-    translateOverflowX(realIndex: number, calcRowCards: number) {
+    translateOverflowX(realIndex: number, calcIndex: number, calcRowCards: number) {
+
+        let trans = 0;
+
+        // trans += (calcIndex * this.panelWidth()) + this.panelWidth()
+
+        // return trans
         if (realIndex < this.maxRowCount) {
             return 0;
         }
@@ -398,10 +404,10 @@ export class CollectionCardView extends CollectionSubView() {
                         width: this.panelWidth(),
                         height: this.panelHeight(childPair.layout)(),
                         transform: `
-                            rotate(${!isSelected ? this.rotate(amCards, calcRowIndex) : 0}deg)
-                            translateY(${this.calculateTranslateY(isHovered, isSelected, realIndex, amCards, calcRowIndex)}px)
-                            translateX(${isSelected ? this.translateSelected(calcRowIndex) : this.translateOverflowX(realIndex, amCards)}px)
-                            scale(${isSelected ? 1.25 : 1})
+                        translateY(${this.calculateTranslateY(isHovered, isSelected, realIndex, amCards, calcRowIndex)}px)
+                        translateX(${isSelected ? this.translateSelected(calcRowIndex) : this.translateOverflowX(realIndex, calcRowIndex, amCards)}px)
+                        rotate(${!isSelected ? this.rotate(amCards, calcRowIndex) : 0}deg)
+                        scale(${isSelected ? 1.25 : 1})
                         `,
                     }}
                     onMouseEnter={() => this.setHoveredNodeIndex(index)}>
-- 
cgit v1.2.3-70-g09d2


From 3c5b2353cb20843e968e51fdff58cc92f101ed51 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Tue, 14 May 2024 02:14:58 -0400
Subject: getting close to done

---
 src/client/documents/Documents.ts                  |  3 +-
 src/client/util/CurrentUserUtils.ts                |  6 ++-
 src/client/views/MainView.tsx                      |  4 +-
 .../views/collections/CollectionCardDeckView.scss  |  2 +-
 .../views/collections/CollectionCardDeckView.tsx   | 63 +++++++++++++++++-----
 .../CollectionFreeFormLayoutEngines.tsx            | 26 ++++-----
 .../collections/collectionFreeForm/MarqueeView.tsx |  4 +-
 src/client/views/global/globalScripts.ts           | 35 +++++++-----
 8 files changed, 94 insertions(+), 49 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index df69e215a..57f91399a 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -500,7 +500,8 @@ export class DocumentOptions {
     userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)');
 
     cardSort?: STRt = new StrInfo('way cards are sorted in deck view');
-    customSortCount?: NUMt = new NumInfo('number of custom sorts the user has created');
+    customSortNumber?: NUMt = new NumInfo('number of custom sorts the user has created');
+    customHashMap?: List<string>
     // card_sort_time?: BOOLt = new BoolInfo('whether sorting cards in deck view by time');
     // card_sort_type?: BOOLt = new BoolInfo('whether sorting cards in deck view by type');
     // card_sort_color?: BOOLt = new BoolInfo('whether sorting cards in deck view by color');
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 59560dd78..abc728cd3 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -730,7 +730,11 @@ pie title Minerals in my tap water
             { title: "Type",   icon:"eye", toolTip:"Sort by document type",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"docType",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Color",   icon:"palette", toolTip:"Sort by document color",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"color",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Links",   icon:"link", toolTip:"Sort by its links",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"links",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Create",   icon:"plus", toolTip:"Create new custom groupings!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Create",   icon:"robot", toolTip:"Create your first custom grouping!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom1",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Create",   icon:"star", toolTip:"Create your second custom grouping!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom2",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Create",   icon:"satellite", toolTip:"Create your third custom grouping!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom3",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+
+
             // ...customs
 
         ]
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index a5882dcf3..f1296e46a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -527,7 +527,9 @@ export class MainView extends ObservableReactComponent<{}> {
                 fa.faArrowsDownToLine,
                 fa.faPalette,
                 fa.faHourglassHalf,
-                fa.faRobot
+                fa.faRobot,
+                fa.faSatellite,
+                fa.faStar
             ]
         );
     }
diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss
index 09d6f70ea..babc604b5 100644
--- a/src/client/views/collections/CollectionCardDeckView.scss
+++ b/src/client/views/collections/CollectionCardDeckView.scss
@@ -68,7 +68,7 @@ button {
 .card-item-active,
 .card-item {
     position: relative;
-    transition: transform 0.3s ease-in-out;
+    transition: transform 0.5s ease-in-out;
 }
 
 .card-item-active {
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 996c5711b..fc183b72b 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -2,7 +2,7 @@ import { ObservableMap, action, computed, makeObservable, observable } from 'mob
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { Utils, returnFalse, returnTrue, returnZero } from '../../../Utils';
-import { Doc, DocListCast, Field } from '../../../fields/Doc';
+import { Doc, DocListCast, Field, StrListCast } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { NumCast, ScriptCast, StrCast, BoolCast, DocCast } from '../../../fields/Types';
 import { DragManager } from '../../util/DragManager';
@@ -17,6 +17,7 @@ import { LinkManager } from '../../util/LinkManager';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { forEach } from 'lodash';
 import { SnappingManager } from '../../util/SnappingManager';
+import { List } from '../../../fields/List';
 
 @observer
 export class CollectionCardView extends CollectionSubView() {
@@ -70,11 +71,49 @@ export class CollectionCardView extends CollectionSubView() {
         super(props);
         makeObservable(this);
         // this.rotationDegree(7);
+
+        if (this._props.Document.customHashMap != undefined){
+            this.customGroupDictionary = this.getCustoms(StrListCast(this._props.Document.customHashMap))
+        }
+    }
+
+    @observable customGroupDictionary: Map<number, number>[] = [new Map<number, number>(), new Map<number, number>(), new Map<number, number>()];
+
+    @computed get mapToField(): List<string> {
+        const resultList = new List<string>(); // Creating a new ListImpl instance for strings
+
+        this.customGroupDictionary.forEach(map => {
+            // Convert each map to a single string with entries formatted as "key:value"
+            const mapString = Array.from(map.entries())
+                                   .map(([key, value]) => `${key}:${value}`)
+                                   .join(','); // Join all key-value pairs with commas
+            resultList.push(mapString); // Add the formatted string of the current map to the list
+        });
+
+        return resultList;
+    }
+
+    getCustoms = (stringFrom: string[]): Map<number, number>[] => {
+        return stringFrom.map(s => {
+            // Create a new Map object for each string.
+            const map = new Map<number, number>();
+            // Split the string by commas to get key-value pairs, then process each pair.
+            s.split(',').forEach(pair => {
+                const [key, value] = pair.split(':');
+                map.set(Number(key), Number(value));
+            });
+            return map;
+        });
     }
 
+
+
+
+
     private _dropDisposer?: DragManager.DragDropDisposer;
 
     componentWillUnmount() {
+        console.log("hey hey hey")
         this._dropDisposer?.();
     }
 
@@ -253,9 +292,11 @@ export class CollectionCardView extends CollectionSubView() {
                     break;
 
                 case 'custom':
-                    typeA = this.customGroupDictionary.get(docs.indexOf(docA)) ?? '';
+
+                console.log(this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)])
+                    typeA = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(docs.indexOf(docA)) ?? '9999';
                     // console.log(typeA + "A")
-                    typeB = this.customGroupDictionary.get(docs.indexOf(docB)) ?? '';
+                    typeB = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(docs.indexOf(docB)) ?? '9999';
                     // console.log(typeB + 'b')
                     break;
 
@@ -397,7 +438,7 @@ export class CollectionCardView extends CollectionSubView() {
                                 SnappingManager.SetIsResizing(undefined);
                                 this._forceChildXf = !this._forceChildXf;
                             }),
-                            400
+                            700
                         );
                     }}
                     style={{
@@ -419,11 +460,13 @@ export class CollectionCardView extends CollectionSubView() {
         });
     }
 
-    @observable amGroups = 0;
-    @observable customGroupDictionary = new Map<number, number>();
+   
+    
+    
 
     @action toggleButton(childPairIndex: number, buttonID: number) {
-        this.customGroupDictionary.set(childPairIndex, buttonID);
+        this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].set(childPairIndex, buttonID);
+        this._props.Document.customHashMap = this.mapToField
     }
 
     renderButtons(childPairIndex: number) {
@@ -431,11 +474,7 @@ export class CollectionCardView extends CollectionSubView() {
 
         let amButtons = 4;
 
-        if (this.amGroups > 4) {
-            amButtons = this.amGroups;
-        }
-
-        let activeButtonIndex = this.customGroupDictionary.get(childPairIndex);
+        let activeButtonIndex = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(childPairIndex);
 
         for (let i = 0; i < amButtons; i++) {
             const isActive = activeButtonIndex == i;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index becad63f6..22005eb23 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -126,27 +126,20 @@ export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc
 }
 
 // export function computeCardDeckLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
-//     console.log('hi');
 //     const docMap = new Map<string, PoolData>();
-//     const spreadWidth = Math.min(panelDim[0], childPairs.length * 50); // Total width of the spread
-//     const startX = -(spreadWidth / 2); // Starting X position
-//     const fanAngle = 5; // Angle in degrees for fanning out cards
-//     const baseZIndex = 1000; // Base Z-index to ensure cards are stacked in order
+//     const burstDiam = [NumCast(pivotDoc._width), NumCast(pivotDoc._height)];
+//     const burstScale = NumCast(pivotDoc._starburstDocScale, 1);
 
 //     childPairs.forEach(({ layout, data }, i) => {
 //         const aspect = NumCast(layout._height) / NumCast(layout._width);
-//         const docSize = Math.min(400, NumCast(layout._width)) * NumCast(pivotDoc._starburstDocScale, 1);
-//         const posX = startX + (spreadWidth / childPairs.length) * i;
-//         const posY = 0; // Adjust if you want to change the vertical alignment
-//         const rotation = (i - (childPairs.length / 2)) * fanAngle; // Calculate rotation for fanning effect
-
+//         const docSize = Math.min(Math.min(400, NumCast(layout._width)), Math.min(400, NumCast(layout._width)) / aspect) * burstScale;
+//         const deg = (i / childPairs.length) * Math.PI * 2;
 //         docMap.set(layout[Id], {
-//             x: posX,
-//             y: posY,
+//             x: Math.min(burstDiam[0] / 2 - docSize, Math.max(-burstDiam[0] / 2, (Math.cos(deg) * burstDiam[0]) / 2 - docSize / 2)),
+//             y: Math.min(burstDiam[1] / 2 - docSize * aspect, Math.max(-burstDiam[1] / 2, (Math.sin(deg) * burstDiam[1]) / 2 - (docSize / 2) * aspect)),
 //             width: docSize,
 //             height: docSize * aspect,
-//             zIndex: baseZIndex + i,
-//             rotation: rotation,
+//             zIndex: NumCast(layout.zIndex),
 //             pair: { layout, data },
 //             replica: '',
 //             color: 'white',
@@ -154,9 +147,8 @@ export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc
 //             transition: 'all 0.3s',
 //         });
 //     });
-
-//     const divider = { type: 'div', color: 'transparent', x: -panelDim[0] / 2, y: -panelDim[1] / 2, width: 15, height: 15, payload: undefined };
-//     return normalizeResults(panelDim, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]);
+//     const divider = { type: 'div', color: 'transparent', x: -burstDiam[0] / 2, y: -burstDiam[1] / 2, width: 15, height: 15, payload: undefined };
+//     return normalizeResults(burstDiam, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]);
 // }
 
 export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 98684ae98..7e7a5c2c6 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -533,7 +533,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
             this.delete(e, e.key === 'h');
             e.stopPropagation();
         }
-        if ('ctsSpga'.indexOf(e.key) !== -1) {
+        if ('ctsSpgac'.indexOf(e.key) !== -1) {
 
             this._commandExecuted = true;
             e.stopPropagation();
@@ -543,7 +543,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
             if (e.key === 'c' || e.key === 't') this.collection(e);
             if (e.key === 's' || e.key === 'S') this.summary(e);
             if (e.key === 'p') this.pileup(e)
-            // if (e.key === 'a') this.spreadCards(e);
+            // if (e.key === 'c') this.spreadCards(e);
 
             this.cleanupInteractions(false);
         }
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 9056917fe..30602ea0b 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -107,10 +107,10 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
 
 
 
-ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom' | 'create', checkResult?: boolean, persist?: boolean, customNumber?: number) {
+ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom1' | 'custom2' | 'custom3', checkResult?: boolean, persist?: boolean, customNumber?: number) {
     const selected = SelectionManager.Docs.lastElement();
     // prettier-ignore
-    const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom' | 'create', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([
+    const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom1' | 'custom2' | 'custom3', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([
         ['grid', {
             checkResult: (doc:Doc) => BoolCast(doc?._freeform_backgroundGrid, false),
             setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid,
@@ -158,21 +158,28 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid
             setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "links",
         }],
 
-        ['custom', {
+        ['custom1', {
             checkResult: (doc:Doc) => StrCast(doc?.cardSort),
-            setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "custom",
-        }],
-
-        //in an ideal world lol
-
-
-        // ['create', {
-        //     checkResult: (doc:Doc) => NumCast(doc?.customSortCount),
-        //     setDoc: (doc:Doc,dv:DocumentView) => doc.customSortCount = NumCast(doc.customSortCount) + 1,
-        // }],
+            setDoc: (doc, dv) => {
+                doc.cardSort = "custom";
+                doc.customSortNumber = 0;
+            }
+        }],        
 
-       
+        ['custom2', {
+            checkResult: (doc:Doc) => StrCast(doc?.cardSort),
+            setDoc: (doc, dv) => {
+                doc.cardSort = "custom";
+                doc.customSortNumber = 1;
+                console.log(doc.customSortNumber + " numberrrrrrrr")
+            }        }],
 
+        ['custom3', {
+            checkResult: (doc:Doc) => StrCast(doc?.cardSort),
+            setDoc: (doc, dv) => {
+                doc.cardSort = "custom";
+                doc.customSortNumber = 2;
+            }        }],
    ]);
 
     if (checkResult) {
-- 
cgit v1.2.3-70-g09d2


From 938a9d94823064fa3487a8f8f378cdfb7d8db437 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Tue, 14 May 2024 11:01:36 -0400
Subject: me vs the gpt API who will win

---
 .../views/collections/CollectionCardDeckView.tsx   | 87 +++++-----------------
 1 file changed, 18 insertions(+), 69 deletions(-)

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

diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index fc183b72b..7af6eb614 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
 import { Utils, returnFalse, returnTrue, returnZero } from '../../../Utils';
 import { Doc, DocListCast, Field, StrListCast } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
-import { NumCast, ScriptCast, StrCast, BoolCast, DocCast } from '../../../fields/Types';
+import { NumCast, ScriptCast, StrCast, BoolCast, DocCast, RTFCast } from '../../../fields/Types';
 import { DragManager } from '../../util/DragManager';
 import { SelectionManager } from '../../util/SelectionManager';
 import { StyleProp } from '../StyleProvider';
@@ -52,6 +52,9 @@ export class CollectionCardView extends CollectionSubView() {
             // console.log('good mornings');
             this.setSelectedNodeIndex(index);
         }
+
+        
+
     };
 
     isSelected = (index: number): boolean => {
@@ -62,7 +65,11 @@ export class CollectionCardView extends CollectionSubView() {
 
     inactiveDocs = () => {
         const docs = this.myChildLayoutPairs;
+
+       
         return docs.filter(d => !SelectionManager.IsSelected(d.layout));
+
+        
     };
 
     middleIndex = Math.floor(this.inactiveDocs().length / 2);
@@ -80,7 +87,7 @@ export class CollectionCardView extends CollectionSubView() {
     @observable customGroupDictionary: Map<number, number>[] = [new Map<number, number>(), new Map<number, number>(), new Map<number, number>()];
 
     @computed get mapToField(): List<string> {
-        const resultList = new List<string>(); // Creating a new ListImpl instance for strings
+        const resultList = new List<string>(); 
 
         this.customGroupDictionary.forEach(map => {
             // Convert each map to a single string with entries formatted as "key:value"
@@ -110,10 +117,11 @@ export class CollectionCardView extends CollectionSubView() {
 
 
 
+
+
     private _dropDisposer?: DragManager.DragDropDisposer;
 
     componentWillUnmount() {
-        console.log("hey hey hey")
         this._dropDisposer?.();
     }
 
@@ -238,7 +246,7 @@ export class CollectionCardView extends CollectionSubView() {
             // }
 
             case 'custom':
-                console.log('hiiiii');
+                // console.log('hiiiii');
                 return this.sort(sorted, 'custom', desc);
 
             default:
@@ -293,7 +301,7 @@ export class CollectionCardView extends CollectionSubView() {
 
                 case 'custom':
 
-                console.log(this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)])
+                // console.log(this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)])
                     typeA = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(docs.indexOf(docA)) ?? '9999';
                     // console.log(typeA + "A")
                     typeB = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(docs.indexOf(docB)) ?? '9999';
@@ -467,6 +475,9 @@ export class CollectionCardView extends CollectionSubView() {
     @action toggleButton(childPairIndex: number, buttonID: number) {
         this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].set(childPairIndex, buttonID);
         this._props.Document.customHashMap = this.mapToField
+
+        // const queryText = RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']));
+        // console.log(queryText)
     }
 
     renderButtons(childPairIndex: number) {
@@ -490,54 +501,6 @@ export class CollectionCardView extends CollectionSubView() {
         );
     }
 
-    // @computed get content() {
-    //     // const currentIndex = NumCast(this.layoutDoc._carousel_index);
-    //     const amCards = this.inactiveDocs().length;
-    //     console.log(amCards + "lol")
-    //     // const sortedDocs = this.sortedDocsType.docs; // Retrieve sorted documents
-
-    //     // const myInactives =
-
-    //     return this.myChildLayoutPairs.map((childPair, index) => {
-    //         const isSelected = this.isSelected(index);
-    //         const isHovered = this.hoveredNodeIndex === index;
-    //         const isOverflow = amCards > 8 && index > amCards / 2;
-    //         // const  inactiveIndex = this.sortedDocsType.docs.indexOf(childPair);
-    //         const inactiveIndex = this.inactiveDocs().indexOf(childPair.layout);
-    //         // const yOffset = this.verticalOffset(index);
-
-    //         const childScreenToLocal = () => {
-    //             const dref = this.docRefs.get(childPair.layout);
-    //             const { translateX, translateY, scale } = Utils.GetScreenTransform(dref?.ContentDiv);
-    //             // the document view may center its contents and if so, will prepend that onto the screenToLocalTansform.  so we have to subtract that off
-    //             return new Transform(-translateX + (dref?.centeringX || 0) * scale, -translateY + (dref?.centeringY || 0) * scale, 1).scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, inactiveIndex) : 0);
-    //         };
-
-    //         return (
-    //             <div
-    //                 key={childPair.layout[Id]}
-    //                 className={`card-item${isSelected ? '-active' : ''}`}
-    //                 style={{
-    //                     width: this.panelWidth(),
-    //                     height: this.panelHeight(childPair.layout)(),
-    //                     transform: `
-    //                         rotate(${!isSelected ? this.rotate(amCards, inactiveIndex) : 0}deg)
-    //                         translateY(${isHovered ? this.translateHover(index) : isSelected ? 50 * this.fitContentScale : this.translateY(amCards, inactiveIndex)}px)
-    //                         translateX(${isSelected ? (this._props.PanelWidth() / 2) * this.fitContentScale - this.childDocumentWidth : 0}px)
-    //                         scale(${isSelected ? 1.25 : 1})
-
-    //                         `, //scale has to be applied last or selected offset gets messed up
-    //                 }}
-    //                 // onClick={() => this.setSelectedNodeIndex(index)}
-
-    //                 onMouseEnter={() => this.setHoveredNodeIndex(index)}>
-    //                 {/* {this.lol(childPair.data, index)} */}
-
-    //                 {this.displayDoc(childPair, childScreenToLocal)}
-    //             </div>
-    //         );
-    //     });
-    // }
 
     @computed get translateWrapperX() {
         let translate = 0;
@@ -546,8 +509,8 @@ export class CollectionCardView extends CollectionSubView() {
             translate += this.panelWidth() / 2;
         }
 
-        console.log('in' + this.inactiveDocs().length);
-        console.log('max' + this.maxRowCount);
+        // console.log('in' + this.inactiveDocs().length);
+        // console.log('max' + this.maxRowCount);
 
         // if (this.inactiveDocs().length > this.maxRowCount){
         //     translate += ((this.inactiveDocs().length % this.maxRowCount) * this.panelWidth()) / 2
@@ -586,20 +549,6 @@ export class CollectionCardView extends CollectionSubView() {
                     onMouseLeave={() => this.setHoveredNodeIndex(-1)}>
                     {this.contentSorted}
                 </div>
-
-                {/* <Card  className={`custom-modal-position step-${step}`}>
-                <Card.Header>
-                    {header}
-                    <Button variant="close" className= "tut-close"onClick={store.toggleTutorial} style={{ position: 'absolute', top: 0, right: 0 }} />
-                </Card.Header>
-                <Card.Body>{body}
-                <Button className= 'next-step' variant="primary" onClick={store.nextStep}>Next Step!</Button>
-                </Card.Body>
-
-
-            </Card> */}
-
-                {/* {this.focusContent} */}
             </div>
         );
     }
-- 
cgit v1.2.3-70-g09d2


From df7532553db3df270fcbd25a46e7a349a82bd712 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Thu, 16 May 2024 12:52:14 -0400
Subject: pdfs being weird

---
 src/client/apis/gpt/GPT.ts                         | 35 +++++------
 src/client/util/CurrentUserUtils.ts                |  4 +-
 .../views/collections/CollectionCardDeckView.tsx   | 70 ++++++++++++++++++++--
 src/client/views/global/globalScripts.ts           |  8 ++-
 src/client/views/nodes/PDFBox.tsx                  |  1 +
 5 files changed, 92 insertions(+), 26 deletions(-)

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

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 30194f9f8..1f50ad0b8 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -5,7 +5,8 @@ enum GPTCallType {
     SUMMARY = 'summary',
     COMPLETION = 'completion',
     EDIT = 'edit',
-    DATA = 'data',
+    // MERMAID='mermaid'
+    SORT = 'sort'
 }
 
 type GPTCallOpts = {
@@ -15,45 +16,40 @@ type GPTCallOpts = {
     prompt: string;
 };
 
-/**
- * Replace completions (deprecated) with chat
- */
-
 const callTypeMap: { [type: string]: GPTCallOpts } = {
-    // newest model: gpt-4
-    summary: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: 'Summarize the text given in simpler terms.' },
-    edit: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: 'Reword the text.' },
-    completion: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: "You are a helpful assistant. Answer the user's prompt." },
-    data: { model: 'gpt-3.5-turbo', maxTokens: 256, temp: 0.5, prompt: "You are a helpful resarch assistant. Analyze the user's data to find meaningful patterns and/or correlation. Please keep your response short and to the point." },
+    summary: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' },
+    edit: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' },
+    completion: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: '' },
+    sort:{model:'gpt-4-turbo',maxTokens:2048,temp:0,prompt:"I'm going to give you a list of strings. Sort them into lists by shared content. The format of should be: '[string1, string2], [string 3, string4][string5, string6]' where the actual numbers of strings in each category / total categorys is arbritray and up to your interpretation"}
 };
 
-/**
+
+/**`
  * Calls the OpenAI API.
  *
  * @param inputText Text to process
  * @returns AI Output
  */
-const gptAPICall = async (inputText: string, callType: GPTCallType, prompt?: any) => {
+const gptAPICall = async (inputText: string, callType: GPTCallType) => {
     if (callType === GPTCallType.SUMMARY) inputText += '.';
     const opts: GPTCallOpts = callTypeMap[callType];
     try {
         const configuration: ClientOptions = {
-            apiKey: process.env.OPENAI_KEY,
+            apiKey: "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",
             dangerouslyAllowBrowser: true,
         };
         const openai = new OpenAI(configuration);
 
-        let usePrompt = prompt ? opts.prompt + prompt : opts.prompt;
         let messages: ChatCompletionMessageParam[] = [
-            { role: 'system', content: usePrompt },
+            { role: 'system', content: opts.prompt },
             { role: 'user', content: inputText },
         ];
 
         const response = await openai.chat.completions.create({
             model: opts.model,
-            max_tokens: opts.maxTokens,
+            messages: messages,
             temperature: opts.temp,
-            messages,
+            max_tokens: opts.maxTokens,
         });
         const content = response.choices[0].message.content;
         return content;
@@ -76,11 +72,12 @@ const gptImageCall = async (prompt: string, n?: number) => {
             n: n ?? 1,
             size: '1024x1024',
         });
-        return response.data.map(data => data.url);
+        return response.data.map((data: any) => data.url);
+        // return response.data.data[0].url;
     } catch (err) {
         console.error(err);
         return;
     }
 };
 
-export { gptAPICall, gptImageCall, GPTCallType };
+export { gptAPICall, gptImageCall, GPTCallType };
\ No newline at end of file
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index abc728cd3..e18c22bac 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -730,9 +730,11 @@ pie title Minerals in my tap water
             { title: "Type",   icon:"eye", toolTip:"Sort by document type",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"docType",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Color",   icon:"palette", toolTip:"Sort by document color",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"color",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Links",   icon:"link", toolTip:"Sort by its links",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"links",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Create",   icon:"robot", toolTip:"Create your first custom grouping!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom1",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Create",   icon:"heart", toolTip:"Create your first custom grouping!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom1",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Create",   icon:"star", toolTip:"Create your second custom grouping!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom2",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Create",   icon:"satellite", toolTip:"Create your third custom grouping!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom3",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Create",   icon:"robot", toolTip:"Have ChatGPT sort your text-based nodes !",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"chat",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+
 
 
             // ...customs
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 7af6eb614..561b9f806 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -4,7 +4,7 @@ import * as React from 'react';
 import { Utils, returnFalse, returnTrue, returnZero } from '../../../Utils';
 import { Doc, DocListCast, Field, StrListCast } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
-import { NumCast, ScriptCast, StrCast, BoolCast, DocCast, RTFCast } from '../../../fields/Types';
+import { NumCast, ScriptCast, StrCast, BoolCast, DocCast, RTFCast, Cast} from '../../../fields/Types';
 import { DragManager } from '../../util/DragManager';
 import { SelectionManager } from '../../util/SelectionManager';
 import { StyleProp } from '../StyleProvider';
@@ -18,6 +18,9 @@ import { DocumentType } from '../../documents/DocumentTypes';
 import { forEach } from 'lodash';
 import { SnappingManager } from '../../util/SnappingManager';
 import { List } from '../../../fields/List';
+import { gptAPICall } from '../../apis/gpt/GPT';
+import { GPTCallType } from '../../apis/gpt/GPT';
+import { ImageField } from '../../../fields/URLField';
 
 @observer
 export class CollectionCardView extends CollectionSubView() {
@@ -249,6 +252,11 @@ export class CollectionCardView extends CollectionSubView() {
                 // console.log('hiiiii');
                 return this.sort(sorted, 'custom', desc);
 
+            case 'chat':
+                this.smartSort()
+                docs = this.myChildLayoutPairs;
+                return {docs}
+
             default:
                 docs = this.myChildLayoutPairs;
                 return { docs };
@@ -306,7 +314,7 @@ export class CollectionCardView extends CollectionSubView() {
                     // console.log(typeA + "A")
                     typeB = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(docs.indexOf(docB)) ?? '9999';
                     // console.log(typeB + 'b')
-                    break;
+                    break;                    
 
                 default:
                     typeA = docA.layout.type ?? ''; // If docA.type is undefined, use an empty string
@@ -468,6 +476,8 @@ export class CollectionCardView extends CollectionSubView() {
         });
     }
 
+    
+
    
     
     
@@ -476,10 +486,62 @@ export class CollectionCardView extends CollectionSubView() {
         this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].set(childPairIndex, buttonID);
         this._props.Document.customHashMap = this.mapToField
 
-        // const queryText = RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']));
-        // console.log(queryText)
+        // Cast(selected.Document.data, WebField, null)?.url?.href)
+
+        // StrCast(selected.Document.data, Cast(selected.Document.data, WebField, null)?.url?.href)
+
+
+
+        // const
+        const imgurlperchance = Cast(this.childDocs[1].data, ImageField, null)?.url;
+
+        console.log('Print Front of cards: ' + RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text);
+        console.log('Print Back of cards: ' + RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text);
+
+
+        // const queryText = (RTFCast(this.myChildLayoutPairs[0].layout.text)).Text;
+        // const queryText = RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text;
+
+        console.log(imgurlperchance ?? "sad")
     }
 
+    childPairStringList () {
+        let string = ""
+        for (let i=0; i < this.myChildLayoutPairs.length; i++){
+            string += StrCast((RTFCast(this.myChildLayoutPairs[i].layout.text)).Text) + ", "
+        }
+        return string
+    }
+
+    @observable isLoading = false
+
+    @action async smartSort() {
+        this.isLoading = true;
+        console.log("loading")
+        let prompt= ""
+
+        if (this.childPairStringList() == ""){
+            console.log("no child pairs :(")
+        }
+
+        else{
+            console.log(this.childPairStringList() + "og list")
+            prompt =  "Sort this list of strings by shared content " + this.childPairStringList()
+        }
+        
+
+        let res = await gptAPICall(prompt, GPTCallType.SORT);
+        this.isLoading = false;
+        if (res == 'Error connecting with API.') {
+            // If GPT call failed
+            console.error('GPT call failed');
+        } else if (res != null) {
+            // If GPT call succeeded, set htmlCode;;; TODO: check if valid html
+           console.log(res)
+        }
+        this.isLoading = false;
+    };
+
     renderButtons(childPairIndex: number) {
         const buttons = []; // Array to hold the button elements
 
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 30602ea0b..14f83beb6 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -107,10 +107,10 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
 
 
 
-ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom1' | 'custom2' | 'custom3', checkResult?: boolean, persist?: boolean, customNumber?: number) {
+ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom1' | 'custom2' | 'custom3'| 'chat', checkResult?: boolean, persist?: boolean, customNumber?: number) {
     const selected = SelectionManager.Docs.lastElement();
     // prettier-ignore
-    const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom1' | 'custom2' | 'custom3', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([
+    const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom1' | 'custom2' | 'custom3'| 'chat', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([
         ['grid', {
             checkResult: (doc:Doc) => BoolCast(doc?._freeform_backgroundGrid, false),
             setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid,
@@ -180,6 +180,10 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid
                 doc.cardSort = "custom";
                 doc.customSortNumber = 2;
             }        }],
+        ['chat', {
+            checkResult: (doc:Doc) => StrCast(doc?.cardSort),
+            setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "chat",
+        }],
    ]);
 
     if (checkResult) {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 1274220b6..90d6133e4 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -33,6 +33,7 @@ import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
 import { ImageBox } from './ImageBox';
 import './PDFBox.scss';
 import { PinProps, PresBox } from './trails';
+import { Networking } from '../../Network';
 
 @observer
 export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
-- 
cgit v1.2.3-70-g09d2


From d56ce09de98d843cf46b0d6eda5f8c82446f7849 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Thu, 16 May 2024 23:04:38 -0400
Subject: ab to do some silly thing

---
 src/client/apis/gpt/GPT.ts                         | 50 +++++++++++++--
 .../views/collections/CollectionCardDeckView.tsx   | 72 +++++++++++++++++++---
 .../collectionSchema/CollectionSchemaView.tsx      |  2 +-
 src/client/views/pdf/PDFViewer.tsx                 |  2 +-
 4 files changed, 111 insertions(+), 15 deletions(-)

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

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 1f50ad0b8..7c53ee83e 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -5,8 +5,8 @@ enum GPTCallType {
     SUMMARY = 'summary',
     COMPLETION = 'completion',
     EDIT = 'edit',
-    // MERMAID='mermaid'
-    SORT = 'sort'
+    SORT = 'sort',
+    DESCRIBE = 'describe'
 }
 
 type GPTCallOpts = {
@@ -20,7 +20,10 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
     summary: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' },
     edit: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' },
     completion: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: '' },
-    sort:{model:'gpt-4-turbo',maxTokens:2048,temp:0,prompt:"I'm going to give you a list of strings. Sort them into lists by shared content. The format of should be: '[string1, string2], [string 3, string4][string5, string6]' where the actual numbers of strings in each category / total categorys is arbritray and up to your interpretation"}
+    sort:{model:'gpt-4-turbo',maxTokens:2048,temp:0.5,prompt:"I'm going to give you a list of strings. Sort them into lists by shared content. The format of should be: [string1, string2], [string 3, string4], [string5, string6] where the actual numbers of strings in each category / total categorys is arbritray and up to your interpretation"},
+    describe:{model:'gpt-4-vision-preview',maxTokens:2048,temp:0,prompt:"Describe these images in 3-5 words"},
+
+
 };
 
 
@@ -30,6 +33,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
  * @param inputText Text to process
  * @returns AI Output
  */
+
 const gptAPICall = async (inputText: string, callType: GPTCallType) => {
     if (callType === GPTCallType.SUMMARY) inputText += '.';
     const opts: GPTCallOpts = callTypeMap[callType];
@@ -59,6 +63,44 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => {
     }
 };
 
+
+const gptImageLabel = async (imgUrl: string): Promise<string> => {
+    try {
+        const configuration: ClientOptions = {
+            apiKey: "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",
+            dangerouslyAllowBrowser: true,
+        };
+
+        const openai = new OpenAI(configuration);
+        const response = await openai.chat.completions.create({
+            model: 'gpt-4o',
+            messages: [
+                {
+                    role: 'user',
+                    content: [
+                        { type: 'text', text: 'Describe this image in 3-5 words' },
+                        {
+                            type: 'image_url',
+                            image_url: {
+                                url: `${imgUrl}`,
+                            },
+                        },
+                    ],
+                },
+            ],
+        });
+        if (response.choices[0].message.content) {
+            return response.choices[0].message.content;
+        } else {
+            return ':(';
+        }
+    } catch (err) {
+        console.log(err);
+        return 'Error connecting with API';
+    }
+};
+
+
 const gptImageCall = async (prompt: string, n?: number) => {
     try {
         const configuration: ClientOptions = {
@@ -80,4 +122,4 @@ const gptImageCall = async (prompt: string, n?: number) => {
     }
 };
 
-export { gptAPICall, gptImageCall, GPTCallType };
\ No newline at end of file
+export { gptAPICall, gptImageCall, gptImageLabel, GPTCallType };
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 561b9f806..e7e2cba4a 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -18,9 +18,9 @@ import { DocumentType } from '../../documents/DocumentTypes';
 import { forEach } from 'lodash';
 import { SnappingManager } from '../../util/SnappingManager';
 import { List } from '../../../fields/List';
-import { gptAPICall } from '../../apis/gpt/GPT';
+import { gptAPICall, gptImageLabel } from '../../apis/gpt/GPT';
 import { GPTCallType } from '../../apis/gpt/GPT';
-import { ImageField } from '../../../fields/URLField';
+import { ImageField, PdfField, URLField } from '../../../fields/URLField';
 
 @observer
 export class CollectionCardView extends CollectionSubView() {
@@ -253,7 +253,7 @@ export class CollectionCardView extends CollectionSubView() {
                 return this.sort(sorted, 'custom', desc);
 
             case 'chat':
-                this.smartSort()
+                this.getImageDesc()
                 docs = this.myChildLayoutPairs;
                 return {docs}
 
@@ -493,28 +493,83 @@ export class CollectionCardView extends CollectionSubView() {
 
 
         // const
-        const imgurlperchance = Cast(this.childDocs[1].data, ImageField, null)?.url;
+        const imgurlperchance = Cast(this.childDocs[1].data, ImageField, null)?.url.href;
+        const perchance = this.imageUrlToBase64(imgurlperchance)
+        // const imgurlperchance = Cast(this.childDocs[1].data, PdfField, null)?.url.;
 
-        console.log('Print Front of cards: ' + RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text);
-        console.log('Print Back of cards: ' + RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text);
 
+        // console.log('Print Front of cards: ' + RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text);
+        // console.log('Print Back of cards: ' + RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text);
+
+        // const perchance = StrCast(this.childDocs[0].text);
+
+
+
+        // const pdf = (StrCast(this.myChildLayoutPairs[0].layout.text)); //pdf
+        // const queryText = (RTFCast(this.myChildLayoutPairs[0].layout.text)); //everything else
 
-        // const queryText = (RTFCast(this.myChildLayoutPairs[0].layout.text)).Text;
         // const queryText = RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text;
 
-        console.log(imgurlperchance ?? "sad")
+        console.log(perchance ?? "sad")
     }
 
     childPairStringList () {
         let string = ""
         for (let i=0; i < this.myChildLayoutPairs.length; i++){
+
+            
+            let text = StrCast((RTFCast(this.myChildLayoutPairs[i].layout.text)).Text)
             string += StrCast((RTFCast(this.myChildLayoutPairs[i].layout.text)).Text) + ", "
+            this.textToDoc.has()
         }
         return string
     }
 
     @observable isLoading = false
 
+    imageUrlToBase64 = async (imageUrl: string): Promise<string> => {
+        try {
+            const response = await fetch(imageUrl);
+            const blob = await response.blob();
+    
+            return new Promise((resolve, reject) => {
+                const reader = new FileReader();
+                reader.readAsDataURL(blob); 
+                reader.onloadend = () => resolve(reader.result as string); 
+                reader.onerror = error => reject(error); 
+            });
+        } catch (error) {
+            console.error('Error:', error);
+            throw error; 
+        }
+    };
+
+    textToDoc = new Map<string, Doc>()
+    gptProccessedImages = new Set<Doc>()
+
+    @action async getImageDesc(){
+        const images = this.childDocs.filter(d => d.type == DocumentType.IMG).filter(d=> !this.gptProccessedImages.has(d));
+        images.map(doc => {
+            let href = (doc['data'] as URLField).url.href;
+            let hrefParts = href.split('.');
+            let hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`;
+            return this.imageUrlToBase64(hrefComplete).then(hrefBase64 => {
+                return gptImageLabel(hrefBase64).then(response => {
+                    this.textToDoc.set(response, doc)
+                    this.gptProccessedImages.add(doc)
+                    console.log(response);
+                    
+                });
+            });
+        });
+    }
+
+   
+
+    //a set of all the images that have already been given desc
+    //a map from the text to the Doc (for everything)
+    
+
     @action async smartSort() {
         this.isLoading = true;
         console.log("loading")
@@ -536,7 +591,6 @@ export class CollectionCardView extends CollectionSubView() {
             // If GPT call failed
             console.error('GPT call failed');
         } else if (res != null) {
-            // If GPT call succeeded, set htmlCode;;; TODO: check if valid html
            console.log(res)
         }
         this.isLoading = false;
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 6a956f2ac..731ea1235 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -51,7 +51,7 @@ export const FInfotoColType: { [key: string]: ColumnType } = {
     enumeration: ColumnType.Enumeration,
 };
 
-const defaultColumnKeys: string[] = ['title', 'type', 'author', 'author_date', 'text'];
+const  defaultColumnKeys: string[] = ['title', 'type', 'author', 'author_date', 'text'];
 
 @observer
 export class CollectionSchemaView extends CollectionSubView() {
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index aaff2a342..b87e29d4e 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -30,7 +30,7 @@ const _global = (window /* browser */ || global) /* node */ as any;
 
 //pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`;
 // The workerSrc property shall be specified.
-Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.1.392/build/pdf.worker.mjs';
+Pdfjs.GlobalWorkerOptions.workerSrc = 'https://unpkg.com/pdfjs-dist@4.2.67/build/pdf.worker.mjs';
 
 interface IViewerProps extends FieldViewProps {
     pdfBox: PDFBox;
-- 
cgit v1.2.3-70-g09d2


From 825463b4f7bb609082d16b302d40c6af56c98b26 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Fri, 17 May 2024 02:31:22 -0400
Subject: we did it joe

---
 src/client/apis/gpt/GPT.ts                         |   2 +-
 .../views/collections/CollectionCardDeckView.tsx   | 188 ++++++++++++++-------
 2 files changed, 126 insertions(+), 64 deletions(-)

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

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 7c53ee83e..9a24c808c 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -20,7 +20,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
     summary: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' },
     edit: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' },
     completion: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: '' },
-    sort:{model:'gpt-4-turbo',maxTokens:2048,temp:0.5,prompt:"I'm going to give you a list of strings. Sort them into lists by shared content. The format of should be: [string1, string2], [string 3, string4], [string5, string6] where the actual numbers of strings in each category / total categorys is arbritray and up to your interpretation"},
+    sort:{model:'gpt-4o',maxTokens:2048,temp:0.5,prompt:"I'm going to give you a list of descriptions. Each one is seperated by ====== on either side. They will vary in length, so make sure to only seperate when you see ======. Sort them into lists by shared content. MAKE SURE EACH DESCRIPTOR IS IN ONLY ONE LIST. Generate only the list with each list seperated by ====== with the elements seperated by ~~~~~~"},
     describe:{model:'gpt-4-vision-preview',maxTokens:2048,temp:0,prompt:"Describe these images in 3-5 words"},
 
 
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index e7e2cba4a..e64c013f1 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -24,7 +24,10 @@ import { ImageField, PdfField, URLField } from '../../../fields/URLField';
 
 @observer
 export class CollectionCardView extends CollectionSubView() {
-    @observable selectedNodeIndex = -1;
+
+    //TODO: for saving custom groups instead of string field hashmap nonsense should instead just set teh index for each child document per group as a fie;d
+    // so like, group1Ibndex = 1 etc etc as they change. That way, if the documents change it doesnt matter since the idnexes are tied to the 
+    // documents themselves
 
     @observable hoveredNodeIndex = -1;
 
@@ -223,40 +226,34 @@ export class CollectionCardView extends CollectionSubView() {
         // return 0;
     };
 
+    @observable sortedDocs = [] as { layout: Doc; data: Doc }[];
+
+
     @computed get sortedDocsType() {
+        if (this._props.Document.cardSort === 'chat' && this.sortedDocs.length === 0) {
+            this.smartSort(); // Trigger the sorting if it hasn't been done yet
+            return { docs: [] }; // Return an empty array or a loading state
+        }
+    
         const desc = BoolCast(this.layoutDoc.sortDesc);
         let sorted = [];
         let docs = [];
-
+    
         for (let i = 0; i < this.myChildLayoutPairs.length; i++) {
             //copying everything in childlayout pairs to sorted so that i can use the sort function without altering the original list
             sorted[i] = this.myChildLayoutPairs[i];
         }
-
+    
         switch (this._props.Document.cardSort) {
             case 'type':
                 // Copy and sort documents by type
                 return this.sort(sorted, 'type', desc);
             case 'color':
                 return this.sort(sorted, 'color', desc);
-            // case "links":
-            //     console.log("hi")
-
-            //    let links = LinkManager.Instance.getAllRelatedLinks(this.myChildLayoutPairs[0].layout)
-
-            //    console.log(links)
-
-            // }
-
             case 'custom':
-                // console.log('hiiiii');
                 return this.sort(sorted, 'custom', desc);
-
             case 'chat':
-                this.getImageDesc()
-                docs = this.myChildLayoutPairs;
-                return {docs}
-
+                return { docs: this.sortedDocs }; // Use the sorted docs from the observable
             default:
                 docs = this.myChildLayoutPairs;
                 return { docs };
@@ -314,7 +311,21 @@ export class CollectionCardView extends CollectionSubView() {
                     // console.log(typeA + "A")
                     typeB = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(docs.indexOf(docB)) ?? '9999';
                     // console.log(typeB + 'b')
-                    break;                    
+                    break;   
+                    
+                case 'gpt':
+
+                // console.log(this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)])
+                    typeA = this.gptGroups.get(docA.layout) ?? '9999';
+                    console.log(typeA + "typea")
+                    typeB = this.gptGroups.get(docB.layout) ?? '9999';
+                    console.log(typeB + "typeB")
+
+
+                    // console.log(typeA + "A")
+                    // typeB = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(docs.indexOf(docB)) ?? '9999';
+                    // console.log(typeB + 'b')
+                    break;  
 
                 default:
                     typeA = docA.layout.type ?? ''; // If docA.type is undefined, use an empty string
@@ -488,6 +499,8 @@ export class CollectionCardView extends CollectionSubView() {
 
         // Cast(selected.Document.data, WebField, null)?.url?.href)
 
+        //note richtext
+
         // StrCast(selected.Document.data, Cast(selected.Document.data, WebField, null)?.url?.href)
 
 
@@ -506,23 +519,42 @@ export class CollectionCardView extends CollectionSubView() {
 
 
         // const pdf = (StrCast(this.myChildLayoutPairs[0].layout.text)); //pdf
-        // const queryText = (RTFCast(this.myChildLayoutPairs[0].layout.text)); //everything else
+        const queryText = this.myChildLayoutPairs[0].layout.type; //everything else
 
         // const queryText = RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text;
 
-        console.log(perchance ?? "sad")
+        console.log(queryText ?? "sad")
     }
 
-    childPairStringList () {
-        let string = ""
-        for (let i=0; i < this.myChildLayoutPairs.length; i++){
-
-            
-            let text = StrCast((RTFCast(this.myChildLayoutPairs[i].layout.text)).Text)
-            string += StrCast((RTFCast(this.myChildLayoutPairs[i].layout.text)).Text) + ", "
-            this.textToDoc.has()
+    async childPairStringList(): Promise<string> {
+        let string = "";
+        for (let i = 0; i < this.childDocs.length; i++) {
+            switch (this.childDocs[i].type) {
+                case DocumentType.IMG:
+                    string += `======${await this.getImageDesc(this.childDocs[i])}======`;
+                    break;
+    
+                case DocumentType.PDF:
+                    let pdfText = StrCast(this.childDocs[i].text);
+                    let words = pdfText.split(/\s+/);
+                    let first50Words = words.slice(0, 50);
+                    string += `======${first50Words.join(' ')}======`;
+    
+                    this.textToDoc.set(first50Words.join(' ').trim(), this.childDocs[i]);
+                    break;
+    
+                case DocumentType.RTF:
+                    let rtfText = StrCast((RTFCast(this.childDocs[i].text)).Text);
+                    string += `======${StrCast((RTFCast(this.childDocs[i].text)).Text)}======`;
+                    this.textToDoc.set(rtfText.trim(), this.childDocs[i]);
+                    break;
+    
+                default:
+                    string += `======${StrCast(this.childDocs[i].title)}======`;
+                    this.textToDoc.set(StrCast(this.childDocs[i].title).trim(), this.childDocs[i]);
+            }
         }
-        return string
+        return string;
     }
 
     @observable isLoading = false
@@ -547,21 +579,20 @@ export class CollectionCardView extends CollectionSubView() {
     textToDoc = new Map<string, Doc>()
     gptProccessedImages = new Set<Doc>()
 
-    @action async getImageDesc(){
-        const images = this.childDocs.filter(d => d.type == DocumentType.IMG).filter(d=> !this.gptProccessedImages.has(d));
-        images.map(doc => {
-            let href = (doc['data'] as URLField).url.href;
+    @action async getImageDesc(image: Doc){
+            let href = (image['data'] as URLField).url.href;
             let hrefParts = href.split('.');
             let hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`;
-            return this.imageUrlToBase64(hrefComplete).then(hrefBase64 => {
-                return gptImageLabel(hrefBase64).then(response => {
-                    this.textToDoc.set(response, doc)
-                    this.gptProccessedImages.add(doc)
-                    console.log(response);
-                    
-                });
-            });
-        });
+            try {
+                let hrefBase64 = await this.imageUrlToBase64(hrefComplete);
+                let response = await gptImageLabel(hrefBase64);
+                this.textToDoc.set(response.trim(), image);
+                this.gptProccessedImages.add(image);
+                console.log(response);
+                return response; // Return the response from gptImageLabel
+            } catch (error) {
+                console.log("bad things have happened");
+            }
     }
 
    
@@ -572,29 +603,60 @@ export class CollectionCardView extends CollectionSubView() {
 
     @action async smartSort() {
         this.isLoading = true;
-        console.log("loading")
-        let prompt= ""
-
-        if (this.childPairStringList() == ""){
-            console.log("no child pairs :(")
+        console.log("loading");
+        
+        // Store the result of childPairStringList in a variable
+        const childPairStrings = await this.childPairStringList();
+        
+        if (childPairStrings === "") {
+            console.log("no child pairs :(");
+        } else {
+            console.log(childPairStrings + " og list");
+            let prompt = childPairStrings;
+        
+            let res = await gptAPICall(prompt, GPTCallType.SORT);
+            this.isLoading = false;
+            if (res == 'Error connecting with API.') {
+                // If GPT call failed
+                console.error('GPT call failed');
+            } else if (res != null) {
+                console.log(res);
+                this.processGptOutput(res);
+                // Update the observable with the sorted documents
+                this.sortedDocs = this.sort(this.myChildLayoutPairs, 'gpt', BoolCast(this.layoutDoc.sortDesc)).docs;
+            }
+            this.isLoading = false;
         }
+    }
 
-        else{
-            console.log(this.childPairStringList() + "og list")
-            prompt =  "Sort this list of strings by shared content " + this.childPairStringList()
-        }
-        
+    gptGroups = new Map<Doc, number>
+
+     // Method to convert the GPT-produced string into a map
+    processGptOutput(gptOutput: string) {
+
+        // Split the string into individual list items
+        const listItems = gptOutput.split('======').filter(item => item.trim() !== '');
+
+        listItems.forEach((item, index) => {
+            // Split the item by '~~~~~~' to get all descriptors
+            const parts = item.split('~~~~~~').map(part => part.trim());
+
+            parts.forEach(part => {
+                console.log(part + "part")
+                // Find the corresponding Doc in the textToDoc map
+                if (this.textToDoc.has(part)) {
+                    const doc = this.textToDoc.get(part);
+                    console.log("hewoooooo")
+                    if (doc) {
+                        console.log('hi')
+                        this.gptGroups.set(doc, index);
+                    }
+                }
+            });
+        });
+    }
 
-        let res = await gptAPICall(prompt, GPTCallType.SORT);
-        this.isLoading = false;
-        if (res == 'Error connecting with API.') {
-            // If GPT call failed
-            console.error('GPT call failed');
-        } else if (res != null) {
-           console.log(res)
-        }
-        this.isLoading = false;
-    };
+    
 
     renderButtons(childPairIndex: number) {
         const buttons = []; // Array to hold the button elements
-- 
cgit v1.2.3-70-g09d2


From 10ae244338383662992350b2b6f6ef80106558c3 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Fri, 17 May 2024 14:28:09 -0400
Subject: yay git add -A

---
 .../views/collections/CollectionCardDeckView.tsx   |  52 +++++++-
 src/client/views/pdf/GPTPopup/GPTPopup.scss        |  33 +++++
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         | 138 ++++++++++++++++++++-
 3 files changed, 215 insertions(+), 8 deletions(-)

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

diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index e64c013f1..e96fbc161 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -21,7 +21,9 @@ import { List } from '../../../fields/List';
 import { gptAPICall, gptImageLabel } from '../../apis/gpt/GPT';
 import { GPTCallType } from '../../apis/gpt/GPT';
 import { ImageField, PdfField, URLField } from '../../../fields/URLField';
-
+import { GPTPopup } from '../pdf/GPTPopup/GPTPopup';
+import { GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
+import { reaction } from 'mobx';
 @observer
 export class CollectionCardView extends CollectionSubView() {
 
@@ -88,6 +90,15 @@ export class CollectionCardView extends CollectionSubView() {
         if (this._props.Document.customHashMap != undefined){
             this.customGroupDictionary = this.getCustoms(StrListCast(this._props.Document.customHashMap))
         }
+
+        reaction(
+            () => this._props.Document.cardSort,
+            (cardSort) => {
+                if (cardSort === 'chat') {
+                    this.openChatPopup();
+                }
+            }
+        );
     }
 
     @observable customGroupDictionary: Map<number, number>[] = [new Map<number, number>(), new Map<number, number>(), new Map<number, number>()];
@@ -230,10 +241,18 @@ export class CollectionCardView extends CollectionSubView() {
 
 
     @computed get sortedDocsType() {
-        if (this._props.Document.cardSort === 'chat' && this.sortedDocs.length === 0) {
-            this.smartSort(); // Trigger the sorting if it hasn't been done yet
-            return { docs: [] }; // Return an empty array or a loading state
-        }
+        // if (this._props.Document.cardSort === 'chat'){
+        //     this.openChatPopup()
+        //     const textDesc = await this.childPairStringList();
+        //     GPTPopup.Instance.setSortDesc(textDesc)
+        // }
+
+        // if (this._props.Document.cardSort === 'chat' && this.sortedDocs.length === 0) {
+
+        //     this.smartSort(); // Trigger the sorting if it hasn't been done yet
+        //     return { docs: [] }; // Return an empty array or a loading state
+        // }
+
     
         const desc = BoolCast(this.layoutDoc.sortDesc);
         let sorted = [];
@@ -253,7 +272,7 @@ export class CollectionCardView extends CollectionSubView() {
             case 'custom':
                 return this.sort(sorted, 'custom', desc);
             case 'chat':
-                return { docs: this.sortedDocs }; // Use the sorted docs from the observable
+                return { docs: this.sortedDocs || this.myChildLayoutPairs}; // Use the sorted docs from the observable
             default:
                 docs = this.myChildLayoutPairs;
                 return { docs };
@@ -656,6 +675,25 @@ export class CollectionCardView extends CollectionSubView() {
         });
     }
 
+    @observable isChatPopupOpen = false;
+
+    @action openChatPopup = async () => {
+    this.isChatPopupOpen = true;
+    GPTPopup.Instance.setVisible(true);
+    GPTPopup.Instance.setMode(GPTPopupMode.SORT);
+    
+    // Await the promise to get the string result
+    const sortDesc = await this.childPairStringList();
+    GPTPopup.Instance.setSortDesc(sortDesc);
+    GPTPopup.Instance.onSortComplete = this.handleGptSortResult;
+};
+    @action handleGptSortResult = (sortResult: string) => {
+        this.processGptOutput(sortResult);
+        this.sortedDocs = this.sort(this.myChildLayoutPairs, 'gpt', BoolCast(this.layoutDoc.sortDesc)).docs;
+    };
+
+
+
     
 
     renderButtons(childPairIndex: number) {
@@ -708,6 +746,8 @@ export class CollectionCardView extends CollectionSubView() {
     //     }
     // }
 
+   
+
     render() {
         return (
             <div
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index 5d966395c..4425cf158 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -60,6 +60,17 @@ $highlightedText: #82e0ff;
         display: flex;
         justify-content: space-between;
         align-items: center;
+        transform: translateY(30px);
+
+
+        .searchBox-input{
+            transform: translateY(-15px);
+            height: 50px;
+            border-radius: 10px;
+            border-color: #5b97ff;
+        }
+
+        
 
         .summarizing {
             display: flex;
@@ -111,6 +122,28 @@ $highlightedText: #82e0ff;
     }
 }
 
+.loading-spinner {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    height: 100px;
+    font-size: 20px;
+    font-weight: bold;
+    color: #666;
+}
+
+
+
+
+
+@keyframes spin {
+    to {
+        transform: rotate(360deg);
+    }
+}
+
+
+
 .image-content-wrapper {
     display: flex;
     flex-direction: column;
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 29b1ca365..faf66af85 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -21,6 +21,7 @@ export enum GPTPopupMode {
     EDIT,
     IMAGE,
     DATA,
+    SORT
 }
 
 interface GPTPopupProps {}
@@ -94,10 +95,21 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     private done: boolean = false;
     @action
     public setDone = (done: boolean) => {
+        console.log("HIIIIIIIII")
         this.done = done;
         this.chatMode = false;
     };
 
+    @observable
+    private sortDone: boolean = false // this is so redundant but the og done variable was causing weird unknown problems and im just a girl
+
+    @action
+    public setSortDone = (done: boolean) => {
+        console.log("HIIIIIIIII")
+        this.sortDone = done;
+    };
+
+
     // change what can be a ref into a ref
     @observable
     private sidebarId: string = '';
@@ -120,9 +132,61 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         this.textAnchor = anchor;
     };
 
+    @observable
+    public sortPrompt: string = ''; 
+    @action
+    public setSortPrompt = ((e: React.ChangeEvent<HTMLInputElement>) => {
+        this.sortPrompt = e.target.value;
+    });
+
+    @observable
+    public sortText: string = ''
+    @action
+    public setSortText = (t: string) => {
+        this.sortText = t
+    }
+
+    @observable
+    public sortDesc: string = ''
+
+    @action public setSortDesc = (t:string) => {
+        this.sortDesc = t
+    }
+
+    @observable onSortComplete?: (sortResult: string) => void;
+
+
+
+
+
+
     public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false;
     public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined;
 
+
+    generateSort = async () => {
+        this.setLoading(true);
+        this.setSortDone(false);
+
+        try {
+            const res = await gptAPICall(this.sortPrompt + this.sortDesc, GPTCallType.SORT);
+            this.setSortText(res || 'Something went wrong :(');
+
+            // Trigger the callback with the result
+            if (this.onSortComplete) {
+                this.onSortComplete(res || 'Something went wrong :(');
+            }
+        } catch (err) {
+            console.error(err);
+            this.setSortText('Something went wrong :(');
+        }
+
+        this.setLoading(false);
+        this.setSortDone(true);
+    }
+
+
+
     /**
      * Generates a Dalle image and uploads it to the server.
      */
@@ -243,6 +307,74 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         }
     };
 
+    sortBox = () => (
+        <>
+            <div>
+                {this.heading("SORTING")}
+                {this.loading ? (
+                    <div className="content-wrapper">
+                        <div className="loading-spinner">
+                            <ReactLoading type="spin" color="#000" height={30} width={30} />
+                            <span>Loading...</span>
+                        </div>
+                    </div>
+                ) : !this.sortDone && (
+                    <div className="btns-wrapper">
+                        <input
+                            defaultValue=""
+                            autoComplete="off"
+                            onChange={this.setSortPrompt}
+                            onKeyDown={e => {
+                                if (e.key === 'Enter') {
+                                    this.generateSort();
+                                }
+                                e.stopPropagation();
+                            }}
+                            type="text"
+                            placeholder="Enter sorting instructions..."
+                            id="search-input"
+                            className="searchBox-input"
+                            style={{ width: "100%" }}
+                        />
+                    </div>
+                )}
+    
+                {this.sortDone && (
+                    <div>
+                        <div className="content-wrapper">
+
+                        <p>{this.text== "Something went wrong :(" ? "Something went wrong :(" : "Sorting done! Feel free to move things around / regenerate :) !"}</p>
+                       
+
+                            <IconButton
+                                tooltip="Generate Again"
+                                onClick={() => this.setSortDone(false)}
+                                icon={<FontAwesomeIcon icon="redo-alt" size="lg" />}
+                                color={StrCast(Doc.UserDoc().userVariantColor)}
+                            />
+
+                    </div>  
+                    </div>
+                )}
+            </div>
+        </>
+    );
+    
+    
+
+
+//     <>
+//     <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
+//     <Button tooltip="Chat with AI" text="Chat with AI" onClick={this.chatWithAI} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
+// </>
+// ) : (
+// <div className="summarizing">
+//     <span>Sorting</span>
+//     <ReactLoading type="bubbles" color="#bcbcbc" width={20} height={20} />
+//     <Button text="Stop Animation" onClick={() => {this.setDone(true);}} color={StrCast(Doc.UserDoc().userVariantColor)}  type={Type.TERT}/>
+// </div>
+// )}
+
     imageBox = () => {
         return (
             <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
@@ -396,8 +528,10 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     render() {
         return (
             <div className="summary-box" style={{ display: this.visible ? 'flex' : 'none' }}>
-                {this.mode === GPTPopupMode.SUMMARY? this.summaryBox() : this.mode === GPTPopupMode.DATA? this.dataAnalysisBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : <></>}
-            </div>
+                {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() :
+                this.mode === GPTPopupMode.DATA ? this.dataAnalysisBox() :
+                this.mode === GPTPopupMode.IMAGE ? this.imageBox() :
+                this.mode === GPTPopupMode.SORT ? this.sortBox() : <></>}            </div>
         );
     }
 }
-- 
cgit v1.2.3-70-g09d2


From 6c7615110dcf8334196bbfddfd89b684e4ceee4f Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Fri, 17 May 2024 21:30:02 -0400
Subject: GOD IS GOOD

---
 src/client/apis/gpt/GPT.ts                         |   2 +-
 src/client/documents/Documents.ts                  |   2 +
 src/client/util/CurrentUserUtils.ts                |  29 ++-
 .../views/collections/CollectionCardDeckView.tsx   | 231 +++++++++++++--------
 src/client/views/global/globalScripts.ts           | 153 ++++++++++----
 src/client/views/pdf/GPTPopup/GPTPopup.scss        |   6 +-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         | 102 +++++----
 7 files changed, 343 insertions(+), 182 deletions(-)

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

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 9a24c808c..398f8ae39 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -20,7 +20,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
     summary: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' },
     edit: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' },
     completion: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: '' },
-    sort:{model:'gpt-4o',maxTokens:2048,temp:0.5,prompt:"I'm going to give you a list of descriptions. Each one is seperated by ====== on either side. They will vary in length, so make sure to only seperate when you see ======. Sort them into lists by shared content. MAKE SURE EACH DESCRIPTOR IS IN ONLY ONE LIST. Generate only the list with each list seperated by ====== with the elements seperated by ~~~~~~"},
+    sort:{model:'gpt-4o',maxTokens:2048,temp:0.5,prompt:"I'm going to give you a list of descriptions. Each one is seperated by ====== on either side. They will vary in length, so make sure to only seperate when you see ======. Sort them into lists by shared content. MAKE SURE EACH DESCRIPTOR IS IN ONLY ONE LIST. Generate only the list with each list seperated by ====== with the elements seperated by ~~~~~~. Try to do around 4 groups, but a little more or less is ok."},
     describe:{model:'gpt-4-vision-preview',maxTokens:2048,temp:0,prompt:"Describe these images in 3-5 words"},
 
 
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 57f91399a..eded2b485 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -502,6 +502,8 @@ export class DocumentOptions {
     cardSort?: STRt = new StrInfo('way cards are sorted in deck view');
     customSortNumber?: NUMt = new NumInfo('number of custom sorts the user has created');
     customHashMap?: List<string>
+    visibleGroupNumbers?: List<number>
+
     // card_sort_time?: BOOLt = new BoolInfo('whether sorting cards in deck view by time');
     // card_sort_type?: BOOLt = new BoolInfo('whether sorting cards in deck view by type');
     // card_sort_color?: BOOLt = new BoolInfo('whether sorting cards in deck view by color');
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index e18c22bac..251771e8e 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -729,18 +729,34 @@ pie title Minerals in my tap water
             { title: "Time",   icon:"hourglass-half", toolTip:"Sort by most recent document creation",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"time",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Type",   icon:"eye", toolTip:"Sort by document type",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"docType",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
             { title: "Color",   icon:"palette", toolTip:"Sort by document color",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"color",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Links",   icon:"link", toolTip:"Sort by its links",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"links",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Create",   icon:"heart", toolTip:"Create your first custom grouping!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom1",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Create",   icon:"star", toolTip:"Create your second custom grouping!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom2",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Create",   icon:"satellite", toolTip:"Create your third custom grouping!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom3",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Create",   icon:"robot", toolTip:"Have ChatGPT sort your text-based nodes !",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"chat",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Smart Sort",   icon:"robot", toolTip:"Have ChatGPT sort your text-based nodes !",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"chat",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+        ]
+    }
 
+    static customCardTools(): Button[] {
+            return [
+                { title: "Create",   icon:"heart", toolTip:"Create your first custom grouping!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom1",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+                { title: "Create",   icon:"star", toolTip:"Create your second custom grouping!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom2",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+                { title: "Create",   icon:"satellite", toolTip:"Create your third custom grouping!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom3",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+                { title: "Custom 1",    icon: "Custom 1",    width: 10,    toolTip: "Set visibilty!",     subMenu: CurrentUserUtils.cardGroupTools("heart"),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!isThatCardGroup(0)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
+                { title: "Custom 2",    icon: "Custom 2",     width: 80,    toolTip: "Set visibilty!",     subMenu: CurrentUserUtils.cardGroupTools("star"),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!isThatCardGroup(1)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
+                { title: "Custom 3",    icon: "Custom 3",      width: 80,   toolTip: "Set visibilty!",     subMenu: CurrentUserUtils.cardGroupTools("satellite"),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!isThatCardGroup(2)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
 
+            ]
+        }
 
-            // ...customs
+    static cardGroupTools(icon: string): Button[] {
+        return [
+            { title: "",   icon:icon, toolTip:"Click to toggle visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"1",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "",   icon:icon, toolTip:"Click to toggle visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"2",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "",   icon:icon, toolTip:"Click to toggle visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"3",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "",   icon:icon, toolTip:"Click to toggle visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"4",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
 
         ]
     }
+
+    
+
     static viewTools(): Button[] {
         return [
             { title: "Snap",      icon: "th",                  toolTip: "Show Snap Lines",     btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
@@ -842,6 +858,7 @@ pie title Minerals in my tap water
             { title: "View",    icon: "View",          toolTip: "View tools",           subMenu: CurrentUserUtils.viewTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "Stack",   icon: "View",          toolTip: "Stacking tools",       subMenu: CurrentUserUtils.stackTools(),  expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "Card",    icon: "Sort",          toolTip: "Card sort",            subMenu: CurrentUserUtils.cardTools(),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
+            { title: "CustomCard",    icon: "Create",        toolTip: "Create custom groupings!",     subMenu: CurrentUserUtils.customCardTools(),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
             { title: "Video",   icon: "Video",         toolTip: "Video functions",      subMenu: CurrentUserUtils.videoTools(),  expertMode: false, toolType:DocumentType.VID,            funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected
             { title: "Image",   icon: "Image",         toolTip: "Image functions",      subMenu: CurrentUserUtils.imageTools(),  expertMode: false, toolType:DocumentType.IMG,            funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index e96fbc161..42c82ca74 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -24,6 +24,7 @@ import { ImageField, PdfField, URLField } from '../../../fields/URLField';
 import { GPTPopup } from '../pdf/GPTPopup/GPTPopup';
 import { GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
 import { reaction } from 'mobx';
+import { NumListCast } from '../../../fields/Doc';
 @observer
 export class CollectionCardView extends CollectionSubView() {
 
@@ -32,8 +33,42 @@ export class CollectionCardView extends CollectionSubView() {
     // documents themselves
 
     @observable hoveredNodeIndex = -1;
+    //index to group
+    @observable customGroupDictionary: Map<number, number>[] = [new Map<number, number>(), new Map<number, number>(), new Map<number, number>()];
+
 
     @computed get myChildLayoutPairs() {
+        let activeGroups = NumListCast(this._props.Document.visibleGroupNumbers);
+        let currCustom = NumCast(this._props.Document.customSortNumber);
+
+        // console.log("Active Groups:", activeGroups);
+
+        for (let i=0; i< activeGroups.length; i++){
+            console.log("Active Groups" + activeGroups[i])
+        }
+        // console.log("Current Custom Sort Number:", currCustom);
+
+        if (activeGroups.length <= 0) {
+            return this.childLayoutPairs.filter(l => l.layout.type != DocumentType.LINK);
+        }
+
+        if (StrCast(this._props.Document.cardSort).includes("custom")) {
+            return this.childLayoutPairs.filter((l, index) => {
+                if (l.layout.type === DocumentType.LINK) {
+                    return false;
+                }
+
+                // Get the group number for the current index from the customGroupDictionary
+                const groupNumber = this.customGroupDictionary[currCustom].get(index);
+
+                // console.log(`Index: ${index}, Group Number: ${groupNumber}`);
+
+                // Check if the group number is in the active groups
+                return groupNumber !== undefined && activeGroups.includes(groupNumber);
+            });
+        }
+
+        // Default return for non-custom cardSort or other cases, filtering out links
         return this.childLayoutPairs.filter(l => l.layout.type != DocumentType.LINK);
     }
 
@@ -90,6 +125,9 @@ export class CollectionCardView extends CollectionSubView() {
         if (this._props.Document.customHashMap != undefined){
             this.customGroupDictionary = this.getCustoms(StrListCast(this._props.Document.customHashMap))
         }
+        // this._props.Document.visibleGroupNumbers = new List<number>([1,2,3,4])
+
+
 
         reaction(
             () => this._props.Document.cardSort,
@@ -101,7 +139,6 @@ export class CollectionCardView extends CollectionSubView() {
         );
     }
 
-    @observable customGroupDictionary: Map<number, number>[] = [new Map<number, number>(), new Map<number, number>(), new Map<number, number>()];
 
     @computed get mapToField(): List<string> {
         const resultList = new List<string>(); 
@@ -272,7 +309,8 @@ export class CollectionCardView extends CollectionSubView() {
             case 'custom':
                 return this.sort(sorted, 'custom', desc);
             case 'chat':
-                return { docs: this.sortedDocs || this.myChildLayoutPairs}; // Use the sorted docs from the observable
+                return this.sort(this.myChildLayoutPairs, 'gpt', BoolCast(this.layoutDoc.sortDesc));            
+                
             default:
                 docs = this.myChildLayoutPairs;
                 return { docs };
@@ -458,7 +496,8 @@ export class CollectionCardView extends CollectionSubView() {
             // const childPair = { layout: doc, data: doc };
 
             const isHovered = this.hoveredNodeIndex === index;
-            const childPairIndex = this.myChildLayoutPairs.indexOf(childPair);
+            // const childPairIndex = this.myChildLayoutPairs.indexOf(childPair);
+            const childPairIndex  = this.childLayoutPairs.filter(d => d.layout.type != DocumentType.LINK).indexOf(childPair)
             const realIndex = this.sortedDocsType.docs.filter(d => !SelectionManager.IsSelected(d.layout)).indexOf(childPair);
             const calcRowIndex = this.overflowIndexCalc(realIndex);
 
@@ -501,6 +540,8 @@ export class CollectionCardView extends CollectionSubView() {
                     {this.displayDoc(childPair, childScreenToLocal)}
 
                     {this._props.Document.cardSort == 'custom' ? this.renderButtons(childPairIndex) : ''}
+                    {this._props.Document.cardSort == 'chat' ? this.renderButtons(childPairIndex, childPair.layout, true) : ''}
+
                 </div>
             );
         });
@@ -512,38 +553,23 @@ export class CollectionCardView extends CollectionSubView() {
     
     
 
-    @action toggleButton(childPairIndex: number, buttonID: number) {
-        this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].set(childPairIndex, buttonID);
-        this._props.Document.customHashMap = this.mapToField
-
-        // Cast(selected.Document.data, WebField, null)?.url?.href)
-
-        //note richtext
-
-        // StrCast(selected.Document.data, Cast(selected.Document.data, WebField, null)?.url?.href)
-
-
-
-        // const
-        const imgurlperchance = Cast(this.childDocs[1].data, ImageField, null)?.url.href;
-        const perchance = this.imageUrlToBase64(imgurlperchance)
-        // const imgurlperchance = Cast(this.childDocs[1].data, PdfField, null)?.url.;
-
-
-        // console.log('Print Front of cards: ' + RTFCast(DocCast(this.dataDoc[this.fieldKey + '_0']).text)?.Text);
-        // console.log('Print Back of cards: ' + RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text);
-
-        // const perchance = StrCast(this.childDocs[0].text);
-
-
-
-        // const pdf = (StrCast(this.myChildLayoutPairs[0].layout.text)); //pdf
-        const queryText = this.myChildLayoutPairs[0].layout.type; //everything else
-
-        // const queryText = RTFCast(DocCast(this.dataDoc[this.fieldKey + '_1']).text)?.Text;
-
-        console.log(queryText ?? "sad")
+    @action toggleButton(childPairIndex: number, buttonID: number, isChat = false, doc?: Doc) {
+        if (!isChat) {
+            this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].set(childPairIndex, buttonID);
+            this._props.Document.customHashMap = this.mapToField;
+        }
+    
+        if (isChat && doc) {
+            this.gptGroups.set(doc, buttonID);
+        }
+    
+        // console.log(`Toggled button for childPairIndex: ${childPairIndex}, buttonID: ${buttonID}, isChat: ${isChat}`);
+        // console.log(`Updated customGroupDictionary:`, this.customGroupDictionary);
+        if (isChat && doc) {
+            console.log(`Updated gptGroups for doc ${doc}:`, this.gptGroups.get(doc));
+        }
     }
+    
 
     async childPairStringList(): Promise<string> {
         let string = "";
@@ -598,20 +624,25 @@ export class CollectionCardView extends CollectionSubView() {
     textToDoc = new Map<string, Doc>()
     gptProccessedImages = new Set<Doc>()
 
-    @action async getImageDesc(image: Doc){
-            let href = (image['data'] as URLField).url.href;
-            let hrefParts = href.split('.');
-            let hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`;
-            try {
-                let hrefBase64 = await this.imageUrlToBase64(hrefComplete);
-                let response = await gptImageLabel(hrefBase64);
-                this.textToDoc.set(response.trim(), image);
-                this.gptProccessedImages.add(image);
-                console.log(response);
-                return response; // Return the response from gptImageLabel
-            } catch (error) {
-                console.log("bad things have happened");
-            }
+    @action async getImageDesc(image: Doc) {
+        if (this.gptProccessedImages.has(image)) {
+            // Return the already processed description
+            return Array.from(this.textToDoc.keys()).find(key => this.textToDoc.get(key) === image) || '';
+        }
+    
+        let href = (image['data'] as URLField).url.href;
+        let hrefParts = href.split('.');
+        let hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`;
+        try {
+            let hrefBase64 = await this.imageUrlToBase64(hrefComplete);
+            let response = await gptImageLabel(hrefBase64);
+            this.textToDoc.set(response.trim(), image);
+            this.gptProccessedImages.add(image);
+            console.log(response);
+            return response; // Return the response from gptImageLabel
+        } catch (error) {
+            console.log("bad things have happened");
+        }
     }
 
    
@@ -620,41 +651,43 @@ export class CollectionCardView extends CollectionSubView() {
     //a map from the text to the Doc (for everything)
     
 
-    @action async smartSort() {
-        this.isLoading = true;
-        console.log("loading");
+    // @action async smartSort() {
+    //     this.isLoading = true;
+    //     console.log("loading");
         
-        // Store the result of childPairStringList in a variable
-        const childPairStrings = await this.childPairStringList();
+    //     // Store the result of childPairStringList in a variable
+    //     const childPairStrings = await this.childPairStringList();
         
-        if (childPairStrings === "") {
-            console.log("no child pairs :(");
-        } else {
-            console.log(childPairStrings + " og list");
-            let prompt = childPairStrings;
+    //     if (childPairStrings === "") {
+    //         console.log("no child pairs :(");
+    //     } else {
+    //         console.log(childPairStrings + " og list");
+    //         let prompt = childPairStrings;
         
-            let res = await gptAPICall(prompt, GPTCallType.SORT);
-            this.isLoading = false;
-            if (res == 'Error connecting with API.') {
-                // If GPT call failed
-                console.error('GPT call failed');
-            } else if (res != null) {
-                console.log(res);
-                this.processGptOutput(res);
-                // Update the observable with the sorted documents
-                this.sortedDocs = this.sort(this.myChildLayoutPairs, 'gpt', BoolCast(this.layoutDoc.sortDesc)).docs;
-            }
-            this.isLoading = false;
-        }
-    }
+    //         let res = await gptAPICall(prompt, GPTCallType.SORT);
+    //         this.isLoading = false;
+    //         if (res == 'Error connecting with API.') {
+    //             // If GPT call failed
+    //             console.error('GPT call failed');
+    //         } else if (res != null) {
+    //             console.log(res);
+    //             this.processGptOutput(res);
+    //             // Update the observable with the sorted documents
+    //             this.sortedDocs = this.sort(this.myChildLayoutPairs, 'gpt', BoolCast(this.layoutDoc.sortDesc)).docs;
+    //         }
+    //         this.isLoading = false;
+    //     }
+    // }
 
-    gptGroups = new Map<Doc, number>
+    gptGroups = new ObservableMap<Doc, number>
+    @observable amGPTGroups = 0
 
      // Method to convert the GPT-produced string into a map
     processGptOutput(gptOutput: string) {
 
         // Split the string into individual list items
         const listItems = gptOutput.split('======').filter(item => item.trim() !== '');
+        this.amGPTGroups = listItems.length
 
         listItems.forEach((item, index) => {
             // Split the item by '~~~~~~' to get all descriptors
@@ -678,14 +711,15 @@ export class CollectionCardView extends CollectionSubView() {
     @observable isChatPopupOpen = false;
 
     @action openChatPopup = async () => {
-    this.isChatPopupOpen = true;
-    GPTPopup.Instance.setVisible(true);
-    GPTPopup.Instance.setMode(GPTPopupMode.SORT);
-    
-    // Await the promise to get the string result
-    const sortDesc = await this.childPairStringList();
-    GPTPopup.Instance.setSortDesc(sortDesc);
-    GPTPopup.Instance.onSortComplete = this.handleGptSortResult;
+        this.isChatPopupOpen = true;
+        GPTPopup.Instance.setVisible(true);
+        GPTPopup.Instance.setMode(GPTPopupMode.SORT);
+        
+        // Await the promise to get the string result
+        const sortDesc = await this.childPairStringList();
+        GPTPopup.Instance.setCardsDoneLoading(true); // Set dataDoneLoading to true after data is loaded
+        GPTPopup.Instance.setSortDesc(sortDesc);
+        GPTPopup.Instance.onSortComplete = this.handleGptSortResult;
 };
     @action handleGptSortResult = (sortResult: string) => {
         this.processGptOutput(sortResult);
@@ -696,19 +730,34 @@ export class CollectionCardView extends CollectionSubView() {
 
     
 
-    renderButtons(childPairIndex: number) {
+    renderButtons(childPairIndex: number, doc?: Doc, isChat = false) {
         const buttons = []; // Array to hold the button elements
-
-        let amButtons = 4;
-
-        let activeButtonIndex = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(childPairIndex);
-
+        const groupNumber = NumCast(this._props.Document.customSortNumber);
+    
+        let amButtons = 4; // Adjusted to your context
+        let activeButtonIndex = this.customGroupDictionary[groupNumber].get(childPairIndex);
+    
+        if (isChat && doc) {
+            if (this.amGPTGroups > 4){
+                amButtons = this.amGPTGroups;
+            }
+            activeButtonIndex = this.gptGroups.get(doc);
+        }
+    
+        console.log("childPairIndex:", childPairIndex, "activeButtonIndex:", activeButtonIndex, "groupNumber:", groupNumber);
+    
         for (let i = 0; i < amButtons; i++) {
-            const isActive = activeButtonIndex == i;
-
-            buttons.push(<button key={i} style={{ backgroundColor: `${isActive ? '#4476f7' : '#323232'}` }} onClick={() => this.toggleButton(childPairIndex, i)}></button>);
+            const isActive = activeButtonIndex === i;
+            console.log(`Rendering button ${i} for childPairIndex ${childPairIndex} isActive: ${isActive}`);
+            buttons.push(
+                <button
+                    key={i}
+                    style={{ backgroundColor: isActive ? '#4476f7' : '#323232' }}
+                    onClick={() => this.toggleButton(childPairIndex, i, isChat, doc)}
+                ></button>
+            );
         }
-
+    
         const totalWidth = amButtons * 35 + amButtons * 2 * 5 + 6;
         return (
             <div className="card-button-container" style={{ width: `${totalWidth}px` }}>
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 14f83beb6..496d7482c 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -22,7 +22,8 @@ import { VideoBox } from '../nodes/VideoBox';
 import { WebBox } from '../nodes/WebBox';
 import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
 import { ImageBox } from '../nodes/ImageBox';
-
+import { NumListCast } from '../../../fields/Doc';
+import { List } from '../../../fields/List';
 ScriptingGlobals.add(function IsNoneSelected() {
     return SelectionManager.Views.length <= 0;
 }, 'are no document selected');
@@ -107,84 +108,146 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
 
 
 
-ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom1' | 'custom2' | 'custom3'| 'chat', checkResult?: boolean, persist?: boolean, customNumber?: number) {
+ScriptingGlobals.add(function showFreeform(
+    attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom1' | 'custom2' | 'custom3' | 'chat' | '1' | '2' | '3' | '4',
+    checkResult?: boolean,
+    persist?: boolean,
+    isDoubleClick?: boolean
+) {
     const selected = SelectionManager.Docs.lastElement();
     // prettier-ignore
-    const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom1' | 'custom2' | 'custom3'| 'chat', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([
+    const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom1' | 'custom2' | 'custom3' | 'chat' | '1' | '2' | '3' | '4', 
+    { 
+        waitForRender?: boolean; 
+        checkResult: (doc: Doc) => any; 
+        setDoc: (doc: Doc, dv: DocumentView) => void; 
+    }> = new Map([
         ['grid', {
-            checkResult: (doc:Doc) => BoolCast(doc?._freeform_backgroundGrid, false),
-            setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid,
+            checkResult: (doc: Doc) => BoolCast(doc?._freeform_backgroundGrid, false),
+            setDoc: (doc: Doc, dv: DocumentView) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid,
         }],
         ['snaplines', {
-            checkResult: (doc:Doc) => BoolCast(doc?._freeform_snapLines, false),
-            setDoc: (doc:Doc, dv:DocumentView) => doc._freeform_snapLines = !doc._freeform_snapLines,
+            checkResult: (doc: Doc) => BoolCast(doc?._freeform_snapLines, false),
+            setDoc: (doc: Doc, dv: DocumentView) => doc._freeform_snapLines = !doc._freeform_snapLines,
         }],
         ['viewAll', {
-            checkResult: (doc:Doc) => BoolCast(doc?._freeform_fitContentsToBox, false),
-            setDoc: (doc:Doc,dv:DocumentView) => {
+            checkResult: (doc: Doc) => BoolCast(doc?._freeform_fitContentsToBox, false),
+            setDoc: (doc: Doc, dv: DocumentView) => {
                 if (persist) doc._freeform_fitContentsToBox = !doc._freeform_fitContentsToBox;
                 else if (doc._freeform_fitContentsToBox) doc._freeform_fitContentsToBox = undefined;
                 else (dv.ComponentView as CollectionFreeFormView)?.fitContentOnce();
             },
         }],
         ['center', {
-            checkResult: (doc:Doc) => BoolCast(doc?._stacking_alignCenter, false),
-            setDoc: (doc:Doc,dv:DocumentView) => doc._stacking_alignCenter = !doc._stacking_alignCenter,
+            checkResult: (doc: Doc) => BoolCast(doc?._stacking_alignCenter, false),
+            setDoc: (doc: Doc, dv: DocumentView) => doc._stacking_alignCenter = !doc._stacking_alignCenter,
         }],
         ['clusters', {
-            waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire
-            checkResult: (doc:Doc) => BoolCast(doc?._freeform_useClusters, false),
-            setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_useClusters = !doc._freeform_useClusters,
+            waitForRender: true,
+            checkResult: (doc: Doc) => BoolCast(doc?._freeform_useClusters, false),
+            setDoc: (doc: Doc, dv: DocumentView) => doc._freeform_useClusters = !doc._freeform_useClusters,
         }],
         ['flashcards', {
-            checkResult: (doc:Doc) => BoolCast(Doc.UserDoc().defaultToFlashcards, false),
-            setDoc: (doc:Doc,dv:DocumentView) => Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards,
+            checkResult: (doc: Doc) => BoolCast(Doc.UserDoc().defaultToFlashcards, false),
+            setDoc: (doc: Doc, dv: DocumentView) => Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards,
         }],
         ['time', {
-            checkResult: (doc:Doc) => StrCast(doc?.cardSort),
-            setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "time", 
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "time",
+            setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "time",
         }],
         ['docType', {
-            checkResult: (doc:Doc) => StrCast(doc?.cardSort),
-            setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "type",
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "type",
+            setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "type",
         }],
         ['color', {
-            checkResult: (doc:Doc) => StrCast(doc?.cardSort),
-            setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "color",
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "color",
+            setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "color",
         }],
-
         ['links', {
-            checkResult: (doc:Doc) => StrCast(doc?.cardSort),
-            setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "links",
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "links",
+            setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "links",
         }],
-
         ['custom1', {
-            checkResult: (doc:Doc) => StrCast(doc?.cardSort),
-            setDoc: (doc, dv) => {
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && NumCast(doc?.customSortNumber) === 0,
+            setDoc: (doc: Doc, dv: DocumentView) => {
                 doc.cardSort = "custom";
                 doc.customSortNumber = 0;
+                doc.visibleGroupNumbers = new List<number>();
             }
-        }],        
-
+        }],
         ['custom2', {
-            checkResult: (doc:Doc) => StrCast(doc?.cardSort),
-            setDoc: (doc, dv) => {
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && NumCast(doc?.customSortNumber) === 1,
+            setDoc: (doc: Doc, dv: DocumentView) => {
                 doc.cardSort = "custom";
                 doc.customSortNumber = 1;
-                console.log(doc.customSortNumber + " numberrrrrrrr")
-            }        }],
-
+                doc.visibleGroupNumbers = new List<number>();
+            }
+        }],
         ['custom3', {
-            checkResult: (doc:Doc) => StrCast(doc?.cardSort),
-            setDoc: (doc, dv) => {
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && NumCast(doc?.customSortNumber) === 2,
+            setDoc: (doc: Doc, dv: DocumentView) => {
                 doc.cardSort = "custom";
                 doc.customSortNumber = 2;
-            }        }],
+                doc.visibleGroupNumbers = new List<number>();
+            }
+        }],
         ['chat', {
-            checkResult: (doc:Doc) => StrCast(doc?.cardSort),
-            setDoc: (doc:Doc,dv:DocumentView) => doc.cardSort = "chat",
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "chat",
+            setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "chat",
+        }],
+        ['1', {
+            checkResult: (doc: Doc) => NumListCast(doc?.visibleGroupNumbers).includes(0),
+            setDoc: (doc: Doc, dv: DocumentView) => {
+                let list = NumListCast(doc.visibleGroupNumbers);
+                if (list.includes(0)) {
+                    let newList = new List<number>(list.filter(d => d !== 0));
+                    doc.visibleGroupNumbers = newList;
+                } else {
+                    list.push(0);
+                    doc.visibleGroupNumbers = new List<number>(list);
+                }
+            }
         }],
-   ]);
+        ['2', {
+            checkResult: (doc: Doc) => NumListCast(doc?.visibleGroupNumbers).includes(1),
+            setDoc: (doc: Doc, dv: DocumentView) => {
+                let list = NumListCast(doc.visibleGroupNumbers);
+                if (list.includes(1)) {
+                    let newList = new List<number>(list.filter(d => d !== 1));
+                    doc.visibleGroupNumbers = newList;
+                } else {
+                    list.push(1);
+                    doc.visibleGroupNumbers = new List<number>(list);
+                }
+            }
+        }],
+        ['3', {
+            checkResult: (doc: Doc) => NumListCast(doc?.visibleGroupNumbers).includes(2),
+            setDoc: (doc: Doc, dv: DocumentView) => {
+                let list = NumListCast(doc.visibleGroupNumbers);
+                if (list.includes(2)) {
+                    let newList = new List<number>(list.filter(d => d !== 2));
+                    doc.visibleGroupNumbers = newList;
+                } else {
+                    list.push(2);
+                    doc.visibleGroupNumbers = new List<number>(list);
+                }
+            }
+        }],
+        ['4', {
+            checkResult: (doc: Doc) => NumListCast(doc?.visibleGroupNumbers).includes(3),
+            setDoc: (doc: Doc, dv: DocumentView) => {
+                let list = NumListCast(doc.visibleGroupNumbers);
+                if (list.includes(3)) {
+                    let newList = new List<number>(list.filter(d => d !== 3));
+                    doc.visibleGroupNumbers = newList;
+                } else {
+                    list.push(3);
+                    doc.visibleGroupNumbers = new List<number>(list);
+                }
+            }
+        }],
+    ]);
 
     if (checkResult) {
         return map.get(attr)?.checkResult(selected);
@@ -194,6 +257,14 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid
     setTimeout(() => batch.end(), 100);
 });
 
+
+ScriptingGlobals.add(function isThatCardGroup(n: number){
+    const canvas = SelectionManager.Docs.lastElement();
+    return canvas.customSortNumber == n
+
+
+})
+
 // ScriptingGlobals.add(function setCardSortAttr(attr: 'time' | 'docType' | 'color', value: any, checkResult?: boolean) {
 //     // const editorView = RichTextMenu.Instance?.TextView?.EditorView;
 //     const selected = SelectionManager.Docs.lastElement();
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index 4425cf158..551c8e5af 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -55,10 +55,10 @@ $highlightedText: #82e0ff;
         overflow-y: auto;
     }
 
-    .btns-wrapper {
+    .btns-wrapper-gpt {
         height: 50px;
         display: flex;
-        justify-content: space-between;
+        justify-content: center;
         align-items: center;
         transform: translateY(30px);
 
@@ -76,6 +76,8 @@ $highlightedText: #82e0ff;
             display: flex;
             align-items: center;
         }
+
+        
     }
 
     button {
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index faf66af85..d94be1860 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -154,6 +154,13 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     }
 
     @observable onSortComplete?: (sortResult: string) => void;
+    @observable cardsDoneLoading = false;
+
+    @action setCardsDoneLoading(done: boolean) {
+        console.log(done + "HI HIHI")
+        this.cardsDoneLoading = done;
+    }
+
 
 
 
@@ -169,12 +176,14 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         this.setSortDone(false);
 
         try {
-            const res = await gptAPICall(this.sortPrompt + this.sortDesc, GPTCallType.SORT);
+
+            const res = await gptAPICall(this.sortDesc, GPTCallType.SORT);
             this.setSortText(res || 'Something went wrong :(');
 
             // Trigger the callback with the result
             if (this.onSortComplete) {
                 this.onSortComplete(res || 'Something went wrong :(');
+                console.log(res)
             }
         } catch (err) {
             console.error(err);
@@ -313,52 +322,63 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
                 {this.heading("SORTING")}
                 {this.loading ? (
                     <div className="content-wrapper">
-                        <div className="loading-spinner">
-                            <ReactLoading type="spin" color="#000" height={30} width={30} />
-                            <span>Loading...</span>
-                        </div>
-                    </div>
-                ) : !this.sortDone && (
-                    <div className="btns-wrapper">
-                        <input
-                            defaultValue=""
-                            autoComplete="off"
-                            onChange={this.setSortPrompt}
-                            onKeyDown={e => {
-                                if (e.key === 'Enter') {
-                                    this.generateSort();
-                                }
-                                e.stopPropagation();
-                            }}
-                            type="text"
-                            placeholder="Enter sorting instructions..."
-                            id="search-input"
-                            className="searchBox-input"
-                            style={{ width: "100%" }}
-                        />
+                    <div className="loading-spinner">
+                        <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} />
+                        <span>Loading...</span>
                     </div>
-                )}
+                </div>
+                ) : (
+                    <>
+                        {!this.cardsDoneLoading ? (
+                            <div className="content-wrapper">
+                                <div className="loading-spinner">
+                                    <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} />
+                                    <span>Reading Cards...</span>
+                                </div>
+                            </div>
+                        ) : (
+                            !this.sortDone && (
+                                <div className="btns-wrapper-gpt">
+                                    <Button
+                                        tooltip="Have ChatGPT sort your cards for you!"
+                                        text="Sort!"
+                                        onClick={this.generateSort}
+                                        color={StrCast(Doc.UserDoc().userVariantColor)}
+                                        type={Type.TERT}
+                                        style={{
+                                            width: '90%', // Almost as wide as the container
+                                            textAlign: 'center',
+                                            color: '#ffffff', // White text
+                                            fontSize: '16px' // Adjust font size as needed
+                                        }}
+                                    />
+                                </div>
+                            )
+                        )}
     
-                {this.sortDone && (
-                    <div>
-                        <div className="content-wrapper">
-
-                        <p>{this.text== "Something went wrong :(" ? "Something went wrong :(" : "Sorting done! Feel free to move things around / regenerate :) !"}</p>
-                       
-
-                            <IconButton
-                                tooltip="Generate Again"
-                                onClick={() => this.setSortDone(false)}
-                                icon={<FontAwesomeIcon icon="redo-alt" size="lg" />}
-                                color={StrCast(Doc.UserDoc().userVariantColor)}
-                            />
-
-                    </div>  
-                    </div>
+                        {this.sortDone && (
+                            <div>
+                                <div className="content-wrapper">
+                                    <p>
+                                        {this.text === "Something went wrong :(" ? 
+                                        "Something went wrong :(" : 
+                                        "Sorting done! Feel free to move things around / regenerate :) !"}
+                                    </p>
+                                    <IconButton
+                                        tooltip="Generate Again"
+                                        onClick={() => this.setSortDone(false)}
+                                        icon={<FontAwesomeIcon icon="redo-alt" size="lg" />}
+                                        color={StrCast(Doc.UserDoc().userVariantColor)}
+                                    />
+                                </div>
+                            </div>
+                        )}
+                    </>
                 )}
             </div>
         </>
     );
+
     
     
 
-- 
cgit v1.2.3-70-g09d2


From 0b32642fc9c2d33810252cd90e45b8b505ef49b2 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Sat, 18 May 2024 00:16:31 -0400
Subject: done :)

---
 src/client/documents/Documents.ts                  |  14 +-
 .../views/collections/CollectionCardDeckView.tsx   | 188 +++++++++++----------
 2 files changed, 112 insertions(+), 90 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index eded2b485..80774f4ad 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -501,8 +501,20 @@ export class DocumentOptions {
 
     cardSort?: STRt = new StrInfo('way cards are sorted in deck view');
     customSortNumber?: NUMt = new NumInfo('number of custom sorts the user has created');
-    customHashMap?: List<string>
+    // customGroup1?: List<Doc>
+    // customGroup2?: List<Doc>
+    // customGroup3?: List<Doc>
     visibleGroupNumbers?: List<number>
+    custom1Group?: NUMt = new NumInfo('Which group a card is in for the 1st custom grouping');
+    custom2Group?: NUMt = new NumInfo('Which group a card is in for the 2nd custom grouping');
+    custom3Group?: NUMt = new NumInfo('Which group a card is in for the 3rd custom grouping');
+    chatGroup?: NUMt = new NumInfo("Which group a card is in for the chat's grouping");
+    chatAmGroups?: NUMt = new NumInfo("Number of cards created by chat sort");
+
+
+
+
+
 
     // card_sort_time?: BOOLt = new BoolInfo('whether sorting cards in deck view by time');
     // card_sort_type?: BOOLt = new BoolInfo('whether sorting cards in deck view by type');
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 42c82ca74..c719c9e19 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -33,7 +33,8 @@ export class CollectionCardView extends CollectionSubView() {
     // documents themselves
 
     @observable hoveredNodeIndex = -1;
-    //index to group
+
+    //key is the index in the child pair list, value is the id# for the group its in
     @observable customGroupDictionary: Map<number, number>[] = [new Map<number, number>(), new Map<number, number>(), new Map<number, number>()];
 
 
@@ -43,9 +44,9 @@ export class CollectionCardView extends CollectionSubView() {
 
         // console.log("Active Groups:", activeGroups);
 
-        for (let i=0; i< activeGroups.length; i++){
-            console.log("Active Groups" + activeGroups[i])
-        }
+        // for (let i=0; i< activeGroups.length; i++){
+        //     console.log("Active Groups" + activeGroups[i])
+        // }
         // console.log("Current Custom Sort Number:", currCustom);
 
         if (activeGroups.length <= 0) {
@@ -121,10 +122,32 @@ export class CollectionCardView extends CollectionSubView() {
         super(props);
         makeObservable(this);
         // this.rotationDegree(7);
+        const pairs = this.childLayoutPairs.filter(d => d.layout.type != DocumentType.LINK);
+
 
-        if (this._props.Document.customHashMap != undefined){
-            this.customGroupDictionary = this.getCustoms(StrListCast(this._props.Document.customHashMap))
+        for (let i=0; i< pairs.length; i++){
+            if (pairs[i].layout.custom1Group != undefined){
+                this.customGroupDictionary[0].set(i, NumCast(pairs[i].layout.custom1Group))
+            }
+
+            if (pairs[i].layout.custom2Group != undefined){
+                this.customGroupDictionary[1].set(i, NumCast(pairs[i].layout.custom2Group))
+            }
+
+            if (pairs[i].layout.custom3Group != undefined){
+                this.customGroupDictionary[2].set(i, NumCast(pairs[i].layout.custom3Group))
+            }
+
+            if (pairs[i].layout.chatGroup != undefined){
+                this.gptGroups.set(pairs[i].layout, NumCast(pairs[i].layout.chatGroup))
+                this.amGPTGroups = NumCast(this._props.Document.chatAmGroups)
+
+            }
         }
+
+        // if (this._props.Document.customHashMap != undefined){
+        //     this.customGroupDictionary = this.convertListsToArrayOfMaps(DocListCast(this._props.Document.customGroup1),DocListCast(this._props.Document.customGroup2), DocListCast(this._props.Document.customGroup3))
+        // }
         // this._props.Document.visibleGroupNumbers = new List<number>([1,2,3,4])
 
 
@@ -139,32 +162,52 @@ export class CollectionCardView extends CollectionSubView() {
         );
     }
 
+    //converts the customGroupDictionary into a Lists of Docs
+    mapToField(groupNumber: number) {
+        const groupList = new List<Doc>();
+    
+        const pairs = this.childLayoutPairs.filter(d => d.layout.type != DocumentType.LINK);
+        console.log(groupNumber+ "group#")
+    
+        this.customGroupDictionary[groupNumber].forEach((group, index) => {
+            const doc = pairs[index].layout;
+            groupList.push(doc);
+        });
+    
+        this._props.Document[`group${groupNumber}`] = groupList;
+    }
 
-    @computed get mapToField(): List<string> {
-        const resultList = new List<string>(); 
-
-        this.customGroupDictionary.forEach(map => {
-            // Convert each map to a single string with entries formatted as "key:value"
-            const mapString = Array.from(map.entries())
-                                   .map(([key, value]) => `${key}:${value}`)
-                                   .join(','); // Join all key-value pairs with commas
-            resultList.push(mapString); // Add the formatted string of the current map to the list
+    // This function converts three Lists of Docs back into an array of maps
+    convertListsToArrayOfMaps = (group0: Doc[], group1: Doc[], group2: Doc[]): Map<number, number>[] => {
+        const mapsArray: Map<number, number>[] = [
+            new Map<number, number>(),
+            new Map<number, number>(),
+            new Map<number, number>()
+        ];
+        const layoutPairs = this.childLayoutPairs.filter(d => d.layout.type != DocumentType.LINK)
+
+        group0.forEach((doc, index) => {
+            const docIndex = layoutPairs.findIndex(pair => pair.layout === doc);
+            if (docIndex !== -1) {
+                mapsArray[0].set(docIndex, 0);
+            }
         });
 
-        return resultList;
-    }
+        group1.forEach((doc, index) => {
+            const docIndex = layoutPairs.findIndex(pair => pair.layout === doc);
+            if (docIndex !== -1) {
+                mapsArray[1].set(docIndex, 1);
+            }
+        });
 
-    getCustoms = (stringFrom: string[]): Map<number, number>[] => {
-        return stringFrom.map(s => {
-            // Create a new Map object for each string.
-            const map = new Map<number, number>();
-            // Split the string by commas to get key-value pairs, then process each pair.
-            s.split(',').forEach(pair => {
-                const [key, value] = pair.split(':');
-                map.set(Number(key), Number(value));
-            });
-            return map;
+        group2.forEach((doc, index) => {
+            const docIndex = layoutPairs.findIndex(pair => pair.layout === doc);
+            if (docIndex !== -1) {
+                mapsArray[2].set(docIndex, 2);
+            }
         });
+
+        return mapsArray;
     }
 
 
@@ -278,17 +321,6 @@ export class CollectionCardView extends CollectionSubView() {
 
 
     @computed get sortedDocsType() {
-        // if (this._props.Document.cardSort === 'chat'){
-        //     this.openChatPopup()
-        //     const textDesc = await this.childPairStringList();
-        //     GPTPopup.Instance.setSortDesc(textDesc)
-        // }
-
-        // if (this._props.Document.cardSort === 'chat' && this.sortedDocs.length === 0) {
-
-        //     this.smartSort(); // Trigger the sorting if it hasn't been done yet
-        //     return { docs: [] }; // Return an empty array or a loading state
-        // }
 
     
         const desc = BoolCast(this.layoutDoc.sortDesc);
@@ -374,9 +406,9 @@ export class CollectionCardView extends CollectionSubView() {
 
                 // console.log(this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)])
                     typeA = this.gptGroups.get(docA.layout) ?? '9999';
-                    console.log(typeA + "typea")
+                    // console.log(typeA + "typea")
                     typeB = this.gptGroups.get(docB.layout) ?? '9999';
-                    console.log(typeB + "typeB")
+                    // console.log(typeB + "typeB")
 
 
                     // console.log(typeA + "A")
@@ -539,35 +571,45 @@ export class CollectionCardView extends CollectionSubView() {
                     onMouseEnter={() => this.setHoveredNodeIndex(index)}>
                     {this.displayDoc(childPair, childScreenToLocal)}
 
-                    {this._props.Document.cardSort == 'custom' ? this.renderButtons(childPairIndex) : ''}
+                    {this._props.Document.cardSort == 'custom' ? this.renderButtons(childPairIndex, childPair.layout, false) : ''}
                     {this._props.Document.cardSort == 'chat' ? this.renderButtons(childPairIndex, childPair.layout, true) : ''}
 
                 </div>
             );
         });
     }
+  
 
-    
-
-   
-    
-    
-
-    @action toggleButton(childPairIndex: number, buttonID: number, isChat = false, doc?: Doc) {
+    @action toggleButton(childPairIndex: number, buttonID: number, isChat = false, doc: Doc) {
         if (!isChat) {
-            this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].set(childPairIndex, buttonID);
-            this._props.Document.customHashMap = this.mapToField;
+            const sortNumber = NumCast(this._props.Document.customSortNumber)
+            this.customGroupDictionary[sortNumber].set(childPairIndex, buttonID)
+            switch (sortNumber){
+                case 0:
+                    doc.custom1Group = buttonID
+                    break
+                case 1:
+                    doc.custom2Group = buttonID
+                    break
+                case 2:
+                    doc.custom3Group = buttonID
+                    break
+                default:
+                    break
+            }
+            // this.mapToField(buttonID);
         }
     
         if (isChat && doc) {
             this.gptGroups.set(doc, buttonID);
+            doc.chatGroup = buttonID
         }
     
         // console.log(`Toggled button for childPairIndex: ${childPairIndex}, buttonID: ${buttonID}, isChat: ${isChat}`);
         // console.log(`Updated customGroupDictionary:`, this.customGroupDictionary);
-        if (isChat && doc) {
-            console.log(`Updated gptGroups for doc ${doc}:`, this.gptGroups.get(doc));
-        }
+        // if (isChat && doc) {
+        //     // console.log(`Updated gptGroups for doc ${doc}:`, this.gptGroups.get(doc));
+        // }
     }
     
 
@@ -645,39 +687,6 @@ export class CollectionCardView extends CollectionSubView() {
         }
     }
 
-   
-
-    //a set of all the images that have already been given desc
-    //a map from the text to the Doc (for everything)
-    
-
-    // @action async smartSort() {
-    //     this.isLoading = true;
-    //     console.log("loading");
-        
-    //     // Store the result of childPairStringList in a variable
-    //     const childPairStrings = await this.childPairStringList();
-        
-    //     if (childPairStrings === "") {
-    //         console.log("no child pairs :(");
-    //     } else {
-    //         console.log(childPairStrings + " og list");
-    //         let prompt = childPairStrings;
-        
-    //         let res = await gptAPICall(prompt, GPTCallType.SORT);
-    //         this.isLoading = false;
-    //         if (res == 'Error connecting with API.') {
-    //             // If GPT call failed
-    //             console.error('GPT call failed');
-    //         } else if (res != null) {
-    //             console.log(res);
-    //             this.processGptOutput(res);
-    //             // Update the observable with the sorted documents
-    //             this.sortedDocs = this.sort(this.myChildLayoutPairs, 'gpt', BoolCast(this.layoutDoc.sortDesc)).docs;
-    //         }
-    //         this.isLoading = false;
-    //     }
-    // }
 
     gptGroups = new ObservableMap<Doc, number>
     @observable amGPTGroups = 0
@@ -688,6 +697,7 @@ export class CollectionCardView extends CollectionSubView() {
         // Split the string into individual list items
         const listItems = gptOutput.split('======').filter(item => item.trim() !== '');
         this.amGPTGroups = listItems.length
+        this._props.Document.chatAmGroups = this.amGPTGroups
 
         listItems.forEach((item, index) => {
             // Split the item by '~~~~~~' to get all descriptors
@@ -730,7 +740,7 @@ export class CollectionCardView extends CollectionSubView() {
 
     
 
-    renderButtons(childPairIndex: number, doc?: Doc, isChat = false) {
+    renderButtons(childPairIndex: number, doc: Doc, isChat = false) {
         const buttons = []; // Array to hold the button elements
         const groupNumber = NumCast(this._props.Document.customSortNumber);
     
@@ -744,11 +754,11 @@ export class CollectionCardView extends CollectionSubView() {
             activeButtonIndex = this.gptGroups.get(doc);
         }
     
-        console.log("childPairIndex:", childPairIndex, "activeButtonIndex:", activeButtonIndex, "groupNumber:", groupNumber);
+        // console.log("childPairIndex:", childPairIndex, "activeButtonIndex:", activeButtonIndex, "groupNumber:", groupNumber);
     
         for (let i = 0; i < amButtons; i++) {
             const isActive = activeButtonIndex === i;
-            console.log(`Rendering button ${i} for childPairIndex ${childPairIndex} isActive: ${isActive}`);
+            // console.log(`Rendering button ${i} for childPairIndex ${childPairIndex} isActive: ${isActive}`);
             buttons.push(
                 <button
                     key={i}
-- 
cgit v1.2.3-70-g09d2


From e168e0e880db808589d4c0a85e37c30b177bdb8e Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Sat, 18 May 2024 01:33:27 -0400
Subject: comments lock in

---
 src/Utils.ts                                       |   1 +
 .../views/collections/CollectionCardDeckView.tsx   | 323 ++++++++-------------
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |  17 --
 3 files changed, 119 insertions(+), 222 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 291d7c799..fb72a5836 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -179,6 +179,7 @@ export namespace Utils {
     export function toRGBAstr(col: { r: number; g: number; b: number; a?: number }) {
         return 'rgba(' + col.r + ',' + col.g + ',' + col.b + (col.a !== undefined ? ',' + col.a : '') + ')';
     }
+    
 
     export function HSLtoRGB(h: number, s: number, l: number) {
         // Must be fractions of 1
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index c719c9e19..8a63fc6d9 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -25,30 +25,23 @@ import { GPTPopup } from '../pdf/GPTPopup/GPTPopup';
 import { GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
 import { reaction } from 'mobx';
 import { NumListCast } from '../../../fields/Doc';
+import { undoBatch } from '../../util/UndoManager';
 @observer
 export class CollectionCardView extends CollectionSubView() {
 
-    //TODO: for saving custom groups instead of string field hashmap nonsense should instead just set teh index for each child document per group as a fie;d
-    // so like, group1Ibndex = 1 etc etc as they change. That way, if the documents change it doesnt matter since the idnexes are tied to the 
-    // documents themselves
-
     @observable hoveredNodeIndex = -1;
 
     //key is the index in the child pair list, value is the id# for the group its in
     @observable customGroupDictionary: Map<number, number>[] = [new Map<number, number>(), new Map<number, number>(), new Map<number, number>()];
 
-
+    /**
+     * The child documents to be rendered-- either all of them except the Links or the docs in the currently active 
+     * custom group
+     */
     @computed get myChildLayoutPairs() {
         let activeGroups = NumListCast(this._props.Document.visibleGroupNumbers);
         let currCustom = NumCast(this._props.Document.customSortNumber);
 
-        // console.log("Active Groups:", activeGroups);
-
-        // for (let i=0; i< activeGroups.length; i++){
-        //     console.log("Active Groups" + activeGroups[i])
-        // }
-        // console.log("Current Custom Sort Number:", currCustom);
-
         if (activeGroups.length <= 0) {
             return this.childLayoutPairs.filter(l => l.layout.type != DocumentType.LINK);
         }
@@ -58,12 +51,8 @@ export class CollectionCardView extends CollectionSubView() {
                 if (l.layout.type === DocumentType.LINK) {
                     return false;
                 }
-
                 // Get the group number for the current index from the customGroupDictionary
                 const groupNumber = this.customGroupDictionary[currCustom].get(index);
-
-                // console.log(`Index: ${index}, Group Number: ${groupNumber}`);
-
                 // Check if the group number is in the active groups
                 return groupNumber !== undefined && activeGroups.includes(groupNumber);
             });
@@ -73,13 +62,18 @@ export class CollectionCardView extends CollectionSubView() {
         return this.childLayoutPairs.filter(l => l.layout.type != DocumentType.LINK);
     }
 
+
     @action
     setHoveredNodeIndex = (index: number) => {
         if (!this.isSelected(index)) {
             this.hoveredNodeIndex = index;
         }
     };
-
+    /**
+     * Translates the hovered node to the center of the screen
+     * @param index 
+     * @returns 
+     */
     translateHover = (index: number): number => {
         if (this.hoveredNodeIndex == index && !this.isSelected(index)) {
             return -50;
@@ -90,30 +84,22 @@ export class CollectionCardView extends CollectionSubView() {
     @action
     setSelectedNodeIndex = (index: number) => {
         const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
-        // console.log('goodnight');
-
         if (SelectionManager.IsSelected(docs[index])) {
-            // console.log('good mornings');
             this.setSelectedNodeIndex(index);
         }
-
-        
-
     };
 
     isSelected = (index: number): boolean => {
         const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
-
         return SelectionManager.IsSelected(docs[index]);
     };
 
+    /**
+     * Returns all the documents except the one that's currently selected
+     */
     inactiveDocs = () => {
-        const docs = this.myChildLayoutPairs;
-
-       
-        return docs.filter(d => !SelectionManager.IsSelected(d.layout));
-
-        
+        const docs = this.myChildLayoutPairs;    
+        return docs.filter(d => !SelectionManager.IsSelected(d.layout));   
     };
 
     middleIndex = Math.floor(this.inactiveDocs().length / 2);
@@ -121,10 +107,9 @@ export class CollectionCardView extends CollectionSubView() {
     constructor(props: any) {
         super(props);
         makeObservable(this);
-        // this.rotationDegree(7);
         const pairs = this.childLayoutPairs.filter(d => d.layout.type != DocumentType.LINK);
 
-
+        //gets the values from the last time the view was loaded and puts them into their respective places in the hashmaps(s)
         for (let i=0; i< pairs.length; i++){
             if (pairs[i].layout.custom1Group != undefined){
                 this.customGroupDictionary[0].set(i, NumCast(pairs[i].layout.custom1Group))
@@ -144,14 +129,6 @@ export class CollectionCardView extends CollectionSubView() {
 
             }
         }
-
-        // if (this._props.Document.customHashMap != undefined){
-        //     this.customGroupDictionary = this.convertListsToArrayOfMaps(DocListCast(this._props.Document.customGroup1),DocListCast(this._props.Document.customGroup2), DocListCast(this._props.Document.customGroup3))
-        // }
-        // this._props.Document.visibleGroupNumbers = new List<number>([1,2,3,4])
-
-
-
         reaction(
             () => this._props.Document.cardSort,
             (cardSort) => {
@@ -162,60 +139,6 @@ export class CollectionCardView extends CollectionSubView() {
         );
     }
 
-    //converts the customGroupDictionary into a Lists of Docs
-    mapToField(groupNumber: number) {
-        const groupList = new List<Doc>();
-    
-        const pairs = this.childLayoutPairs.filter(d => d.layout.type != DocumentType.LINK);
-        console.log(groupNumber+ "group#")
-    
-        this.customGroupDictionary[groupNumber].forEach((group, index) => {
-            const doc = pairs[index].layout;
-            groupList.push(doc);
-        });
-    
-        this._props.Document[`group${groupNumber}`] = groupList;
-    }
-
-    // This function converts three Lists of Docs back into an array of maps
-    convertListsToArrayOfMaps = (group0: Doc[], group1: Doc[], group2: Doc[]): Map<number, number>[] => {
-        const mapsArray: Map<number, number>[] = [
-            new Map<number, number>(),
-            new Map<number, number>(),
-            new Map<number, number>()
-        ];
-        const layoutPairs = this.childLayoutPairs.filter(d => d.layout.type != DocumentType.LINK)
-
-        group0.forEach((doc, index) => {
-            const docIndex = layoutPairs.findIndex(pair => pair.layout === doc);
-            if (docIndex !== -1) {
-                mapsArray[0].set(docIndex, 0);
-            }
-        });
-
-        group1.forEach((doc, index) => {
-            const docIndex = layoutPairs.findIndex(pair => pair.layout === doc);
-            if (docIndex !== -1) {
-                mapsArray[1].set(docIndex, 1);
-            }
-        });
-
-        group2.forEach((doc, index) => {
-            const docIndex = layoutPairs.findIndex(pair => pair.layout === doc);
-            if (docIndex !== -1) {
-                mapsArray[2].set(docIndex, 2);
-            }
-        });
-
-        return mapsArray;
-    }
-
-
-
-
-
-
-
     private _dropDisposer?: DragManager.DragDropDisposer;
 
     componentWillUnmount() {
@@ -241,31 +164,34 @@ export class CollectionCardView extends CollectionSubView() {
         }
         return (this.childDocumentWidth * length) / this._props.PanelWidth();
     }
+
     panelWidth = () => this.childDocumentWidth;
     panelHeight = (layout: Doc) => () => (2 * (this.panelWidth() * NumCast(layout._height))) / NumCast(layout._width);
     onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
     isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
     isChildContentActive = () => (this.isContentActive() ? true : false);
 
+    /**
+     * Returns the degree to rotate a card dependind on the amount of cards in their row and their index in said row
+     * @param amCards 
+     * @param index 
+     * @returns 
+     */
     rotate = (amCards: number, index: number) => {
-        // console.log(amCards + "wtf")
         const possRotate = -30 + index * (30 / ((amCards - (amCards % 2)) / 2));
         const stepMag = Math.abs(-30 + (amCards / 2 - 1) * (30 / ((amCards - (amCards % 2)) / 2)));
 
-        // console.log(possRotate + "poss")
-
         if (amCards % 2 == 0 && possRotate == 0) {
-            // console.log('whaddup');
             return possRotate + Math.abs(-30 + (index - 1) * (30 / (amCards / 2)));
         } else if (amCards % 2 == 0 && index > (amCards + 1) / 2) {
-            // console.log("sup" + stepMag);
-
             return possRotate + stepMag;
         }
 
         return possRotate;
     };
-
+    /**
+     * Returns the degree to which a card should be translated in the y direction for the arch effect
+     */
     translateY = (amCards: number, index: number, realIndex: number) => {
         const evenOdd = amCards % 2;
         const apex = (amCards - evenOdd) / 2;
@@ -273,14 +199,9 @@ export class CollectionCardView extends CollectionSubView() {
         let rowOffset = 0;
 
         if (realIndex > this.maxRowCount - 1) {
-            // 11 - 1 = 10
             rowOffset = 400 * ((realIndex - (realIndex % this.maxRowCount)) / this.maxRowCount);
         }
-
-        // console.log('steo' + stepMag);
-
         if (evenOdd == 1 || index < apex - 1) {
-            // console.log('hi' + index);
             return Math.abs(stepMag * (apex - index)) - rowOffset;
         } else {
             if (index == apex || index == apex - 1) {
@@ -291,13 +212,11 @@ export class CollectionCardView extends CollectionSubView() {
         }
     };
 
-    // translateOverFlowY = (amCards: number, index: number) => {
-    //     if (amCards > 8 && index > amCards / 2) {
-    //         return 100;
-    //     }
-    //     return 0;
-    // };
-
+    /**
+     * Translates the selected node to the middle fo the screen
+     * @param index 
+     * @returns 
+     */
     translateSelected = (index: number): number => {
         // if (this.isSelected(index)) {
         const middleOfPanel = this._props.PanelWidth() / 2;
@@ -312,17 +231,12 @@ export class CollectionCardView extends CollectionSubView() {
         const translation = middleOfPanel - scaledNodeCenter - scaledNodeWidth - scaledNodeWidth / 4;
 
         return translation;
-        // }
-
-        // return 0;
     };
 
-    @observable sortedDocs = [] as { layout: Doc; data: Doc }[];
-
-
+    /**
+     * Determines the order in which the cards will be rendered depending on the current sort type
+     */
     @computed get sortedDocsType() {
-
-    
         const desc = BoolCast(this.layoutDoc.sortDesc);
         let sorted = [];
         let docs = [];
@@ -348,7 +262,11 @@ export class CollectionCardView extends CollectionSubView() {
                 return { docs };
         }
     }
-
+     /**
+      * Converts a hex # to its hsv color value
+      * @param hex 
+      * @returns 
+      */
     hexToHsv = (hex: string): [number, number, number] => {
         if (!hex) return [0, 0, 0]; // Default to black if hex is not defined
         const r = parseInt(hex.slice(1, 3), 16) / 255;
@@ -381,7 +299,14 @@ export class CollectionCardView extends CollectionSubView() {
         h /= 6;
         return [h, s, v];
     };
-
+    /**
+     * Called in the sortedDocsType method. Compares the cards' value in regards to the desired sort type-- earlier cards are move to the
+     * front, latter cards to the back
+     * @param docs 
+     * @param sortType 
+     * @param isDesc 
+     * @returns 
+     */
     sort = (docs: { layout: Doc; data: Doc }[], sortType: string, isDesc: boolean) => {
         docs.sort((docA, docB) => {
             let typeA;
@@ -394,26 +319,13 @@ export class CollectionCardView extends CollectionSubView() {
                     break;
 
                 case 'custom':
-
-                // console.log(this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)])
                     typeA = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(docs.indexOf(docA)) ?? '9999';
-                    // console.log(typeA + "A")
                     typeB = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(docs.indexOf(docB)) ?? '9999';
-                    // console.log(typeB + 'b')
                     break;   
                     
                 case 'gpt':
-
-                // console.log(this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)])
                     typeA = this.gptGroups.get(docA.layout) ?? '9999';
-                    // console.log(typeA + "typea")
                     typeB = this.gptGroups.get(docB.layout) ?? '9999';
-                    // console.log(typeB + "typeB")
-
-
-                    // console.log(typeA + "A")
-                    // typeB = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(docs.indexOf(docB)) ?? '9999';
-                    // console.log(typeB + 'b')
                     break;  
 
                 default:
@@ -433,9 +345,6 @@ export class CollectionCardView extends CollectionSubView() {
         return { docs };
     };
 
-    // @computed get cards(){
-
-    // }
 
     displayDoc = (childPair: { layout: Doc; data: Doc }, screenToLocalTransform: () => Transform) => {
         return (
@@ -444,8 +353,6 @@ export class CollectionCardView extends CollectionSubView() {
                 ref={action((r: DocumentView) => r?.ContentDiv && this.docRefs.set(childPair.layout, r))}
                 Document={childPair.layout}
                 TemplateDataDocument={childPair.data}
-                // onClickScript={this.toggleIcon}
-                //suppressSetHeight={true}
                 NativeWidth={returnZero}
                 NativeHeight={returnZero}
                 layout_fitWidth={returnFalse}
@@ -453,7 +360,6 @@ export class CollectionCardView extends CollectionSubView() {
                 renderDepth={this._props.renderDepth + 1}
                 LayoutTemplate={this._props.childLayoutTemplate}
                 LayoutTemplateString={this._props.childLayoutString}
-                // focus={this.focus}
                 ScreenToLocalTransform={screenToLocalTransform} //makes sure the box wrapper thing is in the right spot
                 isContentActive={this.isChildContentActive}
                 isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
@@ -467,6 +373,11 @@ export class CollectionCardView extends CollectionSubView() {
 
     @observable maxRowCount = 10;
 
+    /**
+     * Determines how many cards are in the row of a card at a specific index
+     * @param index 
+     * @returns 
+     */
     overflowAmCardsCalc(index: number) {
         if (this.inactiveDocs().length < this.maxRowCount) {
             return this.inactiveDocs().length;
@@ -480,7 +391,11 @@ export class CollectionCardView extends CollectionSubView() {
         //(3)
         return totalCards % 10;
     }
-
+    /**
+     * Determines the index a card is in in a row
+     * @param realIndex 
+     * @returns 
+     */
     overflowIndexCalc(realIndex: number) {
         if (realIndex < 10) {
             return realIndex;
@@ -488,21 +403,29 @@ export class CollectionCardView extends CollectionSubView() {
 
         return realIndex % 10;
     }
-
+    /**
+     * Translates the cards in the second rows and beyond over to the right
+     * @param realIndex 
+     * @param calcIndex 
+     * @param calcRowCards 
+     * @returns 
+     */
     translateOverflowX(realIndex: number, calcIndex: number, calcRowCards: number) {
-
-        let trans = 0;
-
-        // trans += (calcIndex * this.panelWidth()) + this.panelWidth()
-
-        // return trans
         if (realIndex < this.maxRowCount) {
             return 0;
         }
 
         return (10 - calcRowCards) * (this.panelWidth() / 2);
     }
-
+    /**
+     * Determines how far to translate a card in the y direction depending on its index, whether or not its being hovered, or if it's selected
+     * @param isHovered 
+     * @param isSelected 
+     * @param realIndex 
+     * @param amCards 
+     * @param calcRowIndex 
+     * @returns 
+     */
     calculateTranslateY(isHovered: boolean, isSelected: boolean, realIndex: number, amCards: number, calcRowIndex: number) {
         let trans = 0;
         if (isHovered) {
@@ -510,25 +433,23 @@ export class CollectionCardView extends CollectionSubView() {
         }
 
         trans += this.translateY(amCards, calcRowIndex, realIndex);
-
         if (isSelected) {
             trans = 50 * this.fitContentScale;
         }
-
         return trans;
     }
 
     @observable _forceChildXf = false;
+
+    /**
+     * Actually renders all the cards
+     */
     @computed get contentSorted() {
         const sortedDocs = this.sortedDocsType.docs;
-        // const amCards = this.inactiveDocs().length;
-
         // Map sorted documents to their rendered components
         return sortedDocs.map((childPair, index) => {
-            // const childPair = { layout: doc, data: doc };
 
             const isHovered = this.hoveredNodeIndex === index;
-            // const childPairIndex = this.myChildLayoutPairs.indexOf(childPair);
             const childPairIndex  = this.childLayoutPairs.filter(d => d.layout.type != DocumentType.LINK).indexOf(childPair)
             const realIndex = this.sortedDocsType.docs.filter(d => !SelectionManager.IsSelected(d.layout)).indexOf(childPair);
             const calcRowIndex = this.overflowIndexCalc(realIndex);
@@ -578,8 +499,16 @@ export class CollectionCardView extends CollectionSubView() {
             );
         });
     }
-  
 
+    /**
+     * Toggles the buttons between on and off when creating custom sort groupings/changing those created by gpt 
+     * @param childPairIndex 
+     * @param buttonID 
+     * @param isChat 
+     * @param doc 
+     */
+  
+    @undoBatch
     @action toggleButton(childPairIndex: number, buttonID: number, isChat = false, doc: Doc) {
         if (!isChat) {
             const sortNumber = NumCast(this._props.Document.customSortNumber)
@@ -597,22 +526,20 @@ export class CollectionCardView extends CollectionSubView() {
                 default:
                     break
             }
-            // this.mapToField(buttonID);
         }
     
         if (isChat && doc) {
             this.gptGroups.set(doc, buttonID);
             doc.chatGroup = buttonID
         }
-    
-        // console.log(`Toggled button for childPairIndex: ${childPairIndex}, buttonID: ${buttonID}, isChat: ${isChat}`);
-        // console.log(`Updated customGroupDictionary:`, this.customGroupDictionary);
-        // if (isChat && doc) {
-        //     // console.log(`Updated gptGroups for doc ${doc}:`, this.gptGroups.get(doc));
-        // }
     }
     
-
+    /**
+     * A list of the text content of all the child docs. RTF documents will have just their text and pdf documents will have the first 50 words. 
+     * Image documents are converted to bse64 and gpt generates a description for them. all other documents use their title. This string is
+     * inputted into the gpt prompt to sort everything together 
+     * @returns 
+     */
     async childPairStringList(): Promise<string> {
         let string = "";
         for (let i = 0; i < this.childDocs.length; i++) {
@@ -645,7 +572,8 @@ export class CollectionCardView extends CollectionSubView() {
     }
 
     @observable isLoading = false
-
+    
+   
     imageUrlToBase64 = async (imageUrl: string): Promise<string> => {
         try {
             const response = await fetch(imageUrl);
@@ -666,6 +594,11 @@ export class CollectionCardView extends CollectionSubView() {
     textToDoc = new Map<string, Doc>()
     gptProccessedImages = new Set<Doc>()
 
+    /**
+     * Calls the gpt API to generate descriptions for the images in the view
+     * @param image 
+     * @returns 
+     */
     @action async getImageDesc(image: Doc) {
         if (this.gptProccessedImages.has(image)) {
             // Return the already processed description
@@ -687,11 +620,14 @@ export class CollectionCardView extends CollectionSubView() {
         }
     }
 
-
+    //child doc to its group determind by gpt
     gptGroups = new ObservableMap<Doc, number>
     @observable amGPTGroups = 0
-
-     // Method to convert the GPT-produced string into a map
+    
+    /**
+     * Converts the gpt output into a hashmap that can be used for sorting. lists are seperated by ==== while elements within the list are seperated by ~~~~~~
+     * @param gptOutput 
+     */
     processGptOutput(gptOutput: string) {
 
         // Split the string into individual list items
@@ -708,9 +644,7 @@ export class CollectionCardView extends CollectionSubView() {
                 // Find the corresponding Doc in the textToDoc map
                 if (this.textToDoc.has(part)) {
                     const doc = this.textToDoc.get(part);
-                    console.log("hewoooooo")
                     if (doc) {
-                        console.log('hi')
                         this.gptGroups.set(doc, index);
                     }
                 }
@@ -719,7 +653,9 @@ export class CollectionCardView extends CollectionSubView() {
     }
 
     @observable isChatPopupOpen = false;
-
+    /**
+     * Opens up the chat popup and starts the process for smart sorting. 
+     */
     @action openChatPopup = async () => {
         this.isChatPopupOpen = true;
         GPTPopup.Instance.setVisible(true);
@@ -733,18 +669,20 @@ export class CollectionCardView extends CollectionSubView() {
 };
     @action handleGptSortResult = (sortResult: string) => {
         this.processGptOutput(sortResult);
-        this.sortedDocs = this.sort(this.myChildLayoutPairs, 'gpt', BoolCast(this.layoutDoc.sortDesc)).docs;
     };
 
-
-
-    
-
+    /**
+     * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups 
+     * @param childPairIndex 
+     * @param doc 
+     * @param isChat 
+     * @returns 
+     */
     renderButtons(childPairIndex: number, doc: Doc, isChat = false) {
-        const buttons = []; // Array to hold the button elements
+        const buttons = [];
         const groupNumber = NumCast(this._props.Document.customSortNumber);
     
-        let amButtons = 4; // Adjusted to your context
+        let amButtons = 4;
         let activeButtonIndex = this.customGroupDictionary[groupNumber].get(childPairIndex);
     
         if (isChat && doc) {
@@ -754,11 +692,8 @@ export class CollectionCardView extends CollectionSubView() {
             activeButtonIndex = this.gptGroups.get(doc);
         }
     
-        // console.log("childPairIndex:", childPairIndex, "activeButtonIndex:", activeButtonIndex, "groupNumber:", groupNumber);
-    
         for (let i = 0; i < amButtons; i++) {
             const isActive = activeButtonIndex === i;
-            // console.log(`Rendering button ${i} for childPairIndex ${childPairIndex} isActive: ${isActive}`);
             buttons.push(
                 <button
                     key={i}
@@ -783,30 +718,8 @@ export class CollectionCardView extends CollectionSubView() {
         if (this.inactiveDocs().length != this.myChildLayoutPairs.length && this.inactiveDocs().length < 10) {
             translate += this.panelWidth() / 2;
         }
-
-        // console.log('in' + this.inactiveDocs().length);
-        // console.log('max' + this.maxRowCount);
-
-        // if (this.inactiveDocs().length > this.maxRowCount){
-        //     translate += ((this.inactiveDocs().length % this.maxRowCount) * this.panelWidth()) / 2
-        //     console.log("trans" + (this.inactiveDocs().length % this.maxRowCount) / 2 )
-        // }
-
         return translate;
     }
-
-    // @computed get renderCardsSort(){
-    //     if (StrCast(this._props.Document.cardSort) == "type"){
-    //         return this.contentSorted
-    //     }
-
-    //     else{
-    //         return this.content
-    //     }
-    // }
-
-   
-
     render() {
         return (
             <div
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index d94be1860..3d138fa7d 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -132,20 +132,6 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         this.textAnchor = anchor;
     };
 
-    @observable
-    public sortPrompt: string = ''; 
-    @action
-    public setSortPrompt = ((e: React.ChangeEvent<HTMLInputElement>) => {
-        this.sortPrompt = e.target.value;
-    });
-
-    @observable
-    public sortText: string = ''
-    @action
-    public setSortText = (t: string) => {
-        this.sortText = t
-    }
-
     @observable
     public sortDesc: string = ''
 
@@ -178,8 +164,6 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         try {
 
             const res = await gptAPICall(this.sortDesc, GPTCallType.SORT);
-            this.setSortText(res || 'Something went wrong :(');
-
             // Trigger the callback with the result
             if (this.onSortComplete) {
                 this.onSortComplete(res || 'Something went wrong :(');
@@ -187,7 +171,6 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
             }
         } catch (err) {
             console.error(err);
-            this.setSortText('Something went wrong :(');
         }
 
         this.setLoading(false);
-- 
cgit v1.2.3-70-g09d2


From 68b00ed7e782fd237cdcdcb30f55814874854187 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Sat, 18 May 2024 02:49:43 -0400
Subject: perchance time to pull

---
 src/client/util/CurrentUserUtils.ts                |  6 +++---
 .../views/collections/CollectionCardDeckView.tsx   | 23 ++++++++++++++++++----
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |  4 +++-
 3 files changed, 25 insertions(+), 8 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 251771e8e..009a07314 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -738,9 +738,9 @@ pie title Minerals in my tap water
                 { title: "Create",   icon:"heart", toolTip:"Create your first custom grouping!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom1",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
                 { title: "Create",   icon:"star", toolTip:"Create your second custom grouping!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom2",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
                 { title: "Create",   icon:"satellite", toolTip:"Create your third custom grouping!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom3",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-                { title: "Custom 1",    icon: "Custom 1",    width: 10,    toolTip: "Set visibilty!",     subMenu: CurrentUserUtils.cardGroupTools("heart"),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!isThatCardGroup(0)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
-                { title: "Custom 2",    icon: "Custom 2",     width: 80,    toolTip: "Set visibilty!",     subMenu: CurrentUserUtils.cardGroupTools("star"),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!isThatCardGroup(1)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
-                { title: "Custom 3",    icon: "Custom 3",      width: 80,   toolTip: "Set visibilty!",     subMenu: CurrentUserUtils.cardGroupTools("satellite"),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!isThatCardGroup(2)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
+                { title: "Custom 1",    icon: "Custom 1",    width: 10,    toolTip: "Set visibilty!",     subMenu: CurrentUserUtils.cardGroupTools("heart"),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!showFreeform("custom1", true)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
+                { title: "Custom 2",    icon: "Custom 2",     width: 80,    toolTip: "Set visibilty!",     subMenu: CurrentUserUtils.cardGroupTools("star"),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!showFreeform("custom2", true)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
+                { title: "Custom 3",    icon: "Custom 3",      width: 80,   toolTip: "Set visibilty!",     subMenu: CurrentUserUtils.cardGroupTools("satellite"),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!showFreeform("custom3", true)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
 
             ]
         }
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 8a63fc6d9..2ca03a40d 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -26,6 +26,7 @@ import { GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
 import { reaction } from 'mobx';
 import { NumListCast } from '../../../fields/Doc';
 import { undoBatch } from '../../util/UndoManager';
+import { CalendarContainer } from 'react-datepicker';
 @observer
 export class CollectionCardView extends CollectionSubView() {
 
@@ -52,7 +53,7 @@ export class CollectionCardView extends CollectionSubView() {
                     return false;
                 }
                 // Get the group number for the current index from the customGroupDictionary
-                const groupNumber = this.customGroupDictionary[currCustom].get(index);
+                const groupNumber = this.getButtonGroup(currCustom, l.layout)
                 // Check if the group number is in the active groups
                 return groupNumber !== undefined && activeGroups.includes(groupNumber);
             });
@@ -319,8 +320,8 @@ export class CollectionCardView extends CollectionSubView() {
                     break;
 
                 case 'custom':
-                    typeA = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(docs.indexOf(docA)) ?? '9999';
-                    typeB = this.customGroupDictionary[NumCast(this._props.Document.customSortNumber)].get(docs.indexOf(docB)) ?? '9999';
+                    typeA = this.getButtonGroup(NumCast(this._props.Document.customSortNumber), docA.layout) ?? '9999'
+                    typeB = this.getButtonGroup(NumCast(this._props.Document.customSortNumber), docB.layout) ?? '9999'
                     break;   
                     
                 case 'gpt':
@@ -411,6 +412,7 @@ export class CollectionCardView extends CollectionSubView() {
      * @returns 
      */
     translateOverflowX(realIndex: number, calcIndex: number, calcRowCards: number) {
+        
         if (realIndex < this.maxRowCount) {
             return 0;
         }
@@ -683,7 +685,7 @@ export class CollectionCardView extends CollectionSubView() {
         const groupNumber = NumCast(this._props.Document.customSortNumber);
     
         let amButtons = 4;
-        let activeButtonIndex = this.customGroupDictionary[groupNumber].get(childPairIndex);
+        let activeButtonIndex = this.getButtonGroup(groupNumber, doc)
     
         if (isChat && doc) {
             if (this.amGPTGroups > 4){
@@ -711,6 +713,19 @@ export class CollectionCardView extends CollectionSubView() {
         );
     }
 
+    getButtonGroup(groupNumber: number, doc: Doc) {
+        switch (groupNumber){
+            case 0:
+                return NumCast(doc.custom1Group)
+            case 1:
+                return NumCast(doc.custom2Group)
+            case 2:
+                return NumCast(doc.custom3Group)
+            default:
+                break
+        }
+    }
+
 
     @computed get translateWrapperX() {
         let translate = 0;
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 3d138fa7d..4fb757d8a 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -156,7 +156,9 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false;
     public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined;
 
-
+    /**
+     * Sorts cards in the CollectionCardDeckView
+     */
     generateSort = async () => {
         this.setLoading(true);
         this.setSortDone(false);
-- 
cgit v1.2.3-70-g09d2


From 184d078644806668a213aa7fd4ed242bb24a2932 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Sat, 18 May 2024 03:20:57 -0400
Subject: done fr perchance

---
 src/client/util/CurrentUserUtils.ts                     | 10 +++++-----
 src/client/views/collections/CollectionCardDeckView.tsx | 15 +++++++++++----
 2 files changed, 16 insertions(+), 9 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 009a07314..89f1375bc 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -738,9 +738,9 @@ pie title Minerals in my tap water
                 { title: "Create",   icon:"heart", toolTip:"Create your first custom grouping!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom1",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
                 { title: "Create",   icon:"star", toolTip:"Create your second custom grouping!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom2",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
                 { title: "Create",   icon:"satellite", toolTip:"Create your third custom grouping!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom3",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-                { title: "Custom 1",    icon: "Custom 1",    width: 10,    toolTip: "Set visibilty!",     subMenu: CurrentUserUtils.cardGroupTools("heart"),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!showFreeform("custom1", true)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
-                { title: "Custom 2",    icon: "Custom 2",     width: 80,    toolTip: "Set visibilty!",     subMenu: CurrentUserUtils.cardGroupTools("star"),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!showFreeform("custom2", true)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
-                { title: "Custom 3",    icon: "Custom 3",      width: 80,   toolTip: "Set visibilty!",     subMenu: CurrentUserUtils.cardGroupTools("satellite"),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!showFreeform("custom3", true)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
+                { title: "Custom 1",    icon: "Custom",    width: 10,    toolTip: "Set visibilty!",     subMenu: CurrentUserUtils.cardGroupTools("heart"),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!showFreeform("custom1", true)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
+                { title: "Custom 2",    icon: "Custom",     width: 80,    toolTip: "Set visibilty!",     subMenu: CurrentUserUtils.cardGroupTools("star"),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!showFreeform("custom2", true)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
+                { title: "Custom 3",    icon: "Custom",      width: 80,   toolTip: "Set visibilty!",     subMenu: CurrentUserUtils.cardGroupTools("satellite"),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!showFreeform("custom3", true)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
 
             ]
         }
@@ -841,8 +841,8 @@ pie title Minerals in my tap water
             { btnList: new List<string>([CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree,
                     CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Multicolumn,
                     CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel,
-                    CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map,
-                    CollectionViewType.Grid, CollectionViewType.NoteTaking, CollectionViewType.Card]),
+                    CollectionViewType.Carousel3D, CollectionViewType.Card, CollectionViewType.Linear, CollectionViewType.Map,
+                    CollectionViewType.Grid, CollectionViewType.NoteTaking, ]),
               title: "Perspective",                    toolTip: "View",                 btnType: ButtonType.DropdownList,                                                                                                                                    ignoreClick: true, width: 100, scripts: { script:  'setView(value, _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()"}},
             { title: "Header",  icon: "heading",       toolTip: "Doc Titlebar Color",   btnType: ButtonType.ColorButton,         expertMode: false,                                                                                                          ignoreClick: true,             scripts: { script:  'return setHeaderColor(value, _readOnly_)'} },
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 2ca03a40d..f2da61844 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -325,8 +325,8 @@ export class CollectionCardView extends CollectionSubView() {
                     break;   
                     
                 case 'gpt':
-                    typeA = this.gptGroups.get(docA.layout) ?? '9999';
-                    typeB = this.gptGroups.get(docB.layout) ?? '9999';
+                    typeA = this.getButtonGroup(99999, docA.layout, true) ?? '9999';
+                    typeB = this.getButtonGroup(99999, docB.layout, true) ?? '9999';
                     break;  
 
                 default:
@@ -648,6 +648,7 @@ export class CollectionCardView extends CollectionSubView() {
                     const doc = this.textToDoc.get(part);
                     if (doc) {
                         this.gptGroups.set(doc, index);
+                        doc.chatGroup = index
                     }
                 }
             });
@@ -691,7 +692,7 @@ export class CollectionCardView extends CollectionSubView() {
             if (this.amGPTGroups > 4){
                 amButtons = this.amGPTGroups;
             }
-            activeButtonIndex = this.gptGroups.get(doc);
+            activeButtonIndex = this.getButtonGroup(9999, doc, true);
         }
     
         for (let i = 0; i < amButtons; i++) {
@@ -713,7 +714,11 @@ export class CollectionCardView extends CollectionSubView() {
         );
     }
 
-    getButtonGroup(groupNumber: number, doc: Doc) {
+    getButtonGroup(groupNumber: number, doc: Doc, isChat?: boolean) {
+        if (isChat){
+            return NumCast(doc.chatGroup)
+        }
+        
         switch (groupNumber){
             case 0:
                 return NumCast(doc.custom1Group)
@@ -724,6 +729,8 @@ export class CollectionCardView extends CollectionSubView() {
             default:
                 break
         }
+
+        
     }
 
 
-- 
cgit v1.2.3-70-g09d2


From 57f0d29c0a783bfa797b8e5584bbf800a1f076de Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Sat, 18 May 2024 03:23:00 -0400
Subject: bed time

---
 src/client/views/collections/CollectionCardDeckView.tsx | 1 -
 1 file changed, 1 deletion(-)

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

diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index f2da61844..b2a94c3c6 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -718,7 +718,6 @@ export class CollectionCardView extends CollectionSubView() {
         if (isChat){
             return NumCast(doc.chatGroup)
         }
-        
         switch (groupNumber){
             case 0:
                 return NumCast(doc.custom1Group)
-- 
cgit v1.2.3-70-g09d2


From 38d2d361aa723917b5721e2635933d2d8b9f483a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 18 May 2024 22:57:22 -0400
Subject: updated cardDeckView code

---
 src/client/util/CurrentUserUtils.ts                |  59 +-
 .../views/collections/CollectionCardDeckView.scss  |  25 +-
 .../views/collections/CollectionCardDeckView.tsx   | 859 ++++++++-------------
 src/client/views/global/globalScripts.ts           | 105 +--
 4 files changed, 373 insertions(+), 675 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 89f1375bc..f2a7f953d 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -715,48 +715,35 @@ pie title Minerals in my tap water
         ]
     }
     static cardTools(): Button[] {
-       
-
-    //     if (view != undefined){
-    //         for (let i=0; i< NumCast(view.layoutDoc.customSortCount); i++){
-    //             customs.push(   
-    //                 { title: "Custom " + i,   icon:"robot", toolTip:"Custom sort option " + i,  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}
-    //             )
-
-    //         }
-    // }
         return [
-            { title: "Time",   icon:"hourglass-half", toolTip:"Sort by most recent document creation",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"time",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Type",   icon:"eye", toolTip:"Sort by document type",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"docType",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Color",   icon:"palette", toolTip:"Sort by document color",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"color",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "Smart Sort",   icon:"robot", toolTip:"Have ChatGPT sort your text-based nodes !",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"chat",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Time",      icon:"hourglass-half", toolTip:"Sort by most recent document creation",      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"time",   funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Type",      icon:"eye",            toolTip:"Sort by document type",                      btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"docType",funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Color",     icon:"palette",        toolTip:"Sort by document color",                     btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"color",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+         ]
+    }
+    static labelTools(): Button[] {
+        return [
+            { title: "AI",    icon:"robot",     toolTip:"Add AI labels",           btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"chat",                  funcs: {hidden:`showFreeform ("chat", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "AIs",   icon:"AI Sort",   toolTip:"Filter AI labels",        subMenu: this.cardGroupTools("chat"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("chat", true)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} },
+            { title: "Like",  icon:"heart",     toolTip:"Add Like labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"like",                  funcs: {hidden:`showFreeform ("like", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Likes", icon:"Likes",     toolTip:"Filter likes", width: 10, subMenu: this.cardGroupTools("heart"),     expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("like", true)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} },
+            { title: "Star",  icon:"star",      toolTip:"Add Star labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"star",                  funcs: {hidden:`showFreeform ("star", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Stars", icon:"Stars",     toolTip:"Filter stars", width: 80, subMenu: this.cardGroupTools("star"),      expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("star", true)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, 
+            { title: "Idea",  icon:"satellite", toolTip:"Add Idea labels",         btnType: ButtonType.ToggleButton,          expertMode: false, toolType:"idea",                  funcs: {hidden:`showFreeform ("idea", true)`},scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "Ideas", icon:"Ideas",     toolTip:"Filter ideas", width: 80, subMenu: this.cardGroupTools("satellite"), expertMode: false, toolType:CollectionViewType.Card, funcs: {hidden:`!showFreeform("idea", true)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} },
         ]
     }
-
-    static customCardTools(): Button[] {
-            return [
-                { title: "Create",   icon:"heart", toolTip:"Create your first custom grouping!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom1",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-                { title: "Create",   icon:"star", toolTip:"Create your second custom grouping!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom2",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-                { title: "Create",   icon:"satellite", toolTip:"Create your third custom grouping!",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"custom3",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-                { title: "Custom 1",    icon: "Custom",    width: 10,    toolTip: "Set visibilty!",     subMenu: CurrentUserUtils.cardGroupTools("heart"),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!showFreeform("custom1", true)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
-                { title: "Custom 2",    icon: "Custom",     width: 80,    toolTip: "Set visibilty!",     subMenu: CurrentUserUtils.cardGroupTools("star"),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!showFreeform("custom2", true)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
-                { title: "Custom 3",    icon: "Custom",      width: 80,   toolTip: "Set visibilty!",     subMenu: CurrentUserUtils.cardGroupTools("satellite"),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!showFreeform("custom3", true)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
-
-            ]
-        }
-
     static cardGroupTools(icon: string): Button[] {
         return [
-            { title: "",   icon:icon, toolTip:"Click to toggle visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"1",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "",   icon:icon, toolTip:"Click to toggle visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"2",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "",   icon:icon, toolTip:"Click to toggle visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"3",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-            { title: "",   icon:icon, toolTip:"Click to toggle visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"4",    funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
-
+            { title: "1", icon, toolTip:"Click to toggle visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"1", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "2", icon, toolTip:"Click to toggle visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"2", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "3", icon, toolTip:"Click to toggle visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"3", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "4", icon, toolTip:"Click to toggle visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"4", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "5", icon, toolTip:"Click to toggle visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"5", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "6", icon, toolTip:"Click to toggle visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"6", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
+            { title: "7", icon, toolTip:"Click to toggle visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"7", funcs: {hidden:`!cardHasLabel(this.toolType)`}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}},
         ]
     }
-
-    
-
     static viewTools(): Button[] {
         return [
             { title: "Snap",      icon: "th",                  toolTip: "Show Snap Lines",     btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines",  funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
@@ -858,7 +845,7 @@ pie title Minerals in my tap water
             { title: "View",    icon: "View",          toolTip: "View tools",           subMenu: CurrentUserUtils.viewTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "Stack",   icon: "View",          toolTip: "Stacking tools",       subMenu: CurrentUserUtils.stackTools(),  expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "Card",    icon: "Sort",          toolTip: "Card sort",            subMenu: CurrentUserUtils.cardTools(),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
-            { title: "CustomCard",    icon: "Create",        toolTip: "Create custom groupings!",     subMenu: CurrentUserUtils.customCardTools(),   expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
+            { title: "Label",   icon: "Label",         toolTip: "Assign card labels",   subMenu: CurrentUserUtils.labelTools(),  expertMode: false, toolType:CollectionViewType.Card,     funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
             { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
             { title: "Video",   icon: "Video",         toolTip: "Video functions",      subMenu: CurrentUserUtils.videoTools(),  expertMode: false, toolType:DocumentType.VID,            funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected
             { title: "Image",   icon: "Image",         toolTip: "Image functions",      subMenu: CurrentUserUtils.imageTools(),  expertMode: false, toolType:DocumentType.IMG,            funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected
diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss
index babc604b5..a089b248d 100644
--- a/src/client/views/collections/CollectionCardDeckView.scss
+++ b/src/client/views/collections/CollectionCardDeckView.scss
@@ -12,6 +12,7 @@
     display: grid;
     grid-template-columns: repeat(10, 1fr);
     // width: 100%;
+    transform-origin: top left;
 
     position: absolute;
     align-items: center;
@@ -22,15 +23,15 @@
 }
 
 .card-button-container {
-    display: flex;         
+    display: flex;
     padding: 3px;
-    // width: 300px;        
+    // width: 300px;
     background-color: rgb(218, 218, 218); /* Background color of the container */
-    border-radius: 50px;    /* Rounds the corners of the container */
-    transform: translateY(-50px); 
+    border-radius: 50px; /* Rounds the corners of the container */
+    transform: translateY(75px);
     // box-shadow: 0 4px 8px rgba(0,0,0,0.1); /* Optional: Adds shadow for depth */
-    align-items: center;    /* Centers buttons vertically */
-    justify-content: start;/* Centers buttons horizontally */
+    align-items: center; /* Centers buttons vertically */
+    justify-content: start; /* Centers buttons horizontally */
 }
 
 button {
@@ -39,11 +40,11 @@ button {
     border-radius: 50%;
     background-color: $dark-gray;
     // border-color: $medium-blue;
-    margin: 5px;    // transform: translateY(-50px); 
+    margin: 5px; // transform: translateY(-50px);
 }
 
 // button:hover {
-//     transform: translateY(-50px); 
+//     transform: translateY(-50px);
 // }
 
 // .card-wrapper::after {
@@ -62,13 +63,19 @@ button {
 //     align-items: center;
 //     transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955);
 
-
 // }
 
+.card-item-inactive,
 .card-item-active,
 .card-item {
     position: relative;
     transition: transform 0.5s ease-in-out;
+    display: flex;
+    flex-direction: column;
+}
+
+.card-item-inactive {
+    opacity: 0.5;
 }
 
 .card-item-active {
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index b2a94c3c6..9e5668ffa 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -1,182 +1,173 @@
-import { ObservableMap, action, computed, makeObservable, observable } from 'mobx';
+import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { Utils, returnFalse, returnTrue, returnZero } from '../../../Utils';
-import { Doc, DocListCast, Field, StrListCast } from '../../../fields/Doc';
+import { DashColor, Utils, numberRange, returnFalse, returnZero } from '../../../Utils';
+import { Doc, NumListCast } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
-import { NumCast, ScriptCast, StrCast, BoolCast, DocCast, RTFCast, Cast} from '../../../fields/Types';
+import { BoolCast, Cast, DateCast, NumCast, RTFCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { URLField } from '../../../fields/URLField';
+import { gptImageLabel } from '../../apis/gpt/GPT';
+import { DocumentType } from '../../documents/DocumentTypes';
 import { DragManager } from '../../util/DragManager';
 import { SelectionManager } from '../../util/SelectionManager';
+import { SnappingManager } from '../../util/SnappingManager';
+import { Transform } from '../../util/Transform';
+import { undoable } from '../../util/UndoManager';
 import { StyleProp } from '../StyleProvider';
 import { DocumentView } from '../nodes/DocumentView';
+import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
 import './CollectionCardDeckView.scss';
 import { CollectionSubView } from './CollectionSubView';
-import { Transform } from '../../util/Transform';
-import { LinkManager } from '../../util/LinkManager';
-// import Card from 'react-bootstrap/Card';
-import { DocumentType } from '../../documents/DocumentTypes';
-import { forEach } from 'lodash';
-import { SnappingManager } from '../../util/SnappingManager';
-import { List } from '../../../fields/List';
-import { gptAPICall, gptImageLabel } from '../../apis/gpt/GPT';
-import { GPTCallType } from '../../apis/gpt/GPT';
-import { ImageField, PdfField, URLField } from '../../../fields/URLField';
-import { GPTPopup } from '../pdf/GPTPopup/GPTPopup';
-import { GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
-import { reaction } from 'mobx';
-import { NumListCast } from '../../../fields/Doc';
-import { undoBatch } from '../../util/UndoManager';
-import { CalendarContainer } from 'react-datepicker';
+
+enum cardSortings {
+    Time = 'time',
+    Type = 'type',
+    Color = 'color',
+    Custom = 'custom',
+    None = '',
+}
 @observer
 export class CollectionCardView extends CollectionSubView() {
+    private _dropDisposer?: DragManager.DragDropDisposer;
+    private _childDocumentWidth = 600; // target width of a Doc...
+    private _disposers: { [key: string]: IReactionDisposer } = {};
+    private _textToDoc = new Map<string, Doc>();
 
-    @observable hoveredNodeIndex = -1;
-
-    //key is the index in the child pair list, value is the id# for the group its in
-    @observable customGroupDictionary: Map<number, number>[] = [new Map<number, number>(), new Map<number, number>(), new Map<number, number>()];
-
-    /**
-     * The child documents to be rendered-- either all of them except the Links or the docs in the currently active 
-     * custom group
-     */
-    @computed get myChildLayoutPairs() {
-        let activeGroups = NumListCast(this._props.Document.visibleGroupNumbers);
-        let currCustom = NumCast(this._props.Document.customSortNumber);
-
-        if (activeGroups.length <= 0) {
-            return this.childLayoutPairs.filter(l => l.layout.type != DocumentType.LINK);
-        }
-
-        if (StrCast(this._props.Document.cardSort).includes("custom")) {
-            return this.childLayoutPairs.filter((l, index) => {
-                if (l.layout.type === DocumentType.LINK) {
-                    return false;
-                }
-                // Get the group number for the current index from the customGroupDictionary
-                const groupNumber = this.getButtonGroup(currCustom, l.layout)
-                // Check if the group number is in the active groups
-                return groupNumber !== undefined && activeGroups.includes(groupNumber);
-            });
-        }
+    @observable _forceChildXf = false;
+    @observable _isLoading = false;
+    @observable _hoveredNodeIndex = -1;
+    @observable _docRefs = new ObservableMap<Doc, DocumentView>();
+    @observable _maxRowCount = 10;
 
-        // Default return for non-custom cardSort or other cases, filtering out links
-        return this.childLayoutPairs.filter(l => l.layout.type != DocumentType.LINK);
+    static getButtonGroup(groupFieldKey: 'chat' | 'star' | 'idea' | 'like', doc: Doc): number | undefined {
+        return Cast(doc[groupFieldKey], 'number', null);
     }
 
+    static imageUrlToBase64 = async (imageUrl: string): Promise<string> => {
+        try {
+            const response = await fetch(imageUrl);
+            const blob = await response.blob();
 
-    @action
-    setHoveredNodeIndex = (index: number) => {
-        if (!this.isSelected(index)) {
-            this.hoveredNodeIndex = index;
-        }
-    };
-    /**
-     * Translates the hovered node to the center of the screen
-     * @param index 
-     * @returns 
-     */
-    translateHover = (index: number): number => {
-        if (this.hoveredNodeIndex == index && !this.isSelected(index)) {
-            return -50;
+            return new Promise((resolve, reject) => {
+                const reader = new FileReader();
+                reader.readAsDataURL(blob);
+                reader.onloadend = () => resolve(reader.result as string);
+                reader.onerror = error => reject(error);
+            });
+        } catch (error) {
+            console.error('Error:', error);
+            throw error;
         }
-        return 0;
     };
 
-    @action
-    setSelectedNodeIndex = (index: number) => {
-        const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
-        if (SelectionManager.IsSelected(docs[index])) {
-            this.setSelectedNodeIndex(index);
+    protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
+        this._dropDisposer?.();
+        if (ele) {
+            this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
         }
     };
 
-    isSelected = (index: number): boolean => {
-        const docs = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]);
-        return SelectionManager.IsSelected(docs[index]);
-    };
-
-    /**
-     * Returns all the documents except the one that's currently selected
-     */
-    inactiveDocs = () => {
-        const docs = this.myChildLayoutPairs;    
-        return docs.filter(d => !SelectionManager.IsSelected(d.layout));   
-    };
-
-    middleIndex = Math.floor(this.inactiveDocs().length / 2);
-
     constructor(props: any) {
         super(props);
         makeObservable(this);
-        const pairs = this.childLayoutPairs.filter(d => d.layout.type != DocumentType.LINK);
+    }
 
-        //gets the values from the last time the view was loaded and puts them into their respective places in the hashmaps(s)
-        for (let i=0; i< pairs.length; i++){
-            if (pairs[i].layout.custom1Group != undefined){
-                this.customGroupDictionary[0].set(i, NumCast(pairs[i].layout.custom1Group))
-            }
+    componentDidMount(): void {
+        this._disposers.sort = reaction(
+            () => ({ cardSort: this.cardSort, field: this.customSortField }),
+            ({ cardSort, field }) => (cardSort === cardSortings.Custom && field === 'chat' ? this.openChatPopup() : GPTPopup.Instance.setVisible(false))
+        );
+    }
 
-            if (pairs[i].layout.custom2Group != undefined){
-                this.customGroupDictionary[1].set(i, NumCast(pairs[i].layout.custom2Group))
-            }
+    componentWillUnmount() {
+        Object.keys(this._disposers).forEach(key => this._disposers[key]?.());
+        this._dropDisposer?.();
+    }
 
-            if (pairs[i].layout.custom3Group != undefined){
-                this.customGroupDictionary[2].set(i, NumCast(pairs[i].layout.custom3Group))
-            }
+    @computed get customSortField() {
+        return StrCast(this.Document.customSortField) as any as 'chat' | 'star' | 'idea' | 'like';
+    }
 
-            if (pairs[i].layout.chatGroup != undefined){
-                this.gptGroups.set(pairs[i].layout, NumCast(pairs[i].layout.chatGroup))
-                this.amGPTGroups = NumCast(this._props.Document.chatAmGroups)
+    @computed get cardSort() {
+        return StrCast(this.Document.cardSort) as any as cardSortings;
+    }
+    /**
+     * how much to scale down the contents of the view so that everything will fit
+     */
+    @computed get fitContentScale() {
+        const length = Math.min(this.childDocsWithoutLinks.length, this._maxRowCount);
+        return (this._childDocumentWidth * length) / this._props.PanelWidth();
+    }
 
-            }
+    @computed get translateWrapperX() {
+        let translate = 0;
+
+        if (this.inactiveDocs().length !== this.childDocsWithoutLinks.length && this.inactiveDocs().length < 10) {
+            translate += this.panelWidth() / 2;
         }
-        reaction(
-            () => this._props.Document.cardSort,
-            (cardSort) => {
-                if (cardSort === 'chat') {
-                    this.openChatPopup();
-                }
-            }
-        );
+        return translate;
     }
 
-    private _dropDisposer?: DragManager.DragDropDisposer;
+    /**
+     * The child documents to be rendered-- either all of them except the Links or the docs in the currently active
+     * custom group
+     */
+    @computed get childDocsWithoutLinks() {
+        const regularDocs = this.childDocs.filter(l => l.type !== DocumentType.LINK);
+        const activeGroups = NumListCast(this.Document.visibleGroupNumbers);
+
+        if (activeGroups.length > 0 && this.cardSort === cardSortings.Custom) {
+            return regularDocs.filter(doc => {
+                // Get the group number for the current index
+                const groupNumber = CollectionCardView.getButtonGroup(this.customSortField, doc);
+                // Check if the group number is in the active groups
+                return groupNumber !== undefined && activeGroups.includes(groupNumber);
+            });
+        }
 
-    componentWillUnmount() {
-        this._dropDisposer?.();
+        // Default return for non-custom cardSort or other cases, filtering out links
+        return regularDocs;
     }
 
-    protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
-        this._dropDisposer?.();
-        if (ele) {
-            this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
+    /**
+     * Determines the order in which the cards will be rendered depending on the current sort type
+     */
+    @computed get sortedDocs() {
+        return this.sort(this.childDocsWithoutLinks, this.cardSort, BoolCast(this.layoutDoc.sortDesc));
+    }
+
+    @action
+    setHoveredNodeIndex = (index: number) => {
+        if (!SelectionManager.IsSelected(this.childDocs[index])) {
+            this._hoveredNodeIndex = index;
         }
     };
+    /**
+     * Translates the hovered node to the center of the screen
+     * @param index
+     * @returns
+     */
+    translateHover = (index: number) => (this._hoveredNodeIndex === index && !SelectionManager.IsSelected(this.childDocs[index]) ? -50 : 0);
+
+    isSelected = (index: number) => SelectionManager.IsSelected(this.childDocs[index]);
 
-    childDocumentWidth = 600; // target width of a Doc...
     /**
-     * how much to scale down the contents of the view so that everything will fit
+     * Returns all the documents except the one that's currently selected
      */
-    @computed get fitContentScale() {
-        if (this.myChildLayoutPairs.length < this.maxRowCount) {
-            length = this.myChildLayoutPairs.length;
-        } else {
-            length = this.maxRowCount;
-        }
-        return (this.childDocumentWidth * length) / this._props.PanelWidth();
-    }
+    inactiveDocs = () => this.childDocsWithoutLinks.filter(d => !SelectionManager.IsSelected(d));
 
-    panelWidth = () => this.childDocumentWidth;
-    panelHeight = (layout: Doc) => () => (2 * (this.panelWidth() * NumCast(layout._height))) / NumCast(layout._width);
+    panelWidth = () => this._childDocumentWidth;
+    panelHeight = (layout: Doc) => () => (this.panelWidth() * NumCast(layout._height)) / NumCast(layout._width);
     onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
     isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
     isChildContentActive = () => (this.isContentActive() ? true : false);
 
     /**
      * Returns the degree to rotate a card dependind on the amount of cards in their row and their index in said row
-     * @param amCards 
-     * @param index 
-     * @returns 
+     * @param amCards
+     * @param index
+     * @returns
      */
     rotate = (amCards: number, index: number) => {
         const possRotate = -30 + index * (30 / ((amCards - (amCards % 2)) / 2));
@@ -184,7 +175,8 @@ export class CollectionCardView extends CollectionSubView() {
 
         if (amCards % 2 == 0 && possRotate == 0) {
             return possRotate + Math.abs(-30 + (index - 1) * (30 / (amCards / 2)));
-        } else if (amCards % 2 == 0 && index > (amCards + 1) / 2) {
+        }
+        if (amCards % 2 == 0 && index > (amCards + 1) / 2) {
             return possRotate + stepMag;
         }
 
@@ -197,26 +189,25 @@ export class CollectionCardView extends CollectionSubView() {
         const evenOdd = amCards % 2;
         const apex = (amCards - evenOdd) / 2;
         const stepMag = 200 / ((amCards - evenOdd) / 2) + Math.abs((apex - index) * 25);
-        let rowOffset = 0;
 
-        if (realIndex > this.maxRowCount - 1) {
-            rowOffset = 400 * ((realIndex - (realIndex % this.maxRowCount)) / this.maxRowCount);
+        let rowOffset = 0;
+        if (realIndex > this._maxRowCount - 1) {
+            rowOffset = 400 * ((realIndex - (realIndex % this._maxRowCount)) / this._maxRowCount);
         }
         if (evenOdd == 1 || index < apex - 1) {
             return Math.abs(stepMag * (apex - index)) - rowOffset;
-        } else {
-            if (index == apex || index == apex - 1) {
-                return 0 - rowOffset;
-            }
-
-            return Math.abs(stepMag * (apex - index - 1)) - rowOffset;
         }
+        if (index == apex || index == apex - 1) {
+            return 0 - rowOffset;
+        }
+
+        return Math.abs(stepMag * (apex - index - 1)) - rowOffset;
     };
 
     /**
      * Translates the selected node to the middle fo the screen
-     * @param index 
-     * @returns 
+     * @param index
+     * @returns
      */
     translateSelected = (index: number): number => {
         // if (this.isSelected(index)) {
@@ -234,126 +225,46 @@ export class CollectionCardView extends CollectionSubView() {
         return translation;
     };
 
-    /**
-     * Determines the order in which the cards will be rendered depending on the current sort type
-     */
-    @computed get sortedDocsType() {
-        const desc = BoolCast(this.layoutDoc.sortDesc);
-        let sorted = [];
-        let docs = [];
-    
-        for (let i = 0; i < this.myChildLayoutPairs.length; i++) {
-            //copying everything in childlayout pairs to sorted so that i can use the sort function without altering the original list
-            sorted[i] = this.myChildLayoutPairs[i];
-        }
-    
-        switch (this._props.Document.cardSort) {
-            case 'type':
-                // Copy and sort documents by type
-                return this.sort(sorted, 'type', desc);
-            case 'color':
-                return this.sort(sorted, 'color', desc);
-            case 'custom':
-                return this.sort(sorted, 'custom', desc);
-            case 'chat':
-                return this.sort(this.myChildLayoutPairs, 'gpt', BoolCast(this.layoutDoc.sortDesc));            
-                
-            default:
-                docs = this.myChildLayoutPairs;
-                return { docs };
-        }
-    }
-     /**
-      * Converts a hex # to its hsv color value
-      * @param hex 
-      * @returns 
-      */
-    hexToHsv = (hex: string): [number, number, number] => {
-        if (!hex) return [0, 0, 0]; // Default to black if hex is not defined
-        const r = parseInt(hex.slice(1, 3), 16) / 255;
-        const g = parseInt(hex.slice(3, 5), 16) / 255;
-        const b = parseInt(hex.slice(5, 7), 16) / 255;
-        const max = Math.max(r, g, b),
-            min = Math.min(r, g, b);
-        const d = max - min;
-        let h: number;
-        const s = max === 0 ? 0 : d / max;
-        const v = max;
-
-        switch (max) {
-            case min:
-                h = 0;
-                break;
-            case r:
-                h = (g - b) / d + (g < b ? 6 : 0);
-                break;
-            case g:
-                h = (b - r) / d + 2;
-                break;
-            case b:
-                h = (r - g) / d + 4;
-                break;
-            default:
-                h = 0;
-                break;
-        }
-        h /= 6;
-        return [h, s, v];
-    };
     /**
      * Called in the sortedDocsType method. Compares the cards' value in regards to the desired sort type-- earlier cards are move to the
      * front, latter cards to the back
-     * @param docs 
-     * @param sortType 
-     * @param isDesc 
-     * @returns 
+     * @param docs
+     * @param sortType
+     * @param isDesc
+     * @returns
      */
-    sort = (docs: { layout: Doc; data: Doc }[], sortType: string, isDesc: boolean) => {
+    sort = (docs: Doc[], sortType: cardSortings, isDesc: boolean) => {
+        if (sortType === cardSortings.None) return docs;
         docs.sort((docA, docB) => {
-            let typeA;
-            let typeB;
-
-            switch (sortType) {
-                case 'color':
-                    typeA = this.hexToHsv(StrCast(docA.layout.backgroundColor)) ?? ''; // If docA.type is undefined, use an empty string
-                    typeB = this.hexToHsv(StrCast(docB.layout.backgroundColor)) ?? ''; // If docB.type is undefined, use an empty string
-                    break;
-
-                case 'custom':
-                    typeA = this.getButtonGroup(NumCast(this._props.Document.customSortNumber), docA.layout) ?? '9999'
-                    typeB = this.getButtonGroup(NumCast(this._props.Document.customSortNumber), docB.layout) ?? '9999'
-                    break;   
-                    
-                case 'gpt':
-                    typeA = this.getButtonGroup(99999, docA.layout, true) ?? '9999';
-                    typeB = this.getButtonGroup(99999, docB.layout, true) ?? '9999';
-                    break;  
-
-                default:
-                    typeA = docA.layout.type ?? ''; // If docA.type is undefined, use an empty string
-                    typeB = docB.layout.type ?? ''; // If docB.type is undefined, use an empty string
-                    break;
-            }
-
-            // Perform a basic string comparison if types are strings
-            let out = 0;
-            if (typeA < typeB) out = -1;
-            if (typeA > typeB) out = 1;
-            if (isDesc) out *= -1; // Reverse the sort order if descending is true
-            return out;
+            const [typeA, typeB] = (() => {
+                switch (sortType) {
+                    case cardSortings.Time: 
+                                return   [DateCast(docA.author_date)?.date ?? Date.now(),
+                                          DateCast(docB.author_date)?.date ?? Date.now()];
+                    case cardSortings.Color: 
+                                  return [DashColor(StrCast(docA.backgroundColor)).hsv().toString(), // If docA.type is undefined, use an empty string
+                                          DashColor(StrCast(docB.backgroundColor)).hsv().toString()]; // If docB.type is undefined, use an empty string
+                    case cardSortings.Custom:
+                                  return [CollectionCardView.getButtonGroup(this.customSortField, docA)??0,
+                                          CollectionCardView.getButtonGroup(this.customSortField, docB)??0];
+                    default:      return [StrCast(docA.type), // If docA.type is undefined, use an empty string
+                                          StrCast(docB.type)]; // If docB.type is undefined, use an empty string
+                    } // prettier-ignore
+            })();
+
+            const out = typeA < typeB ? -1 : typeA > typeB ? 1 : 0;
+            return isDesc ? -out : out; // Reverse the sort order if descending is true
         });
 
-        return { docs };
+        return docs;
     };
 
-
-    displayDoc = (childPair: { layout: Doc; data: Doc }, screenToLocalTransform: () => Transform) => {
+    displayDoc = (doc: Doc, screenToLocalTransform: () => Transform) => {
         return (
             <DocumentView
                 {...this._props}
-                ref={action((r: DocumentView) => r?.ContentDiv && this.docRefs.set(childPair.layout, r))}
-                Document={childPair.layout}
-                TemplateDataDocument={childPair.data}
+                ref={action((r: DocumentView) => r?.ContentDiv && this._docRefs.set(doc, r))}
+                Document={doc}
                 NativeWidth={returnZero}
                 NativeHeight={returnZero}
                 layout_fitWidth={returnFalse}
@@ -365,382 +276,219 @@ export class CollectionCardView extends CollectionSubView() {
                 isContentActive={this.isChildContentActive}
                 isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
                 PanelWidth={this.panelWidth}
-                PanelHeight={this.panelHeight(childPair.layout)}
+                PanelHeight={this.panelHeight(doc)}
             />
         );
     };
 
-    @observable docRefs = new ObservableMap<Doc, DocumentView>();
-
-    @observable maxRowCount = 10;
-
     /**
      * Determines how many cards are in the row of a card at a specific index
-     * @param index 
-     * @returns 
+     * @param index
+     * @returns
      */
-    overflowAmCardsCalc(index: number) {
-        if (this.inactiveDocs().length < this.maxRowCount) {
+    overflowAmCardsCalc = (index: number) => {
+        if (this.inactiveDocs().length < this._maxRowCount) {
             return this.inactiveDocs().length;
         }
         // 13 - 3 = 10
         const totalCards = this.inactiveDocs().length;
         // if 9 or less
         if (index < totalCards - (totalCards % 10)) {
-            return this.maxRowCount;
+            return this._maxRowCount;
         }
         //(3)
         return totalCards % 10;
-    }
+    };
     /**
      * Determines the index a card is in in a row
-     * @param realIndex 
-     * @returns 
+     * @param realIndex
+     * @returns
      */
-    overflowIndexCalc(realIndex: number) {
-        if (realIndex < 10) {
-            return realIndex;
-        }
-
-        return realIndex % 10;
-    }
+    overflowIndexCalc = (realIndex: number) => realIndex % 10;
     /**
      * Translates the cards in the second rows and beyond over to the right
-     * @param realIndex 
-     * @param calcIndex 
-     * @param calcRowCards 
-     * @returns 
+     * @param realIndex
+     * @param calcIndex
+     * @param calcRowCards
+     * @returns
      */
-    translateOverflowX(realIndex: number, calcIndex: number, calcRowCards: number) {
-        
-        if (realIndex < this.maxRowCount) {
-            return 0;
-        }
+    translateOverflowX = (realIndex: number, calcRowCards: number) => (realIndex < this._maxRowCount ? 0 : (10 - calcRowCards) * (this.panelWidth() / 2));
 
-        return (10 - calcRowCards) * (this.panelWidth() / 2);
-    }
     /**
      * Determines how far to translate a card in the y direction depending on its index, whether or not its being hovered, or if it's selected
-     * @param isHovered 
-     * @param isSelected 
-     * @param realIndex 
-     * @param amCards 
-     * @param calcRowIndex 
-     * @returns 
+     * @param isHovered
+     * @param isSelected
+     * @param realIndex
+     * @param amCards
+     * @param calcRowIndex
+     * @returns
      */
-    calculateTranslateY(isHovered: boolean, isSelected: boolean, realIndex: number, amCards: number, calcRowIndex: number) {
-        let trans = 0;
-        if (isHovered) {
-            trans += this.translateHover(realIndex);
-        }
-
-        trans += this.translateY(amCards, calcRowIndex, realIndex);
-        if (isSelected) {
-            trans = 50 * this.fitContentScale;
-        }
-        return trans;
-    }
-
-    @observable _forceChildXf = false;
+    calculateTranslateY = (isHovered: boolean, isSelected: boolean, realIndex: number, amCards: number, calcRowIndex: number) => {
+        if (isSelected) return 50 * this.fitContentScale;
+        const trans = isHovered ? this.translateHover(realIndex) : 0;
+        return trans + this.translateY(amCards, calcRowIndex, realIndex);
+    };
 
     /**
-     * Actually renders all the cards
+     * Toggles the buttons between on and off when creating custom sort groupings/changing those created by gpt
+     * @param childPairIndex
+     * @param buttonID
+     * @param doc
      */
-    @computed get contentSorted() {
-        const sortedDocs = this.sortedDocsType.docs;
-        // Map sorted documents to their rendered components
-        return sortedDocs.map((childPair, index) => {
-
-            const isHovered = this.hoveredNodeIndex === index;
-            const childPairIndex  = this.childLayoutPairs.filter(d => d.layout.type != DocumentType.LINK).indexOf(childPair)
-            const realIndex = this.sortedDocsType.docs.filter(d => !SelectionManager.IsSelected(d.layout)).indexOf(childPair);
-            const calcRowIndex = this.overflowIndexCalc(realIndex);
-
-            const amCards = this.overflowAmCardsCalc(realIndex);
-
-            const isSelected = SelectionManager.IsSelected(childPair.layout);
-
-            const childScreenToLocal = () => {
-                this._forceChildXf;
-                const dref = this.docRefs.get(childPair.layout);
-                const { translateX, translateY, scale } = Utils.GetScreenTransform(dref?.ContentDiv);
-                return new Transform(-translateX + (dref?.centeringX || 0) * scale, -translateY + (dref?.centeringY || 0) * scale, 1).scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, calcRowIndex) : 0);
-            };
-
-            return (
-                <div
-                    key={childPair.layout[Id]}
-                    className={`card-item${isSelected ? '-active' : ''}`}
-                    onPointerUp={e => {
-                        SnappingManager.SetIsResizing(this.Document);
-                        setTimeout(
-                            action(() => {
-                                SnappingManager.SetIsResizing(undefined);
-                                this._forceChildXf = !this._forceChildXf;
-                            }),
-                            700
-                        );
-                    }}
-                    style={{
-                        width: this.panelWidth(),
-                        height: this.panelHeight(childPair.layout)(),
-                        transform: `
-                        translateY(${this.calculateTranslateY(isHovered, isSelected, realIndex, amCards, calcRowIndex)}px)
-                        translateX(${isSelected ? this.translateSelected(calcRowIndex) : this.translateOverflowX(realIndex, calcRowIndex, amCards)}px)
-                        rotate(${!isSelected ? this.rotate(amCards, calcRowIndex) : 0}deg)
-                        scale(${isSelected ? 1.25 : 1})
-                        `,
-                    }}
-                    onMouseEnter={() => this.setHoveredNodeIndex(index)}>
-                    {this.displayDoc(childPair, childScreenToLocal)}
-
-                    {this._props.Document.cardSort == 'custom' ? this.renderButtons(childPairIndex, childPair.layout, false) : ''}
-                    {this._props.Document.cardSort == 'chat' ? this.renderButtons(childPairIndex, childPair.layout, true) : ''}
-
-                </div>
-            );
-        });
-    }
+    toggleButton = undoable((buttonID: number, doc: Doc) => this.customSortField && (doc[this.customSortField] = buttonID), 'toggle custom button');
 
     /**
-     * Toggles the buttons between on and off when creating custom sort groupings/changing those created by gpt 
-     * @param childPairIndex 
-     * @param buttonID 
-     * @param isChat 
-     * @param doc 
-     */
-  
-    @undoBatch
-    @action toggleButton(childPairIndex: number, buttonID: number, isChat = false, doc: Doc) {
-        if (!isChat) {
-            const sortNumber = NumCast(this._props.Document.customSortNumber)
-            this.customGroupDictionary[sortNumber].set(childPairIndex, buttonID)
-            switch (sortNumber){
-                case 0:
-                    doc.custom1Group = buttonID
-                    break
-                case 1:
-                    doc.custom2Group = buttonID
-                    break
-                case 2:
-                    doc.custom3Group = buttonID
-                    break
-                default:
-                    break
-            }
-        }
-    
-        if (isChat && doc) {
-            this.gptGroups.set(doc, buttonID);
-            doc.chatGroup = buttonID
-        }
-    }
-    
-    /**
-     * A list of the text content of all the child docs. RTF documents will have just their text and pdf documents will have the first 50 words. 
+     * A list of the text content of all the child docs. RTF documents will have just their text and pdf documents will have the first 50 words.
      * Image documents are converted to bse64 and gpt generates a description for them. all other documents use their title. This string is
-     * inputted into the gpt prompt to sort everything together 
-     * @returns 
+     * inputted into the gpt prompt to sort everything together
+     * @returns
      */
-    async childPairStringList(): Promise<string> {
-        let string = "";
-        for (let i = 0; i < this.childDocs.length; i++) {
-            switch (this.childDocs[i].type) {
-                case DocumentType.IMG:
-                    string += `======${await this.getImageDesc(this.childDocs[i])}======`;
-                    break;
-    
-                case DocumentType.PDF:
-                    let pdfText = StrCast(this.childDocs[i].text);
-                    let words = pdfText.split(/\s+/);
-                    let first50Words = words.slice(0, 50);
-                    string += `======${first50Words.join(' ')}======`;
-    
-                    this.textToDoc.set(first50Words.join(' ').trim(), this.childDocs[i]);
-                    break;
-    
-                case DocumentType.RTF:
-                    let rtfText = StrCast((RTFCast(this.childDocs[i].text)).Text);
-                    string += `======${StrCast((RTFCast(this.childDocs[i].text)).Text)}======`;
-                    this.textToDoc.set(rtfText.trim(), this.childDocs[i]);
-                    break;
-    
-                default:
-                    string += `======${StrCast(this.childDocs[i].title)}======`;
-                    this.textToDoc.set(StrCast(this.childDocs[i].title).trim(), this.childDocs[i]);
-            }
-        }
-        return string;
-    }
-
-    @observable isLoading = false
-    
-   
-    imageUrlToBase64 = async (imageUrl: string): Promise<string> => {
-        try {
-            const response = await fetch(imageUrl);
-            const blob = await response.blob();
-    
-            return new Promise((resolve, reject) => {
-                const reader = new FileReader();
-                reader.readAsDataURL(blob); 
-                reader.onloadend = () => resolve(reader.result as string); 
-                reader.onerror = error => reject(error); 
-            });
-        } catch (error) {
-            console.error('Error:', error);
-            throw error; 
-        }
+    childPairStringList = () => {
+        const docToText = (doc: Doc) => {
+            switch (doc.type) {
+                case DocumentType.PDF:  const words = StrCast(doc.text).split(/\s+/);
+                                        return words.slice(0, 50).join(' '); // first 50 words of pdf text
+                case DocumentType.IMG:  return this.getImageDesc(doc);
+                case DocumentType.RTF:  return StrCast(RTFCast(doc.text).Text);
+                default:                return StrCast(doc.title);
+            } // prettier-ignore
+        };
+        const docTextPromises = this.childDocsWithoutLinks.map(async doc => {
+            const docText = (await docToText(doc)) ?? '';
+            this._textToDoc.set(docText.trim(), doc);
+            return `======${docText.replace(/\n/g, ' ').trim()}======`;
+        });
+        return Promise.all<string>(docTextPromises);
     };
 
-    textToDoc = new Map<string, Doc>()
-    gptProccessedImages = new Set<Doc>()
-
     /**
      * Calls the gpt API to generate descriptions for the images in the view
-     * @param image 
-     * @returns 
+     * @param image
+     * @returns
      */
-    @action async getImageDesc(image: Doc) {
-        if (this.gptProccessedImages.has(image)) {
-            // Return the already processed description
-            return Array.from(this.textToDoc.keys()).find(key => this.textToDoc.get(key) === image) || '';
-        }
-    
-        let href = (image['data'] as URLField).url.href;
-        let hrefParts = href.split('.');
-        let hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`;
+    getImageDesc = async (image: Doc) => {
+        if (StrCast(image.description)) return StrCast(image.description); // Return existing description
+        const href = (image.data as URLField).url.href;
+        const hrefParts = href.split('.');
+        const hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`;
         try {
-            let hrefBase64 = await this.imageUrlToBase64(hrefComplete);
-            let response = await gptImageLabel(hrefBase64);
-            this.textToDoc.set(response.trim(), image);
-            this.gptProccessedImages.add(image);
-            console.log(response);
+            const hrefBase64 = await CollectionCardView.imageUrlToBase64(hrefComplete);
+            const response = await gptImageLabel(hrefBase64);
+            image[DocData].description = response.trim();
             return response; // Return the response from gptImageLabel
         } catch (error) {
-            console.log("bad things have happened");
+            console.log('bad things have happened');
         }
-    }
+        return '';
+    };
 
-    //child doc to its group determind by gpt
-    gptGroups = new ObservableMap<Doc, number>
-    @observable amGPTGroups = 0
-    
     /**
      * Converts the gpt output into a hashmap that can be used for sorting. lists are seperated by ==== while elements within the list are seperated by ~~~~~~
-     * @param gptOutput 
+     * @param gptOutput
      */
-    processGptOutput(gptOutput: string) {
-
+    processGptOutput = (gptOutput: string) => {
         // Split the string into individual list items
         const listItems = gptOutput.split('======').filter(item => item.trim() !== '');
-        this.amGPTGroups = listItems.length
-        this._props.Document.chatAmGroups = this.amGPTGroups
-
         listItems.forEach((item, index) => {
             // Split the item by '~~~~~~' to get all descriptors
             const parts = item.split('~~~~~~').map(part => part.trim());
 
             parts.forEach(part => {
-                console.log(part + "part")
                 // Find the corresponding Doc in the textToDoc map
-                if (this.textToDoc.has(part)) {
-                    const doc = this.textToDoc.get(part);
-                    if (doc) {
-                        this.gptGroups.set(doc, index);
-                        doc.chatGroup = index
-                    }
+                const doc = this._textToDoc.get(part);
+                if (doc) {
+                    doc.chat = index;
                 }
             });
         });
-    }
-
-    @observable isChatPopupOpen = false;
+    };
     /**
-     * Opens up the chat popup and starts the process for smart sorting. 
+     * Opens up the chat popup and starts the process for smart sorting.
      */
-    @action openChatPopup = async () => {
-        this.isChatPopupOpen = true;
+    openChatPopup = async () => {
         GPTPopup.Instance.setVisible(true);
         GPTPopup.Instance.setMode(GPTPopupMode.SORT);
-        
-        // Await the promise to get the string result
-        const sortDesc = await this.childPairStringList();
+        const sortDesc = await this.childPairStringList(); // Await the promise to get the string result
         GPTPopup.Instance.setCardsDoneLoading(true); // Set dataDoneLoading to true after data is loaded
-        GPTPopup.Instance.setSortDesc(sortDesc);
-        GPTPopup.Instance.onSortComplete = this.handleGptSortResult;
-};
-    @action handleGptSortResult = (sortResult: string) => {
-        this.processGptOutput(sortResult);
+        GPTPopup.Instance.setSortDesc(sortDesc.join());
+        GPTPopup.Instance.onSortComplete = (sortResult: string) => this.processGptOutput(sortResult);
     };
 
     /**
-     * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups 
-     * @param childPairIndex 
-     * @param doc 
-     * @param isChat 
-     * @returns 
+     * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups
+     * @param childPairIndex
+     * @param doc
+     * @returns
      */
-    renderButtons(childPairIndex: number, doc: Doc, isChat = false) {
-        const buttons = [];
-        const groupNumber = NumCast(this._props.Document.customSortNumber);
-    
-        let amButtons = 4;
-        let activeButtonIndex = this.getButtonGroup(groupNumber, doc)
-    
-        if (isChat && doc) {
-            if (this.amGPTGroups > 4){
-                amButtons = this.amGPTGroups;
-            }
-            activeButtonIndex = this.getButtonGroup(9999, doc, true);
-        }
-    
-        for (let i = 0; i < amButtons; i++) {
-            const isActive = activeButtonIndex === i;
-            buttons.push(
-                <button
-                    key={i}
-                    style={{ backgroundColor: isActive ? '#4476f7' : '#323232' }}
-                    onClick={() => this.toggleButton(childPairIndex, i, isChat, doc)}
-                ></button>
-            );
-        }
-    
+    renderButtons = (doc: Doc, cardSort: cardSortings) => {
+        if (cardSort !== cardSortings.Custom) return '';
+        const amButtons = Math.max(4, this.childDocs?.reduce((set, doc) => this.customSortField && set.add(NumCast(doc[this.customSortField])), new Set<number>()).size ?? 0);
+        const activeButtonIndex = CollectionCardView.getButtonGroup(this.customSortField, doc);
         const totalWidth = amButtons * 35 + amButtons * 2 * 5 + 6;
         return (
             <div className="card-button-container" style={{ width: `${totalWidth}px` }}>
-                {buttons}
+                {numberRange(amButtons).map(i => (
+                    <button
+                        key={i}
+                        type="button"
+                        style={{ backgroundColor: activeButtonIndex === i ? '#4476f7' : '#323232' }} //
+                        onClick={() => this.toggleButton(i, doc)}
+                    />
+                ))}
             </div>
         );
-    }
-
-    getButtonGroup(groupNumber: number, doc: Doc, isChat?: boolean) {
-        if (isChat){
-            return NumCast(doc.chatGroup)
-        }
-        switch (groupNumber){
-            case 0:
-                return NumCast(doc.custom1Group)
-            case 1:
-                return NumCast(doc.custom2Group)
-            case 2:
-                return NumCast(doc.custom3Group)
-            default:
-                break
-        }
-
-        
-    }
-
+    };
+    /**
+     * Actually renders all the cards
+     */
+    renderCards = () => {
+        const anySelected = this.childDocs.some(doc => SelectionManager.IsSelected(doc));
+        // Map sorted documents to their rendered components
+        return this.sortedDocs.map((doc, index) => {
+            const realIndex = this.sortedDocs.filter(sortDoc => !SelectionManager.IsSelected(sortDoc)).indexOf(doc);
+            const calcRowIndex = this.overflowIndexCalc(realIndex);
+            const amCards = this.overflowAmCardsCalc(realIndex);
+            const isSelected = SelectionManager.IsSelected(doc);
 
-    @computed get translateWrapperX() {
-        let translate = 0;
+            const childScreenToLocal = () => {
+                this._forceChildXf;
+                const dref = this._docRefs.get(doc);
+                const { translateX, translateY, scale } = Utils.GetScreenTransform(dref?.ContentDiv);
+                return new Transform(-translateX + (dref?.centeringX || 0) * scale, 
+                                     -translateY + (dref?.centeringY || 0) * scale, 1)
+                          .scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, calcRowIndex) : 0); // prettier-ignore
+            };
 
-        if (this.inactiveDocs().length != this.myChildLayoutPairs.length && this.inactiveDocs().length < 10) {
-            translate += this.panelWidth() / 2;
-        }
-        return translate;
-    }
+            return (
+                <div
+                    key={doc[Id]}
+                    className={`card-item${isSelected ? '-active' : anySelected ? '-inactive' : ''}`}
+                    onPointerUp={() => {
+                        // this turns off documentDecorations during a transition, then turns them back on afterward.
+                        SnappingManager.SetIsResizing(this.Document);
+                        setTimeout(
+                            action(() => {
+                                SnappingManager.SetIsResizing(undefined);
+                                this._forceChildXf = !this._forceChildXf;
+                            }),
+                            700
+                        );
+                    }}
+                    style={{
+                        width: this.panelWidth(),
+                        height: 'max-content', // this.panelHeight(childPair.layout)(),
+                        transform: `translateY(${this.calculateTranslateY(this._hoveredNodeIndex === index, isSelected, realIndex, amCards, calcRowIndex)}px)
+                                    translateX(${isSelected ? this.translateSelected(calcRowIndex) : this.translateOverflowX(realIndex, amCards)}px)
+                                    rotate(${!isSelected ? this.rotate(amCards, calcRowIndex) : 0}deg)
+                                    scale(${isSelected ? 1.25 : 1})`,
+                    }}
+                    onMouseEnter={() => this.setHoveredNodeIndex(index)}>
+                    {this.displayDoc(doc, childScreenToLocal)}
+                    {this.renderButtons(doc, this.cardSort)}
+                </div>
+            );
+        });
+    };
     render() {
         return (
             <div
@@ -754,11 +502,10 @@ export class CollectionCardView extends CollectionSubView() {
                     className="card-wrapper"
                     style={{
                         transform: ` scale(${1 / this.fitContentScale}) translateX(${this.translateWrapperX}px)`,
-                        transformOrigin: 'top left',
                         height: `${100 * this.fitContentScale}%`,
                     }}
                     onMouseLeave={() => this.setHoveredNodeIndex(-1)}>
-                    {this.contentSorted}
+                    {this.renderCards()}
                 </div>
             </div>
         );
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 496d7482c..35a3a2e31 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -1,7 +1,7 @@
 import { Colors } from 'browndash-components';
 import { action, runInAction } from 'mobx';
 import { aggregateBounds } from '../../../Utils';
-import { Doc, Opt } from '../../../fields/Doc';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { InkTool } from '../../../fields/InkField';
 import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types';
 import { WebField } from '../../../fields/URLField';
@@ -106,17 +106,15 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
     selected ? selected.CollectionFreeFormDocumentView?.float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
 });
 
-
-
 ScriptingGlobals.add(function showFreeform(
-    attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom1' | 'custom2' | 'custom3' | 'chat' | '1' | '2' | '3' | '4',
+    attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'like' | 'star' | 'idea' | 'chat' | '1' | '2' | '3' | '4',
     checkResult?: boolean,
     persist?: boolean,
     isDoubleClick?: boolean
 ) {
     const selected = SelectionManager.Docs.lastElement();
     // prettier-ignore
-    const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'custom1' | 'custom2' | 'custom3' | 'chat' | '1' | '2' | '3' | '4', 
+    const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'like' | 'star' | 'idea' | 'chat' | '1' | '2' | '3' | '4', 
     { 
         waitForRender?: boolean; 
         checkResult: (doc: Doc) => any; 
@@ -167,87 +165,48 @@ ScriptingGlobals.add(function showFreeform(
             checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "links",
             setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "links",
         }],
-        ['custom1', {
-            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && NumCast(doc?.customSortNumber) === 0,
+        ['like', {
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && StrCast(doc?.customSortField) === "like",
             setDoc: (doc: Doc, dv: DocumentView) => {
                 doc.cardSort = "custom";
-                doc.customSortNumber = 0;
+                doc.customSortField = "like";
                 doc.visibleGroupNumbers = new List<number>();
             }
         }],
-        ['custom2', {
-            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && NumCast(doc?.customSortNumber) === 1,
+        ['star', {
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && StrCast(doc?.customSortField) === "star",
             setDoc: (doc: Doc, dv: DocumentView) => {
                 doc.cardSort = "custom";
-                doc.customSortNumber = 1;
+                doc.customSortField = "star";
                 doc.visibleGroupNumbers = new List<number>();
             }
         }],
-        ['custom3', {
-            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && NumCast(doc?.customSortNumber) === 2,
+        ['idea', {
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && StrCast(doc?.customSortField) === "idea",
             setDoc: (doc: Doc, dv: DocumentView) => {
                 doc.cardSort = "custom";
-                doc.customSortNumber = 2;
+                doc.customSortField = "idea";
                 doc.visibleGroupNumbers = new List<number>();
             }
         }],
         ['chat', {
-            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "chat",
-            setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "chat",
-        }],
-        ['1', {
-            checkResult: (doc: Doc) => NumListCast(doc?.visibleGroupNumbers).includes(0),
-            setDoc: (doc: Doc, dv: DocumentView) => {
-                let list = NumListCast(doc.visibleGroupNumbers);
-                if (list.includes(0)) {
-                    let newList = new List<number>(list.filter(d => d !== 0));
-                    doc.visibleGroupNumbers = newList;
-                } else {
-                    list.push(0);
-                    doc.visibleGroupNumbers = new List<number>(list);
-                }
-            }
-        }],
-        ['2', {
-            checkResult: (doc: Doc) => NumListCast(doc?.visibleGroupNumbers).includes(1),
-            setDoc: (doc: Doc, dv: DocumentView) => {
-                let list = NumListCast(doc.visibleGroupNumbers);
-                if (list.includes(1)) {
-                    let newList = new List<number>(list.filter(d => d !== 1));
-                    doc.visibleGroupNumbers = newList;
-                } else {
-                    list.push(1);
-                    doc.visibleGroupNumbers = new List<number>(list);
-                }
-            }
-        }],
-        ['3', {
-            checkResult: (doc: Doc) => NumListCast(doc?.visibleGroupNumbers).includes(2),
-            setDoc: (doc: Doc, dv: DocumentView) => {
-                let list = NumListCast(doc.visibleGroupNumbers);
-                if (list.includes(2)) {
-                    let newList = new List<number>(list.filter(d => d !== 2));
-                    doc.visibleGroupNumbers = newList;
-                } else {
-                    list.push(2);
-                    doc.visibleGroupNumbers = new List<number>(list);
-                }
-            }
-        }],
-        ['4', {
-            checkResult: (doc: Doc) => NumListCast(doc?.visibleGroupNumbers).includes(3),
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom"  && StrCast(doc?.customSortField) === "chat",
             setDoc: (doc: Doc, dv: DocumentView) => {
-                let list = NumListCast(doc.visibleGroupNumbers);
-                if (list.includes(3)) {
-                    let newList = new List<number>(list.filter(d => d !== 3));
-                    doc.visibleGroupNumbers = newList;
-                } else {
-                    list.push(3);
-                    doc.visibleGroupNumbers = new List<number>(list);
-                }
-            }
+                doc.cardSort = "custom";
+                doc.customSortField = "chat";
+                doc.visibleGroupNumbers = new List<number>();
+            },
         }],
     ]);
+    for (let i = 0; i < 8; i++) {
+        map.set((i + 1 + '') as any, {
+            checkResult: (doc: Doc) => NumListCast(doc?.visibleGroupNumbers).includes(i),
+            setDoc: (doc: Doc, dv: DocumentView) => {
+                const list = NumListCast(doc.visibleGroupNumbers);
+                doc.visibleGroupNumbers = new List<number>(list.includes(i) ? list.filter(d => d !== i) : [...list, i]);
+            },
+        });
+    }
 
     if (checkResult) {
         return map.get(attr)?.checkResult(selected);
@@ -257,13 +216,11 @@ ScriptingGlobals.add(function showFreeform(
     setTimeout(() => batch.end(), 100);
 });
 
-
-ScriptingGlobals.add(function isThatCardGroup(n: number){
-    const canvas = SelectionManager.Docs.lastElement();
-    return canvas.customSortNumber == n
-
-
-})
+ScriptingGlobals.add(function cardHasLabel(label: string) {
+    const selected = SelectionManager.Docs.lastElement();
+    const labelNum = Number(label) - 1;
+    return labelNum < 4 || (selected && DocListCast(selected[Doc.LayoutFieldKey(selected)]).some(doc => doc[StrCast(selected.customSortField)] == labelNum));
+}, '');
 
 // ScriptingGlobals.add(function setCardSortAttr(attr: 'time' | 'docType' | 'color', value: any, checkResult?: boolean) {
 //     // const editorView = RichTextMenu.Instance?.TextView?.EditorView;
-- 
cgit v1.2.3-70-g09d2


From 9badfc489f00afd10489f0dde0a3b9e21817feb5 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sat, 18 May 2024 23:15:49 -0400
Subject: more cardView cleanup

---
 src/Utils.ts                                       |   1 -
 src/client/apis/gpt/GPT.ts                         |  22 ++--
 src/client/documents/Documents.ts                  |  65 +----------
 src/client/util/SelectionManager.ts                |   1 -
 src/client/views/DocComponent.tsx                  |   2 +-
 .../views/collections/CollectionCardDeckView.tsx   |  20 ++--
 src/client/views/collections/CollectionMenu.tsx    |   2 -
 src/client/views/collections/CollectionView.tsx    |   2 -
 .../CollectionFreeFormLayoutEngines.tsx            |  26 -----
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   8 +-
 .../collections/collectionFreeForm/MarqueeView.tsx |  19 +---
 .../collectionSchema/CollectionSchemaView.tsx      |   2 +-
 src/client/views/global/globalScripts.ts           |  32 +++---
 src/client/views/nodes/PDFBox.tsx                  |   3 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   7 +-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         | 126 +++++++++------------
 16 files changed, 105 insertions(+), 233 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index fb72a5836..291d7c799 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -179,7 +179,6 @@ export namespace Utils {
     export function toRGBAstr(col: { r: number; g: number; b: number; a?: number }) {
         return 'rgba(' + col.r + ',' + col.g + ',' + col.b + (col.a !== undefined ? ',' + col.a : '') + ')';
     }
-    
 
     export function HSLtoRGB(h: number, s: number, l: number) {
         // Must be fractions of 1
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 398f8ae39..3b3b3b9a0 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -6,7 +6,7 @@ enum GPTCallType {
     COMPLETION = 'completion',
     EDIT = 'edit',
     SORT = 'sort',
-    DESCRIBE = 'describe'
+    DESCRIBE = 'describe',
 }
 
 type GPTCallOpts = {
@@ -20,13 +20,15 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
     summary: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' },
     edit: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' },
     completion: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: '' },
-    sort:{model:'gpt-4o',maxTokens:2048,temp:0.5,prompt:"I'm going to give you a list of descriptions. Each one is seperated by ====== on either side. They will vary in length, so make sure to only seperate when you see ======. Sort them into lists by shared content. MAKE SURE EACH DESCRIPTOR IS IN ONLY ONE LIST. Generate only the list with each list seperated by ====== with the elements seperated by ~~~~~~. Try to do around 4 groups, but a little more or less is ok."},
-    describe:{model:'gpt-4-vision-preview',maxTokens:2048,temp:0,prompt:"Describe these images in 3-5 words"},
-
-
+    sort: {
+        model: 'gpt-4o',
+        maxTokens: 2048,
+        temp: 0.5,
+        prompt: "I'm going to give you a list of descriptions. Each one is seperated by ====== on either side. They will vary in length, so make sure to only seperate when you see ======. Sort them into lists by shared content. MAKE SURE EACH DESCRIPTOR IS IN ONLY ONE LIST. Generate only the list with each list seperated by ====== with the elements seperated by ~~~~~~. Try to do around 4 groups, but a little more or less is ok.",
+    },
+    describe: { model: 'gpt-4-vision-preview', maxTokens: 2048, temp: 0, prompt: 'Describe these images in 3-5 words' },
 };
 
-
 /**`
  * Calls the OpenAI API.
  *
@@ -39,7 +41,7 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => {
     const opts: GPTCallOpts = callTypeMap[callType];
     try {
         const configuration: ClientOptions = {
-            apiKey: "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",
+            apiKey: process.env.OPENAI_KEY,
             dangerouslyAllowBrowser: true,
         };
         const openai = new OpenAI(configuration);
@@ -63,11 +65,10 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => {
     }
 };
 
-
 const gptImageLabel = async (imgUrl: string): Promise<string> => {
     try {
         const configuration: ClientOptions = {
-            apiKey: "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",
+            apiKey: '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',
             dangerouslyAllowBrowser: true,
         };
 
@@ -100,7 +101,6 @@ const gptImageLabel = async (imgUrl: string): Promise<string> => {
     }
 };
 
-
 const gptImageCall = async (prompt: string, n?: number) => {
     try {
         const configuration: ClientOptions = {
@@ -122,4 +122,4 @@ const gptImageCall = async (prompt: string, n?: number) => {
     }
 };
 
-export { gptAPICall, gptImageCall, gptImageLabel, GPTCallType };
\ No newline at end of file
+export { gptAPICall, gptImageCall, gptImageLabel, GPTCallType };
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 80774f4ad..903bd907a 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -500,27 +500,8 @@ export class DocumentOptions {
     userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)');
 
     cardSort?: STRt = new StrInfo('way cards are sorted in deck view');
-    customSortNumber?: NUMt = new NumInfo('number of custom sorts the user has created');
-    // customGroup1?: List<Doc>
-    // customGroup2?: List<Doc>
-    // customGroup3?: List<Doc>
-    visibleGroupNumbers?: List<number>
-    custom1Group?: NUMt = new NumInfo('Which group a card is in for the 1st custom grouping');
-    custom2Group?: NUMt = new NumInfo('Which group a card is in for the 2nd custom grouping');
-    custom3Group?: NUMt = new NumInfo('Which group a card is in for the 3rd custom grouping');
-    chatGroup?: NUMt = new NumInfo("Which group a card is in for the chat's grouping");
-    chatAmGroups?: NUMt = new NumInfo("Number of cards created by chat sort");
-
-
-
-
-
-
-    // card_sort_time?: BOOLt = new BoolInfo('whether sorting cards in deck view by time');
-    // card_sort_type?: BOOLt = new BoolInfo('whether sorting cards in deck view by type');
-    // card_sort_color?: BOOLt = new BoolInfo('whether sorting cards in deck view by color');
-
-
+    cardSort_customField?: STRt = new StrInfo('field key used for sorting cards');
+    cardSort_visibleSortGroups?: List<number>; // which sorting values are being filtered (shown)
 }
 
 export const DocOptions = new DocumentOptions();
@@ -1239,8 +1220,6 @@ export namespace Docs {
             );
         }
 
-        
-
         export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Linear }, id);
         }
@@ -1258,7 +1237,7 @@ export namespace Docs {
         }
 
         export function CardDeckDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Card});
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Card });
         }
 
         export function SchemaDocument(schemaHeaders: SchemaHeaderField[], documents: Array<Doc>, options: DocumentOptions) {
@@ -1911,44 +1890,6 @@ export namespace DocUtils {
         }
     }
 
-    export function spreadCards(docList: Doc[], x: number = 0, y: number = 0, spreadAngle: number = 30, radius: number = 100, create: boolean = true) {
-        console.log('spread cards');
-        const totalCards = docList.length;
-        const halfSpreadAngle = spreadAngle * 0.5;
-        const angleStep = spreadAngle / (totalCards - 1);
-        
-        runInAction(() => {
-            docList.forEach((d, i) => {
-                DocUtils.iconify(d);
-                const angle = (-halfSpreadAngle + angleStep * i) * (Math.PI / 180); // Convert degrees to radians
-                d.x = x + Math.cos(angle) * radius;
-                d.y = y + Math.sin(angle) * radius;
-                d.rotation = angle; 
-                d._timecodeToShow = undefined;
-            });
-        });
-
-        if (create) {
-            const newCollection = Docs.Create.CardDeckDocument(docList, { 
-                title: 'card-spread', 
-                _freeform_noZoom: true, 
-                x: x - radius, 
-                y: y - radius, 
-                _width: radius * 2, 
-                _height: radius * 2, 
-                dragWhenActive: true, 
-                _layout_fitWidth: false 
-            });
-            // Adjust position based on the collection's dimensions if needed
-            newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - radius;
-            newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - radius;
-            newCollection._width = newCollection._height = radius * 2;
-            return newCollection;
-        }
-    }
-
-
-
     export function makeIntoPortal(doc: Doc, layoutDoc: Doc, allLinks: Doc[]) {
         const portalLink = allLinks.find(d => d.link_anchor_1 === doc && d.link_relationship === 'portal to:portal from');
         if (!portalLink) {
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index d507e35ad..36b926053 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -68,7 +68,6 @@ export class SelectionManager {
     public static IsSelected = (doc?: Doc) => Array.from(doc?.[DocViews] ?? []).some(dv => dv?.IsSelected);
     public static get Views() { return this.Instance.SelectedViews; } // prettier-ignore
     public static get SelectedSchemaDoc() { return this.Instance.SelectedSchemaDocument; } // prettier-ignore
-    
     public static get Docs() { return this.Instance.SelectedViews.map(dv => dv.Document).filter(doc => doc?._type_collection !== CollectionViewType.Docking); } // prettier-ignore
 }
 
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index ef4257937..de4df1830 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -35,7 +35,7 @@ export interface ViewBoxInterface {
     addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections)
     removeDocument?: (doc: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean, dontAddToRemoved?: boolean) => boolean; // add a document (used only by collections)
     select?: (ctrlKey: boolean, shiftKey: boolean) => void;
-    focus?: (textAnchor: Doc, options: FocusViewOptions) => Opt<number>; 
+    focus?: (textAnchor: Doc, options: FocusViewOptions) => Opt<number>;
     viewTransition?: () => Opt<string>; // duration of a view transition animation
     isAnyChildContentActive?: () => boolean; // is any child content of the document active
     onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 9e5668ffa..5f8ddd5c1 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -75,7 +75,7 @@ export class CollectionCardView extends CollectionSubView() {
 
     componentDidMount(): void {
         this._disposers.sort = reaction(
-            () => ({ cardSort: this.cardSort, field: this.customSortField }),
+            () => ({ cardSort: this.cardSort, field: this.cardSort_customField }),
             ({ cardSort, field }) => (cardSort === cardSortings.Custom && field === 'chat' ? this.openChatPopup() : GPTPopup.Instance.setVisible(false))
         );
     }
@@ -85,8 +85,8 @@ export class CollectionCardView extends CollectionSubView() {
         this._dropDisposer?.();
     }
 
-    @computed get customSortField() {
-        return StrCast(this.Document.customSortField) as any as 'chat' | 'star' | 'idea' | 'like';
+    @computed get cardSort_customField() {
+        return StrCast(this.Document.cardSort_customField) as any as 'chat' | 'star' | 'idea' | 'like';
     }
 
     @computed get cardSort() {
@@ -115,12 +115,12 @@ export class CollectionCardView extends CollectionSubView() {
      */
     @computed get childDocsWithoutLinks() {
         const regularDocs = this.childDocs.filter(l => l.type !== DocumentType.LINK);
-        const activeGroups = NumListCast(this.Document.visibleGroupNumbers);
+        const activeGroups = NumListCast(this.Document.cardSort_visibleSortGroups);
 
         if (activeGroups.length > 0 && this.cardSort === cardSortings.Custom) {
             return regularDocs.filter(doc => {
                 // Get the group number for the current index
-                const groupNumber = CollectionCardView.getButtonGroup(this.customSortField, doc);
+                const groupNumber = CollectionCardView.getButtonGroup(this.cardSort_customField, doc);
                 // Check if the group number is in the active groups
                 return groupNumber !== undefined && activeGroups.includes(groupNumber);
             });
@@ -245,8 +245,8 @@ export class CollectionCardView extends CollectionSubView() {
                                   return [DashColor(StrCast(docA.backgroundColor)).hsv().toString(), // If docA.type is undefined, use an empty string
                                           DashColor(StrCast(docB.backgroundColor)).hsv().toString()]; // If docB.type is undefined, use an empty string
                     case cardSortings.Custom:
-                                  return [CollectionCardView.getButtonGroup(this.customSortField, docA)??0,
-                                          CollectionCardView.getButtonGroup(this.customSortField, docB)??0];
+                                  return [CollectionCardView.getButtonGroup(this.cardSort_customField, docA)??0,
+                                          CollectionCardView.getButtonGroup(this.cardSort_customField, docB)??0];
                     default:      return [StrCast(docA.type), // If docA.type is undefined, use an empty string
                                           StrCast(docB.type)]; // If docB.type is undefined, use an empty string
                     } // prettier-ignore
@@ -335,7 +335,7 @@ export class CollectionCardView extends CollectionSubView() {
      * @param buttonID
      * @param doc
      */
-    toggleButton = undoable((buttonID: number, doc: Doc) => this.customSortField && (doc[this.customSortField] = buttonID), 'toggle custom button');
+    toggleButton = undoable((buttonID: number, doc: Doc) => this.cardSort_customField && (doc[this.cardSort_customField] = buttonID), 'toggle custom button');
 
     /**
      * A list of the text content of all the child docs. RTF documents will have just their text and pdf documents will have the first 50 words.
@@ -422,8 +422,8 @@ export class CollectionCardView extends CollectionSubView() {
      */
     renderButtons = (doc: Doc, cardSort: cardSortings) => {
         if (cardSort !== cardSortings.Custom) return '';
-        const amButtons = Math.max(4, this.childDocs?.reduce((set, doc) => this.customSortField && set.add(NumCast(doc[this.customSortField])), new Set<number>()).size ?? 0);
-        const activeButtonIndex = CollectionCardView.getButtonGroup(this.customSortField, doc);
+        const amButtons = Math.max(4, this.childDocs?.reduce((set, doc) => this.cardSort_customField && set.add(NumCast(doc[this.cardSort_customField])), new Set<number>()).size ?? 0);
+        const activeButtonIndex = CollectionCardView.getButtonGroup(this.cardSort_customField, doc);
         const totalWidth = amButtons * 35 + amButtons * 2 * 5 + 6;
         return (
             <div className="card-button-container" style={{ width: `${totalWidth}px` }}>
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 81d9f4eea..94896f277 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -321,8 +321,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
                 return this._freeform_commands;
             case CollectionViewType.Card:
                 return this._freeform_commands;
-            
-            
         }
     }
     private _commandRef = React.createRef<HTMLInputElement>();
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index b7805bf3f..97a0b7bef 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -137,8 +137,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
             case CollectionViewType.Time:         return <CollectionTimeView        key="collview" {...props} />;
             case CollectionViewType.Grid:         return <CollectionGridView        key="collview" {...props} />;
             case CollectionViewType.Card:         return <CollectionCardView        key="collview" {...props} />;
-
-
         }
     };
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 22005eb23..c83c26509 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -125,32 +125,6 @@ export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc
     return normalizeResults(burstDiam, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]);
 }
 
-// export function computeCardDeckLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
-//     const docMap = new Map<string, PoolData>();
-//     const burstDiam = [NumCast(pivotDoc._width), NumCast(pivotDoc._height)];
-//     const burstScale = NumCast(pivotDoc._starburstDocScale, 1);
-
-//     childPairs.forEach(({ layout, data }, i) => {
-//         const aspect = NumCast(layout._height) / NumCast(layout._width);
-//         const docSize = Math.min(Math.min(400, NumCast(layout._width)), Math.min(400, NumCast(layout._width)) / aspect) * burstScale;
-//         const deg = (i / childPairs.length) * Math.PI * 2;
-//         docMap.set(layout[Id], {
-//             x: Math.min(burstDiam[0] / 2 - docSize, Math.max(-burstDiam[0] / 2, (Math.cos(deg) * burstDiam[0]) / 2 - docSize / 2)),
-//             y: Math.min(burstDiam[1] / 2 - docSize * aspect, Math.max(-burstDiam[1] / 2, (Math.sin(deg) * burstDiam[1]) / 2 - (docSize / 2) * aspect)),
-//             width: docSize,
-//             height: docSize * aspect,
-//             zIndex: NumCast(layout.zIndex),
-//             pair: { layout, data },
-//             replica: '',
-//             color: 'white',
-//             backgroundColor: 'white',
-//             transition: 'all 0.3s',
-//         });
-//     });
-//     const divider = { type: 'div', color: 'transparent', x: -burstDiam[0] / 2, y: -burstDiam[1] / 2, width: 15, height: 15, payload: undefined };
-//     return normalizeResults(burstDiam, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]);
-// }
-
 export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
     const docMap = new Map<string, PoolData>();
     const fieldKey = 'data';
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 11193f496..079a5d977 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -49,9 +49,7 @@ import { CollectionSubView } from '../CollectionSubView';
 import { TreeViewType } from '../CollectionTreeView';
 import { CollectionFreeFormBackgroundGrid } from './CollectionFreeFormBackgroundGrid';
 import { CollectionFreeFormInfoUI } from './CollectionFreeFormInfoUI';
-import { computePassLayout, computePivotLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult,
-    //  computeCardDeckLayout 
-    } from './CollectionFreeFormLayoutEngines';
+import { computePassLayout, computePivotLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines';
 import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannableContents';
 import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors';
 import './CollectionFreeFormView.scss';
@@ -1385,8 +1383,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             case computeTimelineLayout.name:  return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
             case computePivotLayout.name:     return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) };
             case computeStarburstLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeStarburstLayout) };
-            // case computeCardDeckLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeCardDeckLayout) };
-
         }
         return { newPool, computedElementData: this.doFreeformLayout(newPool) };
     }
@@ -2010,4 +2006,4 @@ ScriptingGlobals.add(function datavizFromSchema(doc: Doc) {
             SchemaCSVPopUp.Instance.setVisible(true);
         }
     });
-});
\ No newline at end of file
+});
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index e44c37873..25a31fd6e 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -385,18 +385,6 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
         this.hideMarquee();
     });
 
-    // @undoBatch
-    // spreadCards = action((e: KeyboardEvent | React.PointerEvent | undefined) => {
-    //     const selected = this.marqueeSelect(false);
-    //     SelectionManager.DeselectAll();
-    //     selected.forEach(d => this._props.removeDocument?.(d));
-    //     const newCollection = DocUtils.spreadCards(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2)!;
-    //     this._props.addDocument?.(newCollection);
-    //     this._props.selectDocuments([newCollection]);
-    //     MarqueeOptionsMenu.Instance.fadeOut(true);
-    //     this.hideMarquee();
-    // });
-
     /**
      * This triggers the TabDocView.PinDoc method which is the universal method
      * used to pin documents to the currently active presentation trail.
@@ -522,7 +510,6 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
 
     @action
     marqueeCommand = (e: KeyboardEvent) => {
-
         if (this._commandExecuted || (e as any).propagationIsStopped) {
             return;
         }
@@ -533,8 +520,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
             this.delete(e, e.key === 'h');
             e.stopPropagation();
         }
-        if ('ctsSpgac'.indexOf(e.key) !== -1) {
-
+        if ('ctsSpg'.indexOf(e.key) !== -1) {
             this._commandExecuted = true;
             e.stopPropagation();
             e.preventDefault();
@@ -542,8 +528,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
             if (e.key === 'g') this.collection(e, true);
             if (e.key === 'c' || e.key === 't') this.collection(e);
             if (e.key === 's' || e.key === 'S') this.summary(e);
-            if (e.key === 'p') this.pileup(e)
-            // if (e.key === 'c') this.spreadCards(e);
+            if (e.key === 'p') this.pileup(e);
 
             this.cleanupInteractions(false);
         }
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 731ea1235..6a956f2ac 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -51,7 +51,7 @@ export const FInfotoColType: { [key: string]: ColumnType } = {
     enumeration: ColumnType.Enumeration,
 };
 
-const  defaultColumnKeys: string[] = ['title', 'type', 'author', 'author_date', 'text'];
+const defaultColumnKeys: string[] = ['title', 'type', 'author', 'author_date', 'text'];
 
 @observer
 export class CollectionSchemaView extends CollectionSubView() {
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 35a3a2e31..d5f6921a2 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -166,44 +166,44 @@ ScriptingGlobals.add(function showFreeform(
             setDoc: (doc: Doc, dv: DocumentView) => doc.cardSort = "links",
         }],
         ['like', {
-            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && StrCast(doc?.customSortField) === "like",
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && StrCast(doc?.cardSort_customField) === "like",
             setDoc: (doc: Doc, dv: DocumentView) => {
                 doc.cardSort = "custom";
-                doc.customSortField = "like";
-                doc.visibleGroupNumbers = new List<number>();
+                doc.cardSort_customField = "like";
+                doc.cardSort_visibleSortGroups = new List<number>();
             }
         }],
         ['star', {
-            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && StrCast(doc?.customSortField) === "star",
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && StrCast(doc?.cardSort_customField) === "star",
             setDoc: (doc: Doc, dv: DocumentView) => {
                 doc.cardSort = "custom";
-                doc.customSortField = "star";
-                doc.visibleGroupNumbers = new List<number>();
+                doc.cardSort_customField = "star";
+                doc.cardSort_visibleSortGroups = new List<number>();
             }
         }],
         ['idea', {
-            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && StrCast(doc?.customSortField) === "idea",
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom" && StrCast(doc?.cardSort_customField) === "idea",
             setDoc: (doc: Doc, dv: DocumentView) => {
                 doc.cardSort = "custom";
-                doc.customSortField = "idea";
-                doc.visibleGroupNumbers = new List<number>();
+                doc.cardSort_customField = "idea";
+                doc.cardSort_visibleSortGroups = new List<number>();
             }
         }],
         ['chat', {
-            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom"  && StrCast(doc?.customSortField) === "chat",
+            checkResult: (doc: Doc) => StrCast(doc?.cardSort) === "custom"  && StrCast(doc?.cardSort_customField) === "chat",
             setDoc: (doc: Doc, dv: DocumentView) => {
                 doc.cardSort = "custom";
-                doc.customSortField = "chat";
-                doc.visibleGroupNumbers = new List<number>();
+                doc.cardSort_customField = "chat";
+                doc.cardSort_visibleSortGroups = new List<number>();
             },
         }],
     ]);
     for (let i = 0; i < 8; i++) {
         map.set((i + 1 + '') as any, {
-            checkResult: (doc: Doc) => NumListCast(doc?.visibleGroupNumbers).includes(i),
+            checkResult: (doc: Doc) => NumListCast(doc?.cardSort_visibleSortGroups).includes(i),
             setDoc: (doc: Doc, dv: DocumentView) => {
-                const list = NumListCast(doc.visibleGroupNumbers);
-                doc.visibleGroupNumbers = new List<number>(list.includes(i) ? list.filter(d => d !== i) : [...list, i]);
+                const list = NumListCast(doc.cardSort_visibleSortGroups);
+                doc.cardSort_visibleSortGroups = new List<number>(list.includes(i) ? list.filter(d => d !== i) : [...list, i]);
             },
         });
     }
@@ -219,7 +219,7 @@ ScriptingGlobals.add(function showFreeform(
 ScriptingGlobals.add(function cardHasLabel(label: string) {
     const selected = SelectionManager.Docs.lastElement();
     const labelNum = Number(label) - 1;
-    return labelNum < 4 || (selected && DocListCast(selected[Doc.LayoutFieldKey(selected)]).some(doc => doc[StrCast(selected.customSortField)] == labelNum));
+    return labelNum < 4 || (selected && DocListCast(selected[Doc.LayoutFieldKey(selected)]).some(doc => doc[StrCast(selected.cardSort_customField)] == labelNum));
 }, '');
 
 // ScriptingGlobals.add(function setCardSortAttr(attr: 'time' | 'docType' | 'color', value: any, checkResult?: boolean) {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 90d6133e4..3e0270aa3 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -29,11 +29,10 @@ import { CreateImage } from '../nodes/WebBoxRenderer';
 import { PDFViewer } from '../pdf/PDFViewer';
 import { SidebarAnnos } from '../SidebarAnnos';
 import { DocumentView, OpenWhere } from './DocumentView';
-import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
+import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView';
 import { ImageBox } from './ImageBox';
 import './PDFBox.scss';
 import { PinProps, PresBox } from './trails';
-import { Networking } from '../../Network';
 
 @observer
 export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index f856d9637..43010b2ed 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -972,8 +972,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 event: () => (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight),
                 icon: this.Document._layout_autoHeight ? 'lock' : 'unlock',
             });
-        optionItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: <BsMarkdownFill /> });
-        !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'sliders' });
+        !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
+        const help = cm.findByDescription('Help...');
+        const helpItems = help && 'subitems' in help ? help.subitems : [];
+        helpItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: <BsMarkdownFill /> });
+        !help && cm.addItem({ description: 'Help...', subitems: helpItems, icon: 'eye' });
         this._downX = this._downY = Number.NaN;
     };
 
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 4fb757d8a..0ba62d60e 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -21,7 +21,7 @@ export enum GPTPopupMode {
     EDIT,
     IMAGE,
     DATA,
-    SORT
+    SORT,
 }
 
 interface GPTPopupProps {}
@@ -60,7 +60,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     public dataChatPrompt: string | null = null;
     @action
     public setDataJson = (text: string) => {
-        if (text=="") this.dataChatPrompt = "";
+        if (text == '') this.dataChatPrompt = '';
         this.dataJson = text;
     };
 
@@ -95,21 +95,18 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     private done: boolean = false;
     @action
     public setDone = (done: boolean) => {
-        console.log("HIIIIIIIII")
         this.done = done;
         this.chatMode = false;
     };
 
     @observable
-    private sortDone: boolean = false // this is so redundant but the og done variable was causing weird unknown problems and im just a girl
+    private sortDone: boolean = false; // this is so redundant but the og done variable was causing weird unknown problems and im just a girl
 
     @action
     public setSortDone = (done: boolean) => {
-        console.log("HIIIIIIIII")
         this.sortDone = done;
     };
 
-
     // change what can be a ref into a ref
     @observable
     private sidebarId: string = '';
@@ -133,26 +130,20 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     };
 
     @observable
-    public sortDesc: string = ''
+    public sortDesc: string = '';
 
-    @action public setSortDesc = (t:string) => {
-        this.sortDesc = t
-    }
+    @action public setSortDesc = (t: string) => {
+        this.sortDesc = t;
+    };
 
     @observable onSortComplete?: (sortResult: string) => void;
     @observable cardsDoneLoading = false;
 
     @action setCardsDoneLoading(done: boolean) {
-        console.log(done + "HI HIHI")
+        console.log(done + 'HI HIHI');
         this.cardsDoneLoading = done;
     }
 
-
-
-
-
-
-
     public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false;
     public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined;
 
@@ -164,12 +155,11 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         this.setSortDone(false);
 
         try {
-
             const res = await gptAPICall(this.sortDesc, GPTCallType.SORT);
             // Trigger the callback with the result
             if (this.onSortComplete) {
                 this.onSortComplete(res || 'Something went wrong :(');
-                console.log(res)
+                console.log(res);
             }
         } catch (err) {
             console.error(err);
@@ -177,9 +167,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
 
         this.setLoading(false);
         this.setSortDone(true);
-    }
-
-
+    };
 
     /**
      * Generates a Dalle image and uploads it to the server.
@@ -219,7 +207,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
             console.error(err);
         }
         GPTPopup.Instance.setLoading(false);
-    }
+    };
 
     generateDataAnalysis = async () => {
         GPTPopup.Instance.setVisible(true);
@@ -231,7 +219,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
             console.error(err);
         }
         GPTPopup.Instance.setLoading(false);
-    }
+    };
 
     /**
      * Transfers the summarization text to a sidebar annotation text document.
@@ -282,7 +270,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
      */
     private chatWithAI = () => {
         this.chatMode = true;
-    }
+    };
     dataPromptChanged = action((e: React.ChangeEvent<HTMLInputElement>) => {
         this.dataChatPrompt = e.target.value;
     });
@@ -304,14 +292,14 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     sortBox = () => (
         <>
             <div>
-                {this.heading("SORTING")}
+                {this.heading('SORTING')}
                 {this.loading ? (
                     <div className="content-wrapper">
-                    <div className="loading-spinner">
-                        <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} />
-                        <span>Loading...</span>
+                        <div className="loading-spinner">
+                            <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} />
+                            <span>Loading...</span>
+                        </div>
                     </div>
-                </div>
                 ) : (
                     <>
                         {!this.cardsDoneLoading ? (
@@ -334,27 +322,18 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
                                             width: '90%', // Almost as wide as the container
                                             textAlign: 'center',
                                             color: '#ffffff', // White text
-                                            fontSize: '16px' // Adjust font size as needed
+                                            fontSize: '16px', // Adjust font size as needed
                                         }}
                                     />
                                 </div>
                             )
                         )}
-    
+
                         {this.sortDone && (
                             <div>
                                 <div className="content-wrapper">
-                                    <p>
-                                        {this.text === "Something went wrong :(" ? 
-                                        "Something went wrong :(" : 
-                                        "Sorting done! Feel free to move things around / regenerate :) !"}
-                                    </p>
-                                    <IconButton
-                                        tooltip="Generate Again"
-                                        onClick={() => this.setSortDone(false)}
-                                        icon={<FontAwesomeIcon icon="redo-alt" size="lg" />}
-                                        color={StrCast(Doc.UserDoc().userVariantColor)}
-                                    />
+                                    <p>{this.text === 'Something went wrong :(' ? 'Something went wrong :(' : 'Sorting done! Feel free to move things around / regenerate :) !'}</p>
+                                    <IconButton tooltip="Generate Again" onClick={() => this.setSortDone(false)} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />
                                 </div>
                             </div>
                         )}
@@ -364,21 +343,17 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
         </>
     );
 
-    
-    
-
-
-//     <>
-//     <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
-//     <Button tooltip="Chat with AI" text="Chat with AI" onClick={this.chatWithAI} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
-// </>
-// ) : (
-// <div className="summarizing">
-//     <span>Sorting</span>
-//     <ReactLoading type="bubbles" color="#bcbcbc" width={20} height={20} />
-//     <Button text="Stop Animation" onClick={() => {this.setDone(true);}} color={StrCast(Doc.UserDoc().userVariantColor)}  type={Type.TERT}/>
-// </div>
-// )}
+    //     <>
+    //     <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
+    //     <Button tooltip="Chat with AI" text="Chat with AI" onClick={this.chatWithAI} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
+    // </>
+    // ) : (
+    // <div className="summarizing">
+    //     <span>Sorting</span>
+    //     <ReactLoading type="bubbles" color="#bcbcbc" width={20} height={20} />
+    //     <Button text="Stop Animation" onClick={() => {this.setDone(true);}} color={StrCast(Doc.UserDoc().userVariantColor)}  type={Type.TERT}/>
+    // </div>
+    // )}
 
     imageBox = () => {
         return (
@@ -457,7 +432,7 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     dataAnalysisBox = () => (
         <>
             <div>
-                {this.heading("ANALYSIS")}
+                {this.heading('ANALYSIS')}
                 <div className="content-wrapper">
                     {!this.loading &&
                         (!this.done ? (
@@ -479,8 +454,8 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
             </div>
             {!this.loading && (
                 <div className="btns-wrapper">
-                    {this.done? 
-                        this.chatMode?(
+                    {this.done ? (
+                        this.chatMode ? (
                             <input
                                 defaultValue=""
                                 autoComplete="off"
@@ -493,19 +468,26 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
                                 placeholder="Ask GPT a question about the data..."
                                 id="search-input"
                                 className="searchBox-input"
-                                style={{width: "100%"}}
+                                style={{ width: '100%' }}
                             />
-                            )
-                        :(
-                        <>
-                            <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
-                            <Button tooltip="Chat with AI" text="Chat with AI" onClick={this.chatWithAI} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
-                        </>
+                        ) : (
+                            <>
+                                <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
+                                <Button tooltip="Chat with AI" text="Chat with AI" onClick={this.chatWithAI} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
+                            </>
+                        )
                     ) : (
                         <div className="summarizing">
                             <span>Summarizing</span>
                             <ReactLoading type="bubbles" color="#bcbcbc" width={20} height={20} />
-                            <Button text="Stop Animation" onClick={() => {this.setDone(true);}} color={StrCast(Doc.UserDoc().userVariantColor)}  type={Type.TERT}/>
+                            <Button
+                                text="Stop Animation"
+                                onClick={() => {
+                                    this.setDone(true);
+                                }}
+                                color={StrCast(Doc.UserDoc().userVariantColor)}
+                                type={Type.TERT}
+                            />
                         </div>
                     )}
                 </div>
@@ -533,10 +515,8 @@ export class GPTPopup extends ObservableReactComponent<GPTPopupProps> {
     render() {
         return (
             <div className="summary-box" style={{ display: this.visible ? 'flex' : 'none' }}>
-                {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() :
-                this.mode === GPTPopupMode.DATA ? this.dataAnalysisBox() :
-                this.mode === GPTPopupMode.IMAGE ? this.imageBox() :
-                this.mode === GPTPopupMode.SORT ? this.sortBox() : <></>}            </div>
+                {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.DATA ? this.dataAnalysisBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : this.mode === GPTPopupMode.SORT ? this.sortBox() : <></>}{' '}
+            </div>
         );
     }
 }
-- 
cgit v1.2.3-70-g09d2


From 13dc6de0e0099f699ad0d2bb54401e6a0aa25018 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 19 May 2024 00:05:23 -0400
Subject: merged with soon-to-be-master

---
 .../views/collections/CollectionCardDeckView.tsx   | 26 +++++++++++-----------
 1 file changed, 13 insertions(+), 13 deletions(-)

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

diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index 5f8ddd5c1..34800040c 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -1,7 +1,8 @@
 import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { DashColor, Utils, numberRange, returnFalse, returnZero } from '../../../Utils';
+import { ClientUtils, DashColor, returnFalse, returnZero } from '../../../ClientUtils';
+import { numberRange } from '../../../Utils';
 import { Doc, NumListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
@@ -10,11 +11,10 @@ import { URLField } from '../../../fields/URLField';
 import { gptImageLabel } from '../../apis/gpt/GPT';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DragManager } from '../../util/DragManager';
-import { SelectionManager } from '../../util/SelectionManager';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoable } from '../../util/UndoManager';
-import { StyleProp } from '../StyleProvider';
+import { StyleProp } from '../StyleProp';
 import { DocumentView } from '../nodes/DocumentView';
 import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
 import './CollectionCardDeckView.scss';
@@ -139,7 +139,7 @@ export class CollectionCardView extends CollectionSubView() {
 
     @action
     setHoveredNodeIndex = (index: number) => {
-        if (!SelectionManager.IsSelected(this.childDocs[index])) {
+        if (!DocumentView.SelectedDocs().includes(this.childDocs[index])) {
             this._hoveredNodeIndex = index;
         }
     };
@@ -148,14 +148,14 @@ export class CollectionCardView extends CollectionSubView() {
      * @param index
      * @returns
      */
-    translateHover = (index: number) => (this._hoveredNodeIndex === index && !SelectionManager.IsSelected(this.childDocs[index]) ? -50 : 0);
+    translateHover = (index: number) => (this._hoveredNodeIndex === index && !DocumentView.SelectedDocs().includes(this.childDocs[index]) ? -50 : 0);
 
-    isSelected = (index: number) => SelectionManager.IsSelected(this.childDocs[index]);
+    isSelected = (index: number) => DocumentView.SelectedDocs().includes(this.childDocs[index]);
 
     /**
      * Returns all the documents except the one that's currently selected
      */
-    inactiveDocs = () => this.childDocsWithoutLinks.filter(d => !SelectionManager.IsSelected(d));
+    inactiveDocs = () => this.childDocsWithoutLinks.filter(d => !DocumentView.SelectedDocs().includes(d));
 
     panelWidth = () => this._childDocumentWidth;
     panelHeight = (layout: Doc) => () => (this.panelWidth() * NumCast(layout._height)) / NumCast(layout._width);
@@ -267,7 +267,7 @@ export class CollectionCardView extends CollectionSubView() {
                 Document={doc}
                 NativeWidth={returnZero}
                 NativeHeight={returnZero}
-                layout_fitWidth={returnFalse}
+                fitWidth={returnFalse}
                 onDoubleClickScript={this.onChildDoubleClick}
                 renderDepth={this._props.renderDepth + 1}
                 LayoutTemplate={this._props.childLayoutTemplate}
@@ -442,18 +442,18 @@ export class CollectionCardView extends CollectionSubView() {
      * Actually renders all the cards
      */
     renderCards = () => {
-        const anySelected = this.childDocs.some(doc => SelectionManager.IsSelected(doc));
+        const anySelected = this.childDocs.some(doc => DocumentView.SelectedDocs().includes(doc));
         // Map sorted documents to their rendered components
         return this.sortedDocs.map((doc, index) => {
-            const realIndex = this.sortedDocs.filter(sortDoc => !SelectionManager.IsSelected(sortDoc)).indexOf(doc);
+            const realIndex = this.sortedDocs.filter(sortDoc => !DocumentView.SelectedDocs().includes(sortDoc)).indexOf(doc);
             const calcRowIndex = this.overflowIndexCalc(realIndex);
             const amCards = this.overflowAmCardsCalc(realIndex);
-            const isSelected = SelectionManager.IsSelected(doc);
+            const isSelected = DocumentView.SelectedDocs().includes(doc);
 
             const childScreenToLocal = () => {
                 this._forceChildXf;
                 const dref = this._docRefs.get(doc);
-                const { translateX, translateY, scale } = Utils.GetScreenTransform(dref?.ContentDiv);
+                const { translateX, translateY, scale } = ClientUtils.GetScreenTransform(dref?.ContentDiv);
                 return new Transform(-translateX + (dref?.centeringX || 0) * scale, 
                                      -translateY + (dref?.centeringY || 0) * scale, 1)
                           .scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, calcRowIndex) : 0); // prettier-ignore
@@ -465,7 +465,7 @@ export class CollectionCardView extends CollectionSubView() {
                     className={`card-item${isSelected ? '-active' : anySelected ? '-inactive' : ''}`}
                     onPointerUp={() => {
                         // this turns off documentDecorations during a transition, then turns them back on afterward.
-                        SnappingManager.SetIsResizing(this.Document);
+                        SnappingManager.SetIsResizing(this.Document[Id]);
                         setTimeout(
                             action(() => {
                                 SnappingManager.SetIsResizing(undefined);
-- 
cgit v1.2.3-70-g09d2

