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.tsx101
1 files changed, 70 insertions, 31 deletions
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index e2b0ee7df..a79dda74e 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,20 +1,23 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
-import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction } from 'mobx';
+import { Colors } from 'browndash-components';
+import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { extname } from 'path';
import * as React from 'react';
-import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { Doc, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
+import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
-import { Cast, NumCast, StrCast } from '../../../fields/Types';
+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 { 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';
@@ -23,29 +26,39 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm/Collec
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent';
import { MarqueeAnnotator } from '../MarqueeAnnotator';
+import { OverlayView } from '../OverlayView';
import { AnchorMenu } from '../pdf/AnchorMenu';
import { StyleProp } from '../StyleProvider';
import { OpenWhere } from './DocumentView';
-import { FocusViewOptions, FieldView, FieldViewProps } from './FieldView';
+import { FieldView, FieldViewProps, FocusViewOptions } from './FieldView';
import './ImageBox.scss';
import { PinProps, PresBox } from './trails';
+export class ImageEditorData {
+ 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 });
+
+ 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 set Open(open: boolean) { ImageEditorData.set(open, this.imageData.rootDoc, this.imageData.source, this.imageData.addDoc); } // prettier-ignore
+ public static set Source(source: string) { ImageEditorData.set(this.imageData.open, this.imageData.rootDoc, source, this.imageData.addDoc); } // 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 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
export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implements ViewBoxInterface {
public static LayoutString(fieldKey: string) {
return FieldView.LayoutString(ImageBox, fieldKey);
}
-
- @observable public static imageRootDoc: Doc | undefined = undefined;
- @observable public static imageEditorOpen: boolean = false;
- @observable public static imageEditorSource: string = '';
- @observable public static addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined = undefined;
- @action public static setImageEditorOpen(open: boolean) {
- ImageBox.imageEditorOpen = open;
- }
- @action public static setImageEditorSource(source: string) {
- ImageBox.imageEditorSource = source;
- }
private _ignoreScroll = false;
private _forcedScroll = false;
private _dropDisposer?: DragManager.DragDropDisposer;
@@ -134,7 +147,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
if (de.metaKey || targetIsBullseye(e.target as HTMLElement)) {
added = de.complete.docDragData.droppedDocuments.reduce((last: boolean, drop: Doc) => {
this.layoutDoc[this.fieldKey + '_usePath'] = 'alternate:hover';
- return last && Doc.AddDocToList(this.dataDoc, this.fieldKey + '-alternates', drop);
+ return last && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', drop);
}, true);
} else if (de.altKey || !this.dataDoc[this.fieldKey]) {
const layoutDoc = de.complete.docDragData?.draggedDocuments[0];
@@ -159,8 +172,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
@undoBatch
setNativeSize = action(() => {
- const scaling = (this.DocumentView?.().screenToViewTransform().Scale || 1) / NumCast(this.layoutDoc._freeform_scale, 1);
- const nscale = NumCast(this._props.PanelWidth()) / scaling;
+ const nscale = NumCast(this._props.PanelWidth()) * NumCast(this.layoutDoc._freeform_scale, 1);
const nw = nscale / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']);
this.dataDoc[this.fieldKey + '_nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) * nw;
this.dataDoc[this.fieldKey + '_nativeWidth'] = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) * nw;
@@ -243,10 +255,10 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
funcs.push({
description: 'Open Image Editor',
event: action(() => {
- ImageBox.setImageEditorOpen(true);
- ImageBox.setImageEditorSource(this.choosePath(field.url));
- ImageBox.addDoc = this._props.addDocument;
- ImageBox.imageRootDoc = this.Document;
+ ImageEditorData.Open = true;
+ ImageEditorData.Source = this.choosePath(field.url);
+ ImageEditorData.AddDoc = this._props.addDocument;
+ ImageEditorData.RootDoc = this.Document;
}),
icon: 'pencil-alt',
});
@@ -259,7 +271,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
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 (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) return `/assets/unknown-file-icon-hi.png`;
+ 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);
return url.href.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
@@ -297,7 +309,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
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))}
style={{
- display: (this._props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || DocListCast(this.dataDoc[this.fieldKey + '-alternates']).length ? 'block' : 'none',
+ display: (this._props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || this.dataDoc[this.fieldKey + '_alternates'] ? 'block' : 'none',
width: 'min(10%, 25px)',
height: 'min(10%, 25px)',
background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
@@ -311,13 +323,15 @@ 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 = 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 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 altpaths =
+ alts
+ ?.map(doc => (doc instanceof Doc ? ImageCast(doc[Doc.LayoutFieldKey(doc)])?.url ?? defaultUrl : defaultUrl))
+ .filter(url => url)
+ .map(url => this.choosePath(url)) ?? []; // acc ess the primary layout data of the alternate documents
const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
- return paths.length ? paths : [Utils.CorsProxy('https://cs.brown.edu/~bcz/noImage.png')];
+ return paths.length ? paths : [defaultUrl.href];
}
@observable _error = '';
@@ -326,7 +340,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
@computed get content() {
TraceMobx();
- const backColor = DashColor(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor));
+ const backColor = DashColor(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) ?? Colors.WHITE);
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();
@@ -370,6 +384,7 @@ 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)) {
setupMoveUpEvents(
this,
@@ -468,4 +483,28 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() impl
</div>
);
}
+
+ public chooseImage = () => {
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.multiple = true;
+ input.accept = 'image/*';
+ input.onchange = async _e => {
+ const file = input.files?.[0];
+ if (file) {
+ const disposer = OverlayView.ShowSpinner();
+ const [{ result }] = await Networking.UploadFilesToServer({ file });
+ if (result instanceof Error) {
+ alert('Error uploading files - possibly due to unsupported file types');
+ } else {
+ this.dataDoc[this.fieldKey] = new ImageField(result.accessPaths.agnostic.client);
+ !(result instanceof Error) && DocUtils.assignImageInfo(result, this.dataDoc);
+ }
+ disposer();
+ } else {
+ console.log('No file selected');
+ }
+ };
+ input.click();
+ };
}