From 6cf715a76dfb3f6a80fbf7c33e643681ea1a584c Mon Sep 17 00:00:00 2001
From: IEatChili <nanunguyen99@gmail.com>
Date: Tue, 30 Jul 2024 14:30:08 -0400
Subject: feat: adjusted ui:

---
 src/client/views/KeywordBox.tsx | 263 +++++++++++++++++++++++++---------------
 1 file changed, 165 insertions(+), 98 deletions(-)

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

diff --git a/src/client/views/KeywordBox.tsx b/src/client/views/KeywordBox.tsx
index 321362299..68584a7fa 100644
--- a/src/client/views/KeywordBox.tsx
+++ b/src/client/views/KeywordBox.tsx
@@ -1,4 +1,4 @@
-import { Colors, IconButton } from 'browndash-components';
+import { Button, Colors, IconButton } from 'browndash-components';
 import { action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import React from 'react';
@@ -6,14 +6,11 @@ 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 { NumCast, StrCast } from '../../fields/Types';
 import { emptyFunction } from '../../Utils';
-import { Docs } from '../documents/Documents';
-import { DocUtils } from '../documents/DocUtils';
-import { DragManager, SetupDrag } from '../util/DragManager';
+import { DocumentType } from '../documents/DocumentTypes';
+import { DragManager } 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';
 
@@ -21,11 +18,13 @@ interface KeywordItemProps {
     doc: Doc;
     keyword: string;
     keywordDoc: Doc;
-    keywordCollection: Doc[];
     setToEditing: () => void;
     isEditing: boolean;
 }
 
+/**
+ * A component that handles individual keywords.
+ */
 @observer
 export class KeywordItem extends ObservableReactComponent<KeywordItemProps> {
     constructor(props: any) {
@@ -36,8 +35,12 @@ export class KeywordItem extends ObservableReactComponent<KeywordItemProps> {
 
     private ref: React.RefObject<HTMLDivElement>;
 
+    /**
+     * Gets the documents that a keyword is associated with.
+     * @returns An array of documents that contain the keyword.
+     */
     getKeywordCollectionDocs = () => {
-        for (const doc of DocListCast(Doc.UserDoc().myKeywordCollections)) {
+        for (const doc of DocListCast(Doc.ActiveDashboard?.myKeywordCollections)) {
             if (doc.title === this._props.keyword) {
                 return doc[DocData].docs;
             }
@@ -45,9 +48,16 @@ export class KeywordItem extends ObservableReactComponent<KeywordItemProps> {
         return null;
     };
 
+    /**
+     * Creates a smart collection.
+     * @returns
+     */
     createCollection = () => {
+        // Get the documents that contain the keyword.
         const selected = DocListCast(this.getKeywordCollectionDocs()!);
         const newEmbeddings = selected.map(doc => Doc.MakeEmbedding(doc));
+
+        // Create a new collection and set up configurations.
         const newCollection = ((doc: Doc) => {
             const docData = doc[DocData];
             docData.data = new List<Doc>(newEmbeddings);
@@ -59,9 +69,12 @@ export class KeywordItem extends ObservableReactComponent<KeywordItemProps> {
         newCollection._width = 900;
         newCollection._height = 900;
         newCollection.layout_fitWidth = true;
-        //newCollection[DocData].smartCollection = this._props.keywordDoc;
 
+        // Add the collection to the keyword document's list of associated smart collections.
         this._props.keywordDoc.collections = new List<Doc>([...DocListCast(this._props.keywordDoc.collections), newCollection]);
+        newCollection[DocData].data_labels = new List<string>([this._props.keyword]);
+        newCollection[DocData][`${this._props.keyword}`] = true;
+        newCollection[DocData].showLabels = true;
         return newCollection;
     };
 
@@ -89,16 +102,24 @@ export class KeywordItem extends ObservableReactComponent<KeywordItemProps> {
     @action
     removeLabel = () => {
         if (this._props.doc[DocData].data_labels) {
-            const filtered_docs = new List<Doc>(DocListCast(this.getKeywordCollectionDocs()!).filter(doc => doc !== this._props.doc));
-            this._props.keywordDoc[DocData].docs = filtered_docs;
+            if (this._props.doc.type === DocumentType.COL) {
+                const filtered_collections = new List<Doc>(DocListCast(this._props.keywordDoc.collections).filter(doc => doc !== this._props.doc));
+                this._props.keywordDoc.collections = filtered_collections;
 
-            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 cur_doc of DocListCast(this.getKeywordCollectionDocs()!, [])) {
+                    this._props.doc[DocData].data = new List<Doc>(DocListCast(this._props.doc[DocData].data).filter(doc => !Doc.AreProtosEqual(cur_doc, doc)));
+                }
+            } else {
+                const filtered_docs = new List<Doc>(DocListCast(this.getKeywordCollectionDocs()!).filter(doc => doc !== this._props.doc));
+                this._props.keywordDoc[DocData].docs = filtered_docs;
 
-            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)));
+                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)));
+                }
             }
         }
+        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;
     };
 
     render() {
@@ -116,6 +137,9 @@ interface KeywordBoxProps {
     isEditing: boolean;
 }
 
+/**
+ * A component that handles the keyword display for documents.
+ */
 @observer
 export class KeywordBox extends ObservableReactComponent<KeywordBoxProps> {
     @observable _currentInput: string = '';
@@ -127,30 +151,32 @@ export class KeywordBox extends ObservableReactComponent<KeywordBoxProps> {
         return NumCast((this._props.doc.embedContainer as Doc)?._freeform_scale, 1);
     }
 
+    @computed
+    get cur_height() {
+        return this.ref.current?.offsetHeight ? this.ref.current?.offsetHeight : 0;
+    }
+
     constructor(props: any) {
         super(props);
         makeObservable(this);
         this.ref = React.createRef();
-    }
-
-    componentDidMount(): void {
-        this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0;
-        this._props.doc._keywordHeight = this.height;
 
         reaction(
-            () => this.currentScale,
+            () => this.cur_height,
             () => {
-                if (this.currentScale < 1) {
-                    this.height = this.ref.current?.getBoundingClientRect().height ? this.ref.current?.getBoundingClientRect().height : 0;
-                    this._props.doc._keywordHeight = this.height;
-                }
+                this._props.doc[DocData].keywordHeight = this.height;
             }
         );
     }
 
+    componentDidMount(): void {
+        this.height = this.ref.current?.offsetHeight ? this.ref.current?.offsetHeight : 0;
+        this._props.doc[DocData].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;
+        this.height = this.ref.current?.offsetHeight ? this.ref.current?.offsetHeight : 0;
+        this._props.doc[DocData].keywordHeight = this.height;
     }
 
     @action
@@ -163,30 +189,44 @@ export class KeywordBox extends ObservableReactComponent<KeywordBoxProps> {
         this._props.isEditing = false;
     };
 
+    /**
+     * Gets the document associated with a keyword.
+     * @param keyword The keyword being searched for
+     * @returns A Doc containing keyword information
+     */
     getKeywordCollection = (keyword: string) => {
-        for (const doc of DocListCast(Doc.UserDoc().myKeywordCollections)) {
+        // Look for the keyword document.
+        for (const doc of DocListCast(Doc.ActiveDashboard!.myKeywordCollections)) {
             if (doc.title === keyword) {
                 return doc;
             }
         }
 
+        // If not contained, create a new document and add it to the active Dashboard's keyword list.
         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]);
+        if (Doc.ActiveDashboard) {
+            Doc.ActiveDashboard.myKeywordCollections = new List<Doc>([...DocListCast(Doc.ActiveDashboard.myKeywordCollections), keywordCollection]);
+        }
 
         return keywordCollection;
     };
 
-    submitLabel = () => {
-        if (!Doc.UserDoc().myKeywordCollections) {
-            Doc.UserDoc().myKeywordCollections = new List<Doc>();
+    /**
+     * Adds the keyword to the document.
+     * @param keyword
+     */
+    submitLabel = (keyword: string) => {
+        // If the active Dashboard does not have a keyword collection, create it.
+        if (Doc.ActiveDashboard && !Doc.ActiveDashboard.myKeywordCollections) {
+            Doc.ActiveDashboard.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 submittedLabel = keyword.trim();
+        if (submittedLabel && !this._props.doc![DocData][`${submittedLabel}`]) {
+            // If the keyword collection is not in active Dashboard, 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.
@@ -194,20 +234,31 @@ export class KeywordBox extends ObservableReactComponent<KeywordBoxProps> {
                 this._props.doc[DocData].data_labels = new List<string>();
             }
 
-            // 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;
+            // If the document is of type COLLECTION, make it a smart collection, otherwise, add the keyword to the document.
+            if (this._props.doc.type === DocumentType.COL) {
+                keywordCollection.collections = new List<Doc>([...DocListCast(keywordCollection.collections), this._props.doc]);
 
-            // 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;
+                // Iterate through the keyword document's collections and add a copy of the document to each collection
+                for (const doc of DocListCast(keywordCollection[DocData].docs)) {
+                    const newEmbedding = Doc.MakeEmbedding(doc);
+                    this._props.doc[DocData].data = new List<Doc>([...DocListCast(this._props.doc[DocData].data), newEmbedding]);
+                    newEmbedding.embedContainer = this._props.doc;
+                }
+            } else {
+                // Add this document to the keyword's collection of associated documents.
+                keywordCollection[DocData].docs = new List<Doc>([...DocListCast(keywordCollection[DocData].docs), this._props.doc]);
+
+                // 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;
+                }
             }
 
+            // 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][`${submittedLabel}`] = true;
             this._currentInput = ''; // Clear the input box
         }
     };
@@ -223,9 +274,9 @@ 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();
                 })
             );
@@ -237,59 +288,75 @@ export class KeywordBox extends ObservableReactComponent<KeywordBoxProps> {
                 ref={this.ref}
                 style={{
                     transformOrigin: 'top left',
+                    overflow: 'hidden',
                     transform: `scale(${1 / this.currentScale})`,
                     backgroundColor: this._props.isEditing ? Colors.LIGHT_GRAY : Colors.TRANSPARENT,
                     borderColor: this._props.isEditing ? Colors.BLACK : Colors.TRANSPARENT,
+                    maxWidth: `400px`,
                 }}>
-                <div className="keywords-list">
-                    {(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 ? (
-                    <div className="keyword-editing-box">
-                        <div className="keyword-input-box">
-                            <input
-                                value={this._currentInput}
-                                autoComplete="off"
-                                onChange={this.onInputChange}
-                                onKeyDown={e => {
-                                    e.key === 'Enter' ? this.submitLabel() : null;
-                                    e.stopPropagation();
-                                }}
-                                type="text"
-                                placeholder="Input keywords for document..."
-                                aria-label="keyword-input"
-                                className="keyword-input"
-                                style={{ width: '100%', borderRadius: '5px' }}
-                            />
-                        </div>
-                        <div className="keyword-buttons">
-                            <IconButton
-                                tooltip={'Close Menu'}
-                                onPointerDown={() => {
-                                    if ((keywordsList as List<string>).length === 0) {
-                                        this._props.doc[DocData].showLabels = false;
-                                    } else {
-                                        this.setToView();
-                                    }
-                                }}
-                                icon={'x'}
-                                style={{ width: '4px' }}
-                            />
-                        </div>
+                <div className="keywords-content">
+                    <div className="keywords-list">
+                        {(keywordsList as List<string>).map(keyword => {
+                            return <KeywordItem doc={this._props.doc} keyword={keyword} keywordDoc={this.getKeywordCollection(keyword)} setToEditing={this.setToEditing} isEditing={this._props.isEditing}></KeywordItem>;
+                        })}
                     </div>
-                ) : (
-                    <div></div>
-                )}
+                    {this._props.isEditing ? (
+                        <div className="keyword-editing-box">
+                            <div className="keyword-input-box">
+                                <input
+                                    value={this._currentInput}
+                                    autoComplete="off"
+                                    onChange={this.onInputChange}
+                                    onKeyDown={e => {
+                                        e.key === 'Enter' ? this.submitLabel(this._currentInput) : null;
+                                        e.stopPropagation();
+                                    }}
+                                    type="text"
+                                    placeholder="Input keywords for document..."
+                                    aria-label="keyword-input"
+                                    className="keyword-input"
+                                    style={{ width: '100%', borderRadius: '5px' }}
+                                />
+                            </div>
+                            {Doc.ActiveDashboard?.myKeywordCollections ? (
+                                <div className="keyword-suggestions-box">
+                                    {DocListCast(Doc.ActiveDashboard?.myKeywordCollections).map(doc => {
+                                        const keyword = StrCast(doc.title);
+                                        return (
+                                            <Button
+                                                style={{ margin: '2px 2px', border: '1px solid black', backgroundColor: 'lightblue', color: 'black' }}
+                                                text={keyword}
+                                                color={SnappingManager.userVariantColor}
+                                                tooltip={'Add existing keyword'}
+                                                onClick={() => {
+                                                    this.submitLabel(keyword);
+                                                }}
+                                            />
+                                        );
+                                    })}
+                                </div>
+                            ) : (
+                                <div></div>
+                            )}
+                            <div className="keyword-buttons">
+                                <IconButton
+                                    tooltip={'Close Menu'}
+                                    onPointerDown={() => {
+                                        if ((keywordsList as List<string>).length === 0) {
+                                            this._props.doc[DocData].showLabels = false;
+                                        } else {
+                                            this.setToView();
+                                        }
+                                    }}
+                                    icon={'x'}
+                                    style={{ width: '4px' }}
+                                />
+                            </div>
+                        </div>
+                    ) : (
+                        <div></div>
+                    )}
+                </div>
             </div>
         );
     }
-- 
cgit v1.2.3-70-g09d2

