diff options
33 files changed, 157 insertions, 159 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index b51fcb454..80b040cc0 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1310,13 +1310,13 @@ export namespace DocUtils { options?.afterFocus?.(false); } - export let ActiveRecordings: { props: FieldViewProps; getAnchor: () => Doc }[] = []; + export let ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = []; export function MakeLinkToActiveAudio(getSourceDoc: () => Doc | undefined, broadcastEvent = true) { broadcastEvent && runInAction(() => (DocumentManager.Instance.RecordingEvent = DocumentManager.Instance.RecordingEvent + 1)); return DocUtils.ActiveRecordings.map(audio => { const sourceDoc = getSourceDoc(); - const link = sourceDoc && DocUtils.MakeLink({ doc: sourceDoc }, { doc: audio.getAnchor() || audio.props.Document }, 'recording annotation:linked recording', 'recording timeline'); + const link = sourceDoc && DocUtils.MakeLink({ doc: sourceDoc }, { doc: audio.getAnchor(true) || audio.props.Document }, 'recording annotation:linked recording', 'recording timeline'); link && (link.followLinkLocation = OpenWhere.addRight); return link; }); diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index d0690fa10..a56f87075 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -251,8 +251,8 @@ export namespace DragManager { } // drags a linker button and creates a link on drop - export function StartLinkDrag(ele: HTMLElement, sourceView: DocumentView, sourceDocGetAnchor: undefined | (() => Doc), downX: number, downY: number, options?: DragOptions) { - StartDrag([ele], new DragManager.LinkDragData(sourceView, () => sourceDocGetAnchor?.() ?? sourceView.rootDoc), downX, downY, options); + export function StartLinkDrag(ele: HTMLElement, sourceView: DocumentView, sourceDocGetAnchor: undefined | ((addAsAnnotation: boolean) => Doc), downX: number, downY: number, options?: DragOptions) { + StartDrag([ele], new DragManager.LinkDragData(sourceView, () => sourceDocGetAnchor?.(true) ?? sourceView.rootDoc), downX, downY, options); } // drags a column from a schema view diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index 0f216e349..5bdfca54a 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -111,15 +111,9 @@ export class LinkFollower { easeFunc: StrCast(sourceDoc.followLinkEase, 'ease') as any, effect: sourceDoc, originatingDoc: sourceDoc, - zoomTextSelections: false, + zoomTextSelections: BoolCast(sourceDoc.followLinkZoomText), }; - if (target.TourMap) { - const fieldKey = Doc.LayoutFieldKey(target); - const tour = DocListCast(target[fieldKey]).reverse(); - LightboxView.SetLightboxDoc(currentContext, undefined, tour); - setTimeout(LightboxView.Next); - allFinished(); - } else if (target.type === DocumentType.PRES) { + if (target.type === DocumentType.PRES) { const containerAnnoDoc = Cast(sourceDoc, Doc, null); const containerDoc = containerAnnoDoc || sourceDoc; var containerDocContext = containerDoc?.context ? [Cast(await containerDoc?.context, Doc, null)] : ([] as Doc[]); diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index c9c09b63b..78ab2b3d4 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -130,6 +130,13 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>() isAnyChildContentActive = () => this._isAnyChildContentActive; + isContentActive = (outsideReaction?: boolean) => + this.props.isContentActive?.() === false + ? false + : Doc.ActiveTool !== InkTool.None || this.props.isContentActive?.() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this.props.rootSelected(outsideReaction) || this.isAnyChildContentActive() + ? true + : undefined; + lookupField = (field: string) => ScriptCast((this.layoutDoc as any).lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field }).result; protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 90c6c040c..f06dc93e3 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -501,7 +501,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV key="popup" showPopup={this._showLinkPopup} linkCreated={link => (link.linkDisplay = !this.props.views().lastElement()?.rootDoc.isLinkButton)} - linkCreateAnchor={() => this.props.views().lastElement()?.ComponentView?.getAnchor?.()} + linkCreateAnchor={() => this.props.views().lastElement()?.ComponentView?.getAnchor?.(true)} linkFrom={() => this.props.views().lastElement()?.rootDoc} /> </div> diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index a6fa2f04b..319a9419e 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -78,8 +78,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps>() { // fit within its panel (e.g., for content fitting views like Lightbox or multicolumn, etc) screenToLocal = () => this.props.ScreenToLocalTransform().scale(this.props.NativeDimScaling?.() || 1); - getAnchor = () => { - return this._subContentView?.getAnchor?.() || this.rootDoc; + getAnchor = (addAsAnnotation: boolean) => { + return this._subContentView?.getAnchor?.(addAsAnnotation) || this.rootDoc; }; scrollFocus = (textAnchor: Doc, options: DocFocusOptions) => { diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index d79b696a3..3627aa783 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -37,7 +37,6 @@ export class LightboxView extends React.Component<LightboxViewProps> { @observable private static _doc: Opt<Doc>; @observable private static _docTarget: Opt<Doc>; @observable private static _docFilters: string[] = []; // filters - @observable private static _tourMap: Opt<Doc[]> = []; // list of all tours available from the current target private static _savedState: Opt<{ panX: Opt<number>; panY: Opt<number>; scale: Opt<number>; scrollTop: Opt<number> }>; private static _history: Opt<{ doc: Doc; target?: Doc }[]> = []; @observable private static _future: Opt<Doc[]> = []; @@ -90,13 +89,6 @@ export class LightboxView extends React.Component<LightboxViewProps> { this._doc = doc; this._layoutTemplate = layoutTemplate; this._docTarget = target || doc; - this._tourMap = DocListCast(doc?.links) - .map(link => { - const opp = LinkManager.getOppositeAnchor(link, doc!); - return opp?.TourMap ? opp : undefined; - }) - .filter(m => m) - .map(m => m!); return true; } @@ -164,7 +156,7 @@ export class LightboxView extends React.Component<LightboxViewProps> { const target = (LightboxView._docTarget = this._future?.pop()); const targetDocView = target && DocumentManager.Instance.getLightboxDocumentView(target); if (targetDocView && target) { - const l = DocUtils.MakeLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.() || target).lastElement(); + const l = DocUtils.MakeLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.(true) || target).lastElement(); l && (Cast(l.anchor2, Doc, null).backgroundColor = 'lightgreen'); targetDocView.focus(target, { originalTarget: target, willPanZoom: true, zoomScale: 0.9 }); if (LightboxView._history?.lastElement().target !== target) LightboxView._history?.push({ doc, target }); @@ -189,13 +181,6 @@ export class LightboxView extends React.Component<LightboxViewProps> { LightboxView.SetLightboxDoc(target); } } - LightboxView._tourMap = DocListCast(LightboxView._docTarget?.links) - .map(link => { - const opp = LinkManager.getOppositeAnchor(link, LightboxView._docTarget!); - return opp?.TourMap ? opp : undefined; - }) - .filter(m => m) - .map(m => m!); } @action public static Previous() { @@ -214,13 +199,6 @@ export class LightboxView extends React.Component<LightboxViewProps> { LightboxView.SetLightboxDoc(doc, target); } if (LightboxView._future?.lastElement() !== previous.target || previous.doc) LightboxView._future?.push(previous.target || previous.doc); - LightboxView._tourMap = DocListCast(LightboxView._docTarget?.links) - .map(link => { - const opp = LinkManager.getOppositeAnchor(link, LightboxView._docTarget!); - return opp?.TourMap ? opp : undefined; - }) - .filter(m => m) - .map(m => m!); } @action stepInto = () => { @@ -231,27 +209,20 @@ export class LightboxView extends React.Component<LightboxViewProps> { history: LightboxView._history, saved: LightboxView._savedState, }); - const tours = LightboxView._tourMap; - if (tours && tours.length) { - const fieldKey = Doc.LayoutFieldKey(tours[0]); - LightboxView._future?.push(...DocListCast(tours[0][fieldKey]).reverse()); - } else { - const coll = LightboxView._docTarget; - if (coll) { - const fieldKey = Doc.LayoutFieldKey(coll); - const contents = [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + '-annotations'])]; - const links = DocListCast(coll.links) - .map(link => LinkManager.getOppositeAnchor(link, coll)) - .filter(doc => doc) - .map(doc => doc!); - LightboxView.SetLightboxDoc(coll, undefined, contents.length ? contents : links); - TabDocView.PinDoc(coll, { hidePresBox: true }); - } + const coll = LightboxView._docTarget; + if (coll) { + const fieldKey = Doc.LayoutFieldKey(coll); + const contents = [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + '-annotations'])]; + const links = DocListCast(coll.links) + .map(link => LinkManager.getOppositeAnchor(link, coll)) + .filter(doc => doc) + .map(doc => doc!); + LightboxView.SetLightboxDoc(coll, undefined, contents.length ? contents : links); + TabDocView.PinDoc(coll, { hidePresBox: true }); } }; future = () => LightboxView._future; - tourMap = () => LightboxView._tourMap; render() { let downx = 0, downy = 0; @@ -345,7 +316,7 @@ export class LightboxView extends React.Component<LightboxViewProps> { }, this.future()?.length.toString() )} - <LightboxTourBtn navBtn={this.navBtn} future={this.future} stepInto={this.stepInto} tourMap={this.tourMap} /> + <LightboxTourBtn navBtn={this.navBtn} future={this.future} stepInto={this.stepInto} /> <div className="lightboxView-navBtn" title={'toggle fit width'} @@ -393,7 +364,6 @@ export class LightboxView extends React.Component<LightboxViewProps> { } interface LightboxTourBtnProps { navBtn: (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: string, display: () => string, click: (e: React.MouseEvent) => void, color?: string) => JSX.Element; - tourMap: () => Opt<Doc[]>; future: () => Opt<Doc[]>; stepInto: () => void; } @@ -410,7 +380,7 @@ export class LightboxTourBtn extends React.Component<LightboxTourBtnProps> { e.stopPropagation(); this.props.stepInto(); }, - StrCast(this.props.tourMap()?.lastElement()?.TourMap) + '' ); } } diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index bf1242346..b1965c5c8 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -52,8 +52,8 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> { AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true)); AnchorMenu.Instance.OnAudio = unimplementedFunction; AnchorMenu.Instance.Highlight = this.highlight; - AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations); - AnchorMenu.Instance.onMakeAnchor = AnchorMenu.Instance.GetAnchor; + AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations, addAsAnnotation); + AnchorMenu.Instance.onMakeAnchor = () => AnchorMenu.Instance.GetAnchor(undefined, true); } @action @@ -194,11 +194,11 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> { return textRegionAnno; }; @action - highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => { + highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => { // creates annotation documents for current highlights const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DataSym]); const annotationDoc = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations); - !savedAnnotations && annotationDoc && this.props.addDocument(annotationDoc); + addAsAnnotation && !savedAnnotations && annotationDoc && this.props.addDocument(annotationDoc); return (annotationDoc as Doc) ?? undefined; }; diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 8d495d286..af50478b0 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -1680,6 +1680,16 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { </button> </div> <div className="propertiesView-input inline"> + <p>Zoom Text Selections</p> + <button + style={{ background: !this.sourceAnchor?.followLinkZoomText ? '' : '#4476f7', borderRadius: 3 }} + onPointerDown={e => this.toggleAnchorProp(e, 'followLinkZoomText', this.sourceAnchor)} + onClick={e => e.stopPropagation()} + className="propertiesButton"> + <FontAwesomeIcon className="fa-icon" icon={faAnchor as IconLookup} size="lg" /> + </button> + </div> + <div className="propertiesView-input inline"> <p>Toggle Target (Show/Hide)</p> <button style={{ background: !this.sourceAnchor?.followLinkToggle ? '' : '#4476f7', borderRadius: 3 }} diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index e9bf03208..b7ea26b44 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -190,7 +190,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack CollectionStackedTimeline.SelectingRegion = this; } else { this._markerEnd = this.currentTime; - CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this._markerStart, this._markerEnd); + CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this._markerStart, this._markerEnd, undefined, true); this._markerEnd = undefined; CollectionStackedTimeline.SelectingRegion = undefined; } @@ -257,7 +257,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack this._markerEnd = tmp; } if (!isClick && Math.abs(movement[0]) > 15 && !this.IsTrimming) { - const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this._markerStart, this._markerEnd); + const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this._markerStart, this._markerEnd, undefined, true); setTimeout(() => DocumentManager.Instance.getDocumentView(anchor)?.select(false)); } (!isClick || !wasSelecting) && (this._markerEnd = undefined); @@ -273,7 +273,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack undefined, () => { if (shiftKey) { - CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this.currentTime); + CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this.currentTime, undefined, undefined, true); } else { !wasPlaying && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width)); } @@ -388,7 +388,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack // creates marker on timeline @undoBatch @action - static createAnchor(rootDoc: Doc, dataDoc: Doc, fieldKey: string, startTag: string, endTag: string, anchorStartTime?: number, anchorEndTime?: number, docAnchor?: Doc) { + static createAnchor(rootDoc: Doc, dataDoc: Doc, fieldKey: string, startTag: string, endTag: string, anchorStartTime: Opt<number>, anchorEndTime: Opt<number>, docAnchor: Opt<Doc>, addAsAnnotation: boolean) { if (anchorStartTime === undefined) return rootDoc; const anchor = docAnchor ?? @@ -407,10 +407,12 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack }); Doc.GetProto(anchor)[startTag] = anchorStartTime; Doc.GetProto(anchor)[endTag] = anchorEndTime; - if (Cast(dataDoc[fieldKey], listSpec(Doc), null)) { - Cast(dataDoc[fieldKey], listSpec(Doc), []).push(anchor); - } else { - dataDoc[fieldKey] = new List<Doc>([anchor]); + if (addAsAnnotation) { + if (Cast(dataDoc[fieldKey], listSpec(Doc), null)) { + Cast(dataDoc[fieldKey], listSpec(Doc), []).push(anchor); + } else { + dataDoc[fieldKey] = new List<Doc>([anchor]); + } } return anchor; } diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index d2074219a..27ae3041f 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -334,7 +334,7 @@ export function CollectionSubView<X>(moreProps?: X) { const iframe = SelectionManager.Views()[0].ContentDiv?.getElementsByTagName('iframe')?.[0]; const focusNode = iframe?.contentDocument?.getSelection()?.focusNode as any; if (focusNode) { - const anchor = srcWeb?.ComponentView?.getAnchor?.(); + const anchor = srcWeb?.ComponentView?.getAnchor?.(true); anchor && DocUtils.MakeLink({ doc: htmlDoc }, { doc: anchor }); } } diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index a1466bcd0..8c613198d 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -32,7 +32,7 @@ export class CollectionTimeView extends CollectionSubView() { @observable _viewDefDivClick: Opt<ScriptField>; @observable _focusPivotField: Opt<string>; - getAnchor = () => { + getAnchor = (addAsAnnotation: boolean) => { const anchor = Docs.Create.HTMLAnchorDocument([], { title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any, annotationOn: this.rootDoc, @@ -45,11 +45,13 @@ export class CollectionTimeView extends CollectionSubView() { proto.docRangeFilters = ObjectField.MakeCopy(this.layoutDoc._docRangeFilters as ObjectField) || new List<string>([]); proto[ViewSpecPrefix + '_viewType'] = this.layoutDoc._viewType; - // store anchor in annotations list of document (not technically needed since these anchors are never drawn) - if (Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), null) !== undefined) { - Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), []).push(anchor); - } else { - this.dataDoc[this.props.fieldKey + '-annotations'] = new List<Doc>([anchor]); + if (addAsAnnotation) { + // when added as an annotation, links to anchors can be found as links to the document even if the anchors are not rendered + if (Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), null) !== undefined) { + Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), []).push(anchor); + } else { + this.dataDoc[this.props.fieldKey + '-annotations'] = new List<Doc>([anchor]); + } } return anchor; }; diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 5c05b5c13..f6e4b45ee 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -247,7 +247,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { alert('Cannot pin presentation document to itself'); return; } - const anchorDoc = DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.getAnchor?.(); + const anchorDoc = DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.getAnchor?.(false); const pinDoc = Doc.MakeAlias(anchorDoc ?? doc); pinDoc.presentationTargetDoc = anchorDoc ?? doc; pinDoc.title = doc.title + ' - Slide'; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 8919b1c01..9811c239b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -290,7 +290,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo stroke="antiquewhite" strokeWidth="4" className="collectionfreeformlinkview-linkLine" - style={{ pointerEvents: 'all', opacity: this._opacity, stroke, strokeWidth }} + style={{ pointerEvents: 'visibleStroke', opacity: this._opacity, stroke, strokeWidth }} onClick={this.onClickLine} d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`} markerEnd={link.linkDisplayArrow ? `url(#${link[Id] + 'arrowhead'})` : ''} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index dc0eb69f3..5cf4cb31f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1565,7 +1565,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection : undefined; }; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document - getAnchor = () => { + getAnchor = (addAsAnnotation: boolean) => { if (this.props.Document.annotationOn) { return this.rootDoc; } @@ -1574,10 +1574,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const proto = Doc.GetProto(anchor); proto[ViewSpecPrefix + '_viewType'] = this.layoutDoc._viewType; proto.docFilters = ObjectField.MakeCopy(this.layoutDoc.docFilters as ObjectField) || new List<string>([]); - if (Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), null) !== undefined) { - Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), []).push(anchor); - } else { - this.dataDoc[this.props.fieldKey + '-annotations'] = new List<Doc>([anchor]); + if (addAsAnnotation) { + if (Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), null) !== undefined) { + Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), []).push(anchor); + } else { + this.dataDoc[this.props.fieldKey + '-annotations'] = new List<Doc>([anchor]); + } } return anchor; }; diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index d95668c89..1d59d3356 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -136,7 +136,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp return { la1, la2, linkTime }; } - getAnchor = () => { + getAnchor = (addAsAnnotation: boolean) => { return ( CollectionStackedTimeline.createAnchor( this.rootDoc, @@ -144,7 +144,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp this.annotationKey, '_timecodeToShow' /* audioStart */, '_timecodeToHide' /* audioEnd */, - this._ele?.currentTime || Cast(this.props.Document._currentTimecode, 'number', null) || (this.mediaState === media_state.Recording ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined) + this._ele?.currentTime || Cast(this.props.Document._currentTimecode, 'number', null) || (this.mediaState === media_state.Recording ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined), + undefined, + undefined, + addAsAnnotation ) || this.rootDoc ); }; diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 6f3152981..a39e0f65f 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -135,7 +135,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp DocumentLinksButton.AnnotationId = undefined; } else if (DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document) { const sourceDoc = DocumentLinksButton.StartLink; - const targetDoc = this.props.View.ComponentView?.getAnchor?.() || this.props.View.Document; + const targetDoc = this.props.View.ComponentView?.getAnchor?.(true) || this.props.View.Document; const linkDoc = DocUtils.MakeLink({ doc: sourceDoc }, { doc: targetDoc }, 'links'); //why is long drag here when this is used for completing links by clicking? LinkManager.currentLink = linkDoc; @@ -183,8 +183,8 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp DocumentLinksButton.AnnotationUri = undefined; //!this.props.StartLink } else if (startLink !== endLink) { - endLink = endLinkView?.docView?._componentView?.getAnchor?.() || endLink; - startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.() || startLink; + endLink = endLinkView?.docView?._componentView?.getAnchor?.(true) || endLink; + startLink = DocumentLinksButton.StartLinkView?.docView?._componentView?.getAnchor?.(true) || startLink; const linkDoc = DocUtils.MakeLink({ doc: startLink }, { doc: endLink }, DocumentLinksButton.AnnotationId ? 'hypothes.is annotation' : undefined, undefined, undefined, true); LinkManager.currentLink = linkDoc; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 1df46488b..054d01d8e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -121,7 +121,7 @@ export type DocFocusFunc = (doc: Doc, options: DocFocusOptions) => void; export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => any; export interface DocComponentView { updateIcon?: () => void; // updates the icon representation of the document - getAnchor?: () => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) + getAnchor?: (addAsAnnotation: boolean) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) scrollFocus?: (doc: Doc, options: DocFocusOptions) => Opt<number>; // returns the duration of the focus brushView?: (view: { width: number; height: number; panX: number; panY: number }) => void; setViewSpec?: (anchor: Doc, preview: boolean) => void; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document @@ -783,9 +783,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps @undoBatch @action - toggleFollowLink = (location: Opt<string>, zoom?: boolean, setPushpin?: boolean): void => { + toggleFollowLink = (location: Opt<string>, zoom?: boolean, setTargetToggle?: boolean): void => { this.Document.ignoreClick = false; - if (setPushpin) { + if (setTargetToggle) { this.Document.followLinkToggle = !this.Document.followLinkToggle; this.Document._isLinkButton = this.Document.followLinkToggle || this.Document._isLinkButton; } else { @@ -854,7 +854,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(undefined); } if (de.complete.annoDragData || this.rootDoc !== linkdrag.linkSourceDoc.context) { - const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.() ?? this.props.Document; + const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.(true) ?? this.props.Document; de.complete.linkDocument = DocUtils.MakeLink({ doc: linkdrag.linkSourceDoc }, { doc: dropDoc }, undefined, undefined, undefined, undefined, [de.x, de.y - 50]); } } @@ -979,7 +979,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps onClicks.push({ description: this.Document.isLinkButton ? 'Remove Follow Behavior' : 'Follow Link in Place', event: () => this.toggleFollowLink('inPlace', false, false), icon: 'link' }); !this.Document.isLinkButton && onClicks.push({ description: 'Follow Link on Right', event: () => this.toggleFollowLink('add:right', false, false), icon: 'link' }); onClicks.push({ description: this.Document.isLinkButton || this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(undefined, false, false), icon: 'link' }); - onClicks.push({ description: (this.Document.followLinkToggle ? 'Remove' : 'Make') + ' Pushpin', event: () => this.toggleFollowLink(undefined, false, true), icon: 'map-pin' }); + onClicks.push({ description: (this.Document.followLinkToggle ? 'Remove' : 'Make') + ' Target Visibility Toggle', event: () => this.toggleFollowLink(undefined, false, true), icon: 'map-pin' }); onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' }); !existingOnClick && cm.addItem({ description: 'OnClick...', addDivider: true, noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); } else if (DocListCast(this.Document.links).length) { @@ -1091,7 +1091,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin); onClickFunc = () => this.onClickHandler; setHeight = (height: number) => (this.layoutDoc._height = height); - setContentView = action((view: { getAnchor?: () => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view)); + setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view)); isContentActive = (outsideReaction?: boolean) => { return this.props.isContentActive() === false ? false @@ -1264,7 +1264,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps playAnnotation = () => { const self = this; const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations'], listSpec(AudioField), null); - const anno = audioAnnos.lastElement(); + const anno = audioAnnos?.lastElement(); if (anno instanceof AudioField && this.audioAnnoState === 'stopped') { new Howl({ src: [anno.url.href], diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx index 24562ccbd..5c0005dae 100644 --- a/src/client/views/nodes/FunctionPlotBox.tsx +++ b/src/client/views/nodes/FunctionPlotBox.tsx @@ -12,7 +12,7 @@ import { TraceMobx } from '../../../fields/util'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; -import { ViewBoxBaseComponent } from '../DocComponent'; +import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { DocFocusOptions } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; @@ -22,7 +22,7 @@ type EquationDocument = makeInterface<[typeof EquationSchema, typeof documentSch const EquationDocument = makeInterface(EquationSchema, documentSchema); @observer -export class FunctionPlotBox extends ViewBoxBaseComponent<FieldViewProps>() { +export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FunctionPlotBox, fieldKey); } @@ -41,10 +41,11 @@ export class FunctionPlotBox extends ViewBoxBaseComponent<FieldViewProps>() { () => this.createGraph() ); } - getAnchor = () => { + getAnchor = (addAsAnnotation: boolean) => { const anchor = Docs.Create.TextanchorDocument({ annotationOn: this.rootDoc }); anchor.xRange = new List<number>(Array.from(this._plot.options.xAxis.domain)); anchor.yRange = new List<number>(Array.from(this._plot.options.yAxis.domain)); + if (addAsAnnotation) this.addDocument(anchor); return anchor; }; @action diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index ac953d13b..e2ecca0b6 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -52,7 +52,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp } private _dropDisposer?: DragManager.DragDropDisposer; private _disposers: { [name: string]: IReactionDisposer } = {}; - private _getAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined; + private _getAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined; @observable _curSuffix = ''; @observable _uploadIcon = uploadIcons.idle; @@ -86,13 +86,13 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp : undefined; }; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document - getAnchor = () => { + getAnchor = (addAsAnnotation: boolean) => { const anchor = - this._getAnchor?.(this._savedAnnotations) ?? // use marquee anchor, otherwise, save zoom/pan as anchor + this._getAnchor?.(this._savedAnnotations, false) ?? // use marquee anchor, otherwise, save zoom/pan as anchor Docs.Create.ImageanchorDocument({ presTransition: 1000, unrendered: true, annotationOn: this.rootDoc }); if (anchor) { PresBox.pinDocView(anchor, { pinData: { pannable: true, dataview: true, dataannos: true } }, this.rootDoc); - this.addDocument(anchor); + addAsAnnotation && this.addDocument(anchor); return anchor; } return this.rootDoc; diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 10897b48f..a2143f629 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -36,9 +36,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp this._timeout && clearTimeout(this._timeout); } - getAnchor = () => { - return this.rootDoc; - }; + getAnchor = (addAsAnnotation: boolean) => this.rootDoc; getTitle() { return this.rootDoc['title-custom'] ? StrCast(this.rootDoc.title) : this.props.label ? this.props.label : typeof this.rootDoc[this.fieldKey] === 'string' ? StrCast(this.rootDoc[this.fieldKey]) : StrCast(this.rootDoc.title); diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 232c3459e..7eb42994b 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -113,7 +113,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget; } this._toolTipText = ''; - if (LinkDocPreview.LinkInfo?.noPreview || this._markerTargetDoc?.type === DocumentType.PRES) this.followLink(); + if (LinkDocPreview.LinkInfo?.noPreview || this._linkSrc?.followLinkToggle || this._markerTargetDoc?.type === DocumentType.PRES) this.followLink(); } }) ); diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index c8d5b0154..95cb49037 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -522,10 +522,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps ); } - getAnchor = () => { - const anchor = AnchorMenu.Instance?.GetAnchor(this._savedAnnotations) ?? this.rootDoc; - return anchor; - }; + getAnchor = (addAsAnnotation: boolean) => AnchorMenu.Instance?.GetAnchor(this._savedAnnotations, addAsAnnotation) ?? this.rootDoc; /** * render contents in allMapMarkers (e.g. images with exifData) into google maps as map marker diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index e22ee5021..71ef9bc84 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -212,7 +212,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps PresBox.restoreTargetDocView(this.props.DocumentView?.(), {}, doc, options.zoomTime ?? 500, { pannable: doc.presPinData ? true : false }); return this._pdfViewer?.scrollFocus(doc, NumCast(doc.presPinViewScroll, NumCast(doc.y)), options) ?? (didToggle ? 1 : undefined); }; - getAnchor = () => { + getAnchor = (addAsAnnotation: boolean) => { let ele: Opt<HTMLDivElement> = undefined; if (this._pdfViewer?.selectionContent()) { ele = document.createElement('div'); @@ -226,10 +226,12 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps PresBox.pinDocView(anchor, { pinData: { scrollable: true, pannable: true } }, this.rootDoc); return anchor; }; - const anchor = this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations()) ?? docAnchor(); + const anchor = this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations(), false) ?? docAnchor(); anchor.text = ele?.textContent ?? ''; anchor.textHtml = ele?.innerHTML; - this.addDocument(anchor); + if (addAsAnnotation) { + this.addDocument(anchor); + } return anchor; }; diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index 76a24d831..f94996c66 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -128,9 +128,9 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl this.setupDictation(); } } - getAnchor = () => { + getAnchor = (addAsAnnotation: boolean) => { const startTime = Cast(this.layoutDoc._currentTimecode, 'number', null) || (this._videoRec ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined); - return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, '_timecodeToShow', '_timecodeToHide', startTime, startTime === undefined ? undefined : startTime + 3) || this.rootDoc; + return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, '_timecodeToShow', '_timecodeToHide', startTime, startTime === undefined ? undefined : startTime + 3, undefined, addAsAnnotation) || this.rootDoc; }; videoLoad = () => { diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index a8f78edd5..1dfa55c64 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -385,15 +385,18 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp Doc.SetNativeWidth(Doc.GetProto(imageSummary), Doc.NativeWidth(this.layoutDoc)); Doc.SetNativeHeight(Doc.GetProto(imageSummary), Doc.NativeHeight(this.layoutDoc)); this.props.addDocument?.(imageSummary); - const link = DocUtils.MakeLink({ doc: imageSummary }, { doc: this.getAnchor() }, 'video snapshot'); + const link = DocUtils.MakeLink({ doc: imageSummary }, { doc: this.getAnchor(true) }, 'video snapshot'); link && (Doc.GetProto(link.anchor2 as Doc).timecodeToHide = NumCast((link.anchor2 as Doc).timecodeToShow) + 3); setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSummary)?.startDragging(downX, downY, 'move', true)); }; - getAnchor = () => { + getAnchor = (addAsAnnotation: boolean) => { const timecode = Cast(this.layoutDoc._currentTimecode, 'number', null); - const marquee = AnchorMenu.Instance.GetAnchor?.(); - return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, '_timecodeToShow' /* videoStart */, '_timecodeToHide' /* videoEnd */, timecode ? timecode : undefined, undefined, marquee) || this.rootDoc; + const marquee = AnchorMenu.Instance.GetAnchor?.(undefined, addAsAnnotation); + return ( + CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, '_timecodeToShow' /* videoStart */, '_timecodeToHide' /* videoEnd */, timecode ? timecode : undefined, undefined, marquee, addAsAnnotation) || + this.rootDoc + ); }; // sets video info on load diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 8be4884ce..acf4fe4b0 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -307,7 +307,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return undefined; }; - getAnchor = () => { + getAnchor = (addAsAnnotation: boolean) => { let ele: Opt<HTMLDivElement> = undefined; try { const contents = this._iframe?.contentWindow?.getSelection()?.getRangeAt(0).cloneContents(); @@ -317,7 +317,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } } catch (e) {} const anchor = - this._getAnchor(this._savedAnnotations) ?? + this._getAnchor(this._savedAnnotations, false) ?? Docs.Create.WebanchorDocument(this._url, { title: StrCast(this.rootDoc.title + ' ' + this.layoutDoc._scrollTop), y: NumCast(this.layoutDoc._scrollTop), @@ -325,7 +325,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }); anchor.text = ele?.textContent ?? ''; anchor.textHtml = ele?.innerHTML; - this.addDocumentWrapper(anchor); + addAsAnnotation && this.addDocumentWrapper(anchor); return anchor; }; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 62e215521..deb1d68a7 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -231,7 +231,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } } - getAnchor = () => this.makeLinkAnchor(undefined, 'add:right', undefined, 'Anchored Selection', false); + getAnchor = (addAsAnnotation: boolean) => this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', false, addAsAnnotation); @action setupAnchorMenu = () => { @@ -239,23 +239,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps AnchorMenu.Instance.OnClick = (e: PointerEvent) => { !this.layoutDoc.showSidebar && this.toggleSidebar(); - setTimeout(() => this._sidebarRef.current?.anchorMenuClick(this.makeLinkAnchor(undefined, 'add:right', undefined, 'Anchored Selection', true))); // give time for sidebarRef to be created + setTimeout(() => this._sidebarRef.current?.anchorMenuClick(this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', true))); // give time for sidebarRef to be created }; AnchorMenu.Instance.OnAudio = (e: PointerEvent) => { !this.layoutDoc.showSidebar && this.toggleSidebar(); - const anchor = this.getAnchor(); - const target = this._sidebarRef.current?.anchorMenuClick(anchor); - if (target) { - anchor.followLinkAudio = true; - DocumentViewInternal.recordAudioAnnotation(Doc.GetProto(target), Doc.LayoutFieldKey(target)); - target.title = ComputedField.MakeFunction(`self["text-audioAnnotations-text"].lastElement()`); - } + const anchor = this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', true, true); + setTimeout(() => { + const target = this._sidebarRef.current?.anchorMenuClick(anchor); + if (target) { + anchor.followLinkAudio = true; + DocumentViewInternal.recordAudioAnnotation(Doc.GetProto(target), Doc.LayoutFieldKey(target)); + target.title = ComputedField.MakeFunction(`self["text-audioAnnotations-text"].lastElement()`); + } + }); }; AnchorMenu.Instance.Highlight = action((color: string, isLinkButton: boolean) => { this._editorView?.state && RichTextMenu.Instance.setHighlight(color, this._editorView, this._editorView?.dispatch); return undefined; }); - AnchorMenu.Instance.onMakeAnchor = this.getAnchor; + AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true); AnchorMenu.Instance.StartCropDrag = unimplementedFunction; /** * This function is used by the PDFmenu to create an anchor highlight and a new linked text annotation. @@ -270,7 +272,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps return target; }; - DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docViewPath().lastElement(), this.getAnchor, targetCreator), e.pageX, e.pageY); + DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docViewPath().lastElement(), () => this.getAnchor(true), targetCreator), e.pageX, e.pageY); }); const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to); this.props.isSelected(true) && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom); @@ -518,7 +520,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { - if (de.complete.annoDragData) de.complete.annoDragData.dropDocCreator = this.getAnchor; + if (de.complete.annoDragData) de.complete.annoDragData.dropDocCreator = () => this.getAnchor(true); const dragData = de.complete.docDragData; if (dragData) { const draggedDoc = dragData.draggedDocuments.length && dragData.draggedDocuments[0]; @@ -689,9 +691,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps pinToPres = (anchor: Doc) => this.props.pinToPres(anchor, {}); @undoBatch - makePushpin = (anchor: Doc) => (anchor.followLinkToggle = !anchor.followLinkToggle); + makeTargetToggle = (anchor: Doc) => (anchor.followLinkToggle = !anchor.followLinkToggle); - isPushpin = (anchor: Doc) => BoolCast(anchor.followLinkToggle); + isTargetToggler = (anchor: Doc) => BoolCast(anchor.followLinkToggle); specificContextMenu = (e: React.MouseEvent): void => { const cm = ContextMenu.Instance; @@ -714,8 +716,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps AnchorMenu.Instance.Delete = () => this.deleteAnnotation(anchor as Doc); AnchorMenu.Instance.Pinned = false; AnchorMenu.Instance.PinToPres = () => this.pinToPres(anchor as Doc); - AnchorMenu.Instance.MakePushpin = () => this.makePushpin(anchor as Doc); - AnchorMenu.Instance.IsPushpin = () => this.isPushpin(anchor as Doc); + AnchorMenu.Instance.MakeTargetToggle = () => this.makeTargetToggle(anchor as Doc); + AnchorMenu.Instance.IsTargetToggler = () => this.isTargetToggler(anchor as Doc); AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true); }) ); @@ -891,7 +893,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps }; // TODO: nda -- Look at how link anchors are added - makeLinkAnchor(anchorDoc?: Doc, location?: string, targetHref?: string, title?: string, noPreview?: boolean) { + makeLinkAnchor(anchorDoc?: Doc, location?: string, targetHref?: string, title?: string, noPreview?: boolean, addAsAnnotation?: boolean) { const state = this._editorView?.state; if (state) { let selectedText = ''; @@ -901,7 +903,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps if (sel.from !== sel.to) { const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: '#' + this._editorView?.state.doc.textBetween(sel.from, sel.to), annotationOn: this.dataDoc, unrendered: true }); const href = targetHref ?? Doc.localServerPath(anchor); - if (anchor !== anchorDoc) this.addDocument(anchor); + if (anchor !== anchorDoc && addAsAnnotation) this.addDocument(anchor); tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => { if (node.firstChild === null && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) { const allAnchors = [{ href, title, anchorId: anchor[Id] }]; @@ -1476,7 +1478,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span> while (target && !target.dataset?.targethrefs) target = target.parentElement; FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true'); - if (target) return; } if (e.button === 0 && this.props.isSelected(true) && !e.altKey) { diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index e3c67ad2e..5675776fb 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -259,7 +259,7 @@ export class RichTextRules { this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3)))); } const target = (docx instanceof Doc && docx) || Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500 }, docid); - DocUtils.MakeLink({ doc: this.TextBox.getAnchor() }, { doc: target }, 'portal to:portal from', undefined); + DocUtils.MakeLink({ doc: this.TextBox.getAnchor(true) }, { doc: target }, 'portal to:portal from', undefined); const fstate = this.TextBox.EditorView?.state; if (fstate && selection) { diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 855a7f171..226fd8d7d 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -104,7 +104,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { return NumCast(this.rootDoc._itemIndex); } @computed get activeItem() { - return Cast(this.childDocs[NumCast(this.rootDoc._itemIndex)], Doc, null); + return DocCast(this.childDocs[NumCast(this.rootDoc._itemIndex)]); } @computed get targetDoc() { return Cast(this.activeItem?.presentationTargetDoc, Doc, null); @@ -452,7 +452,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { pinDoc.presWidth = NumCast(targetDoc.width); pinDoc.presHeight = NumCast(targetDoc.height); } - if (pinProps.pinAudioPlay) pinDoc.followLinkAudio = true; + if (pinProps.pinAudioPlay) pinDoc.presPlayAudio = true; if (pinProps.pinData) { pinDoc.presPinData = pinProps.pinData.scrollable || @@ -577,7 +577,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { noSelect: true, originatingDoc: activeItem, easeFunc: StrCast(activeItem.presEaseFunc, 'ease') as any, - zoomTextSelections: true, + zoomTextSelections: BoolCast(activeItem.presZoomText), + playAudio: BoolCast(activeItem.presPlayAudio), }; if (activeItem.presentationTargetDoc instanceof Doc) activeItem.presentationTargetDoc[AnimationSym] = undefined; var containerDocContext = srcContext ? [srcContext] : []; @@ -1416,7 +1417,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { Effects <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}> <div className="presBox-subheading">Play Audio Annotation</div> - <input className="presBox-checkbox" style={{ margin: 10 }} type="checkbox" onChange={() => (activeItem.followLinkAudio = !BoolCast(activeItem.followLinkAudio))} checked={BoolCast(activeItem.followLinkAudio)} /> + <input className="presBox-checkbox" style={{ margin: 10 }} type="checkbox" onChange={() => (activeItem.presPlayAudio = !BoolCast(activeItem.presPlayAudio))} checked={BoolCast(activeItem.presPlayAudio)} /> + </div> + <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}> + <div className="presBox-subheading">Zoom Text Selections</div> + <input className="presBox-checkbox" style={{ margin: 10 }} type="checkbox" onChange={() => (activeItem.presZoomText = !BoolCast(activeItem.presZoomText))} checked={BoolCast(activeItem.presZoomText)} /> </div> <div className="presBox-dropdown" diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 265328036..2fb795b06 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -11,6 +11,7 @@ import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu'; import { LinkPopup } from '../linking/LinkPopup'; import { ButtonDropdown } from '../nodes/formattedText/RichTextMenu'; import './AnchorMenu.scss'; +import { truncate } from 'lodash'; @observer export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { @@ -52,13 +53,13 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { public OnAudio: (e: PointerEvent) => void = unimplementedFunction; public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; - public Highlight: (color: string, isPushpin: boolean) => Opt<Doc> = (color: string, isPushpin: boolean) => undefined; - public GetAnchor: (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>) => Opt<Doc> = () => undefined; + public Highlight: (color: string, isTargetToggler: boolean) => Opt<Doc> = (color: string, isTargetToggler: boolean) => undefined; + public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => undefined; public Delete: () => void = unimplementedFunction; public AddTag: (key: string, value: string) => boolean = returnFalse; public PinToPres: () => void = unimplementedFunction; - public MakePushpin: () => void = unimplementedFunction; - public IsPushpin: () => boolean = returnFalse; + public MakeTargetToggle: () => void = unimplementedFunction; + public IsTargetToggler: () => boolean = returnFalse; public get Active() { return this._left > 0; } @@ -238,8 +239,8 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { <FontAwesomeIcon icon="map-pin" size="lg" /> </button> </Tooltip>, - <Tooltip key="pushpin" title={<div className="dash-tooltip">{'toggle pushpin behavior'}</div>}> - <button className="antimodeMenu-button" style={{ color: this.IsPushpin() ? 'black' : 'white', backgroundColor: this.IsPushpin() ? 'white' : 'black' }} onPointerDown={this.MakePushpin}> + <Tooltip key="toggle" title={<div className="dash-tooltip">{'make target visibility toggle on click'}</div>}> + <button className="antimodeMenu-button" style={{ color: this.IsTargetToggler() ? 'black' : 'white', backgroundColor: this.IsTargetToggler() ? 'white' : 'black' }} onPointerDown={this.MakeTargetToggle}> <FontAwesomeIcon icon="thumbtack" size="lg" /> </button> </Tooltip>, diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 7069ff399..d8e44ae9d 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -55,9 +55,9 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> { pinToPres = () => this.props.pinToPres(this.annoTextRegion, {}); @undoBatch - makePushpin = () => (this.annoTextRegion.followLinkToggle = !this.annoTextRegion.followLinkToggle); + makeTargretToggle = () => (this.annoTextRegion.followLinkToggle = !this.annoTextRegion.followLinkToggle); - isPushpin = () => BoolCast(this.annoTextRegion.followLinkToggle); + isTargetToggler = () => BoolCast(this.annoTextRegion.followLinkToggle); @action onPointerDown = (e: React.PointerEvent) => { @@ -67,8 +67,8 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> { AnchorMenu.Instance.Pinned = false; AnchorMenu.Instance.AddTag = this.addTag.bind(this); AnchorMenu.Instance.PinToPres = this.pinToPres; - AnchorMenu.Instance.MakePushpin = this.makePushpin; - AnchorMenu.Instance.IsPushpin = this.isPushpin; + AnchorMenu.Instance.MakeTargetToggle = this.makeTargretToggle; + AnchorMenu.Instance.IsTargetToggler = this.isTargetToggler; AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true); e.stopPropagation(); } else if (e.button === 0) { diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index f95d5ac2e..b0b7816b8 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -135,7 +135,7 @@ export class PDFViewer extends React.Component<IViewerProps> { copy = (e: ClipboardEvent) => { if (this.props.isContentActive() && e.clipboardData) { e.clipboardData.setData('text/plain', this._selectionText); - const anchor = this._getAnchor(); + const anchor = this._getAnchor(undefined, false); if (anchor) { anchor.textCopied = true; e.clipboardData.setData('dash/pdfAnchor', anchor[Id]); @@ -317,7 +317,7 @@ export class PDFViewer extends React.Component<IViewerProps> { this._ignoreScroll = false; if (this._scrollTimer) clearTimeout(this._scrollTimer); // wait until a scrolling pause, then create an anchor to audio this._scrollTimer = setTimeout(() => { - DocUtils.MakeLinkToActiveAudio(() => this.props.DocumentView?.().ComponentView?.getAnchor!()!, false); + DocUtils.MakeLinkToActiveAudio(() => this.props.DocumentView?.().ComponentView?.getAnchor!(true)!, false); this._scrollTimer = undefined; }, 200); } |