diff options
-rw-r--r-- | src/client/views/MarqueeAnnotator.tsx | 27 | ||||
-rw-r--r-- | src/client/views/StyleProvider.tsx | 2 | ||||
-rw-r--r-- | src/client/views/nodes/PDFBox.tsx | 4 | ||||
-rw-r--r-- | src/client/views/nodes/WebBox.tsx | 2 | ||||
-rw-r--r-- | src/client/views/nodes/WebBoxRenderer.js | 133 | ||||
-rw-r--r-- | src/client/views/pdf/Annotation.tsx | 157 | ||||
-rw-r--r-- | src/client/views/pdf/PDFViewer.tsx | 6 |
7 files changed, 157 insertions, 174 deletions
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index c4c00e0c3..ea73a53a9 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -100,22 +100,19 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP let maxX = -Number.MAX_VALUE; let minY = Number.MAX_VALUE; let maxY = -Number.MIN_VALUE; - const annoDocs: Doc[] = []; + const annoRects: string[] = []; savedAnnoMap.forEach((value: HTMLDivElement[]) => value.forEach(anno => { - const textRegion = new Doc(); - textRegion.x = parseInt(anno.style.left ?? '0'); - textRegion.y = parseInt(anno.style.top ?? '0'); - textRegion._height = parseInt(anno.style.height ?? '0'); - textRegion._width = parseInt(anno.style.width ?? '0'); - textRegion.embedContainer = textRegionAnnoProto; - textRegion.backgroundColor = color; - annoDocs.push(textRegion); + const x = parseInt(anno.style.left ?? '0'); + const y = parseInt(anno.style.top ?? '0'); + const height = parseInt(anno.style.height ?? '0'); + const width = parseInt(anno.style.width ?? '0'); + annoRects.push(`${x}:${y}:${width}:${height}`); anno.remove(); - minY = Math.min(NumCast(textRegion.y), minY); - minX = Math.min(NumCast(textRegion.x), minX); - maxY = Math.max(NumCast(textRegion.y) + NumCast(textRegion._height), maxY); - maxX = Math.max(NumCast(textRegion.x) + NumCast(textRegion._width), maxX); + minY = Math.min(NumCast(y), minY); + minX = Math.min(NumCast(x), minX); + maxY = Math.max(NumCast(y) + NumCast(height), maxY); + maxX = Math.max(NumCast(x) + NumCast(width), maxX); }) ); @@ -123,8 +120,10 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP textRegionAnnoProto.x = Math.max(minX, 0); textRegionAnnoProto.height = Math.max(maxY, 0) - Math.max(minY, 0); textRegionAnnoProto.width = Math.max(maxX, 0) - Math.max(minX, 0); + textRegionAnnoProto.backgroundColor = color; // mainAnnoDocProto.text = this._selectionText; - textRegionAnnoProto.text_inlineAnnotations = new List<Doc>(annoDocs); + textRegionAnnoProto.text_inlineAnnotations = new List<string>(annoRects); + textRegionAnnoProto.opacity = 0; textRegionAnnoProto.layout_unrendered = true; savedAnnoMap.clear(); return textRegionAnno; diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 3697aa010..6d4f69e6e 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -177,7 +177,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps & return undefined; case StyleProp.DocContents: return undefined; case StyleProp.WidgetColor: return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey'; - case StyleProp.Opacity: return LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null)); + case StyleProp.Opacity: return LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null)); case StyleProp.FontColor: return StrCast(doc?.[fieldKey + 'fontColor'], StrCast(Doc.UserDoc().fontColor, color())); case StyleProp.FontSize: return StrCast(doc?.[fieldKey + 'fontSize'], StrCast(Doc.UserDoc().fontSize)); case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(Doc.UserDoc().fontFamily)); diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index b03b90418..fdba9ff49 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -110,6 +110,10 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem crop = (region: Doc | undefined, addCrop?: boolean) => { if (!region) return undefined; const cropping = Doc.MakeCopy(region, true); + cropping.layout_unrendered = false; // text selection have this + cropping.text_inlineAnnotations = undefined; // text selections have this -- it causes them not to be rendered. + cropping.backgroundColor = undefined; // text selections have this -- it causes images to be fully transparent + cropping.opacity = undefined; // text selections have this -- it causes images to be fully transparent const regionData = region[DocData]; regionData.lockedPosition = true; regionData.title = 'region:' + this.Document.title; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index fc2e4bf61..c0fffd66c 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -1030,7 +1030,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem .sort((a, b) => NumCast(a.y) - NumCast(b.y)) .map(anno => ( // eslint-disable-next-line react/jsx-props-no-spreading - <Annotation {...this._props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} /> + <Annotation {...this._props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} containerDataDoc={this.dataDoc} annoDoc={anno} key={`${anno[Id]}-annotation`} /> ))} </div> ); diff --git a/src/client/views/nodes/WebBoxRenderer.js b/src/client/views/nodes/WebBoxRenderer.js index 914adb404..6fb8f4957 100644 --- a/src/client/views/nodes/WebBoxRenderer.js +++ b/src/client/views/nodes/WebBoxRenderer.js @@ -1,3 +1,4 @@ +/* eslint-disable no-undef */ /** * * @param {StyleSheetList} styleSheets @@ -9,15 +10,14 @@ const ForeignHtmlRenderer = function (styleSheets) { * * @param {String} binStr */ - const binaryStringToBase64 = function (binStr) { - return new Promise(resolve => { + const binaryStringToBase64 = binStr => + new Promise(resolve => { const reader = new FileReader(); reader.readAsDataURL(binStr); reader.onloadend = function () { resolve(reader.result); }; }); - }; function prepend(extension) { return window.location.origin + extension; @@ -30,8 +30,8 @@ const ForeignHtmlRenderer = function (styleSheets) { * @param {String} url * @returns {Promise} */ - const getResourceAsBase64 = function (webUrl, inurl) { - return new Promise((resolve, reject) => { + const getResourceAsBase64 = (webUrl, inurl) => + new Promise(resolve => { const xhr = new XMLHttpRequest(); // const url = inurl.startsWith("/") && !inurl.startsWith("//") ? webUrl + inurl : inurl; // const url = CorsProxy(inurl.startsWith("/") && !inurl.startsWith("//") ? webUrl + inurl : inurl);// inurl.startsWith("http") ? CorsProxy(inurl) : inurl; @@ -67,16 +67,15 @@ const ForeignHtmlRenderer = function (styleSheets) { xhr.send(null); }); - }; /** * * @param {String[]} urls * @returns {Promise} */ - const getMultipleResourcesAsBase64 = function (webUrl, urls) { + const getMultipleResourcesAsBase64 = (webUrl, urls) => { const promises = []; - for (let i = 0; i < urls.length; i += 1) { + for (let i = 0; webUrl && i < urls.length; i += 1) { promises.push(getResourceAsBase64(webUrl, urls[i])); } return Promise.all(promises); @@ -130,6 +129,7 @@ const ForeignHtmlRenderer = function (styleSheets) { const urlsFound = []; let searchStartIndex = 0; + // eslint-disable-next-line no-constant-condition while (true) { const url = parseValue(cssRuleStr, searchStartIndex, selector, delimiters); if (url === null) { @@ -155,9 +155,6 @@ const ForeignHtmlRenderer = function (styleSheets) { const getImageUrlsFromFromHtml = function (html) { return getUrlsFromCssString(html, 'src=', [' ', '>', '\t'], true); }; - const getSourceUrlsFromFromHtml = function (html) { - return getUrlsFromCssString(html, 'source=', [' ', '>', '\t'], true); - }; const escapeRegExp = function (string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string @@ -171,46 +168,45 @@ const ForeignHtmlRenderer = function (styleSheets) { * * @returns {Promise<String>} */ - const buildSvgDataUri = async function (webUrl, inputContentHtml, width, height, scroll, xoff) { - return new Promise(async (resolve, reject) => { - /* !! The problems !! - * 1. CORS (not really an issue, expect perhaps for images, as this is a general security consideration to begin with) - * 2. Platform won't wait for external assets to load (fonts, images, etc.) - */ - - // copy styles - let cssStyles = ''; - const urlsFoundInCss = []; - - for (let i = 0; i < styleSheets.length; i += 1) { - try { - const rules = styleSheets[i].cssRules; - for (let j = 0; j < rules.length; j += 1) { - const cssRuleStr = rules[j].cssText; - urlsFoundInCss.push(...getUrlsFromCssString(cssRuleStr)); - cssStyles += cssRuleStr; - } - } catch (e) { - /* empty */ + const buildSvgDataUri = (webUrl, inputContentHtml, width, height, scroll, xoff) => { + /* !! The problems !! + * 1. CORS (not really an issue, expect perhaps for images, as this is a general security consideration to begin with) + * 2. Platform won't wait for external assets to load (fonts, images, etc.) + */ + + // copy styles + let cssStyles = ''; + const urlsFoundInCss = []; + + for (let i = 0; i < styleSheets.length; i += 1) { + try { + const rules = styleSheets[i].cssRules; + for (let j = 0; j < rules.length; j += 1) { + const cssRuleStr = rules[j].cssText; + urlsFoundInCss.push(...getUrlsFromCssString(cssRuleStr)); + cssStyles += cssRuleStr; } + } catch (e) { + /* empty */ } + } - // const fetchedResourcesFromStylesheets = await getMultipleResourcesAsBase64(webUrl, urlsFoundInCss); - // for (let i = 0; i < fetchedResourcesFromStylesheets.length; i++) { - // const r = fetchedResourcesFromStylesheets[i]; - // if (r.resourceUrl) { - // cssStyles = cssStyles.replace(new RegExp(escapeRegExp(r.resourceUrl), "g"), r.resourceBase64); - // } - // } - - let contentHtml = inputContentHtml - .replace(/<source[^>]*>/g, '') // <picture> tags have a <source> which has a srcset field of image refs. instead of converting each, just use the default <img> of the picture - .replace(/noscript/g, 'div') - .replace(/<div class="mediaset"><\/div>/g, '') // when scripting isn't available (ie, rendering web pages here), <noscript> tags should become <div>'s. But for Brown CS, there's a layout problem if you leave the empty <mediaset> tag - .replace(/<link[^>]*>/g, '') // don't need to keep any linked style sheets because we've already processed all style sheets above - .replace(/srcset="([^ "]*)[^"]*"/g, 'src="$1"'); // instead of converting each item in the srcset to a data url, just convert the first one and use that - const urlsFoundInHtml = getImageUrlsFromFromHtml(contentHtml).filter(url => !url.startsWith('data:')); - const fetchedResources = webUrl ? await getMultipleResourcesAsBase64(webUrl, urlsFoundInHtml) : []; + // const fetchedResourcesFromStylesheets = await getMultipleResourcesAsBase64(webUrl, urlsFoundInCss); + // for (let i = 0; i < fetchedResourcesFromStylesheets.length; i++) { + // const r = fetchedResourcesFromStylesheets[i]; + // if (r.resourceUrl) { + // cssStyles = cssStyles.replace(new RegExp(escapeRegExp(r.resourceUrl), "g"), r.resourceBase64); + // } + // } + + let contentHtml = inputContentHtml + .replace(/<source[^>]*>/g, '') // <picture> tags have a <source> which has a srcset field of image refs. instead of converting each, just use the default <img> of the picture + .replace(/noscript/g, 'div') + .replace(/<div class="mediaset"><\/div>/g, '') // when scripting isn't available (ie, rendering web pages here), <noscript> tags should become <div>'s. But for Brown CS, there's a layout problem if you leave the empty <mediaset> tag + .replace(/<link[^>]*>/g, '') // don't need to keep any linked style sheets because we've already processed all style sheets above + .replace(/srcset="([^ "]*)[^"]*"/g, 'src="$1"'); // instead of converting each item in the srcset to a data url, just convert the first one and use that + const urlsFoundInHtml = getImageUrlsFromFromHtml(contentHtml).filter(url => !url.startsWith('data:')); + return getMultipleResourcesAsBase64(webUrl, urlsFoundInHtml).then(fetchedResources => { for (let i = 0; i < fetchedResources.length; i += 1) { const r = fetchedResources[i]; if (r.resourceUrl) { @@ -243,9 +239,7 @@ const ForeignHtmlRenderer = function (styleSheets) { </svg>`; // convert SVG to data-uri - const dataUri = `data:image/svg+xml;base64,${window.btoa(unescape(encodeURIComponent(svg)))}`; - - resolve(dataUri); + return `data:image/svg+xml;base64,${window.btoa(unescape(encodeURIComponent(svg)))}`; }); }; @@ -256,18 +250,19 @@ const ForeignHtmlRenderer = function (styleSheets) { * * @return {Promise<Image>} */ - this.renderToImage = async function (webUrl, html, width, height, scroll, xoff) { - return new Promise(async (resolve, reject) => { + this.renderToImage = (webUrl, html, width, height, scroll, xoff) => + new Promise(resolve => { const img = new Image(); - console.log(`BUILDING SVG for: ${webUrl}`); - img.src = await buildSvgDataUri(webUrl, html, width, height, scroll, xoff); - img.onload = function () { console.log(`IMAGE SVG created: ${webUrl}`); resolve(img); }; + console.log(`BUILDING SVG for: ${webUrl}`); + buildSvgDataUri(webUrl, html, width, height, scroll, xoff).then(uri => { + img.src = uri; + return img; + }); }); - }; /** * @param {String} html @@ -276,20 +271,16 @@ const ForeignHtmlRenderer = function (styleSheets) { * * @return {Promise<Image>} */ - this.renderToCanvas = async function (webUrl, html, width, height, scroll, xoff, oversample) { - return new Promise(async (resolve, reject) => { - const img = await self.renderToImage(webUrl, html, width, height, scroll, xoff); - + this.renderToCanvas = (webUrl, html, width, height, scroll, xoff, oversample) => + self.renderToImage(webUrl, html, width, height, scroll, xoff).then(img => { const canvas = document.createElement('canvas'); canvas.width = img.width * oversample; canvas.height = img.height * oversample; const canvasCtx = canvas.getContext('2d'); canvasCtx.drawImage(img, 0, 0, img.width * oversample, img.height * oversample); - - resolve(canvas); + return canvas; }); - }; /** * @param {String} html @@ -298,12 +289,10 @@ const ForeignHtmlRenderer = function (styleSheets) { * * @return {Promise<String>} */ - this.renderToBase64Png = async function (webUrl, html, width, height, scroll, xoff, oversample) { - return new Promise(async (resolve, reject) => { - const canvas = await self.renderToCanvas(webUrl, html, width, height, scroll, xoff, oversample); - resolve(canvas.toDataURL('image/png')); - }); - }; + this.renderToBase64Png = (webUrl, html, width, height, scroll, xoff, oversample) => + self + .renderToCanvas(webUrl, html, width, height, scroll, xoff, oversample) // + .then(canvas => canvas.toDataURL('image/png')); }; export function CreateImage(webUrl, styleSheets, html, width, height, scroll, xoff = 0, oversample = 1) { @@ -379,11 +368,11 @@ const ClipboardUtils = new (function () { .then(result => { loadFile(result, callback); }) - .catch(error => { + .catch(() => { callback(null, 'Reading clipboard error.'); }); }) - .catch(error => { + .catch(() => { callback(null, 'Reading clipboard error.'); }); } else { diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 38578837a..f1cd1a4f7 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -1,9 +1,8 @@ -/* eslint-disable react/jsx-props-no-spreading */ import { action, computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, DocListCast, Opt } from '../../../fields/Doc'; -import { Id } from '../../../fields/FieldSymbols'; +import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc'; +import { Highlight } from '../../../fields/DocSymbols'; import { List } from '../../../fields/List'; import { BoolCast, DocCast, NumCast, StrCast } from '../../../fields/Types'; import { LinkFollower } from '../../util/LinkFollower'; @@ -14,13 +13,38 @@ import { OpenWhere } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { AnchorMenu } from './AnchorMenu'; import './Annotation.scss'; -import { Highlight } from '../../../fields/DocSymbols'; + +interface IRegionAnnotationProps { + x: number; + y: number; + width: number; + height: number; + opacity: () => number; + background: () => string; + outline: () => string | undefined; +} + +const RegionAnnotation = function (props: IRegionAnnotationProps) { + return ( + <div + className="htmlAnnotation" + style={{ + left: NumCast(props.x), + top: NumCast(props.y), + width: NumCast(props.width), + height: NumCast(props.height), + opacity: props.opacity(), + outline: props.outline(), + backgroundColor: props.background(), + }} + /> + ); +}; interface IAnnotationProps extends FieldViewProps { - anno: Doc; - dataDoc: Doc; + annoDoc: Doc; + containerDataDoc: Doc; fieldKey: string; - showInfo?: (anno: Opt<Doc>) => void; pointerEvents?: () => Opt<string>; } @observer @@ -31,64 +55,25 @@ export class Annotation extends ObservableReactComponent<IAnnotationProps> { } @computed get linkHighlighted() { - const found = LinkManager.Instance.getAllDirectLinks(this._props.anno).find(link => { - const a1 = LinkManager.getOppositeAnchor(link, this._props.anno); + const found = LinkManager.Instance.getAllDirectLinks(this._props.annoDoc).find(link => { + const a1 = LinkManager.getOppositeAnchor(link, this._props.annoDoc); return a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, a1)); }); return found; } - linkHighlightedFunc = () => this.linkHighlighted; - highlightedFunc = () => this._props.anno[Highlight]; deleteAnnotation = undoable(() => { - const docAnnotations = DocListCast(this._props.dataDoc[this._props.fieldKey]); - this._props.dataDoc[this._props.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this._props.anno)); + const docAnnotations = DocListCast(this._props.containerDataDoc[this._props.fieldKey]); + this._props.containerDataDoc[this._props.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this._props.annoDoc)); AnchorMenu.Instance.fadeOut(true); this._props.select(false); }, 'delete annotation'); - pinToPres = undoable(() => this._props.pinToPres(this._props.anno, {}), 'pin to pres'); - - render() { - return ( - <div style={{ display: this._props.anno.textCopied && !Doc.GetBrushHighlightStatus(this._props.anno) ? 'none' : undefined }}> - {DocListCast(this._props.anno.text_inlineAnnotations).map(a => ( - // eslint-disable-next-line no-use-before-define - <RegionAnnotation - pointerEvents={this._props.pointerEvents} - {...this._props} - highlighted={this.highlightedFunc} - linkHighlighted={this.linkHighlightedFunc} - pinToPres={this.pinToPres} - deleteAnnotation={this.deleteAnnotation} - document={a} - key={a[Id]} - /> - ))} - </div> - ); - } -} - -interface IRegionAnnotationProps extends IAnnotationProps { - document: Doc; - linkHighlighted: () => Doc | undefined; - highlighted: () => any; - deleteAnnotation: () => void; - pinToPres: (...args: any[]) => void; - pointerEvents?: () => Opt<string>; -} -@observer -class RegionAnnotation extends ObservableReactComponent<IRegionAnnotationProps> { - private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); - - @computed get regionDoc() { - return DocCast(this._props.document.embedContainer, this._props.document); - } + pinToPres = undoable(() => this._props.pinToPres(this._props.annoDoc, {}), 'pin to pres'); - makeTargetToggle = undoable(() => { this.regionDoc.followLinkToggle = !this.regionDoc.followLinkToggle }, "set link toggle"); // prettier-ignore + makeTargetToggle = undoable(() => { this._props.annoDoc.followLinkToggle = !this._props.annoDoc.followLinkToggle }, "set link toggle"); // prettier-ignore - isTargetToggler = () => BoolCast(this.regionDoc.followLinkToggle); + isTargetToggler = () => BoolCast(this._props.annoDoc.followLinkToggle); showTargetTrail = undoable((anchor: Doc) => { const trail = DocCast(anchor.presentationTrail); @@ -101,12 +86,12 @@ class RegionAnnotation extends ObservableReactComponent<IRegionAnnotationProps> @action onContextMenu = (e: React.MouseEvent) => { AnchorMenu.Instance.Status = 'annotation'; - AnchorMenu.Instance.Delete = this._props.deleteAnnotation; + AnchorMenu.Instance.Delete = this.deleteAnnotation; AnchorMenu.Instance.Pinned = false; - AnchorMenu.Instance.PinToPres = this._props.pinToPres; + AnchorMenu.Instance.PinToPres = this.pinToPres; AnchorMenu.Instance.MakeTargetToggle = this.makeTargetToggle; AnchorMenu.Instance.IsTargetToggler = this.isTargetToggler; - AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(this.regionDoc); + AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(this._props.annoDoc); AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true); e.stopPropagation(); e.preventDefault(); @@ -118,37 +103,43 @@ class RegionAnnotation extends ObservableReactComponent<IRegionAnnotationProps> e.preventDefault(); } else if (e.button === 0) { e.stopPropagation(); - LinkFollower.FollowLink(undefined, this.regionDoc, false); + LinkFollower.FollowLink(undefined, this._props.annoDoc, false); } }; - + brushed = () => this._props.annoDoc && Doc.GetBrushHighlightStatus(this._props.annoDoc); + opacity = () => (this.brushed() === Doc.DocBrushStatus.highlighted ? 0.5 : 1); + outline = () => (this.linkHighlighted ? 'solid 1px lightBlue' : undefined); + background = () => (this._props.annoDoc[Highlight] ? 'orange' : StrCast(this._props.annoDoc.backgroundColor)); render() { - const brushed = this.regionDoc && Doc.GetBrushHighlightStatus(this.regionDoc); return ( - <div - className="htmlAnnotation" - ref={this._mainCont} - onPointerEnter={action(() => { - Doc.BrushDoc(this._props.anno); - this._props.showInfo?.(this._props.anno); - })} - onPointerLeave={action(() => { - Doc.UnBrushDoc(this._props.anno); - this._props.showInfo?.(undefined); - })} - onPointerDown={this.onPointerDown} - onContextMenu={this.onContextMenu} - style={{ - left: NumCast(this._props.document.x), - top: NumCast(this._props.document.y), - width: NumCast(this._props.document._width), - height: NumCast(this._props.document._height), - opacity: brushed === Doc.DocBrushStatus.highlighted ? 0.5 : undefined, - pointerEvents: this._props.pointerEvents?.() as any, - outline: this._props.linkHighlighted() ? 'solid 1px lightBlue' : undefined, - backgroundColor: this._props.highlighted() ? 'orange' : StrCast(this._props.document.backgroundColor), - }} - /> + <div style={{ display: this._props.annoDoc.textCopied && !Doc.GetBrushHighlightStatus(this._props.annoDoc) ? 'none' : undefined }}> + {StrListCast(this._props.annoDoc.text_inlineAnnotations) + .map(a => a.split?.(':')) + .filter(fields => fields) + .map(([x, y, width, height]) => ( + <div + key={'' + x + y + width + height} + style={{ pointerEvents: this._props.pointerEvents?.() as any }} + onPointerDown={this.onPointerDown} + onContextMenu={this.onContextMenu} + onPointerEnter={() => { + Doc.BrushDoc(this._props.annoDoc); + }} + onPointerLeave={() => { + Doc.UnBrushDoc(this._props.annoDoc); + }}> + <RegionAnnotation // + x={Number(x)} + y={Number(y)} + width={Number(width)} + height={Number(height)} + outline={this.outline} + background={this.background} + opacity={this.opacity} + /> + </div> + ))} + </div> ); } } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index a3fd192f7..22355bc57 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -388,7 +388,6 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { if (!e.altKey && e.button === 0 && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) { this._props.select(false); MarqueeAnnotator.clearAnnotations(this._savedAnnotations); - this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]); this.isAnnotating = true; const target = e.target as any; if (e.target && (target.className.includes('endOfContent') || (target.parentElement.className !== 'textLayer' && target.parentElement.parentElement?.className !== 'textLayer'))) { @@ -400,6 +399,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, 'htmlAnnotation', { 'pointer-events': 'none' }); document.addEventListener('pointerup', this.onSelectEnd); } + this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]); } }; @@ -505,7 +505,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this._props.Document), transform: `scale(${NumCast(this._props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}> {inlineAnnos.map(anno => ( // eslint-disable-next-line react/jsx-props-no-spreading - <Annotation {...this._props} fieldKey={this._props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} dataDoc={this._props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} /> + <Annotation {...this._props} fieldKey={this._props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} containerDataDoc={this._props.dataDoc} annoDoc={anno} key={`${anno[Id]}-annotation`} /> ))} </div> ); @@ -615,7 +615,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> { selectionText={this.selectionText} annotationLayer={this._annotationLayer.current} marqueeContainer={this._mainCont.current} - anchorMenuCrop={this._textSelecting ? undefined : this.crop} + anchorMenuCrop={this.crop} /> )} </div> |