aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/ImageBox.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/ImageBox.tsx')
-rw-r--r--src/client/views/nodes/ImageBox.tsx124
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();