From a958577d4c27b276aa37484e3f895e196138b17c Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 1 Sep 2024 12:55:59 -0400 Subject: consolidated image resampling. fixed importing zipped workspace. --- .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 2 +- src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index c4cf8dee7..dbf781e63 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1811,7 +1811,7 @@ export class CollectionFreeFormView extends CollectionSubView { error && console.log(error); data && - ClientUtils.convertDataUri(data, this._props.Document[Id] + '-thumb-frozen').then(returnedfilename => { - this._props.Document['thumb-frozen'] = new ImageField(returnedfilename); + ClientUtils.convertDataUri(data, this._props.Document[Id] + '_icon_' + new Date().getTime()).then(returnedfilename => { + this._props.Document[DocData].icon = new ImageField(returnedfilename); }); }) ); -- cgit v1.2.3-70-g09d2 From b9f15c10e4cfa1288e176cbd1d312c628c5998ad Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 10 Sep 2024 14:54:44 -0400 Subject: moved TagsView up into DocumentView so that one row of tags will be visible. fixed setting pixel size of images to resize annotations so they don't appear to change. added vert/horiz centering for stacking views. fixed pres box to not have scroll bars. fixed resizing properties panel. --- src/client/documents/Documents.ts | 3 ++- src/client/util/CurrentUserUtils.ts | 12 ++++++----- src/client/views/MainView.tsx | 8 +++++--- src/client/views/SidebarAnnos.tsx | 1 + src/client/views/StyleProvider.tsx | 2 +- src/client/views/TagsView.tsx | 7 +++++-- .../views/collections/CollectionCalendarView.tsx | 1 + .../views/collections/CollectionStackingView.scss | 7 ++----- .../views/collections/CollectionStackingView.tsx | 13 +++++++++--- .../CollectionStackingViewFieldColumn.tsx | 24 ++++++++++------------ src/client/views/collections/TreeView.tsx | 2 +- .../collectionFreeForm/FaceCollectionBox.tsx | 1 + src/client/views/global/globalScripts.ts | 14 ++++++++----- src/client/views/nodes/DocumentView.scss | 2 +- src/client/views/nodes/FieldView.tsx | 1 + src/client/views/nodes/ImageBox.tsx | 14 +++++++++++-- 16 files changed, 70 insertions(+), 42 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d5a7b0465..fbdf361dd 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -260,6 +260,7 @@ export class DocumentOptions { _layout_noSidebar?: BOOLt = new BoolInfo('whether to display the sidebar toggle button'); layout_boxShadow?: string; // box-shadow css string OR "standard" to use dash standard box shadow layout_maxShown?: NUMt = new NumInfo('maximum number of children to display at one time (see multicolumnview)'); + _layout_dontCenter?: STRt = new StrInfo("whether collections will center their content - values of 'x', 'xy', or 'y'"); _layout_autoHeight?: BOOLt = new BoolInfo('whether document automatically resizes vertically to display contents'); _layout_autoHeightMargins?: NUMt = new NumInfo('Margin heights to be added to the computed auto height of a Doc'); _layout_curPage?: NUMt = new NumInfo('current page of a PDF or other? paginated document', false); @@ -979,7 +980,7 @@ export namespace Docs { } export function StackingDocument(documents: Array, options: DocumentOptions, id?: string, protoId?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Stacking }, id, undefined, protoId); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _layout_dontCenter: 'y', ...options, _type_collection: CollectionViewType.Stacking }, id, undefined, protoId); } export function NoteTakingDocument(documents: Array, options: DocumentOptions, id?: string, protoId?: string) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index f042f33ce..2014c48e1 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -368,7 +368,7 @@ pie title Minerals in my tap water {key: "Chat", creator: Docs.Create.ChatDocument, opts: { _width: 300, _height: 300, }}, {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 120, _header_pointerEvents: "all", _header_height: 50, _header_fontSize: 9,_layout_autoHeightMargins: 50, _layout_autoHeight: true, treeView_HideUnrendered: true}}, {key: "ViewSlide", creator: slideView, opts: { _width: 400, _height: 300, _xMargin: 3, _yMargin: 3,}}, - {key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, dropAction: dropActionType.embed, treeView_HideTitle: true, _layout_fitWidth:true, layout_boxShadow: "0 0" }}, + {key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, _layout_dontCenter:'xy', dropAction: dropActionType.embed, treeView_HideTitle: true, _layout_fitWidth:true, layout_boxShadow: "0 0" }}, {key: "Tab", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 500, _height: 800, _layout_fitWidth: true, _freeform_backgroundGrid: true, }}, {key: "Slide", creator: opts => Docs.Create.TreeDocument([], opts), opts: { _width: 300, _height: 200, _type_collection: CollectionViewType.Tree, treeView_HasOverlay: true, _text_fontSize: "20px", _layout_autoHeight: true, @@ -477,6 +477,7 @@ pie title Minerals in my tap water const reqdStackOpts:DocumentOptions ={ title: "menuItemPanel", childDragAction: dropActionType.same, layout_boxShadow: "rgba(0,0,0,0)", dontRegisterView: true, ignoreClick: true, + _layout_dontCenter: 'y', _chromeHidden: true, _gridGap: 0, _yMargin: 0, _xMargin: 0, _layout_autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, isSystem: true, }; return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" }); @@ -510,7 +511,7 @@ pie title Minerals in my tap water // doc.myUserBtns = new PrefetchProxy(userBtns); const reqdToolOps:DocumentOptions = { title: "My Tools", isSystem: true, ignoreClick: true, layout_boxShadow: "0 0", - layout_explainer: "This is a palette of documents that can be created.", + layout_explainer: "This is a palette of documents that can be created.", _layout_dontCenter: "y", _layout_showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _dragOnlyWithinContainer: true, layout_hideContextMenu: true, _chromeHidden: true, }; return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdToolOps, [creatorBtns, userBtns]); @@ -663,7 +664,8 @@ pie title Minerals in my tap water } static stackTools(): Button[] { return [ - { title: "Center", icon: "align-center", toolTip: "Center Align Stack", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"center", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "V. Align", icon: "pallet", toolTip: "Center Align Stack", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"vcenter", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "Center", icon: "align-center", toolTip: "Center Align Stack", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"hcenter", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform ] } static cardTools(): Button[] { @@ -777,8 +779,8 @@ pie title Minerals in my tap water } static imageTools() { return [ - { title: "Pixels",toolTip: "Set Native Pixel Sizze", btnType: ButtonType.ClickButton, icon: "portrait", scripts: { onClick: 'imageSetPixelSize();' }}, - { title: "Rotate",toolTip: "Rotate 90", btnType: ButtonType.ClickButton, icon: "redo-alt", scripts: { onClick: 'imageRotate90();' }}, + { title: "Pixels",toolTip: "Set Native Pixel Size", btnType: ButtonType.ClickButton, icon: "portrait", scripts: { onClick: 'imageSetPixelSize();' }}, + { title: "Rotate",toolTip: "Rotate 90", btnType: ButtonType.ClickButton, icon: "redo-alt", scripts: { onClick: 'imageRotate90();' }}, ]; } static contextMenuTools():Button[] { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 9f1c7da3d..393abea53 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -3,7 +3,7 @@ import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons'; import * as far from '@fortawesome/free-regular-svg-icons'; import * as fa from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, configure, makeObservable, observable, reaction, runInAction } from 'mobx'; +import { action, computed, configure, makeObservable, observable, reaction, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import ResizeObserver from 'resize-observer-polyfill'; @@ -707,8 +707,8 @@ export class MainView extends ObservableReactComponent { setupMoveUpEvents( this, e, - action(() => { - SnappingManager.SetPropertiesWidth(Math.max(0, this._dashUIWidth - e.clientX)); + action(moveEv => { + SnappingManager.SetPropertiesWidth(Math.max(0, this._dashUIWidth - moveEv.clientX)); return !SnappingManager.PropertiesWidth; }), action(() => { @@ -807,6 +807,7 @@ export class MainView extends ObservableReactComponent { childFilters={returnEmptyFilter} childFiltersByRanges={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} + dontCenter="y" /> ); @@ -832,6 +833,7 @@ export class MainView extends ObservableReactComponent { }; @computed get mainInnerContent() { + trace(); const leftMenuFlyoutWidth = this._leftMenuFlyoutWidth + this.leftMenuWidth(); const width = this.propertiesWidth() + leftMenuFlyoutWidth; return ( diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 8f0a35df0..dd60bfa65 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -246,6 +246,7 @@ export class SidebarAnnos extends ObservableReactComponent, props: Opt ); }; - const tags = () => props?.DocumentView?.() && CollectionFreeFormDocumentView.from(props.DocumentView()) ? : null; + const tags = () => props?.DocumentView?.() ? : null; return ( <> {paint()} diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index 65e70e8fc..8047363d9 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -91,7 +91,7 @@ export class TagItem extends ObservableReactComponent { const tagCollection = TagItem.findTagCollectionDoc(tag) ?? TagItem.createTagCollectionDoc(tag); // If the document is of type COLLECTION, make it a smart collection, otherwise, add the tag to the document. - if (doc.type === DocumentType.COL) { + if (doc.type === DocumentType.COL && !doc.annotationOn) { Doc.AddDocToList(tagCollection[DocData], 'collections', doc); // Iterate through the tag Doc collections and add a copy of the document to each collection @@ -257,6 +257,7 @@ export class TagsView extends ObservableReactComponent { super(props); makeObservable(this); } + InsetDist = 25; // how far tag panel is moved up to overlap DocumentView. @observable _panelHeightDirty = 0; @observable _currentInput = ''; @@ -328,7 +329,7 @@ export class TagsView extends ObservableReactComponent { return !this._props.View.Document.showTags ? null : (
r && new ResizeObserver(action(() => (this._props.View.TagPanelHeight = r?.getBoundingClientRect().height ?? 0))).observe(r)} + ref={r => r && new ResizeObserver(action(() => (this._props.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)} style={{ transformOrigin: 'top left', maxWidth: `${100 * this.currentScale}%`, @@ -336,6 +337,8 @@ export class TagsView extends ObservableReactComponent { transform: `scale(${1 / this.currentScale})`, 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)`, }}>
diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx index 30fd9fc2b..0ea9f8ebc 100644 --- a/src/client/views/collections/CollectionCalendarView.tsx +++ b/src/client/views/collections/CollectionCalendarView.tsx @@ -82,6 +82,7 @@ export class CollectionCalendarView extends CollectionSubView() { isAnnotationOverlay={false} // select={emptyFunction} What does this mean? isAnyChildContentActive={returnTrue} // ?? + dontCenter="y" // childDocumentsActive={} // whenChildContentsActiveChanged={} childHideDecorationTitle={false} diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss index 6225cc52a..6400a0a8e 100644 --- a/src/client/views/collections/CollectionStackingView.scss +++ b/src/client/views/collections/CollectionStackingView.scss @@ -54,11 +54,8 @@ } .collectionStackingViewFieldColumn { - height: max-content; - } - - .collectionStackingViewFieldColumnDragging { - height: 100%; + display: flex; + flex-direction: column; } .collectionSchemaView-previewDoc { diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 6402ef16c..2cfe9329a 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -146,7 +146,7 @@ export class CollectionStackingView extends CollectionSubView @@ -343,7 +343,7 @@ export class CollectionStackingView extends CollectionSubView Array.from(this.Sections); // what a section looks like if we're in stacking view sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { @@ -565,6 +571,7 @@ export class CollectionStackingView extends CollectionSubView ); }; diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 5ae08e535..aba6b51d4 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -41,6 +41,7 @@ interface CSVFieldColumnProps { columnWidth: number; numGroupColumns: number; gridGap: number; + dontCenter: 'x' | 'xy' | 'y'; type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined; headings: () => object[]; // I think that stacking view actually has a single column and then supposedly you can add more columns? Unsure @@ -345,15 +346,6 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< - {/* {evContents === noValueHeader ? null : ( -
- - - -
- )} */}
{this._props.Document._columnsHideIfEmpty ? null : headingView} {this.collapsed ? null : ( -
+
headings.indexOf(i) === idx); return (
{ }; docTransform = () => this.refTransform(this._dref?.ContentDiv); getTransform = () => this.refTransform(this._tref.current); - embeddedPanelWidth = () => this._props.panelWidth() / (this.treeView._props.NativeDimScaling?.() || 1); + embeddedPanelWidth = () => this._props.panelWidth() / (this.treeView._props.NativeDimScaling?.() || 1) - 3 /* paddingRight for bullet */; embeddedPanelHeight = () => { const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.Document))(this.treeView._props.childLayoutTemplate?.()) || this.layoutDoc; return Math.min( diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx index 717081666..f9f6c81ab 100644 --- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx +++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx @@ -264,6 +264,7 @@ export class FaceCollectionBox extends ViewBoxBaseComponent() { isContentActive={returnTrue} isAnyChildContentActive={returnTrue} childHideDecorations={true} + dontCenter="y" />
); diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 2c7920bdd..934ed6c3e 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -133,10 +133,10 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { }); // eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce', checkResult?: boolean, persist?: boolean) { +ScriptingGlobals.add(function showFreeform(attr: 'hcenter' | 'vcenter' | 'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce', checkResult?: boolean, persist?: boolean) { const selected = DocumentView.SelectedDocs().lastElement(); // prettier-ignore - const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'like' | 'star' | 'idea' | 'chat' | '1' | '2' | '3' | '4', + const map: Map<'flashcards' | 'hcenter' | 'vcenter' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'like' | 'star' | 'idea' | 'chat' | '1' | '2' | '3' | '4', { waitForRender?: boolean; checkResult: (doc: Doc) => boolean; @@ -158,9 +158,13 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines' else (dv.ComponentView as CollectionFreeFormView)?.fitContentOnce(); }, }], - ['center', { - checkResult: (doc:Doc) => BoolCast(doc?._stacking_alignCenter, false), - setDoc: (doc:Doc) => { doc._stacking_alignCenter = !doc._stacking_alignCenter; }, + ['vcenter', { + checkResult: (doc:Doc) => !StrCast(doc?._layout_dontCenter).includes('y'), + setDoc: (doc:Doc) => { doc._layout_dontCenter = StrCast(doc.layout_dontCenter).includes('y') ? StrCast(doc.layout_dontCenter).replace(/y/,"") : StrCast(doc.layout_dontCenter) + 'y'; }, + }], + ['hcenter', { + checkResult: (doc:Doc) => !StrCast(doc?._layout_dontCenter).includes('x'), + setDoc: (doc:Doc) => { doc._layout_dontCenter = StrCast(doc.layout_dontCenter).includes('x') ? StrCast(doc.layout_dontCenter).replace(/x/,"") : 'x'+ StrCast(doc.layout_dontCenter); }, }], ['clusters', { waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 23dada260..2873b4bba 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -242,7 +242,7 @@ .contentFittingDocumentView { position: relative; - display: flex; + display: block; width: 100%; height: 100%; transition: inherit; diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index dd71fd946..c269c7bcb 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -82,6 +82,7 @@ export interface FieldViewSharedProps { // eslint-disable-next-line no-use-before-define onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined; fitWidth?: (doc: Doc) => boolean | undefined; + dontCenter?: 'x' | 'y' | 'xy' | undefined; searchFilterDocs: () => Doc[]; showTitle?: () => string; whenChildContentsActiveChanged: (isActive: boolean) => void; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index d0a7fc6ac..be3525544 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -11,7 +11,7 @@ import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { ObjectField } from '../../../fields/ObjectField'; -import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; +import { Cast, ImageCast, NumCast, RTFCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction } from '../../../Utils'; @@ -188,8 +188,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { @undoBatch setNativeSize = action(() => { + const oldnativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']); const nscale = NumCast(this._props.PanelWidth()) * NumCast(this.layoutDoc._freeform_scale, 1); - const nw = nscale / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']); + const nw = nscale / oldnativeWidth; this.dataDoc[this.fieldKey + '_nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) * nw; this.dataDoc[this.fieldKey + '_nativeWidth'] = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) * nw; this.dataDoc._freeform_panX = nw * NumCast(this.dataDoc._freeform_panX); @@ -198,6 +199,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { this.dataDoc._freeform_panX_min = this.dataDoc._freeform_panX_min ? nw * NumCast(this.dataDoc._freeform_panX_min) : undefined; this.dataDoc._freeform_panY_max = this.dataDoc._freeform_panY_max ? nw * NumCast(this.dataDoc._freeform_panY_max) : undefined; this.dataDoc._freeform_panY_min = this.dataDoc._freeform_panY_min ? nw * NumCast(this.dataDoc._freeform_panY_min) : undefined; + const newnativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']); + DocListCast(this.dataDoc[this.annotationKey]).forEach(doc => { + doc.x = (NumCast(doc.x) / oldnativeWidth) * newnativeWidth; + doc.y = (NumCast(doc.y) / oldnativeWidth) * newnativeWidth; + if (!RTFCast(doc[Doc.LayoutFieldKey(doc)])) { + doc.width = (NumCast(doc.width) / oldnativeWidth) * newnativeWidth; + doc.height = (NumCast(doc.height) / oldnativeWidth) * newnativeWidth; + } + }); }); @undoBatch rotate = action(() => { -- cgit v1.2.3-70-g09d2 From 623da0b4eec34fbd238cc26a5e0c105426a6711e Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 12 Sep 2024 09:50:31 -0400 Subject: added tags for multi-selections. --- src/client/views/DocumentButtonBar.tsx | 10 +++- src/client/views/DocumentDecorations.scss | 7 +++ src/client/views/DocumentDecorations.tsx | 11 +++- src/client/views/StyleProvider.tsx | 2 +- src/client/views/TagsView.tsx | 65 ++++++++++++++-------- .../collectionFreeForm/FaceCollectionBox.tsx | 2 +- 6 files changed, 69 insertions(+), 28 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm') 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 : ( Open keyword menu
}> -
DocumentView.Selected().map(dv => (dv.dataDoc.showTags = !dv.dataDoc.showTags))}> +
{ + const showing = DocumentView.Selected().some(dv => dv.dataDoc.showTags); + DocumentView.Selected().forEach(dv => { + dv.dataDoc.showTags = !showing; + }); + }}>
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 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) `, }}> DocumentView.Selected()} />
)} +
+ {DocumentView.Selected().length > 1 ? : null} +
{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, props: Opt ); }; - const tags = () => props?.DocumentView?.() ? : null; + const tags = () => 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; showRemoveUI: boolean; @@ -207,8 +207,12 @@ export class TagItem extends ObservableReactComponent { 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 { {metadata ? ( {tag}  - {typeof this._props.doc[metadata] === 'boolean' ? ( + {typeof this.doc[metadata] === 'boolean' ? ( 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]) )} ) : ( @@ -234,7 +238,7 @@ export class TagItem extends ObservableReactComponent { {this.props.showRemoveUI && this._props.tagDoc && ( 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={} style={{ width: '8px', height: '8px', marginLeft: '10px' }} /> @@ -245,7 +249,7 @@ export class TagItem extends ObservableReactComponent { } interface TagViewProps { - View: DocumentView; + Views: DocumentView[]; } /** @@ -261,12 +265,12 @@ export class TagsView extends ObservableReactComponent { @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 { 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 { @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 { 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 { * When the dropdown is clicked, this will toggle an extended UI that allows additional tags to be added/removed. */ render() { - const tagsList = new Set(StrListCast(this._props.View.dataDoc.tags)); - const chatTagsList = new Set(StrListCast(this._props.View.dataDoc.tags_chat)); + const tagsList = new Set(StrListCast(this.View.dataDoc.tags)); + const chatTagsList = new Set(StrListCast(this.View.dataDoc.tags_chat)); const facesList = new Set( - 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 : (
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 { 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)`, }}>
@@ -346,10 +356,17 @@ export class TagsView extends ObservableReactComponent { this.setToEditing(!this._isEditing)} icon={} /> )} {Array.from(tagsList).map((tag, i) => ( - + view.Document)} + tag={tag} + tagDoc={TagItem.findTagCollectionDoc(tag) ?? TagItem.createTagCollectionDoc(tag)} + setToEditing={this.setToEditing} + showRemoveUI={this.isEditing} + /> ))} {Array.from(facesList).map((tag, i) => ( - + view.Document)} tag={tag} tagDoc={undefined} setToEditing={this.setToEditing} showRemoveUI={this.isEditing} /> ))}
{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() { 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 (
Date: Thu, 12 Sep 2024 14:42:10 -0400 Subject: changed closing of face rectangles to hide them. added Shift+click of tags button to show face rectangles. --- src/client/documents/Documents.ts | 1 + src/client/views/DocumentButtonBar.tsx | 14 +++++++++----- src/client/views/DocumentDecorations.tsx | 7 ++++--- src/client/views/TagsView.tsx | 6 +++--- .../views/collections/collectionFreeForm/ImageLabelBox.tsx | 4 ++-- src/client/views/nodes/formattedText/RichTextRules.ts | 2 +- src/client/views/search/FaceRecognitionHandler.tsx | 3 +++ 7 files changed, 23 insertions(+), 14 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index fbdf361dd..89356072a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -273,6 +273,7 @@ export class DocumentOptions { _layout_showTitle?: string; // field name to display in header (:hover is an optional suffix) _layout_showSidebar?: BOOLt = new BoolInfo('whether an annotationsidebar should be displayed for text docuemnts'); _layout_showCaption?: string; // which field to display in the caption area. leave empty to have no caption + _layout_showTags?: BOOLt = new BoolInfo('whether to show the list of document tags at the bottom of a DocView'); _chromeHidden?: BOOLt = new BoolInfo('whether the editing chrome for a document is hidden'); hideClickBehaviors?: BOOLt = new BoolInfo('whether to hide click behaviors in context menu'); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 785e69f84..f14fd033b 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -9,7 +9,7 @@ import * as React from 'react'; import { FaEdit } from 'react-icons/fa'; import { returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; -import { Doc } from '../../fields/Doc'; +import { Doc, DocListCast } from '../../fields/Doc'; import { Cast, DocCast } from '../../fields/Types'; import { DocUtils, IsFollowLinkScript } from '../documents/DocUtils'; import { CalendarManager } from '../util/CalendarManager'; @@ -270,12 +270,16 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
{ - const showing = DocumentView.Selected().some(dv => dv.dataDoc.showTags); + onClick={undoable(e => { + const showing = DocumentView.Selected().some(dv => dv.layoutDoc._layout_showTags); DocumentView.Selected().forEach(dv => { - dv.dataDoc.showTags = !showing; + dv.layoutDoc._layout_showTags = !showing; + if (e.shiftKey) + DocListCast(dv.Document[Doc.LayoutFieldKey(dv.Document) + '_annotations']).forEach(doc => { + if (doc.face) doc.hidden = showing; + }); }); - }}> + }, 'show Doc tags')}>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 118a5bd3d..907bb2614 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -232,7 +232,8 @@ export class DocumentDecorations extends ObservableReactComponent 1 ? 0 : `${doc[DocData].showTags ? 4 + seldocview.TagPanelHeight : 4}px`, + top: DocumentView.Selected().length > 1 ? 0 : `${doc._layout_showTags ? 4 + seldocview.TagPanelHeight : 4}px`, transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `, }}> DocumentView.Selected()} /> @@ -845,7 +846,7 @@ export class DocumentDecorations extends ObservableReactComponent {DocumentView.Selected().length > 1 ? : null} diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index ae9d42749..0b303f6f5 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -172,12 +172,12 @@ export class TagItem extends ObservableReactComponent { docData.data = new List(newEmbeddings); docData.title = this._props.tag; docData.tags = new List([this._props.tag]); - docData.showTags = true; docData.freeform_fitContentsToBox = true; doc._freeform_panX = doc._freeform_panY = 0; doc._width = 900; doc._height = 900; doc.layout_fitWidth = true; + doc._layout_showTags = true; return doc; })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true)); newEmbeddings.forEach(embed => Doc.SetContainer(embed, newCollection)); @@ -322,7 +322,7 @@ export class TagsView extends ObservableReactComponent { ); /** - * When 'showTags' is set on a Doc, this displays a wrapping panel of tagItemViews corresponding to all the tags set on the Doc). + * When 'layout_showTags' is set on a Doc, this displays a wrapping panel of tagItemViews corresponding to all the tags set on the Doc). * When the dropdown is clicked, this will toggle an extended UI that allows additional tags to be added/removed. */ render() { @@ -336,7 +336,7 @@ export class TagsView extends ObservableReactComponent { ); this._panelHeightDirty; - return !this.View.Document.showTags && this._props.Views.length === 1 ? null : ( + return !this.View.Document._layout_showTags && this._props.Views.length === 1 ? null : (
r && new ResizeObserver(action(() => this._props.Views.length === 1 && (this.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)} diff --git a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx index e419e522c..033d1590d 100644 --- a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx +++ b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx @@ -139,9 +139,9 @@ export class ImageLabelBox extends ViewBoxBaseComponent() { toggleDisplayInformation = () => { this._displayImageInformation = !this._displayImageInformation; if (this._displayImageInformation) { - this._selectedImages.forEach(doc => (doc[DocData].showTags = true)); + this._selectedImages.forEach(doc => (doc._layout_showTags = true)); } else { - this._selectedImages.forEach(doc => (doc[DocData].showTags = false)); + this._selectedImages.forEach(doc => (doc._layout_showTags = false)); } }; diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index e0d6c7c05..0ef67b4be 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -404,7 +404,7 @@ export class RichTextRules { if (!tags.includes(tag)) { tags.push(tag); this.Document[DocData].tags = new List(tags); - this.Document[DocData].showTags = true; + this.Document._layout_showTags = true; } const fieldView = state.schema.nodes.dashField.create({ fieldKey: '#' + tag }); return state.tr diff --git a/src/client/views/search/FaceRecognitionHandler.tsx b/src/client/views/search/FaceRecognitionHandler.tsx index 636a44984..c507e54b6 100644 --- a/src/client/views/search/FaceRecognitionHandler.tsx +++ b/src/client/views/search/FaceRecognitionHandler.tsx @@ -220,6 +220,7 @@ export class FaceRecognitionHandler { .then(imgDocFaceDescriptions => { // For each face detected, find a match. const annos = [] as Doc[]; const scale = NumCast(imgDoc.data_nativeWidth) / img.width; + const showTags= imgDocFaceDescriptions.length > 1; imgDocFaceDescriptions.forEach((fd, i) => { const faceDescriptor = new List(Array.from(fd.descriptor)); const matchedUniqueFace = this.findMatchingFaceDoc(fd.descriptor) ?? this.createUniqueFaceDoc(activeDashboard); @@ -233,12 +234,14 @@ export class FaceRecognitionHandler { y: fd.alignedRect.box.top * scale, _width: fd.alignedRect.box.width * scale, _height: fd.alignedRect.box.height * scale, + _layout_showTags: showTags }) FaceRecognitionHandler.UniqueFaceAddFaceImage(faceAnno, matchedUniqueFace); // add image/faceDescriptor to matched unique face annos.push(faceAnno); }); imgDoc[DocData].data_annotations = new List(annos); + imgDoc._layout_showTags = annos.length > 0; return imgDocFaceDescriptions; }) ); // prettier-ignore -- cgit v1.2.3-70-g09d2 From d95730d904612640184ca6fdc00864b0c81b0c0c Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 18 Sep 2024 20:46:38 -0400 Subject: lots of changes to fix dragging cards, integrate iconTags with other tags, sizing docs when selected to fit window, --- eslint.config.mjs | 1 + src/client/util/CurrentUserUtils.ts | 14 +- src/client/util/DragManager.ts | 3 +- src/client/views/DocumentButtonBar.tsx | 44 ++---- src/client/views/DocumentDecorations.tsx | 4 +- src/client/views/StyleProvider.tsx | 4 - src/client/views/TagsView.tsx | 20 +-- .../views/collections/CollectionCardDeckView.scss | 8 +- .../views/collections/CollectionCardDeckView.tsx | 152 ++++++++++++--------- .../views/collections/CollectionCarouselView.tsx | 1 - .../views/collections/CollectionStackingView.tsx | 3 - src/client/views/collections/CollectionSubView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 5 +- src/client/views/nodes/DocumentView.tsx | 5 + src/client/views/nodes/IconTagBox.scss | 24 ++-- src/client/views/nodes/IconTagBox.tsx | 106 +++++++------- .../views/nodes/formattedText/FormattedTextBox.tsx | 1 + src/client/views/search/FaceRecognitionHandler.tsx | 3 +- 18 files changed, 198 insertions(+), 204 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm') diff --git a/eslint.config.mjs b/eslint.config.mjs index 8926afd7c..f7063caa5 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -36,6 +36,7 @@ export default [ 'no-multi-assign': 'off', 'no-underscore-dangle': 'off', 'no-nested-ternary': 'off', + 'no-param-reassign': 'error', 'lines-between-class-members': 'off', 'no-shadow': 'off', '@typescript-eslint/no-shadow': 'warn', diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index eee6be937..262916312 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -2,7 +2,7 @@ import { reaction, runInAction } from "mobx"; import * as rp from 'request-promise'; import { ClientUtils, OmitKeys } from "../../ClientUtils"; -import { Doc, DocListCast, DocListCastAsync, FieldType, Opt } from "../../fields/Doc"; +import { Doc, DocListCast, DocListCastAsync, FieldType, Opt, StrListCast } from "../../fields/Doc"; import { DocData } from "../../fields/DocSymbols"; import { InkTool } from "../../fields/InkField"; import { List } from "../../fields/List"; @@ -688,18 +688,18 @@ pie title Minerals in my tap water static tagGroupTools(): Button[] { if (!Doc.UserDoc().activeDashboard) { - Doc.UserDoc().myFilterHotKeyTitles = new List(['Star', 'Heart', 'Bolt', 'Cloud']); + Doc.UserDoc().myFilterHotKeyTitles = new List(['star', 'heart', 'bolt', 'cloud']); - ['Star', 'Heart', 'Bolt', 'Cloud'].forEach(key => { + StrListCast(Doc.UserDoc().myFilterHotKeyTitles).forEach(key => { Doc.UserDoc()[key] = key.toLowerCase(); }); } // hack: if there's no dashboard, create default filters. otherwise, just make sure that the Options button is preserved return (Doc.UserDoc().activeDashboard ? [] : [ - { title: "Star", isSystem: false, icon: "star", toolTip:"Click to toggle the star group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, - { title: "Heart", isSystem: false,icon: "heart", toolTip:"Click to toggle the heart group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, - { title: "Bolt", isSystem: false,icon: "bolt", toolTip:"Click to toggle the bolt group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, - { title: "Cloud", isSystem: false,icon: "cloud", toolTip:"Click to toggle the cloud group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, + { title: "star", isSystem: false,icon: "star", toolTip:"Click to toggle the star group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"star", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, + { title: "heart", isSystem: false,icon: "heart", toolTip:"Click to toggle the heart group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"heart", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, + { title: "bolt", isSystem: false,icon: "bolt", toolTip:"Click to toggle the bolt group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"bolt", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, + { title: "cloud", isSystem: false,icon: "cloud", toolTip:"Click to toggle the cloud group's visibility", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"cloud", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}}, ]).concat([ { title: "Options", isSystem: true,icon: "gear", toolTip:"Click to customize your filter panel", btnType: ButtonType.ClickButton, expertMode: false, toolType:"opts", funcs: {}, scripts: { onClick: '{ return handleTags(this.toolType, _readOnly_);}'}} ]) diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index d55d193cc..81ea840f1 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -29,7 +29,7 @@ import { dropActionType } from './DropActionTypes'; import { SnappingManager } from './SnappingManager'; import { UndoManager } from './UndoManager'; -// eslint-disable-next-line @typescript-eslint/no-var-requires +// eslint-disable-next-line @typescript-eslint/no-require-imports const { contextMenuZindex } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore /** @@ -101,7 +101,6 @@ export namespace DragManager { // event called when the drag operation results in a drop action export class DropEvent { - // eslint-disable-next-line no-useless-constructor constructor( readonly x: number, readonly y: number, diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 87dd5f45a..32bf67df1 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -9,7 +9,7 @@ import * as React from 'react'; import { FaEdit } from 'react-icons/fa'; import { returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; -import { Doc } from '../../fields/Doc'; +import { Doc, DocListCast } from '../../fields/Doc'; import { Cast, DocCast } from '../../fields/Types'; import { DocUtils, IsFollowLinkScript } from '../documents/DocUtils'; import { CalendarManager } from '../util/CalendarManager'; @@ -28,7 +28,6 @@ import { DocumentLinksButton } from './nodes/DocumentLinksButton'; import { DocumentView } from './nodes/DocumentView'; import { OpenWhere } from './nodes/OpenWhere'; import { DashFieldView } from './nodes/formattedText/DashFieldView'; -import { DocData } from '../../fields/DocSymbols'; @observer export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (DocumentView | undefined)[]; stack?: unknown }> { @@ -287,44 +286,27 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( get keywordButton() { const targetDoc = this.view0?.Document; - const metaBtn = (name: string, icon: IconProp) => { - const tooltip = `Toggle ${name}`; - return ( - {tooltip}
}> -
- { - if (name === 'tags') { - targetDoc && (targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags); - } else { - targetDoc && (targetDoc[DocData].showLabels = !targetDoc[DocData].showLabels); - } - }} - /> -
- - ); - }; - return !targetDoc ? null : (
-
+ {/*
{metaBtn('tags', 'star')} {metaBtn('keywords', 'id-card')} -
+
*/} Open keyword menu
}>
{ - // targetDoc[DocData].showIconTags = !targetDoc[DocData].showIconTags; - }}> + onClick={undoable(e => { + const showing = DocumentView.Selected().some(dv => dv.showTags); + DocumentView.Selected().forEach(dv => { + dv.layoutDoc._layout_showTags = !showing; + if (e.shiftKey) + DocListCast(dv.Document[Doc.LayoutFieldKey(dv.Document) + '_annotations']).forEach(doc => { + if (doc.face) doc.hidden = showing; + }); + }); + }, 'show Doc tags')}>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 5e7908725..2ba96c2a9 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -837,7 +837,7 @@ export class DocumentDecorations extends ObservableReactComponent 1 ? 0 : `${seldocview.Document._layout_showTags ? 4 + seldocview.TagPanelHeight : 4}px`, + top: DocumentView.Selected().length > 1 ? 0 : `${seldocview.showTags ? 4 + seldocview.TagPanelHeight : 4}px`, transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `, }}> DocumentView.Selected()} /> @@ -846,7 +846,7 @@ export class DocumentDecorations extends ObservableReactComponent {DocumentView.Selected().length > 1 ? : null} diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index eb434db40..3545afcee 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -9,7 +9,6 @@ import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs'; import { FaFilter } from 'react-icons/fa'; import { ClientUtils, DashColor, lightOrDark } from '../../ClientUtils'; import { Doc, Opt, StrListCast } from '../../fields/Doc'; -import { DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { ScriptField } from '../../fields/ScriptField'; import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types'; @@ -22,7 +21,6 @@ import { TreeSort } from './collections/TreeSort'; import { Colors } from './global/globalEnums'; import { DocumentView, DocumentViewProps } from './nodes/DocumentView'; import { FieldViewProps } from './nodes/FieldView'; -import { IconTagBox } from './nodes/IconTagBox'; import { StyleProp } from './StyleProp'; import './StyleProvider.scss'; import { TagsView } from './TagsView'; @@ -366,7 +364,6 @@ export function DefaultStyleProvider(doc: Opt, props: Opt docView?.() ? : null; - const iconTags = () => doc?.[DocData].showIconTags ? : null; return ( <> @@ -375,7 +372,6 @@ export function DefaultStyleProvider(doc: Opt, props: Opt ); } diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index be2c28185..a678d5580 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -18,6 +18,7 @@ import { ObservableReactComponent } from './ObservableReactComponent'; import './TagsView.scss'; import { DocumentView } from './nodes/DocumentView'; import { FaceRecognitionHandler } from './search/FaceRecognitionHandler'; +import { IconTagBox } from './nodes/IconTagBox'; /** * The TagsView is a metadata input/display panel shown at the bottom of a DocumentView in a freeform collection. @@ -59,7 +60,7 @@ export class TagItem extends ObservableReactComponent { * @param tag tag string * @returns tag collection Doc or undefined */ - public static findTagCollectionDoc = (tag: String) => TagItem.AllTagCollectionDocs.find(doc => doc.title === tag); + public static findTagCollectionDoc = (tag: string) => TagItem.AllTagCollectionDocs.find(doc => doc.title === tag); /** * Creates a Doc that collects Docs with the specified tag / value @@ -148,7 +149,7 @@ export class TagItem extends ObservableReactComponent { private _ref: React.RefObject; - constructor(props: any) { + constructor(props: TagItemProps) { super(props); makeObservable(this); this._ref = React.createRef(); @@ -225,7 +226,7 @@ export class TagItem extends ObservableReactComponent { type="checkbox" onClick={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()} - onChange={undoable(e => (this.doc[metadata] = !this.doc[metadata]), 'metadata toggle')} + onChange={undoable(() => (this.doc[metadata] = !this.doc[metadata]), 'metadata toggle')} checked={this.doc[metadata] as boolean} /> ) : ( @@ -257,7 +258,7 @@ interface TagViewProps { */ @observer export class TagsView extends ObservableReactComponent { - constructor(props: any) { + constructor(props: TagViewProps) { super(props); makeObservable(this); } @@ -271,7 +272,7 @@ export class TagsView extends ObservableReactComponent { componentDidMount() { this._heightDisposer = reaction( () => this.View.screenToContentsTransform(), - xf => { + () => { this._panelHeightDirty = this._panelHeightDirty + 1; } ); @@ -288,7 +289,7 @@ export class TagsView extends ObservableReactComponent { return this._props.Views.length > 1 ? 1 : Math.max(1, 1 / this.View.screenToLocalScale()); } @computed get isEditing() { - return this._isEditing && (this._props.Views.length > 1 || DocumentView.SelectedDocs().includes(this.View.Document)); + return this._isEditing && (this._props.Views.length > 1 || (DocumentView.Selected().length === 1 && DocumentView.Selected().includes(this.View))); } /** @@ -336,7 +337,7 @@ export class TagsView extends ObservableReactComponent { ); this._panelHeightDirty; - return this.View.ComponentView?.isUnstyledView?.() || (!this.View.Document._layout_showTags && this._props.Views.length === 1) ? null : ( + return this.View.ComponentView?.isUnstyledView?.() || (!this.View.showTags && this._props.Views.length === 1) ? null : (
r && new ResizeObserver(action(() => this._props.Views.length === 1 && (this.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)} @@ -352,9 +353,10 @@ export class TagsView extends ObservableReactComponent { }}>
- {!tagsList.size && !facesList.size ? null : ( // + {this._props.Views.length === 1 && !this.View.showTags ? null : ( // this.setToEditing(!this._isEditing)} icon={} /> )} + {Array.from(tagsList).map((tag, i) => ( { />
- {TagItem.AllTagCollectionDocs.map((doc, i) => { + {TagItem.AllTagCollectionDocs.map(doc => { const tag = StrCast(doc.title); return ( - - ))} -
- ); - }; /** * Opens the filter panel in the properties menu @@ -93,13 +50,13 @@ export class IconTagBox extends ObservableReactComponent { }; /** - * Toggles the buttons between on and off when creating custom sort groupings/changing those created by gpt - * @param childPairIndex * @param buttonID * @param doc */ - toggleButton = undoable((doc: Doc, icon: string) => { - BoolCast(doc[icon]) ? (doc[icon] = false) : (doc[icon] = true); + setIconTag = undoable((icon: string, state: boolean) => { + this._props.Views.forEach(view => { + view.dataDoc[icon] = state; + }); }, 'toggle card tag'); /** @@ -112,10 +69,43 @@ export class IconTagBox extends ObservableReactComponent { const isActive = doc[icon.toString()]; const color = isActive ? '#4476f7' : '#323232'; - return ; + return ; }; + /** + * Renders the buttons to customize sorting depending on which group the card belongs to and the amount of total groups + */ render() { - return <>{this.renderButtons(this._props.doc)}; + const amButtons = StrListCast(Doc.UserDoc().myFilterHotKeyTitles).length + 1; + + const keys = StrListCast(Doc.UserDoc().myFilterHotKeyTitles); + + const iconMap = (buttonID: number) => { + return StrCast(Doc.UserDoc()[keys[buttonID]]) as IconProp; + }; + const buttons = numberRange(amButtons - 1) + .filter(i => this._props.IsEditing || this.View.Document[iconMap(i).toString()] || (DocumentView.Selected.length === 1 && this.View.IsSelected)) + .map(i => ( + Click to add/remove this card from the {iconMap(i).toString()} group
}> + + + )); + return !buttons.length ? null : ( +
+ {buttons} +
+ ); } } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 343e255dc..e0331a422 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -275,6 +275,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent 1; - imgDocFaceDescriptions.forEach((fd, i) => { + imgDocFaceDescriptions.forEach(fd => { const faceDescriptor = new List(Array.from(fd.descriptor)); const matchedUniqueFace = this.findMatchingFaceDoc(fd.descriptor) ?? this.createUniqueFaceDoc(activeDashboard); const faceAnno = Docs.Create.FreeformDocument([], { -- cgit v1.2.3-70-g09d2 From 65a1a272b15fb9c08ef75a12946d7f3751a500c7 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 19 Sep 2024 02:54:28 -0400 Subject: added stop propagation for clicking on tags and icon tags. fixed dragging crash from doc nested in card view to card view. still needs more work for card view to honor canEmbed --- src/client/views/TagsView.tsx | 25 +++++++++++++++-- .../views/collections/CollectionCardDeckView.tsx | 32 +++++++++++----------- .../views/collections/CollectionNoteTakingView.tsx | 1 - .../collectionFreeForm/CollectionFreeFormView.tsx | 1 - src/client/views/nodes/IconTagBox.tsx | 14 ++++++---- 5 files changed, 47 insertions(+), 26 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm') diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index a9224e437..cae30218c 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -207,7 +207,10 @@ export class TagItem extends ObservableReactComponent { return false; }, returnFalse, - emptyFunction + clickEv => { + clickEv.stopPropagation(); + this._props.setToEditing(); + } ); e.preventDefault(); }; @@ -220,7 +223,7 @@ export class TagItem extends ObservableReactComponent { 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 metadata = this._props.tag.startsWith('@') ? this._props.tag.replace(/^@/, '') : ''; return ( -
+
{metadata ? ( {'@' + metadata}  @@ -364,7 +367,23 @@ export class TagsView extends ObservableReactComponent {
{this._props.Views.length === 1 && !this.View.showTags ? null : ( // - this.setToEditing(!this._isEditing)} icon={} /> + + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + upEv => { + this.setToEditing(!this._isEditing); + upEv.stopPropagation(); + } + ) + } + icon={} + /> )} {Array.from(tagsList) diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 8fea3dbd6..cab7d51e4 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -6,6 +6,7 @@ import { emptyFunction } from '../../../Utils'; import { Doc, StrListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; +import { List } from '../../../fields/List'; import { BoolCast, DateCast, DocCast, NumCast, RTFCast, ScriptCast, StrCast } from '../../../fields/Types'; import { URLField } from '../../../fields/URLField'; import { gptImageLabel } from '../../apis/gpt/GPT'; @@ -21,7 +22,6 @@ import { DocumentView } from '../nodes/DocumentView'; import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup'; import './CollectionCardDeckView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; -import { List } from '../../../fields/List'; enum cardSortings { Time = 'time', @@ -51,7 +51,6 @@ export class CollectionCardView extends CollectionSubView() { @observable _docRefs = new ObservableMap(); @observable _maxRowCount = 10; @observable _docDraggedIndex: number = -1; - @observable _isACardBeingDragged: boolean = false; @observable overIndex: number = -1; static imageUrlToBase64 = async (imageUrl: string): Promise => { @@ -76,7 +75,6 @@ export class CollectionCardView extends CollectionSubView() { makeObservable(this); this.setRegenerateCallback(); } - protected createDashEventsTarget = (ele: HTMLDivElement | null) => { this._dropDisposer?.(); if (ele) { @@ -269,11 +267,10 @@ export class CollectionCardView extends CollectionSubView() { */ @action - onPointerMove = (e: React.PointerEvent) => { - if (DragManager.docsBeingDragged.length != 0) { - this._isACardBeingDragged = true; - - const newIndex = this.findCardDropIndex(e.clientX, e.clientY); + onPointerMove = (x: number, y: number) => { + this._docDraggedIndex = -1; + if (DragManager.docsBeingDragged.length) { + const newIndex = this.findCardDropIndex(x, y); if (newIndex !== this._docDraggedIndex && newIndex != -1) { this._docDraggedIndex = newIndex; @@ -297,18 +294,19 @@ export class CollectionCardView extends CollectionSubView() { onInternalDrop = undoable( action((e: Event, de: DragManager.DropEvent) => { if (de.complete.docDragData) { - this._isACardBeingDragged = false; const dragIndex = this._docDraggedIndex; - if (dragIndex > -1) { + const draggedDoc = DragManager.docsBeingDragged[0]; + if (dragIndex > -1 && draggedDoc) { this._docDraggedIndex = -1; - const draggedDoc = DragManager.docsBeingDragged[0]; const sorted = this.sortedDocs; const originalIndex = sorted.findIndex(doc => doc === draggedDoc); this.Document.cardSort = ''; - sorted.splice(originalIndex, 1); + originalIndex !== -1 && sorted.splice(originalIndex, 1); sorted.splice(dragIndex, 0, draggedDoc); - this.dataDoc[this.fieldKey] = new List(sorted); + if (de.complete.docDragData.removeDocument?.(draggedDoc)) { + this.dataDoc[this.fieldKey] = new List(sorted); + } } e.stopPropagation(); return true; @@ -397,7 +395,7 @@ export class CollectionCardView extends CollectionSubView() { const draggedDoc = DragManager.docsBeingDragged[0]; const originalIndex = docs.findIndex(doc => doc === draggedDoc); - docs.splice(originalIndex, 1); + originalIndex !== -1 && docs.splice(originalIndex, 1); docs.splice(dragIndex, 0, draggedDoc); } @@ -619,7 +617,8 @@ export class CollectionCardView extends CollectionSubView() { const realIndex = sortedDocs.indexOf(doc); const calcRowIndex = this.overflowIndexCalc(realIndex); const amCards = this.overflowAmCardsCalc(realIndex); - const isSelected = DocumentView.SelectedDocs().includes(doc); + const view = DocumentView.getDocumentView(doc, this.DocumentView?.()); + const isSelected = view?.ComponentView?.isAnyChildContentActive?.() || view?.IsSelected ? true : false; const childScreenToLocal = () => { this._forceChildXf; @@ -645,6 +644,7 @@ export class CollectionCardView extends CollectionSubView() { key={doc[Id]} className={`card-item${isSelected ? '-active' : anySelected ? '-inactive' : ''}`} onPointerUp={() => { + if (DocumentView.SelectedDocs().includes(doc)) return; // this turns off documentDecorations during a transition, then turns them back on afterward. SnappingManager.SetIsResizing(doc[Id]); setTimeout( @@ -675,9 +675,9 @@ export class CollectionCardView extends CollectionSubView() { return (
this.onPointerMove(e)} className="collectionCardView-outer" ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)} + onPointerMove={e => this.onPointerMove(e.clientX, e.clientY)} onDrop={this.onExternalDrop.bind(this)} style={{ background: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string, diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index e1f0a3e41..ac8e37358 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -653,7 +653,6 @@ export class CollectionNoteTakingView extends CollectionSubView() { const sections = Array.from(this.Sections.entries()); return sections.reduce((list, sec, i) => { list.push(this.sectionNoteTaking(sec[0], sec[1])); - // eslint-disable-next-line react/no-array-index-key i !== sections.length - 1 && list.push(); return list; }, [] as JSX.Element[]); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index cbbf063b4..f106eba26 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1783,7 +1783,6 @@ export class CollectionFreeFormView extends CollectionSubView { -- cgit v1.2.3-70-g09d2