aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/views/KeywordBox.tsx390
-rw-r--r--src/client/views/StyleProvider.tsx8
2 files changed, 180 insertions, 218 deletions
diff --git a/src/client/views/KeywordBox.tsx b/src/client/views/KeywordBox.tsx
index 20cb63d66..703299ae6 100644
--- a/src/client/views/KeywordBox.tsx
+++ b/src/client/views/KeywordBox.tsx
@@ -3,17 +3,17 @@ import { action, computed, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import React from 'react';
import { returnFalse, setupMoveUpEvents } from '../../ClientUtils';
-import { Doc, DocListCast } from '../../fields/Doc';
+import { Utils, emptyFunction } from '../../Utils';
+import { Doc, DocListCast, StrListCast } from '../../fields/Doc';
import { DocData } from '../../fields/DocSymbols';
import { List } from '../../fields/List';
import { NumCast, StrCast } from '../../fields/Types';
-import { emptyFunction, Utils } from '../../Utils';
import { DocumentType } from '../documents/DocumentTypes';
import { DragManager } from '../util/DragManager';
+import { SelectionManager } from '../util/SelectionManager';
import { SnappingManager } from '../util/SnappingManager';
-import { DocumentView } from './nodes/DocumentView';
-import { ObservableReactComponent } from './ObservableReactComponent';
import { undoable } from '../util/UndoManager';
+import { ObservableReactComponent } from './ObservableReactComponent';
interface KeywordItemProps {
doc: Doc;
@@ -28,69 +28,149 @@ interface KeywordItemProps {
*/
@observer
export class KeywordItem extends ObservableReactComponent<KeywordItemProps> {
- constructor(props: any) {
- super(props);
- makeObservable(this);
- this.ref = React.createRef();
+ /**
+ * return list of all Docs that collect Docs with specified keywords
+ */
+ public static get AllKeywordCollections() {
+ return DocListCast(Doc.ActiveDashboard?.myKeywordCollections);
}
+ /**
+ * Find Doc that collects all Docs with given keyword
+ * @param keyword keyword string
+ * @returns keyword collection Doc or undefined
+ */
+ public static findKeywordCollectionDoc = (keyword: String) => KeywordItem.AllKeywordCollections.find(doc => doc.title === keyword);
- private ref: React.RefObject<HTMLDivElement>;
+ /**
+ * Creates a Doc that collects Docs with the specified keyword
+ * @param keyword keyword string
+ * @returns collection Doc
+ */
+ public static createKeywordCollectionDoc = (keyword: string) => {
+ const newKeywordCol = new Doc();
+ newKeywordCol.title = keyword;
+ newKeywordCol.collections = new List<Doc>();
+ newKeywordCol[DocData].docs = new List<Doc>();
+ // If the active Dashboard does not have a keyword collection, create it.
+ if (Doc.ActiveDashboard) {
+ if (!Doc.ActiveDashboard.myKeywordCollections) Doc.ActiveDashboard.myKeywordCollections = new List<Doc>();
+ Doc.AddDocToList(Doc.ActiveDashboard, 'myKeywordCollections', newKeywordCol);
+ }
+ return newKeywordCol;
+ };
/**
- * Gets the documents that a keyword is associated with.
+ * Gets all Docs that have the specified keyword
+ * @param keyword keyword string
* @returns An array of documents that contain the keyword.
*/
- getKeywordCollectionDocs = () => {
- for (const doc of DocListCast(Doc.ActiveDashboard?.myKeywordCollections)) {
- if (doc.title === this._props.keyword) {
- return doc[DocData].docs;
+ public static allDocsWithKeyword = (keyword: string) => DocListCast(KeywordItem.findKeywordCollectionDoc(keyword)?.[DocData].docs);
+
+ /**
+ * Adds a keyword to the metadata of this document
+ * @param keyword keyword string
+ */
+ public static addLabelToDoc = (doc: Doc, keyword: string) => {
+ // If the keyword collection is not in active Dashboard, add it as a new doc, with the keyword as its title.
+ const keywordCollection = KeywordItem.findKeywordCollectionDoc(keyword) ?? KeywordItem.createKeywordCollectionDoc(keyword);
+
+ // If the document is of type COLLECTION, make it a smart collection, otherwise, add the keyword to the document.
+ if (doc.type === DocumentType.COL) {
+ Doc.AddDocToList(keywordCollection[DocData], 'collections', doc);
+
+ // Iterate through the keyword Doc collections and add a copy of the document to each collection
+ for (const cdoc of DocListCast(keywordCollection[DocData].docs)) {
+ if (!DocListCast(doc[DocData].data).find(d => Doc.AreProtosEqual(d, cdoc))) {
+ const newEmbedding = Doc.MakeEmbedding(cdoc);
+ Doc.AddDocToList(doc[DocData], 'data', newEmbedding);
+ Doc.SetContainer(newEmbedding, doc);
+ }
+ }
+ } else {
+ // Add this document to the keyword's collection of associated documents.
+ Doc.AddDocToList(keywordCollection[DocData], 'docs', 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)) {
+ if (!DocListCast(collection[DocData].data).find(d => Doc.AreProtosEqual(d, doc))) {
+ const newEmbedding = Doc.MakeEmbedding(doc);
+ Doc.AddDocToList(collection[DocData], 'data', newEmbedding);
+ Doc.SetContainer(newEmbedding, collection);
+ }
}
}
- return null;
+
+ if (!doc[DocData].data_labels) doc[DocData].data_labels = new List<string>();
+ (doc[DocData].data_labels as List<string>).push(keyword);
+ doc[DocData][keyword] = true;
};
+ public static RemoveLabel = (doc: Doc, keyword: string, keywordDoc: Doc) => {
+ if (doc[DocData].data_labels) {
+ if (doc.type === DocumentType.COL) {
+ Doc.RemoveDocFromList(keywordDoc[DocData], 'collections', doc);
+
+ for (const cur_doc of KeywordItem.allDocsWithKeyword(keyword)) {
+ doc[DocData].data = new List<Doc>(DocListCast(doc[DocData].data).filter(d => !Doc.AreProtosEqual(cur_doc, d)));
+ }
+ } else {
+ Doc.RemoveDocFromList(keywordDoc[DocData], 'docs', doc);
+
+ for (const collection of DocListCast(keywordDoc.collections)) {
+ collection[DocData].data = new List<Doc>(DocListCast(collection[DocData].data).filter(d => !Doc.AreProtosEqual(doc, d)));
+ }
+ }
+ }
+ doc[DocData].data_labels = new List<string>((doc[DocData].data_labels as List<string>).filter(label => label !== keyword));
+ doc[DocData][keyword] = undefined;
+ };
+
+ private _ref: React.RefObject<HTMLDivElement>;
+
+ constructor(props: any) {
+ super(props);
+ makeObservable(this);
+ this._ref = React.createRef();
+ }
+
/**
* 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));
+ const newEmbeddings = KeywordItem.allDocsWithKeyword(this._props.keyword).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);
docData.title = this._props.keyword;
+ docData.data_labels = new List<string>([this._props.keyword]);
+ docData[`${this._props.keyword}`] = true;
+ docData.showLabels = true;
doc._freeform_panX = doc._freeform_panY = 0;
+ doc._width = 900;
+ doc._height = 900;
+ doc.layout_fitWidth = true;
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;
+ newEmbeddings.forEach(embed => Doc.SetContainer(embed, newCollection));
// 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;
+ Doc.AddDocToList(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, {});
+ DragManager.StartDocumentDrag([this._ref.current!], dragData, e.clientX, e.clientY, {});
return true;
},
returnFalse,
@@ -100,51 +180,34 @@ export class KeywordItem extends ObservableReactComponent<KeywordItemProps> {
}
};
- @action
- removeLabel = () => {
- if (this._props.doc[DocData].data_labels) {
- 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;
-
- 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)));
- }
- }
- }
- 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() {
const keyword = this._props.keyword.replace(/^@/, '');
const metadata = this._props.keyword.startsWith('@');
return (
- <div className="keyword" onClick={this._props.setToEditing} onPointerDown={this.handleDragStart} ref={this.ref} key={Utils.GenerateGuid()}>
+ <div className="keyword" onClick={this._props.setToEditing} onPointerDown={this.handleDragStart} ref={this._ref} key={Utils.GenerateGuid()}>
{metadata ? (
<span>
<b style={{ fontSize: 'smaller' }}>{keyword}&nbsp;</b>
- {this._props.doc[keyword] as string}{' '}
+ {this._props.doc[keyword] as string}
</span>
) : (
keyword
)}
- {this.props.isEditing && <IconButton tooltip={'Remove label'} onPointerDown={this.removeLabel} icon={'X'} style={{ width: '8px', height: '8px', marginLeft: '10px' }} />}
+ {this.props.isEditing && (
+ <IconButton
+ tooltip="Remove label"
+ onPointerDown={undoable(() => KeywordItem.RemoveLabel(this._props.doc, this._props.keyword, this._props.keywordDoc), `remove label ${this._props.keyword}`)}
+ icon={'X'}
+ style={{ width: '8px', height: '8px', marginLeft: '10px' }}
+ />
+ )}
</div>
);
}
}
interface KeywordBoxProps {
- doc: Doc;
- isEditing: boolean;
+ Document: Doc;
}
/**
@@ -152,76 +215,50 @@ interface KeywordBoxProps {
*/
@observer
export class KeywordBox extends ObservableReactComponent<KeywordBoxProps> {
- @observable _currentInput: string = '';
- private height: number = 0;
- private ref: React.RefObject<HTMLDivElement>;
-
- @computed
- get currentScale() {
- 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;
- }
+ private _height: number = 0;
+ private _ref: React.RefObject<HTMLDivElement>;
constructor(props: any) {
super(props);
makeObservable(this);
- this.ref = React.createRef();
+ this._ref = React.createRef();
reaction(
() => this.cur_height,
() => {
- this._props.doc[DocData].keywordHeight = this.height;
+ this._props.Document[DocData].keywordHeight = this._height;
}
);
}
- componentDidMount(): void {
- this.height = this.ref.current?.offsetHeight ? this.ref.current?.offsetHeight : 0;
- this._props.doc[DocData].keywordHeight = this.height;
- }
+ @observable _currentInput = '';
+ @observable _isEditing = !StrListCast(this._props.Document[DocData].data_labels).length;
- componentDidUpdate(prevProps: Readonly<KeywordBoxProps>): void {
- this.height = this.ref.current?.offsetHeight ? this.ref.current?.offsetHeight : 0;
- this._props.doc[DocData].keywordHeight = this.height;
+ @computed get currentScale() {
+ return NumCast((this._props.Document.embedContainer as Doc)?._freeform_scale, 1);
}
- @action
- setToEditing = () => {
- this._props.isEditing = true;
- };
+ @computed get cur_height() {
+ return this._ref.current?.offsetHeight ?? 0;
+ }
- @action
- setToView = () => {
- this._props.isEditing = false;
- };
+ @computed get isEditing() {
+ return this._isEditing && SelectionManager.Docs().includes(this._props.Document);
+ }
- /**
- * Gets the document associated with a keyword.
- * @param keyword The keyword being searched for
- * @returns A Doc containing keyword information
- */
- getKeywordCollection = (keyword: string) => {
- // Look for the keyword document.
- for (const doc of DocListCast(Doc.ActiveDashboard!.myKeywordCollections)) {
- if (doc.title === keyword) {
- return doc;
- }
- }
+ componentDidMount() {
+ this._height = this._ref.current?.offsetHeight ?? 0;
+ this._props.Document[DocData].keywordHeight = this._height;
+ }
- // 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>();
- if (Doc.ActiveDashboard) {
- Doc.ActiveDashboard.myKeywordCollections = new List<Doc>([...DocListCast(Doc.ActiveDashboard.myKeywordCollections), keywordCollection]);
- }
+ componentDidUpdate(prevProps: Readonly<KeywordBoxProps>): void {
+ this._height = this._ref.current?.offsetHeight ?? 0;
+ this._props.Document[DocData].keywordHeight = this._height;
+ }
- return keywordCollection;
+ @action
+ setToEditing = () => {
+ this._isEditing = true;
};
/**
@@ -229,94 +266,42 @@ export class KeywordBox extends ObservableReactComponent<KeywordBoxProps> {
* @param keyword
*/
submitLabel = undoable((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 = 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.
- if (!this._props.doc[DocData].data_labels) {
- this._props.doc[DocData].data_labels = new List<string>();
- }
-
- // 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 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;
+ if (submittedLabel && !this._props.Document[DocData][submittedLabel]) {
+ KeywordItem.addLabelToDoc(this._props.Document, submittedLabel);
this._currentInput = ''; // Clear the input box
}
}, 'added doc label');
- @action
- onInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- this._currentInput = e.target.value;
- };
-
render() {
- const keywordsList = this._props.doc[DocData].data_labels ? this._props.doc[DocData].data_labels : new List<string>();
- const seldoc = DocumentView.SelectedDocs().lastElement();
- 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;
- }
- this.setToView();
- })
- );
- }
+ const keywordsList = StrListCast(this._props.Document[DocData].data_labels);
- return (
+ return !this._props.Document.showLabels ? null : (
<div
className="keywords-container"
- ref={this.ref}
+ ref={this._ref}
style={{
transformOrigin: 'top left',
- overflow: 'hidden',
+ maxWidth: `${100 * this.currentScale}%`,
+ width: 'max-content',
transform: `scale(${1 / this.currentScale})`,
- backgroundColor: this._props.isEditing ? Colors.LIGHT_GRAY : Colors.TRANSPARENT,
- borderColor: this._props.isEditing ? Colors.BLACK : Colors.TRANSPARENT,
- maxWidth: `400px`,
+ backgroundColor: this.isEditing ? Colors.LIGHT_GRAY : Colors.TRANSPARENT,
+ borderColor: this.isEditing ? Colors.BLACK : Colors.TRANSPARENT,
}}>
- <div className="keywords-content">
+ <div className="keywords-content" style={{ width: '100%' }}>
<div className="keywords-list">
- {(keywordsList as List<string>).map(keyword => {
- return <KeywordItem key={Utils.GenerateGuid()} doc={this._props.doc} keyword={keyword} keywordDoc={this.getKeywordCollection(keyword)} setToEditing={this.setToEditing} isEditing={this._props.isEditing}></KeywordItem>;
+ {keywordsList.map(keyword => {
+ const keywordDoc = KeywordItem.findKeywordCollectionDoc(keyword);
+ return !keywordDoc ? null : <KeywordItem key={Utils.GenerateGuid()} doc={this._props.Document} keyword={keyword} keywordDoc={keywordDoc} setToEditing={this.setToEditing} isEditing={this.isEditing} />;
})}
</div>
- {this._props.isEditing ? (
+ {this.isEditing ? (
<div className="keyword-editing-box">
<div className="keyword-input-box">
<input
value={this._currentInput}
autoComplete="off"
- onChange={this.onInputChange}
+ onChange={action(e => (this._currentInput = e.target.value))}
onKeyDown={e => {
e.key === 'Enter' ? this.submitLabel(this._currentInput) : null;
e.stopPropagation();
@@ -328,45 +313,28 @@ export class KeywordBox extends ObservableReactComponent<KeywordBoxProps> {
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);
- }}
- key={Utils.GenerateGuid()}
- />
- );
- })}
- </div>
- ) : (
- <div></div>
- )}
+ <div className="keyword-suggestions-box">
+ {KeywordItem.AllKeywordCollections.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)}
+ key={Utils.GenerateGuid()}
+ />
+ );
+ })}
+ </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' }}
- />
+ {!keywordsList.length ? null : ( //
+ <IconButton style={{ width: '4px' }} tooltip="Close Menu" onPointerDown={action(() => (this._isEditing = false))} icon="x" />
+ )}
</div>
</div>
- ) : (
- <div></div>
- )}
+ ) : null}
</div>
</div>
);
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 374399445..1e80e7ee5 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -364,13 +364,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
</Tooltip>
);
};
- const keywords = () => {
- if (doc && doc![DocData].showLabels && (!doc[DocData].data_labels || (doc[DocData].data_labels as List<string>).length === 0)){
- return (<KeywordBox isEditing={true} doc={doc}></KeywordBox>)
- } else if (doc && doc![DocData].data_labels && doc![DocData].showLabels) {
- return (<KeywordBox isEditing={false} doc={doc}></KeywordBox>)
- }
- }
+ const keywords = () => doc ? <KeywordBox Document={doc}/> : null;
return (
<>
{paint()}