From 19adcca1a89405a47da57fee64fe5fde4720c16a Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 5 May 2025 14:05:32 -0400 Subject: got rid of dash _xPadding/_yPadding in favor of just using _xMargin/_yMargin. cleaned up linearView field names --- src/client/documents/DocUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/documents/DocUtils.ts') diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index df14dce5a..9135899d7 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -603,7 +603,7 @@ export namespace DocUtils { layout_hideAllLinks: true, _width: 15, _height: 15, - _xPadding: 0, + _xMargin: 0, onClick: FollowLinkScript(), _timecodeToShow: Cast(doc._timecodeToShow, 'number', null), }); -- cgit v1.2.3-70-g09d2 From 299398cb7e21259de3bf7597995840b84e7e0590 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 9 May 2025 23:01:36 -0400 Subject: added user templates to template_user global field. fixed schemas to not erase field values when setting a new column name. fixed templates to render properly when switching by fixing contentsRef to be observable. fixed templates with fields in stacks to autoHeight update correctly by . fixed images in templates to set their nativeDim after rendering. fixed text views of descriptions to auto-updated by setting the modification date of the description field. --- src/client/documents/DocUtils.ts | 20 +++++++++---------- src/client/util/DropConverter.ts | 4 +++- .../CollectionStackingViewFieldColumn.tsx | 6 ++++-- src/client/views/collections/CollectionSubView.tsx | 13 ++++++++++-- .../collectionSchema/CollectionSchemaView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 6 +++--- src/client/views/nodes/ImageBox.tsx | 5 +++-- .../views/nodes/formattedText/FormattedTextBox.tsx | 3 ++- src/fields/Doc.ts | 23 +++++++++++++++++++--- src/server/SharedMediaTypes.ts | 3 +++ 10 files changed, 60 insertions(+), 25 deletions(-) (limited to 'src/client/documents/DocUtils.ts') diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index 9135899d7..36e03daed 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -642,8 +642,15 @@ export namespace DocUtils { return dd; } - export function assignImageInfo(result: Upload.FileInformation, protoIn: Doc) { + export function assignUploadInfo(result: Upload.FileInformation, protoIn: Doc) { const proto = protoIn; + + if (Upload.isTextInformation(result)) { + proto.text = result.rawText; + } + if (Upload.isVideoInformation(result)) { + proto.data_duration = result.duration; + } if (Upload.isImageInformation(result)) { const maxNativeDim = Math.max(result.nativeHeight, result.nativeWidth); const exifRotation = StrCast(result.exifData?.data?.Orientation).toLowerCase(); @@ -677,15 +684,8 @@ export namespace DocUtils { const pathname = result.accessPaths.agnostic.client; const doc = await DocUtils.DocumentFromType(type, pathname, full, overwriteDoc); if (doc) { - const proto = Doc.GetProto(doc); - proto.text = result.rawText; - !(result instanceof Error) && DocUtils.assignImageInfo(result, proto); - if (Upload.isVideoInformation(result)) { - proto.data_duration = result.duration; - } - if (overwriteDoc) { - Doc.removeCurrentlyLoading(overwriteDoc); - } + DocUtils.assignUploadInfo(result, Doc.GetProto(doc)); + overwriteDoc && Doc.removeCurrentlyLoading(overwriteDoc); generatedDocuments.push(doc); } return doc; diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index b6b111930..1d4779d8a 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -41,7 +41,7 @@ function makeTemplate(doc: Doc, first: boolean = true): boolean { if (first && !docs.length) { // bcz: feels hacky : if the root level document has items, it's not a field template isTemplate = Doc.MakeMetadataFieldTemplate(doc, layoutDoc[DocData], true) || isTemplate; - } else if (docData instanceof RichTextField || docData instanceof ImageField) { + } else if (docData instanceof RichTextField || docData instanceof ImageField || (docData === undefined && doc.type === DocumentType.IMG)) { if (!StrCast(layoutDoc.title).startsWith('-')) { isTemplate = Doc.MakeMetadataFieldTemplate(layoutDoc, layoutDoc[DocData], true); } @@ -86,6 +86,8 @@ export function makeUserTemplateButtonOrImage(doc: Doc, image?: string) { dbox.dragFactory = layoutDoc; dbox.dropPropertiesToRemove = doc.dropPropertiesToRemove instanceof ObjectField ? ObjectField.MakeCopy(doc.dropPropertiesToRemove) : undefined; dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory)'); + const userTemplatesDoc = DocCast(Doc.UserDoc().template_user); + userTemplatesDoc && Doc.AddDocToList(userTemplatesDoc, 'data', layoutDoc); return dbox; } diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 994669734..345f60e75 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -71,6 +71,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< } _ele: HTMLElement | null = null; + _eleMasonrySingle = React.createRef(); protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetDropAction: dropActionType) => { const dragData = de.complete.docDragData; @@ -92,13 +93,13 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< createColumnDropRef = (ele: HTMLDivElement | null) => { this.dropDisposer?.(); if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Doc, this.onInternalPreDrop.bind(this)); - else if (this._ele) this.props.refList.splice(this.props.refList.indexOf(this._ele), 1); + else if (this._eleMasonrySingle.current) this.props.refList.splice(this.props.refList.indexOf(this._eleMasonrySingle.current), 1); this._ele = ele; }; @action componentDidMount() { - this._ele && this.props.refList.push(this._ele); + this._eleMasonrySingle.current && this.props.refList.push(this._eleMasonrySingle.current); this._disposers.collapser = reaction( () => this._props.headingObject?.collapsed, collapsed => { this.collapsed = collapsed !== undefined ? BoolCast(collapsed) : false; }, // prettier-ignore @@ -363,6 +364,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< }}>
() { } const loading = Docs.Create.LoadingDocument(file, options); Doc.addCurrentlyLoading(loading); - DocUtils.uploadFileToDoc(file, {}, loading); + DocUtils.uploadFileToDoc(file, {}, loading).then(d => { + if (d && d?.type === DocumentType.IMG) { + const imgTemplate = DocListCast(DocCast(Doc.UserDoc().template_user)?.data).find(d => d.title === 'shower'); + if (imgTemplate) { + d.layout_fieldKey = 'layout_shower'; + d.layout_shower = imgTemplate; + } + } + }); + return loading; }) )) diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index c06391f35..6442385c0 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -359,7 +359,7 @@ export class CollectionSchemaView extends CollectionSubView() { @action addNewKey = (key: string, defaultVal: FieldType | undefined) => { this.childDocs.forEach(doc => { - doc[DocData][key] = defaultVal; + if (doc[DocData][key] === undefined) doc[DocData][key] = defaultVal; }); }; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index bdb97d7bb..f9c21451e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -740,7 +740,7 @@ export class DocumentViewInternal extends DocComponent (this.widgetDecorations ? this.widgetOverlay : null); viewingAiEditor = () => (this._props.showAIEditor && this._componentView?.componentAIView?.() !== undefined ? this.aiEditor : null); - _contentsRef = React.createRef(); + @observable _contentsRef: DocumentContentsView | undefined = undefined; @computed get viewBoxContents() { TraceMobx(); const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString; @@ -756,7 +756,7 @@ export class DocumentViewInternal extends DocComponent (this._contentsRef = r))} layoutFieldKey={StrCast(this.Document.layout_fieldKey, 'layout')} pointerEvents={this.contentPointerEvents} setContentViewBox={this.setContentView} @@ -1168,7 +1168,7 @@ export class DocumentView extends DocComponent() { * @returns boolean whether sub-component Doc is in synch with the layoutDoc that this view thinks its rendering */ IsInvalid = (renderDoc?: Doc): boolean => { - const docContents = this._docViewInternal?._contentsRef.current; + const docContents = this._docViewInternal?._contentsRef; return !( (!renderDoc || (docContents?.layoutDoc === renderDoc && // diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index b648f2adb..f7ad5c7e2 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -152,7 +152,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { this._disposers.path = reaction( () => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width), height: this.layoutDoc._height }), ({ nativeSize, width, height }) => { - if (!this.layoutDoc._layout_nativeDimEditable || !height) { + if (!this.layoutDoc._layout_nativeDimEditable || !height || this.layoutDoc.layout_resetNativeDim) { + this.layoutDoc.layout_resetNativeDim = undefined; // template images need to reset their dimensions when they are rendered with content. afterwards, remove this flag. this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth; } }, @@ -1048,7 +1049,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { alert('Error uploading files - possibly due to unsupported file types'); } else { this.dataDoc[this.fieldKey] = new ImageField(result.accessPaths.agnostic.client); - !(result instanceof Error) && DocUtils.assignImageInfo(result, this.dataDoc); + !(result instanceof Error) && DocUtils.assignUploadInfo(result, this.dataDoc); } disposer(); } else { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 824ac97da..57720baae 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1224,13 +1224,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { if (this.EditorView && this.ApplyingChange !== this.fieldKey) { if (incomingValue?.data) { - const updatedState = JSON.parse(incomingValue.data.Data); + const updatedState = JSON.parse(incomingValue.data.Data.replace(/\n/g, '')); if (JSON.stringify(this.EditorView.state.toJSON()) !== JSON.stringify(updatedState)) { this.EditorView.updateState(EditorState.fromJSON(this.config, updatedState)); this.tryUpdateScrollHeight(); } } else if (this.EditorView.state.doc.textContent !== (incomingValue?.str ?? '')) { selectAll(this.EditorView.state, tx => this.EditorView?.dispatch(tx.insertText(incomingValue?.str ?? ''))); + this.tryUpdateScrollHeight(); } } }, diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index ba94f0504..990b6606f 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -25,6 +25,7 @@ import { ComputedField, ScriptField } from './ScriptField'; import { BoolCast, Cast, DocCast, FieldValue, ImageCastWithSuffix, NumCast, RTFCast, StrCast, ToConstructor, toList } from './Types'; import { containedFieldChangedHandler, deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, setter, SharingPermissions } from './util'; import { gptImageLabel } from '../client/apis/gpt/GPT'; +import { DateField } from './DateField'; export let ObjGetRefField: (id: string, force?: boolean) => Promise; export let ObjGetRefFields: (ids: string[]) => Promise>; @@ -1109,6 +1110,10 @@ export namespace Doc { Cast(templateFieldValue, listSpec(Doc), [])?.map(d => d instanceof Doc && MakeMetadataFieldTemplate(d, templateDoc)); Doc.GetProto(templateField)[metadataFieldKey] = ObjectField.MakeCopy(templateFieldValue); } + if (templateField.type === DocumentType.IMG) { + // bcz: should be a better way .. but, if the image is a template, then we can't expect to know the aspect ratio. When the image is replaced by data and rendered, we want to recomputed the native dimensions. + templateField[DocData].layout_resetNativeDim = true; + } // get the layout string that the template uses to specify its layout const templateFieldLayoutString = StrCast(Doc.LayoutField(templateField[DocLayout])); @@ -1184,13 +1189,15 @@ export namespace Doc { return Doc.NativeWidth(doc, dataDoc, useDim) / (Doc.NativeHeight(doc, dataDoc, useDim) || 1); } export function NativeWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) { - return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_nativeWidth'], useWidth ? NumCast(doc._width) : 0)); + // if this is a field template, then don't use the doc's nativeWidth/height + return !doc ? 0 : NumCast(doc.isTemplateForField ? undefined : doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_nativeWidth'], !doc.isTemplateForField && useWidth ? NumCast(doc._width) : 0)); } export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) { if (!doc) return 0; const nheight = (Doc.NativeWidth(doc, dataDoc, useHeight) / NumCast(doc._width)) * NumCast(doc._height); // divide before multiply to avoid floating point errrorin case nativewidth = width const dheight = NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_nativeHeight'], useHeight ? NumCast(doc._height) : 0); - return NumCast(doc._nativeHeight, nheight || dheight); + // if this is a field template, then don't use the doc's nativeWidth/height + return NumCast(doc.isTemplateForField ? undefined : doc._nativeHeight, nheight || dheight); } export function OutpaintingWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) { @@ -1504,7 +1511,13 @@ export namespace Doc { case DocumentType.RTF: return RTFCast(tdoc[Doc.LayoutDataKey(tdoc)])?.Text ?? StrCast(tdoc[Doc.LayoutDataKey(tdoc)]); default: return StrCast(tdoc.title).startsWith("Untitled") ? "" : StrCast(tdoc.title); }}); // prettier-ignore - return docText(doc).then(text => (doc['$' + Doc.LayoutDataKey(doc) + '_description'] = text)); + return docText(doc).then( + action(text => { + // set the time when the date changes. This also allows a live textbox view to react to the update, otherwise, it wouldn't take effect until the next time the view is rerendered. + doc['$' + Doc.LayoutDataKey(doc) + '_description_modificationDate'] = new DateField(); + return (doc['$' + Doc.LayoutDataKey(doc) + '_description'] = text); + }) + ); } // prettier-ignore @@ -1820,3 +1833,7 @@ ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, ran ScriptingGlobals.add(function toJavascriptString(str: string) { return Field.toJavascriptString(str as FieldType); }); +// eslint-disable-next-line prefer-arrow-callback +ScriptingGlobals.add(function getDescription(doc: Doc) { + return Doc.getDescription(doc); +}); diff --git a/src/server/SharedMediaTypes.ts b/src/server/SharedMediaTypes.ts index 9aa4b120f..43a9ce963 100644 --- a/src/server/SharedMediaTypes.ts +++ b/src/server/SharedMediaTypes.ts @@ -19,6 +19,9 @@ export enum AudioAnnoState { } export namespace Upload { + export function isTextInformation(uploadResponse: Upload.FileInformation): uploadResponse is Upload.ImageInformation { + return 'rawText' in uploadResponse; + } export function isImageInformation(uploadResponse: Upload.FileInformation): uploadResponse is Upload.ImageInformation { return 'nativeWidth' in uploadResponse; } -- cgit v1.2.3-70-g09d2 From 87484331f805f583c98226c5a8c6b1ec5fc88925 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 13 May 2025 13:37:54 -0400 Subject: fixed setting up link anchors for video to be added to Doc. fixed imageTemplate to work with templates in tabs. fixed link following from link menu. cleaned up getView with sidebars to open them when needed properly.. --- src/client/documents/DocUtils.ts | 2 +- src/client/util/DocumentManager.ts | 2 +- src/client/views/MarqueeAnnotator.tsx | 3 +- src/client/views/SidebarAnnos.tsx | 29 +++++++++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 7 +-- src/client/views/linking/LinkMenuItem.tsx | 18 +++---- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/MapBox/MapBox.tsx | 59 +++++++--------------- .../views/nodes/MapboxMapBox/MapboxContainer.tsx | 51 ++++++++++--------- src/client/views/nodes/PDFBox.tsx | 17 +++---- src/client/views/nodes/VideoBox.tsx | 17 ++++--- src/client/views/nodes/WebBox.tsx | 26 +++++----- .../views/nodes/formattedText/FormattedTextBox.tsx | 17 +++---- 13 files changed, 124 insertions(+), 126 deletions(-) (limited to 'src/client/documents/DocUtils.ts') diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index 36e03daed..41a61b154 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -724,7 +724,7 @@ export namespace DocUtils { _width: width || BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 * 6 : 200, _height: BoolCast(Doc.UserDoc().fitBox) ? Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')) * 1.5 : 35, _layout_autoHeight: true, - backgroundColor: StrCast(Doc.UserDoc().textBackgroundColor), + backgroundColor: backgroundColor ?? StrCast(Doc.UserDoc().textBackgroundColor), borderColor: Doc.UserDoc().borderColor as string, borderWidth: Doc.UserDoc().borderWidth as number, text_centered: BoolCast(Doc.UserDoc().textCentered), diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index fc8f22cf3..422613968 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -250,7 +250,7 @@ export class DocumentManager { const options = optionsIn; Doc.MyRecentlyClosed && Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, targetDoc); const docContextPath = DocumentManager.GetContextPath(targetDoc, true); - if (docContextPath.some(doc => doc.hidden)) options.toggleTarget = false; + if (docContextPath.some(doc => doc !== targetDoc && doc.hidden)) options.toggleTarget = false; let activatedTab = false; if (DocumentView.activateTabView(docContextPath[0])) { options.toggleTarget = false; diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index b2e42652d..d354dd2c0 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -194,14 +194,13 @@ export class MarqueeAnnotator extends ObservableReactComponent { e.preventDefault(); e.stopPropagation(); - const sourceAnchorCreator = () => this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true); // hyperlink color - const targetCreator = (annotationOn: Doc | undefined) => { const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.Document.title, 0, 0, 100, 100, annotationOn, 'yellow'); target.layout_fitWidth = true; DocumentView.SetSelectOnLoad(target); return target; }; + const sourceAnchorCreator = () => this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true); // hyperlink color DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView(), sourceAnchorCreator, targetCreator), e.pageX, e.pageY, { dragComplete: dragEv => { if (!dragEv.aborted && dragEv.annoDragData && dragEv.annoDragData.linkSourceDoc && dragEv.annoDragData.dropDocument && dragEv.linkDocument) { diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 573c28ccf..71b479a22 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -3,7 +3,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { ClientUtils, returnFalse, returnOne, returnZero } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; -import { Doc, DocListCast, Field, FieldResult, FieldType, StrListCast } from '../../fields/Doc'; +import { Doc, DocListCast, Field, FieldResult, FieldType, Opt, StrListCast } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; import { RichTextField } from '../../fields/RichTextField'; @@ -19,6 +19,7 @@ import { StyleProp } from './StyleProp'; import { CollectionStackingView } from './collections/CollectionStackingView'; import { DocumentView } from './nodes/DocumentView'; import { FieldViewProps } from './nodes/FieldView'; +import { FocusViewOptions } from './nodes/FocusViewOptions'; interface ExtraProps { fieldKey: string; @@ -149,15 +150,33 @@ export class SidebarAnnos extends ObservableReactComponent { if (DocListCast(this._props.Doc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) { - if (this.childFilters()) { + if (this.childFilters().length) { // if any child filters exist, get rid of them this._props.layoutDoc._childFilters = new List(); + return true; } - return true; } return false; }; + public static getView(sidebar: SidebarAnnos | null, sidebarShown: boolean, toggleSidebar: () => void, doc: Doc, options: FocusViewOptions) { + if (!sidebarShown) { + options.didMove = true; + toggleSidebar(); + } + options.didMove = sidebar?.makeDocUnfiltered(doc) || options.didMove; + + if (!doc.hidden) { + if (!options.didMove && options.toggleTarget) { + options.toggleTarget = false; + options.didMove = doc.hidden = true; + } + } else { + options.didMove = !(doc.hidden = false); + } + return new Promise>(res => DocumentView.addViewRenderedCb(doc, res)); + } + get sidebarKey() { return this._props.fieldKey + '_sidebar'; } @@ -181,8 +200,8 @@ export class SidebarAnnos extends ObservableReactComponent 'title'; setHeightCallback = (height: number) => this._props.setHeight?.(height + this.filtersHeight()); sortByLinkAnchorY = (a: Doc, b: Doc) => { - const ay = Doc.Links(a).length && DocCast(Doc.Links(a)[0].link_anchor_1).y; - const by = Doc.Links(b).length && DocCast(Doc.Links(b)[0].link_anchor_1).y; + const ay = Doc.Links(a).length && DocCast(Doc.Links(a)[0].link_anchor_1)?.y; + const by = Doc.Links(b).length && DocCast(Doc.Links(b)[0].link_anchor_1)?.y; return NumCast(ay) - NumCast(by); }; render() { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index bb3c59eae..77615c650 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1738,10 +1738,11 @@ export class CollectionFreeFormView extends CollectionSubView([anchor]); + this.dataDoc[fieldKey] = new List([anchor]); } } return anchor; diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index c984a7053..6c1ad568d 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -105,11 +105,7 @@ export class LinkMenuItem extends ObservableReactComponent { LinkManager.Instance.currentLinkAnchor = LinkManager.Instance.currentLink ? this.sourceAnchor : undefined; if ((SnappingManager.PropertiesWidth ?? 0) < 100) { - setTimeout( - action(() => { - SnappingManager.SetPropertiesWidth(250); - }) - ); + setTimeout(action(() => SnappingManager.SetPropertiesWidth(250))); } } }) @@ -136,14 +132,14 @@ export class LinkMenuItem extends ObservableReactComponent { this._props.itemHandler?.(this._props.linkDoc); } else { const focusDoc = - Cast(this._props.linkDoc.link_anchor_1, Doc, null)?.annotationOn === this._props.sourceDoc - ? Cast(this._props.linkDoc.link_anchor_1, Doc, null) - : Cast(this._props.linkDoc.link_anchor_2, Doc, null)?.annotationOn === this._props.sourceDoc - ? Cast(this._props.linkDoc.link_anchor_12, Doc, null) - : undefined; + DocCast(this._props.linkDoc.link_anchor_1)?.annotationOn === this._props.sourceDoc + ? DocCast(this._props.linkDoc.link_anchor_1) + : DocCast(this._props.linkDoc.link_anchor_2)?.annotationOn === this._props.sourceDoc + ? DocCast(this._props.linkDoc.link_anchor_2) + : undefined; // prettier-ignore if (focusDoc) this._props.docView._props.focus(focusDoc, { instant: true }); - DocumentView.FollowLink(this._props.linkDoc, this._props.sourceDoc, false); + DocumentView.FollowLink(this._props.linkDoc, focusDoc ?? this._props.sourceDoc, false); } } ); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 05706fe6b..b0415ef18 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1412,7 +1412,7 @@ export class DocumentView extends DocComponent() { } public static setDefaultImageTemplate(checkResult?: boolean) { if (checkResult) return Doc.UserDoc().defaultImageLayout; - const view = DocumentView.Selected()[0]?._props.renderDepth > 0 ? DocumentView.Selected()[0] : undefined; + const view = DocumentView.Selected()[0]?._props.renderDepth > 0 || DocumentView.Selected()[0]?.Document.isTemplateDoc ? DocumentView.Selected()[0] : undefined; undoable(() => { const tempDoc = DocumentView.getTemplate(view); Doc.UserDoc().defaultImageLayout = tempDoc ? new PrefetchProxy(tempDoc) : undefined; diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index a563b7c1b..a279ccc48 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -13,7 +13,7 @@ import { CirclePicker, ColorResult } from 'react-color'; import { Layer, MapProvider, MapRef, Map as MapboxMap, Marker, Source, ViewState, ViewStateChangeEvent } from 'react-map-gl/mapbox'; import { ClientUtils, setupMoveUpEvents } from '../../../../ClientUtils'; import { emptyFunction } from '../../../../Utils'; -import { Doc, DocListCast, Field, LinkedTo, Opt, StrListCast } from '../../../../fields/Doc'; +import { Doc, DocListCast, Field, LinkedTo, StrListCast } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; import { RichTextField } from '../../../../fields/RichTextField'; import { DocCast, NumCast, StrCast, toList } from '../../../../fields/Types'; @@ -111,12 +111,12 @@ export class MapBox extends ViewBoxAnnotatableComponent() { // this list contains pushpins and configs @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); } // prettier-ignore - @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); } // prettier-ignore + @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.sidebarKey]); } // prettier-ignore @computed get allPushpins() { return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN); } // prettier-ignore @computed get allRoutes() { return this.allAnnotations.filter(anno => anno.type === DocumentType.MAPROUTE); } // prettier-ignore @computed get SidebarShown() { return !!this.layoutDoc._layout_showSidebar; } // prettier-ignore @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); } // prettier-ignore - @computed get SidebarKey() { return this.fieldKey + '_sidebar'; } // prettier-ignore + @computed get sidebarKey() { return this.fieldKey + '_sidebar'; } // prettier-ignore @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4')); } @@ -260,7 +260,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() { removeMapDocument = (doc: Doc | Doc[], annotationKey?: string) => { this.allAnnotations - .filter(anno => toList(doc).includes(DocCast(anno.mapPin))) + .filter(anno => toList(doc).includes(DocCast(anno.mapPin)!)) .forEach(anno => { anno.mapPin = undefined; }); @@ -339,27 +339,19 @@ export class MapBox extends ViewBoxAnnotatableComponent() { startAnchorDrag = (e: PointerEvent, ele: HTMLElement) => { e.preventDefault(); e.stopPropagation(); - - const sourceAnchorCreator = action(() => { - const note = this.getAnchor(true); - if (note && this._selectedPinOrRoute) { - note.latitude = this._selectedPinOrRoute.latitude; - note.longitude = this._selectedPinOrRoute.longitude; - note.map = this._selectedPinOrRoute.map; - } - return note as Doc; - }); - const targetCreator = (annotationOn: Doc | undefined) => { const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow'); + target.layout_fitWidth = true; DocumentView.SetSelectOnLoad(target); return target; }; + + const sourceAnchorCreator = () => this.getAnchor(true); const docView = this.DocumentView?.(); docView && DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, { dragComplete: dragEv => { - if (!dragEv.aborted && dragEv.annoDragData && dragEv.annoDragData.linkSourceDoc && dragEv.annoDragData.dropDocument && dragEv.linkDocument) { + if (!dragEv.aborted && dragEv.annoDragData?.linkSourceDoc && dragEv.annoDragData.dropDocument && dragEv.linkDocument) { dragEv.annoDragData.linkSourceDoc.followLinkToggle = dragEv.annoDragData.dropDocument.annotationOn === this.Document; dragEv.annoDragData.linkSourceDoc.followLinkZoom = false; } @@ -368,17 +360,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() { }; createNoteAnnotation = () => { - const createFunc = undoable( - action(() => { - const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(true), ['latitude', 'longitude', LinkedTo]); - if (note && this._selectedPinOrRoute) { - note.latitude = this._selectedPinOrRoute.latitude; - note.longitude = this._selectedPinOrRoute.longitude; - note.map = this._selectedPinOrRoute.map; - } - }), - 'create note annotation' - ); + const createFunc = undoable(() => this._sidebarRef.current?.anchorMenuClick(this.getAnchor(true), ['latitude', 'longitude', LinkedTo]), 'create note annotation'); if (!this.layoutDoc.layout_showSidebar) { this.toggleSidebar(); setTimeout(createFunc); @@ -428,14 +410,11 @@ export class MapBox extends ViewBoxAnnotatableComponent() { } }; - getView = (doc: Doc, options: FocusViewOptions) => { - if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) { - this.toggleSidebar(); - options.didMove = true; + getView = async (doc: Doc, options: FocusViewOptions) => { + if (DocListCast(this.dataDoc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) { + SidebarAnnos.getView(this._sidebarRef.current, this.SidebarShown, this.toggleSidebar, doc, options); } - return new Promise>(res => { - DocumentView.addViewRenderedCb(doc, dv => res(dv)); - }); + return undefined; }; /* @@ -476,13 +455,14 @@ export class MapBox extends ViewBoxAnnotatableComponent() { @action deleteSelectedPinOrRoute = undoable(() => { - if (this._selectedPinOrRoute) { + const selPin = DocCast(this._selectedPinOrRoute); + if (selPin) { // Removes filter - Doc.setDocFilter(this.Document, 'latitude', NumCast(this._selectedPinOrRoute.latitude), 'remove'); - Doc.setDocFilter(this.Document, 'longitude', NumCast(this._selectedPinOrRoute.longitude), 'remove'); - Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this._selectedPinOrRoute))}`, 'remove'); + Doc.setDocFilter(this.Document, 'latitude', NumCast(selPin.latitude), 'remove'); + Doc.setDocFilter(this.Document, 'longitude', NumCast(selPin.longitude), 'remove'); + Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(selPin)}`, 'remove'); - this.removePushpinOrRoute(this._selectedPinOrRoute); + this.removePushpinOrRoute(selPin); } MapAnchorMenu.Instance.fadeOut(true); document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true); @@ -1299,7 +1279,6 @@ export class MapBox extends ViewBoxAnnotatableComponent() { removeMapDocument = (docsIn: Doc | Doc[], annotationKey?: string) => { const docs = toList(docsIn); this.allAnnotations - .filter(anno => docs.includes(DocCast(anno.mapPin))) + .filter(anno => docs.includes(DocCast(anno.mapPin)!)) .forEach(anno => { anno.mapPin = undefined; }); @@ -224,6 +224,12 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent startAnchorDrag = (e: PointerEvent, ele: HTMLElement) => { e.preventDefault(); e.stopPropagation(); + const targetCreator = (annotationOn: Doc | undefined) => { + const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow'); + target.layout_fitWidth = true; + DocumentView.SetSelectOnLoad(target); + return target; + }; const sourceAnchorCreator = action(() => { const note = this.getAnchor(true); @@ -235,11 +241,6 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent return note as Doc; }); - const targetCreator = (annotationOn: Doc | undefined) => { - const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow'); - DocumentView.SetSelectOnLoad(target); - return target; - }; const docView = this.DocumentView?.(); docView && DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, { @@ -362,22 +363,22 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent @action deselectPin = () => { - if (this.selectedPin) { + const selPin = DocCast(this.selectedPin); + if (selPin) { // Removes filter - Doc.setDocFilter(this.Document, 'latitude', NumCast(this.selectedPin.latitude), 'remove'); - Doc.setDocFilter(this.Document, 'longitude', NumCast(this.selectedPin.longitude), 'remove'); - Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove'); + Doc.setDocFilter(this.Document, 'latitude', NumCast(selPin.latitude), 'remove'); + Doc.setDocFilter(this.Document, 'longitude', NumCast(selPin.longitude), 'remove'); + Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(selPin)}`, 'remove'); - const temp = this.selectedPin; if (!this._unmounting) { - this._bingMap.current.entities.remove(this.map_docToPinMap.get(temp)); + this._bingMap.current.entities.remove(this.map_docToPinMap.get(selPin)); } - const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(temp.latitude, temp.longitude)); - this.MicrosoftMaps.Events.addHandler(newpin, 'click', () => this.pushpinClicked(temp as Doc)); + const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(selPin.latitude, selPin.longitude)); + this.MicrosoftMaps.Events.addHandler(newpin, 'click', () => this.pushpinClicked(selPin)); if (!this._unmounting) { this._bingMap.current.entities.push(newpin); } - this.map_docToPinMap.set(temp, newpin); + this.map_docToPinMap.set(selPin, newpin); this.selectedPin = undefined; this.bingSearchBarContents = this.Document.map; } @@ -388,9 +389,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent this.toggleSidebar(); options.didMove = true; } - return new Promise>(res => { - DocumentView.addViewRenderedCb(doc, dv => res(dv)); - }); + return new Promise>(res => DocumentView.addViewRenderedCb(doc, res)); }; /* * Pushpin onclick @@ -535,13 +534,14 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent @action deleteSelectedPin = undoable(() => { - if (this.selectedPin) { + const selPin = this.selectedPin; + if (selPin) { // Removes filter - Doc.setDocFilter(this.Document, 'latitude', NumCast(this.selectedPin.latitude), 'remove'); - Doc.setDocFilter(this.Document, 'longitude', NumCast(this.selectedPin.longitude), 'remove'); - Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove'); + Doc.setDocFilter(this.Document, 'latitude', NumCast(selPin.latitude), 'remove'); + Doc.setDocFilter(this.Document, 'longitude', NumCast(selPin.longitude), 'remove'); + Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(selPin)}`, 'remove'); - this.removePushpin(this.selectedPin); + this.removePushpin(selPin); } MapAnchorMenu.Instance.fadeOut(true); document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true); @@ -638,7 +638,10 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent this._disposers.highlight = reaction( () => this.allAnnotations.map(doc => doc[Highlight]), () => { - const allConfigPins = this.allAnnotations.map(doc => ({ doc, pushpin: DocCast(doc.mapPin) })).filter(pair => pair.pushpin); + const allConfigPins = this.allAnnotations + .map(doc => ({ doc, pushpin: DocCast(doc.mapPin) })) + .filter(pair => pair.pushpin) + .map(pair => ({ doc: pair.doc, pushpin: pair.pushpin! })); allConfigPins.forEach(({ pushpin }) => { if (!pushpin[Highlight] && this.map_pinHighlighted.get(pushpin)) { this.recolorPin(pushpin); diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 55e6d5596..e4050a3c3 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -10,7 +10,7 @@ import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { ComputedField } from '../../../fields/ScriptField'; -import { Cast, FieldValue, NumCast, StrCast, toList } from '../../../fields/Types'; +import { Cast, DocCast, FieldValue, NumCast, StrCast, toList } from '../../../fields/Types'; import { ImageField, PdfField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction } from '../../../Utils'; @@ -27,7 +27,6 @@ import { Colors } from '../global/globalEnums'; import { PDFViewer } from '../pdf/PDFViewer'; import { PinDocView, PinProps } from '../PinFuncs'; import { SidebarAnnos } from '../SidebarAnnos'; -import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { FocusViewOptions } from './FocusViewOptions'; import { ImageBox } from './ImageBox'; @@ -56,6 +55,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent() { @observable private _pdf: Opt = undefined; @observable private _pageControls = false; + @computed get sidebarKey() { + return this.fieldKey + '_sidebar'; + } @computed get pdfUrl() { return Cast(this.dataDoc[this._props.fieldKey], PdfField); } @@ -232,14 +234,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent() { return this._pdfViewer?.scrollFocus(anchor, NumCast(anchor.y, NumCast(anchor.config_scrollTop)), options); }; - getView = (doc: Doc, options: FocusViewOptions) => { - if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) { - options.didMove = true; - this.toggleSidebar(false); + getView = async (doc: Doc, options: FocusViewOptions) => { + if (DocListCast(this.dataDoc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) { + SidebarAnnos.getView(this._sidebarRef.current, this.SidebarShown, () => this.toggleSidebar(false), doc, options); } - return new Promise>(res => { - DocumentView.addViewRenderedCb(doc, dv => res(dv)); - }); + return undefined; }; getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index fa099178c..b3cb0e1db 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -338,12 +338,17 @@ export class VideoBox extends ViewBoxAnnotatableComponent() { getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { const timecode = Cast(this.layoutDoc._layout_currentTimecode, 'number', null); const marquee = AnchorMenu.Instance.GetAnchor?.(undefined, addAsAnnotation); + const docAnchor = () => + Docs.Create.ConfigDocument({ + title: '#' + timecode, + _timecodeToShow: timecode, + annotationOn: this.Document, + }); if (!addAsAnnotation && marquee) marquee.backgroundColor = 'transparent'; - const anchor = - addAsAnnotation && marquee - ? CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.annotationKey, timecode || undefined, undefined, marquee, addAsAnnotation) || this.Document - : Docs.Create.ConfigDocument({ title: '#' + timecode, _timecodeToShow: timecode, annotationOn: this.Document }); + const visibleAnchor = () => addAsAnnotation && marquee && (CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.annotationKey, timecode || undefined, undefined, marquee, addAsAnnotation) || this.Document); + const anchor = visibleAnchor() || docAnchor(); PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), temporal: true, pannable: true } }, this.Document); + addAsAnnotation && this.addDocument(anchor); return anchor; }; @@ -376,9 +381,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent() { } return this._stackedTimeline.getView(doc, options); } - return new Promise>(res => { - DocumentView.addViewRenderedCb(doc, dv => res(dv)); - }); + return new Promise>(res => DocumentView.addViewRenderedCb(doc, res)); }; // extracts video thumbnails and saves them as field of doc diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 838dbea9d..779ccab40 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -13,7 +13,7 @@ import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; import { RefField } from '../../../fields/RefField'; import { listSpec } from '../../../fields/Schema'; -import { Cast, NumCast, StrCast, toList, WebCast } from '../../../fields/Types'; +import { Cast, DocCast, NumCast, StrCast, toList, WebCast } from '../../../fields/Types'; import { ImageField, WebField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, stringHash } from '../../../Utils'; @@ -104,6 +104,9 @@ export class WebBox extends ViewBoxAnnotatableComponent() { @computed get webField() { return Cast(this.Document[this._props.fieldKey], WebField)?.url; } + @computed get sidebarKey() { + return this.fieldKey + '_sidebar'; + } constructor(props: FieldViewProps) { super(props); @@ -308,18 +311,17 @@ export class WebBox extends ViewBoxAnnotatableComponent() { }; @action - getView = (doc: Doc /* , options: FocusViewOptions */) => { - if (Doc.AreProtosEqual(doc, this.Document)) - return new Promise>(res => { - res(this.DocumentView?.()); - }); + getView = async (doc: Doc, options: FocusViewOptions) => { + if (Doc.AreProtosEqual(doc, this.Document)) return new Promise>(res => res(this.DocumentView?.())); + if (this.Document.layout_fieldKey === 'layout_icon') this.DocumentView?.().iconify(); const webUrl = WebCast(doc.config_data)?.url; if (this._url && webUrl && webUrl.href !== this._url) this.setData(webUrl.href); - if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(false); - return new Promise>(res => { - DocumentView.addViewRenderedCb(doc, dv => res(dv)); - }); + + if (DocListCast(this.dataDoc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) { + return SidebarAnnos.getView(this._sidebarRef.current, this.SidebarShown, () => this.toggleSidebar(false), doc, options); + } + return undefined; }; sidebarAddDocTab = (doc: Doc, where: OpenWhere) => { @@ -454,7 +456,7 @@ export class WebBox extends ViewBoxAnnotatableComponent() { iframeDown = (e: PointerEvent) => { this._textAnnotationCreator = undefined; const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection(); - if (sel?.empty && !(e.target as any).textContent) + if (sel?.empty && !(e.target as HTMLElement).textContent) sel.empty(); // Chrome else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox @@ -565,7 +567,7 @@ export class WebBox extends ViewBoxAnnotatableComponent() { 'click', undoable( action((e: MouseEvent) => { - let eleHref = (e.target as any)?.outerHTML?.split('"="')[1]?.split('"')[0]; + let eleHref = (e.target as HTMLElement)?.outerHTML?.split('"="')[1]?.split('"')[0]; for (let ele = e.target as HTMLElement | Element | null; ele; ele = ele.parentElement) { if ('href' in ele) { eleHref = (typeof ele.href === 'string' ? ele.href : eleHref) || (ele.parentElement && 'href' in ele.parentElement ? (ele.parentElement.href as string) : eleHref); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 57720baae..bab97f681 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -270,13 +270,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn); + const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow'); + target.layout_fitWidth = true; DocumentView.SetSelectOnLoad(target); return target; }; + const sourceAnchorCreator = () => this.getAnchor(true); + const docView = this.DocumentView?.(); - docView && DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, () => this.getAnchor(true), targetCreator), e.pageX, e.pageY); + docView && DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY); }); AnchorMenu.Instance.AddDrawingAnnotation = (drawing: Doc) => { @@ -1070,15 +1073,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { if (DocListCast(this.dataDoc[this.sidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) { - if (!this.SidebarShown) { - this.toggleSidebar(false); - options.didMove = true; - } - setTimeout(() => this._sidebarRef?.current?.makeDocUnfiltered(doc)); + return SidebarAnnos.getView(this._sidebarRef.current, this.SidebarShown, () => this.toggleSidebar(false), doc, options); } - return new Promise>(res => { - DocumentView.addViewRenderedCb(doc, dv => res(dv)); - }); + return new Promise>(res => DocumentView.addViewRenderedCb(doc, res)); }; focus = (textAnchor: Doc, options: FocusViewOptions) => { const focusSpeed = options.zoomTime ?? 500; -- cgit v1.2.3-70-g09d2 From 76a21badf70c82388872ec5485858ab06e303bca Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 30 May 2025 09:57:46 -0400 Subject: fixed using empty images in templates to support assigning a new image and getting native dimensions right. fixed native dimensions of images within templates to not use native dims of template. fixed undo for text templates that don't have a text box. fixed image dropping on images that are empty (to assign the dropped image). --- src/client/documents/DocUtils.ts | 20 +++--- .../collections/collectionFreeForm/MarqueeView.tsx | 1 + src/client/views/nodes/EquationBox.tsx | 9 +-- src/client/views/nodes/ImageBox.tsx | 84 +++++++++++++--------- src/client/views/nodes/LabelBox.tsx | 8 ++- .../views/nodes/formattedText/FormattedTextBox.tsx | 11 +-- src/fields/Doc.ts | 4 +- 7 files changed, 80 insertions(+), 57 deletions(-) (limited to 'src/client/documents/DocUtils.ts') diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index 41a61b154..5aae50ee8 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -642,27 +642,27 @@ export namespace DocUtils { return dd; } - export function assignUploadInfo(result: Upload.FileInformation, protoIn: Doc) { + export function assignUploadInfo(result: Upload.FileInformation, protoIn: Doc, fieldKey: string = 'data') { const proto = protoIn; if (Upload.isTextInformation(result)) { proto.text = result.rawText; } if (Upload.isVideoInformation(result)) { - proto.data_duration = result.duration; + proto[`${fieldKey}_duration`] = result.duration; } if (Upload.isImageInformation(result)) { const maxNativeDim = Math.max(result.nativeHeight, result.nativeWidth); const exifRotation = StrCast(result.exifData?.data?.Orientation).toLowerCase(); - proto.data_nativeOrientation = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined); - proto.data_nativeWidth = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim; - proto.data_nativeHeight = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight); - if (NumCast(proto.data_nativeOrientation) >= 5) { - proto.data_nativeHeight = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim; - proto.data_nativeWidth = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight); + proto[`${fieldKey}_nativeOrientation`] = result.exifData?.data?.image?.Orientation ?? (exifRotation.includes('rotate 90') || exifRotation.includes('rotate 270') ? 5 : undefined); + proto[`${fieldKey}_nativeWidth`] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim; + proto[`${fieldKey}_nativeHeight`] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight); + if (NumCast(proto[`${fieldKey}_nativeOrientation`]) >= 5) { + proto[`${fieldKey}_nativeHeight`] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim; + proto[`${fieldKey}_nativeWidth`] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight); } - proto.data_exif = JSON.stringify(result.exifData?.data); - proto.data_contentSize = result.contentSize; + proto[`${fieldKey}_exif`] = JSON.stringify(result.exifData?.data); + proto[`${fieldKey}_contentSize`] = result.contentSize; // exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates const latitude = result.exifData?.data?.GPSLatitude; const latitudeDirection = result.exifData?.data?.GPSLatitudeRef; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 37959a5f0..c120cddf0 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -190,6 +190,7 @@ export class MarqueeView extends ObservableReactComponent FormattedTextBox.LiveTextUndo?.end(), 100); e.stopPropagation(); } }; diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx index 3cacb6692..8e48a0b54 100644 --- a/src/client/views/nodes/EquationBox.tsx +++ b/src/client/views/nodes/EquationBox.tsx @@ -6,7 +6,7 @@ import { TraceMobx } from '../../../fields/util'; import { DocUtils } from '../../documents/DocUtils'; import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; -import { undoBatch } from '../../util/UndoManager'; +import { undoBatch, UndoManager } from '../../util/UndoManager'; import { ViewBoxBaseComponent } from '../DocComponent'; import { StyleProp } from '../StyleProp'; import { DocumentView } from './DocumentView'; @@ -21,6 +21,7 @@ export class EquationBox extends ViewBoxBaseComponent() { return FieldView.LayoutString(EquationBox, fieldKey); } _ref: React.RefObject = React.createRef(); + _liveTextUndo: UndoManager.Batch | undefined; // captured undo batch when typing a new text note into a collection constructor(props: FieldViewProps) { super(props); @@ -30,6 +31,8 @@ export class EquationBox extends ViewBoxBaseComponent() { componentDidMount() { this._props.setContentViewBox?.(this); if (DocumentView.SelectOnLoad === this.Document && (!DocumentView.LightboxDoc() || DocumentView.LightboxContains(this.DocumentView?.()))) { + this._liveTextUndo = FormattedTextBox.LiveTextUndo; + FormattedTextBox.LiveTextUndo = undefined; this._props.select(false); this._ref.current?.mathField.focus(); @@ -113,9 +116,7 @@ export class EquationBox extends ViewBoxBaseComponent() { className="equationBox-cont" onKeyDown={e => e.stopPropagation()} onPointerDown={e => !e.ctrlKey && e.stopPropagation()} - onBlur={() => { - FormattedTextBox.LiveTextUndo?.end(); - }} + onBlur={() => this._liveTextUndo?.end()} style={{ transform: `scale(${scale})`, minWidth: `${100 / scale}%`, diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 6380dec17..30fc44f62 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -112,6 +112,28 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { this._props.setContentViewBox?.(this); } + @computed get oupaintOriginalSize(): { width: number; height: number } { + return { + width: NumCast(this.Document[this.fieldKey + '_outpaintOriginalWidth']), + height: NumCast(this.Document[this.fieldKey + '_outpaintOriginalHeight']), + }; + } + set outpaintOriginalSize(prop: { width: number; height: number } | undefined) { + this.Document[this.fieldKey + '_outpaintOriginalWidth'] = prop?.width; + this.Document[this.fieldKey + '_outpaintOriginalHeight'] = prop?.height; + } + + @computed get imgNativeSize() { + return { + nativeWidth: NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth'], 500)), + nativeHeight: NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], NumCast(this.layoutDoc[this.fieldKey + '_nativeHeight'], 500)), + }; + } + set imgNativeSize(prop: { nativeWidth: number; nativeHeight: number }) { + this.dataDoc[this.fieldKey + '_nativeWidth'] = prop.nativeWidth; + this.dataDoc[this.fieldKey + '_nativeHeight'] = prop.nativeHeight; + } + protected createDropTarget = (ele: HTMLDivElement) => { this._mainCont = ele; this._dropDisposer?.(); @@ -153,7 +175,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { () => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width), height: this.layoutDoc._height }), ({ nativeSize, width, height }) => { if (!this.layoutDoc._layout_nativeDimEditable || !height || this.layoutDoc.layout_resetNativeDim) { - this.layoutDoc.layout_resetNativeDim = undefined; // template images need to reset their dimensions when they are rendered with content. afterwards, remove this flag. + this.layoutDoc.layout_resetNativeDim = undefined; // reset dimensions of templates rendered with content or if image changes. afterwards, remove this flag. this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth; } }, @@ -170,7 +192,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { { fireImmediately: true } ); this._disposers.outpaint = reaction( - () => this.Document[this.fieldKey + '_outpaintOriginalWidth'] !== undefined && !SnappingManager.ShiftKey, + () => this.outpaintOriginalSize?.width && !SnappingManager.ShiftKey, complete => complete && this.openOutpaintPrompt(), { fireImmediately: true } ); @@ -231,14 +253,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { DrawingFillHandler.drawingToImage(this.Document, 90, newPrompt(descText), drag)?.then(action(() => (this._regenerateLoading = false))); added = false; } else if (de.altKey || !this.dataDoc[this.fieldKey]) { - const layoutDoc = de.complete.docDragData?.draggedDocuments[0]; - const targetField = Doc.LayoutDataKey(layoutDoc); - const targetDoc = layoutDoc[DocData]; - if (targetDoc[targetField] instanceof ImageField) { + const dropDoc = de.complete.docDragData?.draggedDocuments[0]; + const dropDocFieldKey = Doc.LayoutDataKey(dropDoc); + const dropDataDoc = dropDoc[DocData]; + if (dropDataDoc[dropDocFieldKey] instanceof ImageField) { added = true; - this.dataDoc[this.fieldKey] = ObjectField.MakeCopy(targetDoc[targetField] as ImageField); - Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(targetDoc), this.fieldKey); - Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(targetDoc), this.fieldKey); + this.dataDoc.layout_resetNativeDim = true; + this.dataDoc[this.fieldKey] = ObjectField.MakeCopy(dropDataDoc[dropDocFieldKey] as ImageField); + Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(dropDataDoc), this.fieldKey); + Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(dropDataDoc), this.fieldKey); } } added === false && e.preventDefault(); @@ -257,18 +280,17 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { @undoBatch setNativeSize = action(() => { - const oldnativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']); + const oldnativeWidth = this.imgNativeSize.nativeWidth; const nscale = NumCast(this._props.PanelWidth()) * NumCast(this.layoutDoc._freeform_scale, 1); 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.imgNativeSize = { nativeWidth: this.imgNativeSize.nativeWidth * nw, nativeHeight: this.imgNativeSize.nativeHeight * nw }; this.dataDoc.freeform_panX = nw * NumCast(this.dataDoc.freeform_panX); this.dataDoc.freeform_panY = nw * NumCast(this.dataDoc.freeform_panY); this.dataDoc.freeform_panX_max = this.dataDoc.freeform_panX_max ? nw * NumCast(this.dataDoc.freeform_panX_max) : undefined; 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']); + const newnativeWidth = this.imgNativeSize.nativeWidth; DocListCast(this.dataDoc[this.annotationKey]).forEach(doc => { doc.x = (NumCast(doc.x) / oldnativeWidth) * newnativeWidth; doc.y = (NumCast(doc.y) / oldnativeWidth) * newnativeWidth; @@ -280,13 +302,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { }); @undoBatch rotate = action(() => { - const nw = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']); - const nh = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']); + const nativeSize = this.imgNativeSize; const w = this.layoutDoc._width; const h = this.layoutDoc._height; this.dataDoc[this.fieldKey + '_rotation'] = (NumCast(this.dataDoc[this.fieldKey + '_rotation']) + 90) % 360; - this.dataDoc[this.fieldKey + '_nativeWidth'] = nh; - this.dataDoc[this.fieldKey + '_nativeHeight'] = nw; + this.imgNativeSize = { nativeWidth: nativeSize.nativeHeight, nativeHeight: nativeSize.nativeWidth }; // swap width and height this.layoutDoc._width = h; this.layoutDoc._height = w; }); @@ -302,7 +322,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { const anchy = NumCast(cropping.y); const anchw = NumCast(cropping._width); const anchh = NumCast(cropping._height); - const viewScale = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) / anchw; + const viewScale = this.nativeSize.nativeWidth / anchw; cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width); cropping.y = NumCast(this.Document.y); cropping.onClick = undefined; @@ -364,12 +384,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { @action cancelOutpaintPrompt = () => { - const origWidth = NumCast(this.Document[this.fieldKey + '_outpaintOriginalWidth']); - const origHeight = NumCast(this.Document[this.fieldKey + '_outpaintOriginalHeight']); - this.Document._width = origWidth; - this.Document._height = origHeight; + [this.Document._width, this.Document._height] = [this.oupaintOriginalSize.width, this.oupaintOriginalSize.height]; this._outpaintingInProgress = false; - this.Document[this.fieldKey + '_outpaintOriginalWidth'] = undefined; + this.outpaintOriginalSize = undefined; this.closeOutpaintPrompt(); }; @@ -417,8 +434,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { loadingOverlay.innerHTML = '
Generating outpainted image...
'; this._mainCont?.appendChild(loadingOverlay); - const origWidth = NumCast(this.Document[this.fieldKey + '_outpaintOriginalWidth']); - const origHeight = NumCast(this.Document[this.fieldKey + '_outpaintOriginalHeight']); + const { width: origWidth, height: origHeight } = this.oupaintOriginalSize; const response = await Networking.PostToServer('/outpaintImage', { imageUrl: currentPath, prompt: customPrompt, @@ -455,8 +471,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { this.Document.$ai = true; this.Document.$ai_outpainted = true; this.Document.$ai_outpaint_prompt = customPrompt; - this.Document[this.fieldKey + '_outpaintOriginalWidth'] = undefined; - this.Document[this.fieldKey + '_outpaintOriginalHeight'] = undefined; + this.outpaintOriginalSize = undefined; } else { this.cancelOutpaintPrompt(); alert('Failed to receive a valid image URL from server.'); @@ -669,8 +684,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { @computed get nativeSize() { TraceMobx(); if (this.paths.length && this.paths[0].includes(DefaultPath)) return { nativeWidth: NumCast(this.layoutDoc._width), nativeHeight: NumCast(this.layoutDoc._height), nativeOrientation: 0 }; - const nativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth'], 500)); - const nativeHeight = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], NumCast(this.layoutDoc[this.fieldKey + '_nativeHeight'], 500)); + const { nativeWidth, nativeHeight } = this.imgNativeSize; const nativeOrientation = NumCast(this.dataDoc[this.fieldKey + '_nativeOrientation'], 1); return { nativeWidth, nativeHeight, nativeOrientation }; } @@ -821,7 +835,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { transform, transformOrigin, width: this._outpaintAlign ? 'max-content' : this._outpaintAlign ? '100%' : undefined, - height: this._outpaintVAlign ? 'max-content' : this.Document[this.fieldKey + '_outpaintOriginalWidth'] !== undefined ? '100%' : undefined, + height: this._outpaintVAlign ? 'max-content' : this.outpaintOriginalSize?.width ? '100%' : undefined, }} onError={action(e => (this._error = e.toString()))} draggable={false} @@ -944,6 +958,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { return { width, height }; }; savedAnnotations = () => this._savedAnnotations; + rejectDrop = (de: DragManager.DropEvent, subView?: DocumentView | undefined) => (this.dataDoc[this.fieldKey] === undefined ? true : (this._props.rejectDrop?.(de, subView) ?? false)); render() { TraceMobx(); const borderRad = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding) as string; @@ -987,7 +1002,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { ScreenToLocalTransform={this.screenToLocalTransform} select={emptyFunction} focus={this.focus} - rejectDrop={this._props.rejectDrop} + rejectDrop={this.rejectDrop} getScrollHeight={this.getScrollHeight} NativeDimScaling={returnOne} isAnyChildContentActive={returnFalse} @@ -1049,8 +1064,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { if (result instanceof Error) { alert('Error uploading files - possibly due to unsupported file types'); } else { - this.dataDoc[this.fieldKey] = new ImageField(result.accessPaths.agnostic.client); - !(result instanceof Error) && DocUtils.assignUploadInfo(result, this.dataDoc); + runInAction(() => { + this.dataDoc.layout_resetNativeDim = true; + !(result instanceof Error) && DocUtils.assignUploadInfo(result, this.dataDoc, this.fieldKey); + this.dataDoc[this.fieldKey] = new ImageField(result.accessPaths.agnostic.client); + }); } disposer(); } else { diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 4cbe01b82..e1ecc2018 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -9,7 +9,7 @@ import { TraceMobx } from '../../../fields/util'; import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; -import { undoable } from '../../util/UndoManager'; +import { undoable, UndoManager } from '../../util/UndoManager'; import { ViewBoxBaseComponent } from '../DocComponent'; import { PinDocView, PinProps } from '../PinFuncs'; import { StyleProp } from '../StyleProp'; @@ -28,6 +28,7 @@ export class LabelBox extends ViewBoxBaseComponent() { private _timeout: NodeJS.Timeout | undefined; private _divRef: HTMLDivElement | null = null; private _disposers: { [key: string]: IReactionDisposer } = {}; + private _liveTextUndo: UndoManager.Batch | undefined; // captured undo batch when typing a new text note into a collection constructor(props: FieldViewProps) { super(props); @@ -220,8 +221,7 @@ export class LabelBox extends ViewBoxBaseComponent() { this.setText(this._divRef?.innerText ?? ''); if (!FormattedTextBox.tryKeepingFocus(e.relatedTarget, () => this._divRef?.focus())) { RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, undefined); - FormattedTextBox.LiveTextUndo?.end(); - FormattedTextBox.LiveTextUndo = undefined; + this._liveTextUndo?.end(); } }} dangerouslySetInnerHTML={{ @@ -236,6 +236,8 @@ export class LabelBox extends ViewBoxBaseComponent() { if (DocumentView.SelectOnLoad === this.Document) { DocumentView.SetSelectOnLoad(undefined); + this._liveTextUndo = FormattedTextBox.LiveTextUndo; + FormattedTextBox.LiveTextUndo = undefined; this._divRef.focus(); } this.fitTextToBox(this._divRef); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 1827cd2dc..c8df6e50f 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -98,7 +98,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { [key: string]: NodeViewConstructor }; private _curHighlights = new ObservableSet(['Audio Tags']); @@ -1536,6 +1537,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent disposer?.()); this.endUndoTypingBatch(); - FormattedTextBox.LiveTextUndo?.end(); - FormattedTextBox.LiveTextUndo = undefined; + this._liveTextUndo?.end(); this.unhighlightSearchTerms(); this.EditorView?.destroy(); RichTextMenu.Instance?.TextView === this && RichTextMenu.Instance.updateMenu(undefined, undefined, undefined, undefined); @@ -1795,8 +1797,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent !this.ProseRef?.contains(document.activeElement) && this._props.onBlur?.()); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 366dc6a20..25e70d950 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1202,12 +1202,12 @@ export namespace Doc { } export function NativeWidth(doc?: Doc, dataDoc?: Doc, useWidth?: boolean) { // if this is a field template, then don't use the doc's nativeWidth/height - return !doc ? 0 : NumCast(doc._nativeWidth, NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_nativeWidth'], useWidth ? NumCast(doc._width) : 0)); + return !doc ? 0 : NumCast(doc._nativeWidth, doc[DocLayout].isTemplateDoc ? 0 : NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_nativeWidth'], useWidth ? NumCast(doc._width) : 0)); } export function NativeHeight(doc?: Doc, dataDoc?: Doc, useHeight?: boolean) { if (!doc) return 0; const nheight = (Doc.NativeWidth(doc, dataDoc, useHeight) / NumCast(doc._width)) * NumCast(doc._height); // divide before multiply to avoid floating point errrorin case nativewidth = width - const dheight = NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_nativeHeight'], useHeight ? NumCast(doc._height) : 0); + const dheight = doc[DocLayout].isTemplateDoc ? 0 : NumCast((dataDoc || doc)[Doc.LayoutDataKey(doc) + '_nativeHeight'], useHeight ? NumCast(doc._height) : 0); // if this is a field template, then don't use the doc's nativeWidth/height return NumCast(doc._nativeHeight, nheight || dheight); } -- cgit v1.2.3-70-g09d2