diff options
author | IEatChili <nanunguyen99@gmail.com> | 2024-07-30 14:30:08 -0400 |
---|---|---|
committer | IEatChili <nanunguyen99@gmail.com> | 2024-07-30 14:30:08 -0400 |
commit | 6cf715a76dfb3f6a80fbf7c33e643681ea1a584c (patch) | |
tree | bc8ef7b1162bf8225604c7f9ab63e9b7777eef31 | |
parent | 732a00ddba502e3692fde374554c2ed394d275e4 (diff) |
feat: adjusted ui:
-rw-r--r-- | src/client/views/DocumentDecorations.tsx | 2 | ||||
-rw-r--r-- | src/client/views/KeywordBox.tsx | 263 | ||||
-rw-r--r-- | src/client/views/StyleProvider.scss | 26 |
3 files changed, 189 insertions, 102 deletions
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index dc40562e8..bd6952620 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -832,7 +832,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora <div className="link-button-container" style={{ - top: `${doc[DocData].showLabels ? 4 + (doc._keywordHeight as number) : 4}px`, + top: `${doc[DocData].showLabels ? 4 + (doc[DocData].keywordHeight as number) : 4}px`, transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `, }}> <DocumentButtonBar views={() => DocumentView.Selected()} /> 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> ); } diff --git a/src/client/views/StyleProvider.scss b/src/client/views/StyleProvider.scss index 4267762aa..1d41697f5 100644 --- a/src/client/views/StyleProvider.scss +++ b/src/client/views/StyleProvider.scss @@ -77,14 +77,34 @@ align-items: center; } +.keyword-suggestions-box { + display: flex; + flex-wrap: wrap; + margin: auto; + align-self: center; + width: 90%; + border: 1px solid black; + border-radius: 2px; + margin-top: 8px; +} + +.keyword-suggestion { + cursor: pointer; + padding: 1px 1px; + margin: 2px 2px; + background-color: lightblue; + border: 1px solid black; + border-radius: 5px; + white-space: nowrap; + display: flex; + align-items: center; +} + .keyword-editing-box { margin-top: 8px; } .keyword-input-box { - // display: flex; - // align-items: center; - // align-content: center; margin: auto; align-self: center; width: 90%; |