From fc3e2b18b9dad38920e1cec5e58bf9fbd06f4aaf Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 8 Oct 2024 03:30:21 -0400
Subject: changed carousel view to resize ui buttons based on screen scaling
 and document dimensions.  lint errors.

---
 .../views/collections/CollectionCarouselView.tsx   | 176 +++++++++++----------
 1 file changed, 96 insertions(+), 80 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 936226baf..559dcfe2a 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -7,7 +7,6 @@ import { StopEvent, returnOne, returnZero } from '../../../ClientUtils';
 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';
 import { DragManager } from '../../util/DragManager';
 import { StyleProp } from '../StyleProp';
 import { TagItem } from '../TagsView';
@@ -36,6 +35,8 @@ export class CollectionCarouselView extends CollectionSubView() {
     get sideField()     { return "_" + this.fieldKey + "_usePath"; } // prettier-ignore
     get starField()     { return "#star"; } // prettier-ignore
 
+    _sideBtnWidth = 35;
+
     _fadeTimer: NodeJS.Timeout | undefined;
 
     constructor(props: SubCollectionViewProps) {
@@ -155,7 +156,11 @@ export class CollectionCarouselView extends CollectionSubView() {
     onContentDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
     onContentClick = () => ScriptCast(this.layoutDoc.onChildClick);
     captionWidth = () => this._props.PanelWidth() - 2 * this.marginX;
-    contentScreentToLocalXf = () => this._props.ScreenToLocalTransform().translate(-NumCast(this.layoutDoc.xMargin), -NumCast(this.layoutDoc.yMargin));
+    contentScreenToLocalXf = () =>
+        this._props
+            .ScreenToLocalTransform()
+            .translate(-NumCast(this.layoutDoc.xMargin), -NumCast(this.layoutDoc.yMargin))
+            .scale(this._props.NativeDimScaling?.() || 1);
 
     contentPanelWidth = () => this._props.PanelWidth() - 2 * NumCast(this.layoutDoc.xMargin);
 
@@ -168,9 +173,9 @@ 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) => {
+        const screenScale = this.ScreenToLocalBoxXf().Scale;
+        const fitWidthScale = (NumCast(this.Document.width, 1) / NumCast(this.carouselItems[this.carouselIndex]?._width)) * (this._props.NativeDimScaling?.() || 1);
         return (
             <DocumentView
                 {...this._props}
@@ -195,10 +200,10 @@ export class CollectionCarouselView extends CollectionSubView() {
                 childFilters={this.childDocFilters}
                 hideDecorations={BoolCast(this.layoutDoc.layout_hideDecorations)}
                 addDocument={this._props.addDocument}
-                ScreenToLocalTransform={this.contentScreentToLocalXf}
+                ScreenToLocalTransform={this.contentScreenToLocalXf}
                 PanelWidth={this.contentPanelWidth}
                 PanelHeight={this.contentPanelHeight}
-                xPadding={35}
+                xPadding={(this._sideBtnWidth * Math.min(this.maxWidgetScale, screenScale * screenScale)) / fitWidthScale} // padding shrinks based on screenScale to maintain its size, and then again by screenSize to get smaller
             />
         );
     };
@@ -259,46 +264,6 @@ export class CollectionCarouselView extends CollectionSubView() {
         );
     }
 
-    addFlashcard() {
-        const newDoc = Docs.Create.ComparisonDocument('', { _layout_isFlashcard: true, _width: 300, _height: 300 });
-        this.addDocument?.(newDoc);
-    }
-
-    @computed get buttons() {
-        if (!this.carouselItems?.[this.carouselIndex]) return null;
-        return (
-            <>
-                <div>
-                    {/* <Tooltip title="add new flashcard to pile">
-                        <div key="add" className="carouselView-add" onClick={this.addFlashcard}>
-                            <FontAwesomeIcon icon="plus" color={this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)].layout[this.starField] ? 'yellow' : 'gray'} size="1x" />
-                        </div>
-                    </Tooltip> */}
-                </div>
-                <div key="back" className="carouselView-back" onClick={this.goback}>
-                    <FontAwesomeIcon icon="chevron-left" size="2x" />
-                </div>
-                <div key="fwd" className="carouselView-fwd" onClick={this.advance}>
-                    <FontAwesomeIcon icon="chevron-right" size="2x" />
-                </div>
-                {this.practiceMode == practiceMode.PRACTICE ? (
-                    <div>
-                        <Tooltip title="Incorrect. View again later.">
-                            <div key="remove" className="carouselView-remove" onClick={e => this.setPracticeVal(e, practiceVal.MISSED)}>
-                                <FontAwesomeIcon icon="xmark" color="red" size="1x" />
-                            </div>
-                        </Tooltip>
-                        <Tooltip title="Correct">
-                            <div key="check" className="carouselView-check" onClick={e => this.setPracticeVal(e, practiceVal.CORRECT)}>
-                                <FontAwesomeIcon icon="check" color="green" size="1x" />
-                            </div>
-                        </Tooltip>
-                    </div>
-                ) : null}
-            </>
-        );
-    }
-
     togglePracticeMode = (mode: practiceMode) => this.setPracticeMode(mode === this.practiceMode ? undefined : mode);
     toggleFilterMode = () => Doc.setDocFilter(this.Document, 'tags', this.starField, 'check', true);
     setColor = (mode: practiceMode | cardMode, which: string) => (which === mode ? 'white' : 'light gray');
@@ -306,50 +271,75 @@ export class CollectionCarouselView extends CollectionSubView() {
     @computed get filterDoc() {
         return DocListCast(Doc.MyContextMenuBtns.data).find(doc => doc.title === 'Filter');
     }
-    filterHeight = () => NumCast(this.filterDoc?.height);
+    filterHeight = () => NumCast(this.filterDoc?.height) * Math.min(1, this.ScreenToLocalBoxXf().Scale);
     filterWidth = () => (!this.filterDoc ? 1 : (this.filterHeight() * NumCast(this.filterDoc._width)) / NumCast(this.filterDoc._height));
+
+    /**
+     * How much the content of the carousel view is being scaled based on its nesting and its fit-to-width settings
+     */
+    @computed get contentScaling() {
+        return this.ScreenToLocalBoxXf().Scale * (this._props.NativeDimScaling?.() ?? 1);
+    }
+
+    /**
+     * The maximum size a UI widget can be scaled so that it won't be bigger in screen pixels than its normal 35 pixel size.
+     */
+    @computed get maxWidgetScale() {
+        const maxWidgetSize = Math.min(this._sideBtnWidth * this.contentScaling, 0.1 * NumCast(this.Document.width, 1));
+        return Math.max(maxWidgetSize / this._sideBtnWidth, 1);
+    }
+    /**
+     * How much to reactively scale a UI element so that it is as big as it can be (up to its normal 35pixel size) without being too big for the Doc content
+     */
+    @computed get uiBtnScaleTransform() {
+        return `scale(${this.maxWidgetScale * Math.min(1, this.contentScaling)})`;
+    }
     @computed get menu() {
         const curDoc = this.carouselItems?.[this.carouselIndex];
         return (
-            <div className="carouselView-menu">
+            <div className="carouselView-menu" style={{ height: this.filterHeight(), width: this.filterHeight(), transform: this.uiBtnScaleTransform }}>
                 {!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>
+                    <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.contentScreenToLocalXf}
+                        PanelWidth={this.filterWidth}
+                        PanelHeight={this.filterHeight}
+                    />
                 )}
-                <div className="carouselView-practiceModes" style={{ display: BoolCast(curDoc?._layout_isFlashcard) ? undefined : 'none' }}>
+                <div
+                    className="carouselView-practiceModes"
+                    style={{
+                        transformOrigin: `0px ${-this.filterHeight()}px`,
+                        transform: `scale(${Math.max(1, 1 / this.ScreenToLocalBoxXf().Scale / this.maxWidgetScale)})`,
+                        display: BoolCast(curDoc?._layout_isFlashcard) ? undefined : 'none',
+                    }}>
                     <Tooltip title="Practice flashcards using GPT">
-                        <div key="back" className="carouselView-quiz" onClick={() => this.togglePracticeMode(practiceMode.QUIZ)}>
+                        <div key="back" className="carouselView-quiz" style={{ width: this.filterWidth(), height: this.filterHeight() }} onClick={() => this.togglePracticeMode(practiceMode.QUIZ)}>
                             <FontAwesomeIcon icon="file-pen" color={this.setColor(practiceMode.QUIZ, StrCast(this.practiceMode))} size="1x" />
                         </div>
                     </Tooltip>
                     <Tooltip title={this.practiceMode === practiceMode.PRACTICE ? 'Exit practice mode' : 'Practice flashcards manually'}>
-                        <div key="back" className="carouselView-practice" onClick={() => this.togglePracticeMode(practiceMode.PRACTICE)}>
+                        <div key="back" className="carouselView-practice" style={{ width: this.filterWidth(), height: this.filterHeight() }} onClick={() => this.togglePracticeMode(practiceMode.PRACTICE)}>
                             <FontAwesomeIcon icon="check" color={this.setColor(practiceMode.PRACTICE, StrCast(this.practiceMode))} size="1x" />
                         </div>
                     </Tooltip>
@@ -357,6 +347,32 @@ export class CollectionCarouselView extends CollectionSubView() {
             </div>
         );
     }
+    @computed get buttons() {
+        return (
+            <>
+                <div key="back" className="carouselView-back" style={{ transform: this.uiBtnScaleTransform }} onClick={this.goback}>
+                    <FontAwesomeIcon icon="chevron-left" size="2x" />
+                </div>
+                <div key="fwd" className="carouselView-fwd" style={{ transform: this.uiBtnScaleTransform }} onClick={this.advance}>
+                    <FontAwesomeIcon icon="chevron-right" size="2x" />
+                </div>
+                {this.practiceMode == practiceMode.PRACTICE ? (
+                    <div style={{ transform: this.uiBtnScaleTransform, bottom: `${this._sideBtnWidth}px`, height: `${this._sideBtnWidth}px`, position: 'absolute', width: `100%` }}>
+                        <Tooltip title="Incorrect. View again later.">
+                            <div key="remove" className="carouselView-remove" onClick={e => this.setPracticeVal(e, practiceVal.MISSED)}>
+                                <FontAwesomeIcon icon="xmark" color="red" size="1x" />
+                            </div>
+                        </Tooltip>
+                        <Tooltip title="Correct">
+                            <div key="check" className="carouselView-check" onClick={e => this.setPracticeVal(e, practiceVal.CORRECT)}>
+                                <FontAwesomeIcon icon="check" color="green" size="1x" />
+                            </div>
+                        </Tooltip>
+                    </div>
+                ) : null}
+            </>
+        );
+    }
 
     render() {
         return (
@@ -388,7 +404,7 @@ export class CollectionCarouselView extends CollectionSubView() {
                     )}
                 </div>
                 {!this.Document._chromeHidden ? this.menu : null}
-                {!this.Document._chromeHidden ? this.buttons : null}
+                {!this.Document._chromeHidden && this.carouselItems?.[this.carouselIndex] ? this.buttons : null}
             </div>
         );
     }
-- 
cgit v1.2.3-70-g09d2

