aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/views/DocumentButtonBar.tsx10
-rw-r--r--src/client/views/DocumentDecorations.scss7
-rw-r--r--src/client/views/DocumentDecorations.tsx11
-rw-r--r--src/client/views/StyleProvider.tsx2
-rw-r--r--src/client/views/TagsView.tsx65
-rw-r--r--src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx2
6 files changed, 69 insertions, 28 deletions
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 437ef045f..785e69f84 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -267,7 +267,15 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
get keywordButton() {
return !DocumentView.Selected().length ? null : (
<Tooltip title={<div className="dash-keyword-button">Open keyword menu</div>}>
- <div className="documentButtonBar-icon" style={{ color: 'white' }} onClick={() => DocumentView.Selected().map(dv => (dv.dataDoc.showTags = !dv.dataDoc.showTags))}>
+ <div
+ className="documentButtonBar-icon"
+ style={{ color: 'white' }}
+ onClick={() => {
+ const showing = DocumentView.Selected().some(dv => dv.dataDoc.showTags);
+ DocumentView.Selected().forEach(dv => {
+ dv.dataDoc.showTags = !showing;
+ });
+ }}>
<FontAwesomeIcon className="documentdecorations-icon" icon="tag" />
</div>
</Tooltip>
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 67e1054c3..346df10d5 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -38,6 +38,13 @@ $resizeHandler: 8px;
background: green;
border-radius: 50%;
}
+ .documentDecorations-tagsView {
+ position: absolute;
+ height: 100%;
+ pointer-events: all;
+ border-radius: 50%;
+ color: black;
+ }
}
.documentDecorations-container {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index da35459bb..118a5bd3d 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -35,6 +35,7 @@ import { DocumentView } from './nodes/DocumentView';
import { ImageBox } from './nodes/ImageBox';
import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere';
import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
+import { TagsView } from './TagsView';
interface DocumentDecorationsProps {
PanelWidth: number;
@@ -835,12 +836,20 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
<div
className="link-button-container"
style={{
- top: `${doc[DocData].showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
+ top: DocumentView.Selected().length > 1 ? 0 : `${doc[DocData].showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
}}>
<DocumentButtonBar views={() => DocumentView.Selected()} />
</div>
)}
+ <div
+ className="documentDecorations-tagsView"
+ style={{
+ top: `${doc[DocData].showTags ? 4 + seldocview.TagPanelHeight : 4}px`,
+ transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `,
+ }}>
+ {DocumentView.Selected().length > 1 ? <TagsView Views={DocumentView.Selected()} /> : null}
+ </div>
</div>
{useRotation && (
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 513953d17..262f888fb 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -365,7 +365,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
</Tooltip>
);
};
- const tags = () => props?.DocumentView?.() ? <TagsView View={props.DocumentView()}/> : null;
+ const tags = () => props?.DocumentView?.() ? <TagsView Views={[props.DocumentView()]}/> : null;
return (
<>
{paint()}
diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx
index 8047363d9..ae9d42749 100644
--- a/src/client/views/TagsView.tsx
+++ b/src/client/views/TagsView.tsx
@@ -33,7 +33,7 @@ import { FaceRecognitionHandler } from './search/FaceRecognitionHandler';
*/
interface TagItemProps {
- doc: Doc;
+ docs: Doc[];
tag: string;
tagDoc: Opt<Doc>;
showRemoveUI: boolean;
@@ -207,8 +207,12 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
e.preventDefault();
};
+ @computed get doc() {
+ return this._props.docs.lastElement();
+ }
+
render() {
- this._props.tagDoc && setTimeout(() => TagItem.addTagToDoc(this._props.doc, this._props.tag)); // bcz: hack to make sure that Docs are added to their tag Doc collection since metadata can get set anywhere without a guard triggering an add to the collection
+ this._props.tagDoc && setTimeout(() => this._props.docs.forEach(doc => TagItem.addTagToDoc(doc, this._props.tag))); // bcz: hack to make sure that Docs are added to their tag Doc collection since metadata can get set anywhere without a guard triggering an add to the collection
const tag = this._props.tag.replace(/^#/, '');
const metadata = tag.startsWith('@') ? tag.replace(/^@/, '') : '';
return (
@@ -216,16 +220,16 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
{metadata ? (
<span>
<b style={{ fontSize: 'smaller' }}>{tag}&nbsp;</b>
- {typeof this._props.doc[metadata] === 'boolean' ? (
+ {typeof this.doc[metadata] === 'boolean' ? (
<input
type="checkbox"
onClick={e => e.stopPropagation()}
onPointerDown={e => e.stopPropagation()}
- onChange={undoable(e => (this._props.doc[metadata] = !this._props.doc[metadata]), 'metadata toggle')}
- checked={this._props.doc[metadata] as boolean}
+ onChange={undoable(e => (this.doc[metadata] = !this.doc[metadata]), 'metadata toggle')}
+ checked={this.doc[metadata] as boolean}
/>
) : (
- Field.toString(this._props.doc[metadata])
+ Field.toString(this.doc[metadata])
)}
</span>
) : (
@@ -234,7 +238,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
{this.props.showRemoveUI && this._props.tagDoc && (
<IconButton
tooltip="Remove tag"
- onPointerDown={undoable(() => TagItem.removeTagFromDoc(this._props.doc, this._props.tag, this._props.tagDoc), `remove tag ${this._props.tag}`)}
+ onPointerDown={undoable(() => this._props.docs.forEach(doc => TagItem.removeTagFromDoc(doc, this._props.tag, this._props.tagDoc)), `remove tag ${this._props.tag}`)}
icon={<FontAwesomeIcon icon="times" size="sm" />}
style={{ width: '8px', height: '8px', marginLeft: '10px' }}
/>
@@ -245,7 +249,7 @@ export class TagItem extends ObservableReactComponent<TagItemProps> {
}
interface TagViewProps {
- View: DocumentView;
+ Views: DocumentView[];
}
/**
@@ -261,12 +265,12 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
@observable _panelHeightDirty = 0;
@observable _currentInput = '';
- @observable _isEditing = !StrListCast(this._props.View.dataDoc.tags).length;
+ @observable _isEditing = !StrListCast(this.View.dataDoc.tags).length;
_heightDisposer: IReactionDisposer | undefined;
componentDidMount() {
this._heightDisposer = reaction(
- () => this._props.View.screenToContentsTransform(),
+ () => this.View.screenToContentsTransform(),
xf => {
this._panelHeightDirty = this._panelHeightDirty + 1;
}
@@ -276,11 +280,15 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
this._heightDisposer?.();
}
+ @computed get View() {
+ return this._props.Views.lastElement();
+ }
+
@computed get currentScale() {
- return Math.max(1, 1 / this._props.View.screenToLocalScale());
+ return this._props.Views.length > 1 ? 1 : Math.max(1, 1 / this.View.screenToLocalScale());
}
@computed get isEditing() {
- return this._isEditing && DocumentView.SelectedDocs().includes(this._props.View.Document);
+ return this._isEditing && (this._props.Views.length > 1 || DocumentView.SelectedDocs().includes(this.View.Document));
}
/**
@@ -290,7 +298,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
@action
setToEditing = (editing = true) => {
this._isEditing = editing;
- editing && this._props.View.select(false);
+ editing && this._props.Views.length === 1 && this.View.select(false);
};
/**
@@ -303,8 +311,10 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
action((tag: string) => {
const submittedLabel = tag.trim().replace(/^#/, '').split(':');
if (submittedLabel[0]) {
- TagItem.addTagToDoc(this._props.View.Document, '#' + submittedLabel[0]);
- if (submittedLabel.length > 1) Doc.SetField(this._props.View.Document, submittedLabel[0].replace(/^@/, ''), ':' + submittedLabel[1]);
+ this._props.Views.forEach(view => {
+ TagItem.addTagToDoc(view.Document, '#' + submittedLabel[0]);
+ if (submittedLabel.length > 1) Doc.SetField(view.Document, submittedLabel[0].replace(/^@/, ''), ':' + submittedLabel[1]);
+ });
}
this._currentInput = ''; // Clear the input box
}),
@@ -316,20 +326,20 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
* When the dropdown is clicked, this will toggle an extended UI that allows additional tags to be added/removed.
*/
render() {
- const tagsList = new Set<string>(StrListCast(this._props.View.dataDoc.tags));
- const chatTagsList = new Set<string>(StrListCast(this._props.View.dataDoc.tags_chat));
+ const tagsList = new Set<string>(StrListCast(this.View.dataDoc.tags));
+ const chatTagsList = new Set<string>(StrListCast(this.View.dataDoc.tags_chat));
const facesList = new Set<string>(
- DocListCast(this._props.View.dataDoc[Doc.LayoutFieldKey(this._props.View.Document) + '_annotations'])
- .concat(this._props.View.Document)
+ DocListCast(this.View.dataDoc[Doc.LayoutFieldKey(this.View.Document) + '_annotations'])
+ .concat(this.View.Document)
.filter(d => d.face)
.map(doc => StrCast(DocCast(doc.face)?.title))
);
this._panelHeightDirty;
- return !this._props.View.Document.showTags ? null : (
+ return !this.View.Document.showTags && this._props.Views.length === 1 ? null : (
<div
className="tagsView-container"
- ref={r => r && new ResizeObserver(action(() => (this._props.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)}
+ ref={r => r && new ResizeObserver(action(() => this._props.Views.length === 1 && (this.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)}
style={{
transformOrigin: 'top left',
maxWidth: `${100 * this.currentScale}%`,
@@ -338,7 +348,7 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
backgroundColor: this.isEditing ? Colors.LIGHT_GRAY : Colors.TRANSPARENT,
borderColor: this.isEditing ? Colors.BLACK : Colors.TRANSPARENT,
position: 'relative',
- top: `calc(-${this.InsetDist} * ${1 / this.currentScale}px)`,
+ top: this._props.Views.length > 1 ? 25 : `calc(-${this.InsetDist} * ${1 / this.currentScale}px)`,
}}>
<div className="tagsView-content" style={{ width: '100%' }}>
<div className="tagsView-list">
@@ -346,10 +356,17 @@ export class TagsView extends ObservableReactComponent<TagViewProps> {
<IconButton style={{ width: '8px' }} tooltip="Close Menu" onPointerDown={() => this.setToEditing(!this._isEditing)} icon={<FontAwesomeIcon icon={this._isEditing ? 'chevron-up' : 'chevron-down'} size="sm" />} />
)}
{Array.from(tagsList).map((tag, i) => (
- <TagItem key={i} doc={this._props.View.Document} tag={tag} tagDoc={TagItem.findTagCollectionDoc(tag) ?? TagItem.createTagCollectionDoc(tag)} setToEditing={this.setToEditing} showRemoveUI={this.isEditing} />
+ <TagItem
+ key={i}
+ docs={this._props.Views.map(view => view.Document)}
+ tag={tag}
+ tagDoc={TagItem.findTagCollectionDoc(tag) ?? TagItem.createTagCollectionDoc(tag)}
+ setToEditing={this.setToEditing}
+ showRemoveUI={this.isEditing}
+ />
))}
{Array.from(facesList).map((tag, i) => (
- <TagItem key={i} doc={this._props.View.Document} tag={tag} tagDoc={undefined} setToEditing={this.setToEditing} showRemoveUI={this.isEditing} />
+ <TagItem key={i} docs={this._props.Views.map(view => view.Document)} tag={tag} tagDoc={undefined} setToEditing={this.setToEditing} showRemoveUI={this.isEditing} />
))}
</div>
{this.isEditing ? (
diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx
index f9f6c81ab..534f67927 100644
--- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx
+++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx
@@ -179,7 +179,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() {
ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
})}>
{FaceRecognitionHandler.UniqueFaceImages(this.Document).map((doc, i) => {
- const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)]).url.href.split('.');
+ const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)])?.url.href.split('.') ?? ['-missing-', '.png'];
return (
<div
className="image-wrapper"