From 9b0fa4d5581942c54375c10e3887c8010d3eb5e4 Mon Sep 17 00:00:00 2001
From: aidahosa1 <aisosa_idahosa@brown.edu>
Date: Thu, 27 Jun 2024 14:06:22 -0400
Subject: time

---
 src/client/views/collections/CollectionCarouselView.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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

diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 2adad68e0..4884db709 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -33,7 +33,7 @@ enum practiceVal {
 export class CollectionCarouselView extends CollectionSubView() {
     private _dropDisposer?: DragManager.DragDropDisposer;
     get practiceField() { return this.fieldKey + "_practice"; } // prettier-ignore
-    get starField()     { return this.fieldKey + "_star"; } // prettier-ignore
+    get starField()     { return "star"; } // prettier-ignore
 
     constructor(props: any) {
         super(props);
-- 
cgit v1.2.3-70-g09d2


From d95730d904612640184ca6fdc00864b0c81b0c0c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 18 Sep 2024 20:46:38 -0400
Subject: lots of changes to fix dragging cards, integrate iconTags with other
 tags, sizing docs when selected to fit window,

---
 eslint.config.mjs                                  |   1 +
 src/client/util/CurrentUserUtils.ts                |  14 +-
 src/client/util/DragManager.ts                     |   3 +-
 src/client/views/DocumentButtonBar.tsx             |  44 ++----
 src/client/views/DocumentDecorations.tsx           |   4 +-
 src/client/views/StyleProvider.tsx                 |   4 -
 src/client/views/TagsView.tsx                      |  20 +--
 .../views/collections/CollectionCardDeckView.scss  |   8 +-
 .../views/collections/CollectionCardDeckView.tsx   | 152 ++++++++++++---------
 .../views/collections/CollectionCarouselView.tsx   |   1 -
 .../views/collections/CollectionStackingView.tsx   |   3 -
 src/client/views/collections/CollectionSubView.tsx |   4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   5 +-
 src/client/views/nodes/DocumentView.tsx            |   5 +
 src/client/views/nodes/IconTagBox.scss             |  24 ++--
 src/client/views/nodes/IconTagBox.tsx              | 106 +++++++-------
 .../views/nodes/formattedText/FormattedTextBox.tsx |   1 +
 src/client/views/search/FaceRecognitionHandler.tsx |   3 +-
 18 files changed, 198 insertions(+), 204 deletions(-)

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

diff --git a/eslint.config.mjs b/eslint.config.mjs
index 8926afd7c..f7063caa5 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -36,6 +36,7 @@ export default [
             'no-multi-assign': 'off',
             'no-underscore-dangle': 'off',
             'no-nested-ternary': 'off',
+            'no-param-reassign': 'error',
             'lines-between-class-members': 'off',
             'no-shadow': 'off',
             '@typescript-eslint/no-shadow': 'warn',
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index eee6be937..262916312 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -2,7 +2,7 @@
 import { reaction, runInAction } from "mobx";
 import * as rp from 'request-promise';
 import { ClientUtils, OmitKeys } from "../../ClientUtils";
-import { Doc, DocListCast, DocListCastAsync, FieldType, Opt } from "../../fields/Doc";
+import { Doc, DocListCast, DocListCastAsync, FieldType, Opt, StrListCast } from "../../fields/Doc";
 import { DocData } from "../../fields/DocSymbols";
 import { InkTool } from "../../fields/InkField";
 import { List } from "../../fields/List";
@@ -688,18 +688,18 @@ pie title Minerals in my tap water
   
     static tagGroupTools(): Button[] {
         if (!Doc.UserDoc().activeDashboard) {
-        Doc.UserDoc().myFilterHotKeyTitles = new List<string>(['Star', 'Heart', 'Bolt', 'Cloud']);
+        Doc.UserDoc().myFilterHotKeyTitles = new List<string>(['star', 'heart', 'bolt', 'cloud']);
         
-        ['Star', 'Heart', 'Bolt', 'Cloud'].forEach(key => {
+        StrListCast(Doc.UserDoc().myFilterHotKeyTitles).forEach(key => {
             Doc.UserDoc()[key] = key.toLowerCase();
         });
 }
         // hack: if there's no dashboard, create default filters.  otherwise, just make sure that the Options button is preserved
         return (Doc.UserDoc().activeDashboard ? [] : [
-            { title: "Star", isSystem: false, icon: "star", toolTip:"Click to toggle the star group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
-            { title: "Heart", isSystem: false,icon: "heart", toolTip:"Click to toggle the heart group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
-            { title: "Bolt", isSystem: false,icon: "bolt", toolTip:"Click to toggle the bolt group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
-            { title: "Cloud", isSystem: false,icon: "cloud", toolTip:"Click to toggle the cloud group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "star",  isSystem: false,icon: "star", toolTip:"Click to toggle the star group's visibility",   btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "heart", isSystem: false,icon: "heart", toolTip:"Click to toggle the heart group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "bolt",  isSystem: false,icon: "bolt", toolTip:"Click to toggle the bolt group's visibility",    btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
+            { title: "cloud", isSystem: false,icon: "cloud", toolTip:"Click to toggle the cloud group's visibility",  btnType: ButtonType.ToggleButton,  expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}},
         ]).concat([
             { title: "Options", isSystem: true,icon: "gear", toolTip:"Click to customize your filter panel", btnType: ButtonType.ClickButton,  expertMode: false, toolType:"opts", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}
         ])
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index d55d193cc..81ea840f1 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -29,7 +29,7 @@ import { dropActionType } from './DropActionTypes';
 import { SnappingManager } from './SnappingManager';
 import { UndoManager } from './UndoManager';
 
-// eslint-disable-next-line @typescript-eslint/no-var-requires
+// eslint-disable-next-line  @typescript-eslint/no-require-imports
 const { contextMenuZindex } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
 
 /**
@@ -101,7 +101,6 @@ export namespace DragManager {
 
     // event called when the drag operation results in a drop action
     export class DropEvent {
-        // eslint-disable-next-line no-useless-constructor
         constructor(
             readonly x: number,
             readonly y: number,
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 87dd5f45a..32bf67df1 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -9,7 +9,7 @@ import * as React from 'react';
 import { FaEdit } from 'react-icons/fa';
 import { returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../ClientUtils';
 import { emptyFunction } from '../../Utils';
-import { Doc } from '../../fields/Doc';
+import { Doc, DocListCast } from '../../fields/Doc';
 import { Cast, DocCast } from '../../fields/Types';
 import { DocUtils, IsFollowLinkScript } from '../documents/DocUtils';
 import { CalendarManager } from '../util/CalendarManager';
@@ -28,7 +28,6 @@ import { DocumentLinksButton } from './nodes/DocumentLinksButton';
 import { DocumentView } from './nodes/DocumentView';
 import { OpenWhere } from './nodes/OpenWhere';
 import { DashFieldView } from './nodes/formattedText/DashFieldView';
-import { DocData } from '../../fields/DocSymbols';
 
 @observer
 export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (DocumentView | undefined)[]; stack?: unknown }> {
@@ -287,44 +286,27 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
     get keywordButton() {
         const targetDoc = this.view0?.Document;
 
-        const metaBtn = (name: string, icon: IconProp) => {
-            const tooltip = `Toggle ${name}`;
-            return (
-                <Tooltip title={<div className="dash-tooltip">{tooltip}</div>}>
-                    <div className="documentButtonBar-pinIcon">
-                        <FontAwesomeIcon
-                            className="documentdecorations-icon"
-                            style={{ width: 20 }}
-                            key={icon.toString()}
-                            size="sm"
-                            icon={icon}
-                            onClick={() => {
-                                if (name === 'tags') {
-                                    targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags);
-                                } else {
-                                    targetDoc && (targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels);
-                                }
-                            }}
-                        />
-                    </div>
-                </Tooltip>
-            );
-        };
-
         return !targetDoc ? null : (
             <div className="documentButtonBar-icon">
-                <div className="documentButtonBar-pinTypes" style={{ width: '40px' }}>
+                {/* <div className="documentButtonBar-pinTypes" style={{ width: '40px' }}>
                     {metaBtn('tags', 'star')}
                     {metaBtn('keywords', 'id-card')}
-                </div>
+                </div> */}
 
                 <Tooltip title={<div className="dash-keyword-button">Open keyword menu</div>}>
                     <div
                         className="documentButtonBar-icon"
                         style={{ color: 'white' }}
-                        onClick={() => {
-                            // targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags;
-                        }}>
+                        onClick={undoable(e => {
+                            const showing = DocumentView.Selected().some(dv => dv.showTags);
+                            DocumentView.Selected().forEach(dv => {
+                                dv.layoutDoc._layout_showTags = !showing;
+                                if (e.shiftKey)
+                                    DocListCast(dv.Document[Doc.LayoutFieldKey(dv.Document) + '_annotations']).forEach(doc => {
+                                        if (doc.face) doc.hidden = showing;
+                                    });
+                            });
+                        }, 'show Doc tags')}>
                         <FontAwesomeIcon className="documentdecorations-icon" icon="tag" />
                     </div>
                 </Tooltip>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 5e7908725..2ba96c2a9 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -837,7 +837,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                                 <div
                                     className="link-button-container"
                                     style={{
-                                        top: DocumentView.Selected().length > 1 ? 0 : `${seldocview.Document._layout_showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
+                                        top: DocumentView.Selected().length > 1 ? 0 : `${seldocview.showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
                                         transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
                                     }}>
                                     <DocumentButtonBar views={() => DocumentView.Selected()} />
@@ -846,7 +846,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                             <div
                                 className="documentDecorations-tagsView"
                                 style={{
-                                    top: `${seldocview.Document._layout_showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
+                                    top: `${seldocview.showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
                                     transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
                                 }}>
                                 {DocumentView.Selected().length > 1 ? <TagsView Views={DocumentView.Selected()} /> : null}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index eb434db40..3545afcee 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -9,7 +9,6 @@ import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs';
 import { FaFilter } from 'react-icons/fa';
 import { ClientUtils, DashColor, lightOrDark } from '../../ClientUtils';
 import { Doc, Opt, StrListCast } from '../../fields/Doc';
-import { DocData } from '../../fields/DocSymbols';
 import { Id } from '../../fields/FieldSymbols';
 import { ScriptField } from '../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
@@ -22,7 +21,6 @@ import { TreeSort } from './collections/TreeSort';
 import { Colors } from './global/globalEnums';
 import { DocumentView, DocumentViewProps } from './nodes/DocumentView';
 import { FieldViewProps } from './nodes/FieldView';
-import { IconTagBox } from './nodes/IconTagBox';
 import { StyleProp } from './StyleProp';
 import './StyleProvider.scss';
 import { TagsView } from './TagsView';
@@ -366,7 +364,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                 );
             };
             const tags = () => docView?.() ? <TagsView Views={[docView?.()]}/> :  null;
-            const iconTags = () => doc?.[DocData].showIconTags ? <IconTagBox doc= {doc}/> : null;
         
             return (
                 <>
@@ -375,7 +372,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                     {filter()}
                     {audio()}
                     {tags()}
-                    {iconTags()}
                 </>
             );
         }
diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx
index be2c28185..a678d5580 100644
--- a/src/client/views/TagsView.tsx
+++ b/src/client/views/TagsView.tsx
@@ -18,6 +18,7 @@ import { ObservableReactComponent } from './ObservableReactComponent';
 import './TagsView.scss';
 import { DocumentView } from './nodes/DocumentView';
 import { FaceRecognitionHandler } from './search/FaceRecognitionHandler';
+import { IconTagBox } from './nodes/IconTagBox';
 
 /**
  * The TagsView is a metadata input/display panel shown at the bottom of a DocumentView in a freeform collection.
@@ -59,7 +60,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
      * @param tag tag string
      * @returns tag collection Doc or undefined
      */
-    public static findTagCollectionDoc = (tag: String) => TagItem.AllTagCollectionDocs.find(doc => doc.title === tag);
+    public static findTagCollectionDoc = (tag: string) => TagItem.AllTagCollectionDocs.find(doc => doc.title === tag);
 
     /**
      * Creates a Doc that collects Docs with the specified tag / value
@@ -148,7 +149,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
 
     private _ref: React.RefObject<HTMLDivElement>;
 
-    constructor(props: any) {
+    constructor(props: TagItemProps) {
         super(props);
         makeObservable(this);
         this._ref = React.createRef();
@@ -225,7 +226,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
                                 type="checkbox"
                                 onClick={e => e.stopPropagation()}
                                 onPointerDown={e => e.stopPropagation()}
-                                onChange={undoable(e => (this.doc[metadata] = !this.doc[metadata]), 'metadata toggle')}
+                                onChange={undoable(() => (this.doc[metadata] = !this.doc[metadata]), 'metadata toggle')}
                                 checked={this.doc[metadata] as boolean}
                             />
                         ) : (
@@ -257,7 +258,7 @@ interface TagViewProps {
  */
 @observer
 export class TagsView extends ObservableReactComponent<TagViewProps> {
-    constructor(props: any) {
+    constructor(props: TagViewProps) {
         super(props);
         makeObservable(this);
     }
@@ -271,7 +272,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
     componentDidMount() {
         this._heightDisposer = reaction(
             () => this.View.screenToContentsTransform(),
-            xf => {
+            () => {
                 this._panelHeightDirty = this._panelHeightDirty + 1;
             }
         );
@@ -288,7 +289,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
         return this._props.Views.length > 1 ? 1 : Math.max(1, 1 / this.View.screenToLocalScale());
     }
     @computed get isEditing() {
-        return this._isEditing && (this._props.Views.length > 1 || DocumentView.SelectedDocs().includes(this.View.Document));
+        return this._isEditing && (this._props.Views.length > 1 || (DocumentView.Selected().length === 1 && DocumentView.Selected().includes(this.View)));
     }
 
     /**
@@ -336,7 +337,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
         );
         this._panelHeightDirty;
 
-        return this.View.ComponentView?.isUnstyledView?.() || (!this.View.Document._layout_showTags && this._props.Views.length === 1) ? null : (
+        return this.View.ComponentView?.isUnstyledView?.() || (!this.View.showTags && this._props.Views.length === 1) ? null : (
             <div
                 className="tagsView-container"
                 ref={r => r && new ResizeObserver(action(() => this._props.Views.length === 1 && (this.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)}
@@ -352,9 +353,10 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
                 }}>
                 <div className="tagsView-content" style={{ width: '100%' }}>
                     <div className="tagsView-list">
-                        {!tagsList.size && !facesList.size ? null : ( //
+                        {this._props.Views.length === 1 && !this.View.showTags ? null : ( //
                             <IconButton style={{ width: '8px' }} tooltip="Close Menu" onPointerDown={() => this.setToEditing(!this._isEditing)} icon={<FontAwesomeIcon icon={this._isEditing ? 'chevron-up' : 'chevron-down'} size="sm" />} />
                         )}
+                        <IconTagBox Views={this._props.Views} IsEditing={this._isEditing} />
                         {Array.from(tagsList).map((tag, i) => (
                             <TagItem
                                 key={i}
@@ -388,7 +390,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
                                 />
                             </div>
                             <div className="tagsView-suggestions-box">
-                                {TagItem.AllTagCollectionDocs.map((doc, i) => {
+                                {TagItem.AllTagCollectionDocs.map(doc => {
                                     const tag = StrCast(doc.title);
                                     return (
                                         <Button
diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss
index 5ccc3d9a8..e5fb7aba6 100644
--- a/src/client/views/collections/CollectionCardDeckView.scss
+++ b/src/client/views/collections/CollectionCardDeckView.scss
@@ -29,18 +29,13 @@
     transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955);
 }
 
-.no-card-span{
+.no-card-span {
     position: relative;
     width: fit-content;
     text-align: center;
     font-size: 65px;
-    
-
-
 }
 
-
-
 .card-item-inactive,
 .card-item-active,
 .card-item {
@@ -50,7 +45,6 @@
     flex-direction: column;
 }
 
-
 .card-item-inactive {
     opacity: 0.5;
 }
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx
index fced9fd37..1952cc707 100644
--- a/src/client/views/collections/CollectionCardDeckView.tsx
+++ b/src/client/views/collections/CollectionCardDeckView.tsx
@@ -21,6 +21,7 @@ import { DocumentView } from '../nodes/DocumentView';
 import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup';
 import './CollectionCardDeckView.scss';
 import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
+import { List } from '../../../fields/List';
 
 enum cardSortings {
     Time = 'time',
@@ -42,7 +43,6 @@ enum cardSortings {
 @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>();
 
@@ -77,6 +77,12 @@ export class CollectionCardView extends CollectionSubView() {
         this.setRegenerateCallback();
     }
 
+    protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
+        this._dropDisposer?.();
+        if (ele) {
+            this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
+        }
+    };
     /**
      * Callback to ensure gpt's text versions of the child docs are updated
      */
@@ -94,10 +100,7 @@ export class CollectionCardView extends CollectionSubView() {
     };
 
     componentDidMount() {
-        this.Document.childFilters_boolean = 'OR';
-        this.childDocsWithoutLinks.forEach(c => {
-            c[DocData].showIconTags = true;
-        });
+        this.Document.childFilters_boolean = 'OR'; // bcz: really shouldn't be assigning to fields from within didMount -- this should be a default/override beahavior somehow
 
         // Reaction to cardSort changes
         this._disposers.sort = reaction(
@@ -117,28 +120,24 @@ export class CollectionCardView extends CollectionSubView() {
         this._dropDisposer?.();
     }
 
-    @computed get cardSort_customField() {
-        return StrCast(this.Document.cardSort_customField) as 'chat' | 'star' | 'idea' | 'like';
-    }
-
     @computed get cardSort() {
         return StrCast(this.Document.cardSort) as cardSortings;
     }
 
     /**
-     * how much to scale down the contents of the view so that everything will fit
+     * The child documents to be rendered-- either all of them except the Links or the docs in the currently active
+     * custom group
      */
-    @computed get fitContentScale() {
-        const length = Math.min(this.childDocsWithoutLinks.length, this._maxRowCount);
-        return (this._childDocumentWidth * length) / this._props.PanelWidth();
+    @computed get childDocsWithoutLinks() {
+        return (this.childDocList as Doc[]).filter(l => l.type !== DocumentType.LINK);
     }
 
     /**
-     * The child documents to be rendered-- either all of them except the Links or the docs in the currently active
-     * custom group
+     * how much to scale down the contents of the view so that everything will fit
      */
-    @computed get childDocsWithoutLinks() {
-        return this.childDocs.filter(l => l.type !== DocumentType.LINK);
+    @computed get fitContentScale() {
+        const length = Math.min(this.childDocsWithoutLinks.length, this._maxRowCount);
+        return (this.childPanelWidth() * length) / this._props.PanelWidth();
     }
 
     /**
@@ -154,7 +153,7 @@ export class CollectionCardView extends CollectionSubView() {
      * Number of rows of cards to be rendered
      */
     @computed get numRows() {
-        return Math.ceil(this.sortedDocs.length / 10);
+        return Math.ceil(this.sortedDocs.length / this._maxRowCount);
     }
 
     @action
@@ -177,8 +176,7 @@ export class CollectionCardView extends CollectionSubView() {
      */
     inactiveDocs = () => this.childDocsWithoutLinks.filter(d => !DocumentView.SelectedDocs().includes(d));
 
-    panelWidth = () => this._childDocumentWidth;
-    panelHeight = (layout: Doc) => () => (this.panelWidth() * NumCast(layout._height)) / NumCast(layout._width);
+    childPanelWidth = () => NumCast(this.layoutDoc.childPanelWidth, this._props.PanelWidth() / 2);
     onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
     isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
     isChildContentActive = () => !!this.isContentActive();
@@ -245,6 +243,7 @@ export class CollectionCardView extends CollectionSubView() {
         const currRow = Math.floor((mouseY - 100) / rowHeight); //rows start at 0
 
         if (adjustedX < 0) {
+            console.log('DROP INDEX NO ');
             return 0; // Before the first column
         }
 
@@ -259,6 +258,7 @@ export class CollectionCardView extends CollectionSubView() {
 
             index = Math.floor(adjustedX / cardWidth) + currRow * this._maxRowCount;
         }
+        console.log('DROP INDEX = ' + index);
         return index;
     };
 
@@ -280,21 +280,42 @@ export class CollectionCardView extends CollectionSubView() {
         }
     };
 
+    /**
+     * Handles external drop of images/PDFs etc from outside Dash.
+     */
+    onExternalDrop = async (e: React.DragEvent): Promise<void> => {
+        super.onExternalDrop(e, {});
+    };
+
     /**
      * Resets all the doc dragging vairables once a card is dropped
      * @param e
      * @param de drop event
      * @returns true if a card has been dropped, falls if not
      */
-    onInternalDrop = undoable((e: Event, de: DragManager.DropEvent) => {
-        if (de.complete.docDragData) {
-            this._isACardBeingDragged = false;
-            this._docDraggedIndex = -1;
-            e.stopPropagation();
-            return true;
-        }
-        return false;
-    }, '');
+    onInternalDrop = undoable(
+        action((e: Event, de: DragManager.DropEvent) => {
+            if (de.complete.docDragData) {
+                this._isACardBeingDragged = false;
+                const dragIndex = this._docDraggedIndex;
+                if (dragIndex > -1) {
+                    this._docDraggedIndex = -1;
+                    const draggedDoc = DragManager.docsBeingDragged[0];
+                    const sorted = this.sortedDocs;
+                    const originalIndex = sorted.findIndex(doc => doc === draggedDoc);
+
+                    this.Document.cardSort = '';
+                    sorted.splice(originalIndex, 1);
+                    sorted.splice(dragIndex, 0, draggedDoc);
+                    this.dataDoc[this.fieldKey] = new List<Doc>(sorted);
+                }
+                e.stopPropagation();
+                return true;
+            }
+            return false;
+        }),
+        ''
+    );
 
     @computed get sortedDocs() {
         return this.sort(this.childDocsWithoutLinks, this.cardSort, BoolCast(this.Document.cardSort_isDesc), this._docDraggedIndex);
@@ -348,28 +369,29 @@ export class CollectionCardView extends CollectionSubView() {
      * @returns
      */
     sort = (docs: Doc[], sortType: cardSortings, isDesc: boolean, dragIndex: number) => {
-        docs.sort((docA, docB) => {
-            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: {
-                        const d1 = DashColor(StrCast(docA.backgroundColor));
-                        const d2 = DashColor(StrCast(docB.backgroundColor));
-                        return [d1.hsv().hue(), d2.hsv().hue()];
+        sortType &&
+            docs.sort((docA, docB) => {
+                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: {
+                            const d1 = DashColor(StrCast(docA.backgroundColor));
+                            const d2 = DashColor(StrCast(docB.backgroundColor));
+                            return [d1.hsv().hue(), d2.hsv().hue()];
+                        }
+                        case cardSortings.Tag:
+                            return [this.tagValue(docA) ?? 9999, this.tagValue(docB) ?? 9999];
+                        case cardSortings.Chat:
+                            return [NumCast(docA.chatIndex) ?? 9999, NumCast(docB.chatIndex) ?? 9999];
+                        default:
+                            return [StrCast(docA.type), StrCast(docB.type)];
                     }
-                    case cardSortings.Tag:
-                        return [this.tagValue(docA) ?? 9999, this.tagValue(docB) ?? 9999];
-                    case cardSortings.Chat:
-                        return [NumCast(docA.chatIndex) ?? 9999, NumCast(docB.chatIndex) ?? 9999];
-                    default:
-                        return [StrCast(docA.type), StrCast(docB.type)];
-                }
-            })();
+                })();
 
-            const out = typeA < typeB ? -1 : typeA > typeB ? 1 : 0;
-            return isDesc ? out : -out;
-        });
+                const out = typeA < typeB ? -1 : typeA > typeB ? 1 : 0;
+                return isDesc ? out : -out;
+            });
         if (dragIndex != -1) {
             const draggedDoc = DragManager.docsBeingDragged[0];
             const originalIndex = docs.findIndex(doc => doc === draggedDoc);
@@ -396,9 +418,11 @@ export class CollectionCardView extends CollectionSubView() {
             ScreenToLocalTransform={screenToLocalTransform} // makes sure the box wrapper thing is in the right spot
             isContentActive={emptyFunction}
             isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
-            PanelWidth={this.panelWidth}
-            PanelHeight={this.panelHeight(doc)}
+            PanelWidth={this.childPanelWidth}
+            PanelHeight={() => this._props.PanelHeight() * this.fitContentScale}
+            dontCenter="y" // Don't center it vertically, because the grid it's in is already doing that and we don't want to do it twice.
             dragAction={(this.Document.childDragAction ?? this._props.childDragAction) as dropActionType}
+            showTags={true}
             dontHideOnDrag
         />
     );
@@ -415,18 +439,18 @@ export class CollectionCardView extends CollectionSubView() {
         // 13 - 3 = 10
         const totalCards = this.sortedDocs.length;
         // if 9 or less
-        if (index < totalCards - (totalCards % 10)) {
+        if (index < totalCards - (totalCards % this._maxRowCount)) {
             return this._maxRowCount;
         }
         // (3)
-        return totalCards % 10;
+        return totalCards % this._maxRowCount;
     };
     /**
      * Determines the index a card is in in a row
      * @param realIndex
      * @returns
      */
-    overflowIndexCalc = (realIndex: number) => realIndex % 10;
+    overflowIndexCalc = (realIndex: number) => realIndex % this._maxRowCount;
     /**
      * Translates the cards in the second rows and beyond over to the right
      * @param realIndex
@@ -434,7 +458,7 @@ export class CollectionCardView extends CollectionSubView() {
      * @param calcRowCards
      * @returns
      */
-    translateOverflowX = (realIndex: number, calcRowCards: number) => (realIndex < this._maxRowCount ? 0 : (10 - calcRowCards) * (this.panelWidth() / 2));
+    translateOverflowX = (realIndex: number, calcRowCards: number) => (realIndex < this._maxRowCount ? 0 : (this._maxRowCount - calcRowCards) * (this.childPanelWidth() / 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
@@ -613,6 +637,9 @@ export class CollectionCardView extends CollectionSubView() {
                 const rowCenterIndex = Math.min(this._maxRowCount, sortedDocs.length - rowIndex * this._maxRowCount) / 2;
                 return (rowCenterIndex - indexInRow) * 100 - 50;
             };
+            const aspect = NumCast(doc.height) / NumCast(doc.width, 1);
+            const vscale = ((this._props.PanelHeight() * .95) * this.fitContentScale) / (aspect * this.childPanelWidth());
+            const hscale = this._maxRowCount / 2; // bcz: hack - the grid is divided evenly into maxRowCount cells, so the max scaling would be maxRowCount -- but making things that wide is ugly, so cap it off at half the window size
             return (
                 <div
                     key={doc[Id]}
@@ -629,13 +656,13 @@ export class CollectionCardView extends CollectionSubView() {
                         );
                     }}
                     style={{
-                        width: this.panelWidth(),
+                        width: this.childPanelWidth(),
                         height: 'max-content',
                         transform: `translateY(${this.calculateTranslateY(this._hoveredNodeIndex === index, isSelected, realIndex, amCards, calcRowIndex)}px)
-                                    translateX(calc(${(isSelected ? translateIfSelected() : 0) + '% + ' + this.translateOverflowX(realIndex, amCards) + 'px'}))
+                                    translateX(calc(${isSelected ? translateIfSelected() : 0}% + ${this.translateOverflowX(realIndex, amCards)}px))
                                     rotate(${!isSelected ? this.rotate(amCards, calcRowIndex) : 0}deg)
-                                    scale(${isSelected ? 2 : this._hoveredNodeIndex === index ? 1.05 : 1})`,
-                    }}
+                                    scale(${isSelected ? `${Math.min(hscale, vscale) * 100}%` : this._hoveredNodeIndex === index ? 1.05 : 1})`,
+                    }} // prettier-ignore
                     onMouseEnter={() => this.setHoveredNodeIndex(index)}>
                     {this.displayDoc(doc, childScreenToLocal)}
                 </div>
@@ -645,14 +672,13 @@ export class CollectionCardView extends CollectionSubView() {
 
     render() {
         const isEmpty = this.childDocsWithoutLinks.length === 0;
-        const transformValue = `scale(${1 / this.fitContentScale})`;
-        const heightValue = `${100 * this.fitContentScale}%`;
 
         return (
             <div
                 onPointerMove={e => this.onPointerMove(e)}
                 className="collectionCardView-outer"
                 ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)}
+                onDrop={this.onExternalDrop.bind(this)}
                 style={{
                     background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string,
                     color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string,
@@ -660,8 +686,8 @@ export class CollectionCardView extends CollectionSubView() {
                 <div
                     className="card-wrapper"
                     style={{
-                        ...(!isEmpty && { transform: transformValue }),
-                        ...(!isEmpty && { height: heightValue }),
+                        ...(!isEmpty && { transform: `scale(${1 / this.fitContentScale})` }),
+                        ...(!isEmpty && { height: `${100 * this.fitContentScale}%` }),
                         gridAutoRows: `${100 / this.numRows}%`,
                     }}
                     onMouseLeave={() => this.setHoveredNodeIndex(-1)}>
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index ad868fd1e..4609be374 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/jsx-props-no-spreading */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index e97ee713e..1ac0b6d70 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/jsx-props-no-spreading */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import * as CSS from 'csstype';
 import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
@@ -540,7 +539,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         if (this.pivotField) {
             const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
             if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
-                // eslint-disable-next-line prefer-destructuring
                 type = types[0];
             }
         }
@@ -577,7 +575,6 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
         let type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined;
         const types = docList.length ? docList.map(d => typeof d[key]) : this.filteredChildren.map(d => typeof d[key]);
         if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
-            // eslint-disable-next-line prefer-destructuring
             type = types[0];
         }
         const rows = () => (!this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, Math.floor((this._props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))));
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 6aca8f2ca..99373da04 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -122,6 +122,9 @@ export function CollectionSubView<X>() {
                 );
             return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types
         }
+        /**
+         * This is the raw, stored list of children on a collection.  If you modify this list, the database will be updated
+         */
         @computed get childDocList() {
             return Cast(this.dataField, listSpec(Doc));
         }
@@ -218,7 +221,6 @@ export function CollectionSubView<X>() {
                 if (!cursors) {
                     proto.cursors = cursors = new List<CursorField>();
                 }
-                // eslint-disable-next-line no-cond-assign
                 if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.data.metadata.id === id)) > -1) {
                     cursors[ind].setPosition(pos);
                 } else {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index dbf781e63..cbbf063b4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/jsx-props-no-spreading */
 import { Bezier } from 'bezier-js';
 import { Colors } from 'browndash-components';
 import { Property } from 'csstype';
@@ -1211,7 +1210,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 for (let j = 0; j < otherCtrlPts.length - 3; j += 4) {
                     const neighboringSegment = i === j || i === j - 4 || i === j + 4;
                     // Ensuring that the curve intersected by the eraser is not checked for further ink intersections.
-                    // eslint-disable-next-line no-continue
                     if (ink?.Document === otherInk.Document && neighboringSegment) continue;
 
                     const otherCurve = new Bezier(otherCtrlPts.slice(j, j + 4).map(p => ({ x: p.X, y: p.Y })));
@@ -1481,8 +1479,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
         const childData = entry.pair.data;
         return (
             <CollectionFreeFormDocumentView
-                // eslint-disable-next-line react/jsx-props-no-spreading, @typescript-eslint/no-explicit-any
-                {...(OmitKeys(entry, ['replica', 'pair']).omit as any)}
+                {...(OmitKeys(entry, ['replica', 'pair']).omit as { x: number; y: number; z: number; width: number; height: number })}
                 key={childLayout[Id] + (entry.replica || '')}
                 Document={childLayout}
                 reactParent={this}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4c357cf45..758e70508 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -67,6 +67,7 @@ export interface DocumentViewProps extends FieldViewSharedProps {
     hideCaptions?: boolean;
     contentPointerEvents?: Property.PointerEvents | undefined; // pointer events allowed for content of a document view.  eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
     dontCenter?: 'x' | 'y' | 'xy';
+    showTags?: boolean;
     childHideDecorationTitle?: boolean;
     childHideResizeHandles?: boolean;
     childDragAction?: dropActionType; // allows child documents to be dragged out of collection without holding the embedKey or dragging the doc decorations title bar.
@@ -1126,6 +1127,10 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     @observable public static CurrentlyPlaying: DocumentView[] = []; // audio or video media views that are currently playing
     @observable public TagPanelHeight = 0;
 
+    @computed get showTags() {
+        return this.Document._layout_showTags || this._props.showTags;
+    }
+
     @computed private get shouldNotScale() {
         return (this.layout_fitWidth && !this.nativeWidth) || this.ComponentView?.isUnstyledView?.();
     }
diff --git a/src/client/views/nodes/IconTagBox.scss b/src/client/views/nodes/IconTagBox.scss
index 211a961c1..90cc06092 100644
--- a/src/client/views/nodes/IconTagBox.scss
+++ b/src/client/views/nodes/IconTagBox.scss
@@ -2,23 +2,25 @@
 
 .card-button-container {
     display: flex;
-    padding: 3px;
-    position: absolute;
+    position: relative;
     pointer-events: none;
-    background-color: rgb(218, 218, 218); 
+    background-color: rgb(218, 218, 218);
     border-radius: 50px;
-    transform: translateY(25px);
-    align-items: center; 
-    justify-content: start;
+    align-items: center;
+    gap: 5px;
+    padding-left: 5px;
+    padding-right: 5px;
+    padding-top: 2px;
+    padding-bottom: 2px;
 
     button {
         pointer-events: auto;
-        transform: translateY(-7.5px);
-        width: 30px;
-        height: 30px;
+        width: 20px;
+        height: 20px;
+        margin: auto;
+        padding: 0;
         border-radius: 50%;
         background-color: $dark-gray;
-        margin: 5px; 
         background-color: transparent;
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx
index f86372ec0..3d021fd73 100644
--- a/src/client/views/nodes/IconTagBox.tsx
+++ b/src/client/views/nodes/IconTagBox.tsx
@@ -6,18 +6,18 @@ import { observer } from 'mobx-react';
 import React from 'react';
 import { numberRange } from '../../../Utils';
 import { Doc, StrListCast } from '../../../fields/Doc';
-import { DocData } from '../../../fields/DocSymbols';
-import { BoolCast, DocCast, NumCast, StrCast } from '../../../fields/Types';
-import { CollectionViewType } from '../../documents/DocumentTypes';
+import { StrCast } from '../../../fields/Types';
 import { SnappingManager } from '../../util/SnappingManager';
 import { undoable } from '../../util/UndoManager';
 import { MainView } from '../MainView';
 import { ObservableReactComponent } from '../ObservableReactComponent';
 import { PropertiesView } from '../PropertiesView';
+import { DocumentView } from './DocumentView';
 import './IconTagBox.scss';
 
 export interface IconTagProps {
-    doc: Doc;
+    Views: DocumentView[];
+    IsEditing: boolean;
 }
 
 /**
@@ -26,59 +26,16 @@ export interface IconTagProps {
  */
 @observer
 export class IconTagBox extends ObservableReactComponent<IconTagProps> {
-    @computed
-    get currentScale() {
-        return NumCast((this._props.doc.embedContainer as Doc)?._freeform_scale, 1);
-    }
-
     constructor(props: IconTagProps) {
         super(props);
     }
 
-    componentDidUpdate(): void {
-        this._props.doc[DocData].tagHeight = 36 * this.currentScale;
+    @computed get View() {
+        return this._props.Views.lastElement();
+    }
+    @computed get currentScale() {
+        return this.View?.screenToLocalScale();
     }
-
-    /**
-     * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups
-     * @param doc
-     * @param cardSort
-     * @returns
-     */
-    renderButtons = (doc: Doc): JSX.Element | null => {
-        const amButtons = StrListCast(Doc.UserDoc().myFilterHotKeyTitles).length + 1;
-
-        const keys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
-
-        const totalWidth = (amButtons - 1) * 35 + (amButtons - 1) * 2 * 5 + 6;
-
-        const iconMap = (buttonID: number) => {
-            return StrCast(Doc.UserDoc()[keys[buttonID]]) as IconProp;
-        };
-
-        const isCard = DocCast(this._props.doc.embedContainer).type_collection === CollectionViewType.Card;
-
-        return (
-            <div
-                className="card-button-container"
-                style={{
-                    transformOrigin: 'top left',
-                    transform: `scale(${isCard ? 2 : 0.6 / this.currentScale})
-                translateY(${doc[DocData].showLabels ? NumCast(doc[DocData].keywordHeight) * (1 - this.currentScale) : 0}px)
-                `,
-                    width: `${totalWidth}px`,
-                    fontSize: '50px',
-                }}>
-                {numberRange(amButtons - 1).map(i => (
-                    <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from the {iconMap(i).toString()} group</div>}>
-                        <button key={i} type="button" onClick={() => this.toggleButton(doc, iconMap(i))}>
-                            {this.getButtonIcon(doc, iconMap(i))}
-                        </button>
-                    </Tooltip>
-                ))}
-            </div>
-        );
-    };
 
     /**
      * Opens the filter panel in the properties menu
@@ -93,13 +50,13 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
     };
 
     /**
-     * Toggles the buttons between on and off when creating custom sort groupings/changing those created by gpt
-     * @param childPairIndex
      * @param buttonID
      * @param doc
      */
-    toggleButton = undoable((doc: Doc, icon: string) => {
-        BoolCast(doc[icon]) ? (doc[icon] = false) : (doc[icon] = true);
+    setIconTag = undoable((icon: string, state: boolean) => {
+        this._props.Views.forEach(view => {
+            view.dataDoc[icon] = state;
+        });
     }, 'toggle card tag');
 
     /**
@@ -112,10 +69,43 @@ export class IconTagBox extends ObservableReactComponent<IconTagProps> {
         const isActive = doc[icon.toString()];
         const color = isActive ? '#4476f7' : '#323232';
 
-        return <FontAwesomeIcon icon={icon} style={{ color, height: '30px', width: '30px' }} />;
+        return <FontAwesomeIcon icon={icon} style={{ color, height: '20px', width: '20px' }} />;
     };
 
+    /**
+     * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups
+     */
     render() {
-        return <>{this.renderButtons(this._props.doc)}</>;
+        const amButtons = StrListCast(Doc.UserDoc().myFilterHotKeyTitles).length + 1;
+
+        const keys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles);
+
+        const iconMap = (buttonID: number) => {
+            return StrCast(Doc.UserDoc()[keys[buttonID]]) as IconProp;
+        };
+        const buttons = numberRange(amButtons - 1)
+            .filter(i => this._props.IsEditing || this.View.Document[iconMap(i).toString()] || (DocumentView.Selected.length === 1 && this.View.IsSelected))
+            .map(i => (
+                <Tooltip key={i} title={<div className="dash-tooltip">Click to add/remove this card from the {iconMap(i).toString()} group</div>}>
+                    <button
+                        key={i}
+                        type="button"
+                        onClick={() => {
+                            const state = this.View.Document[iconMap(i).toString()];
+                            this.setIconTag(iconMap(i), !state);
+                        }}>
+                        {this.getButtonIcon(this.View.Document, iconMap(i))}
+                    </button>
+                </Tooltip>
+            ));
+        return !buttons.length ? null : (
+            <div
+                className="card-button-container"
+                style={{
+                    fontSize: '50px',
+                }}>
+                {buttons}
+            </div>
+        );
     }
 }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 343e255dc..e0331a422 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -275,6 +275,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 ele.append(contents);
             }
             this._selectionHTML = ele?.innerHTML;
+            // eslint-disable-next-line @typescript-eslint/no-unused-vars
         } catch (e) {
             /* empty */
         }
diff --git a/src/client/views/search/FaceRecognitionHandler.tsx b/src/client/views/search/FaceRecognitionHandler.tsx
index c507e54b6..6f70e96ab 100644
--- a/src/client/views/search/FaceRecognitionHandler.tsx
+++ b/src/client/views/search/FaceRecognitionHandler.tsx
@@ -30,6 +30,7 @@ import { DocumentManager } from '../../util/DocumentManager';
  *     face_annos - a list of face annotations, where each anno has
  */
 export class FaceRecognitionHandler {
+    // eslint-disable-next-line no-use-before-define
     static _instance: FaceRecognitionHandler;
     private _apiModelReady = false;
     private _pendingAPIModelReadyDocs: Doc[] = [];
@@ -221,7 +222,7 @@ export class FaceRecognitionHandler {
                                 const annos = [] as Doc[];
                                 const scale = NumCast(imgDoc.data_nativeWidth) / img.width;
                                 const showTags= imgDocFaceDescriptions.length > 1;
-                                imgDocFaceDescriptions.forEach((fd, i) => {
+                                imgDocFaceDescriptions.forEach(fd => {
                                     const faceDescriptor = new List<number>(Array.from(fd.descriptor));
                                     const matchedUniqueFace = this.findMatchingFaceDoc(fd.descriptor) ?? this.createUniqueFaceDoc(activeDashboard);
                                     const faceAnno = Docs.Create.FreeformDocument([], {
-- 
cgit v1.2.3-70-g09d2


From 302b9ab98001a50ca2117df823216a99b7c25869 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 19 Sep 2024 20:03:49 -0400
Subject: changed carousel to set #star tags instead of a metaadata field.

---
 src/client/views/FilterPanel.tsx                   | 41 +++++-----------------
 src/client/views/TagsView.tsx                      |  2 +-
 .../views/collections/CollectionCarouselView.tsx   | 10 ++++--
 3 files changed, 17 insertions(+), 36 deletions(-)

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

diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx
index a3c8fc383..e34b66963 100644
--- a/src/client/views/FilterPanel.tsx
+++ b/src/client/views/FilterPanel.tsx
@@ -40,38 +40,17 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey /*, sel
         isEditing: false,
         myHotKey: hotKey,
 
-        toggleActive() {
-            this.isActive = !this.isActive;
-        },
-        deactivate() {
-            this.isActive = false;
-        },
-        startEditing() {
-            this.isEditing = true;
-        },
-        stopEditing() {
-            this.isEditing = false;
-        },
-        setHotKey(newHotKey: string) {
-            this.myHotKey.title = newHotKey;
-        },
-    }));
+        toggleActive() { this.isActive = !this.isActive; },
+        deactivate()   { this.isActive = false; },
+        startEditing() { this.isEditing = true; },
+        stopEditing()  { this.isEditing = false; },
+        setHotKey(newHotKey: string) { this.myHotKey.title = newHotKey; },
+    })); // prettier-ignore
 
     const panelRef = useRef<HTMLDivElement>(null);
     const inputRef = useRef<HTMLInputElement>(null);
 
-    const handleClick = () => {
-        state.toggleActive();
-    };
-
-    const buttons = DocCast(Doc.UserDoc().myContextMenuBtns);
-
-    /**
-     * Removes a hotkey from list
-     */
-    const removeHotKey = () => {
-        Doc.RemFromFilterHotKeys(hotKey);
-    };
+    const handleClick = () => state.toggleActive();
 
     /**
      * Updates the list of hotkeys based on the users input. replaces the old title with the new one and then assigns this new
@@ -99,9 +78,7 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey /*, sel
 
     useEffect(() => {
         document.addEventListener('mousedown', handleClickOutside);
-        return () => {
-            document.removeEventListener('mousedown', handleClickOutside);
-        };
+        return () => document.removeEventListener('mousedown', handleClickOutside);
     }, []);
 
     const iconOpts = ['star', 'heart', 'bolt', 'satellite', 'palette', 'robot', 'lightbulb', 'highlighter', 'book', 'chalkboard'] as IconProp[];
@@ -172,7 +149,7 @@ const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey /*, sel
                 className="hotKey-close"
                 onClick={(e: React.MouseEvent) => {
                     e.stopPropagation();
-                    removeHotKey();
+                    Doc.RemFromFilterHotKeys(hotKey);
                 }}>
                 <FontAwesomeIcon icon={'x' as IconProp} color={SnappingManager.userColor} />
             </button>
diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx
index 2f30edb80..f44fd1d03 100644
--- a/src/client/views/TagsView.tsx
+++ b/src/client/views/TagsView.tsx
@@ -85,7 +85,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
     public static allDocsWithTag = (tag: string) => DocListCast(TagItem.findTagCollectionDoc(tag)?.[DocData].docs);
 
     public static docHasTag = (doc: Doc, tag: string) => {
-        return StrListCast(doc.tags).includes(tag);
+        return StrListCast(doc?.tags).includes(tag);
     };
     /**
      * Adds a tag to the metadata of this document and adds the Doc to the corresponding tag collection Doc (or creates it)
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 4609be374..974cd3e36 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -14,6 +14,7 @@ import { FieldViewProps } from '../nodes/FieldView';
 import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import './CollectionCarouselView.scss';
 import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
+import { TagItem } from '../TagsView';
 
 enum cardMode {
     PRACTICE = 'practice',
@@ -28,7 +29,7 @@ enum practiceVal {
 export class CollectionCarouselView extends CollectionSubView() {
     private _dropDisposer?: DragManager.DragDropDisposer;
     get practiceField() { return this.fieldKey + "_practice"; } // prettier-ignore
-    get starField()     { return "star"; } // prettier-ignore
+    get starField()     { return "#star"; } // prettier-ignore
 
     _fadeTimer: NodeJS.Timeout | undefined;
     _resetter: IReactionDisposer | undefined;
@@ -108,7 +109,10 @@ export class CollectionCarouselView extends CollectionSubView() {
     star = (e: React.MouseEvent) => {
         e.stopPropagation();
         const curDoc = this.carouselItems[this.carouselIndex];
-        curDoc && (curDoc[this.starField] = curDoc[this.starField] ? undefined : true);
+        if (curDoc) {
+            if (TagItem.docHasTag(curDoc, this.starField)) TagItem.removeTagFromDoc(curDoc, this.starField);
+            else TagItem.addTagToDoc(curDoc, this.starField);
+        }
     };
 
     /*
@@ -241,7 +245,7 @@ export class CollectionCarouselView extends CollectionSubView() {
                     <FontAwesomeIcon icon="chevron-right" size="2x" />
                 </div>
                 <div key="star" className="carouselView-star" onClick={this.star}>
-                    <FontAwesomeIcon icon="star" color={this.carouselItems?.[this.carouselIndex][this.starField] ? 'yellow' : 'gray'} size="1x" />
+                    <FontAwesomeIcon icon="star" color={TagItem.docHasTag(this.carouselItems?.[this.carouselIndex], this.starField) ? 'yellow' : 'gray'} size="1x" />
                 </div>
                 <div key="remove" className="carouselView-remove" onClick={e => this.setPracticeVal(e, practiceVal.MISSED)} style={{ visibility: this.layoutDoc.filterOp === cardMode.PRACTICE ? 'visible' : 'hidden' }}>
                     <FontAwesomeIcon icon="xmark" color="red" size="1x" />
-- 
cgit v1.2.3-70-g09d2


From 0da361d49f063d43a0ad87d13665595f9e383e13 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 24 Sep 2024 17:44:49 -0400
Subject: fixed screen to local for carousel views (especially when in
 lightbox)

---
 .../views/collections/CollectionCarousel3DView.tsx | 35 +++++++++++++++++-----
 .../views/collections/CollectionCarouselView.tsx   |  3 ++
 2 files changed, 30 insertions(+), 8 deletions(-)

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

diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 54cc02825..139aebb02 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -14,6 +14,7 @@ import { DocumentView } from '../nodes/DocumentView';
 import { FocusViewOptions } from '../nodes/FocusViewOptions';
 import './CollectionCarousel3DView.scss';
 import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
+import { Transform } from '../../util/Transform';
 
 // eslint-disable-next-line @typescript-eslint/no-var-requires
 const { CAROUSEL3D_CENTER_SCALE, CAROUSEL3D_SIDE_SCALE, CAROUSEL3D_TOP } = require('../global/globalCssVariables.module.scss');
@@ -46,15 +47,32 @@ export class CollectionCarousel3DView extends CollectionSubView() {
     }
 
     centerScale = Number(CAROUSEL3D_CENTER_SCALE);
+    sideScale = Number(CAROUSEL3D_SIDE_SCALE);
     panelWidth = () => this._props.PanelWidth() / 3;
-    panelHeight = () => this._props.PanelHeight() * Number(CAROUSEL3D_SIDE_SCALE);
+    panelHeight = () => this._props.PanelHeight() * this.sideScale;
     onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
     isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
     isChildContentActive = () => !!this.isContentActive();
-    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)
+    childScreenLeftToLocal = () =>
+        this._props
+            .ScreenToLocalTransform()
+            .scale(this._props.NativeDimScaling?.() || 1)
+            .translate(-(this.panelWidth() - this.panelWidth() * this.sideScale) / 2, -(this.panelHeight() - this.panelHeight() * this.sideScale) / 2 - (Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight())
+            .scale(1 / this.sideScale);
+    childScreenRightToLocal = () =>
+        this._props
+            .ScreenToLocalTransform()
+            .scale(this._props.NativeDimScaling?.() || 1)
+            .translate(-2 * this.panelWidth() - (this.panelWidth() - this.panelWidth() * this.sideScale) / 2, -(this.panelHeight() - this.panelHeight() * this.sideScale) / 2 - (Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight())
+            .scale(1 / this.sideScale);
+    childCenterScreenToLocal = () =>
+        this._props
+            .ScreenToLocalTransform()
+            .scale(this._props.NativeDimScaling?.() || 1)
+            .translate(
+                -this.panelWidth() + ((this.centerScale - 1) * this.panelWidth()) / 2, // Focused Doc is shifted right by 1/3 panel width then left by increased size percent of center * 1/2 * panel width / 3
+                -((Number(CAROUSEL3D_TOP) / 100) * this._props.PanelHeight()) + ((this.centerScale - 1) * this.panelHeight()) / 2
+            ) // top is top margin % of panelHeight - increased size percent of center * panelHeight / 2
             .scale(1 / this.centerScale);
 
     focus = (anchor: Doc, options: FocusViewOptions): Opt<number> => {
@@ -66,9 +84,10 @@ export class CollectionCarousel3DView extends CollectionSubView() {
         index !== -1 && (this.layoutDoc._carousel_index = index);
         return undefined;
     };
+
     @computed get content() {
         const currentIndex = NumCast(this.layoutDoc._carousel_index);
-        const displayDoc = (childPair: { layout: Doc; data: Doc }) => (
+        const displayDoc = (childPair: { layout: Doc; data: Doc }, dxf: () => Transform) => (
             <DocumentView
                 // eslint-disable-next-line react/jsx-props-no-spreading
                 {...this._props}
@@ -84,7 +103,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
                 LayoutTemplate={this._props.childLayoutTemplate}
                 LayoutTemplateString={this._props.childLayoutString}
                 focus={this.focus}
-                ScreenToLocalTransform={this.childScreenToLocal}
+                ScreenToLocalTransform={dxf}
                 isContentActive={this.isChildContentActive}
                 isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
                 PanelWidth={this.panelWidth}
@@ -94,7 +113,7 @@ export class CollectionCarousel3DView extends CollectionSubView() {
 
         return this.carouselItems.map((childPair, index) => (
             <div key={childPair.layout[Id]} className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`} style={{ width: this.panelWidth() }}>
-                {displayDoc(childPair)}
+                {displayDoc(childPair, index < currentIndex ? this.childScreenLeftToLocal : index === currentIndex ? this.childCenterScreenToLocal : this.childScreenRightToLocal)}
             </div>
         ));
     }
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 974cd3e36..75886b30d 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -154,6 +154,8 @@ export class CollectionCarouselView extends CollectionSubView() {
                 ? false
                 : undefined;
 
+    childScreenToLocalXf = () => this._props.ScreenToLocalTransform().scale(this._props.NativeDimScaling?.() || 1);
+
     renderDoc = (doc: Doc, showCaptions: boolean, overlayFunc?: (r: DocumentView | null) => void) => {
         return (
             <DocumentView
@@ -165,6 +167,7 @@ export class CollectionCarouselView extends CollectionSubView() {
                 fitWidth={undefined}
                 containerViewPath={this.childContainerViewPath}
                 setContentViewBox={undefined}
+                ScreenToLocalTransform={this.childScreenToLocalXf}
                 onDoubleClickScript={this.onContentDoubleClick}
                 onClickScript={this.onContentClick}
                 isDocumentActive={this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this._props.isContentActive}
-- 
cgit v1.2.3-70-g09d2


From ecd8ed4e41940089ec31d9601afaa0d5932c6401 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 25 Sep 2024 11:35:42 -0400
Subject: removed star icon from carousel -- added showTags instead.

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

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

diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 75886b30d..6ea263215 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -71,7 +71,7 @@ export class CollectionCarouselView extends CollectionSubView() {
     @computed get marginX()       { return NumCast(this.layoutDoc.caption_xMargin, 50); } // prettier-ignore
     @computed get carouselIndex() { return NumCast(this.layoutDoc._carousel_index) % this.carouselItems.length; } // prettier-ignore
     @computed get carouselItems() {
-        return DocListCast(this.childDocList)
+        return this.childDocs
             .filter(doc => doc.type !== DocumentType.LINK)
             .filter(doc => {
                 switch (StrCast(this.layoutDoc.filterOp)) {
@@ -165,6 +165,7 @@ export class CollectionCarouselView extends CollectionSubView() {
                 NativeWidth={returnZero}
                 NativeHeight={returnZero}
                 fitWidth={undefined}
+                showTags={true}
                 containerViewPath={this.childContainerViewPath}
                 setContentViewBox={undefined}
                 ScreenToLocalTransform={this.childScreenToLocalXf}
@@ -247,9 +248,6 @@ export class CollectionCarouselView extends CollectionSubView() {
                 <div key="fwd" className="carouselView-fwd" onClick={this.advance}>
                     <FontAwesomeIcon icon="chevron-right" size="2x" />
                 </div>
-                <div key="star" className="carouselView-star" onClick={this.star}>
-                    <FontAwesomeIcon icon="star" color={TagItem.docHasTag(this.carouselItems?.[this.carouselIndex], this.starField) ? 'yellow' : 'gray'} size="1x" />
-                </div>
                 <div key="remove" className="carouselView-remove" onClick={e => this.setPracticeVal(e, practiceVal.MISSED)} style={{ visibility: this.layoutDoc.filterOp === cardMode.PRACTICE ? 'visible' : 'hidden' }}>
                     <FontAwesomeIcon icon="xmark" color="red" size="1x" />
                 </div>
-- 
cgit v1.2.3-70-g09d2


From 14296a5e2b9726d449a9ddf59a3af0a6945e3cb7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 25 Sep 2024 13:37:36 -0400
Subject: updated carousel to show same filter as in context menu bar.

---
 src/client/util/CurrentUserUtils.ts                |  2 -
 src/client/views/StyleProvider.tsx                 |  2 +-
 .../views/collections/CollectionCarouselView.tsx   | 44 ++++++++++++++++++----
 src/client/views/nodes/DocumentView.tsx            |  1 +
 4 files changed, 39 insertions(+), 10 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 3e7921a08..03975a5e7 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -981,12 +981,10 @@ pie title Minerals in my tap water
         doc.fontColor ?? (doc.fontColor = "black");
         doc.fontHighlight ?? (doc.fontHighlight = "");
         doc.defaultAclPrivate ?? (doc.defaultAclPrivate = false);
-        doc.savedFilters ?? (doc.savedFilters = new List<Doc>());
         doc.userBackgroundColor ?? (doc.userBackgroundColor = Colors.DARK_GRAY);
         doc.userVariantColor ?? (doc.userVariantColor = Colors.MEDIUM_BLUE);
         doc.userColor ?? (doc.userColor = Colors.LIGHT_GRAY);
         doc.userTheme ?? (doc.userTheme = ColorScheme.Dark);
-        doc.filterDocCount = 0;
         doc.treeView_FreezeChildren = "remove|add";
         doc.activePage = doc.activeDashboard === undefined ? 'home': doc.activePage;
 
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 3545afcee..16f6aa40b 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -318,7 +318,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                         : childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f) && f !== ClientUtils.noDragDocsFilter).length || childFiltersByRanges?.().length
                         ? 'orange' // 'inheritsFilter'
                         : undefined;
-                return !showFilterIcon ? null : (
+                return !showFilterIcon || props?.hideFilterStatus ? null : (
                     <div className="styleProvider-filter">
                         <Dropdown
                             type={Type.TERT}
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 43ab4462b..b6c3744e6 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -4,7 +4,7 @@ import { IReactionDisposer, action, computed, makeObservable, observable, reacti
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { StopEvent, returnOne, returnZero } from '../../../ClientUtils';
-import { Doc, Opt } from '../../../fields/Doc';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
 import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { Docs } from '../../documents/Documents';
@@ -16,6 +16,7 @@ import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
 import './CollectionCarouselView.scss';
 import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
 import { TagItem } from '../TagsView';
+import { tickStep } from 'd3';
 
 enum cardMode {
     STAR = 'star',
@@ -180,9 +181,9 @@ export class CollectionCarouselView extends CollectionSubView() {
                 NativeHeight={returnZero}
                 fitWidth={undefined}
                 showTags={true}
+                hideFilterStatus={true}
                 containerViewPath={this.childContainerViewPath}
                 setContentViewBox={undefined}
-                ScreenToLocalTransform={this.childScreenToLocalXf}
                 onDoubleClickScript={this.onContentDoubleClick}
                 onClickScript={this.onContentClick}
                 isDocumentActive={this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this._props.isContentActive}
@@ -311,16 +312,45 @@ export class CollectionCarouselView extends CollectionSubView() {
     toggleFilterMode = () => Doc.setDocFilter(this.Document, 'tags', this.starField, 'check', true);
     setColor = (mode: practiceMode | cardMode, which: string) => (which === mode ? 'white' : 'light gray');
 
+    @computed get filterDoc() {
+        return DocListCast(Doc.MyContextMenuBtns.data).find(doc => doc.title === 'Filter');
+    }
+    filterHeight = () => NumCast(this.filterDoc?.height);
+    filterWidth = () => (!this.filterDoc ? 1 : (this.filterHeight() * NumCast(this.filterDoc._width)) / NumCast(this.filterDoc._height));
     @computed get menu() {
         const curDoc = this.carouselItems?.[this.carouselIndex];
         return (
             <div className="carouselView-menu">
-                {/* NOTE: this should really show the same filter as in the workspace View menu, not just something hardwire for 'star' */}
-                <Tooltip title={Doc.hasDocFilter(this.Document, 'tags', this.starField) ? 'Show all cards' : 'Show only starred cards'}>
-                    <div key="back" className="carouselView-starFilter" onClick={e => this.toggleFilterMode()}>
-                        <FontAwesomeIcon icon="filter" color={Doc.hasDocFilter(this.Document, 'tags', this.starField) ? 'white' : 'lightgray'} size="1x" />
+                {!this.filterDoc ? (null) : (
+                    <div style={{ height: this.filterHeight(), width: this.filterHeight() }}>
+                        <DocumentView
+                            {...this._props}
+                            Document={this.filterDoc}
+                            TemplateDataDocument={undefined}
+                            LayoutTemplate={this._props.childLayoutTemplate}
+                            LayoutTemplateString={this._props.childLayoutString}
+                            renderDepth={this._props.renderDepth + 1}
+                            NativeWidth={returnZero}
+                            NativeHeight={returnZero}
+                            fitWidth={undefined}
+                            showTags={false}
+                            hideFilterStatus={true}
+                            containerViewPath={this.childContainerViewPath}
+                            setContentViewBox={undefined}
+                            onDoubleClickScript={this.onContentDoubleClick}
+                            onClickScript={this.onContentClick}
+                            isDocumentActive={this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this._props.isContentActive}
+                            isContentActive={this.isChildContentActive}
+                            hideCaptions={true}
+                            childFilters={this.childDocFilters}
+                            hideDecorations={BoolCast(this.layoutDoc.layout_hideDecorations)}
+                            addDocument={this._props.addDocument}
+                            ScreenToLocalTransform={this.contentScreentToLocalXf}
+                            PanelWidth={this.filterWidth}
+                            PanelHeight={this.filterHeight}
+                        />
                     </div>
-                </Tooltip>
+                )}
                 <div className="carouselView-practiceModes" style={{ display: BoolCast(curDoc?._layout_isFlashcard) ? undefined : 'none' }}>
                     <Tooltip title="Practice flashcards using GPT">
                         <div key="back" className="carouselView-quiz" onClick={e => this.togglePracticeMode(practiceMode.QUIZ)}>
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 884220722..b85cb22bb 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -68,6 +68,7 @@ export interface DocumentViewProps extends FieldViewSharedProps {
     contentPointerEvents?: Property.PointerEvents | undefined; // pointer events allowed for content of a document view.  eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
     dontCenter?: 'x' | 'y' | 'xy';
     showTags?: boolean;
+    hideFilterStatus?: boolean;
     childHideDecorationTitle?: boolean;
     childHideResizeHandles?: boolean;
     childDragAction?: dropActionType; // allows child documents to be dragged out of collection without holding the embedKey or dragging the doc decorations title bar.
-- 
cgit v1.2.3-70-g09d2

