aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/apis/youtube/YoutubeBox.tsx5
-rw-r--r--src/client/documents/Documents.ts1
-rw-r--r--src/client/util/Import & Export/ImageUtils.ts41
-rw-r--r--src/client/views/DocumentDecorations.tsx12
-rw-r--r--src/client/views/PreviewCursor.tsx2
-rw-r--r--src/client/views/collections/CollectionSubView.tsx31
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx4
-rw-r--r--src/client/views/nodes/DocumentView.tsx1
-rw-r--r--src/client/views/nodes/VideoBox.tsx2
-rw-r--r--src/client/views/nodes/WebBox.tsx12
-rw-r--r--src/client/views/webcam/DashWebRTCVideo.tsx5
-rw-r--r--src/server/DashUploadUtils.ts46
12 files changed, 91 insertions, 71 deletions
diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx
index ceb80acbc..d3a15cd84 100644
--- a/src/client/apis/youtube/YoutubeBox.tsx
+++ b/src/client/apis/youtube/YoutubeBox.tsx
@@ -11,6 +11,7 @@ import { FieldView, FieldViewProps } from '../../views/nodes/FieldView';
import '../../views/nodes/WebBox.scss';
import './YoutubeBox.scss';
import * as React from 'react';
+import { SnappingManager } from '../../util/SnappingManager';
interface VideoTemplate {
thumbnailUrl: string;
@@ -355,9 +356,9 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
</div>
);
- const frozen = !this.props.isSelected() || DocumentView.Interacting;
+ const frozen = !this.props.isSelected() || SnappingManager.IsResizing;
- const classname = 'webBox-cont' + (this.props.isSelected() && Doc.ActiveTool === InkTool.None && !DocumentView.Interacting ? '-interactive' : '');
+ const classname = 'webBox-cont' + (this.props.isSelected() && Doc.ActiveTool === InkTool.None && !SnappingManager.IsResizing ? '-interactive' : '');
return (
<>
<div className={classname}>{content}</div>
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 0a4d3a294..79285deb5 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1873,6 +1873,7 @@ export namespace DocUtils {
proto['data_nativeHeight'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim;
proto['data_nativeWidth'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight);
}
+ proto.data_exif = JSON.stringify(result.exifData?.data);
proto.data_contentSize = result.contentSize;
// exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates
const latitude = result.exifData?.data?.GPSLatitude;
diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts
index 55d37f544..d99828956 100644
--- a/src/client/util/Import & Export/ImageUtils.ts
+++ b/src/client/util/Import & Export/ImageUtils.ts
@@ -4,28 +4,33 @@ import { Cast, StrCast, NumCast } from '../../../fields/Types';
import { Networking } from '../../Network';
import { Id } from '../../../fields/FieldSymbols';
import { Utils } from '../../../Utils';
+import { DocData } from '../../../fields/DocSymbols';
export namespace ImageUtils {
- export const ExtractExif = async (document: Doc): Promise<boolean> => {
+ export type imgInfo = {
+ contentSize: number;
+ nativeWidth: number;
+ nativeHeight: number;
+ source: string;
+ exifData: { error: string | undefined; data: string };
+ };
+ export const ExtractImgInfo = async (document: Doc): Promise<imgInfo | undefined> => {
const field = Cast(document.data, ImageField);
- if (!field) {
- return false;
+ return field ? await Networking.PostToServer('/inspectImage', { source: field.url.href }) : undefined;
+ };
+
+ export const AssignImgInfo = (document: Doc, data?: imgInfo) => {
+ if (data) {
+ data.nativeWidth && (document._height = (NumCast(document._width) * data.nativeHeight) / data.nativeWidth);
+ const proto = document[DocData];
+ const field = Doc.LayoutFieldKey(document);
+ proto[`${field}_nativeWidth`] = data.nativeWidth;
+ proto[`${field}_nativeHeight`] = data.nativeHeight;
+ proto[`${field}_path`] = data.source;
+ proto[`${field}_exif`] = JSON.stringify(data.exifData.data);
+ proto[`${field}_contentSize`] = data.contentSize ? data.contentSize : undefined;
}
- const source = field.url.href;
- const {
- contentSize,
- nativeWidth,
- nativeHeight,
- exifData: { error, data },
- } = await Networking.PostToServer('/inspectImage', { source });
- document.exif = error || Doc.Get.FromJson({ data });
- const proto = Doc.GetProto(document);
- nativeWidth && (document._height = (NumCast(document._width) * nativeHeight) / nativeWidth);
- proto['data_nativeWidth'] = nativeWidth;
- proto['data_nativeHeight'] = nativeHeight;
- proto['data-path'] = source;
- proto.data_contentSize = contentSize ? contentSize : undefined;
- return data !== undefined;
+ return document;
};
export const ExportHierarchyToFileSystem = async (collection: Doc): Promise<void> => {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 7003485d2..f3da133c3 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -310,7 +310,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
*/
@action
onRadiusDown = (e: React.PointerEvent): void => {
- this._isRounding = DocumentView.Interacting = true;
+ SnappingManager.SetIsResizing(SelectionManager.Docs.lastElement());
+ this._isRounding = true;
this._resizeUndo = UndoManager.StartBatch('DocDecs set radius');
setupMoveUpEvents(
this,
@@ -327,7 +328,8 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
return false;
},
action(e => {
- DocumentView.Interacting = this._isRounding = false;
+ SnappingManager.SetIsResizing(undefined);
+ this._isRounding = false;
this._resizeUndo?.end();
}), // upEvent
emptyFunction,
@@ -439,10 +441,9 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
@action
onPointerDown = (e: React.PointerEvent): void => {
- SnappingManager.SetIsResizing(SelectionManager.Docs.lastElement());
+ SnappingManager.SetIsResizing(SelectionManager.Docs.lastElement()); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
e.stopPropagation();
- DocumentView.Interacting = true; // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
this._resizeHdlId = e.currentTarget.className;
const bounds = e.currentTarget.getBoundingClientRect();
this._offset = { x: this._resizeHdlId.toLowerCase().includes('left') ? bounds.right - e.clientX : bounds.left - e.clientX, y: this._resizeHdlId.toLowerCase().includes('top') ? bounds.bottom - e.clientY : bounds.top - e.clientY };
@@ -595,7 +596,6 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
onPointerUp = (e: PointerEvent): void => {
SnappingManager.SetIsResizing(undefined);
SnappingManager.clearSnapLines();
- DocumentView.Interacting = false;
this._resizeHdlId = '';
this._resizeUndo?.end();
@@ -766,7 +766,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora
top: bounds.y - this._resizeBorderWidth / 2,
transformOrigin,
background: SnappingManager.ShiftKey ? undefined : 'yellow',
- pointerEvents: SnappingManager.ShiftKey || DocumentView.Interacting ? 'none' : 'all',
+ pointerEvents: SnappingManager.ShiftKey || SnappingManager.IsResizing ? 'none' : 'all',
display: SelectionManager.Views.length <= 1 || hideDecorations ? 'none' : undefined,
transform: `rotate(${rotation}deg)`,
}}
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 5ec9a7d46..166afc0c2 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -102,7 +102,7 @@ export class PreviewCursor extends ObservableReactComponent<{}> {
x: newPoint[0],
y: newPoint[1],
});
- ImageUtils.ExtractExif(doc);
+ ImageUtils.ExtractImgInfo(doc);
this._addDocument?.(doc);
})();
}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index b56973dc6..2ff435ce2 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -4,11 +4,11 @@ import * as rp from 'request-promise';
import { Utils, returnFalse } from '../../../Utils';
import CursorField from '../../../fields/CursorField';
import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
-import { AclPrivate } from '../../../fields/DocSymbols';
+import { AclPrivate, DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
-import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
import { WebField } from '../../../fields/URLField';
import { GetEffectiveAcl, TraceMobx } from '../../../fields/util';
import { GestureUtils } from '../../../pen-gestures/GestureUtils';
@@ -309,20 +309,19 @@ export function CollectionSubView<X>(moreProps?: X) {
const cors = img.includes('corsProxy') ? img.match(/http.*corsProxy\//)![0] : '';
img = cors ? img.replace(cors, '') : img;
if (img) {
- const split = img.split('src="')[1].split('"')[0];
- let source = split;
- if (split.startsWith('data:image') && split.includes('base64')) {
- const [{ accessPaths }] = await Networking.PostToServer('/uploadRemoteImage', { sources: [split] });
- if (accessPaths.agnostic.client.indexOf('dashblobstore') === -1) {
- source = Utils.prepend(accessPaths.agnostic.client);
- } else {
- source = accessPaths.agnostic.client;
- }
- }
- if (source.startsWith('http')) {
- const doc = Docs.Create.ImageDocument(source, { ...options, _width: 300 });
- ImageUtils.ExtractExif(doc);
- addDocument(doc);
+ const imgSrc = img.split('src="')[1].split('"')[0];
+ const imgOpts = { ...options, _width: 300 };
+ if (imgSrc.startsWith('data:image') && imgSrc.includes('base64')) {
+ const result = (await Networking.PostToServer('/uploadRemoteImage', { sources: [imgSrc] })).lastElement();
+ const newImgSrc =
+ result.accessPaths.agnostic.client.indexOf('dashblobstore') === -1 //
+ ? Utils.prepend(result.accessPaths.agnostic.client)
+ : result.accessPaths.agnostic.client;
+
+ addDocument(ImageUtils.AssignImgInfo(Docs.Create.ImageDocument(newImgSrc, imgOpts), result));
+ } else if (imgSrc.startsWith('http')) {
+ const doc = Docs.Create.ImageDocument(imgSrc, imgOpts);
+ addDocument(ImageUtils.AssignImgInfo(doc, await ImageUtils.ExtractImgInfo(doc)));
}
return;
} else {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 1574deede..645e9cff7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1190,7 +1190,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
@computed get childPointerEvents() {
const engine = this._props.layoutEngine?.() || StrCast(this._props.Document._layoutEngine);
- const pointerevents = DocumentView.Interacting
+ const pointerevents = SnappingManager.IsResizing
? 'none'
: this._props.childPointerEvents?.() ??
(this._props.viewDefDivClick || //
@@ -1479,7 +1479,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._disposers.pointerevents = reaction(
() => {
const engine = this._props.layoutEngine?.() || StrCast(this._props.Document._layoutEngine);
- return DocumentView.Interacting
+ return SnappingManager.IsResizing
? 'none'
: this._props.childPointerEvents?.() ??
(this._props.viewDefDivClick || //
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2752fa7f5..823ec885b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1336,7 +1336,6 @@ export class DocumentView extends ObservableReactComponent<DocumentViewProps> {
public set SELECTED(val) {
runInAction(() => (this._selected = val));
}
- @observable public static Interacting = false;
@observable public static LongPress = false;
@observable public static ExploreMode = false;
@observable public static LastPressedSidebarBtn: Opt<Doc>; // bcz: this is a hack to handle highlighting buttons in the leftpanel menu .. need to find a cleaner approach
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index df73dffe4..f205dbd56 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -630,7 +630,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
() => !this._playing && this.Seek(NumCast(this.layoutDoc._layout_currentTimecode))
);
this._disposers.youtubeReactionDisposer = reaction(
- () => Doc.ActiveTool === InkTool.None && this._props.isSelected() && !SnappingManager.IsDragging && !DocumentView.Interacting,
+ () => Doc.ActiveTool === InkTool.None && this._props.isSelected() && !SnappingManager.IsDragging && !SnappingManager.IsResizing,
interactive => (iframe.style.pointerEvents = interactive ? 'all' : 'none'),
{ fireImmediately: true }
);
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 2522a674d..758c919d2 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -220,7 +220,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
} // else it's an HTMLfield
} else if (this.webField && !this.dataDoc.text) {
WebRequest.get(Utils.CorsProxy(this.webField.href)) //
- .then(result => result && (this.dataDoc.text = htmlToText.convert(result.content)));
+ .then(result => result && (this.dataDoc.text = htmlToText(result.content)));
}
this._disposers.scrollReaction = reaction(
@@ -491,12 +491,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this.layoutDoc.height = Math.min(NumCast(this.layoutDoc._height), (NumCast(this.layoutDoc._width) * NumCast(this.Document.nativeHeight)) / NumCast(this.Document.nativeWidth));
}
};
- const swidth = Math.max(NumCast(this.layoutDoc.nativeWidth), iframeContent.body.scrollWidth || 0);
+ const swidth = Math.max(NumCast(this.Document.nativeWidth), iframeContent.body.scrollWidth || 0);
if (swidth) {
- const aspectResize = swidth / NumCast(this.Document.nativeWidth);
- this.Document.nativeWidth = swidth;
- this.Document.nativeHeight = NumCast(this.Document.nativeHeight) * aspectResize;
+ const aspectResize = swidth / NumCast(this.Document.nativeWidth, swidth);
this.layoutDoc.height = NumCast(this.layoutDoc._height) * aspectResize;
+ this.Document.nativeWidth = swidth;
+ this.Document.nativeHeight = (swidth * NumCast(this.layoutDoc._height)) / NumCast(this.layoutDoc._width);
}
initHeights();
this._iframetimeout && clearTimeout(this._iframetimeout);
@@ -813,7 +813,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
key={this._warning}
className="webBox-iframe"
ref={action((r: HTMLIFrameElement | null) => (this._iframe = r))}
- style={{ pointerEvents: DocumentView.Interacting ? 'none' : undefined }}
+ style={{ pointerEvents: SnappingManager.IsResizing ? 'none' : undefined }}
src={url}
onLoad={this.iframeLoaded}
scrolling="no" // ugh.. on windows, I get an inner scroll bar for the iframe's body even though the scrollHeight should be set to the full height of the document.
diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx
index 093806127..f1739a41a 100644
--- a/src/client/views/webcam/DashWebRTCVideo.tsx
+++ b/src/client/views/webcam/DashWebRTCVideo.tsx
@@ -12,6 +12,7 @@ import { FieldView, FieldViewProps } from '../nodes/FieldView';
import './DashWebRTCVideo.scss';
import { hangup, initialize, refreshVideos } from './WebCamLogic';
import * as React from 'react';
+import { SnappingManager } from '../../util/SnappingManager';
/**
* This models the component that will be rendered, that can be used as a doc that will reflect the video cams.
@@ -71,8 +72,8 @@ export class DashWebRTCVideo extends React.Component<CollectionFreeFormDocumentV
</div>
);
- const frozen = !this.props.isSelected() || DocumentView.Interacting;
- const classname = 'webBox-cont' + (this.props.isSelected() && Doc.ActiveTool === InkTool.None && !DocumentView.Interacting ? '-interactive' : '');
+ const frozen = !this.props.isSelected() || SnappingManager.IsResizing;
+ const classname = 'webBox-cont' + (this.props.isSelected() && Doc.ActiveTool === InkTool.None && !SnappingManager.IsResizing ? '-interactive' : '');
return (
<>
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index a8e09818e..e2419e60a 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -26,6 +26,7 @@ import { ffprobe, FfmpegCommand } from 'fluent-ffmpeg';
import * as fs from 'fs';
import * as md5File from 'md5-file';
import * as autorotate from 'jpeg-autorotate';
+const { Duplex } = require('stream'); // Native Node Module
export enum SizeSuffix {
Small = '_s',
@@ -349,7 +350,11 @@ export namespace DashUploadUtils {
if (metadata instanceof Error) {
return { name: metadata.name, message: metadata.message };
}
- return UploadInspectedImage(metadata, filename || metadata.filename, prefix);
+ const outputFile = filename || metadata.filename;
+ if (!outputFile) {
+ return { name: source, message: 'output file not found' };
+ }
+ return UploadInspectedImage(metadata, outputFile, prefix);
};
export async function buildFileDirectories() {
@@ -399,13 +404,14 @@ export namespace DashUploadUtils {
const response = await AzureManager.UploadBase64ImageBlob(resolved, data);
source = `${AzureManager.BASE_STRING}/${resolved}`;
} else {
+ source = `${resolvedServerUrl}${clientPathToFile(Directory.images, resolved)}`;
+ source = serverPathToFile(Directory.images, resolved);
const error = await new Promise<Error | null>(resolve => {
writeFile(serverPathToFile(Directory.images, resolved), data, 'base64', resolve);
});
if (error !== null) {
return error;
}
- source = `${resolvedServerUrl}${clientPathToFile(Directory.images, resolved)}`;
}
}
let resolvedUrl: string;
@@ -514,7 +520,7 @@ export namespace DashUploadUtils {
* @param cleanUp a boolean indicating if the files should be deleted after upload. True by default.
* @returns the accessPaths for the resized files.
*/
- export const UploadInspectedImage = async (metadata: Upload.InspectionResults, filename?: string, prefix = '', cleanUp = true): Promise<Upload.ImageInformation> => {
+ export const UploadInspectedImage = async (metadata: Upload.InspectionResults, filename: string, prefix = '', cleanUp = true): Promise<Upload.ImageInformation> => {
const { requestable, source, ...remaining } = metadata;
const resolved = filename || `${prefix}upload_${Utils.GenerateGuid()}.${remaining.contentType.split('/')[1].toLowerCase()}`;
const { images } = Directory;
@@ -593,35 +599,43 @@ export namespace DashUploadUtils {
force: true,
};
+ async function correctRotation(imgSourcePath: string) {
+ const buffer = fs.readFileSync(imgSourcePath);
+ try {
+ return (await autorotate.rotate(buffer, { quality: 30 })).buffer;
+ } catch (e) {
+ return buffer;
+ }
+ }
+
/**
* outputResizedImages takes in a readable stream and resizes the images according to the sizes defined at the top of this file.
*
* The new images will be saved to the server with the corresponding prefixes.
- * @param streamProvider a Stream of the image to process, taken from the /parsed_files location
+ * @param imgSourcePath file path for image being resized
* @param outputFileName the basename (No suffix) of the outputted file.
* @param outputDirectory the directory to output to, usually Directory.Images
* @returns a map with suffixes as keys and resized filenames as values.
*/
- export async function outputResizedImages(sourcePath: string, outputFileName: string, outputDirectory: string) {
+ export async function outputResizedImages(imgSourcePath: string, outputFileName: string, outputDirectory: string) {
const writtenFiles: { [suffix: string]: string } = {};
const sizes = imageResampleSizes(path.extname(outputFileName));
+
+ const imgBuffer = await correctRotation(imgSourcePath);
+ const imgReadStream = new Duplex();
+ imgReadStream.push(imgBuffer);
+ imgReadStream.push(null);
const outputPath = (suffix: SizeSuffix) => path.resolve(outputDirectory, (writtenFiles[suffix] = InjectSize(outputFileName, suffix)));
await Promise.all(
sizes.filter(({ width }) => !width).map(({ suffix }) =>
- new Promise<void>(res => createReadStream(sourcePath).pipe(createWriteStream(outputPath(suffix))).on('close', res))
+ new Promise<void>(res => imgReadStream.pipe(createWriteStream(outputPath(suffix))).on('close', res))
)); // prettier-ignore
- const fileIn = fs.readFileSync(sourcePath);
- let buffer: any;
- try {
- const { buffer2 } = await autorotate.rotate(fileIn, { quality: 30 });
- buffer = buffer2;
- } catch (e) {}
- return Jimp.read(buffer ?? fileIn)
+ return Jimp.read(imgBuffer)
.then(async (img: any) => {
- await Promise.all( sizes.filter(({ width }) => width) .map(({ width, suffix }) =>
- img = img.resize(width, Jimp.AUTO).write(outputPath(suffix))
- )); // prettier-ignore
+ await Promise.all( sizes.filter(({ width }) => width).map(({ width, suffix }) =>
+ img = img.resize(width, Jimp.AUTO).write(outputPath(suffix))
+ )); // prettier-ignore
return writtenFiles;
})
.catch((e: any) => {