From 732a00ddba502e3692fde374554c2ed394d275e4 Mon Sep 17 00:00:00 2001
From: IEatChili <nanunguyen99@gmail.com>
Date: Wed, 17 Jul 2024 15:13:11 -0400
Subject: feat: created smart collections

---
 src/client/views/KeywordBox.tsx | 197 ++++++++++++++++++++++++++++++++--------
 1 file changed, 158 insertions(+), 39 deletions(-)

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

diff --git a/src/client/views/KeywordBox.tsx b/src/client/views/KeywordBox.tsx
index d94f011f4..321362299 100644
--- a/src/client/views/KeywordBox.tsx
+++ b/src/client/views/KeywordBox.tsx
@@ -2,17 +2,26 @@ import { Colors, IconButton } from 'browndash-components';
 import { action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import React from 'react';
-import { Doc } from '../../fields/Doc';
+import { returnFalse, setupMoveUpEvents } from '../../ClientUtils';
+import { Doc, DocListCast } from '../../fields/Doc';
 import { DocData } from '../../fields/DocSymbols';
 import { List } from '../../fields/List';
+import { DocCast, NumCast } from '../../fields/Types';
+import { emptyFunction } from '../../Utils';
+import { Docs } from '../documents/Documents';
+import { DocUtils } from '../documents/DocUtils';
 import { DragManager, SetupDrag } from '../util/DragManager';
 import { SnappingManager } from '../util/SnappingManager';
+import { CollectionFreeFormView } from './collections/collectionFreeForm';
+import { MainView } from './MainView';
 import { DocumentView } from './nodes/DocumentView';
 import { ObservableReactComponent } from './ObservableReactComponent';
 
 interface KeywordItemProps {
     doc: Doc;
-    label: string;
+    keyword: string;
+    keywordDoc: Doc;
+    keywordCollection: Doc[];
     setToEditing: () => void;
     isEditing: boolean;
 }
@@ -25,28 +34,77 @@ export class KeywordItem extends ObservableReactComponent<KeywordItemProps> {
         this.ref = React.createRef();
     }
 
-    private _dropDisposer?: DragManager.DragDropDisposer;
     private ref: React.RefObject<HTMLDivElement>;
 
-    protected createDropTarget = (ele: HTMLDivElement) => {
-        this._dropDisposer?.();
-        SetupDrag(this.ref, () => undefined);
-        //ele && (this._dropDisposer = DragManager. (ele, this.onInternalDrop.bind(this), this.layoutDoc));
-        //ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc));
+    getKeywordCollectionDocs = () => {
+        for (const doc of DocListCast(Doc.UserDoc().myKeywordCollections)) {
+            if (doc.title === this._props.keyword) {
+                return doc[DocData].docs;
+            }
+        }
+        return null;
+    };
+
+    createCollection = () => {
+        const selected = DocListCast(this.getKeywordCollectionDocs()!);
+        const newEmbeddings = selected.map(doc => Doc.MakeEmbedding(doc));
+        const newCollection = ((doc: Doc) => {
+            const docData = doc[DocData];
+            docData.data = new List<Doc>(newEmbeddings);
+            docData.title = this._props.keyword;
+            doc._freeform_panX = doc._freeform_panY = 0;
+            return doc;
+        })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true));
+        newEmbeddings.forEach(embed => (embed.embedContainer = newCollection));
+        newCollection._width = 900;
+        newCollection._height = 900;
+        newCollection.layout_fitWidth = true;
+        //newCollection[DocData].smartCollection = this._props.keywordDoc;
+
+        this._props.keywordDoc.collections = new List<Doc>([...DocListCast(this._props.keywordDoc.collections), newCollection]);
+        return newCollection;
+    };
+
+    @action
+    handleDragStart = (e: React.PointerEvent) => {
+        if (this._props.isEditing) {
+            const clone = this.ref.current?.cloneNode(true) as HTMLElement;
+            if (!clone) return;
+
+            setupMoveUpEvents(
+                this,
+                e,
+                () => {
+                    const dragData = new DragManager.DocumentDragData([this.createCollection()]);
+                    DragManager.StartDocumentDrag([this.ref.current!], dragData, e.clientX, e.clientY, {});
+                    return true;
+                },
+                returnFalse,
+                emptyFunction
+            );
+            e.preventDefault();
+        }
     };
 
     @action
     removeLabel = () => {
         if (this._props.doc[DocData].data_labels) {
-            this._props.doc[DocData].data_labels = (this._props.doc[DocData].data_labels as List<string>).filter(label => label !== this._props.label) as List<string>;
-            this._props.doc![DocData][`${this._props.label}`] = false;
+            const filtered_docs = new List<Doc>(DocListCast(this.getKeywordCollectionDocs()!).filter(doc => doc !== this._props.doc));
+            this._props.keywordDoc[DocData].docs = filtered_docs;
+
+            this._props.doc[DocData].data_labels = (this._props.doc[DocData].data_labels as List<string>).filter(label => label !== this._props.keyword) as List<string>;
+            this._props.doc![DocData][`${this._props.keyword}`] = false;
+
+            for (const collection of DocListCast(this._props.keywordDoc.collections)) {
+                collection[DocData].data = new List<Doc>(DocListCast(collection[DocData].data).filter(doc => !Doc.AreProtosEqual(this._props.doc, doc)));
+            }
         }
     };
 
     render() {
         return (
-            <div className="keyword" onClick={this._props.setToEditing} onPointerDown={() => {}} ref={this.ref}>
-                {this._props.label}
+            <div className="keyword" onClick={this._props.setToEditing} onPointerDown={this.handleDragStart} ref={this.ref}>
+                {this._props.keyword}
                 {this.props.isEditing && <IconButton tooltip={'Remove label'} onPointerDown={this.removeLabel} icon={'X'} style={{ width: '8px', height: '8px', marginLeft: '10px' }} />}
             </div>
         );
@@ -61,31 +119,39 @@ interface KeywordBoxProps {
 @observer
 export class KeywordBox extends ObservableReactComponent<KeywordBoxProps> {
     @observable _currentInput: string = '';
-    //private disposer: () => void;
+    private height: number = 0;
+    private ref: React.RefObject<HTMLDivElement>;
+
+    @computed
+    get currentScale() {
+        return NumCast((this._props.doc.embedContainer as Doc)?._freeform_scale, 1);
+    }
 
     constructor(props: any) {
         super(props);
         makeObservable(this);
+        this.ref = React.createRef();
     }
 
-    // componentDidMount(): void {
-    //     reaction(
-    //         () => ({
-    //             isDragging: SnappingManager.IsDragging,
-    //             selectedDoc: DocumentView.SelectedDocs().lastElement(),
-    //             isEditing: this._props.isEditing,
-    //         }),
-    //         ({ isDragging, selectedDoc, isEditing }) => {
-    //             if (isDragging || selectedDoc !== this._props.doc || !isEditing) {
-    //                 this.setToView();
-    //             }
-    //         }
-    //     );
-    // }
-
-    // componentWillUnmount() {
-    //     this.disposer();
-    // }
+    componentDidMount(): void {
+        this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0;
+        this._props.doc._keywordHeight = this.height;
+
+        reaction(
+            () => this.currentScale,
+            () => {
+                if (this.currentScale < 1) {
+                    this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0;
+                    this._props.doc._keywordHeight = this.height;
+                }
+            }
+        );
+    }
+
+    componentDidUpdate(prevProps: Readonly<KeywordBoxProps>): void {
+        this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0;
+        this._props.doc._keywordHeight = this.height;
+    }
 
     @action
     setToEditing = () => {
@@ -97,14 +163,51 @@ export class KeywordBox extends ObservableReactComponent<KeywordBoxProps> {
         this._props.isEditing = false;
     };
 
+    getKeywordCollection = (keyword: string) => {
+        for (const doc of DocListCast(Doc.UserDoc().myKeywordCollections)) {
+            if (doc.title === keyword) {
+                return doc;
+            }
+        }
+
+        const keywordCollection = new Doc();
+        keywordCollection.title = keyword;
+        keywordCollection[DocData].docs = new List<Doc>();
+        keywordCollection.collections = new List<Doc>();
+        Doc.UserDoc().myKeywordCollections = new List<Doc>([...DocListCast(Doc.UserDoc().myKeywordCollections), keywordCollection]);
+
+        return keywordCollection;
+    };
+
     submitLabel = () => {
-        if (this._currentInput.trim()) {
+        if (!Doc.UserDoc().myKeywordCollections) {
+            Doc.UserDoc().myKeywordCollections = new List<Doc>();
+        }
+
+        const submittedLabel = this._currentInput.trim();
+        if (submittedLabel) {
+            // If the keyword collection is not in the user doc, add it as a new doc, with the keyword as its title.
+            const keywordCollection = this.getKeywordCollection(submittedLabel);
+
+            // If the document has no keywords field, create the field.
             if (!this._props.doc[DocData].data_labels) {
                 this._props.doc[DocData].data_labels = new List<string>();
             }
 
-            (this._props.doc![DocData].data_labels! as List<string>).push(this._currentInput.trim());
+            // Add this document to the keyword's collection of associated documents.
+            keywordCollection[DocData].docs = new List<Doc>([...DocListCast(keywordCollection[DocData].docs), this._props.doc]);
+
+            // Push the keyword to the document's keyword list field.
+            (this._props.doc![DocData].data_labels! as List<string>).push(submittedLabel);
             this._props.doc![DocData][`${this._currentInput}`] = true;
+
+            // Iterate through the keyword document's collections and add a copy of the document to each collection
+            for (const collection of DocListCast(keywordCollection.collections)) {
+                const newEmbedding = Doc.MakeEmbedding(this._props.doc);
+                collection[DocData].data = new List<Doc>([...DocListCast(collection.data), newEmbedding]);
+                newEmbedding.embedContainer = collection;
+            }
+
             this._currentInput = ''; // Clear the input box
         }
     };
@@ -120,19 +223,35 @@ export class KeywordBox extends ObservableReactComponent<KeywordBoxProps> {
         if (SnappingManager.IsDragging || !(seldoc === this._props.doc) || !this._props.isEditing) {
             setTimeout(
                 action(() => {
-                    if ((keywordsList as List<string>).length === 0) {
-                        this._props.doc[DocData].showLabels = false;
-                    }
+                    // if ((keywordsList as List<string>).length === 0) {
+                    //     this._props.doc[DocData].showLabels = false;
+                    // }
                     this.setToView();
                 })
             );
         }
 
         return (
-            <div className="keywords-container" style={{ backgroundColor: this._props.isEditing ? Colors.LIGHT_GRAY : Colors.TRANSPARENT, borderColor: this._props.isEditing ? Colors.BLACK : Colors.TRANSPARENT }}>
+            <div
+                className="keywords-container"
+                ref={this.ref}
+                style={{
+                    transformOrigin: 'top left',
+                    transform: `scale(${1 / this.currentScale})`,
+                    backgroundColor: this._props.isEditing ? Colors.LIGHT_GRAY : Colors.TRANSPARENT,
+                    borderColor: this._props.isEditing ? Colors.BLACK : Colors.TRANSPARENT,
+                }}>
                 <div className="keywords-list">
-                    {(keywordsList as List<string>).map(label => {
-                        return <KeywordItem doc={this._props.doc} label={label} setToEditing={this.setToEditing} isEditing={this._props.isEditing}></KeywordItem>;
+                    {(keywordsList as List<string>).map(keyword => {
+                        return (
+                            <KeywordItem
+                                doc={this._props.doc}
+                                keyword={keyword}
+                                keywordDoc={this.getKeywordCollection(keyword)}
+                                keywordCollection={DocListCast(this.getKeywordCollection(keyword))}
+                                setToEditing={this.setToEditing}
+                                isEditing={this._props.isEditing}></KeywordItem>
+                        );
                     })}
                 </div>
                 {this._props.isEditing ? (
-- 
cgit v1.2.3-70-g09d2

