From d818ef151ca65008e5c6bb5e92b709decb3026d8 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 14 Apr 2025 18:35:49 -0400
Subject: fixed how templates are expanded to avoid template sub-component
 conflicts by changing how field keys are named.   fixed various Cast
 functions to be more typesafe by including undefined as part of return type. 
 overhaul of Doc.MakeClone, MakeCopy, FindRefernces - makeClone is no longer
 async.  fixed inlined docs in text docs.

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

(limited to 'src/client/views/Main.tsx')

diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 22725a2b9..e4bbb1c0f 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -61,7 +61,7 @@ import { FootnoteView } from './nodes/formattedText/FootnoteView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { SummaryView } from './nodes/formattedText/SummaryView';
 import { ImportElementBox } from './nodes/importBox/ImportElementBox';
-import { PresBox, PresElementBox } from './nodes/trails';
+import { PresBox, PresSlideBox } from './nodes/trails';
 import { FaceRecognitionHandler } from './search/FaceRecognitionHandler';
 import { SearchBox } from './search/SearchBox';
 import { StickerPalette } from './smartdraw/StickerPalette';
@@ -135,7 +135,7 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' };
             AudioBox,
             RecordingBox,
             PresBox,
-            PresElementBox,
+            PresSlideBox,
             SearchBox,
             ImageLabelBox,
             FaceCollectionBox,
-- 
cgit v1.2.3-70-g09d2


From 5290e6a69fdadb6037f3d8a03f5ed82e00927520 Mon Sep 17 00:00:00 2001
From: sharkiecodes <lanyi_stroud@brown.edu>
Date: Mon, 21 Apr 2025 19:41:51 -0400
Subject: attempting to integrate scrapbooks

---
 src/client/documents/DocumentTypes.ts              |   1 +
 src/client/documents/Documents.ts                  |  44 +++++++++
 src/client/util/CurrentUserUtils.ts                |   3 +
 src/client/views/Main.tsx                          |   2 +
 src/client/views/nodes/scrapbook/ScrapbookBox.tsx  | 106 +++++++++++++++++++++
 .../views/nodes/scrapbook/ScrapbookContent.tsx     |  23 +++++
 src/client/views/nodes/scrapbook/ScrapbookSlot.tsx |  26 +++++
 .../views/nodes/scrapbook/ScrapbookSlotTypes.ts    |  25 +++++
 8 files changed, 230 insertions(+)
 create mode 100644 src/client/views/nodes/scrapbook/ScrapbookBox.tsx
 create mode 100644 src/client/views/nodes/scrapbook/ScrapbookContent.tsx
 create mode 100644 src/client/views/nodes/scrapbook/ScrapbookSlot.tsx
 create mode 100644 src/client/views/nodes/scrapbook/ScrapbookSlotTypes.ts

(limited to 'src/client/views/Main.tsx')

diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 5c6559836..cef44e999 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -44,6 +44,7 @@ export enum DocumentType {
     SCRIPTDB = 'scriptdb', // database of scripts
     GROUPDB = 'groupdb', // database of groups
 
+    SCRAPBOOK = 'scrapbook',
     JOURNAL = 'journal', // AARAV ADD
 }
 export enum CollectionViewType {
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index be857da6d..2a2f9d342 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -526,6 +526,16 @@ export class DocumentOptions {
     ai_firefly_seed?: number;
     ai_firefly_prompt?: string;
 
+      /**
+   * JSON‐stringified slot configuration for ScrapbookBox
+   */
+  scrapbookConfig?: string;
+
+  /**
+   * The list of embedded Doc instances in each Scrapbook slot
+   */
+  scrapbookContents?: List<Doc>;
+  
     _outpaintingMetadata?: STRt = new StrInfo('serialized JSON metadata needed for image outpainting', false);
 
 }
@@ -590,6 +600,18 @@ export namespace Docs {
                 },
             ],
             // AARAV ADD //
+
+            [
+                DocumentType.SCRAPBOOK,
+                {
+                    layout: { view: EmptyBox, dataField: 'text' },
+                    options: {
+                        title: 'Scrapbook',
+                        acl_Guest: SharingPermissions.View,
+                    },
+                    
+                },
+            ],
         ]);
 
         const suffix = 'Proto';
@@ -922,6 +944,26 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey);
         }
 
+        export function ScrapbookDocument(
+            items: Doc[] = [],
+            options: DocumentOptions = {},
+            fieldKey: string = 'items'
+        ) {
+            return InstanceFromProto(
+                Prototypes.get(DocumentType.SCRAPBOOK),
+                new List<Doc>(items),
+                {
+                    title: options.title
+                        ?? new Date().toLocaleDateString(undefined, {
+                            year: 'numeric', month: 'short', day: 'numeric'
+                        }),
+                    ...options,
+                },
+                undefined,
+                fieldKey
+            );
+        }
+
         // AARAV ADD //
 
         export function DailyJournalDocument(text: string | RichTextField, options: DocumentOptions = {}, fieldKey: string = 'text') {
@@ -964,6 +1006,8 @@ export namespace Docs {
 
         // AARAV ADD //
 
+     
+
         export function LinkDocument(source: Doc, target: Doc, options: DocumentOptions = {}, id?: string) {
             const linkDoc = InstanceFromProto(
                 Prototypes.get(DocumentType.LINK),
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 99a67ebb2..8f4d568ab 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -407,6 +407,8 @@ pie title Minerals in my tap water
             {key: "DataViz",    creator: opts => Docs.Create.DataVizDocument("", opts),     opts: { _width: 300, _height: 300, }},
             // AARAV ADD //
             {key: "DailyJournal",creator:opts => Docs.Create.DailyJournalDocument("", opts),opts: { _width: 300, _height: 300, }},
+            {key: "Scrapbook",creator:opts => Docs.Create.ScrapbookDocument([], opts),opts:{ _width: 300, _height: 300}},
+            //{key: "Scrapbook",creator:opts => Docs.Create.ScrapbookDocument([], opts),opts:{ _width: 300, _height: 300}},
             {key: "Chat",       creator: Docs.Create.ChatDocument,                          opts: { _width: 500, _height: 500, _layout_fitWidth: true, }},
             {key: "MetaNote",   creator: metaNoteTemplate,                                  opts: { _width: 300, _height: 120, _header_pointerEvents: "all", _header_height: 50, _header_fontSize: 9,_layout_autoHeightMargins: 50, _layout_autoHeight: true, treeView_HideUnrendered: true}},
             {key: "ViewSlide",  creator: slideView,                                         opts: { _width: 400, _height: 300, _xMargin: 3, _yMargin: 3,}},
@@ -449,6 +451,7 @@ pie title Minerals in my tap water
             { toolTip: "Tap or drag to create a button",          title: "Button",      icon: "circle",          dragFactory: doc.emptyButton as Doc,     clickFactory: DocCast(doc.emptyButton)},
             { toolTip: "Tap or drag to create a scripting box",   title: "Script",      icon: "terminal",        dragFactory: doc.emptyScript as Doc,     clickFactory: DocCast(doc.emptyScript),                                             funcs: { hidden: "IsNoviceMode()"}},
             { toolTip: "Tap or drag to create a data viz node",   title: "DataViz",     icon: "chart-bar",       dragFactory: doc.emptyDataViz as Doc,    clickFactory: DocCast(doc.emptyDataViz)},
+            { toolTip: "Tap or drag to create a scrapbook template",   title: "Scrapbook", icon: "palette",       dragFactory: doc.emptyScrapbook as Doc,clickFactory:DocCast(doc.emptyScrapbook), },
             { toolTip: "Tap or drag to create a journal entry",   title: "Journal",     icon: "book",            dragFactory:doc.emptyDailyJournal as Doc,clickFactory: DocCast(doc.emptyDataJournal), },
             { toolTip: "Tap or drag to create a bullet slide",    title: "PPT Slide",   icon:"person-chalkboard",dragFactory: doc.emptySlide as Doc,      clickFactory: DocCast(doc.emptySlide),     openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
             { toolTip: "Tap or drag to create a view slide",      title: "View Slide",  icon: "address-card",    dragFactory: doc.emptyViewSlide as Doc,  clickFactory: DocCast(doc.emptyViewSlide), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}},
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index e4bbb1c0f..7992ed412 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -53,6 +53,7 @@ import { WebBox } from './nodes/WebBox';
 import { CalendarBox } from './nodes/calendarBox/CalendarBox';
 import { ChatBox } from './nodes/chatbot/chatboxcomponents/ChatBox';
 import { DailyJournal } from './nodes/formattedText/DailyJournal';
+import { ScrapbookVersionTwo } from './nodes/scrapbook/ScrapbookVersionTwo';
 import { DashDocCommentView } from './nodes/formattedText/DashDocCommentView';
 import { DashDocView } from './nodes/formattedText/DashDocView';
 import { DashFieldView } from './nodes/formattedText/DashFieldView';
@@ -120,6 +121,7 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' };
             FormattedTextBox,
             DailyJournal, // AARAV
             ImageBox,
+            ScrapbookVersionTwo,
             FontIconBox,
             LabelBox,
             EquationBox,
diff --git a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx
new file mode 100644
index 000000000..56cfcda70
--- /dev/null
+++ b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx
@@ -0,0 +1,106 @@
+import { makeObservable } from 'mobx';
+import * as React from 'react';
+import { ViewBoxAnnotatableComponent } from '../../DocComponent';
+import { FieldView, FieldViewProps } from '../FieldView';
+import { Docs } from '../../../documents/Documents';
+import { DocumentType } from '../../../documents/DocumentTypes';
+import { action, observable } from 'mobx';
+import { DocListCast } from '../../../../fields/Doc';
+import { Doc } from '../../../../fields/Doc';
+import { DocumentView } from '../DocumentView';
+import { FormattedTextBox } from '../formattedText/FormattedTextBox';
+import { List } from '../../../../fields/List';
+// Scrapbook view: a container that lays out its child items in a grid/template
+export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
+    @observable createdDate: string;
+
+    constructor(props: FieldViewProps) {
+        super(props);
+        makeObservable(this);
+        this.createdDate = this.getFormattedDate();
+  
+        // ensure we always have a List<Doc> in dataDoc['items']
+        if (!this.dataDoc[this.fieldKey]) {
+            this.dataDoc[this.fieldKey] = new List<Doc>();
+        }
+        this.createdDate = this.getFormattedDate();
+        this.setTitle();
+    }
+
+    public static LayoutString(fieldStr: string) {
+        return FieldView.LayoutString(ScrapbookBox, fieldStr);
+    }
+
+
+    
+    
+     
+    
+
+    getFormattedDate(): string {
+        return new Date().toLocaleDateString(undefined, {
+            year: 'numeric',
+            month: 'short',
+            day: 'numeric',
+        });
+    }
+
+    @action
+    setTitle() {
+        const title = `Scrapbook - ${this.createdDate}`;
+        if (this.dataDoc.title !== title) {
+            this.dataDoc.title = title;
+        }
+    }
+
+    componentDidMount() {
+        this.setTitle();
+        if (!this.dataDoc[this.fieldKey]) {
+            this.dataDoc[this.fieldKey] = new List<Doc>();
+        }
+    }
+
+    render() {
+        // cast into an array even if empty
+        const items: Doc[] = DocListCast(this.dataDoc[this.fieldKey]);
+    
+        return (
+            <div style={{ background: 'beige', width: '100%', height: '100%' }}>
+                        
+            </div>
+        
+        //   <div
+        //     style={{
+        //     }}
+        //   >
+        //     {items.length === 0
+        //       ? <div style={{color:'#888',fontStyle:'italic'}}>Drop docs here</div>
+        //       : items.map((childDoc, idx) => (
+        //           <DocumentView
+        //             key={String(childDoc.id ?? idx)}
+        //             {...this._props}
+        //             Document={childDoc}
+        //           />
+        //         ))
+        //     }
+        //   </div>
+        );
+      }
+}
+
+
+// Register scrapbook
+Docs.Prototypes.TemplateMap.set(DocumentType.SCRAPBOOK, {
+    layout: { view: ScrapbookBox, dataField: 'items' },
+    options: {
+        acl: '',
+        _height: 200,
+        _xMargin: 10,
+        _yMargin: 10,
+        _layout_autoHeight: true,
+        _layout_reflowVertical: true,
+        _layout_reflowHorizontal: true,
+        defaultDoubleClick: 'ignore',
+        systemIcon: 'BsImages',
+    },
+});
diff --git a/src/client/views/nodes/scrapbook/ScrapbookContent.tsx b/src/client/views/nodes/scrapbook/ScrapbookContent.tsx
new file mode 100644
index 000000000..ad1d308e8
--- /dev/null
+++ b/src/client/views/nodes/scrapbook/ScrapbookContent.tsx
@@ -0,0 +1,23 @@
+import React from "react";
+import { observer } from "mobx-react-lite";
+// Import the Doc type from your actual module.
+import { Doc } from "../../../../fields/Doc";
+
+export interface ScrapbookContentProps {
+  doc: Doc;
+}
+
+// A simple view that displays a document's title and content.
+// Adjust how you extract the text if your Doc fields are objects.
+export const ScrapbookContent: React.FC<ScrapbookContentProps> = observer(({ doc }) => {
+  // If doc.title or doc.content are not plain strings, convert them.
+  const titleText = doc.title ? doc.title.toString() : "Untitled";
+  const contentText = doc.content ? doc.content.toString() : "No content available.";
+
+  return (
+    <div className="scrapbook-content">
+      <h3>{titleText}</h3>
+      <p>{contentText}</p>
+    </div>
+  );
+});
diff --git a/src/client/views/nodes/scrapbook/ScrapbookSlot.tsx b/src/client/views/nodes/scrapbook/ScrapbookSlot.tsx
new file mode 100644
index 000000000..05215d3e5
--- /dev/null
+++ b/src/client/views/nodes/scrapbook/ScrapbookSlot.tsx
@@ -0,0 +1,26 @@
+export interface SlotDefinition {
+    id: string;
+    x: number; y: number;
+    defaultWidth: number;
+    defaultHeight: number;
+  }
+  
+  export interface SlotContentMap {
+    slotId: string;
+    docId?: string;
+  }
+  
+  export interface ScrapbookConfig {
+    slots: SlotDefinition[];
+    contents?: SlotContentMap[];
+  }
+  
+  export const DEFAULT_SCRAPBOOK_CONFIG: ScrapbookConfig = {
+    slots: [
+      { id: "slot1", x: 10, y: 10, defaultWidth: 180, defaultHeight: 120 },
+      { id: "slot2", x: 200, y: 10, defaultWidth: 180, defaultHeight: 120 },
+      // …etc
+    ],
+    contents: []
+  };
+  
\ No newline at end of file
diff --git a/src/client/views/nodes/scrapbook/ScrapbookSlotTypes.ts b/src/client/views/nodes/scrapbook/ScrapbookSlotTypes.ts
new file mode 100644
index 000000000..686917d9a
--- /dev/null
+++ b/src/client/views/nodes/scrapbook/ScrapbookSlotTypes.ts
@@ -0,0 +1,25 @@
+// ScrapbookSlotTypes.ts
+export interface SlotDefinition {
+    id: string;
+    title: string;
+    x: number;
+    y: number;
+    defaultWidth: number;
+    defaultHeight: number;
+  }
+  
+  export interface ScrapbookConfig {
+    slots: SlotDefinition[];
+    contents?: { slotId: string; docId: string }[];
+  }
+  
+  // give it three slots by default:
+  export const DEFAULT_SCRAPBOOK_CONFIG: ScrapbookConfig = {
+    slots: [
+      { id: "main",      title: "Main Content", x: 20,  y: 20,  defaultWidth: 360, defaultHeight: 200 },
+      { id: "notes",     title: "Notes",        x: 20,  y: 240, defaultWidth: 360, defaultHeight: 160 },
+      { id: "resources", title: "Resources",    x: 400, y: 20,  defaultWidth: 320, defaultHeight: 380 },
+    ],
+    contents: [],
+  };
+  
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


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

---
 src/client/documents/DocUtils.ts                   | 60 +++++++--------
 src/client/views/DocumentDecorations.tsx           |  2 +-
 src/client/views/InkingStroke.tsx                  |  2 +-
 src/client/views/Main.tsx                          |  4 +-
 .../views/collections/CollectionStackingView.tsx   |  5 +-
 src/client/views/collections/CollectionSubView.tsx |  3 +-
 src/client/views/collections/CollectionView.tsx    | 20 ++---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +
 .../collectionGrid/CollectionGridView.tsx          |  1 +
 .../collectionLinear/CollectionLinearView.tsx      |  1 +
 .../CollectionMulticolumnView.tsx                  |  1 +
 .../CollectionMultirowView.tsx                     |  1 +
 src/client/views/nodes/FieldView.tsx               |  2 +-
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |  4 +-
 src/client/views/nodes/ImageBox.tsx                |  5 +-
 src/client/views/nodes/scrapbook/ScrapbookBox.tsx  | 87 ++++++++++++----------
 16 files changed, 104 insertions(+), 96 deletions(-)

(limited to 'src/client/views/Main.tsx')

diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts
index db0770b12..df14dce5a 100644
--- a/src/client/documents/DocUtils.ts
+++ b/src/client/documents/DocUtils.ts
@@ -308,13 +308,7 @@ export namespace DocUtils {
      */
     export async function DocumentFromType(type: string, path: string, options: DocumentOptions, overwriteDoc?: Doc): Promise<Opt<Doc>> {
         let ctor: ((path: string, options: DocumentOptions, overwriteDoc?: Doc) => Doc | Promise<Doc | undefined>) | undefined;
-       
-        if (type.indexOf('scrapbook') !== -1) { 
-            ctor = Docs.Create.ScrapbookDocument;
-            if (!options._width) options._width = 400;
-            if (!options._title) options._title = 'New Scrapbook';
-        }
-        
+
         if (type.indexOf('image') !== -1) {
             ctor = Docs.Create.ImageDocument;
             if (!options._width) options._width = 300;
@@ -718,33 +712,31 @@ export namespace DocUtils {
                       nativeWidth: 40,
                       nativeHeight: 40,
                   })
-                : (defaultTextTemplate?.type === DocumentType.JOURNAL ? Docs.Create.DailyJournalDocument:Docs.Create.TextDocument)(
-                    '', 
-                    {
-                        annotationOn,
-                        backgroundColor,
-                        x,
-                        y,
-                        title,
-                        ...(defaultTextTemplate
-                            ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance
-                            : {
-                                  _width: width || BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 * 6 : 200,
-                                  _height: BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 : 35,
-                                  _layout_autoHeight: true,
-                                  backgroundColor: StrCast(Doc.UserDoc().textBackgroundColor),
-                                  borderColor: Doc.UserDoc().borderColor as string,
-                                  borderWidth: Doc.UserDoc().borderWidth as number,
-                                  text_centered: BoolCast(Doc.UserDoc().textCentered),
-                                  text_fitBox: BoolCast(Doc.UserDoc().fitBox),
-                                  text_align: StrCast(Doc.UserDoc().textAlign),
-                                  text_fontColor: StrCast(Doc.UserDoc().fontColor),
-                                  text_fontFamily: StrCast(Doc.UserDoc().fontFamily),
-                                  text_fontWeight: StrCast(Doc.UserDoc().fontWeight),
-                                  text_fontStyle: StrCast(Doc.UserDoc().fontStyle),
-                                  text_fontDecoration: StrCast(Doc.UserDoc().fontDecoration),
-                              }),
-                    });
+                : (defaultTextTemplate?.type === DocumentType.JOURNAL ? Docs.Create.DailyJournalDocument : Docs.Create.TextDocument)('', {
+                      annotationOn,
+                      backgroundColor,
+                      x,
+                      y,
+                      title,
+                      ...(defaultTextTemplate
+                          ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance
+                          : {
+                                _width: width || BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 * 6 : 200,
+                                _height: BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 : 35,
+                                _layout_autoHeight: true,
+                                backgroundColor: StrCast(Doc.UserDoc().textBackgroundColor),
+                                borderColor: Doc.UserDoc().borderColor as string,
+                                borderWidth: Doc.UserDoc().borderWidth as number,
+                                text_centered: BoolCast(Doc.UserDoc().textCentered),
+                                text_fitBox: BoolCast(Doc.UserDoc().fitBox),
+                                text_align: StrCast(Doc.UserDoc().textAlign),
+                                text_fontColor: StrCast(Doc.UserDoc().fontColor),
+                                text_fontFamily: StrCast(Doc.UserDoc().fontFamily),
+                                text_fontWeight: StrCast(Doc.UserDoc().fontWeight),
+                                text_fontStyle: StrCast(Doc.UserDoc().fontStyle),
+                                text_fontDecoration: StrCast(Doc.UserDoc().fontDecoration),
+                            }),
+                  });
 
         if (defaultTextTemplate) {
             tbox.layout_fieldKey = 'layout_' + StrCast(defaultTextTemplate.title);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 7424aaf2c..3f11a4713 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -240,7 +240,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
                         } else {
                             // if Doc is in the sticker palette, remove the flag indicating that it's saved
                             const dragFactory = DocCast(iconView.Document.dragFactory);
-                            if (dragFactory && DocCast(dragFactory.cloneOf).savedAsSticker) DocCast(dragFactory.cloneOf).savedAsSticker = undefined;
+                            if (dragFactory && DocCast(dragFactory.cloneOf)?.savedAsSticker) DocCast(dragFactory.cloneOf)!.savedAsSticker = undefined;
 
                             // if this is a face Annotation doc, then just hide it.
                             if (iconView.Document.annotationOn && iconView.Document.face) iconView.Document.hidden = true;
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index f555808ef..0a49ea28e 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -245,7 +245,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>()
      * factor for converting between ink and screen space.
      */
     inkScaledData = () => {
-        const inkData = Cast(this.dataDoc[this.fieldKey], InkField, Cast(this.layoutDoc[this.fieldKey], InkField, null))?.inkData ?? [];
+        const inkData = Cast(this.dataDoc[this.fieldKey], InkField, Cast(this.layoutDoc[this.fieldKey], InkField, null) ?? null)?.inkData ?? [];
         const inkStrokeWidth = NumCast(this.layoutDoc.stroke_width, 1);
         const inkTop = Math.min(...inkData.map(p => p.Y)) - inkStrokeWidth / 2;
         const inkBottom = Math.max(...inkData.map(p => p.Y)) + inkStrokeWidth / 2;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 7992ed412..b884eb8c8 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -53,7 +53,6 @@ import { WebBox } from './nodes/WebBox';
 import { CalendarBox } from './nodes/calendarBox/CalendarBox';
 import { ChatBox } from './nodes/chatbot/chatboxcomponents/ChatBox';
 import { DailyJournal } from './nodes/formattedText/DailyJournal';
-import { ScrapbookVersionTwo } from './nodes/scrapbook/ScrapbookVersionTwo';
 import { DashDocCommentView } from './nodes/formattedText/DashDocCommentView';
 import { DashDocView } from './nodes/formattedText/DashDocView';
 import { DashFieldView } from './nodes/formattedText/DashFieldView';
@@ -66,6 +65,7 @@ import { PresBox, PresSlideBox } from './nodes/trails';
 import { FaceRecognitionHandler } from './search/FaceRecognitionHandler';
 import { SearchBox } from './search/SearchBox';
 import { StickerPalette } from './smartdraw/StickerPalette';
+import { ScrapbookBox } from './nodes/scrapbook/ScrapbookBox';
 
 dotenv.config();
 
@@ -121,7 +121,6 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' };
             FormattedTextBox,
             DailyJournal, // AARAV
             ImageBox,
-            ScrapbookVersionTwo,
             FontIconBox,
             LabelBox,
             EquationBox,
@@ -136,6 +135,7 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' };
             VideoBox,
             AudioBox,
             RecordingBox,
+            ScrapbookBox,
             PresBox,
             PresSlideBox,
             SearchBox,
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 9155227dd..f11e646cc 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -343,7 +343,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
     childFitWidth = (doc: Doc) => Cast(this.Document.childLayoutFitWidth, 'boolean', this._props.childLayoutFitWidth?.(doc) ?? Cast(doc.layout_fitWidth, 'boolean', null) ?? null);
     // this is what renders the document that you see on the screen
     // called in Children: this actually adds a document to our children list
-    getDisplayDoc(doc: Doc, trans: () => string, count: number) {
+    getDisplayDoc = (doc: Doc, trans: () => string, count: number) => {
         const dataDoc = doc.isTemplateDoc || doc.isTemplateForField ? this._props.TemplateDataDocument : undefined;
         this._docXfs.push({ stackedDocTransform: this.getDocTransform(doc), width: this.getDocWidth(doc), height: this.getDocHeight(doc) });
         return count > this._renderCount ? null : (
@@ -384,6 +384,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 searchFilterDocs={this.searchFilterDocs}
                 xPadding={NumCast(this.layoutDoc._childXPadding, this._props.childXPadding)}
                 yPadding={NumCast(this.layoutDoc._childYPadding, this._props.childYPadding)}
+                rejectDrop={this._props.childRejectDrop}
                 addDocument={this._props.addDocument}
                 moveDocument={this._props.moveDocument}
                 removeDocument={this._props.removeDocument}
@@ -393,7 +394,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection
                 pinToPres={this._props.pinToPres}
             />
         );
-    }
+    };
 
     getDocTransform = computedFn((doc: Doc) => () => {
         // these must be referenced for document decorations to update when the text box container is scrolled
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index bc7d6f897..9a0fda3f8 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -58,6 +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
     childHideDecorationTitle?: boolean;
     childHideResizeHandles?: boolean;
     childHideDecorations?: boolean;
@@ -323,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)) {
+            if (docDragData && !docDragData.draggedDocuments.includes(this.Document) && !this._props.rejectDrop?.(docDragData.draggedDocuments, 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/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index c9af92a1b..eb9caf29d 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -183,15 +183,17 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr
                         },
                     })
                 );
-                DocListCast(Cast(Doc.UserDoc()['clickFuncs-child'], Doc, null)?.data).forEach(childClick =>
-                    onClicks.push({
-                        description: `Set child ${childClick.title}`,
-                        icon: 'edit',
-                        event: () => {
-                            this.dataDoc[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data));
-                        },
-                    })
-                );
+                DocListCast(Cast(Doc.UserDoc()['clickFuncs-child'], Doc, null)?.data)
+                    .filter(childClick => ScriptCast(childClick.data))
+                    .forEach(childClick =>
+                        onClicks.push({
+                            description: `Set child ${childClick.title}`,
+                            icon: 'edit',
+                            event: () => {
+                                this.dataDoc[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)!);
+                            },
+                        })
+                    );
                 !Doc.IsSystem(this.Document) && !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' });
             }
 
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}
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index a7603a45b..1d5e70be7 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -201,6 +201,7 @@ export class CollectionGridView extends CollectionSubView() {
             isContentActive={this.isChildContentActive}
             PanelWidth={width}
             PanelHeight={height}
+            rejectDrop={this._props.childRejectDrop}
             ScreenToLocalTransform={dxf}
             setContentViewBox={emptyFunction}
             whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
index 8d3947653..3c2a99b1e 100644
--- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
+++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx
@@ -192,6 +192,7 @@ export class CollectionLinearView extends CollectionSubView() {
                     renderDepth={this._props.renderDepth + 1}
                     dontRegisterView={BoolCast(this.Document.childDontRegisterViews)}
                     focus={emptyFunction}
+                    rejectDrop={this._props.childRejectDrop}
                     styleProvider={this._props.styleProvider}
                     containerViewPath={this.childContainerViewPath}
                     whenChildContentsActiveChanged={emptyFunction}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index adaceb053..caad1c7e0 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -281,6 +281,7 @@ export class CollectionMulticolumnView extends CollectionSubView() {
             PanelWidth={this.childWidth(childLayout)}
             PanelHeight={this.childHeight}
             rootSelected={this.rootSelected}
+            rejectDrop={this._props.childRejectDrop}
             dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
             onClickScript={this.onChildClickHandler}
             onDoubleClickScript={this.onChildDoubleClickHandler}
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 27c41583c..593598479 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -263,6 +263,7 @@ export class CollectionMultirowView extends CollectionSubView() {
             PanelWidth={this.childWidth}
             PanelHeight={this.childHeight(childLayout)}
             rootSelected={this.rootSelected}
+            rejectDrop={this._props.childRejectDrop}
             dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType}
             onClickScript={this.onChildClickHandler}
             onDoubleClickScript={this.onChildDoubleClickHandler}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index de49f502f..b4473d9dc 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
     PanelWidth: () => number;
     PanelHeight: () => number;
     isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 3190757e2..5941e1669 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -117,10 +117,10 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
             default:                               type = 'slider';
                 break;
         } // prettier-ignore
-        const numScript = (value?: number) => ScriptCast(this.Document.script).script.run({ this: this.Document, value, _readOnly_: value === undefined });
+        const numScript = (value?: number) => ScriptCast(this.Document.script)?.script.run({ this: this.Document, value, _readOnly_: value === undefined });
         const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string;
         // Script for checking the outcome of the toggle
-        const checkResult = Number(Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)));
+        const checkResult = Number(Number(numScript()?.result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)));
 
         return (
             <NumberDropdown
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 7ad3ee2b6..c59c38fd8 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -2,7 +2,7 @@ import { Button, Colors, EditableText, Size, Type } from '@dash/components';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Slider, Tooltip } from '@mui/material';
 import axios from 'axios';
-import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { extname } from 'path';
 import * as React from 'react';
@@ -213,7 +213,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     drop = undoable(
         action((e: Event, de: DragManager.DropEvent) => {
-            if (de.complete.docDragData) {
+            if (de.complete.docDragData && this._props.rejectDrop?.(de.complete.docDragData?.draggedDocuments, this.DocumentView?.())) {
                 let added: boolean | undefined;
                 const hitDropTarget = (ele: HTMLElement, dropTarget: HTMLDivElement | null): boolean => {
                     if (!ele) return false;
@@ -938,6 +938,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         ScreenToLocalTransform={this.screenToLocalTransform}
                         select={emptyFunction}
                         focus={this.focus}
+                        rejectDrop={this._props.rejectDrop}
                         getScrollHeight={this.getScrollHeight}
                         NativeDimScaling={returnOne}
                         isAnyChildContentActive={returnFalse}
diff --git a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx
index 56cfcda70..b02976067 100644
--- a/src/client/views/nodes/scrapbook/ScrapbookBox.tsx
+++ b/src/client/views/nodes/scrapbook/ScrapbookBox.tsx
@@ -1,24 +1,25 @@
-import { makeObservable } from 'mobx';
+import { action, makeObservable, observable } from 'mobx';
 import * as React from 'react';
-import { ViewBoxAnnotatableComponent } from '../../DocComponent';
-import { FieldView, FieldViewProps } from '../FieldView';
+import { Doc } from '../../../../fields/Doc';
+import { List } from '../../../../fields/List';
+import { emptyFunction } from '../../../../Utils';
 import { Docs } from '../../../documents/Documents';
 import { DocumentType } from '../../../documents/DocumentTypes';
-import { action, observable } from 'mobx';
-import { DocListCast } from '../../../../fields/Doc';
-import { Doc } from '../../../../fields/Doc';
+import { CollectionView } from '../../collections/CollectionView';
+import { ViewBoxAnnotatableComponent } from '../../DocComponent';
 import { DocumentView } from '../DocumentView';
-import { FormattedTextBox } from '../formattedText/FormattedTextBox';
-import { List } from '../../../../fields/List';
+import { FieldView, FieldViewProps } from '../FieldView';
+import { DragManager } from '../../../util/DragManager';
 // 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);
         makeObservable(this);
         this.createdDate = this.getFormattedDate();
-  
+
         // ensure we always have a List<Doc> in dataDoc['items']
         if (!this.dataDoc[this.fieldKey]) {
             this.dataDoc[this.fieldKey] = new List<Doc>();
@@ -31,12 +32,6 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
         return FieldView.LayoutString(ScrapbookBox, fieldStr);
     }
 
-
-    
-    
-     
-    
-
     getFormattedDate(): string {
         return new Date().toLocaleDateString(undefined, {
             year: 'numeric',
@@ -55,40 +50,50 @@ export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
 
     componentDidMount() {
         this.setTitle();
-        if (!this.dataDoc[this.fieldKey]) {
-            this.dataDoc[this.fieldKey] = new List<Doc>();
-        }
     }
 
+    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;
+    };
+    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;
+    };
+
+    protected createDashEventsTarget = (ele: HTMLDivElement | null) => {
+        this._dropDisposer?.();
+        if (ele) {
+            this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc);
+        }
+    };
+
     render() {
-        // cast into an array even if empty
-        const items: Doc[] = DocListCast(this.dataDoc[this.fieldKey]);
-    
         return (
-            <div style={{ background: 'beige', width: '100%', height: '100%' }}>
-                        
+            <div style={{ background: 'beige', width: '100%', height: '100%' }} ref={r => r && this.createDashEventsTarget(r)}>
+                <CollectionView
+                    {...this._props} //
+                    setContentViewBox={emptyFunction}
+                    rejectDrop={this.rejectDrop}
+                    childRejectDrop={this.childRejectDrop}
+                />
             </div>
-        
-        //   <div
-        //     style={{
-        //     }}
-        //   >
-        //     {items.length === 0
-        //       ? <div style={{color:'#888',fontStyle:'italic'}}>Drop docs here</div>
-        //       : items.map((childDoc, idx) => (
-        //           <DocumentView
-        //             key={String(childDoc.id ?? idx)}
-        //             {...this._props}
-        //             Document={childDoc}
-        //           />
-        //         ))
-        //     }
-        //   </div>
         );
-      }
+    }
 }
 
-
 // Register scrapbook
 Docs.Prototypes.TemplateMap.set(DocumentType.SCRAPBOOK, {
     layout: { view: ScrapbookBox, dataField: 'items' },
-- 
cgit v1.2.3-70-g09d2

