From e8f1d494d36a5e1f1ee33d59faa4be2559cd752e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 22 Apr 2025 10:54:38 -0400
Subject: adding drop handling code for scrapbookBox

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx     | 2 ++
 1 file changed, 2 insertions(+)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 842293358..08126d4fe 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -496,6 +496,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }, 'link drop');
 
     onInternalDrop = (e: Event, de: DragManager.DropEvent): boolean => {
+        if (this._props.rejectDrop?.(de.complete.docDragData?.draggedDocuments, this._props.DocumentView?.())) return false;
         if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de, de.complete.annoDragData);
         if (de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData);
         if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData);
@@ -1562,6 +1563,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                 pinToPres={this._props.pinToPres}
                 whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
                 dragAction={(this.Document.childDragAction ?? this._props.childDragAction) as dropActionType}
+                rejectDrop={this._props.childRejectDrop}
                 showTitle={this._props.childlayout_showTitle}
                 dontRegisterView={this._props.dontRegisterView}
                 pointerEvents={this.childPointerEventsFunc}
-- 
cgit v1.2.3-70-g09d2


From 7671e543dc677c82071d6eaccc33bd164450620d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 22 Apr 2025 14:45:05 -0400
Subject: protoype version of scrapboxBox.  added rejectDrop prop to prevent
 drops on child documentviews

---
 src/client/views/PropertiesView.tsx                |  2 +-
 src/client/views/collections/CollectionSubView.tsx |  4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  6 +-
 src/client/views/nodes/FieldView.tsx               |  2 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 src/client/views/nodes/scrapbook/ScrapbookBox.tsx  | 81 +++++++++++++++-------
 6 files changed, 64 insertions(+), 33 deletions(-)

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

diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index e7186c0e3..acf6f928a 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -161,7 +161,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
             CollectionViewType.Card,
             CollectionViewType.Carousel,
             CollectionViewType.Grid,
-        ].includes(this.selectedDoc?.type_collection as CollectionViewType);
+        ].includes(this.selectedDoc?.type_collection as CollectionViewType) || this.selectedDoc.$type === DocumentType.SCRAPBOOK; // prettier-ignore
     }
 
     rtfWidth = () => (!this.selectedLayoutDoc ? 0 : Math.min(NumCast(this.selectedLayoutDoc?._width), this._props.width - 20));
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 9a0fda3f8..e79d0a76d 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -58,7 +58,7 @@ export interface CollectionViewProps extends React.PropsWithChildren<FieldViewPr
     childOpacity?: () => number;
     childContextMenuItems?: () => { script: ScriptField; label: string }[];
     childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection
-    childRejectDrop?: (draggedDoc: Doc[] | undefined, subView?: DocumentView) => boolean; // whether a child document can be dropped on this document
+    childRejectDrop?: (de: DragManager.DropEvent, subView?: DocumentView) => boolean; // whether a child document can be dropped on this document
     childHideDecorationTitle?: boolean;
     childHideResizeHandles?: boolean;
     childHideDecorations?: boolean;
@@ -324,7 +324,7 @@ export function CollectionSubView<X>() {
 
         protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
             const { docDragData } = de.complete;
-            if (docDragData && !docDragData.draggedDocuments.includes(this.Document) && !this._props.rejectDrop?.(docDragData.draggedDocuments, this.DocumentView?.())) {
+            if (docDragData && !docDragData.draggedDocuments.includes(this.Document) && !this._props.rejectDrop?.(de, this.DocumentView?.())) {
                 let added;
                 const dropAction = docDragData.dropAction || docDragData.userDropAction;
                 const targetDocments = DocListCast(this.dataDoc[this._props.fieldKey]);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 08126d4fe..c4971c204 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -256,8 +256,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     override contentBounds = () => {
         const { x, y, r, b } = aggregateBounds(
             this._layoutElements.filter(e => e.bounds?.width && !e.bounds.z).map(e => e.bounds!),
-            NumCast(this.layoutDoc._xPadding, this._props.xPadding ?? 0),
-            NumCast(this.layoutDoc._yPadding, this._props.yPadding ?? 0)
+            NumCast(this.layoutDoc._xPadding, NumCast(this.layoutDoc._xMargin, this._props.xPadding ?? 0)),
+            NumCast(this.layoutDoc._yPadding, NumCast(this.layoutDoc._yMargin, this._props.yPadding ?? 0))
         );
         const [width, height] = [r - x, b - y];
         return {
@@ -496,7 +496,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }, 'link drop');
 
     onInternalDrop = (e: Event, de: DragManager.DropEvent): boolean => {
-        if (this._props.rejectDrop?.(de.complete.docDragData?.draggedDocuments, this._props.DocumentView?.())) return false;
+        if (this._props.rejectDrop?.(de, this._props.DocumentView?.())) return false;
         if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de, de.complete.annoDragData);
         if (de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData);
         if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData);
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index b4473d9dc..34e3a5d76 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -68,7 +68,7 @@ export interface FieldViewSharedProps {
     isGroupActive?: () => string | undefined; // is this document part of a group that is active
     // eslint-disable-next-line no-use-before-define
     setContentViewBox?: (view: ViewBoxInterface<FieldViewProps>) => void; // called by rendered field's viewBox so that DocumentView can make direct calls to the viewBox
-    rejectDrop?: (draggedDoc: Doc[] | undefined, subView?: DocumentView) => boolean; // whether a document drop is rejected
+    rejectDrop?: (de: DragManager.DropEvent, subView?: DocumentView) => boolean; // whether a document drop is rejected
     PanelWidth: () => number;
     PanelHeight: () => number;
     isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index dc23a695d..9897a0062 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -586,7 +586,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             return true;
         }
         const dragData = de.complete.docDragData;
-        if (dragData) {
+        if (dragData && !this._props.rejectDrop?.(de, this.DocumentView?.())) {
             const layoutProto = DocCast(this.layoutDoc.proto);
             const dataDoc = layoutProto && Doc.IsDelegateField(layoutProto, this.fieldKey) ? layoutProto : this.dataDoc;
             const effectiveAcl = GetEffectiveAcl(dataDoc);
diff --git a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx
index 24946f4d2..6ee9f39ab 100644
--- a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx
+++ b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx
@@ -1,6 +1,6 @@
 import { action, makeObservable, observable } from 'mobx';
 import * as React from 'react';
-import { Doc } from '../../../../fields/Doc';
+import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
 import { List } from '../../../../fields/List';
 import { emptyFunction } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
@@ -10,10 +10,11 @@ import { ViewBoxAnnotatableComponent } from '../../DocComponent';
 import { DocumentView } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { DragManager } from '../../../util/DragManager';
+import { RTFCast, StrCast, toList } from '../../../../fields/Types';
+import { undoable } from '../../../util/UndoManager';
 // Scrapbook view: a container that lays out its child items in a grid/template
 export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @observable createdDate: string;
-    private _dropDisposer?: DragManager.DragDropDisposer;
 
     constructor(props: FieldViewProps) {
         super(props);
@@ -45,6 +46,28 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         const title = `Scrapbook - ${this.createdDate}`;
         if (this.dataDoc.title !== title) {
             this.dataDoc.title = title;
+
+            const image = Docs.Create.TextDocument('image');
+            image.accepts_docType = DocumentType.IMG;
+            const placeholder = new Doc();
+            placeholder.proto = image;
+            placeholder.original = image;
+            placeholder.height = 200;
+            placeholder.x = 0;
+            placeholder.y = -100;
+            //placeholder.overrideFields = new List<string>(['x', 'y']);  // shouldn't need to do this for layout fields since the placeholder already overrides its protos
+
+            const summary = Docs.Create.TextDocument('summary');
+            summary.accepts_docType = DocumentType.RTF;
+            summary.accepts_textType = 'one line';
+            const placeholder2 = new Doc();
+            placeholder2.proto = summary;
+            placeholder2.original = summary;
+            placeholder2.x = 0;
+            placeholder2.y = 200;
+            placeholder2._width = 250;
+            //placeholder2.overrideFields = new List<string>(['x', 'y', '_width']); // shouldn't need to do this for layout fields since the placeholder already overrides its protos
+            this.dataDoc[this.fieldKey] = new List<Doc>([placeholder, placeholder2]);
         }
     }
 
@@ -52,43 +75,49 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         this.setTitle();
     }
 
-    childRejectDrop = (draggedDoc: Doc[] | undefined, subView?: DocumentView) => {
-        if (draggedDoc?.length === 1 && subView) {
-            if (subView.Document.type === DocumentType.IMG && draggedDoc[0].$type !== DocumentType.IMG) {
-                return true;
-            }
-        }
-        return false;
+    childRejectDrop = (de: DragManager.DropEvent, subView?: DocumentView) => {
+        return true; // disable dropping documents onto any child of the scrapbook.
     };
-    rejectDrop = (draggedDoc: Doc[] | undefined /* , subView?: DocumentView */) => {
-        if (draggedDoc?.length === 1 && draggedDoc[0].$type !== DocumentType.IMG) {
-            return true;
-        }
-        return false;
-    };
-    onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
-        if (de.complete.docDragData?.draggedDocuments[0]?.$type === DocumentType.IMG) {
-            return true;
-        }
-        return false;
+    rejectDrop = (de: DragManager.DropEvent, subView?: DocumentView) => {
+        // Test to see if the dropped doc is dropped on an acceptable location (anywerhe? on a specific box).
+        // const draggedDocs = de.complete.docDragData?.draggedDocuments;
+        return false; // allow all Docs to be dropped onto scrapbook -- let filterAddDocument make the final decision.
     };
 
-    protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
-        this._dropDisposer?.();
-        if (ele) {
-            this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
+    filterAddDocument = (docIn: Doc | Doc[]) => {
+        const docs = toList(docIn);
+        if (docs?.length === 1) {
+            const placeholder = DocListCast(this.dataDoc[this.fieldKey]).find(d =>
+                 (d.accepts_docType === docs[0].$type ||   // match fields based on type, or by analyzing content .. simple example of matching text in placeholder to dropped doc's type
+                  RTFCast(d[Doc.LayoutDataKey(d)])?.Text.includes(StrCast(docs[0].$type)))
+            ); // prettier-ignore
+
+            if (placeholder) {
+                // ugh.  we have to tell the underlying view not to add the Doc so that we can add it where we want it.
+                // However, returning 'false' triggers an undo. so this settimeout is needed to make the assignment happen after the undo.
+                setTimeout(
+                    undoable(() => {
+                        //StrListCast(placeholder.overrideFields).map(field => (docs[0][field] = placeholder[field])); // // shouldn't need to do this for layout fields since the placeholder already overrides its protos
+                        placeholder.proto = docs[0];
+                    }, 'Scrapbook add')
+                );
+                return false;
+            }
         }
+        return false;
     };
 
     render() {
         return (
-            <div style={{ background: 'beige', width: '100%', height: '100%' }} ref={r => r && this.createDashEventsTarget(r)}>
+            <div style={{ background: 'beige', width: '100%', height: '100%' }}>
                 <CollectionView
                     {...this._props} //
                     setContentViewBox={emptyFunction}
                     rejectDrop={this.rejectDrop}
                     childRejectDrop={this.childRejectDrop}
+                    filterAddDocument={this.filterAddDocument}
                 />
+                {/* <div style={{ border: '1px black', borderStyle: 'dotted', position: 'absolute', top: '50%', width: '100%', textAlign: 'center' }}>Drop an image here</div> */}
             </div>
         );
     }
@@ -102,9 +131,11 @@ Docs.Prototypes.TemplateMap.set(DocumentType.SCRAPBOOK, {
         _height: 200,
         _xMargin: 10,
         _yMargin: 10,
+        _layout_fitWidth: false,
         _layout_autoHeight: true,
         _layout_reflowVertical: true,
         _layout_reflowHorizontal: true,
+        _freeform_fitContentsToBox: true,
         defaultDoubleClick: 'ignore',
         systemIcon: 'BsImages',
     },
-- 
cgit v1.2.3-70-g09d2

