From 146f8622d5bac2edc6b09f57c173bd057dfbcfad Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 8 Jul 2022 00:17:26 -0400 Subject: restructured currentUserUtils to avoid having import cycles. --- src/client/views/nodes/ImageBox.tsx | 362 +++++++++++++++++++----------------- 1 file changed, 188 insertions(+), 174 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 60eb48114..ffa839fcb 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,5 +1,5 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx'; -import { observer } from "mobx-react"; +import { observer } from 'mobx-react'; import { extname } from 'path'; import { DataSym, Doc, DocListCast, Opt, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; @@ -14,13 +14,12 @@ import { TraceMobx } from '../../../fields/util'; import { emptyFunction, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices'; -import { Docs, DocUtils } from '../../documents/Documents'; +import { DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { Networking } from '../../Network'; -import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; -import { ContextMenu } from "../../views/ContextMenu"; +import { ContextMenu } from '../../views/ContextMenu'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; @@ -29,62 +28,64 @@ import { AnchorMenu } from '../pdf/AnchorMenu'; import { StyleProp } from '../StyleProvider'; import { FaceRectangles } from './FaceRectangles'; import { FieldView, FieldViewProps } from './FieldView'; -import "./ImageBox.scss"; -import React = require("react"); +import './ImageBox.scss'; +import React = require('react'); export const pageSchema = createSchema({ - googlePhotosUrl: "string", - googlePhotosTags: "string" + googlePhotosUrl: 'string', + googlePhotosTags: 'string', }); const uploadIcons = { - idle: "downarrow.png", - loading: "loading.gif", - success: "greencheck.png", - failure: "redx.png" + idle: 'downarrow.png', + loading: 'loading.gif', + success: 'greencheck.png', + failure: 'redx.png', }; @observer export class ImageBox extends ViewBoxAnnotatableComponent() { - protected _multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined; - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); } + protected _multiTouchDisposer?: import('../../util/InteractionUtils').InteractionUtils.MultiTouchEventDisposer | undefined; + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(ImageBox, fieldKey); + } private _dropDisposer?: DragManager.DragDropDisposer; private _disposers: { [name: string]: IReactionDisposer } = {}; private _getAnchor: (savedAnnotations?: ObservableMap) => Opt = () => undefined; - @observable _curSuffix = ""; + @observable _curSuffix = ''; @observable _uploadIcon = uploadIcons.idle; 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 - + }; + 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 getAnchor = () => { const anchor = this._getAnchor?.(this._savedAnnotations); anchor && this.addDocument(anchor); return anchor ?? this.rootDoc; - } + }; componentDidMount() { this.props.setContentView?.(this); // bcz: do not remove this. without it, stepping into an image in the lightbox causes an infinite loop.... - this._disposers.sizer = reaction(() => ( - { + this._disposers.sizer = reaction( + () => ({ forceFull: this.props.renderDepth < 1 || this.layoutDoc._showFullRes, scrSize: this.props.ScreenToLocalTransform().inverse().transformDirection(this.nativeSize.nativeWidth, this.nativeSize.nativeHeight)[0] / this.nativeSize.nativeWidth, - selected: this.props.isSelected() + selected: this.props.isSelected(), }), - ({ forceFull, scrSize, selected }) => this._curSuffix = this.fieldKey === "icon" ? "_m" : forceFull ? "_o" : scrSize < 0.25 ? "_s" : scrSize < 0.5 ? "_m" : scrSize < 0.8 || !selected ? "_l" : "_o", - { fireImmediately: true, delay: 1000 }); - this._disposers.path = reaction(() => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }), + ({ forceFull, scrSize, selected }) => (this._curSuffix = this.fieldKey === 'icon' ? '_m' : forceFull ? '_o' : scrSize < 0.25 ? '_s' : scrSize < 0.5 ? '_m' : scrSize < 0.8 || !selected ? '_l' : '_o'), + { fireImmediately: true, delay: 1000 } + ); + this._disposers.path = reaction( + () => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }), ({ nativeSize, width }) => { if (true || !this.layoutDoc._height) { - this.layoutDoc._height = width * nativeSize.nativeHeight / nativeSize.nativeWidth; + this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth; } }, - { fireImmediately: true }); + { fireImmediately: true } + ); } componentWillUnmount() { @@ -96,10 +97,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent { if (de.complete.docDragData) { if (de.metaKey) { - de.complete.docDragData.droppedDocuments.forEach(action((drop: Doc) => { - Doc.AddDocToList(this.dataDoc, this.fieldKey + "-alternates", drop); - e.stopPropagation(); - })); + de.complete.docDragData.droppedDocuments.forEach( + action((drop: Doc) => { + Doc.AddDocToList(this.dataDoc, this.fieldKey + '-alternates', drop); + e.stopPropagation(); + }) + ); } else if (de.altKey || !this.dataDoc[this.fieldKey]) { const layoutDoc = de.complete.docDragData?.draggedDocuments[0]; const targetField = Doc.LayoutFieldKey(layoutDoc); @@ -112,20 +115,20 @@ export class ImageBox extends ViewBoxAnnotatableComponent this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes + resolution = () => (this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes); @undoBatch rotate = action(() => { - const nw = NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"]); - const nh = NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"]); + const nw = NumCast(this.dataDoc[this.fieldKey + '-nativeWidth']); + 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 + "-nativeWidth"] = nh; - this.dataDoc[this.fieldKey + "-nativeHeight"] = nw; + 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; this.layoutDoc._height = w; }); @@ -134,15 +137,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent { const field = Cast(this.dataDoc[this.fieldKey], ImageField); if (field) { const funcs: ContextMenuProps[] = []; - funcs.push({ description: "Rotate Clockwise 90", event: this.rotate, icon: "expand-arrows-alt" }); - funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? "Dynamic Res" : "Full Res"}`, event: this.resolution, icon: "expand-arrows-alt" }); - funcs.push({ description: "Copy path", event: () => Utils.CopyText(this.choosePath(field.url)), icon: "expand-arrows-alt" }); + funcs.push({ description: 'Rotate Clockwise 90', event: this.rotate, icon: 'expand-arrows-alt' }); + funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? 'Dynamic Res' : 'Full Res'}`, event: this.resolution, icon: 'expand-arrows-alt' }); + funcs.push({ description: 'Copy path', event: () => 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" }); + funcs.push({ description: 'Export to Google Photos', event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: 'caret-square-right' }); - const existingAnalyze = ContextMenu.Instance?.findByDescription("Analyzers..."); - const modes: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : []; - modes.push({ description: "Generate Tags", event: this.generateMetadata, icon: "tag" }); - modes.push({ description: "Find Faces", event: this.extractFaces, icon: "camera" }); + const existingAnalyze = ContextMenu.Instance?.findByDescription('Analyzers...'); + const modes: ContextMenuProps[] = existingAnalyze && 'subitems' in existingAnalyze ? existingAnalyze.subitems : []; + modes.push({ description: 'Generate Tags', event: this.generateMetadata, icon: 'tag' }); + modes.push({ description: 'Find Faces', event: this.extractFaces, icon: 'camera' }); //modes.push({ description: "Recommend", event: this.extractText, icon: "brain" }); - !existingAnalyze && ContextMenu.Instance?.addItem({ description: "Analyzers...", subitems: modes, icon: "hand-point-right" }); + !existingAnalyze && ContextMenu.Instance?.addItem({ description: 'Analyzers...', subitems: modes, icon: 'hand-point-right' }); } - ContextMenu.Instance?.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); + ContextMenu.Instance?.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' }); } - } + }; extractFaces = () => { const converter = (results: any) => { return results.map((face: CognitiveServices.Image.Face) => Doc.Get.FromJson({ data: face, title: `Face: ${face.faceId}` })!); }; - this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.fieldKey + "-faces"], this.url, Service.Face, converter); - } + this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.fieldKey + '-faces'], this.url, Service.Face, converter); + }; generateMetadata = (threshold: Confidence = Confidence.Excellent) => { const converter = (results: any) => { - const tagDoc = new Doc; + const tagDoc = new Doc(); const tagsList = new List(); results.tags.map((tag: Tag) => { tagsList.push(tag.name); - const sanitized = tag.name.replace(" ", "_"); + const sanitized = tag.name.replace(' ', '_'); tagDoc[sanitized] = ComputedField.MakeFunction(`(${tag.confidence} >= this.confidence) ? ${tag.confidence} : "${ComputedField.undefined}"`); }); - this.dataDoc[this.fieldKey + "-generatedTags"] = tagsList; - tagDoc.title = "Generated Tags Doc"; + this.dataDoc[this.fieldKey + '-generatedTags'] = tagsList; + tagDoc.title = 'Generated Tags Doc'; tagDoc.confidence = threshold; return tagDoc; }; - this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.fieldKey + "-generatedTagsDoc"], this.url, Service.ComputerVision, converter); - } + this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.fieldKey + '-generatedTagsDoc'], this.url, Service.ComputerVision, converter); + }; @computed private get url() { const data = Cast(this.dataDoc[this.fieldKey], ImageField); @@ -225,9 +228,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent { const remoteUrl = this.dataDoc.googlePhotosUrl; - return !remoteUrl ? (null) : ( window.open(remoteUrl)} - />); - } + return !remoteUrl ? null : window.open(remoteUrl)} />; + }; considerGooglePhotosTags = () => { const tags = this.dataDoc.googlePhotosTags; - return !tags ? (null) : (); - } + return !tags ? null : ; + }; @computed private get considerDownloadIcon() { const data = this.dataDoc[this.fieldKey]; if (!(data instanceof ImageField)) { - return (null); + return null; } const primary = data.url.href; if (primary.includes(window.location.origin)) { - return (null); + return null; } return ( { const { dataDoc } = this; const { success, failure, idle, loading } = uploadIcons; - runInAction(() => this._uploadIcon = loading); - const [{ accessPaths }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [primary] }); - dataDoc[this.props.fieldKey + "-originalUrl"] = primary; + runInAction(() => (this._uploadIcon = loading)); + const [{ accessPaths }] = await Networking.PostToServer('/uploadRemoteImage', { sources: [primary] }); + dataDoc[this.props.fieldKey + '-originalUrl'] = primary; let succeeded = true; let data: ImageField | undefined; try { @@ -276,13 +275,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent this._uploadIcon = succeeded ? success : failure); - setTimeout(action(() => { - this._uploadIcon = idle; - if (data) { - dataDoc[this.fieldKey] = data; - } - }), 2000); + runInAction(() => (this._uploadIcon = succeeded ? success : failure)); + setTimeout( + action(() => { + this._uploadIcon = idle; + if (data) { + dataDoc[this.fieldKey] = data; + } + }), + 2000 + ); }} /> ); @@ -290,18 +292,21 @@ export class ImageBox extends ViewBoxAnnotatableComponent Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url).filter(url => url).map(url => this.choosePath(url)); // access the primary layout data of the alternate documents + const alts = DocListCast(this.dataDoc[this.fieldKey + '-alternates']); // retrieve alternate documents that may be rendered as alternate images + const altpaths = alts + .map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url) + .filter(url => url) + .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths; - return paths.length ? paths : [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")]; + return paths.length ? paths : [Utils.CorsProxy('http://www.cs.brown.edu/~bcz/noImage.png')]; } @computed get content() { @@ -310,39 +315,35 @@ export class ImageBox extends ViewBoxAnnotatableComponent -
- - {fadepath === srcpath ? (null) : -
- -
} + return ( +
+
+ + {fadepath === srcpath ? null : ( +
+ +
+ )} +
+ {this.considerDownloadIcon} + {this.considerGooglePhotosLink()} +
- {this.considerDownloadIcon} - {this.considerGooglePhotosLink()} - -
; + ); } screenToLocalTransform = this.props.ScreenToLocalTransform; @@ -357,66 +358,79 @@ export class ImageBox extends ViewBoxAnnotatableComponent; } marqueeDown = (e: React.PointerEvent) => { - if (!e.altKey && e.button === 0 && NumCast(this.rootDoc._viewScale,1) <= NumCast(this.rootDoc.viewScaleMin,1) && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool)) { - setupMoveUpEvents(this, e, action(e => { - MarqueeAnnotator.clearAnnotations(this._savedAnnotations); - this._marqueeing = [e.clientX, e.clientY]; - return true; - }), returnFalse, () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), false); + if (!e.altKey && e.button === 0 && NumCast(this.rootDoc._viewScale, 1) <= NumCast(this.rootDoc.viewScaleMin, 1) && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) { + setupMoveUpEvents( + this, + e, + action(e => { + MarqueeAnnotator.clearAnnotations(this._savedAnnotations); + this._marqueeing = [e.clientX, e.clientY]; + return true; + }), + returnFalse, + () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), + false + ); } - } + }; @action finishMarquee = () => { this._getAnchor = AnchorMenu.Instance?.GetAnchor; this._marqueeing = undefined; this.props.select(false); - } + }; savedAnnotations = () => this._savedAnnotations; render() { TraceMobx(); const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding); - const borderRadius = borderRad?.includes("px") ? `${Number(borderRad.split("px")[0]) / (this.props.scaling?.() || 1)}px` : borderRad; - return (
- - {this.contentFunc} - - {this.annotationLayer} - {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) : - } -
); + const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / (this.props.scaling?.() || 1)}px` : borderRad; + return ( +
+ + {this.contentFunc} + + {this.annotationLayer} + {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : ( + + )} +
+ ); } - } -- cgit v1.2.3-70-g09d2