diff options
Diffstat (limited to 'src/client/views/nodes/ImageBox.tsx')
-rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 124 |
1 files changed, 82 insertions, 42 deletions
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index bb1f70f97..90b4a6740 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,10 +1,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; import { Colors } from 'browndash-components'; -import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { extname } from 'path'; import * as React from 'react'; +import { ClientUtils, DashColor, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents } from '../../../ClientUtils'; import { Doc, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; @@ -14,17 +15,17 @@ import { ObjectField } from '../../../fields/ObjectField'; import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { DashColor, emptyFunction, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils'; +import { emptyFunction } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { Networking } from '../../Network'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; -import { ContextMenu } from '../../views/ContextMenu'; +import { ContextMenu } from '../ContextMenu'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { ContextMenuProps } from '../ContextMenuItem'; -import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; +import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; import { MarqueeAnnotator } from '../MarqueeAnnotator'; import { OverlayView } from '../OverlayView'; import { AnchorMenu } from '../pdf/AnchorMenu'; @@ -32,26 +33,29 @@ import { StyleProp } from '../StyleProvider'; import { OpenWhere } from './DocumentView'; import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView'; import './ImageBox.scss'; -import { PinProps, PresBox } from './trails'; +import { PresBox } from './trails'; export class ImageEditorData { + // eslint-disable-next-line no-use-before-define private static _instance: ImageEditorData; private static get imageData() { return (ImageEditorData._instance ?? new ImageEditorData()).imageData; } // prettier-ignore @observable imageData: { rootDoc: Doc | undefined; open: boolean; source: string; addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean> } = observable({ rootDoc: undefined, open: false, source: '', addDoc: undefined }); - @action private static set = (open: boolean, rootDoc: Doc | undefined, source: string, addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) => (this._instance.imageData = { open, rootDoc, source, addDoc }); + @action private static set = (open: boolean, rootDoc: Doc | undefined, source: string, addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) => { + this._instance.imageData = { open, rootDoc, source, addDoc }; + }; constructor() { makeObservable(this); ImageEditorData._instance = this; } - public static get Open() { return ImageEditorData.imageData.open; } // prettier-ignore - public static get Source() { return ImageEditorData.imageData.source; } // prettier-ignore - public static get RootDoc() { return ImageEditorData.imageData.rootDoc; } // prettier-ignore - public static get AddDoc() { return ImageEditorData.imageData.addDoc; } // prettier-ignore + public static get Open() { return ImageEditorData.imageData.open; } // prettier-ignore public static set Open(open: boolean) { ImageEditorData.set(open, this.imageData.rootDoc, this.imageData.source, this.imageData.addDoc); } // prettier-ignore + public static get Source() { return ImageEditorData.imageData.source; } // prettier-ignore public static set Source(source: string) { ImageEditorData.set(this.imageData.open, this.imageData.rootDoc, source, this.imageData.addDoc); } // prettier-ignore + public static get RootDoc() { return ImageEditorData.imageData.rootDoc; } // prettier-ignore public static set RootDoc(rootDoc: Opt<Doc>) { ImageEditorData.set(this.imageData.open, rootDoc, this.imageData.source, this.imageData.addDoc); } // prettier-ignore + public static get AddDoc() { return ImageEditorData.imageData.addDoc; } // prettier-ignore public static set AddDoc(addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) { ImageEditorData.set(this.imageData.open, this.imageData.rootDoc, this.imageData.source, addDoc); } // prettier-ignore } @observer @@ -93,7 +97,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl if (anchor) { if (!addAsAnnotation) anchor.backgroundColor = 'transparent'; addAsAnnotation && this.addDocument(anchor); - PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: visibleAnchor ? false : true } }, this.Document); + PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !visibleAnchor } }, this.Document); return anchor; } return this.Document; @@ -106,10 +110,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl scrSize: (this.ScreenToLocalBoxXf().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0] / this.nativeSize.nativeWidth) * NumCast(this.layoutDoc._freeform_scale, 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'), + ({ 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 } ); - const layoutDoc = this.layoutDoc; + const { layoutDoc } = this; this._disposers.path = reaction( () => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width) }), ({ nativeSize, width }) => { @@ -121,10 +127,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl ); this._disposers.scroll = reaction( () => this.layoutDoc.layout_scrollTop, - s_top => { + sTop => { this._forcedScroll = true; - !this._ignoreScroll && this._mainCont.current && (this._mainCont.current.scrollTop = NumCast(s_top)); - this._mainCont.current?.scrollTo({ top: NumCast(s_top) }); + !this._ignoreScroll && this._mainCont.current && (this._mainCont.current.scrollTop = NumCast(sTop)); + this._mainCont.current?.scrollTo({ top: NumCast(sTop) }); this._forcedScroll = false; }, { fireImmediately: true } @@ -138,7 +144,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl @undoBatch drop = (e: Event, de: DragManager.DropEvent) => { if (de.complete.docDragData) { - let added: boolean | undefined = undefined; + let added: boolean | undefined; const targetIsBullseye = (ele: HTMLElement): boolean => { if (!ele) return false; if (ele === this._overlayIconRef.current) return true; @@ -168,7 +174,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl }; @undoBatch - resolution = () => (this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes); + resolution = () => { + this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes; + }; @undoBatch setNativeSize = action(() => { @@ -189,7 +197,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl const nh = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']); const w = this.layoutDoc._width; const h = this.layoutDoc._height; - this.dataDoc[this.fieldKey + '-rotation'] = (NumCast(this.dataDoc[this.fieldKey + '-rotation']) + 90) % 360; + this.dataDoc[this.fieldKey + '_rotation'] = (NumCast(this.dataDoc[this.fieldKey + '_rotation']) + 90) % 360; this.dataDoc[this.fieldKey + '_nativeWidth'] = nh; this.dataDoc[this.fieldKey + '_nativeHeight'] = nw; this.layoutDoc._width = h; @@ -197,7 +205,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl }); crop = (region: Doc | undefined, addCrop?: boolean) => { - if (!region) return; + if (!region) return undefined; const cropping = Doc.MakeCopy(region, true); const regionData = region[DocData]; regionData.lockedPosition = true; @@ -223,8 +231,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl croppingProto.type = DocumentType.IMG; croppingProto.layout = ImageBox.LayoutString('data'); croppingProto.data = ObjectField.MakeCopy(this.dataDoc[this.fieldKey] as ObjectField); - croppingProto['data_nativeWidth'] = anchw; - croppingProto['data_nativeHeight'] = anchh; + croppingProto.data_nativeWidth = anchw; + croppingProto.data_nativeHeight = anchh; croppingProto.freeform_scale = viewScale; croppingProto.freeform_scale_min = viewScale; croppingProto.freeform_panX = anchx / viewScale; @@ -244,14 +252,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl return cropping; }; - specificContextMenu = (e: React.MouseEvent): void => { + specificContextMenu = (): void => { const field = Cast(this.dataDoc[this.fieldKey], ImageField); if (field) { const funcs: ContextMenuProps[] = []; funcs.push({ description: 'Rotate Clockwise 90', event: this.rotate, icon: 'redo-alt' }); funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? 'Dynamic Res' : 'Full Res'}`, event: this.resolution, icon: 'expand' }); funcs.push({ description: 'Set Native Pixel Size', event: this.setNativeSize, icon: 'expand-arrows-alt' }); - funcs.push({ description: 'Copy path', event: () => Utils.CopyText(this.choosePath(field.url)), icon: 'copy' }); + funcs.push({ description: 'Copy path', event: () => ClientUtils.CopyText(this.choosePath(field.url)), icon: 'copy' }); funcs.push({ description: 'Open Image Editor', event: action(() => { @@ -270,7 +278,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl if (!url?.href) return ''; const lower = url.href.toLowerCase(); if (url.protocol === 'data') return url.href; - if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf('dashblobstore') === -1) return Utils.CorsProxy(url.href); + if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf('dashblobstore') === -1) return ClientUtils.CorsProxy(url.href); if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower) || lower.endsWith('/assets/unknown-file-icon-hi.png')) return `/assets/unknown-file-icon-hi.png`; const ext = extname(url.href); @@ -282,7 +290,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl TraceMobx(); const nativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth'], 500)); const nativeHeight = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], NumCast(this.layoutDoc[this.fieldKey + '_nativeHeight'], 500)); - const nativeOrientation = NumCast(this.dataDoc[this.fieldKey + '-nativeOrientation'], 1); + const nativeOrientation = NumCast(this.dataDoc[this.fieldKey + '_nativeOrientation'], 1); return { nativeWidth, nativeHeight, nativeOrientation }; } @computed get overlayImageIcon() { @@ -307,7 +315,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl <div className="imageBox-alternateDropTarget" ref={this._overlayIconRef} - onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => (this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined))} + onPointerDown={e => + setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, () => { + this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined; + }) + } style={{ display: (this._props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || this.dataDoc[this.fieldKey + '_alternates'] ? 'block' : 'none', width: 'min(10%, 25px)', @@ -324,7 +336,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl @computed get paths() { const field = Cast(this.dataDoc[this.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc const alts = this.dataDoc[this.fieldKey + '_alternates'] as any as List<Doc>; // retrieve alternate documents that may be rendered as alternate images - const defaultUrl = new URL(Utils.prepend('/assets/unknown-file-icon-hi.png')); + const defaultUrl = new URL(ClientUtils.prepend('/assets/unknown-file-icon-hi.png')); const altpaths = alts ?.map(doc => (doc instanceof Doc ? ImageCast(doc[Doc.LayoutFieldKey(doc)])?.url ?? defaultUrl : defaultUrl)) @@ -344,8 +356,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl const backAlpha = backColor.red() === 0 && backColor.green() === 0 && backColor.blue() === 0 ? backColor.alpha() : 1; const srcpath = this.layoutDoc.hideImage ? '' : this.paths[0]; const fadepath = this.layoutDoc.hideImage ? '' : this.paths.lastElement(); - const { nativeWidth, nativeHeight, nativeOrientation } = this.nativeSize; - const rotation = NumCast(this.dataDoc[this.fieldKey + '-rotation']); + const { nativeWidth, nativeHeight /* , nativeOrientation */ } = this.nativeSize; + const rotation = NumCast(this.dataDoc[this.fieldKey + '_rotation']); const aspect = rotation % 180 ? nativeHeight / nativeWidth : 1; let transformOrigin = 'center center'; let transform = `translate(0%, 0%) rotate(${rotation}deg) scale(${aspect})`; @@ -361,12 +373,32 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl const usePath = this.layoutDoc[`_${this.fieldKey}_usePath`]; return ( - <div className="imageBox-cont" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))} key={this.layoutDoc[Id]} ref={this.createDropTarget} onPointerDown={this.marqueeDown}> + <div + className="imageBox-cont" + onPointerEnter={action(() => { + this._isHovering = true; + })} + onPointerLeave={action(() => { + this._isHovering = false; + })} + key={this.layoutDoc[Id]} + ref={this.createDropTarget} + onPointerDown={this.marqueeDown}> <div className="imageBox-fader" style={{ opacity: backAlpha }}> - <img key="paths" src={srcpath} style={{ transform, transformOrigin }} onError={action(e => (this._error = e.toString()))} draggable={false} width={nativeWidth} /> + <img + alt="" + key="paths" + src={srcpath} + style={{ transform, transformOrigin }} + onError={action(e => { + this._error = e.toString(); + })} + draggable={false} + width={nativeWidth} + /> {fadepath === srcpath ? null : ( <div className={`imageBox-fadeBlocker${(this._isHovering && usePath === 'alternate:hover') || usePath === 'alternate' ? '-hover' : ''}`} style={{ transition: StrCast(this.layoutDoc.viewTransition, 'opacity 1000ms') }}> - <img className="imageBox-fadeaway" key="fadeaway" src={fadepath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} /> + <img alt="" className="imageBox-fadeaway" key="fadeaway" src={fadepath} style={{ transform, transformOrigin }} draggable={false} width={nativeWidth} /> </div> )} </div> @@ -384,14 +416,21 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl } screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this.ScreenToLocalBoxXf().Scale); marqueeDown = (e: React.PointerEvent) => { - if (!this.dataDoc[this.fieldKey]) return this.chooseImage(); - if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive() && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) { + if (!this.dataDoc[this.fieldKey]) { + this.chooseImage(); + } else if ( + !e.altKey && + e.button === 0 && + NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && + this._props.isContentActive() && + ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool) + ) { setupMoveUpEvents( this, e, - action(e => { + action(moveEv => { MarqueeAnnotator.clearAnnotations(this._savedAnnotations); - this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]); + this._marqueeref.current?.onInitiateSelection([moveEv.clientX, moveEv.clientY]); return true; }), returnFalse, @@ -419,7 +458,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl className="imageBox" onContextMenu={this.specificContextMenu} ref={this._mainCont} - onScroll={action(e => { + onScroll={action(() => { if (!this._forcedScroll) { if (this.layoutDoc._layout_scrollTop || this._mainCont.current?.scrollTop) { this._ignoreScroll = true; @@ -437,6 +476,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl }}> <CollectionFreeFormView ref={this._ffref} + // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} setContentViewBox={emptyFunction} NativeWidth={returnZero} @@ -444,8 +484,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl renderDepth={this._props.renderDepth + 1} fieldKey={this.annotationKey} styleProvider={this._props.styleProvider} - isAnnotationOverlay={true} - annotationLayerHostsContent={true} + isAnnotationOverlay + annotationLayerHostsContent PanelWidth={this._props.PanelWidth} PanelHeight={this._props.PanelHeight} ScreenToLocalTransform={this.screenToLocalTransform} @@ -476,7 +516,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl selectionText={returnEmptyString} annotationLayer={this._annotationLayer.current} marqueeContainer={this._mainCont.current} - highlightDragSrcColor={''} + highlightDragSrcColor="" anchorMenuCrop={this.crop} /> )} @@ -489,7 +529,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl input.type = 'file'; input.multiple = true; input.accept = 'image/*'; - input.onchange = async _e => { + input.onchange = async () => { const file = input.files?.[0]; if (file) { const disposer = OverlayView.ShowSpinner(); |