diff options
Diffstat (limited to 'src/client/views/pdf')
| -rw-r--r-- | src/client/views/pdf/AnchorMenu.tsx | 186 | ||||
| -rw-r--r-- | src/client/views/pdf/Annotation.tsx | 4 | ||||
| -rw-r--r-- | src/client/views/pdf/PDFViewer.scss | 8 | ||||
| -rw-r--r-- | src/client/views/pdf/PDFViewer.tsx | 41 |
4 files changed, 112 insertions, 127 deletions
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index 5480600b0..8e53a87f6 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -15,6 +15,9 @@ import { GPTPopup, GPTPopupMode } from './GPTPopup/GPTPopup'; import { LightboxView } from '../LightboxView'; import { EditorView } from 'prosemirror-view'; import './AnchorMenu.scss'; +import { ColorPicker, Group, IconButton, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components'; +import { StrCast } from '../../../fields/Types'; +import { DocumentType } from '../../documents/DocumentTypes'; @observer export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { @@ -42,7 +45,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { ]; @observable private highlightColor: string = 'rgba(245, 230, 95, 0.616)'; - @observable private _showLinkPopup: boolean = false; @observable public Status: 'marquee' | 'annotation' | '' = ''; @@ -131,7 +133,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { () => this._opacity, opacity => { if (!opacity) { - this._showLinkPopup = false; this.setGPTPopupVis(false); this.setGPTPopupText(''); } @@ -141,7 +142,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { this._disposer = reaction( () => SelectionManager.Views().slice(), selected => { - this._showLinkPopup = false; this.setGPTPopupVis(false); this.setGPTPopupText(''); AnchorMenu.Instance.fadeOut(true); @@ -253,49 +253,22 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { AnchorMenu.Instance.fadeOut(true); }; - @action - toggleLinkPopup = (e: React.MouseEvent) => { - //ignore the potential null type error because this method cannot be called unless the user selects text and clicks the link button - //change popup visibility field to visible - this._showLinkPopup = !this._showLinkPopup; - }; - @computed get highlighter() { - const button = ( - <button className="antimodeMenu-button anchor-color-preview-button" title="" key="highlighter-button" onClick={this.highlightClicked}> - <div className="anchor-color-preview"> - <FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: 'transform 0.1s', transform: 'rotate(-45deg)' }} /> - <div className="color-preview" style={{ backgroundColor: this.highlightColor }}></div> - </div> - </button> - ); - - const dropdownContent = ( - <div className="dropdown"> - <p>Change highlighter color:</p> - <div className="color-wrapper"> - {this._palette.map(color => { - if (color) { - return this.highlightColor === color ? ( - <button className="color-button active" key={`active ${color}`} style={{ backgroundColor: color }} onPointerDown={e => this.changeHighlightColor(color, e)}></button> - ) : ( - <button className="color-button" key={`inactive ${color}`} style={{ backgroundColor: color }} onPointerDown={e => this.changeHighlightColor(color, e)}></button> - ); - } - })} - </div> - </div> - ); return ( - <Tooltip key="highlighter" title={<div className="dash-tooltip">{'Click to Highlight'}</div>}> - <div className="anchorMenu-highlighter"> - <ButtonDropdown key={'highlighter'} button={button} dropdownContent={dropdownContent} pdf={true} /> - </div> - </Tooltip> + <Group> + <IconButton + icon={<FontAwesomeIcon icon="highlighter" style={{ transition: 'transform 0.1s', transform: 'rotate(-45deg)' }} />} + tooltip={'Click to Highlight'} + onClick={this.highlightClicked} + colorPicker={this.highlightColor} + color={StrCast(Doc.UserDoc().userColor)} + /> + <ColorPicker selectedColor={this.highlightColor} setFinalColor={this.changeHighlightColor} setSelectedColor={this.changeHighlightColor} size={Size.XSMALL} /> + </Group> ); } - @action changeHighlightColor = (color: string, e: React.PointerEvent) => { + @action changeHighlightColor = (color: string) => { const col: ColorState = { hex: color, hsl: { a: 0, h: 0, s: 0, l: 0, source: '' }, @@ -304,8 +277,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { oldHue: 0, source: '', }; - e.preventDefault(); - e.stopPropagation(); this.highlightColor = Utils.colorString(col); }; @@ -317,7 +288,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { canSummarize = (): boolean => { const docs = SelectionManager.Docs(); if (docs.length > 0) { - return docs.some(doc => doc.type === 'pdf' || doc.type === 'web'); + return docs.some(doc => doc.type === DocumentType.PDF || doc.type === DocumentType.WEB); } return false; }; @@ -339,18 +310,20 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { this.Status === 'marquee' ? ( <> {this.highlighter} - <Tooltip key="annotate" title={<div className="dash-tooltip">Drag to Place Annotation</div>}> - <button className="antimodeMenu-button annotate" ref={this._commentCont} onPointerDown={this.pointerDown} style={{ cursor: 'grab' }}> - <FontAwesomeIcon icon="comment-alt" size="lg" /> - </button> - </Tooltip> + <IconButton + tooltip="Drag to Place Annotation" // + onPointerDown={this.pointerDown} + icon={<FontAwesomeIcon icon="comment-alt" />} + color={StrCast(Doc.UserDoc().userColor)} + /> {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/} {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && ( - <Tooltip key="gpt" title={<div className="dash-tooltip">Summarize with AI</div>}> - <button className="antimodeMenu-button annotate" onPointerDown={this.gptSummarize} style={{ cursor: 'grab' }}> - <FontAwesomeIcon icon="comment-dots" size="lg" /> - </button> - </Tooltip> + <IconButton + tooltip="Summarize with AI" // + onPointerDown={this.gptSummarize} + icon={<FontAwesomeIcon icon="comment-dots" size="lg" />} + color={StrCast(Doc.UserDoc().userColor)} + /> )} <GPTPopup key="gptpopup" @@ -364,59 +337,74 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> { mode={this.GPTMode} /> {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : ( - <Tooltip key="annoaudiotate" title={<div className="dash-tooltip">Click to Record Annotation</div>}> - <button className="antimodeMenu-button annotate" onPointerDown={this.audioDown} style={{ cursor: 'grab' }}> - <FontAwesomeIcon icon="microphone" size="lg" /> - </button> - </Tooltip> + <IconButton + tooltip="Click to Record Annotation" // + onPointerDown={this.audioDown} + icon={<FontAwesomeIcon icon="microphone" />} + color={StrCast(Doc.UserDoc().userColor)} + /> )} {this.canEdit() && ( - <Tooltip key="gpttextedit" title={<div className="dash-tooltip">AI edit suggestions</div>}> - <button className="antimodeMenu-button annotate" onPointerDown={this.gptEdit} style={{ cursor: 'grab' }}> - <FontAwesomeIcon icon="pencil-alt" size="lg" /> - </button> - </Tooltip> + <IconButton + tooltip="AI edit suggestions" // + onPointerDown={this.gptEdit} + icon={<FontAwesomeIcon icon="pencil-alt" />} + color={StrCast(Doc.UserDoc().userColor)} + /> )} - <Tooltip key="link" title={<div className="dash-tooltip">Find document to link to selected text</div>}> - <button className="antimodeMenu-button link" onPointerDown={this.toggleLinkPopup}> - <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(1.5)' }} icon={'search'} size="lg" /> - <FontAwesomeIcon style={{ position: 'absolute', transform: 'scale(0.5)', transformOrigin: 'top left', top: 12, left: 12 }} icon={'link'} size="lg" /> - </button> - </Tooltip> - <LinkPopup key="popup" showPopup={this._showLinkPopup} linkCreateAnchor={this.onMakeAnchor} />, + <Popup + tooltip="Find document to link to selected text" // + type={Type.PRIM} + icon={<FontAwesomeIcon icon={'search'} />} + popup={<LinkPopup key="popup" linkCreateAnchor={this.onMakeAnchor} />} + color={StrCast(Doc.UserDoc().userColor)} + /> {AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? null : ( - <Tooltip key="crop" title={<div className="dash-tooltip">Click/Drag to create cropped image</div>}> - <button className="antimodeMenu-button annotate" onPointerDown={this.cropDown} style={{ cursor: 'grab' }}> - <FontAwesomeIcon icon="image" size="lg" /> - </button> - </Tooltip> + <IconButton + tooltip="Click/Drag to create cropped image" // + onPointerDown={this.cropDown} + icon={<FontAwesomeIcon icon="image" />} + color={StrCast(Doc.UserDoc().userColor)} + /> )} </> ) : ( <> - <Tooltip key="trash" title={<div className="dash-tooltip">Remove Link Anchor</div>}> - <button className="antimodeMenu-button" style={{ display: this.Delete === returnFalse ? 'none' : undefined }} onPointerDown={this.Delete}> - <FontAwesomeIcon icon="trash-alt" size="lg" /> - </button> - </Tooltip> - <Tooltip key="Pin" title={<div className="dash-tooltip">Pin to Presentation</div>}> - <button className="antimodeMenu-button" style={{ display: this.PinToPres === returnFalse ? 'none' : undefined }} onPointerDown={this.PinToPres}> - <FontAwesomeIcon icon="map-pin" size="lg" /> - </button> - </Tooltip> - <Tooltip key="trail" title={<div className="dash-tooltip">Show Linked Trail</div>}> - <button className="antimodeMenu-button" style={{ display: this.ShowTargetTrail === returnFalse ? 'none' : undefined }} onPointerDown={this.ShowTargetTrail}> - <FontAwesomeIcon icon="taxi" size="lg" /> - </button> - </Tooltip> - <Tooltip key="toggle" title={<div className="dash-tooltip">make target visibility toggle on click</div>}> - <button - className="antimodeMenu-button" - style={{ display: this.IsTargetToggler === returnFalse ? 'none' : undefined, color: this.IsTargetToggler() ? 'black' : 'white', backgroundColor: this.IsTargetToggler() ? 'white' : 'black' }} - onPointerDown={this.MakeTargetToggle}> - <FontAwesomeIcon icon="thumbtack" size="lg" /> - </button> - </Tooltip> + {this.Delete !== returnFalse && ( + <IconButton + tooltip="Remove Link Anchor" // + onPointerDown={this.Delete} + icon={<FontAwesomeIcon icon="trash-alt" />} + color={StrCast(Doc.UserDoc().userColor)} + /> + )} + {this.PinToPres !== returnFalse && ( + <IconButton + tooltip="Pin to Presentation" // + onPointerDown={this.PinToPres} + icon={<FontAwesomeIcon icon="map-pin" />} + color={StrCast(Doc.UserDoc().userColor)} + /> + )} + {this.ShowTargetTrail !== returnFalse && ( + <IconButton + tooltip="Show Linked Trail" // + onPointerDown={this.ShowTargetTrail} + icon={<FontAwesomeIcon icon="taxi" />} + color={StrCast(Doc.UserDoc().userColor)} + /> + )} + {this.IsTargetToggler !== returnFalse && ( + <Toggle + tooltip={'Make target visibility toggle on click'} + type={Type.PRIM} + toggleType={ToggleType.BUTTON} + toggleStatus={this.IsTargetToggler()} + onClick={this.MakeTargetToggle} + icon={<FontAwesomeIcon icon="thumbtack" />} + color={StrCast(Doc.UserDoc().userColor)} + /> + )} </> ); diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index db6b1f011..caa72c9dc 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -24,7 +24,7 @@ export class Annotation extends React.Component<IAnnotationProps> { render() { return ( <div style={{ display: this.props.anno.textCopied && !Doc.isBrushedHighlightedDegree(this.props.anno) ? 'none' : undefined }}> - {DocListCast(this.props.anno.textInlineAnnotations).map(a => ( + {DocListCast(this.props.anno.text_inlineAnnotations).map(a => ( <RegionAnnotation pointerEvents={this.props.pointerEvents} {...this.props} document={a} key={a[Id]} /> ))} </div> @@ -61,7 +61,7 @@ class RegionAnnotation extends React.Component<IRegionAnnotationProps> { isTargetToggler = () => BoolCast(this.annoTextRegion.followLinkToggle); @undoBatch showTargetTrail = (anchor: Doc) => { - const trail = DocCast(anchor.presTrail); + const trail = DocCast(anchor.presentationTrail); if (trail) { Doc.ActivePresentation = trail; this.props.addDocTab(trail, OpenWhere.replaceRight); diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss index 470aa3eb1..cfe07f6cb 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -26,15 +26,9 @@ .textLayer { opacity: unset; mix-blend-mode: multiply; // bcz: makes text fuzzy! - - // span { - // padding-right: 5px; - // padding-bottom: 4px; - // } } - .textLayer ::selection { - background: #accef7; + background: #accef76a; } // should match the backgroundColor in createAnnotation() diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index dd202418b..395fbd1ca 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -26,6 +26,7 @@ import { Annotation } from './Annotation'; import './PDFViewer.scss'; import React = require('react'); import { GPTPopup } from './GPTPopup/GPTPopup'; +import { InkingStroke } from '../InkingStroke'; const PDFJSViewer = require('pdfjs-dist/web/pdf_viewer'); const pdfjsLib = require('pdfjs-dist'); const _global = (window /* browser */ || global) /* node */ as any; @@ -67,7 +68,7 @@ export class PDFViewer extends React.Component<IViewerProps> { private _styleRule: any; // stylesheet rule for making hyperlinks clickable private _retries = 0; // number of times tried to create the PDF viewer private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean) => void); - private _setBrushViewer: undefined | ((view: { width: number; height: number; panX: number; panY: number }) => void); + private _setBrushViewer: undefined | ((view: { width: number; height: number; panX: number; panY: number }, transTime: number) => void); private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef(); private _disposers: { [name: string]: IReactionDisposer } = {}; private _viewer: React.RefObject<HTMLDivElement> = React.createRef(); @@ -94,7 +95,7 @@ export class PDFViewer extends React.Component<IViewerProps> { return DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + '_annotations']), this.props.childFilters(), this.props.childFiltersByRanges()); } @computed get inlineTextAnnotations() { - return this.allAnnotations.filter(a => a.textInlineAnnotations); + return this.allAnnotations.filter(a => a.text_inlineAnnotations); } componentDidMount = async () => { @@ -194,7 +195,7 @@ export class PDFViewer extends React.Component<IViewerProps> { return focusSpeed; }; crop = (region: Doc | undefined, addCrop?: boolean) => this.props.crop(region, addCrop); - brushView = (view: { width: number; height: number; panX: number; panY: number }) => this._setBrushViewer?.(view); + brushView = (view: { width: number; height: number; panX: number; panY: number }, transTime: number) => this._setBrushViewer?.(view, transTime); @action setupPdfJsViewer = async () => { @@ -469,7 +470,7 @@ export class PDFViewer extends React.Component<IViewerProps> { }; setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func); - setBrushViewer = (func?: (view: { width: number; height: number; panX: number; panY: number }) => void) => (this._setBrushViewer = func); + setBrushViewer = (func?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number) => void) => (this._setBrushViewer = func); @action onZoomWheel = (e: React.WheelEvent) => { @@ -483,16 +484,17 @@ export class PDFViewer extends React.Component<IViewerProps> { } }; - pointerEvents = () => (this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none'); + pointerEvents = () => + this.props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() + ? 'all' // + : 'none'; @computed get annotationLayer() { + const inlineAnnos = this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).filter(anno => !anno.hidden); return ( <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.props.Document), transform: `scale(${NumCast(this.props.layoutDoc._freeform_scale, 1)})` }} ref={this._annotationLayer}> - {this.inlineTextAnnotations - .sort((a, b) => NumCast(a.y) - NumCast(b.y)) - .filter(anno => !anno.hidden) - .map(anno => ( - <Annotation {...this.props} fieldKey={this.props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} /> - ))} + {inlineAnnos.map(anno => ( + <Annotation {...this.props} fieldKey={this.props.fieldKey + '_annotations'} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} /> + ))} </div> ); } @@ -514,11 +516,12 @@ export class PDFViewer extends React.Component<IViewerProps> { panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1); panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()]; - opaqueFilter = () => [...this.props.childFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length ? [] : [Utils.IsOpaqueFilter()])]; + opaqueFilter = () => [...this.props.childFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length && this.props.isContentActive() ? [] : [Utils.IsOpaqueFilter()])]; childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => { if (doc instanceof Doc && property === StyleProp.PointerEvents) { - if (this.inlineTextAnnotations.includes(doc)) return 'none'; - return 'all'; + if (this.inlineTextAnnotations.includes(doc) || this.props.isContentActive() === false) return 'none'; + const isInk = doc && StrCast(Doc.Layout(doc).layout).includes(InkingStroke.name) && !props?.LayoutTemplateString; + return isInk ? 'visiblePainted' : 'all'; } return this.props.styleProvider?.(doc, props, property); }; @@ -536,8 +539,8 @@ export class PDFViewer extends React.Component<IViewerProps> { NativeWidth={returnZero} NativeHeight={returnZero} setContentView={emptyFunction} // override setContentView to do nothing - pointerEvents={SnappingManager.GetIsDragging() ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it. - childPointerEvents="all" // but freeform children need to get events to allow text editing, etc + pointerEvents={this.props.isContentActive() && (SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? returnAll : returnNone} // freeform view doesn't get events unless something is being dragged onto it. + childPointerEvents={this.props.isContentActive() !== false ? 'all' : 'none'} // but freeform children need to get events to allow text editing, etc renderDepth={this.props.renderDepth + 1} isAnnotationOverlay={true} fieldKey={this.props.fieldKey + '_annotations'} @@ -549,7 +552,6 @@ export class PDFViewer extends React.Component<IViewerProps> { ScreenToLocalTransform={this.overlayTransform} isAnyChildContentActive={returnFalse} isAnnotationOverlayScrollable={true} - dropAction="embed" childFilters={childFilters} select={emptyFunction} bringToFront={emptyFunction} @@ -558,14 +560,15 @@ export class PDFViewer extends React.Component<IViewerProps> { </div> ); @computed get overlayTransparentAnnotations() { - return this.renderAnnotations(this.transparentFilter, 'multiply', DragManager.docsBeingDragged.length ? 'none' : undefined); + const transparentChildren = DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + '_annotations']), this.transparentFilter(), []); + return !transparentChildren.length ? null : this.renderAnnotations(this.transparentFilter, 'multiply', DragManager.docsBeingDragged.length && this.props.isContentActive() ? 'none' : undefined); } @computed get overlayOpaqueAnnotations() { return this.renderAnnotations(this.opaqueFilter, this.allAnnotations.some(anno => anno.mixBlendMode) ? 'hard-light' : undefined); } @computed get overlayLayer() { return ( - <div style={{ pointerEvents: SnappingManager.GetIsDragging() ? 'all' : 'none' }}> + <div style={{ pointerEvents: this.props.isContentActive() && SnappingManager.GetIsDragging() ? 'all' : 'none' }}> {this.overlayTransparentAnnotations} {this.overlayOpaqueAnnotations} </div> |
