From 510f9145fa1bffd72aaee1c861e2013a0cad42ca Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 21 Sep 2022 12:06:07 -0400 Subject: fixed following links to toggle alias if it's displayed when actual target isn't. changed cropping images to leave behind an outline not, a solid rectangle. double clicking on link buttons no longer opens them in lightbox view. --- src/client/views/nodes/ImageBox.tsx | 1 + 1 file changed, 1 insertion(+) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 9590bcb15..11f221448 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -136,6 +136,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent { if (!region) return; const cropping = Doc.MakeCopy(region, true); + Doc.GetProto(region).backgroundColor = 'transparent'; Doc.GetProto(region).lockedPosition = true; Doc.GetProto(region).title = 'region:' + this.rootDoc.title; Doc.GetProto(region).isPushpin = true; -- cgit v1.2.3-70-g09d2 From 0046f6eab6ec28b509398b5184922296014a66d0 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 21 Sep 2022 13:24:02 -0400 Subject: added cropping for videos. made images render at full res when selected. --- src/client/views/nodes/ImageBox.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 53 +++++++++++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 3 deletions(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 11f221448..d3d68d835 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -74,7 +74,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent (this._curSuffix = this.fieldKey === 'icon' ? '_m' : forceFull ? '_o' : scrSize < 0.25 ? '_s' : scrSize < 0.5 ? '_m' : scrSize < 0.8 || !selected ? '_l' : '_o'), + ({ forceFull, scrSize, selected }) => (this._curSuffix = selected ? '_o' : this.fieldKey === 'icon' ? '_m' : forceFull ? '_o' : scrSize < 0.25 ? '_s' : scrSize < 0.5 ? '_m' : scrSize < 0.8 ? '_l' : '_o'), { fireImmediately: true, delay: 1000 } ); this._disposers.path = reaction( diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index b45ee7f6e..ac472aa89 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -30,6 +30,7 @@ import { StyleProp } from '../StyleProvider'; import { FieldView, FieldViewProps } from './FieldView'; import { RecordingBox } from './RecordingBox'; import './VideoBox.scss'; +import { ObjectField } from '../../../fields/ObjectField'; const path = require('path'); /** @@ -397,7 +398,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent { const aspect = this.player!.videoWidth / (this.player!.videoHeight || 1); - if (aspect) { + if (aspect && !this.dataDoc.viewScaleMin) { Doc.SetNativeWidth(this.dataDoc, this.player!.videoWidth); Doc.SetNativeHeight(this.dataDoc, this.player!.videoHeight); this.layoutDoc._height = NumCast(this.layoutDoc._width) / aspect; @@ -570,7 +571,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent this.Play()} @@ -986,6 +987,53 @@ export class VideoBox extends ViewBoxAnnotatableComponent; } + crop = (region: Doc | undefined, addCrop?: boolean) => { + if (!region) return; + const cropping = Doc.MakeCopy(region, true); + Doc.GetProto(region).backgroundColor = 'transparent'; + Doc.GetProto(region).lockedPosition = true; + Doc.GetProto(region).title = 'region:' + this.rootDoc.title; + Doc.GetProto(region).isPushpin = true; + region._timecodeToHide = region._timecodeToShow; + this.addDocument(region); + const anchx = NumCast(cropping.x); + const anchy = NumCast(cropping.y); + const anchw = NumCast(cropping._width); + const anchh = NumCast(cropping._height); + const viewScale = NumCast(this.rootDoc[this.fieldKey + '-nativeWidth']) / anchw; + cropping.title = 'crop: ' + this.rootDoc.title; + cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width); + cropping.y = NumCast(this.rootDoc.y); + cropping._width = anchw * (this.props.NativeDimScaling?.() || 1); + cropping._height = anchh * (this.props.NativeDimScaling?.() || 1); + cropping.timecodeToHide = undefined; + cropping.timecodeToShow = undefined; + cropping.isLinkButton = undefined; + const croppingProto = Doc.GetProto(cropping); + croppingProto.annotationOn = undefined; + croppingProto.isPrototype = true; + croppingProto.proto = Cast(this.rootDoc.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO + croppingProto.type = DocumentType.VID; + croppingProto.layout = VideoBox.LayoutString('data'); + croppingProto.data = ObjectField.MakeCopy(this.rootDoc[this.fieldKey] as ObjectField); + croppingProto['data-nativeWidth'] = anchw; + croppingProto['data-nativeHeight'] = anchh; + croppingProto.currentTimecode = this.layoutDoc._currentTimecode; + croppingProto.viewScale = viewScale; + croppingProto.viewScaleMin = viewScale; + croppingProto.panX = anchx / viewScale; + croppingProto.panY = anchy / viewScale; + croppingProto.panXMin = anchx / viewScale; + croppingProto.panXMax = anchw / viewScale; + croppingProto.panYMin = anchy / viewScale; + croppingProto.panYMax = anchh / viewScale; + if (addCrop) { + DocUtils.MakeLink({ doc: region }, { doc: cropping }, 'cropped image', ''); + } + this.props.bringToFront(cropping); + return cropping; + }; + savedAnnotations = () => this._savedAnnotations; render() { const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding); @@ -1048,6 +1096,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent )} {this.renderTimeline} -- cgit v1.2.3-70-g09d2 From 604b3e566eaf762ede05f4a31c459c20d0df020b Mon Sep 17 00:00:00 2001 From: Geireann Lindfield Roberts <60007097+geireann@users.noreply.github.com> Date: Thu, 22 Sep 2022 13:10:56 -0400 Subject: added more things to novice mode --- src/client/views/nodes/ImageBox.tsx | 2 +- src/client/views/nodes/trails/PresElementBox.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index d3d68d835..486c9c48c 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -340,7 +340,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent )} - {this.considerDownloadIcon} + {!Doc.noviceMode && this.considerDownloadIcon} {this.considerGooglePhotosLink()} diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index e3f93c637..e6d08cd53 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -511,11 +511,11 @@ export class PresElementBox extends ViewBoxBaseComponent() { V - {this.recordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}}> + {!Doc.noviceMode && {this.recordingIsInOverlay ? 'Hide Recording' : `${PresElementBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}}>
(this.recordingIsInOverlay ? this.hideRecording(e, true) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}> e.stopPropagation()} />
-
+
} {activeItem.groupWithUp ? 'Ungroup' : 'Group with up'}}>
Date: Wed, 5 Oct 2022 14:06:34 -0400 Subject: fixed uploading tif and other unsupported images to generate an error quickly with a better erorr message. --- deploy/assets/unknown-file-icon-hi.png | Bin 0 -> 32861 bytes src/client/util/request-image-size.js | 26 ++++++++++++++------------ src/client/views/nodes/ImageBox.tsx | 2 +- src/server/DashUploadUtils.ts | 11 ++++++----- 4 files changed, 21 insertions(+), 18 deletions(-) create mode 100644 deploy/assets/unknown-file-icon-hi.png (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/deploy/assets/unknown-file-icon-hi.png b/deploy/assets/unknown-file-icon-hi.png new file mode 100644 index 000000000..be8a5ece0 Binary files /dev/null and b/deploy/assets/unknown-file-icon-hi.png differ diff --git a/src/client/util/request-image-size.js b/src/client/util/request-image-size.js index beb030635..502e0fbac 100644 --- a/src/client/util/request-image-size.js +++ b/src/client/util/request-image-size.js @@ -15,15 +15,18 @@ const HttpError = require('standard-http-error'); module.exports = function requestImageSize(options) { let opts = { - encoding: null + encoding: null, }; if (options && typeof options === 'object') { opts = Object.assign(options, opts); } else if (options && typeof options === 'string') { - opts = Object.assign({ - uri: options - }, opts); + opts = Object.assign( + { + uri: options, + }, + opts + ); } else { return Promise.reject(new Error('You should provide an URI string or a "request" options object.')); } @@ -38,9 +41,8 @@ module.exports = function requestImageSize(options) { return reject(new HttpError(res.statusCode, res.statusMessage)); } - let buffer = new Buffer.from([]); + let buffer = Buffer.from([]); let size; - let imageSizeError; res.on('data', chunk => { buffer = Buffer.concat([buffer, chunk]); @@ -48,8 +50,8 @@ module.exports = function requestImageSize(options) { try { size = imageSize(buffer); } catch (err) { - imageSizeError = err; - return; + reject(err); + return req.abort(); } if (size) { @@ -58,11 +60,11 @@ module.exports = function requestImageSize(options) { } }); - res.on('error', err => reject(err)); + res.on('error', reject); res.on('end', () => { if (!size) { - return reject(imageSizeError); + return reject(new Error('Image has no size')); } size.downloaded = buffer.length; @@ -70,6 +72,6 @@ module.exports = function requestImageSize(options) { }); }); - req.on('error', err => reject(err)); + req.on('error', reject); }); -}; \ No newline at end of file +}; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 486c9c48c..959c641a8 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -231,7 +231,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent => { const metadata = await InspectImage(source); if (metadata instanceof Error) { - return metadata; + return { name: metadata.name, message: metadata.message }; } return UploadInspectedImage(metadata, filename || metadata.filename, prefix); }; @@ -395,10 +394,12 @@ export namespace DashUploadUtils { exifData, requestable: resolvedUrl, }; + // Use the request library to parse out file level image information in the headers const { headers } = await new Promise((resolve, reject) => { - request.head(resolvedUrl, (error, res) => (error ? reject(error) : resolve(res))); - }).catch(console.error); + return request.head(resolvedUrl, (error, res) => (error ? reject(error) : resolve(res))); + }).catch(reject); + try { // Compute the native width and height ofthe image with an npm module const { width: nativeWidth, height: nativeHeight } = await requestImageSize(resolvedUrl); -- cgit v1.2.3-70-g09d2 From a81ab2e6f75681bc4fb3a7b49d2056144e396b94 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 19 Oct 2022 12:38:19 -0400 Subject: fixed erasing straight line stroke segments caused by intersections with other strokes. cleaned up more with gestures. changed docView lock icon to be part of docDecorations. --- src/client/views/DocumentDecorations.scss | 28 ++++++- src/client/views/DocumentDecorations.tsx | 41 +++++++++- src/client/views/GestureOverlay.tsx | 21 +++--- src/client/views/StyleProvider.scss | 18 ++--- src/client/views/StyleProvider.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 58 ++++++-------- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/ImageBox.tsx | 5 +- src/client/views/nodes/button/FontIconBox.tsx | 20 ++--- src/pen-gestures/GestureUtils.ts | 2 - src/pen-gestures/ndollar.ts | 88 +++++++++------------- 11 files changed, 148 insertions(+), 137 deletions(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 5c8dd36cc..4e0b061a6 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -1,7 +1,7 @@ @import 'global/globalCssVariables'; $linkGap: 3px; -$headerHeight: 20px; +$headerHeight: 25px; $resizeHandler: 8px; .documentDecorations-Dark, @@ -54,6 +54,8 @@ $resizeHandler: 8px; grid-column-end: 4; flex-direction: row; gap: 2px; + pointer-events: all; + cursor: move; .documentDecorations-openButton { display: flex; @@ -236,8 +238,8 @@ $resizeHandler: 8px; .documentDecorations-borderRadius { position: absolute; border-radius: 100%; - left: 3px; - top: 23px; + left: 7px; + top: 27px; background: $medium-gray; height: 10; width: 10; @@ -245,6 +247,21 @@ $resizeHandler: 8px; cursor: nwse-resize; } + .documentDecorations-lock { + position: absolute; + background: black; + right: 11; + top: 30px; + color: gray; + height: 14; + width: 14; + pointer-events: all; + margin: auto; + display: flex; + align-items: center; + flex-direction: column; + } + .documentDecorations-rotationPath { position: absolute; width: 100%; @@ -260,6 +277,7 @@ $resizeHandler: 8px; cursor: nwse-resize; background: unset; opacity: 1; + transform: scale(2); } .documentDecorations-topLeftResizer { @@ -314,6 +332,7 @@ $resizeHandler: 8px; .documentDecorations-topLeftResizer:hover, .documentDecorations-bottomRightResizer:hover { opacity: 1; + background: black; } .documentDecorations-bottomRightResizer { @@ -325,6 +344,7 @@ $resizeHandler: 8px; cursor: nesw-resize; background: unset; opacity: 1; + transform: scale(2); } .documentDecorations-topRightResizer { @@ -339,7 +359,6 @@ $resizeHandler: 8px; .documentDecorations-topRightResizer:hover, .documentDecorations-bottomLeftResizer:hover { - cursor: nesw-resize; background: black; opacity: 1; } @@ -392,6 +411,7 @@ $resizeHandler: 8px; justify-content: center; align-items: center; gap: 5px; + top: 4px; background: $light-gray; } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 9881ef9f1..6cf7df357 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -140,6 +140,16 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P } }; + @action onContainerDown = (e: React.PointerEvent): void => { + setupMoveUpEvents( + this, + e, + e => this.onBackgroundMove(true, e), + e => {}, + emptyFunction + ); + }; + @action onTitleDown = (e: React.PointerEvent): void => { setupMoveUpEvents( this, @@ -311,6 +321,27 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P ); }; + @action + onLockDown = (e: React.PointerEvent): void => { + // Call util move event function + setupMoveUpEvents( + this, // target + e, // pointerEvent + returnFalse, // moveEvent + emptyFunction, // upEvent + e => { + UndoManager.RunInBatch( + () => + SelectionManager.Views().map(dv => { + dv.rootDoc._lockedPosition = !dv.rootDoc._lockedPosition; + dv.rootDoc._pointerEvents = dv.rootDoc._lockedPosition ? 'none' : undefined; + }), + 'toggleBackground' + ); + } // clickEvent + ); + }; + setRotateCenter = (seldocview: DocumentView, rotCenter: number[]) => { const newloccentern = seldocview.props.ScreenToLocalTransform().transformPoint(rotCenter[0], rotCenter[1]); const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2]; @@ -791,7 +822,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P width: bounds.r - bounds.x + this._resizeBorderWidth + 'px', height: bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight + 'px', }}> -
+
{hideDeleteButton ?
: topBtn('close', 'times', undefined, e => this.onCloseClick(true), 'Close')} {hideResizers || hideDeleteButton ?
: topBtn('minimize', 'window-maximize', undefined, e => this.onCloseClick(undefined), 'Minimize')} {hideResizers ?
: titleArea} @@ -817,21 +848,23 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P key="rad" style={{ background: `${this._isRounding ? Colors.MEDIUM_BLUE : undefined}`, - left: `${radiusHandleLocation + 3}`, - top: `${radiusHandleLocation + 23}`, + transform: `translate(${radiusHandleLocation}px, ${radiusHandleLocation}px)`, }} className={`documentDecorations-borderRadius`} onPointerDown={this.onRadiusDown} onContextMenu={e => e.preventDefault()} /> )} +
e.preventDefault()}> + +
{hideDocumentButtonBar ? null : (
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 23b03bc50..362aa3c86 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -1,7 +1,7 @@ import React = require('react'); import * as fitCurve from 'fit-curve'; import { action, computed, observable, runInAction, trace } from 'mobx'; -import { Doc } from '../../fields/Doc'; +import { Doc, Opt } from '../../fields/Doc'; import { InkData, InkTool } from '../../fields/InkField'; import { List } from '../../fields/List'; import { ScriptField } from '../../fields/ScriptField'; @@ -49,7 +49,7 @@ interface GestureOverlayProps { export class GestureOverlay extends Touchable { static Instance: GestureOverlay; - @observable public InkShape: string = ''; + @observable public InkShape: Opt; @observable public SavedColor?: string; @observable public SavedWidth?: number; @observable public Tool: ToolglassTools = ToolglassTools.None; @@ -644,8 +644,6 @@ export class GestureOverlay extends Touchable { if (this._points.length > 1) { const B = this.svgBounds; const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top })); - //push first points to so interactionUtil knows pointer is up - this._points.push({ X: this._points[0].X, Y: this._points[0].Y }); const initialPoint = this._points[0]; const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height; @@ -685,10 +683,10 @@ export class GestureOverlay extends Touchable { //if any of the shape is activated in the CollectionFreeFormViewChrome else if (this.InkShape) { this.makeBezierPolygon(this.InkShape, false); - this.dispatchGesture(GestureUtils.Gestures.Stroke); + this.dispatchGesture(this.InkShape); this._points.length = 0; if (!CollectionFreeFormViewChrome.Instance?._keepPrimitiveMode) { - this.InkShape = ''; + this.InkShape = undefined; Doc.ActiveTool = InkTool.None; } } @@ -703,12 +701,13 @@ export class GestureOverlay extends Touchable { case GestureUtils.Gestures.Rectangle: case GestureUtils.Gestures.Circle: this.makeBezierPolygon(result.Name, true); - case GestureUtils.Gestures.StartBracket: - case GestureUtils.Gestures.EndBracket: actionPerformed = this.dispatchGesture(result.Name); break; case GestureUtils.Gestures.Line: - actionPerformed = this.handleLineGesture(); + if (!(actionPerformed = this.handleLineGesture())) { + this.makeBezierPolygon(result.Name, true); + actionPerformed = this.dispatchGesture(GestureUtils.Gestures.Stroke); + } break; case GestureUtils.Gestures.Scribble: console.log('scribble'); @@ -972,7 +971,7 @@ export class GestureOverlay extends Touchable { ActiveDash(), 1, 1, - this.InkShape, + this.InkShape ?? '', 'none', 1.0, false @@ -999,7 +998,7 @@ export class GestureOverlay extends Touchable { ActiveDash(), 1, 1, - this.InkShape, + this.InkShape ?? '', 'none', 1.0, false diff --git a/src/client/views/StyleProvider.scss b/src/client/views/StyleProvider.scss index 8929954c8..b1c97164a 100644 --- a/src/client/views/StyleProvider.scss +++ b/src/client/views/StyleProvider.scss @@ -1,11 +1,11 @@ .styleProvider-lock { - font-size: 12px; - width: 20; - height: 20; - position: absolute; - right: -25; - top: -5; - background: transparent; + font-size: 10; + width: 15; + height: 15; + position: absolute; + right: -0; + top: 0; + background: black; pointer-events: all; opacity: 0.3; display: flex; @@ -15,7 +15,7 @@ cursor: default; } .styleProvider-lock:hover { - opacity:1; + opacity: 1; } .styleProvider-treeView-icon, @@ -26,4 +26,4 @@ .styleProvider-treeView-icon { opacity: 0; -} \ No newline at end of file +} diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index aadcd7169..a5a886f42 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -276,7 +276,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt 0 && ((doc.type === DocumentType.COL && doc._viewType !== CollectionViewType.Pile) || [DocumentType.RTF, DocumentType.IMG, DocumentType.INK].includes(doc.type as DocumentType)) ? ( diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index daf69d4f6..983cf4f9b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -568,6 +568,8 @@ export class CollectionFreeFormView extends CollectionSubView { switch (ge.gesture) { + case GestureUtils.Gestures.Line: + break; default: case GestureUtils.Gestures.Circle: case GestureUtils.Gestures.Rectangle: @@ -602,33 +604,6 @@ export class CollectionFreeFormView extends CollectionSubView p.X)), Math.min(...ge.points.map(p => p.Y))); - const rb = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y))); - const bounds = { x: lt[0], r: rb[0], y: lt[1], b: rb[1] }; - const bWidth = bounds.r - bounds.x; - const bHeight = bounds.b - bounds.y; - const sel = this.getActiveDocuments().filter(doc => { - const l = NumCast(doc.x); - const r = l + doc[WidthSym](); - const t = NumCast(doc.y); - const b = t + doc[HeightSym](); - const pass = !(bounds.x > r || bounds.r < l || bounds.y > b || bounds.b < t); - if (pass) { - doc.x = l - bounds.x - bWidth / 2; - doc.y = t - bounds.y - bHeight / 2; - } - return pass; - }); - this.addDocument(Docs.Create.FreeformDocument(sel, { title: 'nested collection', x: bounds.x, y: bounds.y, _width: bWidth, _height: bHeight, _panX: 0, _panY: 0 })); - sel.forEach(d => this.props.removeDocument?.(d)); - e.stopPropagation(); - break; - case GestureUtils.Gestures.StartBracket: - const start = this.getTransform().transformPoint(Math.min(...ge.points.map(p => p.X)), Math.min(...ge.points.map(p => p.Y))); - this._inkToTextStartX = start[0]; - this._inkToTextStartY = start[1]; - break; - case GestureUtils.Gestures.EndBracket: if (this._inkToTextStartX && this._inkToTextStartY) { const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y))); const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === 'rtf' && s.color); @@ -805,15 +780,15 @@ export class CollectionFreeFormView extends CollectionSubView ({ inkView, t: +t + Math.floor(i / 4) }))); // convert string t's to numbers and add start of curve segment to convert from local t value to t value along complete curve } @@ -862,6 +837,15 @@ export class CollectionFreeFormView extends CollectionSubView { + if ((otherCurve as any)._linear) { + return curve.lineIntersects({ p1: otherCurve.points[0], p2: otherCurve.points[3] }); + } + return curve.intersects(otherCurve); + }; + /** * Determines all possible intersections of the current curve of the intersected ink stroke with all other curves of all * ink strokes in the current collection. @@ -886,7 +870,7 @@ export class CollectionFreeFormView extends CollectionSubView ({ x: p.X, y: p.Y }))); - curve.intersects(otherCurve).forEach((val: string | number, i: number) => { + this.bintersects(curve, otherCurve).forEach((val: string | number, i: number) => { // Converting the Bezier.js Split type to a t-value number. const t = +val.toString().split('/')[0]; if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical). diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 1c48d47e9..040e03150 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1082,7 +1082,7 @@ export class DocumentViewInternal extends DocComponent )} {audioView} diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 959c641a8..461d6984d 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -8,7 +8,7 @@ import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { createSchema } from '../../../fields/Schema'; import { ComputedField } from '../../../fields/ScriptField'; -import { Cast, NumCast } from '../../../fields/Types'; +import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; @@ -77,10 +77,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent (this._curSuffix = selected ? '_o' : this.fieldKey === 'icon' ? '_m' : forceFull ? '_o' : scrSize < 0.25 ? '_s' : scrSize < 0.5 ? '_m' : scrSize < 0.8 ? '_l' : '_o'), { fireImmediately: true, delay: 1000 } ); + const layoutDoc = this.layoutDoc; this._disposers.path = reaction( () => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }), ({ nativeSize, width }) => { - if (true || !this.layoutDoc._height) { + if (layoutDoc === this.layoutDoc || !this.layoutDoc._height) { this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth; } }, diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 6eaf3c31a..fd0c0d141 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -771,36 +771,26 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) { CollectionFreeFormView.collectionsWithUnprocessedInk.clear(); } -/** INK - * setActiveTool - * setStrokeWidth - * setStrokeColor - **/ - -ScriptingGlobals.add(function setActiveTool(tool: string, checkResult?: boolean) { +ScriptingGlobals.add(function setActiveTool(tool: InkTool | GestureUtils.Gestures, checkResult?: boolean) { InkTranscription.Instance?.createInkGroup(); if (checkResult) { return (Doc.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool ? Colors.MEDIUM_BLUE : 'transparent'; } - if ([GestureUtils.Gestures.Circle, GestureUtils.Gestures.Rectangle, GestureUtils.Gestures.Line, GestureUtils.Gestures.Triangle].includes(tool as any)) { + if (Object.values(GestureUtils.Gestures).includes(tool as any)) { if (GestureOverlay.Instance.InkShape === tool) { Doc.ActiveTool = InkTool.None; - GestureOverlay.Instance.InkShape = InkTool.None; + GestureOverlay.Instance.InkShape = undefined; } else { Doc.ActiveTool = InkTool.Pen; - GestureOverlay.Instance.InkShape = tool; + GestureOverlay.Instance.InkShape = tool as GestureUtils.Gestures; } } else if (tool) { // pen or eraser if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape) { Doc.ActiveTool = InkTool.None; - } else if (tool == InkTool.Write) { - // console.log("write mode selected - create groupDoc here!", tool) - Doc.ActiveTool = tool; - GestureOverlay.Instance.InkShape = ''; } else { Doc.ActiveTool = tool as any; - GestureOverlay.Instance.InkShape = ''; + GestureOverlay.Instance.InkShape = undefined; } } else { Doc.ActiveTool = InkTool.None; diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts index 2d3b1fdb8..41917aac9 100644 --- a/src/pen-gestures/GestureUtils.ts +++ b/src/pen-gestures/GestureUtils.ts @@ -19,8 +19,6 @@ export namespace GestureUtils { export enum Gestures { Line = 'line', - StartBracket = 'startbracket', - EndBracket = 'endbracket', Stroke = 'stroke', Scribble = 'scribble', Text = 'text', diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts index b10a9da17..3ee9506cb 100644 --- a/src/pen-gestures/ndollar.ts +++ b/src/pen-gestures/ndollar.ts @@ -145,7 +145,7 @@ export class Result { // // NDollarRecognizer constants // -const NumMultistrokes = 6; +let NumMultistrokes = 0; const NumPoints = 96; const SquareSize = 250.0; const OneDThreshold = 0.25; // customize to desired gesture set (usually 0.20 - 0.35) @@ -162,73 +162,59 @@ const AngleSimilarityThreshold = Deg2Rad(30.0); // NDollarRecognizer class // export class NDollarRecognizer { - public Multistrokes: Multistroke[]; + public Multistrokes: Multistroke[] = []; - /** - * @IMPORTANT - IF YOU'RE ADDING A NEW GESTURE, BE SURE TO INCREMENT THE NumMultiStrokes CONST RIGHT ABOVE THIS CLASS. - */ constructor( useBoundedRotationInvariance: boolean // constructor ) { // // one predefined multistroke for each multistroke type // - this.Multistrokes = new Array(NumMultistrokes); - this.Multistrokes[0] = new Multistroke( - GestureUtils.Gestures.Rectangle, - useBoundedRotationInvariance, - new Array( + this.Multistrokes.push( + new Multistroke( + GestureUtils.Gestures.Rectangle, + useBoundedRotationInvariance, new Array( - new Point(30, 146), //new Point(29, 160), new Point(30, 180), new Point(31, 200), - new Point(30, 222), //new Point(50, 219), new Point(70, 225), new Point(90, 230), - new Point(106, 225), //new Point(100, 200), new Point(106, 180), new Point(110, 160), - new Point(106, 146), //new Point(80, 150), new Point(50, 146), - new Point(30, 143) + new Array( + new Point(30, 146), //new Point(29, 160), new Point(30, 180), new Point(31, 200), + new Point(30, 222), //new Point(50, 219), new Point(70, 225), new Point(90, 230), + new Point(106, 225), //new Point(100, 200), new Point(106, 180), new Point(110, 160), + new Point(106, 146), //new Point(80, 150), new Point(50, 146), + new Point(30, 143) + ) ) ) ); - this.Multistrokes[1] = new Multistroke(GestureUtils.Gestures.Line, useBoundedRotationInvariance, new Array(new Array(new Point(12, 347), new Point(119, 347)))); - this.Multistrokes[2] = new Multistroke( - GestureUtils.Gestures.StartBracket, - useBoundedRotationInvariance, - new Array( - // new Array(new Point(145, 20), new Point(30, 21), new Point(34, 150)) - new Array(new Point(31, 25), new Point(145, 20), new Point(31, 25), new Point(34, 150)) + this.Multistrokes.push(new Multistroke(GestureUtils.Gestures.Line, useBoundedRotationInvariance, new Array(new Array(new Point(12, 347), new Point(119, 347))))); + this.Multistrokes.push( + new Multistroke( + GestureUtils.Gestures.Triangle, // equilateral + useBoundedRotationInvariance, + new Array(new Array(new Point(40, 100), new Point(100, 200), new Point(140, 102), new Point(42, 100))) ) ); - this.Multistrokes[3] = new Multistroke( - GestureUtils.Gestures.EndBracket, - useBoundedRotationInvariance, - new Array( - // new Array(new Point(150, 21), new Point(149, 150), new Point(26, 152)) - // new Array(new Point(150, 150), new Point(150, 0), new Point(150, 150), new Point(0, 150)) - new Array(new Point(10, 100), new Point(100, 100), new Point(150, 12), new Point(200, 103), new Point(300, 100)) - ) - ); - this.Multistrokes[4] = new Multistroke( - GestureUtils.Gestures.Triangle, // equilateral - useBoundedRotationInvariance, - new Array(new Array(new Point(40, 100), new Point(100, 200), new Point(140, 102), new Point(42, 100))) - ); - this.Multistrokes[5] = new Multistroke( - GestureUtils.Gestures.Circle, - useBoundedRotationInvariance, - new Array( + this.Multistrokes.push( + new Multistroke( + GestureUtils.Gestures.Circle, + useBoundedRotationInvariance, new Array( - new Point(200, 250), - new Point(240, 230), - new Point(248, 210), - new Point(248, 190), - new Point(240, 170), - new Point(200, 150), - new Point(160, 170), - new Point(151, 190), - new Point(151, 210), - new Point(160, 230), - new Point(201, 250) + new Array( + new Point(200, 250), + new Point(240, 230), + new Point(248, 210), + new Point(248, 190), + new Point(240, 170), + new Point(200, 150), + new Point(160, 170), + new Point(151, 190), + new Point(151, 210), + new Point(160, 230), + new Point(201, 250) + ) ) ) ); + NumMultistrokes = this.Multistrokes.length; // NumMultistrokes flags the end of the non user-defined gstures strokes // // PREDEFINED STROKES // -- cgit v1.2.3-70-g09d2 From 51e8786c33a1af207081212802e6da03476edf4a Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 9 Nov 2022 15:46:33 -0500 Subject: added tab highlighting when tab doc is target of hyperlink. made region annotations on images be transparent. don't show lock icon for lockedPosition documents that still get pointer events. --- src/client/views/MarqueeAnnotator.tsx | 9 +++++---- src/client/views/StyleProvider.tsx | 3 +-- src/client/views/collections/TabDocView.tsx | 17 ++++++++++------- .../views/nodes/CollectionFreeFormDocumentView.tsx | 5 ----- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/ImageBox.tsx | 5 +++-- 6 files changed, 20 insertions(+), 21 deletions(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index ce0e58d90..c0dd62a05 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -34,6 +34,7 @@ export interface MarqueeAnnotatorProps { finishMarquee: (x?: number, y?: number, PointerEvent?: PointerEvent) => void; anchorMenuClick?: () => undefined | ((anchor: Doc) => void); anchorMenuCrop?: (anchor: Doc | undefined, addCrop: boolean) => Doc | undefined; + highlightDragSrcColor?: string; } @observer export class MarqueeAnnotator extends React.Component { @@ -64,8 +65,8 @@ export class MarqueeAnnotator extends React.Component { doc.addEventListener('pointermove', this.onSelectMove); doc.addEventListener('pointerup', this.onSelectEnd); - AnchorMenu.Instance.OnCrop = (e: PointerEvent) => this.props.anchorMenuCrop?.(this.highlight('rgba(173, 216, 230, 0.75)', true), true); - AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight('rgba(173, 216, 230, 0.75)', true)); + AnchorMenu.Instance.OnCrop = (e: PointerEvent) => this.props.anchorMenuCrop?.(this.highlight('', true), true); + 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) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations); @@ -79,7 +80,7 @@ export class MarqueeAnnotator extends React.Component { e.preventDefault(); e.stopPropagation(); const sourceAnchorCreator = () => { - const annoDoc = this.highlight('rgba(173, 216, 230, 0.75)', true); // hyperlink color + const annoDoc = this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true); // hyperlink color annoDoc && this.props.addDocument(annoDoc); return annoDoc; }; @@ -107,7 +108,7 @@ export class MarqueeAnnotator extends React.Component { e.stopPropagation(); var cropRegion: Doc | undefined; const sourceAnchorCreator = () => { - cropRegion = this.highlight('rgba(173, 216, 230, 0.75)', true); // hyperlink color + cropRegion = this.highlight('', true); // hyperlink color cropRegion && this.props.addDocument(cropRegion); return cropRegion; }; diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 5abf4bde2..a268707e0 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -253,7 +253,6 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, props: Opt 0 ? ( + return doc && doc.pointerEvents === 'none' && isBackground() && !Doc.IsSystem(doc) && (props?.renderDepth || 0) > 0 ? (
toggleLockedPosition(doc)}>
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 31ed5a83b..e0c422f94 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -55,6 +55,11 @@ export class TabDocView extends React.Component { @computed get layoutDoc() { return this._document && Doc.Layout(this._document); } + @computed get tabBorderColor() { + const highlight = DefaultStyleProvider(this._document, undefined, StyleProp.Highlighting); + if (highlight?.highlightIndex >= Doc.DocBrushStatus.highlighted) return highlight.highlightColor; + return 'transparent'; + } @computed get tabColor() { let tabColor = StrCast(this._document?._backgroundColor, StrCast(this._document?.backgroundColor, DefaultStyleProvider(this._document, undefined, StyleProp.BackgroundColor))); if (tabColor === 'transparent') return 'black'; @@ -147,17 +152,15 @@ export class TabDocView extends React.Component { ReactDOM.createRoot(closeWrap).render(closeIcon); tab.reactComponents = [iconWrap, closeWrap]; tab.element[0].prepend(iconWrap); - tab._disposers.layerDisposer = reaction( - () => ({ layer: tab.DashDoc.activeLayer, color: this.tabColor }), - ({ layer, color }) => { - // console.log("TabDocView: " + this.tabColor); - // console.log("lightOrDark: " + lightOrDark(this.tabColor)); + tab._disposers.color = reaction( + () => ({ color: this.tabColor, borderColor: this.tabBorderColor }), + coloring => { const textColor = lightOrDark(this.tabColor); //not working with StyleProp.Color titleEle.style.color = textColor; - titleEle.style.backgroundColor = 'transparent'; + titleEle.style.backgroundColor = coloring.borderColor; iconWrap.style.color = textColor; closeWrap.style.color = textColor; - tab.element[0].style.background = !layer ? color : 'dimgrey'; + tab.element[0].style.background = coloring.color; }, { fireImmediately: true } ); diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 260c98816..570039550 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -24,7 +24,6 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { sizeProvider?: (doc: Doc, replica: string) => { width: number; height: number } | undefined; renderCutoffProvider: (doc: Doc) => boolean; zIndex?: number; - highlight?: boolean; rotation: number; dataTransition?: string; replica: string; @@ -75,9 +74,6 @@ export class CollectionFreeFormDocumentView extends DocComponent { if (!region) return; const cropping = Doc.MakeCopy(region, true); - Doc.GetProto(region).backgroundColor = 'transparent'; Doc.GetProto(region).lockedPosition = true; Doc.GetProto(region).title = 'region:' + this.rootDoc.title; Doc.GetProto(region).isPushpin = true; @@ -427,8 +426,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent )} -- cgit v1.2.3-70-g09d2 From fe2ee94451bfef6942ad8538f6766848ff433a3c Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 10 Nov 2022 14:34:44 -0500 Subject: fixed playing presentation when a slide is a tab. --- src/client/views/nodes/ImageBox.tsx | 2 +- src/client/views/nodes/trails/PresBox.tsx | 15 ++++----------- 2 files changed, 5 insertions(+), 12 deletions(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 08c0065d1..d02f181cc 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -71,7 +71,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent ({ forceFull: this.props.renderDepth < 1 || this.layoutDoc._showFullRes, - scrSize: this.props.ScreenToLocalTransform().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0] / this.nativeSize.nativeWidth, + scrSize: (this.props.ScreenToLocalTransform().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0] / this.nativeSize.nativeWidth) * NumCast(this.rootDoc._viewScale, 1), selected: this.props.isSelected(), }), ({ forceFull, scrSize, selected }) => (this._curSuffix = selected ? '_o' : this.fieldKey === 'icon' ? '_m' : forceFull ? '_o' : scrSize < 0.25 ? '_s' : scrSize < 0.5 ? '_m' : scrSize < 0.8 ? '_l' : '_o'), diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 1dfad510f..ccf19efc9 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -246,7 +246,6 @@ export class PresBox extends ViewBoxBaseComponent() { // Called when the user activates 'next' - to move to the next part of the pres. trail @action next = () => { - const activeNext = Cast(this.childDocs[this.itemIndex + 1], Doc, null); const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; const lastFrame = Cast(targetDoc?.lastFrame, 'number', null); @@ -478,7 +477,8 @@ export class PresBox extends ViewBoxBaseComponent() { const presCollection = Cast(this.layoutDoc.presCollection, Doc, null); const collectionDocView = presCollection ? DocumentManager.Instance.getDocumentView(presCollection) : undefined; const includesDoc: boolean = DocListCast(presCollection?.data).includes(targetDoc); - const tab = CollectionDockingView.Instance && Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === srcContext); + const tabMap = CollectionDockingView.Instance?.tabMap; + const tab = tabMap && Array.from(tabMap).find(tab => tab.DashDoc === srcContext || tab.DashDoc === targetDoc); // Handles the setting of presCollection if (includesDoc) { //Case 1: Pres collection should not change as it is already the same @@ -766,14 +766,12 @@ export class PresBox extends ViewBoxBaseComponent() { if (this.childDocs[stopDocIndex - 1].mediaStopTriggerList) { const list = DocListCast(this.childDocs[stopDocIndex - 1].mediaStopTriggerList); list.push(activeItem); - // this.childDocs[stopDocIndex - 1].mediaStopTriggerList = list; - console.log(list); + // this.childDocs[stopDocIndex - 1].mediaStopTriggerList = list;\ } else { this.childDocs[stopDocIndex - 1].mediaStopTriggerList = new List(); const list = DocListCast(this.childDocs[stopDocIndex - 1].mediaStopTriggerList); list.push(activeItem); // this.childDocs[stopDocIndex - 1].mediaStopTriggerList = list; - console.log(list); } }); @@ -2418,12 +2416,7 @@ export class PresBox extends ViewBoxBaseComponent() {
{'Edit in collection'}
}> -
{ - e.stopPropagation(); - console.log('New frame'); - }}> +
e.stopPropagation()}> e.stopPropagation()} />
-- cgit v1.2.3-70-g09d2 From 90bf9ea9e32ed695a36d0334e99fd859bbe51c15 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 10 Nov 2022 19:59:13 -0500 Subject: made alternate image use a field data-useAlt. added alt images to preselements. fixed removing TabDoc documentviews from document view list. added playing audio tags to preselements --- src/client/goldenLayout.js | 1 + src/client/util/LinkFollower.ts | 7 ++----- src/client/views/nodes/ImageBox.tsx | 9 ++++++--- .../nodes/formattedText/DashDocCommentView.tsx | 3 +++ .../views/nodes/formattedText/DashDocView.tsx | 1 + .../views/nodes/formattedText/DashFieldView.tsx | 2 +- .../views/nodes/formattedText/EquationView.tsx | 1 + .../views/nodes/formattedText/SummaryView.tsx | 1 + src/client/views/nodes/trails/PresBox.tsx | 22 ++++++++++++++++++---- 9 files changed, 34 insertions(+), 13 deletions(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js index bc08b4d0b..9cb20d834 100644 --- a/src/client/goldenLayout.js +++ b/src/client/goldenLayout.js @@ -5408,6 +5408,7 @@ * @returns {void} */ _destroy: function () { + this._reactComponent.unmount(); // ReactDOM.unmountComponentAtNode(this._container.getElement()[0]); this._container.off('open', this._render, this); this._container.off('destroy', this._destroy, this); diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index 43b2caf88..1608a77f2 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -1,8 +1,5 @@ -import { action, observable, observe, runInAction } from 'mobx'; -import { computedFn } from 'mobx-utils'; -import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../fields/Doc'; -import { List } from '../../fields/List'; -import { ProxyField } from '../../fields/Proxy'; +import { action, runInAction } from 'mobx'; +import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { BoolCast, Cast, StrCast } from '../../fields/Types'; import { DocumentDecorations } from '../views/DocumentDecorations'; import { LightboxView } from '../views/LightboxView'; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index d02f181cc..d0df41023 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -8,7 +8,7 @@ import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { createSchema } from '../../../fields/Schema'; import { ComputedField } from '../../../fields/ScriptField'; -import { Cast, NumCast, StrCast } from '../../../fields/Types'; +import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; @@ -120,6 +120,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent (this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes); + @undoBatch + setUseAlt = () => (this.layoutDoc[this.fieldKey + '-useAlt'] = !this.layoutDoc[this.fieldKey + '-useAlt']); @undoBatch rotate = action(() => { @@ -182,6 +184,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent Utils.CopyText(this.choosePath(field.url)), icon: 'expand-arrows-alt' }); if (!Doc.noviceMode) { funcs.push({ description: 'Export to Google Photos', event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: 'caret-square-right' }); @@ -335,8 +338,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent {fadepath === srcpath ? null : ( -
- +
+
)}
diff --git a/src/client/views/nodes/formattedText/DashDocCommentView.tsx b/src/client/views/nodes/formattedText/DashDocCommentView.tsx index 4bff57842..fcd6e0c55 100644 --- a/src/client/views/nodes/formattedText/DashDocCommentView.tsx +++ b/src/client/views/nodes/formattedText/DashDocCommentView.tsx @@ -36,6 +36,9 @@ export class DashDocCommentView { (this as any).dom = this.dom; } + destroy() { + this.root.unmount(); + } deselectNode() { this.dom.classList.remove('ProseMirror-selectednode'); } diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 63ee7d1f3..722d0cc4f 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -45,6 +45,7 @@ export class DashDocView { ); } destroy() { + this.root.unmount(); // ReactDOM.unmountComponentAtNode(this.dom); } selectNode() {} diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 41b92a3c7..1e7cb6ea5 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -47,7 +47,7 @@ export class DashFieldView { ); } destroy() { - //this.root.unmount(); + this.root.unmount(); } deselectNode() { this.dom.classList.remove('ProseMirror-selectednode'); diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx index 89f2a7c81..714ae458c 100644 --- a/src/client/views/nodes/formattedText/EquationView.tsx +++ b/src/client/views/nodes/formattedText/EquationView.tsx @@ -32,6 +32,7 @@ export class EquationView { _editor: EquationEditor | undefined; setEditor = (editor?: EquationEditor) => (this._editor = editor); destroy() { + this.root.unmount(); // ReactDOM.unmountComponentAtNode(this.dom); } setSelection() { diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx index 1fe6d822b..4e75d374c 100644 --- a/src/client/views/nodes/formattedText/SummaryView.tsx +++ b/src/client/views/nodes/formattedText/SummaryView.tsx @@ -42,6 +42,7 @@ export class SummaryView { className = (visible: boolean) => 'formattedTextBox-summarizer' + (visible ? '' : '-collapsed'); destroy() { + this.root.unmount(); // ReactDOM.unmountComponentAtNode(this.dom); } selectNode() {} diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 14f767595..c1973c4b4 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -349,7 +349,7 @@ export class PresBox extends ViewBoxBaseComponent() { const pannable = [DocumentType.IMG].includes(target.type as any) || (target.type === DocumentType.COL && target._viewType === CollectionViewType.Freeform); const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(target.type as any); const clippable = [DocumentType.COMPARISON].includes(target.type as any); - const dataview = [DocumentType.INK, DocumentType.COL].includes(target.type as any) && target.activeFrame === undefined; + const dataview = [DocumentType.INK, DocumentType.COL, DocumentType.IMG].includes(target.type as any) && target.activeFrame === undefined; const poslayoutview = [DocumentType.COL].includes(target.type as any) && target.activeFrame === undefined; const textview = [DocumentType.RTF].includes(target.type as any) && target.activeFrame === undefined; return { scrollable, pannable, temporal, clippable, dataview, textview, poslayoutview }; @@ -371,7 +371,11 @@ export class PresBox extends ViewBoxBaseComponent() { dv?.brushView?.({ panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] }); } } - if (dataview && activeItem.presData !== undefined) Doc.GetProto(bestTarget)[Doc.LayoutFieldKey(bestTarget)] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; + if (dataview && activeItem.presData !== undefined) { + const fkey = Doc.LayoutFieldKey(bestTarget); + Doc.GetProto(bestTarget)[fkey] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; + bestTarget[fkey + '-useAlt'] = activeItem.presUseAlt; + } if (textview && activeItem.presData !== undefined) Doc.GetProto(bestTarget)[Doc.LayoutFieldKey(bestTarget)] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; if (poslayoutview) { StrListCast(activeItem.presPinLayoutData) @@ -434,7 +438,11 @@ export class PresBox extends ViewBoxBaseComponent() { } if (pinProps?.pinDocContent) { pinDoc.presPinData = scrollable || temporal || pannable || clippable || dataview || textview || poslayoutview || pinProps.activeFrame !== undefined; - if (dataview) pinDoc.presData = targetDoc[Doc.LayoutFieldKey(targetDoc)] instanceof ObjectField ? (targetDoc[Doc.LayoutFieldKey(targetDoc)] as ObjectField)[Copy]() : targetDoc.data; + if (dataview) { + const fkey = Doc.LayoutFieldKey(targetDoc); + pinDoc.presUseAlt = targetDoc[fkey + '-useAlt']; + pinDoc.presData = targetDoc[fkey] instanceof ObjectField ? (targetDoc[fkey] as ObjectField)[Copy]() : targetDoc.data; + } if (textview) pinDoc.presData = targetDoc[Doc.LayoutFieldKey(targetDoc)] instanceof ObjectField ? (targetDoc[Doc.LayoutFieldKey(targetDoc)] as ObjectField)[Copy]() : targetDoc.text; if (scrollable) pinDoc.presPinViewScroll = pinDoc._scrollTop; if (clippable) pinDoc.presPinClipWidth = pinDoc._clipWidth; @@ -541,8 +549,10 @@ export class PresBox extends ViewBoxBaseComponent() { if (activeItem.presPinData || activeItem.presPinView) { clearTimeout(PresBox._navTimer); // targetDoc may or may not be displayed. this gets the first available document (or alias) view that matches targetDoc - const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document; + const bestTargetView = DocumentManager.Instance.getFirstDocumentView(targetDoc); + const bestTarget = bestTargetView?.props.Document; if (bestTarget) PresBox._navTimer = PresBox.restoreTargetDocView(bestTarget, activeItem); + activeItem.presPinAudioPlay && bestTargetView?.docView?.playAnnotation(); } } @@ -1369,6 +1379,10 @@ export class PresBox extends ViewBoxBaseComponent() { {isPresCollection ? null : (
Effects +
+
Play Audio Annotation
+ (activeItem.presPinAudioPlay = !BoolCast(activeItem.presPinAudioPlay))} checked={BoolCast(activeItem.presPinAudioPlay)} /> +
{ -- cgit v1.2.3-70-g09d2 From 19f317bd43a7cc8df0daf1c0642011cc8754e14b Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 15 Nov 2022 12:49:53 -0500 Subject: made InPlace container tool button apply more settings to be more like a template. added followAllLInks flag. added image anchors to save pan zoom. added follow link button bar option for follow all links. added hideDecorations flag and property --- src/client/documents/Documents.ts | 5 ++ src/client/util/LinkFollower.ts | 11 ++-- src/client/views/DocumentButtonBar.scss | 31 ++++++++++- src/client/views/DocumentButtonBar.tsx | 60 +++++++++++++++------- src/client/views/DocumentDecorations.tsx | 19 +++---- src/client/views/PropertiesButtons.tsx | 17 ++++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- src/client/views/linking/LinkEditor.tsx | 1 - src/client/views/nodes/DocumentView.tsx | 3 +- src/client/views/nodes/ImageBox.tsx | 52 ++++++++++++++++--- src/client/views/nodes/trails/PresBox.tsx | 3 +- 11 files changed, 154 insertions(+), 50 deletions(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index e44004f45..8f45802fe 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -257,6 +257,7 @@ export class DocumentOptions { lastFrame?: number; // the last frame of a frame-based collection (e.g., progressive slide) activeFrame?: number; // the active frame of a document in a frame base collection appearFrame?: number; // the frame in which the document appears + viewTransitionTime?: number; // transition duration for view parameters presTransition?: number; //the time taken for the transition TO a document presDuration?: number; //the duration of the slide in presentation view borderRounding?: string; @@ -1047,6 +1048,10 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.MARKER), options?.data, options, id); } + export function ImageanchorDocument(options: DocumentOptions = {}, id?: string) { + return InstanceFromProto(Prototypes.get(DocumentType.MARKER), options?.data, options, id); + } + export function HTMLAnchorDocument(documents: Array, options: DocumentOptions, id?: string) { return InstanceFromProto(Prototypes.get(DocumentType.MARKER), new List(documents), options, id); } diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index c09c9d1c5..c6fc7b372 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -1,6 +1,7 @@ import { action, runInAction } from 'mobx'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; -import { BoolCast, Cast, StrCast } from '../../fields/Types'; +import { BoolCast, Cast, DocCast, StrCast } from '../../fields/Types'; +import { DocumentType } from '../documents/DocumentTypes'; import { DocumentDecorations } from '../views/DocumentDecorations'; import { LightboxView } from '../views/LightboxView'; import { DocumentViewSharedProps, ViewAdjustment } from '../views/nodes/DocumentView'; @@ -76,11 +77,11 @@ export class LinkFollower { const linkDocs = link ? [link] : DocListCast(sourceDoc.links); const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor1 const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor2 - const fwdLinkWithoutTargetView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); - const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0); + const fwdLinkWithoutTargetView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews((d.anchor2 as Doc).type === DocumentType.MARKER ? DocCast((d.anchor2 as Doc).annotationOn) : (d.anchor2 as Doc)).length === 0); + const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews((d.anchor1 as Doc).type === DocumentType.MARKER ? DocCast((d.anchor1 as Doc).annotationOn) : (d.anchor1 as Doc)).length === 0); const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView; - const linkDocList = linkWithoutTargetDoc ? [linkWithoutTargetDoc] : traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs; - const followLinks = sourceDoc.isPushpin ? linkDocList : linkDocList.slice(0, 1); + const linkDocList = linkWithoutTargetDoc && !sourceDoc.followAllLinks ? [linkWithoutTargetDoc] : traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs; + const followLinks = sourceDoc.isPushpin || sourceDoc.followAllLinks ? linkDocList : linkDocList.slice(0, 1); var count = 0; const allFinished = () => ++count === followLinks.length && finished?.(); followLinks.forEach(async linkDoc => { diff --git a/src/client/views/DocumentButtonBar.scss b/src/client/views/DocumentButtonBar.scss index 1e93ba5e2..f9c988fdd 100644 --- a/src/client/views/DocumentButtonBar.scss +++ b/src/client/views/DocumentButtonBar.scss @@ -18,15 +18,44 @@ $linkGap: 3px; cursor: pointer; } +.documentButtonBar-followTypes, .documentButtonBar-pinTypes { position: absolute; - display: flex; + display: none; width: 60px; top: -14px; background: black; height: 20px; align-items: center; } +.documentButtonBar-followTypes { + width: 20px; + display: none; +} +.documentButtonBar-followIcon { + align-items: center; + display: flex; + height: 100%; + &:hover { + background-color: lightblue; + } +} +.documentButtonBar-follow { + &:hover { + .documentButtonBar-followTypes { + display: flex; + } + } +} +.documentButtonBar-pin { + color: white; + &:hover { + .documentButtonBar-pinTypes { + display: flex; + } + } +} + .documentButtonBar-pinIcon { &:hover { background-color: lightblue; diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 36875290e..794b51cc5 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -5,7 +5,7 @@ import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../fields/Doc'; import { RichTextField } from '../../fields/RichTextField'; -import { Cast, NumCast } from '../../fields/Types'; +import { BoolCast, Cast, NumCast } from '../../fields/Types'; import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; @@ -209,21 +209,52 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV ); } + @observable subFollow = ''; @computed get followLinkButton() { const targetDoc = this.view0?.props.Document; + const followBtn = (allDocs: boolean, click: (doc: Doc) => void, isSet: (doc?: Doc) => boolean, icon: IconProp) => { + const tooltip = `Follow ${this.subPin}documents`; + return !tooltip ? null : ( + {tooltip}
}> +
+ (this.subPin = allDocs ? 'All ' : ''))} + onPointerLeave={action(e => (this.subPin = ''))} + onClick={e => { + this.props.views().forEach(dv => click(dv!.rootDoc)); + e.stopPropagation(); + }} + /> +
+ + ); + }; return !targetDoc ? null : ( - {'Set onClick to follow primary link'}
}> + Set onClick to follow primary link
}>
this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, undefined, false)))}> +
+ {followBtn( + true, + (doc: Doc) => (doc.followAllLinks = !doc.followAllLinks), + (doc?: Doc) => (doc?.followAllLinks ? true : false), + 'window-maximize' + )} +
); } - @observable expandPin = false; + @observable subPin = ''; @computed get pinButton() { @@ -264,10 +295,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV return !targetDoc ? null : ( {`Pin Document ${SelectionManager.Views().length > 1 ? 'multiple documents' : ''} to Trail`}
}>
(this.expandPin = true))} - onPointerLeave={action(e => (this.expandPin = false))} + className="documentButtonBar-icon documentButtonBar-pin" onClick={e => { const docs = this.props .views() @@ -276,13 +304,11 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV TabDocView.PinDoc(docs, { pinDocLayout: e.shiftKey, pinDocContent: e.altKey, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); e.stopPropagation(); }}> - {this.expandPin ? ( -
- {pinBtn(true, false, 'window-maximize')} - {pinBtn(false, true, 'address-card')} - {pinBtn(true, true, 'id-card')} -
- ) : null} +
+ {pinBtn(true, false, 'window-maximize')} + {pinBtn(false, true, 'address-card')} + {pinBtn(true, true, 'id-card')} +
@@ -460,9 +486,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV ) : null} { Doc.noviceMode ? null :
{this.templateButton}
- /*
- {this.metadataButton} -
*/ + /*
{this.metadataButton}
*/ } {Doc.noviceMode || !SelectionManager.Views()?.some(v => v.allLinks.length) ? null :
{this.followLinkButton}
}
{this.pinButton}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 3fac137fe..3efb5fb37 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -708,24 +708,25 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P } render() { - const { b, c, r, x, y } = this.Bounds; - const bounds = { b, c, r, x, y }; + const { b, r, x, y } = this.Bounds; + const bounds = { b, r, x, y }; const seldocview = SelectionManager.Views().slice(-1)[0]; if (SnappingManager.GetIsDragging() || bounds.r - bounds.x < 1 || bounds.x === Number.MAX_VALUE || !seldocview || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) { return null; } // hide the decorations if the parent chooses to hide it or if the document itself hides it - const hideResizers = seldocview.props.hideResizeHandles || seldocview.rootDoc.hideResizeHandles || seldocview.rootDoc._isGroup || this._isRounding || this._isRotating; - const hideTitle = seldocview.props.hideDecorationTitle || seldocview.rootDoc.hideDecorationTitle || this._isRounding || this._isRotating; - const hideDocumentButtonBar = seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.hideDocumentButtonBar || this._isRounding || this._isRotating; + const hideDecorations = seldocview.props.hideDecorations || seldocview.rootDoc.hideDecorations; + const hideResizers = hideDecorations || seldocview.props.hideResizeHandles || seldocview.rootDoc.hideResizeHandles || seldocview.rootDoc._isGroup || this._isRounding || this._isRotating; + const hideTitle = hideDecorations || seldocview.props.hideDecorationTitle || seldocview.rootDoc.hideDecorationTitle || this._isRounding || this._isRotating; + const hideDocumentButtonBar = hideDecorations || seldocview.props.hideDocumentButtonBar || seldocview.rootDoc.hideDocumentButtonBar || this._isRounding || this._isRotating; // if multiple documents have been opened at the same time, then don't show open button - const hideOpenButton = + const hideOpenButton =hideDecorations || seldocview.props.hideOpenButton || seldocview.rootDoc.hideOpenButton || SelectionManager.Views().some(docView => docView.props.Document._stayInCollection || docView.props.Document.isGroup || docView.props.Document.hideOpenButton) || this._isRounding || this._isRotating; - const hideDeleteButton = + const hideDeleteButton =hideDecorations || this._isRounding || this._isRotating || seldocview.props.hideDeleteButton || @@ -767,7 +768,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight)); const useLock = bounds.r - bounds.x > 135 && seldocview.props.CollectionFreeFormDocumentView; - const useRotation = seldocview.rootDoc.type !== DocumentType.EQUATION && seldocview.props.CollectionFreeFormDocumentView; // when do we want an object to not rotate? + const useRotation = !hideResizers && seldocview.rootDoc.type !== DocumentType.EQUATION && seldocview.props.CollectionFreeFormDocumentView; // when do we want an object to not rotate? const rotation = NumCast(seldocview.rootDoc._rotation); const resizerScheme = colorScheme ? 'documentDecorations-resizer' + colorScheme : ''; @@ -837,7 +838,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P
{hideDeleteButton ?
: topBtn('close', 'times', undefined, e => this.onCloseClick(true), 'Close')} {hideResizers || hideDeleteButton ?
: topBtn('minimize', 'window-maximize', undefined, e => this.onCloseClick(undefined), 'Minimize')} - {titleArea} + {hideTitle ? null : titleArea} {hideOpenButton ?
: topBtn('open', 'external-link-alt', this.onMaximizeDown, undefined, 'Open in Lightbox (ctrl: as alias, shift: in new collection)')}
{hideResizers ? null : ( diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 5992b3eb9..d27848a90 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -121,15 +121,24 @@ export class PropertiesButtons extends React.Component<{}, {}> { onClick => { SelectionManager.Views().forEach(dv => { const containerDoc = dv.rootDoc; - containerDoc.noShadow = containerDoc.noHighlighting = containerDoc._isLinkButton = containerDoc._fitContentsToBox = containerDoc._forceActive = containerDoc._isInPlaceContainer = !containerDoc._isInPlaceContainer; + containerDoc.followAllLinks = + containerDoc.noShadow = + containerDoc.noHighlighting = + containerDoc._isLinkButton = + containerDoc._fitContentsToBox = + containerDoc._forceActive = + containerDoc._isInPlaceContainer = + !containerDoc._isInPlaceContainer; containerDoc.followLinkLocation = containerDoc._isInPlaceContainer ? 'inPlace' : undefined; containerDoc._xPadding = containerDoc._yPadding = containerDoc._isInPlaceContainer ? 10 : undefined; const menuDoc = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]).lastElement(); if (menuDoc) { - menuDoc._forceActive = menuDoc._fitContentsToBox = menuDoc._isLinkButton = menuDoc._noShadow = menuDoc.noHighlighting = containerDoc._isInPlaceContainer; - DocUtils.MakeLink({ doc: dv.rootDoc }, { doc: menuDoc }, 'back link to container'); + menuDoc.hideDecorations = menuDoc._forceActive = menuDoc._fitContentsToBox = menuDoc._isLinkButton = menuDoc._noShadow = menuDoc.noHighlighting = containerDoc._isInPlaceContainer; + if (!dv.allLinks.find(link => link.anchor1 === menuDoc || link.anchor2 === menuDoc)) { + DocUtils.MakeLink({ doc: dv.rootDoc }, { doc: menuDoc }, 'back link to container'); + } DocListCast(menuDoc[Doc.LayoutFieldKey(menuDoc)]).forEach(menuItem => { - menuItem._isLinkButton = true; + menuItem.followAllLinks = menuItem._isLinkButton = true; menuItem._followLinkLocation = 'inPlace'; }); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index e24b116d0..8fe5ad63f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1157,7 +1157,7 @@ export class CollectionFreeFormView extends CollectionSubView { @computed get editAudioFollow() { //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS - console.log('AudioFollow:' + this.audioFollow); return (
Play Target Audio:
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 7accd49a4..76d6d3532 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -166,7 +166,8 @@ export interface DocumentViewSharedProps { // these props are specific to DocuentViews export interface DocumentViewProps extends DocumentViewSharedProps { // properties specific to DocumentViews but not to FieldView - hideResizeHandles?: boolean; // whether to suppress DocumentDecorations when this document is selected + hideDecorations?: boolean; // whether to suppress all DocumentDecorations when doc is selected + hideResizeHandles?: boolean; // whether to suppress resized handles on doc decorations when this document is selected hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings hideDocumentButtonBar?: boolean; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index d0df41023..d67481b22 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -14,7 +14,7 @@ import { TraceMobx } from '../../../fields/util'; import { emptyFunction, OmitKeys, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices'; -import { DocUtils } from '../../documents/Documents'; +import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { Networking } from '../../Network'; import { DragManager } from '../../util/DragManager'; @@ -49,21 +49,58 @@ export class ImageBox extends ViewBoxAnnotatableComponent) => Opt = () => undefined; @observable _curSuffix = ''; @observable _uploadIcon = uploadIcons.idle; + @observable _focusViewScale: number | undefined = 1; + @observable _focusPanX: number | undefined = 0; + @observable _focusPanY: number | undefined = 0; + get viewScale() { + return this._focusViewScale || StrCast(this.layoutDoc._viewScale); + } + get panX() { + return this._focusPanX || StrCast(this.layoutDoc._panX); + } + get panY() { + return this._focusPanY || StrCast(this.layoutDoc._panY); + } protected createDropTarget = (ele: HTMLDivElement) => { this._dropDisposer?.(); ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.props.Document)); }; - setViewSpec = (anchor: Doc, preview: boolean) => {}; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document + + @action + setViewSpec = (anchor: Doc, preview: boolean) => { + if (preview && anchor._viewScale !== undefined) { + this._focusViewScale = Cast(anchor._viewScale, 'number', null); + this._focusPanX = Cast(anchor._panX, 'number', null); + this._focusPanY = Cast(anchor._panX, 'number', null); + } else if (anchor._viewScale !== undefined) { + const smoothTime = NumCast(anchor.viewTransitionTime); + this.layoutDoc.viewTransition = `all ${smoothTime}ms`; + this.layoutDoc._panX = NumCast(anchor._panX, NumCast(this.layoutDoc._panY)); + this.layoutDoc._panY = NumCast(anchor._panY, NumCast(this.layoutDoc._panX)); + this.layoutDoc._viewScale = NumCast(anchor._viewScale, NumCast(this.layoutDoc._viewScale)); + if (anchor.type === DocumentType.MARKER) { + this.dataDoc[this.annotationKey] = new List(DocListCast(anchor._annotations)); + } + clearTimeout(this._transitioning); + this._transitioning = setTimeout(() => (this.layoutDoc.viewTransition = undefined), smoothTime); + } + }; // 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 = () => { - const anchor = this._getAnchor?.(this._savedAnnotations); - anchor && this.addDocument(anchor); - return anchor ?? this.rootDoc; + const zoomedAnchor = () => Docs.Create.ImageanchorDocument({ viewTransitionTime: 1000, _viewScale: NumCast(this.layoutDoc._viewScale), _panX: NumCast(this.layoutDoc._panX), _panY: NumCast(this.layoutDoc._panY) }); + const anchor = this._getAnchor?.(this._savedAnnotations) ?? (this.layoutDoc.viewScale ? zoomedAnchor() : undefined); + if (anchor) { + anchor._annotations = new List(DocListCast(this.dataDoc[this.annotationKey])); + this.addDocument(anchor); + return anchor; + } + return this.rootDoc; }; componentDidMount() { @@ -90,6 +127,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent disposer?.()); } @@ -283,9 +321,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent { this._uploadIcon = idle; - if (data) { - dataDoc[this.fieldKey] = data; - } + data && (dataDoc[this.fieldKey] = data); }), 2000 ); diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index b495a9399..8d805c663 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -509,8 +509,7 @@ export class PresBox extends ViewBoxBaseComponent() { static NavigateToTarget(targetDoc: Doc, activeItem: Doc, openInTab: any, srcContext: Doc, finished?: () => void) { if ((activeItem.presPinLayout || activeItem.presPinView) && DocCast(targetDoc.context)?._currentFrame === undefined) { const transTime = NumCast(activeItem.presTransition, 500); - const presTransitionTime = `all ${transTime}ms`; - targetDoc._dataTransition = presTransitionTime; + targetDoc._dataTransition = `all ${transTime}ms`; targetDoc.x = NumCast(activeItem.presX, NumCast(targetDoc.x)); targetDoc.y = NumCast(activeItem.presY, NumCast(targetDoc.y)); targetDoc.rotation = NumCast(activeItem.presRot, NumCast(targetDoc.rotation)); -- cgit v1.2.3-70-g09d2 From 51a85bae74c3d8ccddb57f6063e93b237cdf1ebd Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 15 Nov 2022 14:24:11 -0500 Subject: added playback of audio annotation on inPlace container. fixed image anchors to be marked un-rendered so link lines show up. --- src/client/util/DocumentManager.ts | 2 +- src/client/views/PropertiesButtons.tsx | 2 +- src/client/views/nodes/DocumentView.scss | 6 +++--- src/client/views/nodes/ImageBox.tsx | 11 +++++++---- 4 files changed, 12 insertions(+), 9 deletions(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index ef0a339ce..519f75dbd 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -252,7 +252,7 @@ export class DocumentManager { } else { if (!targetDocContext) { // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default - createViewFunc(Doc.BrushDoc(targetDoc), finished); // bcz: should we use this?: Doc.MakeAlias(targetDoc))); + createViewFunc(Doc.BrushDoc(targetDoc), () => focusAndFinish(true)); // bcz: should we use this?: Doc.MakeAlias(targetDoc))); } else { // otherwise try to get a view of the context of the target if (targetDocContextView) { diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index d27848a90..656a56a15 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -138,7 +138,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { DocUtils.MakeLink({ doc: dv.rootDoc }, { doc: menuDoc }, 'back link to container'); } DocListCast(menuDoc[Doc.LayoutFieldKey(menuDoc)]).forEach(menuItem => { - menuItem.followAllLinks = menuItem._isLinkButton = true; + menuItem.followLinkAudio = menuItem.followAllLinks = menuItem._isLinkButton = true; menuItem._followLinkLocation = 'inPlace'; }); } diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index e437d295e..01188d3fa 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -198,9 +198,9 @@ > .documentView-titleWrapper-hover { display: inline-block; } - > .documentView-contentsView { - opacity: 0.5; - } + // > .documentView-contentsView { + // opacity: 0.5; + // } > .documentView-captionWrapper { opacity: 1; } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index d67481b22..8ecd8104a 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -84,6 +84,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent(DocListCast(anchor._annotations)); } @@ -93,9 +94,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent { - const zoomedAnchor = () => Docs.Create.ImageanchorDocument({ viewTransitionTime: 1000, _viewScale: NumCast(this.layoutDoc._viewScale), _panX: NumCast(this.layoutDoc._panX), _panY: NumCast(this.layoutDoc._panY) }); - const anchor = this._getAnchor?.(this._savedAnnotations) ?? (this.layoutDoc.viewScale ? zoomedAnchor() : undefined); + const anchor = + this._getAnchor?.(this._savedAnnotations) ?? // use marquee anchor, otherwise, save zoom/pan as anchor + Docs.Create.ImageanchorDocument({ viewTransitionTime: 1000, unrendered: true, annotationOn: this.rootDoc, _viewScale: NumCast(this.layoutDoc._viewScale, 1), _panX: NumCast(this.layoutDoc._panX), _panY: NumCast(this.layoutDoc._panY) }); if (anchor) { + anchor._useAlt = Cast(this.layoutDoc[this.fieldKey + '-useAlt'], 'boolean', null); anchor._annotations = new List(DocListCast(this.dataDoc[this.annotationKey])); this.addDocument(anchor); return anchor; @@ -222,7 +225,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent Utils.CopyText(this.choosePath(field.url)), icon: 'expand-arrows-alt' }); if (!Doc.noviceMode) { funcs.push({ description: 'Export to Google Photos', event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: 'caret-square-right' }); @@ -353,7 +356,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent Date: Tue, 15 Nov 2022 15:47:24 -0500 Subject: added canEmbed prop so collection can allow its items to be dragged out (eg., pileView). made imageBox viewSpec transition time customizable --- src/client/util/DocumentManager.ts | 4 +- .../views/collections/CollectionPileView.tsx | 135 ++++++++++++--------- src/client/views/collections/CollectionView.tsx | 1 + .../collectionFreeForm/CollectionFreeFormView.tsx | 1 + src/client/views/nodes/DocumentView.tsx | 12 +- src/client/views/nodes/ImageBox.scss | 18 +-- src/client/views/nodes/ImageBox.tsx | 4 +- 7 files changed, 91 insertions(+), 84 deletions(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 519f75dbd..9336717c0 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -190,7 +190,7 @@ export class DocumentManager { finalTargetDoc.hidden && (finalTargetDoc.hidden = undefined); !noSelect && docView?.select(false); if (originatingDoc?.followLinkAudio) { - const anno = Cast(finalTargetDoc[Doc.LayoutFieldKey(finalTargetDoc) + '-audioAnnotations'], listSpec(AudioField), null).lastElement(); + const anno = Cast(finalTargetDoc[Doc.LayoutFieldKey(finalTargetDoc) + '-audioAnnotations'], listSpec(AudioField), null)?.lastElement(); if (anno) { if (anno instanceof AudioField) { new Howl({ @@ -326,7 +326,7 @@ export class DocumentManager { docContext, linkDoc, true /* if target not found, get rid of context just created */, - undefined, + originatingDoc, finished, originalTarget ) diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index 4489601db..38e240ac6 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -1,33 +1,35 @@ -import { action, computed, IReactionDisposer, reaction } from "mobx"; -import { observer } from "mobx-react"; -import { Doc, HeightSym, WidthSym } from "../../../fields/Doc"; -import { NumCast, StrCast } from "../../../fields/Types"; -import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from "../../../Utils"; -import { DocUtils } from "../../documents/Documents"; -import { SelectionManager } from "../../util/SelectionManager"; -import { SnappingManager } from "../../util/SnappingManager"; -import { undoBatch, UndoManager } from "../../util/UndoManager"; -import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; -import "./CollectionPileView.scss"; -import { CollectionSubView } from "./CollectionSubView"; -import React = require("react"); -import { ScriptField } from "../../../fields/ScriptField"; +import { action, computed, IReactionDisposer, reaction } from 'mobx'; +import { observer } from 'mobx-react'; +import { Doc, HeightSym, WidthSym } from '../../../fields/Doc'; +import { NumCast, StrCast } from '../../../fields/Types'; +import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils'; +import { DocUtils } from '../../documents/Documents'; +import { SelectionManager } from '../../util/SelectionManager'; +import { SnappingManager } from '../../util/SnappingManager'; +import { undoBatch, UndoManager } from '../../util/UndoManager'; +import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; +import './CollectionPileView.scss'; +import { CollectionSubView } from './CollectionSubView'; +import React = require('react'); +import { ScriptField } from '../../../fields/ScriptField'; @observer export class CollectionPileView extends CollectionSubView() { - _originalChrome: any = ""; + _originalChrome: any = ''; _disposers: { [name: string]: IReactionDisposer } = {}; componentDidMount() { - if (this.layoutEngine() !== "pass" && this.layoutEngine() !== "starburst") { - this.Document._pileLayoutEngine = "pass"; + if (this.layoutEngine() !== 'pass' && this.layoutEngine() !== 'starburst') { + this.Document._pileLayoutEngine = 'pass'; } this._originalChrome = this.layoutDoc._chromeHidden; this.layoutDoc._chromeHidden = true; - // pileups are designed to go away when they are empty. - this._disposers.selected = reaction(() => this.childDocs.length, - (num) => !num && this.props.ContainingCollectionView?.removeDocument(this.props.Document)); + // pileups are designed to go away when they are empty. + this._disposers.selected = reaction( + () => this.childDocs.length, + num => !num && this.props.ContainingCollectionView?.removeDocument(this.props.Document) + ); } componentWillUnmount() { this.layoutDoc._chromeHidden = this._originalChrome; @@ -38,32 +40,36 @@ export class CollectionPileView extends CollectionSubView() { @undoBatch addPileDoc = (doc: Doc | Doc[]) => { - (doc instanceof Doc ? [doc] : doc).map((d) => DocUtils.iconify(d)); + (doc instanceof Doc ? [doc] : doc).map(d => DocUtils.iconify(d)); return this.props.addDocument?.(doc) || false; - } + }; @undoBatch removePileDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { - (doc instanceof Doc ? [doc] : doc).map(undoBatch((d) => Doc.deiconifyView(d))); + (doc instanceof Doc ? [doc] : doc).map(undoBatch(d => Doc.deiconifyView(d))); return this.props.moveDocument?.(doc, targetCollection, addDoc) || false; - } + }; toggleIcon = () => { - return ScriptField.MakeScript("documentView.iconify()", { documentView: "any" }); - } + return ScriptField.MakeScript('documentView.iconify()', { documentView: 'any' }); + }; // returns the contents of the pileup in a CollectionFreeFormView @computed get contents() { - const isStarburst = this.layoutEngine() === "starburst"; - return
- -
; + const isStarburst = this.layoutEngine() === 'starburst'; + return ( +
+ +
+ ); } // toggles the pileup between starburst to compact @@ -99,27 +105,35 @@ export class CollectionPileView extends CollectionSubView() { pointerDown = (e: React.PointerEvent) => { let dist = 0; SnappingManager.SetIsDragging(true); - setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { - if (this.layoutEngine() === "pass" && this.childDocs.length && e.shiftKey) { - dist += Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]); - if (dist > 100) { - if (!this._undoBatch) { - this._undoBatch = UndoManager.StartBatch("layout pile"); + setupMoveUpEvents( + this, + e, + (e: PointerEvent, down: number[], delta: number[]) => { + if (this.layoutEngine() === 'pass' && this.childDocs.length && e.shiftKey) { + dist += Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]); + if (dist > 100) { + if (!this._undoBatch) { + this._undoBatch = UndoManager.StartBatch('layout pile'); + } + const doc = this.childDocs[0]; + doc.x = e.clientX; + doc.y = e.clientY; + this.props.addDocTab(doc, 'inParent') && (this.props.removeDocument?.(doc) || false); + dist = 0; } - const doc = this.childDocs[0]; - doc.x = e.clientX; - doc.y = e.clientY; - this.props.addDocTab(doc, "inParent") && (this.props.removeDocument?.(doc) || false); - dist = 0; } - } - return false; - }, () => { - this._undoBatch?.end(); - this._undoBatch = undefined; - SnappingManager.SetIsDragging(false); - }, emptyFunction, e.shiftKey && this.layoutEngine() === "pass", this.layoutEngine() === "pass" && e.shiftKey); // this sets _doubleTap - } + return false; + }, + () => { + this._undoBatch?.end(); + this._undoBatch = undefined; + SnappingManager.SetIsDragging(false); + }, + emptyFunction, + e.shiftKey && this.layoutEngine() === 'pass', + this.layoutEngine() === 'pass' && e.shiftKey + ); // this sets _doubleTap + }; // onClick for toggling the pileup view @undoBatch @@ -130,12 +144,13 @@ export class CollectionPileView extends CollectionSubView() { this.toggleStarburst(); e.stopPropagation(); } - } + }; render() { - return
- {this.contents} -
; + return ( +
+ {this.contents} +
+ ); } } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 9f63a11aa..625d4e9e5 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -54,6 +54,7 @@ interface CollectionViewProps_ extends FieldViewProps { childHideDecorationTitle?: () => boolean; childHideResizeHandles?: () => boolean; childLayoutTemplate?: () => Doc | undefined; // specify a layout Doc template to use for children of the collection + childCanEmbedOnDrag?: boolean; childXPadding?: number; childYPadding?: number; childLayoutString?: string; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 8fe5ad63f..932bd789d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1314,6 +1314,7 @@ export class CollectionFreeFormView extends CollectionSubView void; ScreenToLocalTransform: () => Transform; bringToFront: (doc: Doc, sendToBack?: boolean) => void; + canEmbedOnDrag?: boolean; xPadding?: number; yPadding?: number; dropAction?: dropActionType; @@ -505,6 +506,7 @@ export class DocumentViewInternal extends DocComponent this._titleRef.current?.setIsFocused(true), 0); + if (!this._titleRef.current) setTimeout(() => this._titleRef.current?.setIsFocused(true)); else if (!this._titleRef.current.setIsFocused(true)) { // if focus didn't change, focus on interior text... this._titleRef.current?.setIsFocused(false); @@ -540,12 +542,10 @@ export class DocumentViewInternal extends DocComponent key.startsWith(ViewSpecPrefix)) - .forEach(spec => { - this.layoutDoc[spec.replace(ViewSpecPrefix, '')] = (field => (field instanceof ObjectField ? ObjectField.MakeCopy(field) : field))(anchor[spec]); - }); - // after a timeout, the right _componentView should have been created, so call it to update its view spec values + .forEach(spec => (this.layoutDoc[spec.replace(ViewSpecPrefix, '')] = (field => (field instanceof ObjectField ? ObjectField.MakeCopy(field) : field))(anchor[spec]))); + // after a render the general viewSpec should have created the right _componentView, so after a timeout, call the componentview to update its specific view specs setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false)); - const focusSpeed = this._componentView?.scrollFocus?.(anchor, options?.instant === false || !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here + const focusSpeed = this._componentView?.scrollFocus?.(anchor, options?.instant === false || !LinkDocPreview.LinkInfo); const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus?.(true) ?? ViewAdjustment.doNothing; this.props.focus(options?.docTransform ? anchor : this.rootDoc, { ...options, diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index cd2b23f02..6359a9491 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -5,7 +5,6 @@ position: relative; transform-origin: top left; - .imageBox-annotationLayer { position: absolute; transform-origin: left top; @@ -95,7 +94,6 @@ height: 100%; } - .imageBox-fader { position: relative; width: 100%; @@ -103,7 +101,8 @@ display: flex; height: 100%; - .imageBox-fadeBlocker, .imageBox-fadeBlocker-hover{ + .imageBox-fadeBlocker, + .imageBox-fadeBlocker-hover { width: 100%; height: 100%; position: absolute; @@ -126,17 +125,6 @@ left: 0; } -.imageBox-fadeBlocker { - -webkit-transition: opacity 1s ease-in-out; - -moz-transition: opacity 1s ease-in-out; - -o-transition: opacity 1s ease-in-out; - transition: opacity 1s ease-in-out; -} - .imageBox-fadeBlocker-hover { - -webkit-transition: opacity 1s ease-in-out; - -moz-transition: opacity 1s ease-in-out; - -o-transition: opacity 1s ease-in-out; - transition: opacity 1s ease-in-out; opacity: 0; -} \ No newline at end of file +} diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 8ecd8104a..76ba7765c 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -377,7 +377,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent {fadepath === srcpath ? null : ( -
+
)} -- cgit v1.2.3-70-g09d2 From ae324ff50865929be836edf3bbf129207638a9c9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 16 Nov 2022 16:26:31 -0500 Subject: big changes to make link following use the same code as pinning docs for trails. --- src/client/documents/Documents.ts | 1 + src/client/util/DocumentManager.ts | 30 ++--- src/client/util/LinkFollower.ts | 7 +- src/client/views/DocumentButtonBar.tsx | 6 +- src/client/views/GlobalKeyHandler.ts | 4 +- src/client/views/MarqueeAnnotator.tsx | 1 + src/client/views/StyleProvider.tsx | 2 +- .../collections/CollectionStackedTimeline.tsx | 2 +- src/client/views/collections/TabDocView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 18 ++- src/client/views/nodes/ComparisonBox.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 8 +- src/client/views/nodes/ImageBox.tsx | 60 ++++------ src/client/views/nodes/PDFBox.tsx | 16 ++- .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/client/views/nodes/trails/PresBox.tsx | 131 +++++++++++++-------- src/client/views/pdf/Annotation.tsx | 2 +- src/client/views/pdf/PDFViewer.tsx | 11 +- 18 files changed, 178 insertions(+), 129 deletions(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 8f45802fe..e68b9e27b 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -237,6 +237,7 @@ export class DocumentOptions { childContextMenuScripts?: List; childContextMenuLabels?: List; childContextMenuIcons?: List; + followLinkZoom?: boolean; // whether to zoom to the target of a link hideLinkButton?: boolean; // whether the blue link counter button should be hidden hideDecorationTitle?: boolean; hideOpenButton?: boolean; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 9336717c0..a60c1ed6b 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -151,6 +151,21 @@ export class DocumentManager { return toReturn; } + static playAudioAnno(doc: Doc) { + const anno = Cast(doc[Doc.LayoutFieldKey(doc) + '-audioAnnotations'], listSpec(AudioField), null)?.lastElement(); + if (anno) { + if (anno instanceof AudioField) { + new Howl({ + src: [anno.url.href], + format: ['mp3'], + autoplay: true, + loop: false, + volume: 0.5, + }); + } + } + } + static addView = (doc: Doc, finished?: () => void) => { CollectionDockingView.AddSplit(doc, 'right'); finished?.(); @@ -189,20 +204,6 @@ export class DocumentManager { } else { finalTargetDoc.hidden && (finalTargetDoc.hidden = undefined); !noSelect && docView?.select(false); - if (originatingDoc?.followLinkAudio) { - const anno = Cast(finalTargetDoc[Doc.LayoutFieldKey(finalTargetDoc) + '-audioAnnotations'], listSpec(AudioField), null)?.lastElement(); - if (anno) { - if (anno instanceof AudioField) { - new Howl({ - src: [anno.url.href], - format: ['mp3'], - autoplay: true, - loop: false, - volume: 0.5, - }); - } - } - } } finished?.(); }; @@ -233,6 +234,7 @@ export class DocumentManager { } if (focusView) { !noSelect && Doc.linkFollowHighlight(focusView.rootDoc); //TODO:glr make this a setting in PresBox + if (originatingDoc?.followLinkAudio) DocumentManager.playAudioAnno(focusView.rootDoc); const doFocus = (forceDidFocus: boolean) => focusView.focus(originalTarget ?? targetDoc, { originalTarget, diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index c6fc7b372..68716a207 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -6,6 +6,7 @@ import { DocumentDecorations } from '../views/DocumentDecorations'; import { LightboxView } from '../views/LightboxView'; import { DocumentViewSharedProps, ViewAdjustment } from '../views/nodes/DocumentView'; import { DocumentManager } from './DocumentManager'; +import { LinkManager } from './LinkManager'; import { UndoManager } from './UndoManager'; type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void; @@ -25,7 +26,7 @@ export class LinkFollower { // follows a link - if the target is on screen, it highlights/pans to it. // if the target isn't onscreen, then it will open up the target in the lightbox, or in place // depending on the followLinkLocation property of the source (or the link itself as a fallback); - public static FollowLink = (linkDoc: Opt, sourceDoc: Doc, docViewProps: DocumentViewSharedProps, altKey: boolean, zoom: boolean = false) => { + public static FollowLink = (linkDoc: Opt, sourceDoc: Doc, docViewProps: DocumentViewSharedProps, altKey: boolean) => { const batch = UndoManager.StartBatch('follow link click'); // open up target if it's not already in view ... const createViewFunc = (doc: Doc, followLoc: string, finished?: Opt<() => void>) => { @@ -63,7 +64,6 @@ export class LinkFollower { linkDoc, sourceDoc, createViewFunc, - BoolCast(sourceDoc.followLinkZoom, zoom), docViewProps.ContainingCollectionDoc, action(() => { batch.end(); @@ -73,7 +73,7 @@ export class LinkFollower { ); }; - public static traverseLink(link: Opt, sourceDoc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) { + public static traverseLink(link: Opt, sourceDoc: Doc, createViewFunc: CreateViewFunc, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) { const linkDocs = link ? [link] : DocListCast(sourceDoc.links); const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor1 const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor2 @@ -96,6 +96,7 @@ export class LinkFollower { : linkDoc.anchor1 ) as Doc; if (target) { + const zoom = BoolCast(LinkManager.getOppositeAnchor(linkDoc, target)?.followLinkZoom, false); if (target.TourMap) { const fieldKey = Doc.LayoutFieldKey(target); const tour = DocListCast(target[fieldKey]).reverse(); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 794b51cc5..681349ccf 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -284,7 +284,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV .views() .filter(v => v) .map(dv => dv!.rootDoc); - TabDocView.PinDoc(docs, { pinDocLayout, pinDocContent, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); + TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout, pinDocContent, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); e.stopPropagation(); }} /> @@ -301,7 +301,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV .views() .filter(v => v) .map(dv => dv!.rootDoc); - TabDocView.PinDoc(docs, { pinDocLayout: e.shiftKey, pinDocContent: e.altKey, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); + TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: e.shiftKey, pinDocContent: e.altKey, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); e.stopPropagation(); }}>
@@ -488,7 +488,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV Doc.noviceMode ? null :
{this.templateButton}
/*
{this.metadataButton}
*/ } - {Doc.noviceMode || !SelectionManager.Views()?.some(v => v.allLinks.length) ? null :
{this.followLinkButton}
} + {!SelectionManager.Views()?.some(v => v.allLinks.length) ? null :
{this.followLinkButton}
}
{this.pinButton}
{this.recordButton}
{!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null :
{this.shareButton}
} diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 5a6caf995..4890d9624 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -6,7 +6,7 @@ import { Id } from '../../fields/FieldSymbols'; import { InkTool } from '../../fields/InkField'; import { List } from '../../fields/List'; import { ScriptField } from '../../fields/ScriptField'; -import { Cast, PromiseValue } from '../../fields/Types'; +import { Cast, DocCast, PromiseValue } from '../../fields/Types'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { DocServer } from '../DocServer'; import { DocumentType } from '../documents/DocumentTypes'; @@ -245,7 +245,7 @@ export class KeyManager { if (SelectionManager.Views().length === 1 && SelectionManager.Views()[0].ComponentView?.search) { SelectionManager.Views()[0].ComponentView?.search?.('', false, false); } else { - const searchBtn = Doc.MySearcher; + const searchBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MySearcher); if (searchBtn) { MainView.Instance.selectMenu(searchBtn); } diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index c0dd62a05..1162cde50 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -93,6 +93,7 @@ export class MarqueeAnnotator extends React.Component { dragComplete: e => { if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) { e.annoDragData.linkSourceDoc.isPushpin = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc; + e.annoDragData.linkSourceDoc.followLinkZoom = false; } }, }); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index a04f4a4f4..8ee5ebdb6 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -236,7 +236,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (darkScheme() ? Colors.BLACK : 'linear-gradient(#065fff, #85c1f9)')); diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 7bf798656..39ae470b6 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -748,7 +748,7 @@ class StackedTimelineAnchor extends React.Component time < NumCast(this.props.mark[this.props.endTag]) && this._lastTimecode < NumCast(this.props.mark[this.props.startTag]) - 1e-5 ) { - LinkFollower.FollowLink(undefined, this.props.mark, this.props as any as DocumentViewProps, false, true); + LinkFollower.FollowLink(undefined, this.props.mark, this.props as any as DocumentViewProps, false); } this._lastTimecode = time; } diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 1a9006356..e21649648 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -229,7 +229,7 @@ export class TabDocView extends React.Component { * Adds a document to the presentation view **/ @action - public static PinDoc(docs: Doc | Doc[], pinProps?: PinProps) { + public static PinDoc(docs: Doc | Doc[], pinProps: PinProps) { const docList = docs instanceof Doc ? [docs] : docs; const batch = UndoManager.StartBatch('pinning doc'); @@ -271,7 +271,7 @@ export class TabDocView extends React.Component { pinDoc.presStartTime = NumCast(doc.clipStart); pinDoc.presEndTime = NumCast(doc.clipEnd, duration); } - PresBox.pinDocView(pinDoc, pinProps, doc); + PresBox.pinDocView(pinDoc, pinProps.pinDocContent ? { ...pinProps, pinData: PresBox.pinDataTypes(doc) } : pinProps, doc); pinDoc.onClick = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)'); Doc.AddDocToList(curPres, 'data', pinDoc, presSelected); //save position diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 932bd789d..8a97797c7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1530,11 +1530,27 @@ export class CollectionFreeFormView extends CollectionSubView { + let focusSpeed: Opt; + PresBox.restoreTargetDocView( + this.rootDoc, // + { pinDocLayout: BoolCast(anchor.presPinDocLayout) }, + anchor, + (focusSpeed = !smooth ? 0 : NumCast(anchor.presTransition)), + { + pannable: anchor.presPinData ? true : false, + } + ); + return focusSpeed; + }; // 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 = () => { if (this.props.Document.annotationOn) { return this.rootDoc; } - const anchor = Docs.Create.TextanchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), annotationOn: this.rootDoc }); + const anchor = Docs.Create.TextanchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), presTransition: 500, annotationOn: this.rootDoc }); + PresBox.pinDocView(anchor, { pinData: { pannable: true } }, this.rootDoc); const proto = Doc.GetProto(anchor); proto[ViewSpecPrefix + '_viewType'] = this.layoutDoc._viewType; proto.docFilters = ObjectField.MakeCopy(this.layoutDoc.docFilters as ObjectField) || new List([]); diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index d74da9748..dd03b9b99 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -103,7 +103,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent { - whichDoc !== targetDoc && r?.focus(whichDoc, {}); + whichDoc !== targetDoc && r?.focus(whichDoc, { instant: true }); }} {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit} isContentActive={returnFalse} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 84cacd919..dc468cf89 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -51,7 +51,7 @@ import { LinkAnchorBox } from './LinkAnchorBox'; import { LinkDocPreview } from './LinkDocPreview'; import { RadialMenu } from './RadialMenu'; import { ScriptingBox } from './ScriptingBox'; -import { PresBox } from './trails/PresBox'; +import { PinProps, PresBox } from './trails/PresBox'; import React = require('react'); const { Howl } = require('howler'); @@ -144,7 +144,7 @@ export interface DocumentViewSharedProps { addDocument?: (doc: Doc | Doc[]) => boolean; removeDocument?: (doc: Doc | Doc[]) => boolean; moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; - pinToPres: (document: Doc) => void; + pinToPres: (document: Doc, pinProps: PinProps) => void; ScreenToLocalTransform: () => Transform; bringToFront: (doc: Doc, sendToBack?: boolean) => void; canEmbedOnDrag?: boolean; @@ -486,7 +486,7 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(this.props.Document, "add:right"), icon: "trash", selected: -1 }); - RadialMenu.Instance.addItem({ description: 'Pin', event: () => this.props.pinToPres(this.props.Document), icon: 'map-pin', selected: -1 }); + RadialMenu.Instance.addItem({ description: 'Pin', event: () => this.props.pinToPres(this.props.Document, {}), icon: 'map-pin', selected: -1 }); RadialMenu.Instance.addItem({ description: 'Open', event: () => MobileInterface.Instance.handleClick(this.props.Document), icon: 'trash', selected: -1 }); SelectionManager.DeselectAll(); @@ -545,7 +545,7 @@ export class DocumentViewInternal extends DocComponent (this.layoutDoc[spec.replace(ViewSpecPrefix, '')] = (field => (field instanceof ObjectField ? ObjectField.MakeCopy(field) : field))(anchor[spec]))); // after a render the general viewSpec should have created the right _componentView, so after a timeout, call the componentview to update its specific view specs setTimeout(() => this._componentView?.setViewSpec?.(anchor, LinkDocPreview.LinkInfo ? true : false)); - const focusSpeed = this._componentView?.scrollFocus?.(anchor, options?.instant === false || !LinkDocPreview.LinkInfo); + const focusSpeed = this._componentView?.scrollFocus?.(anchor, options?.instant !== true && !LinkDocPreview.LinkInfo); const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus?.(true) ?? ViewAdjustment.doNothing; this.props.focus(options?.docTransform ? anchor : this.rootDoc, { ...options, diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 76ba7765c..2e594d96a 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -30,6 +30,8 @@ import { FaceRectangles } from './FaceRectangles'; import { FieldView, FieldViewProps } from './FieldView'; import './ImageBox.scss'; import React = require('react'); +import { PresBox } from './trails'; +import { DocumentViewProps } from './DocumentView'; export const pageSchema = createSchema({ googlePhotosUrl: 'string', @@ -49,23 +51,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent) => Opt = () => undefined; @observable _curSuffix = ''; @observable _uploadIcon = uploadIcons.idle; - @observable _focusViewScale: number | undefined = 1; - @observable _focusPanX: number | undefined = 0; - @observable _focusPanY: number | undefined = 0; - get viewScale() { - return this._focusViewScale || StrCast(this.layoutDoc._viewScale); - } - get panX() { - return this._focusPanX || StrCast(this.layoutDoc._panX); - } - get panY() { - return this._focusPanY || StrCast(this.layoutDoc._panY); - } protected createDropTarget = (ele: HTMLDivElement) => { this._dropDisposer?.(); @@ -73,33 +62,30 @@ export class ImageBox extends ViewBoxAnnotatableComponent { - if (preview && anchor._viewScale !== undefined) { - this._focusViewScale = Cast(anchor._viewScale, 'number', null); - this._focusPanX = Cast(anchor._panX, 'number', null); - this._focusPanY = Cast(anchor._panX, 'number', null); - } else if (anchor._viewScale !== undefined) { - const smoothTime = NumCast(anchor.viewTransitionTime); - this.layoutDoc.viewTransition = `all ${smoothTime}ms`; - this.layoutDoc._panX = NumCast(anchor._panX, NumCast(this.layoutDoc._panY)); - this.layoutDoc._panY = NumCast(anchor._panY, NumCast(this.layoutDoc._panX)); - this.layoutDoc._viewScale = NumCast(anchor._viewScale, NumCast(this.layoutDoc._viewScale)); - this.layoutDoc[this.fieldKey + '-useAlt'] = Cast(anchor._useAlt, 'boolean', null); - if (anchor.type === DocumentType.MARKER) { - this.dataDoc[this.annotationKey] = new List(DocListCast(anchor._annotations)); - } - clearTimeout(this._transitioning); - this._transitioning = setTimeout(() => (this.layoutDoc.viewTransition = undefined), smoothTime); - } + scrollFocus = (anchor: Doc, smooth: boolean) => { + let focusSpeed: Opt; + PresBox.restoreTargetDocView( + this.rootDoc, // + { pinDocLayout: BoolCast(anchor.presPinDocLayout) }, + anchor, + (focusSpeed = !smooth ? 0 : NumCast(anchor.presTransition)), + !anchor.presPinData + ? {} + : { + pannable: true, + dataannos: anchor.presAnnotations !== undefined, + dataview: true, + } + ); + return focusSpeed; }; // 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 = () => { const anchor = this._getAnchor?.(this._savedAnnotations) ?? // use marquee anchor, otherwise, save zoom/pan as anchor - Docs.Create.ImageanchorDocument({ viewTransitionTime: 1000, unrendered: true, annotationOn: this.rootDoc, _viewScale: NumCast(this.layoutDoc._viewScale, 1), _panX: NumCast(this.layoutDoc._panX), _panY: NumCast(this.layoutDoc._panY) }); + Docs.Create.ImageanchorDocument({ presTransition: 1000, unrendered: true, annotationOn: this.rootDoc }); if (anchor) { - anchor._useAlt = Cast(this.layoutDoc[this.fieldKey + '-useAlt'], 'boolean', null); - anchor._annotations = new List(DocListCast(this.dataDoc[this.annotationKey])); + PresBox.pinDocView(anchor, { pinData: { pannable: true, dataview: true, dataannos: true } }, this.rootDoc); this.addDocument(anchor); return anchor; } @@ -130,7 +116,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent disposer?.()); } @@ -425,6 +410,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent this._savedAnnotations; + styleProvider = (doc: Opt, props: Opt, property: string): any => { + if (property === StyleProp.BoxShadow) return undefined; + return this.props.styleProvider?.(doc, props, property); + }; render() { TraceMobx(); @@ -445,6 +434,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { @@ -207,16 +208,19 @@ export class PDFBox extends ViewBoxAnnotatableComponent { - const anchor = - this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations()) ?? - Docs.Create.TextanchorDocument({ + const docAnchor = () => { + const anchor = Docs.Create.TextanchorDocument({ title: StrCast(this.rootDoc.title + '@' + NumCast(this.layoutDoc._scrollTop)?.toFixed(0)), - y: NumCast(this.layoutDoc._scrollTop), unrendered: true, }); + PresBox.pinDocView(anchor, { pinData: { scrollable: true, pannable: true } }, this.rootDoc); + return anchor; + }; + const anchor = this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations()) ?? docAnchor(); this.addDocument(anchor); return anchor; }; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 2984feba5..fdd61463d 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -686,7 +686,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this.props.pinToPres(anchor); + pinToPres = (anchor: Doc) => this.props.pinToPres(anchor, {}); @undoBatch makePushpin = (anchor: Doc) => (anchor.isPushpin = !anchor.isPushpin); diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 8d805c663..10f2dc016 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -12,7 +12,8 @@ import { List } from '../../../../fields/List'; import { ObjectField } from '../../../../fields/ObjectField'; import { listSpec } from '../../../../fields/Schema'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnFalse, returnOne, returnTrue, setupMoveUpEvents, StopEvent } from '../../../../Utils'; +import { AudioField } from '../../../../fields/URLField'; +import { emptyFunction, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { Docs } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; @@ -33,6 +34,7 @@ import { FieldView, FieldViewProps } from '../FieldView'; import { ScriptingBox } from '../ScriptingBox'; import './PresBox.scss'; import { PresEffect, PresMovement, PresStatus } from './PresEnums'; +const { Howl } = require('howler'); export interface PinProps { audioRange?: boolean; @@ -41,6 +43,17 @@ export interface PinProps { pinViewport?: MarqueeViewBounds; // pin a specific viewport on a freeform view (use MarqueeView.CurViewBounds to compute if no region has been selected) pinDocLayout?: boolean; // pin layout info (width/height/x/y) pinDocContent?: boolean; // pin data info (scroll/pan/zoom/text) + pinAudioPlay?: boolean; // pin audio annotation + pinData?: { + scrollable?: boolean | undefined; + pannable?: boolean | undefined; + temporal?: boolean | undefined; + clippable?: boolean | undefined; + dataview?: boolean | undefined; + textview?: boolean | undefined; + poslayoutview?: boolean | undefined; + dataannos?: boolean | undefined; + }; } @observer @@ -333,26 +346,37 @@ export class PresBox extends ViewBoxBaseComponent() { this.onHideDocument(); //Handles hide after/before } }); - static pinDataTypes(target: Doc) { + static pinDataTypes(target: Doc): { scrollable?: boolean; pannable?: boolean; temporal?: boolean; clippable?: boolean; dataview?: boolean; textview?: boolean; poslayoutview?: boolean; dataannos?: boolean } { const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(target.type as any) || target._viewType === CollectionViewType.Stacking; - const pannable = [DocumentType.IMG].includes(target.type as any) || (target.type === DocumentType.COL && target._viewType === CollectionViewType.Freeform); + const pannable = [DocumentType.IMG, DocumentType.PDF].includes(target.type as any) || (target.type === DocumentType.COL && target._viewType === CollectionViewType.Freeform); const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(target.type as any); const clippable = [DocumentType.COMPARISON].includes(target.type as any); const dataview = [DocumentType.INK, DocumentType.COL, DocumentType.IMG].includes(target.type as any) && target.activeFrame === undefined; const poslayoutview = [DocumentType.COL].includes(target.type as any) && target.activeFrame === undefined; const textview = [DocumentType.RTF].includes(target.type as any) && target.activeFrame === undefined; - return { scrollable, pannable, temporal, clippable, dataview, textview, poslayoutview }; + const dataannos = false; + return { scrollable, pannable, temporal, clippable, dataview, textview, poslayoutview, dataannos }; } @action - static restoreTargetDocView(bestTarget: Doc, activeItem: Doc) { - const transTime = NumCast(activeItem.presTransition, 500); + playAnnotation = (anno: AudioField) => {}; + @action + static restoreTargetDocView(bestTarget: Doc, pinProps: PinProps | undefined, activeItem: Doc, transTime: number, pinDataTypes = this.pinDataTypes(bestTarget)) { const presTransitionTime = `all ${transTime}ms`; - const { scrollable, pannable, temporal, clippable, dataview, textview, poslayoutview } = this.pinDataTypes(bestTarget); bestTarget._viewTransition = presTransitionTime; - if (clippable) bestTarget._clipWidth = activeItem.presPinClipWidth; - if (temporal) bestTarget._currentTimecode = activeItem.presStartTime; - if (scrollable) { + if (pinProps?.pinDocLayout) { + const transTime = NumCast(activeItem.presTransition, 500); + bestTarget._dataTransition = `all ${transTime}ms`; + bestTarget.x = NumCast(activeItem.presX, NumCast(bestTarget.x)); + bestTarget.y = NumCast(activeItem.presY, NumCast(bestTarget.y)); + bestTarget.rotation = NumCast(activeItem.presRot, NumCast(bestTarget.rotation)); + bestTarget.width = NumCast(activeItem.presWidth, NumCast(bestTarget.width)); + bestTarget.height = NumCast(activeItem.presHeight, NumCast(bestTarget.height)); + setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10); + } + if (pinDataTypes.clippable) bestTarget._clipWidth = activeItem.presPinClipWidth; + if (pinDataTypes.temporal) bestTarget._currentTimecode = activeItem.presStartTime; + if (pinDataTypes.scrollable) { bestTarget._scrollTop = activeItem.presPinViewScroll; const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number')); if (contentBounds) { @@ -360,13 +384,17 @@ export class PresBox extends ViewBoxBaseComponent() { dv?.brushView?.({ panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] }); } } - if (dataview && activeItem.presData !== undefined) { + if (pinDataTypes.dataannos) { + const fkey = Doc.LayoutFieldKey(bestTarget); + Doc.GetProto(bestTarget)[fkey + '-annotations'] = new List(DocListCast(activeItem.presAnnotations)); + } + if (pinDataTypes.dataview && activeItem.presData !== undefined) { const fkey = Doc.LayoutFieldKey(bestTarget); Doc.GetProto(bestTarget)[fkey] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; bestTarget[fkey + '-useAlt'] = activeItem.presUseAlt; } - if (textview && activeItem.presData !== undefined) Doc.GetProto(bestTarget)[Doc.LayoutFieldKey(bestTarget)] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; - if (poslayoutview) { + if (pinDataTypes.textview && activeItem.presData !== undefined) Doc.GetProto(bestTarget)[Doc.LayoutFieldKey(bestTarget)] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; + if (pinDataTypes.poslayoutview) { StrListCast(activeItem.presPinLayoutData) .map(str => JSON.parse(str) as { id: string; x: number; y: number; w: number; h: number }) .forEach(data => { @@ -385,7 +413,7 @@ export class PresBox extends ViewBoxBaseComponent() { transTime + 10 ); } - if (pannable) { + if (pinDataTypes.pannable) { const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number')); if (contentBounds) { const viewport = { panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] }; @@ -410,9 +438,8 @@ export class PresBox extends ViewBoxBaseComponent() { /// reserved fields on the pinDoc so that those values can be restored to the /// target doc when navigating to it. @action - static pinDocView(pinDoc: Doc, pinProps: PinProps | undefined, targetDoc: Doc) { - const { scrollable, pannable, temporal, clippable, dataview, textview, poslayoutview } = this.pinDataTypes(pinDoc); - if (pinProps?.pinDocLayout) { + static pinDocView(pinDoc: Doc, pinProps: PinProps, targetDoc: Doc) { + if (pinProps.pinDocLayout) { pinDoc.presPinLayout = true; pinDoc.presX = NumCast(targetDoc.x); pinDoc.presY = NumCast(targetDoc.y); @@ -420,33 +447,46 @@ export class PresBox extends ViewBoxBaseComponent() { pinDoc.presWidth = NumCast(targetDoc.width); pinDoc.presHeight = NumCast(targetDoc.height); } - if (pinProps?.pinDocContent) { - pinDoc.presPinData = scrollable || temporal || pannable || clippable || dataview || textview || poslayoutview || pinProps.activeFrame !== undefined; - if (dataview) { + if (pinProps.pinAudioPlay) pinDoc.followLinkAudio = true; + if (pinProps.pinData) { + pinDoc.presPinData = + pinProps.pinData.scrollable || + pinProps.pinData.temporal || + pinProps.pinData.pannable || + pinProps.pinData.clippable || + pinProps.pinData.dataview || + pinProps.pinData.textview || + pinProps.pinData.poslayoutview || + pinProps?.activeFrame !== undefined; + if (pinProps.pinData.dataview) { const fkey = Doc.LayoutFieldKey(targetDoc); pinDoc.presUseAlt = targetDoc[fkey + '-useAlt']; pinDoc.presData = targetDoc[fkey] instanceof ObjectField ? (targetDoc[fkey] as ObjectField)[Copy]() : targetDoc.data; } - if (textview) pinDoc.presData = targetDoc[Doc.LayoutFieldKey(targetDoc)] instanceof ObjectField ? (targetDoc[Doc.LayoutFieldKey(targetDoc)] as ObjectField)[Copy]() : targetDoc.text; - if (scrollable) pinDoc.presPinViewScroll = pinDoc._scrollTop; - if (clippable) pinDoc.presPinClipWidth = pinDoc._clipWidth; - if (poslayoutview) pinDoc.presPinLayoutData = new List(DocListCast(pinDoc.presData).map(d => JSON.stringify({ id: d[Id], x: NumCast(d.x), y: NumCast(d.y), w: NumCast(d._width), h: NumCast(d._height) }))); - if (pannable) { - pinDoc.presPinViewX = NumCast(pinDoc._panX); - pinDoc.presPinViewY = NumCast(pinDoc._panY); - pinDoc.presPinViewScale = NumCast(pinDoc._viewScale, 1); + if (pinProps.pinData.dataannos) { + const fkey = Doc.LayoutFieldKey(targetDoc); + pinDoc.presAnnotations = new List(DocListCast(Doc.GetProto(targetDoc)[fkey + '-annotations'])); + } + if (pinProps.pinData.textview) pinDoc.presData = targetDoc[Doc.LayoutFieldKey(targetDoc)] instanceof ObjectField ? (targetDoc[Doc.LayoutFieldKey(targetDoc)] as ObjectField)[Copy]() : targetDoc.text; + if (pinProps.pinData.scrollable) pinDoc.presPinViewScroll = targetDoc._scrollTop; + if (pinProps.pinData.clippable) pinDoc.presPinClipWidth = targetDoc._clipWidth; + if (pinProps.pinData.poslayoutview) pinDoc.presPinLayoutData = new List(DocListCast(targetDoc.presData).map(d => JSON.stringify({ id: d[Id], x: NumCast(d.x), y: NumCast(d.y), w: NumCast(d._width), h: NumCast(d._height) }))); + if (pinProps.pinData.pannable) { + pinDoc.presPinViewX = NumCast(targetDoc._panX); + pinDoc.presPinViewY = NumCast(targetDoc._panY); + pinDoc.presPinViewScale = NumCast(targetDoc._viewScale, 1); } - if (temporal) { - pinDoc.presStartTime = pinDoc._currentTimecode; - const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], NumCast(pinDoc.presStartTime) + 0.1); - pinDoc.presEndTime = NumCast(pinDoc.clipEnd, duration); + if (pinProps.pinData.temporal) { + pinDoc.presStartTime = targetDoc._currentTimecode; + const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], NumCast(targetDoc.presStartTime) + 0.1); + pinDoc.presEndTime = NumCast(targetDoc.clipEnd, duration); } } if (pinProps?.pinViewport) { // If pinWithView option set then update scale and x / y props of slide const bounds = pinProps.pinViewport; pinDoc.presPinView = true; - pinDoc.presPinViewScale = NumCast(pinDoc._viewScale, 1); + pinDoc.presPinViewScale = NumCast(targetDoc._viewScale, 1); pinDoc.presPinViewX = bounds.left + bounds.width / 2; pinDoc.presPinViewY = bounds.top + bounds.height / 2; pinDoc.presPinViewBounds = new List([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]); @@ -507,35 +547,24 @@ export class PresBox extends ViewBoxBaseComponent() { }; static NavigateToTarget(targetDoc: Doc, activeItem: Doc, openInTab: any, srcContext: Doc, finished?: () => void) { - if ((activeItem.presPinLayout || activeItem.presPinView) && DocCast(targetDoc.context)?._currentFrame === undefined) { - const transTime = NumCast(activeItem.presTransition, 500); - targetDoc._dataTransition = `all ${transTime}ms`; - targetDoc.x = NumCast(activeItem.presX, NumCast(targetDoc.x)); - targetDoc.y = NumCast(activeItem.presY, NumCast(targetDoc.y)); - targetDoc.rotation = NumCast(activeItem.presRot, NumCast(targetDoc.rotation)); - targetDoc.width = NumCast(activeItem.presWidth, NumCast(targetDoc.width)); - targetDoc.height = NumCast(activeItem.presHeight, NumCast(targetDoc.height)); - setTimeout(() => (targetDoc._dataTransition = undefined), transTime + 10); - } // If openDocument is selected then it should open the document for the user if (activeItem.openDocument) { LightboxView.SetLightboxDoc(targetDoc); // openInTab(targetDoc); } else if (targetDoc && activeItem.presMovement !== PresMovement.None) { LightboxView.SetLightboxDoc(undefined); const zooming = activeItem.presMovement !== PresMovement.Pan; - DocumentManager.Instance.jumpToDocument(targetDoc, zooming, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, finished, undefined, true, NumCast(activeItem.presZoom, 1)); + DocumentManager.Instance.jumpToDocument(targetDoc, zooming, openInTab, srcContext ? [srcContext] : [], undefined, undefined, activeItem, finished, undefined, true, NumCast(activeItem.presZoom, 1)); } else if (activeItem.presMovement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) { (DocumentManager.Instance.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.(); } // After navigating to the document, if it is added as a presPinView then it will // adjust the pan and scale to that of the pinView when it was added. - if (activeItem.presPinData || activeItem.presPinView) { + const pinDocLayout = (BoolCast(activeItem.presPinLayout) || BoolCast(activeItem.presPinView)) && DocCast(targetDoc.context)?._currentFrame === undefined; + if (activeItem.presPinData || activeItem.presPinView || pinDocLayout) { clearTimeout(PresBox._navTimer); // targetDoc may or may not be displayed. this gets the first available document (or alias) view that matches targetDoc const bestTargetView = DocumentManager.Instance.getFirstDocumentView(targetDoc); - const bestTarget = bestTargetView?.props.Document; - if (bestTarget) PresBox._navTimer = PresBox.restoreTargetDocView(bestTarget, activeItem); - activeItem.presPinAudioPlay && bestTargetView?.docView?.playAnnotation(); + if (bestTargetView?.props.Document) PresBox._navTimer = PresBox.restoreTargetDocView(bestTargetView?.props.Document, { pinDocLayout }, activeItem, NumCast(activeItem.presTransition, 500)); } } @@ -747,7 +776,7 @@ export class PresBox extends ViewBoxBaseComponent() { } else { if (!doc.aliasOf) { const original = Doc.MakeAlias(doc); - TabDocView.PinDoc(original); + TabDocView.PinDoc(original, {}); setTimeout(() => this.removeDocument(doc), 0); return false; } else { @@ -1316,7 +1345,7 @@ export class PresBox extends ViewBoxBaseComponent() { Effects
Play Audio Annotation
- (activeItem.presPinAudioPlay = !BoolCast(activeItem.presPinAudioPlay))} checked={BoolCast(activeItem.presPinAudioPlay)} /> + (activeItem.followLinkAudio = !BoolCast(activeItem.followLinkAudio))} checked={BoolCast(activeItem.followLinkAudio)} />
() { const presData = Cast(this.rootDoc.data, listSpec(Doc)); if (data && presData) { data.push(doc); - TabDocView.PinDoc(doc); + TabDocView.PinDoc(doc, {}); this.gotoDocument(this.childDocs.length, this.activeItem); } else { this.props.addDocTab(doc, 'add:right'); diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 9af0949eb..133b882f6 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -52,7 +52,7 @@ class RegionAnnotation extends React.Component { }; @undoBatch - pinToPres = () => this.props.pinToPres(this.annoTextRegion); + pinToPres = () => this.props.pinToPres(this.annoTextRegion, {}); @undoBatch makePushpin = () => (this.annoTextRegion.isPushpin = !this.annoTextRegion.isPushpin); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 6ff87ef9f..5a5c63c3d 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -166,12 +166,12 @@ export class PDFViewer extends React.Component { // scrolls to focus on a nested annotation document. if this is part a link preview then it will jump to the scroll location, // otherwise it will scroll smoothly. - scrollFocus = (doc: Doc, smooth: boolean) => { + scrollFocus = (doc: Doc, scrollTop: number, smooth: boolean) => { const mainCont = this._mainCont.current; let focusSpeed: Opt; if (doc !== this.props.rootDoc && mainCont) { const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); - const scrollTo = doc.unrendered ? NumCast(doc.y) : Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, 0.1 * windowHeight, NumCast(this.props.Document.scrollHeight)); + const scrollTo = doc.unrendered ? scrollTop : Utils.scrollIntoView(scrollTop, doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), windowHeight, 0.1 * windowHeight, NumCast(this.props.Document.scrollHeight)); if (scrollTo !== undefined && scrollTo !== this.props.layoutDoc._scrollTop) { if (!this._pdfViewer) this._initialScroll = scrollTo; else if (smooth) smoothScroll((focusSpeed = NumCast(doc.focusSpeed, 500)), mainCont, scrollTo); @@ -203,6 +203,11 @@ export class PDFViewer extends React.Component { } document.removeEventListener('pagesinit', this.pagesinit); var quickScroll: string | undefined = this._initialScroll ? this._initialScroll.toString() : ''; + this._disposers.scale = reaction( + () => NumCast(this.props.layoutDoc._viewScale, 1), + scale => (this._pdfViewer.currentScaleValue = scale), + { fireImmediately: true } + ); this._disposers.scroll = reaction( () => Math.abs(NumCast(this.props.Document._scrollTop)), pos => { @@ -290,7 +295,7 @@ export class PDFViewer extends React.Component { @action scrollToAnnotation = (scrollToAnnotation: Doc) => { if (scrollToAnnotation) { - this.scrollFocus(scrollToAnnotation, true); + this.scrollFocus(scrollToAnnotation, NumCast(scrollToAnnotation.y), true); Doc.linkFollowHighlight(scrollToAnnotation); } }; -- cgit v1.2.3-70-g09d2 From 54dc7b2c44194d98111100bc1350b7ac6c5901bc Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 17 Nov 2022 09:25:48 -0500 Subject: typo --- src/client/views/nodes/ImageBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 2e594d96a..4092a2575 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -364,7 +364,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent + style={{ transition: StrCast(this.layoutDoc.viewTransition, 'opacity 1000ms') }}>
)} -- cgit v1.2.3-70-g09d2 From 30e7fc1b2cb4b5c5f8d5f5e4f808b91e69629245 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 17 Nov 2022 11:00:06 -0500 Subject: fixed pushpin behaviors by not animating when anchor viewspec already matched document. fixed recording button highlighting. switched LinkEditor to edit properties of destination insteqad of source anchor --- src/client/views/DocumentButtonBar.tsx | 22 +++---- .../collectionFreeForm/CollectionFreeFormView.tsx | 11 ++-- src/client/views/linking/LinkEditor.tsx | 14 ++--- src/client/views/nodes/ImageBox.tsx | 11 ++-- src/client/views/nodes/trails/PresBox.tsx | 72 +++++++++++++++------- 5 files changed, 80 insertions(+), 50 deletions(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 681349ccf..ecf330792 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -386,18 +386,18 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV style={{ backgroundColor: this._isRecording ? Colors.ERROR_RED : Colors.DARK_GRAY, color: Colors.WHITE }} onPointerDown={action((e: React.PointerEvent) => { this._isRecording = true; - this.props.views().map( - view => - view && - DocumentViewInternal.recordAudioAnnotation( - view.dataDoc, - view.LayoutFieldKey, - stopFunc => (this._stopFunc = stopFunc), - action(() => (this._isRecording = false)) - ) - ); + this.props.views().map(view => view && DocumentViewInternal.recordAudioAnnotation(view.dataDoc, view.LayoutFieldKey, stopFunc => (this._stopFunc = stopFunc), emptyFunction)); const b = UndoManager.StartBatch('Recording'); - setupMoveUpEvents(this, e, returnFalse, () => this._stopFunc(), emptyFunction); + setupMoveUpEvents( + this, + e, + returnFalse, + action(() => { + this._isRecording = false; + this._stopFunc(); + }), + emptyFunction + ); })}>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 8a97797c7..57cccec4a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1532,17 +1532,18 @@ export class CollectionFreeFormView extends CollectionSubView { - let focusSpeed: Opt; - PresBox.restoreTargetDocView( + const focusSpeed = !smooth ? 0 : NumCast(anchor.presTransition); + return PresBox.restoreTargetDocView( this.rootDoc, // { pinDocLayout: BoolCast(anchor.presPinDocLayout) }, anchor, - (focusSpeed = !smooth ? 0 : NumCast(anchor.presTransition)), + focusSpeed, { pannable: anchor.presPinData ? true : false, } - ); - return focusSpeed; + ) + ? focusSpeed + : 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 = () => { diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index d90a91ab7..8c4d756d2 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -297,13 +297,13 @@ export class LinkEditor extends React.Component { ); } - @computed get sourceAnchor() { + @computed get destinationAnchor() { const ldoc = this.props.linkDoc; if (this.props.sourceDoc !== ldoc.anchor1 && this.props.sourceDoc !== ldoc.anchor2) { - if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.anchor1).annotationOn), this.props.sourceDoc)) return DocCast(ldoc.anchor1); - if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.anchor2).annotationOn), this.props.sourceDoc)) return DocCast(ldoc.anchor2); + if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.anchor1).annotationOn), this.props.sourceDoc)) return DocCast(ldoc.anchor2); + if (Doc.AreProtosEqual(DocCast(DocCast(ldoc.anchor2).annotationOn), this.props.sourceDoc)) return DocCast(ldoc.anchor1); } - return this.props.sourceDoc; + return LinkManager.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc) ?? this.props.sourceDoc; } @action changeEffectDropdown = () => { @@ -313,7 +313,7 @@ export class LinkEditor extends React.Component { @undoBatch changeEffect = action((follow: string) => { this.openEffectDropdown = false; - this.sourceAnchor.presEffect = follow; + this.destinationAnchor.presEffect = follow; }); @computed @@ -323,7 +323,7 @@ export class LinkEditor extends React.Component {
Transition Effect:
- {StrCast(this.sourceAnchor.presEffect, 'default')} + {StrCast(this.destinationAnchor.presEffect, 'default')}
@@ -447,7 +447,7 @@ export class LinkEditor extends React.Component {
{this.followingDropdown} {this.effectDropdown} - {PresBox.inputter('0.1', '0.1', '10', NumCast(this.sourceAnchor.presTransition) / 1000, true, (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => (this.sourceAnchor.presTransition = timeInMS)))} + {PresBox.inputter('0.1', '0.1', '10', NumCast(this.destinationAnchor.presTransition) / 1000, true, (val: string) => PresBox.SetTransitionTime(val, (timeInMS: number) => (this.destinationAnchor.presTransition = timeInMS)))}
{ - let focusSpeed: Opt; - PresBox.restoreTargetDocView( + const focusSpeed = !smooth ? 0 : NumCast(anchor.presTransition); + return PresBox.restoreTargetDocView( this.rootDoc, // { pinDocLayout: BoolCast(anchor.presPinDocLayout) }, anchor, - (focusSpeed = !smooth ? 0 : NumCast(anchor.presTransition)), + focusSpeed, !anchor.presPinData ? {} : { @@ -76,8 +76,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent { diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 7235481e0..e19b53f50 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -365,25 +365,46 @@ export class PresBox extends ViewBoxBaseComponent() { @action static restoreTargetDocView(bestTarget: Doc, pinProps: PinProps | undefined, activeItem: Doc, transTime: number, pinDataTypes = this.pinDataTypes(bestTarget)) { const presTransitionTime = `all ${transTime}ms`; - bestTarget._viewTransition = presTransitionTime; + let changed = false; if (pinProps?.pinDocLayout) { - const transTime = NumCast(activeItem.presTransition, 500); - bestTarget._dataTransition = `all ${transTime}ms`; - bestTarget.x = NumCast(activeItem.presX, NumCast(bestTarget.x)); - bestTarget.y = NumCast(activeItem.presY, NumCast(bestTarget.y)); - bestTarget.rotation = NumCast(activeItem.presRot, NumCast(bestTarget.rotation)); - bestTarget.width = NumCast(activeItem.presWidth, NumCast(bestTarget.width)); - bestTarget.height = NumCast(activeItem.presHeight, NumCast(bestTarget.height)); - setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10); + if ( + bestTarget.x !== NumCast(activeItem.presX, NumCast(bestTarget.x)) || + bestTarget.y !== NumCast(activeItem.presY, NumCast(bestTarget.y)) || + bestTarget.rotation !== NumCast(activeItem.presRot, NumCast(bestTarget.rotation)) || + bestTarget.width !== NumCast(activeItem.presWidth, NumCast(bestTarget.width)) || + bestTarget.height !== NumCast(activeItem.presHeight, NumCast(bestTarget.height)) + ) { + bestTarget._dataTransition = `all ${transTime}ms`; + bestTarget.x = NumCast(activeItem.presX, NumCast(bestTarget.x)); + bestTarget.y = NumCast(activeItem.presY, NumCast(bestTarget.y)); + bestTarget.rotation = NumCast(activeItem.presRot, NumCast(bestTarget.rotation)); + bestTarget.width = NumCast(activeItem.presWidth, NumCast(bestTarget.width)); + bestTarget.height = NumCast(activeItem.presHeight, NumCast(bestTarget.height)); + setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10); + changed = true; + } + } + if (pinDataTypes.clippable) { + if (bestTarget._clipWidth !== activeItem.presPinClipWidth) { + bestTarget._clipWidth = activeItem.presPinClipWidth; + changed = true; + } + } + if (pinDataTypes.temporal) { + if (bestTarget._currentTimecode !== activeItem.presStartTime) { + bestTarget._currentTimecode = activeItem.presStartTime; + changed = true; + } } - if (pinDataTypes.clippable) bestTarget._clipWidth = activeItem.presPinClipWidth; - if (pinDataTypes.temporal) bestTarget._currentTimecode = activeItem.presStartTime; if (pinDataTypes.scrollable) { - bestTarget._scrollTop = activeItem.presPinViewScroll; - const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number')); - if (contentBounds) { - const dv = DocumentManager.Instance.getDocumentView(bestTarget)?.ComponentView; - dv?.brushView?.({ panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] }); + if (bestTarget._scrollTop !== activeItem.presPinViewScroll) { + bestTarget._scrollTop = activeItem.presPinViewScroll; + changed = true; + const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number')); + if (contentBounds) { + const dv = DocumentManager.Instance.getDocumentView(bestTarget)?.ComponentView; + dv?.brushView?.({ panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] }); + } } } if (pinDataTypes.dataannos) { @@ -397,6 +418,7 @@ export class PresBox extends ViewBoxBaseComponent() { } if (pinDataTypes.textview && activeItem.presData !== undefined) Doc.GetProto(bestTarget)[Doc.LayoutFieldKey(bestTarget)] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; if (pinDataTypes.poslayoutview) { + changed = true; StrListCast(activeItem.presPinLayoutData) .map(str => JSON.parse(str) as { id: string; x: number; y: number; w: number; h: number }) .forEach(data => { @@ -428,12 +450,18 @@ export class PresBox extends ViewBoxBaseComponent() { dv.ComponentView?.brushView?.(viewport); } } else { - bestTarget._panX = activeItem.presPinViewX; - bestTarget._panY = activeItem.presPinViewY; - bestTarget._viewScale = activeItem.presPinViewScale; + if (bestTarget._panX !== activeItem.presPinViewX || bestTarget._panY !== activeItem.presPinViewY || bestTarget._viewScale !== activeItem.presPinViewScale) { + bestTarget._panX = activeItem.presPinViewX; + bestTarget._panY = activeItem.presPinViewY; + bestTarget._viewScale = activeItem.presPinViewScale; + changed = true; + } } } - return setTimeout(() => (bestTarget._viewTransition = undefined), transTime + 10); + if (changed) { + bestTarget._viewTransition = presTransitionTime; + return setTimeout(() => (bestTarget._viewTransition = undefined), transTime + 10); + } } /// copies values from the targetDoc (which is the prototype of the pinDoc) to @@ -495,7 +523,7 @@ export class PresBox extends ViewBoxBaseComponent() { } } - static _navTimer: NodeJS.Timeout; + static _navTimer: NodeJS.Timeout | undefined; /** * This method makes sure that cursor navigates to the element that * has the option open and last in the group. @@ -563,7 +591,7 @@ export class PresBox extends ViewBoxBaseComponent() { // adjust the pan and scale to that of the pinView when it was added. const pinDocLayout = (BoolCast(activeItem.presPinLayout) || BoolCast(activeItem.presPinView)) && DocCast(targetDoc.context)?._currentFrame === undefined; if (activeItem.presPinData || activeItem.presPinView || pinDocLayout) { - clearTimeout(PresBox._navTimer); + PresBox._navTimer && clearTimeout(PresBox._navTimer); // targetDoc may or may not be displayed. this gets the first available document (or alias) view that matches targetDoc const bestTargetView = DocumentManager.Instance.getFirstDocumentView(targetDoc); if (bestTargetView?.props.Document) PresBox._navTimer = PresBox.restoreTargetDocView(bestTargetView?.props.Document, { pinDocLayout }, activeItem, NumCast(activeItem.presTransition, 500)); -- cgit v1.2.3-70-g09d2 From 66184a172006de4d4bf72d9da33858e04d298181 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 1 Dec 2022 10:13:03 -0500 Subject: refactored process of following links / jumping to docs and added following options for zoomTime, etc instead of setting temporary fields on docs. --- src/client/documents/Documents.ts | 8 +- src/client/util/DocumentManager.ts | 98 +++--- .../util/Import & Export/DirectoryImportBox.tsx | 334 +++++++++++---------- src/client/util/LinkFollower.ts | 84 +++--- src/client/util/SharingManager.tsx | 2 +- src/client/views/DocComponent.tsx | 2 +- src/client/views/InkingStroke.tsx | 6 +- src/client/views/MainView.tsx | 2 +- src/client/views/MarqueeAnnotator.tsx | 2 +- src/client/views/PropertiesView.tsx | 28 +- .../views/collections/CollectionNoteTakingView.tsx | 5 +- .../views/collections/CollectionStackingView.tsx | 5 +- src/client/views/collections/TabDocView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 8 +- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- .../collectionLinear/CollectionLinearView.tsx | 2 +- .../collectionSchema/CollectionSchemaCells.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 26 +- src/client/views/nodes/ImageBox.tsx | 8 +- src/client/views/nodes/PDFBox.tsx | 13 +- src/client/views/nodes/VideoBox.tsx | 6 +- src/client/views/nodes/WebBox.tsx | 10 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 12 +- src/client/views/nodes/trails/PresBox.tsx | 24 +- src/client/views/pdf/Annotation.tsx | 4 +- src/client/views/pdf/PDFViewer.tsx | 8 +- src/client/views/search/SearchBox.tsx | 2 +- 27 files changed, 354 insertions(+), 353 deletions(-) (limited to 'src/client/views/nodes/ImageBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index eed839520..d13d96dd3 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -157,7 +157,7 @@ export class DocumentOptions { _contentBounds?: List; // the (forced) bounds of the document to display. format is: [left, top, right, bottom] _lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged _lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed - _isPushpin?: boolean; // whether document, when clicked, toggles display of its link target + _followLinkToggle?: boolean; // whether document, when clicked, toggles display of its link target _showTitle?: string; // field name to display in header (:hover is an optional suffix) _showCaption?: string; // which field to display in the caption area. leave empty to have no caption _scrollTop?: number; // scroll location for pdfs @@ -272,7 +272,7 @@ export class DocumentOptions { clipWidth?: number; // percent transition from before to after in comparisonBox dockingConfig?: string; annotationOn?: Doc; - isPushpin?: boolean; + followLinkToggle?: boolean; isGroup?: boolean; // whether a collection should use a grouping UI behavior _removeDropProperties?: List; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document noteType?: string; @@ -1703,7 +1703,7 @@ export namespace DocUtils { } export function LeavePushpin(doc: Doc, annotationField: string) { - if (doc.isPushpin) return undefined; + if (doc.followLinkToggle) return undefined; const context = Cast(doc.context, Doc, null) ?? Cast(doc.annotationOn, Doc, null); const hasContextAnchor = DocListCast(doc.links).some(l => (l.anchor2 === doc && Cast(l.anchor1, Doc, null)?.annotationOn === context) || (l.anchor1 === doc && Cast(l.anchor2, Doc, null)?.annotationOn === context)); if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) { @@ -1711,7 +1711,7 @@ export namespace DocUtils { title: 'pushpin', label: '', annotationOn: Cast(doc.annotationOn, Doc, null), - isPushpin: true, + followLinkToggle: true, icon: 'map-pin', x: Cast(doc.x, 'number', null), y: Cast(doc.y, 'number', null), diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 4f02a8202..1b63b615b 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -5,7 +5,7 @@ import { Cast, DocCast } from '../../fields/Types'; import { returnFalse } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; import { LightboxView } from '../views/LightboxView'; -import { DocumentView, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView'; +import { DocFocusOptions, DocumentView, OpenWhereMod, ViewAdjustment } from '../views/nodes/DocumentView'; import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; @@ -171,19 +171,13 @@ export class DocumentManager { }; public jumpToDocument = ( targetDoc: Doc, // document to display - willZoom: boolean, // whether to zoom doc to take up most of screen + options: DocFocusOptions, // options for how to navigate to target createViewFunc = DocumentManager.addView, // how to create a view of the doc if it doesn't exist docContext: Doc[], // context to load that should contain the target - linkDoc?: Doc, // link that's being followed - closeContextIfNotFound: boolean = false, // after opening a context where the document should be, this determines whether the context should be closed if the Doc isn't actually there - originatingDoc: Opt = undefined, // doc that initiated the display of the target odoc - finished?: () => void, - originalTarget?: Doc, - noSelect?: boolean, - presZoomScale?: number + finished?: () => void ): void => { - originalTarget = originalTarget ?? targetDoc; - const docView = this.getFirstDocumentView(targetDoc, originatingDoc); + const originalTarget = options.originalTarget ?? targetDoc; + const docView = this.getFirstDocumentView(targetDoc, options.originatingDoc); const annotatedDoc = Cast(targetDoc.annotationOn, Doc, null); const resolvedTarget = targetDoc.type === DocumentType.MARKER ? annotatedDoc ?? docView?.rootDoc ?? targetDoc : docView?.rootDoc ?? targetDoc; // if target is a marker, then focus toggling should apply to the document it's on since the marker itself doesn't have a hidden field var wasHidden = resolvedTarget.hidden; @@ -195,14 +189,14 @@ export class DocumentManager { } const focusAndFinish = (didFocus: boolean) => { const finalTargetDoc = resolvedTarget; - if (originatingDoc?.isPushpin) { + if (options.toggleTarget) { if (!didFocus && !wasHidden) { // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal finalTargetDoc.hidden = !finalTargetDoc.hidden; } } else { finalTargetDoc.hidden && (finalTargetDoc.hidden = undefined); - !noSelect && docView?.select(false); + !options.noSelect && docView?.select(false); } finished?.(); }; @@ -216,9 +210,8 @@ export class DocumentManager { if (annoContainerView.props.Document.layoutKey === 'layout_icon') { annoContainerView.iconify(() => annoContainerView.focus(targetDoc, { + ...options, originalTarget, - willZoom, - scale: presZoomScale, afterFocus: (didFocus: boolean) => new Promise(res => { focusAndFinish(true); @@ -232,13 +225,12 @@ export class DocumentManager { } } if (focusView) { - !noSelect && Doc.linkFollowHighlight(focusView.rootDoc, undefined, targetDoc); //TODO:glr make this a setting in PresBox - if (originatingDoc?.followLinkAudio) DocumentManager.playAudioAnno(focusView.rootDoc); + !options.noSelect && Doc.linkFollowHighlight(focusView.rootDoc, undefined, targetDoc); //TODO:glr make this a setting in PresBox + if (options.playAudio) DocumentManager.playAudioAnno(focusView.rootDoc); const doFocus = (forceDidFocus: boolean) => - focusView.focus(originalTarget ?? targetDoc, { + focusView.focus(originalTarget, { + ...options, originalTarget, - willZoom, - scale: presZoomScale, afterFocus: (didFocus: boolean) => new Promise(res => { focusAndFinish(forceDidFocus || didFocus); @@ -262,13 +254,12 @@ export class DocumentManager { targetDocContextView.rootDoc.hidden = false; // make sure context isn't hidden targetDocContext._viewTransition = 'transform 500ms'; targetDocContextView.props.focus(targetDocContextView.rootDoc, { - willZoom, + ...options, + // originalTarget, // needed? afterFocus: async () => { targetDocContext._viewTransition = undefined; if (targetDocContext.layoutKey === 'layout_icon') { - targetDocContextView.iconify(() => - this.jumpToDocument(resolvedTarget ?? targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc, finished, originalTarget, noSelect, presZoomScale) - ); + targetDocContextView.iconify(() => this.jumpToDocument(resolvedTarget ?? targetDoc, { ...options /* originalTarget - needed?*/ }, createViewFunc, docContext, finished)); } return ViewAdjustment.doNothing; }, @@ -281,56 +272,35 @@ export class DocumentManager { finished?.(); } else { // no timecode means we need to find the context view and focus on our target - const findView = (delay: number) => { - const retryDocView = this.getFirstDocumentView(resolvedTarget); // test again for the target view snce we presumably created the context above by focusing on it - if (retryDocView) { - // we found the target in the context. - Doc.linkFollowHighlight(retryDocView.rootDoc); - retryDocView.focus(targetDoc, { - willZoom, - afterFocus: (didFocus: boolean) => - new Promise(res => { - !noSelect && focusAndFinish(true); - res(ViewAdjustment.doNothing); - }), - }); // focus on the target in the context - } else if (delay > 1000) { - // we didn't find the target, so it must have moved out of the context. Go back to just creating it. - if (closeContextIfNotFound) targetDocContextView.props.removeDocument?.(targetDocContextView.rootDoc); - if (targetDoc.layout) { - // there will no layout for a TEXTANCHOR type document - createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target - } - } else { - setTimeout(() => findView(delay + 200), 200); - } - }; - setTimeout(() => findView(0), 0); + const retryDocView = this.getFirstDocumentView(resolvedTarget); // test again for the target view snce we presumably created the context above by focusing on it + if (retryDocView) { + // we found the target in the context. + Doc.linkFollowHighlight(retryDocView.rootDoc); + retryDocView.focus(targetDoc, { + ...options, + // originalTarget -- needed? + afterFocus: (didFocus: boolean) => + new Promise(res => { + !options.noSelect && focusAndFinish(true); + res(ViewAdjustment.doNothing); + }), + }); // focus on the target in the context + } else if (targetDoc.layout) { + // there will no layout for a TEXTANCHOR type document + createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target + } } } else { if (docContext.length && docContext[0]?.layoutKey === 'layout_icon') { const docContextView = this.getFirstDocumentView(docContext[0]); if (docContextView) { - return docContextView.iconify(() => - this.jumpToDocument(targetDoc, willZoom, createViewFunc, docContext.slice(1, docContext.length), linkDoc, closeContextIfNotFound, originatingDoc, finished, originalTarget, noSelect, presZoomScale) - ); + return docContextView.iconify(() => this.jumpToDocument(targetDoc, { ...options, originalTarget }, createViewFunc, docContext.slice(1, docContext.length), finished)); } } // there's no context view so we need to create one first and try again when that finishes createViewFunc( targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target - () => - this.jumpToDocument( - targetDoc, - willZoom, - (doc: Doc, finished?: () => void) => doc !== targetDocContext && createViewFunc(doc, finished), - docContext, - linkDoc, - true /* if target not found, get rid of context just created */, - originatingDoc, - finished, - originalTarget - ) + () => this.jumpToDocument(targetDoc, { ...options, originalTarget }, (doc: Doc, finished?: () => void) => doc !== targetDocContext && createViewFunc(doc, finished), docContext, finished) ); } } diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 37571ae01..916eee4b7 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -1,27 +1,27 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { BatchedArray } from "array-batcher"; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { extname } from "path"; -import Measure, { ContentRect } from "react-measure"; -import { Doc, DocListCast, DocListCastAsync, Opt } from "../../../fields/Doc"; -import { Id } from "../../../fields/FieldSymbols"; -import { List } from "../../../fields/List"; -import { listSpec } from "../../../fields/Schema"; -import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; -import { BoolCast, Cast, NumCast } from "../../../fields/Types"; -import { AcceptableMedia, Upload } from "../../../server/SharedMediaTypes"; -import { Utils } from "../../../Utils"; -import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils"; -import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents"; -import { Networking } from "../../Network"; -import { FieldView, FieldViewProps } from "../../views/nodes/FieldView"; -import { DocumentManager } from "../DocumentManager"; -import "./DirectoryImportBox.scss"; -import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from "./ImportMetadataEntry"; -import React = require("react"); +import { BatchedArray } from 'array-batcher'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { extname } from 'path'; +import Measure, { ContentRect } from 'react-measure'; +import { Doc, DocListCast, DocListCastAsync, Opt } from '../../../fields/Doc'; +import { Id } from '../../../fields/FieldSymbols'; +import { List } from '../../../fields/List'; +import { listSpec } from '../../../fields/Schema'; +import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; +import { BoolCast, Cast, NumCast } from '../../../fields/Types'; +import { AcceptableMedia, Upload } from '../../../server/SharedMediaTypes'; +import { Utils } from '../../../Utils'; +import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; +import { Docs, DocumentOptions, DocUtils } from '../../documents/Documents'; +import { Networking } from '../../Network'; +import { FieldView, FieldViewProps } from '../../views/nodes/FieldView'; +import { DocumentManager } from '../DocumentManager'; +import './DirectoryImportBox.scss'; +import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from './ImportMetadataEntry'; +import React = require('react'); -const unsupported = ["text/html", "text/plain"]; +const unsupported = ['text/html', 'text/plain']; @observer export class DirectoryImportBox extends React.Component { @@ -29,7 +29,7 @@ export class DirectoryImportBox extends React.Component { @observable private top = 0; @observable private left = 0; private dimensions = 50; - @observable private phase = ""; + @observable private phase = ''; private disposer: Opt; @observable private entries: ImportMetadataEntry[] = []; @@ -40,7 +40,9 @@ export class DirectoryImportBox extends React.Component { @observable private uploading = false; @observable private removeHover = false; - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DirectoryImportBox, fieldKey); } + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(DirectoryImportBox, fieldKey); + } constructor(props: FieldViewProps) { super(props); @@ -71,7 +73,7 @@ export class DirectoryImportBox extends React.Component { handleSelection = async (e: React.ChangeEvent) => { runInAction(() => { this.uploading = true; - this.phase = "Initializing download..."; + this.phase = 'Initializing download...'; }); const docs: Doc[] = []; @@ -79,7 +81,7 @@ export class DirectoryImportBox extends React.Component { const files = e.target.files; if (!files || files.length === 0) return; - const directory = (files.item(0) as any).webkitRelativePath.split("/", 1)[0]; + const directory = (files.item(0) as any).webkitRelativePath.split('/', 1)[0]; const validated: File[] = []; for (let i = 0; i < files.length; i++) { @@ -100,7 +102,7 @@ export class DirectoryImportBox extends React.Component { const sizes: number[] = []; const modifiedDates: number[] = []; - runInAction(() => this.phase = `Internal: uploading ${this.quota - this.completed} files to Dash...`); + runInAction(() => (this.phase = `Internal: uploading ${this.quota - this.completed} files to Dash...`)); const batched = BatchedArray.from(validated, { batchSize: 15 }); const uploads = await batched.batchedMapAsync>(async (batch, collector) => { @@ -109,23 +111,28 @@ export class DirectoryImportBox extends React.Component { modifiedDates.push(file.lastModified); }); collector.push(...(await Networking.UploadFilesToServer(batch))); - runInAction(() => this.completed += batch.length); + runInAction(() => (this.completed += batch.length)); }); - await Promise.all(uploads.map(async response => { - const { source: { type }, result } = response; - if (result instanceof Error) { - return; - } - const { accessPaths, exifData } = result; - const path = Utils.prepend(accessPaths.agnostic.client); - const document = type && await DocUtils.DocumentFromType(type, path, { _width: 300 }); - const { data, error } = exifData; - if (document) { - Doc.GetProto(document).exif = error || Doc.Get.FromJson({ data }); - docs.push(document); - } - })); + await Promise.all( + uploads.map(async response => { + const { + source: { type }, + result, + } = response; + if (result instanceof Error) { + return; + } + const { accessPaths, exifData } = result; + const path = Utils.prepend(accessPaths.agnostic.client); + const document = type && (await DocUtils.DocumentFromType(type, path, { _width: 300 })); + const { data, error } = exifData; + if (document) { + Doc.GetProto(document).exif = error || Doc.Get.FromJson({ data }); + docs.push(document); + } + }) + ); for (let i = 0; i < docs.length; i++) { const doc = docs[i]; @@ -146,7 +153,7 @@ export class DirectoryImportBox extends React.Component { _height: 500, _chromeHidden: true, x: NumCast(doc.x), - y: NumCast(doc.y) + offset + y: NumCast(doc.y) + offset, }; const parent = this.props.ContainingCollectionView; if (parent) { @@ -154,14 +161,14 @@ export class DirectoryImportBox extends React.Component { if (docs.length < 50) { importContainer = Docs.Create.MasonryDocument(docs, options); } else { - const headers = [new SchemaHeaderField("title"), new SchemaHeaderField("size")]; + const headers = [new SchemaHeaderField('title'), new SchemaHeaderField('size')]; importContainer = Docs.Create.SchemaDocument(headers, docs, options); } - runInAction(() => this.phase = 'External: uploading files to Google Photos...'); + runInAction(() => (this.phase = 'External: uploading files to Google Photos...')); await GooglePhotos.Export.CollectionToAlbum({ collection: importContainer }); - Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer); + Doc.AddDocToList(Doc.GetProto(parent.props.Document), 'data', importContainer); !this.persistent && this.props.removeDocument && this.props.removeDocument(doc); - DocumentManager.Instance.jumpToDocument(importContainer, true, undefined, []); + DocumentManager.Instance.jumpToDocument(importContainer, { willZoom: true }, undefined, []); } runInAction(() => { @@ -169,14 +176,14 @@ export class DirectoryImportBox extends React.Component { this.quota = 1; this.completed = 0; }); - } + }; componentDidMount() { - this.selector.current!.setAttribute("directory", ""); - this.selector.current!.setAttribute("webkitdirectory", ""); + this.selector.current!.setAttribute('directory', ''); + this.selector.current!.setAttribute('webkitdirectory', ''); this.disposer = reaction( () => this.completed, - completed => runInAction(() => this.phase = `Internal: uploading ${this.quota - completed} files to Dash...`) + completed => runInAction(() => (this.phase = `Internal: uploading ${this.quota - completed} files to Dash...`)) ); } @@ -193,7 +200,7 @@ export class DirectoryImportBox extends React.Component { const offset = this.dimensions / 2; this.left = bounds.width / 2 - offset; this.top = bounds.height / 2 - offset; - } + }; @action addMetadataEntry = async () => { @@ -201,8 +208,8 @@ export class DirectoryImportBox extends React.Component { entryDoc.checked = false; entryDoc.key = keyPlaceholder; entryDoc.value = valuePlaceholder; - Doc.AddDocToList(this.props.Document, "data", entryDoc); - } + Doc.AddDocToList(this.props.Document, 'data', entryDoc); + }; @action remove = async (entry: ImportMetadataEntry) => { @@ -217,7 +224,7 @@ export class DirectoryImportBox extends React.Component { } } } - } + }; render() { const dimensions = 50; @@ -228,193 +235,204 @@ export class DirectoryImportBox extends React.Component { const uploading = this.uploading; const showRemoveLabel = this.removeHover; const persistent = this.persistent; - let percent = `${completed / quota * 100}`; - percent = percent.split(".")[0]; - percent = percent.startsWith("100") ? "99" : percent; + let percent = `${(completed / quota) * 100}`; + percent = percent.split('.')[0]; + percent = percent.startsWith('100') ? '99' : percent; const marginOffset = (percent.length === 1 ? 5 : 0) - 1.6; - const message = {this.phase}; - const centerPiece = this.phase.includes("Google Photos") ? - - :
{this.phase}; + const centerPiece = this.phase.includes('Google Photos') ? ( + + ) : ( +
{percent}%
; + color: 'white', + marginLeft: this.left + marginOffset, + }}> + {percent}% +
+ ); return ( - {({ measureRef }) => -
+ {({ measureRef }) => ( +
{message} + position: 'absolute', + display: 'none', + }} + />