aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes')
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx2
-rw-r--r--src/client/views/nodes/DocumentView.tsx44
-rw-r--r--src/client/views/nodes/FaceRectangle.tsx29
-rw-r--r--src/client/views/nodes/FaceRectangles.tsx46
-rw-r--r--src/client/views/nodes/FieldView.tsx3
-rw-r--r--src/client/views/nodes/ImageBox.tsx20
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx2
-rw-r--r--src/client/views/nodes/VideoBox.tsx7
8 files changed, 126 insertions, 27 deletions
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index fa8d5dca3..36d7b38f4 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -65,7 +65,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
get dataDoc() {
if (this.props.DataDoc === undefined && this.props.Document.layout instanceof Doc) {
- // if there is no dataDoc (ie, we're not rendering a temlplate layout), but this document
+ // if there is no dataDoc (ie, we're not rendering a template layout), but this document
// has a template layout document, then we will render the template layout but use
// this document as the data document for the layout.
return this.props.Document;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e0975f7bd..35a4613af 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -37,6 +37,7 @@ import { RouteStore } from '../../../server/RouteStore';
import { FormattedTextBox } from './FormattedTextBox';
import { OverlayView } from '../OverlayView';
import { ScriptingRepl } from '../ScriptingRepl';
+import { ClientUtils } from '../../util/ClientUtils';
import { EditableView } from '../EditableView';
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
@@ -59,6 +60,7 @@ library.add(fa.faCrosshairs);
library.add(fa.faDesktop);
library.add(fa.faUnlock);
library.add(fa.faLock);
+library.add(fa.faLaptopCode);
// const linkSchema = createSchema({
@@ -295,6 +297,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
if (this._doubleTap && this.props.renderDepth) {
let fullScreenAlias = Doc.MakeAlias(this.props.Document);
fullScreenAlias.templates = new List<string>();
+ Doc.UseDetailLayout(fullScreenAlias);
this.props.addDocTab(fullScreenAlias, this.dataDoc, "inTab");
SelectionManager.DeselectAll();
this.props.Document.libraryBrush = undefined;
@@ -351,7 +354,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
// @TODO: shouldn't always follow target context
let linkedFwdContextDocs = [first.length ? await (first[0].targetContext) as Doc : undefined, undefined];
- let linkedFwdPage = [first.length ? NumCast(first[0].linkedToPage, undefined) : undefined, undefined];
+ let linkedFwdPage = [first.length ? NumCast(first[0].anchor2Page, undefined) : undefined, undefined];
if (!linkedFwdDocs.some(l => l instanceof Promise)) {
let maxLocation = StrCast(linkedFwdDocs[0].maximizeLocation, "inTab");
@@ -503,7 +506,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@undoBatch
@action
freezeNativeDimensions = (): void => {
- let proto = Doc.GetProto(this.props.Document);
+ let proto = this.props.Document.isTemplate ? this.props.Document : Doc.GetProto(this.props.Document);
if (proto.ignoreAspect === undefined && !proto.nativeWidth) {
proto.nativeWidth = this.props.PanelWidth();
proto.nativeHeight = this.props.PanelHeight();
@@ -553,17 +556,23 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this.props.removeDocument && this.props.removeDocument(this.props.Document);
}, icon: "window-restore"
});
- cm.addItem({
- description: "Find aliases", event: async () => {
- const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
- this.props.addDocTab && this.props.addDocTab(Docs.Create.SchemaDocument(["title"], aliases, {}), undefined, "onRight"); // bcz: dataDoc?
- }, icon: "search"
- });
+ // cm.addItem({
+ // description: "Find aliases", event: async () => {
+ // const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
+ // this.props.addDocTab && this.props.addDocTab(Docs.Create.SchemaDocument(["title"], aliases, {}), undefined, "onRight"); // bcz: dataDoc?
+ // }, icon: "search"
+ // });
+ if (this.props.Document.detailedLayout && !this.props.Document.isTemplate) {
+ cm.addItem({ description: "Toggle detail", event: () => Doc.ToggleDetailLayout(this.props.Document), icon: "image" });
+ }
+ cm.addItem({ description: "Add Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
cm.addItem({ description: "Add Repl", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
cm.addItem({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" });
- cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "link" });
- cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
+ if (!ClientUtils.RELEASE) {
+ cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "link" });
+ cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
+ }
cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" });
type User = { email: string, userDocumentId: string };
let usersMenu: ContextMenuProps[] = [];
@@ -626,10 +635,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%";
var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
let showOverlays = this.props.showOverlays ? this.props.showOverlays(this.props.Document) : undefined;
- let showTitle = showOverlays && showOverlays.title ? showOverlays.title : StrCast(this.props.Document.showTitle);
- let showCaption = showOverlays && showOverlays.caption ? showOverlays.caption : StrCast(this.props.Document.showCaption);
+ let showTitle = showOverlays && showOverlays.title !== "undefined" ? showOverlays.title : StrCast(this.props.Document.showTitle);
+ let showCaption = showOverlays && showOverlays.caption !== "undefined" ? showOverlays.caption : StrCast(this.props.Document.showCaption);
let templates = Cast(this.props.Document.templates, listSpec("string"));
- if (templates instanceof List) {
+ if (!showOverlays && templates instanceof List) {
templates.map(str => {
if (str.indexOf("{props.Document.title}") !== -1) showTitle = "title";
if (str.indexOf("fieldKey={\"caption\"}") !== -1) showCaption = "caption";
@@ -660,12 +669,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
{!showTitle && !showCaption ? this.contents :
<div style={{ position: "absolute", display: "inline-block", width: "100%", height: "100%", pointerEvents: "none" }}>
- <div style={{ width: "100%", height: showTextTitle ? "calc(100% - 25px)" : "100%", display: "inline-block", position: "absolute", top: showTextTitle ? "25px" : undefined }}>
+ <div style={{ width: "100%", height: showTextTitle ? "calc(100% - 33px)" : "100%", display: "inline-block", position: "absolute", top: showTextTitle ? "29px" : undefined }}>
{this.contents}
</div>
{!showTitle ? (null) :
<div style={{
- position: showTextTitle ? "relative" : "absolute", top: 0, textAlign: "center", textOverflow: "ellipsis", whiteSpace: "pre",
+ position: showTextTitle ? "relative" : "absolute", top: 0, padding: "4px", textAlign: "center", textOverflow: "ellipsis", whiteSpace: "pre",
pointerEvents: "all",
overflow: "hidden", width: `${100 * this.props.ContentScaling()}%`, height: 25, background: "rgba(0, 0, 0, .4)", color: "white",
transformOrigin: "top left", transform: `scale(${1 / this.props.ContentScaling()})`
@@ -674,8 +683,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
contents={this.props.Document[showTitle]}
display={"block"}
height={72}
- GetValue={() => StrCast(this.props.Document[showTitle])}
- SetValue={(value: string) => (Doc.GetProto(this.props.Document)[showTitle] = value) ? true : true}
+ fontSize={12}
+ GetValue={() => StrCast(this.props.Document[showTitle!])}
+ SetValue={(value: string) => (Doc.GetProto(this.props.Document)[showTitle!] = value) ? true : true}
/>
</div>
}
diff --git a/src/client/views/nodes/FaceRectangle.tsx b/src/client/views/nodes/FaceRectangle.tsx
new file mode 100644
index 000000000..887efc0d5
--- /dev/null
+++ b/src/client/views/nodes/FaceRectangle.tsx
@@ -0,0 +1,29 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import { observable, runInAction } from "mobx";
+import { RectangleTemplate } from "./FaceRectangles";
+
+@observer
+export default class FaceRectangle extends React.Component<{ rectangle: RectangleTemplate }> {
+ @observable private opacity = 0;
+
+ componentDidMount() {
+ setTimeout(() => runInAction(() => this.opacity = 1), 500);
+ }
+
+ render() {
+ let rectangle = this.props.rectangle;
+ return (
+ <div
+ style={{
+ ...rectangle.style,
+ opacity: this.opacity,
+ transition: "1s ease opacity",
+ position: "absolute",
+ borderRadius: 5
+ }}
+ />
+ );
+ }
+
+} \ No newline at end of file
diff --git a/src/client/views/nodes/FaceRectangles.tsx b/src/client/views/nodes/FaceRectangles.tsx
new file mode 100644
index 000000000..3570531b2
--- /dev/null
+++ b/src/client/views/nodes/FaceRectangles.tsx
@@ -0,0 +1,46 @@
+import React = require("react");
+import { Doc, DocListCast } from "../../../new_fields/Doc";
+import { Cast, NumCast } from "../../../new_fields/Types";
+import { observer } from "mobx-react";
+import { Id } from "../../../new_fields/FieldSymbols";
+import FaceRectangle from "./FaceRectangle";
+
+interface FaceRectanglesProps {
+ document: Doc;
+ color: string;
+ backgroundColor: string;
+}
+
+export interface RectangleTemplate {
+ id: string;
+ style: Partial<React.CSSProperties>;
+}
+
+@observer
+export default class FaceRectangles extends React.Component<FaceRectanglesProps> {
+
+ render() {
+ let faces = DocListCast(Doc.GetProto(this.props.document).faces);
+ let templates: RectangleTemplate[] = faces.map(faceDoc => {
+ let rectangle = Cast(faceDoc.faceRectangle, Doc) as Doc;
+ let style = {
+ top: NumCast(rectangle.top),
+ left: NumCast(rectangle.left),
+ width: NumCast(rectangle.width),
+ height: NumCast(rectangle.height),
+ backgroundColor: `${this.props.backgroundColor}33`,
+ border: `solid 2px ${this.props.color}`,
+ } as React.CSSProperties;
+ return {
+ id: rectangle[Id],
+ style: style
+ };
+ });
+ return (
+ <div>
+ {templates.map(rectangle => <FaceRectangle key={rectangle.id} rectangle={rectangle} />)}
+ </div>
+ );
+ }
+
+} \ No newline at end of file
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index ea6730cd0..ffaee8042 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -87,7 +87,8 @@ export class FieldView extends React.Component<FieldViewProps> {
return <p>{field.date.toLocaleString()}</p>;
}
else if (field instanceof Doc) {
- return <p><b>{field.title + " : id= " + field[Id]}</b></p>;
+ return <p><b>{field.title}</b></p>;
+ //return <p><b>{field.title + " : id= " + field[Id]}</b></p>;
// let returnHundred = () => 100;
// return (
// <DocumentContentsView Document={field}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index c3ee1e823..0f60bd0fb 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -25,6 +25,8 @@ import { Docs } from '../../documents/Documents';
import { DocServer } from '../../DocServer';
import { Font } from '@react-pdf/renderer';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { CognitiveServices } from '../../cognitive_services/CognitiveServices';
+import FaceRectangles from './FaceRectangles';
var requestImageSize = require('../../util/request-image-size');
var path = require('path');
const { Howl, Howler } = require('howler');
@@ -195,10 +197,10 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
let field = Cast(this.Document[this.props.fieldKey], ImageField);
if (field) {
let url = field.url.href;
- let subitems: ContextMenuProps[] = [];
- subitems.push({ description: "Copy path", event: () => Utils.CopyText(url), icon: "expand-arrows-alt" });
- subitems.push({ description: "Record 1sec audio", event: this.recordAudioAnnotation, icon: "expand-arrows-alt" });
- subitems.push({
+ let funcs: ContextMenuProps[] = [];
+ funcs.push({ description: "Copy path", event: () => Utils.CopyText(url), icon: "expand-arrows-alt" });
+ funcs.push({ description: "Record 1sec audio", event: this.recordAudioAnnotation, icon: "expand-arrows-alt" });
+ funcs.push({
description: "Rotate", event: action(() => {
let proto = Doc.GetProto(this.props.Document);
let nw = this.props.Document.nativeWidth;
@@ -212,7 +214,14 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
this.props.Document.height = w;
}), icon: "expand-arrows-alt"
});
- ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: subitems });
+
+ let modes: ContextMenuProps[] = [];
+ let dataDoc = Doc.GetProto(this.Document);
+ modes.push({ description: "Generate Tags", event: () => CognitiveServices.Image.generateMetadata(dataDoc), icon: "tag" });
+ modes.push({ description: "Find Faces", event: () => CognitiveServices.Image.extractFaces(dataDoc), icon: "camera" });
+
+ ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: funcs });
+ ContextMenu.Instance.addItem({ description: "Analyze...", subitems: modes });
}
}
@@ -371,6 +380,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
style={{ color: [DocListCast(this.extensionDoc.audioAnnotations).length ? "blue" : "gray", "green", "red"][this._audioState] }} icon={faFileAudio} size="sm" />
</div>
{/* {this.lightbox(paths)} */}
+ <FaceRectangles document={this.props.Document} color={"#0000FF"} backgroundColor={"#0000FF"} />
</div>);
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index c9dd9a64e..9fc0f2080 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -114,7 +114,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
let protos = Doc.GetAllPrototypes(doc);
for (const proto of protos) {
Object.keys(proto).forEach(key => {
- if (!(key in ids)) {
+ if (!(key in ids) && realDoc[key] !== ComputedField.undefined) {
ids[key] = key;
}
});
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 0f2d18f6b..30ad75000 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -52,25 +52,27 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
this.Document.nativeHeight = this.Document.nativeWidth / aspect;
this.Document.height = FieldValue(this.Document.width, 0) / aspect;
}
+ if (!this.Document.duration) this.Document.duration = this.player!.duration;
}
@action public Play = (update: boolean = true) => {
this.Playing = true;
update && this.player && this.player.play();
update && this._youtubePlayer && this._youtubePlayer.playVideo();
- !this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 500));
+ this._youtubePlayer && !this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 5));
this.updateTimecode();
}
@action public Seek(time: number) {
this._youtubePlayer && this._youtubePlayer.seekTo(Math.round(time), true);
+ this.player && (this.player.currentTime = time);
}
@action public Pause = (update: boolean = true) => {
this.Playing = false;
update && this.player && this.player.pause();
update && this._youtubePlayer && this._youtubePlayer.pauseVideo();
- this._playTimer && clearInterval(this._playTimer);
+ this._youtubePlayer && this._playTimer && clearInterval(this._playTimer);
this._playTimer = undefined;
this.updateTimecode();
}
@@ -112,6 +114,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
setVideoRef = (vref: HTMLVideoElement | null) => {
this._videoRef = vref;
if (vref) {
+ this._videoRef!.ontimeupdate = this.updateTimecode;
vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen);
if (this._reactionDisposer) this._reactionDisposer();
this._reactionDisposer = reaction(() => this.props.Document.curPage, () =>