aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authoraidahosa1 <aisosa_idahosa@brown.edu>2024-07-18 11:25:59 -0400
committeraidahosa1 <aisosa_idahosa@brown.edu>2024-07-18 11:25:59 -0400
commit61bc1dd6df886e50fefb03e6477a9173d1d55907 (patch)
treee8083b13902c2b30ca303011167e5f17489ac55b /src
parentabacdb311ee658b74f009106f4e7751fc216af4b (diff)
parent732a00ddba502e3692fde374554c2ed394d275e4 (diff)
Merge branch 'keanu-comments' into aisosa-starter
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/Documents.ts8
-rw-r--r--src/client/views/DocumentDecorations.scss2
-rw-r--r--src/client/views/DocumentDecorations.tsx2
-rw-r--r--src/client/views/KeywordBox.tsx197
-rw-r--r--src/client/views/StyleProvider.scss1
-rw-r--r--src/client/views/collections/CollectionSubView.tsx4
-rw-r--r--src/client/views/collections/CollectionView.tsx1
-rw-r--r--src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx3
9 files changed, 173 insertions, 49 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 6730a9a25..e8e9b4fb6 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -37,12 +37,13 @@ export enum FInfoFieldType {
date = 'date',
list = 'list',
rtf = 'rich text',
+ map = 'map',
}
export class FInfo {
description: string = '';
readOnly: boolean = false;
fieldType?: FInfoFieldType;
- values?: FieldType[];
+ values?: FieldType[] | Map<any, any>;
filterable?: boolean = true; // can be used as a Filter in FilterPanel
// format?: string; // format to display values (e.g, decimal places, $, etc)
@@ -143,6 +144,10 @@ class ListInfo extends FInfo {
fieldType? = FInfoFieldType.list;
values?: List<any>[] = [];
}
+class MapInfo extends FInfo {
+ fieldType? = FInfoFieldType.map;
+ values?: Map<any, any> = new Map();
+}
type BOOLt = BoolInfo | boolean;
type NUMt = NumInfo | number;
type STRt = StrInfo | string;
@@ -155,6 +160,7 @@ type COLLt = CTypeInfo | CollectionViewType;
type DROPt = DAInfo | dropActionType;
type DATEt = DateInfo | number;
type DTYPEt = DTypeInfo | string;
+type MAPt = MapInfo | Map<any, any>;
export class DocumentOptions {
// coordinate and dimensions depending on view
x?: NUMt = new NumInfo('horizontal coordinate in freeform view', false);
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 239c0a977..67e1054c3 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -512,7 +512,7 @@ $resizeHandler: 8px;
justify-content: center;
align-items: center;
gap: 5px;
- top: 4px;
+ //top: 4px;
background: $light-gray;
opacity: 0.2;
pointer-events: all;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 20bf8fd9f..dc40562e8 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -640,6 +640,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
render() {
const { b, r, x, y } = this.Bounds;
const seldocview = DocumentView.Selected().lastElement();
+ const doc = DocumentView.SelectedDocs().lastElement();
if (SnappingManager.IsDragging || r - x < 1 || x === Number.MAX_VALUE || !seldocview || this._hidden || isNaN(r) || isNaN(b) || isNaN(x) || isNaN(y)) {
setTimeout(
action(() => {
@@ -831,6 +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`,
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 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 ? (
diff --git a/src/client/views/StyleProvider.scss b/src/client/views/StyleProvider.scss
index 7cc06f922..4267762aa 100644
--- a/src/client/views/StyleProvider.scss
+++ b/src/client/views/StyleProvider.scss
@@ -58,7 +58,6 @@
display: flex;
flex-wrap: wrap;
flex-direction: column;
- padding-bottom: 4px;
border: 1px solid;
border-radius: 4px;
}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index e250d7a90..26528b2b3 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, makeObservable, observable } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import * as React from 'react';
import * as rp from 'request-promise';
import { ClientUtils, returnFalse } from '../../../ClientUtils';
@@ -9,7 +9,7 @@ import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, DocCast, ScriptCast, StrCast } from '../../../fields/Types';
import { WebField } from '../../../fields/URLField';
import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
import { GestureUtils } from '../../../pen-gestures/GestureUtils';
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 5c304b4a9..a750b731a 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -34,6 +34,7 @@ import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMul
import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView';
import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView';
import { CollectionCardView } from './CollectionCardDeckView';
+import { DocData } from '../../../fields/DocSymbols';
@observer
export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewProps>() {
diff --git a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
index fec4d3e12..af01d6cbc 100644
--- a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
+++ b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx
@@ -238,14 +238,14 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
if (this._selectedImages.length === 0) {
return (
- <div className="searchBox-container" style={{ pointerEvents: 'all', color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }} ref={ele => this.createDropTarget(ele)}>
+ <div className="searchBox-container" style={{ pointerEvents: 'all', color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }} ref={ele => this.createDropTarget(ele!)}>
<p style={{ fontSize: 'large' }}>In order to classify and sort images, marquee select the desired images and press the 'Classify and Sort Images' button. Then, add the desired groups for the images to be put in.</p>
</div>
);
}
return (
- <div className="searchBox-container" style={{ pointerEvents: 'all', color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }} ref={ele => this.createDropTarget(ele)}>
+ <div className="searchBox-container" style={{ pointerEvents: 'all', color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }} ref={ele => this.createDropTarget(ele!)}>
<div className="searchBox-bar" style={{ pointerEvents: 'all', color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }}>
<IconButton
tooltip={'See image information'}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 07e3acb1d..d7a41df64 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -482,11 +482,8 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
}
labelToCollection.set(label, newCollection);
this._props.addDocument?.(newCollection);
- console.log('added collection!');
}
- console.log(labelToCollection);
-
for (const doc of selectedImages) {
if (doc[DocData].data_label) {
Doc.AddDocToList(labelToCollection.get(doc[DocData].data_label as string)!, undefined, doc);