aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBob Zeleznik <zzzman@gmail.com>2020-01-27 17:32:50 -0500
committerBob Zeleznik <zzzman@gmail.com>2020-01-27 17:32:50 -0500
commitd866969264e96d97f77f80727df53a66865f463f (patch)
tree913c931d135b24c81f54c2f5d3b55dd20cd8f5f7
parent5e67aa8ab95e40730acb584cb5ff812fe470608f (diff)
parent9a9524b4c6e2980eb37ece288890d6cb746eb81c (diff)
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web
-rw-r--r--deploy/assets/greencheck.pngbin0 -> 49332 bytes
-rw-r--r--deploy/assets/redx.pngbin0 -> 7353 bytes
-rw-r--r--src/Utils.ts2
-rw-r--r--src/client/cognitive_services/CognitiveServices.ts3
-rw-r--r--src/client/documents/Documents.ts2
-rw-r--r--src/client/util/Import & Export/DirectoryImportBox.tsx4
-rw-r--r--src/client/util/Import & Export/ImageUtils.ts4
-rw-r--r--src/client/views/DocComponent.tsx2
-rw-r--r--src/client/views/InkingStroke.tsx34
-rw-r--r--src/client/views/collections/CollectionPivotView.scss36
-rw-r--r--src/client/views/collections/CollectionPivotView.tsx22
-rw-r--r--src/client/views/collections/CollectionSubView.tsx33
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx19
-rw-r--r--src/client/views/nodes/AudioBox.tsx2
-rw-r--r--src/client/views/nodes/ImageBox.scss24
-rw-r--r--src/client/views/nodes/ImageBox.tsx59
-rw-r--r--src/mobile/ImageUpload.tsx2
-rw-r--r--src/new_fields/Doc.ts4
-rw-r--r--src/server/ApiManagers/UploadManager.ts14
-rw-r--r--src/server/DashUploadUtils.ts12
21 files changed, 207 insertions, 73 deletions
diff --git a/deploy/assets/greencheck.png b/deploy/assets/greencheck.png
new file mode 100644
index 000000000..064e9def1
--- /dev/null
+++ b/deploy/assets/greencheck.png
Binary files differ
diff --git a/deploy/assets/redx.png b/deploy/assets/redx.png
new file mode 100644
index 000000000..0c2c9ccc5
--- /dev/null
+++ b/deploy/assets/redx.png
Binary files differ
diff --git a/src/Utils.ts b/src/Utils.ts
index 7bf05a6fc..4deac9035 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -48,7 +48,7 @@ export namespace Utils {
}
export async function getApiKey(target: string): Promise<string> {
- const response = await fetch(prepend(`environment/${target.toUpperCase()}`));
+ const response = await fetch(prepend(`/environment/${target.toUpperCase()}`));
return response.text();
}
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index 57296c961..9e2ceac62 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -185,8 +185,9 @@ export namespace CognitiveServices {
results.recognitionUnits && (results = results.recognitionUnits);
target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Ink Analysis");
const recognizedText = results.map((item: any) => item.recognizedText);
+ const recognizedObjects = results.map((item: any) => item.recognizedObject);
const individualWords = recognizedText.filter((text: string) => text && text.split(" ").length === 1);
- target[keys[1]] = individualWords.join(" ");
+ target[keys[1]] = individualWords.length ? individualWords.join(" ") : recognizedObjects.join(", ");
}
batch.end();
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index ac643f91f..a7f939d7d 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -324,7 +324,7 @@ export namespace Docs {
// whatever options pertain to this specific prototype
const options = { title, type, baseProto: true, ...defaultOptions, ...(template.options || {}) };
options.layout = layout.view.LayoutString(layout.dataField);
- let doc = Doc.assign(new Doc(prototypeId, true), { layoutKey: "layout", ...options });
+ const doc = Doc.assign(new Doc(prototypeId, true), { layoutKey: "layout", ...options });
return doc;
}
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index c7c94abed..071015193 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx
@@ -116,13 +116,13 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
formData.append(Utils.GenerateGuid(), file);
});
- collector.push(...(await Networking.PostFormDataToServer("/upload", formData)));
+ collector.push(...(await Networking.PostFormDataToServer("/uploadFormData", formData)));
runInAction(() => this.completed += batch.length);
});
await Promise.all(uploads.map(async ({ name, type, clientAccessPath, exifData }) => {
const path = Utils.prepend(clientAccessPath);
- const document = await Docs.Get.DocumentFromType(type, path, { width: 300, title: name });
+ const document = await Docs.Get.DocumentFromType(type, path, { _width: 300, title: name });
const { data, error } = exifData;
if (document) {
Doc.GetProto(document).exif = error || Docs.Get.DocumentHierarchyFromJson(data);
diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts
index 2f4db0e17..ff909cc6b 100644
--- a/src/client/util/Import & Export/ImageUtils.ts
+++ b/src/client/util/Import & Export/ImageUtils.ts
@@ -22,8 +22,8 @@ export namespace ImageUtils {
} = await Networking.PostToServer("/inspectImage", { source });
document.exif = error || Docs.Get.DocumentHierarchyFromJson(data);
const proto = Doc.GetProto(document);
- proto.nativeWidth = nativeWidth;
- proto.nativeHeight = nativeHeight;
+ proto["data-nativeWidth"] = nativeWidth;
+ proto["data-nativeHeight"] = nativeHeight;
proto.contentSize = contentSize;
return data !== undefined;
};
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index a31997a10..ce48e1215 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -61,7 +61,7 @@ export function DocAnnotatableComponent<P extends DocAnnotatableProps, T>(schema
_annotationKey: string = "annotations";
public set annotationKey(val: string) { this._annotationKey = val; }
- public get annotationKey() { return this._annotationKey }
+ public get annotationKey() { return this._annotationKey; }
@action.bound
removeDocument(doc: Doc): boolean {
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index aca507147..f315ce12a 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -11,6 +11,12 @@ import { FieldView, FieldViewProps } from "./nodes/FieldView";
import React = require("react");
import { TraceMobx } from "../../new_fields/util";
import { InteractionUtils } from "../util/InteractionUtils";
+import { ContextMenu } from "./ContextMenu";
+import { CognitiveServices } from "../cognitive_services/CognitiveServices";
+import { faPaintBrush } from "@fortawesome/free-solid-svg-icons";
+import { library } from "@fortawesome/fontawesome-svg-core";
+
+library.add(faPaintBrush);
type InkDocument = makeInterface<[typeof documentSchema]>;
const InkDocument = makeInterface(documentSchema);
@@ -22,6 +28,11 @@ export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocu
@computed get PanelWidth() { return this.props.PanelWidth(); }
@computed get PanelHeight() { return this.props.PanelHeight(); }
+ private analyzeStrokes = () => {
+ const data: InkData = Cast(this.Document.data, InkField)?.inkData ?? [];
+ CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.Document, ["inkAnalysis", "handwriting"], [data]);
+ }
+
render() {
TraceMobx();
const data: InkData = Cast(this.Document.data, InkField)?.inkData ?? [];
@@ -37,12 +48,23 @@ export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocu
const scaleX = this.PanelWidth / width;
const scaleY = this.PanelHeight / height;
return (
- <svg width={width} height={height} style={{
- transformOrigin: "top left",
- transform: `scale(${scaleX}, ${scaleY})`,
- mixBlendMode: this.Document.tool === InkTool.Highlighter ? "multiply" : "unset",
- pointerEvents: "all"
- }}>
+ <svg
+ width={width}
+ height={height}
+ style={{
+ transformOrigin: "top left",
+ transform: `scale(${scaleX}, ${scaleY})`,
+ mixBlendMode: this.Document.tool === InkTool.Highlighter ? "multiply" : "unset",
+ pointerEvents: "all"
+ }}
+ onContextMenu={() => {
+ ContextMenu.Instance.addItem({
+ description: "Analyze Stroke",
+ event: this.analyzeStrokes,
+ icon: "paint-brush"
+ });
+ }}
+ >
{points}
</svg>
);
diff --git a/src/client/views/collections/CollectionPivotView.scss b/src/client/views/collections/CollectionPivotView.scss
index 2ddcc97ea..505091e98 100644
--- a/src/client/views/collections/CollectionPivotView.scss
+++ b/src/client/views/collections/CollectionPivotView.scss
@@ -2,12 +2,14 @@
display: flex;
flex-direction: row;
position: absolute;
- height:100%;
- width:100%;
+ height: 100%;
+ width: 100%;
+
.collectionPivotView-flyout {
width: 400px;
height: 300px;
display: inline-block;
+
.collectionPivotView-flyout-item {
background-color: lightgray;
text-align: left;
@@ -17,43 +19,61 @@
}
}
+ .pivotKeyEntry {
+ position: absolute;
+ top: 5px;
+ right: 5px;
+ z-index: 10;
+ pointer-events: all;
+ padding: 5px;
+ border: 1px solid black;
+ }
+
.collectionPivotView-treeView {
- display:flex;
+ display: flex;
flex-direction: column;
width: 200px;
height: 100%;
+
.collectionPivotView-addfacet {
- display:inline-block;
+ display: inline-block;
width: 200px;
height: 30px;
background: darkGray;
text-align: center;
+
.collectionPivotView-button {
align-items: center;
display: flex;
width: 100%;
height: 100%;
+
.collectionPivotView-span {
margin: auto;
}
}
- > div, > div > div {
+
+ >div,
+ >div>div {
width: 100%;
height: 100%;
text-align: center;
}
}
+
.collectionPivotView-tree {
- display:inline-block;
+ display: inline-block;
width: 100%;
height: calc(100% - 30px);
}
}
+
.collectionPivotView-pivot {
- display:inline-block;
+ display: inline-block;
width: calc(100% - 200px);
height: 100%;
}
+
.collectionPivotView-dragger {
background-color: lightgray;
height: 40px;
@@ -63,6 +83,6 @@
top: 55%;
border: 1px black solid;
z-index: 2;
- left:-10px;
+ left: -10px;
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx
index a44f990e1..98fc54b3b 100644
--- a/src/client/views/collections/CollectionPivotView.tsx
+++ b/src/client/views/collections/CollectionPivotView.tsx
@@ -16,6 +16,8 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { List } from "../../../new_fields/List";
import { Set } from "typescript-collections";
import { PrefetchProxy } from "../../../new_fields/Proxy";
+import { EditableView } from "../EditableView";
+import { listSpec } from "../../../new_fields/Schema";
@observer
export class CollectionPivotView extends CollectionSubView(doc => doc) {
@@ -76,6 +78,13 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) {
const found = DocListCast(facetCollection.data).findIndex(doc => doc.title === facetHeader);
if (found !== -1) {
(facetCollection.data as List<Doc>).splice(found, 1);
+ const docFilter = Cast(this.props.Document._docFilter, listSpec("string"));
+ if (docFilter) {
+ let index: number;
+ while ((index = docFilter.findIndex(item => item === facetHeader)) !== -1) {
+ docFilter.splice(index, 3);
+ }
+ }
} else {
const newFacet = Docs.Create.TreeDocument([], { title: facetHeader, treeViewOpen: true, isFacetFilter: true });
const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc };
@@ -129,6 +138,19 @@ export class CollectionPivotView extends CollectionSubView(doc => doc) {
);
return !facetCollection ? (null) :
<div className="collectionPivotView">
+ <div className={"pivotKeyEntry"}>
+ <EditableView
+ contents={this.props.Document.pivotField}
+ GetValue={() => StrCast(this.props.Document.pivotField)}
+ SetValue={value => {
+ if (value && value.length) {
+ this.props.Document.pivotField = value;
+ return true;
+ }
+ return false;
+ }}
+ />
+ </div>
<div className="collectionPivotView-dragger" key="dragger" onPointerDown={this.onPointerDown} style={{ transform: `translate(${this._facetWidth}px, 0px)` }} >
<span title="library View Dragger" style={{ width: "5px", position: "absolute", top: "0" }} />
</div>
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index db3fde797..321a6d34c 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -70,7 +70,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
(args) => {
const childLayout = Cast(this.props.Document.childLayout, Doc);
if (childLayout instanceof Doc) {
- this.childDocs.map(doc => Doc.ApplyTemplateTo(childLayout as Doc, doc, "layoutFromParent"));
+ this.childDocs.map(doc => Doc.ApplyTemplateTo(childLayout, doc, "layoutFromParent"));
}
else if (!(childLayout instanceof Promise)) {
this.childDocs.filter(d => !d.isTemplateForField).map(doc => doc.layoutKey === "layoutFromParent" && (doc.layoutKey = "layout"));
@@ -109,15 +109,30 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField);
const viewedDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs;
const docFilters = Cast(this.props.Document._docFilter, listSpec("string"), []);
+ const clusters: { [key: string]: { [value: string]: string } } = {};
+ for (let i = 0; i < docFilters.length; i += 3) {
+ const [key, value, modifiers] = docFilters.slice(i, i + 3);
+ const cluster = clusters[key];
+ if (!cluster) {
+ const child: { [value: string]: string } = {};
+ child[value] = modifiers;
+ clusters[key] = child;
+ } else {
+ cluster[value] = modifiers;
+ }
+ }
const filteredDocs = docFilters.length ? viewedDocs.filter(d => {
- let result = false;
- for (let i = 0; i < docFilters.length; i += 3) {
- const key = docFilters[i];
- const value = docFilters[i + 1];
- const modifiers = docFilters[i + 2];
- result = result || ((modifiers === "x") !== Doc.matchFieldValue(d, key, value));
+ for (const key of Object.keys(clusters)) {
+ const cluster = clusters[key];
+ const satisfiesFacet = Object.keys(cluster).some(inner => {
+ const modifier = cluster[inner];
+ return (modifier === "x") !== Doc.matchFieldValue(d, key, inner);
+ });
+ if (!satisfiesFacet) {
+ return false;
+ }
}
- return result;
+ return true;
}) : viewedDocs;
return filteredDocs;
}
@@ -309,7 +324,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
formData.append('file', file);
const dropFileName = file ? file.name : "-empty-";
- promises.push(Networking.PostFormDataToServer("/upload", formData).then(results => {
+ promises.push(Networking.PostFormDataToServer("/uploadFormData", formData).then(results => {
results.map(action((result: any) => {
const { clientAccessPath, nativeWidth, nativeHeight, contentSize } = result;
const full = { ...options, _width: 300, title: dropFileName };
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 09ee3255d..be1317b25 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -59,7 +59,7 @@ export function computePivotLayout(poolData: ObservableMap<string, any>, pivotDo
let numCols = NumCast(pivotDoc.pivotNumColumns, Math.ceil(Math.sqrt(minSize)));
const docMap = new Map<Doc, ViewDefBounds>();
const groupNames: PivotData[] = [];
- numCols = Math.min(panelDim[0] / pivotAxisWidth, numCols)
+ numCols = Math.min(panelDim[0] / pivotAxisWidth, numCols);
const expander = 1.05;
const gap = .15;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 70f8a0e73..4ba58ac84 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -819,23 +819,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}, "arrange contents");
}
-
- analyzeStrokes = async () => {
- const children = await DocListCastAsync(this.dataDoc.data);
- if (!children) {
- return;
- }
- const inkData: InkData[] = [];
- for (const doc of children) {
- const data = Cast(doc.data, InkField)?.inkData;
- data && inkData.push(data);
- }
- if (!inkData.length) {
- return;
- }
- CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], inkData);
- }
-
private thumbIdentifier?: number;
// @action
@@ -887,7 +870,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
layoutItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
layoutItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
- layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
+ // layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" });
layoutItems.push({
description: "Import document", icon: "upload", event: ({ x, y }) => {
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 8ede79edc..62a479b2a 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -138,7 +138,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
self._recorder.ondataavailable = async function (e: any) {
const formData = new FormData();
formData.append("file", e.data);
- const res = await fetch(Utils.prepend("/upload"), {
+ const res = await fetch(Utils.prepend("/uploadFormData"), {
method: 'POST',
body: formData
});
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index cf5d999a7..43f4a0ba9 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -1,10 +1,12 @@
-.imageBox, .imageBox-dragging{
+.imageBox,
+.imageBox-dragging {
pointer-events: all;
border-radius: inherit;
- width:100%;
- height:100%;
+ width: 100%;
+ height: 100%;
position: absolute;
transform-origin: top left;
+
.imageBox-fader {
pointer-events: all;
}
@@ -16,6 +18,14 @@
}
}
+#upload-icon {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ width: 20px;
+ height: 20px;
+}
+
.imageBox-cont {
padding: 0vw;
position: absolute;
@@ -25,7 +35,8 @@
max-width: 100%;
max-height: 100%;
pointer-events: none;
- background:transparent;
+ background: transparent;
+
img {
height: auto;
width: 100%;
@@ -55,9 +66,10 @@
padding: 3px;
background: white;
cursor: pointer;
- opacity:0.15;
+ opacity: 0.15;
}
-#google-photos:hover{
+
+#google-photos:hover {
opacity: 1;
}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 8db66d367..ffcbe459e 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -8,7 +8,7 @@ import { Doc, DocListCast, HeightSym, WidthSym, DataSym } from '../../../new_fie
import { List } from '../../../new_fields/List';
import { createSchema, listSpec, makeInterface } from '../../../new_fields/Schema';
import { ComputedField } from '../../../new_fields/ScriptField';
-import { Cast, NumCast } from '../../../new_fields/Types';
+import { Cast, NumCast, StrCast } from '../../../new_fields/Types';
import { AudioField, ImageField } from '../../../new_fields/URLField';
import { Utils, returnOne, emptyFunction } from '../../../Utils';
import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices';
@@ -29,6 +29,7 @@ import { TraceMobx } from '../../../new_fields/util';
import { SelectionManager } from '../../util/SelectionManager';
import { cache } from 'sharp';
import { ObjectField } from '../../../new_fields/ObjectField';
+import { Networking } from '../../Network';
const requestImageSize = require('../../util/request-image-size');
const path = require('path');
const { Howl } = require('howler');
@@ -57,6 +58,13 @@ declare class MediaRecorder {
type ImageDocument = makeInterface<[typeof pageSchema, typeof documentSchema]>;
const ImageDocument = makeInterface(pageSchema, documentSchema);
+const uploadIcons = {
+ idle: "downarrow.png",
+ loading: "loading.gif",
+ success: "greencheck.png",
+ failure: "redx.png"
+};
+
@observer
export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocument>(ImageDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); }
@@ -64,6 +72,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
private _dropDisposer?: DragManager.DragDropDisposer;
@observable private _audioState = 0;
@observable static _showControls: boolean;
+ @observable uploadIcon = uploadIcons.idle;
protected createDropTarget = (ele: HTMLDivElement) => {
this._dropDisposer && this._dropDisposer();
@@ -104,7 +113,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
recorder.ondataavailable = async function (e: any) {
const formData = new FormData();
formData.append("file", e.data);
- const res = await fetch(Utils.prepend("/upload"), {
+ const res = await fetch(Utils.prepend("/uploadFormData"), {
method: 'POST',
body: formData
});
@@ -213,11 +222,15 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
if (this._curSuffix === "_m") this._mediumRetryCount++;
if (this._curSuffix === "_l") this._largeRetryCount++;
}
- @action onError = () => {
+ @action onError = (error: any) => {
const timeout = this._curSuffix === "_s" ? this._smallRetryCount : this._curSuffix === "_m" ? this._mediumRetryCount : this._largeRetryCount;
if (timeout < 10) {
// setTimeout(this.retryPath, 500);
}
+ const original = StrCast(this.dataDoc.originalUrl);
+ if (error.type === "error" && original) {
+ this.dataDoc[this.props.fieldKey] = new ImageField(original);
+ }
}
_curSuffix = "_m";
@@ -287,6 +300,45 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
return !tags ? (null) : (<img id={"google-tags"} src={"/assets/google_tags.png"} />);
}
+ @computed
+ private get considerDownloadIcon() {
+ const data = this.dataDoc[this.props.fieldKey];
+ if (!(data instanceof ImageField)) {
+ return (null);
+ }
+ const primary = data.url.href;
+ if (primary.includes(window.location.origin)) {
+ return (null);
+ }
+ return (
+ <img
+ id={"upload-icon"}
+ src={`/assets/${this.uploadIcon}`}
+ onClick={async () => {
+ const { dataDoc } = this;
+ const { success, failure, idle, loading } = uploadIcons;
+ runInAction(() => this.uploadIcon = loading);
+ const [{ clientAccessPath }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [primary] });
+ dataDoc.originalUrl = primary;
+ let succeeded = true;
+ let data: ImageField | undefined;
+ try {
+ data = new ImageField(Utils.prepend(clientAccessPath));
+ } catch {
+ succeeded = false;
+ }
+ runInAction(() => this.uploadIcon = succeeded ? success : failure);
+ setTimeout(action(() => {
+ this.uploadIcon = idle;
+ if (data) {
+ dataDoc[this.props.fieldKey] = data;
+ }
+ }), 2000);
+ }}
+ />
+ );
+ }
+
@computed get nativeSize() {
const pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50;
const nativeWidth = NumCast(this.dataDoc[this.props.fieldKey + "-nativeWidth"], pw);
@@ -347,6 +399,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
style={{ color: [DocListCast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._audioState] }}
icon={!DocListCast(this.dataDoc[this.props.fieldKey + "-audioAnnotations"]).length ? "microphone" : faFileAudio} size="sm" />
</div>
+ {this.considerDownloadIcon}
{this.considerGooglePhotosLink()}
<FaceRectangles document={this.dataDoc} color={"#0000FF"} backgroundColor={"#0000FF"} />
</div>;
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index dab4de110..1583e3d5d 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -45,7 +45,7 @@ class Uploader extends React.Component {
const formData = new FormData();
formData.append("file", files[0]);
- const upload = window.location.origin + "/upload";
+ const upload = window.location.origin + "/uploadFormData";
this.status = "uploading image";
const res = await fetch(upload, {
method: 'POST',
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index cc146a121..41a9dddf2 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -761,7 +761,7 @@ export namespace Doc {
return vals.some(v => v === value);
}
const fieldStr = Field.toString(fieldVal as Field);
- return fieldStr == value;
+ return fieldStr === value;
}
}
@@ -791,7 +791,7 @@ Scripting.addGlobal(function setDocFilter(container: Doc, key: string, value: an
}
if (modifiers !== undefined) {
docFilters.push(key);
- docFilters.push(value)
+ docFilters.push(value);
docFilters.push(modifiers);
container._docFilter = new List<string>(docFilters);
}
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index 583eaa59b..a92b613b7 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -40,7 +40,7 @@ export default class UploadManager extends ApiManager {
register({
method: Method.POST,
- subscription: "/upload",
+ subscription: "/uploadFormData",
secureHandler: async ({ req, res }) => {
const form = new formidable.IncomingForm();
form.uploadDir = pathToDirectory(Directory.parsed_files);
@@ -61,6 +61,18 @@ export default class UploadManager extends ApiManager {
register({
method: Method.POST,
+ subscription: "/uploadRemoteImage",
+ secureHandler: async ({ req, res }) => {
+ const { sources } = req.body;
+ if (Array.isArray(sources)) {
+ return res.send(await Promise.all(sources.map(url => DashUploadUtils.UploadImage(url))));
+ }
+ res.send();
+ }
+ });
+
+ register({
+ method: Method.POST,
subscription: "/uploadDoc",
secureHandler: ({ req, res }) => {
const form = new formidable.IncomingForm();
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index b826b4882..cb7104757 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -108,13 +108,7 @@ export namespace DashUploadUtils {
return MoveParsedFile(absolutePath, Directory.pdfs);
}
- const generate = (prefix: string, url: string) => `${prefix}upload_${Utils.GenerateGuid()}${sanitizeExtension(url)}`;
- const sanitizeExtension = (source: string) => {
- let extension = path.extname(source);
- extension = extension.toLowerCase();
- extension = extension.split("?")[0];
- return extension;
- };
+ const generate = (prefix: string, extension: string) => `${prefix}upload_${Utils.GenerateGuid()}.${extension}`;
/**
* Uploads an image specified by the @param source to Dash's /public/files/
@@ -209,8 +203,8 @@ export namespace DashUploadUtils {
export const UploadInspectedImage = async (metadata: InspectionResults, filename?: string, format?: string, prefix = ""): Promise<ImageUploadInformation> => {
const { requestable, source, ...remaining } = metadata;
- const resolved = filename || generate(prefix, requestable);
- const extension = format || sanitizeExtension(requestable || resolved);
+ const extension = remaining.contentType.toLowerCase().split("/")[1]; //format || sanitizeExtension(requestable || resolved);
+ const resolved = filename || generate(prefix, extension);
const information: ImageUploadInformation = {
clientAccessPath: clientPathToFile(Directory.images, resolved),
serverAccessPaths: {},